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