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