]> sigrok.org Git - libsigrokdecode.git/blob - decoders/mdio/pd.py
Add an MDIO protocol decoder.
[libsigrokdecode.git] / decoders / mdio / pd.py
1 ##
2 ## This file is part of the libsigrokdecode project.
3 ##
4 ## Copyright (C) 2016 Elias Oenal <sigrok@eliasoenal.com>
5 ## All rights reserved.
6 ##
7 ## Redistribution and use in source and binary forms, with or without
8 ## modification, are permitted provided that the following conditions are met:
9 ##
10 ## 1. Redistributions of source code must retain the above copyright notice,
11 ##    this list of conditions and the following disclaimer.
12 ## 2. Redistributions in binary form must reproduce the above copyright notice,
13 ##    this list of conditions and the following disclaimer in the documentation
14 ##    and/or other materials provided with the distribution.
15 ##
16 ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 ## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 ## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 ## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 ## POSSIBILITY OF SUCH DAMAGE.
27 ##
28
29 import sigrokdecode as srd
30
31 class Decoder(srd.Decoder):
32     api_version = 2
33     id = 'mdio'
34     name = 'MDIO'
35     longname = 'Management Data Input/Output'
36     desc = 'Half-duplex sync serial bus for MII management between MAC and PHY.'
37     license = 'bsd'
38     inputs = ['logic']
39     outputs = ['mdio']
40     channels = (
41         {'id': 'mdc', 'name': 'MDC', 'desc': 'Clock'},
42         {'id': 'mdio', 'name': 'MDIO', 'desc': 'Data'},
43     )
44     options = (
45         {'id': 'show_debug_bits', 'desc': 'Show debug bits',
46             'default': 'no', 'values': ('yes', 'no')},
47     )
48     annotations = (
49         ('bit-val', 'Bit value'),
50         ('bit-num', 'Bit number'),
51         ('frame', 'Frame'),
52         ('frame-idle', 'Bus idle state'),
53         ('frame-error', 'Frame error'),
54         ('decode', 'Decode'),
55     )
56     annotation_rows = (
57         ('bit-val', 'Bit value', (0,)),
58         ('bit-num', 'Bit number', (1,)),
59         ('frame', 'Frame', (2, 3)),
60         ('frame-error', 'Frame error', (4,)),
61         ('decode', 'Decode', (5,)),
62     )
63
64     def __init__(self):
65         self.last_mdc = 1
66         self.illegal_bus = 0
67         self.samplenum = -1
68         self.clause45_addr = -1 # Clause 45 is context sensitive.
69         self.reset_decoder_state()
70
71     def start(self):
72         self.out_python = self.register(srd.OUTPUT_PYTHON)
73         self.out_ann = self.register(srd.OUTPUT_ANN)
74
75     def putbit(self, mdio, ss, es):
76         self.put(ss, es, self.out_ann, [0, ['%d' % mdio]])
77         if self.options['show_debug_bits'] == 'yes':
78             self.put(ss, es, self.out_ann, [1, ['%d' % (self.bitcount - 1), '%d' % ((self.bitcount - 1) % 10)]])
79
80     def putdata(self):
81         self.put(self.ss_frame_field, self.mdiobits[0][2], self.out_ann,
82                  [2, ['DATA: %04X' % self.data, 'DATA', 'D']])
83
84         if self.clause45 and self.opcode == 0:
85             self.clause45_addr = self.data
86
87         # Decode data.
88         if self.opcode > 0 or not self.clause45:
89             decoded_min = ''
90             if self.clause45 and self.clause45_addr != -1:
91                 decoded_min += str.format('ADDR: %04X ' % self.clause45_addr)
92             elif self.clause45:
93                 decoded_min += str.format('ADDR: UKWN ' % self.clause45_addr)
94
95             if self.clause45 and self.opcode > 1 \
96             or (not self.clause45 and self.opcode):
97                 decoded_min += str.format('READ:  %04X' % self.data)
98                 is_read = 1
99             else:
100                 decoded_min += str.format('WRITE: %04X' % self.data)
101                 is_read = 0
102             decoded_ext = str.format(' %s: %02d' % \
103                         ('PRTAD' if self.clause45 else 'PHYAD', self.portad))
104             decoded_ext += str.format(' %s: %02d' % \
105                         ('DEVAD' if self.clause45 else 'REGAD', self.devad))
106             if self.ta_invalid or self.op_invalid:
107                 decoded_ext += ' ERROR'
108             self.put(self.ss_frame, self.mdiobits[0][2], self.out_ann,
109                      [5, [decoded_min + decoded_ext, decoded_min]])
110
111             self.put(self.ss_frame, self.mdiobits[0][2], self.out_python,
112                      [(bool(self.clause45), int(self.clause45_addr), \
113                        bool(is_read), int(self.portad), int(self.devad), \
114                        int(self.data))])
115
116         # Post read increment address.
117         if self.clause45 and self.opcode == 2 and self.clause45_addr != -1:
118             self.clause45_addr += 1
119
120     def reset_decoder_state(self):
121         self.mdiobits = []
122         self.bitcount = -1
123         self.opcode = -1
124         self.clause45 = 0
125         self.ss_frame = -1
126         self.ss_frame_field = -1
127         self.preamble_len = 0
128         self.ta_invalid = -1
129         self.op_invalid = ''
130         self.portad = -1
131         self.portad_bits = 5
132         self.devad = -1
133         self.devad_bits = 5
134         self.data = -1
135         self.data_bits = 16
136         self.state = 'PRE'
137
138     def state_PRE(self, mdio):
139         if self.illegal_bus:
140             if mdio == 0:   # Stay in illegal bus state.
141                 return
142             else:           # Leave and continue parsing.
143                 self.illegal_bus = 0
144                 self.put(self.ss_illegal, self.samplenum, self.out_ann,
145                          [4, ['ILLEGAL BUS STATE', 'ILL']])
146                 self.ss_frame = self.samplenum
147
148         if self.ss_frame == -1:
149             self.ss_frame = self.samplenum
150
151         if mdio == 1:
152             self.preamble_len += 1
153
154         # Valid MDIO can't clock more than 16 succeeding ones without being
155         # in either IDLE or PRE.
156         if self.preamble_len > 16:
157             if self.preamble_len >= 10000 + 32:
158                 self.put(self.ss_frame, self.mdiobits[32][1], self.out_ann,
159                     [3, ['IDLE #%d' % (self.preamble_len - 32), 'IDLE', 'I']])
160                 self.ss_frame = self.mdiobits[32][1]
161                 self.preamble_len = 32
162                 # This is getting out of hand, free some memory.
163                 del self.mdiobits[33:-1]
164             if mdio == 0:
165                 if self.preamble_len < 32:
166                     self.ss_frame = self.mdiobits[self.preamble_len][1]
167                     self.put(self.ss_frame, self.samplenum, self.out_ann,
168                              [4, ['SHORT PREAMBLE', 'SHRT PRE']])
169                 elif self.preamble_len > 32:
170                     self.ss_frame = self.mdiobits[32][1]
171                     self.put(self.mdiobits[self.preamble_len][1],
172                              self.mdiobits[32][1], self.out_ann,
173                              [3, ['IDLE #%d' % (self.preamble_len - 32),
174                              'IDLE', 'I']])
175                     self.preamble_len = 32
176                 else:
177                     self.ss_frame = self.mdiobits[32][1]
178                 self.put(self.ss_frame, self.samplenum, self.out_ann,
179                          [2, ['PRE #%d' % self.preamble_len, 'PRE', 'P']])
180                 self.ss_frame_field = self.samplenum
181                 self.state = 'ST'
182         elif mdio == 0:
183                 self.ss_illegal = self.ss_frame
184                 self.illegal_bus = 1
185
186     def state_ST(self, mdio):
187         if mdio == 0:
188             self.clause45 = 1
189         self.state = 'OP'
190
191     def state_OP(self, mdio):
192         if self.opcode == -1:
193             if self.clause45:
194                 st = ['ST (Clause 45)', 'ST 45']
195             else:
196                 st = ['ST (Clause 22)', 'ST 22']
197             self.put(self.ss_frame_field, self.samplenum, self.out_ann,
198                      [2, st + ['ST', 'S']])
199             self.ss_frame_field = self.samplenum
200
201             if mdio:
202                 self.opcode = 2
203             else:
204                 self.opcode = 0
205         else:
206             if self.clause45:
207                 self.state = 'PRTAD'
208                 self.opcode += mdio
209             else:
210                 if mdio == self.opcode:
211                     self.op_invalid = 'invalid for Clause 22'
212                 self.state = 'PRTAD'
213
214     def state_PRTAD(self, mdio):
215         if self.portad == -1:
216             self.portad = 0
217             if self.clause45:
218                 if self.opcode == 0:
219                     op = ['OP: ADDR', 'OP: A']
220                 elif self.opcode == 1:
221                     op = ['OP: WRITE', 'OP: W']
222                 elif self.opcode == 2:
223                     op = ['OP: READINC', 'OP: RI']
224                 elif self.opcode == 3:
225                     op = ['OP: READ', 'OP: R']
226             else:
227                 op = ['OP: READ', 'OP: R'] if self.opcode else ['OP: WRITE', 'OP: W']
228             self.put(self.ss_frame_field, self.samplenum, self.out_ann,
229                      [2, op + ['OP', 'O']])
230             if self.op_invalid:
231                 self.put(self.ss_frame_field, self.samplenum, self.out_ann,
232                          [4, ['OP %s' % self.op_invalid, 'OP', 'O']])
233             self.ss_frame_field = self.samplenum
234         self.portad_bits -= 1
235         self.portad |= mdio << self.portad_bits
236         if not self.portad_bits:
237             self.state = 'DEVAD'
238
239     def state_DEVAD(self, mdio):
240         if self.devad == -1:
241             self.devad = 0
242             if self.clause45:
243                 prtad = ['PRTAD: %02d' % self.portad, 'PRT', 'P']
244             else:
245                 prtad = ['PHYAD: %02d' % self.portad, 'PHY', 'P']
246             self.put(self.ss_frame_field, self.samplenum, self.out_ann,
247                      [2, prtad])
248             self.ss_frame_field = self.samplenum
249         self.devad_bits -= 1
250         self.devad |= mdio << self.devad_bits
251         if not self.devad_bits:
252             self.state = 'TA'
253
254     def state_TA(self, mdio):
255         if self.ta_invalid == -1:
256             self.ta_invalid = ''
257             if self.clause45:
258                 regad = ['DEVAD: %02d' % self.devad, 'DEV', 'D']
259             else:
260                 regad = ['REGAD: %02d' % self.devad, 'REG', 'R']
261             self.put(self.ss_frame_field, self.samplenum, self.out_ann,
262                      [2, regad])
263             self.ss_frame_field = self.samplenum
264             if mdio != 1 and ((self.clause45 and self.opcode < 2)
265             or (not self.clause45 and self.opcode == 0)):
266                 self.ta_invalid = ' invalid (bit1)'
267         else:
268             if mdio != 0:
269                 if self.ta_invalid:
270                     self.ta_invalid = ' invalid (bit1 and bit2)'
271                 else:
272                     self.ta_invalid = ' invalid (bit2)'
273             self.state = 'DATA'
274
275     def state_DATA(self, mdio):
276         if self.data == -1:
277             self.data = 0
278             self.put(self.ss_frame_field, self.samplenum, self.out_ann,
279                      [2, ['TA', 'T']])
280             if self.ta_invalid:
281                 self.put(self.ss_frame_field, self.samplenum, self.out_ann,
282                          [4, ['TA%s' % self.ta_invalid, 'TA', 'T']])
283             self.ss_frame_field = self.samplenum
284         self.data_bits -= 1
285         self.data |= mdio << self.data_bits
286         if not self.data_bits:
287             # Output final bit.
288             self.mdiobits[0][2] = self.mdiobits[0][1] + self.quartile_cycle_length()
289             self.bitcount += 1
290             self.putbit(self.mdiobits[0][0], self.mdiobits[0][1], self.mdiobits[0][2])
291             self.putdata()
292             self.reset_decoder_state()
293
294     def process_state(self, argument, mdio):
295         method_name = 'state_' + str(argument)
296         method = getattr(self, method_name)
297         return method(mdio)
298
299     # Returns the first quartile point of the frames cycle lengths. This is a
300     # conservative guess for the end of the last cycle. On average it will be
301     # more likely to fall short, than being too long, which makes for better
302     # readability in GUIs.
303     def quartile_cycle_length(self):
304         # 48 is the minimum number of samples we have to have at the end of a
305         # frame. The last sample only has a leading clock edge and is ignored.
306         bitlen = []
307         for i in range(1, 49):
308             bitlen.append(self.mdiobits[i][2] - self.mdiobits[i][1])
309         bitlen = sorted(bitlen)
310         return bitlen[12]
311
312     def handle_bit(self, mdio):
313         self.bitcount += 1
314         self.mdiobits.insert(0, [mdio, self.samplenum, -1])
315
316         if self.bitcount > 0:
317             self.mdiobits[1][2] = self.samplenum # Note end of last cycle.
318             # Output the last bit we processed.
319             self.putbit(self.mdiobits[1][0], self.mdiobits[1][1], self.mdiobits[1][2])
320
321         self.process_state(self.state, mdio)
322
323     def decode(self, ss, es, data):
324         for (self.samplenum, pins) in data:
325             # Ignore identical samples early on (for performance reasons).
326             if self.last_mdc == pins[0]:
327                 continue
328             self.last_mdc = pins[0]
329             if pins[0] == 0: # Check for rising edge.
330                 continue
331
332             # Found the correct clock edge, now get/handle the bit(s).
333             self.handle_bit(pins[1])