From: Rahul Lakkireddy Date: Fri, 6 May 2016 07:43:18 +0000 (+0530) Subject: net/cxgbe: support EEPROM access X-Git-Tag: spdx-start~6561 X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=fe0bd9ee5da3fd52766458a5d0fa9a8728182be1;p=dpdk.git net/cxgbe: support EEPROM access Add operations to get/set EEPROM data. Signed-off-by: Rahul Lakkireddy Signed-off-by: Kumar Sanghvi --- diff --git a/doc/guides/nics/overview.rst b/doc/guides/nics/overview.rst index 82a7206b6a..a9c38c0e99 100644 --- a/doc/guides/nics/overview.rst +++ b/doc/guides/nics/overview.rst @@ -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 diff --git a/drivers/net/cxgbe/base/adapter.h b/drivers/net/cxgbe/base/adapter.h index 73e7aca6cd..5e3bd509ba 100644 --- a/drivers/net/cxgbe/base/adapter.h +++ b/drivers/net/cxgbe/base/adapter.h @@ -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 diff --git a/drivers/net/cxgbe/base/common.h b/drivers/net/cxgbe/base/common.h index cf2e82dd11..853edd8070 100644 --- a/drivers/net/cxgbe/base/common.h +++ b/drivers/net/cxgbe/base/common.h @@ -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 */ diff --git a/drivers/net/cxgbe/base/t4_hw.c b/drivers/net/cxgbe/base/t4_hw.c index 7882f9af57..ff8594ad05 100644 --- a/drivers/net/cxgbe/base/t4_hw.c +++ b/drivers/net/cxgbe/base/t4_hw.c @@ -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; diff --git a/drivers/net/cxgbe/base/t4_hw.h b/drivers/net/cxgbe/base/t4_hw.h index bf623cf4a2..5e62c4172c 100644 --- a/drivers/net/cxgbe/base/t4_hw.h +++ b/drivers/net/cxgbe/base/t4_hw.h @@ -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 */ diff --git a/drivers/net/cxgbe/cxgbe_ethdev.c b/drivers/net/cxgbe/cxgbe_ethdev.c index 04eddaf756..dbc38002e5 100644 --- a/drivers/net/cxgbe/cxgbe_ethdev.c +++ b/drivers/net/cxgbe/cxgbe_ethdev.c @@ -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, }; /*