X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=blobdiff_plain;f=decoders%2Fusb_power_delivery%2Fpd.py;h=cc0276a9e230bdfbbbf4d0374a78a5a75ef91420;hp=312cf52a3143c5436d516bc1059504f8ff844022;hb=d0d63deadf5b841199c42721bc498fc5c9a86126;hpb=fddb311472d4458382418de3f091bd1b997d3a89 diff --git a/decoders/usb_power_delivery/pd.py b/decoders/usb_power_delivery/pd.py index 312cf52..cc0276a 100644 --- a/decoders/usb_power_delivery/pd.py +++ b/decoders/usb_power_delivery/pd.py @@ -2,6 +2,8 @@ ## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2015 Google, Inc +## Copyright (C) 2018 davidanger +## Copyright (C) 2018 Peter Hazenberg ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -14,15 +16,14 @@ ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## along with this program; if not, see . ## import sigrokdecode as srd import struct import zlib # for crc32 -# BMC encoding with a 600Khz datarate +# BMC encoding with a 600kHz datarate UI_US = 1000000/600000.0 # Threshold to discriminate half-1 from 0 in Binary Mark Conding @@ -30,31 +31,40 @@ THRESHOLD_US = (UI_US + 2 * UI_US) / 2 # Control Message type CTRL_TYPES = { - 0: "reserved", - 1: "GOOD CRC", - 2: "GOTO MIN", - 3: "ACCEPT", - 4: "REJECT", - 5: "PING", - 6: "PS RDY", - 7: "GET SOURCE CAP", - 8: "GET SINK CAP", - 9: "DR SWAP", - 10: "PR SWAP", - 11: "VCONN SWAP", - 12: "WAIT", - 13: "SOFT RESET", - 14: "reserved", - 15: "reserved" + 0: 'reserved', + 1: 'GOOD CRC', + 2: 'GOTO MIN', + 3: 'ACCEPT', + 4: 'REJECT', + 5: 'PING', + 6: 'PS RDY', + 7: 'GET SOURCE CAP', + 8: 'GET SINK CAP', + 9: 'DR SWAP', + 10: 'PR SWAP', + 11: 'VCONN SWAP', + 12: 'WAIT', + 13: 'SOFT RESET', + 14: 'reserved', + 15: 'reserved', + 16: 'Not Supported', + 17: 'Get_Source_Cap_Extended', + 18: 'Get_Status', + 19: 'FR_Swap', + 20: 'Get_PPS_Status', + 21: 'Get_Country_Codes', } # Data message type DATA_TYPES = { - 1: "SOURCE CAP", - 2: "REQUEST", - 3: "BIST", - 4: "SINK CAP", - 15: "VDM" + 1: 'SOURCE CAP', + 2: 'REQUEST', + 3: 'BIST', + 4: 'SINK CAP', + 5: 'Battery_Status', + 6: 'Alert', + 7: 'Get_Country_Info', + 15: 'VDM' } # 4b5b encoding of the symbols @@ -102,14 +112,23 @@ EOP = 0x16 SYNC_CODES = [SYNC1, SYNC2, SYNC3] HRST_CODES = [RST1, RST1, RST1, RST2] +SOP_SEQUENCES = [ + (SYNC1, SYNC1, SYNC1, SYNC2), + (SYNC1, SYNC1, SYNC3, SYNC3), + (SYNC1, SYNC3, SYNC1, SYNC3), + (SYNC1, RST2, RST2, SYNC3), + (SYNC1, RST2, SYNC3, SYNC2), + (RST1, SYNC1, RST1, SYNC3), + (RST1, RST1, RST1, RST2), +] START_OF_PACKETS = { - (SYNC1, SYNC1, SYNC1, SYNC2): "SOP", - (SYNC1, SYNC1, SYNC3, SYNC3): "SOP'", - (SYNC1, SYNC3, SYNC1, SYNC3): 'SOP"', - (SYNC1, RST2, RST2, SYNC3): "SOP' Debug", - (SYNC1, RST2, SYNC3, SYNC2): 'SOP" Debug', - (RST1, SYNC1, RST1, SYNC3): "Cable Reset", - (RST1, RST1, RST1, RST2): "Hard Reset", + SOP_SEQUENCES[0]: 'SOP', + SOP_SEQUENCES[1]: "SOP'", + SOP_SEQUENCES[2]: 'SOP"', + SOP_SEQUENCES[3]: "SOP' Debug", + SOP_SEQUENCES[4]: 'SOP" Debug', + SOP_SEQUENCES[5]: 'Cable Reset', + SOP_SEQUENCES[6]: 'Hard Reset', } SYM_NAME = [ @@ -139,48 +158,44 @@ SYM_NAME = [ ] RDO_FLAGS = { - (1 << 24): "no_suspend", - (1 << 25): "comm_cap", - (1 << 26): "cap_mismatch", - (1 << 27): "give_back" -} -PDO_TYPE = ["", "BATT:", "VAR:", ""] -PDO_FLAGS = { - (1 << 29): "dual_role_power", - (1 << 28): "suspend", - (1 << 27): "ext", - (1 << 26): "comm_cap", - (1 << 25): "dual_role_data" + (1 << 23): 'unchunked', + (1 << 24): 'no_suspend', + (1 << 25): 'comm_cap', + (1 << 26): 'cap_mismatch', + (1 << 27): 'give_back' } BIST_MODES = { - 0: "Receiver", - 1: "Transmit", - 2: "Counters", - 3: "Carrier 0", - 4: "Carrier 1", - 5: "Carrier 2", - 6: "Carrier 3", - 7: "Eye", + 0: 'Receiver', + 1: 'Transmit', + 2: 'Counters', + 3: 'Carrier 0', + 4: 'Carrier 1', + 5: 'Carrier 2', + 6: 'Carrier 3', + 7: 'Eye', } VDM_CMDS = { - 1: "Disc Ident", - 2: "Disc SVID", - 3: "Disc Mode", - 4: "Enter Mode", - 5: "Exit Mode", - 6: "Attention", + 1: 'Disc Ident', + 2: 'Disc SVID', + 3: 'Disc Mode', + 4: 'Enter Mode', + 5: 'Exit Mode', + 6: 'Attention', # 16..31: SVID Specific Commands # DisplayPort Commands - 16: "DP Status", - 17: "DP Configure", + 16: 'DP Status', + 17: 'DP Configure', } -VDM_ACK = ["REQ", "ACK", "NAK", "BSY"] +VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY'] +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): - api_version = 2 + api_version = 3 id = 'usb_power_delivery' name = 'USB PD' longname = 'USB Power Delivery' @@ -189,7 +204,10 @@ class Decoder(srd.Decoder): inputs = ['logic'] outputs = ['usb_pd'] channels = ( - {'id': 'cc', 'name': 'CC', 'desc': 'Control channel'}, + {'id': 'cc1', 'name': 'CC1', 'desc': 'Configuration Channel 1'}, + ) + optional_channels = ( + {'id': 'cc2', 'name': 'CC2', 'desc': 'Configuration Channel 2'}, ) options = ( {'id': 'fulltext', 'desc': 'full text decoding of the packet', @@ -222,122 +240,168 @@ class Decoder(srd.Decoder): ('raw-data', 'RAW binary data'), ) + stored_pdos = {} + + def get_request(self, rdo): pos = (rdo >> 28) & 7 - op_ma = ((rdo >> 10) & 0x3ff) * 10 - max_ma = (rdo & 0x3ff) * 10 - flags = "" - for f in RDO_FLAGS.keys(): - if rdo & f: - flags += " " + RDO_FLAGS[f] - return "[%d]%d/%d mA%s" % (pos, op_ma, max_ma, flags) - - def get_source_cap(self, pdo): - t = (pdo >> 30) & 3 - if t == 0: - mv = ((pdo >> 10) & 0x3ff) * 50 - ma = ((pdo >> 0) & 0x3ff) * 10 - p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0) - elif t == 1: - minv = ((pdo >> 10) & 0x3ff) * 50 - maxv = ((pdo >> 20) & 0x3ff) * 50 - mw = ((pdo >> 0) & 0x3ff) * 250 - p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0) - elif t == 2: - minv = ((pdo >> 10) & 0x3ff) * 50 - maxv = ((pdo >> 20) & 0x3ff) * 50 - ma = ((pdo >> 0) & 0x3ff) * 10 - p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0) + + op_ma = ((rdo >> 10) & 0x3ff) * 0.01 + max_ma = (rdo & 0x3ff) * 0.01 + + mark = self.cap_mark[pos] + if mark == 3: + op_v = ((rdo >> 9) & 0x7ff) * 0.02 + op_a = (rdo & 0x3f) * 0.05 + t_settings = '%gV %gA' % (op_v, op_a) + elif mark == 2: + op_w = ((rdo >> 10) & 0x3ff) * 0.25 + mp_w = (rdo & 0x3ff) * 0.25 + t_settings = '%gW (operating)' % op_w else: - p = "" - flags = "" - for f in PDO_FLAGS.keys(): - if pdo & f: - flags += " " + PDO_FLAGS[f] - return "%s%s%s" % (PDO_TYPE[t], p, flags) - - def get_sink_cap(self, pdo): - t = (pdo >> 30) & 3 - if t == 0: - mv = ((pdo >> 10) & 0x3ff) * 50 - ma = ((pdo >> 0) & 0x3ff) * 10 - p = "%.1fV %.1fA" % (mv/1000.0, ma/1000.0) - elif t == 1: - minv = ((pdo >> 10) & 0x3ff) * 50 - maxv = ((pdo >> 20) & 0x3ff) * 50 - mw = ((pdo >> 0) & 0x3ff) * 250 - p = "%.1f/%.1fV %.1fW" % (minv/1000.0, maxv/1000.0, mw/1000.0) - elif t == 2: - minv = ((pdo >> 10) & 0x3ff) * 50 - maxv = ((pdo >> 20) & 0x3ff) * 50 - ma = ((pdo >> 0) & 0x3ff) * 10 - p = "%.1f/%.1fV %.1fA" % (minv/1000.0, maxv/1000.0, ma/1000.0) + op_a = ((rdo >> 10) & 0x3ff) * 0.01 + max_a = (rdo & 0x3ff) * 0.01 + t_settings = '%gA (operating) / %gA (max)' % (op_a, max_a) + + t_flags = '' + for f in sorted(RDO_FLAGS.keys(), reverse = True): + if rdo & f: + t_flags += ' [' + RDO_FLAGS[f] + ']' + + if pos in self.stored_pdos.keys(): + t_pdo = '#%d: %s' % (pos, self.stored_pdos[pos]) else: - p = "" - flags = "" - for f in PDO_FLAGS.keys(): + t_pdo = '#d' % (pos) + + return '(PDO %s) %s%s' % (t_pdo, t_settings, t_flags) + + def get_source_sink_cap(self, pdo, idx, source): + t1 = (pdo >> 30) & 3 + self.cap_mark[idx] = t1 + + flags = {} + if t1 == 0: + t_name = 'Fixed' + if source: + flags = { + (1 << 29): 'dual_role_power', + (1 << 28): 'suspend', + (1 << 27): 'unconstrained', + (1 << 26): 'comm_cap', + (1 << 25): 'dual_role_data', + (1 << 24): 'unchunked', + } + else: #sink + flags = { + (1 << 29): 'dual_role_power', + (1 << 28): 'high_capability', + (1 << 27): 'unconstrained', + (1 << 26): 'comm_cap', + (1 << 25): 'dual_role_data', + (0b01 << 23): 'fr_swap default power', + (0b10 << 23): 'fr_swap 1.5 A', + (0b11 << 23): 'fr_swap 3.0 A', + } + mv = ((pdo >> 10) & 0x3ff) * 0.05 + ma = ((pdo >> 0) & 0x3ff) * 0.01 + p = '%gV %gA (%gW)' % (mv, ma, mv*ma) + self.stored_pdos[idx] = '%s %gV' % (t_name, mv) + elif t1 == 1: + t_name = 'Battery' + flags = {} # No flags defined for Battery PDO in PD 3.0 spec + minv = ((pdo >> 10) & 0x3ff) * 0.05 + maxv = ((pdo >> 20) & 0x3ff) * 0.05 + mw = ((pdo >> 0) & 0x3ff) * 0.25 + p = '%g/%gV %gW' % (minv, maxv, mw) + self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) + elif t1 == 2: + t_name = 'Variable' + flags = {} # No flags defined for Variable PDO in PD 3.0 spec + minv = ((pdo >> 10) & 0x3ff) * 0.05 + maxv = ((pdo >> 20) & 0x3ff) * 0.05 + ma = ((pdo >> 0) & 0x3ff) * 0.01 + p = '%g/%gV %gA' % (minv, maxv, ma) + self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) + elif t1 == 3: + t2 = (pdo >> 28) & 3 + if t2 == 0: + t_name = 'Programmable|PPS' + flags = { + (1 << 29): 'power_limited', + } + minv = ((pdo >> 8) & 0xff) * 0.1 + maxv = ((pdo >> 17) & 0xff) * 0.1 + ma = ((pdo >> 0) & 0xff) * 0.05 + p = '%g/%gV %gA' % (minv, maxv, ma) + if (pdo >> 27) & 0x1: + p += ' [limited]' + self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) + else: + t_name = 'Reserved APDO: '+bin(t2) + p = '[raw: %s]' % (bin(pdo)) + self.stored_pdos[idx] = '%s %s' % (t_name, p) + t_flags = '' + for f in sorted(flags.keys(), reverse = True): if pdo & f: - flags += " " + PDO_FLAGS[f] - return "%s%s%s" % (PDO_TYPE[t], p, flags) + t_flags += ' [' + flags[f] + ']' + return '[%s] %s%s' % (t_name, p, t_flags) def get_vdm(self, idx, data): if idx == 0: # VDM header - vid = data >> 16 - struct = data & (1 << 15) - txt = "VDM" - if struct: # Structured VDM - cmd = data & 0x1f - src = data & (1 << 5) - ack = (data >> 6) & 3 - pos = (data >> 8) & 7 - ver = (data >> 13) & 3 - txt = VDM_ACK[ack] + " " - txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else "cmd?" - txt += " pos %d" % (pos) if pos else " " - else: # Unstructured VDM - txt = "unstruct [%04x]" % (data & 0x7fff) - txt += " SVID:%04x" % (vid) + vid = data >> 16 + struct = data & (1 << 15) + txt = 'VDM' + if struct: # Structured VDM + cmd = data & 0x1f + src = data & (1 << 5) + ack = (data >> 6) & 3 + pos = (data >> 8) & 7 + ver = (data >> 13) & 3 + txt = VDM_ACK[ack] + ' ' + txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?' + txt += ' pos %d' % (pos) if pos else ' ' + else: # Unstructured VDM + txt = 'unstruct [%04x]' % (data & 0x7fff) + txt += ' SVID:%04x' % (vid) else: # VDM payload - txt = "VDO:%08x" % (data) + txt = 'VDO:%08x' % (data) return txt def get_bist(self, idx, data): mode = data >> 28 counter = data & 0xffff - mode_name = BIST_MODES[mode] if mode in BIST_MODES else "INVALID" + mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID' if mode == 2: - mode_name = "Counter[= %d]" % (counter) + mode_name = 'Counter[= %d]' % (counter) # TODO check all 0 bits are 0 / emit warnings - return "mode %s" % (mode_name) if idx == 0 else "invalid BRO" + return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO' def putpayload(self, s0, s1, idx): t = self.head_type() - txt = "???" + txt = '['+str(idx+1)+'] ' if t == 2: - txt = self.get_request(self.data[idx]) - elif t == 1: - txt = self.get_source_cap(self.data[idx]) - elif t == 4: - txt = self.get_sink_cap(self.data[idx]) + txt += self.get_request(self.data[idx]) + elif t == 1 or t == 4: + txt += self.get_source_sink_cap(self.data[idx], idx+1, t==1) elif t == 15: - txt = self.get_vdm(idx, self.data[idx]) + txt += self.get_vdm(idx, self.data[idx]) elif t == 3: - txt = self.get_bist(idx, self.data[idx]) + txt += self.get_bist(idx, self.data[idx]) self.putx(s0, s1, [11, [txt, txt]]) - self.text += " - " + txt + self.text += ' - ' + txt def puthead(self): ann_type = 9 if self.head_power_role() else 10 - role = "SRC" if self.head_power_role() else "SNK" + role = 'SRC' if self.head_power_role() else 'SNK' if self.head_data_role() != self.head_power_role(): - role += "/DFP" if self.head_data_role() else "/UFP" + role += '/DFP' if self.head_data_role() else '/UFP' t = self.head_type() if self.head_count() == 0: shortm = CTRL_TYPES[t] else: - shortm = DATA_TYPES[t] if t in DATA_TYPES else "DAT???" + shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???' - longm = "{:s}[{:d}]:{:s}".format(role, self.head_id(), shortm) + longm = '(r{:d}) {:s}[{:d}]: {:s}'.format(self.head_rev(), role, self.head_id(), shortm) self.putx(0, -1, [ann_type, [longm, shortm]]) self.text += longm @@ -366,7 +430,7 @@ class Decoder(srd.Decoder): self.putx(0, -1, [8, [longm, shortm]]) def compute_crc32(self): - bdata = struct.pack("= 3: return START_OF_PACKETS[seq] return None @@ -411,7 +475,7 @@ class Decoder(srd.Decoder): for i in range(len(self.bits) - 19): k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False), self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False)) - sym = START_OF_PACKETS[k] if k in START_OF_PACKETS else None + sym = START_OF_PACKETS.get(k, None) if not sym: sym = self.find_corrupted_sop(k) # We have an interesting symbol sequence @@ -424,33 +488,35 @@ class Decoder(srd.Decoder): self.rec_sym(i+10, k[2]) self.rec_sym(i+15, k[3]) if sym == 'Hard Reset': - self.text += "HRST" + self.text += 'HRST' return -1 # Hard reset elif sym == 'Cable Reset': - self.text += "CRST" + self.text += 'CRST' return -1 # Cable reset else: self.putx(i, i+20, [2, [sym, 'S']]) return i+20 self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']]) - self.text += "Junk???" - self.putwarn("No start of packet found", "XXX") + self.text += 'Junk???' + self.putwarn('No start of packet found', 'XXX') return -1 # No Start Of Packet - def __init__(self, **kwargs): + def __init__(self): + self.reset() + + def reset(self): self.samplerate = None self.idx = 0 self.packet_seq = 0 - - self.samplenum = 0 self.previous = 0 - self.oldpins = [0] self.startsample = None self.bits = [] self.edges = [] self.bad = [] self.half_one = False self.start_one = 0 + self.stored_pdos = {} + self.cap_mark = [0, 0, 0, 0, 0, 0, 0, 0] def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: @@ -470,21 +536,19 @@ class Decoder(srd.Decoder): ) def us2samples(self, us): - if self.samplerate is None: - raise Exception("Need the samplerate.") return int(us * self.samplerate / 1000000) def decode_packet(self): self.data = [] self.idx = 0 - self.text = "" + self.text = '' if len(self.edges) < 50: return # Not a real PD packet self.packet_seq += 1 tstamp = float(self.startsample) / self.samplerate - self.text += "#%-4d (%8.6fms): " % (self.packet_seq, tstamp*1000) + self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000) self.idx = self.scan_eop() if self.idx < 0: @@ -508,7 +572,7 @@ class Decoder(srd.Decoder): self.crc = self.get_word() ccrc = self.compute_crc32() if self.crc != ccrc: - self.putwarn("Bad CRC %08x != %08x" % (self.crc, ccrc), "CRC!") + self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!') self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']]) # End of Packet @@ -516,7 +580,7 @@ class Decoder(srd.Decoder): self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']]) self.idx += 5 else: - self.putwarn("No EOP", "EOP!") + self.putwarn('No EOP', 'EOP!') # Full text trace if self.options['fulltext'] == 'yes': self.putx(0, self.idx, [12, [self.text, '...']]) @@ -526,17 +590,13 @@ class Decoder(srd.Decoder): bitrate = self.samplerate*len(self.bits) / float(es - ss) self.put(es, ss, self.out_bitrate, int(bitrate)) # Raw binary data (BMC decoded) - self.put(es, ss, self.out_binary, (0, bytes(self.bits))) - - def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") - for (self.samplenum, pins) in data: - # find edges ... - if self.oldpins == pins: - continue + self.put(es, ss, self.out_binary, [0, bytes(self.bits)]) - self.oldpins, (cc, ) = pins, pins + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + pins = self.wait([{0: 'e'}, {1: 'e'}, {'skip': int(self.samplerate/1e3)}]) # First sample of the packet, just record the start date if not self.startsample: