]>
Commit | Line | Data |
---|---|---|
d12e106f EO |
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]) |