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