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,38 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Meta Platforms network device configuration
#
config NET_VENDOR_META
bool "Meta Platforms devices"
default y
help
If you have a network (Ethernet) card designed by Meta, say Y.
That's Meta as in the parent company of Facebook.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all
the questions about Meta cards. If you say Y, you will be asked for
your specific card in the following questions.
if NET_VENDOR_META
config FBNIC
tristate "Meta Platforms Host Network Interface"
depends on 64BIT || COMPILE_TEST
depends on !S390
depends on MAX_SKB_FRAGS < 22
depends on PCI_MSI
depends on PTP_1588_CLOCK_OPTIONAL
select NET_DEVLINK
select PAGE_POOL
select PCS_XPCS
select PHYLINK
select PLDMFW
help
This driver supports Meta Platforms Host Network Interface.
To compile this driver as a module, choose M here. The module
will be called fbnic. MSI-X interrupt support is required.
endif # NET_VENDOR_META
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Meta Platforms network device drivers.
#
obj-$(CONFIG_FBNIC) += fbnic/
@@ -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_ */