Commit 05e05951 authored by Paolo Abeni's avatar Paolo Abeni
Browse files

Merge branch 'eth-fbnic-add-fbnic-self-tests'

Mike Marciniszyn says:

====================
eth fbnic: Add fbnic self tests

From: "Mike Marciniszyn (Meta)" <mike.marciniszyn@gmail.com>

This series adds self tests to test the registers, the
msix interrupts, the tlv, and the firmware mailbox.

This series assumes that the
[PATCH net-next 0/2] Add debugfs hooks [1]
is present.

When the self tests are run the with ethtool -t:

        ethtool -t eth0
        The test result is PASS
        The test extra info:
        Register test (offline)  0
        MSI-X Interrupt test (offline)   0
        FW mailbox test (on/offline)     0
====================

Link: https://patch.msgid.link/20260307105847.1438-1-mike.marciniszyn@gmail.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parents 89fe91c6 8e521819
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -197,6 +197,38 @@ 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);

+128 −0
Original line number Diff line number Diff line
@@ -147,3 +147,131 @@ int fbnic_csr_regs_len(struct fbnic_dev *fbd)

	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;
}
+19 −0
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@

#include <linux/bitops.h>

struct fbnic_dev;

#define CSR_BIT(nr)		(1u << (nr))
#define CSR_GENMASK(h, l)	GENMASK(h, l)

@@ -1221,4 +1223,21 @@ enum{
	FBNIC_CSR_VERSION_V1_0_ASIC = 1,
};

/**
 * enum fbnic_reg_self_test_codes - return codes from self test routines
 *
 * This is the code that is returned from the register self test
 * routines.
 *
 * The test either returns success or the register number
 * that failed during the test.
 *
 * @FBNIC_REG_TEST_SUCCESS: no errors
 */
enum fbnic_reg_self_test_codes {
	FBNIC_REG_TEST_SUCCESS = 0,
};

enum fbnic_reg_self_test_codes fbnic_csr_regs_test(struct fbnic_dev *fbd);

#endif /* _FBNIC_CSR_H_ */
+93 −0
Original line number Diff line number Diff line
@@ -126,6 +126,20 @@ static const struct fbnic_stat fbnic_gstrings_xdp_stats[] = {
#define FBNIC_STATS_LEN \
	(FBNIC_HW_STATS_LEN + FBNIC_XDP_STATS_LEN * FBNIC_MAX_XDPQS)

enum fbnic_self_test_results {
	TEST_REG = 0,
	TEST_MSIX,
	TEST_MBX,
};

static const char fbnic_gstrings_self_test[][ETH_GSTRING_LEN] = {
	[TEST_REG]	= "Register test (offline)",
	[TEST_MSIX]	= "MSI-X Interrupt test (offline)",
	[TEST_MBX]      = "FW mailbox test (on/offline)",
};

#define FBNIC_TEST_LEN ARRAY_SIZE(fbnic_gstrings_self_test)

static void
fbnic_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *drvinfo)
{
@@ -475,6 +489,10 @@ static void fbnic_get_strings(struct net_device *dev, u32 sset, u8 *data)
		for (i = 0; i < FBNIC_MAX_XDPQS; i++)
			fbnic_get_xdp_queue_strings(&data, i);
		break;
	case ETH_SS_TEST:
		memcpy(data, fbnic_gstrings_self_test,
		       sizeof(fbnic_gstrings_self_test));
		break;
	}
}

@@ -566,6 +584,8 @@ static int fbnic_get_sset_count(struct net_device *dev, int sset)
	switch (sset) {
	case ETH_SS_STATS:
		return FBNIC_STATS_LEN;
	case ETH_SS_TEST:
		return FBNIC_TEST_LEN;
	default:
		return -EOPNOTSUPP;
	}
@@ -1478,6 +1498,78 @@ fbnic_remove_rxfh_context(struct net_device *netdev,
	return 0;
}

static int fbnic_ethtool_regs_test(struct net_device *netdev, u64 *data)
{
	struct fbnic_net *fbn = netdev_priv(netdev);
	struct fbnic_dev *fbd = fbn->fbd;

	*data = fbnic_csr_regs_test(fbd);

	return !!*data;
}

/**
 * fbnic_ethtool_msix_test - Verify behavior of NIC interrupts
 * @netdev: netdev device to test
 * @data: Pointer to results storage
 *
 * This function is meant to test the global interrupt registers and the
 * PCIe IP MSI-X functionality. It essentially goes through and tests
 * test 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: non-zero on failure.
 **/
static int fbnic_ethtool_msix_test(struct net_device *netdev, u64 *data)
{
	struct fbnic_net *fbn = netdev_priv(netdev);
	struct fbnic_dev *fbd = fbn->fbd;

	*data = fbnic_msix_test(fbd);

	return !!*data;
}

static int fbnic_ethtool_mbx_self_test(struct net_device *netdev, u64 *data)
{
	struct fbnic_net *fbn = netdev_priv(netdev);
	struct fbnic_dev *fbd = fbn->fbd;

	*data = fbnic_fw_mbx_self_test(fbd);

	return !!*data;
}

static void fbnic_self_test(struct net_device *netdev,
			    struct ethtool_test *eth_test, u64 *data)
{
	bool if_running = netif_running(netdev);

	if (fbnic_ethtool_mbx_self_test(netdev, &data[TEST_MBX]))
		eth_test->flags |= ETH_TEST_FL_FAILED;

	if (!(eth_test->flags & ETH_TEST_FL_OFFLINE)) {
		data[TEST_REG] = 0;
		data[TEST_MSIX] = 0;
		return;
	}

	if (if_running)
		netif_close(netdev);

