sfc: Add ethtool -m support for QSFP modules
authorMartin Habets <mhabets@solarflare.com>
Tue, 18 Jul 2017 15:43:19 +0000 (16:43 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 19 Jul 2017 23:23:28 +0000 (16:23 -0700)
This also adds support for non-QSFP modules attached to QSFP.

Signed-off-by: Martin Habets <mhabets@solarflare.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/sfc/mcdi_port.c

index c905971..d3f96a8 100644 (file)
@@ -746,59 +746,171 @@ static const char *efx_mcdi_phy_test_name(struct efx_nic *efx,
        return NULL;
 }
 
-#define SFP_PAGE_SIZE  128
-#define SFP_NUM_PAGES  2
-static int efx_mcdi_phy_get_module_eeprom(struct efx_nic *efx,
-                                         struct ethtool_eeprom *ee, u8 *data)
+#define SFP_PAGE_SIZE          128
+#define SFF_DIAG_TYPE_OFFSET   92
+#define SFF_DIAG_ADDR_CHANGE   BIT(2)
+#define SFF_8079_NUM_PAGES     2
+#define SFF_8472_NUM_PAGES     4
+#define SFF_8436_NUM_PAGES     5
+#define SFF_DMT_LEVEL_OFFSET   94
+
+/** efx_mcdi_phy_get_module_eeprom_page() - Get a single page of module eeprom
+ * @efx:       NIC context
+ * @page:      EEPROM page number
+ * @data:      Destination data pointer
+ * @offset:    Offset in page to copy from in to data
+ * @space:     Space available in data
+ *
+ * Return:
+ *   >=0 - amount of data copied
+ *   <0  - error
+ */
+static int efx_mcdi_phy_get_module_eeprom_page(struct efx_nic *efx,
+                                              unsigned int page,
+                                              u8 *data, ssize_t offset,
+                                              ssize_t space)
 {
        MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_PHY_MEDIA_INFO_OUT_LENMAX);
        MCDI_DECLARE_BUF(inbuf, MC_CMD_GET_PHY_MEDIA_INFO_IN_LEN);
        size_t outlen;
-       int rc;
        unsigned int payload_len;
-       unsigned int space_remaining = ee->len;
-       unsigned int page;
-       unsigned int page_off;
        unsigned int to_copy;
-       u8 *user_data = data;
+       int rc;
 
-       BUILD_BUG_ON(SFP_PAGE_SIZE * SFP_NUM_PAGES != ETH_MODULE_SFF_8079_LEN);
+       if (offset > SFP_PAGE_SIZE)
+               return -EINVAL;
 
-       page_off = ee->offset % SFP_PAGE_SIZE;
-       page = ee->offset / SFP_PAGE_SIZE;
+       to_copy = min(space, SFP_PAGE_SIZE - offset);
 
-       while (space_remaining && (page < SFP_NUM_PAGES)) {
-               MCDI_SET_DWORD(inbuf, GET_PHY_MEDIA_INFO_IN_PAGE, page);
+       MCDI_SET_DWORD(inbuf, GET_PHY_MEDIA_INFO_IN_PAGE, page);
+       rc = efx_mcdi_rpc_quiet(efx, MC_CMD_GET_PHY_MEDIA_INFO,
+                               inbuf, sizeof(inbuf),
+                               outbuf, sizeof(outbuf),
+                               &outlen);
 
-               rc = efx_mcdi_rpc(efx, MC_CMD_GET_PHY_MEDIA_INFO,
-                                 inbuf, sizeof(inbuf),
-                                 outbuf, sizeof(outbuf),
-                                 &outlen);
-               if (rc)
-                       return rc;
+       if (rc)
+               return rc;
+
+       if (outlen < (MC_CMD_GET_PHY_MEDIA_INFO_OUT_DATA_OFST +
+                       SFP_PAGE_SIZE))
+               return -EIO;
+
+       payload_len = MCDI_DWORD(outbuf, GET_PHY_MEDIA_INFO_OUT_DATALEN);
+       if (payload_len != SFP_PAGE_SIZE)
+               return -EIO;
 
-               if (outlen < (MC_CMD_GET_PHY_MEDIA_INFO_OUT_DATA_OFST +
-                             SFP_PAGE_SIZE))
-                       return -EIO;
+       memcpy(data, MCDI_PTR(outbuf, GET_PHY_MEDIA_INFO_OUT_DATA) + offset,
+              to_copy);
 
-               payload_len = MCDI_DWORD(outbuf,
-                                        GET_PHY_MEDIA_INFO_OUT_DATALEN);
-               if (payload_len != SFP_PAGE_SIZE)
-                       return -EIO;
+       return to_copy;
+}
 
-               /* Copy as much as we can into data */
-               payload_len -= page_off;
-               to_copy = (space_remaining < payload_len) ?
-                       space_remaining : payload_len;
+static int efx_mcdi_phy_get_module_eeprom_byte(struct efx_nic *efx,
+                                              unsigned int page,
+                                              u8 byte)
+{
+       int rc;
+       u8 data;
 
-               memcpy(user_data,
-                      MCDI_PTR(outbuf, GET_PHY_MEDIA_INFO_OUT_DATA) + page_off,
-                      to_copy);
+       rc = efx_mcdi_phy_get_module_eeprom_page(efx, page, &data, byte, 1);
+       if (rc == 1)
+               return data;
+
+       return rc;
+}
+
+static int efx_mcdi_phy_diag_type(struct efx_nic *efx)
+{
+       /* Page zero of the EEPROM includes the diagnostic type at byte 92. */
+       return efx_mcdi_phy_get_module_eeprom_byte(efx, 0,
+                                                  SFF_DIAG_TYPE_OFFSET);
+}
 
-               space_remaining -= to_copy;
-               user_data += to_copy;
-               page_off = 0;
-               page++;
+static int efx_mcdi_phy_sff_8472_level(struct efx_nic *efx)
+{
+       /* Page zero of the EEPROM includes the DMT level at byte 94. */
+       return efx_mcdi_phy_get_module_eeprom_byte(efx, 0,
+                                                  SFF_DMT_LEVEL_OFFSET);
+}
+
+static u32 efx_mcdi_phy_module_type(struct efx_nic *efx)
+{
+       struct efx_mcdi_phy_data *phy_data = efx->phy_data;
+
+       if (phy_data->media != MC_CMD_MEDIA_QSFP_PLUS)
+               return phy_data->media;
+
+       /* A QSFP+ NIC may actually have an SFP+ module attached.
+        * The ID is page 0, byte 0.
+        */
+       switch (efx_mcdi_phy_get_module_eeprom_byte(efx, 0, 0)) {
+       case 0x3:
+               return MC_CMD_MEDIA_SFP_PLUS;
+       case 0xc:
+       case 0xd:
+               return MC_CMD_MEDIA_QSFP_PLUS;
+       default:
+               return 0;
+       }
+}
+
+static int efx_mcdi_phy_get_module_eeprom(struct efx_nic *efx,
+                                         struct ethtool_eeprom *ee, u8 *data)
+{
+       int rc;
+       ssize_t space_remaining = ee->len;
+       unsigned int page_off;
+       bool ignore_missing;
+       int num_pages;
+       int page;
+
+       switch (efx_mcdi_phy_module_type(efx)) {
+       case MC_CMD_MEDIA_SFP_PLUS:
+               num_pages = efx_mcdi_phy_sff_8472_level(efx) > 0 ?
+                               SFF_8472_NUM_PAGES : SFF_8079_NUM_PAGES;
+               page = 0;
+               ignore_missing = false;
+               break;
+       case MC_CMD_MEDIA_QSFP_PLUS:
+               num_pages = SFF_8436_NUM_PAGES;
+               page = -1; /* We obtain the lower page by asking for -1. */
+               ignore_missing = true; /* Ignore missing pages after page 0. */
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       page_off = ee->offset % SFP_PAGE_SIZE;
+       page += ee->offset / SFP_PAGE_SIZE;
+
+       while (space_remaining && (page < num_pages)) {
+               rc = efx_mcdi_phy_get_module_eeprom_page(efx, page,
+                                                        data, page_off,
+                                                        space_remaining);
+
+               if (rc > 0) {
+                       space_remaining -= rc;
+                       data += rc;
+                       page_off = 0;
+                       page++;
+               } else if (rc == 0) {
+                       space_remaining = 0;
+               } else if (ignore_missing && (page > 0)) {
+                       int intended_size = SFP_PAGE_SIZE - page_off;
+
+                       space_remaining -= intended_size;
+                       if (space_remaining < 0) {
+                               space_remaining = 0;
+                       } else {
+                               memset(data, 0, intended_size);
+                               data += intended_size;
+                               page_off = 0;
+                               page++;
+                               rc = 0;
+                       }
+               } else {
+                       return rc;
+               }
        }
 
        return 0;
@@ -807,16 +919,42 @@ static int efx_mcdi_phy_get_module_eeprom(struct efx_nic *efx,
 static int efx_mcdi_phy_get_module_info(struct efx_nic *efx,
                                        struct ethtool_modinfo *modinfo)
 {
-       struct efx_mcdi_phy_data *phy_cfg = efx->phy_data;
+       int sff_8472_level;
+       int diag_type;
 
-       switch (phy_cfg->media) {
+       switch (efx_mcdi_phy_module_type(efx)) {
        case MC_CMD_MEDIA_SFP_PLUS:
-               modinfo->type = ETH_MODULE_SFF_8079;
-               modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
-               return 0;
+               sff_8472_level = efx_mcdi_phy_sff_8472_level(efx);
+
+               /* If we can't read the diagnostics level we have none. */
+               if (sff_8472_level < 0)
+                       return -EOPNOTSUPP;
+
+               /* Check if this module requires the (unsupported) address
+                * change operation.
+                */
+               diag_type = efx_mcdi_phy_diag_type(efx);
+
+               if ((sff_8472_level == 0) ||
+                   (diag_type & SFF_DIAG_ADDR_CHANGE)) {
+                       modinfo->type = ETH_MODULE_SFF_8079;
+                       modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN;
+               } else {
+                       modinfo->type = ETH_MODULE_SFF_8472;
+                       modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
+               }
+               break;
+
+       case MC_CMD_MEDIA_QSFP_PLUS:
+               modinfo->type = ETH_MODULE_SFF_8436;
+               modinfo->eeprom_len = ETH_MODULE_SFF_8436_LEN;
+               break;
+
        default:
                return -EOPNOTSUPP;
        }
+
+       return 0;
 }
 
 static const struct efx_phy_operations efx_mcdi_phy_ops = {