[1/2] net/e1000: add support for NIC loopback
diff mbox series

Message ID 20200122050940.4695-1-d.c.ddcc@gmail.com
State New
Delegated to: xiaolong ye
Headers show
Series
  • [1/2] net/e1000: add support for NIC loopback
Related show

Checks

Context Check Description
ci/Intel-compilation success Compilation OK
ci/iol-mellanox-Performance success Performance Testing PASS
ci/iol-nxp-Performance success Performance Testing PASS
ci/iol-testing success Testing PASS
ci/iol-intel-Performance fail Performance Testing issues
ci/checkpatch success coding style OK

Commit Message

Dominic Chen Jan. 22, 2020, 5:09 a.m. UTC
Check the lpbk_mode field of struct rte_eth_conf to determine if
loopback should be enabled. Works with 82540EM and 82574L in
QEMU 4.0.0.

Signed-off-by: Dominic Chen <d.c.ddcc@gmail.com>
---
 drivers/net/e1000/base/e1000_api.c     |  42 +++++++
 drivers/net/e1000/base/e1000_api.h     |   1 +
 drivers/net/e1000/base/e1000_hw.h      |   1 +
 drivers/net/e1000/base/e1000_ich8lan.h |   4 +
 drivers/net/e1000/base/e1000_phy.c     | 158 +++++++++++++++++++++++++
 drivers/net/e1000/base/e1000_phy.h     |   3 +
 drivers/net/e1000/em_ethdev.c          |   5 +
 drivers/net/e1000/em_rxtx.c            |   9 +-
 drivers/net/e1000/igb_ethdev.c         |   5 +
 drivers/net/e1000/igb_rxtx.c           |   9 +-
 10 files changed, 233 insertions(+), 4 deletions(-)

Patch
diff mbox series

diff --git a/drivers/net/e1000/base/e1000_api.c b/drivers/net/e1000/base/e1000_api.c
index 718952801..f7f883889 100644
--- a/drivers/net/e1000/base/e1000_api.c
+++ b/drivers/net/e1000/base/e1000_api.c
@@ -671,6 +671,48 @@  s32 e1000_get_speed_and_duplex(struct e1000_hw *hw, u16 *speed, u16 *duplex)
 	return -E1000_ERR_CONFIG;
 }
 
+/**
+ *  e1000_set_loopback - Set adapter in loopback mode
+ *  @hw: pointer to the HW structure
+ *
+ *  This sets the MAC and/or PHY into loopback mode.
+ **/
+s32 e1000_set_loopback(struct e1000_hw *hw)
+{
+	s32 ret_val = 0;
+
+	if (hw->phy.media_type == e1000_media_type_fiber ||
+	    hw->phy.media_type == e1000_media_type_internal_serdes) {
+		switch (hw->mac.type) {
+		case e1000_80003es2lan:
+		case e1000_82571:
+		case e1000_82572:
+			/* not implemented */
+			return -E1000_ERR_PHY_TYPE;
+
+		case e1000_82545:
+		case e1000_82546:
+		case e1000_82545_rev_3:
+		case e1000_82546_rev_3:
+			ret_val = e1000_set_phy_loopback(hw);
+			if (ret_val)
+				return ret_val;
+		break;
+
+		default:
+			/* use transceiver loopback */
+			break;
+		}
+	} else if (hw->phy.media_type == e1000_media_type_copper) {
+		ret_val = e1000_set_phy_loopback(hw);
+		if (ret_val)
+			return ret_val;
+	}
+
+	hw->mac.loopback = true;
+	return E1000_SUCCESS;
+}
+
 /**
  *  e1000_setup_led - Configures SW controllable LED
  *  @hw: pointer to the HW structure
diff --git a/drivers/net/e1000/base/e1000_api.h b/drivers/net/e1000/base/e1000_api.h
index 3054d5b9d..a1b0653d3 100644
--- a/drivers/net/e1000/base/e1000_api.h
+++ b/drivers/net/e1000/base/e1000_api.h
@@ -37,6 +37,7 @@  s32 e1000_reset_hw(struct e1000_hw *hw);
 s32 e1000_init_hw(struct e1000_hw *hw);
 s32 e1000_setup_link(struct e1000_hw *hw);
 s32 e1000_get_speed_and_duplex(struct e1000_hw *hw, u16 *speed, u16 *duplex);
+s32 e1000_set_loopback(struct e1000_hw *hw);
 s32 e1000_disable_pcie_master(struct e1000_hw *hw);
 void e1000_config_collision_dist(struct e1000_hw *hw);
 int e1000_rar_set(struct e1000_hw *hw, u8 *addr, u32 index);
diff --git a/drivers/net/e1000/base/e1000_hw.h b/drivers/net/e1000/base/e1000_hw.h
index 9793b724e..820b5cf88 100644
--- a/drivers/net/e1000/base/e1000_hw.h
+++ b/drivers/net/e1000/base/e1000_hw.h
@@ -789,6 +789,7 @@  struct e1000_mac_info {
 	enum e1000_serdes_link_state serdes_link_state;
 	bool serdes_has_link;
 	bool tx_pkt_filtering;
+	bool loopback;
 };
 
 struct e1000_phy_info {
diff --git a/drivers/net/e1000/base/e1000_ich8lan.h b/drivers/net/e1000/base/e1000_ich8lan.h
index 699a92c4b..d3f955c22 100644
--- a/drivers/net/e1000/base/e1000_ich8lan.h
+++ b/drivers/net/e1000/base/e1000_ich8lan.h
@@ -144,6 +144,7 @@ 
 #define HV_LED_CONFIG		PHY_REG(768, 30) /* LED Configuration */
 #define HV_MUX_DATA_CTRL	PHY_REG(776, 16)
 #define HV_MUX_DATA_CTRL_GEN_TO_MAC	0x0400
