net/cxgbe: support EEPROM access
authorRahul Lakkireddy <rahul.lakkireddy@chelsio.com>
Fri, 6 May 2016 07:43:18 +0000 (13:13 +0530)
committerBruce Richardson <bruce.richardson@intel.com>
Wed, 15 Jun 2016 15:13:55 +0000 (17:13 +0200)
Add operations to get/set EEPROM data.

Signed-off-by: Rahul Lakkireddy <rahul.lakkireddy@chelsio.com>
Signed-off-by: Kumar Sanghvi <kumaras@chelsio.com>
doc/guides/nics/overview.rst
drivers/net/cxgbe/base/adapter.h
drivers/net/cxgbe/base/common.h
drivers/net/cxgbe/base/t4_hw.c
drivers/net/cxgbe/base/t4_hw.h
drivers/net/cxgbe/cxgbe_ethdev.c

index 82a7206..a9c38c0 100644 (file)
@@ -130,7 +130,7 @@ Most of these differences are summarized below.
    Basic stats            Y Y   Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y       Y Y Y   Y Y Y Y
    Extended stats                   Y   Y Y Y Y Y Y Y Y Y Y Y Y Y Y                   Y Y
    Stats per queue              Y                   Y Y     Y Y Y Y Y Y         Y Y   Y   Y Y
-   EEPROM dump                                  Y   Y Y
+   EEPROM dump                  Y               Y   Y Y
    Registers dump                               Y Y Y Y Y Y
    Multiprocess aware                   Y Y Y Y     Y Y Y Y Y Y Y Y Y Y       Y Y Y
    BSD nic_uio                  Y Y   Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y                       Y Y
index 73e7aca..5e3bd50 100644 (file)
@@ -318,6 +318,9 @@ struct adapter {
        unsigned int mbox;     /* associated mailbox */
        unsigned int pf;       /* associated physical function id */
 
+       unsigned int vpd_busy;
+       unsigned int vpd_flag;
+
        int use_unpacked_mode; /* unpacked rx mode state */
 };
 
@@ -435,6 +438,10 @@ static inline void t4_write_reg64(struct adapter *adapter, u32 reg_addr,
 #define PCI_CAP_LIST_ID         0       /* Capability ID */
 #define PCI_CAP_LIST_NEXT       1       /* Next capability in the list */
 #define PCI_EXP_DEVCTL2         40      /* Device Control 2 */
+#define PCI_CAP_ID_VPD          0x03    /* Vital Product Data */
+#define PCI_VPD_ADDR            2       /* Address to access (15 bits!) */
+#define PCI_VPD_ADDR_F          0x8000  /* Write 0, 1 indicates completion */
+#define PCI_VPD_DATA            4       /* 32-bits of data returned here */
 
 /**
  * t4_os_pci_write_cfg4 - 32-bit write to PCI config space
index cf2e82d..853edd8 100644 (file)
@@ -1,7 +1,7 @@
 /*-
  *   BSD LICENSE
  *
- *   Copyright(c) 2014-2015 Chelsio Communications.
+ *   Copyright(c) 2014-2016 Chelsio Communications.
  *   All rights reserved.
  *
  *   Redistribution and use in source and binary forms, with or without
@@ -398,4 +398,7 @@ int t4_init_sge_params(struct adapter *adapter);
 int t4_init_tp_params(struct adapter *adap);
 int t4_filter_field_shift(const struct adapter *adap, unsigned int filter_sel);
 int t4_handle_fw_rpl(struct adapter *adap, const __be64 *rpl);
+int t4_seeprom_read(struct adapter *adapter, u32 addr, u32 *data);
+int t4_seeprom_write(struct adapter *adapter, u32 addr, u32 data);
+int t4_seeprom_wp(struct adapter *adapter, int enable);
 #endif /* __CHELSIO_COMMON_H */
index 7882f9a..ff8594a 100644 (file)
@@ -569,6 +569,185 @@ int t4_wr_mbox_meat(struct adapter *adap, int mbox, const void *cmd, int size,
                                       FW_CMD_MAX_TIMEOUT);
 }
 
