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