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']
192 class SamplerateError(Exception):
195 class Decoder(srd.Decoder):
197 id = 'usb_power_delivery'
199 longname = 'USB Power Delivery'
200 desc = 'USB Power Delivery protocol.'
205 {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
208 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
209 'default': 'no', 'values': ('yes', 'no')},
212 ('type', 'Packet Type'),
213 ('Preamble', 'Preamble'),
214 ('SOP', 'Start of Packet'),
218 ('EOP', 'End Of Packet'),
219 ('Sym', '4b5b symbols'),
220 ('warnings', 'Warnings'),
221 ('src', 'Source Message'),
222 ('snk', 'Sink Message'),
223 ('payload', 'Payload'),
224 ('text', 'Plain text'),
227 ('4B5B', 'symbols', (7, )),
228 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
229 ('payload', 'Payload', (11, )),
230 ('type', 'Type', (0, 9, 10, )),
231 ('warnings', 'Warnings', (8, )),
232 ('text', 'Full text', (12, )),
235 ('raw-data', 'RAW binary data'),
238 def get_request(self, rdo):
239 pos = (rdo >> 28) & 7
240 op_ma = ((rdo >> 10) & 0x3ff) * 10
241 max_ma = (rdo & 0x3ff) * 10
243 for f in sorted(RDO_FLAGS.keys(), reverse = True):
245 flags += ' [' + RDO_FLAGS[f] + ']'
246 return '(PDO #%d: %s) %gA (operating) / %gA (max)%s' % (pos, STORED_PDOS[pos], op_ma/1000.0, max_ma/1000.0, flags)
248 def get_source_sink_cap(self, pdo, idx):
252 mv = ((pdo >> 10) & 0x3ff) * 0.05
253 ma = ((pdo >> 0) & 0x3ff) * 0.01
254 p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
255 STORED_PDOS[idx] = '%s %gV' % (t_name, mv)
258 minv = ((pdo >> 10) & 0x3ff) * 0.05
259 maxv = ((pdo >> 20) & 0x3ff) * 0.05
260 mw = ((pdo >> 0) & 0x3ff) * 0.25
261 p = '%g/%gV %gW' % (minv, maxv, mw)
262 STORED_PDOS[idx] = '%s %g/%gV' % (t_name, minv, maxv)
265 minv = ((pdo >> 10) & 0x3ff) * 0.05
266 maxv = ((pdo >> 20) & 0x3ff) * 0.05
267 ma = ((pdo >> 0) & 0x3ff) * 0.01
268 p = '%g/%gV %gA' % (minv, maxv, ma)
269 STORED_PDOS[idx] = '%s %g/%gV' % (t_name, minv, maxv)
273 t_name = 'Programmable'
274 p = 'TODO: PPS support'
276 t_name = 'Reserved APDO: '+bin(t2)
279 for f in sorted(PDO_FLAGS.keys(), reverse = True):
281 flags += ' [' + PDO_FLAGS[f] + ']'
282 return '[%s] %s%s' % (t_name, 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):
316 txt = '['+str(idx+1)+'] '
318 txt += self.get_request(self.data[idx])
319 elif t == 1 or t == 4:
320 txt += self.get_source_sink_cap(self.data[idx], idx+1)
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 SOP_SEQUENCES:
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.get(k, 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
443 self.samplerate = None
447 self.startsample = None
451 self.half_one = False
454 def metadata(self, key, value):
455 if key == srd.SRD_CONF_SAMPLERATE:
456 self.samplerate = value
457 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
458 self.maxbit = self.us2samples(3 * UI_US)
459 # duration threshold between half 1 and 0
460 self.threshold = self.us2samples(THRESHOLD_US)
463 self.out_python = self.register(srd.OUTPUT_PYTHON)
464 self.out_ann = self.register(srd.OUTPUT_ANN)
465 self.out_binary = self.register(srd.OUTPUT_BINARY)
466 self.out_bitrate = self.register(
468 meta=(int, 'Bitrate', 'Bitrate during the packet')
471 def us2samples(self, us):
472 return int(us * self.samplerate / 1000000)
474 def decode_packet(self):
479 if len(self.edges) < 50:
480 return # Not a real PD packet
483 tstamp = float(self.startsample) / self.samplerate
484 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
486 self.idx = self.scan_eop()
488 # Full text trace of the issue
489 self.putx(0, self.idx, [12, [self.text, '...']])
490 return # No real packet: ABORT
493 self.head = self.get_short()
494 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
497 # Decode data payload
498 for i in range(self.head_count()):
499 self.data.append(self.get_word())
500 self.putx(self.idx-40, self.idx,
501 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
502 self.putpayload(self.idx-40, self.idx, i)
505 self.crc = self.get_word()
506 ccrc = self.compute_crc32()
508 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
509 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
512 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
513 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
516 self.putwarn('No EOP', 'EOP!')
518 if self.options['fulltext'] == 'yes':
519 self.putx(0, self.idx, [12, [self.text, '...']])
521 # Meta data for bitrate
522 ss, es = self.edges[0], self.edges[-1]
523 bitrate = self.samplerate*len(self.bits) / float(es - ss)
524 self.put(es, ss, self.out_bitrate, int(bitrate))
525 # Raw binary data (BMC decoded)
526 self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
529 if not self.samplerate:
530 raise SamplerateError('Cannot decode without samplerate.')
534 # First sample of the packet, just record the start date
535 if not self.startsample:
536 self.startsample = self.samplenum
537 self.previous = self.samplenum
540 diff = self.samplenum - self.previous
542 # Large idle: use it as the end of packet
543 if diff > self.maxbit:
544 # the last edge of the packet
545 self.edges.append(self.previous)
548 # Reset for next packet
549 self.startsample = self.samplenum
553 self.half_one = False
555 else: # add the bit to the packet
556 is_zero = diff > self.threshold
557 if is_zero and not self.half_one:
559 self.edges.append(self.previous)
560 elif not is_zero and self.half_one:
562 self.edges.append(self.start_one)
563 self.half_one = False
564 elif not is_zero and not self.half_one:
566 self.start_one = self.previous
567 else: # Invalid BMC sequence
568 self.bad.append((self.start_one, self.previous))
569 # TODO try to recover
571 self.edges.append(self.previous)
572 self.half_one = False
573 self.previous = self.samplenum