]>
Commit | Line | Data |
---|---|---|
1 | ## | |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz> | |
5 | ## Copyright (C) 2012-2014 Uwe Hermann <uwe@hermann-uwe.de> | |
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 | |
18 | ## along with this program; if not, see <http://www.gnu.org/licenses/>. | |
19 | ## | |
20 | ||
21 | import sigrokdecode as srd | |
22 | ||
23 | ''' | |
24 | OUTPUT_PYTHON format: | |
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> | |
35 | - 'CRC5 ERROR', <crc5> | |
36 | - 'CRC16', <crc16> | |
37 | - 'CRC16 ERROR', <crc16> | |
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>] | |
59 | - 'HANDSHAKE', 'NAK', [<sync>, <pid>, <eop>] | |
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 | ||
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'], | |
115 | #'00111100': ['ERR', 'Split transaction error handshake'], | |
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 | ||
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' | |
126 | elif pidname in ('ACK', 'NAK', 'STALL', 'NYET'): | |
127 | return 'HANDSHAKE' | |
128 | else: | |
129 | return 'SPECIAL' | |
130 | ||
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 | ||
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 | ||
146 | def reverse_number(num, count): | |
147 | out = list(count * '0') | |
148 | for i in range(0, count): | |
149 | if num >> i & 1: | |
150 | out[i] = '1'; | |
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 | |
158 | if int(bit) != (crc5 >> 5): | |
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 | |
169 | if int(bit) != (crc16 >> 16): | |
170 | crc16 ^= poly16 | |
171 | crc16 &= 0xffff | |
172 | crc16 ^= 0xffff | |
173 | return reverse_number(crc16, 16) | |
174 | ||
175 | class Decoder(srd.Decoder): | |
176 | api_version = 2 | |
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.' | |
181 | license = 'gplv2+' | |
182 | inputs = ['usb_signalling'] | |
183 | outputs = ['usb_packet'] | |
184 | options = ( | |
185 | {'id': 'signalling', 'desc': 'Signalling', | |
186 | 'default': 'full-speed', 'values': ('full-speed', 'low-speed')}, | |
187 | ) | |
188 | annotations = ( | |
189 | ('sync-ok', 'SYNC'), | |
190 | ('sync-err', 'SYNC (error)'), | |
191 | ('pid', 'PID'), | |
192 | ('framenum', 'FRAMENUM'), | |
193 | ('addr', 'ADDR'), | |
194 | ('ep', 'EP'), | |
195 | ('crc5-ok', 'CRC5'), | |
196 | ('crc5-err', 'CRC5 (error)'), | |
197 | ('data', 'DATA'), | |
198 | ('crc16-ok', 'CRC16'), | |
199 | ('crc16-err', 'CRC16 (error)'), | |
200 | ('packet-out', 'Packet: OUT'), | |
201 | ('packet-in', 'Packet: IN'), | |
202 | ('packet-sof', 'Packet: SOF'), | |
203 | ('packet-setup', 'Packet: SETUP'), | |
204 | ('packet-data0', 'Packet: DATA0'), | |
205 | ('packet-data1', 'Packet: DATA1'), | |
206 | ('packet-data2', 'Packet: DATA2'), | |
207 | ('packet-mdata', 'Packet: MDATA'), | |
208 | ('packet-ack', 'Packet: ACK'), | |
209 | ('packet-nak', 'Packet: NAK'), | |
210 | ('packet-stall', 'Packet: STALL'), | |
211 | ('packet-nyet', 'Packet: NYET'), | |
212 | ('packet-pre', 'Packet: PRE'), | |
213 | ('packet-err', 'Packet: ERR'), | |
214 | ('packet-split', 'Packet: SPLIT'), | |
215 | ('packet-ping', 'Packet: PING'), | |
216 | ('packet-reserved', 'Packet: Reserved'), | |
217 | ('packet-invalid', 'Packet: Invalid'), | |
218 | ) | |
219 | annotation_rows = ( | |
220 | ('fields', 'Packet fields', tuple(range(10 + 1))), | |
221 | ('packet', 'Packets', tuple(range(11, 28 + 1))), | |
222 | ) | |
223 | ||
224 | def __init__(self): | |
225 | self.bits = [] | |
226 | self.packet = [] | |
227 | self.packet_summary = '' | |
228 | self.ss = self.es = None | |
229 | self.ss_packet = self.es_packet = None | |
230 | self.state = 'WAIT FOR SOP' | |
231 | ||
232 | def putpb(self, data): | |
233 | self.put(self.ss, self.es, self.out_python, data) | |
234 | ||
235 | def putb(self, data): | |
236 | self.put(self.ss, self.es, self.out_ann, data) | |
237 | ||
238 | def putpp(self, data): | |
239 | self.put(self.ss_packet, self.es_packet, self.out_python, data) | |
240 | ||
241 | def putp(self, data): | |
242 | self.put(self.ss_packet, self.es_packet, self.out_ann, data) | |
243 | ||
244 | def start(self): | |
245 | self.out_python = self.register(srd.OUTPUT_PYTHON) | |
246 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
247 | ||
248 | def handle_packet(self): | |
249 | packet = '' | |
250 | for (bit, ss, es) in self.bits: | |
251 | packet += bit | |
252 | ||
253 | if len(packet) < 8: | |
254 | self.putp([28, ['Invalid packet (shorter than 8 bits)']]) | |
255 | return | |
256 | ||
257 | # Bits[0:7]: SYNC | |
258 | sync = packet[:7 + 1] | |
259 | self.ss, self.es = self.bits[0][1], self.bits[7][2] | |
260 | # The SYNC pattern for low-speed/full-speed is KJKJKJKK (00000001). | |
261 | if sync != '00000001': | |
262 | self.putpb(['SYNC ERROR', sync]) | |
263 | self.putb([1, ['SYNC ERROR: %s' % sync, 'SYNC ERR: %s' % sync, | |
264 | 'SYNC ERR', 'SE', 'S']]) | |
265 | else: | |
266 | self.putpb(['SYNC', sync]) | |
267 | self.putb([0, ['SYNC: %s' % sync, 'SYNC', 'S']]) | |
268 | self.packet.append(sync) | |
269 | ||
270 | if len(packet) < 16: | |
271 | self.putp([28, ['Invalid packet (shorter than 16 bits)']]) | |
272 | return | |
273 | ||
274 | # Bits[8:15]: PID | |
275 | pid = packet[8:15 + 1] | |
276 | pidname = pids.get(pid, ('UNKNOWN', 'Unknown PID'))[0] | |
277 | self.ss, self.es = self.bits[8][1], self.bits[15][2] | |
278 | self.putpb(['PID', pidname]) | |
279 | self.putb([2, ['PID: %s' % pidname, pidname, pidname[0]]]) | |
280 | self.packet.append(pid) | |
281 | self.packet_summary += pidname | |
282 | ||
283 | if pidname in ('OUT', 'IN', 'SOF', 'SETUP', 'PING'): | |
284 | if len(packet) < 32: | |
285 | self.putp([28, ['Invalid packet (shorter than 32 bits)']]) | |
286 | return | |
287 | ||
288 | if pidname == 'SOF': | |
289 | # Bits[16:26]: Framenum | |
290 | framenum = bitstr_to_num(packet[16:26 + 1]) | |
291 | self.ss, self.es = self.bits[16][1], self.bits[26][2] | |
292 | self.putpb(['FRAMENUM', framenum]) | |
293 | self.putb([3, ['Frame: %d' % framenum, 'Frame', 'Fr', 'F']]) | |
294 | self.packet.append(framenum) | |
295 | self.packet_summary += ' %d' % framenum | |
296 | else: | |
297 | # Bits[16:22]: Addr | |
298 | addr = bitstr_to_num(packet[16:22 + 1]) | |
299 | self.ss, self.es = self.bits[16][1], self.bits[22][2] | |
300 | self.putpb(['ADDR', addr]) | |
301 | self.putb([4, ['Address: %d' % addr, 'Addr: %d' % addr, | |
302 | 'Addr', 'A']]) | |
303 | self.packet.append(addr) | |
304 | self.packet_summary += ' ADDR %d' % addr | |
305 | ||
306 | # Bits[23:26]: EP | |
307 | ep = bitstr_to_num(packet[23:26 + 1]) | |
308 | self.ss, self.es = self.bits[23][1], self.bits[26][2] | |
309 | self.putpb(['EP', ep]) | |
310 | self.putb([5, ['Endpoint: %d' % ep, 'EP: %d' % ep, 'EP', 'E']]) | |
311 | self.packet.append(ep) | |
312 | self.packet_summary += ' EP %d' % ep | |
313 | ||
314 | # Bits[27:31]: CRC5 | |
315 | crc5 = bitstr_to_num(packet[27:31 + 1]) | |
316 | crc5_calc = calc_crc5(packet[16:27]) | |
317 | self.ss, self.es = self.bits[27][1], self.bits[31][2] | |
318 | if crc5 == crc5_calc: | |
319 | self.putpb(['CRC5', crc5]) | |
320 | self.putb([6, ['CRC5: 0x%02X' % crc5, 'CRC5', 'C']]) | |
321 | else: | |
322 | self.putpb(['CRC5 ERROR', crc5]) | |
323 | self.putb([7, ['CRC5 ERROR: 0x%02X' % crc5, 'CRC5 ERR', 'CE', 'C']]) | |
324 | self.packet.append(crc5) | |
325 | elif pidname in ('DATA0', 'DATA1', 'DATA2', 'MDATA'): | |
326 | # Bits[16:packetlen-16]: Data | |
327 | data = packet[16:-16] | |
328 | # TODO: len(data) must be a multiple of 8. | |
329 | databytes = [] | |
330 | self.packet_summary += ' [' | |
331 | for i in range(0, len(data), 8): | |
332 | db = bitstr_to_num(data[i:i + 8]) | |
333 | self.ss, self.es = self.bits[16 + i][1], self.bits[23 + i][2] | |
334 | self.putpb(['DATABYTE', db]) | |
335 | self.putb([8, ['Databyte: %02X' % db, 'Data: %02X' % db, | |
336 | 'DB: %02X' % db, '%02X' % db]]) | |
337 | databytes.append(db) | |
338 | self.packet_summary += ' %02X' % db | |
339 | self.packet_summary += ' ]' | |
340 | ||
341 | # Convenience Python output (no annotation) for all bytes together. | |
342 | self.ss, self.es = self.bits[16][1], self.bits[-16][2] | |
343 | self.putpb(['DATABYTES', databytes]) | |
344 | self.packet.append(databytes) | |
345 | ||
346 | # Bits[packetlen-16:packetlen]: CRC16 | |
347 | crc16 = bitstr_to_num(packet[-16:]) | |
348 | crc16_calc = calc_crc16(packet[16:-16]) | |
349 | self.ss, self.es = self.bits[-16][1], self.bits[-1][2] | |
350 | if crc16 == crc16_calc: | |
351 | self.putpb(['CRC16', crc16]) | |
352 | self.putb([9, ['CRC16: 0x%04X' % crc16, 'CRC16', 'C']]) | |
353 | else: | |
354 | self.putpb(['CRC16 ERROR', crc16]) | |
355 | self.putb([10, ['CRC16 ERROR: 0x%04X' % crc16, 'CRC16 ERR', 'CE', 'C']]) | |
356 | self.packet.append(crc16) | |
357 | elif pidname in ('ACK', 'NAK', 'STALL', 'NYET', 'ERR'): | |
358 | pass # Nothing to do, these only have SYNC+PID+EOP fields. | |
359 | elif pidname in ('PRE'): | |
360 | pass # Nothing to do, PRE only has SYNC+PID fields. | |
361 | else: | |
362 | pass # TODO: Handle 'SPLIT' and possibly 'Reserved' packets. | |
363 | ||
364 | # Output a (summary of) the whole packet. | |
365 | pcategory, pname, pinfo = get_category(pidname), pidname, self.packet | |
366 | self.putpp(['PACKET', [pcategory, pname, pinfo]]) | |
367 | self.putp([ann_index(pidname), ['%s' % self.packet_summary]]) | |
368 | ||
369 | self.packet, self.packet_summary = [], '' | |
370 | ||
371 | def decode(self, ss, es, data): | |
372 | (ptype, pdata) = data | |
373 | ||
374 | # We only care about certain packet types for now. | |
375 | if ptype not in ('SOP', 'BIT', 'EOP', 'ERR'): | |
376 | return | |
377 | ||
378 | # State machine. | |
379 | if self.state == 'WAIT FOR SOP': | |
380 | if ptype != 'SOP': | |
381 | return | |
382 | self.ss_packet = ss | |
383 | self.state = 'GET BIT' | |
384 | elif self.state == 'GET BIT': | |
385 | if ptype == 'BIT': | |
386 | self.bits.append([pdata, ss, es]) | |
387 | elif ptype == 'EOP' or ptype == 'ERR': | |
388 | self.es_packet = es | |
389 | self.handle_packet() | |
390 | self.packet, self.packet_summary = [], '' | |
391 | self.bits, self.state = [], 'WAIT FOR SOP' | |
392 | else: | |
393 | pass # TODO: Error |