]> sigrok.org Git - libsigrokdecode.git/blob - 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
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 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 ## 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 ##
20
21 import sigrokdecode as srd
22 import struct
23 import zlib   # for crc32
24
25 # BMC encoding with a 600Khz datarate
26 UI_US = 1000000/600000.0
27
28 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
29 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
30
31 # Control Message type
32 CTRL_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
52 DATA_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
61 DEC4B5B = [
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 ]
95 SYM_ERR = 0x10
96 SYNC1 = 0x11
97 SYNC2 = 0x12
98 SYNC3 = 0x13
99 RST1 = 0x14
100 RST2 = 0x15
101 EOP = 0x16
102 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
103 HRST_CODES = [RST1, RST1, RST1, RST2]
104
105 START_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
115 SYM_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
141 RDO_FLAGS = {
142     (1 << 24): "no_suspend",
143     (1 << 25): "comm_cap",
144     (1 << 26): "cap_mismatch",
145     (1 << 27): "give_back"
146 }
147 PDO_TYPE = ["", "BATT:", "VAR:", "<bad>"]
148 PDO_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
156 BIST_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
167 VDM_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 }
179 VDM_ACK = ["REQ", "ACK", "NAK", "BSY"]
180
181
182 class 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