2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2015 Google, Inc
5 ## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl>
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.
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.
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/>.
21 import sigrokdecode as srd
23 import zlib # for crc32
25 # BMC encoding with a 600kHz datarate
26 UI_US = 1000000/600000.0
28 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
29 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
31 # Control Message type
60 # 4b5b encoding of the symbols
71 0x01, # 1 = 0001 01001
72 0x04, # 4 = 0100 01010
73 0x05, # 5 = 0101 01011
76 0x06, # 6 = 0110 01110
77 0x07, # 7 = 0111 01111
80 0x08, # 8 = 1000 10010
81 0x09, # 9 = 1001 10011
82 0x02, # 2 = 0010 10100
83 0x03, # 3 = 0011 10101
84 0x0A, # A = 1010 10110
85 0x0B, # B = 1011 10111
88 0x0C, # C = 1100 11010
89 0x0D, # D = 1101 11011
90 0x0E, # E = 1110 11100
91 0x0F, # F = 1111 11101
92 0x00, # 0 = 0000 11110
102 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
103 HRST_CODES = [RST1, RST1, RST1, RST2]
106 (SYNC1, SYNC1, SYNC1, SYNC2),
107 (SYNC1, SYNC1, SYNC3, SYNC3),
108 (SYNC1, SYNC3, SYNC1, SYNC3),
109 (SYNC1, RST2, RST2, SYNC3),
110 (SYNC1, RST2, SYNC3, SYNC2),
111 (RST1, SYNC1, RST1, SYNC3),
112 (RST1, RST1, RST1, RST2),
115 SOP_SEQUENCES[0]: 'SOP',
116 SOP_SEQUENCES[1]: "SOP'",
117 SOP_SEQUENCES[2]: 'SOP"',
118 SOP_SEQUENCES[3]: "SOP' Debug",
119 SOP_SEQUENCES[4]: 'SOP" Debug',
120 SOP_SEQUENCES[5]: 'Cable Reset',
121 SOP_SEQUENCES[6]: 'Hard Reset',
151 (1 << 24): 'no_suspend',
152 (1 << 25): 'comm_cap',
153 (1 << 26): 'cap_mismatch',
154 (1 << 27): 'give_back'
158 (1 << 29): 'dual_role_power',
159 (1 << 28): 'suspend',
161 (1 << 26): 'comm_cap',
162 (1 << 25): 'dual_role_data'
183 # 16..31: SVID Specific Commands
184 # DisplayPort Commands
188 VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
191 class SamplerateError(Exception):
194 class Decoder(srd.Decoder):
196 id = 'usb_power_delivery'
198 longname = 'USB Power Delivery'
199 desc = 'USB Power Delivery protocol.'
204 {'id': 'cc1', 'name': 'CC1', 'desc': 'Control channel 1'},
206 optional_channels = (
207 {'id': 'cc2', 'name': 'CC2', 'desc': 'Control channel 2'},
210 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
211 'default': 'no', 'values': ('yes', 'no')},
214 ('type', 'Packet Type'),
215 ('Preamble', 'Preamble'),
216 ('SOP', 'Start of Packet'),
220 ('EOP', 'End Of Packet'),
221 ('Sym', '4b5b symbols'),
222 ('warnings', 'Warnings'),
223 ('src', 'Source Message'),
224 ('snk', 'Sink Message'),
225 ('payload', 'Payload'),
226 ('text', 'Plain text'),
229 ('4B5B', 'symbols', (7, )),
230 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
231 ('payload', 'Payload', (11, )),
232 ('type', 'Type', (0, 9, 10, )),
233 ('warnings', 'Warnings', (8, )),
234 ('text', 'Full text', (12, )),
237 ('raw-data', 'RAW binary data'),
243 def get_request(self, rdo):
244 pos = (rdo >> 28) & 7
245 op_ma = ((rdo >> 10) & 0x3ff) * 0.01
246 max_ma = (rdo & 0x3ff) * 0.01
248 for f in sorted(RDO_FLAGS.keys(), reverse = True):
250 flags += ' [' + RDO_FLAGS[f] + ']'
251 if pos in self.stored_pdos.keys():
252 return '(PDO #%d: %s) %gA (operating) / %gA (max)%s' % (pos, self.stored_pdos[pos], op_ma, max_ma, flags)
254 return '(PDO #%d) %gA (operating) / %gA (max)%s' % (pos, op_ma, max_ma, flags)
256 def get_source_sink_cap(self, pdo, idx):
260 mv = ((pdo >> 10) & 0x3ff) * 0.05
261 ma = ((pdo >> 0) & 0x3ff) * 0.01
262 p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
263 self.stored_pdos[idx] = '%s %gV' % (t_name, mv)
266 minv = ((pdo >> 10) & 0x3ff) * 0.05
267 maxv = ((pdo >> 20) & 0x3ff) * 0.05
268 mw = ((pdo >> 0) & 0x3ff) * 0.25
269 p = '%g/%gV %gW' % (minv, maxv, mw)
270 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
273 minv = ((pdo >> 10) & 0x3ff) * 0.05
274 maxv = ((pdo >> 20) & 0x3ff) * 0.05
275 ma = ((pdo >> 0) & 0x3ff) * 0.01
276 p = '%g/%gV %gA' % (minv, maxv, ma)
277 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
281 t_name = 'Programmable|PPS'
282 minv = ((pdo >> 8) & 0xff) * 0.1
283 maxv = ((pdo >> 17) & 0xff) * 0.1
284 ma = ((pdo >> 0) & 0xff) * 0.05
285 p = '%g/%gV %gA' % (minv, maxv, ma)
286 if (pdo >> 27) & 0x1:
288 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
290 t_name = 'Reserved APDO: '+bin(t2)
291 p = '[raw: %s]' % (bin(pdo))
292 self.stored_pdos[idx] = '%s %s' % (t_name, p)
294 for f in sorted(PDO_FLAGS.keys(), reverse = True):
296 flags += ' [' + PDO_FLAGS[f] + ']'
297 return '[%s] %s%s' % (t_name, p, flags)
299 def get_vdm(self, idx, data):
300 if idx == 0: # VDM header
302 struct = data & (1 << 15)
304 if struct: # Structured VDM
306 src = data & (1 << 5)
307 ack = (data >> 6) & 3
308 pos = (data >> 8) & 7
309 ver = (data >> 13) & 3
310 txt = VDM_ACK[ack] + ' '
311 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
312 txt += ' pos %d' % (pos) if pos else ' '
313 else: # Unstructured VDM
314 txt = 'unstruct [%04x]' % (data & 0x7fff)
315 txt += ' SVID:%04x' % (vid)
317 txt = 'VDO:%08x' % (data)
320 def get_bist(self, idx, data):
322 counter = data & 0xffff
323 mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
325 mode_name = 'Counter[= %d]' % (counter)
326 # TODO check all 0 bits are 0 / emit warnings
327 return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
329 def putpayload(self, s0, s1, idx):
331 txt = '['+str(idx+1)+'] '
333 txt += self.get_request(self.data[idx])
334 elif t == 1 or t == 4:
335 txt += self.get_source_sink_cap(self.data[idx], idx+1)
337 txt += self.get_vdm(idx, self.data[idx])
339 txt += self.get_bist(idx, self.data[idx])
340 self.putx(s0, s1, [11, [txt, txt]])
341 self.text += ' - ' + txt
344 ann_type = 9 if self.head_power_role() else 10
345 role = 'SRC' if self.head_power_role() else 'SNK'
346 if self.head_data_role() != self.head_power_role():
347 role += '/DFP' if self.head_data_role() else '/UFP'
349 if self.head_count() == 0:
350 shortm = CTRL_TYPES[t]
352 shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
354 longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
355 self.putx(0, -1, [ann_type, [longm, shortm]])
359 return (self.head >> 9) & 7
361 def head_power_role(self):
362 return (self.head >> 8) & 1
364 def head_data_role(self):
365 return (self.head >> 5) & 1
368 return ((self.head >> 6) & 3) + 1
371 return self.head & 0xF
373 def head_count(self):
374 return (self.head >> 12) & 7
376 def putx(self, s0, s1, data):
377 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
379 def putwarn(self, longm, shortm):
380 self.putx(0, -1, [8, [longm, shortm]])
382 def compute_crc32(self):
383 bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
384 *tuple([d & 0xffffffff for d in self.data]))
385 return zlib.crc32(bdata)
387 def rec_sym(self, i, sym):
388 self.putx(i, i+5, [7, SYM_NAME[sym]])
390 def get_sym(self, i, rec=True):
391 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
392 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
400 # Check it's not a truncated packet
401 if len(self.bits) - i <= 20:
402 self.putwarn('Truncated', '!')
404 k = [self.get_sym(i), self.get_sym(i+5),
405 self.get_sym(i+10), self.get_sym(i+15)]
406 # TODO check bad symbols
407 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
412 lo = self.get_short()
413 hi = self.get_short()
414 return lo | (hi << 16)
416 def find_corrupted_sop(self, k):
417 # Start of packet are valid even if they have only 3 correct symbols
419 for seq in SOP_SEQUENCES:
420 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
421 return START_OF_PACKETS[seq]
425 for i in range(len(self.bits) - 19):
426 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
427 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
428 sym = START_OF_PACKETS.get(k, None)
430 sym = self.find_corrupted_sop(k)
431 # We have an interesting symbol sequence
433 # annotate the preamble
434 self.putx(0, i, [1, ['Preamble', '...']])
435 # annotate each symbol
436 self.rec_sym(i, k[0])
437 self.rec_sym(i+5, k[1])
438 self.rec_sym(i+10, k[2])
439 self.rec_sym(i+15, k[3])
440 if sym == 'Hard Reset':
442 return -1 # Hard reset
443 elif sym == 'Cable Reset':
445 return -1 # Cable reset
447 self.putx(i, i+20, [2, [sym, 'S']])
449 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
450 self.text += 'Junk???'
451 self.putwarn('No start of packet found', 'XXX')
452 return -1 # No Start Of Packet
458 self.samplerate = None
462 self.startsample = None
466 self.half_one = False
468 self.stored_pdos = {}
470 def metadata(self, key, value):
471 if key == srd.SRD_CONF_SAMPLERATE:
472 self.samplerate = value
473 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
474 self.maxbit = self.us2samples(3 * UI_US)
475 # duration threshold between half 1 and 0
476 self.threshold = self.us2samples(THRESHOLD_US)
479 self.out_python = self.register(srd.OUTPUT_PYTHON)
480 self.out_ann = self.register(srd.OUTPUT_ANN)
481 self.out_binary = self.register(srd.OUTPUT_BINARY)
482 self.out_bitrate = self.register(
484 meta=(int, 'Bitrate', 'Bitrate during the packet')
487 def us2samples(self, us):
488 return int(us * self.samplerate / 1000000)
490 def decode_packet(self):
495 if len(self.edges) < 50:
496 return # Not a real PD packet
499 tstamp = float(self.startsample) / self.samplerate
500 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
502 self.idx = self.scan_eop()
504 # Full text trace of the issue
505 self.putx(0, self.idx, [12, [self.text, '...']])
506 return # No real packet: ABORT
509 self.head = self.get_short()
510 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
513 # Decode data payload
514 for i in range(self.head_count()):
515 self.data.append(self.get_word())
516 self.putx(self.idx-40, self.idx,
517 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
518 self.putpayload(self.idx-40, self.idx, i)
521 self.crc = self.get_word()
522 ccrc = self.compute_crc32()
524 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
525 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
528 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
529 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
532 self.putwarn('No EOP', 'EOP!')
534 if self.options['fulltext'] == 'yes':
535 self.putx(0, self.idx, [12, [self.text, '...']])
537 # Meta data for bitrate
538 ss, es = self.edges[0], self.edges[-1]
539 bitrate = self.samplerate*len(self.bits) / float(es - ss)
540 self.put(es, ss, self.out_bitrate, int(bitrate))
541 # Raw binary data (BMC decoded)
542 self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
545 if not self.samplerate:
546 raise SamplerateError('Cannot decode without samplerate.')
548 pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': 100*1000}])
551 # First sample of the packet, just record the start date
552 if not self.startsample:
553 self.startsample = self.samplenum
554 self.previous = self.samplenum
557 diff = self.samplenum - self.previous
559 # Large idle: use it as the end of packet
560 if diff > self.maxbit:
561 # the last edge of the packet
562 self.edges.append(self.previous)
565 # Reset for next packet
566 self.startsample = self.samplenum
570 self.half_one = False
572 else: # add the bit to the packet
573 is_zero = diff > self.threshold
574 if is_zero and not self.half_one:
576 self.edges.append(self.previous)
577 elif not is_zero and self.half_one:
579 self.edges.append(self.start_one)
580 self.half_one = False
581 elif not is_zero and not self.half_one:
583 self.start_one = self.previous
584 else: # Invalid BMC sequence
585 self.bad.append((self.start_one, self.previous))
586 # TODO try to recover
588 self.edges.append(self.previous)
589 self.half_one = False
590 self.previous = self.samplenum