]> sigrok.org Git - libsigrokdecode.git/blob - decoders/xfp/pd.py
cb67c2d1987556c6c47378149aadae76e4b5ca61
[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 = 1
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     probes = []
203     optional_probes = []
204     options = {}
205     annotations = [
206         ['fieldnames-and-values', 'XFP structure field names and values'],
207         ['fields', 'XFP structure fields'],
208     ]
209
210     def __init__(self, **kwargs):
211         # Received data items, used as an index into samplenum/data
212         self.cnt = -1
213         # Start/end sample numbers per data item
214         self.sn = []
215         # Multi-byte structure buffer
216         self.buf = []
217         # Filled in by address 0x7f in low memory
218         self.cur_highmem_page = 0
219         # Filled in by extended ID value in table 2
220         self.have_clei = False
221         # Handlers for each field in the structure, keyed by the end
222         # index of that field. Each handler is fed all unhandled bytes
223         # up until that point, so mark unused space with the dummy
224         # handler self.ignore().
225         self.MAP_LOWER_MEMORY = {
226             0:  self.module_id,
227             1:  self.signal_cc,
228             57: self.alarm_warnings,
229             59: self.vps,
230             69: self.ignore,
231             71: self.ber,
232             75: self.wavelength_cr,
233             79: self.fec_cr,
234             95: self.int_ctrl,
235             109: self.ad_readout,
236             111: self.gcs,
237             117: self.ignore,
238             118: self.ignore,
239             122: self.ignore,
240             126: self.ignore,
241             127: self.page_select,
242         }
243         self.MAP_HIGH_TABLE_1 = {
244             128: self.module_id,
245             129: self.ext_module_id,
246             130: self.connector,
247             138: self.transceiver,
248             139: self.serial_encoding,
249             140: self.br_min,
250             141: self.br_max,
251             142: self.link_length_smf,
252             143: self.link_length_e50,
253             144: self.link_length_50um,
254             145: self.link_length_625um,
255             146: self.link_length_copper,
256             147: self.device_tech,
257             163: self.vendor,
258             164: self.cdr,
259             167: self.vendor_oui,
260             183: self.vendor_pn,
261             185: self.vendor_rev,
262             187: self.wavelength,
263             189: self.wavelength_tolerance,
264             190: self.max_case_temp,
265             191: self.ignore,
266             195: self.power_supply,
267             211: self.vendor_sn,
268             219: self.manuf_date,
269             220: self.diag_mon,
270             221: self.enhanced_opts,
271             222: self.aux_mon,
272             223: self.ignore,
273             255: self.maybe_ascii,
274         }
275
276     def start(self):
277         self.out_ann = self.register(srd.OUTPUT_ANN)
278
279     def decode(self, ss, es, data):
280         cmd, data = data
281
282         # We only care about actual data bytes that are read (for now).
283         if cmd != 'DATA READ':
284             return
285
286         self.cnt += 1
287         self.sn.append([ss, es])
288
289         self.buf.append(data)
290         if self.cnt < 0x80:
291             if self.cnt in self.MAP_LOWER_MEMORY:
292                 self.MAP_LOWER_MEMORY[self.cnt](self.buf)
293                 self.buf.clear()
294         elif self.cnt < 0x0100 and self.cur_highmem_page == 0x01:
295             # Serial ID memory map
296             if self.cnt in self.MAP_HIGH_TABLE_1:
297                 self.MAP_HIGH_TABLE_1[self.cnt](self.buf)
298                 self.buf.clear()
299
300     # Annotation helper
301     def annotate(self, key, value, start_cnt=None, end_cnt=None):
302         if start_cnt is None:
303             start_cnt = self.cnt - len(self.buf) + 1
304         if end_cnt is None:
305             end_cnt = self.cnt
306         self.put(self.sn[start_cnt][0], self.sn[end_cnt][1],
307                 self.out_ann, [0, [key + ": " + value]])
308         self.put(self.sn[start_cnt][0], self.sn[end_cnt][1],
309                  self.out_ann, [1, [value]])
310
311     # Placeholder handler, needed to advance the buffer past unused or
312     # reserved space in the structures.
313     def ignore(self, data):
314         pass
315
316     # Show as ASCII if possible
317     def maybe_ascii(self, data):
318         for i in range(len(data)):
319             if data[i] >= 0x20 and data[i] < 0x7f:
320                 cnt = self.cnt - len(data) + 1
321                 self.annotate("Vendor ID", chr(data[i]), cnt, cnt)
322
323     # Convert 16-bit two's complement values, with each increment
324     # representing 1/256C, to degrees Celcius.
325     def to_temp(self, value):
326         if value & 0x8000:
327             value = -((value ^ 0xffff) + 1)
328         temp = value / 256.0
329         return "%.1f C" % temp
330
331     # TX bias current in uA. Each increment represents 0.2uA
332     def to_current(self, value):
333         current = value / 500000.0
334         return "%.1f mA" % current
335
336     # Power in mW, with each increment representing 0.1uW
337     def to_power(self, value):
338         power = value / 10000.0
339         return "%.2f mW" % power
340
341     # Wavelength in increments of 0.05nm
342     def to_wavelength(self, value):
343         wl = value / 20
344         return "%d nm" % wl
345
346     # Wavelength in increments of 0.005nm
347     def to_wavelength_tolerance(self, value):
348         wl = value / 200.0
349         return "%.1f nm" % wl
350
351     def module_id(self, data):
352         self.annotate("Module identifier", MODULE_ID.get(data[0], "Unknown"))
353
354     def signal_cc(self, data):
355         # No good data available.
356         if (data[0] != 0x00):
357             self.annotate("Signal Conditioner Control", "%.2x" % data[0])
358
359     def alarm_warnings(self, data):
360         cnt_idx = self.cnt - len(data)
361         idx = 0
362         while idx < 56:
363             if idx == 8:
364                 # Skip over reserved A/D flag thresholds
365                 idx += 8
366             value = (data[idx] << 8) | data[idx + 1]
367             if value != 0:
368                 name = ALARM_THRESHOLDS.get(idx, "...")
369                 if idx in (0, 2, 4, 6):
370                     self.annotate(name, self.to_temp(value),
371                             cnt_idx + idx, cnt_idx + idx + 1)
372                 elif idx in (16, 18, 20, 22):
373                     self.annotate(name, self.to_current(value),
374                             cnt_idx + idx, cnt_idx + idx + 1)
375                 elif idx in (24, 26, 28, 30, 32, 34, 36, 38):
376                     self.annotate(name, self.to_power(value),
377                             cnt_idx + idx, cnt_idx + idx + 1)
378                 else:
379                     self.annotate(name, "%d" % name, value, cnt_idx + idx,
380                             cnt_idx + idx + 1)
381             idx += 2
382
383     def vps(self, data):
384         # No good data available.
385         if (data != [0, 0]):
386             self.annotate("VPS", "%.2x%.2x" % (data[0], data[1]))
387
388     def ber(self, data):
389         # No good data available.
390         if (data != [0, 0]):
391             self.annotate("BER", str(data))
392
393     def wavelength_cr(self, data):
394         # No good data available.
395         if (data != [0, 0, 0, 0]):
396             self.annotate("WCR", str(data))
397
398     def fec_cr(self, data):
399         if (data != [0, 0, 0, 0]):
400             self.annotate("FEC", str(data))
401
402     def int_ctrl(self, data):
403         # No good data available. Also boring.
404         out = []
405         for d in data:
406             out.append("%.2x" % d)
407         self.annotate("Interrupt bits", ' '.join(out))
408
409     def ad_readout(self, data):
410         cnt_idx = self.cnt - len(data) + 1
411         idx = 0
412         while idx < 14:
413             if idx == 2:
414                 # Skip over reserved field
415                 idx += 2
416             value = (data[idx] << 8) | data[idx + 1]
417             name = AD_READOUTS.get(idx, "...")
418             if value != 0:
419                 if idx == 0:
420                     self.annotate(name, self.to_temp(value),
421                             cnt_idx + idx, cnt_idx + idx + 1)
422                 elif idx == 4:
423                     self.annotate(name, self.to_current(value),
424                             cnt_idx + idx, cnt_idx + idx + 1)
425                 elif idx in (6, 8):
426                     self.annotate(name, self.to_power(value),
427                             cnt_idx + idx, cnt_idx + idx + 1)
428                 else:
429                     self.annotate(name, str(value), cnt_idx + idx,
430                             cnt_idx + idx + 1)
431             idx += 2
432
433     def gcs(self, data):
434         allbits = (data[0] << 8) | data[1]
435         out = []
436         for b in range(13):
437             if allbits & 0x8000:
438                 out.append(GCS_BITS[b])
439             allbits <<= 1
440         self.annotate("General Control/Status", ', '.join(out))
441
442     def page_select(self, data):
443         self.cur_highmem_page = data[0]
444
445     def ext_module_id(self, data):
446         out = ["Power level %d module" % ((data[0] >> 6) + 1)]
447         if data[0] & 0x20 == 0:
448             out.append("CDR")
449         if data[0] & 0x10 == 0:
450             out.append("TX ref clock input required")
451         if data[0] & 0x08 == 0:
452             self.have_clei = True
453         self.annotate("Extended id", ', '.join(out))
454
455     def connector(self, data):
456         if data[0] in CONNECTOR:
457             self.annotate("Connector", CONNECTOR[data[0]])
458
459     def transceiver(self, data):
460         out = []
461         for t in range(8):
462             if data[t] == 0:
463                 continue
464             value = data[t]
465             for b in range(8):
466                 if value & 0x80:
467                     if len(TRANSCEIVER[t]) < b + 1:
468                         out.append("(unknown)")
469                     else:
470                         out.append(TRANSCEIVER[t][b])
471                 value <<= 1
472         self.annotate("Transceiver compliance", ', '.join(out))
473
474     def serial_encoding(self, data):
475         out = []
476         value = data[0]
477         for b in range(8):
478             if value & 0x80:
479                 if len(SERIAL_ENCODING) < b + 1:
480                     out.append("(unknown)")
481                 else:
482                     out.append(SERIAL_ENCODING[b])
483                 value <<= 1
484         self.annotate("Serial encoding support", ', '.join(out))
485
486     def br_min(self, data):
487         # Increments represent 100Mb/s
488         rate = data[0] / 10.0
489         self.annotate("Minimum bit rate", "%.3f GB/s" % rate)
490
491     def br_max(self, data):
492         # Increments represent 100Mb/s
493         rate = data[0] / 10.0
494         self.annotate("Maximum bit rate", "%.3f GB/s" % rate)
495
496     def link_length_smf(self, data):
497         if data[0] == 0:
498             length = "(standard)"
499         elif data[0] == 255:
500             length = "> 254 km"
501         else:
502             length = "%d km" % data[0]
503         self.annotate("Link length (SMF)", length)
504
505     def link_length_e50(self, data):
506         if data[0] == 0:
507             length = "(standard)"
508         elif data[0] == 255:
509             length = "> 508 m"
510         else:
511             length = "%d m" % (data[0] * 2)
512         self.annotate("Link length (extended, 50μm MMF)", length)
513
514     def link_length_50um(self, data):
515         if data[0] == 0:
516             length = "(standard)"
517         elif data[0] == 255:
518             length = "> 254 m"
519         else:
520             length = "%d m" % data[0]
521         self.annotate("Link length (50μm MMF)", length)
522
523     def link_length_625um(self, data):
524         if data[0] == 0:
525             length = "(standard)"
526         elif data[0] == 255:
527             length = "> 254 m"
528         else:
529             length = "%d m" % (data[0])
530         self.annotate("Link length (62.5μm MMF)", length)
531
532     def link_length_copper(self, data):
533         if data[0] == 0:
534             length = "(unknown)"
535         elif data[0] == 255:
536             length = "> 254 m"
537         else:
538             length = "%d m" % (data[0] * 2)
539         self.annotate("Link length (copper)", length)
540
541     def device_tech(self, data):
542         out = []
543         xmit = data[0] >> 4
544         if xmit <= len(XMIT_TECH) - 1:
545             out.append("%s transmitter" % XMIT_TECH[xmit])
546         dev = data[0] & 0x0f
547         for b in range(4):
548             out.append(DEVICE_TECH[b][(dev >> (3 - b)) & 0x01])
549         self.annotate("Device technology", ', '.join(out))
550
551     def vendor(self, data):
552         name = bytes(data).strip().decode('ascii').strip('\x00')
553         if name:
554             self.annotate("Vendor", name)
555
556     def cdr(self, data):
557         out = []
558         value = data[0]
559         for b in range(8):
560             if value & 0x80:
561                 out.append(CDR[b])
562             value <<= 1
563         self.annotate("CDR support", ', '.join(out))
564
565     def vendor_oui(self, data):
566         if data != [0, 0, 0]:
567             self.annotate("Vendor OUI", "%.2X-%.2X-%.2X" % tuple(data))
568
569     def vendor_pn(self, data):
570         name = bytes(data).strip().decode('ascii').strip('\x00')
571         if name:
572             self.annotate("Vendor part number", name)
573
574     def vendor_rev(self, data):
575         name = bytes(data).strip().decode('ascii').strip('\x00')
576         if name:
577             self.annotate("Vendor revision", name)
578
579     def wavelength(self, data):
580         value = (data[0] << 8) | data[1]
581         self.annotate("Wavelength", self.to_wavelength(value))
582
583     def wavelength_tolerance(self, data):
584         value = (data[0] << 8) | data[1]
585         self.annotate("Wavelength tolerance", self.to_wavelength_tolerance(value))
586
587     def max_case_temp(self, data):
588         self.annotate("Maximum case temperature", "%d C" % data[0])
589
590     def power_supply(self, data):
591         out = []
592         self.annotate("Max power dissipation",
593                 "%.3f W" % (data[0] * 0.02), self.cnt - 3, self.cnt - 3)
594         self.annotate("Max power dissipation (powered down)",
595                 "%.3f W" % (data[1] * 0.01), self.cnt - 2, self.cnt - 2)
596         value = (data[2] >> 4) * 0.050
597         self.annotate("Max current required (5V supply)",
598                 "%.3f A" % value, self.cnt - 1, self.cnt - 1)
599         value = (data[2] & 0x0f) * 0.100
600         self.annotate("Max current required (3.3V supply)",
601                 "%.3f A" % value, self.cnt - 1, self.cnt - 1)
602         value = (data[3] >> 4) * 0.100
603         self.annotate("Max current required (1.8V supply)",
604                 "%.3f A" % value, self.cnt, self.cnt)
605         value = (data[3] & 0x0f) * 0.050
606         self.annotate("Max current required (-5.2V supply)",
607                 "%.3f A" % value, self.cnt, self.cnt)
608
609     def vendor_sn(self, data):
610         name = bytes(data).strip().decode('ascii').strip('\x00')
611         if name:
612             self.annotate("Vendor serial number", name)
613
614     def manuf_date(self, data):
615         y = int(bytes(data[0:2])) + 2000
616         m = int(bytes(data[2:4]))
617         d = int(bytes(data[4:6]))
618         mnf = "%.4d-%.2d-%.2d" % (y, m, d)
619         lot = bytes(data[6:]).strip().decode('ascii').strip('\x00')
620         if lot:
621             mnf += " lot " + lot
622         self.annotate("Manufacturing date", mnf)
623
624     def diag_mon(self, data):
625         out = []
626         if data[0] & 0x10:
627             out.append("BER support")
628         else:
629             out.append("no BER support")
630         if data[0] & 0x08:
631             out.append("average power measurement")
632         else:
633             out.append("OMA power measurement")
634         self.annotate("Diagnostic monitoring", ', '.join(out))
635
636     def enhanced_opts(self, data):
637         out = []
638         value = data[0]
639         for b in range(8):
640             if value & 0x80:
641                 out.append(ENHANCED_OPTS[b])
642             value <<= 1
643         self.annotate("Enhanced option support", ', '.join(out))
644
645     def aux_mon(self, data):
646         aux = AUX_TYPES[data[0] >> 4]
647         self.annotate("AUX1 monitoring", aux)
648         aux = AUX_TYPES[data[0] & 0x0f]
649         self.annotate("AUX2 monitoring", aux)
650