+/* EEPROM reads take a few tens of us while writes can take a bit over 5 ms. */
+#define EEPROM_DELAY            10              /* 10us per poll spin */
+#define EEPROM_MAX_POLL         5000            /* x 5000 == 50ms */
+
+#define EEPROM_STAT_ADDR        0x7bfc
+
+/**
+ * Small utility function to wait till any outstanding VPD Access is complete.
+ * We have a per-adapter state variable "VPD Busy" to indicate when we have a
+ * VPD Access in flight.  This allows us to handle the problem of having a
+ * previous VPD Access time out and prevent an attempt to inject a new VPD
+ * Request before any in-flight VPD request has completed.
+ */
+static int t4_seeprom_wait(struct adapter *adapter)
+{
+       unsigned int base = adapter->params.pci.vpd_cap_addr;
+       int max_poll;
+
+       /* If no VPD Access is in flight, we can just return success right
+        * away.
+        */
+       if (!adapter->vpd_busy)
+               return 0;
+
+       /* Poll the VPD Capability Address/Flag register waiting for it
+        * to indicate that the operation is complete.
+        */
+       max_poll = EEPROM_MAX_POLL;
+       do {
+               u16 val;
+
+               udelay(EEPROM_DELAY);
+               t4_os_pci_read_cfg2(adapter, base + PCI_VPD_ADDR, &val);
+
+               /* If the operation is complete, mark the VPD as no longer
+                * busy and return success.
+                */
+               if ((val & PCI_VPD_ADDR_F) == adapter->vpd_flag) {
+                       adapter->vpd_busy = 0;
+                       return 0;
+               }
+       } while (--max_poll);
+
+       /* Failure!  Note that we leave the VPD Busy status set in order to
+        * avoid pushing a new VPD Access request into the VPD Capability till
+        * the current operation eventually succeeds.  It's a bug to issue a
+        * new request when an existing request is in flight and will result
+        * in corrupt hardware state.
+        */
+       return -ETIMEDOUT;
+}
+
+/**
+ * t4_seeprom_read - read a serial EEPROM location
+ * @adapter: adapter to read
+ * @addr: EEPROM virtual address
+ * @data: where to store the read data
+ *
+ * Read a 32-bit word from a location in serial EEPROM using the card's PCI
+ * VPD capability.  Note that this function must be called with a virtual
+ * address.
+ */
+int t4_seeprom_read(struct adapter *adapter, u32 addr, u32 *data)
+{
+       unsigned int base = adapter->params.pci.vpd_cap_addr;
+       int ret;
+
+       /* VPD Accesses must alway be 4-byte aligned!
+        */
+       if (addr >= EEPROMVSIZE || (addr & 3))
+               return -EINVAL;
+
+       /* Wait for any previous operation which may still be in flight to
+        * complete.
+        */
+       ret = t4_seeprom_wait(adapter);
+       if (ret) {
+               dev_err(adapter, "VPD still busy from previous operation\n");
+               return ret;
+       }
+
+       /* Issue our new VPD Read request, mark the VPD as being busy and wait
+        * for our request to complete.  If it doesn't complete, note the
+        * error and return it to our caller.  Note that we do not reset the
+        * VPD Busy status!
+        */
+       t4_os_pci_write_cfg2(adapter, base + PCI_VPD_ADDR, (u16)addr);
+       adapter->vpd_busy = 1;
+       adapter->vpd_flag = PCI_VPD_ADDR_F;
+       ret = t4_seeprom_wait(adapter);
+       if (ret) {
+               dev_err(adapter, "VPD read of address %#x failed\n", addr);
+               return ret;
+       }
+
+       /* Grab the returned data, swizzle it into our endianness and
+        * return success.
+        */
+       t4_os_pci_read_cfg4(adapter, base + PCI_VPD_DATA, data);
+       *data = le32_to_cpu(*data);
+       return 0;
+}
+
+/**
+ * t4_seeprom_write - write a serial EEPROM location
+ * @adapter: adapter to write
+ * @addr: virtual EEPROM address
+ * @data: value to write
+ *
+ * Write a 32-bit word to a location in serial EEPROM using the card's PCI
+ * VPD capability.  Note that this function must be called with a virtual
+ * address.
+ */
+int t4_seeprom_write(struct adapter *adapter, u32 addr, u32 data)
+{
+       unsigned int base = adapter->params.pci.vpd_cap_addr;
+       int ret;
+       u32 stats_reg;
+       int max_poll;
+
+       /* VPD Accesses must alway be 4-byte aligned!
+        */
+       if (addr >= EEPROMVSIZE || (addr & 3))
+               return -EINVAL;
+
+       /* Wait for any previous operation which may still be in flight to
+        * complete.
+        */
+       ret = t4_seeprom_wait(adapter);
+       if (ret) {
+               dev_err(adapter, "VPD still busy from previous operation\n");
+               return ret;
+       }
+
+       /* Issue our new VPD Read request, mark the VPD as being busy and wait
+        * for our request to complete.  If it doesn't complete, note the
+        * error and return it to our caller.  Note that we do not reset the
+        * VPD Busy status!
+        */
+       t4_os_pci_write_cfg4(adapter, base + PCI_VPD_DATA,
+                            cpu_to_le32(data));
+       t4_os_pci_write_cfg2(adapter, base + PCI_VPD_ADDR,
+                            (u16)addr | PCI_VPD_ADDR_F);
+       adapter->vpd_busy = 1;
+       adapter->vpd_flag = 0;
+       ret = t4_seeprom_wait(adapter);
+       if (ret) {
+               dev_err(adapter, "VPD write of address %#x failed\n", addr);
+               return ret;
+       }
+
+       /* Reset PCI_VPD_DATA register after a transaction and wait for our
+        * request to complete. If it doesn't complete, return error.
+        */
+       t4_os_pci_write_cfg4(adapter, base + PCI_VPD_DATA, 0);
+       max_poll = EEPROM_MAX_POLL;
+       do {
+               udelay(EEPROM_DELAY);
+               t4_seeprom_read(adapter, EEPROM_STAT_ADDR, &stats_reg);
+       } while ((stats_reg & 0x1) && --max_poll);
+       if (!max_poll)
+               return -ETIMEDOUT;
+
+       /* Return success! */
+       return 0;
+}
+
+/**
+ * t4_seeprom_wp - enable/disable EEPROM write protection
+ * @adapter: the adapter
+ * @enable: whether to enable or disable write protection
+ *
+ * Enables or disables write protection on the serial EEPROM.
+ */
+int t4_seeprom_wp(struct adapter *adapter, int enable)
+{
+       return t4_seeprom_write(adapter, EEPROM_STAT_ADDR, enable ? 0xc : 0);
+}
+
 /**
  * t4_config_rss_range - configure a portion of the RSS mapping table
  * @adapter: the adapter
@@ -2384,6 +2563,9 @@ int t4_prep_adapter(struct adapter *adapter)
                return -EINVAL;
        }
 
+       adapter->params.pci.vpd_cap_addr =
+               t4_os_find_pci_capability(adapter, PCI_CAP_ID_VPD);
+
        ret = t4_get_flash_params(adapter);
        if (ret < 0)
                return ret;
index bf623cf..5e62c41 100644 (file)
@@ -1,7 +1,7 @@
 /*-
  *   BSD LICENSE
  *
- *   Copyright(c) 2014-2015 Chelsio Communications.
+ *   Copyright(c) 2014-2016 Chelsio Communications.
  *   All rights reserved.
  *
  *   Redistribution and use in source and binary forms, with or without
@@ -36,6 +36,9 @@
 
 enum {
        NCHAN           = 4,     /* # of HW channels */
