]> sigrok.org Git - libsigrokdecode.git/blame_incremental - decoders/usb_power_delivery/pd.py
usb_power_delivery: wait 1ms instead of 100k samples
[libsigrokdecode.git] / decoders / usb_power_delivery / pd.py
... / ...
CommitLineData
1##
2## This file is part of the libsigrokdecode project.
3##
4## Copyright (C) 2015 Google, Inc
5## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl>
6##
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.
11##
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.
16##
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/>.
19##
20
21import sigrokdecode as srd
22import struct
23import zlib # for crc32
24
25# BMC encoding with a 600kHz datarate
26UI_US = 1000000/600000.0
27
28# Threshold to discriminate half-1 from 0 in Binary Mark Conding
29THRESHOLD_US = (UI_US + 2 * UI_US) / 2
30
31# Control Message type
32CTRL_TYPES = {
33 0: 'reserved',
34 1: 'GOOD CRC',
35 2: 'GOTO MIN',
36 3: 'ACCEPT',
37 4: 'REJECT',
38 5: 'PING',
39 6: 'PS RDY',
40 7: 'GET SOURCE CAP',
41 8: 'GET SINK CAP',
42 9: 'DR SWAP',
43 10: 'PR SWAP',
44 11: 'VCONN SWAP',
45 12: 'WAIT',
46 13: 'SOFT RESET',
47 14: 'reserved',
48 15: 'reserved'
49}
50
51# Data message type
52DATA_TYPES = {
53 1: 'SOURCE CAP',
54 2: 'REQUEST',
55 3: 'BIST',
56 4: 'SINK CAP',
57 15: 'VDM'
58}
59
60# 4b5b encoding of the symbols
61DEC4B5B = [
62 0x10, # Error 00000
63 0x10, # Error 00001
64 0x10, # Error 00010
65 0x10, # Error 00011
66 0x10, # Error 00100
67 0x10, # Error 00101
68 0x13, # Sync-3 00110
69 0x14, # RST-1 00111
70 0x10, # Error 01000
71 0x01, # 1 = 0001 01001
72 0x04, # 4 = 0100 01010
73 0x05, # 5 = 0101 01011
74 0x10, # Error 01100
75 0x16, # EOP 01101
76 0x06, # 6 = 0110 01110
77 0x07, # 7 = 0111 01111
78 0x10, # Error 10000
79 0x12, # Sync-2 10001
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
86 0x11, # Sync-1 11000
87 0x15, # RST-2 11001
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
93 0x10, # Error 11111
94]
95SYM_ERR = 0x10
96SYNC1 = 0x11
97SYNC2 = 0x12
98SYNC3 = 0x13
99RST1 = 0x14
100RST2 = 0x15
101EOP = 0x16
102SYNC_CODES = [SYNC1, SYNC2, SYNC3]
103HRST_CODES = [RST1, RST1, RST1, RST2]
104
105SOP_SEQUENCES = [
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),
113]
114START_OF_PACKETS = {
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',
122}
123
124SYM_NAME = [
125 ['0x0', '0'],
126 ['0x1', '1'],
127 ['0x2', '2'],
128 ['0x3', '3'],
129 ['0x4', '4'],
130 ['0x5', '5'],
131 ['0x6', '6'],
132 ['0x7', '7'],
133 ['0x8', '8'],
134 ['0x9', '9'],
135 ['0xA', 'A'],
136 ['0xB', 'B'],
137 ['0xC', 'C'],
138 ['0xD', 'D'],
139 ['0xE', 'E'],
140 ['0xF', 'F'],
141 ['ERROR', 'X'],
142 ['SYNC-1', 'S1'],
143 ['SYNC-2', 'S2'],
144 ['SYNC-3', 'S3'],
145 ['RST-1', 'R1'],
146 ['RST-2', 'R2'],
147 ['EOP', '#'],
148]
149
150RDO_FLAGS = {
151 (1 << 24): 'no_suspend',
152 (1 << 25): 'comm_cap',
153 (1 << 26): 'cap_mismatch',
154 (1 << 27): 'give_back'
155}
156
157PDO_FLAGS = {
158 (1 << 29): 'dual_role_power',
159 (1 << 28): 'suspend',
160 (1 << 27): 'ext',
161 (1 << 26): 'comm_cap',
162 (1 << 25): 'dual_role_data'
163}
164
165BIST_MODES = {
166 0: 'Receiver',
167 1: 'Transmit',
168 2: 'Counters',
169 3: 'Carrier 0',
170 4: 'Carrier 1',
171 5: 'Carrier 2',
172 6: 'Carrier 3',
173 7: 'Eye',
174}
175
176VDM_CMDS = {
177 1: 'Disc Ident',
178 2: 'Disc SVID',
179 3: 'Disc Mode',
180 4: 'Enter Mode',
181 5: 'Exit Mode',
182 6: 'Attention',
183 # 16..31: SVID Specific Commands
184 # DisplayPort Commands
185 16: 'DP Status',
186 17: 'DP Configure',
187}
188VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
189
190
191class SamplerateError(Exception):
192 pass
193
194class Decoder(srd.Decoder):
195 api_version = 3
196 id = 'usb_power_delivery'
197 name = 'USB PD'
198 longname = 'USB Power Delivery'
199 desc = 'USB Power Delivery protocol.'
200 license = 'gplv2+'
201 inputs = ['logic']
202 outputs = ['usb_pd']
203 channels = (
204 {'id': 'cc1', 'name': 'CC1', 'desc': 'Configuration Channel 1'},
205 )
206 optional_channels = (
207 {'id': 'cc2', 'name': 'CC2', 'desc': 'Configuration Channel 2'},
208 )
209 options = (
210 {'id': 'fulltext', 'desc': 'full text decoding of the packet',
211 'default': 'no', 'values': ('yes', 'no')},
212 )
213 annotations = (
214 ('type', 'Packet Type'),
215 ('Preamble', 'Preamble'),
216 ('SOP', 'Start of Packet'),
217 ('Head', 'Header'),
218 ('Data', 'Data'),
219 ('CRC', 'Checksum'),
220 ('EOP', 'End Of Packet'),
221 ('Sym', '4b5b symbols'),
222 ('warnings', 'Warnings'),
223 ('src', 'Source Message'),
224 ('snk', 'Sink Message'),
225 ('payload', 'Payload'),
226 ('text', 'Plain text'),
227 )
228 annotation_rows = (
229 ('4B5B', 'symbols', (7, )),
230 ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
231 ('payload', 'Payload', (11, )),
232 ('type', 'Type', (0, 9, 10, )),
233 ('warnings', 'Warnings', (8, )),
234 ('text', 'Full text', (12, )),
235 )
236 binary = (
237 ('raw-data', 'RAW binary data'),
238 )
239
240 stored_pdos = {}
241
242
243 def get_request(self, rdo):
244 pos = (rdo >> 28) & 7
245 op_ma = ((rdo >> 10) & 0x3ff) * 0.01
246 max_ma = (rdo & 0x3ff) * 0.01
247 flags = ''
248 for f in sorted(RDO_FLAGS.keys(), reverse = True):
249 if rdo & f:
250 flags += ' [' + RDO_FLAGS[f] + ']'
251 if pos in self.stored_pdos.keys():
252 return '(PDO #%d: %s) %gA (operating) / %gA (max)%s' % (pos, self.stored_pdos[pos], op_ma, max_ma, flags)
253 else:
254 return '(PDO #%d) %gA (operating) / %gA (max)%s' % (pos, op_ma, max_ma, flags)
255
256 def get_source_sink_cap(self, pdo, idx):
257 t1 = (pdo >> 30) & 3
258 if t1 == 0:
259 t_name = 'Fixed'
260 mv = ((pdo >> 10) & 0x3ff) * 0.05
261 ma = ((pdo >> 0) & 0x3ff) * 0.01
262 p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
263 self.stored_pdos[idx] = '%s %gV' % (t_name, mv)
264 elif t1 == 1:
265 t_name = 'Battery'
266 minv = ((pdo >> 10) & 0x3ff) * 0.05
267 maxv = ((pdo >> 20) & 0x3ff) * 0.05
268 mw = ((pdo >> 0) & 0x3ff) * 0.25
269 p = '%g/%gV %gW' % (minv, maxv, mw)
270 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
271 elif t1 == 2:
272 t_name = 'Variable'
273 minv = ((pdo >> 10) & 0x3ff) * 0.05
274 maxv = ((pdo >> 20) & 0x3ff) * 0.05
275 ma = ((pdo >> 0) & 0x3ff) * 0.01
276 p = '%g/%gV %gA' % (minv, maxv, ma)
277 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
278 elif t1 == 3:
279 t2 = (pdo >> 28) & 3
280 if t2 == 0:
281 t_name = 'Programmable|PPS'
282 minv = ((pdo >> 8) & 0xff) * 0.1
283 maxv = ((pdo >> 17) & 0xff) * 0.1
284 ma = ((pdo >> 0) & 0xff) * 0.05
285 p = '%g/%gV %gA' % (minv, maxv, ma)
286 if (pdo >> 27) & 0x1:
287 p += ' [limited]'
288 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
289 else:
290 t_name = 'Reserved APDO: '+bin(t2)
291 p = '[raw: %s]' % (bin(pdo))
292 self.stored_pdos[idx] = '%s %s' % (t_name, p)
293 flags = ''
294 for f in sorted(PDO_FLAGS.keys(), reverse = True):
295 if pdo & f:
296 flags += ' [' + PDO_FLAGS[f] + ']'
297 return '[%s] %s%s' % (t_name, p, flags)
298
299 def get_vdm(self, idx, data):
300 if idx == 0: # VDM header
301 vid = data >> 16
302 struct = data & (1 << 15)
303 txt = 'VDM'
304 if struct: # Structured VDM
305 cmd = data & 0x1f
306 src = data & (1 << 5)
307 ack = (data >> 6) & 3
308 pos = (data >> 8) & 7
309 ver = (data >> 13) & 3
310 txt = VDM_ACK[ack] + ' '
311 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
312 txt += ' pos %d' % (pos) if pos else ' '
313 else: # Unstructured VDM
314 txt = 'unstruct [%04x]' % (data & 0x7fff)
315 txt += ' SVID:%04x' % (vid)
316 else: # VDM payload
317 txt = 'VDO:%08x' % (data)
318 return txt
319
320 def get_bist(self, idx, data):
321 mode = data >> 28
322 counter = data & 0xffff
323 mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
324 if mode == 2:
325 mode_name = 'Counter[= %d]' % (counter)
326 # TODO check all 0 bits are 0 / emit warnings
327 return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
328
329 def putpayload(self, s0, s1, idx):
330 t = self.head_type()
331 txt = '['+str(idx+1)+'] '
332 if t == 2:
333 txt += self.get_request(self.data[idx])
334 elif t == 1 or t == 4:
335 txt += self.get_source_sink_cap(self.data[idx], idx+1)
336 elif t == 15:
337 txt += self.get_vdm(idx, self.data[idx])
338 elif t == 3:
339 txt += self.get_bist(idx, self.data[idx])
340 self.putx(s0, s1, [11, [txt, txt]])
341 self.text += ' - ' + txt
342
343 def puthead(self):
344 ann_type = 9 if self.head_power_role() else 10
345 role = 'SRC' if self.head_power_role() else 'SNK'
346 if self.head_data_role() != self.head_power_role():
347 role += '/DFP' if self.head_data_role() else '/UFP'
348 t = self.head_type()
349 if self.head_count() == 0:
350 shortm = CTRL_TYPES[t]
351 else:
352 shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
353
354 longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
355 self.putx(0, -1, [ann_type, [longm, shortm]])
356 self.text += longm
357
358 def head_id(self):
359 return (self.head >> 9) & 7
360
361 def head_power_role(self):
362 return (self.head >> 8) & 1
363
364 def head_data_role(self):
365 return (self.head >> 5) & 1
366
367 def head_rev(self):
368 return ((self.head >> 6) & 3) + 1
369
370 def head_type(self):
371 return self.head & 0xF
372
373 def head_count(self):
374 return (self.head >> 12) & 7
375
376 def putx(self, s0, s1, data):
377 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
378
379 def putwarn(self, longm, shortm):
380 self.putx(0, -1, [8, [longm, shortm]])
381
382 def compute_crc32(self):
383 bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
384 *tuple([d & 0xffffffff for d in self.data]))
385 return zlib.crc32(bdata)
386
387 def rec_sym(self, i, sym):
388 self.putx(i, i+5, [7, SYM_NAME[sym]])
389
390 def get_sym(self, i, rec=True):
391 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
392 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
393 sym = DEC4B5B[v]
394 if rec:
395 self.rec_sym(i, sym)
396 return sym
397
398 def get_short(self):
399 i = self.idx
400 # Check it's not a truncated packet
401 if len(self.bits) - i <= 20:
402 self.putwarn('Truncated', '!')
403 return 0x0BAD
404 k = [self.get_sym(i), self.get_sym(i+5),
405 self.get_sym(i+10), self.get_sym(i+15)]
406 # TODO check bad symbols
407 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
408 self.idx += 20
409 return val
410
411 def get_word(self):
412 lo = self.get_short()
413 hi = self.get_short()
414 return lo | (hi << 16)
415
416 def find_corrupted_sop(self, k):
417 # Start of packet are valid even if they have only 3 correct symbols
418 # out of 4.
419 for seq in SOP_SEQUENCES:
420 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
421 return START_OF_PACKETS[seq]
422 return None
423
424 def scan_eop(self):
425 for i in range(len(self.bits) - 19):
426 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
427 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
428 sym = START_OF_PACKETS.get(k, None)
429 if not sym:
430 sym = self.find_corrupted_sop(k)
431 # We have an interesting symbol sequence
432 if sym:
433 # annotate the preamble
434 self.putx(0, i, [1, ['Preamble', '...']])
435 # annotate each symbol
436 self.rec_sym(i, k[0])
437 self.rec_sym(i+5, k[1])
438 self.rec_sym(i+10, k[2])
439 self.rec_sym(i+15, k[3])
440 if sym == 'Hard Reset':
441 self.text += 'HRST'
442 return -1 # Hard reset
443 elif sym == 'Cable Reset':
444 self.text += 'CRST'
445 return -1 # Cable reset
446 else:
447 self.putx(i, i+20, [2, [sym, 'S']])
448 return i+20
449 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
450 self.text += 'Junk???'
451 self.putwarn('No start of packet found', 'XXX')
452 return -1 # No Start Of Packet
453
454 def __init__(self):
455 self.reset()
456
457 def reset(self):
458 self.samplerate = None
459 self.idx = 0
460 self.packet_seq = 0
461 self.previous = 0
462 self.startsample = None
463 self.bits = []
464 self.edges = []
465 self.bad = []
466 self.half_one = False
467 self.start_one = 0
468 self.stored_pdos = {}
469
470 def metadata(self, key, value):
471 if key == srd.SRD_CONF_SAMPLERATE:
472 self.samplerate = value
473 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
474 self.maxbit = self.us2samples(3 * UI_US)
475 # duration threshold between half 1 and 0
476 self.threshold = self.us2samples(THRESHOLD_US)
477
478 def start(self):
479 self.out_python = self.register(srd.OUTPUT_PYTHON)
480 self.out_ann = self.register(srd.OUTPUT_ANN)
481 self.out_binary = self.register(srd.OUTPUT_BINARY)
482 self.out_bitrate = self.register(
483 srd.OUTPUT_META,
484 meta=(int, 'Bitrate', 'Bitrate during the packet')
485 )
486
487 def us2samples(self, us):
488 return int(us * self.samplerate / 1000000)
489
490 def decode_packet(self):
491 self.data = []
492 self.idx = 0
493 self.text = ''
494
495 if len(self.edges) < 50:
496 return # Not a real PD packet
497
498 self.packet_seq += 1
499 tstamp = float(self.startsample) / self.samplerate
500 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
501
502 self.idx = self.scan_eop()
503 if self.idx < 0:
504 # Full text trace of the issue
505 self.putx(0, self.idx, [12, [self.text, '...']])
506 return # No real packet: ABORT
507
508 # Packet header
509 self.head = self.get_short()
510 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
511 self.puthead()
512
513 # Decode data payload
514 for i in range(self.head_count()):
515 self.data.append(self.get_word())
516 self.putx(self.idx-40, self.idx,
517 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
518 self.putpayload(self.idx-40, self.idx, i)
519
520 # CRC check
521 self.crc = self.get_word()
522 ccrc = self.compute_crc32()
523 if self.crc != ccrc:
524 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
525 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
526
527 # End of Packet
528 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
529 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
530 self.idx += 5
531 else:
532 self.putwarn('No EOP', 'EOP!')
533 # Full text trace
534 if self.options['fulltext'] == 'yes':
535 self.putx(0, self.idx, [12, [self.text, '...']])
536
537 # Meta data for bitrate
538 ss, es = self.edges[0], self.edges[-1]
539 bitrate = self.samplerate*len(self.bits) / float(es - ss)
540 self.put(es, ss, self.out_bitrate, int(bitrate))
541 # Raw binary data (BMC decoded)
542 self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
543
544 def decode(self):
545 if not self.samplerate:
546 raise SamplerateError('Cannot decode without samplerate.')
547 while True:
548 pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': int(self.samplerate/1e3)}])
549
550 # First sample of the packet, just record the start date
551 if not self.startsample:
552 self.startsample = self.samplenum
553 self.previous = self.samplenum
554 continue
555
556 diff = self.samplenum - self.previous
557
558 # Large idle: use it as the end of packet
559 if diff > self.maxbit:
560 # the last edge of the packet
561 self.edges.append(self.previous)
562 # Export the packet
563 self.decode_packet()
564 # Reset for next packet
565 self.startsample = self.samplenum
566 self.bits = []
567 self.edges = []
568 self.bad = []
569 self.half_one = False
570 self.start_one = 0
571 else: # add the bit to the packet
572 is_zero = diff > self.threshold
573 if is_zero and not self.half_one:
574 self.bits.append(0)
575 self.edges.append(self.previous)
576 elif not is_zero and self.half_one:
577 self.bits.append(1)
578 self.edges.append(self.start_one)
579 self.half_one = False
580 elif not is_zero and not self.half_one:
581 self.half_one = True
582 self.start_one = self.previous
583 else: # Invalid BMC sequence
584 self.bad.append((self.start_one, self.previous))
585 # TODO try to recover
586 self.bits.append(0)
587 self.edges.append(self.previous)
588 self.half_one = False
589 self.previous = self.samplenum