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