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