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'
282 p = 'TODO: PPS support'
284 t_name = 'Reserved APDO: '+bin(t2)
287 for f in sorted(PDO_FLAGS.keys(), reverse = True):
289 flags += ' [' + PDO_FLAGS[f] + ']'
290 return '[%s] %s%s' % (t_name, p, flags)
292 def get_vdm(self, idx, data):
293 if idx == 0: # VDM header
295 struct = data & (1 << 15)
297 if struct: # Structured VDM
299 src = data & (1 << 5)
300 ack = (data >> 6) & 3
301 pos = (data >> 8) & 7
302 ver = (data >> 13) & 3
303 txt = VDM_ACK[ack] + ' '
304 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
305 txt += ' pos %d' % (pos) if pos else ' '
306 else: # Unstructured VDM
307 txt = 'unstruct [%04x]' % (data & 0x7fff)
308 txt += ' SVID:%04x' % (vid)
310 txt = 'VDO:%08x' % (data)
313 def get_bist(self, idx, data):
315 counter = data & 0xffff
316 mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
318 mode_name = 'Counter[= %d]' % (counter)
319 # TODO check all 0 bits are 0 / emit warnings
320 return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
322 def putpayload(self, s0, s1, idx):
324 txt = '['+str(idx+1)+'] '
326 txt += self.get_request(self.data[idx])
327 elif t == 1 or t == 4:
328 txt += self.get_source_sink_cap(self.data[idx], idx+1)
330 txt += self.get_vdm(idx, self.data[idx])
332 txt += self.get_bist(idx, self.data[idx])
333 self.putx(s0, s1, [11, [txt, txt]])
334 self.text += ' - ' + txt
337 ann_type = 9 if self.head_power_role() else 10
338 role = 'SRC' if self.head_power_role() else 'SNK'
339 if self.head_data_role() != self.head_power_role():
340 role += '/DFP' if self.head_data_role() else '/UFP'
342 if self.head_count() == 0:
343 shortm = CTRL_TYPES[t]
345 shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
347 longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
348 self.putx(0, -1, [ann_type, [longm, shortm]])
352 return (self.head >> 9) & 7
354 def head_power_role(self):
355 return (self.head >> 8) & 1
357 def head_data_role(self):
358 return (self.head >> 5) & 1
361 return ((self.head >> 6) & 3) + 1
364 return self.head & 0xF
366 def head_count(self):
367 return (self.head >> 12) & 7
369 def putx(self, s0, s1, data):
370 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
372 def putwarn(self, longm, shortm):
373 self.putx(0, -1, [8, [longm, shortm]])
375 def compute_crc32(self):
376 bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
377 *tuple([d & 0xffffffff for d in self.data]))
378 return zlib.crc32(bdata)
380 def rec_sym(self, i, sym):
381 self.putx(i, i+5, [7, SYM_NAME[sym]])
383 def get_sym(self, i, rec=True):
384 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
385 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
393 # Check it's not a truncated packet
394 if len(self.bits) - i <= 20:
395 self.putwarn('Truncated', '!')
397 k = [self.get_sym(i), self.get_sym(i+5),
398 self.get_sym(i+10), self.get_sym(i+15)]
399 # TODO check bad symbols
400 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
405 lo = self.get_short()
406 hi = self.get_short()
407 return lo | (hi << 16)
409 def find_corrupted_sop(self, k):
410 # Start of packet are valid even if they have only 3 correct symbols
412 for seq in SOP_SEQUENCES:
413 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
414 return START_OF_PACKETS[seq]
418 for i in range(len(self.bits) - 19):
419 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
420 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
421 sym = START_OF_PACKETS.get(k, None)
423 sym = self.find_corrupted_sop(k)
424 # We have an interesting symbol sequence
426 # annotate the preamble
427 self.putx(0, i, [1, ['Preamble', '...']])
428 # annotate each symbol
429 self.rec_sym(i, k[0])
430 self.rec_sym(i+5, k[1])
431 self.rec_sym(i+10, k[2])
432 self.rec_sym(i+15, k[3])
433 if sym == 'Hard Reset':
435 return -1 # Hard reset
436 elif sym == 'Cable Reset':
438 return -1 # Cable reset
440 self.putx(i, i+20, [2, [sym, 'S']])
442 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
443 self.text += 'Junk???'
444 self.putwarn('No start of packet found', 'XXX')
445 return -1 # No Start Of Packet
451 self.samplerate = None
455 self.startsample = None
459 self.half_one = False
461 self.stored_pdos = {}
463 def metadata(self, key, value):
464 if key == srd.SRD_CONF_SAMPLERATE:
465 self.samplerate = value
466 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
467 self.maxbit = self.us2samples(3 * UI_US)
468 # duration threshold between half 1 and 0
469 self.threshold = self.us2samples(THRESHOLD_US)
472 self.out_python = self.register(srd.OUTPUT_PYTHON)
473 self.out_ann = self.register(srd.OUTPUT_ANN)
474 self.out_binary = self.register(srd.OUTPUT_BINARY)
475 self.out_bitrate = self.register(
477 meta=(int, 'Bitrate', 'Bitrate during the packet')
480 def us2samples(self, us):
481 return int(us * self.samplerate / 1000000)
483 def decode_packet(self):
488 if len(self.edges) < 50:
489 return # Not a real PD packet
492 tstamp = float(self.startsample) / self.samplerate
493 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
495 self.idx = self.scan_eop()
497 # Full text trace of the issue
498 self.putx(0, self.idx, [12, [self.text, '...']])
499 return # No real packet: ABORT
502 self.head = self.get_short()
503 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
506 # Decode data payload
507 for i in range(self.head_count()):
508 self.data.append(self.get_word())
509 self.putx(self.idx-40, self.idx,
510 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
511 self.putpayload(self.idx-40, self.idx, i)
514 self.crc = self.get_word()
515 ccrc = self.compute_crc32()
517 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
518 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
521 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
522 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
525 self.putwarn('No EOP', 'EOP!')
527 if self.options['fulltext'] == 'yes':
528 self.putx(0, self.idx, [12, [self.text, '...']])
530 # Meta data for bitrate
531 ss, es = self.edges[0], self.edges[-1]
532 bitrate = self.samplerate*len(self.bits) / float(es - ss)
533 self.put(es, ss, self.out_bitrate, int(bitrate))
534 # Raw binary data (BMC decoded)
535 self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
538 if not self.samplerate:
539 raise SamplerateError('Cannot decode without samplerate.')
541 pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': 100*1000}])
544 # First sample of the packet, just record the start date
545 if not self.startsample:
546 self.startsample = self.samplenum
547 self.previous = self.samplenum
550 diff = self.samplenum - self.previous
552 # Large idle: use it as the end of packet
553 if diff > self.maxbit:
554 # the last edge of the packet
555 self.edges.append(self.previous)
558 # Reset for next packet
559 self.startsample = self.samplenum
563 self.half_one = False
565 else: # add the bit to the packet
566 is_zero = diff > self.threshold
567 if is_zero and not self.half_one:
569 self.edges.append(self.previous)
570 elif not is_zero and self.half_one:
572 self.edges.append(self.start_one)
573 self.half_one = False
574 elif not is_zero and not self.half_one:
576 self.start_one = self.previous
577 else: # Invalid BMC sequence
578 self.bad.append((self.start_one, self.previous))
579 # TODO try to recover
581 self.edges.append(self.previous)
582 self.half_one = False
583 self.previous = self.samplenum