]>
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, write to the Free Software | |
19 | ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | ## | |
21 | ||
22 | import sigrokdecode as srd | |
23 | ||
24 | ''' | |
25 | OUTPUT_PYTHON format: | |
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>] | |
58 | - 'HANDSHAKE', 'NAK', [<sync>, <pid>, <eop>] | |
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 | ||
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 | ||
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' | |
125 | elif pidname in ('ACK', 'NAK', 'STALL', 'NYET'): | |
126 | return 'HANDSHAKE' | |
127 | else: | |
128 | return 'SPECIAL' | |
129 | ||
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 | ||
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 | ||
145 | class Decoder(srd.Decoder): | |
146 | api_version = 2 | |
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.' | |
151 | license = 'gplv2+' | |
152 | inputs = ['usb_signalling'] | |
153 | outputs = ['usb_packet'] | |
154 | options = ( | |
155 | {'id': 'signalling', 'desc': 'Signalling', | |
156 | 'default': 'full-speed', 'values': ('full-speed', 'low-speed')}, | |
157 | ) | |
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 | ) | |
189 | annotation_rows = ( | |
190 | ('fields', 'Packet fields', tuple(range(10 + 1))), | |
191 | ('packet', 'Packets', tuple(range(11, 28 + 1))), | |
192 | ) | |
193 | ||
194 | def __init__(self): | |
195 | self.samplenum = 0 | |
196 | self.bits = [] | |
197 | self.packet = [] | |
198 | self.packet_summary = '' | |
199 | self.ss = self.es = None | |
200 | self.ss_packet = self.es_packet = None | |
201 | self.state = 'WAIT FOR SOP' | |
202 | ||
203 | def putpb(self, data): | |
204 | self.put(self.ss, self.es, self.out_python, data) | |
205 | ||
206 | def putb(self, data): | |
207 | self.put(self.ss, self.es, self.out_ann, data) | |
208 | ||
209 | def putpp(self, data): | |
210 | self.put(self.ss_packet, self.es_packet, self.out_python, data) | |
211 | ||
212 | def putp(self, data): | |
213 | self.put(self.ss_packet, self.es_packet, self.out_ann, data) | |
214 | ||
215 | def start(self): | |
216 | self.out_python = self.register(srd.OUTPUT_PYTHON) | |
217 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
218 | ||
219 | def handle_packet(self): | |
220 | packet = '' | |
221 | for (bit, ss, es) in self.bits: | |
222 | packet += bit | |
223 | ||
224 | # Bits[0:7]: SYNC | |
225 | sync = packet[:7 + 1] | |
226 | self.ss, self.es = self.bits[0][1], self.bits[7][2] | |
227 | # The SYNC pattern for low-speed/full-speed is KJKJKJKK (00000001). | |
228 | if sync != '00000001': | |
229 | self.putpb(['SYNC ERROR', sync]) | |
230 | self.putb([1, ['SYNC ERROR: %s' % sync, 'SYNC ERR: %s' % sync, | |
231 | 'SYNC ERR', 'SE', 'S']]) | |
232 | else: | |
233 | self.putpb(['SYNC', sync]) | |
234 | self.putb([0, ['SYNC: %s' % sync, 'SYNC', 'S']]) | |
235 | self.packet.append(sync) | |
236 | ||
237 | # Bits[8:15]: PID | |
238 | pid = packet[8:15 + 1] | |
239 | pidname = pids.get(pid, (pid, ''))[0] | |
240 | self.ss, self.es = self.bits[8][1], self.bits[15][2] | |
241 | self.putpb(['PID', pidname]) | |
242 | self.putb([2, ['PID: %s' % pidname, pidname, pidname[0]]]) | |
243 | self.packet.append(pid) | |
244 | self.packet_summary += pidname | |
245 | ||
246 | if pidname in ('OUT', 'IN', 'SOF', 'SETUP', 'PRE', 'PING'): | |
247 | if pidname == 'SOF': | |
248 | # Bits[16:26]: Framenum | |
249 | framenum = bitstr_to_num(packet[16:26 + 1]) | |
250 | self.ss, self.es = self.bits[16][1], self.bits[26][2] | |
251 | self.putpb(['FRAMENUM', framenum]) | |
252 | self.putb([3, ['Frame: %d' % framenum, 'Frame', 'Fr', 'F']]) | |
253 | self.packet.append(framenum) | |
254 | self.packet_summary += ' %d' % framenum | |
255 | else: | |
256 | # Bits[16:22]: Addr | |
257 | addr = bitstr_to_num(packet[16:22 + 1]) | |
258 | self.ss, self.es = self.bits[16][1], self.bits[22][2] | |
259 | self.putpb(['ADDR', addr]) | |
260 | self.putb([4, ['Address: %d' % addr, 'Addr: %d' % addr, | |
261 | 'Addr', 'A']]) | |
262 | self.packet.append(addr) | |
263 | self.packet_summary += ' ADDR %d' % addr | |
264 | ||
265 | # Bits[23:26]: EP | |
266 | ep = bitstr_to_num(packet[23:26 + 1]) | |
267 | self.ss, self.es = self.bits[23][1], self.bits[26][2] | |
268 | self.putpb(['EP', ep]) | |
269 | self.putb([5, ['Endpoint: %d' % ep, 'EP: %d' % ep, 'EP', 'E']]) | |
270 | self.packet.append(ep) | |
271 | self.packet_summary += ' EP %d' % ep | |
272 | ||
273 | # Bits[27:31]: CRC5 | |
274 | crc5 = bitstr_to_num(packet[27:31 + 1]) | |
275 | self.ss, self.es = self.bits[27][1], self.bits[31][2] | |
276 | self.putpb(['CRC5', crc5]) | |
277 | self.putb([6, ['CRC5: 0x%02X' % crc5, 'CRC5', 'C']]) | |
278 | self.packet.append(crc5) | |
279 | elif pidname in ('DATA0', 'DATA1', 'DATA2', 'MDATA'): | |
280 | # Bits[16:packetlen-16]: Data | |
281 | data = packet[16:-16] | |
282 | # TODO: len(data) must be a multiple of 8. | |
283 | databytes = [] | |
284 | self.packet_summary += ' [' | |
285 | for i in range(0, len(data), 8): | |
286 | db = bitstr_to_num(data[i:i + 8]) | |
287 | self.ss, self.es = self.bits[16 + i][1], self.bits[23 + i][2] | |
288 | self.putpb(['DATABYTE', db]) | |
289 | self.putb([8, ['Databyte: %02X' % db, 'Data: %02X' % db, | |
290 | 'DB: %02X' % db, '%02X' % db]]) | |
291 | databytes.append(db) | |
292 | self.packet_summary += ' %02X' % db | |
293 | data = data[8:] | |
294 | self.packet_summary += ' ]' | |
295 | ||
296 | # Convenience Python output (no annotation) for all bytes together. | |
297 | self.ss, self.es = self.bits[16][1], self.bits[-16][2] | |
298 | self.putpb(['DATABYTES', databytes]) | |
299 | self.packet.append(databytes) | |
300 | ||
301 | # Bits[packetlen-16:packetlen]: CRC16 | |
302 | crc16 = bitstr_to_num(packet[-16:]) | |
303 | self.ss, self.es = self.bits[-16][1], self.bits[-1][2] | |
304 | self.putpb(['CRC16', crc16]) | |
305 | self.putb([9, ['CRC16: 0x%04X' % crc16, 'CRC16', 'C']]) | |
306 | self.packet.append(crc16) | |
307 | elif pidname in ('ACK', 'NAK', 'STALL', 'NYET', 'ERR'): | |
308 | pass # Nothing to do, these only have SYNC+PID+EOP fields. | |
309 | else: | |
310 | pass # TODO: Handle 'SPLIT' and possibly 'Reserved' packets. | |
311 | ||
312 | # Output a (summary of) the whole packet. | |
313 | pcategory, pname, pinfo = get_category(pidname), pidname, self.packet | |
314 | self.putpp(['PACKET', [pcategory, pname, pinfo]]) | |
315 | self.putp([ann_index(pidname), ['%s' % self.packet_summary]]) | |
316 | ||
317 | self.packet, self.packet_summary = [], '' | |
318 | ||
319 | def decode(self, ss, es, data): | |
320 | (ptype, pdata) = data | |
321 | ||
322 | # We only care about certain packet types for now. | |
323 | if ptype not in ('SOP', 'BIT', 'EOP'): | |
324 | return | |
325 | ||
326 | # State machine. | |
327 | if self.state == 'WAIT FOR SOP': | |
328 | if ptype != 'SOP': | |
329 | return | |
330 | self.ss_packet = ss | |
331 | self.state = 'GET BIT' | |
332 | elif self.state == 'GET BIT': | |
333 | if ptype == 'BIT': | |
334 | self.bits.append([pdata, ss, es]) | |
335 | elif ptype == 'EOP': | |
336 | self.es_packet = es | |
337 | self.handle_packet() | |
338 | self.bits, self.state = [], 'WAIT FOR SOP' | |
339 | else: | |
340 | pass # TODO: Error | |
341 | else: | |
342 | raise Exception('Invalid state: %s' % self.state) | |
343 |