// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2013 - 2018 Intel Corporation. */ #include "fm10k_common.h" /** * fm10k_get_bus_info_generic - Generic set PCI bus info * @hw: pointer to hardware structure * * Gets the PCI bus info (speed, width, type) then calls helper function to * store this data within the fm10k_hw structure. **/ s32 fm10k_get_bus_info_generic(struct fm10k_hw *hw) { u16 link_cap, link_status, device_cap, device_control; /* Get the maximum link width and speed from PCIe config space */ link_cap = fm10k_read_pci_cfg_word(hw, FM10K_PCIE_LINK_CAP); switch (link_cap & FM10K_PCIE_LINK_WIDTH) { case FM10K_PCIE_LINK_WIDTH_1: hw->bus_caps.width = fm10k_bus_width_pcie_x1; break; case FM10K_PCIE_LINK_WIDTH_2: hw->bus_caps.width = fm10k_bus_width_pcie_x2; break; case FM10K_PCIE_LINK_WIDTH_4: hw->bus_caps.width = fm10k_bus_width_pcie_x4; break; case FM10K_PCIE_LINK_WIDTH_8: hw->bus_caps.width = fm10k_bus_width_pcie_x8; break; default: hw->bus_caps.width = fm10k_bus_width_unknown; break; } switch (link_cap & FM10K_PCIE_LINK_SPEED) { case FM10K_PCIE_LINK_SPEED_2500: hw->bus_caps.speed = fm10k_bus_speed_2500; break; case FM10K_PCIE_LINK_SPEED_5000: hw->bus_caps.speed = fm10k_bus_speed_5000; break; case FM10K_PCIE_LINK_SPEED_8000: hw->bus_caps.speed = fm10k_bus_speed_8000; break; default: hw->bus_caps.speed = fm10k_bus_speed_unknown; break; } /* Get the PCIe maximum payload size for the PCIe function */ device_cap = fm10k_read_pci_cfg_word(hw, FM10K_PCIE_DEV_CAP); switch (device_cap & FM10K_PCIE_DEV_CAP_PAYLOAD) { case FM10K_PCIE_DEV_CAP_PAYLOAD_128: hw->bus_caps.payload = fm10k_bus_payload_128; break; case FM10K_PCIE_DEV_CAP_PAYLOAD_256: hw->bus_caps.payload = fm10k_bus_payload_256; break; case FM10K_PCIE_DEV_CAP_PAYLOAD_512: hw->bus_caps.payload = fm10k_bus_payload_512; break; default: hw->bus_caps.payload = fm10k_bus_payload_unknown; break; } /* Get the negotiated link width and speed from PCIe config space */ link_status = fm10k_read_pci_cfg_word(hw, FM10K_PCIE_LINK_STATUS); switch (link_status & FM10K_PCIE_LINK_WIDTH) { case FM10K_PCIE_LINK_WIDTH_1: hw->bus.width = fm10k_bus_width_pcie_x1; break; case FM10K_PCIE_LINK_WIDTH_2: hw->bus.width = fm10k_bus_width_pcie_x2; break; case FM10K_PCIE_LINK_WIDTH_4: hw->bus.width = fm10k_bus_width_pcie_x4; break; case FM10K_PCIE_LINK_WIDTH_8: hw->bus.width = fm10k_bus_width_pcie_x8; break; default: hw->bus.width = fm10k_bus_width_unknown; break; } switch (link_status & FM10K_PCIE_LINK_SPEED) { case FM10K_PCIE_LINK_SPEED_2500: hw->bus.speed = fm10k_bus_speed_2500; break; case FM10K_PCIE_LINK_SPEED_5000: hw->bus.speed = fm10k_bus_speed_5000; break; case FM10K_PCIE_LINK_SPEED_8000: hw->bus.speed = fm10k_bus_speed_8000; break; default: hw->bus.speed = fm10k_bus_speed_unknown; break; } /* Get the negotiated PCIe maximum payload size for the PCIe function */ device_control = fm10k_read_pci_cfg_word(hw, FM10K_PCIE_DEV_CTRL); switch (device_control & FM10K_PCIE_DEV_CTRL_PAYLOAD) { case FM10K_PCIE_DEV_CTRL_PAYLOAD_128: hw->bus.payload = fm10k_bus_payload_128; break; case FM10K_PCIE_DEV_CTRL_PAYLOAD_256: hw->bus.payload = fm10k_bus_payload_256; break; case FM10K_PCIE_DEV_CTRL_PAYLOAD_512: hw->bus.payload = fm10k_bus_payload_512; break; default: hw->bus.payload = fm10k_bus_payload_unknown; break; } return 0; } static u16 fm10k_get_pcie_msix_count_generic(struct fm10k_hw *hw) { u16 msix_count; /* read in value from MSI-X capability register */ msix_count = fm10k_read_pci_cfg_word(hw, FM10K_PCI_MSIX_MSG_CTRL); msix_count &= FM10K_PCI_MSIX_MSG_CTRL_TBL_SZ_MASK; /* MSI-X count is zero-based in HW */ msix_count++; if (msix_count > FM10K_MAX_MSIX_VECTORS) msix_count = FM10K_MAX_MSIX_VECTORS; return msix_count; } /** * fm10k_get_invariants_generic - Inits constant values * @hw: pointer to the hardware structure * * Initialize the common invariants for the device. **/ s32 fm10k_get_invariants_generic(struct fm10k_hw *hw) { struct fm10k_mac_info *mac = &hw->mac; /* initialize GLORT state to avoid any false hits */ mac->dglort_map = FM10K_DGLORTMAP_NONE; /* record maximum number of MSI-X vectors */ mac->max_msix_vectors = fm10k_get_pcie_msix_count_generic(hw); return 0; } /** * fm10k_start_hw_generic - Prepare hardware for Tx/Rx * @hw: pointer to hardware structure * * This function sets the Tx ready flag to indicate that the Tx path has * been initialized. **/ s32 fm10k_start_hw_generic(struct fm10k_hw *hw) { /* set flag indicating we are beginning Tx */ hw->mac.tx_ready = true; return 0; } /** * fm10k_disable_queues_generic - Stop Tx/Rx queues * @hw: pointer to hardware structure * @q_cnt: number of queues to be disabled * **/ s32 fm10k_disable_queues_generic(struct fm10k_hw *hw, u16 q_cnt) { u32 reg; u16 i, time; /* clear tx_ready to prevent any false hits for reset */ hw->mac.tx_ready = false; if (FM10K_REMOVED(hw->hw_addr)) return 0; /* clear the enable bit for all rings */ for (i = 0; i < q_cnt; i++) { reg = fm10k_read_reg(hw, FM10K_TXDCTL(i)); fm10k_write_reg(hw, FM10K_TXDCTL(i), reg & ~FM10K_TXDCTL_ENABLE); reg = fm10k_read_reg(hw, FM10K_RXQCTL(i)); fm10k_write_reg(hw, FM10K_RXQCTL(i), reg & ~FM10K_RXQCTL_ENABLE); } fm10k_write_flush(hw); udelay(1); /* loop through all queues to verify that they are all disabled */ for (i = 0, time = FM10K_QUEUE_DISABLE_TIMEOUT; time;) { /* if we are at end of rings all rings are disabled */ if (i == q_cnt) return 0; /* if queue enables cleared, then move to next ring pair */ reg = fm10k_read_reg(hw, FM10K_TXDCTL(i)); if (!~reg || !(reg & FM10K_TXDCTL_ENABLE)) { reg = fm10k_read_reg(hw, FM10K_RXQCTL(i)); if (!~reg || !(reg & FM10K_RXQCTL_ENABLE)) { i++; continue; } } /* decrement time and wait 1 usec */ time--; if (time) udelay(1); } return FM10K_ERR_REQUESTS_PENDING; } /** * fm10k_stop_hw_generic - Stop Tx/Rx units * @hw: pointer to hardware structure * **/ s32 fm10k_stop_hw_generic(struct fm10k_hw *hw) { return fm10k_disable_queues_generic(hw, hw->mac.max_queues); } /** * fm10k_read_hw_stats_32b - Reads value of 32-bit registers * @hw: pointer to the hardware structure * @addr: address of register containing a 32-bit value * @stat: pointer to structure holding hw stat information * * Function reads the content of the register and returns the delta * between the base and the current value. * **/ u32 fm10k_read_hw_stats_32b(struct fm10k_hw *hw, u32 addr, struct fm10k_hw_stat *stat) { u32 delta = fm10k_read_reg(hw, addr) - stat->base_l; if (FM10K_REMOVED(hw->hw_addr)) stat->base_h = 0; return delta; } /** * fm10k_read_hw_stats_48b - Reads value of 48-bit registers * @hw: pointer to the hardware structure * @addr: address of register containing the lower 32-bit value * @stat: pointer to structure holding hw stat information * * Function reads the content of 2 registers, combined to represent a 48-bit * statistical value. Extra processing is required to handle overflowing. * Finally, a delta value is returned representing the difference between the * values stored in registers and values stored in the statistic counters. * **/ static u64 fm10k_read_hw_stats_48b(struct fm10k_hw *hw, u32 addr, struct fm10k_hw_stat *stat) { u32 count_l; u32 count_h; u32 count_tmp; u64 delta; count_h = fm10k_read_reg(hw, addr + 1); /* Check for overflow */ do { count_tmp = count_h; count_l = fm10k_read_reg(hw, addr); count_h = fm10k_read_reg(hw, addr + 1); } while (count_h != count_tmp); delta = ((u64)(count_h - stat->base_h) << 32) + count_l; delta -= stat->base_l; return delta & FM10K_48_BIT_MASK; } /** * fm10k_update_hw_base_48b - Updates 48-bit statistic base value * @stat: pointer to the hardware statistic structure * @delta: value to be updated into the hardware statistic structure * * Function receives a value and determines if an update is required based on * a delta calculation. Only the base value will be updated. **/ static void fm10k_update_hw_base_48b(struct fm10k_hw_stat *stat, u64 delta) { if (!delta) return; /* update lower 32 bits */ delta += stat->base_l; stat->base_l = (u32)delta; /* update upper 32 bits */ stat->base_h += (u32)(delta >> 32); } /** * fm10k_update_hw_stats_tx_q - Updates TX queue statistics counters * @hw: pointer to the hardware structure * @q: pointer to the ring of hardware statistics queue * @idx: index pointing to the start of the ring iteration * * Function updates the TX queue statistics counters that are related to the * hardware. **/ static void fm10k_update_hw_stats_tx_q(struct fm10k_hw *hw, struct fm10k_hw_stats_q *q, u32 idx) { u32 id_tx, id_tx_prev, tx_packets; u64 tx_bytes = 0; /* Retrieve TX Owner Data */ id_tx = fm10k_read_reg(hw, FM10K_TXQCTL(idx)); /* Process TX Ring */ do { tx_packets = fm10k_read_hw_stats_32b(hw, FM10K_QPTC(idx), &q->tx_packets); if (tx_packets) tx_bytes = fm10k_read_hw_stats_48b(hw, FM10K_QBTC_L(idx), &q->tx_bytes); /* Re-Check Owner Data */ id_tx_prev = id_tx; id_tx = fm10k_read_reg(hw, FM10K_TXQCTL(idx)); } while ((id_tx ^ id_tx_prev) & FM10K_TXQCTL_ID_MASK); /* drop non-ID bits and set VALID ID bit */ id_tx &= FM10K_TXQCTL_ID_MASK; id_tx |= FM10K_STAT_VALID; /* update packet counts */ if (q->tx_stats_idx == id_tx) { q->tx_packets.count += tx_packets; q->tx_bytes.count += tx_bytes; } /* update bases and record ID */ fm10k_update_hw_base_32b(&q->tx_packets, tx_packets); fm10k_update_hw_base_48b(&q->tx_bytes, tx_bytes); q->tx_stats_idx = id_tx; } /** * fm10k_update_hw_stats_rx_q - Updates RX queue statistics counters * @hw: pointer to the hardware structure * @q: pointer to the ring of hardware statistics queue * @idx: index pointing to the start of the ring iteration * * Function updates the RX queue statistics counters that are related to the * hardware. **/ static void fm10k_update_hw_stats_rx_q(struct fm10k_hw *hw, struct fm10k_hw_stats_q *q, u32 idx) { u32 id_rx, id_rx_prev, rx_packets, rx_drops; u64 rx_bytes = 0; /* Retrieve RX Owner Data */ id_rx = fm10k_read_reg(hw, FM10K_RXQCTL(idx)); /* Process RX Ring */ do { rx_drops = fm10k_read_hw_stats_32b(hw, FM10K_QPRDC(idx), &q->rx_drops); rx_packets = fm10k_read_hw_stats_32b(hw, FM10K_QPRC(idx), &q->rx_packets); if (rx_packets) rx_bytes = fm10k_read_hw_stats_48b(hw, FM10K_QBRC_L(idx), &q->rx_bytes); /* Re-Check Owner Data */ id_rx_prev = id_rx; id_rx = fm10k_read_reg(hw, FM10K_RXQCTL(idx)); } while ((id_rx ^ id_rx_prev) & FM10K_RXQCTL_ID_MASK); /* drop non-ID bits and set VALID ID bit */ id_rx &= FM10K_RXQCTL_ID_MASK; id_rx |= FM10K_STAT_VALID; /* update packet counts */ if (q->rx_stats_idx == id_rx) { q->rx_drops.count += rx_drops; q->rx_packets.count += rx_packets; q->rx_bytes.count += rx_bytes; } /* update bases and record ID */ fm10k_update_hw_base_32b(&q->rx_drops, rx_drops); fm10k_update_hw_base_32b(&q->rx_packets, rx_packets); fm10k_update_hw_base_48b(&q->rx_bytes, rx_bytes); q->rx_stats_idx = id_rx; } /** * fm10k_update_hw_stats_q - Updates queue statistics counters * @hw: pointer to the hardware structure * @q: pointer to the ring of hardware statistics queue * @idx: index pointing to the start of the ring iteration * @count: number of queues to iterate over * * Function updates the queue statistics counters that are related to the * hardware. **/ void fm10k_update_hw_stats_q(struct fm10k_hw *hw, struct fm10k_hw_stats_q *q, u32 idx, u32 count) { u32 i; for (i = 0; i < count; i++, idx++, q++) { fm10k_update_hw_stats_tx_q(hw, q, idx); fm10k_update_hw_stats_rx_q(hw, q, idx); } } /** * fm10k_unbind_hw_stats_q - Unbind the queue counters from their queues * @q: pointer to the ring of hardware statistics queue * @idx: index pointing to the start of the ring iteration * @count: number of queues to iterate over * * Function invalidates the index values for the queues so any updates that * may have happened are ignored and the base for the queue stats is reset. **/ void fm10k_unbind_hw_stats_q(struct fm10k_hw_stats_q *q, u32 idx, u32 count) { u32 i; for (i = 0; i < count; i++, idx++, q++) { q->rx_stats_idx = 0; q->tx_stats_idx = 0; } } /** * fm10k_get_host_state_generic - Returns the state of the host * @hw: pointer to hardware structure * @host_ready: pointer to boolean value that will record host state * * This function will check the health of the mailbox and Tx queue 0 * in order to determine if we should report that the link is up or not. **/ s32 fm10k_get_host_state_generic(struct fm10k_hw *hw, bool *host_ready) { struct fm10k_mbx_info *mbx = &hw->mbx; struct fm10k_mac_info *mac = &hw->mac; s32 ret_val = 0; u32 txdctl = fm10k_read_reg(hw, FM10K_TXDCTL(0)); /* process upstream mailbox in case interrupts were disabled */ mbx->ops.process(hw, mbx); /* If Tx is no longer enabled link should come down */ if (!(~txdctl) || !(txdctl & FM10K_TXDCTL_ENABLE)) mac->get_host_state = true; /* exit if not checking for link, or link cannot be changed */ if (!mac->get_host_state || !(~txdctl)) goto out; /* if we somehow dropped the Tx enable we should reset */ if (mac->tx_ready && !(txdctl & FM10K_TXDCTL_ENABLE)) { ret_val = FM10K_ERR_RESET_REQUESTED; goto out; } /* if Mailbox timed out we should request reset */ if (!mbx->timeout) { ret_val = FM10K_ERR_RESET_REQUESTED; goto out; } /* verify Mailbox is still open */ if (mbx->state != FM10K_STATE_OPEN) goto out; /* interface cannot receive traffic without logical ports */ if (mac->dglort_map == FM10K_DGLORTMAP_NONE) { if (mac->ops.request_lport_map) ret_val = mac->ops.request_lport_map(hw); goto out; } /* if we passed all the tests above then the switch is ready and we no * longer need to check for link */ mac->get_host_state = false; out: *host_ready = !mac->get_host_state; return ret_val; }