]>
Commit | Line | Data |
---|---|---|
1 | ## | |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2015 Google, Inc | |
5 | ## Copyright (C) 2018 davidanger <davidanger@163.com> | |
6 | ## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl> | |
7 | ## | |
8 | ## This program is free software; you can redistribute it and/or modify | |
9 | ## it under the terms of the GNU General Public License as published by | |
10 | ## the Free Software Foundation; either version 2 of the License, or | |
11 | ## (at your option) any later version. | |
12 | ## | |
13 | ## This program is distributed in the hope that it will be useful, | |
14 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | ## GNU General Public License for more details. | |
17 | ## | |
18 | ## You should have received a copy of the GNU General Public License | |
19 | ## along with this program; if not, see <http://www.gnu.org/licenses/>. | |
20 | ## | |
21 | ||
22 | import sigrokdecode as srd | |
23 | import struct | |
24 | import zlib # for crc32 | |
25 | ||
26 | # BMC encoding with a 600kHz datarate | |
27 | UI_US = 1000000/600000.0 | |
28 | ||
29 | # Threshold to discriminate half-1 from 0 in Binary Mark Conding | |
30 | THRESHOLD_US = (UI_US + 2 * UI_US) / 2 | |
31 | ||
32 | # Control Message type | |
33 | CTRL_TYPES = { | |
34 | 0: 'reserved', | |
35 | 1: 'GOOD CRC', | |
36 | 2: 'GOTO MIN', | |
37 | 3: 'ACCEPT', | |
38 | 4: 'REJECT', | |
39 | 5: 'PING', | |
40 | 6: 'PS RDY', | |
41 | 7: 'GET SOURCE CAP', | |
42 | 8: 'GET SINK CAP', | |
43 | 9: 'DR SWAP', | |
44 | 10: 'PR SWAP', | |
45 | 11: 'VCONN SWAP', | |
46 | 12: 'WAIT', | |
47 | 13: 'SOFT RESET', | |
48 | 14: 'reserved', | |
49 | 15: 'reserved', | |
50 | 16: 'Not Supported', | |
51 | 17: 'Get_Source_Cap_Extended', | |
52 | 18: 'Get_Status', | |
53 | 19: 'FR_Swap', | |
54 | 20: 'Get_PPS_Status', | |
55 | 21: 'Get_Country_Codes', | |
56 | } | |
57 | ||
58 | # Data message type | |
59 | DATA_TYPES = { | |
60 | 1: 'SOURCE CAP', | |
61 | 2: 'REQUEST', | |
62 | 3: 'BIST', | |
63 | 4: 'SINK CAP', | |
64 | 5: 'Battery_Status', | |
65 | 6: 'Alert', | |
66 | 7: 'Get_Country_Info', | |
67 | 15: 'VDM' | |
68 | } | |
69 | ||
70 | # 4b5b encoding of the symbols | |
71 | DEC4B5B = [ | |
72 | 0x10, # Error 00000 | |
73 | 0x10, # Error 00001 | |
74 | 0x10, # Error 00010 | |
75 | 0x10, # Error 00011 | |
76 | 0x10, # Error 00100 | |
77 | 0x10, # Error 00101 | |
78 | 0x13, # Sync-3 00110 | |
79 | 0x14, # RST-1 00111 | |
80 | 0x10, # Error 01000 | |
81 | 0x01, # 1 = 0001 01001 | |
82 | 0x04, # 4 = 0100 01010 | |
83 | 0x05, # 5 = 0101 01011 | |
84 | 0x10, # Error 01100 | |
85 | 0x16, # EOP 01101 | |
86 | 0x06, # 6 = 0110 01110 | |
87 | 0x07, # 7 = 0111 01111 | |
88 | 0x10, # Error 10000 | |
89 | 0x12, # Sync-2 10001 | |
90 | 0x08, # 8 = 1000 10010 | |
91 | 0x09, # 9 = 1001 10011 | |
92 | 0x02, # 2 = 0010 10100 | |
93 | 0x03, # 3 = 0011 10101 | |
94 | 0x0A, # A = 1010 10110 | |
95 | 0x0B, # B = 1011 10111 | |
96 | 0x11, # Sync-1 11000 | |
97 | 0x15, # RST-2 11001 | |
98 | 0x0C, # C = 1100 11010 | |
99 | 0x0D, # D = 1101 11011 | |
100 | 0x0E, # E = 1110 11100 | |
101 | 0x0F, # F = 1111 11101 | |
102 | 0x00, # 0 = 0000 11110 | |
103 | 0x10, # Error 11111 | |
104 | ] | |
105 | SYM_ERR = 0x10 | |
106 | SYNC1 = 0x11 | |
107 | SYNC2 = 0x12 | |
108 | SYNC3 = 0x13 | |
109 | RST1 = 0x14 | |
110 | RST2 = 0x15 | |
111 | EOP = 0x16 | |
112 | SYNC_CODES = [SYNC1, SYNC2, SYNC3] | |
113 | HRST_CODES = [RST1, RST1, RST1, RST2] | |
114 | ||
115 | SOP_SEQUENCES = [ | |
116 | (SYNC1, SYNC1, SYNC1, SYNC2), | |
117 | (SYNC1, SYNC1, SYNC3, SYNC3), | |
118 | (SYNC1, SYNC3, SYNC1, SYNC3), | |
119 | (SYNC1, RST2, RST2, SYNC3), | |
120 | (SYNC1, RST2, SYNC3, SYNC2), | |
121 | (RST1, SYNC1, RST1, SYNC3), | |
122 | (RST1, RST1, RST1, RST2), | |
123 | ] | |
124 | START_OF_PACKETS = { | |
125 | SOP_SEQUENCES[0]: 'SOP', | |
126 | SOP_SEQUENCES[1]: "SOP'", | |
127 | SOP_SEQUENCES[2]: 'SOP"', | |
128 | SOP_SEQUENCES[3]: "SOP' Debug", | |
129 | SOP_SEQUENCES[4]: 'SOP" Debug', | |
130 | SOP_SEQUENCES[5]: 'Cable Reset', | |
131 | SOP_SEQUENCES[6]: 'Hard Reset', | |
132 | } | |
133 | ||
134 | SYM_NAME = [ | |
135 | ['0x0', '0'], | |
136 | ['0x1', '1'], | |
137 | ['0x2', '2'], | |
138 | ['0x3', '3'], | |
139 | ['0x4', '4'], | |
140 | ['0x5', '5'], | |
141 | ['0x6', '6'], | |
142 | ['0x7', '7'], | |
143 | ['0x8', '8'], | |
144 | ['0x9', '9'], | |
145 | ['0xA', 'A'], | |
146 | ['0xB', 'B'], | |
147 | ['0xC', 'C'], | |
148 | ['0xD', 'D'], | |
149 | ['0xE', 'E'], | |
150 | ['0xF', 'F'], | |
151 | ['ERROR', 'X'], | |
152 | ['SYNC-1', 'S1'], | |
153 | ['SYNC-2', 'S2'], | |
154 | ['SYNC-3', 'S3'], | |
155 | ['RST-1', 'R1'], | |
156 | ['RST-2', 'R2'], | |
157 | ['EOP', '#'], | |
158 | ] | |
159 | ||
160 | RDO_FLAGS = { | |
161 | (1 << 23): 'unchunked', | |
162 | (1 << 24): 'no_suspend', | |
163 | (1 << 25): 'comm_cap', | |
164 | (1 << 26): 'cap_mismatch', | |
165 | (1 << 27): 'give_back' | |
166 | } | |
167 | ||
168 | BIST_MODES = { | |
169 | 0: 'Receiver', | |
170 | 1: 'Transmit', | |
171 | 2: 'Counters', | |
172 | 3: 'Carrier 0', | |
173 | 4: 'Carrier 1', | |
174 | 5: 'Carrier 2', | |
175 | 6: 'Carrier 3', | |
176 | 7: 'Eye', | |
177 | } | |
178 | ||
179 | VDM_CMDS = { | |
180 | 1: 'Disc Ident', | |
181 | 2: 'Disc SVID', | |
182 | 3: 'Disc Mode', | |
183 | 4: 'Enter Mode', | |
184 | 5: 'Exit Mode', | |
185 | 6: 'Attention', | |
186 | # 16..31: SVID Specific Commands | |
187 | # DisplayPort Commands | |
188 | 16: 'DP Status', | |
189 | 17: 'DP Configure', | |
190 | } | |
191 | VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY'] | |
192 | ||
193 | ||
194 | class SamplerateError(Exception): | |
195 | pass | |
196 | ||
197 | class Decoder(srd.Decoder): | |
198 | api_version = 3 | |
199 | id = 'usb_power_delivery' | |
200 | name = 'USB PD' | |
201 | longname = 'USB Power Delivery' | |
202 | desc = 'USB Power Delivery protocol.' | |
203 | license = 'gplv2+' | |
204 | inputs = ['logic'] | |
205 | outputs = ['usb_pd'] | |
206 | channels = ( | |
207 | {'id': 'cc1', 'name': 'CC1', 'desc': 'Configuration Channel 1'}, | |
208 | ) | |
209 | optional_channels = ( | |
210 | {'id': 'cc2', 'name': 'CC2', 'desc': 'Configuration Channel 2'}, | |
211 | ) | |
212 | options = ( | |
213 | {'id': 'fulltext', 'desc': 'full text decoding of the packet', | |
214 | 'default': 'no', 'values': ('yes', 'no')}, | |
215 | ) | |
216 | annotations = ( | |
217 | ('type', 'Packet Type'), | |
218 | ('Preamble', 'Preamble'), | |
219 | ('SOP', 'Start of Packet'), | |
220 | ('Head', 'Header'), | |
221 | ('Data', 'Data'), | |
222 | ('CRC', 'Checksum'), | |
223 | ('EOP', 'End Of Packet'), | |
224 | ('Sym', '4b5b symbols'), | |
225 | ('warnings', 'Warnings'), | |
226 | ('src', 'Source Message'), | |
227 | ('snk', 'Sink Message'), | |
228 | ('payload', 'Payload'), | |
229 | ('text', 'Plain text'), | |
230 | ) | |
231 | annotation_rows = ( | |
232 | ('4B5B', 'symbols', (7, )), | |
233 | ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )), | |
234 | ('payload', 'Payload', (11, )), | |
235 | ('type', 'Type', (0, 9, 10, )), | |
236 | ('warnings', 'Warnings', (8, )), | |
237 | ('text', 'Full text', (12, )), | |
238 | ) | |
239 | binary = ( | |
240 | ('raw-data', 'RAW binary data'), | |
241 | ) | |
242 | ||
243 | stored_pdos = {} | |
244 | ||
245 | ||
246 | def get_request(self, rdo): | |
247 | pos = (rdo >> 28) & 7 | |
248 | ||
249 | op_ma = ((rdo >> 10) & 0x3ff) * 0.01 | |
250 | max_ma = (rdo & 0x3ff) * 0.01 | |
251 | ||
252 | mark = self.cap_mark[pos] | |
253 | if mark == 3: | |
254 | op_v = ((rdo >> 9) & 0x7ff) * 0.02 | |
255 | op_a = (rdo & 0x3f) * 0.05 | |
256 | t_settings = '%gV %gA' % (op_v, op_a) | |
257 | elif mark == 2: | |
258 | op_w = ((rdo >> 10) & 0x3ff) * 0.25 | |
259 | mp_w = (rdo & 0x3ff) * 0.25 | |
260 | t_settings = '%gW (operating)' % op_w | |
261 | else: | |
262 | op_a = ((rdo >> 10) & 0x3ff) * 0.01 | |
263 | max_a = (rdo & 0x3ff) * 0.01 | |
264 | t_settings = '%gA (operating) / %gA (max)' % (op_a, max_a) | |
265 | ||
266 | t_flags = '' | |
267 | for f in sorted(RDO_FLAGS.keys(), reverse = True): | |
268 | if rdo & f: | |
269 | t_flags += ' [' + RDO_FLAGS[f] + ']' | |
270 | ||
271 | if pos in self.stored_pdos.keys(): | |
272 | t_pdo = '#%d: %s' % (pos, self.stored_pdos[pos]) | |
273 | else: | |
274 | t_pdo = '#d' % (pos) | |
275 | ||
276 | return '(PDO %s) %s%s' % (t_pdo, t_settings, t_flags) | |
277 | ||
278 | def get_source_sink_cap(self, pdo, idx, source): | |
279 | t1 = (pdo >> 30) & 3 | |
280 | self.cap_mark[idx] = t1 | |
281 | ||
282 | flags = {} | |
283 | if t1 == 0: | |
284 | t_name = 'Fixed' | |
285 | if source: | |
286 | flags = { | |
287 | (1 << 29): 'dual_role_power', | |
288 | (1 << 28): 'suspend', | |
289 | (1 << 27): 'unconstrained', | |
290 | (1 << 26): 'comm_cap', | |
291 | (1 << 25): 'dual_role_data', | |
292 | (1 << 24): 'unchunked', | |
293 | } | |
294 | else: #sink | |
295 | flags = { | |
296 | (1 << 29): 'dual_role_power', | |
297 | (1 << 28): 'high_capability', | |
298 | (1 << 27): 'unconstrained', | |
299 | (1 << 26): 'comm_cap', | |
300 | (1 << 25): 'dual_role_data', | |
301 | (0b01 << 23): 'fr_swap default power', | |
302 | (0b10 << 23): 'fr_swap 1.5 A', | |
303 | (0b11 << 23): 'fr_swap 3.0 A', | |
304 | } | |
305 | mv = ((pdo >> 10) & 0x3ff) * 0.05 | |
306 | ma = ((pdo >> 0) & 0x3ff) * 0.01 | |
307 | p = '%gV %gA (%gW)' % (mv, ma, mv*ma) | |
308 | self.stored_pdos[idx] = '%s %gV' % (t_name, mv) | |
309 | elif t1 == 1: | |
310 | t_name = 'Battery' | |
311 | flags = {} # No flags defined for Battery PDO in PD 3.0 spec | |
312 | minv = ((pdo >> 10) & 0x3ff) * 0.05 | |
313 | maxv = ((pdo >> 20) & 0x3ff) * 0.05 | |
314 | mw = ((pdo >> 0) & 0x3ff) * 0.25 | |
315 | p = '%g/%gV %gW' % (minv, maxv, mw) | |
316 | self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) | |
317 | elif t1 == 2: | |
318 | t_name = 'Variable' | |
319 | flags = {} # No flags defined for Variable PDO in PD 3.0 spec | |
320 | minv = ((pdo >> 10) & 0x3ff) * 0.05 | |
321 | maxv = ((pdo >> 20) & 0x3ff) * 0.05 | |
322 | ma = ((pdo >> 0) & 0x3ff) * 0.01 | |
323 | p = '%g/%gV %gA' % (minv, maxv, ma) | |
324 | self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) | |
325 | elif t1 == 3: | |
326 | t2 = (pdo >> 28) & 3 | |
327 | if t2 == 0: | |
328 | t_name = 'Programmable|PPS' | |
329 | flags = { | |
330 | (1 << 29): 'power_limited', | |
331 | } | |
332 | minv = ((pdo >> 8) & 0xff) * 0.1 | |
333 | maxv = ((pdo >> 17) & 0xff) * 0.1 | |
334 | ma = ((pdo >> 0) & 0xff) * 0.05 | |
335 | p = '%g/%gV %gA' % (minv, maxv, ma) | |
336 | if (pdo >> 27) & 0x1: | |
337 | p += ' [limited]' | |
338 | self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) | |
339 | else: | |
340 | t_name = 'Reserved APDO: '+bin(t2) | |
341 | p = '[raw: %s]' % (bin(pdo)) | |
342 | self.stored_pdos[idx] = '%s %s' % (t_name, p) | |
343 | t_flags = '' | |
344 | for f in sorted(flags.keys(), reverse = True): | |
345 | if pdo & f: | |
346 | t_flags += ' [' + flags[f] + ']' | |
347 | return '[%s] %s%s' % (t_name, p, t_flags) | |
348 | ||
349 | def get_vdm(self, idx, data): | |
350 | if idx == 0: # VDM header | |
351 | vid = data >> 16 | |
352 | struct = data & (1 << 15) | |
353 | txt = 'VDM' | |
354 | if struct: # Structured VDM | |
355 | cmd = data & 0x1f | |
356 | src = data & (1 << 5) | |
357 | ack = (data >> 6) & 3 | |
358 | pos = (data >> 8) & 7 | |
359 | ver = (data >> 13) & 3 | |
360 | txt = VDM_ACK[ack] + ' ' | |
361 | txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?' | |
362 | txt += ' pos %d' % (pos) if pos else ' ' | |
363 | else: # Unstructured VDM | |
364 | txt = 'unstruct [%04x]' % (data & 0x7fff) | |
365 | txt += ' SVID:%04x' % (vid) | |
366 | else: # VDM payload | |
367 | txt = 'VDO:%08x' % (data) | |
368 | return txt | |
369 | ||
370 | def get_bist(self, idx, data): | |
371 | mode = data >> 28 | |
372 | counter = data & 0xffff | |
373 | mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID' | |
374 | if mode == 2: | |
375 | mode_name = 'Counter[= %d]' % (counter) | |
376 | # TODO check all 0 bits are 0 / emit warnings | |
377 | return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO' | |
378 | ||
379 | def putpayload(self, s0, s1, idx): | |
380 | t = self.head_type() | |
381 | txt = '['+str(idx+1)+'] ' | |
382 | if t == 2: | |
383 | txt += self.get_request(self.data[idx]) | |
384 | elif t == 1 or t == 4: | |
385 | txt += self.get_source_sink_cap(self.data[idx], idx+1, t==1) | |
386 | elif t == 15: | |
387 | txt += self.get_vdm(idx, self.data[idx]) | |
388 | elif t == 3: | |
389 | txt += self.get_bist(idx, self.data[idx]) | |
390 | self.putx(s0, s1, [11, [txt, txt]]) | |
391 | self.text += ' - ' + txt | |
392 | ||
393 | def puthead(self): | |
394 | ann_type = 9 if self.head_power_role() else 10 | |
395 | role = 'SRC' if self.head_power_role() else 'SNK' | |
396 | if self.head_data_role() != self.head_power_role(): | |
397 | role += '/DFP' if self.head_data_role() else '/UFP' | |
398 | t = self.head_type() | |
399 | if self.head_count() == 0: | |
400 | shortm = CTRL_TYPES[t] | |
401 | else: | |
402 | shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???' | |
403 | ||
404 | longm = '(r{:d}) {:s}[{:d}]: {:s}'.format(self.head_rev(), role, self.head_id(), shortm) | |
405 | self.putx(0, -1, [ann_type, [longm, shortm]]) | |
406 | self.text += longm | |
407 | ||
408 | def head_id(self): | |
409 | return (self.head >> 9) & 7 | |
410 | ||
411 | def head_power_role(self): | |
412 | return (self.head >> 8) & 1 | |
413 | ||
414 | def head_data_role(self): | |
415 | return (self.head >> 5) & 1 | |
416 | ||
417 | def head_rev(self): | |
418 | return ((self.head >> 6) & 3) + 1 | |
419 | ||
420 | def head_type(self): | |
421 | return self.head & 0xF | |
422 | ||
423 | def head_count(self): | |
424 | return (self.head >> 12) & 7 | |
425 | ||
426 | def putx(self, s0, s1, data): | |
427 | self.put(self.edges[s0], self.edges[s1], self.out_ann, data) | |
428 | ||
429 | def putwarn(self, longm, shortm): | |
430 | self.putx(0, -1, [8, [longm, shortm]]) | |
431 | ||
432 | def compute_crc32(self): | |
433 | bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff, | |
434 | *tuple([d & 0xffffffff for d in self.data])) | |
435 | return zlib.crc32(bdata) | |
436 | ||
437 | def rec_sym(self, i, sym): | |
438 | self.putx(i, i+5, [7, SYM_NAME[sym]]) | |
439 | ||
440 | def get_sym(self, i, rec=True): | |
441 | v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) | | |
442 | (self.bits[i+3] << 3) | (self.bits[i+4] << 4)) | |
443 | sym = DEC4B5B[v] | |
444 | if rec: | |
445 | self.rec_sym(i, sym) | |
446 | return sym | |
447 | ||
448 | def get_short(self): | |
449 | i = self.idx | |
450 | # Check it's not a truncated packet | |
451 | if len(self.bits) - i <= 20: | |
452 | self.putwarn('Truncated', '!') | |
453 | return 0x0BAD | |
454 | k = [self.get_sym(i), self.get_sym(i+5), | |
455 | self.get_sym(i+10), self.get_sym(i+15)] | |
456 | # TODO check bad symbols | |
457 | val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12) | |
458 | self.idx += 20 | |
459 | return val | |
460 | ||
461 | def get_word(self): | |
462 | lo = self.get_short() | |
463 | hi = self.get_short() | |
464 | return lo | (hi << 16) | |
465 | ||
466 | def find_corrupted_sop(self, k): | |
467 | # Start of packet are valid even if they have only 3 correct symbols | |
468 | # out of 4. | |
469 | for seq in SOP_SEQUENCES: | |
470 | if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3: | |
471 | return START_OF_PACKETS[seq] | |
472 | return None | |
473 | ||
474 | def scan_eop(self): | |
475 | for i in range(len(self.bits) - 19): | |
476 | k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False), | |
477 | self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False)) | |
478 | sym = START_OF_PACKETS.get(k, None) | |
479 | if not sym: | |
480 | sym = self.find_corrupted_sop(k) | |
481 | # We have an interesting symbol sequence | |
482 | if sym: | |
483 | # annotate the preamble | |
484 | self.putx(0, i, [1, ['Preamble', '...']]) | |
485 | # annotate each symbol | |
486 | self.rec_sym(i, k[0]) | |
487 | self.rec_sym(i+5, k[1]) | |
488 | self.rec_sym(i+10, k[2]) | |
489 | self.rec_sym(i+15, k[3]) | |
490 | if sym == 'Hard Reset': | |
491 | self.text += 'HRST' | |
492 | return -1 # Hard reset | |
493 | elif sym == 'Cable Reset': | |
494 | self.text += 'CRST' | |
495 | return -1 # Cable reset | |
496 | else: | |
497 | self.putx(i, i+20, [2, [sym, 'S']]) | |
498 | return i+20 | |
499 | self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']]) | |
500 | self.text += 'Junk???' | |
501 | self.putwarn('No start of packet found', 'XXX') | |
502 | return -1 # No Start Of Packet | |
503 | ||
504 | def __init__(self): | |
505 | self.reset() | |
506 | ||
507 | def reset(self): | |
508 | self.samplerate = None | |
509 | self.idx = 0 | |
510 | self.packet_seq = 0 | |
511 | self.previous = 0 | |
512 | self.startsample = None | |
513 | self.bits = [] | |
514 | self.edges = [] | |
515 | self.bad = [] | |
516 | self.half_one = False | |
517 | self.start_one = 0 | |
518 | self.stored_pdos = {} | |
519 | self.cap_mark = [0, 0, 0, 0, 0, 0, 0, 0] | |
520 | ||
521 | def metadata(self, key, value): | |
522 | if key == srd.SRD_CONF_SAMPLERATE: | |
523 | self.samplerate = value | |
524 | # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong | |
525 | self.maxbit = self.us2samples(3 * UI_US) | |
526 | # duration threshold between half 1 and 0 | |
527 | self.threshold = self.us2samples(THRESHOLD_US) | |
528 | ||
529 | def start(self): | |
530 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
531 | self.out_binary = self.register(srd.OUTPUT_BINARY) | |
532 | self.out_bitrate = self.register( | |
533 | srd.OUTPUT_META, | |
534 | meta=(int, 'Bitrate', 'Bitrate during the packet') | |
535 | ) | |
536 | ||
537 | def us2samples(self, us): | |
538 | return int(us * self.samplerate / 1000000) | |
539 | ||
540 | def decode_packet(self): | |
541 | self.data = [] | |
542 | self.idx = 0 | |
543 | self.text = '' | |
544 | ||
545 | if len(self.edges) < 50: | |
546 | return # Not a real PD packet | |
547 | ||
548 | self.packet_seq += 1 | |
549 | tstamp = float(self.startsample) / self.samplerate | |
550 | self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000) | |
551 | ||
552 | self.idx = self.scan_eop() | |
553 | if self.idx < 0: | |
554 | # Full text trace of the issue | |
555 | self.putx(0, self.idx, [12, [self.text, '...']]) | |
556 | return # No real packet: ABORT | |
557 | ||
558 | # Packet header | |
559 | self.head = self.get_short() | |
560 | self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']]) | |
561 | self.puthead() | |
562 | ||
563 | # Decode data payload | |
564 | for i in range(self.head_count()): | |
565 | self.data.append(self.get_word()) | |
566 | self.putx(self.idx-40, self.idx, | |
567 | [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]]) | |
568 | self.putpayload(self.idx-40, self.idx, i) | |
569 | ||
570 | # CRC check | |
571 | self.crc = self.get_word() | |
572 | ccrc = self.compute_crc32() | |
573 | if self.crc != ccrc: | |
574 | self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!') | |
575 | self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']]) | |
576 | ||
577 | # End of Packet | |
578 | if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP: | |
579 | self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']]) | |
580 | self.idx += 5 | |
581 | else: | |
582 | self.putwarn('No EOP', 'EOP!') | |
583 | # Full text trace | |
584 | if self.options['fulltext'] == 'yes': | |
585 | self.putx(0, self.idx, [12, [self.text, '...']]) | |
586 | ||
587 | # Meta data for bitrate | |
588 | ss, es = self.edges[0], self.edges[-1] | |
589 | bitrate = self.samplerate*len(self.bits) / float(es - ss) | |
590 | self.put(es, ss, self.out_bitrate, int(bitrate)) | |
591 | # Raw binary data (BMC decoded) | |
592 | self.put(es, ss, self.out_binary, [0, bytes(self.bits)]) | |
593 | ||
594 | def decode(self): | |
595 | if not self.samplerate: | |
596 | raise SamplerateError('Cannot decode without samplerate.') | |
597 | while True: | |
598 | pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': int(self.samplerate/1e3)}]) | |
599 | ||
600 | # First sample of the packet, just record the start date | |
601 | if not self.startsample: | |
602 | self.startsample = self.samplenum | |
603 | self.previous = self.samplenum | |
604 | continue | |
605 | ||
606 | diff = self.samplenum - self.previous | |
607 | ||
608 | # Large idle: use it as the end of packet | |
609 | if diff > self.maxbit: | |
610 | # the last edge of the packet | |
611 | self.edges.append(self.previous) | |
612 | # Export the packet | |
613 | self.decode_packet() | |
614 | # Reset for next packet | |
615 | self.startsample = self.samplenum | |
616 | self.bits = [] | |
617 | self.edges = [] | |
618 | self.bad = [] | |
619 | self.half_one = False | |
620 | self.start_one = 0 | |
621 | else: # add the bit to the packet | |
622 | is_zero = diff > self.threshold | |
623 | if is_zero and not self.half_one: | |
624 | self.bits.append(0) | |
625 | self.edges.append(self.previous) | |
626 | elif not is_zero and self.half_one: | |
627 | self.bits.append(1) | |
628 | self.edges.append(self.start_one) | |
629 | self.half_one = False | |
630 | elif not is_zero and not self.half_one: | |
631 | self.half_one = True | |
632 | self.start_one = self.previous | |
633 | else: # Invalid BMC sequence | |
634 | self.bad.append((self.start_one, self.previous)) | |
635 | # TODO try to recover | |
636 | self.bits.append(0) | |
637 | self.edges.append(self.previous) | |
638 | self.half_one = False | |
639 | self.previous = self.samplenum |