Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# Makefile for the Meta(R) Host Network Interface
#
obj-$(CONFIG_FBNIC) += fbnic.o
fbnic-y := fbnic_csr.o \
fbnic_debugfs.o \
fbnic_devlink.o \
fbnic_ethtool.o \
fbnic_fw.o \
fbnic_fw_log.o \
fbnic_hw_stats.o \
fbnic_hwmon.o \
fbnic_irq.o \
fbnic_mac.o \
fbnic_netdev.o \
fbnic_pci.o \
fbnic_phylink.o \
fbnic_rpc.o \
fbnic_mdio.o \
fbnic_time.o \
fbnic_tlv.o \
fbnic_txrx.o \
# End of objects
@@ -0,0 +1,263 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_H_
#define _FBNIC_H_
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include "fbnic_csr.h"
#include "fbnic_fw.h"
#include "fbnic_fw_log.h"
#include "fbnic_hw_stats.h"
#include "fbnic_mac.h"
#include "fbnic_rpc.h"
struct fbnic_napi_vector;
#define FBNIC_MAX_NAPI_VECTORS 128u
#define FBNIC_MBX_CMPL_SLOTS 4
struct fbnic_dev {
struct device *dev;
struct net_device *netdev;
struct dentry *dbg_fbd;
struct device *hwmon;
struct devlink_health_reporter *fw_reporter;
struct devlink_health_reporter *otp_reporter;
u32 __iomem *uc_addr0;
u32 __iomem *uc_addr4;
const struct fbnic_mac *mac;
unsigned int fw_msix_vector;
unsigned int mac_msix_vector;
unsigned short num_irqs;
struct {
u8 users;
char name[IFNAMSIZ + 9];
} napi_irq[FBNIC_MAX_NAPI_VECTORS];
struct delayed_work service_task;
struct fbnic_fw_mbx mbx[FBNIC_IPC_MBX_INDICES];
struct fbnic_fw_cap fw_cap;
struct fbnic_fw_completion *cmpl_data[FBNIC_MBX_CMPL_SLOTS];
/* Lock protecting Tx Mailbox queue to prevent possible races */
spinlock_t fw_tx_lock;
unsigned long last_heartbeat_request;
unsigned long last_heartbeat_response;
u8 fw_heartbeat_enabled;
u64 dsn;
u32 mps;
u32 readrq;
u8 relaxed_ord;
/* Local copy of the devices TCAM */
struct fbnic_act_tcam act_tcam[FBNIC_RPC_TCAM_ACT_NUM_ENTRIES];
struct fbnic_mac_addr mac_addr[FBNIC_RPC_TCAM_MACDA_NUM_ENTRIES];
u8 mac_addr_boundary;
u8 tce_tcam_last;
/* IP TCAM */
struct fbnic_ip_addr ip_src[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
struct fbnic_ip_addr ip_dst[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
struct fbnic_ip_addr ipo_src[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
struct fbnic_ip_addr ipo_dst[FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES];
/* Number of TCQs/RCQs available on hardware */
u16 max_num_queues;
/* Lock protecting writes to @time_high, @time_offset of fbnic_netdev,
* and the HW time CSR machinery.
*/
spinlock_t time_lock;
/* Externally accessible PTP clock, may be NULL */
struct ptp_clock *ptp;
struct ptp_clock_info ptp_info;
/* Last @time_high refresh time in jiffies (to catch stalls) */
unsigned long last_read;
/* PMD specific data */
unsigned long end_of_pmd_training;
u8 pmd_state;
/* Local copy of hardware statistics */
struct fbnic_hw_stats hw_stats;
/* Firmware time since boot in milliseconds */
u64 firmware_time;
u64 prev_firmware_time;
struct fbnic_fw_log fw_log;
/* MDIO bus for PHYs */
struct mii_bus *mdio_bus;
/* In units of ms since API supports values in ms */
u16 ps_timeout;
};
/* Reserve entry 0 in the MSI-X "others" array until we have filled all
* 32 of the possible interrupt slots. By doing this we can avoid any
* potential conflicts should we need to enable one of the debug interrupt
* causes later.
*/
enum {
FBNIC_FW_MSIX_ENTRY,
FBNIC_PCS_MSIX_ENTRY,
FBNIC_NON_NAPI_VECTORS
};
static inline bool fbnic_present(struct fbnic_dev *fbd)
{
return !!READ_ONCE(fbd->uc_addr0);
}
static inline void fbnic_wr32(struct fbnic_dev *fbd, u32 reg, u32 val)
{
u32 __iomem *csr = READ_ONCE(fbd->uc_addr0);
if (csr)
writel(val, csr + reg);
}
u32 fbnic_rd32(struct fbnic_dev *fbd, u32 reg);
static inline void fbnic_wrfl(struct fbnic_dev *fbd)
{
fbnic_rd32(fbd, FBNIC_MASTER_SPARE_0);
}
static inline void
fbnic_rmw32(struct fbnic_dev *fbd, u32 reg, u32 mask, u32 val)
{
u32 v;
v = fbnic_rd32(fbd, reg);
v &= ~mask;
v |= val;
fbnic_wr32(fbd, reg, v);
}
#define wr32(_f, _r, _v) fbnic_wr32(_f, _r, _v)
#define rd32(_f, _r) fbnic_rd32(_f, _r)
#define wrfl(_f) fbnic_wrfl(_f)
bool fbnic_fw_present(struct fbnic_dev *fbd);
u32 fbnic_fw_rd32(struct fbnic_dev *fbd, u32 reg);
void fbnic_fw_wr32(struct fbnic_dev *fbd, u32 reg, u32 val);
#define fw_rd32(_f, _r) fbnic_fw_rd32(_f, _r)
#define fw_wr32(_f, _r, _v) fbnic_fw_wr32(_f, _r, _v)
#define fw_wrfl(_f) fbnic_fw_rd32(_f, FBNIC_FW_ZERO_REG)
static inline bool fbnic_bmc_present(struct fbnic_dev *fbd)
{
return fbd->fw_cap.bmc_present;
}
static inline bool fbnic_init_failure(struct fbnic_dev *fbd)
{
return !fbd->netdev;
}
extern char fbnic_driver_name[];
void fbnic_devlink_free(struct fbnic_dev *fbd);
struct fbnic_dev *fbnic_devlink_alloc(struct pci_dev *pdev);
int fbnic_devlink_health_create(struct fbnic_dev *fbd);
void fbnic_devlink_health_destroy(struct fbnic_dev *fbd);
void fbnic_devlink_register(struct fbnic_dev *fbd);
void fbnic_devlink_unregister(struct fbnic_dev *fbd);
void __printf(2, 3)
fbnic_devlink_fw_report(struct fbnic_dev *fbd, const char *format, ...);
void fbnic_devlink_otp_check(struct fbnic_dev *fbd, const char *msg);
int fbnic_fw_request_mbx(struct fbnic_dev *fbd);
void fbnic_fw_free_mbx(struct fbnic_dev *fbd);
void fbnic_hwmon_register(struct fbnic_dev *fbd);
void fbnic_hwmon_unregister(struct fbnic_dev *fbd);
int fbnic_mac_request_irq(struct fbnic_dev *fbd);
void fbnic_mac_free_irq(struct fbnic_dev *fbd);
void fbnic_napi_name_irqs(struct fbnic_dev *fbd);
int fbnic_napi_request_irq(struct fbnic_dev *fbd,
struct fbnic_napi_vector *nv);
void fbnic_napi_free_irq(struct fbnic_dev *fbd,
struct fbnic_napi_vector *nv);
void fbnic_synchronize_irq(struct fbnic_dev *fbd, int nr);
int fbnic_request_irq(struct fbnic_dev *dev, int nr, irq_handler_t handler,
unsigned long flags, const char *name, void *data);
void fbnic_free_irq(struct fbnic_dev *dev, int nr, void *data);
/**
* enum fbnic_msix_self_test_codes - return codes from self test routines
*
* These are the codes returned from the self test routines and
* stored in the test result array indexed by the specific
* test name.
*
* @FBNIC_TEST_MSIX_SUCCESS: no errors
* @FBNIC_TEST_MSIX_NOMEM: allocation failure
* @FBNIC_TEST_MSIX_IRQ_REQ_FAIL: IRQ request failure
* @FBNIC_TEST_MSIX_MASK: masking failed to prevent IRQ
* @FBNIC_TEST_MSIX_UNMASK: unmasking failure w/ sw status set
* @FBNIC_TEST_MSIX_IRQ_CLEAR: interrupt when clearing mask
* @FBNIC_TEST_MSIX_NO_INTERRUPT: no interrupt when not masked
* @FBNIC_TEST_MSIX_NO_CLEAR_OR_MASK: status not cleared, or mask not set
* @FBNIC_TEST_MSIX_BITS_SET_AFTER_TEST: Bits are set after test
*/
enum fbnic_msix_self_test_codes {
FBNIC_TEST_MSIX_SUCCESS = 0,
FBNIC_TEST_MSIX_NOMEM = 5,
FBNIC_TEST_MSIX_IRQ_REQ_FAIL = 10,
FBNIC_TEST_MSIX_MASK = 20,
FBNIC_TEST_MSIX_UNMASK = 30,
FBNIC_TEST_MSIX_IRQ_CLEAR = 40,
FBNIC_TEST_MSIX_NO_INTERRUPT = 50,
FBNIC_TEST_MSIX_NO_CLEAR_OR_MASK = 60,
FBNIC_TEST_MSIX_BITS_SET_AFTER_TEST = 70,
};
enum fbnic_msix_self_test_codes fbnic_msix_test(struct fbnic_dev *fbd);
void fbnic_free_irqs(struct fbnic_dev *fbd);
int fbnic_alloc_irqs(struct fbnic_dev *fbd);
void fbnic_get_fw_ver_commit_str(struct fbnic_dev *fbd, char *fw_version,
const size_t str_sz);
void fbnic_dbg_fbd_init(struct fbnic_dev *fbd);
void fbnic_dbg_fbd_exit(struct fbnic_dev *fbd);
void fbnic_dbg_init(void);
void fbnic_dbg_exit(void);
void fbnic_rpc_reset_valid_entries(struct fbnic_dev *fbd);
int fbnic_mdiobus_create(struct fbnic_dev *fbd);
void fbnic_csr_get_regs(struct fbnic_dev *fbd, u32 *data, u32 *regs_version);
int fbnic_csr_regs_len(struct fbnic_dev *fbd);
void fbnic_config_txrx_usecs(struct fbnic_napi_vector *nv, u32 arm);
void fbnic_config_rx_frames(struct fbnic_napi_vector *nv);
enum fbnic_boards {
fbnic_board_asic
};
struct fbnic_info {
unsigned int max_num_queues;
unsigned int bar_mask;
};
#endif /* _FBNIC_H_ */
@@ -0,0 +1,277 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include "fbnic.h"
#define FBNIC_BOUNDS(section) { \
.start = FBNIC_CSR_START_##section, \
.end = FBNIC_CSR_END_##section + 1, \
}
struct fbnic_csr_bounds {
u32 start;
u32 end;
};
static const struct fbnic_csr_bounds fbnic_csr_sects[] = {
FBNIC_BOUNDS(INTR),
FBNIC_BOUNDS(INTR_CQ),
FBNIC_BOUNDS(QM_TX),
FBNIC_BOUNDS(QM_RX),
FBNIC_BOUNDS(TCE),
FBNIC_BOUNDS(TCE_RAM),
FBNIC_BOUNDS(TMI),
FBNIC_BOUNDS(PTP),
FBNIC_BOUNDS(RXB),
FBNIC_BOUNDS(RPC),
FBNIC_BOUNDS(FAB),
FBNIC_BOUNDS(MASTER),
FBNIC_BOUNDS(PCS),
FBNIC_BOUNDS(RSFEC),
FBNIC_BOUNDS(MAC_MAC),
FBNIC_BOUNDS(SIG),
FBNIC_BOUNDS(PCIE_SS_COMPHY),
FBNIC_BOUNDS(PUL_USER),
FBNIC_BOUNDS(QUEUE),
FBNIC_BOUNDS(RPC_RAM),
};
#define FBNIC_RPC_TCAM_ACT_DW_PER_ENTRY 14
#define FBNIC_RPC_TCAM_ACT_NUM_ENTRIES 64
#define FBNIC_RPC_TCAM_MACDA_DW_PER_ENTRY 4
#define FBNIC_RPC_TCAM_MACDA_NUM_ENTRIES 32
#define FBNIC_RPC_TCAM_OUTER_IPSRC_DW_PER_ENTRY 9
#define FBNIC_RPC_TCAM_OUTER_IPSRC_NUM_ENTRIES 8
#define FBNIC_RPC_TCAM_OUTER_IPDST_DW_PER_ENTRY 9
#define FBNIC_RPC_TCAM_OUTER_IPDST_NUM_ENTRIES 8
#define FBNIC_RPC_TCAM_IPSRC_DW_PER_ENTRY 9
#define FBNIC_RPC_TCAM_IPSRC_NUM_ENTRIES 8
#define FBNIC_RPC_TCAM_IPDST_DW_PER_ENTRY 9
#define FBNIC_RPC_TCAM_IPDST_NUM_ENTRIES 8
#define FBNIC_RPC_RSS_TBL_DW_PER_ENTRY 2
#define FBNIC_RPC_RSS_TBL_NUM_ENTRIES 256
static void fbnic_csr_get_regs_rpc_ram(struct fbnic_dev *fbd, u32 **data_p)
{
u32 start = FBNIC_CSR_START_RPC_RAM;
u32 end = FBNIC_CSR_END_RPC_RAM;
u32 *data = *data_p;
u32 i, j;
*(data++) = start;
*(data++) = end;
/* FBNIC_RPC_TCAM_ACT */
for (i = 0; i < FBNIC_RPC_TCAM_ACT_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_TCAM_ACT_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_TCAM_ACT(i, j));
}
/* FBNIC_RPC_TCAM_MACDA */
for (i = 0; i < FBNIC_RPC_TCAM_MACDA_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_TCAM_MACDA_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_TCAM_MACDA(i, j));
}
/* FBNIC_RPC_TCAM_OUTER_IPSRC */
for (i = 0; i < FBNIC_RPC_TCAM_OUTER_IPSRC_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_TCAM_OUTER_IPSRC_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_TCAM_OUTER_IPSRC(i, j));
}
/* FBNIC_RPC_TCAM_OUTER_IPDST */
for (i = 0; i < FBNIC_RPC_TCAM_OUTER_IPDST_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_TCAM_OUTER_IPDST_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_TCAM_OUTER_IPDST(i, j));
}
/* FBNIC_RPC_TCAM_IPSRC */
for (i = 0; i < FBNIC_RPC_TCAM_IPSRC_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_TCAM_IPSRC_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_TCAM_IPSRC(i, j));
}
/* FBNIC_RPC_TCAM_IPDST */
for (i = 0; i < FBNIC_RPC_TCAM_IPDST_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_TCAM_IPDST_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_TCAM_IPDST(i, j));
}
/* FBNIC_RPC_RSS_TBL */
for (i = 0; i < FBNIC_RPC_RSS_TBL_NUM_ENTRIES; i++) {
for (j = 0; j < FBNIC_RPC_RSS_TBL_DW_PER_ENTRY; j++)
*(data++) = rd32(fbd, FBNIC_RPC_RSS_TBL(i, j));
}
*data_p = data;
}
void fbnic_csr_get_regs(struct fbnic_dev *fbd, u32 *data, u32 *regs_version)
{
const struct fbnic_csr_bounds *bound;
u32 *start = data;
int i, j;
*regs_version = 1u;
/* Skip RPC_RAM section which cannot be dumped linearly */
for (i = 0, bound = fbnic_csr_sects;
i < ARRAY_SIZE(fbnic_csr_sects) - 1; i++, ++bound) {
*(data++) = bound->start;
*(data++) = bound->end - 1;
for (j = bound->start; j < bound->end; j++)
*(data++) = rd32(fbd, j);
}
/* Dump the RPC_RAM as special case registers */
fbnic_csr_get_regs_rpc_ram(fbd, &data);
WARN_ON(data - start != fbnic_csr_regs_len(fbd));
}
int fbnic_csr_regs_len(struct fbnic_dev *fbd)
{
int i, len = 0;
/* Dump includes start and end information of each section
* which results in an offset of 2
*/
for (i = 0; i < ARRAY_SIZE(fbnic_csr_sects); i++)
len += fbnic_csr_sects[i].end - fbnic_csr_sects[i].start + 2;
return len;
}
/* CSR register test data
*
* The register test will be used to verify hardware is behaving as expected.
*
* The test itself will have us writing to registers that should have no
* side effects due to us resetting after the test has been completed.
* While the test is being run the interface should be offline.
*/
struct fbnic_csr_reg_test_data {
int reg;
u16 reg_offset;
u8 array_len;
u32 read;
u32 write;
};
#define FBNIC_QUEUE_REG_TEST(_name, _read, _write) { \
.reg = FBNIC_QUEUE(0) + FBNIC_QUEUE_##_name, \
.reg_offset = FBNIC_QUEUE_STRIDE, \
.array_len = 64, \
.read = _read, \
.write = _write \
}
static const struct fbnic_csr_reg_test_data pattern_test[] = {
FBNIC_QUEUE_REG_TEST(TWQ0_CTL, FBNIC_QUEUE_TWQ_CTL_RESET,
FBNIC_QUEUE_TWQ_CTL_RESET),
FBNIC_QUEUE_REG_TEST(TWQ0_PTRS, 0, ~0),
FBNIC_QUEUE_REG_TEST(TWQ0_SIZE, FBNIC_QUEUE_TWQ_SIZE_MASK, ~0),
FBNIC_QUEUE_REG_TEST(TWQ0_BAL, FBNIC_QUEUE_BAL_MASK, ~0),
FBNIC_QUEUE_REG_TEST(TWQ0_BAH, ~0, ~0),
FBNIC_QUEUE_REG_TEST(TWQ1_CTL, FBNIC_QUEUE_TWQ_CTL_RESET,
FBNIC_QUEUE_TWQ_CTL_RESET),
FBNIC_QUEUE_REG_TEST(TWQ1_PTRS, 0, ~0),
FBNIC_QUEUE_REG_TEST(TWQ1_SIZE, FBNIC_QUEUE_TWQ_SIZE_MASK, ~0),
FBNIC_QUEUE_REG_TEST(TWQ1_BAL, FBNIC_QUEUE_BAL_MASK, ~0),
FBNIC_QUEUE_REG_TEST(TWQ1_BAH, ~0, ~0),
FBNIC_QUEUE_REG_TEST(TCQ_CTL, FBNIC_QUEUE_TCQ_CTL_RESET,
FBNIC_QUEUE_TCQ_CTL_RESET),
FBNIC_QUEUE_REG_TEST(TCQ_PTRS, 0, ~0),
FBNIC_QUEUE_REG_TEST(TCQ_SIZE, FBNIC_QUEUE_TCQ_SIZE_MASK, ~0),
FBNIC_QUEUE_REG_TEST(TCQ_BAL, FBNIC_QUEUE_BAL_MASK, ~0),
FBNIC_QUEUE_REG_TEST(TCQ_BAH, ~0, ~0),
FBNIC_QUEUE_REG_TEST(RCQ_CTL, FBNIC_QUEUE_RCQ_CTL_RESET,
FBNIC_QUEUE_RCQ_CTL_RESET),
FBNIC_QUEUE_REG_TEST(RCQ_PTRS, 0, ~0),
FBNIC_QUEUE_REG_TEST(RCQ_SIZE, FBNIC_QUEUE_RCQ_SIZE_MASK, ~0),
FBNIC_QUEUE_REG_TEST(RCQ_BAL, FBNIC_QUEUE_BAL_MASK, ~0),
FBNIC_QUEUE_REG_TEST(RCQ_BAH, ~0, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_CTL, FBNIC_QUEUE_BDQ_CTL_RESET,
FBNIC_QUEUE_BDQ_CTL_RESET),
FBNIC_QUEUE_REG_TEST(BDQ_HPQ_PTRS, 0, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_HPQ_SIZE, FBNIC_QUEUE_BDQ_SIZE_MASK, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_HPQ_BAL, FBNIC_QUEUE_BAL_MASK, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_HPQ_BAH, ~0, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_PPQ_PTRS, 0, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_PPQ_SIZE, FBNIC_QUEUE_BDQ_SIZE_MASK, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_PPQ_BAL, FBNIC_QUEUE_BAL_MASK, ~0),
FBNIC_QUEUE_REG_TEST(BDQ_PPQ_BAH, ~0, ~0),
};
static enum fbnic_reg_self_test_codes
fbnic_csr_reg_pattern_test(struct fbnic_dev *fbd, int index,
const struct fbnic_csr_reg_test_data *test_data)
{
static const u32 pattern[] = { ~0, 0x5A5A5A5A, 0xA5A5A5A5, 0};
enum fbnic_reg_self_test_codes reg;
int i;
reg = test_data->reg + test_data->reg_offset * index;
for (i = 0; i < ARRAY_SIZE(pattern); i++) {
u32 val = pattern[i] & test_data->write;
u32 result;
wr32(fbd, reg, val);
result = rd32(fbd, reg);
val &= test_data->read;
if (result == val)
continue;
dev_err(fbd->dev,
"%s: reg 0x%06X failed, expected 0x%08X received 0x%08X\n",
__func__, reg, val, result);
/* Note that FBNIC_INTR_STATUS(0) could be tested and fail
* and the result would not be reported since the register
* offset is 0. However as that register isn't included in
* the register test that isn't an issue.
*/
return reg;
}
return FBNIC_REG_TEST_SUCCESS;
}
/**
* fbnic_csr_regs_test() - Verify behavior of NIC registers
* @fbd: device to test
*
* This function is meant to test the bit values of various registers in
* the NIC device. Specifically this test will verify which bits are
* writable and which ones are not. It will write varying patterns of bits
* to the registers testing for sticky bits, or bits that are writable but
* should not be.
*
* Return: FBNIC_REG_TEST_SUCCESS on success, register number on failure
**/
enum fbnic_reg_self_test_codes fbnic_csr_regs_test(struct fbnic_dev *fbd)
{
const struct fbnic_csr_reg_test_data *test_data;
for (test_data = pattern_test;
test_data < pattern_test + ARRAY_SIZE(pattern_test); test_data++) {
u32 i;
for (i = 0; i < test_data->array_len; i++) {
enum fbnic_reg_self_test_codes reg =
fbnic_csr_reg_pattern_test(fbd, i, test_data);
if (reg)
return reg;
}
}
return FBNIC_REG_TEST_SUCCESS;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,678 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/debugfs.h>
#include <linux/pci.h>
#include <linux/rtnetlink.h>
#include <linux/seq_file.h>
#include "fbnic.h"
#include "fbnic_txrx.h"
static struct dentry *fbnic_dbg_root;
/* Descriptor Seq Functions */
static void fbnic_dbg_desc_break(struct seq_file *s, int i)
{
while (i--)
seq_putc(s, '-');
seq_putc(s, '\n');
}
static void fbnic_dbg_ring_show(struct seq_file *s)
{
struct fbnic_ring *ring = s->private;
unsigned long doorbell_offset;
u32 head = 0, tail = 0;
u32 __iomem *csr_base;
csr_base = fbnic_ring_csr_base(ring);
doorbell_offset = ring->doorbell - csr_base;
seq_printf(s, "doorbell CSR: %#05lx q_idx: %d\n",
doorbell_offset, ring->q_idx);
seq_printf(s, "size_mask: %#06x size: %zu flags: 0x%02x\n",
ring->size_mask, ring->size, ring->flags);
seq_printf(s, "SW: head: %#06x tail: %#06x\n",
ring->head, ring->tail);
switch (doorbell_offset) {
case FBNIC_QUEUE_TWQ0_TAIL:
tail = readl(csr_base + FBNIC_QUEUE_TWQ0_PTRS);
head = FIELD_GET(FBNIC_QUEUE_TWQ_PTRS_HEAD_MASK, tail);
break;
case FBNIC_QUEUE_TWQ1_TAIL:
tail = readl(csr_base + FBNIC_QUEUE_TWQ1_PTRS);
head = FIELD_GET(FBNIC_QUEUE_TWQ_PTRS_HEAD_MASK, tail);
break;
case FBNIC_QUEUE_TCQ_HEAD:
head = readl(csr_base + FBNIC_QUEUE_TCQ_PTRS);
tail = FIELD_GET(FBNIC_QUEUE_TCQ_PTRS_TAIL_MASK, head);
break;
case FBNIC_QUEUE_BDQ_HPQ_TAIL:
tail = readl(csr_base + FBNIC_QUEUE_BDQ_HPQ_PTRS);
head = FIELD_GET(FBNIC_QUEUE_BDQ_PTRS_HEAD_MASK, tail);
break;
case FBNIC_QUEUE_BDQ_PPQ_TAIL:
tail = readl(csr_base + FBNIC_QUEUE_BDQ_PPQ_PTRS);
head = FIELD_GET(FBNIC_QUEUE_BDQ_PTRS_HEAD_MASK, tail);
break;
case FBNIC_QUEUE_RCQ_HEAD:
head = readl(csr_base + FBNIC_QUEUE_RCQ_PTRS);
tail = FIELD_GET(FBNIC_QUEUE_RCQ_PTRS_TAIL_MASK, head);
break;
}
tail &= FBNIC_QUEUE_BDQ_PTRS_TAIL_MASK;
head &= FBNIC_QUEUE_RCQ_PTRS_HEAD_MASK;
seq_printf(s, "HW: head: %#06x tail: %#06x\n", head, tail);
seq_puts(s, "\n");
}
static void fbnic_dbg_twd_desc_seq_show(struct seq_file *s, int i)
{
struct fbnic_ring *ring = s->private;
u64 twd = le64_to_cpu(ring->desc[i]);
switch (FIELD_GET(FBNIC_TWD_TYPE_MASK, twd)) {
case FBNIC_TWD_TYPE_META:
seq_printf(s, "%04x %#06llx %llx %llx %llx %llx %llx %#llx %#llx %llx %#04llx %#04llx %llx %#04llx\n",
i, FIELD_GET(FBNIC_TWD_LEN_MASK, twd),
FIELD_GET(FBNIC_TWD_TYPE_MASK, twd),
FIELD_GET(FBNIC_TWD_FLAG_REQ_COMPLETION, twd),
FIELD_GET(FBNIC_TWD_FLAG_REQ_CSO, twd),
FIELD_GET(FBNIC_TWD_FLAG_REQ_LSO, twd),
FIELD_GET(FBNIC_TWD_FLAG_REQ_TS, twd),
FIELD_GET(FBNIC_TWD_L4_HLEN_MASK, twd),
FIELD_GET(FBNIC_TWD_CSUM_OFFSET_MASK, twd),
FIELD_GET(FBNIC_TWD_L4_TYPE_MASK, twd),
FIELD_GET(FBNIC_TWD_L3_IHLEN_MASK, twd),
FIELD_GET(FBNIC_TWD_L3_OHLEN_MASK, twd),
FIELD_GET(FBNIC_TWD_L3_TYPE_MASK, twd),
FIELD_GET(FBNIC_TWD_L2_HLEN_MASK, twd));
break;
default:
seq_printf(s, "%04x %#06llx %llx %#014llx\n", i,
FIELD_GET(FBNIC_TWD_LEN_MASK, twd),
FIELD_GET(FBNIC_TWD_TYPE_MASK, twd),
FIELD_GET(FBNIC_TWD_ADDR_MASK, twd));
break;
}
}
static int fbnic_dbg_twq_desc_seq_show(struct seq_file *s, void *v)
{
struct fbnic_ring *ring = s->private;
char hdr[80];
int i;
/* Generate header on first entry */
fbnic_dbg_ring_show(s);
snprintf(hdr, sizeof(hdr), "%4s %5s %s %s\n",
"DESC", "LEN/MSS", "T", "METADATA/TIMESTAMP/BUFFER_ADDR");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
/* Display descriptor */
if (!ring->desc) {
seq_puts(s, "Descriptor ring not allocated.\n");
return 0;
}
for (i = 0; i <= ring->size_mask; i++)
fbnic_dbg_twd_desc_seq_show(s, i);
return 0;
}
static int fbnic_dbg_tcq_desc_seq_show(struct seq_file *s, void *v)
{
struct fbnic_ring *ring = s->private;
char hdr[80];
int i;
/* Generate header on first entry */
fbnic_dbg_ring_show(s);
snprintf(hdr, sizeof(hdr), "%4s %s %s %s %5s %-16s %-6s %-6s\n",
"DESC", "D", "T", "Q", "STATUS", "TIMESTAMP", "HEAD1", "HEAD0");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
/* Display descriptor */
if (!ring->desc) {
seq_puts(s, "Descriptor ring not allocated.\n");
return 0;
}
for (i = 0; i <= ring->size_mask; i++) {
u64 tcd = le64_to_cpu(ring->desc[i]);
switch (FIELD_GET(FBNIC_TCD_TYPE_MASK, tcd)) {
case FBNIC_TCD_TYPE_0:
seq_printf(s, "%04x %llx %llx %llx %#05llx %-17s %#06llx %#06llx\n",
i, FIELD_GET(FBNIC_TCD_DONE, tcd),
FIELD_GET(FBNIC_TCD_TYPE_MASK, tcd),
FIELD_GET(FBNIC_TCD_TWQ1, tcd),
FIELD_GET(FBNIC_TCD_STATUS_MASK, tcd),
"",
FIELD_GET(FBNIC_TCD_TYPE0_HEAD1_MASK, tcd),
FIELD_GET(FBNIC_TCD_TYPE0_HEAD0_MASK, tcd));
break;
case FBNIC_TCD_TYPE_1:
seq_printf(s, "%04x %llx %llx %llx %#05llx %#012llx\n",
i, FIELD_GET(FBNIC_TCD_DONE, tcd),
FIELD_GET(FBNIC_TCD_TYPE_MASK, tcd),
FIELD_GET(FBNIC_TCD_TWQ1, tcd),
FIELD_GET(FBNIC_TCD_STATUS_MASK, tcd),
FIELD_GET(FBNIC_TCD_TYPE1_TS_MASK, tcd));
break;
default:
break;
}
}
return 0;
}
static int fbnic_dbg_bdq_desc_seq_show(struct seq_file *s, void *v)
{
struct fbnic_ring *ring = s->private;
char hdr[80];
int i;
/* Generate header on first entry */
fbnic_dbg_ring_show(s);
snprintf(hdr, sizeof(hdr), "%4s %-4s %s\n",
"DESC", "ID", "BUFFER_ADDR");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
/* Display descriptor */
if (!ring->desc) {
seq_puts(s, "Descriptor ring not allocated.\n");
return 0;
}
for (i = 0; i < (ring->size_mask + 1) * FBNIC_BD_FRAG_COUNT; i++) {
u64 bd = le64_to_cpu(ring->desc[i]);
seq_printf(s, "%04x %#04llx %#014llx\n", i,
FIELD_GET(FBNIC_BD_DESC_ID_MASK, bd),
FIELD_GET(FBNIC_BD_DESC_ADDR_MASK, bd));
}
return 0;
}
static void fbnic_dbg_rcd_desc_seq_show(struct seq_file *s, int i)
{
struct fbnic_ring *ring = s->private;
u64 rcd = le64_to_cpu(ring->desc[i]);
switch (FIELD_GET(FBNIC_RCD_TYPE_MASK, rcd)) {
case FBNIC_RCD_TYPE_HDR_AL:
case FBNIC_RCD_TYPE_PAY_AL:
seq_printf(s, "%04x %llx %llx %llx %#06llx %#06llx %#06llx\n",
i, FIELD_GET(FBNIC_RCD_DONE, rcd),
FIELD_GET(FBNIC_RCD_TYPE_MASK, rcd),
FIELD_GET(FBNIC_RCD_AL_PAGE_FIN, rcd),
FIELD_GET(FBNIC_RCD_AL_BUFF_OFF_MASK, rcd),
FIELD_GET(FBNIC_RCD_AL_BUFF_LEN_MASK, rcd),
FIELD_GET(FBNIC_RCD_AL_BUFF_ID_MASK, rcd));
break;
case FBNIC_RCD_TYPE_OPT_META:
seq_printf(s, "%04x %llx %llx %llx %llx %llx %#06llx %#012llx\n",
i, FIELD_GET(FBNIC_RCD_DONE, rcd),
FIELD_GET(FBNIC_RCD_TYPE_MASK, rcd),
FIELD_GET(FBNIC_RCD_OPT_META_TYPE_MASK, rcd),
FIELD_GET(FBNIC_RCD_OPT_META_TS, rcd),
FIELD_GET(FBNIC_RCD_OPT_META_ACTION, rcd),
FIELD_GET(FBNIC_RCD_OPT_META_ACTION_MASK, rcd),
FIELD_GET(FBNIC_RCD_OPT_META_TS_MASK, rcd));
break;
case FBNIC_RCD_TYPE_META:
seq_printf(s, "%04x %llx %llx %llx %llx %llx %llx %llx %llx %llx %#06llx %#010llx\n",
i, FIELD_GET(FBNIC_RCD_DONE, rcd),
FIELD_GET(FBNIC_RCD_TYPE_MASK, rcd),
FIELD_GET(FBNIC_RCD_META_ECN, rcd),
FIELD_GET(FBNIC_RCD_META_L4_CSUM_UNNECESSARY, rcd),
FIELD_GET(FBNIC_RCD_META_ERR_MAC_EOP, rcd),
FIELD_GET(FBNIC_RCD_META_ERR_TRUNCATED_FRAME, rcd),
FIELD_GET(FBNIC_RCD_META_ERR_PARSER, rcd),
FIELD_GET(FBNIC_RCD_META_L4_TYPE_MASK, rcd),
FIELD_GET(FBNIC_RCD_META_L3_TYPE_MASK, rcd),
FIELD_GET(FBNIC_RCD_META_L2_CSUM_MASK, rcd),
FIELD_GET(FBNIC_RCD_META_RSS_HASH_MASK, rcd));
break;
}
}
static int fbnic_dbg_rcq_desc_seq_show(struct seq_file *s, void *v)
{
struct fbnic_ring *ring = s->private;
char hdr[80];
int i;
/* Generate header on first entry */
fbnic_dbg_ring_show(s);
snprintf(hdr, sizeof(hdr),
"%18s %s %s\n", "OFFSET/", "L", "L");
seq_puts(s, hdr);
snprintf(hdr, sizeof(hdr),
"%4s %s %s %s %s %s %s %s %s %s %-8s %s\n",
"DESC", "D", "T", "F", "C", "M", "T", "P", "4", "3", "LEN/CSUM", "ID/TS/RSS");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
/* Display descriptor */
if (!ring->desc) {
seq_puts(s, "Descriptor ring not allocated.\n");
return 0;
}
for (i = 0; i <= ring->size_mask; i++)
fbnic_dbg_rcd_desc_seq_show(s, i);
return 0;
}
static int fbnic_dbg_desc_open(struct inode *inode, struct file *file)
{
struct fbnic_ring *ring = inode->i_private;
int (*show)(struct seq_file *s, void *v);
switch (ring->doorbell - fbnic_ring_csr_base(ring)) {
case FBNIC_QUEUE_TWQ0_TAIL:
case FBNIC_QUEUE_TWQ1_TAIL:
show = fbnic_dbg_twq_desc_seq_show;
break;
case FBNIC_QUEUE_TCQ_HEAD:
show = fbnic_dbg_tcq_desc_seq_show;
break;
case FBNIC_QUEUE_BDQ_HPQ_TAIL:
case FBNIC_QUEUE_BDQ_PPQ_TAIL:
show = fbnic_dbg_bdq_desc_seq_show;
break;
case FBNIC_QUEUE_RCQ_HEAD:
show = fbnic_dbg_rcq_desc_seq_show;
break;
default:
return -EINVAL;
}
return single_open(file, show, ring);
}
static const struct file_operations fbnic_dbg_desc_fops = {
.owner = THIS_MODULE,
.open = fbnic_dbg_desc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
void fbnic_dbg_nv_init(struct fbnic_napi_vector *nv)
{
struct fbnic_dev *fbd = nv->fbd;
char name[16];
int i, j;
/* Generate a folder for each napi vector */
snprintf(name, sizeof(name), "nv.%03d", nv->v_idx);
nv->dbg_nv = debugfs_create_dir(name, fbd->dbg_fbd);
/* Generate a file for each Tx ring in the napi vector */
for (i = 0; i < nv->txt_count; i++) {
struct fbnic_q_triad *qt = &nv->qt[i];
unsigned int hw_idx;
hw_idx = fbnic_ring_csr_base(&qt->cmpl) -
&fbd->uc_addr0[FBNIC_QUEUE(0)];
hw_idx /= FBNIC_QUEUE_STRIDE;
snprintf(name, sizeof(name), "twq0.%03d", hw_idx);
debugfs_create_file(name, 0400, nv->dbg_nv, &qt->sub0,
&fbnic_dbg_desc_fops);
snprintf(name, sizeof(name), "twq1.%03d", hw_idx);
debugfs_create_file(name, 0400, nv->dbg_nv, &qt->sub1,
&fbnic_dbg_desc_fops);
snprintf(name, sizeof(name), "tcq.%03d", hw_idx);
debugfs_create_file(name, 0400, nv->dbg_nv, &qt->cmpl,
&fbnic_dbg_desc_fops);
}
/* Generate a file for each Rx ring in the napi vector */
for (j = 0; j < nv->rxt_count; j++, i++) {
struct fbnic_q_triad *qt = &nv->qt[i];
unsigned int hw_idx;
hw_idx = fbnic_ring_csr_base(&qt->cmpl) -
&fbd->uc_addr0[FBNIC_QUEUE(0)];
hw_idx /= FBNIC_QUEUE_STRIDE;
snprintf(name, sizeof(name), "hpq.%03d", hw_idx);
debugfs_create_file(name, 0400, nv->dbg_nv, &qt->sub0,
&fbnic_dbg_desc_fops);
snprintf(name, sizeof(name), "ppq.%03d", hw_idx);
debugfs_create_file(name, 0400, nv->dbg_nv, &qt->sub1,
&fbnic_dbg_desc_fops);
snprintf(name, sizeof(name), "rcq.%03d", hw_idx);
debugfs_create_file(name, 0400, nv->dbg_nv, &qt->cmpl,
&fbnic_dbg_desc_fops);
}
}
void fbnic_dbg_nv_exit(struct fbnic_napi_vector *nv)
{
debugfs_remove_recursive(nv->dbg_nv);
nv->dbg_nv = NULL;
}
static int fbnic_dbg_mac_addr_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
char hdr[80];
int i;
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-17s %s\n",
"Idx", "S", "TCAM Bitmap", "Addr/Mask");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < FBNIC_RPC_TCAM_MACDA_NUM_ENTRIES; i++) {
struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[i];
seq_printf(s, "%02d %d %64pb %pm\n",
i, mac_addr->state, mac_addr->act_tcam,
mac_addr->value.addr8);
seq_printf(s, " %pm\n",
mac_addr->mask.addr8);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_mac_addr);
static int fbnic_dbg_tce_tcam_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
int i, tcam_idx = 0;
char hdr[80];
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-17s %s\n",
"Idx", "S", "TCAM Bitmap", "Addr/Mask");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < ARRAY_SIZE(fbd->mac_addr); i++) {
struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[i];
/* Verify BMC bit is set */
if (!test_bit(FBNIC_MAC_ADDR_T_BMC, mac_addr->act_tcam))
continue;
if (tcam_idx == FBNIC_TCE_TCAM_NUM_ENTRIES)
break;
seq_printf(s, "%02d %d %64pb %pm\n",
tcam_idx, mac_addr->state, mac_addr->act_tcam,
mac_addr->value.addr8);
seq_printf(s, " %pm\n",
mac_addr->mask.addr8);
tcam_idx++;
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_tce_tcam);
static int fbnic_dbg_act_tcam_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
char hdr[80];
int i;
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-55s %-4s %s\n",
"Idx", "S", "Value/Mask", "RSS", "Dest");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < FBNIC_RPC_TCAM_ACT_NUM_ENTRIES; i++) {
struct fbnic_act_tcam *act_tcam = &fbd->act_tcam[i];
seq_printf(s, "%02d %d %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %08x\n",
i, act_tcam->state,
act_tcam->value.tcam[10], act_tcam->value.tcam[9],
act_tcam->value.tcam[8], act_tcam->value.tcam[7],
act_tcam->value.tcam[6], act_tcam->value.tcam[5],
act_tcam->value.tcam[4], act_tcam->value.tcam[3],
act_tcam->value.tcam[2], act_tcam->value.tcam[1],
act_tcam->value.tcam[0], act_tcam->rss_en_mask,
act_tcam->dest);
seq_printf(s, " %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x\n",
act_tcam->mask.tcam[10], act_tcam->mask.tcam[9],
act_tcam->mask.tcam[8], act_tcam->mask.tcam[7],
act_tcam->mask.tcam[6], act_tcam->mask.tcam[5],
act_tcam->mask.tcam[4], act_tcam->mask.tcam[3],
act_tcam->mask.tcam[2], act_tcam->mask.tcam[1],
act_tcam->mask.tcam[0]);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_act_tcam);
static int fbnic_dbg_ip_addr_show(struct seq_file *s,
struct fbnic_ip_addr *ip_addr)
{
char hdr[80];
int i;
/* Generate Header */
snprintf(hdr, sizeof(hdr), "%3s %s %-17s %s %s\n",
"Idx", "S", "TCAM Bitmap", "V", "Addr/Mask");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES; i++, ip_addr++) {
seq_printf(s, "%02d %d %64pb %d %pi6\n",
i, ip_addr->state, ip_addr->act_tcam,
ip_addr->version, &ip_addr->value);
seq_printf(s, " %pi6\n",
&ip_addr->mask);
}
return 0;
}
static int fbnic_dbg_ip_src_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ip_src);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ip_src);
static int fbnic_dbg_ip_dst_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ip_dst);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ip_dst);
static int fbnic_dbg_ipo_src_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ipo_src);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ipo_src);
static int fbnic_dbg_ipo_dst_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
return fbnic_dbg_ip_addr_show(s, fbd->ipo_dst);
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_ipo_dst);
static void fbnic_dbg_fw_mbx_display(struct seq_file *s,
struct fbnic_dev *fbd, int mbx_idx)
{
struct fbnic_fw_mbx *mbx = &fbd->mbx[mbx_idx];
char hdr[80];
int i;
/* Generate header */
seq_puts(s, mbx_idx == FBNIC_IPC_MBX_RX_IDX ? "Rx\n" : "Tx\n");
seq_printf(s, "Rdy: %d Head: %d Tail: %d\n",
mbx->ready, mbx->head, mbx->tail);
snprintf(hdr, sizeof(hdr), "%3s %-4s %s %-12s %s %-3s %-16s\n",
"Idx", "Len", "E", "Addr", "F", "H", "Raw");
seq_puts(s, hdr);
fbnic_dbg_desc_break(s, strnlen(hdr, sizeof(hdr)));
for (i = 0; i < FBNIC_IPC_MBX_DESC_LEN; i++) {
u64 desc = __fbnic_mbx_rd_desc(fbd, mbx_idx, i);
seq_printf(s, "%-3.2d %04lld %d %012llx %d %-3d %016llx\n",
i, FIELD_GET(FBNIC_IPC_MBX_DESC_LEN_MASK, desc),
!!(desc & FBNIC_IPC_MBX_DESC_EOM),
desc & FBNIC_IPC_MBX_DESC_ADDR_MASK,
!!(desc & FBNIC_IPC_MBX_DESC_FW_CMPL),
!!(desc & FBNIC_IPC_MBX_DESC_HOST_CMPL),
desc);
}
}
static int fbnic_dbg_fw_mbx_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
fbnic_dbg_fw_mbx_display(s, fbd, FBNIC_IPC_MBX_RX_IDX);
/* Add blank line between Rx and Tx */
seq_puts(s, "\n");
fbnic_dbg_fw_mbx_display(s, fbd, FBNIC_IPC_MBX_TX_IDX);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_fw_mbx);
static int fbnic_dbg_fw_log_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
struct fbnic_fw_log_entry *entry;
unsigned long flags;
if (!fbnic_fw_log_ready(fbd))
return -ENXIO;
spin_lock_irqsave(&fbd->fw_log.lock, flags);
list_for_each_entry_reverse(entry, &fbd->fw_log.entries, list) {
seq_printf(s, FBNIC_FW_LOG_FMT, entry->index,
(entry->timestamp / (MSEC_PER_SEC * 60 * 60 * 24)),
(entry->timestamp / (MSEC_PER_SEC * 60 * 60)) % 24,
((entry->timestamp / (MSEC_PER_SEC * 60) % 60)),
((entry->timestamp / MSEC_PER_SEC) % 60),
(entry->timestamp % MSEC_PER_SEC),
entry->msg);
}
spin_unlock_irqrestore(&fbd->fw_log.lock, flags);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_fw_log);
static int fbnic_dbg_pcie_stats_show(struct seq_file *s, void *v)
{
struct fbnic_dev *fbd = s->private;
rtnl_lock();
fbnic_get_hw_stats(fbd);
seq_printf(s, "ob_rd_tlp: %llu\n", fbd->hw_stats.pcie.ob_rd_tlp.value);
seq_printf(s, "ob_rd_dword: %llu\n",
fbd->hw_stats.pcie.ob_rd_dword.value);
seq_printf(s, "ob_wr_tlp: %llu\n", fbd->hw_stats.pcie.ob_wr_tlp.value);
seq_printf(s, "ob_wr_dword: %llu\n",
fbd->hw_stats.pcie.ob_wr_dword.value);
seq_printf(s, "ob_cpl_tlp: %llu\n",
fbd->hw_stats.pcie.ob_cpl_tlp.value);
seq_printf(s, "ob_cpl_dword: %llu\n",
fbd->hw_stats.pcie.ob_cpl_dword.value);
seq_printf(s, "ob_rd_no_tag: %llu\n",
fbd->hw_stats.pcie.ob_rd_no_tag.value);
seq_printf(s, "ob_rd_no_cpl_cred: %llu\n",
fbd->hw_stats.pcie.ob_rd_no_cpl_cred.value);
seq_printf(s, "ob_rd_no_np_cred: %llu\n",
fbd->hw_stats.pcie.ob_rd_no_np_cred.value);
rtnl_unlock();
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fbnic_dbg_pcie_stats);
void fbnic_dbg_fbd_init(struct fbnic_dev *fbd)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
const char *name = pci_name(pdev);
fbd->dbg_fbd = debugfs_create_dir(name, fbnic_dbg_root);
debugfs_create_file("pcie_stats", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_pcie_stats_fops);
debugfs_create_file("mac_addr", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_mac_addr_fops);
debugfs_create_file("tce_tcam", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_tce_tcam_fops);
debugfs_create_file("act_tcam", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_act_tcam_fops);
debugfs_create_file("ip_src", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ip_src_fops);
debugfs_create_file("ip_dst", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ip_dst_fops);
debugfs_create_file("ipo_src", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ipo_src_fops);
debugfs_create_file("ipo_dst", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_ipo_dst_fops);
debugfs_create_file("fw_mbx", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_fw_mbx_fops);
debugfs_create_file("fw_log", 0400, fbd->dbg_fbd, fbd,
&fbnic_dbg_fw_log_fops);
}
void fbnic_dbg_fbd_exit(struct fbnic_dev *fbd)
{
debugfs_remove_recursive(fbd->dbg_fbd);
fbd->dbg_fbd = NULL;
}
void fbnic_dbg_init(void)
{
fbnic_dbg_root = debugfs_create_dir(fbnic_driver_name, NULL);
}
void fbnic_dbg_exit(void)
{
debugfs_remove_recursive(fbnic_dbg_root);
fbnic_dbg_root = NULL;
}
@@ -0,0 +1,669 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/unaligned.h>
#include <linux/pci.h>
#include <linux/pldmfw.h>
#include <linux/types.h>
#include <net/devlink.h>
#include "fbnic.h"
#include "fbnic_fw.h"
#include "fbnic_tlv.h"
#define FBNIC_SN_STR_LEN 24
static int fbnic_version_running_put(struct devlink_info_req *req,
struct fbnic_fw_ver *fw_ver,
char *ver_name)
{
char running_ver[FBNIC_FW_VER_MAX_SIZE];
int err;
fbnic_mk_fw_ver_str(fw_ver->version, running_ver);
err = devlink_info_version_running_put(req, ver_name, running_ver);
if (err)
return err;
if (strlen(fw_ver->commit) > 0) {
char commit_name[FBNIC_SN_STR_LEN];
snprintf(commit_name, FBNIC_SN_STR_LEN, "%s.commit", ver_name);
err = devlink_info_version_running_put(req, commit_name,
fw_ver->commit);
if (err)
return err;
}
return 0;
}
static int fbnic_version_stored_put(struct devlink_info_req *req,
struct fbnic_fw_ver *fw_ver,
char *ver_name)
{
char stored_ver[FBNIC_FW_VER_MAX_SIZE];
int err;
fbnic_mk_fw_ver_str(fw_ver->version, stored_ver);
err = devlink_info_version_stored_put(req, ver_name, stored_ver);
if (err)
return err;
if (strlen(fw_ver->commit) > 0) {
char commit_name[FBNIC_SN_STR_LEN];
snprintf(commit_name, FBNIC_SN_STR_LEN, "%s.commit", ver_name);
err = devlink_info_version_stored_put(req, commit_name,
fw_ver->commit);
if (err)
return err;
}
return 0;
}
static int fbnic_devlink_info_get(struct devlink *devlink,
struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_priv(devlink);
int err;
err = fbnic_version_running_put(req, &fbd->fw_cap.running.mgmt,
DEVLINK_INFO_VERSION_GENERIC_FW);
if (err)
return err;
err = fbnic_version_running_put(req, &fbd->fw_cap.running.bootloader,
DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER);
if (err)
return err;
err = fbnic_version_stored_put(req, &fbd->fw_cap.stored.mgmt,
DEVLINK_INFO_VERSION_GENERIC_FW);
if (err)
return err;
err = fbnic_version_stored_put(req, &fbd->fw_cap.stored.bootloader,
DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER);
if (err)
return err;
err = fbnic_version_stored_put(req, &fbd->fw_cap.stored.undi,
DEVLINK_INFO_VERSION_GENERIC_FW_UNDI);
if (err)
return err;
if (fbd->dsn) {
unsigned char serial[FBNIC_SN_STR_LEN];
u8 dsn[8];
put_unaligned_be64(fbd->dsn, dsn);
err = snprintf(serial, FBNIC_SN_STR_LEN, "%8phD", dsn);
if (err < 0)
return err;
err = devlink_info_serial_number_put(req, serial);
if (err)
return err;
}
return 0;
}
static bool
fbnic_pldm_match_record(struct pldmfw *context, struct pldmfw_record *record)
{
struct pldmfw_desc_tlv *desc;
u32 anti_rollback_ver = 0;
struct devlink *devlink;
struct fbnic_dev *fbd;
struct pci_dev *pdev;
/* First, use the standard PCI matching function */
if (!pldmfw_op_pci_match_record(context, record))
return false;
pdev = to_pci_dev(context->dev);
fbd = pci_get_drvdata(pdev);
devlink = priv_to_devlink(fbd);
/* If PCI match is successful, check for vendor-specific descriptors */
list_for_each_entry(desc, &record->descs, entry) {
if (desc->type != PLDM_DESC_ID_VENDOR_DEFINED)
continue;
if (desc->size < 21 || desc->data[0] != 1 ||
desc->data[1] != 15)
continue;
if (memcmp(desc->data + 2, "AntiRollbackVer", 15) != 0)
continue;
anti_rollback_ver = get_unaligned_le32(desc->data + 17);
break;
}
/* Compare versions and return error if they do not match */
if (anti_rollback_ver < fbd->fw_cap.anti_rollback_version) {
char buf[128];
snprintf(buf, sizeof(buf),
"New firmware anti-rollback version (0x%x) is older than device version (0x%x)!",
anti_rollback_ver, fbd->fw_cap.anti_rollback_version);
devlink_flash_update_status_notify(devlink, buf,
"Anti-Rollback", 0, 0);
return false;
}
return true;
}
static int
fbnic_flash_start(struct fbnic_dev *fbd, struct pldmfw_component *component)
{
struct fbnic_fw_completion *cmpl;
int err;
cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ);
if (!cmpl)
return -ENOMEM;
err = fbnic_fw_xmit_fw_start_upgrade(fbd, cmpl,
component->identifier,
component->component_size);
if (err)
goto cmpl_free;
/* Wait for firmware to ack firmware upgrade start */
if (fbnic_mbx_wait_for_cmpl(cmpl))
err = cmpl->result;
else
err = -ETIMEDOUT;
fbnic_mbx_clear_cmpl(fbd, cmpl);
cmpl_free:
fbnic_fw_put_cmpl(cmpl);
return err;
}
static int
fbnic_flash_component(struct pldmfw *context,
struct pldmfw_component *component)
{
const u8 *data = component->component_data;
const u32 size = component->component_size;
struct fbnic_fw_completion *cmpl;
const char *component_name;
struct devlink *devlink;
struct fbnic_dev *fbd;
struct pci_dev *pdev;
u32 offset = 0;
u32 length = 0;
char buf[32];
int err;
pdev = to_pci_dev(context->dev);
fbd = pci_get_drvdata(pdev);
devlink = priv_to_devlink(fbd);
switch (component->identifier) {
case QSPI_SECTION_CMRT:
component_name = "boot1";
break;
case QSPI_SECTION_CONTROL_FW:
component_name = "boot2";
break;
case QSPI_SECTION_OPTION_ROM:
component_name = "option-rom";
break;
default:
snprintf(buf, sizeof(buf), "Unknown component ID %u!",
component->identifier);
devlink_flash_update_status_notify(devlink, buf, NULL, 0,
size);
return -EINVAL;
}
/* Once firmware receives the request to start upgrading it responds
* with two messages:
* 1. An ACK that it received the message and possible error code
* indicating that an upgrade is not currently possible.
* 2. A request for the first chunk of data
*
* Setup completions for write before issuing the start message so
* the driver can catch both messages.
*/
cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ);
if (!cmpl)
return -ENOMEM;
err = fbnic_mbx_set_cmpl(fbd, cmpl);
if (err)
goto cmpl_free;
devlink_flash_update_timeout_notify(devlink, "Initializing",
component_name, 15);
err = fbnic_flash_start(fbd, component);
if (err)
goto err_no_msg;
while (offset < size) {
if (!fbnic_mbx_wait_for_cmpl(cmpl)) {
err = -ETIMEDOUT;
break;
}
err = cmpl->result;
if (err)
break;
/* Verify firmware is requesting the next chunk in the seq. */
if (cmpl->u.fw_update.offset != offset + length) {
err = -EFAULT;
break;
}
offset = cmpl->u.fw_update.offset;
length = cmpl->u.fw_update.length;
if (length > TLV_MAX_DATA || offset + length > size) {
err = -EFAULT;
break;
}
devlink_flash_update_status_notify(devlink, "Flashing",
component_name,
offset, size);
/* Mailbox will set length to 0 once it receives the finish
* message.
*/
if (!length)
continue;
reinit_completion(&cmpl->done);
err = fbnic_fw_xmit_fw_write_chunk(fbd, data, offset, length,
0);
if (err)
break;
}
if (err) {
fbnic_fw_xmit_fw_write_chunk(fbd, NULL, 0, 0, err);
err_no_msg:
snprintf(buf, sizeof(buf), "Mailbox encountered error %d!",
err);
devlink_flash_update_status_notify(devlink, buf,
component_name, 0, 0);
}
fbnic_mbx_clear_cmpl(fbd, cmpl);
cmpl_free:
fbnic_fw_put_cmpl(cmpl);
return err;
}
static const struct pldmfw_ops fbnic_pldmfw_ops = {
.match_record = fbnic_pldm_match_record,
.flash_component = fbnic_flash_component,
};
static int
fbnic_devlink_flash_update(struct devlink *devlink,
struct devlink_flash_update_params *params,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_priv(devlink);
const struct firmware *fw = params->fw;
struct device *dev = fbd->dev;
struct pldmfw context;
char *err_msg;
int err;
context.ops = &fbnic_pldmfw_ops;
context.dev = dev;
err = pldmfw_flash_image(&context, fw);
if (err) {
switch (err) {
case -EINVAL:
err_msg = "Invalid image";
break;
case -EOPNOTSUPP:
err_msg = "Unsupported image";
break;
case -ENOMEM:
err_msg = "Out of memory";
break;
case -EFAULT:
err_msg = "Invalid header";
break;
case -ENOENT:
err_msg = "No matching record";
break;
case -ENODEV:
err_msg = "No matching device";
break;
case -ETIMEDOUT:
err_msg = "Timed out waiting for reply";
break;
default:
err_msg = "Unknown error";
break;
}
NL_SET_ERR_MSG_FMT_MOD(extack,
"Failed to flash PLDM Image: %s (error: %d)",
err_msg, err);
}
return err;
}
static const struct devlink_ops fbnic_devlink_ops = {
.info_get = fbnic_devlink_info_get,
.flash_update = fbnic_devlink_flash_update,
};
static int fbnic_fw_reporter_dump(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg, void *priv_ctx,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_health_reporter_priv(reporter);
u32 offset, index, index_count, length, size;
struct fbnic_fw_completion *fw_cmpl;
u8 *dump_data, **data;
int err;
fw_cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_COREDUMP_GET_INFO_RESP);
if (!fw_cmpl)
return -ENOMEM;
err = fbnic_fw_xmit_coredump_info_msg(fbd, fw_cmpl, true);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Failed to transmit core dump info msg");
goto cmpl_free;
}
if (!fbnic_mbx_wait_for_cmpl(fw_cmpl)) {
NL_SET_ERR_MSG_MOD(extack,
"Timed out waiting on core dump info");
err = -ETIMEDOUT;
goto cmpl_cleanup;
}
size = fw_cmpl->u.coredump_info.size;
err = fw_cmpl->result;
fbnic_mbx_clear_cmpl(fbd, fw_cmpl);
fbnic_fw_put_cmpl(fw_cmpl);
/* Handle error returned by firmware */
if (err) {
NL_SET_ERR_MSG_MOD(extack, "Firmware core dump returned error");
return err;
}
if (!size) {
NL_SET_ERR_MSG_MOD(extack,
"Firmware core dump returned size 0");
return -EIO;
}
/* Read the dump, we can only transfer TLV_MAX_DATA at a time */
index_count = DIV_ROUND_UP(size, TLV_MAX_DATA);
fw_cmpl = __fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_COREDUMP_READ_RESP,
sizeof(void *) * index_count + size);
if (!fw_cmpl)
return -ENOMEM;
/* Populate pointer table w/ pointer offsets */
dump_data = (void *)&fw_cmpl->u.coredump.data[index_count];
data = fw_cmpl->u.coredump.data;
fw_cmpl->u.coredump.size = size;
fw_cmpl->u.coredump.stride = TLV_MAX_DATA;
for (index = 0; index < index_count; index++) {
/* First iteration installs completion */
struct fbnic_fw_completion *cmpl_arg = index ? NULL : fw_cmpl;
offset = index * TLV_MAX_DATA;
length = min(size - offset, TLV_MAX_DATA);
data[index] = dump_data + offset;
err = fbnic_fw_xmit_coredump_read_msg(fbd, cmpl_arg,
offset, length);
if (err) {
NL_SET_ERR_MSG_MOD(extack,
"Failed to transmit core dump msg");
if (cmpl_arg)
goto cmpl_free;
else
goto cmpl_cleanup;
}
if (fbnic_mbx_wait_for_cmpl(fw_cmpl)) {
reinit_completion(&fw_cmpl->done);
} else {
NL_SET_ERR_MSG_FMT_MOD(extack,
"Timed out waiting on core dump (%d/%d)",
index + 1, index_count);
err = -ETIMEDOUT;
goto cmpl_cleanup;
}
/* If we didn't see the reply record as incomplete */
if (fw_cmpl->u.coredump.data[index]) {
NL_SET_ERR_MSG_FMT_MOD(extack,
"No data for core dump chunk (%d/%d)",
index + 1, index_count);
err = -EIO;
goto cmpl_cleanup;
}
}
devlink_fmsg_binary_pair_nest_start(fmsg, "FW coredump");
for (offset = 0; offset < size; offset += length) {
length = min_t(u32, size - offset, TLV_MAX_DATA);
devlink_fmsg_binary_put(fmsg, dump_data + offset, length);
}
devlink_fmsg_binary_pair_nest_end(fmsg);
cmpl_cleanup:
fbnic_mbx_clear_cmpl(fbd, fw_cmpl);
cmpl_free:
fbnic_fw_put_cmpl(fw_cmpl);
return err;
}
static int
fbnic_fw_reporter_diagnose(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_health_reporter_priv(reporter);
u32 sec, msec;
/* Device is most likely down, we're not exchanging heartbeats */
if (!fbd->prev_firmware_time)
return 0;
sec = div_u64_rem(fbd->firmware_time, MSEC_PER_SEC, &msec);
devlink_fmsg_pair_nest_start(fmsg, "last_heartbeat");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_pair_nest_start(fmsg, "fw_uptime");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_u32_pair_put(fmsg, "sec", sec);
devlink_fmsg_u32_pair_put(fmsg, "msec", msec);
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
return 0;
}
void __printf(2, 3)
fbnic_devlink_fw_report(struct fbnic_dev *fbd, const char *format, ...)
{
char msg[FBNIC_FW_LOG_MAX_SIZE];
va_list args;
va_start(args, format);
vsnprintf(msg, FBNIC_FW_LOG_MAX_SIZE, format, args);
va_end(args);
devlink_health_report(fbd->fw_reporter, msg, fbd);
if (fbnic_fw_log_ready(fbd))
fbnic_fw_log_write(fbd, 0, fbd->firmware_time, msg);
}
static const struct devlink_health_reporter_ops fbnic_fw_ops = {
.name = "fw",
.dump = fbnic_fw_reporter_dump,
.diagnose = fbnic_fw_reporter_diagnose,
};
static u32 fbnic_read_otp_status(struct fbnic_dev *fbd)
{
return fbnic_fw_rd32(fbd, FBNIC_NS_OTP_STATUS);
}
static int
fbnic_otp_reporter_dump(struct devlink_health_reporter *reporter,
struct devlink_fmsg *fmsg, void *priv_ctx,
struct netlink_ext_ack *extack)
{
struct fbnic_dev *fbd = devlink_health_reporter_priv(reporter);
u32 otp_status, otp_write_status, m;
otp_status = fbnic_read_otp_status(fbd);
otp_write_status = fbnic_fw_rd32(fbd, FBNIC_NS_OTP_WRITE_STATUS);
/* Dump OTP status */
devlink_fmsg_pair_nest_start(fmsg, "OTP");
devlink_fmsg_obj_nest_start(fmsg);
devlink_fmsg_u32_pair_put(fmsg, "Status", otp_status);
/* Extract OTP Write Data status */
m = FBNIC_NS_OTP_WRITE_DATA_STATUS_MASK;
devlink_fmsg_u32_pair_put(fmsg, "Data",
FIELD_GET(m, otp_write_status));
/* Extract OTP Write ECC status */
m = FBNIC_NS_OTP_WRITE_ECC_STATUS_MASK;
devlink_fmsg_u32_pair_put(fmsg, "ECC",
FIELD_GET(m, otp_write_status));
devlink_fmsg_obj_nest_end(fmsg);
devlink_fmsg_pair_nest_end(fmsg);
return 0;
}
void fbnic_devlink_otp_check(struct fbnic_dev *fbd, const char *msg)
{
/* Check if there is anything to report */
if (!fbnic_read_otp_status(fbd))
return;
devlink_health_report(fbd->otp_reporter, msg, fbd);
if (fbnic_fw_log_ready(fbd))
fbnic_fw_log_write(fbd, 0, fbd->firmware_time, msg);
}
static const struct devlink_health_reporter_ops fbnic_otp_ops = {
.name = "otp",
.dump = fbnic_otp_reporter_dump,
};
int fbnic_devlink_health_create(struct fbnic_dev *fbd)
{
fbd->fw_reporter = devlink_health_reporter_create(priv_to_devlink(fbd),
&fbnic_fw_ops, fbd);
if (IS_ERR(fbd->fw_reporter)) {
dev_warn(fbd->dev,
"Failed to create FW fault reporter: %pe\n",
fbd->fw_reporter);
return PTR_ERR(fbd->fw_reporter);
}
fbd->otp_reporter = devlink_health_reporter_create(priv_to_devlink(fbd),
&fbnic_otp_ops, fbd);
if (IS_ERR(fbd->otp_reporter)) {
devlink_health_reporter_destroy(fbd->fw_reporter);
dev_warn(fbd->dev,
"Failed to create OTP fault reporter: %pe\n",
fbd->otp_reporter);
return PTR_ERR(fbd->otp_reporter);
}
return 0;
}
void fbnic_devlink_health_destroy(struct fbnic_dev *fbd)
{
devlink_health_reporter_destroy(fbd->otp_reporter);
devlink_health_reporter_destroy(fbd->fw_reporter);
}
void fbnic_devlink_free(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
devlink_free(devlink);
}
struct fbnic_dev *fbnic_devlink_alloc(struct pci_dev *pdev)
{
void __iomem * const *iomap_table;
struct devlink *devlink;
struct fbnic_dev *fbd;
devlink = devlink_alloc(&fbnic_devlink_ops, sizeof(struct fbnic_dev),
&pdev->dev);
if (!devlink)
return NULL;
fbd = devlink_priv(devlink);
pci_set_drvdata(pdev, fbd);
fbd->dev = &pdev->dev;
iomap_table = pcim_iomap_table(pdev);
fbd->uc_addr0 = iomap_table[0];
fbd->uc_addr4 = iomap_table[4];
fbd->dsn = pci_get_dsn(pdev);
fbd->mps = pcie_get_mps(pdev);
fbd->readrq = pcie_get_readrq(pdev);
fbd->relaxed_ord = pcie_relaxed_ordering_enabled(pdev);
fbd->mac_addr_boundary = FBNIC_RPC_TCAM_MACDA_DEFAULT_BOUNDARY;
return fbd;
}
void fbnic_devlink_register(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
devlink_register(devlink);
}
void fbnic_devlink_unregister(struct fbnic_dev *fbd)
{
struct devlink *devlink = priv_to_devlink(fbd);
devlink_unregister(devlink);
}
@@ -0,0 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#define DRV_NAME "fbnic"
#define DRV_SUMMARY "Meta(R) Host Network Interface Driver"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,363 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_FW_H_
#define _FBNIC_FW_H_
#include <linux/completion.h>
#include <linux/if_ether.h>
#include <linux/types.h>
struct fbnic_dev;
struct fbnic_tlv_msg;
struct fbnic_fw_mbx {
u8 ready, head, tail;
struct {
struct fbnic_tlv_msg *msg;
dma_addr_t addr;
} buf_info[FBNIC_IPC_MBX_DESC_LEN];
};
// FW_VER_MAX_SIZE must match ETHTOOL_FWVERS_LEN
#define FBNIC_FW_VER_MAX_SIZE 32
// Formatted version is in the format XX.YY.ZZ_RRR_COMMIT
#define FBNIC_FW_CAP_RESP_COMMIT_MAX_SIZE (FBNIC_FW_VER_MAX_SIZE - 13)
#define FBNIC_FW_LOG_VERSION 1
#define FBNIC_FW_LOG_MAX_SIZE 256
/*
* The max amount of logs which can fit in a single mailbox message. Firmware
* assumes each mailbox message is 4096B. The amount of messages supported is
* calculated as 4096 minus headers for message, arrays, and length minus the
* size of length divided by headers for each array plus the maximum LOG size,
* and the size of MSEC and INDEX. Put another way:
*
* MAX_LOG_HISTORY = ((4096 - TLV_HDR_SZ * 5 - LENGTH_SZ)
* / (FBNIC_FW_LOG_MAX_SIZE + TLV_HDR_SZ * 3 + MSEC_SZ
* + INDEX_SZ))
*/
#define FBNIC_FW_MAX_LOG_HISTORY 14
#define FBNIC_MBX_RX_TO_SEC 10
struct fbnic_fw_ver {
u32 version;
char commit[FBNIC_FW_CAP_RESP_COMMIT_MAX_SIZE];
};
struct fbnic_fw_cap {
struct {
struct fbnic_fw_ver mgmt, bootloader;
} running;
struct {
struct fbnic_fw_ver mgmt, bootloader, undi;
} stored;
u8 active_slot;
u8 bmc_mac_addr[4][ETH_ALEN];
u8 bmc_present : 1;
u8 need_bmc_tcam_reinit : 1;
u8 need_bmc_macda_sync : 1;
u8 all_multi : 1;
u8 link_speed;
u8 link_fec;
u32 anti_rollback_version;
};
struct fbnic_fw_completion {
u32 msg_type;
struct completion done;
struct kref ref_count;
int result;
union {
struct {
u32 size;
} coredump_info;
struct {
u32 size;
u16 stride;
u8 *data[];
} coredump;
struct {
u32 offset;
u32 length;
} fw_update;
struct {
u16 length;
u8 offset;
u8 page;
u8 bank;
u8 data[] __aligned(sizeof(u32)) __counted_by(length);
} qsfp;
struct {
s32 millivolts;
s32 millidegrees;
} tsene;
} u;
};
u64 __fbnic_mbx_rd_desc(struct fbnic_dev *fbd, int mbx_idx, int desc_idx);
void fbnic_mbx_init(struct fbnic_dev *fbd);
void fbnic_mbx_clean(struct fbnic_dev *fbd);
int fbnic_mbx_set_cmpl(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data);
void fbnic_mbx_clear_cmpl(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data);
void fbnic_mbx_poll(struct fbnic_dev *fbd);
int fbnic_mbx_poll_tx_ready(struct fbnic_dev *fbd);
void fbnic_mbx_flush_tx(struct fbnic_dev *fbd);
/**
* enum fbnic_fw_self_test_codes - return codes from self test routines
*
* These are the codes returned from the self test routines and
* stored in the test result array indexed by the specific
* test name.
*
* @FBNIC_TEST_FW_SUCCESS: test success
* @FBNIC_TEST_FW_NO_FIRMWARE: FW interface not present
* @FBNIC_TEST_FW_NO_CMPL: No completion available
* @FBNIC_TEST_FW_NO_XMIT: Could not xmit message
* @FBNIC_TEST_FW_NO_MSG: no message returned
* @FBNIC_TEST_FW_PARSE: returned message had parsing error
*/
enum fbnic_fw_self_test_codes {
FBNIC_TEST_FW_SUCCESS = 0,
FBNIC_TEST_FW_NO_FIRMWARE = 10,
FBNIC_TEST_FW_NO_CMPL = 20,
FBNIC_TEST_FW_NO_XMIT = 30,
FBNIC_TEST_FW_NO_MSG = 40,
FBNIC_TEST_FW_PARSE = 50,
};
enum fbnic_fw_self_test_codes fbnic_fw_mbx_self_test(struct fbnic_dev *fbd);
int fbnic_fw_xmit_test_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *c);
int fbnic_fw_xmit_ownership_msg(struct fbnic_dev *fbd, bool take_ownership);
int fbnic_fw_init_heartbeat(struct fbnic_dev *fbd, bool poll);
void fbnic_fw_check_heartbeat(struct fbnic_dev *fbd);
int fbnic_fw_xmit_coredump_info_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data,
bool force);
int fbnic_fw_xmit_coredump_read_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data,
u32 offset, u32 length);
int fbnic_fw_xmit_fw_start_upgrade(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data,
unsigned int id, unsigned int len);
int fbnic_fw_xmit_fw_write_chunk(struct fbnic_dev *fbd,
const u8 *data, u32 offset, u16 length,
int cancel_error);
int fbnic_fw_xmit_qsfp_read_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data,
u32 page, u32 bank, u32 offset, u32 length);
int fbnic_fw_xmit_tsene_read_msg(struct fbnic_dev *fbd,
struct fbnic_fw_completion *cmpl_data);
int fbnic_fw_xmit_send_logs(struct fbnic_dev *fbd, bool enable,
bool send_log_history);
int fbnic_fw_xmit_rpc_macda_sync(struct fbnic_dev *fbd);
struct fbnic_fw_completion *__fbnic_fw_alloc_cmpl(u32 msg_type,
size_t priv_size);
struct fbnic_fw_completion *fbnic_fw_alloc_cmpl(u32 msg_type);
void fbnic_fw_put_cmpl(struct fbnic_fw_completion *cmpl_data);
static inline unsigned long
fbnic_mbx_wait_for_cmpl(struct fbnic_fw_completion *cmpl)
{
return wait_for_completion_timeout(&cmpl->done,
FBNIC_MBX_RX_TO_SEC * HZ);
}
#define fbnic_mk_full_fw_ver_str(_rev_id, _delim, _commit, _str, _str_sz) \
do { \
const u32 __rev_id = _rev_id; \
snprintf(_str, _str_sz, "%02lu.%02lu.%02lu-%03lu%s%s", \
FIELD_GET(FBNIC_FW_CAP_RESP_VERSION_MAJOR, __rev_id), \
FIELD_GET(FBNIC_FW_CAP_RESP_VERSION_MINOR, __rev_id), \
FIELD_GET(FBNIC_FW_CAP_RESP_VERSION_PATCH, __rev_id), \
FIELD_GET(FBNIC_FW_CAP_RESP_VERSION_BUILD, __rev_id), \
_delim, _commit); \
} while (0)
#define fbnic_mk_fw_ver_str(_rev_id, _str) \
fbnic_mk_full_fw_ver_str(_rev_id, "", "", _str, sizeof(_str))
enum {
QSPI_SECTION_CMRT = 0,
QSPI_SECTION_CONTROL_FW = 1,
QSPI_SECTION_UCODE = 2,
QSPI_SECTION_OPTION_ROM = 3,
QSPI_SECTION_USER = 4,
QSPI_SECTION_INVALID,
};
#define FW_HEARTBEAT_PERIOD (10 * HZ)
enum {
FBNIC_TLV_MSG_ID_HOST_CAP_REQ = 0x10,
FBNIC_TLV_MSG_ID_FW_CAP_RESP = 0x11,
FBNIC_TLV_MSG_ID_OWNERSHIP_REQ = 0x12,
FBNIC_TLV_MSG_ID_OWNERSHIP_RESP = 0x13,
FBNIC_TLV_MSG_ID_HEARTBEAT_REQ = 0x14,
FBNIC_TLV_MSG_ID_HEARTBEAT_RESP = 0x15,
FBNIC_TLV_MSG_ID_COREDUMP_GET_INFO_REQ = 0x18,
FBNIC_TLV_MSG_ID_COREDUMP_GET_INFO_RESP = 0x19,
FBNIC_TLV_MSG_ID_COREDUMP_READ_REQ = 0x20,
FBNIC_TLV_MSG_ID_COREDUMP_READ_RESP = 0x21,
FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ = 0x22,
FBNIC_TLV_MSG_ID_FW_START_UPGRADE_RESP = 0x23,
FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ = 0x24,
FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_RESP = 0x25,
FBNIC_TLV_MSG_ID_FW_FINISH_UPGRADE_REQ = 0x28,
FBNIC_TLV_MSG_ID_FW_FINISH_UPGRADE_RESP = 0x29,
FBNIC_TLV_MSG_ID_QSFP_READ_REQ = 0x38,
FBNIC_TLV_MSG_ID_QSFP_READ_RESP = 0x39,
FBNIC_TLV_MSG_ID_TSENE_READ_REQ = 0x3C,
FBNIC_TLV_MSG_ID_TSENE_READ_RESP = 0x3D,
FBNIC_TLV_MSG_ID_LOG_SEND_LOGS_REQ = 0x43,
FBNIC_TLV_MSG_ID_LOG_MSG_REQ = 0x44,
FBNIC_TLV_MSG_ID_LOG_MSG_RESP = 0x45,
FBNIC_TLV_MSG_ID_RPC_MAC_SYNC_REQ = 0x46,
};
#define FBNIC_FW_CAP_RESP_VERSION_MAJOR CSR_GENMASK(31, 24)
#define FBNIC_FW_CAP_RESP_VERSION_MINOR CSR_GENMASK(23, 16)
#define FBNIC_FW_CAP_RESP_VERSION_PATCH CSR_GENMASK(15, 8)
#define FBNIC_FW_CAP_RESP_VERSION_BUILD CSR_GENMASK(7, 0)
enum {
FBNIC_FW_CAP_RESP_VERSION = 0x0,
FBNIC_FW_CAP_RESP_BMC_PRESENT = 0x1,
FBNIC_FW_CAP_RESP_BMC_MAC_ADDR = 0x2,
FBNIC_FW_CAP_RESP_BMC_MAC_ARRAY = 0x3,
FBNIC_FW_CAP_RESP_STORED_VERSION = 0x4,
FBNIC_FW_CAP_RESP_ACTIVE_FW_SLOT = 0x5,
FBNIC_FW_CAP_RESP_VERSION_COMMIT_STR = 0x6,
FBNIC_FW_CAP_RESP_BMC_ALL_MULTI = 0x8,
FBNIC_FW_CAP_RESP_FW_STATE = 0x9,
FBNIC_FW_CAP_RESP_FW_LINK_SPEED = 0xa,
FBNIC_FW_CAP_RESP_FW_LINK_FEC = 0xb,
FBNIC_FW_CAP_RESP_STORED_COMMIT_STR = 0xc,
FBNIC_FW_CAP_RESP_CMRT_VERSION = 0xd,
FBNIC_FW_CAP_RESP_STORED_CMRT_VERSION = 0xe,
FBNIC_FW_CAP_RESP_CMRT_COMMIT_STR = 0xf,
FBNIC_FW_CAP_RESP_STORED_CMRT_COMMIT_STR = 0x10,
FBNIC_FW_CAP_RESP_UEFI_VERSION = 0x11,
FBNIC_FW_CAP_RESP_UEFI_COMMIT_STR = 0x12,
FBNIC_FW_CAP_RESP_ANTI_ROLLBACK_VERSION = 0x15,
FBNIC_FW_CAP_RESP_MSG_MAX
};
enum {
FBNIC_FW_LINK_MODE_25CR = 1,
FBNIC_FW_LINK_MODE_50CR2 = 2,
FBNIC_FW_LINK_MODE_50CR = 3,
FBNIC_FW_LINK_MODE_100CR2 = 4,
};
enum {
FBNIC_FW_LINK_FEC_NONE = 1,
FBNIC_FW_LINK_FEC_RS = 2,
FBNIC_FW_LINK_FEC_BASER = 3,
};
enum {
FBNIC_FW_QSFP_BANK = 0x0,
FBNIC_FW_QSFP_PAGE = 0x1,
FBNIC_FW_QSFP_OFFSET = 0x2,
FBNIC_FW_QSFP_LENGTH = 0x3,
FBNIC_FW_QSFP_ERROR = 0x4,
FBNIC_FW_QSFP_DATA = 0x5,
FBNIC_FW_QSFP_MSG_MAX
};
enum {
FBNIC_FW_TSENE_THERM = 0x0,
FBNIC_FW_TSENE_VOLT = 0x1,
FBNIC_FW_TSENE_ERROR = 0x2,
FBNIC_FW_TSENE_MSG_MAX
};
enum {
FBNIC_FW_OWNERSHIP_FLAG = 0x0,
FBNIC_FW_OWNERSHIP_TIME = 0x1,
FBNIC_FW_OWNERSHIP_MSG_MAX
};
enum {
FBNIC_FW_HEARTBEAT_UPTIME = 0x0,
FBNIC_FW_HEARTBEAT_NUMBER_OF_MESSAGES = 0x1,
FBNIC_FW_HEARTBEAT_MSG_MAX
};
enum {
FBNIC_FW_COREDUMP_REQ_INFO_CREATE = 0x0,
FBNIC_FW_COREDUMP_REQ_INFO_MSG_MAX
};
enum {
FBNIC_FW_COREDUMP_INFO_AVAILABLE = 0x0,
FBNIC_FW_COREDUMP_INFO_SIZE = 0x1,
FBNIC_FW_COREDUMP_INFO_ERROR = 0x2,
FBNIC_FW_COREDUMP_INFO_MSG_MAX
};
enum {
FBNIC_FW_COREDUMP_READ_OFFSET = 0x0,
FBNIC_FW_COREDUMP_READ_LENGTH = 0x1,
FBNIC_FW_COREDUMP_READ_DATA = 0x2,
FBNIC_FW_COREDUMP_READ_ERROR = 0x3,
FBNIC_FW_COREDUMP_READ_MSG_MAX
};
enum {
FBNIC_FW_START_UPGRADE_ERROR = 0x0,
FBNIC_FW_START_UPGRADE_SECTION = 0x1,
FBNIC_FW_START_UPGRADE_IMAGE_LENGTH = 0x2,
FBNIC_FW_START_UPGRADE_MSG_MAX
};
enum {
FBNIC_FW_WRITE_CHUNK_OFFSET = 0x0,
FBNIC_FW_WRITE_CHUNK_LENGTH = 0x1,
FBNIC_FW_WRITE_CHUNK_DATA = 0x2,
FBNIC_FW_WRITE_CHUNK_ERROR = 0x3,
FBNIC_FW_WRITE_CHUNK_MSG_MAX
};
enum {
FBNIC_FW_FINISH_UPGRADE_ERROR = 0x0,
FBNIC_FW_FINISH_UPGRADE_MSG_MAX
};
enum {
FBNIC_SEND_LOGS = 0x0,
FBNIC_SEND_LOGS_VERSION = 0x1,
FBNIC_SEND_LOGS_HISTORY = 0x2,
FBNIC_SEND_LOGS_MSG_MAX
};
enum {
FBNIC_FW_LOG_MSEC = 0x0,
FBNIC_FW_LOG_INDEX = 0x1,
FBNIC_FW_LOG_MSG = 0x2,
FBNIC_FW_LOG_LENGTH = 0x3,
FBNIC_FW_LOG_MSEC_ARRAY = 0x4,
FBNIC_FW_LOG_INDEX_ARRAY = 0x5,
FBNIC_FW_LOG_MSG_ARRAY = 0x6,
FBNIC_FW_LOG_MSG_MAX
};
enum {
FBNIC_FW_RPC_MAC_SYNC_RX_FLAGS = 0x0,
FBNIC_FW_RPC_MAC_SYNC_UC_ARRAY = 0x1,
FBNIC_FW_RPC_MAC_SYNC_MC_ARRAY = 0x2,
FBNIC_FW_RPC_MAC_SYNC_MAC_ADDR = 0x3,
FBNIC_FW_RPC_MAC_SYNC_MSG_MAX
};
#define FW_RPC_MAC_SYNC_RX_FLAGS_PROMISC 1
#define FW_RPC_MAC_SYNC_RX_FLAGS_ALLMULTI 2
#define FW_RPC_MAC_SYNC_RX_FLAGS_BROADCAST 4
#define FW_RPC_MAC_SYNC_UC_ARRAY_SIZE 8
#define FW_RPC_MAC_SYNC_MC_ARRAY_SIZE 8
#endif /* _FBNIC_FW_H_ */
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include "fbnic.h"
#include "fbnic_fw.h"
#include "fbnic_fw_log.h"
void fbnic_fw_log_enable(struct fbnic_dev *fbd, bool send_hist)
{
int err;
if (!fbnic_fw_log_ready(fbd))
return;
if (fbd->fw_cap.running.mgmt.version < MIN_FW_VER_CODE_HIST)
send_hist = false;
err = fbnic_fw_xmit_send_logs(fbd, true, send_hist);
if (err && err != -EOPNOTSUPP)
dev_warn(fbd->dev, "Unable to enable firmware logs: %d\n", err);
}
void fbnic_fw_log_disable(struct fbnic_dev *fbd)
{
int err;
err = fbnic_fw_xmit_send_logs(fbd, false, false);
if (err && err != -EOPNOTSUPP)
dev_warn(fbd->dev, "Unable to disable firmware logs: %d\n",
err);
}
int fbnic_fw_log_init(struct fbnic_dev *fbd)
{
struct fbnic_fw_log *log = &fbd->fw_log;
void *data;
if (WARN_ON_ONCE(fbnic_fw_log_ready(fbd)))
return -EEXIST;
data = vmalloc(FBNIC_FW_LOG_SIZE);
if (!data)
return -ENOMEM;
spin_lock_init(&fbd->fw_log.lock);
INIT_LIST_HEAD(&log->entries);
log->size = FBNIC_FW_LOG_SIZE;
log->data_start = data;
log->data_end = data + FBNIC_FW_LOG_SIZE;
return 0;
}
void fbnic_fw_log_free(struct fbnic_dev *fbd)
{
struct fbnic_fw_log *log = &fbd->fw_log;
if (!fbnic_fw_log_ready(fbd))
return;
INIT_LIST_HEAD(&log->entries);
log->size = 0;
vfree(log->data_start);
log->data_start = NULL;
log->data_end = NULL;
}
int fbnic_fw_log_write(struct fbnic_dev *fbd, u64 index, u32 timestamp,
const char *msg)
{
struct fbnic_fw_log_entry *entry, *head, *tail, *next;
struct fbnic_fw_log *log = &fbd->fw_log;
size_t msg_len = strlen(msg) + 1;
unsigned long flags;
void *entry_end;
if (!fbnic_fw_log_ready(fbd)) {
dev_err(fbd->dev, "Firmware sent log entry without being requested!\n");
return -ENOSPC;
}
spin_lock_irqsave(&log->lock, flags);
if (list_empty(&log->entries)) {
entry = log->data_start;
} else {
head = list_first_entry(&log->entries, typeof(*head), list);
entry_end = head->msg + head->len + 1;
entry = PTR_ALIGN(entry_end, 8);
}
entry_end = entry->msg + msg_len + 1;
/* We've reached the end of the buffer, wrap around */
if (entry_end > log->data_end) {
entry = log->data_start;
entry_end = entry->msg + msg_len + 1;
}
/* Make room for entry by removing from tail. */
list_for_each_entry_safe_reverse(tail, next, &log->entries, list) {
if (entry <= tail && entry_end > (void *)tail)
list_del(&tail->list);
else
break;
}
entry->index = index;
entry->timestamp = timestamp;
entry->len = msg_len;
strscpy(entry->msg, msg, entry->len);
list_add(&entry->list, &log->entries);
spin_unlock_irqrestore(&log->lock, flags);
return 0;
}
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_FW_LOG_H_
#define _FBNIC_FW_LOG_H_
#include <linux/spinlock.h>
#include <linux/types.h>
/* A 512K log buffer was chosen fairly arbitrarily */
#define FBNIC_FW_LOG_SIZE (512 * 1024) /* bytes */
/* Firmware log output is prepended with log index followed by a timestamp.
* The timestamp is similar to Zephyr's format DD:HH:MM:SS.MMM
*/
#define FBNIC_FW_LOG_FMT "[%5lld] [%02ld:%02ld:%02ld:%02ld.%03ld] %s\n"
struct fbnic_dev;
struct fbnic_fw_log_entry {
struct list_head list;
u64 index;
u32 timestamp;
u16 len;
char msg[] __counted_by(len);
};
struct fbnic_fw_log {
void *data_start;
void *data_end;
size_t size;
struct list_head entries;
/* Spin lock for accessing or modifying entries */
spinlock_t lock;
};
#define fbnic_fw_log_ready(_fbd) (!!(_fbd)->fw_log.data_start)
void fbnic_fw_log_enable(struct fbnic_dev *fbd, bool send_hist);
void fbnic_fw_log_disable(struct fbnic_dev *fbd);
int fbnic_fw_log_init(struct fbnic_dev *fbd);
void fbnic_fw_log_free(struct fbnic_dev *fbd);
int fbnic_fw_log_write(struct fbnic_dev *fbd, u64 index, u32 timestamp,
const char *msg);
#endif /* _FBNIC_FW_LOG_H_ */
@@ -0,0 +1,601 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/rtnetlink.h>
#include "fbnic.h"
static void fbnic_hw_stat_rst32(struct fbnic_dev *fbd, u32 reg,
struct fbnic_stat_counter *stat)
{
/* We do not touch the "value" field here.
* It gets zeroed out on fbd structure allocation.
* After that we want it to grow continuously
* through device resets and power state changes.
*/
stat->u.old_reg_value_32 = rd32(fbd, reg);
}
static void fbnic_hw_stat_rd32(struct fbnic_dev *fbd, u32 reg,
struct fbnic_stat_counter *stat)
{
u32 new_reg_value;
new_reg_value = rd32(fbd, reg);
stat->value += new_reg_value - stat->u.old_reg_value_32;
stat->u.old_reg_value_32 = new_reg_value;
}
u64 fbnic_stat_rd64(struct fbnic_dev *fbd, u32 reg, u32 offset)
{
u32 prev_upper, upper, lower, diff;
prev_upper = rd32(fbd, reg + offset);
lower = rd32(fbd, reg);
upper = rd32(fbd, reg + offset);
diff = upper - prev_upper;
if (!diff)
return ((u64)upper << 32) | lower;
if (diff > 1)
dev_warn_once(fbd->dev,
"Stats inconsistent, upper 32b of %#010x updating too quickly\n",
reg * 4);
/* Return only the upper bits as we cannot guarantee
* the accuracy of the lower bits. We will add them in
* when the counter slows down enough that we can get
* a snapshot with both upper values being the same
* between reads.
*/
return ((u64)upper << 32);
}
static void fbnic_hw_stat_rst64(struct fbnic_dev *fbd, u32 reg, s32 offset,
struct fbnic_stat_counter *stat)
{
/* Record initial counter values and compute deltas from there to ensure
* stats start at 0 after reboot/reset. This avoids exposing absolute
* hardware counter values to userspace.
*/
stat->u.old_reg_value_64 = fbnic_stat_rd64(fbd, reg, offset);
}
static void fbnic_hw_stat_rd64(struct fbnic_dev *fbd, u32 reg, s32 offset,
struct fbnic_stat_counter *stat)
{
u64 new_reg_value;
new_reg_value = fbnic_stat_rd64(fbd, reg, offset);
stat->value += new_reg_value - stat->u.old_reg_value_64;
stat->u.old_reg_value_64 = new_reg_value;
}
static void fbnic_reset_tmi_stats(struct fbnic_dev *fbd,
struct fbnic_tmi_stats *tmi)
{
fbnic_hw_stat_rst32(fbd, FBNIC_TMI_DROP_PKTS, &tmi->drop.frames);
fbnic_hw_stat_rst64(fbd, FBNIC_TMI_DROP_BYTE_L, 1, &tmi->drop.bytes);
fbnic_hw_stat_rst32(fbd,
FBNIC_TMI_ILLEGAL_PTP_REQS,
&tmi->ptp_illegal_req);
fbnic_hw_stat_rst32(fbd, FBNIC_TMI_GOOD_PTP_TS, &tmi->ptp_good_ts);
fbnic_hw_stat_rst32(fbd, FBNIC_TMI_BAD_PTP_TS, &tmi->ptp_bad_ts);
}
static void fbnic_get_tmi_stats32(struct fbnic_dev *fbd,
struct fbnic_tmi_stats *tmi)
{
fbnic_hw_stat_rd32(fbd, FBNIC_TMI_DROP_PKTS, &tmi->drop.frames);
fbnic_hw_stat_rd32(fbd,
FBNIC_TMI_ILLEGAL_PTP_REQS,
&tmi->ptp_illegal_req);
fbnic_hw_stat_rd32(fbd, FBNIC_TMI_GOOD_PTP_TS, &tmi->ptp_good_ts);
fbnic_hw_stat_rd32(fbd, FBNIC_TMI_BAD_PTP_TS, &tmi->ptp_bad_ts);
}
static void fbnic_get_tmi_stats(struct fbnic_dev *fbd,
struct fbnic_tmi_stats *tmi)
{
fbnic_hw_stat_rd64(fbd, FBNIC_TMI_DROP_BYTE_L, 1, &tmi->drop.bytes);
}
static void fbnic_reset_tti_stats(struct fbnic_dev *fbd,
struct fbnic_tti_stats *tti)
{
fbnic_hw_stat_rst32(fbd,
FBNIC_TCE_TTI_CM_DROP_PKTS,
&tti->cm_drop.frames);
fbnic_hw_stat_rst64(fbd,
FBNIC_TCE_TTI_CM_DROP_BYTE_L,
1,
&tti->cm_drop.bytes);
fbnic_hw_stat_rst32(fbd,
FBNIC_TCE_TTI_FRAME_DROP_PKTS,
&tti->frame_drop.frames);
fbnic_hw_stat_rst64(fbd,
FBNIC_TCE_TTI_FRAME_DROP_BYTE_L,
1,
&tti->frame_drop.bytes);
fbnic_hw_stat_rst32(fbd,
FBNIC_TCE_TBI_DROP_PKTS,
&tti->tbi_drop.frames);
fbnic_hw_stat_rst64(fbd,
FBNIC_TCE_TBI_DROP_BYTE_L,
1,
&tti->tbi_drop.bytes);
}
static void fbnic_get_tti_stats32(struct fbnic_dev *fbd,
struct fbnic_tti_stats *tti)
{
fbnic_hw_stat_rd32(fbd,
FBNIC_TCE_TTI_CM_DROP_PKTS,
&tti->cm_drop.frames);
fbnic_hw_stat_rd32(fbd,
FBNIC_TCE_TTI_FRAME_DROP_PKTS,
&tti->frame_drop.frames);
fbnic_hw_stat_rd32(fbd,
FBNIC_TCE_TBI_DROP_PKTS,
&tti->tbi_drop.frames);
}
static void fbnic_get_tti_stats(struct fbnic_dev *fbd,
struct fbnic_tti_stats *tti)
{
fbnic_hw_stat_rd64(fbd,
FBNIC_TCE_TTI_CM_DROP_BYTE_L,
1,
&tti->cm_drop.bytes);
fbnic_hw_stat_rd64(fbd,
FBNIC_TCE_TTI_FRAME_DROP_BYTE_L,
1,
&tti->frame_drop.bytes);
fbnic_hw_stat_rd64(fbd,
FBNIC_TCE_TBI_DROP_BYTE_L,
1,
&tti->tbi_drop.bytes);
}
static void fbnic_reset_rpc_stats(struct fbnic_dev *fbd,
struct fbnic_rpc_stats *rpc)
{
fbnic_hw_stat_rst32(fbd,
FBNIC_RPC_CNTR_UNKN_ETYPE,
&rpc->unkn_etype);
fbnic_hw_stat_rst32(fbd,
FBNIC_RPC_CNTR_UNKN_EXT_HDR,
&rpc->unkn_ext_hdr);
fbnic_hw_stat_rst32(fbd, FBNIC_RPC_CNTR_IPV4_FRAG, &rpc->ipv4_frag);
fbnic_hw_stat_rst32(fbd, FBNIC_RPC_CNTR_IPV6_FRAG, &rpc->ipv6_frag);
fbnic_hw_stat_rst32(fbd, FBNIC_RPC_CNTR_IPV4_ESP, &rpc->ipv4_esp);
fbnic_hw_stat_rst32(fbd, FBNIC_RPC_CNTR_IPV6_ESP, &rpc->ipv6_esp);
fbnic_hw_stat_rst32(fbd, FBNIC_RPC_CNTR_TCP_OPT_ERR, &rpc->tcp_opt_err);
fbnic_hw_stat_rst32(fbd,
FBNIC_RPC_CNTR_OUT_OF_HDR_ERR,
&rpc->out_of_hdr_err);
fbnic_hw_stat_rst32(fbd,
FBNIC_RPC_CNTR_OVR_SIZE_ERR,
&rpc->ovr_size_err);
}
static void fbnic_get_rpc_stats32(struct fbnic_dev *fbd,
struct fbnic_rpc_stats *rpc)
{
fbnic_hw_stat_rd32(fbd,
FBNIC_RPC_CNTR_UNKN_ETYPE,
&rpc->unkn_etype);
fbnic_hw_stat_rd32(fbd,
FBNIC_RPC_CNTR_UNKN_EXT_HDR,
&rpc->unkn_ext_hdr);
fbnic_hw_stat_rd32(fbd, FBNIC_RPC_CNTR_IPV4_FRAG, &rpc->ipv4_frag);
fbnic_hw_stat_rd32(fbd, FBNIC_RPC_CNTR_IPV6_FRAG, &rpc->ipv6_frag);
fbnic_hw_stat_rd32(fbd, FBNIC_RPC_CNTR_IPV4_ESP, &rpc->ipv4_esp);
fbnic_hw_stat_rd32(fbd, FBNIC_RPC_CNTR_IPV6_ESP, &rpc->ipv6_esp);
fbnic_hw_stat_rd32(fbd, FBNIC_RPC_CNTR_TCP_OPT_ERR, &rpc->tcp_opt_err);
fbnic_hw_stat_rd32(fbd,
FBNIC_RPC_CNTR_OUT_OF_HDR_ERR,
&rpc->out_of_hdr_err);
fbnic_hw_stat_rd32(fbd,
FBNIC_RPC_CNTR_OVR_SIZE_ERR,
&rpc->ovr_size_err);
}
static void fbnic_reset_rxb_fifo_stats(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_fifo_stats *fifo)
{
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_DROP_FRMS_STS(i),
&fifo->drop.frames);
fbnic_hw_stat_rst64(fbd, FBNIC_RXB_DROP_BYTES_STS_L(i), 1,
&fifo->drop.bytes);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_TRUN_FRMS_STS(i),
&fifo->trunc.frames);
fbnic_hw_stat_rst64(fbd, FBNIC_RXB_TRUN_BYTES_STS_L(i), 1,
&fifo->trunc.bytes);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_TRANS_DROP_STS(i),
&fifo->trans_drop);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_TRANS_ECN_STS(i),
&fifo->trans_ecn);
fifo->level.u.old_reg_value_32 = 0;
}
static void fbnic_reset_rxb_enq_stats(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_enqueue_stats *enq)
{
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_DRBO_FRM_CNT_SRC(i),
&enq->drbo.frames);
fbnic_hw_stat_rst64(fbd, FBNIC_RXB_DRBO_BYTE_CNT_SRC_L(i), 4,
&enq->drbo.bytes);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_INTEGRITY_ERR(i),
&enq->integrity_err);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_MAC_ERR(i),
&enq->mac_err);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_PARSER_ERR(i),
&enq->parser_err);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_FRM_ERR(i),
&enq->frm_err);
}
static void fbnic_reset_rxb_deq_stats(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_dequeue_stats *deq)
{
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_INTF_FRM_CNT_DST(i),
&deq->intf.frames);
fbnic_hw_stat_rst64(fbd, FBNIC_RXB_INTF_BYTE_CNT_DST_L(i), 4,
&deq->intf.bytes);
fbnic_hw_stat_rst32(fbd, FBNIC_RXB_PBUF_FRM_CNT_DST(i),
&deq->pbuf.frames);
fbnic_hw_stat_rst64(fbd, FBNIC_RXB_PBUF_BYTE_CNT_DST_L(i), 4,
&deq->pbuf.bytes);
}
static void fbnic_reset_rxb_stats(struct fbnic_dev *fbd,
struct fbnic_rxb_stats *rxb)
{
int i;
for (i = 0; i < FBNIC_RXB_FIFO_INDICES; i++)
fbnic_reset_rxb_fifo_stats(fbd, i, &rxb->fifo[i]);
for (i = 0; i < FBNIC_RXB_INTF_INDICES; i++) {
fbnic_reset_rxb_enq_stats(fbd, i, &rxb->enq[i]);
fbnic_reset_rxb_deq_stats(fbd, i, &rxb->deq[i]);
}
}
static void fbnic_get_rxb_fifo_stats32(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_fifo_stats *fifo)
{
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_DROP_FRMS_STS(i),
&fifo->drop.frames);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_TRUN_FRMS_STS(i),
&fifo->trunc.frames);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_TRANS_DROP_STS(i),
&fifo->trans_drop);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_TRANS_ECN_STS(i),
&fifo->trans_ecn);
fifo->level.value = rd32(fbd, FBNIC_RXB_PBUF_FIFO_LEVEL(i));
}
static void fbnic_get_rxb_fifo_stats(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_fifo_stats *fifo)
{
fbnic_hw_stat_rd64(fbd, FBNIC_RXB_DROP_BYTES_STS_L(i), 1,
&fifo->drop.bytes);
fbnic_hw_stat_rd64(fbd, FBNIC_RXB_TRUN_BYTES_STS_L(i), 1,
&fifo->trunc.bytes);
fbnic_get_rxb_fifo_stats32(fbd, i, fifo);
}
static void fbnic_get_rxb_enq_stats32(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_enqueue_stats *enq)
{
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_DRBO_FRM_CNT_SRC(i),
&enq->drbo.frames);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_INTEGRITY_ERR(i),
&enq->integrity_err);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_MAC_ERR(i),
&enq->mac_err);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_PARSER_ERR(i),
&enq->parser_err);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_FRM_ERR(i),
&enq->frm_err);
}
static void fbnic_get_rxb_enq_stats(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_enqueue_stats *enq)
{
fbnic_hw_stat_rd64(fbd, FBNIC_RXB_DRBO_BYTE_CNT_SRC_L(i), 4,
&enq->drbo.bytes);
fbnic_get_rxb_enq_stats32(fbd, i, enq);
}
static void fbnic_get_rxb_deq_stats32(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_dequeue_stats *deq)
{
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_INTF_FRM_CNT_DST(i),
&deq->intf.frames);
fbnic_hw_stat_rd32(fbd, FBNIC_RXB_PBUF_FRM_CNT_DST(i),
&deq->pbuf.frames);
}
static void fbnic_get_rxb_deq_stats(struct fbnic_dev *fbd, int i,
struct fbnic_rxb_dequeue_stats *deq)
{
fbnic_hw_stat_rd64(fbd, FBNIC_RXB_INTF_BYTE_CNT_DST_L(i), 4,
&deq->intf.bytes);
fbnic_hw_stat_rd64(fbd, FBNIC_RXB_PBUF_BYTE_CNT_DST_L(i), 4,
&deq->pbuf.bytes);
fbnic_get_rxb_deq_stats32(fbd, i, deq);
}
static void fbnic_get_rxb_stats32(struct fbnic_dev *fbd,
struct fbnic_rxb_stats *rxb)
{
int i;
for (i = 0; i < FBNIC_RXB_FIFO_INDICES; i++)
fbnic_get_rxb_fifo_stats32(fbd, i, &rxb->fifo[i]);
for (i = 0; i < FBNIC_RXB_INTF_INDICES; i++) {
fbnic_get_rxb_enq_stats32(fbd, i, &rxb->enq[i]);
fbnic_get_rxb_deq_stats32(fbd, i, &rxb->deq[i]);
}
}
static void fbnic_get_rxb_stats(struct fbnic_dev *fbd,
struct fbnic_rxb_stats *rxb)
{
int i;
for (i = 0; i < FBNIC_RXB_FIFO_INDICES; i++)
fbnic_get_rxb_fifo_stats(fbd, i, &rxb->fifo[i]);
for (i = 0; i < FBNIC_RXB_INTF_INDICES; i++) {
fbnic_get_rxb_enq_stats(fbd, i, &rxb->enq[i]);
fbnic_get_rxb_deq_stats(fbd, i, &rxb->deq[i]);
}
}
static void fbnic_reset_hw_rxq_stats(struct fbnic_dev *fbd,
struct fbnic_hw_q_stats *hw_q)
{
int i;
for (i = 0; i < fbd->max_num_queues; i++, hw_q++) {
u32 base = FBNIC_QUEUE(i);
fbnic_hw_stat_rst32(fbd,
base + FBNIC_QUEUE_RDE_PKT_ERR_CNT,
&hw_q->rde_pkt_err);
fbnic_hw_stat_rst32(fbd,
base + FBNIC_QUEUE_RDE_CQ_DROP_CNT,
&hw_q->rde_pkt_cq_drop);
fbnic_hw_stat_rst32(fbd,
base + FBNIC_QUEUE_RDE_BDQ_DROP_CNT,
&hw_q->rde_pkt_bdq_drop);
}
}
static void fbnic_get_hw_rxq_stats32(struct fbnic_dev *fbd,
struct fbnic_hw_q_stats *hw_q)
{
int i;
for (i = 0; i < fbd->max_num_queues; i++, hw_q++) {
u32 base = FBNIC_QUEUE(i);
fbnic_hw_stat_rd32(fbd,
base + FBNIC_QUEUE_RDE_PKT_ERR_CNT,
&hw_q->rde_pkt_err);
fbnic_hw_stat_rd32(fbd,
base + FBNIC_QUEUE_RDE_CQ_DROP_CNT,
&hw_q->rde_pkt_cq_drop);
fbnic_hw_stat_rd32(fbd,
base + FBNIC_QUEUE_RDE_BDQ_DROP_CNT,
&hw_q->rde_pkt_bdq_drop);
}
}
void fbnic_get_hw_q_stats(struct fbnic_dev *fbd,
struct fbnic_hw_q_stats *hw_q)
{
spin_lock(&fbd->hw_stats.lock);
fbnic_get_hw_rxq_stats32(fbd, hw_q);
spin_unlock(&fbd->hw_stats.lock);
}
static void fbnic_reset_pcie_stats_asic(struct fbnic_dev *fbd,
struct fbnic_pcie_stats *pcie)
{
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_RD_TLP_CNT_31_0,
1,
&pcie->ob_rd_tlp);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_RD_DWORD_CNT_31_0,
1,
&pcie->ob_rd_dword);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_CPL_TLP_CNT_31_0,
1,
&pcie->ob_cpl_tlp);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_CPL_DWORD_CNT_31_0,
1,
&pcie->ob_cpl_dword);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_WR_TLP_CNT_31_0,
1,
&pcie->ob_wr_tlp);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_WR_DWORD_CNT_31_0,
1,
&pcie->ob_wr_dword);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_RD_DBG_CNT_TAG_31_0,
1,
&pcie->ob_rd_no_tag);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_RD_DBG_CNT_CPL_CRED_31_0,
1,
&pcie->ob_rd_no_cpl_cred);
fbnic_hw_stat_rst64(fbd,
FBNIC_PUL_USER_OB_RD_DBG_CNT_NP_CRED_31_0,
1,
&pcie->ob_rd_no_np_cred);
}
static void fbnic_get_pcie_stats_asic64(struct fbnic_dev *fbd,
struct fbnic_pcie_stats *pcie)
{
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_RD_TLP_CNT_31_0,
1,
&pcie->ob_rd_tlp);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_RD_DWORD_CNT_31_0,
1,
&pcie->ob_rd_dword);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_WR_TLP_CNT_31_0,
1,
&pcie->ob_wr_tlp);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_WR_DWORD_CNT_31_0,
1,
&pcie->ob_wr_dword);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_CPL_TLP_CNT_31_0,
1,
&pcie->ob_cpl_tlp);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_CPL_DWORD_CNT_31_0,
1,
&pcie->ob_cpl_dword);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_RD_DBG_CNT_TAG_31_0,
1,
&pcie->ob_rd_no_tag);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_RD_DBG_CNT_CPL_CRED_31_0,
1,
&pcie->ob_rd_no_cpl_cred);
fbnic_hw_stat_rd64(fbd,
FBNIC_PUL_USER_OB_RD_DBG_CNT_NP_CRED_31_0,
1,
&pcie->ob_rd_no_np_cred);
}
static void fbnic_reset_phy_stats(struct fbnic_dev *fbd,
struct fbnic_phy_stats *phy_stats)
{
const struct fbnic_mac *mac = fbd->mac;
mac->get_fec_stats(fbd, true, &phy_stats->fec);
mac->get_pcs_stats(fbd, true, &phy_stats->pcs);
}
static void fbnic_get_phy_stats32(struct fbnic_dev *fbd,
struct fbnic_phy_stats *phy_stats)
{
const struct fbnic_mac *mac = fbd->mac;
mac->get_fec_stats(fbd, false, &phy_stats->fec);
mac->get_pcs_stats(fbd, false, &phy_stats->pcs);
}
static void fbnic_reset_hw_mac_stats(struct fbnic_dev *fbd,
struct fbnic_mac_stats *mac_stats)
{
const struct fbnic_mac *mac = fbd->mac;
mac->get_eth_mac_stats(fbd, true, &mac_stats->eth_mac);
mac->get_pause_stats(fbd, true, &mac_stats->pause);
mac->get_eth_ctrl_stats(fbd, true, &mac_stats->eth_ctrl);
mac->get_rmon_stats(fbd, true, &mac_stats->rmon);
}
void fbnic_reset_hw_stats(struct fbnic_dev *fbd)
{
spin_lock(&fbd->hw_stats.lock);
fbnic_reset_phy_stats(fbd, &fbd->hw_stats.phy);
fbnic_reset_tmi_stats(fbd, &fbd->hw_stats.tmi);
fbnic_reset_tti_stats(fbd, &fbd->hw_stats.tti);
fbnic_reset_rpc_stats(fbd, &fbd->hw_stats.rpc);
fbnic_reset_rxb_stats(fbd, &fbd->hw_stats.rxb);
fbnic_reset_hw_rxq_stats(fbd, fbd->hw_stats.hw_q);
fbnic_reset_pcie_stats_asic(fbd, &fbd->hw_stats.pcie);
spin_unlock(&fbd->hw_stats.lock);
/* Once registered, the only other access to MAC stats is via the
* ethtool API which is protected by the rtnl_lock. The call to
* fbnic_reset_hw_stats() during PCI recovery is also protected
* by the rtnl_lock hence, we don't need the spinlock to access
* the MAC stats.
*/
if (fbd->netdev)
ASSERT_RTNL();
fbnic_reset_hw_mac_stats(fbd, &fbd->hw_stats.mac);
}
void fbnic_init_hw_stats(struct fbnic_dev *fbd)
{
spin_lock_init(&fbd->hw_stats.lock);
fbnic_reset_hw_stats(fbd);
}
static void __fbnic_get_hw_stats32(struct fbnic_dev *fbd)
{
fbnic_get_phy_stats32(fbd, &fbd->hw_stats.phy);
fbnic_get_tmi_stats32(fbd, &fbd->hw_stats.tmi);
fbnic_get_tti_stats32(fbd, &fbd->hw_stats.tti);
fbnic_get_rpc_stats32(fbd, &fbd->hw_stats.rpc);
fbnic_get_rxb_stats32(fbd, &fbd->hw_stats.rxb);
fbnic_get_hw_rxq_stats32(fbd, fbd->hw_stats.hw_q);
}
void fbnic_get_hw_stats32(struct fbnic_dev *fbd)
{
spin_lock(&fbd->hw_stats.lock);
__fbnic_get_hw_stats32(fbd);
spin_unlock(&fbd->hw_stats.lock);
}
void fbnic_get_hw_stats(struct fbnic_dev *fbd)
{
spin_lock(&fbd->hw_stats.lock);
__fbnic_get_hw_stats32(fbd);
fbnic_get_tmi_stats(fbd, &fbd->hw_stats.tmi);
fbnic_get_tti_stats(fbd, &fbd->hw_stats.tti);
fbnic_get_rxb_stats(fbd, &fbd->hw_stats.rxb);
fbnic_get_pcie_stats_asic64(fbd, &fbd->hw_stats.pcie);
spin_unlock(&fbd->hw_stats.lock);
}
@@ -0,0 +1,164 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_HW_STATS_H_
#define _FBNIC_HW_STATS_H_
#include <linux/ethtool.h>
#include <linux/spinlock.h>
#include "fbnic_csr.h"
struct fbnic_stat_counter {
u64 value;
union {
u32 old_reg_value_32;
u64 old_reg_value_64;
} u;
bool reported;
};
struct fbnic_hw_stat {
struct fbnic_stat_counter frames;
struct fbnic_stat_counter bytes;
};
struct fbnic_fec_stats {
struct fbnic_stat_counter corrected_blocks, uncorrectable_blocks;
};
struct fbnic_pcs_stats {
struct {
struct fbnic_stat_counter lanes[FBNIC_PCS_MAX_LANES];
} SymbolErrorDuringCarrier;
};
/* Note: not updated by fbnic_get_hw_stats() */
struct fbnic_eth_ctrl_stats {
struct fbnic_stat_counter MACControlFramesTransmitted;
struct fbnic_stat_counter MACControlFramesReceived;
};
/* Note: not updated by fbnic_get_hw_stats() */
struct fbnic_rmon_stats {
struct fbnic_stat_counter undersize_pkts;
struct fbnic_stat_counter oversize_pkts;
struct fbnic_stat_counter fragments;
struct fbnic_stat_counter jabbers;
struct fbnic_stat_counter hist[ETHTOOL_RMON_HIST_MAX];
struct fbnic_stat_counter hist_tx[ETHTOOL_RMON_HIST_MAX];
};
/* Note: not updated by fbnic_get_hw_stats() */
struct fbnic_pause_stats {
struct fbnic_stat_counter tx_pause_frames;
struct fbnic_stat_counter rx_pause_frames;
struct fbnic_stat_counter tx_pause_storm_events;
};
struct fbnic_eth_mac_stats {
struct fbnic_stat_counter FramesTransmittedOK;
struct fbnic_stat_counter FramesReceivedOK;
struct fbnic_stat_counter FrameCheckSequenceErrors;
struct fbnic_stat_counter AlignmentErrors;
struct fbnic_stat_counter OctetsTransmittedOK;
struct fbnic_stat_counter FramesLostDueToIntMACXmitError;
struct fbnic_stat_counter OctetsReceivedOK;
struct fbnic_stat_counter FramesLostDueToIntMACRcvError;
struct fbnic_stat_counter MulticastFramesXmittedOK;
struct fbnic_stat_counter BroadcastFramesXmittedOK;
struct fbnic_stat_counter MulticastFramesReceivedOK;
struct fbnic_stat_counter BroadcastFramesReceivedOK;
struct fbnic_stat_counter FrameTooLongErrors;
};
struct fbnic_phy_stats {
struct fbnic_fec_stats fec;
struct fbnic_pcs_stats pcs;
};
struct fbnic_mac_stats {
struct fbnic_eth_mac_stats eth_mac;
struct fbnic_pause_stats pause;
struct fbnic_eth_ctrl_stats eth_ctrl;
struct fbnic_rmon_stats rmon;
};
struct fbnic_tmi_stats {
struct fbnic_hw_stat drop;
struct fbnic_stat_counter ptp_illegal_req, ptp_good_ts, ptp_bad_ts;
};
struct fbnic_tti_stats {
struct fbnic_hw_stat cm_drop, frame_drop, tbi_drop;
};
struct fbnic_rpc_stats {
struct fbnic_stat_counter unkn_etype, unkn_ext_hdr;
struct fbnic_stat_counter ipv4_frag, ipv6_frag, ipv4_esp, ipv6_esp;
struct fbnic_stat_counter tcp_opt_err, out_of_hdr_err, ovr_size_err;
};
struct fbnic_rxb_enqueue_stats {
struct fbnic_hw_stat drbo;
struct fbnic_stat_counter integrity_err, mac_err;
struct fbnic_stat_counter parser_err, frm_err;
};
struct fbnic_rxb_fifo_stats {
struct fbnic_hw_stat drop, trunc;
struct fbnic_stat_counter trans_drop, trans_ecn;
struct fbnic_stat_counter level;
};
struct fbnic_rxb_dequeue_stats {
struct fbnic_hw_stat intf, pbuf;
};
struct fbnic_rxb_stats {
struct fbnic_rxb_enqueue_stats enq[FBNIC_RXB_ENQUEUE_INDICES];
struct fbnic_rxb_fifo_stats fifo[FBNIC_RXB_FIFO_INDICES];
struct fbnic_rxb_dequeue_stats deq[FBNIC_RXB_DEQUEUE_INDICES];
};
struct fbnic_hw_q_stats {
struct fbnic_stat_counter rde_pkt_err;
struct fbnic_stat_counter rde_pkt_cq_drop;
struct fbnic_stat_counter rde_pkt_bdq_drop;
};
struct fbnic_pcie_stats {
struct fbnic_stat_counter ob_rd_tlp, ob_rd_dword;
struct fbnic_stat_counter ob_wr_tlp, ob_wr_dword;
struct fbnic_stat_counter ob_cpl_tlp, ob_cpl_dword;
struct fbnic_stat_counter ob_rd_no_tag;
struct fbnic_stat_counter ob_rd_no_cpl_cred;
struct fbnic_stat_counter ob_rd_no_np_cred;
};
struct fbnic_hw_stats {
struct fbnic_phy_stats phy;
struct fbnic_mac_stats mac;
struct fbnic_tmi_stats tmi;
struct fbnic_tti_stats tti;
struct fbnic_rpc_stats rpc;
struct fbnic_rxb_stats rxb;
struct fbnic_hw_q_stats hw_q[FBNIC_MAX_QUEUES];
struct fbnic_pcie_stats pcie;
/* Lock protecting the access to hw stats */
spinlock_t lock;
};
u64 fbnic_stat_rd64(struct fbnic_dev *fbd, u32 reg, u32 offset);
void fbnic_reset_hw_stats(struct fbnic_dev *fbd);
void fbnic_init_hw_stats(struct fbnic_dev *fbd);
void fbnic_get_hw_q_stats(struct fbnic_dev *fbd,
struct fbnic_hw_q_stats *hw_q);
void fbnic_get_hw_stats32(struct fbnic_dev *fbd);
void fbnic_get_hw_stats(struct fbnic_dev *fbd);
#endif /* _FBNIC_HW_STATS_H_ */
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/hwmon.h>
#include "fbnic.h"
#include "fbnic_mac.h"
static int fbnic_hwmon_sensor_id(enum hwmon_sensor_types type)
{
if (type == hwmon_temp)
return FBNIC_SENSOR_TEMP;
if (type == hwmon_in)
return FBNIC_SENSOR_VOLTAGE;
return -EOPNOTSUPP;
}
static umode_t fbnic_hwmon_is_visible(const void *drvdata,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
if (type == hwmon_temp && attr == hwmon_temp_input)
return 0444;
if (type == hwmon_in && attr == hwmon_in_input)
return 0444;
return 0;
}
static int fbnic_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct fbnic_dev *fbd = dev_get_drvdata(dev);
const struct fbnic_mac *mac = fbd->mac;
int id;
id = fbnic_hwmon_sensor_id(type);
return id < 0 ? id : mac->get_sensor(fbd, id, val);
}
static const struct hwmon_ops fbnic_hwmon_ops = {
.is_visible = fbnic_hwmon_is_visible,
.read = fbnic_hwmon_read,
};
static const struct hwmon_channel_info *fbnic_hwmon_info[] = {
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
HWMON_CHANNEL_INFO(in, HWMON_I_INPUT),
NULL
};
static const struct hwmon_chip_info fbnic_chip_info = {
.ops = &fbnic_hwmon_ops,
.info = fbnic_hwmon_info,
};
void fbnic_hwmon_register(struct fbnic_dev *fbd)
{
if (!IS_REACHABLE(CONFIG_HWMON))
return;
fbd->hwmon = hwmon_device_register_with_info(fbd->dev, "fbnic",
fbd, &fbnic_chip_info,
NULL);
if (IS_ERR(fbd->hwmon)) {
dev_notice(fbd->dev,
"Failed to register hwmon device %pe\n",
fbd->hwmon);
fbd->hwmon = NULL;
}
}
void fbnic_hwmon_unregister(struct fbnic_dev *fbd)
{
if (!IS_REACHABLE(CONFIG_HWMON) || !fbd->hwmon)
return;
hwmon_device_unregister(fbd->hwmon);
fbd->hwmon = NULL;
}
@@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/pci.h>
#include <linux/types.h>
#include "fbnic.h"
#include "fbnic_netdev.h"
#include "fbnic_txrx.h"
static irqreturn_t fbnic_fw_msix_intr(int __always_unused irq, void *data)
{
struct fbnic_dev *fbd = (struct fbnic_dev *)data;
fbnic_mbx_poll(fbd);
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(0), 1u << FBNIC_FW_MSIX_ENTRY);
return IRQ_HANDLED;
}
static int __fbnic_fw_enable_mbx(struct fbnic_dev *fbd, int vector)
{
int err;
/* Initialize mailbox and attempt to poll it into ready state */
fbnic_mbx_init(fbd);
err = fbnic_mbx_poll_tx_ready(fbd);
if (err) {
dev_warn(fbd->dev, "FW mailbox did not enter ready state\n");
return err;
}
/* Enable interrupt and unmask the vector */
enable_irq(vector);
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(0), 1u << FBNIC_FW_MSIX_ENTRY);
return 0;
}
/**
* fbnic_fw_request_mbx - Configure and initialize Firmware Mailbox
* @fbd: Pointer to device to initialize
*
* This function will allocate the IRQ and then reinitialize the mailbox
* starting communication between the host and firmware.
*
* Return: non-zero on failure.
**/
int fbnic_fw_request_mbx(struct fbnic_dev *fbd)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int vector, err;
WARN_ON(fbd->fw_msix_vector);
vector = pci_irq_vector(pdev, FBNIC_FW_MSIX_ENTRY);
if (vector < 0)
return vector;
/* Request the IRQ for FW Mailbox vector. */
err = request_threaded_irq(vector, NULL, &fbnic_fw_msix_intr,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
dev_name(fbd->dev), fbd);
if (err)
return err;
/* Initialize mailbox and attempt to poll it into ready state */
err = __fbnic_fw_enable_mbx(fbd, vector);
if (err)
free_irq(vector, fbd);
fbd->fw_msix_vector = vector;
return err;
}
/**
* fbnic_fw_disable_mbx - Temporarily place mailbox in standby state
* @fbd: Pointer to device
*
* Shutdown the mailbox by notifying the firmware to stop sending us logs, mask
* and synchronize the IRQ, and then clean up the rings.
**/
static void fbnic_fw_disable_mbx(struct fbnic_dev *fbd)
{
/* Disable interrupt and synchronize the IRQ */
disable_irq(fbd->fw_msix_vector);
/* Mask the vector */
fbnic_wr32(fbd, FBNIC_INTR_MASK_SET(0), 1u << FBNIC_FW_MSIX_ENTRY);
/* Make sure disabling logs message is sent, must be done here to
* avoid risk of completing without a running interrupt.
*/
fbnic_mbx_flush_tx(fbd);
fbnic_mbx_clean(fbd);
}
/**
* fbnic_fw_free_mbx - Disable mailbox and place it in standby state
* @fbd: Pointer to device to disable
*
* This function will disable the mailbox interrupt, free any messages still
* in the mailbox and place it into a disabled state. The firmware is
* expected to see the update and assume that the host is in the reset state.
**/
void fbnic_fw_free_mbx(struct fbnic_dev *fbd)
{
/* Vector has already been freed */
if (!fbd->fw_msix_vector)
return;
fbnic_fw_disable_mbx(fbd);
/* Free the vector */
free_irq(fbd->fw_msix_vector, fbd);
fbd->fw_msix_vector = 0;
}
static irqreturn_t fbnic_mac_msix_intr(int __always_unused irq, void *data)
{
struct fbnic_dev *fbd = data;
struct fbnic_net *fbn;
if (fbd->mac->get_link_event(fbd) == FBNIC_LINK_EVENT_NONE) {
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(0),
1u << FBNIC_PCS_MSIX_ENTRY);
return IRQ_HANDLED;
}
fbn = netdev_priv(fbd->netdev);
/* Record link down events */
if (!fbd->mac->get_link(fbd, fbn->aui, fbn->fec))
phylink_pcs_change(fbn->pcs, false);
return IRQ_HANDLED;
}
/**
* fbnic_mac_request_irq - Configure the MAC to enable it to advertise link
* @fbd: Pointer to device to initialize
*
* This function provides basic bringup for the MAC/PHY IRQ. For now the IRQ
* will remain disabled until we start the MAC/PCS/PHY logic via phylink.
*
* Return: non-zero on failure.
**/
int fbnic_mac_request_irq(struct fbnic_dev *fbd)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int vector, err;
WARN_ON(fbd->mac_msix_vector);
vector = pci_irq_vector(pdev, FBNIC_PCS_MSIX_ENTRY);
if (vector < 0)
return vector;
/* Request the IRQ for PCS link vector.
* Map PCS cause to it, and unmask it
*/
err = request_irq(vector, &fbnic_mac_msix_intr, 0,
fbd->netdev->name, fbd);
if (err)
return err;
/* Map and enable interrupt, unmask vector after link is configured */
fbnic_wr32(fbd, FBNIC_INTR_MSIX_CTRL(FBNIC_INTR_MSIX_CTRL_PCS_IDX),
FBNIC_PCS_MSIX_ENTRY | FBNIC_INTR_MSIX_CTRL_ENABLE);
fbnic_wr32(fbd, FBNIC_INTR_MSIX_CTRL(FBNIC_INTR_MSIX_CTRL_RXB_IDX), 0);
fbd->mac_msix_vector = vector;
return 0;
}
/**
* fbnic_mac_free_irq - Teardown the MAC IRQ to prepare for stopping
* @fbd: Pointer to device that is stopping
*
* This function undoes the work done in fbnic_mac_request_irq and prepares
* the device to no longer receive traffic on the host interface.
**/
void fbnic_mac_free_irq(struct fbnic_dev *fbd)
{
/* Vector has already been freed */
if (!fbd->mac_msix_vector)
return;
/* Disable interrupt */
fbnic_wr32(fbd, FBNIC_INTR_MSIX_CTRL(FBNIC_INTR_MSIX_CTRL_PCS_IDX),
FBNIC_PCS_MSIX_ENTRY);
fbnic_wrfl(fbd);
/* Synchronize IRQ to prevent race that would unmask vector */
synchronize_irq(fbd->mac_msix_vector);
/* Mask the vector */
fbnic_wr32(fbd, FBNIC_INTR_MASK_SET(0), 1u << FBNIC_PCS_MSIX_ENTRY);
/* Free the vector */
free_irq(fbd->mac_msix_vector, fbd);
fbd->mac_msix_vector = 0;
}
void fbnic_synchronize_irq(struct fbnic_dev *fbd, int nr)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int irq = pci_irq_vector(pdev, nr);
if (irq < 0)
return;
synchronize_irq(irq);
}
int fbnic_request_irq(struct fbnic_dev *fbd, int nr, irq_handler_t handler,
unsigned long flags, const char *name, void *data)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int irq = pci_irq_vector(pdev, nr);
if (irq < 0)
return irq;
return request_irq(irq, handler, flags, name, data);
}
void fbnic_free_irq(struct fbnic_dev *fbd, int nr, void *data)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int irq = pci_irq_vector(pdev, nr);
if (irq < 0)
return;
free_irq(irq, data);
}
struct fbnic_msix_test_data {
struct fbnic_dev *fbd;
unsigned long test_msix_status[BITS_TO_LONGS(FBNIC_MAX_MSIX_VECS)];
int irq_vector[FBNIC_MAX_MSIX_VECS];
};
static irqreturn_t fbnic_irq_test(int irq, void *data)
{
struct fbnic_msix_test_data *test_data = data;
struct fbnic_dev *fbd = test_data->fbd;
int i;
for (i = fbd->num_irqs; i--;) {
if (test_data->irq_vector[i] == irq) {
set_bit(i, test_data->test_msix_status);
break;
}
}
return IRQ_HANDLED;
}
/**
* fbnic_msix_test - Verify behavior of NIC interrupts
* @fbd: device to test
*
* This function is meant to test the global interrupt registers and the
* PCIe IP MSI-X functionality. It essentially goes through and tests
* various combinations of the set, clear, and mask bits in order to
* verify the behavior is as we expect it to be from the driver.
*
* Return: See enum fbnic_msix_self_test_codes
**/
enum fbnic_msix_self_test_codes fbnic_msix_test(struct fbnic_dev *fbd)
{
enum fbnic_msix_self_test_codes result = FBNIC_TEST_MSIX_SUCCESS;
struct pci_dev *pdev = to_pci_dev(fbd->dev);
struct fbnic_msix_test_data *test_data;
u32 mask = 0;
int i;
/* Allocate bitmap and IRQ vector table */
test_data = kzalloc_obj(*test_data, GFP_KERNEL);
/* memory allocation failure */
if (!test_data)
return FBNIC_TEST_MSIX_NOMEM;
/* Initialize test data */
test_data->fbd = fbd;
for (i = FBNIC_NON_NAPI_VECTORS; i < fbd->num_irqs; i++) {
/* Add IRQ to vector table so it can be found */
test_data->irq_vector[i] = pci_irq_vector(pdev, i);
/* Enable the interrupt */
if (!fbnic_request_irq(fbd, i, fbnic_irq_test, 0,
fbd->netdev->name, test_data))
continue;
while (i-- > FBNIC_NON_NAPI_VECTORS)
fbnic_free_irq(fbd, i, test_data);
kfree(test_data);
/* IRQ request failure */
return FBNIC_TEST_MSIX_IRQ_REQ_FAIL;
}
/* Test each bit individually */
for (i = FBNIC_NON_NAPI_VECTORS; i < fbd->num_irqs; i++) {
mask = 1U << (i % 32);
/* Start with mask set and interrupt cleared */
fbnic_wr32(fbd, FBNIC_INTR_MASK_SET(i / 32), mask);
fbnic_wrfl(fbd);
fbnic_wr32(fbd, FBNIC_INTR_CLEAR(i / 32), mask);
fbnic_wrfl(fbd);
/* masking failure to prevent interrupt */
result = FBNIC_TEST_MSIX_MASK;
fbnic_wr32(fbd, FBNIC_INTR_SET(i / 32), mask);
fbnic_wrfl(fbd);
usleep_range(10000, 11000);
if (test_bit(i, test_data->test_msix_status))
break;
/* unmasking failure w/ sw status set */
result = FBNIC_TEST_MSIX_UNMASK;
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(i / 32), mask);
fbnic_wrfl(fbd);
usleep_range(10000, 11000);
if (!test_bit(i, test_data->test_msix_status))
break;
/* interrupt when clearing mask */
result = FBNIC_TEST_MSIX_IRQ_CLEAR;
clear_bit(i, test_data->test_msix_status);
fbnic_wr32(fbd, FBNIC_INTR_MASK_CLEAR(i / 32), mask);
fbnic_wrfl(fbd);
usleep_range(10000, 11000);
if (test_bit(i, test_data->test_msix_status))
break;
/* interrupt not triggering when not masked */
result = FBNIC_TEST_MSIX_NO_INTERRUPT;
fbnic_wr32(fbd, FBNIC_INTR_SET(i / 32), mask);
fbnic_wrfl(fbd);
usleep_range(10000, 11000);
if (!test_bit(i, test_data->test_msix_status))
break;
/* status not cleared, or mask not set */
result = FBNIC_TEST_MSIX_NO_CLEAR_OR_MASK;
if (mask & fbnic_rd32(fbd, FBNIC_INTR_STATUS(i / 32)))
break;
if (!(mask & fbnic_rd32(fbd, FBNIC_INTR_MASK(i / 32))))
break;
/* Result = 0 - Success */
result = FBNIC_TEST_MSIX_SUCCESS;
clear_bit(i, test_data->test_msix_status);
}
if (i < fbd->num_irqs) {
fbnic_wr32(fbd, FBNIC_INTR_MASK_SET(i / 32), mask);
fbnic_wrfl(fbd);
fbnic_wr32(fbd, FBNIC_INTR_CLEAR(i / 32), mask);
fbnic_wrfl(fbd);
clear_bit(i, test_data->test_msix_status);
}
for (i = FBNIC_NON_NAPI_VECTORS; i < fbd->num_irqs; i++) {
/* Test for bits set after testing */
if (test_bit(i, test_data->test_msix_status))
result = FBNIC_TEST_MSIX_BITS_SET_AFTER_TEST;
/* Free IRQ */
fbnic_free_irq(fbd, i, test_data);
}
kfree(test_data);
return result;
}
void fbnic_napi_name_irqs(struct fbnic_dev *fbd)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(fbd->napi_irq); i++)
snprintf(fbd->napi_irq[i].name,
sizeof(fbd->napi_irq[i].name),
"%s-TxRx-%u", fbd->netdev->name, i);
}
int fbnic_napi_request_irq(struct fbnic_dev *fbd,
struct fbnic_napi_vector *nv)
{
struct fbnic_net *fbn = netdev_priv(fbd->netdev);
int i = fbnic_napi_idx(nv);
int err;
if (!fbd->napi_irq[i].users) {
err = fbnic_request_irq(fbd, nv->v_idx,
fbnic_msix_clean_rings, 0,
fbd->napi_irq[i].name,
&fbn->napi[i]);
if (err)
return err;
}
fbd->napi_irq[i].users++;
return 0;
}
void fbnic_napi_free_irq(struct fbnic_dev *fbd,
struct fbnic_napi_vector *nv)
{
struct fbnic_net *fbn = netdev_priv(fbd->netdev);
int i = fbnic_napi_idx(nv);
if (--fbd->napi_irq[i].users)
return;
fbnic_free_irq(fbd, nv->v_idx, &fbn->napi[i]);
}
void fbnic_free_irqs(struct fbnic_dev *fbd)
{
struct pci_dev *pdev = to_pci_dev(fbd->dev);
fbd->num_irqs = 0;
pci_free_irq_vectors(pdev);
}
int fbnic_alloc_irqs(struct fbnic_dev *fbd)
{
unsigned int wanted_irqs = FBNIC_NON_NAPI_VECTORS;
struct pci_dev *pdev = to_pci_dev(fbd->dev);
int num_irqs;
wanted_irqs += min_t(unsigned int, num_online_cpus(), FBNIC_MAX_RXQS);
num_irqs = pci_alloc_irq_vectors(pdev, FBNIC_NON_NAPI_VECTORS + 1,
wanted_irqs, PCI_IRQ_MSIX);
if (num_irqs < 0) {
dev_err(fbd->dev, "Failed to allocate MSI-X entries\n");
return num_irqs;
}
if (num_irqs < wanted_irqs)
dev_warn(fbd->dev, "Allocated %d IRQs, expected %d\n",
num_irqs, wanted_irqs);
fbd->num_irqs = num_irqs;
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,149 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_MAC_H_
#define _FBNIC_MAC_H_
#include <linux/types.h>
struct fbnic_dev;
/* The RXB clock runs at 600 MHZ in the ASIC and the PAUSE_STORM_UNIT_WR
* is 10us granularity, so set the clock to 6000 (0x1770)
*/
#define FBNIC_RXB_PS_CLK_DIV 0x1770
/* Convert milliseconds to pause storm timeout units (10us granularity) */
#define FBNIC_MAC_RXB_PS_TO(ms) ((ms) * 100)
/* Convert pause storm timeout units (10us granularity) to milliseconds */
#define FBNIC_MAC_RXB_PS_TO_MS(ps) ((ps) / 100)
/* Set the default timer to 500ms, which should be longer than any
* reasonable period of continuous pausing. The service task, which runs
* once per second, periodically resets the pause storm trigger.
*
* As a result, on a functioning system, if pause continues, we enforce
* a duty cycle determined by the configured pause storm timeout (50%
* default). A crashed system will not have the service task and therefore
* pause will remain disabled until reboot recovery.
*/
#define FBNIC_MAC_PS_TO_DEFAULT_MS 500
#define FBNIC_MAC_PS_TO_MAX_MS \
FBNIC_MAC_RXB_PS_TO_MS(FIELD_MAX(FBNIC_RXB_PAUSE_STORM_THLD_TIME))
#define FBNIC_MAX_JUMBO_FRAME_SIZE 9742
/* States loosely based on section 136.8.11.7.5 of IEEE 802.3-2022 Ethernet
* Standard. These are needed to track the state of the PHY as it has a delay
* of several seconds from the time link comes up until it has completed
* training that we need to wait to report the link.
*
* Currently we treat training as a single block as this is managed by the
* firmware.
*
* We have FBNIC_PMD_SEND_DATA set to 0 as the expected default at driver load
* and we initialize the structure containing it to zero at allocation.
*/
enum {
FBNIC_PMD_SEND_DATA = 0x0,
FBNIC_PMD_INITIALIZE = 0x1,
FBNIC_PMD_TRAINING = 0x2,
FBNIC_PMD_LINK_READY = 0x3,
};
enum {
FBNIC_LINK_EVENT_NONE = 0,
FBNIC_LINK_EVENT_UP = 1,
FBNIC_LINK_EVENT_DOWN = 2,
};
/* Treat the FEC bits as a bitmask laid out as follows:
* Bit 0: RS Enabled
* Bit 1: BASER(Firecode) Enabled
* Bit 2: Retrieve FEC from FW
*/
enum {
FBNIC_FEC_OFF = 0,
FBNIC_FEC_RS = 1,
FBNIC_FEC_BASER = 2,
};
/* Treat the AUI modes as a modulation/lanes bitmask:
* Bit 0: Lane Count, 0 = R1, 1 = R2
* Bit 1: Modulation, 0 = NRZ, 1 = PAM4
* Bit 2: Unknown Modulation/Lane Configuration
*/
enum {
FBNIC_AUI_25GAUI = 0, /* 25.7812GBd 25.78125 * 1 */
FBNIC_AUI_LAUI2 = 1, /* 51.5625GBd 25.78128 * 2 */
FBNIC_AUI_50GAUI1 = 2, /* 53.125GBd 53.125 * 1 */
FBNIC_AUI_100GAUI2 = 3, /* 106.25GBd 53.125 * 2 */
FBNIC_AUI_UNKNOWN = 4,
__FBNIC_AUI_MAX__
};
#define FBNIC_AUI_MODE_R2 (FBNIC_AUI_LAUI2)
#define FBNIC_AUI_MODE_PAM4 (FBNIC_AUI_50GAUI1)
enum fbnic_sensor_id {
FBNIC_SENSOR_TEMP, /* Temp in millidegrees Centigrade */
FBNIC_SENSOR_VOLTAGE, /* Voltage in millivolts */
};
/* This structure defines the interface hooks for the MAC. The MAC hooks
* will be configured as a const struct provided with a set of function
* pointers.
*
* void (*init_regs)(struct fbnic_dev *fbd);
* Initialize MAC registers to enable Tx/Rx paths and FIFOs.
*
* int (*get_link_event)(struct fbnic_dev *fbd)
* Get the current link event status, reports true if link has
* changed to either FBNIC_LINK_EVENT_DOWN or FBNIC_LINK_EVENT_UP
* bool (*get_link)(struct fbnic_dev *fbd, u8 aui, u8 fec);
* Check link status
*
* void (*prepare)(struct fbnic_dev *fbd, u8 aui, u8 fec);
* Prepare PHY for init by fetching settings, disabling interrupts,
* and sending an updated PHY config to FW if needed.
*
* void (*link_down)(struct fbnic_dev *fbd);
* Configure MAC for link down event
* void (*link_up)(struct fbnic_dev *fbd, bool tx_pause, bool rx_pause);
* Configure MAC for link up event;
*
*/
struct fbnic_mac {
void (*init_regs)(struct fbnic_dev *fbd);
int (*get_link_event)(struct fbnic_dev *fbd);
bool (*get_link)(struct fbnic_dev *fbd, u8 aui, u8 fec);
void (*prepare)(struct fbnic_dev *fbd, u8 aui, u8 fec);
void (*get_fec_stats)(struct fbnic_dev *fbd, bool reset,
struct fbnic_fec_stats *fec_stats);
void (*get_pcs_stats)(struct fbnic_dev *fbd, bool reset,
struct fbnic_pcs_stats *pcs_stats);
void (*get_eth_mac_stats)(struct fbnic_dev *fbd, bool reset,
struct fbnic_eth_mac_stats *mac_stats);
void (*get_pause_stats)(struct fbnic_dev *fbd, bool reset,
struct fbnic_pause_stats *pause_stats);
void (*get_eth_ctrl_stats)(struct fbnic_dev *fbd, bool reset,
struct fbnic_eth_ctrl_stats *ctrl_stats);
void (*get_rmon_stats)(struct fbnic_dev *fbd, bool reset,
struct fbnic_rmon_stats *rmon_stats);
void (*link_down)(struct fbnic_dev *fbd);
void (*link_up)(struct fbnic_dev *fbd, bool tx_pause, bool rx_pause);
int (*get_sensor)(struct fbnic_dev *fbd, int id, long *val);
};
int fbnic_mac_init(struct fbnic_dev *fbd);
void fbnic_mac_get_fw_settings(struct fbnic_dev *fbd, u8 *aui, u8 *fec);
int fbnic_mac_ps_protect_to_config(struct fbnic_dev *fbd, u16 timeout);
void fbnic_mac_ps_protect_handler(struct fbnic_dev *fbd);
bool fbnic_mac_check_tx_pause(struct fbnic_dev *fbd);
#endif /* _FBNIC_MAC_H_ */
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/mdio.h>
#include <linux/pcs/pcs-xpcs.h>
#include "fbnic.h"
#include "fbnic_netdev.h"
/* fbnic MDIO Interface Layout
*
* +-------------------+
* | MAC |
* +-------------------+
* | | | | <-- 25GMII, 50GMII, or CGMII
* +-------------------+
* MMD 3 | PCS |
* +-------------------+
* | FEC |
* +-------------------+
* MMD 8 | Separated PMA |
* +-------------------+
* | | <-- PMD Service Interface
* +-------------------+
* MMD 1 | PMD |
* +-------------------+
*/
#define DW_VENDOR BIT(15)
#define FBNIC_PCS_VENDOR BIT(9)
#define FBNIC_PCS_ZERO_MASK (DW_VENDOR - FBNIC_PCS_VENDOR)
static int
fbnic_mdio_ids(int id, int regnum)
{
/* return correct IDs */
switch (regnum) {
case MDIO_DEVID1:
return id >> 16;
case MDIO_DEVID2:
return id & 0xffff;
case MDIO_DEVS1:
return MDIO_DEVS_SEP_PMA1 | MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS;
case MDIO_DEVS2:
return 0;
case MDIO_STAT2:
return MDIO_STAT2_DEVPRST_VAL;
}
return 0;
}
static int
fbnic_mdio_read_pmd(struct fbnic_dev *fbd, int addr, int regnum)
{
u8 aui = FBNIC_AUI_UNKNOWN;
struct fbnic_net *fbn;
int ret = 0;
/* We don't need a second PMD, just one can handle both lanes */
if (addr)
return 0;
if (fbd->netdev) {
fbn = netdev_priv(fbd->netdev);
if (fbn->aui < FBNIC_AUI_UNKNOWN)
aui = fbn->aui;
}
switch (regnum) {
case MDIO_PMA_RXDET:
/* If training isn't complete default to 0 */
if (fbd->pmd_state != FBNIC_PMD_SEND_DATA)
break;
/* Report either 1 or 2 lanes detected depending on config */
ret = (MDIO_PMD_RXDET_GLOBAL | MDIO_PMD_RXDET_0) |
((aui & FBNIC_AUI_MODE_R2) *
(MDIO_PMD_RXDET_1 / FBNIC_AUI_MODE_R2));
break;
default:
ret = fbnic_mdio_ids(MP_FBNIC_XPCS_PMA_100G_ID, regnum);
break;
}
dev_dbg(fbd->dev,
"SWMII PMD Rd: Addr: %d RegNum: %d Value: 0x%04x\n",
addr, regnum, ret);
return ret;
}
static int
fbnic_mdio_read_pcs(struct fbnic_dev *fbd, int addr, int regnum)
{
int ret, offset = 0, overrides = 0;
/* We will need access to both PCS instances to get config info */
if (addr >= 2)
return 0;
/* Report 0 for reserved registers */
if (regnum & FBNIC_PCS_ZERO_MASK)
return 0;
/* Intercept and return correct ID for PCS */
switch (regnum) {
case MDIO_DEVID1 ... MDIO_DEVID2:
ret = fbnic_mdio_ids(DW_XPCS_ID, regnum);
break;
case MDIO_DEVS1:
/* DW IP returns MDIO_DEVS_SEP_PMA1, MDIO_DEVS_PMAPMD,
* and MDIO_DEVS_PCS as 0
*/
overrides = fbnic_mdio_ids(DW_XPCS_ID, regnum);
fallthrough;
default:
/* Swap vendor page bit for FBNIC PCS vendor page bit */
if (regnum & DW_VENDOR)
offset ^= DW_VENDOR | FBNIC_PCS_VENDOR;
ret = fbnic_rd32(fbd, FBNIC_PCS_PAGE(addr) + (regnum ^ offset));
ret |= overrides;
break;
}
dev_dbg(fbd->dev,
"SWMII PCS Rd: Addr: %d RegNum: %d Value: 0x%04x\n",
addr, regnum, ret);
return ret;
}
static int
fbnic_mdio_read_pma(struct fbnic_dev *fbd, int addr, int regnum)
{
int ret = 0;
/* We will need access to both PMA instances to get config info */
if (addr >= 2)
return 0;
switch (regnum) {
case MDIO_PMA_RSFEC_CTRL ... MDIO_PMA_RSFEC_LANE_MAP:
ret = fbnic_rd32(fbd, FBNIC_RSFEC_CONTROL(addr) +
regnum - MDIO_PMA_RSFEC_CTRL);
break;
default:
ret = fbnic_mdio_ids(MP_FBNIC_XPCS_PMA_100G_ID, regnum);
break;
}
dev_dbg(fbd->dev,
"SWMII PMA Rd: Addr: %d RegNum: %d Value: 0x%04x\n",
addr, regnum, ret);
return ret;
}
static int
fbnic_mdio_read_c45(struct mii_bus *bus, int addr, int devnum, int regnum)
{
struct fbnic_dev *fbd = bus->priv;
if (devnum == MDIO_MMD_PMAPMD)
return fbnic_mdio_read_pmd(fbd, addr, regnum);
if (devnum == MDIO_MMD_PCS)
return fbnic_mdio_read_pcs(fbd, addr, regnum);
if (devnum == MDIO_MMD_SEP_PMA1)
return fbnic_mdio_read_pma(fbd, addr, regnum);
return 0;
}
static void
fbnic_mdio_write_pmd(struct fbnic_dev *fbd, int addr, int regnum, u16 val)
{
dev_dbg(fbd->dev,
"SWMII PMD Wr: Addr: %d RegNum: %d Value: 0x%04x\n",
addr, regnum, val);
}
static void
fbnic_mdio_write_pcs(struct fbnic_dev *fbd, int addr, int regnum, u16 val)
{
dev_dbg(fbd->dev,
"SWMII PCS Wr: Addr: %d RegNum: %d Value: 0x%04x\n",
addr, regnum, val);
/* Allow access to both halves of PCS for 50R2 config */
if (addr >= 2)
return;
/* Skip write for reserved registers */
if (regnum & FBNIC_PCS_ZERO_MASK)
return;
/* Swap vendor page bit for FBNIC PCS vendor page bit */
if (regnum & DW_VENDOR)
regnum ^= DW_VENDOR | FBNIC_PCS_VENDOR;
fbnic_wr32(fbd, FBNIC_PCS_PAGE(addr) + regnum, val);
}
static void
fbnic_mdio_write_pma(struct fbnic_dev *fbd, int addr, int regnum, u16 val)
{
dev_dbg(fbd->dev,
"SWMII PMA Wr: Addr: %d RegNum: %d Value: 0x%04x\n",
addr, regnum, val);
if (addr >= 2)
return;
switch (regnum) {
case MDIO_PMA_RSFEC_CTRL ... MDIO_PMA_RSFEC_LANE_MAP:
fbnic_wr32(fbd, FBNIC_RSFEC_CONTROL(addr) +
regnum - MDIO_PMA_RSFEC_CTRL, val);
break;
default:
break;
}
}
static int
fbnic_mdio_write_c45(struct mii_bus *bus, int addr, int devnum,
int regnum, u16 val)
{
struct fbnic_dev *fbd = bus->priv;
if (devnum == MDIO_MMD_PMAPMD)
fbnic_mdio_write_pmd(fbd, addr, regnum, val);
if (devnum == MDIO_MMD_PCS)
fbnic_mdio_write_pcs(fbd, addr, regnum, val);
if (devnum == MDIO_MMD_SEP_PMA1)
fbnic_mdio_write_pma(fbd, addr, regnum, val);
return 0;
}
/**
* fbnic_mdiobus_create - Create an MDIO bus to allow interfacing w/ PHYs
* @fbd: Pointer to FBNIC device structure to populate bus on
*
* Initialize an MDIO bus and place a pointer to it on the fbd struct. This bus
* will be used to interface with the PMA/PMD and PCS.
*
* Return: 0 on success, negative on failure
**/
int fbnic_mdiobus_create(struct fbnic_dev *fbd)
{
struct mii_bus *bus;
int err;
bus = devm_mdiobus_alloc(fbd->dev);
if (!bus)
return -ENOMEM;
bus->name = "fbnic_mii_bus";
bus->read_c45 = &fbnic_mdio_read_c45;
bus->write_c45 = &fbnic_mdio_write_c45;
/* Disable PHY auto probing. We will add PCS manually */
bus->phy_mask = ~0;
bus->parent = fbd->dev;
bus->priv = fbd;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(fbd->dev));
err = devm_mdiobus_register(fbd->dev, bus);
if (err) {
dev_err(fbd->dev, "Failed to create MDIO bus: %d\n", err);
return err;
}
fbd->mdio_bus = bus;
return 0;
}
@@ -0,0 +1,891 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/etherdevice.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/netdev_queues.h>
#include "fbnic.h"
#include "fbnic_netdev.h"
#include "fbnic_txrx.h"
int __fbnic_open(struct fbnic_net *fbn)
{
struct fbnic_dev *fbd = fbn->fbd;
int err;
err = fbnic_alloc_napi_vectors(fbn);
if (err)
return err;
err = fbnic_alloc_resources(fbn);
if (err)
goto free_napi_vectors;
err = fbnic_set_netif_queues(fbn);
if (err)
goto free_resources;
/* Send ownership message and flush to verify FW has seen it */
err = fbnic_fw_xmit_ownership_msg(fbd, true);
if (err) {
dev_warn(fbd->dev,
"Error %d sending host ownership message to the firmware\n",
err);
goto err_reset_queues;
}
err = fbnic_time_start(fbn);
if (err)
goto release_ownership;
err = fbnic_fw_init_heartbeat(fbd, false);
if (err)
goto time_stop;
err = fbnic_mac_request_irq(fbd);
if (err)
goto time_stop;
/* Pull the BMC config and initialize the RPC */
fbnic_bmc_rpc_init(fbd);
fbnic_rss_reinit(fbd, fbn);
phylink_resume(fbn->phylink);
return 0;
time_stop:
fbnic_time_stop(fbn);
release_ownership:
fbnic_fw_xmit_ownership_msg(fbn->fbd, false);
err_reset_queues:
fbnic_reset_netif_queues(fbn);
free_resources:
fbnic_free_resources(fbn);
free_napi_vectors:
fbnic_free_napi_vectors(fbn);
return err;
}
static int fbnic_open(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
int err;
fbnic_napi_name_irqs(fbn->fbd);
err = __fbnic_open(fbn);
if (!err)
fbnic_up(fbn);
return err;
}
static int fbnic_stop(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
fbnic_mac_free_irq(fbn->fbd);
phylink_suspend(fbn->phylink, fbnic_bmc_present(fbn->fbd));
fbnic_down(fbn);
fbnic_time_stop(fbn);
fbnic_fw_xmit_ownership_msg(fbn->fbd, false);
fbnic_reset_netif_queues(fbn);
fbnic_free_resources(fbn);
fbnic_free_napi_vectors(fbn);
return 0;
}
static int fbnic_uc_sync(struct net_device *netdev, const unsigned char *addr)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_mac_addr *avail_addr;
if (WARN_ON(!is_valid_ether_addr(addr)))
return -EADDRNOTAVAIL;
avail_addr = __fbnic_uc_sync(fbn->fbd, addr);
if (!avail_addr)
return -ENOSPC;
/* Add type flag indicating this address is in use by the host */
set_bit(FBNIC_MAC_ADDR_T_UNICAST, avail_addr->act_tcam);
return 0;
}
static int fbnic_uc_unsync(struct net_device *netdev, const unsigned char *addr)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
int i, ret;
/* Scan from middle of list to bottom, filling bottom up.
* Skip the first entry which is reserved for dev_addr and
* leave the last entry to use for promiscuous filtering.
*/
for (i = fbd->mac_addr_boundary, ret = -ENOENT;
i < FBNIC_RPC_TCAM_MACDA_HOST_ADDR_IDX && ret; i++) {
struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[i];
if (!ether_addr_equal(mac_addr->value.addr8, addr))
continue;
ret = __fbnic_uc_unsync(mac_addr);
}
return ret;
}
static int fbnic_mc_sync(struct net_device *netdev, const unsigned char *addr)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_mac_addr *avail_addr;
if (WARN_ON(!is_multicast_ether_addr(addr)))
return -EADDRNOTAVAIL;
avail_addr = __fbnic_mc_sync(fbn->fbd, addr);
if (!avail_addr)
return -ENOSPC;
/* Add type flag indicating this address is in use by the host */
set_bit(FBNIC_MAC_ADDR_T_MULTICAST, avail_addr->act_tcam);
return 0;
}
static int fbnic_mc_unsync(struct net_device *netdev, const unsigned char *addr)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
int i, ret;
/* Scan from middle of list to top, filling top down.
* Skip over the address reserved for the BMC MAC and
* exclude index 0 as that belongs to the broadcast address
*/
for (i = fbd->mac_addr_boundary, ret = -ENOENT;
--i > FBNIC_RPC_TCAM_MACDA_BROADCAST_IDX && ret;) {
struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[i];
if (!ether_addr_equal(mac_addr->value.addr8, addr))
continue;
ret = __fbnic_mc_unsync(mac_addr);
}
return ret;
}
void __fbnic_set_rx_mode(struct fbnic_dev *fbd,
struct netdev_hw_addr_list *uc,
struct netdev_hw_addr_list *mc)
{
bool uc_promisc = false, mc_promisc = false;
struct net_device *netdev = fbd->netdev;
struct fbnic_mac_addr *mac_addr;
int err;
/* Populate host address from dev_addr */
mac_addr = &fbd->mac_addr[FBNIC_RPC_TCAM_MACDA_HOST_ADDR_IDX];
if (!ether_addr_equal(mac_addr->value.addr8, netdev->dev_addr) ||
mac_addr->state != FBNIC_TCAM_S_VALID) {
ether_addr_copy(mac_addr->value.addr8, netdev->dev_addr);
mac_addr->state = FBNIC_TCAM_S_UPDATE;
set_bit(FBNIC_MAC_ADDR_T_UNICAST, mac_addr->act_tcam);
}
/* Populate broadcast address if broadcast is enabled */
mac_addr = &fbd->mac_addr[FBNIC_RPC_TCAM_MACDA_BROADCAST_IDX];
if (netdev->flags & IFF_BROADCAST) {
if (!is_broadcast_ether_addr(mac_addr->value.addr8) ||
mac_addr->state != FBNIC_TCAM_S_VALID) {
eth_broadcast_addr(mac_addr->value.addr8);
mac_addr->state = FBNIC_TCAM_S_ADD;
}
set_bit(FBNIC_MAC_ADDR_T_BROADCAST, mac_addr->act_tcam);
} else if (mac_addr->state == FBNIC_TCAM_S_VALID) {
__fbnic_xc_unsync(mac_addr, FBNIC_MAC_ADDR_T_BROADCAST);
}
/* Synchronize unicast and multicast address lists */
err = __hw_addr_sync_dev(uc, netdev, fbnic_uc_sync, fbnic_uc_unsync);
if (err == -ENOSPC)
uc_promisc = true;
err = __hw_addr_sync_dev(mc, netdev, fbnic_mc_sync, fbnic_mc_unsync);
if (err == -ENOSPC)
mc_promisc = true;
uc_promisc |= !!(netdev->flags & IFF_PROMISC);
mc_promisc |= !!(netdev->flags & IFF_ALLMULTI) || uc_promisc;
/* Update the promiscuous rules */
fbnic_promisc_sync(fbd, uc_promisc, mc_promisc);
/* Add rules for BMC all multicast if it is enabled */
fbnic_bmc_rpc_all_multi_config(fbd, mc_promisc);
/* Sift out any unshared BMC rules and place them in BMC only section */
fbnic_sift_macda(fbd);
/* Write updates to hardware */
fbnic_write_rules(fbd);
fbnic_write_macda(fbd);
fbnic_write_tce_tcam(fbd);
}
static int fbnic_set_rx_mode(struct net_device *netdev,
struct netdev_hw_addr_list *uc,
struct netdev_hw_addr_list *mc)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
/* No need to update the hardware if we are not running */
if (netif_running(netdev))
__fbnic_set_rx_mode(fbd, uc, mc);
return 0;
}
static int fbnic_set_mac(struct net_device *netdev, void *p)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct sockaddr *addr = p;
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
eth_hw_addr_set(netdev, addr->sa_data);
if (netif_running(netdev)) {
netif_addr_lock_bh(netdev);
__fbnic_set_rx_mode(fbn->fbd, &netdev->uc, &netdev->mc);
netif_addr_unlock_bh(netdev);
}
return 0;
}
static int fbnic_change_mtu(struct net_device *dev, int new_mtu)
{
struct fbnic_net *fbn = netdev_priv(dev);
if (fbnic_check_split_frames(fbn->xdp_prog, new_mtu, fbn->hds_thresh)) {
dev_err(&dev->dev,
"MTU %d is larger than HDS threshold %d in XDP mode\n",
new_mtu, fbn->hds_thresh);
return -EINVAL;
}
WRITE_ONCE(dev->mtu, new_mtu);
return 0;
}
void fbnic_clear_rx_mode(struct fbnic_dev *fbd)
{
struct net_device *netdev = fbd->netdev;
int idx;
for (idx = ARRAY_SIZE(fbd->mac_addr); idx--;) {
struct fbnic_mac_addr *mac_addr = &fbd->mac_addr[idx];
if (mac_addr->state != FBNIC_TCAM_S_VALID)
continue;
bitmap_clear(mac_addr->act_tcam,
FBNIC_MAC_ADDR_T_HOST_START,
FBNIC_MAC_ADDR_T_HOST_LEN);
if (bitmap_empty(mac_addr->act_tcam,
FBNIC_RPC_TCAM_ACT_NUM_ENTRIES))
mac_addr->state = FBNIC_TCAM_S_DELETE;
}
/* Write updates to hardware */
fbnic_write_macda(fbd);
netif_addr_lock_bh(netdev);
__dev_uc_unsync(netdev, NULL);
__dev_mc_unsync(netdev, NULL);
netif_addr_unlock_bh(netdev);
}
static int fbnic_hwtstamp_get(struct net_device *netdev,
struct kernel_hwtstamp_config *config)
{
struct fbnic_net *fbn = netdev_priv(netdev);
*config = fbn->hwtstamp_config;
return 0;
}
static int fbnic_hwtstamp_set(struct net_device *netdev,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack)
{
struct fbnic_net *fbn = netdev_priv(netdev);
int old_rx_filter;
if (config->source != HWTSTAMP_SOURCE_NETDEV)
return -EOPNOTSUPP;
if (!kernel_hwtstamp_config_changed(config, &fbn->hwtstamp_config))
return 0;
/* Upscale the filters */
switch (config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
case HWTSTAMP_FILTER_ALL:
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_EVENT:
break;
case HWTSTAMP_FILTER_NTP_ALL:
config->rx_filter = HWTSTAMP_FILTER_ALL;
break;
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
config->rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
break;
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT;
break;
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
break;
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
break;
default:
return -ERANGE;
}
/* Configure */
old_rx_filter = fbn->hwtstamp_config.rx_filter;
memcpy(&fbn->hwtstamp_config, config, sizeof(*config));
if (old_rx_filter != config->rx_filter && netif_running(fbn->netdev)) {
fbnic_rss_reinit(fbn->fbd, fbn);
fbnic_write_rules(fbn->fbd);
}
/* Save / report back filter configuration
* Note that our filter configuration is inexact. Instead of
* filtering for a specific UDP port or L2 Ethertype we are
* filtering in all UDP or all non-IP packets for timestamping. So
* if anything other than FILTER_ALL is requested we report
* FILTER_SOME indicating that we will be timestamping a few
* additional packets.
*/
if (config->rx_filter > HWTSTAMP_FILTER_ALL)
config->rx_filter = HWTSTAMP_FILTER_SOME;
return 0;
}
static void fbnic_get_stats64(struct net_device *dev,
struct rtnl_link_stats64 *stats64)
{
u64 rx_bytes, rx_packets, rx_dropped = 0, rx_errors = 0;
u64 rx_over = 0, rx_missed = 0, rx_length = 0;
u64 tx_bytes, tx_packets, tx_dropped = 0;
struct fbnic_net *fbn = netdev_priv(dev);
struct fbnic_dev *fbd = fbn->fbd;
struct fbnic_queue_stats *stats;
unsigned int start, i;
fbnic_get_hw_stats(fbd);
stats = &fbn->tx_stats;
tx_bytes = stats->bytes;
tx_packets = stats->packets;
tx_dropped = stats->dropped;
/* Record drops from Tx HW Datapath */
spin_lock(&fbd->hw_stats.lock);
tx_dropped += fbd->hw_stats.tmi.drop.frames.value +
fbd->hw_stats.tti.cm_drop.frames.value +
fbd->hw_stats.tti.frame_drop.frames.value +
fbd->hw_stats.tti.tbi_drop.frames.value;
spin_unlock(&fbd->hw_stats.lock);
stats64->tx_bytes = tx_bytes;
stats64->tx_packets = tx_packets;
stats64->tx_dropped = tx_dropped;
for (i = 0; i < fbn->num_tx_queues; i++) {
struct fbnic_ring *txr = fbn->tx[i];
if (!txr)
continue;
stats = &txr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
tx_bytes = stats->bytes;
tx_packets = stats->packets;
tx_dropped = stats->dropped;
} while (u64_stats_fetch_retry(&stats->syncp, start));
stats64->tx_bytes += tx_bytes;
stats64->tx_packets += tx_packets;
stats64->tx_dropped += tx_dropped;
}
stats = &fbn->rx_stats;
rx_bytes = stats->bytes;
rx_packets = stats->packets;
rx_dropped = stats->dropped;
spin_lock(&fbd->hw_stats.lock);
/* Record drops for the host FIFOs.
* 4: network to Host, 6: BMC to Host
* Exclude the BMC and MC FIFOs as those stats may contain drops
* due to unrelated items such as TCAM misses. They are still
* accessible through the ethtool stats.
*/
i = FBNIC_RXB_FIFO_HOST;
rx_missed += fbd->hw_stats.rxb.fifo[i].drop.frames.value;
i = FBNIC_RXB_FIFO_BMC_TO_HOST;
rx_missed += fbd->hw_stats.rxb.fifo[i].drop.frames.value;
for (i = 0; i < fbd->max_num_queues; i++) {
/* Report packets dropped due to CQ/BDQ being full/empty */
rx_over += fbd->hw_stats.hw_q[i].rde_pkt_cq_drop.value;
rx_over += fbd->hw_stats.hw_q[i].rde_pkt_bdq_drop.value;
/* Report packets with errors */
rx_errors += fbd->hw_stats.hw_q[i].rde_pkt_err.value;
}
spin_unlock(&fbd->hw_stats.lock);
stats64->rx_bytes = rx_bytes;
stats64->rx_packets = rx_packets;
stats64->rx_dropped = rx_dropped;
stats64->rx_over_errors = rx_over;
stats64->rx_errors = rx_errors;
stats64->rx_missed_errors = rx_missed;
for (i = 0; i < fbn->num_rx_queues; i++) {
struct fbnic_ring *xdpr = fbn->tx[FBNIC_MAX_TXQS + i];
struct fbnic_ring *rxr = fbn->rx[i];
if (!rxr)
continue;
stats = &rxr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
rx_bytes = stats->bytes;
rx_packets = stats->packets;
rx_dropped = stats->dropped;
rx_length = stats->rx.length_errors;
} while (u64_stats_fetch_retry(&stats->syncp, start));
stats64->rx_bytes += rx_bytes;
stats64->rx_packets += rx_packets;
stats64->rx_dropped += rx_dropped;
stats64->rx_errors += rx_length;
stats64->rx_length_errors += rx_length;
if (!xdpr)
continue;
stats = &xdpr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
tx_bytes = stats->bytes;
tx_packets = stats->packets;
tx_dropped = stats->dropped;
} while (u64_stats_fetch_retry(&stats->syncp, start));
stats64->tx_bytes += tx_bytes;
stats64->tx_packets += tx_packets;
stats64->tx_dropped += tx_dropped;
}
}
bool fbnic_check_split_frames(struct bpf_prog *prog, unsigned int mtu,
u32 hds_thresh)
{
if (!prog)
return false;
if (prog->aux->xdp_has_frags)
return false;
return mtu + ETH_HLEN > hds_thresh;
}
static int fbnic_bpf(struct net_device *netdev, struct netdev_bpf *bpf)
{
struct bpf_prog *prog = bpf->prog, *prev_prog;
struct fbnic_net *fbn = netdev_priv(netdev);
if (bpf->command != XDP_SETUP_PROG)
return -EINVAL;
if (fbnic_check_split_frames(prog, netdev->mtu,
fbn->hds_thresh)) {
NL_SET_ERR_MSG_MOD(bpf->extack,
"MTU too high, or HDS threshold is too low for single buffer XDP");
return -EOPNOTSUPP;
}
prev_prog = xchg(&fbn->xdp_prog, prog);
if (prev_prog)
bpf_prog_put(prev_prog);
return 0;
}
static const struct net_device_ops fbnic_netdev_ops = {
.ndo_open = fbnic_open,
.ndo_stop = fbnic_stop,
.ndo_validate_addr = eth_validate_addr,
.ndo_start_xmit = fbnic_xmit_frame,
.ndo_features_check = fbnic_features_check,
.ndo_set_mac_address = fbnic_set_mac,
.ndo_change_mtu = fbnic_change_mtu,
.ndo_set_rx_mode_async = fbnic_set_rx_mode,
.ndo_get_stats64 = fbnic_get_stats64,
.ndo_bpf = fbnic_bpf,
.ndo_hwtstamp_get = fbnic_hwtstamp_get,
.ndo_hwtstamp_set = fbnic_hwtstamp_set,
};
static void fbnic_get_queue_stats_rx(struct net_device *dev, int idx,
struct netdev_queue_stats_rx *rx)
{
u64 bytes, packets, alloc_fail, alloc_fail_bdq;
struct fbnic_net *fbn = netdev_priv(dev);
struct fbnic_ring *rxr = fbn->rx[idx];
struct fbnic_dev *fbd = fbn->fbd;
struct fbnic_queue_stats *stats;
u64 csum_complete, csum_none;
struct fbnic_q_triad *qt;
unsigned int start;
if (!rxr)
return;
/* fbn->rx points to completion queues */
qt = container_of(rxr, struct fbnic_q_triad, cmpl);
stats = &rxr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
bytes = stats->bytes;
packets = stats->packets;
alloc_fail = stats->rx.alloc_failed;
csum_complete = stats->rx.csum_complete;
csum_none = stats->rx.csum_none;
} while (u64_stats_fetch_retry(&stats->syncp, start));
stats = &qt->sub0.stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
alloc_fail_bdq = stats->bdq.alloc_failed;
} while (u64_stats_fetch_retry(&stats->syncp, start));
alloc_fail += alloc_fail_bdq;
stats = &qt->sub1.stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
alloc_fail_bdq = stats->bdq.alloc_failed;
} while (u64_stats_fetch_retry(&stats->syncp, start));
alloc_fail += alloc_fail_bdq;
rx->bytes = bytes;
rx->packets = packets;
rx->alloc_fail = alloc_fail;
rx->csum_complete = csum_complete;
rx->csum_none = csum_none;
fbnic_get_hw_q_stats(fbd, fbd->hw_stats.hw_q);
spin_lock(&fbd->hw_stats.lock);
rx->hw_drop_overruns = fbd->hw_stats.hw_q[idx].rde_pkt_cq_drop.value +
fbd->hw_stats.hw_q[idx].rde_pkt_bdq_drop.value;
rx->hw_drops = fbd->hw_stats.hw_q[idx].rde_pkt_err.value +
rx->hw_drop_overruns;
spin_unlock(&fbd->hw_stats.lock);
}
static void fbnic_get_queue_stats_tx(struct net_device *dev, int idx,
struct netdev_queue_stats_tx *tx)
{
struct fbnic_net *fbn = netdev_priv(dev);
struct fbnic_ring *txr = fbn->tx[idx];
struct fbnic_queue_stats *stats;
u64 stop, wake, csum, lso;
struct fbnic_ring *xdpr;
unsigned int start;
u64 bytes, packets;
if (!txr)
return;
stats = &txr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
bytes = stats->bytes;
packets = stats->packets;
csum = stats->twq.csum_partial;
lso = stats->twq.lso;
stop = stats->twq.stop;
wake = stats->twq.wake;
} while (u64_stats_fetch_retry(&stats->syncp, start));
tx->bytes = bytes;
tx->packets = packets;
tx->needs_csum = csum + lso;
tx->hw_gso_wire_packets = lso;
tx->stop = stop;
tx->wake = wake;
xdpr = fbn->tx[FBNIC_MAX_TXQS + idx];
if (xdpr) {
stats = &xdpr->stats;
do {
start = u64_stats_fetch_begin(&stats->syncp);
bytes = stats->bytes;
packets = stats->packets;
} while (u64_stats_fetch_retry(&stats->syncp, start));
tx->bytes += bytes;
tx->packets += packets;
}
}
static void fbnic_get_base_stats(struct net_device *dev,
struct netdev_queue_stats_rx *rx,
struct netdev_queue_stats_tx *tx)
{
struct fbnic_net *fbn = netdev_priv(dev);
tx->bytes = fbn->tx_stats.bytes;
tx->packets = fbn->tx_stats.packets;
tx->needs_csum = fbn->tx_stats.twq.csum_partial + fbn->tx_stats.twq.lso;
tx->hw_gso_wire_packets = fbn->tx_stats.twq.lso;
tx->stop = fbn->tx_stats.twq.stop;
tx->wake = fbn->tx_stats.twq.wake;
rx->bytes = fbn->rx_stats.bytes;
rx->packets = fbn->rx_stats.packets;
rx->alloc_fail = fbn->rx_stats.rx.alloc_failed +
fbn->bdq_stats.bdq.alloc_failed;
rx->csum_complete = fbn->rx_stats.rx.csum_complete;
rx->csum_none = fbn->rx_stats.rx.csum_none;
}
static const struct netdev_stat_ops fbnic_stat_ops = {
.get_queue_stats_rx = fbnic_get_queue_stats_rx,
.get_queue_stats_tx = fbnic_get_queue_stats_tx,
.get_base_stats = fbnic_get_base_stats,
};
void fbnic_reset_queues(struct fbnic_net *fbn,
unsigned int tx, unsigned int rx)
{
struct fbnic_dev *fbd = fbn->fbd;
unsigned int max_napis;
max_napis = fbd->num_irqs - FBNIC_NON_NAPI_VECTORS;
tx = min(tx, max_napis);
fbn->num_tx_queues = tx;
rx = min(rx, max_napis);
fbn->num_rx_queues = rx;
fbn->num_napi = max(tx, rx);
}
/**
* fbnic_netdev_free - Free the netdev associate with fbnic
* @fbd: Driver specific structure to free netdev from
*
* Allocate and initialize the netdev and netdev private structure. Bind
* together the hardware, netdev, and pci data structures.
**/
void fbnic_netdev_free(struct fbnic_dev *fbd)
{
fbnic_phylink_destroy(fbd->netdev);
free_netdev(fbd->netdev);
fbd->netdev = NULL;
}
/**
* fbnic_netdev_alloc - Allocate a netdev and associate with fbnic
* @fbd: Driver specific structure to associate netdev with
*
* Allocate and initialize the netdev and netdev private structure. Bind
* together the hardware, netdev, and pci data structures.
*
* Return: Pointer to net_device on success, NULL on failure
**/
struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd)
{
struct net_device *netdev;
struct fbnic_net *fbn;
int default_queues;
netdev = alloc_etherdev_mq(sizeof(*fbn), FBNIC_MAX_RXQS);
if (!netdev)
return NULL;
SET_NETDEV_DEV(netdev, fbd->dev);
fbd->netdev = netdev;
netdev->netdev_ops = &fbnic_netdev_ops;
netdev->stat_ops = &fbnic_stat_ops;
netdev->queue_mgmt_ops = &fbnic_queue_mgmt_ops;
netdev->netmem_tx = NETMEM_TX_DMA;
fbnic_set_ethtool_ops(netdev);
fbn = netdev_priv(netdev);
fbn->netdev = netdev;
fbn->fbd = fbd;
fbn->txq_size = FBNIC_TXQ_SIZE_DEFAULT;
fbn->hpq_size = FBNIC_HPQ_SIZE_DEFAULT;
fbn->ppq_size = FBNIC_PPQ_SIZE_DEFAULT;
fbn->rcq_size = FBNIC_RCQ_SIZE_DEFAULT;
fbn->tx_usecs = FBNIC_TX_USECS_DEFAULT;
fbn->rx_usecs = FBNIC_RX_USECS_DEFAULT;
fbn->rx_max_frames = FBNIC_RX_FRAMES_DEFAULT;
/* Initialize the hds_thresh */
netdev->cfg->hds_thresh = FBNIC_HDS_THRESH_DEFAULT;
fbn->hds_thresh = FBNIC_HDS_THRESH_DEFAULT;
default_queues = netif_get_num_default_rss_queues();
if (default_queues > fbd->max_num_queues)
default_queues = fbd->max_num_queues;
fbnic_reset_queues(fbn, default_queues, default_queues);
fbnic_reset_indir_tbl(fbn);
fbnic_rss_key_fill(fbn->rss_key);
fbnic_rss_init_en_mask(fbn);
netdev->priv_flags |= IFF_UNICAST_FLT;
netdev->gso_partial_features =
NETIF_F_GSO_GRE |
NETIF_F_GSO_GRE_CSUM |
NETIF_F_GSO_IPXIP4 |
NETIF_F_GSO_UDP_TUNNEL |
NETIF_F_GSO_UDP_TUNNEL_CSUM;
netdev->features |=
netdev->gso_partial_features |
FBNIC_TUN_GSO_FEATURES |
NETIF_F_RXHASH |
NETIF_F_SG |
NETIF_F_HW_CSUM |
NETIF_F_RXCSUM |
NETIF_F_TSO |
NETIF_F_TSO_ECN |
NETIF_F_TSO6 |
NETIF_F_GSO_PARTIAL |
NETIF_F_GSO_UDP_L4;
netdev->hw_features |= netdev->features;
netdev->vlan_features |= netdev->features;
netdev->hw_enc_features |= netdev->features;
netdev->features |= NETIF_F_NTUPLE;
netdev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_RX_SG;
netdev->min_mtu = IPV6_MIN_MTU;
netdev->max_mtu = FBNIC_MAX_JUMBO_FRAME_SIZE - ETH_HLEN;
/* TBD: This is workaround for BMC as phylink doesn't have support
* for leavling the link enabled if a BMC is present.
*/
netdev->ethtool->wol_enabled = true;
netif_carrier_off(netdev);
netif_tx_stop_all_queues(netdev);
if (fbnic_phylink_create(netdev)) {
free_netdev(netdev);
fbd->netdev = NULL;
return NULL;
}
return netdev;
}
static int fbnic_dsn_to_mac_addr(u64 dsn, char *addr)
{
addr[0] = (dsn >> 56) & 0xFF;
addr[1] = (dsn >> 48) & 0xFF;
addr[2] = (dsn >> 40) & 0xFF;
addr[3] = (dsn >> 16) & 0xFF;
addr[4] = (dsn >> 8) & 0xFF;
addr[5] = dsn & 0xFF;
return is_valid_ether_addr(addr) ? 0 : -EINVAL;
}
/**
* fbnic_netdev_register - Initialize general software structures
* @netdev: Netdev containing structure to initialize and register
*
* Initialize the MAC address for the netdev and register it.
*
* Return: 0 on success, negative on failure
**/
int fbnic_netdev_register(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
u64 dsn = fbd->dsn;
u8 addr[ETH_ALEN];
int err;
err = fbnic_dsn_to_mac_addr(dsn, addr);
if (!err) {
ether_addr_copy(netdev->perm_addr, addr);
eth_hw_addr_set(netdev, addr);
} else {
/* A randomly assigned MAC address will cause provisioning
* issues so instead just fail to spawn the netdev and
* avoid any confusion.
*/
dev_err(fbd->dev, "MAC addr %pM invalid\n", addr);
return err;
}
return register_netdev(netdev);
}
void fbnic_netdev_unregister(struct net_device *netdev)
{
unregister_netdev(netdev);
}
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_NETDEV_H_
#define _FBNIC_NETDEV_H_
#include <linux/phylink.h>
#include <linux/types.h>
#include "fbnic_csr.h"
#include "fbnic_rpc.h"
#include "fbnic_txrx.h"
#define FBNIC_MAX_NAPI_VECTORS 128u
#define FBNIC_MIN_RXD_PER_FRAME 2
/* Natively supported tunnel GSO features (not thru GSO_PARTIAL) */
#define FBNIC_TUN_GSO_FEATURES NETIF_F_GSO_IPXIP6
struct fbnic_net {
struct bpf_prog *xdp_prog;
struct fbnic_ring *tx[FBNIC_MAX_TXQS + FBNIC_MAX_XDPQS];
struct fbnic_ring *rx[FBNIC_MAX_RXQS];
struct fbnic_napi_vector *napi[FBNIC_MAX_NAPI_VECTORS];
struct net_device *netdev;
struct fbnic_dev *fbd;
u32 txq_size;
u32 hpq_size;
u32 ppq_size;
u32 rcq_size;
u32 hds_thresh;
u16 rx_usecs;
u16 tx_usecs;
u32 rx_max_frames;
u16 num_napi;
struct phylink *phylink;
struct phylink_config phylink_config;
struct phylink_pcs *pcs;
u8 aui;
u8 fec;
/* Cached top bits of the HW time counter for 40b -> 64b conversion */
u32 time_high;
/* Protect readers of @time_offset, writers take @time_lock. */
struct u64_stats_sync time_seq;
/* Offset in ns between free running NIC PHC and time set via PTP
* clock callbacks
*/
s64 time_offset;
u16 num_tx_queues;
u16 num_rx_queues;
u8 indir_tbl[FBNIC_RPC_RSS_TBL_COUNT][FBNIC_RPC_RSS_TBL_SIZE];
u32 rss_key[FBNIC_RPC_RSS_KEY_DWORD_LEN];
u32 rss_flow_hash[FBNIC_NUM_HASH_OPT];
/* Storage for stats after ring destruction */
struct fbnic_queue_stats tx_stats;
struct fbnic_queue_stats rx_stats;
struct fbnic_queue_stats bdq_stats;
u64 link_down_events;
/* Time stamping filter config */
struct kernel_hwtstamp_config hwtstamp_config;
bool tx_pause;
};
int __fbnic_open(struct fbnic_net *fbn);
void fbnic_up(struct fbnic_net *fbn);
void fbnic_down(struct fbnic_net *fbn);
void fbnic_down_noidle(struct fbnic_net *fbn);
struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd);
void fbnic_netdev_free(struct fbnic_dev *fbd);
int fbnic_netdev_register(struct net_device *netdev);
void fbnic_netdev_unregister(struct net_device *netdev);
void fbnic_reset_queues(struct fbnic_net *fbn,
unsigned int tx, unsigned int rx);
void fbnic_set_ethtool_ops(struct net_device *dev);
int fbnic_ptp_setup(struct fbnic_dev *fbd);
void fbnic_ptp_destroy(struct fbnic_dev *fbd);
void fbnic_time_init(struct fbnic_net *fbn);
int fbnic_time_start(struct fbnic_net *fbn);
void fbnic_time_stop(struct fbnic_net *fbn);
void __fbnic_set_rx_mode(struct fbnic_dev *fbd,
struct netdev_hw_addr_list *uc,
struct netdev_hw_addr_list *mc);
void fbnic_clear_rx_mode(struct fbnic_dev *fbd);
void fbnic_phylink_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause);
int fbnic_phylink_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause);
int fbnic_phylink_ethtool_ksettings_get(struct net_device *netdev,
struct ethtool_link_ksettings *cmd);
int fbnic_phylink_get_fecparam(struct net_device *netdev,
struct ethtool_fecparam *fecparam);
int fbnic_phylink_create(struct net_device *netdev);
void fbnic_phylink_destroy(struct net_device *netdev);
int fbnic_phylink_init(struct net_device *netdev);
void fbnic_phylink_pmd_training_complete_notify(struct net_device *netdev);
bool fbnic_check_split_frames(struct bpf_prog *prog,
unsigned int mtu, u32 hds_threshold);
#endif /* _FBNIC_NETDEV_H_ */
@@ -0,0 +1,673 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/rtnetlink.h>
#include <linux/types.h>
#include <net/devlink.h>
#include "fbnic.h"
#include "fbnic_drvinfo.h"
#include "fbnic_hw_stats.h"
#include "fbnic_netdev.h"
char fbnic_driver_name[] = DRV_NAME;
MODULE_DESCRIPTION(DRV_SUMMARY);
MODULE_LICENSE("GPL");
static const struct fbnic_info fbnic_asic_info = {
.max_num_queues = FBNIC_MAX_QUEUES,
.bar_mask = BIT(0) | BIT(4)
};
static const struct fbnic_info *fbnic_info_tbl[] = {
[fbnic_board_asic] = &fbnic_asic_info,
};
static const struct pci_device_id fbnic_pci_tbl[] = {
{ PCI_DEVICE_DATA(META, FBNIC_ASIC, fbnic_board_asic) },
/* Required last entry */
{0, }
};
MODULE_DEVICE_TABLE(pci, fbnic_pci_tbl);
u32 fbnic_rd32(struct fbnic_dev *fbd, u32 reg)
{
u32 __iomem *csr = READ_ONCE(fbd->uc_addr0);
u32 value;
if (!csr)
return ~0U;
value = readl(csr + reg);
/* If any bits are 0 value should be valid */
if (~value)
return value;
/* All 1's may be valid if ZEROs register still works */
if (reg != FBNIC_MASTER_SPARE_0 && ~readl(csr + FBNIC_MASTER_SPARE_0))
return value;
/* Hardware is giving us all 1's reads, assume it is gone */
WRITE_ONCE(fbd->uc_addr0, NULL);
WRITE_ONCE(fbd->uc_addr4, NULL);
dev_err(fbd->dev,
"Failed read (idx 0x%x AKA addr 0x%x), disabled CSR access, awaiting reset\n",
reg, reg << 2);
/* Notify stack that device has lost (PCIe) link */
if (!fbnic_init_failure(fbd))
netif_device_detach(fbd->netdev);
return ~0U;
}
bool fbnic_fw_present(struct fbnic_dev *fbd)
{
return !!READ_ONCE(fbd->uc_addr4);
}
void fbnic_fw_wr32(struct fbnic_dev *fbd, u32 reg, u32 val)
{
u32 __iomem *csr = READ_ONCE(fbd->uc_addr4);
if (csr)
writel(val, csr + reg);
}
u32 fbnic_fw_rd32(struct fbnic_dev *fbd, u32 reg)
{
u32 __iomem *csr = READ_ONCE(fbd->uc_addr4);
u32 value;
if (!csr)
return ~0U;
value = readl(csr + reg);
/* If any bits are 0 value should be valid */
if (~value)
return value;
/* All 1's may be valid if ZEROs register still works */
if (reg != FBNIC_FW_ZERO_REG && ~readl(csr + FBNIC_FW_ZERO_REG))
return value;
/* Hardware is giving us all 1's reads, assume it is gone */
WRITE_ONCE(fbd->uc_addr0, NULL);
WRITE_ONCE(fbd->uc_addr4, NULL);
dev_err(fbd->dev,
"Failed read (idx 0x%x AKA addr 0x%x), disabled CSR access, awaiting reset\n",
reg, reg << 2);
/* Notify stack that device has lost (PCIe) link */
if (!fbnic_init_failure(fbd))
netif_device_detach(fbd->netdev);
return ~0U;
}
static void fbnic_service_task_start(struct fbnic_net *fbn)
{
struct fbnic_dev *fbd = fbn->fbd;
schedule_delayed_work(&fbd->service_task, HZ);
}
static void fbnic_service_task_stop(struct fbnic_net *fbn)
{
struct fbnic_dev *fbd = fbn->fbd;
cancel_delayed_work(&fbd->service_task);
}
void fbnic_up(struct fbnic_net *fbn)
{
fbnic_enable(fbn);
fbnic_fill(fbn);
fbnic_rss_reinit_hw(fbn->fbd, fbn);
netif_addr_lock_bh(fbn->netdev);
__fbnic_set_rx_mode(fbn->fbd, &fbn->netdev->uc, &fbn->netdev->mc);
netif_addr_unlock_bh(fbn->netdev);
/* Enable Tx/Rx processing */
fbnic_napi_enable(fbn);
netif_tx_wake_all_queues(fbn->netdev);
fbnic_service_task_start(fbn);
fbnic_dbg_up(fbn);
}
void fbnic_down_noidle(struct fbnic_net *fbn)
{
fbnic_dbg_down(fbn);
fbnic_service_task_stop(fbn);
/* Disable Tx/Rx Processing */
fbnic_napi_disable(fbn);
netif_tx_disable(fbn->netdev);
fbnic_clear_rx_mode(fbn->fbd);
fbnic_clear_rules(fbn->fbd);
fbnic_rss_disable_hw(fbn->fbd);
fbnic_disable(fbn);
}
void fbnic_down(struct fbnic_net *fbn)
{
fbnic_down_noidle(fbn);
fbnic_wait_all_queues_idle(fbn->fbd, false);
fbnic_flush(fbn);
}
static int fbnic_fw_config_after_crash(struct fbnic_dev *fbd)
{
if (fbnic_fw_xmit_ownership_msg(fbd, true)) {
dev_err(fbd->dev, "NIC failed to take ownership\n");
return -1;
}
fbnic_rpc_reset_valid_entries(fbd);
netif_addr_lock_bh(fbd->netdev);
__fbnic_set_rx_mode(fbd, &fbd->netdev->uc, &fbd->netdev->mc);
netif_addr_unlock_bh(fbd->netdev);
return 0;
}
static void fbnic_health_check(struct fbnic_dev *fbd)
{
struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX];
/* As long as the heart is beating the FW is healthy */
if (fbd->fw_heartbeat_enabled)
return;
/* If the Tx mailbox still has messages sitting in it then there likely
* isn't anything we can do. We will wait until the mailbox is empty to
* report the fault so we can collect the crashlog.
*/
if (tx_mbx->head != tx_mbx->tail)
return;
fbnic_devlink_fw_report(fbd, "Firmware crash detected!");
fbnic_devlink_otp_check(fbd, "error detected after firmware recovery");
if (fbnic_fw_config_after_crash(fbd))
dev_err(fbd->dev, "Firmware recovery failed after crash\n");
}
static void fbnic_service_task(struct work_struct *work)
{
struct fbnic_dev *fbd = container_of(to_delayed_work(work),
struct fbnic_dev, service_task);
struct net_device *netdev = fbd->netdev;
if (netif_running(netdev))
fbnic_phylink_pmd_training_complete_notify(netdev);
rtnl_lock();
fbnic_get_hw_stats32(fbd);
if (fbd->ps_timeout && fbnic_mac_check_tx_pause(fbd))
fbnic_mac_ps_protect_handler(fbd);
fbnic_fw_check_heartbeat(fbd);
fbnic_health_check(fbd);
fbnic_bmc_rpc_check(fbd);
if (netif_carrier_ok(fbd->netdev)) {
netdev_lock(fbd->netdev);
fbnic_napi_depletion_check(fbd->netdev);
netdev_unlock(fbd->netdev);
}
if (netif_running(netdev))
schedule_delayed_work(&fbd->service_task, HZ);
rtnl_unlock();
}
/**
* fbnic_probe - Device Initialization Routine
* @pdev: PCI device information struct
* @ent: entry in fbnic_pci_tbl
*
* Initializes a PCI device identified by a pci_dev structure.
* The OS initialization, configuring of the adapter private structure,
* and a hardware reset occur.
*
* Return: 0 on success, negative on failure
**/
static int fbnic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
const struct fbnic_info *info = fbnic_info_tbl[ent->driver_data];
struct net_device *netdev;
struct fbnic_dev *fbd;
int err;
if (pdev->error_state != pci_channel_io_normal) {
dev_err(&pdev->dev,
"PCI device still in an error state. Unable to load...\n");
return -EIO;
}
err = pcim_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "PCI enable device failed: %d\n", err);
return err;
}
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(46));
if (err)
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (err) {
dev_err(&pdev->dev, "DMA configuration failed: %d\n", err);
return err;
}
err = pcim_iomap_regions(pdev, info->bar_mask, fbnic_driver_name);
if (err) {
dev_err(&pdev->dev,
"pci_request_selected_regions failed: %d\n", err);
return err;
}
fbd = fbnic_devlink_alloc(pdev);
if (!fbd) {
dev_err(&pdev->dev, "Devlink allocation failed\n");
return -ENOMEM;
}
err = fbnic_devlink_health_create(fbd);
if (err)
goto free_fbd;
/* Populate driver with hardware-specific info and handlers */
fbd->max_num_queues = info->max_num_queues;
fbd->ps_timeout = FBNIC_MAC_PS_TO_DEFAULT_MS;
pci_set_master(pdev);
pci_save_state(pdev);
INIT_DELAYED_WORK(&fbd->service_task, fbnic_service_task);
err = fbnic_alloc_irqs(fbd);
if (err)
goto err_destroy_health;
err = fbnic_mac_init(fbd);
if (err) {
dev_err(&pdev->dev, "Failed to initialize MAC: %d\n", err);
goto free_irqs;
}
err = fbnic_fw_log_init(fbd);
if (err)
dev_warn(fbd->dev,
"Unable to initialize firmware log buffer: %d\n",
err);
err = fbnic_fw_request_mbx(fbd);
if (err) {
dev_err(&pdev->dev,
"Firmware mailbox initialization failure\n");
goto free_fw_log;
}
/* Send the request to enable the FW logging to host. Note if this
* fails we ignore the error and just display a message as it is
* possible the FW is just too old to support the logging and needs
* to be updated.
*/
fbnic_fw_log_enable(fbd, true);
fbnic_devlink_register(fbd);
fbnic_devlink_otp_check(fbd, "error detected during probe");
fbnic_dbg_fbd_init(fbd);
/* Capture snapshot of hardware stats so netdev can calculate delta */
fbnic_init_hw_stats(fbd);
fbnic_hwmon_register(fbd);
if (!fbd->dsn) {
dev_warn(&pdev->dev, "Reading serial number failed\n");
goto init_failure_mode;
}
if (fbnic_mdiobus_create(fbd))
goto init_failure_mode;
netdev = fbnic_netdev_alloc(fbd);
if (!netdev) {
dev_err(&pdev->dev, "Netdev allocation failed\n");
goto init_failure_mode;
}
err = fbnic_ptp_setup(fbd);
if (err)
goto ifm_free_netdev;
err = fbnic_netdev_register(netdev);
if (err) {
dev_err(&pdev->dev, "Netdev registration failed: %d\n", err);
goto ifm_destroy_ptp;
}
return 0;
ifm_destroy_ptp:
fbnic_ptp_destroy(fbd);
ifm_free_netdev:
fbnic_netdev_free(fbd);
init_failure_mode:
dev_warn(&pdev->dev, "Probe error encountered, entering init failure mode. Normal networking functionality will not be available.\n");
/* Always return 0 even on error so devlink is registered to allow
* firmware updates for fixes.
*/
return 0;
free_fw_log:
fbnic_fw_log_free(fbd);
free_irqs:
fbnic_free_irqs(fbd);
err_destroy_health:
fbnic_devlink_health_destroy(fbd);
free_fbd:
fbnic_devlink_free(fbd);
return err;
}
/**
* fbnic_remove - Device Removal Routine
* @pdev: PCI device information struct
*
* Called by the PCI subsystem to alert the driver that it should release
* a PCI device. This could be caused by a Hot-Plug event, or because the
* driver is going to be removed from memory.
**/
static void fbnic_remove(struct pci_dev *pdev)
{
struct fbnic_dev *fbd = pci_get_drvdata(pdev);
if (!fbnic_init_failure(fbd)) {
struct net_device *netdev = fbd->netdev;
fbnic_netdev_unregister(netdev);
cancel_delayed_work_sync(&fbd->service_task);
fbnic_ptp_destroy(fbd);
fbnic_netdev_free(fbd);
}
fbnic_hwmon_unregister(fbd);
fbnic_dbg_fbd_exit(fbd);
fbnic_devlink_unregister(fbd);
fbnic_fw_log_disable(fbd);
fbnic_fw_free_mbx(fbd);
fbnic_fw_log_free(fbd);
fbnic_free_irqs(fbd);
fbnic_devlink_health_destroy(fbd);
fbnic_devlink_free(fbd);
}
static int fbnic_pm_suspend(struct device *dev)
{
struct fbnic_dev *fbd = dev_get_drvdata(dev);
struct net_device *netdev = fbd->netdev;
if (fbnic_init_failure(fbd))
goto null_uc_addr;
rtnl_lock();
netdev_lock(netdev);
netif_device_detach(netdev);
if (netif_running(netdev))
netdev->netdev_ops->ndo_stop(netdev);
netdev_unlock(netdev);
rtnl_unlock();
null_uc_addr:
fbnic_fw_log_disable(fbd);
devl_lock(priv_to_devlink(fbd));
fbnic_fw_free_mbx(fbd);
devl_unlock(priv_to_devlink(fbd));
/* Free the IRQs so they aren't trying to occupy sleeping CPUs */
fbnic_free_irqs(fbd);
/* Hardware is about to go away, so switch off MMIO access internally */
WRITE_ONCE(fbd->uc_addr0, NULL);
WRITE_ONCE(fbd->uc_addr4, NULL);
return 0;
}
static int __fbnic_pm_resume(struct device *dev)
{
struct fbnic_dev *fbd = dev_get_drvdata(dev);
struct net_device *netdev = fbd->netdev;
void __iomem * const *iomap_table;
struct fbnic_net *fbn;
int err;
/* Restore MMIO access */
iomap_table = pcim_iomap_table(to_pci_dev(dev));
fbd->uc_addr0 = iomap_table[0];
fbd->uc_addr4 = iomap_table[4];
/* Rerequest the IRQs */
err = fbnic_alloc_irqs(fbd);
if (err)
goto err_invalidate_uc_addr;
fbd->mac->init_regs(fbd);
devl_lock(priv_to_devlink(fbd));
/* Re-enable mailbox */
err = fbnic_fw_request_mbx(fbd);
devl_unlock(priv_to_devlink(fbd));
if (err)
goto err_free_irqs;
/* Only send log history if log buffer is empty to prevent duplicate
* log entries.
*/
fbnic_fw_log_enable(fbd, list_empty(&fbd->fw_log.entries));
/* Since the FW should be up, check if it reported OTP errors */
fbnic_devlink_otp_check(fbd, "error detected after PM resume");
/* No netdev means there isn't a network interface to bring up */
if (fbnic_init_failure(fbd))
return 0;
fbn = netdev_priv(netdev);
/* Reset the queues if needed */
fbnic_reset_queues(fbn, fbn->num_tx_queues, fbn->num_rx_queues);
rtnl_lock();
netdev_lock(netdev);
if (netif_running(netdev))
err = __fbnic_open(fbn);
netdev_unlock(netdev);
rtnl_unlock();
if (err)
goto err_free_mbx;
return 0;
err_free_mbx:
fbnic_fw_log_disable(fbd);
devl_lock(priv_to_devlink(fbd));
fbnic_fw_free_mbx(fbd);
devl_unlock(priv_to_devlink(fbd));
err_free_irqs:
fbnic_free_irqs(fbd);
err_invalidate_uc_addr:
WRITE_ONCE(fbd->uc_addr0, NULL);
WRITE_ONCE(fbd->uc_addr4, NULL);
return err;
}
static void __fbnic_pm_attach(struct device *dev)
{
struct fbnic_dev *fbd = dev_get_drvdata(dev);
struct net_device *netdev = fbd->netdev;
struct fbnic_net *fbn;
rtnl_lock();
fbnic_reset_hw_stats(fbd);
rtnl_unlock();
if (fbnic_init_failure(fbd))
return;
fbn = netdev_priv(netdev);
if (netif_running(netdev))
fbnic_up(fbn);
netif_device_attach(netdev);
}
static int __maybe_unused fbnic_pm_resume(struct device *dev)
{
int err;
err = __fbnic_pm_resume(dev);
if (!err)
__fbnic_pm_attach(dev);
return err;
}
static const struct dev_pm_ops fbnic_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(fbnic_pm_suspend, fbnic_pm_resume)
};
static void fbnic_shutdown(struct pci_dev *pdev)
{
fbnic_pm_suspend(&pdev->dev);
}
static pci_ers_result_t fbnic_err_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
/* Disconnect device if failure is not recoverable via reset */
if (state == pci_channel_io_perm_failure)
return PCI_ERS_RESULT_DISCONNECT;
fbnic_pm_suspend(&pdev->dev);
/* Request a slot reset */
return PCI_ERS_RESULT_NEED_RESET;
}
static pci_ers_result_t fbnic_err_slot_reset(struct pci_dev *pdev)
{
int err;
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
if (pci_enable_device_mem(pdev)) {
dev_err(&pdev->dev,
"Cannot re-enable PCI device after reset.\n");
return PCI_ERS_RESULT_DISCONNECT;
}
/* Restore device to previous state */
err = __fbnic_pm_resume(&pdev->dev);
return err ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED;
}
static void fbnic_err_resume(struct pci_dev *pdev)
{
__fbnic_pm_attach(&pdev->dev);
}
static const struct pci_error_handlers fbnic_err_handler = {
.error_detected = fbnic_err_error_detected,
.slot_reset = fbnic_err_slot_reset,
.resume = fbnic_err_resume,
};
static struct pci_driver fbnic_driver = {
.name = fbnic_driver_name,
.id_table = fbnic_pci_tbl,
.probe = fbnic_probe,
.remove = fbnic_remove,
.driver.pm = &fbnic_pm_ops,
.shutdown = fbnic_shutdown,
.err_handler = &fbnic_err_handler,
};
/**
* fbnic_init_module - Driver Registration Routine
*
* The first routine called when the driver is loaded. All it does is
* register with the PCI subsystem.
*
* Return: 0 on success, negative on failure
**/
static int __init fbnic_init_module(void)
{
int err;
fbnic_dbg_init();
err = pci_register_driver(&fbnic_driver);
if (err) {
fbnic_dbg_exit();
goto out;
}
pr_info(DRV_SUMMARY " (%s)", fbnic_driver.name);
out:
return err;
}
module_init(fbnic_init_module);
/**
* fbnic_exit_module - Driver Exit Cleanup Routine
*
* Called just before the driver is removed from memory.
**/
static void __exit fbnic_exit_module(void)
{
pci_unregister_driver(&fbnic_driver);
fbnic_dbg_exit();
}
module_exit(fbnic_exit_module);
@@ -0,0 +1,311 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/pcs/pcs-xpcs.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include "fbnic.h"
#include "fbnic_mac.h"
#include "fbnic_netdev.h"
static phy_interface_t fbnic_phylink_select_interface(u8 aui)
{
switch (aui) {
case FBNIC_AUI_100GAUI2:
return PHY_INTERFACE_MODE_100GBASEP;
case FBNIC_AUI_50GAUI1:
return PHY_INTERFACE_MODE_50GBASER;
case FBNIC_AUI_LAUI2:
return PHY_INTERFACE_MODE_LAUI;
case FBNIC_AUI_25GAUI:
return PHY_INTERFACE_MODE_25GBASER;
}
return PHY_INTERFACE_MODE_NA;
}
void fbnic_phylink_get_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct fbnic_net *fbn = netdev_priv(netdev);
phylink_ethtool_get_pauseparam(fbn->phylink, pause);
}
int fbnic_phylink_set_pauseparam(struct net_device *netdev,
struct ethtool_pauseparam *pause)
{
struct fbnic_net *fbn = netdev_priv(netdev);
return phylink_ethtool_set_pauseparam(fbn->phylink, pause);
}
static void
fbnic_phylink_get_supported_fec_modes(unsigned long *supported)
{
/* The NIC can support up to 8 possible combinations.
* Either 50G-CR, or 100G-CR2
* This is with RS FEC mode only
* Either 25G-CR, or 50G-CR2
* This is with No FEC, RS, or Base-R
*/
if (phylink_test(supported, 100000baseCR2_Full) ||
phylink_test(supported, 50000baseCR_Full))
phylink_set(supported, FEC_RS);
if (phylink_test(supported, 50000baseCR2_Full) ||
phylink_test(supported, 25000baseCR_Full)) {
phylink_set(supported, FEC_BASER);
phylink_set(supported, FEC_NONE);
phylink_set(supported, FEC_RS);
}
}
int fbnic_phylink_ethtool_ksettings_get(struct net_device *netdev,
struct ethtool_link_ksettings *cmd)
{
struct fbnic_net *fbn = netdev_priv(netdev);
int err;
err = phylink_ethtool_ksettings_get(fbn->phylink, cmd);
if (!err) {
unsigned long *supp = cmd->link_modes.supported;
cmd->base.port = PORT_DA;
cmd->lanes = (fbn->aui & FBNIC_AUI_MODE_R2) ? 2 : 1;
fbnic_phylink_get_supported_fec_modes(supp);
}
return err;
}
int fbnic_phylink_get_fecparam(struct net_device *netdev,
struct ethtool_fecparam *fecparam)
{
struct fbnic_net *fbn = netdev_priv(netdev);
if (fbn->fec & FBNIC_FEC_RS) {
fecparam->active_fec = ETHTOOL_FEC_RS;
fecparam->fec = ETHTOOL_FEC_RS;
} else if (fbn->fec & FBNIC_FEC_BASER) {
fecparam->active_fec = ETHTOOL_FEC_BASER;
fecparam->fec = ETHTOOL_FEC_BASER;
} else {
fecparam->active_fec = ETHTOOL_FEC_OFF;
fecparam->fec = ETHTOOL_FEC_OFF;
}
if (fbn->aui & FBNIC_AUI_MODE_PAM4)
fecparam->fec |= ETHTOOL_FEC_AUTO;
return 0;
}
static struct phylink_pcs *
fbnic_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
return fbn->pcs;
}
static int
fbnic_phylink_mac_prepare(struct phylink_config *config, unsigned int mode,
phy_interface_t iface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbd->mac->prepare(fbd, fbn->aui, fbn->fec);
return 0;
}
static void
fbnic_phylink_mac_config(struct phylink_config *config, unsigned int mode,
const struct phylink_link_state *state)
{
}
static int
fbnic_phylink_mac_finish(struct phylink_config *config, unsigned int mode,
phy_interface_t iface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
/* Retest the link state and restart interrupts */
fbd->mac->get_link(fbd, fbn->aui, fbn->fec);
return 0;
}
static void
fbnic_phylink_mac_link_down(struct phylink_config *config, unsigned int mode,
phy_interface_t interface)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbd->mac->link_down(fbd);
fbn->link_down_events++;
}
static void
fbnic_phylink_mac_link_up(struct phylink_config *config,
struct phy_device *phy, unsigned int mode,
phy_interface_t interface, int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct net_device *netdev = to_net_dev(config->dev);
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
fbn->tx_pause = tx_pause;
fbnic_config_drop_mode(fbn, tx_pause);
fbd->mac->link_up(fbd, tx_pause, rx_pause);
}
static const struct phylink_mac_ops fbnic_phylink_mac_ops = {
.mac_select_pcs = fbnic_phylink_mac_select_pcs,
.mac_prepare = fbnic_phylink_mac_prepare,
.mac_config = fbnic_phylink_mac_config,
.mac_finish = fbnic_phylink_mac_finish,
.mac_link_down = fbnic_phylink_mac_link_down,
.mac_link_up = fbnic_phylink_mac_link_up,
};
/**
* fbnic_phylink_create - Phylink device creation
* @netdev: Network Device struct to attach phylink device
*
* Initialize and attach a phylink instance to the device. The phylink
* device will make use of the netdev struct to track carrier and will
* eventually be used to expose the current state of the MAC and PCS
* setup.
*
* Return: 0 on success, negative on failure
**/
int fbnic_phylink_create(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
struct phylink_pcs *pcs;
struct phylink *phylink;
int err;
pcs = xpcs_create_pcs_mdiodev(fbd->mdio_bus, 0);
if (IS_ERR(pcs)) {
err = PTR_ERR(pcs);
dev_err(fbd->dev, "Failed to create PCS device: %d\n", err);
return err;
}
fbn->pcs = pcs;
fbn->phylink_config.dev = &netdev->dev;
fbn->phylink_config.type = PHYLINK_NETDEV;
fbn->phylink_config.mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_25000FD | MAC_50000FD |
MAC_100000FD;
fbn->phylink_config.default_an_inband = true;
__set_bit(PHY_INTERFACE_MODE_100GBASEP,
fbn->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_50GBASER,
fbn->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_LAUI,
fbn->phylink_config.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_25GBASER,
fbn->phylink_config.supported_interfaces);
fbnic_mac_get_fw_settings(fbd, &fbn->aui, &fbn->fec);
phylink = phylink_create(&fbn->phylink_config, NULL,
fbnic_phylink_select_interface(fbn->aui),
&fbnic_phylink_mac_ops);
if (IS_ERR(phylink)) {
err = PTR_ERR(phylink);
dev_err(netdev->dev.parent,
"Failed to create Phylink interface, err: %d\n", err);
xpcs_destroy_pcs(pcs);
return err;
}
fbn->phylink = phylink;
return 0;
}
/**
* fbnic_phylink_destroy - Teardown phylink related interfaces
* @netdev: Network Device struct containing phylink device
*
* Detach and free resources related to phylink interface.
**/
void fbnic_phylink_destroy(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
if (fbn->phylink)
phylink_destroy(fbn->phylink);
if (fbn->pcs)
xpcs_destroy_pcs(fbn->pcs);
}
/**
* fbnic_phylink_pmd_training_complete_notify - PMD training complete notifier
* @netdev: Netdev struct phylink device attached to
*
* When the link first comes up the PMD will have a period of 2 to 3 seconds
* where the link will flutter due to link training. To avoid spamming the
* kernel log with messages about this we add a delay of 4 seconds from the
* time of the last PCS report of link so that we can guarantee we are unlikely
* to see any further link loss events due to link training.
**/
void fbnic_phylink_pmd_training_complete_notify(struct net_device *netdev)
{
struct fbnic_net *fbn = netdev_priv(netdev);
struct fbnic_dev *fbd = fbn->fbd;
if (fbd->pmd_state != FBNIC_PMD_TRAINING)
return;
/* Prevent reading end_of_pmd_training until we verified state */
smp_rmb();
if (!time_before(READ_ONCE(fbd->end_of_pmd_training), jiffies))
return;
/* At this point we have verified that the link has been up for
* the full training duration. As a first step we will try
* transitioning to link ready.
*/
if (cmpxchg(&fbd->pmd_state, FBNIC_PMD_TRAINING,
FBNIC_PMD_LINK_READY) != FBNIC_PMD_TRAINING)
return;
/* Perform a follow-up check to verify that the link didn't flap
* just before our transition by rechecking the training timer.
*/
if (!time_before(READ_ONCE(fbd->end_of_pmd_training), jiffies))
return;
/* The training timeout has been completed. We are good to swap out
* link_ready for send_data assuming no other events have occurred
* that would have pulled us back into initialization or training.
*/
if (cmpxchg(&fbd->pmd_state, FBNIC_PMD_LINK_READY,
FBNIC_PMD_SEND_DATA) != FBNIC_PMD_LINK_READY)
return;
phylink_pcs_change(fbn->pcs, false);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,232 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_RPC_H_
#define _FBNIC_RPC_H_
#include <uapi/linux/in6.h>
#include <linux/bitfield.h>
struct in_addr;
/* The TCAM state definitions follow an expected ordering.
* They start out disabled, then move through the following states:
* Disabled 0 -> Add 2
* Add 2 -> Valid 1
*
* Valid 1 -> Add/Update 2
* Add 2 -> Valid 1
*
* Valid 1 -> Delete 3
* Delete 3 -> Disabled 0
*/
enum {
FBNIC_TCAM_S_DISABLED = 0,
FBNIC_TCAM_S_VALID = 1,
FBNIC_TCAM_S_ADD = 2,
FBNIC_TCAM_S_UPDATE = FBNIC_TCAM_S_ADD,
FBNIC_TCAM_S_DELETE = 3,
};
/* 32 MAC Destination Address TCAM Entries
* 4 registers DA[1:0], DA[3:2], DA[5:4], Validate
*/
#define FBNIC_RPC_TCAM_MACDA_WORD_LEN 3
#define FBNIC_RPC_TCAM_MACDA_NUM_ENTRIES 32
/* 8 IPSRC and IPDST TCAM Entries each
* 8 registers, Validate each
*/
#define FBNIC_RPC_TCAM_IP_ADDR_WORD_LEN 8
#define FBNIC_RPC_TCAM_IP_ADDR_NUM_ENTRIES 8
#define FBNIC_RPC_TCAM_ACT_WORD_LEN 11
#define FBNIC_RPC_TCAM_ACT_NUM_ENTRIES 64
#define FBNIC_TCE_TCAM_WORD_LEN 3
#define FBNIC_TCE_TCAM_NUM_ENTRIES 8
struct fbnic_mac_addr {
union {
unsigned char addr8[ETH_ALEN];
__be16 addr16[FBNIC_RPC_TCAM_MACDA_WORD_LEN];
} mask, value;
unsigned char state;
DECLARE_BITMAP(act_tcam, FBNIC_RPC_TCAM_ACT_NUM_ENTRIES);
};
struct fbnic_ip_addr {
struct in6_addr mask, value;
unsigned char version;
unsigned char state;
DECLARE_BITMAP(act_tcam, FBNIC_RPC_TCAM_ACT_NUM_ENTRIES);
};
struct fbnic_act_tcam {
struct {
u16 tcam[FBNIC_RPC_TCAM_ACT_WORD_LEN];
} mask, value;
unsigned char state;
u16 rss_en_mask;
u32 dest;
};
enum {
FBNIC_RSS_EN_HOST_UDP6,
FBNIC_RSS_EN_HOST_UDP4,
FBNIC_RSS_EN_HOST_TCP6,
FBNIC_RSS_EN_HOST_TCP4,
FBNIC_RSS_EN_HOST_IP6,
FBNIC_RSS_EN_HOST_IP4,
FBNIC_RSS_EN_HOST_ETHER,
FBNIC_RSS_EN_XCAST_UDP6,
#define FBNIC_RSS_EN_NUM_UNICAST FBNIC_RSS_EN_XCAST_UDP6
FBNIC_RSS_EN_XCAST_UDP4,
FBNIC_RSS_EN_XCAST_TCP6,
FBNIC_RSS_EN_XCAST_TCP4,
FBNIC_RSS_EN_XCAST_IP6,
FBNIC_RSS_EN_XCAST_IP4,
FBNIC_RSS_EN_XCAST_ETHER,
FBNIC_RSS_EN_NUM_ENTRIES
};
/* Reserve the first 2 entries for the use by the BMC so that we can
* avoid allowing rules to get in the way of BMC unicast traffic.
*/
#define FBNIC_RPC_ACT_TBL_BMC_OFFSET 0
#define FBNIC_RPC_ACT_TBL_BMC_ALL_MULTI_OFFSET 1
/* This should leave us with 48 total entries in the TCAM that can be used
* for NFC after also deducting the 14 needed for RSS table programming.
*/
#define FBNIC_RPC_ACT_TBL_NFC_OFFSET 2
/* We reserve the last 14 entries for RSS rules on the host. The BMC
* unicast rule will need to be populated above these and is expected to
* use MACDA TCAM entry 23 to store the BMC MAC address.
*/
#define FBNIC_RPC_ACT_TBL_RSS_OFFSET \
(FBNIC_RPC_ACT_TBL_NUM_ENTRIES - FBNIC_RSS_EN_NUM_ENTRIES)
#define FBNIC_RPC_ACT_TBL_NFC_ENTRIES \
(FBNIC_RPC_ACT_TBL_RSS_OFFSET - FBNIC_RPC_ACT_TBL_NFC_OFFSET)
/* Flags used to identify the owner for this MAC filter. Note that any
* flags set for Broadcast thru Promisc indicate that the rule belongs
* to the RSS filters for the host.
*/
enum {
FBNIC_MAC_ADDR_T_BMC = 0,
FBNIC_MAC_ADDR_T_BROADCAST = FBNIC_RPC_ACT_TBL_RSS_OFFSET,
#define FBNIC_MAC_ADDR_T_HOST_START FBNIC_MAC_ADDR_T_BROADCAST
FBNIC_MAC_ADDR_T_MULTICAST,
FBNIC_MAC_ADDR_T_UNICAST,
FBNIC_MAC_ADDR_T_ALLMULTI, /* BROADCAST ... MULTICAST*/
FBNIC_MAC_ADDR_T_PROMISC, /* BROADCAST ... UNICAST */
FBNIC_MAC_ADDR_T_HOST_LAST
};
#define FBNIC_MAC_ADDR_T_HOST_LEN \
(FBNIC_MAC_ADDR_T_HOST_LAST - FBNIC_MAC_ADDR_T_HOST_START)
#define FBNIC_RPC_TCAM_ACT0_IPSRC_IDX CSR_GENMASK(2, 0)
#define FBNIC_RPC_TCAM_ACT0_IPSRC_VALID CSR_BIT(3)
#define FBNIC_RPC_TCAM_ACT0_IPDST_IDX CSR_GENMASK(6, 4)
#define FBNIC_RPC_TCAM_ACT0_IPDST_VALID CSR_BIT(7)
#define FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_IDX CSR_GENMASK(10, 8)
#define FBNIC_RPC_TCAM_ACT0_OUTER_IPSRC_VALID CSR_BIT(11)
#define FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_IDX CSR_GENMASK(14, 12)
#define FBNIC_RPC_TCAM_ACT0_OUTER_IPDST_VALID CSR_BIT(15)
#define FBNIC_RPC_TCAM_ACT1_L2_MACDA_IDX CSR_GENMASK(9, 5)
#define FBNIC_RPC_TCAM_ACT1_L2_MACDA_VALID CSR_BIT(10)
#define FBNIC_RPC_TCAM_ACT1_IP_IS_V6 CSR_BIT(11)
#define FBNIC_RPC_TCAM_ACT1_IP_VALID CSR_BIT(12)
#define FBNIC_RPC_TCAM_ACT1_OUTER_IP_VALID CSR_BIT(13)
#define FBNIC_RPC_TCAM_ACT1_L4_IS_UDP CSR_BIT(14)
#define FBNIC_RPC_TCAM_ACT1_L4_VALID CSR_BIT(15)
/* TCAM 0 - 3 reserved for BMC MAC addresses */
#define FBNIC_RPC_TCAM_MACDA_BMC_ADDR_IDX 0
/* TCAM 4 reserved for broadcast MAC address */
#define FBNIC_RPC_TCAM_MACDA_BROADCAST_IDX 4
/* TCAMs 5 - 30 will be used for multicast and unicast addresses. The
* boundary between the two can be variable it is currently set to 24
* on which the unicast addresses start. The general idea is that we will
* always go top-down with unicast, and bottom-up with multicast so that
* there should be free-space in the middle between the two.
*
* The entry at MADCA_DEFAULT_BOUNDARY is a special case as it can be used
* for the ALL MULTI address if the list is full, or the BMC has requested
* it.
*/
#define FBNIC_RPC_TCAM_MACDA_MULTICAST_IDX 5
#define FBNIC_RPC_TCAM_MACDA_DEFAULT_BOUNDARY 24
#define FBNIC_RPC_TCAM_MACDA_HOST_ADDR_IDX 30
/* Reserved for use to record Multicast promisc, or Promiscuous */
#define FBNIC_RPC_TCAM_MACDA_PROMISC_IDX 31
enum {
FBNIC_UDP6_HASH_OPT,
FBNIC_UDP4_HASH_OPT,
FBNIC_TCP6_HASH_OPT,
FBNIC_TCP4_HASH_OPT,
#define FBNIC_L4_HASH_OPT FBNIC_TCP4_HASH_OPT
FBNIC_IPV6_HASH_OPT,
FBNIC_IPV4_HASH_OPT,
#define FBNIC_IP_HASH_OPT FBNIC_IPV4_HASH_OPT
FBNIC_ETHER_HASH_OPT,
FBNIC_NUM_HASH_OPT,
};
struct fbnic_dev;
struct fbnic_net;
void fbnic_bmc_rpc_init(struct fbnic_dev *fbd);
void fbnic_bmc_rpc_all_multi_config(struct fbnic_dev *fbd, bool enable_host);
void fbnic_bmc_rpc_check(struct fbnic_dev *fbd);
void fbnic_reset_indir_tbl(struct fbnic_net *fbn);
void fbnic_rss_key_fill(u32 *buffer);
void fbnic_rss_init_en_mask(struct fbnic_net *fbn);
void fbnic_rss_disable_hw(struct fbnic_dev *fbd);
void fbnic_rss_reinit_hw(struct fbnic_dev *fbd, struct fbnic_net *fbn);
void fbnic_rss_reinit(struct fbnic_dev *fbd, struct fbnic_net *fbn);
u16 fbnic_flow_hash_2_rss_en_mask(struct fbnic_net *fbn, int flow_type);
int __fbnic_xc_unsync(struct fbnic_mac_addr *mac_addr, unsigned int tcam_idx);
struct fbnic_mac_addr *__fbnic_uc_sync(struct fbnic_dev *fbd,
const unsigned char *addr);
struct fbnic_mac_addr *__fbnic_mc_sync(struct fbnic_dev *fbd,
const unsigned char *addr);
void fbnic_sift_macda(struct fbnic_dev *fbd);
void fbnic_write_macda(struct fbnic_dev *fbd);
void fbnic_promisc_sync(struct fbnic_dev *fbd,
bool uc_promisc, bool mc_promisc);
struct fbnic_ip_addr *__fbnic_ip4_sync(struct fbnic_dev *fbd,
struct fbnic_ip_addr *ip_addr,
const struct in_addr *addr,
const struct in_addr *mask);
struct fbnic_ip_addr *__fbnic_ip6_sync(struct fbnic_dev *fbd,
struct fbnic_ip_addr *ip_addr,
const struct in6_addr *addr,
const struct in6_addr *mask);
int __fbnic_ip_unsync(struct fbnic_ip_addr *ip_addr, unsigned int tcam_idx);
void fbnic_write_ip_addr(struct fbnic_dev *fbd);
static inline int __fbnic_uc_unsync(struct fbnic_mac_addr *mac_addr)
{
return __fbnic_xc_unsync(mac_addr, FBNIC_MAC_ADDR_T_UNICAST);
}
static inline int __fbnic_mc_unsync(struct fbnic_mac_addr *mac_addr)
{
return __fbnic_xc_unsync(mac_addr, FBNIC_MAC_ADDR_T_MULTICAST);
}
void fbnic_clear_rules(struct fbnic_dev *fbd);
void fbnic_write_rules(struct fbnic_dev *fbd);
void fbnic_write_tce_tcam(struct fbnic_dev *fbd);
#endif /* _FBNIC_RPC_H_ */
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/bitfield.h>
#include <linux/jiffies.h>
#include <linux/limits.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/timer.h>
#include "fbnic.h"
#include "fbnic_csr.h"
#include "fbnic_netdev.h"
/* FBNIC timing & PTP implementation
* Datapath uses truncated 40b timestamps for scheduling and event reporting.
* We need to promote those to full 64b, hence we periodically cache the top
* 32bit of the HW time counter. Since this makes our time reporting non-atomic
* we leave the HW clock free running and adjust time offsets in SW as needed.
* Time offset is 64bit - we need a seq counter for 32bit machines.
* Time offset and the cache of top bits are independent so we don't need
* a coherent snapshot of both - READ_ONCE()/WRITE_ONCE() + writer side lock
* are enough.
*/
/* Period of refresh of top bits of timestamp, give ourselves a 8x margin.
* This should translate to once a minute.
* The use of nsecs_to_jiffies() should be safe for a <=40b nsec value.
*/
#define FBNIC_TS_HIGH_REFRESH_JIF nsecs_to_jiffies((1ULL << 40) / 16)
static struct fbnic_dev *fbnic_from_ptp_info(struct ptp_clock_info *ptp)
{
return container_of(ptp, struct fbnic_dev, ptp_info);
}
/* This function is "slow" because we could try guessing which high part
* is correct based on low instead of re-reading, and skip reading @hi
* twice altogether if @lo is far enough from 0.
*/
static u64 __fbnic_time_get_slow(struct fbnic_dev *fbd)
{
u32 hi, lo;
lockdep_assert_held(&fbd->time_lock);
do {
hi = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI);
lo = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_LO);
} while (hi != fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI));
return (u64)hi << 32 | lo;
}
static void __fbnic_time_set_addend(struct fbnic_dev *fbd, u64 addend)
{
lockdep_assert_held(&fbd->time_lock);
fbnic_wr32(fbd, FBNIC_PTP_ADD_VAL_NS,
FIELD_PREP(FBNIC_PTP_ADD_VAL_NS_MASK, addend >> 32));
fbnic_wr32(fbd, FBNIC_PTP_ADD_VAL_SUBNS, (u32)addend);
}
static void fbnic_ptp_fresh_check(struct fbnic_dev *fbd)
{
if (time_is_after_jiffies(fbd->last_read +
FBNIC_TS_HIGH_REFRESH_JIF * 3 / 2))
return;
dev_warn(fbd->dev, "NIC timestamp refresh stall, delayed by %lu sec\n",
(jiffies - fbd->last_read - FBNIC_TS_HIGH_REFRESH_JIF) / HZ);
}
static void fbnic_ptp_refresh_time(struct fbnic_dev *fbd, struct fbnic_net *fbn)
{
unsigned long flags;
u32 hi;
spin_lock_irqsave(&fbd->time_lock, flags);
hi = fbnic_rd32(fbn->fbd, FBNIC_PTP_CTR_VAL_HI);
if (!fbnic_present(fbd))
goto out; /* Don't bother handling, reset is pending */
/* Let's keep high cached value a bit lower to avoid race with
* incoming timestamps. The logic in fbnic_ts40_to_ns() will
* take care of overflow in this case. It will make cached time
* ~1 minute lower and incoming timestamp will always be later
* then cached time.
*/
WRITE_ONCE(fbn->time_high, hi - 16);
fbd->last_read = jiffies;
out:
spin_unlock_irqrestore(&fbd->time_lock, flags);
}
static long fbnic_ptp_do_aux_work(struct ptp_clock_info *ptp)
{
struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp);
struct fbnic_net *fbn;
fbn = netdev_priv(fbd->netdev);
fbnic_ptp_fresh_check(fbd);
fbnic_ptp_refresh_time(fbd, fbn);
return FBNIC_TS_HIGH_REFRESH_JIF;
}
static int fbnic_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp);
u64 addend, dclk_period;
unsigned long flags;
/* d_clock is 600 MHz; which in Q16.32 fixed point ns is: */
dclk_period = (((u64)1000000000) << 32) / FBNIC_CLOCK_FREQ;
addend = adjust_by_scaled_ppm(dclk_period, scaled_ppm);
spin_lock_irqsave(&fbd->time_lock, flags);
__fbnic_time_set_addend(fbd, addend);
fbnic_wr32(fbd, FBNIC_PTP_ADJUST, FBNIC_PTP_ADJUST_ADDEND_SET);
/* Flush, make sure FBNIC_PTP_ADD_VAL_* is stable for at least 4 clks */
fbnic_rd32(fbd, FBNIC_PTP_SPARE);
spin_unlock_irqrestore(&fbd->time_lock, flags);
return fbnic_present(fbd) ? 0 : -EIO;
}
static int fbnic_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp);
struct fbnic_net *fbn;
unsigned long flags;
fbn = netdev_priv(fbd->netdev);
spin_lock_irqsave(&fbd->time_lock, flags);
u64_stats_update_begin(&fbn->time_seq);
WRITE_ONCE(fbn->time_offset, READ_ONCE(fbn->time_offset) + delta);
u64_stats_update_end(&fbn->time_seq);
spin_unlock_irqrestore(&fbd->time_lock, flags);
return 0;
}
static int
fbnic_ptp_gettimex64(struct ptp_clock_info *ptp, struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp);
struct fbnic_net *fbn;
unsigned long flags;
u64 time_ns;
u32 hi, lo;
fbn = netdev_priv(fbd->netdev);
spin_lock_irqsave(&fbd->time_lock, flags);
do {
hi = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI);
ptp_read_system_prets(sts);
lo = fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_LO);
ptp_read_system_postts(sts);
/* Similarly to comment above __fbnic_time_get_slow()
* - this can be optimized if needed.
*/
} while (hi != fbnic_rd32(fbd, FBNIC_PTP_CTR_VAL_HI));
time_ns = ((u64)hi << 32 | lo) + fbn->time_offset;
spin_unlock_irqrestore(&fbd->time_lock, flags);
if (!fbnic_present(fbd))
return -EIO;
*ts = ns_to_timespec64(time_ns);
return 0;
}
static int
fbnic_ptp_settime64(struct ptp_clock_info *ptp, const struct timespec64 *ts)
{
struct fbnic_dev *fbd = fbnic_from_ptp_info(ptp);
struct fbnic_net *fbn;
unsigned long flags;
u64 dev_ns, host_ns;
int ret;
fbn = netdev_priv(fbd->netdev);
host_ns = timespec64_to_ns(ts);
spin_lock_irqsave(&fbd->time_lock, flags);
dev_ns = __fbnic_time_get_slow(fbd);
if (fbnic_present(fbd)) {
u64_stats_update_begin(&fbn->time_seq);
WRITE_ONCE(fbn->time_offset, host_ns - dev_ns);
u64_stats_update_end(&fbn->time_seq);
ret = 0;
} else {
ret = -EIO;
}
spin_unlock_irqrestore(&fbd->time_lock, flags);
return ret;
}
static const struct ptp_clock_info fbnic_ptp_info = {
.owner = THIS_MODULE,
/* 1,000,000,000 - 1 PPB to ensure increment is positive
* after max negative adjustment.
*/
.max_adj = 999999999,
.do_aux_work = fbnic_ptp_do_aux_work,
.adjfine = fbnic_ptp_adjfine,
.adjtime = fbnic_ptp_adjtime,
.gettimex64 = fbnic_ptp_gettimex64,
.settime64 = fbnic_ptp_settime64,
};
static void fbnic_ptp_reset(struct fbnic_dev *fbd)
{
struct fbnic_net *fbn = netdev_priv(fbd->netdev);
u64 dclk_period;
fbnic_wr32(fbd, FBNIC_PTP_CTRL,
FBNIC_PTP_CTRL_EN |
FIELD_PREP(FBNIC_PTP_CTRL_TICK_IVAL, 1));
/* d_clock is 600 MHz; which in Q16.32 fixed point ns is: */
dclk_period = (((u64)1000000000) << 32) / FBNIC_CLOCK_FREQ;
__fbnic_time_set_addend(fbd, dclk_period);
fbnic_wr32(fbd, FBNIC_PTP_INIT_HI, 0);
fbnic_wr32(fbd, FBNIC_PTP_INIT_LO, 0);
fbnic_wr32(fbd, FBNIC_PTP_ADJUST, FBNIC_PTP_ADJUST_INIT);
fbnic_wr32(fbd, FBNIC_PTP_CTRL,
FBNIC_PTP_CTRL_EN |
FBNIC_PTP_CTRL_TQS_OUT_EN |
FIELD_PREP(FBNIC_PTP_CTRL_MAC_OUT_IVAL, 3) |
FIELD_PREP(FBNIC_PTP_CTRL_TICK_IVAL, 1));
fbnic_rd32(fbd, FBNIC_PTP_SPARE);
fbn->time_offset = 0;
fbn->time_high = 0;
}
void fbnic_time_init(struct fbnic_net *fbn)
{
/* This is not really a statistic, but the locking primitive fits
* our usecase perfectly, we need an atomic 8 bytes READ_ONCE() /
* WRITE_ONCE() behavior.
*/
u64_stats_init(&fbn->time_seq);
}
int fbnic_time_start(struct fbnic_net *fbn)
{
fbnic_ptp_refresh_time(fbn->fbd, fbn);
/* Assume that fbnic_ptp_do_aux_work() will never be called if not
* scheduled here
*/
return ptp_schedule_worker(fbn->fbd->ptp, FBNIC_TS_HIGH_REFRESH_JIF);
}
void fbnic_time_stop(struct fbnic_net *fbn)
{
ptp_cancel_worker_sync(fbn->fbd->ptp);
fbnic_ptp_fresh_check(fbn->fbd);
}
int fbnic_ptp_setup(struct fbnic_dev *fbd)
{
struct device *dev = fbd->dev;
unsigned long flags;
spin_lock_init(&fbd->time_lock);
spin_lock_irqsave(&fbd->time_lock, flags); /* Appease lockdep */
fbnic_ptp_reset(fbd);
spin_unlock_irqrestore(&fbd->time_lock, flags);
memcpy(&fbd->ptp_info, &fbnic_ptp_info, sizeof(fbnic_ptp_info));
fbd->ptp = ptp_clock_register(&fbd->ptp_info, dev);
if (IS_ERR(fbd->ptp))
dev_err(dev, "Failed to register PTP: %pe\n", fbd->ptp);
return PTR_ERR_OR_ZERO(fbd->ptp);
}
void fbnic_ptp_destroy(struct fbnic_dev *fbd)
{
if (!fbd->ptp)
return;
ptp_clock_unregister(fbd->ptp);
}
@@ -0,0 +1,836 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#include <linux/gfp.h>
#include <linux/mm.h>
#include <linux/once.h>
#include <linux/random.h>
#include <linux/string.h>
#include <uapi/linux/if_ether.h>
#include "fbnic_tlv.h"
/**
* fbnic_tlv_msg_alloc - Allocate page and initialize FW message header
* @msg_id: Identifier for new message we are starting
*
* Return: pointer to start of message, or NULL on failure.
*
* Allocates a page and initializes message header at start of page.
* Initial message size is 1 DWORD which is just the header.
**/
struct fbnic_tlv_msg *fbnic_tlv_msg_alloc(u16 msg_id)
{
struct fbnic_tlv_hdr hdr = { 0 };
struct fbnic_tlv_msg *msg;
msg = (struct fbnic_tlv_msg *)__get_free_page(GFP_KERNEL);
if (!msg)
return NULL;
/* Start with zero filled header and then back fill with data */
hdr.type = msg_id;
hdr.is_msg = 1;
hdr.len = cpu_to_le16(1);
/* Copy header into start of message */
msg->hdr = hdr;
return msg;
}
/**
* fbnic_tlv_attr_put_flag - Add flag value to message
* @msg: Message header we are adding flag attribute to
* @attr_id: ID of flag attribute we are adding to message
*
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
*
* Adds a 1 DWORD flag attribute to the message. The presence of this
* attribute can be used as a boolean value indicating true, otherwise the
* value is considered false.
**/
int fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg *msg, const u16 attr_id)
{
int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
struct fbnic_tlv_hdr hdr = { 0 };
struct fbnic_tlv_msg *attr;
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
if (attr_max_len < sizeof(*attr))
return -ENOSPC;
/* Get header pointer and bump attr to start of data */
attr = &msg[le16_to_cpu(msg->hdr.len)];
/* Record attribute type and size */
hdr.type = attr_id;
hdr.len = cpu_to_le16(sizeof(hdr));
attr->hdr = hdr;
le16_add_cpu(&msg->hdr.len,
FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len)));
return 0;
}
/**
* fbnic_tlv_attr_put_value - Add data to message
* @msg: Message header we are adding flag attribute to
* @attr_id: ID of flag attribute we are adding to message
* @value: Pointer to data to be stored
* @len: Size of data to be stored.
*
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
*
* Adds header and copies data pointed to by value into the message. The
* result is rounded up to the nearest DWORD for sizing so that the
* headers remain aligned.
*
* The assumption is that the value field is in a format where byte
* ordering can be guaranteed such as a byte array or a little endian
* format.
**/
int fbnic_tlv_attr_put_value(struct fbnic_tlv_msg *msg, const u16 attr_id,
const void *value, const int len)
{
int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
struct fbnic_tlv_hdr hdr = { 0 };
struct fbnic_tlv_msg *attr;
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
if (attr_max_len < sizeof(*attr) + len)
return -ENOSPC;
/* Get header pointer and bump attr to start of data */
attr = &msg[le16_to_cpu(msg->hdr.len)];
/* Record attribute type and size */
hdr.type = attr_id;
hdr.len = cpu_to_le16(sizeof(hdr) + len);
/* Zero pad end of region to be written if we aren't aligned */
if (len % sizeof(hdr))
attr->value[len / sizeof(hdr)] = 0;
/* Copy data over */
memcpy(attr->value, value, len);
attr->hdr = hdr;
le16_add_cpu(&msg->hdr.len,
FBNIC_TLV_MSG_SIZE(le16_to_cpu(hdr.len)));
return 0;
}
/**
* __fbnic_tlv_attr_put_int - Add integer to message
* @msg: Message header we are adding flag attribute to
* @attr_id: ID of flag attribute we are adding to message
* @value: Data to be stored
* @len: Size of data to be stored, either 4 or 8 bytes.
*
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
*
* Adds header and copies data pointed to by value into the message. Will
* format the data as little endian.
**/
int __fbnic_tlv_attr_put_int(struct fbnic_tlv_msg *msg, const u16 attr_id,
s64 value, const int len)
{
__le64 le64_value = cpu_to_le64(value);
return fbnic_tlv_attr_put_value(msg, attr_id, &le64_value, len);
}
/**
* fbnic_tlv_attr_put_mac_addr - Add mac_addr to message
* @msg: Message header we are adding flag attribute to
* @attr_id: ID of flag attribute we are adding to message
* @mac_addr: Byte pointer to MAC address to be stored
*
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
*
* Adds header and copies data pointed to by mac_addr into the message. Will
* copy the address raw so it will be in big endian with start of MAC
* address at start of attribute.
**/
int fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg *msg, const u16 attr_id,
const u8 *mac_addr)
{
return fbnic_tlv_attr_put_value(msg, attr_id, mac_addr, ETH_ALEN);
}
/**
* fbnic_tlv_attr_put_string - Add string to message
* @msg: Message header we are adding flag attribute to
* @attr_id: ID of flag attribute we are adding to message
* @string: Byte pointer to null terminated string to be stored
*
* Return: -ENOSPC if there is no room for the attribute. Otherwise 0.
*
* Adds header and copies data pointed to by string into the message. Will
* copy the address raw so it will be in byte order.
**/
int fbnic_tlv_attr_put_string(struct fbnic_tlv_msg *msg, u16 attr_id,
const char *string)
{
int attr_max_len = PAGE_SIZE - sizeof(*msg);
int str_len = 1;
/* The max length will be message minus existing message and new
* attribute header. Since the message is measured in DWORDs we have
* to multiply the size by 4.
*
* The string length doesn't include the \0 so we have to add one to
* the final value, so start with that as our initial value.
*
* We will verify if the string will fit in fbnic_tlv_attr_put_value()
*/
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
str_len += strnlen(string, attr_max_len);
return fbnic_tlv_attr_put_value(msg, attr_id, string, str_len);
}
/**
* fbnic_tlv_attr_get_unsigned - Retrieve unsigned value from result
* @attr: Attribute to retrieve data from
* @def: The default value if attr is NULL
*
* Return: unsigned 64b value containing integer value
**/
u64 fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg *attr, u64 def)
{
__le64 le64_value = 0;
if (!attr)
return def;
memcpy(&le64_value, &attr->value[0],
le16_to_cpu(attr->hdr.len) - sizeof(*attr));
return le64_to_cpu(le64_value);
}
/**
* fbnic_tlv_attr_get_signed - Retrieve signed value from result
* @attr: Attribute to retrieve data from
* @def: The default value if attr is NULL
*
* Return: signed 64b value containing integer value
**/
s64 fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg *attr, s64 def)
{
__le64 le64_value = 0;
int shift;
s64 value;
if (!attr)
return def;
shift = (8 + sizeof(*attr) - le16_to_cpu(attr->hdr.len)) * 8;
/* Copy the value and adjust for byte ordering */
memcpy(&le64_value, &attr->value[0],
le16_to_cpu(attr->hdr.len) - sizeof(*attr));
value = le64_to_cpu(le64_value);
/* Sign extend the return value by using a pair of shifts */
return (value << shift) >> shift;
}
/**
* fbnic_tlv_attr_get_string - Retrieve string value from result
* @attr: Attribute to retrieve data from
* @dst: Pointer to an allocated string to store the data
* @dstsize: The maximum size which can be in dst
*
* Return: the size of the string read from firmware or negative error.
**/
ssize_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg *attr, char *dst,
size_t dstsize)
{
size_t srclen, len;
ssize_t ret;
if (!attr)
return -EINVAL;
if (dstsize == 0)
return -E2BIG;
srclen = le16_to_cpu(attr->hdr.len) - sizeof(*attr);
if (srclen > 0 && ((char *)attr->value)[srclen - 1] == '\0')
srclen--;
if (srclen >= dstsize) {
len = dstsize - 1;
ret = -E2BIG;
} else {
len = srclen;
ret = len;
}
memcpy(dst, &attr->value, len);
/* Zero pad end of dst. */
memset(dst + len, 0, dstsize - len);
return ret;
}
/**
* fbnic_tlv_attr_nest_start - Add nested attribute header to message
* @msg: Message header we are adding flag attribute to
* @attr_id: ID of flag attribute we are adding to message
*
* Return: NULL if there is no room for the attribute. Otherwise a pointer
* to the new attribute header.
*
* New header length is stored initially in DWORDs.
**/
struct fbnic_tlv_msg *fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg *msg,
u16 attr_id)
{
int attr_max_len = PAGE_SIZE - offset_in_page(msg) - sizeof(*msg);
struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];
struct fbnic_tlv_hdr hdr = { 0 };
/* Make sure we have space for at least the nest header plus one more */
attr_max_len -= le16_to_cpu(msg->hdr.len) * sizeof(u32);
if (attr_max_len < sizeof(*attr) * 2)
return NULL;
/* Record attribute type and size */
hdr.type = attr_id;
/* Add current message length to account for consumption within the
* page and leave it as a multiple of DWORDs, we will shift to
* bytes when we close it out.
*/
hdr.len = cpu_to_le16(1);
attr->hdr = hdr;
return attr;
}
/**
* fbnic_tlv_attr_nest_stop - Close out nested attribute and add it to message
* @msg: Message header we are adding flag attribute to
*
* Closes out nested attribute, adds length to message, and then bumps
* length from DWORDs to bytes to match other attributes.
**/
void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg *msg)
{
struct fbnic_tlv_msg *attr = &msg[le16_to_cpu(msg->hdr.len)];
u16 len = le16_to_cpu(attr->hdr.len);
/* Add attribute to message if there is more than just a header */
if (len <= 1)
return;
le16_add_cpu(&msg->hdr.len, len);
/* Convert from DWORDs to bytes */
attr->hdr.len = cpu_to_le16(len * sizeof(u32));
}
static int
fbnic_tlv_attr_validate(struct fbnic_tlv_msg *attr,
const struct fbnic_tlv_index *tlv_index)
{
u16 len = le16_to_cpu(attr->hdr.len) - sizeof(*attr);
u16 attr_id = attr->hdr.type;
__le32 *value = &attr->value[0];
if (attr->hdr.is_msg)
return -EINVAL;
if (attr_id >= FBNIC_TLV_RESULTS_MAX)
return -EINVAL;
while (tlv_index->id != attr_id) {
if (tlv_index->id == FBNIC_TLV_ATTR_ID_UNKNOWN) {
if (attr->hdr.cannot_ignore)
return -ENOENT;
return le16_to_cpu(attr->hdr.len);
}
tlv_index++;
}
if (offset_in_page(attr) + len > PAGE_SIZE - sizeof(*attr))
return -E2BIG;
switch (tlv_index->type) {
case FBNIC_TLV_STRING:
if (!len || len > tlv_index->len)
return -EINVAL;
if (((char *)value)[len - 1])
return -EINVAL;
break;
case FBNIC_TLV_FLAG:
if (len)
return -EINVAL;
break;
case FBNIC_TLV_UNSIGNED:
case FBNIC_TLV_SIGNED:
if (tlv_index->len > sizeof(__le64))
return -EINVAL;
fallthrough;
case FBNIC_TLV_BINARY:
if (!len || len > tlv_index->len)
return -EINVAL;
break;
case FBNIC_TLV_NESTED:
case FBNIC_TLV_ARRAY:
if (len % 4)
return -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
/**
* fbnic_tlv_attr_parse_array - Parse array of attributes into results array
* @attr: Start of attributes in the message
* @len: Length of attributes in the message
* @results: Array of pointers to store the results of parsing
* @tlv_index: List of TLV attributes to be parsed from message
* @tlv_attr_id: Specific ID that is repeated in array
* @array_len: Number of results to store in results array
*
* Return: zero on success, or negative value on error.
*
* Will take a list of attributes and a parser definition and will capture
* the results in the results array to have the data extracted later.
**/
int fbnic_tlv_attr_parse_array(struct fbnic_tlv_msg *attr, int len,
struct fbnic_tlv_msg **results,
const struct fbnic_tlv_index *tlv_index,
u16 tlv_attr_id, size_t array_len)
{
int i = 0;
/* Initialize results table to NULL. */
memset(results, 0, array_len * sizeof(results[0]));
/* Nothing to parse if header was only thing there */
if (!len)
return 0;
/* Work through list of attributes, parsing them as necessary */
while (len > 0) {
u16 attr_id = attr->hdr.type;
u16 attr_len;
int err;
if (tlv_attr_id != attr_id)
return -EINVAL;
/* Stop parsing on full error */
err = fbnic_tlv_attr_validate(attr, tlv_index);
if (err < 0)
return err;
if (i >= array_len)
return -ENOSPC;
results[i++] = attr;
attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));
len -= attr_len;
attr += attr_len;
}
return len == 0 ? 0 : -EINVAL;
}
/**
* fbnic_tlv_attr_parse - Parse attributes into a list of attribute results
* @attr: Start of attributes in the message
* @len: Length of attributes in the message
* @results: Array of pointers to store the results of parsing
* @tlv_index: List of TLV attributes to be parsed from message
*
* Return: zero on success, or negative value on error.
*
* Will take a list of attributes and a parser definition and will capture
* the results in the results array to have the data extracted later.
**/
int fbnic_tlv_attr_parse(struct fbnic_tlv_msg *attr, int len,
struct fbnic_tlv_msg **results,
const struct fbnic_tlv_index *tlv_index)
{
/* Initialize results table to NULL. */
memset(results, 0, sizeof(results[0]) * FBNIC_TLV_RESULTS_MAX);
/* Nothing to parse if header was only thing there */
if (!len)
return 0;
/* Work through list of attributes, parsing them as necessary */
while (len > 0) {
int err = fbnic_tlv_attr_validate(attr, tlv_index);
u16 attr_id = attr->hdr.type;
u16 attr_len;
/* Stop parsing on full error */
if (err < 0)
return err;
/* Ignore results for unsupported values */
if (!err) {
/* Do not overwrite existing entries */
if (results[attr_id])
return -EADDRINUSE;
results[attr_id] = attr;
}
attr_len = FBNIC_TLV_MSG_SIZE(le16_to_cpu(attr->hdr.len));
len -= attr_len;
attr += attr_len;
}
return len == 0 ? 0 : -EINVAL;
}
/**
* fbnic_tlv_msg_parse - Parse message and process via predetermined functions
* @opaque: Value passed to parser function to enable driver access
* @msg: Message to be parsed.
* @parser: TLV message parser definition.
*
* Return: zero on success, or negative value on error.
*
* Will take a message a number of message types via the attribute parsing
* definitions and function provided for the parser array.
**/
int fbnic_tlv_msg_parse(void *opaque, struct fbnic_tlv_msg *msg,
const struct fbnic_tlv_parser *parser)
{
struct fbnic_tlv_msg *results[FBNIC_TLV_RESULTS_MAX];
u16 msg_id = msg->hdr.type;
int err;
if (!msg->hdr.is_msg)
return -EINVAL;
if (le16_to_cpu(msg->hdr.len) > PAGE_SIZE / sizeof(u32))
return -E2BIG;
while (parser->id != msg_id) {
if (parser->id == FBNIC_TLV_MSG_ID_UNKNOWN)
return -ENOENT;
parser++;
}
err = fbnic_tlv_attr_parse(&msg[1], le16_to_cpu(msg->hdr.len) - 1,
results, parser->attr);
if (err)
return err;
return parser->func(opaque, results);
}
/**
* fbnic_tlv_parser_error - called if message doesn't match known type
* @opaque: (unused)
* @results: (unused)
*
* Return: -EBADMSG to indicate the message is an unsupported type
**/
int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results)
{
return -EBADMSG;
}
#define FBNIC_TLV_TEST_STRING_LEN 32
struct fbnic_tlv_test {
u64 test_u64;
s64 test_s64;
u32 test_u32;
s32 test_s32;
u16 test_u16;
s16 test_s16;
u8 test_mac[ETH_ALEN];
u8 test_mac_array[4][ETH_ALEN];
u8 test_true;
u8 test_false;
char test_string[FBNIC_TLV_TEST_STRING_LEN];
};
static struct fbnic_tlv_test test_struct;
const struct fbnic_tlv_index fbnic_tlv_test_index[] = {
FBNIC_TLV_ATTR_U64(FBNIC_TLV_TEST_MSG_U64),
FBNIC_TLV_ATTR_S64(FBNIC_TLV_TEST_MSG_S64),
FBNIC_TLV_ATTR_U32(FBNIC_TLV_TEST_MSG_U32),
FBNIC_TLV_ATTR_S32(FBNIC_TLV_TEST_MSG_S32),
FBNIC_TLV_ATTR_U32(FBNIC_TLV_TEST_MSG_U16),
FBNIC_TLV_ATTR_S32(FBNIC_TLV_TEST_MSG_S16),
FBNIC_TLV_ATTR_MAC_ADDR(FBNIC_TLV_TEST_MSG_MAC_ADDR),
FBNIC_TLV_ATTR_FLAG(FBNIC_TLV_TEST_MSG_FLAG_TRUE),
FBNIC_TLV_ATTR_FLAG(FBNIC_TLV_TEST_MSG_FLAG_FALSE),
FBNIC_TLV_ATTR_STRING(FBNIC_TLV_TEST_MSG_STRING,
FBNIC_TLV_TEST_STRING_LEN),
FBNIC_TLV_ATTR_ARRAY(FBNIC_TLV_TEST_MSG_ARRAY),
FBNIC_TLV_ATTR_NESTED(FBNIC_TLV_TEST_MSG_NESTED),
FBNIC_TLV_ATTR_LAST
};
static void fbnic_tlv_test_struct_init(void)
{
int i = FBNIC_TLV_TEST_STRING_LEN - 1;
/* Populate the struct with random data */
get_random_once(&test_struct,
offsetof(struct fbnic_tlv_test, test_string) + i);
/* Force true/false to their expected values */
test_struct.test_false = false;
test_struct.test_true = true;
/* Convert test_string to a true ASCII string */
test_struct.test_string[i] = '\0';
while (i--) {
/* Force characters into displayable range */
if (test_struct.test_string[i] < 64 ||
test_struct.test_string[i] >= 96) {
test_struct.test_string[i] %= 32;
test_struct.test_string[i] += 64;
}
}
}
static int fbnic_tlv_test_attr_data(struct fbnic_tlv_msg *msg)
{
struct fbnic_tlv_msg *array;
int err, i;
err = fbnic_tlv_attr_put_int(msg, FBNIC_TLV_TEST_MSG_U64,
test_struct.test_u64);
if (err)
return err;
err = fbnic_tlv_attr_put_int(msg, FBNIC_TLV_TEST_MSG_S64,
test_struct.test_s64);
if (err)
return err;
err = fbnic_tlv_attr_put_int(msg, FBNIC_TLV_TEST_MSG_U32,
test_struct.test_u32);
if (err)
return err;
err = fbnic_tlv_attr_put_int(msg, FBNIC_TLV_TEST_MSG_S32,
test_struct.test_s32);
if (err)
return err;
err = fbnic_tlv_attr_put_int(msg, FBNIC_TLV_TEST_MSG_U16,
test_struct.test_u16);
if (err)
return err;
err = fbnic_tlv_attr_put_int(msg, FBNIC_TLV_TEST_MSG_S16,
test_struct.test_s16);
if (err)
return err;
err = fbnic_tlv_attr_put_value(msg, FBNIC_TLV_TEST_MSG_MAC_ADDR,
test_struct.test_mac, ETH_ALEN);
if (err)
return err;
/* Start MAC address array */
array = fbnic_tlv_attr_nest_start(msg, FBNIC_TLV_TEST_MSG_ARRAY);
if (!array)
return -ENOSPC;
for (i = 0; i < 4; i++) {
err = fbnic_tlv_attr_put_value(array,
FBNIC_TLV_TEST_MSG_MAC_ADDR,
test_struct.test_mac_array[i],
ETH_ALEN);
if (err)
return err;
}
/* Close array */
fbnic_tlv_attr_nest_stop(msg);
err = fbnic_tlv_attr_put_flag(msg, FBNIC_TLV_TEST_MSG_FLAG_TRUE);
if (err)
return err;
return fbnic_tlv_attr_put_string(msg, FBNIC_TLV_TEST_MSG_STRING,
test_struct.test_string);
}
/**
* fbnic_tlv_test_create - Allocate a test message and fill it w/ data
* @fbd: FBNIC device structure
*
* Return: NULL on failure to allocate or pointer to new TLV test message.
**/
struct fbnic_tlv_msg *fbnic_tlv_test_create(struct fbnic_dev *fbd)
{
struct fbnic_tlv_msg *msg, *nest;
int err;
msg = fbnic_tlv_msg_alloc(FBNIC_TLV_MSG_ID_TEST);
if (!msg)
return NULL;
/* Randomize struct data */
fbnic_tlv_test_struct_init();
/* Add first level of data to message */
err = fbnic_tlv_test_attr_data(msg);
if (err)
goto free_message;
/* Start second level nested */
nest = fbnic_tlv_attr_nest_start(msg, FBNIC_TLV_TEST_MSG_NESTED);
if (!nest)
goto free_message;
/* Add nested data */
err = fbnic_tlv_test_attr_data(nest);
if (err)
goto free_message;
/* Close nest and report full message */
fbnic_tlv_attr_nest_stop(msg);
return msg;
free_message:
free_page((unsigned long)msg);
return NULL;
}
void fbnic_tlv_attr_addr_copy(u8 *dest, struct fbnic_tlv_msg *src)
{
u8 *mac_addr;
mac_addr = fbnic_tlv_attr_get_value_ptr(src);
memcpy(dest, mac_addr, ETH_ALEN);
}
/**
* fbnic_tlv_parser_test_attr - Function loading test attributes into structure
* @str: Test structure to load
* @results: Pointer to results array
*
* Copies attributes into structure. Any attribute that doesn't exist in the
* results array is not populated.
**/
static void fbnic_tlv_parser_test_attr(struct fbnic_tlv_test *str,
struct fbnic_tlv_msg **results)
{
struct fbnic_tlv_msg *array_results[4];
struct fbnic_tlv_msg *attr;
char *string = NULL;
int i, err;
str->test_u64 = fta_get_uint(results, FBNIC_TLV_TEST_MSG_U64);
str->test_u32 = fta_get_uint(results, FBNIC_TLV_TEST_MSG_U32);
str->test_u16 = fta_get_uint(results, FBNIC_TLV_TEST_MSG_U16);
str->test_s64 = fta_get_sint(results, FBNIC_TLV_TEST_MSG_S64);
str->test_s32 = fta_get_sint(results, FBNIC_TLV_TEST_MSG_S32);
str->test_s16 = fta_get_sint(results, FBNIC_TLV_TEST_MSG_S16);
attr = results[FBNIC_TLV_TEST_MSG_MAC_ADDR];
if (attr)
fbnic_tlv_attr_addr_copy(str->test_mac, attr);
attr = results[FBNIC_TLV_TEST_MSG_ARRAY];
if (attr) {
int len = le16_to_cpu(attr->hdr.len) / sizeof(u32) - 1;
err = fbnic_tlv_attr_parse_array(&attr[1], len,
array_results,
fbnic_tlv_test_index,
FBNIC_TLV_TEST_MSG_MAC_ADDR,
4);
if (!err) {
for (i = 0; i < 4 && array_results[i]; i++)
fbnic_tlv_attr_addr_copy(str->test_mac_array[i],
array_results[i]);
}
}
str->test_true = !!results[FBNIC_TLV_TEST_MSG_FLAG_TRUE];
str->test_false = !!results[FBNIC_TLV_TEST_MSG_FLAG_FALSE];
attr = results[FBNIC_TLV_TEST_MSG_STRING];
if (attr) {
string = fbnic_tlv_attr_get_value_ptr(attr);
strscpy(str->test_string, string, FBNIC_TLV_TEST_STRING_LEN);
}
}
static void fbnic_tlv_test_dump(struct fbnic_tlv_test *value, char *prefix)
{
print_hex_dump(KERN_INFO, prefix, DUMP_PREFIX_OFFSET, 16, 1,
value, sizeof(*value), true);
}
/**
* fbnic_tlv_parser_test - Function for parsing and testing test message
* @opaque: Unused value
* @results: Results of parser output
*
* Return: negative value on error, or 0 on success.
*
* Parses attributes to structures and compares the structure to the
* expected test value that should have been used to populate the message.
*
* Used to verify message generation and parser are working correctly.
**/
int fbnic_tlv_parser_test(void *opaque, struct fbnic_tlv_msg **results)
{
struct fbnic_tlv_msg *nest_results[FBNIC_TLV_RESULTS_MAX] = { 0 };
struct fbnic_tlv_test result_struct;
struct fbnic_tlv_msg *attr;
int err;
memset(&result_struct, 0, sizeof(result_struct));
fbnic_tlv_parser_test_attr(&result_struct, results);
if (memcmp(&test_struct, &result_struct, sizeof(test_struct))) {
fbnic_tlv_test_dump(&result_struct, "fbnic: found - ");
fbnic_tlv_test_dump(&test_struct, "fbnic: expected - ");
return -EINVAL;
}
attr = results[FBNIC_TLV_TEST_MSG_NESTED];
if (!attr)
return -EINVAL;
err = fbnic_tlv_attr_parse(&attr[1],
le16_to_cpu(attr->hdr.len) / sizeof(u32) - 1,
nest_results, fbnic_tlv_test_index);
if (err)
return err;
memset(&result_struct, 0, sizeof(result_struct));
fbnic_tlv_parser_test_attr(&result_struct, nest_results);
if (memcmp(&test_struct, &result_struct, sizeof(test_struct))) {
fbnic_tlv_test_dump(&result_struct, "fbnic: found - ");
fbnic_tlv_test_dump(&test_struct, "fbnic: expected - ");
return -EINVAL;
}
return 0;
}
@@ -0,0 +1,185 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_TLV_H_
#define _FBNIC_TLV_H_
#include <asm/byteorder.h>
#include <linux/bits.h>
#include <linux/const.h>
#include <linux/types.h>
struct fbnic_dev;
#define FBNIC_TLV_MSG_ALIGN(len) ALIGN(len, sizeof(u32))
#define FBNIC_TLV_MSG_SIZE(len) \
(FBNIC_TLV_MSG_ALIGN(len) / sizeof(u32))
/* TLV Header Format
* 3 2 1
* 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Length |M|I|RSV| Type / ID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* The TLV header format described above will be used for transferring
* messages between the host and the firmware. To ensure byte ordering
* we have defined all fields as being little endian.
* Type/ID: Identifier for message and/or attribute
* RSV: Reserved field for future use, likely as additional flags
* I: cannot_ignore flag, identifies if unrecognized attribute can be ignored
* M: is_msg, indicates that this is the start of a new message
* Length: Total length of message in dwords including header
* or
* Total length of attribute in bytes including header
*/
struct fbnic_tlv_hdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u16 type : 12; /* 0 .. 11 Type / ID */
u16 rsvd : 2; /* 12 .. 13 Reserved for future use */
u16 cannot_ignore : 1; /* 14 Attribute can be ignored */
u16 is_msg : 1; /* 15 Header belongs to message */
#elif defined(__BIG_ENDIAN_BITFIELD)
u16 is_msg : 1; /* 15 Header belongs to message */
u16 cannot_ignore : 1; /* 14 Attribute can be ignored */
u16 rsvd : 2; /* 13 .. 12 Reserved for future use */
u16 type : 12; /* 11 .. 0 Type / ID */
#else
#error "Missing defines from byteorder.h"
#endif
__le16 len; /* 16 .. 32 length including TLV header */
};
#define FBNIC_TLV_RESULTS_MAX 32
struct fbnic_tlv_msg {
struct fbnic_tlv_hdr hdr;
__le32 value[];
};
#define FBNIC_TLV_MSG_ID_UNKNOWN USHRT_MAX
enum fbnic_tlv_type {
FBNIC_TLV_STRING,
FBNIC_TLV_FLAG,
FBNIC_TLV_UNSIGNED,
FBNIC_TLV_SIGNED,
FBNIC_TLV_BINARY,
FBNIC_TLV_NESTED,
FBNIC_TLV_ARRAY,
__FBNIC_TLV_MAX_TYPE
};
/* TLV Index
* Defines the relationship between the attribute IDs and their types.
* For each entry in the index there will be a size and type associated
* with it so that we can use this to parse the data and verify it matches
* the expected layout.
*/
struct fbnic_tlv_index {
u16 id;
u16 len;
enum fbnic_tlv_type type;
};
#define TLV_MAX_DATA ((PAGE_SIZE - 512) & 0xFFFF)
#define FBNIC_TLV_ATTR_ID_UNKNOWN USHRT_MAX
#define FBNIC_TLV_ATTR_STRING(id, len) { id, len, FBNIC_TLV_STRING }
#define FBNIC_TLV_ATTR_FLAG(id) { id, 0, FBNIC_TLV_FLAG }
#define FBNIC_TLV_ATTR_U32(id) { id, sizeof(u32), FBNIC_TLV_UNSIGNED }
#define FBNIC_TLV_ATTR_U64(id) { id, sizeof(u64), FBNIC_TLV_UNSIGNED }
#define FBNIC_TLV_ATTR_S32(id) { id, sizeof(s32), FBNIC_TLV_SIGNED }
#define FBNIC_TLV_ATTR_S64(id) { id, sizeof(s64), FBNIC_TLV_SIGNED }
#define FBNIC_TLV_ATTR_MAC_ADDR(id) { id, ETH_ALEN, FBNIC_TLV_BINARY }
#define FBNIC_TLV_ATTR_NESTED(id) { id, 0, FBNIC_TLV_NESTED }
#define FBNIC_TLV_ATTR_ARRAY(id) { id, 0, FBNIC_TLV_ARRAY }
#define FBNIC_TLV_ATTR_RAW_DATA(id) { id, TLV_MAX_DATA, FBNIC_TLV_BINARY }
#define FBNIC_TLV_ATTR_LAST { FBNIC_TLV_ATTR_ID_UNKNOWN, 0, 0 }
struct fbnic_tlv_parser {
u16 id;
const struct fbnic_tlv_index *attr;
int (*func)(void *opaque,
struct fbnic_tlv_msg **results);
};
#define FBNIC_TLV_PARSER(id, attr, func) { FBNIC_TLV_MSG_ID_##id, attr, func }
static inline void *
fbnic_tlv_attr_get_value_ptr(struct fbnic_tlv_msg *attr)
{
return (void *)&attr->value[0];
}
static inline bool fbnic_tlv_attr_get_bool(struct fbnic_tlv_msg *attr)
{
return !!attr;
}
u64 fbnic_tlv_attr_get_unsigned(struct fbnic_tlv_msg *attr, u64 def);
s64 fbnic_tlv_attr_get_signed(struct fbnic_tlv_msg *attr, s64 def);
ssize_t fbnic_tlv_attr_get_string(struct fbnic_tlv_msg *attr, char *dst,
size_t dstsize);
struct fbnic_tlv_msg *fbnic_tlv_msg_alloc(u16 msg_id);
int fbnic_tlv_attr_put_flag(struct fbnic_tlv_msg *msg, const u16 attr_id);
int fbnic_tlv_attr_put_value(struct fbnic_tlv_msg *msg, const u16 attr_id,
const void *value, const int len);
int __fbnic_tlv_attr_put_int(struct fbnic_tlv_msg *msg, const u16 attr_id,
s64 value, const int len);
#define fbnic_tlv_attr_put_int(msg, attr_id, value) \
__fbnic_tlv_attr_put_int(msg, attr_id, value, \
FBNIC_TLV_MSG_ALIGN(sizeof(value)))
int fbnic_tlv_attr_put_mac_addr(struct fbnic_tlv_msg *msg, const u16 attr_id,
const u8 *mac_addr);
int fbnic_tlv_attr_put_string(struct fbnic_tlv_msg *msg, u16 attr_id,
const char *string);
struct fbnic_tlv_msg *fbnic_tlv_attr_nest_start(struct fbnic_tlv_msg *msg,
u16 attr_id);
void fbnic_tlv_attr_nest_stop(struct fbnic_tlv_msg *msg);
void fbnic_tlv_attr_addr_copy(u8 *dest, struct fbnic_tlv_msg *src);
int fbnic_tlv_attr_parse_array(struct fbnic_tlv_msg *attr, int len,
struct fbnic_tlv_msg **results,
const struct fbnic_tlv_index *tlv_index,
u16 tlv_attr_id, size_t array_len);
int fbnic_tlv_attr_parse(struct fbnic_tlv_msg *attr, int len,
struct fbnic_tlv_msg **results,
const struct fbnic_tlv_index *tlv_index);
int fbnic_tlv_msg_parse(void *opaque, struct fbnic_tlv_msg *msg,
const struct fbnic_tlv_parser *parser);
int fbnic_tlv_parser_error(void *opaque, struct fbnic_tlv_msg **results);
#define fta_get_uint(_results, _id) \
fbnic_tlv_attr_get_unsigned(_results[_id], 0)
#define fta_get_sint(_results, _id) \
fbnic_tlv_attr_get_signed(_results[_id], 0)
#define fta_get_str(_results, _id, _dst, _dstsize) \
fbnic_tlv_attr_get_string(_results[_id], _dst, _dstsize)
#define FBNIC_TLV_MSG_ID_TEST 0
enum fbnic_tlv_test_attr_id {
FBNIC_TLV_TEST_MSG_U64,
FBNIC_TLV_TEST_MSG_S64,
FBNIC_TLV_TEST_MSG_U32,
FBNIC_TLV_TEST_MSG_S32,
FBNIC_TLV_TEST_MSG_U16,
FBNIC_TLV_TEST_MSG_S16,
FBNIC_TLV_TEST_MSG_MAC_ADDR,
FBNIC_TLV_TEST_MSG_FLAG_TRUE,
FBNIC_TLV_TEST_MSG_FLAG_FALSE,
FBNIC_TLV_TEST_MSG_STRING,
FBNIC_TLV_TEST_MSG_NESTED,
FBNIC_TLV_TEST_MSG_ARRAY,
FBNIC_TLV_TEST_MSG_MAX
};
extern const struct fbnic_tlv_index fbnic_tlv_test_index[];
struct fbnic_tlv_msg *fbnic_tlv_test_create(struct fbnic_dev *fbd);
int fbnic_tlv_parser_test(void *opaque, struct fbnic_tlv_msg **results);
#define FBNIC_TLV_MSG_TEST \
FBNIC_TLV_PARSER(TEST, fbnic_tlv_test_index, \
fbnic_tlv_parser_test)
#define FBNIC_TLV_MSG_ERROR \
FBNIC_TLV_PARSER(UNKNOWN, NULL, fbnic_tlv_parser_error)
#endif /* _FBNIC_TLV_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,207 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) Meta Platforms, Inc. and affiliates. */
#ifndef _FBNIC_TXRX_H_
#define _FBNIC_TXRX_H_
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/types.h>
#include <linux/u64_stats_sync.h>
#include <net/xdp.h>
struct fbnic_net;
/* Guarantee we have space needed for storing the buffer
* To store the buffer we need:
* 1 descriptor per page
* + 1 descriptor for skb head
* + 2 descriptors for metadata and optional metadata
* + 7 descriptors to keep tail out of the same cacheline as head
* If we cannot guarantee that then we should return TX_BUSY
*/
#define FBNIC_MAX_SKB_DESC (MAX_SKB_FRAGS + 10)
#define FBNIC_TX_DESC_WAKEUP (FBNIC_MAX_SKB_DESC * 2)
#define FBNIC_TX_DESC_MIN roundup_pow_of_two(FBNIC_TX_DESC_WAKEUP)
/* To receive the worst case packet we need:
* 1 descriptor for primary metadata
* + 1 descriptor for optional metadata
* + 1 descriptor for headers
* + 4 descriptors for payload
*/
#define FBNIC_MAX_RX_PKT_DESC 7
#define FBNIC_RX_DESC_MIN roundup_pow_of_two(FBNIC_MAX_RX_PKT_DESC * 2)
#define FBNIC_MAX_TXQS 128u
#define FBNIC_MAX_RXQS 128u
#define FBNIC_MAX_XDPQS 128u
/* These apply to TWQs, TCQ, RCQ */
#define FBNIC_QUEUE_SIZE_MIN 64u
#define FBNIC_QUEUE_SIZE_MAX SZ_64K
#define FBNIC_TXQ_SIZE_DEFAULT 1024
#define FBNIC_HPQ_SIZE_DEFAULT 256
#define FBNIC_PPQ_SIZE_DEFAULT 256
#define FBNIC_RCQ_SIZE_DEFAULT 1024
#define FBNIC_TX_USECS_DEFAULT 35
#define FBNIC_RX_USECS_DEFAULT 30
#define FBNIC_RX_FRAMES_DEFAULT 0
#define FBNIC_RX_TROOM \
SKB_DATA_ALIGN(sizeof(struct skb_shared_info))
#define FBNIC_RX_HROOM_PAD 128
#define FBNIC_RX_HROOM \
(ALIGN(FBNIC_RX_TROOM + FBNIC_RX_HROOM_PAD, 128) - FBNIC_RX_TROOM)
#define FBNIC_RX_PAD 0
#define FBNIC_RX_PAYLD_OFFSET 0
#define FBNIC_RX_PAYLD_PG_CL 0
#define FBNIC_RING_F_DISABLED BIT(0)
#define FBNIC_RING_F_CTX BIT(1)
#define FBNIC_RING_F_STATS BIT(2) /* Ring's stats may be used */
#define FBNIC_HDS_THRESH_MAX \
(4096 - FBNIC_RX_HROOM - FBNIC_RX_TROOM - FBNIC_RX_PAD)
#define FBNIC_HDS_THRESH_DEFAULT \
(1536 - FBNIC_RX_PAD)
#define FBNIC_HDR_BYTES_MIN 256
struct fbnic_pkt_buff {
struct xdp_buff buff;
ktime_t hwtstamp;
bool add_frag_failed;
};
struct fbnic_queue_stats {
u64 packets;
u64 bytes;
union {
struct {
u64 csum_partial;
u64 lso;
u64 ts_packets;
u64 ts_lost;
u64 stop;
u64 wake;
} twq;
struct {
u64 alloc_failed;
u64 csum_complete;
u64 csum_none;
u64 length_errors;
} rx;
struct {
u64 alloc_failed;
} bdq;
};
u64 dropped;
struct u64_stats_sync syncp;
};
#define FBNIC_PAGECNT_BIAS_MAX PAGE_SIZE
struct fbnic_rx_buf {
netmem_ref netmem;
long pagecnt_bias;
};
struct fbnic_ring {
/* Pointer to buffer specific info */
union {
struct fbnic_pkt_buff *pkt; /* RCQ */
struct fbnic_rx_buf *rx_buf; /* BDQ */
void **tx_buf; /* TWQ */
void *buffer; /* Generic pointer */
};
u32 __iomem *doorbell; /* Pointer to CSR space for ring */
__le64 *desc; /* Descriptor ring memory */
u16 size_mask; /* Size of ring in descriptors - 1 */
u8 q_idx; /* Logical netdev ring index */
u8 flags; /* Ring flags (FBNIC_RING_F_*) */
u32 head, tail; /* Head/Tail of ring */
union {
/* Rx BDQs only */
struct page_pool *page_pool;
/* Deferred_head is used to cache the head for TWQ1 if
* an attempt is made to clean TWQ1 with zero napi_budget.
* We do not use it for any other ring.
*/
s32 deferred_head;
};
struct fbnic_queue_stats stats;
/* Slow path fields follow */
dma_addr_t dma; /* Phys addr of descriptor memory */
size_t size; /* Size of descriptor ring in memory */
};
struct fbnic_q_triad {
struct fbnic_ring sub0, sub1, cmpl;
struct xdp_rxq_info xdp_rxq;
};
struct fbnic_napi_vector {
struct napi_struct napi;
struct device *dev; /* Device for DMA unmapping */
struct fbnic_dev *fbd;
struct dentry *dbg_nv;
u16 v_idx;
u8 txt_count;
u8 rxt_count;
struct fbnic_q_triad qt[];
};
extern const struct netdev_queue_mgmt_ops fbnic_queue_mgmt_ops;
netdev_tx_t fbnic_xmit_frame(struct sk_buff *skb, struct net_device *dev);
netdev_features_t
fbnic_features_check(struct sk_buff *skb, struct net_device *dev,
netdev_features_t features);
void fbnic_aggregate_ring_rx_counters(struct fbnic_net *fbn,
struct fbnic_ring *rxr);
void fbnic_aggregate_ring_bdq_counters(struct fbnic_net *fbn,
struct fbnic_ring *rxr);
void fbnic_aggregate_ring_tx_counters(struct fbnic_net *fbn,
struct fbnic_ring *txr);
void fbnic_aggregate_ring_xdp_counters(struct fbnic_net *fbn,
struct fbnic_ring *xdpr);
int fbnic_alloc_napi_vectors(struct fbnic_net *fbn);
void fbnic_free_napi_vectors(struct fbnic_net *fbn);
int fbnic_alloc_resources(struct fbnic_net *fbn);
void fbnic_free_resources(struct fbnic_net *fbn);
int fbnic_set_netif_queues(struct fbnic_net *fbn);
void fbnic_reset_netif_queues(struct fbnic_net *fbn);
irqreturn_t fbnic_msix_clean_rings(int irq, void *data);
void fbnic_napi_enable(struct fbnic_net *fbn);
void fbnic_napi_disable(struct fbnic_net *fbn);
void fbnic_config_drop_mode(struct fbnic_net *fbn, bool tx_pause);
void fbnic_enable(struct fbnic_net *fbn);
void fbnic_disable(struct fbnic_net *fbn);
void fbnic_dbg_up(struct fbnic_net *fbn);
void fbnic_dbg_down(struct fbnic_net *fbn);
void fbnic_flush(struct fbnic_net *fbn);
void fbnic_fill(struct fbnic_net *fbn);
u32 __iomem *fbnic_ring_csr_base(const struct fbnic_ring *ring);
void fbnic_napi_depletion_check(struct net_device *netdev);
int fbnic_wait_all_queues_idle(struct fbnic_dev *fbd, bool may_fail);
static inline int fbnic_napi_idx(const struct fbnic_napi_vector *nv)
{
return nv->v_idx - FBNIC_NON_NAPI_VECTORS;
}
void fbnic_dbg_nv_init(struct fbnic_napi_vector *nv);
void fbnic_dbg_nv_exit(struct fbnic_napi_vector *nv);
#endif /* _FBNIC_TXRX_H_ */