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