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