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"]
181 class Decoder(srd.Decoder):
183 id = 'usb_power_delivery'
185 longname = 'USB Power Delivery'
186 desc = 'USB Power Delivery protocol.'
191 {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
194 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
195 'default': 'no', 'values': ('yes', 'no')},
198 ('type', 'Packet Type'),
199 ('Preamble', 'Preamble'),
200 ('SOP', 'Start of Packet'),
204 ('EOP', 'End Of Packet'),
205 ('Sym', '4b5b symbols'),
206 ('warnings', 'Warnings'),
207 ('src', 'Source Message'),
208 ('snk', 'Sink Message'),
209 ('payload', 'Payload'),
210 ('text', 'Plain text'),
213 ('4B5B', 'symbols', (7, )),
214 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
215 ('payload', 'Payload', (11, )),
216 ('type', 'Type', (0, 9, 10, )),
217 ('warnings', 'Warnings', (8, )),
218 ('text', 'Full text', (12, )),
221 ('raw-data', 'RAW binary data'),
224 def get_request(self, rdo):
225 pos = (rdo >> 28) & 7
226 op_ma = ((rdo >> 10) & 0x3ff) * 10
227 max_ma = (rdo & 0x3ff) * 10
229 for f in RDO_FLAGS.keys():
231 flags += " " + RDO_FLAGS[f]
232 return "[%d]%d/%d mA%s" % (pos, op_ma, max_ma, flags)
234 def get_source_cap(self, pdo):
237 mv = ((pdo >> 10) & 0x3ff) * 50
238 ma = ((pdo >> 0) & 0x3ff) * 10
239 p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
241 minv = ((pdo >> 10) & 0x3ff) * 50
242 maxv = ((pdo >> 20) & 0x3ff) * 50
243 mw = ((pdo >> 0) & 0x3ff) * 250
244 p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
246 minv = ((pdo >> 10) & 0x3ff) * 50
247 maxv = ((pdo >> 20) & 0x3ff) * 50
248 ma = ((pdo >> 0) & 0x3ff) * 10
249 p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
253 for f in PDO_FLAGS.keys():
255 flags += " " + PDO_FLAGS[f]
256 return "%s%s%s" % (PDO_TYPE[t], p, flags)
258 def get_sink_cap(self, pdo):
261 mv = ((pdo >> 10) & 0x3ff) * 50
262 ma = ((pdo >> 0) & 0x3ff) * 10
263 p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0)
265 minv = ((pdo >> 10) & 0x3ff) * 50
266 maxv = ((pdo >> 20) & 0x3ff) * 50
267 mw = ((pdo >> 0) & 0x3ff) * 250
268 p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0)
270 minv = ((pdo >> 10) & 0x3ff) * 50
271 maxv = ((pdo >> 20) & 0x3ff) * 50
272 ma = ((pdo >> 0) & 0x3ff) * 10
273 p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0)
277 for f in PDO_FLAGS.keys():
279 flags += " " + PDO_FLAGS[f]
280 return "%s%s%s" % (PDO_TYPE[t], p, flags)
282 def get_vdm(self, idx, data):
283 if idx == 0: # VDM header
285 struct = data & (1 << 15)
287 if struct: # Structured VDM
289 src = data & (1 << 5)
290 ack = (data >> 6) & 3
291 pos = (data >> 8) & 7
292 ver = (data >> 13) & 3
293 txt = VDM_ACK[ack] + " "
294 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else "cmd?"
295 txt += " pos %d" % (pos) if pos else " "
296 else: # Unstructured VDM
297 txt = "unstruct [%04x]" % (data & 0x7fff)
298 txt += " SVID:%04x" % (vid)
300 txt = "VDO:%08x" % (data)
303 def get_bist(self, idx, data):
305 counter = data & 0xffff
306 mode_name = BIST_MODES[mode] if mode in BIST_MODES else "INVALID"
308 mode_name = "Counter[= %d]" % (counter)
309 # TODO check all 0 bits are 0 / emit warnings
310 return "mode %s" % (mode_name) if idx == 0 else "invalid BRO"
312 def putpayload(self, s0, s1, idx):
316 txt = self.get_request(self.data[idx])
318 txt = self.get_source_cap(self.data[idx])
320 txt = self.get_sink_cap(self.data[idx])
322 txt = self.get_vdm(idx, self.data[idx])
324 txt = self.get_bist(idx, self.data[idx])
325 self.putx(s0, s1, [11, [txt, txt]])
326 self.text += " - " + txt
329 ann_type = 9 if self.head_power_role() else 10
330 role = "SRC" if self.head_power_role() else "SNK"
331 if self.head_data_role() != self.head_power_role():
332 role += "/DFP" if self.head_data_role() else "/UFP"
334 if self.head_count() == 0:
335 shortm = CTRL_TYPES[t]
337 shortm = DATA_TYPES[t] if t in DATA_TYPES else "DAT???"
339 longm = "{:s}[{:d}]:{:s}".format(role, self.head_id(), shortm)
340 self.putx(0, -1, [ann_type, [longm, shortm]])
344 return (self.head >> 9) & 7
346 def head_power_role(self):
347 return (self.head >> 8) & 1
349 def head_data_role(self):
350 return (self.head >> 5) & 1
353 return ((self.head >> 6) & 3) + 1
356 return self.head & 0xF
358 def head_count(self):
359 return (self.head >> 12) & 7
361 def putx(self, s0, s1, data):
362 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
364 def putwarn(self, longm, shortm):
365 self.putx(0, -1, [8, [longm, shortm]])
367 def compute_crc32(self):
368 bdata = struct.pack("<H"+"I"*len(self.data), self.head & 0xffff,
369 *tuple([d & 0xffffffff for d in self.data]))
370 return zlib.crc32(bdata)
372 def rec_sym(self, i, sym):
373 self.putx(i, i+5, [7, SYM_NAME[sym]])
375 def get_sym(self, i, rec=True):
376 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
377 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
385 # Check it's not a truncated packet
386 if len(self.bits) - i <= 20:
387 self.putwarn("Truncated", "!")
389 k = [self.get_sym(i), self.get_sym(i+5),
390 self.get_sym(i+10), self.get_sym(i+15)]
391 # TODO check bad symbols
392 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
397 lo = self.get_short()
398 hi = self.get_short()
399 return lo | (hi << 16)
401 def find_corrupted_sop(self, k):
402 # Start of packet are valid even if they have only 3 correct symbols
404 for seq in START_OF_PACKETS.keys():
405 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
406 return START_OF_PACKETS[seq]
410 for i in range(len(self.bits) - 19):
411 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
412 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
413 sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
415 sym = self.find_corrupted_sop(k)
416 # We have an interesting symbol sequence
418 # annotate the preamble
419 self.putx(0, i, [1, ['Preamble', '...']])
420 # annotate each symbol
421 self.rec_sym(i, k[0])
422 self.rec_sym(i+5, k[1])
423 self.rec_sym(i+10, k[2])
424 self.rec_sym(i+15, k[3])
425 if sym == 'Hard Reset':
427 return -1 # Hard reset
428 elif sym == 'Cable Reset':
430 return -1 # Cable reset
432 self.putx(i, i+20, [2, [sym, 'S']])
434 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
435 self.text += "Junk???"
436 self.putwarn("No start of packet found", "XXX")
437 return -1 # No Start Of Packet
439 def __init__(self, **kwargs):
440 self.samplerate = None
446 self.startsample = None
450 self.half_one = False
453 def metadata(self, key, value):
454 if key == srd.SRD_CONF_SAMPLERATE:
455 self.samplerate = value
456 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
457 self.maxbit = self.us2samples(3 * UI_US)
458 # duration threshold between half 1 and 0
459 self.threshold = self.us2samples(THRESHOLD_US)
462 self.out_python = self.register(srd.OUTPUT_PYTHON)
463 self.out_ann = self.register(srd.OUTPUT_ANN)
464 self.out_binary = self.register(srd.OUTPUT_BINARY)
465 self.out_bitrate = self.register(
467 meta=(int, 'Bitrate', 'Bitrate during the packet')
470 def us2samples(self, us):
471 if self.samplerate is None:
472 raise Exception("Need the samplerate.")
473 return int(us * self.samplerate / 1000000)
475 def decode_packet(self):
480 if len(self.edges) < 50:
481 return # Not a real PD packet
484 tstamp = float(self.startsample) / self.samplerate
485 self.text += "#%-4d (%8.6fms): " % (self.packet_seq, tstamp*1000)
487 self.idx = self.scan_eop()
489 # Full text trace of the issue
490 self.putx(0, self.idx, [12, [self.text, '...']])
491 return # No real packet: ABORT
494 self.head = self.get_short()
495 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
498 # Decode data payload
499 for i in range(self.head_count()):
500 self.data.append(self.get_word())
501 self.putx(self.idx-40, self.idx,
502 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
503 self.putpayload(self.idx-40, self.idx, i)
506 self.crc = self.get_word()
507 ccrc = self.compute_crc32()
509 self.putwarn("Bad CRC %08x != %08x" % (self.crc, ccrc), "CRC!")
510 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
513 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
514 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
517 self.putwarn("No EOP", "EOP!")
519 if self.options['fulltext'] == 'yes':
520 self.putx(0, self.idx, [12, [self.text, '...']])
522 # Meta data for bitrate
523 ss, es = self.edges[0], self.edges[-1]
524 bitrate = self.samplerate*len(self.bits) / float(es - ss)
525 self.put(es, ss, self.out_bitrate, int(bitrate))
526 # Raw binary data (BMC decoded)
527 self.put(es, ss, self.out_binary, (0, bytes(self.bits)))
529 def decode(self, ss, es, data):
530 if self.samplerate is None:
531 raise Exception("Cannot decode without samplerate.")
532 for (self.samplenum, pins) in data:
534 if self.oldpins == pins:
537 self.oldpins, (cc, ) = pins, pins
539 # First sample of the packet, just record the start date
540 if not self.startsample:
541 self.startsample = self.samplenum
542 self.previous = self.samplenum
545 diff = self.samplenum - self.previous
547 # Large idle: use it as the end of packet
548 if diff > self.maxbit:
549 # the last edge of the packet
550 self.edges.append(self.previous)
553 # Reset for next packet
554 self.startsample = self.samplenum
558 self.half_one = False
560 else: # add the bit to the packet
561 is_zero = diff > self.threshold
562 if is_zero and not self.half_one:
564 self.edges.append(self.previous)
565 elif not is_zero and self.half_one:
567 self.edges.append(self.start_one)
568 self.half_one = False
569 elif not is_zero and not self.half_one:
571 self.start_one = self.previous
572 else: # Invalid BMC sequence
573 self.bad.append((self.start_one, self.previous))
574 # TODO try to recover
576 self.edges.append(self.previous)
577 self.half_one = False
578 self.previous = self.samplenum