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