]>
Commit | Line | Data |
---|---|---|
2dc6d41c | 1 | ## |
50bd5d25 | 2 | ## This file is part of the libsigrokdecode project. |
2dc6d41c UH |
3 | ## |
4 | ## Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz> | |
a87d7847 | 5 | ## Copyright (C) 2012-2014 Uwe Hermann <uwe@hermann-uwe.de> |
2dc6d41c UH |
6 | ## |
7 | ## This program is free software; you can redistribute it and/or modify | |
8 | ## it under the terms of the GNU General Public License as published by | |
9 | ## the Free Software Foundation; either version 2 of the License, or | |
10 | ## (at your option) any later version. | |
11 | ## | |
12 | ## This program is distributed in the hope that it will be useful, | |
13 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | ## GNU General Public License for more details. | |
16 | ## | |
17 | ## You should have received a copy of the GNU General Public License | |
4539e9ca | 18 | ## along with this program; if not, see <http://www.gnu.org/licenses/>. |
2dc6d41c UH |
19 | ## |
20 | ||
2dc6d41c UH |
21 | import sigrokdecode as srd |
22 | ||
0eb16a7b | 23 | ''' |
c515eed7 | 24 | OUTPUT_PYTHON format: |
0eb16a7b UH |
25 | |
26 | Packet: | |
27 | [<ptype>, <pdata>] | |
28 | ||
29 | <ptype>, <pdata>: | |
30 | - 'SYNC', <sync> | |
31 | - 'PID', <pid> | |
32 | - 'ADDR', <addr> | |
33 | - 'EP', <ep> | |
34 | - 'CRC5', <crc5> | |
91a19cb3 | 35 | - 'CRC5 ERROR', <crc5> |
0eb16a7b | 36 | - 'CRC16', <crc16> |
91a19cb3 | 37 | - 'CRC16 ERROR', <crc16> |
0eb16a7b UH |
38 | - 'EOP', <eop> |
39 | - 'FRAMENUM', <framenum> | |
40 | - 'DATABYTE', <databyte> | |
41 | - 'HUBADDR', <hubaddr> | |
42 | - 'SC', <sc> | |
43 | - 'PORT', <port> | |
44 | - 'S', <s> | |
45 | - 'E/U', <e/u> | |
46 | - 'ET', <et> | |
47 | - 'PACKET', [<pcategory>, <pname>, <pinfo>] | |
48 | ||
49 | <pcategory>, <pname>, <pinfo>: | |
50 | - 'TOKEN', 'OUT', [<sync>, <pid>, <addr>, <ep>, <crc5>, <eop>] | |
51 | - 'TOKEN', 'IN', [<sync>, <pid>, <addr>, <ep>, <crc5>, <eop>] | |
52 | - 'TOKEN', 'SOF', [<sync>, <pid>, <framenum>, <crc5>, <eop>] | |
53 | - 'TOKEN', 'SETUP', [<sync>, <pid>, <addr>, <ep>, <crc5>, <eop>] | |
54 | - 'DATA', 'DATA0', [<sync>, <pid>, <databytes>, <crc16>, <eop>] | |
55 | - 'DATA', 'DATA1', [<sync>, <pid>, <databytes>, <crc16>, <eop>] | |
56 | - 'DATA', 'DATA2', [<sync>, <pid>, <databytes>, <crc16>, <eop>] | |
57 | - 'DATA', 'MDATA', [<sync>, <pid>, <databytes>, <crc16>, <eop>] | |
58 | - 'HANDSHAKE', 'ACK', [<sync>, <pid>, <eop>] | |
eafe8f08 | 59 | - 'HANDSHAKE', 'NAK', [<sync>, <pid>, <eop>] |
0eb16a7b UH |
60 | - 'HANDSHAKE', 'STALL', [<sync>, <pid>, <eop>] |
61 | - 'HANDSHAKE', 'NYET', [<sync>, <pid>, <eop>] | |
62 | - 'SPECIAL', 'PRE', [<sync>, <pid>, <addr>, <ep>, <crc5>, <eop>] | |
63 | - 'SPECIAL', 'ERR', [<sync>, <pid>, <eop>] | |
64 | - 'SPECIAL', 'SPLIT', | |
65 | [<sync>, <pid>, <hubaddr>, <sc>, <port>, <s>, <e/u>, <et>, <crc5>, <eop>] | |
66 | - 'SPECIAL', 'PING', [<sync>, <pid>, <addr>, <ep>, <crc5>, <eop>] | |
67 | - 'SPECIAL', 'Reserved', None | |
68 | ||
69 | <sync>: SYNC field bitstring, normally '00000001' (8 chars). | |
70 | <pid>: Packet ID bitstring, e.g. '11000011' for DATA0 (8 chars). | |
71 | <addr>: Address field number, 0-127 (7 bits). | |
72 | <ep>: Endpoint number, 0-15 (4 bits). | |
73 | <crc5>: CRC-5 number (5 bits). | |
74 | <crc16>: CRC-16 number (16 bits). | |
75 | <eop>: End of packet marker. List of symbols, usually ['SE0', 'SE0', 'J']. | |
76 | <framenum>: USB (micro)frame number, 0-2047 (11 bits). | |
77 | <databyte>: A single data byte, e.g. 0x55. | |
78 | <databytes>: List of data bytes, e.g. [0x55, 0xaa, 0x99] (0 - 1024 bytes). | |
79 | <hubaddr>: TODO | |
80 | <sc>: TODO | |
81 | <port>: TODO | |
82 | <s>: TODO | |
83 | <e/u>: TODO | |
84 | <et>: TODO | |
85 | ''' | |
86 | ||
2dc6d41c UH |
87 | # Packet IDs (PIDs). |
88 | # The first 4 bits are the 'packet type' field, the last 4 bits are the | |
89 | # 'check field' (each bit in the check field must be the inverse of the resp. | |
90 | # bit in the 'packet type' field; if not, that's a 'PID error'). | |
91 | # For the 4-bit strings, the left-most '1' or '0' is the LSB, i.e. it's sent | |
92 | # to the bus first. | |
93 | pids = { | |
94 | # Tokens | |
95 | '10000111': ['OUT', 'Address & EP number in host-to-function transaction'], | |
96 | '10010110': ['IN', 'Address & EP number in function-to-host transaction'], | |
97 | '10100101': ['SOF', 'Start-Of-Frame marker & frame number'], | |
98 | '10110100': ['SETUP', 'Address & EP number in host-to-function transaction for SETUP to a control pipe'], | |
99 | ||
100 | # Data | |
101 | # Note: DATA2 and MDATA are HS-only. | |
102 | '11000011': ['DATA0', 'Data packet PID even'], | |
103 | '11010010': ['DATA1', 'Data packet PID odd'], | |
104 | '11100001': ['DATA2', 'Data packet PID HS, high bandwidth isosynchronous transaction in a microframe'], | |
105 | '11110000': ['MDATA', 'Data packet PID HS for split and high-bandwidth isosynchronous transactions'], | |
106 | ||
107 | # Handshake | |
108 | '01001011': ['ACK', 'Receiver accepts error-free packet'], | |
109 | '01011010': ['NAK', 'Receiver cannot accept or transmitter cannot send'], | |
110 | '01111000': ['STALL', 'EP halted or control pipe request unsupported'], | |
111 | '01101001': ['NYET', 'No response yet from receiver'], | |
112 | ||
113 | # Special | |
114 | '00111100': ['PRE', 'Host-issued preamble; enables downstream bus traffic to low-speed devices'], | |
be0f8fee | 115 | #'00111100': ['ERR', 'Split transaction error handshake'], |
2dc6d41c UH |
116 | '00011110': ['SPLIT', 'HS split transaction token'], |
117 | '00101101': ['PING', 'HS flow control probe for a bulk/control EP'], | |
118 | '00001111': ['Reserved', 'Reserved PID'], | |
119 | } | |
120 | ||
0eb16a7b UH |
121 | def get_category(pidname): |
122 | if pidname in ('OUT', 'IN', 'SOF', 'SETUP'): | |
123 | return 'TOKEN' | |
124 | elif pidname in ('DATA0', 'DATA1', 'DATA2', 'MDATA'): | |
125 | return 'DATA' | |
eafe8f08 | 126 | elif pidname in ('ACK', 'NAK', 'STALL', 'NYET'): |
0eb16a7b UH |
127 | return 'HANDSHAKE' |
128 | else: | |
129 | return 'SPECIAL' | |
130 | ||
a87d7847 UH |
131 | def ann_index(pidname): |
132 | l = ['OUT', 'IN', 'SOF', 'SETUP', 'DATA0', 'DATA1', 'DATA2', 'MDATA', | |
133 | 'ACK', 'NAK', 'STALL', 'NYET', 'PRE', 'ERR', 'SPLIT', 'PING', | |
134 | 'Reserved'] | |
135 | if pidname not in l: | |
136 | return 28 | |
137 | return l.index(pidname) + 11 | |
138 | ||
2dc6d41c UH |
139 | def bitstr_to_num(bitstr): |
140 | if not bitstr: | |
141 | return 0 | |
142 | l = list(bitstr) | |
143 | l.reverse() | |
144 | return int(''.join(l), 2) | |
145 | ||
0e3cb15e SB |
146 | def reverse_number(num, count): |
147 | out = list(count * '0') | |
148 | for i in range(0, count): | |
64b45b20 | 149 | if num >> i & 1: |
f78b814d | 150 | out[i] = '1' |
0e3cb15e SB |
151 | return int(''.join(out), 2) |
152 | ||
153 | def calc_crc5(bitstr): | |
154 | poly5 = 0x25 | |
155 | crc5 = 0x1f | |
156 | for bit in bitstr: | |
157 | crc5 <<= 1 | |
64b45b20 | 158 | if int(bit) != (crc5 >> 5): |
0e3cb15e SB |
159 | crc5 ^= poly5 |
160 | crc5 &= 0x1f | |
161 | crc5 ^= 0x1f | |
162 | return reverse_number(crc5, 5) | |
163 | ||
164 | def calc_crc16(bitstr): | |
165 | poly16 = 0x18005 | |
166 | crc16 = 0xffff | |
167 | for bit in bitstr: | |
168 | crc16 <<= 1 | |
64b45b20 | 169 | if int(bit) != (crc16 >> 16): |
0e3cb15e SB |
170 | crc16 ^= poly16 |
171 | crc16 &= 0xffff | |
172 | crc16 ^= 0xffff | |
173 | return reverse_number(crc16, 16) | |
174 | ||
2dc6d41c | 175 | class Decoder(srd.Decoder): |
b197383c | 176 | api_version = 3 |
a015bd49 UH |
177 | id = 'usb_packet' |
178 | name = 'USB packet' | |
179 | longname = 'Universal Serial Bus (LS/FS) packet' | |
180 | desc = 'USB (low-speed and full-speed) packet protocol.' | |
2dc6d41c UH |
181 | license = 'gplv2+' |
182 | inputs = ['usb_signalling'] | |
a015bd49 | 183 | outputs = ['usb_packet'] |
d6d8a8a4 | 184 | tags = ['PC'] |
84c1c0b5 | 185 | options = ( |
b0918d40 UH |
186 | {'id': 'signalling', 'desc': 'Signalling', |
187 | 'default': 'full-speed', 'values': ('full-speed', 'low-speed')}, | |
84c1c0b5 | 188 | ) |
da9bcbd9 BV |
189 | annotations = ( |
190 | ('sync-ok', 'SYNC'), | |
191 | ('sync-err', 'SYNC (error)'), | |
192 | ('pid', 'PID'), | |
193 | ('framenum', 'FRAMENUM'), | |
194 | ('addr', 'ADDR'), | |
195 | ('ep', 'EP'), | |
196 | ('crc5-ok', 'CRC5'), | |
197 | ('crc5-err', 'CRC5 (error)'), | |
198 | ('data', 'DATA'), | |
199 | ('crc16-ok', 'CRC16'), | |
200 | ('crc16-err', 'CRC16 (error)'), | |
201 | ('packet-out', 'Packet: OUT'), | |
202 | ('packet-in', 'Packet: IN'), | |
203 | ('packet-sof', 'Packet: SOF'), | |
204 | ('packet-setup', 'Packet: SETUP'), | |
205 | ('packet-data0', 'Packet: DATA0'), | |
206 | ('packet-data1', 'Packet: DATA1'), | |
207 | ('packet-data2', 'Packet: DATA2'), | |
208 | ('packet-mdata', 'Packet: MDATA'), | |
209 | ('packet-ack', 'Packet: ACK'), | |
210 | ('packet-nak', 'Packet: NAK'), | |
211 | ('packet-stall', 'Packet: STALL'), | |
212 | ('packet-nyet', 'Packet: NYET'), | |
213 | ('packet-pre', 'Packet: PRE'), | |
214 | ('packet-err', 'Packet: ERR'), | |
215 | ('packet-split', 'Packet: SPLIT'), | |
216 | ('packet-ping', 'Packet: PING'), | |
217 | ('packet-reserved', 'Packet: Reserved'), | |
218 | ('packet-invalid', 'Packet: Invalid'), | |
219 | ) | |
a87d7847 | 220 | annotation_rows = ( |
fa62a8f9 UH |
221 | ('fields', 'Packet fields', tuple(range(10 + 1))), |
222 | ('packet', 'Packets', tuple(range(11, 28 + 1))), | |
a87d7847 | 223 | ) |
2dc6d41c UH |
224 | |
225 | def __init__(self): | |
10aeb8ea GS |
226 | self.reset() |
227 | ||
228 | def reset(self): | |
0eb16a7b UH |
229 | self.bits = [] |
230 | self.packet = [] | |
231 | self.packet_summary = '' | |
232 | self.ss = self.es = None | |
233 | self.ss_packet = self.es_packet = None | |
234 | self.state = 'WAIT FOR SOP' | |
235 | ||
236 | def putpb(self, data): | |
c515eed7 | 237 | self.put(self.ss, self.es, self.out_python, data) |
0eb16a7b UH |
238 | |
239 | def putb(self, data): | |
240 | self.put(self.ss, self.es, self.out_ann, data) | |
241 | ||
242 | def putpp(self, data): | |
c515eed7 | 243 | self.put(self.ss_packet, self.es_packet, self.out_python, data) |
0eb16a7b UH |
244 | |
245 | def putp(self, data): | |
246 | self.put(self.ss_packet, self.es_packet, self.out_ann, data) | |
2dc6d41c | 247 | |
8915b346 | 248 | def start(self): |
c515eed7 | 249 | self.out_python = self.register(srd.OUTPUT_PYTHON) |
be465111 | 250 | self.out_ann = self.register(srd.OUTPUT_ANN) |
2dc6d41c | 251 | |
0eb16a7b UH |
252 | def handle_packet(self): |
253 | packet = '' | |
254 | for (bit, ss, es) in self.bits: | |
255 | packet += bit | |
256 | ||
3e84c443 UH |
257 | if len(packet) < 8: |
258 | self.putp([28, ['Invalid packet (shorter than 8 bits)']]) | |
259 | return | |
260 | ||
0eb16a7b UH |
261 | # Bits[0:7]: SYNC |
262 | sync = packet[:7 + 1] | |
263 | self.ss, self.es = self.bits[0][1], self.bits[7][2] | |
264 | # The SYNC pattern for low-speed/full-speed is KJKJKJKK (00000001). | |
265 | if sync != '00000001': | |
266 | self.putpb(['SYNC ERROR', sync]) | |
7a233067 UH |
267 | self.putb([1, ['SYNC ERROR: %s' % sync, 'SYNC ERR: %s' % sync, |
268 | 'SYNC ERR', 'SE', 'S']]) | |
0eb16a7b UH |
269 | else: |
270 | self.putpb(['SYNC', sync]) | |
7a233067 | 271 | self.putb([0, ['SYNC: %s' % sync, 'SYNC', 'S']]) |
0eb16a7b UH |
272 | self.packet.append(sync) |
273 | ||
3e84c443 UH |
274 | if len(packet) < 16: |
275 | self.putp([28, ['Invalid packet (shorter than 16 bits)']]) | |
276 | return | |
277 | ||
0eb16a7b UH |
278 | # Bits[8:15]: PID |
279 | pid = packet[8:15 + 1] | |
3e84c443 | 280 | pidname = pids.get(pid, ('UNKNOWN', 'Unknown PID'))[0] |
0eb16a7b UH |
281 | self.ss, self.es = self.bits[8][1], self.bits[15][2] |
282 | self.putpb(['PID', pidname]) | |
7a233067 | 283 | self.putb([2, ['PID: %s' % pidname, pidname, pidname[0]]]) |
0eb16a7b UH |
284 | self.packet.append(pid) |
285 | self.packet_summary += pidname | |
286 | ||
be0f8fee | 287 | if pidname in ('OUT', 'IN', 'SOF', 'SETUP', 'PING'): |
3e84c443 UH |
288 | if len(packet) < 32: |
289 | self.putp([28, ['Invalid packet (shorter than 32 bits)']]) | |
290 | return | |
291 | ||
0eb16a7b UH |
292 | if pidname == 'SOF': |
293 | # Bits[16:26]: Framenum | |
294 | framenum = bitstr_to_num(packet[16:26 + 1]) | |
295 | self.ss, self.es = self.bits[16][1], self.bits[26][2] | |
296 | self.putpb(['FRAMENUM', framenum]) | |
7a233067 | 297 | self.putb([3, ['Frame: %d' % framenum, 'Frame', 'Fr', 'F']]) |
0eb16a7b UH |
298 | self.packet.append(framenum) |
299 | self.packet_summary += ' %d' % framenum | |
300 | else: | |
301 | # Bits[16:22]: Addr | |
302 | addr = bitstr_to_num(packet[16:22 + 1]) | |
303 | self.ss, self.es = self.bits[16][1], self.bits[22][2] | |
304 | self.putpb(['ADDR', addr]) | |
7a233067 UH |
305 | self.putb([4, ['Address: %d' % addr, 'Addr: %d' % addr, |
306 | 'Addr', 'A']]) | |
0eb16a7b UH |
307 | self.packet.append(addr) |
308 | self.packet_summary += ' ADDR %d' % addr | |
309 | ||
310 | # Bits[23:26]: EP | |
311 | ep = bitstr_to_num(packet[23:26 + 1]) | |
312 | self.ss, self.es = self.bits[23][1], self.bits[26][2] | |
313 | self.putpb(['EP', ep]) | |
7a233067 | 314 | self.putb([5, ['Endpoint: %d' % ep, 'EP: %d' % ep, 'EP', 'E']]) |
0eb16a7b UH |
315 | self.packet.append(ep) |
316 | self.packet_summary += ' EP %d' % ep | |
317 | ||
318 | # Bits[27:31]: CRC5 | |
319 | crc5 = bitstr_to_num(packet[27:31 + 1]) | |
0e3cb15e | 320 | crc5_calc = calc_crc5(packet[16:27]) |
0eb16a7b | 321 | self.ss, self.es = self.bits[27][1], self.bits[31][2] |
64b45b20 | 322 | if crc5 == crc5_calc: |
0e3cb15e SB |
323 | self.putpb(['CRC5', crc5]) |
324 | self.putb([6, ['CRC5: 0x%02X' % crc5, 'CRC5', 'C']]) | |
325 | else: | |
326 | self.putpb(['CRC5 ERROR', crc5]) | |
327 | self.putb([7, ['CRC5 ERROR: 0x%02X' % crc5, 'CRC5 ERR', 'CE', 'C']]) | |
0eb16a7b UH |
328 | self.packet.append(crc5) |
329 | elif pidname in ('DATA0', 'DATA1', 'DATA2', 'MDATA'): | |
330 | # Bits[16:packetlen-16]: Data | |
331 | data = packet[16:-16] | |
332 | # TODO: len(data) must be a multiple of 8. | |
333 | databytes = [] | |
334 | self.packet_summary += ' [' | |
335 | for i in range(0, len(data), 8): | |
336 | db = bitstr_to_num(data[i:i + 8]) | |
337 | self.ss, self.es = self.bits[16 + i][1], self.bits[23 + i][2] | |
338 | self.putpb(['DATABYTE', db]) | |
7a233067 UH |
339 | self.putb([8, ['Databyte: %02X' % db, 'Data: %02X' % db, |
340 | 'DB: %02X' % db, '%02X' % db]]) | |
0eb16a7b | 341 | databytes.append(db) |
7a233067 | 342 | self.packet_summary += ' %02X' % db |
0eb16a7b UH |
343 | self.packet_summary += ' ]' |
344 | ||
7a233067 | 345 | # Convenience Python output (no annotation) for all bytes together. |
0eb16a7b UH |
346 | self.ss, self.es = self.bits[16][1], self.bits[-16][2] |
347 | self.putpb(['DATABYTES', databytes]) | |
348 | self.packet.append(databytes) | |
349 | ||
350 | # Bits[packetlen-16:packetlen]: CRC16 | |
351 | crc16 = bitstr_to_num(packet[-16:]) | |
0e3cb15e | 352 | crc16_calc = calc_crc16(packet[16:-16]) |
0eb16a7b | 353 | self.ss, self.es = self.bits[-16][1], self.bits[-1][2] |
64b45b20 | 354 | if crc16 == crc16_calc: |
0e3cb15e SB |
355 | self.putpb(['CRC16', crc16]) |
356 | self.putb([9, ['CRC16: 0x%04X' % crc16, 'CRC16', 'C']]) | |
357 | else: | |
358 | self.putpb(['CRC16 ERROR', crc16]) | |
359 | self.putb([10, ['CRC16 ERROR: 0x%04X' % crc16, 'CRC16 ERR', 'CE', 'C']]) | |
0eb16a7b UH |
360 | self.packet.append(crc16) |
361 | elif pidname in ('ACK', 'NAK', 'STALL', 'NYET', 'ERR'): | |
362 | pass # Nothing to do, these only have SYNC+PID+EOP fields. | |
be0f8fee | 363 | elif pidname in ('PRE'): |
502acfc2 | 364 | pass # Nothing to do, PRE only has SYNC+PID fields. |
0eb16a7b UH |
365 | else: |
366 | pass # TODO: Handle 'SPLIT' and possibly 'Reserved' packets. | |
367 | ||
368 | # Output a (summary of) the whole packet. | |
369 | pcategory, pname, pinfo = get_category(pidname), pidname, self.packet | |
370 | self.putpp(['PACKET', [pcategory, pname, pinfo]]) | |
a87d7847 | 371 | self.putp([ann_index(pidname), ['%s' % self.packet_summary]]) |
0eb16a7b UH |
372 | |
373 | self.packet, self.packet_summary = [], '' | |
374 | ||
2dc6d41c UH |
375 | def decode(self, ss, es, data): |
376 | (ptype, pdata) = data | |
377 | ||
0eb16a7b | 378 | # We only care about certain packet types for now. |
e655c0aa | 379 | if ptype not in ('SOP', 'BIT', 'EOP', 'ERR'): |
0eb16a7b | 380 | return |
2dc6d41c | 381 | |
0eb16a7b UH |
382 | # State machine. |
383 | if self.state == 'WAIT FOR SOP': | |
384 | if ptype != 'SOP': | |
385 | return | |
386 | self.ss_packet = ss | |
387 | self.state = 'GET BIT' | |
388 | elif self.state == 'GET BIT': | |
389 | if ptype == 'BIT': | |
390 | self.bits.append([pdata, ss, es]) | |
e655c0aa | 391 | elif ptype == 'EOP' or ptype == 'ERR': |
0eb16a7b UH |
392 | self.es_packet = es |
393 | self.handle_packet() | |
3e84c443 | 394 | self.packet, self.packet_summary = [], '' |
0eb16a7b UH |
395 | self.bits, self.state = [], 'WAIT FOR SOP' |
396 | else: | |
397 | pass # TODO: Error |