]>
Commit | Line | Data |
---|---|---|
1 | ## | |
2 | ## This file is part of the libsigrokdecode project. | |
3 | ## | |
4 | ## Copyright (C) 2013 Bert Vermeulen <bert@biot.com> | |
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 3 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 | from common.plugtrx import (MODULE_ID, ALARM_THRESHOLDS, AD_READOUTS, GCS_BITS, | |
22 | CONNECTOR, TRANSCEIVER, SERIAL_ENCODING, XMIT_TECH, CDR, DEVICE_TECH, | |
23 | ENHANCED_OPTS, AUX_TYPES) | |
24 | ||
25 | class Decoder(srd.Decoder): | |
26 | api_version = 3 | |
27 | id = 'xfp' | |
28 | name = 'XFP' | |
29 | longname = '10 Gigabit Small Form Factor Pluggable Module (XFP)' | |
30 | desc = 'XFP I²C management interface structures/protocol' | |
31 | license = 'gplv3+' | |
32 | inputs = ['i2c'] | |
33 | outputs = [] | |
34 | tags = ['Networking'] | |
35 | annotations = ( | |
36 | ('field-name-and-val', 'Field name and value'), | |
37 | ('field-val', 'Field value'), | |
38 | ) | |
39 | annotation_rows = ( | |
40 | ('field-names-and-vals', 'Field names and values', (0,)), | |
41 | ('field-vals', 'Field values', (1,)), | |
42 | ) | |
43 | ||
44 | def __init__(self): | |
45 | self.reset() | |
46 | ||
47 | def reset(self): | |
48 | # Received data items, used as an index into samplenum/data | |
49 | self.cnt = -1 | |
50 | # Start/end sample numbers per data item | |
51 | self.sn = [] | |
52 | # Multi-byte structure buffer | |
53 | self.buf = [] | |
54 | # Filled in by address 0x7f in low memory | |
55 | self.cur_highmem_page = 0 | |
56 | # Filled in by extended ID value in table 2 | |
57 | self.have_clei = False | |
58 | # Handlers for each field in the structure, keyed by the end | |
59 | # index of that field. Each handler is fed all unhandled bytes | |
60 | # up until that point, so mark unused space with the dummy | |
61 | # handler self.ignore(). | |
62 | self.MAP_LOWER_MEMORY = { | |
63 | 0: self.module_id, | |
64 | 1: self.signal_cc, | |
65 | 57: self.alarm_warnings, | |
66 | 59: self.vps, | |
67 | 69: self.ignore, | |
68 | 71: self.ber, | |
69 | 75: self.wavelength_cr, | |
70 | 79: self.fec_cr, | |
71 | 95: self.int_ctrl, | |
72 | 109: self.ad_readout, | |
73 | 111: self.gcs, | |
74 | 117: self.ignore, | |
75 | 118: self.ignore, | |
76 | 122: self.ignore, | |
77 | 126: self.ignore, | |
78 | 127: self.page_select, | |
79 | } | |
80 | self.MAP_HIGH_TABLE_1 = { | |
81 | 128: self.module_id, | |
82 | 129: self.ext_module_id, | |
83 | 130: self.connector, | |
84 | 138: self.transceiver, | |
85 | 139: self.serial_encoding, | |
86 | 140: self.br_min, | |
87 | 141: self.br_max, | |
88 | 142: self.link_length_smf, | |
89 | 143: self.link_length_e50, | |
90 | 144: self.link_length_50um, | |
91 | 145: self.link_length_625um, | |
92 | 146: self.link_length_copper, | |
93 | 147: self.device_tech, | |
94 | 163: self.vendor, | |
95 | 164: self.cdr, | |
96 | 167: self.vendor_oui, | |
97 | 183: self.vendor_pn, | |
98 | 185: self.vendor_rev, | |
99 | 187: self.wavelength, | |
100 | 189: self.wavelength_tolerance, | |
101 | 190: self.max_case_temp, | |
102 | 191: self.ignore, | |
103 | 195: self.power_supply, | |
104 | 211: self.vendor_sn, | |
105 | 219: self.manuf_date, | |
106 | 220: self.diag_mon, | |
107 | 221: self.enhanced_opts, | |
108 | 222: self.aux_mon, | |
109 | 223: self.ignore, | |
110 | 255: self.maybe_ascii, | |
111 | } | |
112 | ||
113 | def start(self): | |
114 | self.out_ann = self.register(srd.OUTPUT_ANN) | |
115 | ||
116 | def decode(self, ss, es, data): | |
117 | cmd, data = data | |
118 | ||
119 | # We only care about actual data bytes that are read (for now). | |
120 | if cmd != 'DATA READ': | |
121 | return | |
122 | ||
123 | self.cnt += 1 | |
124 | self.sn.append([ss, es]) | |
125 | ||
126 | self.buf.append(data) | |
127 | if self.cnt < 0x80: | |
128 | if self.cnt in self.MAP_LOWER_MEMORY: | |
129 | self.MAP_LOWER_MEMORY[self.cnt](self.buf) | |
130 | self.buf.clear() | |
131 | elif self.cnt < 0x0100 and self.cur_highmem_page == 0x01: | |
132 | # Serial ID memory map | |
133 | if self.cnt in self.MAP_HIGH_TABLE_1: | |
134 | self.MAP_HIGH_TABLE_1[self.cnt](self.buf) | |
135 | self.buf.clear() | |
136 | ||
137 | # Annotation helper | |
138 | def annotate(self, key, value, start_cnt=None, end_cnt=None): | |
139 | if start_cnt is None: | |
140 | start_cnt = self.cnt - len(self.buf) + 1 | |
141 | if end_cnt is None: | |
142 | end_cnt = self.cnt | |
143 | self.put(self.sn[start_cnt][0], self.sn[end_cnt][1], | |
144 | self.out_ann, [0, [key + ": " + value]]) | |
145 | self.put(self.sn[start_cnt][0], self.sn[end_cnt][1], | |
146 | self.out_ann, [1, [value]]) | |
147 | ||
148 | # Placeholder handler, needed to advance the buffer past unused or | |
149 | # reserved space in the structures. | |
150 | def ignore(self, data): | |
151 | pass | |
152 | ||
153 | # Show as ASCII if possible | |
154 | def maybe_ascii(self, data): | |
155 | for i in range(len(data)): | |
156 | if data[i] >= 0x20 and data[i] < 0x7f: | |
157 | cnt = self.cnt - len(data) + 1 | |
158 | self.annotate("Vendor ID", chr(data[i]), cnt, cnt) | |
159 | ||
160 | # Convert 16-bit two's complement values, with each increment | |
161 | # representing 1/256C, to degrees Celsius. | |
162 | def to_temp(self, value): | |
163 | if value & 0x8000: | |
164 | value = -((value ^ 0xffff) + 1) | |
165 | temp = value / 256.0 | |
166 | return "%.1f C" % temp | |
167 | ||
168 | # TX bias current in uA. Each increment represents 0.2uA | |
169 | def to_current(self, value): | |
170 | current = value / 500000.0 | |
171 | return "%.1f mA" % current | |
172 | ||
173 | # Power in mW, with each increment representing 0.1uW | |
174 | def to_power(self, value): | |
175 | power = value / 10000.0 | |
176 | return "%.2f mW" % power | |
177 | ||
178 | # Wavelength in increments of 0.05nm | |
179 | def to_wavelength(self, value): | |
180 | wl = value / 20 | |
181 | return "%d nm" % wl | |
182 | ||
183 | # Wavelength in increments of 0.005nm | |
184 | def to_wavelength_tolerance(self, value): | |
185 | wl = value / 200.0 | |
186 | return "%.1f nm" % wl | |
187 | ||
188 | def module_id(self, data): | |
189 | self.annotate("Module identifier", MODULE_ID.get(data[0], "Unknown")) | |
190 | ||
191 | def signal_cc(self, data): | |
192 | # No good data available. | |
193 | if (data[0] != 0x00): | |
194 | self.annotate("Signal Conditioner Control", "%.2x" % data[0]) | |
195 | ||
196 | def alarm_warnings(self, data): | |
197 | cnt_idx = self.cnt - len(data) | |
198 | idx = 0 | |
199 | while idx < 56: | |
200 | if idx == 8: | |
201 | # Skip over reserved A/D flag thresholds | |
202 | idx += 8 | |
203 | value = (data[idx] << 8) | data[idx + 1] | |
204 | if value != 0: | |
205 | name = ALARM_THRESHOLDS.get(idx, "...") | |
206 | if idx in (0, 2, 4, 6): | |
207 | self.annotate(name, self.to_temp(value), | |
208 | cnt_idx + idx, cnt_idx + idx + 1) | |
209 | elif idx in (16, 18, 20, 22): | |
210 | self.annotate(name, self.to_current(value), | |
211 | cnt_idx + idx, cnt_idx + idx + 1) | |
212 | elif idx in (24, 26, 28, 30, 32, 34, 36, 38): | |
213 | self.annotate(name, self.to_power(value), | |
214 | cnt_idx + idx, cnt_idx + idx + 1) | |
215 | else: | |
216 | self.annotate(name, "%d" % name, value, cnt_idx + idx, | |
217 | cnt_idx + idx + 1) | |
218 | idx += 2 | |
219 | ||
220 | def vps(self, data): | |
221 | # No good data available. | |
222 | if (data != [0, 0]): | |
223 | self.annotate("VPS", "%.2x%.2x" % (data[0], data[1])) | |
224 | ||
225 | def ber(self, data): | |
226 | # No good data available. | |
227 | if (data != [0, 0]): | |
228 | self.annotate("BER", str(data)) | |
229 | ||
230 | def wavelength_cr(self, data): | |
231 | # No good data available. | |
232 | if (data != [0, 0, 0, 0]): | |
233 | self.annotate("WCR", str(data)) | |
234 | ||
235 | def fec_cr(self, data): | |
236 | if (data != [0, 0, 0, 0]): | |
237 | self.annotate("FEC", str(data)) | |
238 | ||
239 | def int_ctrl(self, data): | |
240 | # No good data available. Also boring. | |
241 | out = [] | |
242 | for d in data: | |
243 | out.append("%.2x" % d) | |
244 | self.annotate("Interrupt bits", ' '.join(out)) | |
245 | ||
246 | def ad_readout(self, data): | |
247 | cnt_idx = self.cnt - len(data) + 1 | |
248 | idx = 0 | |
249 | while idx < 14: | |
250 | if idx == 2: | |
251 | # Skip over reserved field | |
252 | idx += 2 | |
253 | value = (data[idx] << 8) | data[idx + 1] | |
254 | name = AD_READOUTS.get(idx, "...") | |
255 | if value != 0: | |
256 | if idx == 0: | |
257 | self.annotate(name, self.to_temp(value), | |
258 | cnt_idx + idx, cnt_idx + idx + 1) | |
259 | elif idx == 4: | |
260 | self.annotate(name, self.to_current(value), | |
261 | cnt_idx + idx, cnt_idx + idx + 1) | |
262 | elif idx in (6, 8): | |
263 | self.annotate(name, self.to_power(value), | |
264 | cnt_idx + idx, cnt_idx + idx + 1) | |
265 | else: | |
266 | self.annotate(name, str(value), cnt_idx + idx, | |
267 | cnt_idx + idx + 1) | |
268 | idx += 2 | |
269 | ||
270 | def gcs(self, data): | |
271 | allbits = (data[0] << 8) | data[1] | |
272 | out = [] | |
273 | for b in range(13): | |
274 | if allbits & 0x8000: | |
275 | out.append(GCS_BITS[b]) | |
276 | allbits <<= 1 | |
277 | self.annotate("General Control/Status", ', '.join(out)) | |
278 | ||
279 | def page_select(self, data): | |
280 | self.cur_highmem_page = data[0] | |
281 | ||
282 | def ext_module_id(self, data): | |
283 | out = ["Power level %d module" % ((data[0] >> 6) + 1)] | |
284 | if data[0] & 0x20 == 0: | |
285 | out.append("CDR") | |
286 | if data[0] & 0x10 == 0: | |
287 | out.append("TX ref clock input required") | |
288 | if data[0] & 0x08 == 0: | |
289 | self.have_clei = True | |
290 | self.annotate("Extended id", ', '.join(out)) | |
291 | ||
292 | def connector(self, data): | |
293 | if data[0] in CONNECTOR: | |
294 | self.annotate("Connector", CONNECTOR[data[0]]) | |
295 | ||
296 | def transceiver(self, data): | |
297 | out = [] | |
298 | for t in range(8): | |
299 | if data[t] == 0: | |
300 | continue | |
301 | value = data[t] | |
302 | for b in range(8): | |
303 | if value & 0x80: | |
304 | if len(TRANSCEIVER[t]) < b + 1: | |
305 | out.append("(unknown)") | |
306 | else: | |
307 | out.append(TRANSCEIVER[t][b]) | |
308 | value <<= 1 | |
309 | self.annotate("Transceiver compliance", ', '.join(out)) | |
310 | ||
311 | def serial_encoding(self, data): | |
312 | out = [] | |
313 | value = data[0] | |
314 | for b in range(8): | |
315 | if value & 0x80: | |
316 | if len(SERIAL_ENCODING) < b + 1: | |
317 | out.append("(unknown)") | |
318 | else: | |
319 | out.append(SERIAL_ENCODING[b]) | |
320 | value <<= 1 | |
321 | self.annotate("Serial encoding support", ', '.join(out)) | |
322 | ||
323 | def br_min(self, data): | |
324 | # Increments represent 100Mb/s | |
325 | rate = data[0] / 10.0 | |
326 | self.annotate("Minimum bit rate", "%.3f GB/s" % rate) | |
327 | ||
328 | def br_max(self, data): | |
329 | # Increments represent 100Mb/s | |
330 | rate = data[0] / 10.0 | |
331 | self.annotate("Maximum bit rate", "%.3f GB/s" % rate) | |
332 | ||
333 | def link_length_smf(self, data): | |
334 | if data[0] == 0: | |
335 | length = "(standard)" | |
336 | elif data[0] == 255: | |
337 | length = "> 254 km" | |
338 | else: | |
339 | length = "%d km" % data[0] | |
340 | self.annotate("Link length (SMF)", length) | |
341 | ||
342 | def link_length_e50(self, data): | |
343 | if data[0] == 0: | |
344 | length = "(standard)" | |
345 | elif data[0] == 255: | |
346 | length = "> 508 m" | |
347 | else: | |
348 | length = "%d m" % (data[0] * 2) | |
349 | self.annotate("Link length (extended, 50μm MMF)", length) | |
350 | ||
351 | def link_length_50um(self, data): | |
352 | if data[0] == 0: | |
353 | length = "(standard)" | |
354 | elif data[0] == 255: | |
355 | length = "> 254 m" | |
356 | else: | |
357 | length = "%d m" % data[0] | |
358 | self.annotate("Link length (50μm MMF)", length) | |
359 | ||
360 | def link_length_625um(self, data): | |
361 | if data[0] == 0: | |
362 | length = "(standard)" | |
363 | elif data[0] == 255: | |
364 | length = "> 254 m" | |
365 | else: | |
366 | length = "%d m" % (data[0]) | |
367 | self.annotate("Link length (62.5μm MMF)", length) | |
368 | ||
369 | def link_length_copper(self, data): | |
370 | if data[0] == 0: | |
371 | length = "(unknown)" | |
372 | elif data[0] == 255: | |
373 | length = "> 254 m" | |
374 | else: | |
375 | length = "%d m" % (data[0] * 2) | |
376 | self.annotate("Link length (copper)", length) | |
377 | ||
378 | def device_tech(self, data): | |
379 | out = [] | |
380 | xmit = data[0] >> 4 | |
381 | if xmit <= len(XMIT_TECH) - 1: | |
382 | out.append("%s transmitter" % XMIT_TECH[xmit]) | |
383 | dev = data[0] & 0x0f | |
384 | for b in range(4): | |
385 | out.append(DEVICE_TECH[b][(dev >> (3 - b)) & 0x01]) | |
386 | self.annotate("Device technology", ', '.join(out)) | |
387 | ||
388 | def vendor(self, data): | |
389 | name = bytes(data).strip().decode('ascii').strip('\x00') | |
390 | if name: | |
391 | self.annotate("Vendor", name) | |
392 | ||
393 | def cdr(self, data): | |
394 | out = [] | |
395 | value = data[0] | |
396 | for b in range(8): | |
397 | if value & 0x80: | |
398 | out.append(CDR[b]) | |
399 | value <<= 1 | |
400 | self.annotate("CDR support", ', '.join(out)) | |
401 | ||
402 | def vendor_oui(self, data): | |
403 | if data != [0, 0, 0]: | |
404 | self.annotate("Vendor OUI", "%.2X-%.2X-%.2X" % tuple(data)) | |
405 | ||
406 | def vendor_pn(self, data): | |
407 | name = bytes(data).strip().decode('ascii').strip('\x00') | |
408 | if name: | |
409 | self.annotate("Vendor part number", name) | |
410 | ||
411 | def vendor_rev(self, data): | |
412 | name = bytes(data).strip().decode('ascii').strip('\x00') | |
413 | if name: | |
414 | self.annotate("Vendor revision", name) | |
415 | ||
416 | def wavelength(self, data): | |
417 | value = (data[0] << 8) | data[1] | |
418 | self.annotate("Wavelength", self.to_wavelength(value)) | |
419 | ||
420 | def wavelength_tolerance(self, data): | |
421 | value = (data[0] << 8) | data[1] | |
422 | self.annotate("Wavelength tolerance", self.to_wavelength_tolerance(value)) | |
423 | ||
424 | def max_case_temp(self, data): | |
425 | self.annotate("Maximum case temperature", "%d C" % data[0]) | |
426 | ||
427 | def power_supply(self, data): | |
428 | out = [] | |
429 | self.annotate("Max power dissipation", | |
430 | "%.3f W" % (data[0] * 0.02), self.cnt - 3, self.cnt - 3) | |
431 | self.annotate("Max power dissipation (powered down)", | |
432 | "%.3f W" % (data[1] * 0.01), self.cnt - 2, self.cnt - 2) | |
433 | value = (data[2] >> 4) * 0.050 | |
434 | self.annotate("Max current required (5V supply)", | |
435 | "%.3f A" % value, self.cnt - 1, self.cnt - 1) | |
436 | value = (data[2] & 0x0f) * 0.100 | |
437 | self.annotate("Max current required (3.3V supply)", | |
438 | "%.3f A" % value, self.cnt - 1, self.cnt - 1) | |
439 | value = (data[3] >> 4) * 0.100 | |
440 | self.annotate("Max current required (1.8V supply)", | |
441 | "%.3f A" % value, self.cnt, self.cnt) | |
442 | value = (data[3] & 0x0f) * 0.050 | |
443 | self.annotate("Max current required (-5.2V supply)", | |
444 | "%.3f A" % value, self.cnt, self.cnt) | |
445 | ||
446 | def vendor_sn(self, data): | |
447 | name = bytes(data).strip().decode('ascii').strip('\x00') | |
448 | if name: | |
449 | self.annotate("Vendor serial number", name) | |
450 | ||
451 | def manuf_date(self, data): | |
452 | y = int(bytes(data[0:2])) + 2000 | |
453 | m = int(bytes(data[2:4])) | |
454 | d = int(bytes(data[4:6])) | |
455 | mnf = "%.4d-%.2d-%.2d" % (y, m, d) | |
456 | lot = bytes(data[6:]).strip().decode('ascii').strip('\x00') | |
457 | if lot: | |
458 | mnf += " lot " + lot | |
459 | self.annotate("Manufacturing date", mnf) | |
460 | ||
461 | def diag_mon(self, data): | |
462 | out = [] | |
463 | if data[0] & 0x10: | |
464 | out.append("BER support") | |
465 | else: | |
466 | out.append("no BER support") | |
467 | if data[0] & 0x08: | |
468 | out.append("average power measurement") | |
469 | else: | |
470 | out.append("OMA power measurement") | |
471 | self.annotate("Diagnostic monitoring", ', '.join(out)) | |
472 | ||
473 | def enhanced_opts(self, data): | |
474 | out = [] | |
475 | value = data[0] | |
476 | for b in range(8): | |
477 | if value & 0x80: | |
478 | out.append(ENHANCED_OPTS[b]) | |
479 | value <<= 1 | |
480 | self.annotate("Enhanced option support", ', '.join(out)) | |
481 | ||
482 | def aux_mon(self, data): | |
483 | aux = AUX_TYPES[data[0] >> 4] | |
484 | self.annotate("AUX1 monitoring", aux) | |
485 | aux = AUX_TYPES[data[0] & 0x0f] | |
486 | self.annotate("AUX2 monitoring", aux) |