]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
de6f39418c6930f647384b75186a0a5382ddfb37
[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 sorted(RDO_FLAGS.keys(), reverse = True):
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 sorted(PDO_FLAGS.keys(), reverse = True):
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 sorted(PDO_FLAGS.keys(), reverse = True):
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.get(k, 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.reset()
443
444     def reset(self):
445         self.samplerate = None
446         self.idx = 0
447         self.packet_seq = 0
448         self.previous = 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         return int(us * self.samplerate / 1000000)
475
476     def decode_packet(self):
477         self.data = []
478         self.idx = 0
479         self.text = ''
480
481         if len(self.edges) < 50:
482             return  # Not a real PD packet
483
484         self.packet_seq += 1
485         tstamp = float(self.startsample) / self.samplerate
486         self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
487
488         self.idx = self.scan_eop()
489         if self.idx < 0:
490             # Full text trace of the issue
491             self.putx(0, self.idx, [12, [self.text, '...']])
492             return  # No real packet: ABORT
493
494         # Packet header
495         self.head = self.get_short()
496         self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
497         self.puthead()
498
499         # Decode data payload
500         for i in range(self.head_count()):
501             self.data.append(self.get_word())
502             self.putx(self.idx-40, self.idx,
503                       [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
504             self.putpayload(self.idx-40, self.idx, i)
505
506         # CRC check
507         self.crc = self.get_word()
508         ccrc = self.compute_crc32()
509         if self.crc != ccrc:
510             self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
511         self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
512
513         # End of Packet
514         if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
515             self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
516             self.idx += 5
517         else:
518             self.putwarn('No EOP', 'EOP!')
519         # Full text trace
520         if self.options['fulltext'] == 'yes':
521             self.putx(0, self.idx, [12, [self.text, '...']])
522
523         # Meta data for bitrate
524         ss, es = self.edges[0], self.edges[-1]
525         bitrate = self.samplerate*len(self.bits) / float(es - ss)
526         self.put(es, ss, self.out_bitrate, int(bitrate))
527         # Raw binary data (BMC decoded)
528         self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
529
530     def decode(self):
531         if not self.samplerate:
532             raise SamplerateError('Cannot decode without samplerate.')
533         while True:
534             self.wait({0: 'e'})
535
536             # First sample of the packet, just record the start date
537             if not self.startsample:
538                 self.startsample = self.samplenum
539                 self.previous = self.samplenum
540                 continue
541
542             diff = self.samplenum - self.previous
543
544             # Large idle: use it as the end of packet
545             if diff > self.maxbit:
546                 # the last edge of the packet
547                 self.edges.append(self.previous)
548                 # Export the packet
549                 self.decode_packet()
550                 # Reset for next packet
551                 self.startsample = self.samplenum
552                 self.bits = []
553                 self.edges = []
554                 self.bad = []
555                 self.half_one = False
556                 self.start_one = 0
557             else:   # add the bit to the packet
558                 is_zero = diff > self.threshold
559                 if is_zero and not self.half_one:
560                     self.bits.append(0)
561                     self.edges.append(self.previous)
562                 elif not is_zero and self.half_one:
563                     self.bits.append(1)
564                     self.edges.append(self.start_one)
565                     self.half_one = False
566                 elif not is_zero and not self.half_one:
567                     self.half_one = True
568                     self.start_one = self.previous
569                 else:   # Invalid BMC sequence
570                     self.bad.append((self.start_one, self.previous))
571                     # TODO try to recover
572                     self.bits.append(0)
573                     self.edges.append(self.previous)
574                     self.half_one = False
575             self.previous = self.samplenum