mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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: Vitaly Lifshits <vitaly.lifshits@intel.com> Reported-by: kbuild test robot <lkp@intel.com> Reported-by: Dan Carpenter <dan.carpenter@oracle.com> Tested-by: Aaron Brown <aaron.f.brown@intel.com> Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
This commit is contained in:
		
							parent
							
								
									25f06eff75
								
							
						
					
					
						commit
						f026d8ca29
					
				
					 7 changed files with 285 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
									
								
								drivers/net/ethernet/intel/igc/igc_diag.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								drivers/net/ethernet/intel/igc/igc_diag.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,186 @@
 | 
			
		|||
// 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
									
								
								drivers/net/ethernet/intel/igc/igc_diag.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								drivers/net/ethernet/intel/igc/igc_diag.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
/* 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
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4380,7 +4380,7 @@ static int __igc_open(struct net_device *netdev, bool resuming)
 | 
			
		|||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int igc_open(struct net_device *netdev)
 | 
			
		||||
int igc_open(struct net_device *netdev)
 | 
			
		||||
{
 | 
			
		||||
	return __igc_open(netdev, false);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4422,7 +4422,7 @@ static int __igc_close(struct net_device *netdev, bool suspending)
 | 
			
		|||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int igc_close(struct net_device *netdev)
 | 
			
		||||
int igc_close(struct net_device *netdev)
 | 
			
		||||
{
 | 
			
		||||
	if (netif_device_present(netdev) || netdev->dismantle)
 | 
			
		||||
		return __igc_close(netdev, false);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,7 @@
 | 
			
		|||
#define IGC_FACTPS		0x05B30
 | 
			
		||||
 | 
			
		||||
/* Interrupt Register Description */
 | 
			
		||||
#define IGC_EICR		0x01580  /* Ext. Interrupt Cause read - W0 */
 | 
			
		||||
#define IGC_EICS		0x01520  /* Ext. Interrupt Cause Set - W0 */
 | 
			
		||||
#define IGC_EIMS		0x01524  /* Ext. Interrupt Mask Set/Read - RW */
 | 
			
		||||
#define IGC_EIMC		0x01528  /* Ext. Interrupt Mask Clear - WO */
 | 
			
		||||
| 
						 | 
				
			
			@ -119,6 +120,7 @@
 | 
			
		|||
#define IGC_RLPML		0x05004  /* Rx Long Packet Max Length */
 | 
			
		||||
#define IGC_RFCTL		0x05008  /* Receive Filter Control*/
 | 
			
		||||
#define IGC_MTA			0x05200  /* Multicast Table Array - RW Array */
 | 
			
		||||
#define IGC_RA			0x05400  /* Receive Address - RW Array */
 | 
			
		||||
#define IGC_UTA			0x0A000  /* Unicast Table Array - RW */
 | 
			
		||||
#define IGC_RAL(_n)		(0x05400 + ((_n) * 0x08))
 | 
			
		||||
#define IGC_RAH(_n)		(0x05404 + ((_n) * 0x08))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue