]> sigrok.org Git - libsigrokdecode.git/blob - decoders/xfp/pd.py
All PDs: Bump api_version to 2.
[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
22 MODULE_ID = {
23     0x01: 'GBIC',
24     0x02: 'Integrated module/connector',
25     0x03: 'SFP',
26     0x04: '300-pin XBI',
27     0x05: 'XENPAK',
28     0x06: 'XFP',
29     0x07: 'XFF',
30     0x08: 'XFP-E',
31     0x09: 'XPAK',
32     0x0a: 'X2',
33 }
34
35 ALARM_THRESHOLDS = {
36     0:  "Temp high alarm",
37     2:  "Temp low alarm",
38     4:  "Temp high warning",
39     6:  "Temp low warning",
40     16: "Bias high alarm",
41     18: "Bias low alarm",
42     20: "Bias high warning",
43     22: "Bias low warning",
44     24: "TX power high alarm",
45     26: "TX power low alarm",
46     28: "TX power high warning",
47     30: "TX power low warning",
48     32: "RX power high alarm",
49     34: "RX power low alarm",
50     36: "RX power high warning",
51     38: "RX power low warning",
52     40: "AUX 1 high alarm",
53     42: "AUX 1 low alarm",
54     44: "AUX 1 high warning",
55     46: "AUX 1 low warning",
56     48: "AUX 2 high alarm",
57     50: "AUX 2 low alarm",
58     52: "AUX 2 high warning",
59     54: "AUX 2 low warning",
60 }
61
62 AD_READOUTS = {
63     0:  "Module temperature",
64     4:  "TX bias current",
65     6:  "Measured TX output power",
66     8:  "Measured RX input power",
67     10: "AUX 1 measurement",
68     12: "AUX 2 measurement",
69 }
70
71 GCS_BITS = [
72     "TX disable",
73     "Soft TX disable",
74     "MOD_NR",
75     "P_Down",
76     "Soft P_Down",
77     "Interrupt",
78     "RX_LOS",
79     "Data_Not_Ready",
80     "TX_NR",
81     "TX_Fault",
82     "TX_CDR not locked",
83     "RX_NR",
84     "RX_CDR not locked",
85 ]
86
87 CONNECTOR = {
88     0x01:   "SC",
89     0x02:   "Fibre Channel style 1 copper",
90     0x03:   "Fibre Channel style 2 copper",
91     0x04:   "BNC/TNC",
92     0x05:   "Fibre Channel coax",
93     0x06:   "FiberJack",
94     0x07:   "LC",
95     0x08:   "MT-RJ",
96     0x09:   "MU",
97     0x0a:   "SG",
98     0x0b:   "Optical pigtail",
99     0x20:   "HSSDC II",
100     0x21:   "Copper pigtail",
101 }
102
103 TRANSCEIVER = [
104     # 10GB Ethernet
105     ["10GBASE-SR", "10GBASE-LR", "10GBASE-ER", "10GBASE-LRM", "10GBASE-SW",
106         "10GBASE-LW",   "10GBASE-EW"],
107     # 10GB Fibre Channel
108     ["1200-MX-SN-I", "1200-SM-LL-L", "Extended Reach 1550 nm",
109         "Intermediate reach 1300 nm FP"],
110     # 10GB Copper
111     [],
112     # 10GB low speed
113     ["1000BASE-SX / 1xFC MMF", "1000BASE-LX / 1xFC SMF", "2xFC MMF",
114         "2xFC SMF", "OC48-SR", "OC48-IR", "OC48-LR"],
115     # 10GB SONET/SDH interconnect
116     ["I-64.1r", "I-64.1", "I-64.2r", "I-64.2", "I-64.3", "I-64.5"],
117     # 10GB SONET/SDH short haul
118     ["S-64.1", "S-64.2a", "S-64.2b", "S-64.3a", "S-64.3b", "S-64.5a", "S-64.5b"],
119     # 10GB SONET/SDH long haul
120     ["L-64.1", "L-64.2a", "L-64.2b", "L-64.2c", "L-64.3", "G.959.1 P1L1-2D2"],
121     # 10GB SONET/SDH very long haul
122     ["V-64.2a", "V-64.2b", "V-64.3"],
123 ]
124
125 SERIAL_ENCODING = [
126     "64B/66B",
127     "8B/10B",
128     "SONET scrambled",
129     "NRZ",
130     "RZ",
131 ]
132
133 XMIT_TECH = [
134     "850 nm VCSEL",
135     "1310 nm VCSEL",
136     "1550 nm VCSEL",
137     "1310 nm FP",
138     "1310 nm DFB",
139     "1550 nm DFB",
140     "1310 nm EML"
141     "1550 nm EML"
142     "copper",
143 ]
144
145 CDR = [
146     "9.95Gb/s",
147     "10.3Gb/s",
148     "10.5Gb/s",
149     "10.7Gb/s",
150     "11.1Gb/s",
151     "(unknown)",
152     "lineside loopback mode",
153     "XFI loopback mode",
154 ]
155
156 DEVICE_TECH = [
157     ["no wavelength control", "sctive wavelength control"],
158     ["uncooled transmitter device", "cooled transmitter"],
159     ["PIN detector", "APD detector"],
160     ["transmitter not tunable", "transmitter tunable"],
161 ]
162
163 ENHANCED_OPTS = [
164     "VPS",
165     "soft TX_DISABLE",
166     "soft P_Down",
167     "VPS LV regulator mode",
168     "VPS bypassed regulator mode",
169     "active FEC control",
170     "wavelength tunability",
171     "CMU",
172 ]
173
174 AUX_TYPES = [
175     "not implemented",
176     "APD bias voltage",
177     "(unknown)",
178     "TEC current",
179     "laser temperature",
180     "laser wavelength",
181     "5V supply voltage",
182     "3.3V supply voltage",
183     "1.8V supply voltage",
184     "-5.2V supply voltage",
185     "5V supply current",
186     "(unknown)",
187     "(unknown)",
188     "3.3V supply current",
189     "1.8V supply current",
190     "-5.2V supply current",
191 ]
192
193 class Decoder(srd.Decoder):
194     api_version = 2
195     id = 'xfp'
196     name = 'XFP'
197     longname = '10 Gigabit Small Form Factor Pluggable Module (XFP)'
198     desc = 'Data structure describing display device capabilities.'
199     license = 'gplv3+'
200     inputs = ['i2c']
201     outputs = ['xfp']
202     annotations = (
203         ('fieldnames-and-values', 'XFP structure field names and values'),
204         ('fields', 'XFP structure fields'),
205     )
206
207     def __init__(self, **kwargs):
208         # Received data items, used as an index into samplenum/data
209         self.cnt = -1
210         # Start/end sample numbers per data item
211         self.sn = []
212         # Multi-byte structure buffer
213         self.buf = []
214         # Filled in by address 0x7f in low memory
215         self.cur_highmem_page = 0
216         # Filled in by extended ID value in table 2
217         self.have_clei = False
218         # Handlers for each field in the structure, keyed by the end
219         # index of that field. Each handler is fed all unhandled bytes
220         # up until that point, so mark unused space with the dummy
221         # handler self.ignore().
222         self.MAP_LOWER_MEMORY = {
223             0:  self.module_id,
224             1:  self.signal_cc,
225             57: self.alarm_warnings,
226             59: self.vps,
227             69: self.ignore,
228             71: self.ber,
229             75: self.wavelength_cr,
230             79: self.fec_cr,
231             95: self.int_ctrl,
232             109: self.ad_readout,
233             111: self.gcs,
234             117: self.ignore,
235             118: self.ignore,
236             122: self.ignore,
237             126: self.ignore,
238             127: self.page_select,
239         }
240         self.MAP_HIGH_TABLE_1 = {
241             128: self.module_id,
242             129: self.ext_module_id,
243             130: self.connector,
244             138: self.transceiver,
245             139: self.serial_encoding,
246             140: self.br_min,
247             141: self.br_max,
248             142: self.link_length_smf,
249             143: self.link_length_e50,
250             144: self.link_length_50um,
251             145: self.link_length_625um,
252             146: self.link_length_copper,
253             147: self.device_tech,
254             163: self.vendor,
255             164: self.cdr,
256             167: self.vendor_oui,
257             183: self.vendor_pn,
258             185: self.vendor_rev,
259             187: self.wavelength,
260             189: self.wavelength_tolerance,
261             190: self.max_case_temp,
262             191: self.ignore,
263             195: self.power_supply,
264             211: self.vendor_sn,
265             219: self.manuf_date,
266             220: self.diag_mon,
267             221: self.enhanced_opts,
268             222: self.aux_mon,
269             223: self.ignore,
270             255: self.maybe_ascii,
271         }
272
273     def start(self):
274         self.out_ann = self.register(srd.OUTPUT_ANN)
275
276     def decode(self, ss, es, data):
277         cmd, data = data
278
279         # We only care about actual data bytes that are read (for now).
280         if cmd != 'DATA READ':
281             return
282
283         self.cnt += 1
284         self.sn.append([ss, es])
285
286         self.buf.append(data)
287         if self.cnt < 0x80:
288             if self.cnt in self.MAP_LOWER_MEMORY:
289                 self.MAP_LOWER_MEMORY[self.cnt](self.buf)
290                 self.buf.clear()
291         elif self.cnt < 0x0100 and self.cur_highmem_page == 0x01:
292             # Serial ID memory map
293             if self.cnt in self.MAP_HIGH_TABLE_1:
294                 self.MAP_HIGH_TABLE_1[self.cnt](self.buf)
295                 self.buf.clear()
296
297     # Annotation helper
298     def annotate(self, key, value, start_cnt=None, end_cnt=None):
299         if start_cnt is None:
300             start_cnt = self.cnt - len(self.buf) + 1
301         if end_cnt is None:
302             end_cnt = self.cnt
303         self.put(self.sn[start_cnt][0], self.sn[end_cnt][1],
304                 self.out_ann, [0, [key + ": " + value]])
305         self.put(self.sn[start_cnt][0], self.sn[end_cnt][1],
306                  self.out_ann, [1, [value]])
307
308     # Placeholder handler, needed to advance the buffer past unused or
309     # reserved space in the structures.
310     def ignore(self, data):
311         pass
312
313     # Show as ASCII if possible
314     def maybe_ascii(self, data):
315         for i in range(len(data)):
316             if data[i] >= 0x20 and data[i] < 0x7f:
317                 cnt = self.cnt - len(data) + 1
318                 self.annotate("Vendor ID", chr(data[i]), cnt, cnt)
319
320     # Convert 16-bit two's complement values, with each increment
321     # representing 1/256C, to degrees Celcius.
322     def to_temp(self, value):
323         if value & 0x8000:
324             value = -((value ^ 0xffff) + 1)
325         temp = value / 256.0
326         return "%.1f C" % temp
327
328     # TX bias current in uA. Each increment represents 0.2uA
329     def to_current(self, value):
330         current = value / 500000.0
331         return "%.1f mA" % current
332
333     # Power in mW, with each increment representing 0.1uW
334     def to_power(self, value):
335         power = value / 10000.0
336         return "%.2f mW" % power
337
338     # Wavelength in increments of 0.05nm
339     def to_wavelength(self, value):
340         wl = value / 20
341         return "%d nm" % wl
342
343     # Wavelength in increments of 0.005nm
344     def to_wavelength_tolerance(self, value):
345         wl = value / 200.0
346         return "%.1f nm" % wl
347
348     def module_id(self, data):
349         self.annotate("Module identifier", MODULE_ID.get(data[0], "Unknown"))
350
351     def signal_cc(self, data):
352         # No good data available.
353         if (data[0] != 0x00):
354             self.annotate("Signal Conditioner Control", "%.2x" % data[0])
355
356     def alarm_warnings(self, data):
357         cnt_idx = self.cnt - len(data)
358         idx = 0
359         while idx < 56:
360             if idx == 8:
361                 # Skip over reserved A/D flag thresholds
362                 idx += 8
363             value = (data[idx] << 8) | data[idx + 1]
364             if value != 0:
365                 name = ALARM_THRESHOLDS.get(idx, "...")
366                 if idx in (0, 2, 4, 6):
367                     self.annotate(name, self.to_temp(value),
368                             cnt_idx + idx, cnt_idx + idx + 1)
369                 elif idx in (16, 18, 20, 22):
370                     self.annotate(name, self.to_current(value),
371                             cnt_idx + idx, cnt_idx + idx + 1)
372                 elif idx in (24, 26, 28, 30, 32, 34, 36, 38):
373                     self.annotate(name, self.to_power(value),
374                             cnt_idx + idx, cnt_idx + idx + 1)
375                 else:
376                     self.annotate(name, "%d" % name, value, cnt_idx + idx,
377                             cnt_idx + idx + 1)
378             idx += 2
379
380     def vps(self, data):
381         # No good data available.
382         if (data != [0, 0]):
383             self.annotate("VPS", "%.2x%.2x" % (data[0], data[1]))
384
385     def ber(self, data):
386         # No good data available.
387         if (data != [0, 0]):
388             self.annotate("BER", str(data))
389
390     def wavelength_cr(self, data):
391         # No good data available.
392         if (data != [0, 0, 0, 0]):
393             self.annotate("WCR", str(data))
394
395     def fec_cr(self, data):
396         if (data != [0, 0, 0, 0]):
397             self.annotate("FEC", str(data))
398
399     def int_ctrl(self, data):
400         # No good data available. Also boring.
401         out = []
402         for d in data:
403             out.append("%.2x" % d)
404         self.annotate("Interrupt bits", ' '.join(out))
405
406     def ad_readout(self, data):
407         cnt_idx = self.cnt - len(data) + 1
408         idx = 0
409         while idx < 14:
410             if idx == 2:
411                 # Skip over reserved field
412                 idx += 2
413             value = (data[idx] << 8) | data[idx + 1]
414             name = AD_READOUTS.get(idx, "...")
415             if value != 0:
416                 if idx == 0:
417                     self.annotate(name, self.to_temp(value),
418                             cnt_idx + idx, cnt_idx + idx + 1)
419                 elif idx == 4:
420                     self.annotate(name, self.to_current(value),
421                             cnt_idx + idx, cnt_idx + idx + 1)
422                 elif idx in (6, 8):
423                     self.annotate(name, self.to_power(value),
424                             cnt_idx + idx, cnt_idx + idx + 1)
425                 else:
426                     self.annotate(name, str(value), cnt_idx + idx,
427                             cnt_idx + idx + 1)
428             idx += 2
429
430     def gcs(self, data):
431         allbits = (data[0] << 8) | data[1]
432         out = []
433         for b in range(13):
434             if allbits & 0x8000:
435                 out.append(GCS_BITS[b])
436             allbits <<= 1
437         self.annotate("General Control/Status", ', '.join(out))
438
439     def page_select(self, data):
440         self.cur_highmem_page = data[0]
441
442     def ext_module_id(self, data):
443         out = ["Power level %d module" % ((data[0] >> 6) + 1)]
444         if data[0] & 0x20 == 0:
445             out.append("CDR")
446         if data[0] & 0x10 == 0:
447             out.append("TX ref clock input required")
448         if data[0] & 0x08 == 0:
449             self.have_clei = True
450         self.annotate("Extended id", ', '.join(out))
451
452     def connector(self, data):
453         if data[0] in CONNECTOR:
454             self.annotate("Connector", CONNECTOR[data[0]])
455
456     def transceiver(self, data):
457         out = []
458         for t in range(8):
459             if data[t] == 0:
460                 continue
461             value = data[t]
462             for b in range(8):
463                 if value & 0x80:
464                     if len(TRANSCEIVER[t]) < b + 1:
465                         out.append("(unknown)")
466                     else:
467                         out.append(TRANSCEIVER[t][b])
468                 value <<= 1
469         self.annotate("Transceiver compliance", ', '.join(out))
470
471     def serial_encoding(self, data):
472         out = []
473         value = data[0]
474         for b in range(8):
475             if value & 0x80:
476                 if len(SERIAL_ENCODING) < b + 1:
477                     out.append("(unknown)")
478                 else:
479                     out.append(SERIAL_ENCODING[b])
480                 value <<= 1
481         self.annotate("Serial encoding support", ', '.join(out))
482
483     def br_min(self, data):
484         # Increments represent 100Mb/s
485         rate = data[0] / 10.0
486         self.annotate("Minimum bit rate", "%.3f GB/s" % rate)
487
488     def br_max(self, data):
489         # Increments represent 100Mb/s
490         rate = data[0] / 10.0
491         self.annotate("Maximum bit rate", "%.3f GB/s" % rate)
492
493     def link_length_smf(self, data):
494         if data[0] == 0:
495             length = "(standard)"
496         elif data[0] == 255:
497             length = "> 254 km"
498         else:
499             length = "%d km" % data[0]
500         self.annotate("Link length (SMF)", length)
501
502     def link_length_e50(self, data):
503         if data[0] == 0:
504             length = "(standard)"
505         elif data[0] == 255:
506             length = "> 508 m"
507         else:
508             length = "%d m" % (data[0] * 2)
509         self.annotate("Link length (extended, 50μm MMF)", length)
510
511     def link_length_50um(self, data):
512         if data[0] == 0:
513             length = "(standard)"
514         elif data[0] == 255:
515             length = "> 254 m"
516         else:
517             length = "%d m" % data[0]
518         self.annotate("Link length (50μm MMF)", length)
519
520     def link_length_625um(self, data):
521         if data[0] == 0:
522             length = "(standard)"
523         elif data[0] == 255:
524             length = "> 254 m"
525         else:
526             length = "%d m" % (data[0])
527         self.annotate("Link length (62.5μm MMF)", length)
528
529     def link_length_copper(self, data):
530         if data[0] == 0:
531             length = "(unknown)"
532         elif data[0] == 255:
533             length = "> 254 m"
534         else:
535             length = "%d m" % (data[0] * 2)
536         self.annotate("Link length (copper)", length)
537
538     def device_tech(self, data):
539         out = []
540         xmit = data[0] >> 4
541         if xmit <= len(XMIT_TECH) - 1:
542             out.append("%s transmitter" % XMIT_TECH[xmit])
543         dev = data[0] & 0x0f
544         for b in range(4):
545             out.append(DEVICE_TECH[b][(dev >> (3 - b)) & 0x01])
546         self.annotate("Device technology", ', '.join(out))
547
548     def vendor(self, data):
549         name = bytes(data).strip().decode('ascii').strip('\x00')
550         if name:
551             self.annotate("Vendor", name)
552
553     def cdr(self, data):
554         out = []
555         value = data[0]
556         for b in range(8):
557             if value & 0x80:
558                 out.append(CDR[b])
559             value <<= 1
560         self.annotate("CDR support", ', '.join(out))
561
562     def vendor_oui(self, data):
563         if data != [0, 0, 0]:
564             self.annotate("Vendor OUI", "%.2X-%.2X-%.2X" % tuple(data))
565
566     def vendor_pn(self, data):
567         name = bytes(data).strip().decode('ascii').strip('\x00')
568         if name:
569             self.annotate("Vendor part number", name)
570
571     def vendor_rev(self, data):
572         name = bytes(data).strip().decode('ascii').strip('\x00')
573         if name:
574             self.annotate("Vendor revision", name)
575
576     def wavelength(self, data):
577         value = (data[0] << 8) | data[1]
578         self.annotate("Wavelength", self.to_wavelength(value))
579
580     def wavelength_tolerance(self, data):
581         value = (data[0] << 8) | data[1]
582         self.annotate("Wavelength tolerance", self.to_wavelength_tolerance(value))
583
584     def max_case_temp(self, data):
585         self.annotate("Maximum case temperature", "%d C" % data[0])
586
587     def power_supply(self, data):
588         out = []
589         self.annotate("Max power dissipation",
590                 "%.3f W" % (data[0] * 0.02), self.cnt - 3, self.cnt - 3)
591         self.annotate("Max power dissipation (powered down)",
592                 "%.3f W" % (data[1] * 0.01), self.cnt - 2, self.cnt - 2)
593         value = (data[2] >> 4) * 0.050
594         self.annotate("Max current required (5V supply)",
595                 "%.3f A" % value, self.cnt - 1, self.cnt - 1)
596         value = (data[2] & 0x0f) * 0.100
597         self.annotate("Max current required (3.3V supply)",
598                 "%.3f A" % value, self.cnt - 1, self.cnt - 1)
599         value = (data[3] >> 4) * 0.100
600         self.annotate("Max current required (1.8V supply)",
601                 "%.3f A" % value, self.cnt, self.cnt)
602         value = (data[3] & 0x0f) * 0.050
603         self.annotate("Max current required (-5.2V supply)",
604                 "%.3f A" % value, self.cnt, self.cnt)
605
606     def vendor_sn(self, data):
607         name = bytes(data).strip().decode('ascii').strip('\x00')
608         if name:
609             self.annotate("Vendor serial number", name)
610
611     def manuf_date(self, data):
612         y = int(bytes(data[0:2])) + 2000
613         m = int(bytes(data[2:4]))
614         d = int(bytes(data[4:6]))
615         mnf = "%.4d-%.2d-%.2d" % (y, m, d)
616         lot = bytes(data[6:]).strip().decode('ascii').strip('\x00')
617         if lot:
618             mnf += " lot " + lot
619         self.annotate("Manufacturing date", mnf)
620
621     def diag_mon(self, data):
622         out = []
623         if data[0] & 0x10:
624             out.append("BER support")
625         else:
626             out.append("no BER support")
627         if data[0] & 0x08:
628             out.append("average power measurement")
629         else:
630             out.append("OMA power measurement")
631         self.annotate("Diagnostic monitoring", ', '.join(out))
632
633     def enhanced_opts(self, data):
634         out = []
635         value = data[0]
636         for b in range(8):
637             if value & 0x80:
638                 out.append(ENHANCED_OPTS[b])
639             value <<= 1
640         self.annotate("Enhanced option support", ', '.join(out))
641
642     def aux_mon(self, data):
643         aux = AUX_TYPES[data[0] >> 4]
644         self.annotate("AUX1 monitoring", aux)
645         aux = AUX_TYPES[data[0] & 0x0f]
646         self.annotate("AUX2 monitoring", aux)
647