]> sigrok.org Git - libsigrokdecode.git/blame - decoders/usb_power_delivery/pd.py
usb_power_delivery: Move stored PDO's inside Decoder class
[libsigrokdecode.git] / decoders / usb_power_delivery / pd.py
CommitLineData
ced6589f
VP
1##
2## This file is part of the libsigrokdecode project.
3##
fddb3114 4## Copyright (C) 2015 Google, Inc
4083a379 5## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl>
ced6589f
VP
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##
fddb3114 17## You should have received a copy of the GNU General Public License
4539e9ca 18## along with this program; if not, see <http://www.gnu.org/licenses/>.
fddb3114 19##
ced6589f
VP
20
21import sigrokdecode as srd
22import struct
23import zlib # for crc32
24
79065c6f 25# BMC encoding with a 600kHz datarate
ced6589f
VP
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 = {
89b9aaf7
VP
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'
ced6589f
VP
49}
50
51# Data message type
52DATA_TYPES = {
89b9aaf7
VP
53 1: 'SOURCE CAP',
54 2: 'REQUEST',
55 3: 'BIST',
56 4: 'SINK CAP',
57 15: 'VDM'
ced6589f
VP
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
f30fdbb6
GS
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]
ced6589f 114START_OF_PACKETS = {
f30fdbb6
GS
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',
ced6589f
VP
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 = {
89b9aaf7
VP
151 (1 << 24): 'no_suspend',
152 (1 << 25): 'comm_cap',
153 (1 << 26): 'cap_mismatch',
154 (1 << 27): 'give_back'
ced6589f 155}
4083a379 156
ced6589f 157PDO_FLAGS = {
89b9aaf7
VP
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'
ced6589f
VP
163}
164
165BIST_MODES = {
89b9aaf7
VP
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',
ced6589f
VP
174}
175
176VDM_CMDS = {
89b9aaf7
VP
177 1: 'Disc Ident',
178 2: 'Disc SVID',
179 3: 'Disc Mode',
180 4: 'Enter Mode',
181 5: 'Exit Mode',
182 6: 'Attention',
ced6589f
VP
183 # 16..31: SVID Specific Commands
184 # DisplayPort Commands
89b9aaf7
VP
185 16: 'DP Status',
186 17: 'DP Configure',
ced6589f 187}
89b9aaf7 188VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
ced6589f 189
4083a379 190
669f30f4
UH
191class SamplerateError(Exception):
192 pass
193
ced6589f 194class Decoder(srd.Decoder):
bc6f82bb 195 api_version = 3
ced6589f
VP
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 = (
a886ba3b
P
204 {'id': 'cc1', 'name': 'CC1', 'desc': 'Control channel 1'},
205 )
206 optional_channels = (
207 {'id': 'cc2', 'name': 'CC2', 'desc': 'Control channel 2'},
ced6589f
VP
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
a886ba3b
P
240 stored_pdos = {}
241
242
ced6589f
VP
243 def get_request(self, rdo):
244 pos = (rdo >> 28) & 7
a886ba3b
P
245 op_ma = ((rdo >> 10) & 0x3ff) * 0.01
246 max_ma = (rdo & 0x3ff) * 0.01
89b9aaf7 247 flags = ''
57ba804a 248 for f in sorted(RDO_FLAGS.keys(), reverse = True):
ced6589f 249 if rdo & f:
4083a379 250 flags += ' [' + RDO_FLAGS[f] + ']'
a886ba3b
P
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)
ced6589f 255
4083a379
P
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)
a886ba3b 263 self.stored_pdos[idx] = '%s %gV' % (t_name, mv)
4083a379
P
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)
a886ba3b 270 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
4083a379
P
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)
a886ba3b 277 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
4083a379
P
278 elif t1 == 3:
279 t2 = (pdo >> 28) & 3
280 if t2 == 0:
281 t_name = 'Programmable'
282 p = 'TODO: PPS support'
283 else:
284 t_name = 'Reserved APDO: '+bin(t2)
285 p = ''
89b9aaf7 286 flags = ''
57ba804a 287 for f in sorted(PDO_FLAGS.keys(), reverse = True):
ced6589f 288 if pdo & f:
4083a379
P
289 flags += ' [' + PDO_FLAGS[f] + ']'
290 return '[%s] %s%s' % (t_name, p, flags)
ced6589f
VP
291
292 def get_vdm(self, idx, data):
293 if idx == 0: # VDM header
79065c6f
UH
294 vid = data >> 16
295 struct = data & (1 << 15)
89b9aaf7 296 txt = 'VDM'
79065c6f
UH
297 if struct: # Structured VDM
298 cmd = data & 0x1f
299 src = data & (1 << 5)
300 ack = (data >> 6) & 3
301 pos = (data >> 8) & 7
302 ver = (data >> 13) & 3
89b9aaf7
VP
303 txt = VDM_ACK[ack] + ' '
304 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
305 txt += ' pos %d' % (pos) if pos else ' '
79065c6f 306 else: # Unstructured VDM
89b9aaf7
VP
307 txt = 'unstruct [%04x]' % (data & 0x7fff)
308 txt += ' SVID:%04x' % (vid)
ced6589f 309 else: # VDM payload
89b9aaf7 310 txt = 'VDO:%08x' % (data)
ced6589f
VP
311 return txt
312
313 def get_bist(self, idx, data):
314 mode = data >> 28
315 counter = data & 0xffff
89b9aaf7 316 mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
ced6589f 317 if mode == 2:
89b9aaf7 318 mode_name = 'Counter[= %d]' % (counter)
ced6589f 319 # TODO check all 0 bits are 0 / emit warnings
89b9aaf7 320 return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
ced6589f
VP
321
322 def putpayload(self, s0, s1, idx):
323 t = self.head_type()
4083a379 324 txt = '['+str(idx+1)+'] '
ced6589f 325 if t == 2:
4083a379
P
326 txt += self.get_request(self.data[idx])
327 elif t == 1 or t == 4:
328 txt += self.get_source_sink_cap(self.data[idx], idx+1)
ced6589f 329 elif t == 15:
4083a379 330 txt += self.get_vdm(idx, self.data[idx])
ced6589f 331 elif t == 3:
4083a379 332 txt += self.get_bist(idx, self.data[idx])
ced6589f 333 self.putx(s0, s1, [11, [txt, txt]])
89b9aaf7 334 self.text += ' - ' + txt
ced6589f
VP
335
336 def puthead(self):
337 ann_type = 9 if self.head_power_role() else 10
89b9aaf7 338 role = 'SRC' if self.head_power_role() else 'SNK'
ced6589f 339 if self.head_data_role() != self.head_power_role():
89b9aaf7 340 role += '/DFP' if self.head_data_role() else '/UFP'
ced6589f
VP
341 t = self.head_type()
342 if self.head_count() == 0:
343 shortm = CTRL_TYPES[t]
344 else:
89b9aaf7 345 shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
ced6589f 346
89b9aaf7 347 longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
ced6589f
VP
348 self.putx(0, -1, [ann_type, [longm, shortm]])
349 self.text += longm
350
351 def head_id(self):
352 return (self.head >> 9) & 7
353
354 def head_power_role(self):
355 return (self.head >> 8) & 1
356
357 def head_data_role(self):
358 return (self.head >> 5) & 1
359
360 def head_rev(self):
361 return ((self.head >> 6) & 3) + 1
362
363 def head_type(self):
364 return self.head & 0xF
365
366 def head_count(self):
367 return (self.head >> 12) & 7
368
369 def putx(self, s0, s1, data):
370 self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
371
372 def putwarn(self, longm, shortm):
373 self.putx(0, -1, [8, [longm, shortm]])
374
375 def compute_crc32(self):
89b9aaf7 376 bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
ced6589f
VP
377 *tuple([d & 0xffffffff for d in self.data]))
378 return zlib.crc32(bdata)
379
380 def rec_sym(self, i, sym):
381 self.putx(i, i+5, [7, SYM_NAME[sym]])
382
383 def get_sym(self, i, rec=True):
384 v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
385 (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
386 sym = DEC4B5B[v]
387 if rec:
388 self.rec_sym(i, sym)
389 return sym
390
391 def get_short(self):
392 i = self.idx
393 # Check it's not a truncated packet
394 if len(self.bits) - i <= 20:
89b9aaf7 395 self.putwarn('Truncated', '!')
ced6589f
VP
396 return 0x0BAD
397 k = [self.get_sym(i), self.get_sym(i+5),
398 self.get_sym(i+10), self.get_sym(i+15)]
399 # TODO check bad symbols
400 val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
401 self.idx += 20
402 return val
403
404 def get_word(self):
405 lo = self.get_short()
406 hi = self.get_short()
407 return lo | (hi << 16)
408
409 def find_corrupted_sop(self, k):
410 # Start of packet are valid even if they have only 3 correct symbols
411 # out of 4.
f30fdbb6 412 for seq in SOP_SEQUENCES:
ced6589f
VP
413 if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
414 return START_OF_PACKETS[seq]
415 return None
416
417 def scan_eop(self):
418 for i in range(len(self.bits) - 19):
419 k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
420 self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
279331bd 421 sym = START_OF_PACKETS.get(k, None)
ced6589f
VP
422 if not sym:
423 sym = self.find_corrupted_sop(k)
424 # We have an interesting symbol sequence
425 if sym:
426 # annotate the preamble
427 self.putx(0, i, [1, ['Preamble', '...']])
428 # annotate each symbol
429 self.rec_sym(i, k[0])
430 self.rec_sym(i+5, k[1])
431 self.rec_sym(i+10, k[2])
432 self.rec_sym(i+15, k[3])
433 if sym == 'Hard Reset':
89b9aaf7 434 self.text += 'HRST'
ced6589f
VP
435 return -1 # Hard reset
436 elif sym == 'Cable Reset':
89b9aaf7 437 self.text += 'CRST'
ced6589f
VP
438 return -1 # Cable reset
439 else:
440 self.putx(i, i+20, [2, [sym, 'S']])
441 return i+20
442 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
89b9aaf7
VP
443 self.text += 'Junk???'
444 self.putwarn('No start of packet found', 'XXX')
ced6589f
VP
445 return -1 # No Start Of Packet
446
92b7b49f 447 def __init__(self):
10aeb8ea
GS
448 self.reset()
449
450 def reset(self):
ced6589f
VP
451 self.samplerate = None
452 self.idx = 0
453 self.packet_seq = 0
ced6589f 454 self.previous = 0
ced6589f
VP
455 self.startsample = None
456 self.bits = []
457 self.edges = []
458 self.bad = []
459 self.half_one = False
460 self.start_one = 0
a886ba3b 461 self.stored_pdos = {}
ced6589f
VP
462
463 def metadata(self, key, value):
464 if key == srd.SRD_CONF_SAMPLERATE:
465 self.samplerate = value
466 # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
467 self.maxbit = self.us2samples(3 * UI_US)
468 # duration threshold between half 1 and 0
469 self.threshold = self.us2samples(THRESHOLD_US)
470
471 def start(self):
472 self.out_python = self.register(srd.OUTPUT_PYTHON)
473 self.out_ann = self.register(srd.OUTPUT_ANN)
474 self.out_binary = self.register(srd.OUTPUT_BINARY)
475 self.out_bitrate = self.register(
476 srd.OUTPUT_META,
477 meta=(int, 'Bitrate', 'Bitrate during the packet')
478 )
479
480 def us2samples(self, us):
ced6589f
VP
481 return int(us * self.samplerate / 1000000)
482
483 def decode_packet(self):
484 self.data = []
485 self.idx = 0
89b9aaf7 486 self.text = ''
ced6589f
VP
487
488 if len(self.edges) < 50:
489 return # Not a real PD packet
490
491 self.packet_seq += 1
492 tstamp = float(self.startsample) / self.samplerate
89b9aaf7 493 self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
ced6589f
VP
494
495 self.idx = self.scan_eop()
496 if self.idx < 0:
497 # Full text trace of the issue
498 self.putx(0, self.idx, [12, [self.text, '...']])
499 return # No real packet: ABORT
500
501 # Packet header
502 self.head = self.get_short()
503 self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
504 self.puthead()
505
506 # Decode data payload
507 for i in range(self.head_count()):
508 self.data.append(self.get_word())
509 self.putx(self.idx-40, self.idx,
510 [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
511 self.putpayload(self.idx-40, self.idx, i)
512
513 # CRC check
514 self.crc = self.get_word()
515 ccrc = self.compute_crc32()
516 if self.crc != ccrc:
89b9aaf7 517 self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
ced6589f
VP
518 self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
519
520 # End of Packet
521 if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
522 self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
523 self.idx += 5
524 else:
89b9aaf7 525 self.putwarn('No EOP', 'EOP!')
ced6589f
VP
526 # Full text trace
527 if self.options['fulltext'] == 'yes':
528 self.putx(0, self.idx, [12, [self.text, '...']])
529
530 # Meta data for bitrate
531 ss, es = self.edges[0], self.edges[-1]
532 bitrate = self.samplerate*len(self.bits) / float(es - ss)
533 self.put(es, ss, self.out_bitrate, int(bitrate))
534 # Raw binary data (BMC decoded)
2824e811 535 self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
ced6589f 536
bc6f82bb 537 def decode(self):
033e7d4d 538 if not self.samplerate:
669f30f4 539 raise SamplerateError('Cannot decode without samplerate.')
bc6f82bb 540 while True:
a886ba3b
P
541 pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': 100*1000}])
542
ced6589f
VP
543
544 # First sample of the packet, just record the start date
545 if not self.startsample:
546 self.startsample = self.samplenum
547 self.previous = self.samplenum
548 continue
549
550 diff = self.samplenum - self.previous
551
552 # Large idle: use it as the end of packet
553 if diff > self.maxbit:
554 # the last edge of the packet
555 self.edges.append(self.previous)
556 # Export the packet
557 self.decode_packet()
558 # Reset for next packet
559 self.startsample = self.samplenum
560 self.bits = []
561 self.edges = []
562 self.bad = []
563 self.half_one = False
564 self.start_one = 0
565 else: # add the bit to the packet
566 is_zero = diff > self.threshold
567 if is_zero and not self.half_one:
568 self.bits.append(0)
569 self.edges.append(self.previous)
570 elif not is_zero and self.half_one:
571 self.bits.append(1)
572 self.edges.append(self.start_one)
573 self.half_one = False
574 elif not is_zero and not self.half_one:
575 self.half_one = True
576 self.start_one = self.previous
577 else: # Invalid BMC sequence
578 self.bad.append((self.start_one, self.previous))
579 # TODO try to recover
580 self.bits.append(0)
581 self.edges.append(self.previous)
582 self.half_one = False
583 self.previous = self.samplenum