]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
usb_power_delivery: PPS support
[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|PPS'
282                 minv = ((pdo >> 8) & 0xff) * 0.1
283                 maxv = ((pdo >> 17) & 0xff) * 0.1
284                 ma = ((pdo >> 0) & 0xff) * 0.05
285                 p = '%g/%gV %gA' % (minv, maxv, ma)
286                 if (pdo >> 27) & 0x1:
287                     p += ' [limited]'
288                 self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv)
289             else:
290                 t_name = 'Reserved APDO: '+bin(t2)
291                 p = '[raw: %s]' % (bin(pdo))
292                 self.stored_pdos[idx] = '%s %s' % (t_name, p)
293         flags = ''
294         for f in sorted(PDO_FLAGS.keys(), reverse = True):
295             if pdo & f:
296                 flags += ' [' + PDO_FLAGS[f] + ']'
297         return '[%s] %s%s' % (t_name, p, flags)
298
299     def get_vdm(self, idx, data):
300         if idx == 0:    # VDM header
301             vid = data >> 16
302             struct = data & (1 << 15)
303             txt = 'VDM'
304             if struct:  # Structured VDM
305                 cmd = data & 0x1f
306                 src = data & (1 << 5)
307                 ack = (data >> 6) & 3
308                 pos = (data >> 8) & 7
309                 ver = (data >> 13) & 3
310                 txt = VDM_ACK[ack] + ' '
311                 txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?'
312                 txt += ' pos %d' % (pos) if pos else ' '
313             else:   # Unstructured VDM
314                 txt = 'unstruct [%04x]' % (data & 0x7fff)
315             txt += ' SVID:%04x' % (vid)
316         else:   # VDM payload
317             txt = 'VDO:%08x' % (data)
318         return txt
319
320     def get_bist(self, idx, data):
321         mode = data >> 28
322         counter = data & 0xffff
323         mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID'
324         if mode == 2:
325             mode_name = 'Counter[= %d]' % (counter)
326         # TODO check all 0 bits are 0 / emit warnings
327         return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO'
328
329     def putpayload(self, s0, s1, idx):
330         t = self.head_type()
331         txt = '['+str(idx+1)+'] '
332         if t == 2:
333             txt += self.get_request(self.data[idx])
334         elif t == 1 or t == 4:
335             txt += self.get_source_sink_cap(self.data[idx], idx+1)
336         elif t == 15:
337             txt += self.get_vdm(idx, self.data[idx])
338         elif t == 3:
339             txt += self.get_bist(idx, self.data[idx])
340         self.putx(s0, s1, [11, [txt, txt]])
341         self.text += ' - ' + txt
342
343     def puthead(self):
344         ann_type = 9 if self.head_power_role() else 10
345         role = 'SRC' if self.head_power_role() else 'SNK'
346         if self.head_data_role() != self.head_power_role():
347             role += '/DFP' if self.head_data_role() else '/UFP'
348         t = self.head_type()
349         if self.head_count() == 0:
350             shortm = CTRL_TYPES[t]
351         else:
352             shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
353
354         longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
355         self.putx(0, -1, [ann_type, [longm, shortm]])
356         self.text += longm
357
358     def head_id(self):
359         return (self.head >> 9) & 7
360
361     def head_power_role(self):
362         return (self.head >> 8) & 1
363
364     def head_data_role(self):
365         return (self.head >> 5) & 1
366
367     def head_rev(self):
368         return ((self.head >> 6) & 3) + 1
369
370     def head_type(self):
371         return self.head & 0xF
372
373     def head_count(self):
374         return (self.head >> 12) & 7
375
376     def putx(self, s0, s1, data):
377         self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
378
379     def putwarn(self, longm, shortm):
380         self.putx(0, -1, [8, [longm, shortm]])
381
382     def compute_crc32(self):
383         bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
384                             *tuple([d & 0xffffffff for d in self.data]))
385         return zlib.crc32(bdata)
386
387     def rec_sym(self, i, sym):
388         self.putx(i, i+5, [7, SYM_NAME[sym]])
389
390     def get_sym(self, i, rec=True):
391         v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
392              (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
393         sym = DEC4B5B[v]
394         if rec:
395             self.rec_sym(i, sym)
396         return sym
397
398     def get_short(self):
399         i = self.idx
400         # Check it's not a truncated packet
401         if len(self.bits) - i <= 20:
402             self.putwarn('Truncated', '!')
403             return 0x0BAD
404         k = [self.get_sym(i), self.get_sym(i+5),
405              self.get_sym(i+10), self.get_sym(i+15)]
406         # TODO check bad symbols
407         val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
408         self.idx += 20
409         return val
410
411     def get_word(self):
412         lo = self.get_short()
413         hi = self.get_short()
414         return lo | (hi << 16)
415
416     def find_corrupted_sop(self, k):
417         # Start of packet are valid even if they have only 3 correct symbols
418         # out of 4.
419         for seq in SOP_SEQUENCES:
420             if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
421                 return START_OF_PACKETS[seq]
422         return None
423
424     def scan_eop(self):
425         for i in range(len(self.bits) - 19):
426             k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
427                  self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
428             sym = START_OF_PACKETS.get(k, None)
429             if not sym:
430                 sym = self.find_corrupted_sop(k)
431             # We have an interesting symbol sequence
432             if sym:
433                 # annotate the preamble
434                 self.putx(0, i, [1, ['Preamble', '...']])
435                 # annotate each symbol
436                 self.rec_sym(i, k[0])
437                 self.rec_sym(i+5, k[1])
438                 self.rec_sym(i+10, k[2])
439                 self.rec_sym(i+15, k[3])
440                 if sym == 'Hard Reset':
441                     self.text += 'HRST'
442                     return -1   # Hard reset
443                 elif sym == 'Cable Reset':
444                     self.text += 'CRST'
445                     return -1   # Cable reset
446                 else:
447                     self.putx(i, i+20, [2, [sym, 'S']])
448                 return i+20
449         self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
450         self.text += 'Junk???'
451         self.putwarn('No start of packet found', 'XXX')
452         return -1   # No Start Of Packet
453
454     def __init__(self):
455         self.reset()
456
457     def reset(self):
458         self.samplerate = None
459         self.idx = 0
460         self.packet_seq = 0
461         self.previous = 0
462         self.startsample = None
463         self.bits = []
464         self.edges = []
465         self.bad = []
466         self.half_one = False
467         self.start_one = 0
468         self.stored_pdos = {}
469
470     def metadata(self, key, value):
471         if key == srd.SRD_CONF_SAMPLERATE:
472             self.samplerate = value
473             # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong
474             self.maxbit = self.us2samples(3 * UI_US)
475             # duration threshold between half 1 and 0
476             self.threshold = self.us2samples(THRESHOLD_US)
477
478     def start(self):
479         self.out_python = self.register(srd.OUTPUT_PYTHON)
480         self.out_ann = self.register(srd.OUTPUT_ANN)
481         self.out_binary = self.register(srd.OUTPUT_BINARY)
482         self.out_bitrate = self.register(
483             srd.OUTPUT_META,
484             meta=(int, 'Bitrate', 'Bitrate during the packet')
485         )
486
487     def us2samples(self, us):
488         return int(us * self.samplerate / 1000000)
489
490     def decode_packet(self):
491         self.data = []
492         self.idx = 0
493         self.text = ''
494
495         if len(self.edges) < 50:
496             return  # Not a real PD packet
497
498         self.packet_seq += 1
499         tstamp = float(self.startsample) / self.samplerate
500         self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000)
501
502         self.idx = self.scan_eop()
503         if self.idx < 0:
504             # Full text trace of the issue
505             self.putx(0, self.idx, [12, [self.text, '...']])
506             return  # No real packet: ABORT
507
508         # Packet header
509         self.head = self.get_short()
510         self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']])
511         self.puthead()
512
513         # Decode data payload
514         for i in range(self.head_count()):
515             self.data.append(self.get_word())
516             self.putx(self.idx-40, self.idx,
517                       [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]])
518             self.putpayload(self.idx-40, self.idx, i)
519
520         # CRC check
521         self.crc = self.get_word()
522         ccrc = self.compute_crc32()
523         if self.crc != ccrc:
524             self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!')
525         self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']])
526
527         # End of Packet
528         if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP:
529             self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']])
530             self.idx += 5
531         else:
532             self.putwarn('No EOP', 'EOP!')
533         # Full text trace
534         if self.options['fulltext'] == 'yes':
535             self.putx(0, self.idx, [12, [self.text, '...']])
536
537         # Meta data for bitrate
538         ss, es = self.edges[0], self.edges[-1]
539         bitrate = self.samplerate*len(self.bits) / float(es - ss)
540         self.put(es, ss, self.out_bitrate, int(bitrate))
541         # Raw binary data (BMC decoded)
542         self.put(es, ss, self.out_binary, [0, bytes(self.bits)])
543
544     def decode(self):
545         if not self.samplerate:
546             raise SamplerateError('Cannot decode without samplerate.')
547         while True:
548             pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': 100*1000}])
549
550
551             # First sample of the packet, just record the start date
552             if not self.startsample:
553                 self.startsample = self.samplenum
554                 self.previous = self.samplenum
555                 continue
556
557             diff = self.samplenum - self.previous
558
559             # Large idle: use it as the end of packet
560             if diff > self.maxbit:
561                 # the last edge of the packet
562                 self.edges.append(self.previous)
563                 # Export the packet
564                 self.decode_packet()
565                 # Reset for next packet
566                 self.startsample = self.samplenum
567                 self.bits = []
568                 self.edges = []
569                 self.bad = []
570                 self.half_one = False
571                 self.start_one = 0
572             else:   # add the bit to the packet
573                 is_zero = diff > self.threshold
574                 if is_zero and not self.half_one:
575                     self.bits.append(0)
576                     self.edges.append(self.previous)
577                 elif not is_zero and self.half_one:
578                     self.bits.append(1)
579                     self.edges.append(self.start_one)
580                     self.half_one = False
581                 elif not is_zero and not self.half_one:
582                     self.half_one = True
583                     self.start_one = self.previous
584                 else:   # Invalid BMC sequence
585                     self.bad.append((self.start_one, self.previous))
586                     # TODO try to recover
587                     self.bits.append(0)
588                     self.edges.append(self.previous)
589                     self.half_one = False
590             self.previous = self.samplenum