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, write to the Free Software
18 ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
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): "SOP",
107 (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'",
108 (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"',
109 (SYNC1, RST2, RST2, SYNC3): "SOP' Debug",
110 (SYNC1, RST2, SYNC3, SYNC2): 'SOP" Debug',
111 (RST1, SYNC1, RST1, SYNC3): "Cable Reset",
112 (RST1, RST1, RST1, RST2): "Hard Reset",
142 (1 << 24): "no_suspend",
143 (1 << 25): "comm_cap",
144 (1 << 26): "cap_mismatch",
145 (1 << 27): "give_back"
147 PDO_TYPE = ["", "BATT:", "VAR:", "<bad>"]
149 (1 << 29): "dual_role_power",
150 (1 << 28): "suspend",
152 (1 << 26): "comm_cap",
153 (1 << 25): "dual_role_data"
174 # 16..31: SVID Specific Commands
175 # DisplayPort Commands
179 VDM_ACK = ["REQ", "ACK", "NAK", "BSY"]
182 class Decoder(srd.Decoder):
184 id = 'usb_power_delivery'
186 longname = 'USB Power Delivery'
187 desc = 'USB Power Delivery protocol.'
192 {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
195 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
196 'default': 'no', 'values': ('yes', 'no')},
199 ('type', 'Packet Type'),
200 ('Preamble', 'Preamble'),
201 ('SOP', 'Start of Packet'),
205 ('EOP', 'End Of Packet'),
206 ('Sym', '4b5b symbols'),
207 ('warnings', 'Warnings'),
208 ('src', 'Source Message'),
209 ('snk', 'Sink Message'),
210 ('payload', 'Payload'),
211 ('text', 'Plain text'),
214 ('4B5B', 'symbols', (7, )),
215 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
216 ('payload', 'Payload', (11, )),
217 ('type', 'Type', (0, 9, 10, )),
218 ('warnings', 'Warnings', (8, )),
219 ('text', 'Full text', (12, )),
222 ('raw-data', 'RAW binary data'),
225 def get_request(self, rdo):
226 pos = (rdo >> 28) & 7
227 op_ma = ((rdo >> 10) & 0x3ff) * 10
228 max_ma = (rdo & 0x3ff) * 10
230 for f in RDO_FLAGS.keys():
232 flags += " " + RDO_FLAGS[f]
233 return "[%d]%d/%d mA%s" % (pos, op_ma, max_ma, flags)
235 def get_source_cap(self, pdo):
238 mv = ((pdo >> 10) & 0x3ff) * 50
239 ma = ((pdo >> 0) & 0x3ff) * 10
240 p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
242 minv = ((pdo >> 10) & 0x3ff) * 50
243 maxv = ((pdo >> 20) & 0x3ff) * 50
244 mw = ((pdo >> 0) & 0x3ff) * 250
245 p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
247 minv = ((pdo >> 10) & 0x3ff) * 50
248 maxv = ((pdo >> 20) & 0x3ff) * 50
249 ma = ((pdo >> 0) & 0x3ff) * 10
250 p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
254 for f in PDO_FLAGS.keys():
256 flags += " " + PDO_FLAGS[f]
257 return "%s%s%s" % (PDO_TYPE[t], p, flags)
259 def get_sink_cap(self, pdo):
262 mv = ((pdo >> 10) & 0x3ff) * 50
263 ma = ((pdo >> 0) & 0x3ff) * 10
264 p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
266 minv = ((pdo >> 10) & 0x3ff) * 50
267 maxv = ((pdo >> 20) & 0x3ff) * 50
268 mw = ((pdo >> 0) & 0x3ff) * 250
269 p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
271 minv = ((pdo >> 10) & 0x3ff) * 50
272 maxv = ((pdo >> 20) & 0x3ff) * 50
273 ma = ((pdo >> 0) & 0x3ff) * 10
274 p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
278 for f in PDO_FLAGS.keys():
280 flags += " " + PDO_FLAGS[f]
281 return "%s%s%s" % (PDO_TYPE[t], p, flags)
283 def get_vdm(self, idx, data):
284 if idx == 0: # VDM header
286 struct = data & (1 << 15)
288 if struct: # Structured VDM
290 src = data & (1 << 5)
291 ack = (data >> 6) & 3
292 pos = (data >> 8) & 7
293 ver = (data >> 13) & 3
294 txt = VDM_ACK[ack] + " "
295 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else "cmd?"
296 txt += " pos %d" % (pos) if pos else " "
297 else: # Unstructured VDM
298 txt = "unstruct [%04x]" % (data & 0x7fff)
299 txt += " SVID:%04x" % (vid)
301 txt = "VDO:%08x" % (data)
304 def get_bist(self, idx, data):
306 counter = data & 0xffff
307 mode_name = BIST_MODES[mode] if mode in BIST_MODES else "INVALID"
309 mode_name = "Counter[= %d]" % (counter)
310 # TODO check all 0 bits are 0 / emit warnings
311 return "mode %s" % (mode_name) if idx == 0 else "invalid BRO"
313 def putpayload(self, s0, s1, idx):
317 txt = self.get_request(self.data[idx])
319 txt = self.get_source_cap(self.data[idx])
321 txt = self.get_sink_cap(self.data[idx])
323 txt = self.get_vdm(idx, self.data[idx])
325 txt = self.get_bist(idx, self.data[idx])
326 self.putx(s0, s1, [11, [txt, txt]])
327 self.text += " - " + txt
330 ann_type = 9 if self.head_power_role() else 10
331 role = "SRC" if self.head_power_role() else "SNK"
332 if self.head_data_role() != self.head_power_role():
333 role += "/DFP" if self.head_data_role() else "/UFP"
335 if self.head_count() == 0:
336 shortm = CTRL_TYPES[t]
338 shortm = DATA_TYPES[t] if t in DATA_TYPES else "DAT???"
340 longm = "{:s}[{:d}]:{:s}".format(role, self.head_id(), shortm)
341 self.putx(0, -1, [ann_type, [longm, shortm]])
345 return (self.head >> 9) & 7
347 def head_power_role(self):
348 return (self.head >> 8) & 1
350 def head_data_role(self):
351 return (self.head >> 5) & 1
354 return ((self.head >> 6) & 3) + 1
357 return self.head & 0xF
359 def head_count(self):
360 return (self.head >> 12) & 7
362 def putx(self, s0, s1, data):
363 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
365 def putwarn(self, longm, shortm):
366 self.putx(0, -1, [8, [longm, shortm]])
368 def compute_crc32(self):
369 bdata = struct.pack("<H"+"I"*len(self.data), self.head & 0xffff,
370 *tuple([d & 0xffffffff for d in self.data]))
371 return zlib.crc32(bdata)
373 def rec_sym(self, i, sym):
374 self.putx(i, i+5, [7, SYM_NAME[sym]])
376 def get_sym(self, i, rec=True):
377 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
378 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
386 # Check it's not a truncated packet
387 if len(self.bits) - i <= 20:
388 self.putwarn("Truncated", "!")
390 k = [self.get_sym(i), self.get_sym(i+5),
391 self.get_sym(i+10), self.get_sym(i+15)]
392 # TODO check bad symbols
393 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
398 lo = self.get_short()
399 hi = self.get_short()
400 return lo | (hi << 16)
402 def find_corrupted_sop(self, k):
403 # Start of packet are valid even if they have only 3 correct symbols
405 for seq in START_OF_PACKETS.keys():
406 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
407 return START_OF_PACKETS[seq]
411 for i in range(len(self.bits) - 19):
412 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
413 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
414 sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
416 sym = self.find_corrupted_sop(k)
417 # We have an interesting symbol sequence
419 # annotate the preamble
420 self.putx(0, i, [1, ['Preamble', '...']])
421 # annotate each symbol
422 self.rec_sym(i, k[0])
423 self.rec_sym(i+5, k[1])
424 self.rec_sym(i+10, k[2])
425 self.rec_sym(i+15, k[3])
426 if sym == 'Hard Reset':
428 return -1 # Hard reset
429 elif sym == 'Cable Reset':
431 return -1 # Cable reset
433 self.putx(i, i+20, [2, [sym, 'S']])
435 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
436 self.text += "Junk???"
437 self.putwarn("No start of packet found", "XXX")
438 return -1 # No Start Of Packet
440 def __init__(self, **kwargs):
441 self.samplerate = None
448 self.startsample = None
452 self.half_one = False
455 def metadata(self, key, value):
456 if key == srd.SRD_CONF_SAMPLERATE:
457 self.samplerate = value
458 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
459 self.maxbit = self.us2samples(3 * UI_US)
460 # duration threshold between half 1 and 0
461 self.threshold = self.us2samples(THRESHOLD_US)
464 self.out_python = self.register(srd.OUTPUT_PYTHON)
465 self.out_ann = self.register(srd.OUTPUT_ANN)
466 self.out_binary = self.register(srd.OUTPUT_BINARY)
467 self.out_bitrate = self.register(
469 meta=(int, 'Bitrate', 'Bitrate during the packet')
472 def us2samples(self, us):
473 if self.samplerate is None:
474 raise Exception("Need the samplerate.")
475 return int(us * self.samplerate / 1000000)
477 def decode_packet(self):
482 if len(self.edges) < 50:
483 return # Not a real PD packet
486 tstamp = float(self.startsample) / self.samplerate
487 self.text += "#%-4d (%8.6fms): " % (self.packet_seq, tstamp*1000)
489 self.idx = self.scan_eop()
491 # Full text trace of the issue
492 self.putx(0, self.idx, [12, [self.text, '...']])
493 return # No real packet: ABORT
496 self.head = self.get_short()
497 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
500 # Decode data payload
501 for i in range(self.head_count()):
502 self.data.append(self.get_word())
503 self.putx(self.idx-40, self.idx,
504 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
505 self.putpayload(self.idx-40, self.idx, i)
508 self.crc = self.get_word()
509 ccrc = self.compute_crc32()
511 self.putwarn("Bad CRC %08x != %08x" % (self.crc, ccrc), "CRC!")
512 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
515 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
516 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
519 self.putwarn("No EOP", "EOP!")
521 if self.options['fulltext'] == 'yes':
522 self.putx(0, self.idx, [12, [self.text, '...']])
524 # Meta data for bitrate
525 ss, es = self.edges[0], self.edges[-1]
526 bitrate = self.samplerate*len(self.bits) / float(es - ss)
527 self.put(es, ss, self.out_bitrate, int(bitrate))
528 # Raw binary data (BMC decoded)
529 self.put(es, ss, self.out_binary, (0, bytes(self.bits)))
531 def decode(self, ss, es, data):
532 if self.samplerate is None:
533 raise Exception("Cannot decode without samplerate.")
534 for (self.samplenum, pins) in data:
536 if self.oldpins == pins:
539 self.oldpins, (cc, ) = pins, pins
541 # First sample of the packet, just record the start date
542 if not self.startsample:
543 self.startsample = self.samplenum
544 self.previous = self.samplenum
547 diff = self.samplenum - self.previous
549 # Large idle: use it as the end of packet
550 if diff > self.maxbit:
551 # the last edge of the packet
552 self.edges.append(self.previous)
555 # Reset for next packet
556 self.startsample = self.samplenum
560 self.half_one = False
562 else: # add the bit to the packet
563 is_zero = diff > self.threshold
564 if is_zero and not self.half_one:
566 self.edges.append(self.previous)
567 elif not is_zero and self.half_one:
569 self.edges.append(self.start_one)
570 self.half_one = False
571 elif not is_zero and not self.half_one:
573 self.start_one = self.previous
574 else: # Invalid BMC sequence
575 self.bad.append((self.start_one, self.previous))
576 # TODO try to recover
578 self.edges.append(self.previous)
579 self.half_one = False
580 self.previous = self.samplenum