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