2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2015 Stefan BrĂ¼ns <stefan.bruens@rwth-aachen.de>
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ## GNU General Public License for more details.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
20 import sigrokdecode
as srd
23 class SamplerateError(Exception):
27 # Linux usbmon format, see Documentation/usb/usbmon.txt
28 h
= b
'\x00\x00\x00\x00' # ID part 1
29 h
+= b
'\x00\x00\x00\x00' # ID part 2
30 h
+= b
'C' # 'S'ubmit / 'C'omplete / 'E'rror
31 h
+= b
'\x03' # ISO (0), Intr, Control, Bulk (3)
32 h
+= b
'\x00' # Endpoint
33 h
+= b
'\x00' # Device address
34 h
+= b
'\x00\x00' # Bus number
35 h
+= b
'-' # Setup tag - 0: Setup present, '-' otherwise
36 h
+= b
'<' # Data tag - '<' no data, 0 otherwise
38 h
+= b
'\x00\x00\x00\x00' # TS seconds part 1
39 h
+= b
'\x00\x00\x00\x00' # TS seconds part 2
40 h
+= b
'\x00\x00\x00\x00' # TS useconds
42 h
+= b
'\x00\x00\x00\x00' # Status 0: OK
43 h
+= b
'\x00\x00\x00\x00' # URB length
44 h
+= b
'\x00\x00\x00\x00' # Data length
45 # Setup packet data, valid if setup tag == 0
46 h
+= b
'\x00' # bmRequestType
47 h
+= b
'\x00' # bRequest
48 h
+= b
'\x00\x00' # wValue
49 h
+= b
'\x00\x00' # wIndex
50 h
+= b
'\x00\x00' # wLength
52 h
+= b
'\x00\x00\x00\x00' # ISO/interrupt interval
53 h
+= b
'\x00\x00\x00\x00' # ISO start frame
54 h
+= b
'\x00\x00\x00\x00' # URB flags
55 h
+= b
'\x00\x00\x00\x00' # Number of ISO descriptors
57 def __init__(self
, req
, ts
, is_submit
):
58 self
.header
= bytearray(pcap_usb_pkt
.h
)
60 self
.set_urbid(req
['id'])
61 self
.set_urbtype('S' if is_submit
else 'C')
62 self
.set_timestamp(ts
)
63 self
.set_addr_ep(req
['addr'], req
['ep'])
64 if req
['type'] in ('SETUP IN', 'SETUP OUT'):
65 self
.set_transfertype(2) # Control
66 self
.set_setup(req
['setup_data'])
67 if req
['type'] in ('BULK IN'):
68 self
.set_addr_ep(req
['addr'], 0x80 | req
['ep'])
69 self
.set_data(req
['data'])
71 def set_urbid(self
, urbid
):
72 self
.header
[4:8] = struct
.pack('>I', urbid
)
74 def set_urbtype(self
, urbtype
):
75 self
.header
[8] = ord(urbtype
)
77 def set_transfertype(self
, transfertype
):
78 self
.header
[9] = transfertype
80 def set_addr_ep(self
, addr
, ep
):
81 self
.header
[11] = addr
84 def set_timestamp(self
, ts
):
86 self
.header
[20:24] = struct
.pack('>I', ts
[0]) # seconds
87 self
.header
[24:28] = struct
.pack('>I', ts
[1]) # microseconds
89 def set_data(self
, data
):
92 self
.header
[36:40] = struct
.pack('>I', len(data
))
94 def set_setup(self
, data
):
96 self
.header
[40:48] = data
99 return bytes(self
.header
) + bytes(self
.data
)
101 def record_header(self
):
102 # See https://wiki.wireshark.org/Development/LibpcapFileFormat.
103 (secs
, usecs
) = self
.timestamp
104 h
= struct
.pack('>I', secs
) # TS seconds
105 h
+= struct
.pack('>I', usecs
) # TS microseconds
106 # No truncation, so both lengths are the same.
107 h
+= struct
.pack('>I', len(self
)) # Captured len (usb hdr + data)
108 h
+= struct
.pack('>I', len(self
)) # Original len
112 return 64 + len(self
.data
)
114 class Decoder(srd
.Decoder
):
118 longname
= 'Universal Serial Bus (LS/FS) transaction/request'
119 desc
= 'USB (low-speed/full-speed) transaction/request protocol.'
121 inputs
= ['usb_packet']
122 outputs
= ['usb_request']
124 {'id': 'in_request_start', 'desc': 'Start IN requests on',
125 'default': 'submit', 'values': ('submit', 'first-ack')},
129 ('request-setup-read', 'Setup: Device-to-host'),
130 ('request-setup-write', 'Setup: Host-to-device'),
131 ('request-bulk-read', 'Bulk: Device-to-host'),
132 ('request-bulk-write', 'Bulk: Host-to-device'),
133 ('error', 'Unexpected packet'),
136 ('request-setup', 'USB SETUP', (0, 1)),
137 ('request-in', 'USB BULK IN', (2,)),
138 ('request-out', 'USB BULK OUT', (3,)),
139 ('errors', 'Errors', (4,)),
142 ('pcap', 'PCAP format'),
149 self
.samplerate
= None
152 self
.transaction_state
= 'IDLE'
153 self
.ss_transaction
= None
154 self
.es_transaction
= None
155 self
.transaction_ep
= None
156 self
.transaction_addr
= None
157 self
.wrote_pcap_header
= False
159 def putr(self
, ss
, es
, data
):
160 self
.put(ss
, es
, self
.out_ann
, data
)
162 def putb(self
, ts
, data
):
163 self
.put(ts
, ts
, self
.out_binary
, data
)
165 def pcap_global_header(self
):
166 # See https://wiki.wireshark.org/Development/LibpcapFileFormat.
167 h
= b
'\xa1\xb2\xc3\xd4' # Magic, indicate microsecond ts resolution
168 h
+= b
'\x00\x02' # Major version 2
169 h
+= b
'\x00\x04' # Minor version 4
170 h
+= b
'\x00\x00\x00\x00' # Correction vs. UTC, seconds
171 h
+= b
'\x00\x00\x00\x00' # Timestamp accuracy
172 h
+= b
'\xff\xff\xff\xff' # Max packet len
173 # LINKTYPE_USB_LINUX_MMAPPED 220
174 # Linux usbmon format, see Documentation/usb/usbmon.txt.
175 h
+= b
'\x00\x00\x00\xdc' # Link layer
178 def metadata(self
, key
, value
):
179 if key
== srd
.SRD_CONF_SAMPLERATE
:
180 self
.samplerate
= value
182 self
.secs_per_sample
= float(1) / float(self
.samplerate
)
185 self
.out_binary
= self
.register(srd
.OUTPUT_BINARY
)
186 self
.out_ann
= self
.register(srd
.OUTPUT_ANN
)
187 self
.in_request_start
= self
.options
['in_request_start']
189 def handle_transfer(self
):
191 request_end
= self
.handshake
in ('ACK', 'STALL', 'timeout')
192 ep
= self
.transaction_ep
193 addr
= self
.transaction_addr
195 # Handle protocol STALLs, condition lasts until next SETUP transfer (8.5.3.4)
196 if self
.transaction_type
== 'SETUP' and (addr
, ep
) in self
.request
:
197 request
= self
.request
[(addr
,ep
)]
198 if request
['type'] in ('SETUP IN', 'SETUP OUT'):
199 request
['es'] = self
.ss_transaction
200 self
.handle_request(0, 1)
202 if not (addr
, ep
) in self
.request
:
203 self
.request
[(addr
, ep
)] = {'setup_data': [], 'data': [],
204 'type': None, 'ss': self
.ss_transaction
, 'es': None,
205 'ss_data': None, 'id': self
.request_id
, 'addr': addr
, 'ep': ep
}
208 request
= self
.request
[(addr
,ep
)]
211 request
['es'] = self
.es_transaction
212 request
['handshake'] = self
.handshake
214 # BULK or INTERRUPT transfer
215 if request
['type'] in (None, 'BULK IN') and self
.transaction_type
== 'IN':
216 request
['type'] = 'BULK IN'
217 if len(request
['data']) == 0 and len(self
.transaction_data
) > 0:
218 request
['ss_data'] = self
.ss_transaction
219 request
['data'] += self
.transaction_data
220 self
.handle_request(request_started
, request_end
)
221 elif request
['type'] in (None, 'BULK OUT') and self
.transaction_type
== 'OUT':
222 request
['type'] = 'BULK OUT'
223 if self
.handshake
== 'ACK':
224 request
['data'] += self
.transaction_data
225 self
.handle_request(request_started
, request_end
)
227 # CONTROL, SETUP stage
228 elif request
['type'] is None and self
.transaction_type
== 'SETUP':
229 request
['setup_data'] = self
.transaction_data
230 request
['wLength'] = struct
.unpack('<H',
231 bytes(self
.transaction_data
[6:8]))[0]
232 if self
.transaction_data
[0] & 0x80:
233 request
['type'] = 'SETUP IN'
234 self
.handle_request(1, 0)
236 request
['type'] = 'SETUP OUT'
237 self
.handle_request(request
['wLength'] == 0, 0)
239 # CONTROL, DATA stage
240 elif request
['type'] == 'SETUP IN' and self
.transaction_type
== 'IN':
241 request
['data'] += self
.transaction_data
243 elif request
['type'] == 'SETUP OUT' and self
.transaction_type
== 'OUT':
244 if self
.handshake
== 'ACK':
245 request
['data'] += self
.transaction_data
246 if request
['wLength'] == len(request
['data']):
247 self
.handle_request(1, 0)
249 # CONTROL, STATUS stage
250 elif request
['type'] == 'SETUP IN' and self
.transaction_type
== 'OUT':
251 self
.handle_request(0, request_end
)
253 elif request
['type'] == 'SETUP OUT' and self
.transaction_type
== 'IN':
254 self
.handle_request(0, request_end
)
261 def ts_from_samplenum(self
, sample
):
262 ts
= float(sample
) * self
.secs_per_sample
263 return (int(ts
), int((ts
% 1.0) * 1e6
))
265 def write_pcap_header(self
):
266 if not self
.wrote_pcap_header
:
267 self
.put(0, 0, self
.out_binary
, [0, self
.pcap_global_header()])
268 self
.wrote_pcap_header
= True
270 def request_summary(self
, request
):
272 if request
['type'] in ('SETUP IN', 'SETUP OUT'):
273 for b
in request
['setup_data']:
276 for b
in request
['data']:
278 s
+= ' ] : %s' % request
['handshake']
281 def handle_request(self
, request_start
, request_end
):
282 if request_start
!= 1 and request_end
!= 1:
284 self
.write_pcap_header()
285 ep
= self
.transaction_ep
286 addr
= self
.transaction_addr
287 request
= self
.request
[(addr
, ep
)]
289 ss
, es
, ss_data
= request
['ss'], request
['es'], request
['ss_data']
290 if self
.in_request_start
== 'submit':
293 if request_start
== 1:
294 # Issue PCAP 'SUBMIT' packet.
295 ts
= self
.ts_from_samplenum(ss
)
296 pkt
= pcap_usb_pkt(request
, ts
, True)
297 self
.putb(ss
, [0, pkt
.record_header()])
298 self
.putb(ss
, [0, pkt
.packet()])
302 summary
= self
.request_summary(request
)
303 if request
['type'] == 'SETUP IN':
304 self
.putr(ss
, es
, [0, ['SETUP in: %s' % summary
]])
305 elif request
['type'] == 'SETUP OUT':
306 self
.putr(ss
, es
, [1, ['SETUP out: %s' % summary
]])
307 elif request
['type'] == 'BULK IN':
308 self
.putr(ss_data
, es
, [2, ['BULK in: %s' % summary
]])
309 elif request
['type'] == 'BULK OUT':
310 self
.putr(ss
, es
, [3, ['BULK out: %s' % summary
]])
312 # Issue PCAP 'COMPLETE' packet.
313 ts
= self
.ts_from_samplenum(es
)
314 pkt
= pcap_usb_pkt(request
, ts
, False)
315 self
.putb(ss
, [0, pkt
.record_header()])
316 self
.putb(ss
, [0, pkt
.packet()])
317 del self
.request
[(addr
, ep
)]
319 def decode(self
, ss
, es
, data
):
320 if not self
.samplerate
:
321 raise SamplerateError('Cannot decode without samplerate.')
324 # We only care about certain packet types for now.
325 if ptype
not in ('PACKET'):
328 pcategory
, pname
, pinfo
= pdata
330 if pcategory
== 'TOKEN':
333 if self
.transaction_state
== 'TOKEN RECEIVED':
334 transaction_timeout
= self
.es_transaction
335 # Token length is 35 bits, timeout is 16..18 bit times
336 # (USB 2.0 7.1.19.1).
337 transaction_timeout
+= int((self
.es_transaction
- self
.ss_transaction
) / 2)
338 if ss
> transaction_timeout
:
339 self
.es_transaction
= transaction_timeout
340 self
.handshake
= 'timeout'
341 self
.handle_transfer()
342 self
.transaction_state
= 'IDLE'
344 if self
.transaction_state
!= 'IDLE':
345 self
.putr(ss
, es
, [4, ['ERR: received %s token in state %s' %
346 (pname
, self
.transaction_state
)]])
349 sync
, pid
, addr
, ep
, crc5
= pinfo
350 self
.transaction_data
= []
351 self
.ss_transaction
= ss
352 self
.es_transaction
= es
353 self
.transaction_state
= 'TOKEN RECEIVED'
354 self
.transaction_ep
= ep
355 if ep
> 0 and pname
== 'IN':
356 self
.transaction_ep
= ep
+ 0x80
357 self
.transaction_addr
= addr
358 self
.transaction_type
= pname
# IN OUT SETUP
360 elif pcategory
== 'DATA':
361 if self
.transaction_state
!= 'TOKEN RECEIVED':
362 self
.putr(ss
, es
, [4, ['ERR: received %s token in state %s' %
363 (pname
, self
.transaction_state
)]])
366 self
.transaction_data
= pinfo
[2]
367 self
.transaction_state
= 'DATA RECEIVED'
369 elif pcategory
== 'HANDSHAKE':
370 if self
.transaction_state
not in ('TOKEN RECEIVED', 'DATA RECEIVED'):
371 self
.putr(ss
, es
, [4, ['ERR: received %s token in state %s' %
372 (pname
, self
.transaction_state
)]])
375 self
.handshake
= pname
376 self
.transaction_state
= 'IDLE'
377 self
.es_transaction
= es
378 self
.handle_transfer()
384 self
.putr(ss
, es
, [4, ['ERR: received unhandled %s token in state %s' %
385 (pname
, self
.transaction_state
)]])