+       EEPROMSIZE      = 17408, /* Serial EEPROM physical size */
+       EEPROMVSIZE     = 32768, /* Serial EEPROM virtual address space size */
+       EEPROMPFSIZE    = 1024,  /* EEPROM writable area size for PFn, n>0 */
        NMTUS           = 16,    /* size of MTU table */
        NCCTRL_WIN      = 32,    /* # of congestion control windows */
        MBOX_LEN        = 64,    /* mailbox size in bytes */
index 04eddaf..dbc3800 100644 (file)
@@ -781,6 +781,145 @@ cxgbe_dev_supported_ptypes_get(struct rte_eth_dev *eth_dev)
        return NULL;
 }
 
+static int cxgbe_get_eeprom_length(struct rte_eth_dev *dev)
+{
+       RTE_SET_USED(dev);
+       return EEPROMSIZE;
+}
+
+/**
+ * eeprom_ptov - translate a physical EEPROM address to virtual
+ * @phys_addr: the physical EEPROM address
+ * @fn: the PCI function number
+ * @sz: size of function-specific area
+ *
+ * Translate a physical EEPROM address to virtual.  The first 1K is
+ * accessed through virtual addresses starting at 31K, the rest is
+ * accessed through virtual addresses starting at 0.
+ *
+ * The mapping is as follows:
+ * [0..1K) -> [31K..32K)
+ * [1K..1K+A) -> [31K-A..31K)
+ * [1K+A..ES) -> [0..ES-A-1K)
+ *
+ * where A = @fn * @sz, and ES = EEPROM size.
+ */
+static int eeprom_ptov(unsigned int phys_addr, unsigned int fn, unsigned int sz)
+{
+       fn *= sz;
+       if (phys_addr < 1024)
+               return phys_addr + (31 << 10);
+       if (phys_addr < 1024 + fn)
+               return fn + phys_addr - 1024;
+       if (phys_addr < EEPROMSIZE)
+               return phys_addr - 1024 - fn;
+       if (phys_addr < EEPROMVSIZE)
+               return phys_addr - 1024;
+       return -EINVAL;
+}
+
+/* The next two routines implement eeprom read/write from physical addresses.
+ */
+static int eeprom_rd_phys(struct adapter *adap, unsigned int phys_addr, u32 *v)
+{
+       int vaddr = eeprom_ptov(phys_addr, adap->pf, EEPROMPFSIZE);
+
+       if (vaddr >= 0)
+               vaddr = t4_seeprom_read(adap, vaddr, v);
+       return vaddr < 0 ? vaddr : 0;
+}
+
+static int eeprom_wr_phys(struct adapter *adap, unsigned int phys_addr, u32 v)
+{
+       int vaddr = eeprom_ptov(phys_addr, adap->pf, EEPROMPFSIZE);
+
+       if (vaddr >= 0)
+               vaddr = t4_seeprom_write(adap, vaddr, v);
+       return vaddr < 0 ? vaddr : 0;
+}
+
+#define EEPROM_MAGIC 0x38E2F10C
+
+static int cxgbe_get_eeprom(struct rte_eth_dev *dev,
+                           struct rte_dev_eeprom_info *e)
+{
+       struct port_info *pi = (struct port_info *)(dev->data->dev_private);
+       struct adapter *adapter = pi->adapter;
+       u32 i, err = 0;
+       u8 *buf = rte_zmalloc(NULL, EEPROMSIZE, 0);
+
+       if (!buf)
+               return -ENOMEM;
+
+       e->magic = EEPROM_MAGIC;
+       for (i = e->offset & ~3; !err && i < e->offset + e->length; i += 4)
+               err = eeprom_rd_phys(adapter, i, (u32 *)&buf[i]);
+
+       if (!err)
+               rte_memcpy(e->data, buf + e->offset, e->length);
+       rte_free(buf);
+       return err;
+}
+
+static int cxgbe_set_eeprom(struct rte_eth_dev *dev,
+                           struct rte_dev_eeprom_info *eeprom)
+{
+       struct port_info *pi = (struct port_info *)(dev->data->dev_private);
+       struct adapter *adapter = pi->adapter;
+       u8 *buf;
+       int err = 0;
+       u32 aligned_offset, aligned_len, *p;
+
+       if (eeprom->magic != EEPROM_MAGIC)
+               return -EINVAL;
+
+       aligned_offset = eeprom->offset & ~3;
+       aligned_len = (eeprom->length + (eeprom->offset & 3) + 3) & ~3;
+
+       if (adapter->pf > 0) {
+               u32 start = 1024 + adapter->pf * EEPROMPFSIZE;
+
+               if (aligned_offset < start ||
+                   aligned_offset + aligned_len > start + EEPROMPFSIZE)
+                       return -EPERM;
+       }
+
+       if (aligned_offset != eeprom->offset || aligned_len != eeprom->length) {
+               /* RMW possibly needed for first or last words.
+                */
+               buf = rte_zmalloc(NULL, aligned_len, 0);
+               if (!buf)
+                       return -ENOMEM;
+               err = eeprom_rd_phys(adapter, aligned_offset, (u32 *)buf);
+               if (!err && aligned_len > 4)
+                       err = eeprom_rd_phys(adapter,
+                                            aligned_offset + aligned_len - 4,
+                                            (u32 *)&buf[aligned_len - 4]);
+               if (err)
+                       goto out;
+               rte_memcpy(buf + (eeprom->offset & 3), eeprom->data,
+                          eeprom->length);
+       } else {
+               buf = eeprom->data;
+       }
+
+       err = t4_seeprom_wp(adapter, false);
+       if (err)
+               goto out;
+
+       for (p = (u32 *)buf; !err && aligned_len; aligned_len -= 4, p++) {
+               err = eeprom_wr_phys(adapter, aligned_offset, *p);
+               aligned_offset += 4;
+       }
+
+       if (!err)
+               err = t4_seeprom_wp(adapter, true);
+out:
+       if (buf != eeprom->data)
+               rte_free(buf);
+       return err;
+}
+
 static const struct eth_dev_ops cxgbe_eth_dev_ops = {
        .dev_start              = cxgbe_dev_start,
        .dev_stop               = cxgbe_dev_stop,
@@ -806,6 +945,9 @@ static const struct eth_dev_ops cxgbe_eth_dev_ops = {
        .stats_reset            = cxgbe_dev_stats_reset,
        .flow_ctrl_get          = cxgbe_flow_ctrl_get,
        .flow_ctrl_set          = cxgbe_flow_ctrl_set,
+       .get_eeprom_length      = cxgbe_get_eeprom_length,
+       .get_eeprom             = cxgbe_get_eeprom,
+       .set_eeprom             = cxgbe_set_eeprom,
 };
 
 /*