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