+#define HV_MUX_DATA_CTRL_SET_LINK_UP	0x0040 /* Set Link Up */
 #define HV_MUX_DATA_CTRL_FORCE_SPEED	0x0004
 #define HV_STATS_PAGE	778
 /* Half-duplex collision counts */
@@ -213,6 +214,8 @@ 
 
 /* KMRN Mode Control */
 #define HV_KMRN_MODE_CTRL	PHY_REG(769, 16)
+#define HV_KMRN_FORCE_FD	0x000C /* Force Full-Duplex */
+#define HV_KMRN_FORCE_LINK	0x0040 /* Force Link */
 #define HV_KMRN_MDIO_SLOW	0x0400
 
 /* KMRN FIFO Control and Status */
@@ -248,6 +251,7 @@ 
 /* 82579 DFT Control */
 #define I82579_DFT_CTRL			PHY_REG(769, 20)
 #define I82579_DFT_CTRL_GATE_PHY_RESET	0x0040 /* Gate PHY Reset on MAC Reset */
+#define I82579_DFT_CTRL_EARLY_LINK_ENABLE	0x0400 /* Early Link Enable */
 
 /* Extended Management Interface (EMI) Registers */
 #define I82579_EMI_ADDR		0x10
diff --git a/drivers/net/e1000/base/e1000_phy.c b/drivers/net/e1000/base/e1000_phy.c
index 956c06747..0ec4bef5d 100644
--- a/drivers/net/e1000/base/e1000_phy.c
+++ b/drivers/net/e1000/base/e1000_phy.c
@@ -2784,6 +2784,162 @@  s32 e1000_get_phy_info_ife(struct e1000_hw *hw)
 	return E1000_SUCCESS;
 }
 
