X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=blobdiff_plain;f=decoders%2Fmdio%2Fpd.py;h=8e970b1db5d2888cb835ad1b4febe4862a229802;hp=bb1f53f82cc1c54813afc069f0c90d98b0cd2ef6;hb=d6d8a8a440ea2a81e6ddde33d16bc84d01cdb432;hpb=e4302cbe5cb375a50bd9a30a8a2855d9fa6ab14d diff --git a/decoders/mdio/pd.py b/decoders/mdio/pd.py index bb1f53f..8e970b1 100644 --- a/decoders/mdio/pd.py +++ b/decoders/mdio/pd.py @@ -1,260 +1,326 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2014 Aurelien Jacobs +## Copyright (C) 2016 Elias Oenal +## All rights reserved. ## -## 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 -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: ## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. +## 1. Redistributions of source code must retain the above copyright notice, +## this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright notice, +## this list of conditions and the following disclaimer in the documentation +## and/or other materials provided with the distribution. ## -## 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 +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. ## import sigrokdecode as srd class Decoder(srd.Decoder): - api_version = 2 + api_version = 3 id = 'mdio' name = 'MDIO' longname = 'Management Data Input/Output' desc = 'Half-duplex sync serial bus for MII management between MAC and PHY.' - license = 'gplv2+' + license = 'bsd' inputs = ['logic'] outputs = ['mdio'] + tags = ['Networking'] channels = ( {'id': 'mdc', 'name': 'MDC', 'desc': 'Clock'}, {'id': 'mdio', 'name': 'MDIO', 'desc': 'Data'}, ) + options = ( + {'id': 'show_debug_bits', 'desc': 'Show debug bits', + 'default': 'no', 'values': ('yes', 'no')}, + ) annotations = ( - ('mdio-data', 'MDIO data'), - ('mdio-bits', 'MDIO bits'), - ('errors', 'Human-readable errors'), + ('bit-val', 'Bit value'), + ('bit-num', 'Bit number'), + ('frame', 'Frame'), + ('frame-idle', 'Bus idle state'), + ('frame-error', 'Frame error'), + ('decode', 'Decode'), ) annotation_rows = ( - ('mdio-data', 'MDIO data', (0,)), - ('mdio-bits', 'MDIO bits', (1,)), - ('other', 'Other', (2,)), + ('bit-val', 'Bit value', (0,)), + ('bit-num', 'Bit number', (1,)), + ('frame', 'Frame', (2, 3)), + ('frame-error', 'Frame error', (4,)), + ('decode', 'Decode', (5,)), ) def __init__(self): - self.oldmdc = 0 - self.ss_block = -1 + self.reset() + + def reset(self): + self.illegal_bus = 0 self.samplenum = -1 - self.oldpins = None + self.clause45_addr = -1 # Clause 45 is context sensitive. self.reset_decoder_state() def start(self): self.out_python = self.register(srd.OUTPUT_PYTHON) self.out_ann = self.register(srd.OUTPUT_ANN) - def putw(self, data): - self.put(self.ss_block, self.samplenum, self.out_ann, data) + def putbit(self, mdio, ss, es): + self.put(ss, es, self.out_ann, [0, ['%d' % mdio]]) + if self.options['show_debug_bits'] == 'yes': + self.put(ss, es, self.out_ann, [1, ['%d' % (self.bitcount - 1), '%d' % ((self.bitcount - 1) % 10)]]) - def putbit(self, mdio, start, stop): - # Bit annotations. - self.put(start, stop, self.out_ann, [1, ['%d' % mdio]]) + def putff(self, data): + self.put(self.ss_frame_field, self.samplenum, self.out_ann, data) def putdata(self): - # FIXME: Only pass data, no bits. - # Pass MDIO bits and then data to the next PD up the stack. - ss, es = self.mdiobits[-1][1], self.mdiobits[0][2] - - # self.put(ss, es, self.out_python, ['BITS', self.mdiobits]) - self.put(ss, es, self.out_python, ['DATA', self.mdiodata]) - - # Bit annotations. - for bit in self.mdiobits: - self.put(bit[1], bit[2], self.out_ann, [1, ['%d' % bit[0]]]) - - # Error annotation if an error happened. - if self.error: - self.put(self.ss_bit, self.es_error, self.out_ann, [2, [self.error]]) - return - - op = 'READ' if self.operation else 'WRITE' - - # Dataword annotations. - if self.ss_preamble != -1: - self.put(self.ss_preamble, self.ss_start, self.out_ann, [0, ['PREAMBLE']]) - self.put(self.ss_start, self.ss_operation, self.out_ann, [0, ['START']]) - self.put(self.ss_operation, self.ss_phy, self.out_ann, [0, [op]]) - self.put(self.ss_phy, self.ss_reg, self.out_ann, [0, ['PHY: %d' % self.phy]]) - self.put(self.ss_reg, self.ss_turnaround, self.out_ann, [0, ['REG: %d' % self.reg]]) - self.put(self.ss_turnaround, self.ss_data, self.out_ann, [0, ['TURNAROUND']]) - self.put(self.ss_data, self.es_data, self.out_ann, [0, ['DATA: %04X' % self.data]]) + self.put(self.ss_frame_field, self.mdiobits[0][2], self.out_ann, + [2, ['DATA: %04X' % self.data, 'DATA', 'D']]) + + if self.clause45 and self.opcode == 0: + self.clause45_addr = self.data + + # Decode data. + if self.opcode > 0 or not self.clause45: + decoded_min = '' + if self.clause45 and self.clause45_addr != -1: + decoded_min += str.format('ADDR: %04X ' % self.clause45_addr) + elif self.clause45: + decoded_min += str.format('ADDR: UKWN ') + + if self.clause45 and self.opcode > 1 \ + or (not self.clause45 and self.opcode): + decoded_min += str.format('READ: %04X' % self.data) + is_read = 1 + else: + decoded_min += str.format('WRITE: %04X' % self.data) + is_read = 0 + decoded_ext = str.format(' %s: %02d' % \ + ('PRTAD' if self.clause45 else 'PHYAD', self.portad)) + decoded_ext += str.format(' %s: %02d' % \ + ('DEVAD' if self.clause45 else 'REGAD', self.devad)) + if self.ta_invalid or self.op_invalid: + decoded_ext += ' ERROR' + self.put(self.ss_frame, self.mdiobits[0][2], self.out_ann, + [5, [decoded_min + decoded_ext, decoded_min]]) + + self.put(self.ss_frame, self.mdiobits[0][2], self.out_python, + [(bool(self.clause45), int(self.clause45_addr), \ + bool(is_read), int(self.portad), int(self.devad), \ + int(self.data))]) + + # Post read increment address. + if self.clause45 and self.opcode == 2 and self.clause45_addr != -1: + self.clause45_addr += 1 def reset_decoder_state(self): - self.mdiodata = 0 self.mdiobits = [] - self.bitcount = 0 - self.ss_preamble = -1 - self.ss_start = -1 - self.ss_operation = -1 - self.ss_phy = -1 - self.ss_reg = -1 - self.ss_turnaround = -1 - self.ss_data = -1 - self.phy = 0 - self.phy_bits = 0 - self.reg = 0 - self.reg_bits = 0 - self.data = 0 - self.data_bits = 0 - self.state = 'PREAMBLE' - self.error = None - - def parse_preamble(self, mdio): - if self.ss_preamble == -1: - self.ss_preamble = self.samplenum - if mdio != 1: - self.error = 'Invalid preamble: could not find 32 consecutive bits set to 1' - self.state = 'ERROR' - elif self.bitcount == 31: - self.state = 'START' + self.bitcount = -1 + self.opcode = -1 + self.clause45 = 0 + self.ss_frame = -1 + self.ss_frame_field = -1 + self.preamble_len = 0 + self.ta_invalid = -1 + self.op_invalid = '' + self.portad = -1 + self.portad_bits = 5 + self.devad = -1 + self.devad_bits = 5 + self.data = -1 + self.data_bits = 16 + self.state = 'PRE' + + def state_PRE(self, mdio): + if self.illegal_bus: + if mdio == 0: # Stay in illegal bus state. + return + else: # Leave and continue parsing. + self.illegal_bus = 0 + self.put(self.ss_illegal, self.samplenum, self.out_ann, + [4, ['ILLEGAL BUS STATE', 'ILL']]) + self.ss_frame = self.samplenum + + if self.ss_frame == -1: + self.ss_frame = self.samplenum + + if mdio == 1: + self.preamble_len += 1 + + # Valid MDIO can't clock more than 16 succeeding ones without being + # in either IDLE or PRE. + if self.preamble_len > 16: + if self.preamble_len >= 10000 + 32: + self.put(self.ss_frame, self.mdiobits[32][1], self.out_ann, + [3, ['IDLE #%d' % (self.preamble_len - 32), 'IDLE', 'I']]) + self.ss_frame = self.mdiobits[32][1] + self.preamble_len = 32 + # This is getting out of hand, free some memory. + del self.mdiobits[33:-1] + if mdio == 0: + if self.preamble_len < 32: + self.ss_frame = self.mdiobits[self.preamble_len][1] + self.put(self.ss_frame, self.samplenum, self.out_ann, + [4, ['SHORT PREAMBLE', 'SHRT PRE']]) + elif self.preamble_len > 32: + self.ss_frame = self.mdiobits[32][1] + self.put(self.mdiobits[self.preamble_len][1], + self.mdiobits[32][1], self.out_ann, + [3, ['IDLE #%d' % (self.preamble_len - 32), + 'IDLE', 'I']]) + self.preamble_len = 32 + else: + self.ss_frame = self.mdiobits[32][1] + self.put(self.ss_frame, self.samplenum, self.out_ann, + [2, ['PRE #%d' % self.preamble_len, 'PRE', 'P']]) + self.ss_frame_field = self.samplenum + self.state = 'ST' + elif mdio == 0: + self.ss_illegal = self.ss_frame + self.illegal_bus = 1 + + def state_ST(self, mdio): + if mdio == 0: + self.clause45 = 1 + self.state = 'OP' + + def state_OP(self, mdio): + if self.opcode == -1: + if self.clause45: + st = ['ST (Clause 45)', 'ST 45'] + else: + st = ['ST (Clause 22)', 'ST 22'] + self.putff([2, st + ['ST', 'S']]) + self.ss_frame_field = self.samplenum - def parse_start(self, mdio): - if self.ss_start == -1: - if mdio != 0: - self.error = 'Invalid start bits: should be 01' - self.state = 'ERROR' + if mdio: + self.opcode = 2 else: - self.ss_start = self.samplenum + self.opcode = 0 else: - if mdio != 1: - self.error = 'Invalid start bits: should be 01' - self.state = 'ERROR' + if self.clause45: + self.state = 'PRTAD' + self.opcode += mdio else: - self.state = 'OPERATION' - - def parse_operation(self, mdio): - if self.ss_operation == -1: - self.ss_operation = self.samplenum - self.operation = mdio - else: - if mdio == self.operation: - self.error = 'Invalid operation bits' - self.state = 'ERROR' + if mdio == self.opcode: + self.op_invalid = 'invalid for Clause 22' + self.state = 'PRTAD' + + def state_PRTAD(self, mdio): + if self.portad == -1: + self.portad = 0 + if self.clause45: + if self.opcode == 0: + op = ['OP: ADDR', 'OP: A'] + elif self.opcode == 1: + op = ['OP: WRITE', 'OP: W'] + elif self.opcode == 2: + op = ['OP: READINC', 'OP: RI'] + elif self.opcode == 3: + op = ['OP: READ', 'OP: R'] else: - self.state = 'PHY' - - def parse_phy(self, mdio): - if self.ss_phy == -1: - self.ss_phy = self.samplenum - self.phy_bits += 1 - self.phy |= mdio << (5 - self.phy_bits) - if self.phy_bits == 5: - self.state = 'REG' - - def parse_reg(self, mdio): - if self.ss_reg == -1: - self.ss_reg = self.samplenum - self.reg_bits += 1 - self.reg |= mdio << (5 - self.reg_bits) - if self.reg_bits == 5: - self.state = 'TURNAROUND' - - def parse_turnaround(self, mdio): - if self.ss_turnaround == -1: - if self.operation == 0 and mdio != 1: - self.error = 'Invalid turnaround bits' - self.state = 'ERROR' + op = ['OP: READ', 'OP: R'] if self.opcode else ['OP: WRITE', 'OP: W'] + self.putff([2, op + ['OP', 'O']]) + if self.op_invalid: + self.putff([4, ['OP %s' % self.op_invalid, 'OP', 'O']]) + self.ss_frame_field = self.samplenum + self.portad_bits -= 1 + self.portad |= mdio << self.portad_bits + if not self.portad_bits: + self.state = 'DEVAD' + + def state_DEVAD(self, mdio): + if self.devad == -1: + self.devad = 0 + if self.clause45: + prtad = ['PRTAD: %02d' % self.portad, 'PRT', 'P'] + else: + prtad = ['PHYAD: %02d' % self.portad, 'PHY', 'P'] + self.putff([2, prtad]) + self.ss_frame_field = self.samplenum + self.devad_bits -= 1 + self.devad |= mdio << self.devad_bits + if not self.devad_bits: + self.state = 'TA' + + def state_TA(self, mdio): + if self.ta_invalid == -1: + self.ta_invalid = '' + if self.clause45: + regad = ['DEVAD: %02d' % self.devad, 'DEV', 'D'] else: - self.ss_turnaround = self.samplenum + regad = ['REGAD: %02d' % self.devad, 'REG', 'R'] + self.putff([2, regad]) + self.ss_frame_field = self.samplenum + if mdio != 1 and ((self.clause45 and self.opcode < 2) + or (not self.clause45 and self.opcode == 0)): + self.ta_invalid = ' invalid (bit1)' else: if mdio != 0: - self.error = 'Invalid turnaround bits' - self.state = 'ERROR' - else: - self.state = 'DATA' - - def parse_data(self, mdio): - if self.ss_data == -1: - self.ss_data = self.samplenum - self.data_bits += 1 - self.data |= mdio << (16 - self.data_bits) - if self.data_bits == 16: - self.es_data = self.samplenum + int((self.samplenum - self.ss_data) / 15) - self.state = 'DONE' - - def parse_error(self, mdio): - if self.bitcount == 63: - self.es_error = self.samplenum + int((self.samplenum - self.ss_bit) / 63) - self.state = 'DONE' - - def handle_bit(self, mdio): - # If this is the first bit of a command, save its sample number. - if self.bitcount == 0: - self.ss_bit = self.samplenum - # No preamble? - if mdio == 0: - self.state = 'START' - - # Guesstimate the endsample for this bit (can be overridden below). - es = self.samplenum - if self.bitcount > 0: - es += self.samplenum - self.mdiobits[0][1] - - self.mdiobits.insert(0, [mdio, self.samplenum, es]) - - if self.bitcount > 0: - self.bitsamples = (self.samplenum - self.ss_bit) / self.bitcount - self.mdiobits[1][2] = self.samplenum - - if self.state == 'PREAMBLE': - self.parse_preamble(mdio) - elif self.state == 'START': - self.parse_start(mdio) - elif self.state == 'OPERATION': - self.parse_operation(mdio) - elif self.state == 'PHY': - self.parse_phy(mdio) - elif self.state == 'REG': - self.parse_reg(mdio) - elif self.state == 'TURNAROUND': - self.parse_turnaround(mdio) - elif self.state == 'DATA': - self.parse_data(mdio) - elif self.state == 'ERROR': - self.parse_error(mdio) - - self.bitcount += 1 - if self.state == 'DONE': - self.putdata() - self.reset_decoder_state() - - def find_mdc_edge(self, mdc, mdio): - # Output the current error annotation if the clock stopped running - if self.state == 'ERROR' and self.samplenum - self.clocksample > (1.5 * self.bitsamples): - self.es_error = self.clocksample + int((self.clocksample - self.ss_bit) / self.bitcount) + if self.ta_invalid: + self.ta_invalid = ' invalid (bit1 and bit2)' + else: + self.ta_invalid = ' invalid (bit2)' + self.state = 'DATA' + + def state_DATA(self, mdio): + if self.data == -1: + self.data = 0 + self.putff([2, ['TA', 'T']]) + if self.ta_invalid: + self.putff([4, ['TA%s' % self.ta_invalid, 'TA', 'T']]) + self.ss_frame_field = self.samplenum + self.data_bits -= 1 + self.data |= mdio << self.data_bits + if not self.data_bits: + # Output final bit. + self.mdiobits[0][2] = self.mdiobits[0][1] + self.quartile_cycle_length() + self.bitcount += 1 + self.putbit(self.mdiobits[0][0], self.mdiobits[0][1], self.mdiobits[0][2]) self.putdata() self.reset_decoder_state() - # Ignore sample if the clock pin hasn't changed. - if mdc == self.oldmdc: - return + def process_state(self, argument, mdio): + method_name = 'state_' + str(argument) + method = getattr(self, method_name) + return method(mdio) + + # Returns the first quartile point of the frames cycle lengths. This is a + # conservative guess for the end of the last cycle. On average it will be + # more likely to fall short, than being too long, which makes for better + # readability in GUIs. + def quartile_cycle_length(self): + # 48 is the minimum number of samples we have to have at the end of a + # frame. The last sample only has a leading clock edge and is ignored. + bitlen = [] + for i in range(1, 49): + bitlen.append(self.mdiobits[i][2] - self.mdiobits[i][1]) + bitlen = sorted(bitlen) + return bitlen[12] - self.oldmdc = mdc - - if mdc == 0: # Sample on rising clock edge. - return + def handle_bit(self, mdio): + self.bitcount += 1 + self.mdiobits.insert(0, [mdio, self.samplenum, -1]) - # Found the correct clock edge, now get/handle the bit(s). - self.clocksample = self.samplenum - self.handle_bit(mdio) + if self.bitcount > 0: + self.mdiobits[1][2] = self.samplenum # Note end of last cycle. + # Output the last bit we processed. + self.putbit(self.mdiobits[1][0], self.mdiobits[1][1], self.mdiobits[1][2]) - def decode(self, ss, es, data): - for (self.samplenum, pins) in data: - # Ignore identical samples early on (for performance reasons). - if self.oldpins == pins: - continue - self.oldpins, (mdc, mdio) = pins, pins + self.process_state(self.state, mdio) - self.find_mdc_edge(mdc, mdio) + def decode(self): + while True: + # Process pin state upon rising MDC edge. + pins = self.wait({0: 'r'}) + self.handle_bit(pins[1])