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