| // SPDX-License-Identifier: GPL-2.0-only |
| /**************************************************************************** |
| * Driver for Solarflare network controllers and boards |
| * Copyright 2005-2018 Solarflare Communications Inc. |
| * Copyright 2019-2022 Xilinx Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published |
| * by the Free Software Foundation, incorporated herein by reference. |
| */ |
| |
| #include "net_driver.h" |
| #include <linux/module.h> |
| #include "efx_common.h" |
| #include "efx_channels.h" |
| #include "io.h" |
| #include "ef100_nic.h" |
| #include "ef100_netdev.h" |
| #include "ef100_sriov.h" |
| #include "ef100_regs.h" |
| #include "ef100.h" |
| |
| #define EFX_EF100_PCI_DEFAULT_BAR 2 |
| |
| /* Number of bytes at start of vendor specified extended capability that indicate |
| * that the capability is vendor specified. i.e. offset from value returned by |
| * pci_find_next_ext_capability() to beginning of vendor specified capability |
| * header. |
| */ |
| #define PCI_EXT_CAP_HDR_LENGTH 4 |
| |
| /* Expected size of a Xilinx continuation address table entry. */ |
| #define ESE_GZ_CFGBAR_CONT_CAP_MIN_LENGTH 16 |
| |
| struct ef100_func_ctl_window { |
| bool valid; |
| unsigned int bar; |
| u64 offset; |
| }; |
| |
| static int ef100_pci_walk_xilinx_table(struct efx_nic *efx, u64 offset, |
| struct ef100_func_ctl_window *result); |
| |
| /* Number of bytes to offset when reading bit position x with dword accessors. */ |
| #define ROUND_DOWN_TO_DWORD(x) (((x) & (~31)) >> 3) |
| |
| #define EXTRACT_BITS(x, lbn, width) \ |
| (((x) >> ((lbn) & 31)) & ((1ull << (width)) - 1)) |
| |
| static u32 _ef100_pci_get_bar_bits_with_width(struct efx_nic *efx, |
| int structure_start, |
| int lbn, int width) |
| { |
| efx_dword_t dword; |
| |
| efx_readd(efx, &dword, structure_start + ROUND_DOWN_TO_DWORD(lbn)); |
| |
| return EXTRACT_BITS(le32_to_cpu(dword.u32[0]), lbn, width); |
| } |
| |
| #define ef100_pci_get_bar_bits(efx, entry_location, bitdef) \ |
| _ef100_pci_get_bar_bits_with_width(efx, entry_location, \ |
| ESF_GZ_CFGBAR_ ## bitdef ## _LBN, \ |
| ESF_GZ_CFGBAR_ ## bitdef ## _WIDTH) |
| |
| static int ef100_pci_parse_ef100_entry(struct efx_nic *efx, int entry_location, |
| struct ef100_func_ctl_window *result) |
| { |
| u64 offset = ef100_pci_get_bar_bits(efx, entry_location, EF100_FUNC_CTL_WIN_OFF) << |
| ESE_GZ_EF100_FUNC_CTL_WIN_OFF_SHIFT; |
| u32 bar = ef100_pci_get_bar_bits(efx, entry_location, EF100_BAR); |
| |
| netif_dbg(efx, probe, efx->net_dev, |
| "Found EF100 function control window bar=%d offset=0x%llx\n", |
| bar, offset); |
| |
| if (result->valid) { |
| netif_err(efx, probe, efx->net_dev, |
| "Duplicated EF100 table entry.\n"); |
| return -EINVAL; |
| } |
| |
| if (bar == ESE_GZ_CFGBAR_EF100_BAR_NUM_EXPANSION_ROM || |
| bar == ESE_GZ_CFGBAR_EF100_BAR_NUM_INVALID) { |
| netif_err(efx, probe, efx->net_dev, |
| "Bad BAR value of %d in Xilinx capabilities EF100 entry.\n", |
| bar); |
| return -EINVAL; |
| } |
| |
| result->bar = bar; |
| result->offset = offset; |
| result->valid = true; |
| return 0; |
| } |
| |
| static bool ef100_pci_does_bar_overflow(struct efx_nic *efx, int bar, |
| u64 next_entry) |
| { |
| return next_entry + ESE_GZ_CFGBAR_ENTRY_HEADER_SIZE > |
| pci_resource_len(efx->pci_dev, bar); |
| } |
| |
| /* Parse a Xilinx capabilities table entry describing a continuation to a new |
| * sub-table. |
| */ |
| static int ef100_pci_parse_continue_entry(struct efx_nic *efx, int entry_location, |
| struct ef100_func_ctl_window *result) |
| { |
| unsigned int previous_bar; |
| efx_oword_t entry; |
| u64 offset; |
| int rc = 0; |
| u32 bar; |
| |
| efx_reado(efx, &entry, entry_location); |
| |
| bar = EFX_OWORD_FIELD32(entry, ESF_GZ_CFGBAR_CONT_CAP_BAR); |
| |
| offset = EFX_OWORD_FIELD64(entry, ESF_GZ_CFGBAR_CONT_CAP_OFFSET) << |
| ESE_GZ_CONT_CAP_OFFSET_BYTES_SHIFT; |
| |
| previous_bar = efx->mem_bar; |
| |
| if (bar == ESE_GZ_VSEC_BAR_NUM_EXPANSION_ROM || |
| bar == ESE_GZ_VSEC_BAR_NUM_INVALID) { |
| netif_err(efx, probe, efx->net_dev, |
| "Bad BAR value of %d in Xilinx capabilities sub-table.\n", |
| bar); |
| return -EINVAL; |
| } |
| |
| if (bar != previous_bar) { |
| efx_fini_io(efx); |
| |
| if (ef100_pci_does_bar_overflow(efx, bar, offset)) { |
| netif_err(efx, probe, efx->net_dev, |
| "Xilinx table will overrun BAR[%d] offset=0x%llx\n", |
| bar, offset); |
| return -EINVAL; |
| } |
| |
| /* Temporarily map new BAR. */ |
| rc = efx_init_io(efx, bar, |
| (dma_addr_t)DMA_BIT_MASK(ESF_GZ_TX_SEND_ADDR_WIDTH), |
| pci_resource_len(efx->pci_dev, bar)); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Mapping new BAR for Xilinx table failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| rc = ef100_pci_walk_xilinx_table(efx, offset, result); |
| if (rc) |
| return rc; |
| |
| if (bar != previous_bar) { |
| efx_fini_io(efx); |
| |
| /* Put old BAR back. */ |
| rc = efx_init_io(efx, previous_bar, |
| (dma_addr_t)DMA_BIT_MASK(ESF_GZ_TX_SEND_ADDR_WIDTH), |
| pci_resource_len(efx->pci_dev, previous_bar)); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Putting old BAR back failed, rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Iterate over the Xilinx capabilities table in the currently mapped BAR and |
| * call ef100_pci_parse_ef100_entry() on any EF100 entries and |
| * ef100_pci_parse_continue_entry() on any table continuations. |
| */ |
| static int ef100_pci_walk_xilinx_table(struct efx_nic *efx, u64 offset, |
| struct ef100_func_ctl_window *result) |
| { |
| u64 current_entry = offset; |
| int rc = 0; |
| |
| while (true) { |
| u32 id = ef100_pci_get_bar_bits(efx, current_entry, ENTRY_FORMAT); |
| u32 last = ef100_pci_get_bar_bits(efx, current_entry, ENTRY_LAST); |
| u32 rev = ef100_pci_get_bar_bits(efx, current_entry, ENTRY_REV); |
| u32 entry_size; |
| |
| if (id == ESE_GZ_CFGBAR_ENTRY_LAST) |
| return 0; |
| |
| entry_size = ef100_pci_get_bar_bits(efx, current_entry, ENTRY_SIZE); |
| |
| netif_dbg(efx, probe, efx->net_dev, |
| "Seen Xilinx table entry 0x%x size 0x%x at 0x%llx in BAR[%d]\n", |
| id, entry_size, current_entry, efx->mem_bar); |
| |
| if (entry_size < sizeof(u32) * 2) { |
| netif_err(efx, probe, efx->net_dev, |
| "Xilinx table entry too short len=0x%x\n", entry_size); |
| return -EINVAL; |
| } |
| |
| switch (id) { |
| case ESE_GZ_CFGBAR_ENTRY_EF100: |
| if (rev != ESE_GZ_CFGBAR_ENTRY_REV_EF100 || |
| entry_size < ESE_GZ_CFGBAR_ENTRY_SIZE_EF100) { |
| netif_err(efx, probe, efx->net_dev, |
| "Bad length or rev for EF100 entry in Xilinx capabilities table. entry_size=%d rev=%d.\n", |
| entry_size, rev); |
| return -EINVAL; |
| } |
| |
| rc = ef100_pci_parse_ef100_entry(efx, current_entry, |
| result); |
| if (rc) |
| return rc; |
| break; |
| case ESE_GZ_CFGBAR_ENTRY_CONT_CAP_ADDR: |
| if (rev != 0 || entry_size < ESE_GZ_CFGBAR_CONT_CAP_MIN_LENGTH) { |
| netif_err(efx, probe, efx->net_dev, |
| "Bad length or rev for continue entry in Xilinx capabilities table. entry_size=%d rev=%d.\n", |
| entry_size, rev); |
| return -EINVAL; |
| } |
| |
| rc = ef100_pci_parse_continue_entry(efx, current_entry, result); |
| if (rc) |
| return rc; |
| break; |
| default: |
| /* Ignore unknown table entries. */ |
| break; |
| } |
| |
| if (last) |
| return 0; |
| |
| current_entry += entry_size; |
| |
| if (ef100_pci_does_bar_overflow(efx, efx->mem_bar, current_entry)) { |
| netif_err(efx, probe, efx->net_dev, |
| "Xilinx table overrun at position=0x%llx.\n", |
| current_entry); |
| return -EINVAL; |
| } |
| } |
| } |
| |
| static int _ef100_pci_get_config_bits_with_width(struct efx_nic *efx, |
| int structure_start, int lbn, |
| int width, u32 *result) |
| { |
| int rc, pos = structure_start + ROUND_DOWN_TO_DWORD(lbn); |
| u32 temp; |
| |
| rc = pci_read_config_dword(efx->pci_dev, pos, &temp); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read PCI config dword at %d\n", |
| pos); |
| return rc; |
| } |
| |
| *result = EXTRACT_BITS(temp, lbn, width); |
| |
| return 0; |
| } |
| |
| #define ef100_pci_get_config_bits(efx, entry_location, bitdef, result) \ |
| _ef100_pci_get_config_bits_with_width(efx, entry_location, \ |
| ESF_GZ_VSEC_ ## bitdef ## _LBN, \ |
| ESF_GZ_VSEC_ ## bitdef ## _WIDTH, result) |
| |
| /* Call ef100_pci_walk_xilinx_table() for the Xilinx capabilities table pointed |
| * to by this PCI_EXT_CAP_ID_VNDR. |
| */ |
| static int ef100_pci_parse_xilinx_cap(struct efx_nic *efx, int vndr_cap, |
| bool has_offset_hi, |
| struct ef100_func_ctl_window *result) |
| { |
| u32 offset_high = 0; |
| u32 offset_lo = 0; |
| u64 offset = 0; |
| u32 bar = 0; |
| int rc = 0; |
| |
| rc = ef100_pci_get_config_bits(efx, vndr_cap, TBL_BAR, &bar); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read ESF_GZ_VSEC_TBL_BAR, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| if (bar == ESE_GZ_CFGBAR_CONT_CAP_BAR_NUM_EXPANSION_ROM || |
| bar == ESE_GZ_CFGBAR_CONT_CAP_BAR_NUM_INVALID) { |
| netif_err(efx, probe, efx->net_dev, |
| "Bad BAR value of %d in Xilinx capabilities sub-table.\n", |
| bar); |
| return -EINVAL; |
| } |
| |
| rc = ef100_pci_get_config_bits(efx, vndr_cap, TBL_OFF_LO, &offset_lo); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read ESF_GZ_VSEC_TBL_OFF_LO, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| /* Get optional extension to 64bit offset. */ |
| if (has_offset_hi) { |
| rc = ef100_pci_get_config_bits(efx, vndr_cap, TBL_OFF_HI, &offset_high); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read ESF_GZ_VSEC_TBL_OFF_HI, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } |
| |
| offset = (((u64)offset_lo) << ESE_GZ_VSEC_TBL_OFF_LO_BYTES_SHIFT) | |
| (((u64)offset_high) << ESE_GZ_VSEC_TBL_OFF_HI_BYTES_SHIFT); |
| |
| if (offset > pci_resource_len(efx->pci_dev, bar) - sizeof(u32) * 2) { |
| netif_err(efx, probe, efx->net_dev, |
| "Xilinx table will overrun BAR[%d] offset=0x%llx\n", |
| bar, offset); |
| return -EINVAL; |
| } |
| |
| /* Temporarily map BAR. */ |
| rc = efx_init_io(efx, bar, |
| (dma_addr_t)DMA_BIT_MASK(ESF_GZ_TX_SEND_ADDR_WIDTH), |
| pci_resource_len(efx->pci_dev, bar)); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "efx_init_io failed, rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = ef100_pci_walk_xilinx_table(efx, offset, result); |
| |
| /* Unmap temporarily mapped BAR. */ |
| efx_fini_io(efx); |
| return rc; |
| } |
| |
| /* Call ef100_pci_parse_ef100_entry() for each Xilinx PCI_EXT_CAP_ID_VNDR |
| * capability. |
| */ |
| static int ef100_pci_find_func_ctrl_window(struct efx_nic *efx, |
| struct ef100_func_ctl_window *result) |
| { |
| int num_xilinx_caps = 0; |
| int cap = 0; |
| |
| result->valid = false; |
| |
| while ((cap = pci_find_next_ext_capability(efx->pci_dev, cap, PCI_EXT_CAP_ID_VNDR)) != 0) { |
| int vndr_cap = cap + PCI_EXT_CAP_HDR_LENGTH; |
| u32 vsec_ver = 0; |
| u32 vsec_len = 0; |
| u32 vsec_id = 0; |
| int rc = 0; |
| |
| num_xilinx_caps++; |
| |
| rc = ef100_pci_get_config_bits(efx, vndr_cap, ID, &vsec_id); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read ESF_GZ_VSEC_ID, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| rc = ef100_pci_get_config_bits(efx, vndr_cap, VER, &vsec_ver); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read ESF_GZ_VSEC_VER, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| /* Get length of whole capability - i.e. starting at cap */ |
| rc = ef100_pci_get_config_bits(efx, vndr_cap, LEN, &vsec_len); |
| if (rc) { |
| netif_err(efx, probe, efx->net_dev, |
| "Failed to read ESF_GZ_VSEC_LEN, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| if (vsec_id == ESE_GZ_XILINX_VSEC_ID && |
| vsec_ver == ESE_GZ_VSEC_VER_XIL_CFGBAR && |
| vsec_len >= ESE_GZ_VSEC_LEN_MIN) { |
| bool has_offset_hi = (vsec_len >= ESE_GZ_VSEC_LEN_HIGH_OFFT); |
| |
| rc = ef100_pci_parse_xilinx_cap(efx, vndr_cap, |
| has_offset_hi, result); |
| if (rc) |
| return rc; |
| } |
| } |
| |
| if (num_xilinx_caps && !result->valid) { |
| netif_err(efx, probe, efx->net_dev, |
| "Seen %d Xilinx tables, but no EF100 entry.\n", |
| num_xilinx_caps); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| /* Final NIC shutdown |
| * This is called only at module unload (or hotplug removal). A PF can call |
| * this on its VFs to ensure they are unbound first. |
| */ |
| static void ef100_pci_remove(struct pci_dev *pci_dev) |
| { |
| struct efx_nic *efx = pci_get_drvdata(pci_dev); |
| struct efx_probe_data *probe_data; |
| |
| if (!efx) |
| return; |
| |
| probe_data = container_of(efx, struct efx_probe_data, efx); |
| ef100_remove_netdev(probe_data); |
| #ifdef CONFIG_SFC_SRIOV |
| efx_fini_struct_tc(efx); |
| #endif |
| |
| ef100_remove(efx); |
| efx_fini_io(efx); |
| |
| pci_dbg(pci_dev, "shutdown successful\n"); |
| |
| pci_set_drvdata(pci_dev, NULL); |
| efx_fini_struct(efx); |
| kfree(probe_data); |
| }; |
| |
| static int ef100_pci_probe(struct pci_dev *pci_dev, |
| const struct pci_device_id *entry) |
| { |
| struct ef100_func_ctl_window fcw = { 0 }; |
| struct efx_probe_data *probe_data; |
| struct efx_nic *efx; |
| int rc; |
| |
| /* Allocate probe data and struct efx_nic */ |
| probe_data = kzalloc(sizeof(*probe_data), GFP_KERNEL); |
| if (!probe_data) |
| return -ENOMEM; |
| probe_data->pci_dev = pci_dev; |
| efx = &probe_data->efx; |
| |
| efx->type = (const struct efx_nic_type *)entry->driver_data; |
| |
| efx->pci_dev = pci_dev; |
| pci_set_drvdata(pci_dev, efx); |
| rc = efx_init_struct(efx, pci_dev); |
| if (rc) |
| goto fail; |
| |
| efx->vi_stride = EF100_DEFAULT_VI_STRIDE; |
| pci_info(pci_dev, "Solarflare EF100 NIC detected\n"); |
| |
| rc = ef100_pci_find_func_ctrl_window(efx, &fcw); |
| if (rc) { |
| pci_err(pci_dev, |
| "Error looking for ef100 function control window, rc=%d\n", |
| rc); |
| goto fail; |
| } |
| |
| if (!fcw.valid) { |
| /* Extended capability not found - use defaults. */ |
| fcw.bar = EFX_EF100_PCI_DEFAULT_BAR; |
| fcw.offset = 0; |
| fcw.valid = true; |
| } |
| |
| if (fcw.offset > pci_resource_len(efx->pci_dev, fcw.bar) - ESE_GZ_FCW_LEN) { |
| pci_err(pci_dev, "Func control window overruns BAR\n"); |
| rc = -EIO; |
| goto fail; |
| } |
| |
| /* Set up basic I/O (BAR mappings etc) */ |
| rc = efx_init_io(efx, fcw.bar, |
| (dma_addr_t)DMA_BIT_MASK(ESF_GZ_TX_SEND_ADDR_WIDTH), |
| pci_resource_len(efx->pci_dev, fcw.bar)); |
| if (rc) |
| goto fail; |
| |
| efx->reg_base = fcw.offset; |
| |
| rc = efx->type->probe(efx); |
| if (rc) |
| goto fail; |
| |
| efx->state = STATE_PROBED; |
| rc = ef100_probe_netdev(probe_data); |
| if (rc) |
| goto fail; |
| |
| pci_dbg(pci_dev, "initialisation successful\n"); |
| |
| return 0; |
| |
| fail: |
| ef100_pci_remove(pci_dev); |
| return rc; |
| } |
| |
| #ifdef CONFIG_SFC_SRIOV |
| static int ef100_pci_sriov_configure(struct pci_dev *dev, int num_vfs) |
| { |
| struct efx_nic *efx = pci_get_drvdata(dev); |
| int rc; |
| |
| if (efx->type->sriov_configure) { |
| rc = efx->type->sriov_configure(efx, num_vfs); |
| if (rc) |
| return rc; |
| else |
| return num_vfs; |
| } |
| return -ENOENT; |
| } |
| #endif |
| |
| /* PCI device ID table */ |
| static const struct pci_device_id ef100_pci_table[] = { |
| {PCI_DEVICE(PCI_VENDOR_ID_XILINX, 0x0100), /* Riverhead PF */ |
| .driver_data = (unsigned long) &ef100_pf_nic_type }, |
| {PCI_DEVICE(PCI_VENDOR_ID_XILINX, 0x1100), /* Riverhead VF */ |
| .driver_data = (unsigned long) &ef100_vf_nic_type }, |
| {0} /* end of list */ |
| }; |
| |
| struct pci_driver ef100_pci_driver = { |
| .name = "sfc_ef100", |
| .id_table = ef100_pci_table, |
| .probe = ef100_pci_probe, |
| .remove = ef100_pci_remove, |
| #ifdef CONFIG_SFC_SRIOV |
| .sriov_configure = ef100_pci_sriov_configure, |
| #endif |
| .err_handler = &efx_err_handlers, |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, ef100_pci_table); |