this adds support to ifconfig for reading info from transceivers. it looks like this:
dlg@ix ifconfig$ sudo ./obj/ifconfig ix0 transceiver ix0: identifier SFP (03) connector: Copper Pigtail (21) vendor: Amphenol product: 616740001 revision: B serial: CN0V250M36J0T86 date: 2013-07-04 dlg@ix ifconfig$ sudo ./obj/ifconfig ix1 transceiver ix1: identifier SFP (03) connector: LC (07) vendor: FINISAR CORP. product: FTLX8571D3BCL-FC revision: A serial: AQG28W3 date: 2013-10-19 temperature: 34.60 C vcc: 3.3553 V tx-bias: 7986.0 uA tx-power: 0.6128 mW rx-power: 0.6153 mW average dlg@ix ifconfig$ sudo ./obj/ifconfig ixl0 transceiver ixl0: identifier QSFP+ (0d) this is all specified by the SFF (small formfactor) group in SNIA, but it is a lot of disparate documentation to get into your head. the top level summary is that sfp modules have an i2c bus wired up to them, and answer reads at device address 0xa0. there is a 256 byte page at that address with information like the type of module, and depending on the type you can find the manufacturer, product name, serial number, and so on. a later spec added support a "digital diagnostics monitoring" (DDM) or "digital optical monitoring" (DOM) capability where there's live status/diag information available at i2c address 0xa2. again, it's a 256 byte page, but the values change all the time based on what the module is doing. this is where the temperature and laser power stuff is. ive implemented basic support for the above, which is specific to some sfp shaped modules (so sfp+ and sfp28 too) and gbics. devices report whether they support the diag page, so it only fetches and parses that if page 0 on 0xa0 says it can. there are different specs for the other types of modules, in particular qsfp and related modules have a very different layout. however, they still use the same device addresses and pages, it's just that the contents of the page vary. support for qsfp will be forthcoming if this goes ahead. dumping more info generally will happen as time and interest permits too. i've only implemented the kernel backend for this on ix and ixl. ixl support is patchy because it relies on a command that only exists in high API versions (like 1.7). ix seems pretty consistent. other nics can grow support as time and hw availability permites. i don't have an em(4) with optics, so that might be hard for me to do myself, but i tried to make the kernel side as easy as possible so people should have a good chance at figuring it out. do those power units sound plausible or are the factors off? this was originally requested by rachel roch on misc@ in "Viewing SFP diagnostic data in OpenBSD ?" thoughts? Index: sys/sys/sockio.h =================================================================== RCS file: /cvs/src/sys/sys/sockio.h,v retrieving revision 1.80 diff -u -p -r1.80 sockio.h --- sys/sys/sockio.h 26 Feb 2019 03:19:11 -0000 1.80 +++ sys/sys/sockio.h 8 Apr 2019 02:05:36 -0000 @@ -68,6 +68,7 @@ /* 53 and 54 used to be SIOC[SG]IFMEDIA with a 32 bit media word */ #define SIOCSIFMEDIA _IOWR('i', 55, struct ifreq) /* set net media */ #define SIOCGIFMEDIA _IOWR('i', 56, struct ifmediareq) /* get net media */ +#define SIOCGIFSFFPAGE _IOWR('i', 57, struct if_sffpage) /* get SFF page */ #define SIOCDIFPHYADDR _IOW('i', 73, struct ifreq) /* delete gif addrs */ #define SIOCSLIFPHYADDR _IOW('i', 74, struct if_laddrreq) /* set gif addrs */ Index: sys/net/if.c =================================================================== RCS file: /cvs/src/sys/net/if.c,v retrieving revision 1.573 diff -u -p -r1.573 if.c --- sys/net/if.c 1 Mar 2019 04:47:32 -0000 1.573 +++ sys/net/if.c 8 Apr 2019 02:05:36 -0000 @@ -144,6 +144,8 @@ int if_detached_ioctl(struct ifnet *, u_ int ifioctl_get(u_long, caddr_t); int ifconf(caddr_t); +static int + if_sffpage_check(const caddr_t); int if_getgroup(caddr_t, struct ifnet *); int if_getgroupmembers(caddr_t); @@ -2143,6 +2172,19 @@ ifioctl(struct socket *so, u_long cmd, c NET_UNLOCK(); break; + case SIOCGIFSFFPAGE: + error = suser(p); + if (error != 0) + break; + + error = if_sffpage_check(data); + if (error != 0) + break; + + /* don't take NET_LOCK because i2c reads take a long time */ + error = ((*ifp->if_ioctl)(ifp, cmd, data)); + break; + case SIOCSETKALIVE: case SIOCDIFPHYADDR: case SIOCSLIFPHYADDR: @@ -2304,6 +2346,22 @@ ifioctl_get(u_long cmd, caddr_t data) return (error); } +static int +if_sffpage_check(const caddr_t data) +{ + const struct if_sffpage *sff = (const struct if_sffpage *)data; + + switch (sff->sff_addr) { + case IFSFF_ADDR_EEPROM: + case IFSFF_ADDR_DDM: + break; + default: + return (EINVAL); + } + + return (0); +} + /* * Return interface configuration * of system. List may be used Index: sys/net/if.h =================================================================== RCS file: /cvs/src/sys/net/if.h,v retrieving revision 1.199 diff -u -p -r1.199 if.h --- sys/net/if.h 23 Jan 2019 08:23:18 -0000 1.199 +++ sys/net/if.h 8 Apr 2019 02:05:36 -0000 @@ -501,6 +501,20 @@ struct if_parent { char ifp_parent[IFNAMSIZ]; }; +/* SIOCGIFSFFPAGE */ + +#define IFSFF_ADDR_EEPROM 0xa0 +#define IFSFF_ADDR_DDM 0xa2 + +#define IFSFF_DATA_LEN 256 + +struct if_sffpage { + char sff_ifname[IFNAMSIZ]; /* u -> k */ + uint8_t sff_addr; /* u -> k */ + uint8_t sff_page; /* u -> k */ + uint8_t sff_data[256]; /* k -> u */ +}; + #include <net/if_arp.h> #ifdef _KERNEL Index: sys/dev/pci/if_ix.c =================================================================== RCS file: /cvs/src/sys/dev/pci/if_ix.c,v retrieving revision 1.156 diff -u -p -r1.156 if_ix.c --- sys/dev/pci/if_ix.c 1 Mar 2019 06:15:59 -0000 1.156 +++ sys/dev/pci/if_ix.c 8 Apr 2019 02:05:36 -0000 @@ -96,6 +96,7 @@ int ixgbe_detach(struct device *, int); void ixgbe_start(struct ifqueue *); int ixgbe_ioctl(struct ifnet *, u_long, caddr_t); int ixgbe_rxrinfo(struct ix_softc *, struct if_rxrinfo *); +int ixgbe_get_sffpage(struct ix_softc *, struct if_sffpage *); void ixgbe_watchdog(struct ifnet *); void ixgbe_init(void *); void ixgbe_stop(void *); @@ -225,6 +226,8 @@ ixgbe_attach(struct device *parent, stru sc->osdep.os_sc = sc; sc->osdep.os_pa = *pa; + rw_init(&sc->sfflock, "ixsff"); + /* Set up the timer callout */ timeout_set(&sc->timer, ixgbe_local_timer, sc); timeout_set(&sc->rx_refill, ixgbe_rxrefill, sc); @@ -498,6 +506,15 @@ ixgbe_ioctl(struct ifnet * ifp, u_long c error = ixgbe_rxrinfo(sc, (struct if_rxrinfo *)ifr->ifr_data); break; + case SIOCGIFSFFPAGE: + error = rw_enter(&sc->sfflock, RW_WRITE|RW_INTR); + if (error != 0) + break; + + error = ixgbe_get_sffpage(sc, (struct if_sffpage *)data); + rw_exit(&sc->sfflock); + break; + default: error = ether_ioctl(ifp, &sc->arpcom, command, data); } @@ -516,6 +533,50 @@ ixgbe_ioctl(struct ifnet * ifp, u_long c } int +ixgbe_get_sffpage(struct ix_softc *sc, struct if_sffpage *sff) +{ + struct ixgbe_hw *hw = &sc->hw; + uint32_t swfw_mask = hw->phy.phy_semaphore_mask; + uint8_t page; + size_t i; + int error = EIO; + + if (hw->phy.type == ixgbe_phy_fw) + return (ENODEV); + + if (hw->mac.ops.acquire_swfw_sync(hw, swfw_mask)) + return (EBUSY); /* XXX */ + + if (sff->sff_addr == IFSFF_ADDR_EEPROM) { + if (hw->phy.ops.read_i2c_byte_unlocked(hw, 127, + IFSFF_ADDR_EEPROM, &page)) + goto error; + if (page != sff->sff_page && + hw->phy.ops.write_i2c_byte_unlocked(hw, 127, + IFSFF_ADDR_EEPROM, sff->sff_page)) + goto error; + } + + for (i = 0; i < sizeof(sff->sff_data); i++) { + if (hw->phy.ops.read_i2c_byte_unlocked(hw, i, + sff->sff_addr, &sff->sff_data[i])) + goto error; + } + + if (sff->sff_addr == IFSFF_ADDR_EEPROM) { + if (page != sff->sff_page && + hw->phy.ops.write_i2c_byte_unlocked(hw, 127, + IFSFF_ADDR_EEPROM, page)) + goto error; + } + + error = 0; +error: + hw->mac.ops.release_swfw_sync(hw, swfw_mask); + return (error); +} + +int ixgbe_rxrinfo(struct ix_softc *sc, struct if_rxrinfo *ifri) { struct if_rxring_info *ifr, ifr1; Index: sys/dev/pci/ixgbe_type.h =================================================================== RCS file: /cvs/src/sys/dev/pci/ixgbe_type.h,v retrieving revision 1.31 diff -u -p -r1.31 ixgbe_type.h --- sys/dev/pci/ixgbe_type.h 18 Nov 2016 14:16:10 -0000 1.31 +++ sys/dev/pci/ixgbe_type.h 8 Apr 2019 02:05:36 -0000 @@ -3142,7 +3142,9 @@ enum ixgbe_phy_type { ixgbe_phy_aq, ixgbe_phy_x550em_kr, ixgbe_phy_x550em_kx4, + ixgbe_phy_x550em_xfi, ixgbe_phy_x550em_ext_t, + ixgbe_phy_ext_1g_t, ixgbe_phy_cu_unknown, ixgbe_phy_qt, ixgbe_phy_xaui, @@ -3160,6 +3162,8 @@ enum ixgbe_phy_type { ixgbe_phy_qsfp_intel, ixgbe_phy_qsfp_unknown, ixgbe_phy_sfp_unsupported, /*Enforce bit set with unsupported module*/ + ixgbe_phy_sgmii, + ixgbe_phy_fw, ixgbe_phy_generic }; Index: sys/dev/pci/if_ixl.c =================================================================== RCS file: /cvs/src/sys/dev/pci/if_ixl.c,v retrieving revision 1.34 diff -u -p -r1.34 if_ixl.c --- sys/dev/pci/if_ixl.c 1 Apr 2019 03:01:14 -0000 1.34 +++ sys/dev/pci/if_ixl.c 8 Apr 2019 02:05:36 -0000 @@ -170,6 +170,8 @@ struct ixl_aq_desc { #define IXL_AQ_OP_PHY_RESTART_AN 0x0605 #define IXL_AQ_OP_PHY_LINK_STATUS 0x0607 #define IXL_AQ_OP_PHY_SET_EVENT_MASK 0x0613 +#define IXL_AQ_OP_PHY_SET_REGISTER 0x0628 +#define IXL_AQ_OP_PHY_GET_REGISTER 0x0629 #define IXL_AQ_OP_LLDP_GET_MIB 0x0a00 #define IXL_AQ_OP_LLDP_MIB_CHG_EV 0x0a01 #define IXL_AQ_OP_LLDP_ADD_TLV 0x0a02 @@ -596,6 +598,18 @@ struct ixl_aq_veb_reply { #define IXL_AQ_PHY_REPORT_QUAL (1 << 0) #define IXL_AQ_PHY_REPORT_INIT (1 << 1) +struct ixl_aq_phy_reg_access { + uint8_t phy_iface; +#define IXL_AQ_PHY_IF_INTERNAL 0 +#define IXL_AQ_PHY_IF_EXTERNAL 1 +#define IXL_AQ_PHY_IF_MODULE 2 + uint8_t dev_addr; + uint16_t _reserved1; + uint32_t reg; + uint32_t val; + uint32_t _reserved2; +} __packed __aligned(16); + /* RESTART_AN param[0] */ #define IXL_AQ_PHY_RESTART_AN (1 << 1) #define IXL_AQ_PHY_LINK_ENABLE (1 << 2) @@ -1124,6 +1138,8 @@ struct ixl_softc { struct rwlock sc_cfg_lock; unsigned int sc_dead; + + struct rwlock sc_sff_lock; }; #define DEVNAME(_sc) ((_sc)->sc_dev.dv_xname) @@ -1171,6 +1187,12 @@ static void ixl_arq(void *); static void ixl_hmc_pack(void *, const void *, const struct ixl_hmc_pack *, unsigned int); +static int ixl_get_sffpage(struct ixl_softc *, struct if_sffpage *); +static int ixl_sff_get_byte(struct ixl_softc *, uint8_t, uint32_t, + uint8_t *); +static int ixl_sff_set_byte(struct ixl_softc *, uint8_t, uint32_t, + uint8_t); + static int ixl_match(struct device *, void *, void *); static void ixl_attach(struct device *, struct device *, void *); @@ -1347,6 +1369,8 @@ ixl_aq_dva(struct ixl_aq_desc *iaq, bus_ #define HTOLE16(_x) (_x) #endif +static struct rwlock ixl_sff_lock = RWLOCK_INITIALIZER("ixlsff"); + static const struct pci_matchid ixl_devices[] = { #ifdef notyet { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_XL710_VF }, @@ -1771,6 +1795,15 @@ ixl_ioctl(struct ifnet *ifp, u_long cmd, } break; + case SIOCGIFSFFPAGE: + error = rw_enter(&ixl_sff_lock, RW_WRITE|RW_INTR); + if (error != 0) + break; + + error = ixl_get_sffpage(sc, (struct if_sffpage *)data); + rw_exit(&ixl_sff_lock); + break; + default: error = ether_ioctl(ifp, &sc->sc_ac, cmd, data); break; @@ -3427,6 +3460,115 @@ ixl_get_link_status(struct ixl_softc *sc } sc->sc_ac.ac_if.if_link_state = ixl_set_link_status(sc, &iaq); + + return (0); +} + +static int +ixl_get_sffpage(struct ixl_softc *sc, struct if_sffpage *sff) +{ + uint8_t page; + size_t i; + int error; + + if (sff->sff_addr == IFSFF_ADDR_EEPROM) { + error = ixl_sff_get_byte(sc, IFSFF_ADDR_EEPROM, 127, &page); + if (error != 0) + return (error); + if (page != sff->sff_page) { + error = ixl_sff_set_byte(sc, IFSFF_ADDR_EEPROM, 127, + sff->sff_page); + if (error != 0) + return (error); + } + } + + for (i = 0; i < sizeof(sff->sff_data); i++) { + error = ixl_sff_get_byte(sc, sff->sff_addr, i, + &sff->sff_data[i]); + if (error != 0) + return (error); + } + + if (sff->sff_addr == IFSFF_ADDR_EEPROM) { + if (page != sff->sff_page) { + error = ixl_sff_set_byte(sc, IFSFF_ADDR_EEPROM, 127, + page); + if (error != 0) + return (error); + } + } + + return (0); +} + +static int +ixl_sff_get_byte(struct ixl_softc *sc, uint8_t dev, uint32_t reg, uint8_t *p) +{ + struct ixl_atq iatq; + struct ixl_aq_desc *iaq; + struct ixl_aq_phy_reg_access *param; + + memset(&iatq, 0, sizeof(iatq)); + iaq = &iatq.iatq_desc; + iaq->iaq_opcode = htole16(IXL_AQ_OP_PHY_GET_REGISTER); + param = (struct ixl_aq_phy_reg_access *)iaq->iaq_param; + param->phy_iface = IXL_AQ_PHY_IF_MODULE; + param->dev_addr = dev; + htolem32(¶m->reg, reg); + + ixl_atq_exec(sc, &iatq, "ixlsffget"); + + switch (iaq->iaq_retval) { + case htole16(IXL_AQ_RC_OK): + break; + case htole16(IXL_AQ_RC_EBUSY): + return (EBUSY); + case htole16(IXL_AQ_RC_ESRCH): + return (ENODEV); + case htole16(IXL_AQ_RC_EIO): + case htole16(IXL_AQ_RC_EINVAL): + default: + printf("%s: %u\n", __func__, lemtoh16(&iaq->iaq_retval)); + return (EIO); + } + + *p = lemtoh32(¶m->val); + + return (0); +} + + +static int +ixl_sff_set_byte(struct ixl_softc *sc, uint8_t dev, uint32_t reg, uint8_t v) +{ + struct ixl_atq iatq; + struct ixl_aq_desc *iaq; + struct ixl_aq_phy_reg_access *param; + + memset(&iatq, 0, sizeof(iatq)); + iaq = &iatq.iatq_desc; + iaq->iaq_opcode = htole16(IXL_AQ_OP_PHY_SET_REGISTER); + param = (struct ixl_aq_phy_reg_access *)iaq->iaq_param; + param->phy_iface = IXL_AQ_PHY_IF_MODULE; + param->dev_addr = dev; + htolem32(¶m->reg, reg); + htolem32(¶m->val, v); + + ixl_atq_exec(sc, &iatq, "ixlsffset"); + + switch (iaq->iaq_retval) { + case htole16(IXL_AQ_RC_OK): + break; + case htole16(IXL_AQ_RC_EBUSY): + return (EBUSY); + case htole16(IXL_AQ_RC_ESRCH): + return (ENODEV); + case htole16(IXL_AQ_RC_EIO): + case htole16(IXL_AQ_RC_EINVAL): + default: + return (EIO); + } return (0); } Index: sbin/ifconfig/Makefile =================================================================== RCS file: /cvs/src/sbin/ifconfig/Makefile,v retrieving revision 1.14 diff -u -p -r1.14 Makefile --- sbin/ifconfig/Makefile 3 May 2016 17:52:33 -0000 1.14 +++ sbin/ifconfig/Makefile 8 Apr 2019 02:05:36 -0000 @@ -1,7 +1,7 @@ # $OpenBSD: Makefile,v 1.14 2016/05/03 17:52:33 jca Exp $ PROG= ifconfig -SRCS= ifconfig.c brconfig.c +SRCS= ifconfig.c brconfig.c sff.c MAN= ifconfig.8 LDADD= -lutil Index: sbin/ifconfig/ifconfig.c =================================================================== RCS file: /cvs/src/sbin/ifconfig/ifconfig.c,v retrieving revision 1.394 diff -u -p -r1.394 ifconfig.c --- sbin/ifconfig/ifconfig.c 20 Feb 2019 19:17:17 -0000 1.394 +++ sbin/ifconfig/ifconfig.c 8 Apr 2019 02:05:36 -0000 @@ -340,6 +340,8 @@ void umb_setclass(const char *, int); void umb_roaming(const char *, int); void utf16_to_char(uint16_t *, int, char *, size_t); int char_to_utf16(const char *, uint16_t *, size_t); +void transceiver(const char *, int); +void transceiverdump(const char *, int); #else void setignore(const char *, int); #endif @@ -587,6 +589,9 @@ const struct cmd { { "datapath", NEXTARG, 0, switch_datapathid }, { "portno", NEXTARG2, 0, NULL, switch_portno }, { "addlocal", NEXTARG, 0, addlocal }, + { "transceiver", 0, 0, transceiver }, + { "sff", 0, 0, transceiver }, + { "sffdump", 0, 0, transceiverdump }, #else /* SMALL */ { "powersave", NEXTARG0, 0, setignore }, { "priority", NEXTARG, 0, setignore }, @@ -4003,6 +4008,22 @@ setmpwcontrolword(const char *value, int imrsave.imr_flags |= IMR_FLAG_CONTROLWORD; else imrsave.imr_flags &= ~IMR_FLAG_CONTROLWORD; +} + +int if_sff_info(int, const char *, int); + +void +transceiver(const char *value, int d) +{ + if (if_sff_info(s, name, 0) == -1) + err(1, "%s %s", name, __func__); +} + +void +transceiverdump(const char *value, int d) +{ + if (if_sff_info(s, name, 1) == -1) + err(1, "%s transceiver", name); } #endif /* SMALL */ Index: sbin/ifconfig/sff.c =================================================================== RCS file: sbin/ifconfig/sff.c diff -N sbin/ifconfig/sff.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ sbin/ifconfig/sff.c 8 Apr 2019 02:05:36 -0000 @@ -0,0 +1,451 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) David Gwynne <d...@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SMALL + +#include <sys/ioctl.h> + +#include <net/if.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <util.h> + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#ifndef ISSET +#define ISSET(_w, _m) ((_w) & (_m)) +#endif + +#define SFF8024_ID_UNKNOWN 0x00 +#define SFF8024_ID_GBIC 0x01 +#define SFF8024_ID_MOBO 0x02 /* Module/connector soldered to mobo */ + /* using SFF-8472 */ +#define SFF8024_ID_SFP 0x03 /* SFP/SFP+/SFP28 */ +#define SFF8024_ID_300PIN_XBI 0x04 /* 300 pin XBI */ +#define SFF8024_ID_XENPAK 0x05 +#define SFF8024_ID_XFP 0x06 +#define SFF8024_ID_XFF 0x07 +#define SFF8024_ID_XFPE 0x08 /* XFP-E */ +#define SFF8024_ID_XPAK 0x09 +#define SFF8024_ID_X2 0x0a +#define SFF8024_ID_DWDM_SFP 0x0b /* DWDM-SFP/SFP+ */ + /* not using SFF-8472 */ +#define SFF8024_ID_QSFP 0x0c +#define SFF8024_ID_QSFP_PLUS 0x0d /* or later */ + /* using SFF-8436/8665/8685 et al */ +#define SFF8024_ID_CXP 0x0e /* or later */ +#define SFF8024_ID_HD4X 0x0f /* shielded mini multilane HD 4X */ +#define SFF8024_ID_HD8X 0x10 /* shielded mini multilane HD 8X */ +#define SFF8024_ID_QSFP28 0x11 /* or later */ + /* using SFF-8665 et al */ +#define SFF8024_ID_CXP2 0x12 /* aka CXP28, or later */ +#define SFF8024_ID_CDFP 0x13 /* style 1/style 2 */ +#define SFF8024_ID_HD4X_FAN 0x14 /* shielded mini multilane HD 4X fanout */ +#define SFF8024_ID_HD8X_FAN 0x15 /* shielded mini multilane HD 8X fanout */ +#define SFF8024_ID_CDFP3 0x16 /* style 3 */ +#define SFF8024_ID_uQSFP 0x17 /* microQSFP */ +#define SFF8024_ID_QSFP_DD 0x18 /* QSFP-DD double density 8x */ + /* INF-8628 */ +#define SFF8024_ID_RESERVED 0x7f /* up to here is reserved */ + /* 0x80 to 0xff is vendor specific */ + +#define SFF8024_ID_IS_RESERVED(_id) ((_id) <= SFF8024_ID_RESERVED) +#define SFF8024_ID_IS_VENDOR(_id) ((_id) > SFF8024_ID_RESERVED) + +#define SFF8024_CON_UNKNOWN 0x00 +#define SFF8024_CON_SC 0x01 /* Subscriber Connector */ +#define SFF8024_CON_FC_1 0x02 /* Fibre Channel Style 1 copper */ +#define SFF8024_CON_FC_2 0x03 /* Fibre Channel Style 2 copper */ +#define SFF8024_CON_BNC_TNC 0x04 /* BNC/TNC */ +#define SFF8024_CON_FC_COAX 0x05 /* Fibre Channel coax headers */ +#define SFF8024_CON_FJ 0x06 /* Fibre Jack */ +#define SFF8024_CON_LC 0x07 /* Lucent Connector */ +#define SFF8024_CON_MT_RJ 0x08 /* Mechanical Transfer - Registered Jack */ +#define SFF8024_CON_MU 0x09 /* Multiple Optical */ +#define SFF8024_CON_SG 0x0a +#define SFF8024_CON_O_PIGTAIL 0x0b /* Optical Pigtail */ +#define SFF8024_CON_MPO_1x12 0x0c /* Multifiber Parallel Optic 1x12 */ +#define SFF8024_CON_MPO_2x16 0x0e /* Multifiber Parallel Optic 2x16 */ +#define SFF8024_CON_HSSDC2 0x20 /* High Speed Serial Data Connector */ +#define SFF8024_CON_Cu_PIGTAIL 0x21 /* Copper Pigtail */ +#define SFF8024_CON_RJ45 0x22 +#define SFF8024_CON_NO 0x23 /* No separable connector */ +#define SFF8024_CON_MXC_2x16 0x24 +#define SFF8024_CON_RESERVED 0x7f /* up to here is reserved */ + /* 0x80 to 0xff is vendor specific */ + +#define SFF8024_CON_IS_RESERVED(_id) ((_id) <= SFF8024_CON_RESERVED) +#define SFF8024_CON_IS_VENDOR(_id) ((_id) > SFF8024_CON_RESERVED) + +static const char *sff8024_id_names[] = { + [SFF8024_ID_UNKNOWN] = "Unknown", + [SFF8024_ID_GBIC] = "GBIC", + [SFF8024_ID_SFP] = "SFP", + [SFF8024_ID_300PIN_XBI] = "300 pin XBI", + [SFF8024_ID_XENPAK] = "XENPAK", + [SFF8024_ID_XFP] = "XFP", + [SFF8024_ID_XFF] = "XFF", + [SFF8024_ID_XFPE] = "XFPE", + [SFF8024_ID_XPAK] = "XPAK", + [SFF8024_ID_X2] = "X2", + [SFF8024_ID_DWDM_SFP] = "DWDM-SFP", + [SFF8024_ID_QSFP] = "QSFP", + [SFF8024_ID_QSFP_PLUS] = "QSFP+", + [SFF8024_ID_CXP] = "CXP", + [SFF8024_ID_HD4X] = "Shielded Mini Multilane HD 4X", + [SFF8024_ID_HD8X] = "Shielded Mini Multilane HD 8X", + [SFF8024_ID_QSFP28] = "QSFP28", + [SFF8024_ID_CXP2] = "CXP2", + [SFF8024_ID_CDFP] = "CDFP Style 1/2", + [SFF8024_ID_HD4X_FAN] = "Shielded Mini Multilane HD 4X Fanout Cable", + [SFF8024_ID_HD8X_FAN] = "Shielded Mini Multilane HD 8X Fanout Cable", + [SFF8024_ID_CDFP3] = "CDFP Style 3", + [SFF8024_ID_uQSFP] = "microQSFP", + [SFF8024_ID_QSFP_DD] = "QSFP Double-Density", +}; + +static const char *sff8024_con_names[] = { + [SFF8024_CON_UNKNOWN] = "Unknown", + [SFF8024_CON_SC] = "SC", + [SFF8024_CON_FC_1] = "Fibre Channel style 1", + [SFF8024_CON_FC_2] = "Fibre Channel style 2", + [SFF8024_CON_BNC_TNC] = "BNC/TNC", + [SFF8024_CON_FC_COAX] = "Fibre Channel coax headers", + [SFF8024_CON_FJ] = "Fiber Jack", + [SFF8024_CON_LC] = "LC", + [SFF8024_CON_MT_RJ] = "MT-RJ", + [SFF8024_CON_MU] = "MU", + [SFF8024_CON_SG] = "SG", + [SFF8024_CON_O_PIGTAIL] = "Optical Pigtail", + [SFF8024_CON_MPO_1x12] = "MPO 2x16", + [SFF8024_CON_MPO_2x16] = "MPO 1x12", + [SFF8024_CON_HSSDC2] = "HSSDC II", + [SFF8024_CON_Cu_PIGTAIL] + = "Copper Pigtail", + [SFF8024_CON_RJ45] = "RJ45", + [SFF8024_CON_NO] = "No separable connector", + [SFF8024_CON_MXC_2x16] = "MXC 2x16", +}; + +#define SFF8472_ID 0 /* SFF8027 for identifier values */ +#define SFF8472_EXT_ID 1 +#define SFF8472_EXT_ID_UNSPECIFIED 0x00 +#define SFF8472_EXT_ID_MOD_DEF_1 0x01 +#define SFF8472_EXT_ID_MOD_DEF_2 0x02 +#define SFF8472_EXT_ID_MOD_DEF_3 0x03 +#define SFF8472_EXT_ID_2WIRE 0x04 +#define SFF8472_EXT_ID_MOD_DEF_5 0x05 +#define SFF8472_EXT_ID_MOD_DEF_6 0x06 +#define SFF8472_EXT_ID_MOD_DEF_7 0x07 +#define SFF8472_CON 2 /* SFF8027 for connector values */ +#define SFF8472_VENDOR_START 20 +#define SFF8472_VENDOR_END 35 +#define SFF8472_PRODUCT_START 40 +#define SFF8472_PRODUCT_END 55 +#define SFF8472_REVISION_START 56 +#define SFF8472_REVISION_END 59 +#define SFF8472_SERIAL_START 68 +#define SFF8472_SERIAL_END 83 +#define SFF8472_DATECODE 84 +#define SFF8472_DDM_TYPE 92 +#define SFF8472_DDM_TYPE_AVG_POWER (1U << 3) +#define SFF8472_DDM_TYPE_CAL_EXT (1U << 4) +#define SFF8472_DDM_TYPE_CAL_INT (1U << 5) +#define SFF8472_DDM_TYPE_IMPL (1U << 6) +#define SFF8472_COMPLIANCE 94 +#define SFF8472_COMPLIANCE_NONE 0x00 +#define SFF8472_COMPLIANCE_9_3 0x01 /* SFF-8472 Rev 9.3 */ +#define SFF8472_COMPLIANCE_9_5 0x02 /* SFF-8472 Rev 9.5 */ +#define SFF8472_COMPLIANCE_10_2 0x03 /* SFF-8472 Rev 10.2 */ +#define SFF8472_COMPLIANCE_10_4 0x04 /* SFF-8472 Rev 10.4 */ +#define SFF8472_COMPLIANCE_11_0 0x05 /* SFF-8472 Rev 11.0 */ +#define SFF8472_COMPLIANCE_11_3 0x06 /* SFF-8472 Rev 11.3 */ +#define SFF8472_COMPLIANCE_11_4 0x07 /* SFF-8472 Rev 11.4 */ +#define SFF8472_COMPLIANCE_12_3 0x08 /* SFF-8472 Rev 12.3 */ + +/* + * page 0xa2 + */ +#define SFF8472_DDM_TEMP 96 +#define SFF8472_DDM_VCC 98 +#define SFF8472_DDM_TX_BIAS 100 +#define SFF8472_DDM_TX_POWER 102 +#define SFF8472_DDM_RX_POWER 104 +#define SFF8472_DDM_LASER 106 /* laser temp/wavelength */ + /* optional */ +#define SFF8472_DDM_TEC 108 /* Measured TEC current */ + /* optional */ + +#define SFF_TEMP_FACTOR 256.0 +#define SFF_TEMP_FMT "%.02f C" +#define SFF_VCC_FACTOR 10000.0 +#define SFF_VCC_FMT "%.04f V" +#define SFF_BIAS_FACTOR 0.5 +#define SFF_BIAS_FMT "%.01f uA" +#define SFF_POWER_FACTOR 10000.0 +#define SFF_POWER_FMT "%.04f mW" + +static void hexdump(const void *, size_t); +static int if_sff8472(int, const char *, int, const struct if_sffpage *); + +static const char * +sff_id_name(uint8_t id) +{ + const char *name = NULL; + + if (id < nitems(sff8024_id_names)) { + name = sff8024_id_names[id]; + if (name != NULL) + return (name); + } + + if (SFF8024_ID_IS_VENDOR(id)) + return ("Vendor Specific"); + + return ("Reserved"); +} + +static const char * +sff_con_name(uint8_t id) +{ + const char *name = NULL; + + if (id < nitems(sff8024_con_names)) { + name = sff8024_con_names[id]; + if (name != NULL) + return (name); + } + + if (SFF8024_CON_IS_VENDOR(id)) + return ("Vendor Specific"); + + return ("Reserved"); +} + +static void +if_sffpage_init(struct if_sffpage *sff, const char *ifname, + uint8_t addr, uint8_t page) +{ + memset(sff, 0, sizeof(*sff)); + + if (strlcpy(sff->sff_ifname, ifname, sizeof(sff->sff_ifname)) >= + sizeof(sff->sff_ifname)) + errx(1, "interface name too long"); + + sff->sff_addr = addr; + sff->sff_page = page; +} + +static void +if_sffpage_dump(const char *ifname, const struct if_sffpage *sff) +{ + printf("%s: addr %02x", ifname, sff->sff_addr); + if (sff->sff_addr == IFSFF_ADDR_EEPROM) + printf(" page %u", sff->sff_page); + putchar('\n'); + hexdump(sff->sff_data, sizeof(sff->sff_data)); +} + +int +if_sff_info(int s, const char *ifname, int dump) +{ + struct if_sffpage pg0; + int error = 0; + uint8_t id, ext_id; + + if_sffpage_init(&pg0, ifname, IFSFF_ADDR_EEPROM, 0); + + if (ioctl(s, SIOCGIFSFFPAGE, (caddr_t)&pg0) == -1) + return (-1); + + if (dump) + if_sffpage_dump(ifname, &pg0); + + id = pg0.sff_data[0]; /* SFF8472_ID */ + + printf("%s: identifier %s (%02x)\n", ifname, sff_id_name(id), id); + switch (id) { + case SFF8024_ID_SFP: + ext_id = pg0.sff_data[SFF8472_EXT_ID]; + if (ext_id != SFF8472_EXT_ID_2WIRE) { + printf("\textended-id: %02xh\n", ext_id); + break; + } + /* FALLTHROUGH */ + case SFF8024_ID_GBIC: + error = if_sff8472(s, ifname, dump, &pg0); + break; + } + + return (error); +} + +static int +printable(int ch) +{ + if (ch == '\0') + return ('_'); + if (!isprint(ch)) + return ('~'); + + return (ch); +} + +static void +if_sff_ascii_print(const struct if_sffpage *sff, const char *name, + size_t start, size_t end) +{ + const uint8_t *d = sff->sff_data; + int ch; + + printf("\t%s: ", name); + + for (;;) { + ch = d[start]; + if (!isspace(ch) && ch != '\0') + break; + + start++; + if (start == end) { + printf("(unknown)\n"); + return; + } + } + + for (;;) { + int ch = d[end]; + if (!isspace(ch) && ch != '\0') + break; + + end--; + } + + do { + putchar(printable(d[start])); + } while (++start <= end); + putchar('\n'); +} + +static void +if_sff_date_print(const struct if_sffpage *sff, const char *name, + size_t start) +{ + const uint8_t *d = sff->sff_data + start; + + /* YYMMDD */ + printf("\t%s: 20%c%c-%c%c-%c%c\n", name, + d[0], d[1], d[2], d[3], d[4], d[5]); +} + +static int16_t +if_sff_int(const struct if_sffpage *sff, size_t start) +{ + const uint8_t *d = sff->sff_data + start; + + return (d[0] << 8 | d[1]); +} + +static uint16_t +if_sff_uint(const struct if_sffpage *sff, size_t start) +{ + const uint8_t *d = sff->sff_data + start; + + return (d[0] << 8 | d[1]); +} + +static int +if_sff8472(int s, const char *ifname, int dump, const struct if_sffpage *pg0) +{ + struct if_sffpage ddm; + uint8_t con, ddm_types; + + con = pg0->sff_data[SFF8472_CON]; + printf("\tconnector: %s (%02x)\n", sff_con_name(con), con); + + if_sff_ascii_print(pg0, "vendor", + SFF8472_VENDOR_START, SFF8472_VENDOR_END); + if_sff_ascii_print(pg0, "product", + SFF8472_PRODUCT_START, SFF8472_PRODUCT_END); + if_sff_ascii_print(pg0, "revision", + SFF8472_REVISION_START, SFF8472_REVISION_END); + if_sff_ascii_print(pg0, "serial", + SFF8472_SERIAL_START, SFF8472_SERIAL_END); + if_sff_date_print(pg0, "date", SFF8472_DATECODE); + + ddm_types = pg0->sff_data[SFF8472_DDM_TYPE]; + if (pg0->sff_data[SFF8472_COMPLIANCE] == SFF8472_COMPLIANCE_NONE || + !ISSET(ddm_types, SFF8472_DDM_TYPE_IMPL)) + return (0); + + if_sffpage_init(&ddm, ifname, IFSFF_ADDR_DDM, 0); + if (ioctl(s, SIOCGIFSFFPAGE, (caddr_t)&ddm) == -1) + return (-1); + + if (dump) + if_sffpage_dump(ifname, &ddm); + + if (ISSET(ddm_types, SFF8472_DDM_TYPE_CAL_EXT)) { + printf("\t" "calibration: external " + "(WARNING: needs more code)\n"); + } + + printf("\t" "temperature: " SFF_TEMP_FMT "\n", + if_sff_int(&ddm, SFF8472_DDM_TEMP) / SFF_TEMP_FACTOR); + printf("\t" "vcc: " SFF_VCC_FMT "\n", + if_sff_uint(&ddm, SFF8472_DDM_VCC) / SFF_VCC_FACTOR); + printf("\t" "tx-bias: " SFF_BIAS_FMT "\n", + if_sff_uint(&ddm, SFF8472_DDM_TX_BIAS) / SFF_BIAS_FACTOR); + printf("\t" "tx-power: " SFF_POWER_FMT "\n", + if_sff_uint(&ddm, SFF8472_DDM_TX_POWER) / SFF_POWER_FACTOR); + printf("\t" "rx-power: " SFF_POWER_FMT " %s\n", + if_sff_uint(&ddm, SFF8472_DDM_RX_POWER) / SFF_POWER_FACTOR, + ISSET(ddm_types, SFF8472_DDM_TYPE_AVG_POWER) ? "average" : "OMA"); + + return (0); +} + +static void +hexdump(const void *d, size_t datalen) +{ + const uint8_t *data = d; + int i, j = 0; + + for (i = 0; i < datalen; i += j) { + printf("% 4d: ", i); + for (j = 0; j < 16 && i+j < datalen; j++) + printf("%02x ", data[i + j]); + while (j++ < 16) + printf(" "); + printf("|"); + for (j = 0; j < 16 && i+j < datalen; j++) + putchar(printable(data[i + j])); + printf("|\n"); + } +} + +#endif /* SMALL */