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