]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
3258ac329674e3ac8f1f0751e34c8e051fd0b55d
[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, see <http://www.gnu.org/licenses/>.
18 ##
19
20 import sigrokdecode as srd
21 import struct
22 import zlib   # for crc32
23
24 # BMC encoding with a 600kHz datarate
25 UI_US = 1000000/600000.0
26
27 # Threshold to discriminate half-1 from 0 in Binary Mark Conding
28 THRESHOLD_US = (UI_US + 2 * UI_US) / 2
29
30 # Control Message type
31 CTRL_TYPES = {
32     0: 'reserved',
33     1: 'GOOD CRC',
34     2: 'GOTO MIN',
35     3: 'ACCEPT',
36     4: 'REJECT',
37     5: 'PING',
38     6: 'PS RDY',
39     7: 'GET SOURCE CAP',
40     8: 'GET SINK CAP',
41     9: 'DR SWAP',
42     10: 'PR SWAP',
43     11: 'VCONN SWAP',
44     12: 'WAIT',
45     13: 'SOFT RESET',
46     14: 'reserved',
47     15: 'reserved'
48 }
49
50 # Data message type
51 DATA_TYPES = {
52     1: 'SOURCE CAP',
53     2: 'REQUEST',
54     3: 'BIST',
55     4: 'SINK CAP',
56     15: 'VDM'
57 }
58
59 # 4b5b encoding of the symbols
60 DEC4B5B = [
61     0x10,   # Error      00000
62     0x10,   # Error      00001
63     0x10,   # Error      00010
64     0x10,   # Error      00011
65     0x10,   # Error      00100
66     0x10,   # Error      00101
67     0x13,   # Sync-3     00110
68     0x14,   # RST-1      00111
69     0x10,   # Error      01000
70     0x01,   # 1 = 0001   01001
71     0x04,   # 4 = 0100   01010
72     0x05,   # 5 = 0101   01011
73     0x10,   # Error      01100
74     0x16,   # EOP        01101
75     0x06,   # 6 = 0110   01110
76     0x07,   # 7 = 0111   01111
77     0x10,   # Error      10000
78     0x12,   # Sync-2     10001
79     0x08,   # 8 = 1000   10010
80     0x09,   # 9 = 1001   10011
81     0x02,   # 2 = 0010   10100
82     0x03,   # 3 = 0011   10101
83     0x0A,   # A = 1010   10110
84     0x0B,   # B = 1011   10111
85     0x11,   # Sync-1     11000
86     0x15,   # RST-2      11001
87     0x0C,   # C = 1100   11010
88     0x0D,   # D = 1101   11011
89     0x0E,   # E = 1110   11100
90     0x0F,   # F = 1111   11101
91     0x00,   # 0 = 0000   11110
92     0x10,   # Error      11111
93 ]
94 SYM_ERR = 0x10
95 SYNC1 = 0x11
96 SYNC2 = 0x12
97 SYNC3 = 0x13
98 RST1 = 0x14
99 RST2 = 0x15
100 EOP = 0x16
101 SYNC_CODES = [SYNC1, SYNC2, SYNC3]
102 HRST_CODES = [RST1, RST1, RST1, RST2]
103
104 START_OF_PACKETS = {
105     (SYNC1, SYNC1, SYNC1, SYNC2): 'SOP',
106     (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'",
107     (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"',
108     (SYNC1, RST2,  RST2,  SYNC3): "SOP' Debug",
109     (SYNC1, RST2,  SYNC3, SYNC2): 'SOP" Debug',
110     (RST1,  SYNC1, RST1,  SYNC3): 'Cable Reset',
111     (RST1,  RST1,  RST1,   RST2): 'Hard Reset',
112 }
113
114 SYM_NAME = [
115     ['0x0', '0'],
116     ['0x1', '1'],
117     ['0x2', '2'],
118     ['0x3', '3'],
119     ['0x4', '4'],
120     ['0x5', '5'],
121     ['0x6', '6'],
122     ['0x7', '7'],
123     ['0x8', '8'],
124     ['0x9', '9'],
125     ['0xA', 'A'],
126     ['0xB', 'B'],
127     ['0xC', 'C'],
128     ['0xD', 'D'],
129     ['0xE', 'E'],
130     ['0xF', 'F'],
131     ['ERROR', 'X'],
132     ['SYNC-1', 'S1'],
133     ['SYNC-2', 'S2'],
134     ['SYNC-3', 'S3'],
135     ['RST-1', 'R1'],
136     ['RST-2', 'R2'],
137     ['EOP', '#'],
138 ]
139
140 RDO_FLAGS = {
141     (1 << 24): 'no_suspend',
142     (1 << 25): 'comm_cap',
143     (1 << 26): 'cap_mismatch',
144     (1 << 27): 'give_back'
145 }
146 PDO_TYPE = ['', 'BATT:', 'VAR:', '<bad>']
147 PDO_FLAGS = {
148     (1 << 29): 'dual_role_power',
149     (1 << 28): 'suspend',
150     (1 << 27): 'ext',
151     (1 << 26): 'comm_cap',
152     (1 << 25): 'dual_role_data'
153 }
154
155 BIST_MODES = {
156         0: 'Receiver',
157         1: 'Transmit',
158         2: 'Counters',
159         3: 'Carrier 0',
160         4: 'Carrier 1',
161         5: 'Carrier 2',
162         6: 'Carrier 3',
163         7: 'Eye',
164 }
165
166 VDM_CMDS = {
167         1: 'Disc Ident',
168         2: 'Disc SVID',
169         3: 'Disc Mode',
170         4: 'Enter Mode',
171         5: 'Exit Mode',
172         6: 'Attention',
173         # 16..31: SVID Specific Commands
174         # DisplayPort Commands
175         16: 'DP Status',
176         17: 'DP Configure',
177 }
178 VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
179
180 class SamplerateError(Exception):
181     pass
182
183 class Decoder(srd.Decoder):
184     api_version = 3
185     id = 'usb_power_delivery'
186     name = 'USB PD'
187     longname = 'USB Power Delivery'
188     desc = 'USB Power Delivery protocol.'
189     license = 'gplv2+'
190     inputs = ['logic']
191     outputs = ['usb_pd']
192     channels = (
193         {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
194     )
195     options = (
196         {'id': 'fulltext', 'desc': 'full text decoding of the packet',
197          'default': 'no', 'values': ('yes', 'no')},
198     )
199     annotations = (
200         ('type', 'Packet Type'),
201         ('Preamble', 'Preamble'),
202         ('SOP', 'Start of Packet'),
203         ('Head', 'Header'),
204         ('Data', 'Data'),
205         ('CRC', 'Checksum'),
206         ('EOP', 'End Of Packet'),
207         ('Sym', '4b5b symbols'),
208         ('warnings', 'Warnings'),
209         ('src', 'Source Message'),
210         ('snk', 'Sink Message'),
211         ('payload', 'Payload'),
212         ('text', 'Plain text'),
213     )
214     annotation_rows = (
215        ('4B5B', 'symbols', (7, )),
216        ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
217        ('payload', 'Payload', (11, )),
218        ('type', 'Type', (0, 9, 10, )),
219        ('warnings', 'Warnings', (8, )),
220        ('text', 'Full text', (12, )),
221     )
222     binary = (
223         ('raw-data', 'RAW binary data'),
224     )
225
226     def get_request(self, rdo):
227         pos = (rdo >> 28) & 7
228         op_ma = ((rdo >> 10) & 0x3ff) * 10
229         max_ma = (rdo & 0x3ff) * 10
230         flags = ''
231         for f in RDO_FLAGS.keys():
232             if rdo & f:
233                 flags += ' ' + RDO_FLAGS[f]
234         return '[%d]%d/%d mA%s' % (pos, op_ma, max_ma, flags)
235
236     def get_source_cap(self, pdo):
237         t = (pdo >> 30) & 3
238         if t == 0:
239             mv = ((pdo >> 10) & 0x3ff) * 50
240             ma = ((pdo >> 0) & 0x3ff) * 10
241             p = '%.1fV %.1fA' % (mv/1000.0, ma/1000.0)
242         elif t == 1:
243             minv = ((pdo >> 10) & 0x3ff) * 50
244             maxv = ((pdo >> 20) & 0x3ff) * 50
245             mw = ((pdo >> 0) & 0x3ff) * 250
246             p = '%.1f/%.1fV %.1fW' % (minv/1000.0, maxv/1000.0, mw/1000.0)
247         elif t == 2:
248             minv = ((pdo >> 10) & 0x3ff) * 50
249             maxv = ((pdo >> 20) & 0x3ff) * 50
250             ma = ((pdo >> 0) & 0x3ff) * 10
251             p = '%.1f/%.1fV %.1fA' % (minv/1000.0, maxv/1000.0, ma/1000.0)
252         else:
253             p = ''
254         flags = ''
255         for f in PDO_FLAGS.keys():
256             if pdo & f:
257                 flags += ' ' + PDO_FLAGS[f]
258         return '%s%s%s' % (PDO_TYPE[t], p, flags)
259
260     def get_sink_cap(self, pdo):
261         t = (pdo >> 30) & 3
262         if t == 0:
263             mv = ((pdo >> 10) & 0x3ff) * 50
264             ma = ((pdo >> 0) & 0x3ff) * 10
265             p = '%.1fV %.1fA' % (mv/1000.0, ma/1000.0)
266         elif t == 1:
267             minv = ((pdo >> 10) & 0x3ff) * 50
268             maxv = ((pdo >> 20) & 0x3ff) * 50
269             mw = ((pdo >> 0) & 0x3ff) * 250
270             p = '%.1f/%.1fV %.1fW' % (minv/1000.0, maxv/1000.0, mw/1000.0)
271         elif t == 2:
272             minv = ((pdo >> 10) & 0x3ff) * 50
273             maxv = ((pdo >> 20) & 0x3ff) * 50
274             ma = ((pdo >> 0) & 0x3ff) * 10
275             p = '%.1f/%.1fV %.1fA' % (minv/1000.0, maxv/1000.0, ma/1000.0)
276         else:
277             p = ''
278         flags = ''
279         for f in PDO_FLAGS.keys():
280             if pdo & f:
281                 flags += ' ' + PDO_FLAGS[f]
282         return '%s%s%s' % (PDO_TYPE[t], p, flags)
283
284     def get_vdm(self, idx, data):
285         if idx == 0:    # VDM header
286             vid = data >> 16
287             struct = data & (1 << 15)
288             txt = 'VDM'
289             if struct:  # Structured VDM
290                 cmd = data & 0x1f
291                 src = data & (1 << 5)
292                 ack = (data >> 6) & 3
293                 pos = (data >> 8) & 7
294                 ver = (data >> 13) & 3
295                 txt = VDM_ACK[ack] + ' '
296                 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
297                 txt += ' pos %d' % (pos) if pos else ' '
298             else:   # Unstructured VDM
299                 txt = 'unstruct [%04x]' % (data & 0x7fff)
300             txt += ' SVID:%04x' % (vid)
301         else:   # VDM payload
302             txt = 'VDO:%08x' % (data)
303         return txt
304
305     def get_bist(self, idx, data):
306         mode = data >> 28
307         counter = data & 0xffff
308         mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
309         if mode == 2:
310             mode_name = 'Counter[= %d]' % (counter)
311         # TODO check all 0 bits are 0 / emit warnings
312         return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
313
314     def putpayload(self, s0, s1, idx):
315         t = self.head_type()
316         txt = '???'
317         if t == 2:
318             txt = self.get_request(self.data[idx])
319         elif t == 1:
320             txt = self.get_source_cap(self.data[idx])
321         elif t == 4:
322             txt = self.get_sink_cap(self.data[idx])
323         elif t == 15:
324             txt = self.get_vdm(idx, self.data[idx])
325         elif t == 3:
326             txt = self.get_bist(idx, self.data[idx])
327         self.putx(s0, s1, [11, [txt, txt]])
328         self.text += ' - ' + txt
329
330     def puthead(self):
331         ann_type = 9 if self.head_power_role() else 10
332         role = 'SRC' if self.head_power_role() else 'SNK'
333         if self.head_data_role() != self.head_power_role():
334             role += '/DFP' if self.head_data_role() else '/UFP'
335         t = self.head_type()
336         if self.head_count() == 0:
337             shortm = CTRL_TYPES[t]
338         else:
339             shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
340
341         longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
342         self.putx(0, -1, [ann_type, [longm, shortm]])
343         self.text += longm
344
345     def head_id(self):
346         return (self.head >> 9) & 7
347
348     def head_power_role(self):
349         return (self.head >> 8) & 1
350
351     def head_data_role(self):
352         return (self.head >> 5) & 1
353
354     def head_rev(self):
355         return ((self.head >> 6) & 3) + 1
356
357     def head_type(self):
358         return self.head & 0xF
359
360     def head_count(self):
361         return (self.head >> 12) & 7
362
363     def putx(self, s0, s1, data):
364         self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
365
366     def putwarn(self, longm, shortm):
367         self.putx(0, -1, [8, [longm, shortm]])
368
369     def compute_crc32(self):
370         bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
371                             *tuple([d & 0xffffffff for d in self.data]))
372         return zlib.crc32(bdata)
373
374     def rec_sym(self, i, sym):
375         self.putx(i, i+5, [7, SYM_NAME[sym]])
376
377     def get_sym(self, i, rec=True):
378         v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
379              (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
380         sym = DEC4B5B[v]
381         if rec:
382             self.rec_sym(i, sym)
383         return sym
384
385     def get_short(self):
386         i = self.idx
387         # Check it's not a truncated packet
388         if len(self.bits) - i <= 20:
389             self.putwarn('Truncated', '!')
390             return 0x0BAD
391         k = [self.get_sym(i), self.get_sym(i+5),
392              self.get_sym(i+10), self.get_sym(i+15)]
393         # TODO check bad symbols
394         val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
395         self.idx += 20
396         return val
397
398     def get_word(self):
399         lo = self.get_short()
400         hi = self.get_short()
401         return lo | (hi << 16)
402
403     def find_corrupted_sop(self, k):
404         # Start of packet are valid even if they have only 3 correct symbols
405         # out of 4.
406         for seq in START_OF_PACKETS.keys():
407             if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
408                 return START_OF_PACKETS[seq]
409         return None
410
411     def scan_eop(self):
412         for i in range(len(self.bits) - 19):
413             k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
414                  self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
415             sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None
416             if not sym:
417                 sym = self.find_corrupted_sop(k)
418             # We have an interesting symbol sequence
419             if sym:
420                 # annotate the preamble
421                 self.putx(0, i, [1, ['Preamble', '...']])
422                 # annotate each symbol
423                 self.rec_sym(i, k[0])
424                 self.rec_sym(i+5, k[1])
425                 self.rec_sym(i+10, k[2])
426                 self.rec_sym(i+15, k[3])
427                 if sym == 'Hard Reset':
428                     self.text += 'HRST'
429                     return -1   # Hard reset
430                 elif sym == 'Cable Reset':
431                     self.text += 'CRST'
432                     return -1   # Cable reset
433                 else:
434                     self.putx(i, i+20, [2, [sym, 'S']])
435                 return i+20
436         self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
437         self.text += 'Junk???'
438         self.putwarn('No start of packet found', 'XXX')
439         return -1   # No Start Of Packet
440
441     def __init__(self):
442         self.samplerate = None
443         self.idx = 0
444         self.packet_seq = 0
445         self.previous = 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         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):
528         if not self.samplerate:
529             raise SamplerateError('Cannot decode without samplerate.')
530         while True:
531             self.wait({0: 'e'})
532
533             # First sample of the packet, just record the start date
534             if not self.startsample:
535                 self.startsample = self.samplenum
536                 self.previous = self.samplenum
537                 continue
538
539             diff = self.samplenum - self.previous
540
541             # Large idle: use it as the end of packet
542             if diff > self.maxbit:
543                 # the last edge of the packet
544                 self.edges.append(self.previous)
545                 # Export the packet
546                 self.decode_packet()
547                 # Reset for next packet
548                 self.startsample = self.samplenum
549                 self.bits = []
550                 self.edges = []
551                 self.bad = []
552                 self.half_one = False
553                 self.start_one = 0
554             else:   # add the bit to the packet
555                 is_zero = diff > self.threshold
556                 if is_zero and not self.half_one:
557                     self.bits.append(0)
558                     self.edges.append(self.previous)
559                 elif not is_zero and self.half_one:
560                     self.bits.append(1)
561                     self.edges.append(self.start_one)
562                     self.half_one = False
563                 elif not is_zero and not self.half_one:
564                     self.half_one = True
565                     self.start_one = self.previous
566                 else:   # Invalid BMC sequence
567                     self.bad.append((self.start_one, self.previous))
568                     # TODO try to recover
569                     self.bits.append(0)
570                     self.edges.append(self.previous)
571                     self.half_one = False
572             self.previous = self.samplenum