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