summaryrefslogtreecommitdiff
path: root/usr/src/lib/libsff/common/libsff.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/lib/libsff/common/libsff.c')
-rw-r--r--usr/src/lib/libsff/common/libsff.c1414
1 files changed, 1414 insertions, 0 deletions
diff --git a/usr/src/lib/libsff/common/libsff.c b/usr/src/lib/libsff/common/libsff.c
new file mode 100644
index 0000000000..43ca69c75d
--- /dev/null
+++ b/usr/src/lib/libsff/common/libsff.c
@@ -0,0 +1,1414 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+/*
+ * Parse raw SFF data into an nvlist that can be processed by users, providing
+ * them with what can be printable strings. At the moment, we handle the
+ * majority of parsing page 0xa0 based on SFF 8472 (thus covering INF-8074 and
+ * friends) and SFF 8636 (thus covering SFF-8436 and friends). Interfaces that
+ * parse data into logical structures may be useful to add when considering
+ * monitoring data in page 0xa2.
+ *
+ * When parsing, we try to make sure that the user has supplied, or at least
+ * thinks they have supplied, a buffer of sufficient length. The general design
+ * is that we require the buffer to be large enough to cover all of the offsets
+ * that we care about. If the buffer isn't this large, then we leave it be.
+ *
+ * This library is private and subject to change at any time.
+ */
+
+#include <assert.h>
+#include <strings.h>
+#include <libsff.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "sff.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+/*
+ * Maximum size of a string buffer while parsing.
+ */
+#define SFP_STRBUF 128
+
+/*
+ * Minimum length of the buffer we require to parse the SFP data.
+ */
+#define SFP_MIN_LEN_8472 96
+#define SFP_MIN_LEN_8636 224
+
+/*
+ * This table is derived from SFF 8024 Section 4.1, Table 4-1.
+ */
+static const char *sff_8024_id_strs[SFF_8024_NIDS] = {
+ "Unknown or Unspecified",
+ "GBIC",
+ "Module/connector soldered to motherboard",
+ "SFP/SFP+/SFP28",
+ "300 pin XBI",
+ "XENPAK",
+ "XFP",
+ "XFF",
+ "XFP-E",
+ "XPAK",
+ "X2",
+ "DWDM-SFP/SFP+ (not using SFF-8472)",
+ "QSFP",
+ "QSFP+ or later",
+ "CXP or later",
+ "Shielded Mini Multilane HD 4X",
+ "Shielded Mini Multilane HD 8X",
+ "QSFP28 or later",
+ "CXP2 (aka CXP28) or later",
+ "CDFP (Style 1/Style2)",
+ "Shielded Mini Multilane HD 4X Fanout Cable",
+ "Shielded Mini Multilane HD 8X Fanout Cable",
+ "CDFP (Style 3)",
+ "microQSFP"
+};
+
+/*
+ * The set of values used for the encoding depends on whether we're a basic SFP
+ * device or not. The values are inconsistent between SFP and QSFP based
+ * devices.
+ *
+ * This table is derived from SFF 8024 r3.9 Table 4-2.
+ */
+#define SFF_8024_NENCS 9
+static const char *sff_8024_enc_sfp[] = {
+ "Unspecified",
+ "8B/10B",
+ "4B/5B",
+ "NRZ",
+ "Manchester",
+ "SONET Scrambled",
+ "64B/66B",
+ "256B/257B",
+ "PAM4"
+};
+
+static const char *sff_8024_enc_qsfp[] = {
+ "Unspecified",
+ "8B/10B",
+ "4B/5B",
+ "NRZ",
+ "SONET Scrambled",
+ "64B/66B",
+ "Manchester",
+ "256B/257B",
+ "PAM4"
+};
+
+/*
+ * This table is derived from SFF 8024 r3.9 Section 4.4.
+ */
+#define SFF_8024_EXT_SPEC_NENTRIES 27
+static const char *sff_8024_ext_spec[] = {
+ "Unspecified",
+ "100G AOC or 25GAUI C2M AOC",
+ "100GBASE-SR4 or 25GBASE-SR",
+ "100GBASE-LR4 or 25GBASE-LR",
+ "100GBASE-ER4 or 25GBASE-ER",
+ "100GBASE-SR10",
+ "100G CWDM4",
+ "100G PSM4 Parallel SMF",
+ "100G ACC or 25GAUI C2M ACC",
+ "Obsolete",
+ "Reserved",
+ "100GBASE-CR4 or 25GBASE-CR CA-L",
+ "25GBASE-CR CA-S",
+ "25GBASE-CR CA-N",
+ "Reserved",
+ "Reserved",
+ "40GBASE-ER4",
+ "4 x 10GBASE-SR",
+ "40G PSM4 Parallel SMF",
+ "G959.1 profile P1I1-2D1",
+ "G959.1 profile P1S1-2D2",
+ "G959.1 profile P1L1-2D2",
+ "10GBASE-T with SFI electrical interface",
+ "100G CLR4",
+ "100G AOC or 25GAUI C2M AOC",
+ "100G ACC or 25GAUI C2M ACC",
+ "100GE-DWDM2"
+};
+
+typedef struct sff_pair {
+ uint_t sp_val;
+ const char *sp_name;
+} sff_pair_t;
+
+/*
+ * This table is derived from SFF 8024 r3.9 Section 4.3.
+ */
+static sff_pair_t sff_8024_connectors[] = {
+ { 0x00, "Unknown" },
+ { 0x01, "SC (Subscriber Connector)" },
+ { 0x02, "Fibre Channel Style 1 copper connector" },
+ { 0x03, "Fibre Channel Style 2 copper connector" },
+ { 0x04, "BNC/TNC (Bayonet/Threaded Neill-Concelman)" },
+ { 0x05, "Fibre Channel coax headers" },
+ { 0x06, "Fiber Jack" },
+ { 0x07, "LC (Lucent Connector)" },
+ { 0x08, "MT-RJ (Mechanical Transfer - Registered Jack)" },
+ { 0x09, "MU (Multiple Optical)" },
+ { 0x0A, "SG" },
+ { 0x0B, "Optical Pigtail" },
+ { 0x0C, "MPO 1x12 (Multifiber Parallel Optic)" },
+ { 0x0D, "MPO 2x16" },
+ { 0x20, "HSSDC II (High Speed Serial Data Connector)" },
+ { 0x21, "Copper pigtail" },
+ { 0x22, "RJ45 (Registered Jack)" },
+ { 0x23, "No separable connector" },
+ { 0x24, "MXC 2x16" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_10GETH_MASK 0xf0
+static sff_pair_t sff_8472_comp_10geth[] = {
+ { 0x80, "10G Base-ER" },
+ { 0x40, "10G Base-LRM" },
+ { 0x20, "10G Base-LR" },
+ { 0x10, "10G Base-SR" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_IB_MASK 0x0f
+static sff_pair_t sff_8472_comp_ib[] = {
+ { 0x08, "1X SX" },
+ { 0x04, "1X LX" },
+ { 0x02, "1X Copper Active" },
+ { 0x01, "1X Copper Passive" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_ESCON_MASK 0xc0
+static sff_pair_t sff_8472_comp_escon[] = {
+ { 0x80, "ESCON MMF, 1310nm LED" },
+ { 0x40, "ESCON SMF, 1310nm Laser" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3. These values come from both
+ * bytes 4 and 5. We treat this as a uint16_t with the low byte as byte 4 and
+ * the high byte as byte 5.
+ */
+#define SFF_8472_COMP_SOCON_MASK 0x773f
+static sff_pair_t sff_8472_comp_sonet[] = {
+ { 0x20, "OC-192, short reach" },
+ { 0x10, "SONET reach specifier bit 1" },
+ { 0x08, "ONET reach specifier bit 2" },
+ { 0x04, "OC-48, long reach" },
+ { 0x02, "OC-48, intermediate reach" },
+ { 0x01, "OC-48, short reach" },
+ /* 0x8000 is unallocated */
+ { 0x4000, "OC-12, single mode, long reach" },
+ { 0x2000, "OC-12, single mode, inter. reach" },
+ { 0x1000, "OC-12, short reach" },
+ /* 0x800 is unallocted */
+ { 0x0400, "OC-3, single mode, long reach" },
+ { 0x0200, "OC-3, single mode, inter. reach" },
+ { 0x0100, "OC-3, short reach" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_ETH_MASK 0xff
+static sff_pair_t sff_8472_comp_eth[] = {
+ { 0x80, "BASE-PX" },
+ { 0x40, "BASE-BX10" },
+ { 0x20, "100BASE-FX" },
+ { 0x10, "100BASE-LX/LX10" },
+ { 0x08, "1000BASE-T" },
+ { 0x04, "1000BASE-CX" },
+ { 0x02, "1000BASE-LX" },
+ { 0x01, "1000BASE-SX" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_FCLEN_MASK 0xf8
+static sff_pair_t sff_8472_comp_fclen[] = {
+ { 0x80, "very long distance (V)" },
+ { 0x40, "short distance (S)" },
+ { 0x20, "intermeddiate distance (I)" },
+ { 0x10, "long distance (L)" },
+ { 0x08, "medium distance (M)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3. These values come from both
+ * bytes 7 and 8. We treat this as a uint16_t with the low byte as byte 7 and
+ * the high byte as byte 8.
+ */
+#define SFF_8472_COMP_TECH_MASK 0xf007
+static sff_pair_t sff_8472_comp_tech[] = {
+ { 0x4, "Shortwave laser, linear Rx (SA)" },
+ { 0x2, "Longwave laser (LC)" },
+ { 0x1, "Electrical inter-enclosure (EL)" },
+ { 0x8000, "Electrical intra-enclosure (EL)" },
+ { 0x4000, "Shortwave laser w/o OFC (SN)" },
+ { 0x2000, "Shortwave laser with OFC (SL)" },
+ { 0x1000, "Longwave laser (LL)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_CABLE_MASK 0x0c
+#define SFF_8472_COMP_CABLE_ACTIVE 0x08
+#define SFF_8472_COMP_CABLE_PASSIVE 0x04
+static sff_pair_t sff_8472_comp_cable[] = {
+ { 0x08, "Active Cable" },
+ { 0x04, "Passive Cable" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_MEDIA_MASK 0xfd
+static sff_pair_t sff_8472_comp_media[] = {
+ { 0x80, "Twin Axial Pair (TW)" },
+ { 0x40, "Twisted Pair (TP)" },
+ { 0x20, "Miniature Coax (MI)" },
+ { 0x10, "Video Coax (TV)" },
+ { 0x08, "Multimode, 62.5um (M6)" },
+ { 0x04, "Multimode, 50um (M5, M5E)" },
+ /* 0x02 is Unallocated */
+ { 0x01, "Single Mode (SM)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 5-3.
+ */
+#define SFF_8472_COMP_SPEED_MASK 0xfd
+static sff_pair_t sff_8472_comp_speed[] = {
+ { 0x80, "1200 MBytes/sec" },
+ { 0x40, "800 MBytes/sec" },
+ { 0x20, "1600 MBytes/sec" },
+ { 0x10, "400 MBytes/sec" },
+ { 0x08, "3200 MBytes/sec" },
+ { 0x04, "200 MBytes/sec" },
+ /* 0x02 is Unallocated */
+ { 0x01, "100 MBytes/sec" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 8-1.
+ * Note, only byte 60 is allocated at this time.
+ */
+#define SFF_8472_PCABLE_COMP_MASK 0x3f
+static sff_pair_t sff_8472_pcable_comp[] = {
+ { 0x20, "Reserved for SFF-8461" },
+ { 0x10, "Reserved for SFF-8461" },
+ { 0x08, "Reserved for SFF-8461" },
+ { 0x04, "Reserved for SFF-8461" },
+ { 0x02, "Compliant to FC-PI-4 Appendix H" },
+ { 0x01, "Compliant to SFF-8431 Appendix E" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 8-2.
+ * Note, only byte 60 is allocated at this time.
+ */
+#define SFF_8472_ACABLE_COMP_MASK 0xf
+static sff_pair_t sff_8472_acable_comp[] = {
+ { 0x08, "Compliant to FC-PI-4 Limiting" },
+ { 0x04, "Compliant to SFF-8431 Limiting" },
+ { 0x02, "Compliant to FC-PI-4 Appendix H" },
+ { 0x01, "Compliant to SFF-8431 Appendix" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 8-3.
+ * Note that we combined byte 64 and 65. Byte 64 is the upper bit.
+ */
+#define SFF_8472_OPTION_MASK 0x3ffe
+static sff_pair_t sff_8472_options[] = {
+ { 0x2000, "Power Level 3 Requirement"},
+ { 0x1000, "Paging Implemented"},
+ { 0x0800, "Retimer or CDR implemented"},
+ { 0x0400, "Cooled Transceiver Implemented"},
+ { 0x0200, "Power Level 2 Requirement"},
+ { 0x0100, "Linear Receiver Output Implemented"},
+ { 0x0080, "Receiver decision threshold implemented"},
+ { 0x0040, "Tunable transmitter"},
+ { 0x0020, "RATE_SELECT implemented"},
+ { 0x0010, "TX_DISABLE implemented"},
+ { 0x0008, "TX_FAULT implemented"},
+ { 0x0004, "Rx_LOS inverted"},
+ { 0x0002, "Rx_LOS implemented"},
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 8-6.
+ */
+#define SFF_8472_EXTOPT_MASK 0xfe
+static sff_pair_t sff_8472_extopts[] = {
+ { 0x80, "Alarm/Warning flags implemented" },
+ { 0x40, "Soft TX_DISABLE implemented" },
+ { 0x20, "Soft TX_FAULT implemented" },
+ { 0x10, "Soft RX_LOS implemented" },
+ { 0x08, "Soft RATE_SELECT implemented" },
+ { 0x04, "Application Select implemented" },
+ { 0x02, "Soft Rate Select Control Implemented" },
+ { 0x01, "" },
+};
+
+/*
+ * This is derived from SFF 8472 r12.2 Table 8-8.
+ */
+#define SFF_8472_8472_COMP_NENTRIES 9
+static const char *sff_8472_8472_comp[] = {
+ "Not compliant",
+ "Rev 9.3",
+ "Rev 9.5",
+ "Rev 10.2",
+ "Rev 10.4",
+ "Rev 11.0",
+ "Rev 11.3",
+ "Rev 11.4",
+ "Rev 12.0"
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_10GETH_MASK 0x7f
+static sff_pair_t sff_8636_comp_10geth[] = {
+ { 0x40, "10GBASE-LRM" },
+ { 0x20, "10GBASE-LR" },
+ { 0x10, "10GBASE-SR" },
+ { 0x08, "40GBASE-CR4" },
+ { 0x04, "40GBASE-SR4" },
+ { 0x02, "40GBASE-LR4" },
+ { 0x01, "40G Active Cable (XLPPI)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_SONET_MASK 0x07
+static sff_pair_t sff_8636_comp_sonet[] = {
+ { 0x04, "OC 48, long reach" },
+ { 0x02, "OC 48, intermediate reach" },
+ { 0x01, "OC 48 short reach" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_SAS_MASK 0xf0
+static sff_pair_t sff_8636_comp_sas[] = {
+ { 0x80, "SAS 24.0 Gb/s" },
+ { 0x40, "SAS 12.0 Gb/s" },
+ { 0x20, "SAS 6.0 Gb/s" },
+ { 0x10, "SAS 3.0 Gb/s" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_ETH_MASK 0x0f
+static sff_pair_t sff_8636_comp_eth[] = {
+ { 0x08, "1000BASE-T" },
+ { 0x04, "1000BASE-CX" },
+ { 0x02, "1000BASE-LX" },
+ { 0x01, "1000BASE-SX" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_FCLEN_MASK 0xf8
+static sff_pair_t sff_8636_comp_fclen[] = {
+ { 0x80, "very long distance (V)" },
+ { 0x40, "short distance (S)" },
+ { 0x20, "intermeddiate distance (I)" },
+ { 0x10, "long distance (L)" },
+ { 0x08, "medium distance (M)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_TECH_MASK 0xf003
+static sff_pair_t sff_8636_comp_tech[] = {
+ { 0x2, "Longwave laser (LC)" },
+ { 0x1, "Electrical inter-enclosure (EL)" },
+ { 0x8000, "Electrical intra-enclosure (EL)" },
+ { 0x4000, "Shortwave laser w/o OFC (SN)" },
+ { 0x2000, "Shortwave laser with OFC (SL)" },
+ { 0x1000, "Longwave laser (LL)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_MEDIA_MASK 0xff
+static sff_pair_t sff_8636_comp_media[] = {
+ { 0x80, "Twin Axial Pair (TW)" },
+ { 0x40, "Twisted Pair (TP)" },
+ { 0x20, "Miniature Coax (MI)" },
+ { 0x10, "Video Coax (TV)" },
+ { 0x08, "Multimode, 62.5um (M6)" },
+ { 0x04, "Multimode, 50m (M5)" },
+ { 0x02, "Multimode, 50um (OM3)" },
+ { 0x01, "Single Mode (SM)" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-17.
+ */
+#define SFF_8636_COMP_SPEED_MASK 0xfd
+static sff_pair_t sff_8636_comp_speed[] = {
+ { 0x80, "1200 MBytes/sec" },
+ { 0x40, "800 MBytes/sec" },
+ { 0x20, "1600 MBytes/sec" },
+ { 0x10, "400 MBytes/sec" },
+ { 0x08, "3200 MBytes/sec" },
+ { 0x04, "200 MBytes/sec" },
+ { 0x01, "100 MBytes/sec" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-20.
+ */
+static const char *sff_8636_trans_tech[] = {
+ "850 nm VCSEL",
+ "1310 nm VCSEL",
+ "1550 nm VCSEL",
+ "1310 nm FP",
+ "1310 nm DFB",
+ "1550 nm DFB",
+ "1310 nm EML",
+ "1550 nm EML",
+ "Other / Undefined",
+ "1490 nm DFB",
+ "Copper cable unequalized",
+ "Copper cable passive equalized",
+ "Copper cable, near and far end limiting active equalizers",
+ "Copper cable, far end limiting active equalizers",
+ "Copper cable, near end limiting active equalizers",
+ "Copper cable, linear active equalizers"
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-21.
+ */
+#define SFF_8636_EXTMOD_CODES 0x1f
+static sff_pair_t sff_8636_extmod_codes[] = {
+ { 0x10, "EDR" },
+ { 0x08, "FDR" },
+ { 0x04, "QDR" },
+ { 0x02, "DDR" },
+ { 0x01, "SDR" },
+ { 0x00, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-22. This combines bytes 193-195.
+ * We treat byte 193 as the most significant.
+ */
+#define SFF_8636_OPTION_MASK 0x0ffffe
+static sff_pair_t sff_8636_options[] = {
+ { 0x080000, "TX Input Equalization Auto Adaptive Capable" },
+ { 0x040000, "TX Input Equalization Fixed Programmable" },
+ { 0x020000, "RX Output Emphasis Fixed Programmable Settings" },
+ { 0x010000, "RX Output Amplitude Fixed Programmable Settings" },
+ { 0x008000, "TX CDR On/Off Control implemented" },
+ { 0x004000, "RX CDR On/Off Control implemented" },
+ { 0x002000, "Tx CDR Loss of Lock Flag implemented" },
+ { 0x001000, "Rx CDR Loss of Lock Flag implemented" },
+ { 0x000800, "Rx Squelch Disable implemented" },
+ { 0x000400, "Rx Output Disable capable" },
+ { 0x000200, "Tx Squelch Disable implemented" },
+ { 0x000100, "Tx Squelch implemented" },
+ { 0x000080, "Memory page 02h provided" },
+ { 0x000040, "Memory page 01h provided" },
+ { 0x000020, "Rate Select implemented" },
+ { 0x000010, "Tx_DISABLE implemented" },
+ { 0x000008, "Tx_FAULT implemented" },
+ { 0x000004, "Tx Squelch for Pave" },
+ { 0x000002, "Tx Loss of Signal implemented" },
+ { 0x0, NULL }
+};
+
+/*
+ * This is derived from SFF 8636 r2.7 Table 6-25.
+ */
+#define SFF_8636_ENHANCED_OPTIONS_MASK 0x1c
+static sff_pair_t sff_8636_eopt[] = {
+ { 0x10, "Initialization Complete Flag Implemented" },
+ { 0x08, "Extended Rate Selection Supported" },
+ { 0x04, "Application Select Table Supported" },
+ { 0x0, NULL }
+};
+
+static const char *
+sff_pair_find(uint_t val, sff_pair_t *pairs)
+{
+ while (pairs->sp_name != NULL) {
+ if (val == pairs->sp_val)
+ return (pairs->sp_name);
+ pairs++;
+ }
+
+ return (NULL);
+}
+
+static int
+sff_parse_id(uint8_t id, nvlist_t *nvl)
+{
+ const char *val;
+
+ if (id >= SFF_8024_VENDOR) {
+ val = "Vendor Specific";
+ } else if (id >= SFF_8024_NIDS) {
+ val = "Reserved";
+ } else {
+ val = sff_8024_id_strs[id];
+ }
+
+ return (nvlist_add_string(nvl, LIBSFF_KEY_IDENTIFIER, val));
+}
+
+static int
+sff_add_unit_string(uint64_t val, uint64_t factor, const char *unit,
+ nvlist_t *nvl, const char *key)
+{
+ char str[SFP_STRBUF];
+
+ val *= factor;
+ (void) snprintf(str, sizeof (str), "%" PRIu64 " %s", val, unit);
+ return (nvlist_add_string(nvl, key, str));
+}
+
+static int
+sff_parse_connector(uint8_t con, nvlist_t *nvl)
+{
+ const char *val;
+
+ if (con >= 0x80) {
+ val = "Vendor Specific";
+ } else {
+ if ((val = sff_pair_find(con, sff_8024_connectors)) == NULL)
+ val = "Reserved";
+ }
+
+ return (nvlist_add_string(nvl, LIBSFF_KEY_CONNECTOR, val));
+}
+
+/*
+ * Many of the values in the specifications are bitfields of which one or more
+ * bits may be set. We represent that as an array of strings. One entry will be
+ * added for each set bit that's found in pairs.
+ */
+static int
+sff_gather_bitfield(uint32_t value, const char *name, sff_pair_t *pairs,
+ nvlist_t *nvl)
+{
+ uint32_t i;
+ const char *vals[32];
+ uint_t count;
+
+ count = 0;
+ for (i = 0; i < 32; i++) {
+ uint32_t bit;
+ const char *str;
+
+ bit = 1 << i;
+ if ((bit & value) == 0)
+ continue;
+
+ str = sff_pair_find(bit, pairs);
+ if (str != NULL) {
+ vals[count++] = str;
+ }
+ }
+
+ if (count == 0)
+ return (0);
+
+ /*
+ * The nvlist routines don't touch the array, so we end up lying about
+ * the type of data so that we can avoid a rash of additional
+ * allocations and strdups.
+ */
+ return (nvlist_add_string_array(nvl, name, (char **)vals, count));
+}
+
+static int
+sff_parse_compliance(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+ uint16_t v;
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_10GE] &
+ SFF_8472_COMP_10GETH_MASK, LIBSFF_KEY_COMPLIANCE_10GBE,
+ sff_8472_comp_10geth, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_IB] &
+ SFF_8472_COMP_IB_MASK, LIBSFF_KEY_COMPLIANCE_IB,
+ sff_8472_comp_ib, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_ESCON] &
+ SFF_8472_COMP_ESCON_MASK, LIBSFF_KEY_COMPLIANCE_ESCON,
+ sff_8472_comp_escon, nvl)) != 0)
+ return (ret);
+
+ v = buf[SFF_8472_COMPLIANCE_SONET_LOW] |
+ (buf[SFF_8472_COMPLIANCE_SONET_HIGH] << 8);
+ if ((ret = sff_gather_bitfield(v & SFF_8472_COMP_SOCON_MASK,
+ LIBSFF_KEY_COMPLIANCE_SONET, sff_8472_comp_sonet, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_ETHERNET] &
+ SFF_8472_COMP_ETH_MASK, LIBSFF_KEY_COMPLIANCE_GBE,
+ sff_8472_comp_eth, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_FCLEN] &
+ SFF_8472_COMP_FCLEN_MASK, LIBSFF_KEY_COMPLIANCE_FC_LEN,
+ sff_8472_comp_fclen, nvl)) != 0)
+ return (ret);
+
+ v = buf[SFF_8472_COMPLIANCE_FC_LOW] |
+ (buf[SFF_8472_COMPLIANCE_FC_HIGH] << 8);
+ if ((ret = sff_gather_bitfield(v & SFF_8472_COMP_TECH_MASK,
+ LIBSFF_KEY_COMPLIANCE_FC_TECH, sff_8472_comp_tech, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_SFP] &
+ SFF_8472_COMP_CABLE_MASK, LIBSFF_KEY_COMPLIANCE_SFP,
+ sff_8472_comp_cable, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_FC_MEDIA] &
+ SFF_8472_COMP_MEDIA_MASK, LIBSFF_KEY_COMPLIANCE_FC_MEDIA,
+ sff_8472_comp_media, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_COMPLIANCE_FC_SPEED] &
+ SFF_8472_COMP_SPEED_MASK, LIBSFF_KEY_COMPLIANCE_FC_SPEED,
+ sff_8472_comp_speed, nvl)) != 0)
+ return (ret);
+
+ return (0);
+}
+
+static int
+sff_parse_encoding(uint8_t val, nvlist_t *nvl, boolean_t sfp)
+{
+ const char *str;
+ if (val >= SFF_8024_NENCS) {
+ str = "Reserved";
+ } else if (sfp) {
+ str = sff_8024_enc_sfp[val];
+ } else {
+ str = sff_8024_enc_qsfp[val];
+ }
+
+ return (nvlist_add_string(nvl, LIBSFF_KEY_ENCODING, str));
+}
+
+static int
+sff_parse_br(const uint8_t *buf, nvlist_t *nvl)
+{
+ if (buf[SFF_8472_BR_NOMINAL] == 0xff) {
+ int ret;
+ if ((ret = sff_add_unit_string(buf[SFF_8472_BR_MAX],
+ SFF_8472_BR_MAX_FACTOR, "MBd", nvl,
+ LIBSFF_KEY_BR_MAX)) != 0)
+ return (ret);
+ return (sff_add_unit_string(buf[SFF_8472_BR_MIN],
+ SFF_8472_BR_MIN_FACTOR, "MBd", nvl, LIBSFF_KEY_BR_MIN));
+ } else {
+ return (sff_add_unit_string(buf[SFF_8472_BR_NOMINAL],
+ SFF_8472_BR_NOMINAL_FACTOR, "MBd", nvl,
+ LIBSFF_KEY_BR_NOMINAL));
+ }
+}
+
+static int
+sff_parse_lengths(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+
+ if (buf[SFF_8472_LENGTH_SMF_KM] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8472_LENGTH_SMF_KM],
+ SFF_8472_LENGTH_SMF_KM_FACTOR, "km", nvl,
+ LIBSFF_KEY_LENGTH_SMF_KM)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8472_LENGTH_SMF] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8472_LENGTH_SMF],
+ SFF_8472_LENGTH_SMF_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_SMF)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8472_LENGTH_50UM] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8472_LENGTH_50UM],
+ SFF_8472_LENGTH_50UM_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_OM2)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8472_LENGTH_62UM] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8472_LENGTH_62UM],
+ SFF_8472_LENGTH_62UM_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_OM1)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8472_LENGTH_COPPER] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8472_LENGTH_COPPER],
+ SFF_8472_LENGTH_COPPER_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_COPPER)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8472_LENGTH_OM3] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8472_LENGTH_OM3],
+ SFF_8472_LENGTH_OM3_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_OM3)) != 0)
+ return (ret);
+ }
+
+ return (0);
+}
+
+/*
+ * Strings in the SFF specification are written into fixed sized buffers. The
+ * strings are padded to the right with spaces (ASCII 0x20) and there is no NUL
+ * character like in a standard C string. While the string is padded with
+ * spaces, spaces may appear in the middle of the string and should not be
+ * confused as padding.
+ */
+static int
+sff_parse_string(const uint8_t *buf, uint_t start, uint_t len,
+ const char *field, nvlist_t *nvl)
+{
+ uint_t i;
+ char strbuf[SFP_STRBUF];
+
+ assert(len < sizeof (strbuf));
+ strbuf[0] = '\0';
+ while (len > 0) {
+ if (buf[start + len - 1] != ' ')
+ break;
+ len--;
+ }
+ if (len == 0)
+ return (0);
+
+ /*
+ * This is supposed to be 7-bit printable ASCII. If we find any
+ * characters that aren't, don't include this string.
+ */
+ for (i = 0; i < len; i++) {
+ if (isascii(buf[start + i]) == 0 ||
+ isprint(buf[start + i]) == 0) {
+ return (0);
+ }
+ }
+ bcopy(&buf[start], strbuf, len);
+ strbuf[len] = '\0';
+
+ return (nvlist_add_string(nvl, field, strbuf));
+}
+
+static int
+sff_parse_optical(const uint8_t *buf, nvlist_t *nvl)
+{
+ /*
+ * The value in byte 8 determines whether we interpret this as
+ * describing aspects of a copper device or if it describes the
+ * wavelength.
+ */
+ if (buf[SFF_8472_COMPLIANCE_SFP] & SFF_8472_COMP_CABLE_PASSIVE) {
+ return (sff_gather_bitfield(buf[SFF_8472_PASSIVE_SPEC] &
+ SFF_8472_PCABLE_COMP_MASK, LIBSFF_KEY_COMPLIANCE_PASSIVE,
+ sff_8472_pcable_comp, nvl));
+ } else if (buf[SFF_8472_COMPLIANCE_SFP] & SFF_8472_COMP_CABLE_ACTIVE) {
+ return (sff_gather_bitfield(buf[SFF_8472_ACTIVE_SPEC] &
+ SFF_8472_ACABLE_COMP_MASK, LIBSFF_KEY_COMPLIANCE_ACTIVE,
+ sff_8472_acable_comp, nvl));
+
+ } else {
+ uint16_t val = (buf[SFF_8472_WAVELENGTH_HI] << 8) |
+ buf[SFF_8472_WAVELENGTH_LOW];
+
+ return (sff_add_unit_string(val, SFF_8472_WAVELENGTH_FACTOR,
+ "nm", nvl, LIBSFF_KEY_WAVELENGTH));
+ }
+}
+
+static int
+sff_parse_options(const uint8_t *buf, nvlist_t *nvl)
+{
+ uint16_t val;
+
+ val = (buf[SFF_8472_OPTIONS_HI] << 8) | buf[SFF_8472_OPTIONS_LOW];
+ return (sff_gather_bitfield(val & SFF_8472_OPTION_MASK,
+ LIBSFF_KEY_OPTIONS, sff_8472_options, nvl));
+}
+
+static int
+sff_parse_8472_comp(uint8_t val, nvlist_t *nvl)
+{
+ const char *str;
+
+ if (val >= SFF_8472_8472_COMP_NENTRIES) {
+ str = "Unallocated";
+ } else {
+ str = sff_8472_8472_comp[val];
+ }
+
+ return (nvlist_add_string(nvl, LIBSFF_KEY_COMPLIANCE_8472, str));
+}
+
+/*
+ * Parse an SFP that is either based on INF 8074 or SFF 8472. These are GBIC,
+ * SFP, SFP+, and SFP28 based devices.
+ *
+ * The SFP parsing into an nvlist_t is incomplete. At the moment we're not
+ * parsing the following pieces from SFF 8472 page 0xa0:
+ *
+ * o Rate Selection Logic
+ * o Diagnostic Monitoring Type
+ */
+static int
+sff_parse_sfp(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+
+ if ((ret = sff_parse_id(buf[SFF_8472_IDENTIFIER], nvl)) != 0)
+ return (ret);
+
+ /*
+ * The extended identifier is derived from SFF 8472, Table 5-2. It
+ * generally is just the value 4. The other values are not well defined.
+ */
+ if ((ret = nvlist_add_uint8(nvl, LIBSFF_KEY_8472_EXT_IDENTIFIER,
+ buf[SFF_8472_EXT_IDENTIFER])) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_connector(buf[SFF_8472_CONNECTOR], nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_compliance(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_encoding(buf[SFF_8472_ENCODING], nvl,
+ B_TRUE)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_br(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_lengths(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8472_VENDOR, SFF_8472_VENDOR_LEN,
+ LIBSFF_KEY_VENDOR, nvl)) != 0)
+ return (ret);
+
+ if ((ret = nvlist_add_byte_array(nvl, LIBSFF_KEY_OUI,
+ (uchar_t *)&buf[SFF_8472_OUI], SFF_8472_OUI_LEN)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8472_VENDOR_PN,
+ SFF_8472_VENDOR_PN_LEN, LIBSFF_KEY_PART, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8472_VENDOR_REV,
+ SFF_8472_VENDOR_REV_LEN, LIBSFF_KEY_REVISION, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_optical(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_options(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8472_VENDOR_SN,
+ SFF_8472_VENDOR_SN_LEN, LIBSFF_KEY_SERIAL, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8472_DATE_CODE,
+ SFF_8472_DATE_CODE_LEN, LIBSFF_KEY_DATECODE, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8472_ENHANCED_OPTIONS] &
+ SFF_8472_EXTOPT_MASK, LIBSFF_KEY_EXTENDED_OPTIONS,
+ sff_8472_extopts, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_8472_comp(buf[SFF_8472_SFF_8472_COMPLIANCE],
+ nvl)) != 0)
+ return (ret);
+
+ return (0);
+}
+
+static int
+sff_qsfp_parse_compliance(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+ uint16_t fc_val;
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_10GBEP] &
+ SFF_8636_COMP_10GETH_MASK, LIBSFF_KEY_COMPLIANCE_10GBE,
+ sff_8636_comp_10geth, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_SONET] &
+ SFF_8636_COMP_SONET_MASK, LIBSFF_KEY_COMPLIANCE_SONET,
+ sff_8636_comp_sonet, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_SAS] &
+ SFF_8636_COMP_SAS_MASK, LIBSFF_KEY_COMPLIANCE_SAS,
+ sff_8636_comp_sas, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_ETHERNET] &
+ SFF_8636_COMP_ETH_MASK, LIBSFF_KEY_COMPLIANCE_GBE,
+ sff_8636_comp_eth, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_FCLEN] &
+ SFF_8636_COMP_FCLEN_MASK, LIBSFF_KEY_COMPLIANCE_FC_LEN,
+ sff_8636_comp_fclen, nvl)) != 0)
+ return (ret);
+
+ fc_val = buf[SFF_8636_COMPLIANCE_FC_LOW] |
+ (buf[SFF_8636_COMPLIANCE_FC_HIGH] << 8);
+ if ((ret = sff_gather_bitfield(fc_val & SFF_8636_COMP_TECH_MASK,
+ LIBSFF_KEY_COMPLIANCE_FC_TECH, sff_8636_comp_tech, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_FC_MEDIA] &
+ SFF_8636_COMP_MEDIA_MASK, LIBSFF_KEY_COMPLIANCE_FC_MEDIA,
+ sff_8636_comp_media, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_COMPLIANCE_FC_SPEED] &
+ SFF_8636_COMP_SPEED_MASK, LIBSFF_KEY_COMPLIANCE_FC_SPEED,
+ sff_8636_comp_speed, nvl)) != 0)
+ return (ret);
+
+ return (0);
+}
+
+static int
+sff_qsfp_parse_br(const uint8_t *buf, nvlist_t *nvl)
+{
+ if (buf[SFF_8636_BR_NOMINAL] == 0xff) {
+ return (sff_add_unit_string(buf[SFF_8636_BR_NOMINAL_EXT],
+ SFF_8636_BR_NOMINAL_EXT_FACTOR, "Mbps", nvl,
+ LIBSFF_KEY_BR_NOMINAL));
+ } else {
+ return (sff_add_unit_string(buf[SFF_8636_BR_NOMINAL],
+ SFF_8636_BR_NOMINAL_FACTOR, "Mbps", nvl,
+ LIBSFF_KEY_BR_NOMINAL));
+ }
+}
+
+static int
+sff_qsfp_parse_lengths(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+
+ if (buf[SFF_8636_LENGTH_SMF] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8636_LENGTH_SMF],
+ SFF_8636_LENGTH_SMF_FACTOR, "km", nvl,
+ LIBSFF_KEY_LENGTH_SMF_KM)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8636_LENGTH_OM3] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8636_LENGTH_OM3],
+ SFF_8636_LENGTH_OM3_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_OM3)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8636_LENGTH_OM2] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8636_LENGTH_OM2],
+ SFF_8636_LENGTH_OM2_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_OM2)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8636_LENGTH_OM1] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8636_LENGTH_OM1],
+ SFF_8636_LENGTH_OM1_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_OM1)) != 0)
+ return (ret);
+ }
+
+ if (buf[SFF_8636_LENGTH_COPPER] != 0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8636_LENGTH_COPPER],
+ SFF_8636_LENGTH_COPPER_FACTOR, "m", nvl,
+ LIBSFF_KEY_LENGTH_COPPER)) != 0)
+ return (ret);
+ }
+
+ return (0);
+}
+
+static int
+sff_qsfp_parse_tech(uint8_t val, nvlist_t *nvl)
+{
+ const char *strs[5];
+
+ strs[0] = sff_8636_trans_tech[(val & 0xf0) >> 4];
+ if (val & 0x08) {
+ strs[1] = "Active Wavelength Control";
+ } else {
+ strs[1] = "No Wavelength Control";
+ }
+
+ if (val & 0x04) {
+ strs[2] = "Cooled Transmitter";
+ } else {
+ strs[2] = "Uncooled Transmitter";
+ }
+
+ if (val & 0x02) {
+ strs[3] = "APD Detector";
+ } else {
+ strs[3] = "Pin Detector";
+ }
+
+ if (val & 0x01) {
+ strs[4] = "Transmitter Tunable";
+ } else {
+ strs[4] = "Transmitter Not Tunable";
+ }
+
+ /*
+ * The nvlist routines don't touch the array, so we end up lying about
+ * the type of data so that we can avoid a rash of additional
+ * allocations and strdups.
+ */
+ return (nvlist_add_string_array(nvl, LIBSFF_KEY_TRAN_TECH,
+ (char **)strs, 5));
+}
+
+static int
+sff_qsfp_parse_copperwave(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+
+ /*
+ * The values that we get depend on whether or not we are a copper
+ * device or not. We can determine this based on the identification
+ * information in the device technology field.
+ */
+ if ((buf[SFF_8636_DEVICE_TECH] & 0xf0) >= 0xa0) {
+ if ((ret = sff_add_unit_string(buf[SFF_8636_ATTENUATE_2G], 1,
+ "dB", nvl, LIBSFF_KEY_ATTENUATE_2G)) != 0)
+ return (ret);
+ if ((ret = sff_add_unit_string(buf[SFF_8636_ATTENUATE_5G], 1,
+ "dB", nvl, LIBSFF_KEY_ATTENUATE_5G)) != 0)
+ return (ret);
+ if ((ret = sff_add_unit_string(buf[SFF_8636_ATTENUATE_7G], 1,
+ "dB", nvl, LIBSFF_KEY_ATTENUATE_7G)) != 0)
+ return (ret);
+ if ((ret = sff_add_unit_string(buf[SFF_8636_ATTENUATE_12G], 1,
+ "dB", nvl, LIBSFF_KEY_ATTENUATE_12G)) != 0)
+ return (ret);
+ } else {
+ uint16_t val;
+ double d;
+ char strbuf[SFP_STRBUF];
+
+ /*
+ * Because we need to divide the units here into doubles, we
+ * can't use the standard unit routine.
+ */
+ val = (buf[SFF_8636_WAVELENGTH_NOMINAL_HI] << 8) |
+ buf[SFF_8636_WAVELENGTH_NOMINAL_LOW];
+ if (val != 0) {
+ d = val / 20.0;
+ (void) snprintf(strbuf, sizeof (strbuf), "%.3lf nm", d);
+ if ((ret = nvlist_add_string(nvl, LIBSFF_KEY_WAVELENGTH,
+ strbuf)) != 0)
+ return (ret);
+ }
+
+ val = (buf[SFF_8636_WAVELENGTH_TOLERANCE_HI] << 8) |
+ buf[SFF_8636_WAVELENGTH_TOLERANCE_LOW];
+ if (val != 0) {
+ d = val / 20.0;
+ (void) snprintf(strbuf, sizeof (strbuf), "%.3lf nm", d);
+ if ((ret = nvlist_add_string(nvl,
+ LIBSFF_KEY_WAVE_TOLERANCE, strbuf)) != 0)
+ return (ret);
+ }
+ }
+
+ return (0);
+}
+
+static int
+sff_qsfp_parse_casetemp(uint8_t val, nvlist_t *nvl)
+{
+ /*
+ * The default temperature per SFF 8636 r2.7 6.3.21 'Maximum Case
+ * Temperature' is 70 C. If the value is zero, we're supposed to assume
+ * it's the default.
+ */
+ if (val == 0)
+ val = 70;
+
+ return (sff_add_unit_string(val, 1, "C", nvl,
+ LIBSFF_KEY_MAX_CASE_TEMP));
+}
+
+static int
+sff_qsfp_parse_extcomp(uint8_t val, nvlist_t *nvl)
+{
+ const char *str;
+
+ if (val >= SFF_8024_EXT_SPEC_NENTRIES) {
+ str = "Reserved";
+ } else {
+ str = sff_8024_ext_spec[val];
+ }
+
+ return (nvlist_add_string(nvl, LIBSFF_KEY_EXT_SPEC, str));
+}
+
+static int
+sff_qsfp_parse_options(const uint8_t *buf, nvlist_t *nvl)
+{
+ uint_t val;
+
+ val = (buf[SFF_8636_OPTIONS_HI] << 16) |
+ (buf[SFF_8636_OPTIONS_MID] << 8) | buf[SFF_8636_OPTIONS_LOW];
+
+ return (sff_gather_bitfield(val & SFF_8636_OPTION_MASK,
+ LIBSFF_KEY_OPTIONS, sff_8636_options, nvl));
+}
+
+static int
+sff_qsfp_parse_diag(uint8_t val, nvlist_t *nvl)
+{
+ const char *buf[2];
+ uint_t count = 1;
+
+ if (val & 0x08) {
+ buf[0] = "Received power measurements: Average Power";
+ } else {
+ buf[0] = "Received power measurements: OMA";
+ }
+
+ if (val & 0x04) {
+ count++;
+ buf[1] = "Transmitter power measurement";
+ }
+
+ /*
+ * The nvlist routines don't touch the array, so we end up lying about
+ * the type of data so that we can avoid a rash of additional
+ * allocations and strdups.
+ */
+ return (nvlist_add_string_array(nvl, LIBSFF_KEY_DIAG_MONITOR,
+ (char **)buf, count));
+}
+
+/*
+ * Parse a QSFP family device that is based on SFF-8436 / SFF-8636. Note that we
+ * ignore the lower half of page 0xa0 at this time and instead focus on the
+ * upper half of page 0xa0 which has identification information.
+ *
+ * For the moment we're not parsing the following fields:
+ *
+ * o Extended Identifier (byte 129)
+ * o Extended Rate Select Compliance (byte 141)
+ */
+static int
+sff_parse_qsfp(const uint8_t *buf, nvlist_t *nvl)
+{
+ int ret;
+
+ if ((ret = sff_parse_id(buf[SFF_8636_IDENTIFIER], nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_connector(buf[SFF_8636_CONNECTOR], nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_compliance(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_encoding(buf[SFF_8636_ENCODING], nvl,
+ B_FALSE)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_br(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_lengths(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_tech(buf[SFF_8636_DEVICE_TECH], nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8636_VENDOR, SFF_8636_VENDOR_LEN,
+ LIBSFF_KEY_VENDOR, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_EXTENDED_MODULE] &
+ SFF_8636_EXTMOD_CODES, LIBSFF_KEY_EXT_MOD_CODES,
+ sff_8636_extmod_codes, nvl)) != 0)
+ return (ret);
+
+ if ((ret = nvlist_add_byte_array(nvl, LIBSFF_KEY_OUI,
+ (uchar_t *)&buf[SFF_8636_OUI], SFF_8636_OUI_LEN)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8636_VENDOR_PN,
+ SFF_8636_VENDOR_PN_LEN, LIBSFF_KEY_PART, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8636_VENDOR_REV,
+ SFF_8636_VENDOR_REV_LEN, LIBSFF_KEY_REVISION, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_copperwave(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_casetemp(buf[SFF_8636_MAX_CASE_TEMP],
+ nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_extcomp(buf[SFF_8636_LINK_CODES], nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_options(buf, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8636_VENDOR_SN,
+ SFF_8636_VENDOR_SN_LEN, LIBSFF_KEY_SERIAL, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_parse_string(buf, SFF_8636_DATE_CODE,
+ SFF_8636_DATE_CODE_LEN, LIBSFF_KEY_DATECODE, nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_qsfp_parse_diag(buf[SFF_8636_DIAG_MONITORING],
+ nvl)) != 0)
+ return (ret);
+
+ if ((ret = sff_gather_bitfield(buf[SFF_8636_ENHANCED_OPTIONS] &
+ SFF_8636_ENHANCED_OPTIONS_MASK, LIBSFF_KEY_ENHANCED_OPTIONS,
+ sff_8636_eopt, nvl)) != 0)
+ return (ret);
+
+ return (0);
+}
+
+int
+libsff_parse(const uint8_t *buf, size_t len, uint_t page, nvlist_t **nvpp)
+{
+ int ret;
+ nvlist_t *nvp = NULL;
+ uint8_t ubuf[256];
+
+ /*
+ * At the moment, we only support page a0.
+ */
+ if (page != 0xa0 || buf == NULL || len == 0 || nvpp == NULL)
+ return (EINVAL);
+
+ *nvpp = NULL;
+
+ /*
+ * Make sure that the library has been given valid data to parse.
+ */
+ if (uucopy(buf, ubuf, MIN(sizeof (ubuf), len)) != 0)
+ return (errno);
+
+ if ((ret = nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0)) != 0)
+ return (ret);
+
+ switch (buf[0]) {
+ case SFF_8024_ID_QSFP:
+ case SFF_8024_ID_QSFP_PLUS:
+ case SFF_8024_ID_QSFP28:
+ /*
+ * For QSFP based products, identification information is spread
+ * across both the top and bottom half of page 0xa0.
+ */
+ if (len < SFP_MIN_LEN_8636) {
+ ret = EINVAL;
+ break;
+ }
+ ret = sff_parse_qsfp(ubuf, nvp);
+ break;
+ default:
+ if (len < SFP_MIN_LEN_8472) {
+ ret = EINVAL;
+ break;
+ }
+ ret = sff_parse_sfp(ubuf, nvp);
+ break;
+ }
+
+ if (ret != 0) {
+ nvlist_free(nvp);
+ } else {
+ *nvpp = nvp;
+ }
+ return (ret);
+}