2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2015 Google, Inc
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ## GNU General Public License for more details.
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
20 import sigrokdecode as srd
22 import zlib # for crc32
24 # BMC encoding with a 600kHz datarate
25 UI_US = 1000000/600000.0
27 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
28 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
30 # Control Message type
59 # 4b5b encoding of the symbols
70 0x01, # 1 = 0001 01001
71 0x04, # 4 = 0100 01010
72 0x05, # 5 = 0101 01011
75 0x06, # 6 = 0110 01110
76 0x07, # 7 = 0111 01111
79 0x08, # 8 = 1000 10010
80 0x09, # 9 = 1001 10011
81 0x02, # 2 = 0010 10100
82 0x03, # 3 = 0011 10101
83 0x0A, # A = 1010 10110
84 0x0B, # B = 1011 10111
87 0x0C, # C = 1100 11010
88 0x0D, # D = 1101 11011
89 0x0E, # E = 1110 11100
90 0x0F, # F = 1111 11101
91 0x00, # 0 = 0000 11110
101 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
102 HRST_CODES = [RST1, RST1, RST1, RST2]
105 (SYNC1, SYNC1, SYNC1, SYNC2): 'SOP',
106 (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'",
107 (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"',
108 (SYNC1, RST2, RST2, SYNC3): "SOP' Debug",
109 (SYNC1, RST2, SYNC3, SYNC2): 'SOP" Debug',
110 (RST1, SYNC1, RST1, SYNC3): 'Cable Reset',
111 (RST1, RST1, RST1, RST2): 'Hard Reset',
141 (1 << 24): 'no_suspend',
142 (1 << 25): 'comm_cap',
143 (1 << 26): 'cap_mismatch',
144 (1 << 27): 'give_back'
146 PDO_TYPE = ['', 'BATT:', 'VAR:', '<bad>']
148 (1 << 29): 'dual_role_power',
149 (1 << 28): 'suspend',
151 (1 << 26): 'comm_cap',
152 (1 << 25): 'dual_role_data'
173 # 16..31: SVID Specific Commands
174 # DisplayPort Commands
178 VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
180 class SamplerateError(Exception):
183 class Decoder(srd.Decoder):
185 id = 'usb_power_delivery'
187 longname = 'USB Power Delivery'
188 desc = 'USB Power Delivery protocol.'
193 {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
196 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
197 'default': 'no', 'values': ('yes', 'no')},
200 ('type', 'Packet Type'),
201 ('Preamble', 'Preamble'),
202 ('SOP', 'Start of Packet'),
206 ('EOP', 'End Of Packet'),
207 ('Sym', '4b5b symbols'),
208 ('warnings', 'Warnings'),
209 ('src', 'Source Message'),
210 ('snk', 'Sink Message'),
211 ('payload', 'Payload'),
212 ('text', 'Plain text'),
215 ('4B5B', 'symbols', (7, )),
216 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
217 ('payload', 'Payload', (11, )),
218 ('type', 'Type', (0, 9, 10, )),
219 ('warnings', 'Warnings', (8, )),
220 ('text', 'Full text', (12, )),
223 ('raw-data', 'RAW binary data'),
226 def get_request(self, rdo):
227 pos = (rdo >> 28) & 7
228 op_ma = ((rdo >> 10) & 0x3ff) * 10
229 max_ma = (rdo & 0x3ff) * 10
231 for f in RDO_FLAGS.keys():
233 flags += ' ' + RDO_FLAGS[f]
234 return '[%d]%d/%d mA%s' % (pos, op_ma, max_ma, flags)
236 def get_source_cap(self, pdo):
239 mv = ((pdo >> 10) & 0x3ff) * 50
240 ma = ((pdo >> 0) & 0x3ff) * 10
241 p = '%.1fV %.1fA' % (mv/1000.0, ma/1000.0)
243 minv = ((pdo >> 10) & 0x3ff) * 50
244 maxv = ((pdo >> 20) & 0x3ff) * 50
245 mw = ((pdo >> 0) & 0x3ff) * 250
246 p = '%.1f/%.1fV %.1fW' % (minv/1000.0, maxv/1000.0, mw/1000.0)
248 minv = ((pdo >> 10) & 0x3ff) * 50
249 maxv = ((pdo >> 20) & 0x3ff) * 50
250 ma = ((pdo >> 0) & 0x3ff) * 10
251 p = '%.1f/%.1fV %.1fA' % (minv/1000.0, maxv/1000.0, ma/1000.0)
255 for f in PDO_FLAGS.keys():
257 flags += ' ' + PDO_FLAGS[f]
258 return '%s%s%s' % (PDO_TYPE[t], p, flags)
260 def get_sink_cap(self, pdo):
263 mv = ((pdo >> 10) & 0x3ff) * 50
264 ma = ((pdo >> 0) & 0x3ff) * 10
265 p = '%.1fV %.1fA' % (mv/1000.0, ma/1000.0)
267 minv = ((pdo >> 10) & 0x3ff) * 50
268 maxv = ((pdo >> 20) & 0x3ff) * 50
269 mw = ((pdo >> 0) & 0x3ff) * 250
270 p = '%.1f/%.1fV %.1fW' % (minv/1000.0, maxv/1000.0, mw/1000.0)
272 minv = ((pdo >> 10) & 0x3ff) * 50
273 maxv = ((pdo >> 20) & 0x3ff) * 50
274 ma = ((pdo >> 0) & 0x3ff) * 10
275 p = '%.1f/%.1fV %.1fA' % (minv/1000.0, maxv/1000.0, ma/1000.0)
279 for f in PDO_FLAGS.keys():
281 flags += ' ' + PDO_FLAGS[f]
282 return '%s%s%s' % (PDO_TYPE[t], p, flags)
284 def get_vdm(self, idx, data):
285 if idx == 0: # VDM header
287 struct = data & (1 << 15)
289 if struct: # Structured VDM
291 src = data & (1 << 5)
292 ack = (data >> 6) & 3
293 pos = (data >> 8) & 7
294 ver = (data >> 13) & 3
295 txt = VDM_ACK[ack] + ' '
296 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
297 txt += ' pos %d' % (pos) if pos else ' '
298 else: # Unstructured VDM
299 txt = 'unstruct [%04x]' % (data & 0x7fff)
300 txt += ' SVID:%04x' % (vid)
302 txt = 'VDO:%08x' % (data)
305 def get_bist(self, idx, data):
307 counter = data & 0xffff
308 mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
310 mode_name = 'Counter[= %d]' % (counter)
311 # TODO check all 0 bits are 0 / emit warnings
312 return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
314 def putpayload(self, s0, s1, idx):
318 txt = self.get_request(self.data[idx])
320 txt = self.get_source_cap(self.data[idx])
322 txt = self.get_sink_cap(self.data[idx])
324 txt = self.get_vdm(idx, self.data[idx])
326 txt = self.get_bist(idx, self.data[idx])
327 self.putx(s0, s1, [11, [txt, txt]])
328 self.text += ' - ' + txt
331 ann_type = 9 if self.head_power_role() else 10
332 role = 'SRC' if self.head_power_role() else 'SNK'
333 if self.head_data_role() != self.head_power_role():
334 role += '/DFP' if self.head_data_role() else '/UFP'
336 if self.head_count() == 0:
337 shortm = CTRL_TYPES[t]
339 shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
341 longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
342 self.putx(0, -1, [ann_type, [longm, shortm]])
346 return (self.head >> 9) & 7
348 def head_power_role(self):
349 return (self.head >> 8) & 1
351 def head_data_role(self):
352 return (self.head >> 5) & 1
355 return ((self.head >> 6) & 3) + 1
358 return self.head & 0xF
360 def head_count(self):
361 return (self.head >> 12) & 7
363 def putx(self, s0, s1, data):
364 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
366 def putwarn(self, longm, shortm):
367 self.putx(0, -1, [8, [longm, shortm]])
369 def compute_crc32(self):
370 bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
371 *tuple([d & 0xffffffff for d in self.data]))
372 return zlib.crc32(bdata)
374 def rec_sym(self, i, sym):
375 self.putx(i, i+5, [7, SYM_NAME[sym]])
377 def get_sym(self, i, rec=True):
378 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
379 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
387 # Check it's not a truncated packet
388 if len(self.bits) - i <= 20:
389 self.putwarn('Truncated', '!')
391 k = [self.get_sym(i), self.get_sym(i+5),
392 self.get_sym(i+10), self.get_sym(i+15)]
393 # TODO check bad symbols
394 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
399 lo = self.get_short()
400 hi = self.get_short()
401 return lo | (hi << 16)
403 def find_corrupted_sop(self, k):
404 # Start of packet are valid even if they have only 3 correct symbols
406 for seq in START_OF_PACKETS.keys():
407 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
408 return START_OF_PACKETS[seq]
412 for i in range(len(self.bits) - 19):
413 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
414 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
415 sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
417 sym = self.find_corrupted_sop(k)
418 # We have an interesting symbol sequence
420 # annotate the preamble
421 self.putx(0, i, [1, ['Preamble', '...']])
422 # annotate each symbol
423 self.rec_sym(i, k[0])
424 self.rec_sym(i+5, k[1])
425 self.rec_sym(i+10, k[2])
426 self.rec_sym(i+15, k[3])
427 if sym == 'Hard Reset':
429 return -1 # Hard reset
430 elif sym == 'Cable Reset':
432 return -1 # Cable reset
434 self.putx(i, i+20, [2, [sym, 'S']])
436 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
437 self.text += 'Junk???'
438 self.putwarn('No start of packet found', 'XXX')
439 return -1 # No Start Of Packet
445 self.samplerate = None
449 self.startsample = None
453 self.half_one = False
456 def metadata(self, key, value):
457 if key == srd.SRD_CONF_SAMPLERATE:
458 self.samplerate = value
459 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
460 self.maxbit = self.us2samples(3 * UI_US)
461 # duration threshold between half 1 and 0
462 self.threshold = self.us2samples(THRESHOLD_US)
465 self.out_python = self.register(srd.OUTPUT_PYTHON)
466 self.out_ann = self.register(srd.OUTPUT_ANN)
467 self.out_binary = self.register(srd.OUTPUT_BINARY)
468 self.out_bitrate = self.register(
470 meta=(int, 'Bitrate', 'Bitrate during the packet')
473 def us2samples(self, us):
474 return int(us * self.samplerate / 1000000)
476 def decode_packet(self):
481 if len(self.edges) < 50:
482 return # Not a real PD packet
485 tstamp = float(self.startsample) / self.samplerate
486 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
488 self.idx = self.scan_eop()
490 # Full text trace of the issue
491 self.putx(0, self.idx, [12, [self.text, '...']])
492 return # No real packet: ABORT
495 self.head = self.get_short()
496 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
499 # Decode data payload
500 for i in range(self.head_count()):
501 self.data.append(self.get_word())
502 self.putx(self.idx-40, self.idx,
503 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
504 self.putpayload(self.idx-40, self.idx, i)
507 self.crc = self.get_word()
508 ccrc = self.compute_crc32()
510 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
511 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
514 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
515 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
518 self.putwarn('No EOP', 'EOP!')
520 if self.options['fulltext'] == 'yes':
521 self.putx(0, self.idx, [12, [self.text, '...']])
523 # Meta data for bitrate
524 ss, es = self.edges[0], self.edges[-1]
525 bitrate = self.samplerate*len(self.bits) / float(es - ss)
526 self.put(es, ss, self.out_bitrate, int(bitrate))
527 # Raw binary data (BMC decoded)
528 self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
531 if not self.samplerate:
532 raise SamplerateError('Cannot decode without samplerate.')
536 # First sample of the packet, just record the start date
537 if not self.startsample:
538 self.startsample = self.samplenum
539 self.previous = self.samplenum
542 diff = self.samplenum - self.previous
544 # Large idle: use it as the end of packet
545 if diff > self.maxbit:
546 # the last edge of the packet
547 self.edges.append(self.previous)
550 # Reset for next packet
551 self.startsample = self.samplenum
555 self.half_one = False
557 else: # add the bit to the packet
558 is_zero = diff > self.threshold
559 if is_zero and not self.half_one:
561 self.edges.append(self.previous)
562 elif not is_zero and self.half_one:
564 self.edges.append(self.start_one)
565 self.half_one = False
566 elif not is_zero and not self.half_one:
568 self.start_one = self.previous
569 else: # Invalid BMC sequence
570 self.bad.append((self.start_one, self.previous))
571 # TODO try to recover
573 self.edges.append(self.previous)
574 self.half_one = False
575 self.previous = self.samplenum