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