sfxge: implement SIOCGI2C to read information from phy modules

The IOCTL is used by 'ifconfig -v' to show SFP+/QSFP+ information
including inventory information and dianostics (temperature, light
levels, voltage etc).

Reviewed by:    gnn,melifaro
Sponsored by:   Solarflare Communications, Inc.
MFC after:      2 days
Differential Revision: https://reviews.freebsd.org/D5240
This commit is contained in:
Andrew Rybchenko 2016-02-10 12:14:56 +00:00
parent d664515e68
commit 1e76f8b895
5 changed files with 284 additions and 0 deletions

View File

@ -879,6 +879,14 @@ efx_phy_media_type_get(
__in efx_nic_t *enp,
__out efx_phy_media_type_t *typep);
extern efx_rc_t
efx_phy_module_get_info(
__in efx_nic_t *enp,
__in uint8_t dev_addr,
__in uint8_t offset,
__in uint8_t len,
__out_bcount(len) uint8_t *data);
#if EFSYS_OPT_PHY_STATS
/* START MKCONFIG GENERATED PhyHeaderStatsBlock 30ed56ad501f8e36 */

View File

@ -2078,5 +2078,217 @@ fail1:
return (rc);
}
/*
* Size of media information page in accordance with SFF-8472 and SFF-8436.
* It is used in MCDI interface as well.
*/
#define EFX_PHY_MEDIA_INFO_PAGE_SIZE 0x80
static __checkReturn efx_rc_t
efx_mcdi_get_phy_media_info(
__in efx_nic_t *enp,
__in uint32_t mcdi_page,
__in uint8_t offset,
__in uint8_t len,
__out_bcount(len) uint8_t *data)
{
efx_mcdi_req_t req;
uint8_t payload[MAX(MC_CMD_GET_PHY_MEDIA_INFO_IN_LEN,
MC_CMD_GET_PHY_MEDIA_INFO_OUT_LEN(
EFX_PHY_MEDIA_INFO_PAGE_SIZE))];
efx_rc_t rc;
EFSYS_ASSERT((uint32_t)offset + len <= EFX_PHY_MEDIA_INFO_PAGE_SIZE);
(void) memset(payload, 0, sizeof (payload));
req.emr_cmd = MC_CMD_GET_PHY_MEDIA_INFO;
req.emr_in_buf = payload;
req.emr_in_length = MC_CMD_GET_PHY_MEDIA_INFO_IN_LEN;
req.emr_out_buf = payload;
req.emr_out_length =
MC_CMD_GET_PHY_MEDIA_INFO_OUT_LEN(EFX_PHY_MEDIA_INFO_PAGE_SIZE);
MCDI_IN_SET_DWORD(req, GET_PHY_MEDIA_INFO_IN_PAGE, mcdi_page);
efx_mcdi_execute(enp, &req);
if (req.emr_rc != 0) {
rc = req.emr_rc;
goto fail1;
}
if (req.emr_out_length_used !=
MC_CMD_GET_PHY_MEDIA_INFO_OUT_LEN(EFX_PHY_MEDIA_INFO_PAGE_SIZE)) {
rc = EMSGSIZE;
goto fail2;
}
if (MCDI_OUT_DWORD(req, GET_PHY_MEDIA_INFO_OUT_DATALEN) !=
EFX_PHY_MEDIA_INFO_PAGE_SIZE) {
rc = EIO;
goto fail3;
}
memcpy(data,
MCDI_OUT2(req, uint8_t, GET_PHY_MEDIA_INFO_OUT_DATA) + offset,
len);
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
/*
* 2-wire device address of the base information in accordance with SFF-8472
* Diagnostic Monitoring Interface for Optical Transceivers section
* 4 Memory Organization.
*/
#define EFX_PHY_MEDIA_INFO_DEV_ADDR_SFP_BASE 0xA0
/*
* 2-wire device address of the digital diagnostics monitoring interface
* in accordance with SFF-8472 Diagnostic Monitoring Interface for Optical
* Transceivers section 4 Memory Organization.
*/
#define EFX_PHY_MEDIA_INFO_DEV_ADDR_SFP_DDM 0xA2
/*
* Hard wired 2-wire device address for QSFP+ in accordance with SFF-8436
* QSFP+ 10 Gbs 4X PLUGGABLE TRANSCEIVER section 7.4 Device Addressing and
* Operation.
*/
#define EFX_PHY_MEDIA_INFO_DEV_ADDR_QSFP 0xA0
__checkReturn efx_rc_t
efx_mcdi_phy_module_get_info(
__in efx_nic_t *enp,
__in uint8_t dev_addr,
__in uint8_t offset,
__in uint8_t len,
__out_bcount(len) uint8_t *data)
{
efx_port_t *epp = &(enp->en_port);
efx_rc_t rc;
uint32_t mcdi_lower_page;
uint32_t mcdi_upper_page;
EFSYS_ASSERT3U(enp->en_mod_flags, &, EFX_MOD_PROBE);
/*
* Map device address to MC_CMD_GET_PHY_MEDIA_INFO pages.
* Offset plus length interface allows to access page 0 only.
* I.e. non-zero upper pages are not accessible.
* See SFF-8472 section 4 Memory Organization and SFF-8436 section 7.6
* QSFP+ Memory Map for details on how information is structured
* and accessible.
*/
switch (epp->ep_fixed_port_type) {
case EFX_PHY_MEDIA_SFP_PLUS:
/*
* In accordance with SFF-8472 Diagnostic Monitoring
* Interface for Optical Transceivers section 4 Memory
* Organization two 2-wire addresses are defined.
*/
switch (dev_addr) {
/* Base information */
case EFX_PHY_MEDIA_INFO_DEV_ADDR_SFP_BASE:
/*
* MCDI page 0 should be used to access lower
* page 0 (0x00 - 0x7f) at the device address 0xA0.
*/
mcdi_lower_page = 0;
/*
* MCDI page 1 should be used to access upper
* page 0 (0x80 - 0xff) at the device address 0xA0.
*/
mcdi_upper_page = 1;
break;
/* Diagnostics */
case EFX_PHY_MEDIA_INFO_DEV_ADDR_SFP_DDM:
/*
* MCDI page 2 should be used to access lower
* page 0 (0x00 - 0x7f) at the device address 0xA2.
*/
mcdi_lower_page = 2;
/*
* MCDI page 3 should be used to access upper
* page 0 (0x80 - 0xff) at the device address 0xA2.
*/
mcdi_upper_page = 3;
break;
default:
rc = ENOTSUP;
goto fail1;
}
break;
case EFX_PHY_MEDIA_QSFP_PLUS:
switch (dev_addr) {
case EFX_PHY_MEDIA_INFO_DEV_ADDR_QSFP:
/*
* MCDI page -1 should be used to access lower page 0
* (0x00 - 0x7f).
*/
mcdi_lower_page = (uint32_t)-1;
/*
* MCDI page 0 should be used to access upper page 0
* (0x80h - 0xff).
*/
mcdi_upper_page = 0;
break;
default:
rc = ENOTSUP;
goto fail1;
}
break;
default:
rc = ENOTSUP;
goto fail1;
}
if (offset < EFX_PHY_MEDIA_INFO_PAGE_SIZE) {
uint8_t read_len =
MIN(len, EFX_PHY_MEDIA_INFO_PAGE_SIZE - offset);
rc = efx_mcdi_get_phy_media_info(enp,
mcdi_lower_page, offset, read_len, data);
if (rc != 0)
goto fail2;
data += read_len;
len -= read_len;
offset = 0;
} else {
offset -= EFX_PHY_MEDIA_INFO_PAGE_SIZE;
}
if (len > 0) {
EFSYS_ASSERT3U(len, <=, EFX_PHY_MEDIA_INFO_PAGE_SIZE);
EFSYS_ASSERT3U(offset, <, EFX_PHY_MEDIA_INFO_PAGE_SIZE);
rc = efx_mcdi_get_phy_media_info(enp,
mcdi_upper_page, offset, len, data);
if (rc != 0)
goto fail3;
}
return (0);
fail3:
EFSYS_PROBE(fail3);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
#endif /* EFSYS_OPT_MCDI */

View File

@ -228,6 +228,14 @@ efx_mcdi_get_loopback_modes(
__in efx_nic_t *enp);
#endif /* EFSYS_OPT_LOOPBACK */
extern __checkReturn efx_rc_t
efx_mcdi_phy_module_get_info(
__in efx_nic_t *enp,
__in uint8_t dev_addr,
__in uint8_t offset,
__in uint8_t len,
__out_bcount(len) uint8_t *data);
#define MCDI_IN(_emr, _type, _ofst) \
((_type *)((_emr).emr_in_buf + (_ofst)))

View File

@ -560,6 +560,38 @@ efx_phy_media_type_get(
*typep = epp->ep_fixed_port_type;
}
__checkReturn efx_rc_t
efx_phy_module_get_info(
__in efx_nic_t *enp,
__in uint8_t dev_addr,
__in uint8_t offset,
__in uint8_t len,
__out_bcount(len) uint8_t *data)
{
efx_rc_t rc;
EFSYS_ASSERT3U(enp->en_magic, ==, EFX_NIC_MAGIC);
EFSYS_ASSERT(data != NULL);
if ((uint32_t)offset + len > 0xff) {
rc = EINVAL;
goto fail1;
}
if ((rc = efx_mcdi_phy_module_get_info(enp, dev_addr,
offset, len, data)) != 0)
goto fail2;
return (0);
fail2:
EFSYS_PROBE(fail2);
fail1:
EFSYS_PROBE1(fail1, efx_rc_t, rc);
return (rc);
}
#if EFSYS_OPT_PHY_STATS
#if EFSYS_OPT_NAMES

View File

@ -500,6 +500,30 @@ sfxge_if_ioctl(struct ifnet *ifp, unsigned long command, caddr_t data)
case SIOCGIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &sc->media, command);
break;
#ifdef SIOCGI2C
case SIOCGI2C:
{
struct ifi2creq i2c;
error = copyin(ifr->ifr_data, &i2c, sizeof(i2c));
if (error != 0)
break;
if (i2c.len > sizeof(i2c.data)) {
error = EINVAL;
break;
}
SFXGE_ADAPTER_LOCK(sc);
error = efx_phy_module_get_info(sc->enp, i2c.dev_addr,
i2c.offset, i2c.len,
&i2c.data[0]);
SFXGE_ADAPTER_UNLOCK(sc);
if (error == 0)
error = copyout(&i2c, ifr->ifr_data, sizeof(i2c));
break;
}
#endif
case SIOCGPRIVATE_0:
error = priv_check(curthread, PRIV_DRIVER);
if (error != 0)