+/**
+ *  e1000_set_phy_loopback - set M88 PHY in loopback mode
+ *  @hw: pointer to the HW structure
+ **/
+s32 e1000_set_phy_loopback_m88(struct e1000_hw *hw)
+{
+	s32 ret_val;
+
+	if (hw->phy.media_type == e1000_media_type_copper) {
+		uint32_t ctrl = E1000_READ_REG(hw, E1000_CTRL);
+		E1000_WRITE_REG(hw, E1000_CTRL, ctrl | E1000_CTRL_ILOS);
+	}
+
+	/* Disable the transceiver */
+	ret_val = hw->phy.ops.write_reg(hw, M88E1000_PHY_PAGE_SELECT, 0x001F);
+	if (ret_val)
+		return ret_val;
+	ret_val = hw->phy.ops.write_reg(hw, M88E1000_PHY_GEN_CONTROL, 0x8FFC);
+	if (ret_val)
+		return ret_val;
+	ret_val = hw->phy.ops.write_reg(hw, M88E1000_PHY_PAGE_SELECT, 0x001A);
+	if (ret_val)
+		return ret_val;
+	ret_val = hw->phy.ops.write_reg(hw, M88E1000_PHY_GEN_CONTROL, 0x8FF0);
+	if (ret_val)
+		return ret_val;
+
+	return E1000_SUCCESS;
+}
+
+/**
+ *  e1000_set_phy_loopback - set BM PHY in loopback mode
+ *  @hw: pointer to the HW structure
+ **/
+s32 e1000_set_phy_loopback_bm(struct e1000_hw *hw)
+{
+	u16 data;
+	s32 ret_val;
+
+	/* Force full duplex */
+	ret_val = hw->phy.ops.read_reg(hw, HV_KMRN_MODE_CTRL, &data);
+	if (ret_val)
+		return ret_val;
+	data |= HV_KMRN_FORCE_FD;
+	ret_val = hw->phy.ops.write_reg(hw, HV_KMRN_MODE_CTRL, data);
+	if (ret_val)
+		return ret_val;
+
+	/* Set link up */
+	ret_val = hw->phy.ops.read_reg(hw, HV_MUX_DATA_CTRL, &data);
+	if (ret_val)
+		return ret_val;
+	data |= HV_MUX_DATA_CTRL_SET_LINK_UP;
+	ret_val = hw->phy.ops.write_reg(hw, HV_MUX_DATA_CTRL, data);
+	if (ret_val)
+		return ret_val;
+
+	/* Force link */
+	ret_val = hw->phy.ops.read_reg(hw, HV_KMRN_MODE_CTRL, &data);
+	if (ret_val)
+		return ret_val;
+	data |= HV_KMRN_FORCE_LINK;
+	ret_val = hw->phy.ops.write_reg(hw, HV_KMRN_MODE_CTRL, data);
+	if (ret_val)
+		return ret_val;
+
+	/* Set early link enable */
+	ret_val = hw->phy.ops.read_reg(hw, I82579_DFT_CTRL, &data);
+	if (ret_val)
+		return ret_val;
+	data |= I82579_DFT_CTRL_EARLY_LINK_ENABLE;
+	ret_val = hw->phy.ops.write_reg(hw, I82579_DFT_CTRL, data);
+	if (ret_val)
+		return ret_val;
+
+	return E1000_SUCCESS;
+}
+
+/**
+ *  e1000_set_phy_loopback - set PHY in loopback mode
+ *  @hw: pointer to the HW structure
+ **/
+s32 e1000_set_phy_loopback(struct e1000_hw *hw)
+{
+	struct e1000_mac_info *mac = &hw->mac;
+	struct e1000_phy_info *phy = &hw->phy;
+	s32 ret_val;
+	u16 phy_ctrl;
+
+	DEBUGFUNC("e1000_set_phy_loopback");
+
+	if (phy->type != e1000_phy_none && phy->type != e1000_phy_ife &&
+		phy->type != e1000_phy_m88 && phy->type != e1000_phy_bm) {
+		/* not implemented */
+		return -E1000_ERR_PHY_TYPE;
+	}
+
+	/* must be forced 1000 full-duplex */
+	if (mac->autoneg || mac->forced_speed_duplex != ADVERTISE_1000_FULL ||
+		phy->autoneg_advertised != ADVERTISE_1000_FULL)
+		return -E1000_ERR_CONFIG;
+
+	/* set the PHY basic mode control register */
+	ret_val = phy->ops.read_reg(hw, PHY_CONTROL, &phy_ctrl);
+	if (ret_val)
+		return ret_val;
+
+	phy_ctrl |= MII_CR_LOOPBACK;
+	ret_val = phy->ops.write_reg(hw, PHY_CONTROL, phy_ctrl);
+	if (ret_val)
+		return ret_val;
+
+	/* put the PHY in loopback */
+	switch (mac->type) {
+	case e1000_82543:
+		if (phy->media_type == e1000_media_type_copper)
+			ret_val = e1000_set_phy_loopback_m88(hw);
+		break;
+
+	case e1000_82540:
+	case e1000_82541:
+	case e1000_82541_rev_2:
+	case e1000_82544:
+	case e1000_82545:
+	case e1000_82545_rev_3:
+	case e1000_82546:
+	case e1000_82546_rev_3:
+	case e1000_82547:
+	case e1000_82547_rev_2:
+		/* certain M88 registers are not implemented in QEMU. for now,
+		 * try to set them but don't check the return value.
+		 */
+		e1000_set_phy_loopback_m88(hw);
+		break;
+
+	case e1000_ich8lan:
+	case e1000_82574:
+	case e1000_82583:
+		/* datasheet doesn't mention more than 7 pages and they aren't
+		 * implemented in QEMU, but they appear to be used. for now, try
+		 * to set them but don't check the return value.
+		 */
+		e1000_set_phy_loopback_bm(hw);
+		break;
+
+	case e1000_pch_spt:
+		ret_val = e1000_set_phy_loopback_bm(hw);
+		break;
+
+	default:
+		break;
+	}
+
+	return ret_val;
+}
+
 /**
  *  e1000_phy_sw_reset_generic - PHY software reset
  *  @hw: pointer to the HW structure
@@ -2805,6 +2961,8 @@  s32 e1000_phy_sw_reset_generic(struct e1000_hw *hw)
 	if (ret_val)
 		return ret_val;
 
+	if (!hw->mac.loopback)
+		phy_ctrl &= ~MII_CR_LOOPBACK;
 	phy_ctrl |= MII_CR_RESET;
 	ret_val = hw->phy.ops.write_reg(hw, PHY_CONTROL, phy_ctrl);
 	if (ret_val)
diff --git a/drivers/net/e1000/base/e1000_phy.h b/drivers/net/e1000/base/e1000_phy.h
index 2c71e64c8..967e1e387 100644
--- a/drivers/net/e1000/base/e1000_phy.h
+++ b/drivers/net/e1000/base/e1000_phy.h
@@ -36,6 +36,9 @@  s32  e1000_get_phy_id(struct e1000_hw *hw);
 s32  e1000_get_phy_info_igp(struct e1000_hw *hw);
 s32  e1000_get_phy_info_m88(struct e1000_hw *hw);
 s32  e1000_get_phy_info_ife(struct e1000_hw *hw);
+s32  e1000_set_phy_loopback_m88(struct e1000_hw *hw);
+s32  e1000_set_phy_loopback_bm(struct e1000_hw *hw);
+s32  e1000_set_phy_loopback(struct e1000_hw *hw);
 s32  e1000_phy_sw_reset_generic(struct e1000_hw *hw);
 void e1000_phy_force_speed_duplex_setup(struct e1000_hw *hw, u16 *phy_ctrl);
 s32  e1000_phy_hw_reset_generic(struct e1000_hw *hw);
diff --git a/drivers/net/e1000/em_ethdev.c b/drivers/net/e1000/em_ethdev.c
index 080cbe2df..c1ffa862c 100644
--- a/drivers/net/e1000/em_ethdev.c
+++ b/drivers/net/e1000/em_ethdev.c
@@ -666,6 +666,11 @@  eth_em_start(struct rte_eth_dev *dev)
 		}
 	}
 
+	/* Set up loopback */
+	if (dev->data->dev_conf.lpbk_mode)
+		if (e1000_set_loopback(hw))
+			goto error_invalid_config;
+
 	e1000_setup_link(hw);
 
 	if (rte_intr_allow_others(intr_handle)) {
diff --git a/drivers/net/e1000/em_rxtx.c b/drivers/net/e1000/em_rxtx.c
index 49c53712a..0bfe6b1e3 100644
--- a/drivers/net/e1000/em_rxtx.c
+++ b/drivers/net/e1000/em_rxtx.c
@@ -1892,10 +1892,15 @@  eth_em_rx_init(struct rte_eth_dev *dev)
 		rctl |= E1000_RCTL_SECRC; /* Strip Ethernet CRC. */
 
 	rctl &= ~(3 << E1000_RCTL_MO_SHIFT);
-	rctl |= E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_LBM_NO |
-		E1000_RCTL_RDMTS_HALF |
+	rctl |= E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_RDMTS_HALF |
 		(hw->mac.mc_filter_type << E1000_RCTL_MO_SHIFT);
 
+	/* Set up transceiver loopback */
+	if (hw->mac.loopback)
+		rctl |= E1000_RCTL_LBM_TCVR;
+	else
+		rctl |= E1000_RCTL_LBM_NO;
+
 	/* Make sure VLAN Filters are off. */
 	rctl &= ~E1000_RCTL_VFE;
 	/* Don't store bad packets. */
diff --git a/drivers/net/e1000/igb_ethdev.c b/drivers/net/e1000/igb_ethdev.c
index 647d5504f..c738fb127 100644
--- a/drivers/net/e1000/igb_ethdev.c
+++ b/drivers/net/e1000/igb_ethdev.c
@@ -1400,6 +1400,11 @@  eth_igb_start(struct rte_eth_dev *dev)
 		}
 	}
 
