]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
USB Power Delivery protocol decoder
[libsigrokdecode.git] / decoders / usb_power_delivery / pd.py
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
17 import sigrokdecode as srd
18 import struct
19 import zlib   # for crc32
20
21 # BMC encoding with a 600Khz datarate
22 UI_US = 1000000/600000.0
23
24 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
25 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
26
27 # Control Message type
28 CTRL_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
48 DATA_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
57 DEC4B5B = [
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 ]
91 SYM_ERR = 0x10
92 SYNC1 = 0x11
93 SYNC2 = 0x12
94 SYNC3 = 0x13
95 RST1 = 0x14
96 RST2 = 0x15
97 EOP = 0x16
98 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
99 HRST_CODES = [RST1, RST1, RST1, RST2]
100
101 START_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
111 SYM_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
137 RDO_FLAGS = {
138     (1 << 24): "no_suspend",
139     (1 << 25): "comm_cap",
140     (1 << 26): "cap_mismatch",
141     (1 << 27): "give_back"
142 }
143 PDO_TYPE = ["", "BATT:", "VAR:", "<bad>"]
144 PDO_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
152 BIST_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
163 VDM_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 }
175 VDM_ACK = ["REQ", "ACK", "NAK", "BSY"]
176
177
178 class 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