Commit f026d8ca authored by Vitaly Lifshits's avatar Vitaly Lifshits Committed by Jeff Kirsher
Browse files

igc: add support to eeprom, registers and link self-tests



Introduced igc_diag.c and igc_diag.h, these files have the
diagnostics functionality of igc driver. For the time being
these files are being used by ethtool self-test callbacks.
Which mean that eeprom, registers and link self-tests for
ethtool were implemented.

Signed-off-by: default avatarVitaly Lifshits <vitaly.lifshits@intel.com>
Reported-by: default avatarkbuild test robot <lkp@intel.com>
Reported-by: default avatarDan Carpenter <dan.carpenter@oracle.com>
Tested-by: default avatarAaron Brown <aaron.f.brown@intel.com>
Signed-off-by: default avatarJeff Kirsher <jeffrey.t.kirsher@intel.com>
parent 25f06eff
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -8,4 +8,4 @@
obj-$(CONFIG_IGC) += igc.o

igc-objs := igc_main.o igc_mac.o igc_i225.o igc_base.o igc_nvm.o igc_phy.o \
igc_ethtool.o igc_ptp.o igc_dump.o igc_tsn.o
igc_diag.o igc_ethtool.o igc_ptp.o igc_dump.o igc_tsn.o
+4 −0
Original line number Diff line number Diff line
@@ -198,6 +198,8 @@ struct igc_adapter {
	unsigned long link_check_timeout;
	struct igc_info ei;

	u32 test_icr;

	struct ptp_clock *ptp_clock;
	struct ptp_clock_info ptp_caps;
	struct work_struct ptp_tx_work;
@@ -215,6 +217,8 @@ struct igc_adapter {

void igc_up(struct igc_adapter *adapter);
void igc_down(struct igc_adapter *adapter);
int igc_open(struct net_device *netdev);
int igc_close(struct net_device *netdev);
int igc_setup_tx_resources(struct igc_ring *ring);
int igc_setup_rx_resources(struct igc_ring *ring);
void igc_free_tx_resources(struct igc_ring *ring);
+186 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c)  2020 Intel Corporation */

#include "igc.h"
#include "igc_diag.h"

static struct igc_reg_test reg_test[] = {
	{ IGC_FCAL,	1,	PATTERN_TEST,	0xFFFFFFFF,	0xFFFFFFFF },
	{ IGC_FCAH,	1,	PATTERN_TEST,	0x0000FFFF,	0xFFFFFFFF },
	{ IGC_FCT,	1,	PATTERN_TEST,	0x0000FFFF,	0xFFFFFFFF },
	{ IGC_RDBAH(0), 4,	PATTERN_TEST,	0xFFFFFFFF,	0xFFFFFFFF },
	{ IGC_RDBAL(0),	4,	PATTERN_TEST,	0xFFFFFF80,	0xFFFFFF80 },
	{ IGC_RDLEN(0),	4,	PATTERN_TEST,	0x000FFF80,	0x000FFFFF },
	{ IGC_RDT(0),	4,	PATTERN_TEST,	0x0000FFFF,	0x0000FFFF },
	{ IGC_FCRTH,	1,	PATTERN_TEST,	0x0003FFF0,	0x0003FFF0 },
	{ IGC_FCTTV,	1,	PATTERN_TEST,	0x0000FFFF,	0x0000FFFF },
	{ IGC_TIPG,	1,	PATTERN_TEST,	0x3FFFFFFF,	0x3FFFFFFF },
	{ IGC_TDBAH(0),	4,	PATTERN_TEST,	0xFFFFFFFF,	0xFFFFFFFF },
	{ IGC_TDBAL(0),	4,	PATTERN_TEST,	0xFFFFFF80,	0xFFFFFF80 },
	{ IGC_TDLEN(0),	4,	PATTERN_TEST,	0x000FFF80,	0x000FFFFF },
	{ IGC_TDT(0),	4,	PATTERN_TEST,	0x0000FFFF,	0x0000FFFF },
	{ IGC_RCTL,	1,	SET_READ_TEST,	0xFFFFFFFF,	0x00000000 },
	{ IGC_RCTL,	1,	SET_READ_TEST,	0x04CFB2FE,	0x003FFFFB },
	{ IGC_RCTL,	1,	SET_READ_TEST,	0x04CFB2FE,	0xFFFFFFFF },
	{ IGC_TCTL,	1,	SET_READ_TEST,	0xFFFFFFFF,	0x00000000 },
	{ IGC_RA,	16,	TABLE64_TEST_LO,
						0xFFFFFFFF,	0xFFFFFFFF },
	{ IGC_RA,	16,	TABLE64_TEST_HI,
						0x900FFFFF,	0xFFFFFFFF },
	{ IGC_MTA,	128,	TABLE32_TEST,
						0xFFFFFFFF,	0xFFFFFFFF },
	{ 0, 0, 0, 0}
};

static bool reg_pattern_test(struct igc_adapter *adapter, u64 *data, int reg,
			     u32 mask, u32 write)
{
	struct igc_hw *hw = &adapter->hw;
	u32 pat, val, before;
	static const u32 test_pattern[] = {
		0x5A5A5A5A, 0xA5A5A5A5, 0x00000000, 0xFFFFFFFF
	};

	for (pat = 0; pat < ARRAY_SIZE(test_pattern); pat++) {
		before = rd32(reg);
		wr32(reg, test_pattern[pat] & write);
		val = rd32(reg);
		if (val != (test_pattern[pat] & write & mask)) {
			netdev_err(adapter->netdev,
				   "pattern test reg %04X failed: got 0x%08X expected 0x%08X",
				   reg, val, test_pattern[pat] & write & mask);
			*data = reg;
			wr32(reg, before);
			return false;
		}
		wr32(reg, before);
	}
	return true;
}

static bool reg_set_and_check(struct igc_adapter *adapter, u64 *data, int reg,
			      u32 mask, u32 write)
{
	struct igc_hw *hw = &adapter->hw;
	u32 val, before;

	before = rd32(reg);
	wr32(reg, write & mask);
	val = rd32(reg);
	if ((write & mask) != (val & mask)) {
		netdev_err(adapter->netdev,
			   "set/check reg %04X test failed: got 0x%08X expected 0x%08X",
			   reg, (val & mask), (write & mask));
		*data = reg;
		wr32(reg, before);
		return false;
	}
	wr32(reg, before);
	return true;
}

bool igc_reg_test(struct igc_adapter *adapter, u64 *data)
{
	struct igc_reg_test *test = reg_test;
	struct igc_hw *hw = &adapter->hw;
	u32 value, before, after;
	u32 i, toggle, b = false;

	/* Because the status register is such a special case,
	 * we handle it separately from the rest of the register
	 * tests.  Some bits are read-only, some toggle, and some
	 * are writeable.
	 */
	toggle = 0x6800D3;
	before = rd32(IGC_STATUS);
	value = before & toggle;
	wr32(IGC_STATUS, toggle);
	after = rd32(IGC_STATUS) & toggle;
	if (value != after) {
		netdev_err(adapter->netdev,
			   "failed STATUS register test got: 0x%08X expected: 0x%08X",
			   after, value);
		*data = 1;
		return false;
	}
	/* restore previous status */
	wr32(IGC_STATUS, before);

	/* Perform the remainder of the register test, looping through
	 * the test table until we either fail or reach the null entry.
	 */
	while (test->reg) {
		for (i = 0; i < test->array_len; i++) {
			switch (test->test_type) {
			case PATTERN_TEST:
				b = reg_pattern_test(adapter, data,
						     test->reg + (i * 0x40),
						     test->mask,
						     test->write);
				break;
			case SET_READ_TEST:
				b = reg_set_and_check(adapter, data,
						      test->reg + (i * 0x40),
						      test->mask,
						      test->write);
				break;
			case TABLE64_TEST_LO:
				b = reg_pattern_test(adapter, data,
						     test->reg + (i * 8),
						     test->mask,
						     test->write);
				break;
			case TABLE64_TEST_HI:
				b = reg_pattern_test(adapter, data,
						     test->reg + 4 + (i * 8),
						     test->mask,
						     test->write);
				break;
			case TABLE32_TEST:
				b = reg_pattern_test(adapter, data,
						     test->reg + (i * 4),
						     test->mask,
						     test->write);
				break;
			}
			if (!b)
				return false;
		}
		test++;
	}
	*data = 0;
	return true;
}

bool igc_eeprom_test(struct igc_adapter *adapter, u64 *data)
{
	struct igc_hw *hw = &adapter->hw;

	*data = 0;

	if (hw->nvm.ops.validate(hw) != IGC_SUCCESS) {
		*data = 1;
		return false;
	}

	return true;
}

bool igc_link_test(struct igc_adapter *adapter, u64 *data)
{
	bool link_up;

	*data = 0;

	/* add delay to give enough time for autonegotioation to finish */
	if (adapter->hw.mac.autoneg)
		ssleep(5);

	link_up = igc_has_link(adapter);
	if (!link_up) {
		*data = 1;
		return false;
	}

	return true;
}
+30 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c)  2020 Intel Corporation */

