2 ## This file is part of the libsigrokdecode project.
4 ## Copyright 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.
17 import sigrokdecode as srd
19 import zlib # for crc32
21 # BMC encoding with a 600Khz datarate
22 UI_US = 1000000/600000.0
24 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
25 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
27 # Control Message type
56 # 4b5b encoding of the symbols
67 0x01, # 1 = 0001 01001
68 0x04, # 4 = 0100 01010
69 0x05, # 5 = 0101 01011
72 0x06, # 6 = 0110 01110
73 0x07, # 7 = 0111 01111
76 0x08, # 8 = 1000 10010
77 0x09, # 9 = 1001 10011
78 0x02, # 2 = 0010 10100
79 0x03, # 3 = 0011 10101
80 0x0A, # A = 1010 10110
81 0x0B, # B = 1011 10111
84 0x0C, # C = 1100 11010
85 0x0D, # D = 1101 11011
86 0x0E, # E = 1110 11100
87 0x0F, # F = 1111 11101
88 0x00, # 0 = 0000 11110
98 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
99 HRST_CODES = [RST1, RST1, RST1, RST2]
102 (SYNC1, SYNC1, SYNC1, SYNC2): "SOP",
103 (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'",
104 (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"',
105 (SYNC1, RST2, RST2, SYNC3): "SOP' Debug",
106 (SYNC1, RST2, SYNC3, SYNC2): 'SOP" Debug',
107 (RST1, SYNC1, RST1, SYNC3): "Cable Reset",
108 (RST1, RST1, RST1, RST2): "Hard Reset",
138 (1 << 24): "no_suspend",
139 (1 << 25): "comm_cap",
140 (1 << 26): "cap_mismatch",
141 (1 << 27): "give_back"
143 PDO_TYPE = ["", "BATT:", "VAR:", "<bad>"]
145 (1 << 29): "dual_role_power",
146 (1 << 28): "suspend",
148 (1 << 26): "comm_cap",
149 (1 << 25): "dual_role_data"
170 # 16..31: SVID Specific Commands
171 # DisplayPort Commands
175 VDM_ACK = ["REQ", "ACK", "NAK", "BSY"]
178 class Decoder(srd.Decoder):
180 id = 'usb_power_delivery'
182 longname = 'USB Power Delivery'
183 desc = 'USB Power Delivery protocol.'
188 {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
191 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
192 'default': 'no', 'values': ('yes', 'no')},
195 ('type', 'Packet Type'),
196 ('Preamble', 'Preamble'),
197 ('SOP', 'Start of Packet'),
201 ('EOP', 'End Of Packet'),
202 ('Sym', '4b5b symbols'),
203 ('warnings', 'Warnings'),
204 ('src', 'Source Message'),
205 ('snk', 'Sink Message'),
206 ('payload', 'Payload'),
207 ('text', 'Plain text'),
210 ('4B5B', 'symbols', (7, )),
211 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
212 ('payload', 'Payload', (11, )),
213 ('type', 'Type', (0, 9, 10, )),
214 ('warnings', 'Warnings', (8, )),
215 ('text', 'Full text', (12, )),
218 ('raw-data', 'RAW binary data'),
221 def get_request(self, rdo):
222 pos = (rdo >> 28) & 7
223 op_ma = ((rdo >> 10) & 0x3ff) * 10
224 max_ma = (rdo & 0x3ff) * 10
226 for f in RDO_FLAGS.keys():
228 flags += " " + RDO_FLAGS[f]
229 return "[%d]%d/%d mA%s" % (pos, op_ma, max_ma, flags)
231 def get_source_cap(self, pdo):
234 mv = ((pdo >> 10) & 0x3ff) * 50
235 ma = ((pdo >> 0) & 0x3ff) * 10
236 p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
238 minv = ((pdo >> 10) & 0x3ff) * 50
239 maxv = ((pdo >> 20) & 0x3ff) * 50
240 mw = ((pdo >> 0) & 0x3ff) * 250
241 p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
243 minv = ((pdo >> 10) & 0x3ff) * 50
244 maxv = ((pdo >> 20) & 0x3ff) * 50
245 ma = ((pdo >> 0) & 0x3ff) * 10
246 p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
250 for f in PDO_FLAGS.keys():
252 flags += " " + PDO_FLAGS[f]
253 return "%s%s%s" % (PDO_TYPE[t], p, flags)
255 def get_sink_cap(self, pdo):
258 mv = ((pdo >> 10) & 0x3ff) * 50
259 ma = ((pdo >> 0) & 0x3ff) * 10
260 p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
262 minv = ((pdo >> 10) & 0x3ff) * 50
263 maxv = ((pdo >> 20) & 0x3ff) * 50
264 mw = ((pdo >> 0) & 0x3ff) * 250
265 p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
267 minv = ((pdo >> 10) & 0x3ff) * 50
268 maxv = ((pdo >> 20) & 0x3ff) * 50
269 ma = ((pdo >> 0) & 0x3ff) * 10
270 p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
274 for f in PDO_FLAGS.keys():
276 flags += " " + PDO_FLAGS[f]
277 return "%s%s%s" % (PDO_TYPE[t], p, flags)
279 def get_vdm(self, idx, data):
280 if idx == 0: # VDM header
282 struct = data & (1 << 15)
284 if struct: # Structured VDM
286 src = data & (1 << 5)
287 ack = (data >> 6) & 3
288 pos = (data >> 8) & 7
289 ver = (data >> 13) & 3
290 txt = VDM_ACK[ack] + " "
291 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else "cmd?"
292 txt += " pos %d" % (pos) if pos else " "
293 else: # Unstructured VDM
294 txt = "unstruct [%04x]" % (data & 0x7fff)
295 txt += " SVID:%04x" % (vid)
297 txt = "VDO:%08x" % (data)
300 def get_bist(self, idx, data):
302 counter = data & 0xffff
303 mode_name = BIST_MODES[mode] if mode in BIST_MODES else "INVALID"
305 mode_name = "Counter[= %d]" % (counter)
306 # TODO check all 0 bits are 0 / emit warnings
307 return "mode %s" % (mode_name) if idx == 0 else "invalid BRO"
309 def putpayload(self, s0, s1, idx):
313 txt = self.get_request(self.data[idx])
315 txt = self.get_source_cap(self.data[idx])
317 txt = self.get_sink_cap(self.data[idx])
319 txt = self.get_vdm(idx, self.data[idx])
321 txt = self.get_bist(idx, self.data[idx])
322 self.putx(s0, s1, [11, [txt, txt]])
323 self.text += " - " + txt
326 ann_type = 9 if self.head_power_role() else 10
327 role = "SRC" if self.head_power_role() else "SNK"
328 if self.head_data_role() != self.head_power_role():
329 role += "/DFP" if self.head_data_role() else "/UFP"
331 if self.head_count() == 0:
332 shortm = CTRL_TYPES[t]
334 shortm = DATA_TYPES[t] if t in DATA_TYPES else "DAT???"
336 longm = "{:s}[{:d}]:{:s}".format(role, self.head_id(), shortm)
337 self.putx(0, -1, [ann_type, [longm, shortm]])
341 return (self.head >> 9) & 7
343 def head_power_role(self):
344 return (self.head >> 8) & 1
346 def head_data_role(self):
347 return (self.head >> 5) & 1
350 return ((self.head >> 6) & 3) + 1
353 return self.head & 0xF
355 def head_count(self):
356 return (self.head >> 12) & 7
358 def putx(self, s0, s1, data):
359 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
361 def putwarn(self, longm, shortm):
362 self.putx(0, -1, [8, [longm, shortm]])
364 def compute_crc32(self):
365 bdata = struct.pack("<H"+"I"*len(self.data), self.head & 0xffff,
366 *tuple([d & 0xffffffff for d in self.data]))
367 return zlib.crc32(bdata)
369 def rec_sym(self, i, sym):
370 self.putx(i, i+5, [7, SYM_NAME[sym]])
372 def get_sym(self, i, rec=True):
373 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
374 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
382 # Check it's not a truncated packet
383 if len(self.bits) - i <= 20:
384 self.putwarn("Truncated", "!")
386 k = [self.get_sym(i), self.get_sym(i+5),
387 self.get_sym(i+10), self.get_sym(i+15)]
388 # TODO check bad symbols
389 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
394 lo = self.get_short()
395 hi = self.get_short()
396 return lo | (hi << 16)
398 def find_corrupted_sop(self, k):
399 # Start of packet are valid even if they have only 3 correct symbols
401 for seq in START_OF_PACKETS.keys():
402 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
403 return START_OF_PACKETS[seq]
407 for i in range(len(self.bits) - 19):
408 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
409 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
410 sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
412 sym = self.find_corrupted_sop(k)
413 # We have an interesting symbol sequence
415 # annotate the preamble
416 self.putx(0, i, [1, ['Preamble', '...']])
417 # annotate each symbol
418 self.rec_sym(i, k[0])
419 self.rec_sym(i+5, k[1])
420 self.rec_sym(i+10, k[2])
421 self.rec_sym(i+15, k[3])
422 if sym == 'Hard Reset':
424 return -1 # Hard reset
425 elif sym == 'Cable Reset':
427 return -1 # Cable reset
429 self.putx(i, i+20, [2, [sym, 'S']])
431 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
432 self.text += "Junk???"
433 self.putwarn("No start of packet found", "XXX")
434 return -1 # No Start Of Packet
436 def __init__(self, **kwargs):
437 self.samplerate = None
444 self.startsample = None
448 self.half_one = False
451 def metadata(self, key, value):
452 if key == srd.SRD_CONF_SAMPLERATE:
453 self.samplerate = value
454 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
455 self.maxbit = self.us2samples(3 * UI_US)
456 # duration threshold between half 1 and 0
457 self.threshold = self.us2samples(THRESHOLD_US)
460 self.out_python = self.register(srd.OUTPUT_PYTHON)
461 self.out_ann = self.register(srd.OUTPUT_ANN)
462 self.out_binary = self.register(srd.OUTPUT_BINARY)
463 self.out_bitrate = self.register(
465 meta=(int, 'Bitrate', 'Bitrate during the packet')
468 def us2samples(self, us):
469 if self.samplerate is None:
470 raise Exception("Need the samplerate.")
471 return int(us * self.samplerate / 1000000)
473 def decode_packet(self):
478 if len(self.edges) < 50:
479 return # Not a real PD packet
482 tstamp = float(self.startsample) / self.samplerate
483 self.text += "#%-4d (%8.6fms): " % (self.packet_seq, tstamp*1000)
485 self.idx = self.scan_eop()
487 # Full text trace of the issue
488 self.putx(0, self.idx, [12, [self.text, '...']])
489 return # No real packet: ABORT
492 self.head = self.get_short()
493 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
496 # Decode data payload
497 for i in range(self.head_count()):
498 self.data.append(self.get_word())
499 self.putx(self.idx-40, self.idx,
500 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
501 self.putpayload(self.idx-40, self.idx, i)
504 self.crc = self.get_word()
505 ccrc = self.compute_crc32()
507 self.putwarn("Bad CRC %08x != %08x" % (self.crc, ccrc), "CRC!")
508 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
511 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
512 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
515 self.putwarn("No EOP", "EOP!")
517 if self.options['fulltext'] == 'yes':
518 self.putx(0, self.idx, [12, [self.text, '...']])
520 # Meta data for bitrate
521 ss, es = self.edges[0], self.edges[-1]
522 bitrate = self.samplerate*len(self.bits) / float(es - ss)
523 self.put(es, ss, self.out_bitrate, int(bitrate))
524 # Raw binary data (BMC decoded)
525 self.put(es, ss, self.out_binary, (0, bytes(self.bits)))
527 def decode(self, ss, es, data):
528 if self.samplerate is None:
529 raise Exception("Cannot decode without samplerate.")
530 for (self.samplenum, pins) in data:
532 if self.oldpins == pins:
535 self.oldpins, (cc, ) = pins, pins
537 # First sample of the packet, just record the start date
538 if not self.startsample:
539 self.startsample = self.samplenum
540 self.previous = self.samplenum
543 diff = self.samplenum - self.previous
545 # Large idle: use it as the end of packet
546 if diff > self.maxbit:
547 # the last edge of the packet
548 self.edges.append(self.previous)
551 # Reset for next packet
552 self.startsample = self.samplenum
556 self.half_one = False
558 else: # add the bit to the packet
559 is_zero = diff > self.threshold
560 if is_zero and not self.half_one:
562 self.edges.append(self.previous)
563 elif not is_zero and self.half_one:
565 self.edges.append(self.start_one)
566 self.half_one = False
567 elif not is_zero and not self.half_one:
569 self.start_one = self.previous
570 else: # Invalid BMC sequence
571 self.bad.append((self.start_one, self.previous))
572 # TODO try to recover
574 self.edges.append(self.previous)
575 self.half_one = False
576 self.previous = self.samplenum