+	/* Set up loopback */
+	if (dev->data->dev_conf.lpbk_mode)
+		if (e1000_set_loopback(hw))
+			goto error_invalid_config;
+
 	e1000_setup_link(hw);
 
 	if (rte_intr_allow_others(intr_handle)) {
diff --git a/drivers/net/e1000/igb_rxtx.c b/drivers/net/e1000/igb_rxtx.c
index 684fa4ad8..5f206fa02 100644
--- a/drivers/net/e1000/igb_rxtx.c
+++ b/drivers/net/e1000/igb_rxtx.c
@@ -2548,10 +2548,15 @@  eth_igb_rx_init(struct rte_eth_dev *dev)
 	}
 
 	rctl &= ~(3 << E1000_RCTL_MO_SHIFT);
-	rctl |= E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_LBM_NO |
-		E1000_RCTL_RDMTS_HALF |
+	rctl |= E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_RDMTS_HALF |
 		(hw->mac.mc_filter_type << E1000_RCTL_MO_SHIFT);
 
+	/* Set up transceiver loopback */
+	if (hw->mac.loopback)
+		rctl |= E1000_RCTL_LBM_TCVR;
+	else
+		rctl |= E1000_RCTL_LBM_NO;
+
 	/* Make sure VLAN Filters are off. */
 	if (dev->data->dev_conf.rxmode.mq_mode != ETH_MQ_RX_VMDQ_ONLY)
 		rctl &= ~E1000_RCTL_VFE;