bool igc_reg_test(struct igc_adapter *adapter, u64 *data);
bool igc_eeprom_test(struct igc_adapter *adapter, u64 *data);
bool igc_link_test(struct igc_adapter *adapter, u64 *data);

struct igc_reg_test {
	u16 reg;
	u8 array_len;
	u8 test_type;
	u32 mask;
	u32 write;
};

/* In the hardware, registers are laid out either singly, in arrays
 * spaced 0x40 bytes apart, or in contiguous tables.  We assume
 * most tests take place on arrays or single registers (handled
 * as a single-element array) and special-case the tables.
 * Table tests are always pattern tests.
 *
 * We also make provision for some required setup steps by specifying
 * registers to be written without any read-back testing.
 */

#define PATTERN_TEST	1
#define SET_READ_TEST	2
#define TABLE32_TEST	3
#define TABLE64_TEST_LO	4
#define TABLE64_TEST_HI	5
+60 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include <linux/pm_runtime.h>

#include "igc.h"
#include "igc_diag.h"

/* forward declaration */
struct igc_stats {
@@ -1896,6 +1897,64 @@ static int igc_set_link_ksettings(struct net_device *netdev,
	return 0;
}

static void igc_diag_test(struct net_device *netdev,
			  struct ethtool_test *eth_test, u64 *data)
{
	struct igc_adapter *adapter = netdev_priv(netdev);
	bool if_running = netif_running(netdev);