	if (fbnic_ethtool_regs_test(netdev, &data[TEST_REG]))
		eth_test->flags |= ETH_TEST_FL_FAILED;

	if (fbnic_ethtool_msix_test(netdev, &data[TEST_MSIX]))
		eth_test->flags |= ETH_TEST_FL_FAILED;

	if (if_running && netif_open(netdev, NULL)) {
		netdev_err(netdev,
			   "Failed to re-initialize hardware following test\n");
		eth_test->flags |= ETH_TEST_FL_FAILED;
	}
}

static void fbnic_get_channels(struct net_device *netdev,
			       struct ethtool_channels *ch)
{
@@ -1940,6 +2032,7 @@ static const struct ethtool_ops fbnic_ethtool_ops = {
	.get_pause_stats		= fbnic_get_pause_stats,
	.get_pauseparam			= fbnic_phylink_get_pauseparam,
	.set_pauseparam			= fbnic_phylink_set_pauseparam,
	.self_test			= fbnic_self_test,
	.get_strings			= fbnic_get_strings,
	.get_ethtool_stats		= fbnic_get_ethtool_stats,
	.get_sset_count			= fbnic_get_sset_count,
+100 −0
Original line number Diff line number Diff line
@@ -378,6 +378,37 @@ fbnic_fw_get_cmpl_by_type(struct fbnic_dev *fbd, u32 msg_type)
	return cmpl_data;
}

/**
 * fbnic_fw_xmit_test_msg - Create and transmit a test message to FW mailbox
 * @fbd: FBNIC device structure
 * @cmpl: fw completion struct
 *
 * Return: zero on success, negative value on failure
 *
 * Generates a single page mailbox test message and places it in the Tx
 * mailbox queue. Expectation is that the FW will validate that the nested
 * value matches the external values, and then will echo them back to us.
 *
 * Also sets a completion slot for use in the completion wait calls when
 * the cmpl arg is non-NULL.
 */
int fbnic_fw_xmit_test_msg(struct fbnic_dev *fbd,
			   struct fbnic_fw_completion *cmpl)
{
	struct fbnic_tlv_msg *test_msg;
	int err;

	test_msg = fbnic_tlv_test_create(fbd);
	if (!test_msg)
		return -ENOMEM;

	err = fbnic_mbx_map_req_w_cmpl(fbd, test_msg, cmpl);
	if (err)
		free_page((unsigned long)test_msg);

	return err;
}

/**
 * fbnic_fw_xmit_simple_msg - Transmit a simple single TLV message w/o data
 * @fbd: FBNIC device structure
@@ -1556,7 +1587,29 @@ int fbnic_fw_xmit_send_logs(struct fbnic_dev *fbd, bool enable,
	return err;
}

static int
fbnic_fw_parser_test(void *opaque, struct fbnic_tlv_msg **results)
{
	struct fbnic_fw_completion *cmpl;
	struct fbnic_dev *fbd = opaque;
	int err;

	/* find cmpl */
	cmpl = fbnic_fw_get_cmpl_by_type(fbd, FBNIC_TLV_MSG_ID_TEST);
	if (!cmpl)
		return -ENOSPC;

	err = fbnic_tlv_parser_test(opaque, results);

	cmpl->result = err;
	complete(&cmpl->done);
	fbnic_fw_put_cmpl(cmpl);

	return err;
}

static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = {
	FBNIC_TLV_PARSER(TEST, fbnic_tlv_test_index, fbnic_fw_parser_test),
	FBNIC_TLV_PARSER(FW_CAP_RESP, fbnic_fw_cap_resp_index,
			 fbnic_fw_parse_cap_resp),
	FBNIC_TLV_PARSER(OWNERSHIP_RESP, fbnic_ownership_resp_index,
@@ -1787,6 +1840,53 @@ void fbnic_mbx_flush_tx(struct fbnic_dev *fbd)
	} while (time_is_after_jiffies(timeout));
}

/**
 * fbnic_fw_mbx_self_test() - verify firmware interface
 * @fbd: device to test
 *
 * This function tests the interfaces to/from the firmware.
 *
 * Return: See enum fbnic_fw_self_test_codes
 **/
enum fbnic_fw_self_test_codes fbnic_fw_mbx_self_test(struct fbnic_dev *fbd)
{
	enum fbnic_fw_self_test_codes err;
	struct fbnic_fw_completion *cmpl;

	/* Skip test if FW interface is not present */
	if (!fbnic_fw_present(fbd))
		return FBNIC_TEST_FW_NO_FIRMWARE;

	cmpl = fbnic_fw_alloc_cmpl(FBNIC_TLV_MSG_ID_TEST);
	if (!cmpl)
		return FBNIC_TEST_FW_NO_CMPL;

	/* Load a test message onto the FW mailbox interface
	 * and arm the completion.
	 */
	err = fbnic_fw_xmit_test_msg(fbd, cmpl);
	if (err) {
		err = FBNIC_TEST_FW_NO_XMIT;
		goto exit_free;
	}

	/* Verify we received a message back */
	if (!fbnic_mbx_wait_for_cmpl(cmpl)) {
		err = FBNIC_TEST_FW_NO_MSG;
		goto exit_cleanup;
	}

	/* Verify there were no parsing errors */
	if (cmpl->result)
		err = FBNIC_TEST_FW_PARSE;
exit_cleanup:
	fbnic_mbx_clear_cmpl(fbd, cmpl);
exit_free:
	fbnic_fw_put_cmpl(cmpl);

	return err;
}

int fbnic_fw_xmit_rpc_macda_sync(struct fbnic_dev *fbd)
{
	struct fbnic_tlv_msg *mac_array;
Loading