]> sigrok.org Git - libsigrokdecode.git/blob - decoders/usb_power_delivery/pd.py
8baf898dc4c02f2a96425c20d5daf4756ff38b99
[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 STORED_PDOS = {}
191
192 class SamplerateError(Exception):
193     pass
194
195 class Decoder(srd.Decoder):
196     api_version = 3
197     id = 'usb_power_delivery'
198     name = 'USB PD'
199     longname = 'USB Power Delivery'
200     desc = 'USB Power Delivery protocol.'
201     license = 'gplv2+'
202     inputs = ['logic']
203     outputs = ['usb_pd']
204     channels = (
205         {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'},
206     )
207     options = (
208         {'id': 'fulltext', 'desc': 'full text decoding of the packet',
209          'default': 'no', 'values': ('yes', 'no')},
210     )
211     annotations = (
212         ('type', 'Packet Type'),
213         ('Preamble', 'Preamble'),
214         ('SOP', 'Start of Packet'),
215         ('Head', 'Header'),
216         ('Data', 'Data'),
217         ('CRC', 'Checksum'),
218         ('EOP', 'End Of Packet'),
219         ('Sym', '4b5b symbols'),
220         ('warnings', 'Warnings'),
221         ('src', 'Source Message'),
222         ('snk', 'Sink Message'),
223         ('payload', 'Payload'),
224         ('text', 'Plain text'),
225     )
226     annotation_rows = (
227        ('4B5B', 'symbols', (7, )),
228        ('Phase', 'parts', (1, 2, 3, 4, 5, 6, )),
229        ('payload', 'Payload', (11, )),
230        ('type', 'Type', (0, 9, 10, )),
231        ('warnings', 'Warnings', (8, )),
232        ('text', 'Full text', (12, )),
233     )
234     binary = (
235         ('raw-data', 'RAW binary data'),
236     )
237
238     def get_request(self, rdo):
239         pos = (rdo >> 28) & 7
240         op_ma = ((rdo >> 10) & 0x3ff) * 10
241         max_ma = (rdo & 0x3ff) * 10
242         flags = ''
243         for f in sorted(RDO_FLAGS.keys(), reverse = True):
244             if rdo & f:
245                 flags += ' [' + RDO_FLAGS[f] + ']'
246         return '(PDO #%d: %s) %gA (operating) / %gA (max)%s' % (pos, STORED_PDOS[pos], op_ma/1000.0, max_ma/1000.0, flags)
247
248     def get_source_sink_cap(self, pdo, idx):
249         t1 = (pdo >> 30) & 3
250         if t1 == 0:
251             t_name = 'Fixed'
252             mv = ((pdo >> 10) & 0x3ff) * 0.05
253             ma = ((pdo >> 0) & 0x3ff) * 0.01
254             p = '%gV %gA (%gW)' % (mv, ma, mv*ma)
255             STORED_PDOS[idx] = '%s %gV' % (t_name, mv)
256         elif t1 == 1:
257             t_name = 'Battery'
258             minv = ((pdo >> 10) & 0x3ff) * 0.05
259             maxv = ((pdo >> 20) & 0x3ff) * 0.05
260             mw = ((pdo >> 0) & 0x3ff) * 0.25
261             p = '%g/%gV %gW' % (minv, maxv, mw)
262             STORED_PDOS[idx] = '%s %g/%gV' % (t_name, minv, maxv)
263         elif t1 == 2:
264             t_name = 'Variable'
265             minv = ((pdo >> 10) & 0x3ff) * 0.05
266             maxv = ((pdo >> 20) & 0x3ff) * 0.05
267             ma = ((pdo >> 0) & 0x3ff) * 0.01
268             p = '%g/%gV %gA' % (minv, maxv, ma)
269             STORED_PDOS[idx] = '%s %g/%gV' % (t_name, minv, maxv)
270         elif t1 == 3:
271             t2 = (pdo >> 28) & 3
272             if t2 == 0:
273                 t_name = 'Programmable'
274                 p = 'TODO: PPS support'
275             else:
276                 t_name = 'Reserved APDO: '+bin(t2)
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' % (t_name, 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 = '['+str(idx+1)+'] '
317         if t == 2:
318             txt += self.get_request(self.data[idx])
319         elif t == 1 or t == 4:
320             txt += self.get_source_sink_cap(self.data[idx], idx+1)
321         elif t == 15:
322             txt += self.get_vdm(idx, self.data[idx])
323         elif t == 3:
324             txt += self.get_bist(idx, self.data[idx])
325         self.putx(s0, s1, [11, [txt, txt]])
326         self.text += ' - ' + txt
327
328     def puthead(self):
329         ann_type = 9 if self.head_power_role() else 10
330         role = 'SRC' if self.head_power_role() else 'SNK'
331         if self.head_data_role() != self.head_power_role():
332             role += '/DFP' if self.head_data_role() else '/UFP'
333         t = self.head_type()
334         if self.head_count() == 0:
335             shortm = CTRL_TYPES[t]
336         else:
337             shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???'
338
339         longm = '{:s}[{:d}]:{:s}'.format(role, self.head_id(), shortm)
340         self.putx(0, -1, [ann_type, [longm, shortm]])
341         self.text += longm
342
343     def head_id(self):
344         return (self.head >> 9) & 7
345
346     def head_power_role(self):
347         return (self.head >> 8) & 1
348
349     def head_data_role(self):
350         return (self.head >> 5) & 1
351
352     def head_rev(self):
353         return ((self.head >> 6) & 3) + 1
354
355     def head_type(self):
356         return self.head & 0xF
357
358     def head_count(self):
359         return (self.head >> 12) & 7
360
361     def putx(self, s0, s1, data):
362         self.put(self.edges[s0], self.edges[s1], self.out_ann, data)
363
364     def putwarn(self, longm, shortm):
365         self.putx(0, -1, [8, [longm, shortm]])
366
367     def compute_crc32(self):
368         bdata = struct.pack('<H'+'I'*len(self.data), self.head & 0xffff,
369                             *tuple([d & 0xffffffff for d in self.data]))
370         return zlib.crc32(bdata)
371
372     def rec_sym(self, i, sym):
373         self.putx(i, i+5, [7, SYM_NAME[sym]])
374
375     def get_sym(self, i, rec=True):
376         v = (self.bits[i] | (self.bits[i+1] << 1) | (self.bits[i+2] << 2) |
377              (self.bits[i+3] << 3) | (self.bits[i+4] << 4))
378         sym = DEC4B5B[v]
379         if rec:
380             self.rec_sym(i, sym)
381         return sym
382
383     def get_short(self):
384         i = self.idx
385         # Check it's not a truncated packet
386         if len(self.bits) - i <= 20:
387             self.putwarn('Truncated', '!')
388             return 0x0BAD
389         k = [self.get_sym(i), self.get_sym(i+5),
390              self.get_sym(i+10), self.get_sym(i+15)]
391         # TODO check bad symbols
392         val = k[0] | (k[1] << 4) | (k[2] << 8) | (k[3] << 12)
393         self.idx += 20
394         return val
395
396     def get_word(self):
397         lo = self.get_short()
398         hi = self.get_short()
399         return lo | (hi << 16)
400
401     def find_corrupted_sop(self, k):
402         # Start of packet are valid even if they have only 3 correct symbols
403         # out of 4.
404         for seq in SOP_SEQUENCES:
405             if [k[i] == seq[i] for i in range(len(k))].count(True) >= 3:
406                 return START_OF_PACKETS[seq]
407         return None
408
409     def scan_eop(self):
410         for i in range(len(self.bits) - 19):
411             k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False),
412                  self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False))
413             sym = START_OF_PACKETS.get(k, None)
414             if not sym:
415                 sym = self.find_corrupted_sop(k)
416             # We have an interesting symbol sequence
417             if sym:
418                 # annotate the preamble
419                 self.putx(0, i, [1, ['Preamble', '...']])
420                 # annotate each symbol
421                 self.rec_sym(i, k[0])
422                 self.rec_sym(i+5, k[1])
423                 self.rec_sym(i+10, k[2])
424                 self.rec_sym(i+15, k[3])
425                 if sym == 'Hard Reset':
426                     self.text += 'HRST'
427                     return -1   # Hard reset
428                 elif sym == 'Cable Reset':
429                     self.text += 'CRST'
430                     return -1   # Cable reset
431                 else:
432                     self.putx(i, i+20, [2, [sym, 'S']])
433                 return i+20
434         self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']])
435         self.text += 'Junk???'
436         self.putwarn('No start of packet found', 'XXX')
437         return -1   # No Start Of Packet
438
439     def __init__(self):
440         self.reset()
441
442     def reset(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