	if (eth_test->flags == ETH_TEST_FL_OFFLINE) {
		netdev_info(adapter->netdev, "offline testing starting");
		set_bit(__IGC_TESTING, &adapter->state);

		/* Link test performed before hardware reset so autoneg doesn't
		 * interfere with test result
		 */
		if (!igc_link_test(adapter, &data[TEST_LINK]))
			eth_test->flags |= ETH_TEST_FL_FAILED;

		if (if_running)
			igc_close(netdev);
		else
			igc_reset(adapter);

		netdev_info(adapter->netdev, "register testing starting");
		if (!igc_reg_test(adapter, &data[TEST_REG]))
			eth_test->flags |= ETH_TEST_FL_FAILED;

		igc_reset(adapter);

		netdev_info(adapter->netdev, "eeprom testing starting");
		if (!igc_eeprom_test(adapter, &data[TEST_EEP]))
			eth_test->flags |= ETH_TEST_FL_FAILED;

		igc_reset(adapter);

		/* loopback and interrupt tests
		 * will be implemented in the future
		 */
		data[TEST_LOOP] = 0;
		data[TEST_IRQ] = 0;

		clear_bit(__IGC_TESTING, &adapter->state);
		if (if_running)
			igc_open(netdev);
	} else {
		netdev_info(adapter->netdev, "online testing starting");

		/* register, eeprom, intr and loopback tests not run online */
		data[TEST_REG] = 0;
		data[TEST_EEP] = 0;
		data[TEST_IRQ] = 0;
		data[TEST_LOOP] = 0;

		if (!igc_link_test(adapter, &data[TEST_LINK]))
			eth_test->flags |= ETH_TEST_FL_FAILED;
	}

	msleep_interruptible(4 * 1000);
}

static const struct ethtool_ops igc_ethtool_ops = {
	.supported_coalesce_params = ETHTOOL_COALESCE_USECS,
	.get_drvinfo		= igc_get_drvinfo,
@@ -1933,6 +1992,7 @@ static const struct ethtool_ops igc_ethtool_ops = {
	.complete		= igc_ethtool_complete,
	.get_link_ksettings	= igc_get_link_ksettings,
	.set_link_ksettings	= igc_set_link_ksettings,
	.self_test		= igc_diag_test,
};

void igc_set_ethtool_ops(struct net_device *netdev)
Loading