]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
702c22a525bf7e04aa7be4576a92cb797bb4d173
[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 ## Copyright (C) 2018 Peter Hazenberg <sigrok@haas-en-berg.nl>
6 ##
7 ## This program is free software; you can redistribute it and/or modify
8 ## it under the terms of the GNU General Public License as published by
9 ## the Free Software Foundation; either version 2 of the License, or
10 ## (at your option) any later version.
11 ##
12 ## This program is distributed in the hope that it will be useful,
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ## GNU General Public License for more details.
16 ##
17 ## You should have received a copy of the GNU General Public License
18 ## along with this program; if not, see <http://www.gnu.org/licenses/>.
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 SOP_SEQUENCES = [
106     (SYNC1, SYNC1, SYNC1, SYNC2),
107     (SYNC1, SYNC1, SYNC3, SYNC3),
108     (SYNC1, SYNC3, SYNC1, SYNC3),
109     (SYNC1, RST2,  RST2,  SYNC3),
110     (SYNC1, RST2,  SYNC3, SYNC2),
111     (RST1,  SYNC1, RST1,  SYNC3),
112     (RST1,  RST1,  RST1,   RST2),
113 ]
114 START_OF_PACKETS = {
115     SOP_SEQUENCES[0]: 'SOP',
116     SOP_SEQUENCES[1]: "SOP'",
117     SOP_SEQUENCES[2]: 'SOP"',
118     SOP_SEQUENCES[3]: "SOP' Debug",
119     SOP_SEQUENCES[4]: 'SOP" Debug',
120     SOP_SEQUENCES[5]: 'Cable Reset',
121     SOP_SEQUENCES[6]: 'Hard Reset',
122 }
123
124 SYM_NAME = [
125     ['0x0', '0'],
126     ['0x1', '1'],
127     ['0x2', '2'],
128     ['0x3', '3'],
129     ['0x4', '4'],
130     ['0x5', '5'],
131     ['0x6', '6'],
132     ['0x7', '7'],
133     ['0x8', '8'],
134     ['0x9', '9'],
135     ['0xA', 'A'],
136     ['0xB', 'B'],
137     ['0xC', 'C'],
138     ['0xD', 'D'],
139     ['0xE', 'E'],
140     ['0xF', 'F'],
141     ['ERROR', 'X'],
142     ['SYNC-1', 'S1'],
143     ['SYNC-2', 'S2'],
144     ['SYNC-3', 'S3'],
145     ['RST-1', 'R1'],
146     ['RST-2', 'R2'],
147     ['EOP', '#'],
148 ]
149
150 RDO_FLAGS = {
151     (1 << 24): 'no_suspend',
152     (1 << 25): 'comm_cap',
153     (1 << 26): 'cap_mismatch',
154     (1 << 27): 'give_back'
155 }
156
157 PDO_FLAGS = {
158     (1 << 29): 'dual_role_power',
159     (1 << 28): 'suspend',
160     (1 << 27): 'ext',
161     (1 << 26): 'comm_cap',
162     (1 << 25): 'dual_role_data'
163 }
164
165 BIST_MODES = {
166         0: 'Receiver',
167         1: 'Transmit',
168         2: 'Counters',
169         3: 'Carrier 0',
170         4: 'Carrier 1',
171         5: 'Carrier 2',
172         6: 'Carrier 3',
173         7: 'Eye',
174 }
175
176 VDM_CMDS = {
177         1: 'Disc Ident',
178         2: 'Disc SVID',
179         3: 'Disc Mode',
180         4: 'Enter Mode',
181         5: 'Exit Mode',
182         6: 'Attention',
183         # 16..31: SVID Specific Commands
184         # DisplayPort Commands
185         16: 'DP Status',
186         17: 'DP Configure',
187 }
188 VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY']
189
190
191 class SamplerateError(Exception):
192     pass
193
194 class Decoder(srd.Decoder):
195     api_version = 3
196     id = 'usb_power_delivery'
197     name = 'USB PD'
198     longname = 'USB Power Delivery'
199     desc = 'USB Power Delivery protocol.'
200     license = 'gplv2+'
201     inputs = ['logic']
202     outputs = ['usb_pd']
203     channels = (
204         {'id': 'cc1', 'name': 'CC1', 'desc': 'Control channel 1'},
205     )
206     optional_channels = (
207         {'id': 'cc2', 'name': 'CC2', 'desc': 'Control channel 2'},
208     )
209     options = (
210         {'id': 'fulltext', 'desc': 'full text decoding of the packet',
211          'default': 'no', 'values': ('yes', 'no')},
212     )
213     annotations = (
214         ('type', 'Packet Type'),
215         ('Preamble', 'Preamble'),
216         ('SOP', 'Start of Packet'),
217         ('Head', 'Header'),
218         ('Data', 'Data'),
219         ('CRC', 'Checksum'),
220         ('EOP', 'End Of Packet'),
221         ('Sym', '4b5b symbols'),
222         ('warnings', 'Warnings'),
223         ('src', 'Source Message'),
224         ('snk', 'Sink Message'),
225         ('payload', 'Payload'),
226         ('text', 'Plain text'),
227     )
228     annotation_rows = (
229        ('4B5B', 'symbols', (7, )),
230        ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
231        ('payload', 'Payload', (11, )),
232        ('type', 'Type', (0, 9, 10, )),
233        ('warnings', 'Warnings', (8, )),
234        ('text', 'Full text', (12, )),
235     )
236     binary = (
237         ('raw-data', 'RAW binary data'),
238     )
239
240     stored_pdos = {}
241
242
243     def get_request(self, rdo):
244         pos = (rdo >> 28) & 7
245         op_ma = ((rdo >> 10) & 0x3ff) * 0.01
246         max_ma = (rdo & 0x3ff) * 0.01
247         flags = ''
248         for f in sorted(RDO_FLAGS.keys(), reverse = True):
249             if rdo & f:
250                 flags += ' [' + RDO_FLAGS[f] + ']'
251         if pos in self.stored_pdos.keys():
252             return '(PDO #%d: %s) %gA (operating) / %gA (max)%s' % (pos, self.stored_pdos[pos], op_ma, max_ma, flags)
253         else:
254             return '(PDO #%d) %gA (operating) / %gA (max)%s' % (pos, op_ma, max_ma, flags)
255
256     def get_source_sink_cap(self, pdo, idx):
257         t1 = (pdo >> 30) & 3
258         if t1 == 0:
259             t_name = 'Fixed'
260             mv = ((pdo >> 10) & 0x3ff) * 0.05
261             ma = ((pdo >> 0) & 0x3ff) * 0.01
262             p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
263             self.stored_pdos[idx] = '%s %gV' % (t_name, mv)
264         elif t1 == 1:
265             t_name = 'Battery'
266             minv = ((pdo >> 10) & 0x3ff) * 0.05
267             maxv = ((pdo >> 20) & 0x3ff) * 0.05
268             mw = ((pdo >> 0) & 0x3ff) * 0.25
269             p = '%g/%gV %gW' % (minv, maxv, mw)
270             self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
271         elif t1 == 2:
272             t_name = 'Variable'
273             minv = ((pdo >> 10) & 0x3ff) * 0.05
274             maxv = ((pdo >> 20) & 0x3ff) * 0.05
275             ma = ((pdo >> 0) & 0x3ff) * 0.01
276             p = '%g/%gV %gA' % (minv, maxv, ma)
277             self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
278         elif t1 == 3:
279             t2 = (pdo >> 28) & 3
280             if t2 == 0:
281                 t_name = 'Programmable'
282                 p = 'TODO: PPS support'
283             else:
284                 t_name = 'Reserved APDO: '+bin(t2)
285                 p = ''
286         flags = ''
287         for f in sorted(PDO_FLAGS.keys(), reverse = True):
288             if pdo & f:
289                 flags += ' [' + PDO_FLAGS[f] + ']'
290         return '[%s] %s%s' % (t_name, p, flags)
291
292     def get_vdm(self, idx, data):
293         if idx == 0:    # VDM header
294             vid = data >> 16
295             struct = data & (1 << 15)
296             txt = 'VDM'
297             if struct:  # Structured VDM
298                 cmd = data & 0x1f
299                 src = data & (1 << 5)
300                 ack = (data >> 6) & 3
301                 pos = (data >> 8) & 7
302                 ver = (data >> 13) & 3
303                 txt = VDM_ACK[ack] + ' '
304                 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
305                 txt += ' pos %d' % (pos) if pos else ' '
306             else:   # Unstructured VDM
307                 txt = 'unstruct [%04x]' % (data & 0x7fff)
308             txt += ' SVID:%04x' % (vid)
309         else:   # VDM payload
310             txt = 'VDO:%08x' % (data)
311         return txt
312
313     def get_bist(self, idx, data):
314         mode = data >> 28
315         counter = data & 0xffff
316         mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
317         if mode == 2:
318             mode_name = 'Counter[= %d]' % (counter)
319         # TODO check all 0 bits are 0 / emit warnings
320         return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
321
322     def putpayload(self, s0, s1, idx):
323         t = self.head_type()
324         txt = '['+str(idx+1)+'] '
325         if t == 2:
326             txt += self.get_request(self.data[idx])
327         elif t == 1 or t == 4:
328             txt += self.get_source_sink_cap(self.data[idx], idx+1)
329         elif t == 15:
330             txt += self.get_vdm(idx, self.data[idx])
331         elif t == 3:
332             txt += self.get_bist(idx, self.data[idx])
333         self.putx(s0, s1, [11, [txt, txt]])
334         self.text += ' - ' + txt
335
336     def puthead(self):
337         ann_type = 9 if self.head_power_role() else 10
338         role = 'SRC' if self.head_power_role() else 'SNK'
339         if self.head_data_role() != self.head_power_role():
340             role += '/DFP' if self.head_data_role() else '/UFP'
341         t = self.head_type()
342         if self.head_count() == 0:
343             shortm = CTRL_TYPES[t]
344         else:
345             shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
346
347         longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
348         self.putx(0, -1, [ann_type, [longm, shortm]])
349         self.text += longm
350
351     def head_id(self):
352         return (self.head >> 9) & 7
353
354     def head_power_role(self):
355         return (self.head >> 8) & 1
356
357     def head_data_role(self):
358         return (self.head >> 5) & 1
359
360     def head_rev(self):
361         return ((self.head >> 6) & 3) + 1
362
363     def head_type(self):
364         return self.head & 0xF
365
366     def head_count(self):
367         return (self.head >> 12) & 7
368
369     def putx(self, s0, s1, data):
370         self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
371
372     def putwarn(self, longm, shortm):
373         self.putx(0, -1, [8, [longm, shortm]])
374
375     def compute_crc32(self):
376         bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
377                             *tuple([d & 0xffffffff for d in self.data]))
378         return zlib.crc32(bdata)
379
380     def rec_sym(self, i, sym):
381         self.putx(i, i+5, [7, SYM_NAME[sym]])
382
383     def get_sym(self, i, rec=True):
384         v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
385              (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
386         sym = DEC4B5B[v]
387         if rec:
388             self.rec_sym(i, sym)
389         return sym
390
391     def get_short(self):
392         i = self.idx
393         # Check it's not a truncated packet
394         if len(self.bits) - i <= 20:
395             self.putwarn('Truncated', '!')
396             return 0x0BAD
397         k = [self.get_sym(i), self.get_sym(i+5),
398              self.get_sym(i+10), self.get_sym(i+15)]
399         # TODO check bad symbols
400         val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
401         self.idx += 20
402         return val
403
404     def get_word(self):
405         lo = self.get_short()
406         hi = self.get_short()
407         return lo | (hi << 16)
408
409     def find_corrupted_sop(self, k):
410         # Start of packet are valid even if they have only 3 correct symbols
411         # out of 4.
412         for seq in SOP_SEQUENCES:
413             if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
414                 return START_OF_PACKETS[seq]
415         return None
416
417     def scan_eop(self):
418         for i in range(len(self.bits) - 19):
419             k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
420                  self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
421             sym = START_OF_PACKETS.get(k, None)
422             if not sym:
423                 sym = self.find_corrupted_sop(k)
424             # We have an interesting symbol sequence
425             if sym:
426                 # annotate the preamble
427                 self.putx(0, i, [1, ['Preamble', '...']])
428                 # annotate each symbol
429                 self.rec_sym(i, k[0])
430                 self.rec_sym(i+5, k[1])
431                 self.rec_sym(i+10, k[2])
432                 self.rec_sym(i+15, k[3])
433                 if sym == 'Hard Reset':
434                     self.text += 'HRST'
435                     return -1   # Hard reset
436                 elif sym == 'Cable Reset':
437                     self.text += 'CRST'
438                     return -1   # Cable reset
439                 else:
440                     self.putx(i, i+20, [2, [sym, 'S']])
441                 return i+20
442         self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
443         self.text += 'Junk???'
444         self.putwarn('No start of packet found', 'XXX')
445         return -1   # No Start Of Packet
446
447     def __init__(self):
448         self.reset()
449
450     def reset(self):
451         self.samplerate = None
452         self.idx = 0
453         self.packet_seq = 0
454         self.previous = 0
455         self.startsample = None
456         self.bits = []
457         self.edges = []
458         self.bad = []
459         self.half_one = False
460         self.start_one = 0
461         self.stored_pdos = {}
462
463     def metadata(self, key, value):
464         if key == srd.SRD_CONF_SAMPLERATE:
465             self.samplerate = value
466             # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
467             self.maxbit = self.us2samples(3 * UI_US)
468             # duration threshold between half 1 and 0
469             self.threshold = self.us2samples(THRESHOLD_US)
470
471     def start(self):
472         self.out_python = self.register(srd.OUTPUT_PYTHON)
473         self.out_ann = self.register(srd.OUTPUT_ANN)
474         self.out_binary = self.register(srd.OUTPUT_BINARY)
475         self.out_bitrate = self.register(
476             srd.OUTPUT_META,
477             meta=(int, 'Bitrate', 'Bitrate during the packet')
478         )
479
480     def us2samples(self, us):
481         return int(us * self.samplerate / 1000000)
482
483     def decode_packet(self):
484         self.data = []
485         self.idx = 0
486         self.text = ''
487
488         if len(self.edges) < 50:
489             return  # Not a real PD packet
490
491         self.packet_seq += 1
492         tstamp = float(self.startsample) / self.samplerate
493         self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
494
495         self.idx = self.scan_eop()
496         if self.idx < 0:
497             # Full text trace of the issue
498             self.putx(0, self.idx, [12, [self.text, '...']])
499             return  # No real packet: ABORT
500
501         # Packet header
502         self.head = self.get_short()
503         self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
504         self.puthead()
505
506         # Decode data payload
507         for i in range(self.head_count()):
508             self.data.append(self.get_word())
509             self.putx(self.idx-40, self.idx,
510                       [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
511             self.putpayload(self.idx-40, self.idx, i)
512
513         # CRC check
514         self.crc = self.get_word()
515         ccrc = self.compute_crc32()
516         if self.crc != ccrc:
517             self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
518         self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
519
520         # End of Packet
521         if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
522             self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
523             self.idx += 5
524         else:
525             self.putwarn('No EOP', 'EOP!')
526         # Full text trace
527         if self.options['fulltext'] == 'yes':
528             self.putx(0, self.idx, [12, [self.text, '...']])
529
530         # Meta data for bitrate
531         ss, es = self.edges[0], self.edges[-1]
532         bitrate = self.samplerate*len(self.bits) / float(es - ss)
533         self.put(es, ss, self.out_bitrate, int(bitrate))
534         # Raw binary data (BMC decoded)
535         self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
536
537     def decode(self):
538         if not self.samplerate:
539             raise SamplerateError('Cannot decode without samplerate.')
540         while True:
541             pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': 100*1000}])
542
543
544             # First sample of the packet, just record the start date
545             if not self.startsample:
546                 self.startsample = self.samplenum
547                 self.previous = self.samplenum
548                 continue
549
550             diff = self.samplenum - self.previous
551
552             # Large idle: use it as the end of packet
553             if diff > self.maxbit:
554                 # the last edge of the packet
555                 self.edges.append(self.previous)
556                 # Export the packet
557                 self.decode_packet()
558                 # Reset for next packet
559                 self.startsample = self.samplenum
560                 self.bits = []
561                 self.edges = []
562                 self.bad = []
563                 self.half_one = False
564                 self.start_one = 0
565             else:   # add the bit to the packet
566                 is_zero = diff > self.threshold
567                 if is_zero and not self.half_one:
568                     self.bits.append(0)
569                     self.edges.append(self.previous)
570                 elif not is_zero and self.half_one:
571                     self.bits.append(1)
572                     self.edges.append(self.start_one)
573                     self.half_one = False
574                 elif not is_zero and not self.half_one:
575                     self.half_one = True
576                     self.start_one = self.previous
577                 else:   # Invalid BMC sequence
578                     self.bad.append((self.start_one, self.previous))
579                     # TODO try to recover
580                     self.bits.append(0)
581                     self.edges.append(self.previous)
582                     self.half_one = False
583             self.previous = self.samplenum