Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 1/5] net: phy: mediatek: export __mtk_tr_write
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek
In-Reply-To: <20260708102341.53919-1-ansuelsmth@gmail.com>

Export __mtk_tr_write to directly write to debug register.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/phy/mediatek/mtk-phy-lib.c | 5 +++--
 drivers/net/phy/mediatek/mtk.h         | 2 ++
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c
index dfd0f4e439a2..78b998af5238 100644
--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
+++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
@@ -36,8 +36,8 @@ static void __mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		*tr_high, *tr_low);
 }
 
-static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
-			   u8 data_addr, u32 tr_data)
+void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+		    u8 data_addr, u32 tr_data)
 {
 	__phy_write(phydev, 0x11, tr_data & 0xffff);
 	__phy_write(phydev, 0x12, tr_data >> 16);
@@ -45,6 +45,7 @@ static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		tr_data >> 16, tr_data & 0xffff);
 	__mtk_tr_access(phydev, false, ch_addr, node_addr, data_addr);
 }
+EXPORT_SYMBOL_GPL(__mtk_tr_write);
 
 void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		     u8 data_addr, u32 mask, u32 set)
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
index 320f76ffa81f..f172f7d671c9 100644
--- a/drivers/net/phy/mediatek/mtk.h
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -72,6 +72,8 @@ struct mtk_socphy_priv {
 	unsigned long		led_state;
 };
 
+void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+		    u8 data_addr, u32 tr_data);
 void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
 		     u8 data_addr, u32 mask, u32 set);
 void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next 2/5] net: phy: mediatek: move MTK GE SoC registers define to dedicated header
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek
In-Reply-To: <20260708102341.53919-1-ansuelsmth@gmail.com>

In preparation for support of special Software Calibration for Airoha
PHY, move the MTK GE SoC registers define to a dedicated header.

It's also needed to generalize the cal_cycle function as Airoha needs
only part of its logic (the wait logic) to complete a calibration cycle.

This is to keep cleaner code and permit sharing these define in multiple
source code.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 MAINTAINERS                           |   1 +
 drivers/net/phy/mediatek/mtk-ge-soc.c | 371 ++------------------------
 drivers/net/phy/mediatek/mtk-ge-soc.h | 351 ++++++++++++++++++++++++
 3 files changed, 373 insertions(+), 350 deletions(-)
 create mode 100644 drivers/net/phy/mediatek/mtk-ge-soc.h

diff --git a/MAINTAINERS b/MAINTAINERS
index c3f72058a2f2..16e47aea84c3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16512,6 +16512,7 @@ L:	netdev@vger.kernel.org
 S:	Maintained
 F:	drivers/net/phy/mediatek/mtk-2p5ge.c
 F:	drivers/net/phy/mediatek/mtk-ge-soc.c
+F:	drivers/net/phy/mediatek/mtk-ge-soc.h
 F:	drivers/net/phy/mediatek/mtk-phy-lib.c
 F:	drivers/net/phy/mediatek/mtk-ge.c
 F:	drivers/net/phy/mediatek/mtk.h
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c
index 9a54949644d5..46bae11ad740 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
@@ -11,322 +11,10 @@
 
 #include "../phylib.h"
 #include "mtk.h"
+#include "mtk-ge-soc.h"
 
 #define MTK_PHY_MAX_LEDS			2
 
-#define MTK_GPHY_ID_MT7981			0x03a29461
-#define MTK_GPHY_ID_MT7988			0x03a29481
-#define MTK_GPHY_ID_AN7581			0x03a294c1
-#define MTK_GPHY_ID_AN7583			0xc0ff0420
-
-#define MTK_EXT_PAGE_ACCESS			0x1f
-#define MTK_PHY_PAGE_STANDARD			0x0000
-#define MTK_PHY_PAGE_EXTENDED_3			0x0003
-
-#define MTK_PHY_LPI_REG_14			0x14
-#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK	GENMASK(8, 0)
-
-#define MTK_PHY_LPI_REG_1c			0x1c
-#define MTK_PHY_SMI_DET_ON_THRESH_MASK		GENMASK(13, 8)
-
-#define MTK_PHY_PAGE_EXTENDED_2A30		0x2a30
-
-/* Registers on Token Ring debug nodes */
-/* ch_addr = 0x0, node_addr = 0x7, data_addr = 0x15 */
-/* NormMseLoThresh */
-#define NORMAL_MSE_LO_THRESH_MASK		GENMASK(15, 8)
-
-/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
-/* RemAckCntLimitCtrl */
-#define REMOTE_ACK_COUNT_LIMIT_CTRL_MASK	GENMASK(2, 1)
-
-/* ch_addr = 0x1, node_addr = 0xd, data_addr = 0x20 */
-/* VcoSlicerThreshBitsHigh */
-#define VCO_SLICER_THRESH_HIGH_MASK		GENMASK(23, 0)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x0 */
-/* DfeTailEnableVgaThresh1000 */
-#define DFE_TAIL_EANBLE_VGA_TRHESH_1000		GENMASK(5, 1)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x1 */
-/* MrvlTrFix100Kp */
-#define MRVL_TR_FIX_100KP_MASK			GENMASK(22, 20)
-/* MrvlTrFix100Kf */
-#define MRVL_TR_FIX_100KF_MASK			GENMASK(19, 17)
-/* MrvlTrFix1000Kp */
-#define MRVL_TR_FIX_1000KP_MASK			GENMASK(16, 14)
-/* MrvlTrFix1000Kf */
-#define MRVL_TR_FIX_1000KF_MASK			GENMASK(13, 11)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x12 */
-/* VgaDecRate */
-#define VGA_DECIMATION_RATE_MASK		GENMASK(8, 5)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */
-/* SlvDSPreadyTime */
-#define SLAVE_DSP_READY_TIME_MASK		GENMASK(22, 15)
-/* MasDSPreadyTime */
-#define MASTER_DSP_READY_TIME_MASK		GENMASK(14, 7)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x18 */
-/* EnabRandUpdTrig */
-#define ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER	BIT(8)
-
-/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */
-/* ResetSyncOffset */
-#define RESET_SYNC_OFFSET_MASK			GENMASK(11, 8)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x0 */
-/* FfeUpdGainForceVal */
-#define FFE_UPDATE_GAIN_FORCE_VAL_MASK		GENMASK(9, 7)
-/* FfeUpdGainForce */
-#define FFE_UPDATE_GAIN_FORCE			BIT(6)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x3 */
-/* TrFreeze */
-#define TR_FREEZE_MASK				GENMASK(11, 0)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */
-/* SS: Steady-state, KP: Proportional Gain */
-/* SSTrKp100 */
-#define SS_TR_KP100_MASK			GENMASK(21, 19)
-/* SSTrKf100 */
-#define SS_TR_KF100_MASK			GENMASK(18, 16)
-/* SSTrKp1000Mas */
-#define SS_TR_KP1000_MASTER_MASK		GENMASK(15, 13)
-/* SSTrKf1000Mas */
-#define SS_TR_KF1000_MASTER_MASK		GENMASK(12, 10)
-/* SSTrKp1000Slv */
-#define SS_TR_KP1000_SLAVE_MASK			GENMASK(9, 7)
-/* SSTrKf1000Slv */
-#define SS_TR_KF1000_SLAVE_MASK			GENMASK(6, 4)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x8 */
-/* clear this bit if wanna select from AFE */
-/* Regsigdet_sel_1000 */
-#define EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE	BIT(4)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */
-/* RegEEE_st2TrKf1000 */
-#define EEE1000_STAGE2_TR_KF_MASK		GENMASK(13, 11)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xf */
-/* RegEEE_slv_waketr_timer_tar */
-#define SLAVE_WAKETR_TIMER_MASK			GENMASK(20, 11)
-/* RegEEE_slv_remtx_timer_tar */
-#define SLAVE_REMTX_TIMER_MASK			GENMASK(10, 1)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x10 */
-/* RegEEE_slv_wake_int_timer_tar */
-#define SLAVE_WAKEINT_TIMER_MASK		GENMASK(10, 1)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x14 */
-/* RegEEE_trfreeze_timer2 */
-#define TR_FREEZE_TIMER2_MASK			GENMASK(9, 0)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x1c */
-/* RegEEE100Stg1_tar */
-#define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK	GENMASK(8, 0)
-
-/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x25 */
-/* REGEEE_wake_slv_tr_wait_dfesigdet_en */
-#define WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN	BIT(11)
-
-#define ANALOG_INTERNAL_OPERATION_MAX_US	20
-#define TXRESERVE_MIN				0
-#define TXRESERVE_MAX				7
-
-#define MTK_PHY_ANARG_RG			0x10
-#define   MTK_PHY_TCLKOFFSET_MASK		GENMASK(12, 8)
-
-/* Registers on MDIO_MMD_VEND1 */
-#define MTK_PHY_TXVLD_DA_RG			0x12
-#define   MTK_PHY_DA_TX_I2MPB_A_GBE_MASK	GENMASK(15, 10)
-#define   MTK_PHY_DA_TX_I2MPB_A_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_A2		0x16
-#define   MTK_PHY_DA_TX_I2MPB_A_HBT_MASK	GENMASK(15, 10)
-#define   MTK_PHY_DA_TX_I2MPB_A_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_B1		0x17
-#define   MTK_PHY_DA_TX_I2MPB_B_GBE_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_B_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_B2		0x18
-#define   MTK_PHY_DA_TX_I2MPB_B_HBT_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_B_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_C1		0x19
-#define   MTK_PHY_DA_TX_I2MPB_C_GBE_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_C_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_C2		0x20
-#define   MTK_PHY_DA_TX_I2MPB_C_HBT_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_C_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_D1		0x21
-#define   MTK_PHY_DA_TX_I2MPB_D_GBE_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_D_TBT_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_TX_I2MPB_TEST_MODE_D2		0x22
-#define   MTK_PHY_DA_TX_I2MPB_D_HBT_MASK	GENMASK(13, 8)
-#define   MTK_PHY_DA_TX_I2MPB_D_TST_MASK	GENMASK(5, 0)
-
-#define MTK_PHY_RXADC_CTRL_RG7			0xc6
-#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK	GENMASK(9, 8)
-
-#define MTK_PHY_RXADC_CTRL_RG9			0xc8
-#define   MTK_PHY_DA_RX_PSBN_TBT_MASK		GENMASK(14, 12)
-#define   MTK_PHY_DA_RX_PSBN_HBT_MASK		GENMASK(10, 8)
-#define   MTK_PHY_DA_RX_PSBN_GBE_MASK		GENMASK(6, 4)
-#define   MTK_PHY_DA_RX_PSBN_LP_MASK		GENMASK(2, 0)
-
-#define MTK_PHY_LDO_OUTPUT_V			0xd7
-
-#define MTK_PHY_RG_ANA_CAL_RG0			0xdb
-#define   MTK_PHY_RG_CAL_CKINV			BIT(12)
-#define   MTK_PHY_RG_ANA_CALEN			BIT(8)
-#define   MTK_PHY_RG_ZCALEN_A			BIT(0)
-
-#define MTK_PHY_RG_ANA_CAL_RG1			0xdc
-#define   MTK_PHY_RG_ZCALEN_B			BIT(12)
-#define   MTK_PHY_RG_ZCALEN_C			BIT(8)
-#define   MTK_PHY_RG_ZCALEN_D			BIT(4)
-#define   MTK_PHY_RG_TXVOS_CALEN		BIT(0)
-
-#define MTK_PHY_RG_ANA_CAL_RG5			0xe0
-#define   MTK_PHY_RG_REXT_TRIM_MASK		GENMASK(13, 8)
-
-#define MTK_PHY_RG_TX_FILTER			0xfe
-
-#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120	0x120
-#define   MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK	GENMASK(12, 8)
-#define   MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK	GENMASK(4, 0)
-
-#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122	0x122
-#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK	GENMASK(7, 0)
-
-#define MTK_PHY_RG_TESTMUX_ADC_CTRL		0x144
-#define   MTK_PHY_RG_TXEN_DIG_MASK		GENMASK(5, 5)
-
-#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B		0x172
-#define   MTK_PHY_CR_TX_AMP_OFFSET_A_MASK	GENMASK(13, 8)
-#define   MTK_PHY_CR_TX_AMP_OFFSET_B_MASK	GENMASK(6, 0)
-
-#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D		0x173
-#define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK	GENMASK(13, 8)
-#define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK	GENMASK(6, 0)
-
-#define MTK_PHY_RG_AD_CAL_COMP			0x17a
-#define   MTK_PHY_AD_CAL_COMP_OUT_MASK		GENMASK(8, 8)
-
-#define MTK_PHY_RG_AD_CAL_CLK			0x17b
-#define   MTK_PHY_DA_CAL_CLK			BIT(0)
-
-#define MTK_PHY_RG_AD_CALIN			0x17c
-#define   MTK_PHY_DA_CALIN_FLAG			BIT(0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_A		0x17d
-#define   MTK_PHY_DASN_DAC_IN0_A_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_B		0x17e
-#define   MTK_PHY_DASN_DAC_IN0_B_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_C		0x17f
-#define   MTK_PHY_DASN_DAC_IN0_C_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN0_D		0x180
-#define   MTK_PHY_DASN_DAC_IN0_D_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_A		0x181
-#define   MTK_PHY_DASN_DAC_IN1_A_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_B		0x182
-#define   MTK_PHY_DASN_DAC_IN1_B_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_C		0x183
-#define   MTK_PHY_DASN_DAC_IN1_C_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DASN_DAC_IN1_D		0x184
-#define   MTK_PHY_DASN_DAC_IN1_D_MASK		GENMASK(9, 0)
-
-#define MTK_PHY_RG_DEV1E_REG19b			0x19b
-#define   MTK_PHY_BYPASS_DSP_LPI_READY		BIT(8)
-
-#define MTK_PHY_RG_LP_IIR2_K1_L			0x22a
-#define MTK_PHY_RG_LP_IIR2_K1_U			0x22b
-#define MTK_PHY_RG_LP_IIR2_K2_L			0x22c
-#define MTK_PHY_RG_LP_IIR2_K2_U			0x22d
-#define MTK_PHY_RG_LP_IIR2_K3_L			0x22e
-#define MTK_PHY_RG_LP_IIR2_K3_U			0x22f
-#define MTK_PHY_RG_LP_IIR2_K4_L			0x230
-#define MTK_PHY_RG_LP_IIR2_K4_U			0x231
-#define MTK_PHY_RG_LP_IIR2_K5_L			0x232
-#define MTK_PHY_RG_LP_IIR2_K5_U			0x233
-
-#define MTK_PHY_RG_DEV1E_REG234			0x234
-#define   MTK_PHY_TR_OPEN_LOOP_EN_MASK		GENMASK(0, 0)
-#define   MTK_PHY_LPF_X_AVERAGE_MASK		GENMASK(7, 4)
-#define   MTK_PHY_TR_LP_IIR_EEE_EN		BIT(12)
-
-#define MTK_PHY_RG_LPF_CNT_VAL			0x235
-
-#define MTK_PHY_RG_DEV1E_REG238			0x238
-#define   MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK	GENMASK(8, 0)
-#define   MTK_PHY_LPI_SLV_SEND_TX_EN		BIT(12)
-
-#define MTK_PHY_RG_DEV1E_REG239			0x239
-#define   MTK_PHY_LPI_SEND_LOC_TIMER_MASK	GENMASK(8, 0)
-#define   MTK_PHY_LPI_TXPCS_LOC_RCV		BIT(12)
-
-#define MTK_PHY_RG_DEV1E_REG27C			0x27c
-#define   MTK_PHY_VGASTATE_FFE_THR_ST1_MASK	GENMASK(12, 8)
-#define MTK_PHY_RG_DEV1E_REG27D			0x27d
-#define   MTK_PHY_VGASTATE_FFE_THR_ST2_MASK	GENMASK(4, 0)
-
-#define MTK_PHY_RG_DEV1E_REG2C7			0x2c7
-#define   MTK_PHY_MAX_GAIN_MASK			GENMASK(4, 0)
-#define   MTK_PHY_MIN_GAIN_MASK			GENMASK(12, 8)
-
-#define MTK_PHY_RG_DEV1E_REG2D1			0x2d1
-#define   MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK	GENMASK(7, 0)
-#define   MTK_PHY_LPI_SKIP_SD_SLV_TR		BIT(8)
-#define   MTK_PHY_LPI_TR_READY			BIT(9)
-#define   MTK_PHY_LPI_VCO_EEE_STG0_EN		BIT(10)
-
-#define MTK_PHY_RG_DEV1E_REG323			0x323
-#define   MTK_PHY_EEE_WAKE_MAS_INT_DC		BIT(0)
-#define   MTK_PHY_EEE_WAKE_SLV_INT_DC		BIT(4)
-
-#define MTK_PHY_RG_DEV1E_REG324			0x324
-#define   MTK_PHY_SMI_DETCNT_MAX_MASK		GENMASK(5, 0)
-#define   MTK_PHY_SMI_DET_MAX_EN		BIT(8)
-
-#define MTK_PHY_RG_DEV1E_REG326			0x326
-#define   MTK_PHY_LPI_MODE_SD_ON		BIT(0)
-#define   MTK_PHY_RESET_RANDUPD_CNT		BIT(1)
-#define   MTK_PHY_TREC_UPDATE_ENAB_CLR		BIT(2)
-#define   MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF	BIT(4)
-#define   MTK_PHY_TR_READY_SKIP_AFE_WAKEUP	BIT(5)
-
-#define MTK_PHY_LDO_PUMP_EN_PAIRAB		0x502
-#define MTK_PHY_LDO_PUMP_EN_PAIRCD		0x503
-
-#define MTK_PHY_DA_TX_R50_PAIR_A		0x53d
-#define MTK_PHY_DA_TX_R50_PAIR_B		0x53e
-#define MTK_PHY_DA_TX_R50_PAIR_C		0x53f
-#define MTK_PHY_DA_TX_R50_PAIR_D		0x540
-
-/* Registers on MDIO_MMD_VEND2 */
-#define MTK_PHY_LED1_DEFAULT_POLARITIES		BIT(1)
-
-#define MTK_PHY_RG_BG_RASEL			0x115
-#define   MTK_PHY_RG_BG_RASEL_MASK		GENMASK(2, 0)
-
-/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
-#define RG_GPIO_MISC_TPBANK0			0x6f0
-#define   RG_GPIO_MISC_TPBANK0_BOOTMODE		GENMASK(11, 8)
-
 /* These macro privides efuse parsing for internal phy. */
 #define EFS_DA_TX_I2MPB_A(x)			(((x) >> 0) & GENMASK(5, 0))
 #define EFS_DA_TX_I2MPB_B(x)			(((x) >> 6) & GENMASK(5, 0))
@@ -346,51 +34,16 @@
 #define EFS_RG_BG_RASEL(x)			(((x) >> 4) & GENMASK(2, 0))
 #define EFS_RG_REXT_TRIM(x)			(((x) >> 7) & GENMASK(5, 0))
 
-enum {
-	NO_PAIR,
-	PAIR_A,
-	PAIR_B,
-	PAIR_C,
-	PAIR_D,
-};
-
-enum calibration_mode {
-	EFUSE_K,
-	SW_K
-};
-
-enum CAL_ITEM {
-	REXT,
-	TX_OFFSET,
-	TX_AMP,
-	TX_R50,
-	TX_VCM
-};
-
-enum CAL_MODE {
-	EFUSE_M,
-	SW_M
-};
-
 struct mtk_socphy_shared {
 	u32			boottrap;
 	struct mtk_socphy_priv	priv[4];
 };
 
-/* One calibration cycle consists of:
- * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
- *   until AD_CAL_COMP is ready to output calibration result.
- * 2.Wait until DA_CAL_CLK is available.
- * 3.Fetch AD_CAL_COMP_OUT.
- */
-static int cal_cycle(struct phy_device *phydev, int devad,
-		     u32 regnum, u16 mask, u16 cal_val)
+int mtk_cal_cycle_wait(struct phy_device *phydev)
 {
 	int reg_val;
 	int ret;
 
-	phy_modify_mmd(phydev, devad, regnum,
-		       mask, cal_val);
 	phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CALIN,
 			 MTK_PHY_DA_CALIN_FLAG);
 
@@ -409,7 +62,25 @@ static int cal_cycle(struct phy_device *phydev, int devad,
 	ret = phy_read_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_AD_CAL_COMP);
 	if (ret < 0)
 		return ret;
-	ret = FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
+
+	return FIELD_GET(MTK_PHY_AD_CAL_COMP_OUT_MASK, ret);
+}
+
+/* One calibration cycle consists of:
+ * 1.Set DA_CALIN_FLAG high to start calibration. Keep it high
+ *   until AD_CAL_COMP is ready to output calibration result.
+ * 2.Wait until DA_CAL_CLK is available.
+ * 3.Fetch AD_CAL_COMP_OUT.
+ */
+static int cal_cycle(struct phy_device *phydev, int devad,
+		     u32 regnum, u16 mask, u16 cal_val)
+{
+	int ret;
+
+	phy_modify_mmd(phydev, devad, regnum,
+		       mask, cal_val);
+
+	ret = mtk_cal_cycle_wait(phydev);
 	phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
 
 	return ret;
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
new file mode 100644
index 000000000000..9aaa7e3caa41
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -0,0 +1,351 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _MTK_GE_SOC_H_
+#define _MTK_GE_SOC_H_
+
+#include <linux/bitfield.h>
+
+#define MTK_GPHY_ID_MT7981			0x03a29461
+#define MTK_GPHY_ID_MT7988			0x03a29481
+#define MTK_GPHY_ID_AN7581			0x03a294c1
+#define MTK_GPHY_ID_AN7583			0xc0ff0420
+
+#define MTK_EXT_PAGE_ACCESS			0x1f
+#define MTK_PHY_PAGE_STANDARD			0x0000
+#define MTK_PHY_PAGE_EXTENDED_3			0x0003
+
+#define MTK_PHY_LPI_REG_14			0x14
+#define MTK_PHY_LPI_WAKE_TIMER_1000_MASK	GENMASK(8, 0)
+
+#define MTK_PHY_LPI_REG_1c			0x1c
+#define MTK_PHY_SMI_DET_ON_THRESH_MASK		GENMASK(13, 8)
+
+#define MTK_PHY_PAGE_EXTENDED_2A30		0x2a30
+
+/* Registers on Token Ring debug nodes */
+/* ch_addr = 0x0, node_addr = 0x7, data_addr = 0x15 */
+/* NormMseLoThresh */
+#define NORMAL_MSE_LO_THRESH_MASK		GENMASK(15, 8)
+
+/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
+/* RemAckCntLimitCtrl */
+#define REMOTE_ACK_COUNT_LIMIT_CTRL_MASK	GENMASK(2, 1)
+
+/* ch_addr = 0x1, node_addr = 0xd, data_addr = 0x20 */
+/* VcoSlicerThreshBitsHigh */
+#define VCO_SLICER_THRESH_HIGH_MASK		GENMASK(23, 0)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x0 */
+/* DfeTailEnableVgaThresh1000 */
+#define DFE_TAIL_EANBLE_VGA_TRHESH_1000		GENMASK(5, 1)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x1 */
+/* MrvlTrFix100Kp */
+#define MRVL_TR_FIX_100KP_MASK			GENMASK(22, 20)
+/* MrvlTrFix100Kf */
+#define MRVL_TR_FIX_100KF_MASK			GENMASK(19, 17)
+/* MrvlTrFix1000Kp */
+#define MRVL_TR_FIX_1000KP_MASK			GENMASK(16, 14)
+/* MrvlTrFix1000Kf */
+#define MRVL_TR_FIX_1000KF_MASK			GENMASK(13, 11)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x12 */
+/* VgaDecRate */
+#define VGA_DECIMATION_RATE_MASK		GENMASK(8, 5)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x17 */
+/* SlvDSPreadyTime */
+#define SLAVE_DSP_READY_TIME_MASK		GENMASK(22, 15)
+/* MasDSPreadyTime */
+#define MASTER_DSP_READY_TIME_MASK		GENMASK(14, 7)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x18 */
+/* EnabRandUpdTrig */
+#define ENABLE_RANDOM_UPDOWN_COUNTER_TRIGGER	BIT(8)
+
+/* ch_addr = 0x1, node_addr = 0xf, data_addr = 0x20 */
+/* ResetSyncOffset */
+#define RESET_SYNC_OFFSET_MASK			GENMASK(11, 8)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x0 */
+/* FfeUpdGainForceVal */
+#define FFE_UPDATE_GAIN_FORCE_VAL_MASK		GENMASK(9, 7)
+/* FfeUpdGainForce */
+#define FFE_UPDATE_GAIN_FORCE			BIT(6)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x3 */
+/* TrFreeze */
+#define TR_FREEZE_MASK				GENMASK(11, 0)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x6 */
+/* SS: Steady-state, KP: Proportional Gain */
+/* SSTrKp100 */
+#define SS_TR_KP100_MASK			GENMASK(21, 19)
+/* SSTrKf100 */
+#define SS_TR_KF100_MASK			GENMASK(18, 16)
+/* SSTrKp1000Mas */
+#define SS_TR_KP1000_MASTER_MASK		GENMASK(15, 13)
+/* SSTrKf1000Mas */
+#define SS_TR_KF1000_MASTER_MASK		GENMASK(12, 10)
+/* SSTrKp1000Slv */
+#define SS_TR_KP1000_SLAVE_MASK			GENMASK(9, 7)
+/* SSTrKf1000Slv */
+#define SS_TR_KF1000_SLAVE_MASK			GENMASK(6, 4)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x8 */
+/* clear this bit if wanna select from AFE */
+/* Regsigdet_sel_1000 */
+#define EEE1000_SELECT_SIGNAL_DETECTION_FROM_DFE	BIT(4)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xd */
+/* RegEEE_st2TrKf1000 */
+#define EEE1000_STAGE2_TR_KF_MASK		GENMASK(13, 11)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0xf */
+/* RegEEE_slv_waketr_timer_tar */
+#define SLAVE_WAKETR_TIMER_MASK			GENMASK(20, 11)
+/* RegEEE_slv_remtx_timer_tar */
+#define SLAVE_REMTX_TIMER_MASK			GENMASK(10, 1)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x10 */
+/* RegEEE_slv_wake_int_timer_tar */
+#define SLAVE_WAKEINT_TIMER_MASK		GENMASK(10, 1)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x14 */
+/* RegEEE_trfreeze_timer2 */
+#define TR_FREEZE_TIMER2_MASK			GENMASK(9, 0)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x1c */
+/* RegEEE100Stg1_tar */
+#define EEE100_LPSYNC_STAGE1_UPDATE_TIMER_MASK	GENMASK(8, 0)
+
+/* ch_addr = 0x2, node_addr = 0xd, data_addr = 0x25 */
+/* REGEEE_wake_slv_tr_wait_dfesigdet_en */
+#define WAKE_SLAVE_TR_WAIT_DFE_DETECTION_EN	BIT(11)
+
+#define ANALOG_INTERNAL_OPERATION_MAX_US	20
+#define TXRESERVE_MIN				0
+#define TXRESERVE_MAX				7
+
+#define MTK_PHY_ANARG_RG			0x10
+#define   MTK_PHY_TCLKOFFSET_MASK		GENMASK(12, 8)
+
+/* Registers on MDIO_MMD_VEND1 */
+#define MTK_PHY_TXVLD_DA_RG			0x12
+#define   MTK_PHY_DA_TX_I2MPB_A_GBE_MASK	GENMASK(15, 10)
+#define   MTK_PHY_DA_TX_I2MPB_A_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_A2		0x16
+#define   MTK_PHY_DA_TX_I2MPB_A_HBT_MASK	GENMASK(15, 10)
+#define   MTK_PHY_DA_TX_I2MPB_A_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_B1		0x17
+#define   MTK_PHY_DA_TX_I2MPB_B_GBE_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_B_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_B2		0x18
+#define   MTK_PHY_DA_TX_I2MPB_B_HBT_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_B_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_C1		0x19
+#define   MTK_PHY_DA_TX_I2MPB_C_GBE_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_C_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_C2		0x20
+#define   MTK_PHY_DA_TX_I2MPB_C_HBT_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_C_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_D1		0x21
+#define   MTK_PHY_DA_TX_I2MPB_D_GBE_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_D_TBT_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_TX_I2MPB_TEST_MODE_D2		0x22
+#define   MTK_PHY_DA_TX_I2MPB_D_HBT_MASK	GENMASK(13, 8)
+#define   MTK_PHY_DA_TX_I2MPB_D_TST_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_RXADC_CTRL_RG7			0xc6
+#define   MTK_PHY_DA_AD_BUF_BIAS_LP_MASK	GENMASK(9, 8)
+
+#define MTK_PHY_RXADC_CTRL_RG9			0xc8
+#define   MTK_PHY_DA_RX_PSBN_TBT_MASK		GENMASK(14, 12)
+#define   MTK_PHY_DA_RX_PSBN_HBT_MASK		GENMASK(10, 8)
+#define   MTK_PHY_DA_RX_PSBN_GBE_MASK		GENMASK(6, 4)
+#define   MTK_PHY_DA_RX_PSBN_LP_MASK		GENMASK(2, 0)
+
+#define MTK_PHY_LDO_OUTPUT_V			0xd7
+
+#define MTK_PHY_RG_ANA_CAL_RG0			0xdb
+#define   MTK_PHY_RG_CAL_CKINV			BIT(12)
+#define   MTK_PHY_RG_ANA_CALEN			BIT(8)
+#define   MTK_PHY_RG_ZCALEN_A			BIT(0)
+
+#define MTK_PHY_RG_ANA_CAL_RG1			0xdc
+#define   MTK_PHY_RG_ZCALEN_B			BIT(12)
+#define   MTK_PHY_RG_ZCALEN_C			BIT(8)
+#define   MTK_PHY_RG_ZCALEN_D			BIT(4)
+#define   MTK_PHY_RG_TXVOS_CALEN		BIT(0)
+
+#define MTK_PHY_RG_ANA_CAL_RG5			0xe0
+#define   MTK_PHY_RG_REXT_TRIM_MASK		GENMASK(13, 8)
+
+#define MTK_PHY_RG_TX_FILTER			0xfe
+
+#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG120	0x120
+#define   MTK_PHY_LPI_SIG_EN_LO_THRESH1000_MASK	GENMASK(12, 8)
+#define   MTK_PHY_LPI_SIG_EN_HI_THRESH1000_MASK	GENMASK(4, 0)
+
+#define MTK_PHY_RG_LPI_PCS_DSP_CTRL_REG122	0x122
+#define   MTK_PHY_LPI_NORM_MSE_HI_THRESH1000_MASK	GENMASK(7, 0)
+
+#define MTK_PHY_RG_TESTMUX_ADC_CTRL		0x144
+#define   MTK_PHY_RG_TXEN_DIG_MASK		GENMASK(5, 5)
+
+#define MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B		0x172
+#define   MTK_PHY_CR_TX_AMP_OFFSET_A_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_AMP_OFFSET_B_MASK	GENMASK(6, 0)
+
+#define MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D		0x173
+#define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK	GENMASK(6, 0)
+
+#define MTK_PHY_RG_AD_CAL_COMP			0x17a
+#define   MTK_PHY_AD_CAL_COMP_OUT_MASK		GENMASK(8, 8)
+
+#define MTK_PHY_RG_AD_CAL_CLK			0x17b
+#define   MTK_PHY_DA_CAL_CLK			BIT(0)
+
+#define MTK_PHY_RG_AD_CALIN			0x17c
+#define   MTK_PHY_DA_CALIN_FLAG			BIT(0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_A		0x17d
+#define   MTK_PHY_DASN_DAC_IN0_A_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_B		0x17e
+#define   MTK_PHY_DASN_DAC_IN0_B_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_C		0x17f
+#define   MTK_PHY_DASN_DAC_IN0_C_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN0_D		0x180
+#define   MTK_PHY_DASN_DAC_IN0_D_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_A		0x181
+#define   MTK_PHY_DASN_DAC_IN1_A_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_B		0x182
+#define   MTK_PHY_DASN_DAC_IN1_B_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_C		0x183
+#define   MTK_PHY_DASN_DAC_IN1_C_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DASN_DAC_IN1_D		0x184
+#define   MTK_PHY_DASN_DAC_IN1_D_MASK		GENMASK(9, 0)
+
+#define MTK_PHY_RG_DEV1E_REG19b			0x19b
+#define   MTK_PHY_BYPASS_DSP_LPI_READY		BIT(8)
+
+#define MTK_PHY_RG_LP_IIR2_K1_L			0x22a
+#define MTK_PHY_RG_LP_IIR2_K1_U			0x22b
+#define MTK_PHY_RG_LP_IIR2_K2_L			0x22c
+#define MTK_PHY_RG_LP_IIR2_K2_U			0x22d
+#define MTK_PHY_RG_LP_IIR2_K3_L			0x22e
+#define MTK_PHY_RG_LP_IIR2_K3_U			0x22f
+#define MTK_PHY_RG_LP_IIR2_K4_L			0x230
+#define MTK_PHY_RG_LP_IIR2_K4_U			0x231
+#define MTK_PHY_RG_LP_IIR2_K5_L			0x232
+#define MTK_PHY_RG_LP_IIR2_K5_U			0x233
+
+#define MTK_PHY_RG_DEV1E_REG234			0x234
+#define   MTK_PHY_TR_OPEN_LOOP_EN_MASK		GENMASK(0, 0)
+#define   MTK_PHY_LPF_X_AVERAGE_MASK		GENMASK(7, 4)
+#define   MTK_PHY_TR_LP_IIR_EEE_EN		BIT(12)
+
+#define MTK_PHY_RG_LPF_CNT_VAL			0x235
+
+#define MTK_PHY_RG_DEV1E_REG238			0x238
+#define   MTK_PHY_LPI_SLV_SEND_TX_TIMER_MASK	GENMASK(8, 0)
+#define   MTK_PHY_LPI_SLV_SEND_TX_EN		BIT(12)
+
+#define MTK_PHY_RG_DEV1E_REG239			0x239
+#define   MTK_PHY_LPI_SEND_LOC_TIMER_MASK	GENMASK(8, 0)
+#define   MTK_PHY_LPI_TXPCS_LOC_RCV		BIT(12)
+
+#define MTK_PHY_RG_DEV1E_REG27C			0x27c
+#define   MTK_PHY_VGASTATE_FFE_THR_ST1_MASK	GENMASK(12, 8)
+#define MTK_PHY_RG_DEV1E_REG27D			0x27d
+#define   MTK_PHY_VGASTATE_FFE_THR_ST2_MASK	GENMASK(4, 0)
+
+#define MTK_PHY_RG_DEV1E_REG2C7			0x2c7
+#define   MTK_PHY_MAX_GAIN_MASK			GENMASK(4, 0)
+#define   MTK_PHY_MIN_GAIN_MASK			GENMASK(12, 8)
+
+#define MTK_PHY_RG_DEV1E_REG2D1			0x2d1
+#define   MTK_PHY_VCO_SLICER_THRESH_BITS_HIGH_EEE_MASK	GENMASK(7, 0)
+#define   MTK_PHY_LPI_SKIP_SD_SLV_TR		BIT(8)
+#define   MTK_PHY_LPI_TR_READY			BIT(9)
+#define   MTK_PHY_LPI_VCO_EEE_STG0_EN		BIT(10)
+
+#define MTK_PHY_RG_DEV1E_REG323			0x323
+#define   MTK_PHY_EEE_WAKE_MAS_INT_DC		BIT(0)
+#define   MTK_PHY_EEE_WAKE_SLV_INT_DC		BIT(4)
+
+#define MTK_PHY_RG_DEV1E_REG324			0x324
+#define   MTK_PHY_SMI_DETCNT_MAX_MASK		GENMASK(5, 0)
+#define   MTK_PHY_SMI_DET_MAX_EN		BIT(8)
+
+#define MTK_PHY_RG_DEV1E_REG326			0x326
+#define   MTK_PHY_LPI_MODE_SD_ON		BIT(0)
+#define   MTK_PHY_RESET_RANDUPD_CNT		BIT(1)
+#define   MTK_PHY_TREC_UPDATE_ENAB_CLR		BIT(2)
+#define   MTK_PHY_LPI_QUIT_WAIT_DFE_SIG_DET_OFF	BIT(4)
+#define   MTK_PHY_TR_READY_SKIP_AFE_WAKEUP	BIT(5)
+
+#define MTK_PHY_LDO_PUMP_EN_PAIRAB		0x502
+#define MTK_PHY_LDO_PUMP_EN_PAIRCD		0x503
+
+#define MTK_PHY_DA_TX_R50_PAIR_A		0x53d
+#define MTK_PHY_DA_TX_R50_PAIR_B		0x53e
+#define MTK_PHY_DA_TX_R50_PAIR_C		0x53f
+#define MTK_PHY_DA_TX_R50_PAIR_D		0x540
+
+/* Registers on MDIO_MMD_VEND2 */
+#define MTK_PHY_LED1_DEFAULT_POLARITIES		BIT(1)
+
+#define MTK_PHY_RG_BG_RASEL			0x115
+#define   MTK_PHY_RG_BG_RASEL_MASK		GENMASK(2, 0)
+
+/* 'boottrap' register reflecting the configuration of the 4 PHY LEDs */
+#define RG_GPIO_MISC_TPBANK0			0x6f0
+#define   RG_GPIO_MISC_TPBANK0_BOOTMODE		GENMASK(11, 8)
+
+enum CAL_ITEM {
+	REXT,
+	TX_OFFSET,
+	TX_AMP,
+	TX_R50,
+	TX_VCM,
+	RX_OFFSET,
+};
+
+enum {
+	NO_PAIR,
+	PAIR_A,
+	PAIR_B,
+	PAIR_C,
+	PAIR_D,
+};
+
+enum calibration_mode {
+	EFUSE_K,
+	SW_K
+};
+
+enum CAL_MODE {
+	EFUSE_M,
+	SW_M
+};
+
+/* MTK GE SoC common functions */
+int mtk_cal_cycle_wait(struct phy_device *phydev);
+
+#endif /* _MTK_GE_SOC_H_ */
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next 3/5] net: phy: mediatek: split Airoha code to dedicated source
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek
In-Reply-To: <20260708102341.53919-1-ansuelsmth@gmail.com>

In preparation for Airoha PHYs software calibration support, split the
Airoha code to dedicated source following Mediatek naming pattern of
"ge-soc".

This is to tidy things up and not overload the mediatek driver of specific
Airoha code and ship a bigger PHY kernel module with dead code.

Move the shared LEDs function to mtk-phy-lib to permit the split instead of
duplicating the code.

Also add myself as MAINTAINER of the now new airoha-ge-soc driver.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 MAINTAINERS                              |   8 ++
 drivers/net/phy/mediatek/Kconfig         |  16 ++-
 drivers/net/phy/mediatek/Makefile        |   1 +
 drivers/net/phy/mediatek/airoha-ge-soc.c | 102 ++++++++++++++++
 drivers/net/phy/mediatek/mtk-ge-soc.c    | 146 -----------------------
 drivers/net/phy/mediatek/mtk-ge-soc.h    |   4 +-
 drivers/net/phy/mediatek/mtk-phy-lib.c   |  72 +++++++++++
 drivers/net/phy/mediatek/mtk.h           |  12 ++
 8 files changed, 211 insertions(+), 150 deletions(-)
 create mode 100644 drivers/net/phy/mediatek/airoha-ge-soc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 16e47aea84c3..e914bccf5cbb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -760,6 +760,14 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/net/airoha,en7581-eth.yaml
 F:	drivers/net/ethernet/airoha/
 
+AIROHA ETHERNET PHY DRIVER
+M:	Christian Marangi <ansuelsmth@gmail.com>
+L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+L:	linux-mediatek@lists.infradead.org (moderated for non-subscribers)
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	drivers/net/phy/mediatek/airoha-ge-soc.c
+
 AIROHA PCIE PHY DRIVER
 M:	Lorenzo Bianconi <lorenzo@kernel.org>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
index bb7dc876271e..fe51f4616c54 100644
--- a/drivers/net/phy/mediatek/Kconfig
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -1,4 +1,17 @@
 # SPDX-License-Identifier: GPL-2.0-only
+config AIROHA_GE_SOC_PHY
+	tristate "Airoha SoC Ethernet PHYs"
+	depends on ARM64 || COMPILE_TEST
+	depends on ARCH_AIROHA || COMPILE_TEST
+	select MTK_NET_PHYLIB
+	select PHY_PACKAGE
+	help
+	  Supports Airoha SoC built-in Gigabit Ethernet PHYs.
+
+	  Include support for built-in Ethernet PHYs which are present in
+	  the AN7581 and AN7583 SoCs. These PHYs d will dynamically
+	  calibrate during startup.
+
 config MEDIATEK_2P5GE_PHY
 	tristate "MediaTek 2.5Gb Ethernet PHYs"
 	depends on (ARM64 && ARCH_MEDIATEK) || COMPILE_TEST
@@ -24,8 +37,7 @@ config MEDIATEK_GE_PHY
 config MEDIATEK_GE_SOC_PHY
 	tristate "MediaTek SoC Ethernet PHYs"
 	depends on ARM64 || COMPILE_TEST
-	depends on ARCH_AIROHA || (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || \
-		   COMPILE_TEST
+	depends on (ARCH_MEDIATEK && NVMEM_MTK_EFUSE) || COMPILE_TEST
 	select MTK_NET_PHYLIB
 	select PHY_PACKAGE
 	help
diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile
index ac57ecc799fc..eb430f008169 100644
--- a/drivers/net/phy/mediatek/Makefile
+++ b/drivers/net/phy/mediatek/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_AIROHA_GE_SOC_PHY)		+= airoha-ge-soc.o
 obj-$(CONFIG_MEDIATEK_2P5GE_PHY)	+= mtk-2p5ge.o
 obj-$(CONFIG_MEDIATEK_GE_PHY)		+= mtk-ge.o
 obj-$(CONFIG_MEDIATEK_GE_SOC_PHY)	+= mtk-ge-soc.o
diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
new file mode 100644
index 000000000000..429594f27792
--- /dev/null
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/pinctrl/consumer.h>
+#include <linux/phy.h>
+
+#include "mtk.h"
+#include "mtk-ge-soc.h"
+
+#define AIROHA_PHY_MAX_LEDS			2
+
+static int an7581_phy_probe(struct phy_device *phydev)
+{
+	struct mtk_socphy_priv *priv;
+	struct pinctrl *pinctrl;
+
+	/* Toggle pinctrl to enable PHY LED */
+	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
+	if (IS_ERR(pinctrl))
+		dev_err(&phydev->mdio.bus->dev,
+			"Failed to setup PHY LED pinctrl\n");
+
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
+				       unsigned long modes)
+{
+	u16 val = 0;
+	u32 mode;
+
+	if (index >= AIROHA_PHY_MAX_LEDS)
+		return -EINVAL;
+
+	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
+		switch (mode) {
+		case PHY_LED_ACTIVE_LOW:
+			val = MTK_PHY_LED_ON_POLARITY;
+			break;
+		case PHY_LED_ACTIVE_HIGH:
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
+			      MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
+			      MTK_PHY_LED_ON_POLARITY, val);
+}
+
+static int an7583_phy_config_init(struct phy_device *phydev)
+{
+	/* BMCR_PDOWN is enabled by default */
+	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+}
+
+static struct phy_driver mtk_socphy_driver[] = {
+	{
+		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581),
+		.name		= "Airoha AN7581 PHY",
+		.config_intr	= genphy_no_config_intr,
+		.handle_interrupt = genphy_handle_interrupt_no_ack,
+		.probe		= an7581_phy_probe,
+		.led_blink_set	= mt798x_phy_led_blink_set,
+		.led_brightness_set = mt798x_phy_led_brightness_set,
+		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+		.led_hw_control_set = mt798x_phy_led_hw_control_set,
+		.led_hw_control_get = mt798x_phy_led_hw_control_get,
+		.led_polarity_set = an7581_phy_led_polarity_set,
+	},
+	{
+		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583),
+		.name		= "Airoha AN7583 PHY",
+		.config_init	= an7583_phy_config_init,
+		.probe		= an7581_phy_probe,
+		.led_blink_set	= mt798x_phy_led_blink_set,
+		.led_brightness_set = mt798x_phy_led_brightness_set,
+		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
+		.led_hw_control_set = mt798x_phy_led_hw_control_set,
+		.led_hw_control_get = mt798x_phy_led_hw_control_get,
+		.led_polarity_set = an7581_phy_led_polarity_set,
+	},
+};
+
+module_phy_driver(mtk_socphy_driver);
+
+static const struct mdio_device_id __maybe_unused airoha_socphy_tbl[] = {
+	{ PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581) },
+	{ PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583) },
+	{ }
+};
+
+MODULE_DESCRIPTION("Airoha SoC Gigabit Ethernet PHY driver");
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_LICENSE("GPL");
+
+MODULE_DEVICE_TABLE(mdio, airoha_socphy_tbl);
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.c b/drivers/net/phy/mediatek/mtk-ge-soc.c
index 46bae11ad740..868ae089013d 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.c
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.c
@@ -886,73 +886,6 @@ static int mt798x_phy_config_init(struct phy_device *phydev)
 	return mt798x_phy_calibration(phydev);
 }
 
-static int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
-				    unsigned long *delay_on,
-				    unsigned long *delay_off)
-{
-	bool blinking = false;
-	int err;
-
-	err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking);
-	if (err < 0)
-		return err;
-
-	err = mtk_phy_hw_led_blink_set(phydev, index, blinking);
-	if (err)
-		return err;
-
-	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
-				     false);
-}
-
-static int mt798x_phy_led_brightness_set(struct phy_device *phydev,
-					 u8 index, enum led_brightness value)
-{
-	int err;
-
-	err = mtk_phy_hw_led_blink_set(phydev, index, false);
-	if (err)
-		return err;
-
-	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
-				     (value != LED_OFF));
-}
-
-static const unsigned long supported_triggers =
-	BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
-	BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
-	BIT(TRIGGER_NETDEV_LINK)        |
-	BIT(TRIGGER_NETDEV_LINK_10)     |
-	BIT(TRIGGER_NETDEV_LINK_100)    |
-	BIT(TRIGGER_NETDEV_LINK_1000)   |
-	BIT(TRIGGER_NETDEV_RX)          |
-	BIT(TRIGGER_NETDEV_TX);
-
-static int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
-					  unsigned long rules)
-{
-	return mtk_phy_led_hw_is_supported(phydev, index, rules,
-					   supported_triggers);
-}
-
-static int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
-					 unsigned long *rules)
-{
-	return mtk_phy_led_hw_ctrl_get(phydev, index, rules,
-				       MTK_GPHY_LED_ON_SET,
-				       MTK_GPHY_LED_RX_BLINK_SET,
-				       MTK_GPHY_LED_TX_BLINK_SET);
-};
-
-static int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
-					 unsigned long rules)
-{
-	return mtk_phy_led_hw_ctrl_set(phydev, index, rules,
-				       MTK_GPHY_LED_ON_SET,
-				       MTK_GPHY_LED_RX_BLINK_SET,
-				       MTK_GPHY_LED_TX_BLINK_SET);
-};
-
 static bool mt7988_phy_led_get_polarity(struct phy_device *phydev, int led_num)
 {
 	struct mtk_socphy_shared *priv = phy_package_get_priv(phydev);
@@ -1089,58 +1022,6 @@ static int mt7981_phy_probe(struct phy_device *phydev)
 	return mt798x_phy_calibration(phydev);
 }
 
-static int an7581_phy_probe(struct phy_device *phydev)
-{
-	struct mtk_socphy_priv *priv;
-	struct pinctrl *pinctrl;
-
-	/* Toggle pinctrl to enable PHY LED */
-	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
-	if (IS_ERR(pinctrl))
-		dev_err(&phydev->mdio.bus->dev,
-			"Failed to setup PHY LED pinctrl\n");
-
-	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
-	if (!priv)
-		return -ENOMEM;
-
-	phydev->priv = priv;
-
-	return 0;
-}
-
-static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
-				       unsigned long modes)
-{
-	u16 val = 0;
-	u32 mode;
-
-	if (index >= MTK_PHY_MAX_LEDS)
-		return -EINVAL;
-
-	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
-		switch (mode) {
-		case PHY_LED_ACTIVE_LOW:
-			val = MTK_PHY_LED_ON_POLARITY;
-			break;
-		case PHY_LED_ACTIVE_HIGH:
-			break;
-		default:
-			return -EINVAL;
-		}
-	}
-
-	return phy_modify_mmd(phydev, MDIO_MMD_VEND2, index ?
-			      MTK_PHY_LED1_ON_CTRL : MTK_PHY_LED0_ON_CTRL,
-			      MTK_PHY_LED_ON_POLARITY, val);
-}
-
-static int an7583_phy_config_init(struct phy_device *phydev)
-{
-	/* BMCR_PDOWN is enabled by default */
-	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
-}
-
 static struct phy_driver mtk_socphy_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981),
@@ -1176,31 +1057,6 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.led_hw_control_set = mt798x_phy_led_hw_control_set,
 		.led_hw_control_get = mt798x_phy_led_hw_control_get,
 	},
-	{
-		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581),
-		.name		= "Airoha AN7581 PHY",
-		.config_intr	= genphy_no_config_intr,
-		.handle_interrupt = genphy_handle_interrupt_no_ack,
-		.probe		= an7581_phy_probe,
-		.led_blink_set	= mt798x_phy_led_blink_set,
-		.led_brightness_set = mt798x_phy_led_brightness_set,
-		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
-		.led_hw_control_set = mt798x_phy_led_hw_control_set,
-		.led_hw_control_get = mt798x_phy_led_hw_control_get,
-		.led_polarity_set = an7581_phy_led_polarity_set,
-	},
-	{
-		PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583),
-		.name		= "Airoha AN7583 PHY",
-		.config_init	= an7583_phy_config_init,
-		.probe		= an7581_phy_probe,
-		.led_blink_set	= mt798x_phy_led_blink_set,
-		.led_brightness_set = mt798x_phy_led_brightness_set,
-		.led_hw_is_supported = mt798x_phy_led_hw_is_supported,
-		.led_hw_control_set = mt798x_phy_led_hw_control_set,
-		.led_hw_control_get = mt798x_phy_led_hw_control_get,
-		.led_polarity_set = an7581_phy_led_polarity_set,
-	},
 };
 
 module_phy_driver(mtk_socphy_driver);
@@ -1208,8 +1064,6 @@ module_phy_driver(mtk_socphy_driver);
 static const struct mdio_device_id __maybe_unused mtk_socphy_tbl[] = {
 	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7981) },
 	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_MT7988) },
-	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7581) },
-	{ PHY_ID_MATCH_EXACT(MTK_GPHY_ID_AN7583) },
 	{ }
 };
 
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
index 9aaa7e3caa41..507e68343576 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.h
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -7,8 +7,8 @@
 
 #define MTK_GPHY_ID_MT7981			0x03a29461
 #define MTK_GPHY_ID_MT7988			0x03a29481
-#define MTK_GPHY_ID_AN7581			0x03a294c1
-#define MTK_GPHY_ID_AN7583			0xc0ff0420
+#define AIROHA_GPHY_ID_AN7581			0x03a294c1
+#define AIROHA_GPHY_ID_AN7583			0xc0ff0420
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_STANDARD			0x0000
diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c
index 78b998af5238..8c2b26f9b840 100644
--- a/drivers/net/phy/mediatek/mtk-phy-lib.c
+++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
@@ -342,6 +342,78 @@ void mtk_phy_leds_state_init(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(mtk_phy_leds_state_init);
 
+int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+			     unsigned long *delay_on,
+			     unsigned long *delay_off)
+{
+	bool blinking = false;
+	int err;
+
+	err = mtk_phy_led_num_dly_cfg(index, delay_on, delay_off, &blinking);
+	if (err < 0)
+		return err;
+
+	err = mtk_phy_hw_led_blink_set(phydev, index, blinking);
+	if (err)
+		return err;
+
+	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
+				     false);
+}
+EXPORT_SYMBOL_GPL(mt798x_phy_led_blink_set);
+
+int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+				  u8 index, enum led_brightness value)
+{
+	int err;
+
+	err = mtk_phy_hw_led_blink_set(phydev, index, false);
+	if (err)
+		return err;
+
+	return mtk_phy_hw_led_on_set(phydev, index, MTK_GPHY_LED_ON_MASK,
+				     (value != LED_OFF));
+}
+EXPORT_SYMBOL_GPL(mt798x_phy_led_brightness_set);
+
+static const unsigned long supported_triggers =
+	BIT(TRIGGER_NETDEV_FULL_DUPLEX) |
+	BIT(TRIGGER_NETDEV_HALF_DUPLEX) |
+	BIT(TRIGGER_NETDEV_LINK)        |
+	BIT(TRIGGER_NETDEV_LINK_10)     |
+	BIT(TRIGGER_NETDEV_LINK_100)    |
+	BIT(TRIGGER_NETDEV_LINK_1000)   |
+	BIT(TRIGGER_NETDEV_RX)          |
+	BIT(TRIGGER_NETDEV_TX);
+
+int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+				   unsigned long rules)
+{
+	return mtk_phy_led_hw_is_supported(phydev, index, rules,
+					   supported_triggers);
+}
+EXPORT_SYMBOL_GPL(mt798x_phy_led_hw_is_supported);
+
+int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+				  unsigned long *rules)
+{
+	return mtk_phy_led_hw_ctrl_get(phydev, index, rules,
+				       MTK_GPHY_LED_ON_SET,
+				       MTK_GPHY_LED_RX_BLINK_SET,
+				       MTK_GPHY_LED_TX_BLINK_SET);
+};
+EXPORT_SYMBOL_GPL(mt798x_phy_led_hw_control_get);
+
+int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+				  unsigned long rules)
+{
+	return mtk_phy_led_hw_ctrl_set(phydev, index, rules,
+				       MTK_GPHY_LED_ON_SET,
+				       MTK_GPHY_LED_RX_BLINK_SET,
+				       MTK_GPHY_LED_TX_BLINK_SET);
+};
+EXPORT_SYMBOL_GPL(mt798x_phy_led_hw_control_set);
+
 MODULE_DESCRIPTION("MediaTek Ethernet PHY driver common");
 MODULE_AUTHOR("Sky Huang <SkyLake.Huang@mediatek.com>");
 MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
index f172f7d671c9..c690aaf38124 100644
--- a/drivers/net/phy/mediatek/mtk.h
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -103,4 +103,16 @@ int mtk_phy_hw_led_blink_set(struct phy_device *phydev, u8 index,
 			     bool blinking);
 void mtk_phy_leds_state_init(struct phy_device *phydev);
 
+int mt798x_phy_led_blink_set(struct phy_device *phydev, u8 index,
+			     unsigned long *delay_on,
+			     unsigned long *delay_off);
+int mt798x_phy_led_brightness_set(struct phy_device *phydev,
+				  u8 index, enum led_brightness value);
+int mt798x_phy_led_hw_is_supported(struct phy_device *phydev, u8 index,
+				   unsigned long rules);
+int mt798x_phy_led_hw_control_get(struct phy_device *phydev, u8 index,
+				  unsigned long *rules);
+int mt798x_phy_led_hw_control_set(struct phy_device *phydev, u8 index,
+				  unsigned long rules);
+
 #endif /* _MTK_EPHY_H_ */
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next 4/5] net: phy: mediatek: add calibration logic for AN7581
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek
In-Reply-To: <20260708102341.53919-1-ansuelsmth@gmail.com>

Airoha AN7581 require dedicated calibration and specific tuneup to make
the Ethernet port work on specific HW PCB layout.

The calibration process is similar to Mediatek PHY but Airoha PHY use
Software calibration process instead of making use of EFUSE.

It does also require some complex table for TX Amp compensation and
multiple register settings for EEE and 10/100M to correctly work.
Also the PHY apply a different TX Amp compensation table based on the
SoC PDIDR version. Such version info is stored in the SCU SoC registers.

Add module version as suggested by Airoha to identify the calibration logic
with a specific version.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/phy/mediatek/airoha-ge-soc.c | 1489 ++++++++++++++++++++++
 drivers/net/phy/mediatek/mtk-ge-soc.h    |   13 +
 2 files changed, 1502 insertions(+)

diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
index 429594f27792..bc487b093cfa 100644
--- a/drivers/net/phy/mediatek/airoha-ge-soc.c
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -1,16 +1,1493 @@
 // SPDX-License-Identifier: GPL-2.0+
+#include <linux/bitfield.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/phy.h>
 
+#include "../phylib.h"
 #include "mtk.h"
 #include "mtk-ge-soc.h"
 
+#define AIROHA_DEFAULT_PORT0_ADDR		0x9
 #define AIROHA_PHY_MAX_LEDS			2
 
+enum airoha_mdi_resister_type {
+	MDI_0R,
+	MDI_5R,
+
+	MDI_TYPE_MAX,
+};
+
+enum airoha_transformer_type {
+	TXMR,
+	DISCRETE,
+
+	TRANSFORMER_TYPE_MAX,
+};
+
+enum airoha_calib_const_type {
+	TX_AMP_TEST_A,
+	TX_AMP_TEST_B,
+	TX_AMP_TEST_C,
+	TX_AMP_TEST_D,
+	TX_AMP_1G_A,
+	TX_AMP_1G_B,
+	TX_AMP_1G_C,
+	TX_AMP_1G_D,
+	TX_AMP_100M_A,
+	TX_AMP_100M_B,
+	TX_AMP_10M_A,
+	TX_AMP_10M_B,
+	R50_A,
+	R50_B,
+	R50_C,
+	R50_D,
+
+	CALIB_CONST_TYPE_MAX,
+};
+
+struct airoha_socphy_shared {
+	struct phy_device *phydev_p0;
+	enum airoha_transformer_type transformer_type[4];
+	enum airoha_mdi_resister_type mdi_resister_type;
+	bool rext_sw_calib_done;
+	int (*tx_amp_compensation_tbl)[TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4];
+	u8 *r50_cal_tbl;
+};
+
+/*
+ * 2 chip revision
+ * TXMR or discrete
+ * 2 MDI type
+ * TX AMP test/TX AMP 1G/TX AMP 100M/TX AMP 10M/R50
+ * 4 PHY
+ */
+static int an7581_tx_amp_compensation_tbl[2][TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4] = {
+	{ /* IC version 1 */
+		[TXMR] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -12, -11, -9, -6 },
+				[TX_AMP_TEST_B] = { -9, -13, -9, -5 },
+				[TX_AMP_TEST_C] = { -5, -4, -3, 1 },
+				[TX_AMP_TEST_D] = { -9, -8, -7, -5 },
+				[TX_AMP_1G_A] = { -12, -11, -9, -6 },
+				[TX_AMP_1G_B] = { -9, -13, -9, -5 },
+				[TX_AMP_1G_C] = { -5, -4, -3, 1 },
+				[TX_AMP_1G_D] = { -9, -8, -7, -5 },
+				[TX_AMP_100M_A] = { -2, -2, 0, 2 },
+				[TX_AMP_100M_B] = { -2, -2, 1, 1 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 15, 15, 15, 15 },
+				[R50_B] = { 15, 15, 18, 15 },
+				[R50_C] = { -8, -8, -8, -8 },
+				[R50_D] = { -8, -8, -8, -8 },
+			},
+		},
+		[DISCRETE] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -9, -7, -7, -5 },
+				[TX_AMP_TEST_B] = { -9, -9, -7, -4 },
+				[TX_AMP_TEST_C] = { -9, -7, -9, -6 },
+				[TX_AMP_TEST_D] = { -11, -9, -10, -8 },
+				[TX_AMP_1G_A] = { -9, -7, -7, -5 },
+				[TX_AMP_1G_B] = { -9, -8, -7, -4 },
+				[TX_AMP_1G_C] = { -9, -7, -9, -6 },
+				[TX_AMP_1G_D] = { -11, -8, -10, -8 },
+				[TX_AMP_100M_A] = { 4, 3, 4, 4 },
+				[TX_AMP_100M_B] = { 4, 4, 5, 4 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 0, 0, 0, 0 },
+				[R50_B] = { 0, 0, 0, 0 },
+				[R50_C] = { 0, 0, 0, 0 },
+				[R50_D] = { 0, 0, 0, 0 },
+			},
+		},
+	},
+	{ /* IC version 2 */
+		[TXMR] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -10, -12, -12, -11 },
+				[TX_AMP_TEST_B] = { -10, -11, -11, -10 },
+				[TX_AMP_TEST_C] = { -5, -7, -9, -3 },
+				[TX_AMP_TEST_D] = { -8, -10, -7, -8 },
+				[TX_AMP_1G_A] = { -12, -6, -6, 4 },
+				[TX_AMP_1G_B] = { -10, -5, -5, 1 },
+				[TX_AMP_1G_C] = { -5, -3, -4, 11 },
+				[TX_AMP_1G_D] = { -8, -3, -3, 4 },
+				[TX_AMP_100M_A] = { 2, 1, 0, 3 },
+				[TX_AMP_100M_B] = { 0, 0, 0, 3 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 15, 15, 15, 15 },
+				[R50_B] = { 15, 15, 18, 15 },
+				[R50_C] = { 4, 4, 4, 4 },
+				[R50_D] = { 4, 4, 4, 4 },
+			},
+		},
+		[DISCRETE] = {
+			{ },
+			{
+				[TX_AMP_TEST_A] = { -11, 11, -12, -7 },
+				[TX_AMP_TEST_B] = { -9, -10, -9, -8 },
+				[TX_AMP_TEST_C] = { -6, -3, -7, -4 },
+				[TX_AMP_TEST_D] = { -7, -8, -10, -7 },
+				[TX_AMP_1G_A] = { -11, -11, -12, -7 },
+				[TX_AMP_1G_B] = { -9, -10, -9, -8 },
+				[TX_AMP_1G_C] = { -6, -3, -7, -4 },
+				[TX_AMP_1G_D] = { -7, -8, -10, -7 },
+				[TX_AMP_100M_A] = { 2, 2, 1, 2 },
+				[TX_AMP_100M_B] = { 3, 3, 3, 3 },
+				[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+				[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+				[R50_A] = { 12, 12, 12, 12 },
+				[R50_B] = { 7, 7, 7, 7 },
+				[R50_C] = { 2, 2, 0, 0 },
+				[R50_D] = { 0, 0, 2, 2 },
+			},
+		},
+	},
+};
+
+static u8 an7581_zcal_to_r45ohm[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 127, 127, 127, 127, 127, 123, 119, 115, 112, 108, 104, 100,  96,
+	 94,  92,  88,  85,  82,  80,  76,  74,  72,  68,  66,  64,  62,  60,  56,  55,
+	 52,  50,  48,  46,  44,  42,  40,  39,  36,  35,  32,  32,  30,  28,  27,  25
+};
+
+static int airoha_cal_cycle(struct phy_device *phydev, int devad,
+			    u32 regnum, u16 mask, u16 cal_val)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	phy_modify_mmd(phydev, devad, regnum, mask, cal_val);
+
+	ret = mtk_cal_cycle_wait(phydev_p0);
+	phydev_dbg(phydev, "cal_val: 0x%x, ret: %d\n", cal_val, ret);
+
+	return ret;
+}
+
+static int airoha_rext_cal_sw(struct phy_device *phydev)
+{
+	int calibration_polarity;
+	u8 zcal_ctrl = 32;
+	int first_calib;
+	int ret;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0xc000);
+	/* tst_mode2 */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0xff, 0x2);
+
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0xff,
+			   GENMASK(15, 4) | GENMASK(1, 0));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN |
+		      MTK_PHY_RG_REXT_CALEN);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0);
+
+	phydev_dbg(phydev, "Start REXT SW cal.\n");
+	first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+
+	if (first_calib < 0) {
+		phydev_err(phydev, "REXT SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	/* If REXT calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If REXT calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > 0 &&
+	       zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl += calibration_polarity;
+
+		ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "REXT SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == 0 ||
+	    zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl = 32;
+		phydev_err(phydev, "REXT SW calibration saturation. Defaulting to %x.\n",
+			   zcal_ctrl);
+	}
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+		       MTK_PHY_RG_REXT_TRIM_MASK,
+		       FIELD_PREP(MTK_PHY_RG_REXT_TRIM_MASK, zcal_ctrl));
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_RG_BG_RASEL,
+		       MTK_PHY_RG_BG_RASEL_MASK,
+		       FIELD_PREP(MTK_PHY_RG_BG_RASEL_MASK, zcal_ctrl >> 3));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+
+	return 0;
+}
+
+static int airoha_tx_offset_cal_sw(struct phy_device *phydev, u8 txg_calen_x)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u16 dev1e_145_tmp, bmcr_tmp;
+	int calibration_polarity;
+	u16 reg_dac1, reg_dac2;
+	int zcal_ctrl = 0;
+	int first_calib;
+	u16 reg, mask;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+		      MTK_PHY_RG_TXVOS_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+		      MTK_PHY_RG_TXVOS_CALEN);
+
+	/* Force 1G full duplex for calibration */
+	bmcr_tmp = phy_read(phydev, MII_BMCR);
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	/* Force MDI */
+	dev1e_145_tmp = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x0145);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, 0x1010);
+
+	/* 1e_96[15]:bypass_tx_offset_cal, Hw bypass, Fw cal */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96,
+		      0x8000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e,
+		      0xf808);
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_A;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_A;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_A_MASK;
+		break;
+	case PAIR_B:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(8));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_B;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_B;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_A_B;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_B_MASK;
+		break;
+	case PAIR_C:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(4));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_C;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_C;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_C_MASK;
+		break;
+	case PAIR_D:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(0));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_D;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_D;
+		reg = MTK_PHY_RG_CR_TX_AMP_OFFSET_C_D;
+		mask = MTK_PHY_CR_TX_AMP_OFFSET_D_MASK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac1,
+		      0x8000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac2,
+		      0x8000);
+
+	phydev_dbg(phydev, "Start TX Offset SW cal.\n");
+
+	first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, mask,
+				       zcal_ctrl << __ffs(mask));
+
+	/* If TX Offset calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If TX Offset calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > -32 && zcal_ctrl < 32) {
+		u32 val;
+
+		zcal_ctrl += calibration_polarity;
+		if (zcal_ctrl >= 0)
+			val = zcal_ctrl;
+		else
+			/* BIT(5) signal negative number for TX Offset */
+			val = BIT(5) | abs(zcal_ctrl);
+
+		ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg, mask,
+				       val << __ffs(mask));
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "TX Offset calibration failed.\n");
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == -32 ||
+	    zcal_ctrl == 32) {
+		zcal_ctrl = 0;
+		phydev_err(phydev, "TX Offset SW calibration saturation. Defaulting to %x.\n",
+			   zcal_ctrl);
+
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg, mask,
+			       zcal_ctrl << __ffs(mask));
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, 0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, 0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0);
+
+	/* Restore BMCR */
+	phy_write(phydev, MII_BMCR, bmcr_tmp);
+
+	/* Restore MDI */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, dev1e_145_tmp);
+
+	return 0;
+}
+
+static int airoha_tx_amp_cal_sw(struct phy_device *phydev, u8 txg_calen_x)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	u16 mask_gbe, mask_tbt, mask_tst, mask_hbt;
+	u16 reg, reg_100, reg_dac1, reg_dac2;
+	struct phy_device *phydev_p0;
+	int calibration_polarity;
+	u8 zcal_ctrl = 32;
+	int first_calib;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x145, 0x1010);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1,
+		      MTK_PHY_RG_TXVOS_CALEN);
+
+	/* select 1V */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10);
+
+	/* enable Tx VLD*/
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xf808);
+
+	/* Force 1G full duplex for calibration */
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_A;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_A;
+		reg = MTK_PHY_TXVLD_DA_RG;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_A_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_A_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_A2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_A_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_A_TST_MASK;
+		break;
+	case PAIR_B:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(8));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_B;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_B;
+		reg = MTK_PHY_TX_I2MPB_TEST_MODE_B1;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_B_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_B_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_B2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_B_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_B_TST_MASK;
+		break;
+	case PAIR_C:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(4));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_C;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_C;
+		reg = MTK_PHY_TX_I2MPB_TEST_MODE_C1;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_C_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_C_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_C2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_C_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_C_TST_MASK;
+		break;
+	case PAIR_D:
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(0));
+		reg_dac1 = MTK_PHY_RG_DASN_DAC_IN0_D;
+		reg_dac2 = MTK_PHY_RG_DASN_DAC_IN1_D;
+		reg = MTK_PHY_TX_I2MPB_TEST_MODE_D1;
+		mask_gbe = MTK_PHY_DA_TX_I2MPB_D_GBE_MASK;
+		mask_tbt = MTK_PHY_DA_TX_I2MPB_D_TBT_MASK;
+		reg_100 = MTK_PHY_TX_I2MPB_TEST_MODE_D2;
+		mask_hbt = MTK_PHY_DA_TX_I2MPB_D_HBT_MASK;
+		mask_tst = MTK_PHY_DA_TX_I2MPB_D_TST_MASK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac1,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, reg_dac2,
+		      0x8000 | 0xf0);
+
+	phydev_dbg(phydev, "Start TX Amp SW cal.\n");
+	first_calib = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg,
+				       mask_gbe, zcal_ctrl << __ffs(mask_gbe));
+
+	/* If TX Amp calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If TX Amp calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > 0 &&
+	       zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl += calibration_polarity;
+
+		ret = airoha_cal_cycle(phydev, MDIO_MMD_VEND1, reg,
+				       mask_gbe, zcal_ctrl << __ffs(mask_gbe));
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "TX AMP calibration failed.\n");
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg,
+			       mask_gbe | mask_tbt,
+			       (zcal_ctrl << __ffs(mask_gbe)) |
+			       (zcal_ctrl << __ffs(mask_tbt)));
+
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == 0 ||
+	    zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK))
+		phydev_warn(phydev, "TX AMP SW calibration saturation.\n");
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg,
+		       mask_gbe | mask_tbt,
+		       (zcal_ctrl << __ffs(mask_gbe)) |
+		       (zcal_ctrl << __ffs(mask_tbt)));
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND1, reg_100,
+		       mask_hbt | mask_tst,
+		       (zcal_ctrl << __ffs(mask_hbt)) |
+		       (zcal_ctrl << __ffs(mask_tst)));
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, 0x145, 0x1000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0);
+
+	/* Restore AN enable */
+	phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+
+	return 0;
+}
+
+static int airoha_tx_r50_cal_sw(struct phy_device *phydev, u8 txg_calen_x)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u16 dev1e_145_tmp, bmcr_tmp;
+	int calibration_polarity;
+	u8 zcal_ctrl = 32;
+	int first_calib;
+	u16 reg;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* BG voltage output */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0xc000);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	/* select 1V */
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0,
+		      MTK_PHY_RG_CAL_CKINV | MTK_PHY_RG_ANA_CALEN);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	/* select 1V */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x10);
+
+	/* Force 1G full duplex for calibration */
+	bmcr_tmp = phy_read(phydev, MII_BMCR);
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	/* Force MDI */
+	dev1e_145_tmp = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x0145);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, 0x1010);
+
+	/* disable tx slew control */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0185, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00fb, 0x0100);
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG0,
+				 MTK_PHY_RG_ZCALEN_A);
+		break;
+	case PAIR_B:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG1,
+				 MTK_PHY_RG_ZCALEN_B);
+		break;
+	case PAIR_C:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG1,
+				 MTK_PHY_RG_ZCALEN_C);
+		break;
+	case PAIR_D:
+		phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+				 MTK_PHY_RG_ANA_CAL_RG1,
+				 MTK_PHY_RG_ZCALEN_D);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phydev_dbg(phydev, "Start TX r50 SW cal.\n");
+	first_calib = airoha_cal_cycle(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+
+	if (first_calib < 0) {
+		phydev_err(phydev, "TX r50 SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	/* If TX r50 calibration failed:
+	 * - increase dB until calibration succeed.
+	 * If TX r50 calibration succeeded:
+	 * - decrease dB until calibration fail to fine tune it.
+	 */
+	if (first_calib == 1)
+		calibration_polarity = -1;
+	else
+		calibration_polarity = 1;
+
+	while (zcal_ctrl > 0 &&
+	       zcal_ctrl < FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK)) {
+		zcal_ctrl += calibration_polarity;
+
+		ret = airoha_cal_cycle(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG5,
+				       MTK_PHY_RG_REXT_ZCAL_CTRL_MASK, zcal_ctrl);
+		/* Exit if we either failed or succeeded compared to the
+		 * first calibration result. (aka we finished fine tuning or
+		 * we succeeded with calibration)
+		 */
+		if (ret != first_calib)
+			break;
+	}
+
+	if (ret < 0) {
+		phydev_err(phydev, "TX r50 SW calibration failed.\n");
+		return -EINVAL;
+	}
+
+	if (zcal_ctrl == 0 ||
+	    zcal_ctrl == FIELD_MAX(MTK_PHY_RG_REXT_ZCAL_CTRL_MASK))
+		phydev_warn(phydev, "TX r50 SW calibration saturation.\n");
+
+	zcal_ctrl = shared->r50_cal_tbl[zcal_ctrl];
+
+	switch (txg_calen_x) {
+	case PAIR_A:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_A_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_A_B,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_A_MASK, reg);
+		break;
+	case PAIR_B:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_B_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_A_B,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_B_MASK, reg);
+		break;
+	case PAIR_C:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_C_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_C_D,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_C_MASK, reg);
+		break;
+	case PAIR_D:
+		reg = BIT(7) | FIELD_PREP(MTK_PHY_CR_TX_R50_OFFSET_D_MASK, zcal_ctrl);
+		phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			       MTK_PHY_RG_CR_TX_R50_OFFSET_C_D,
+			       BIT(7) | MTK_PHY_CR_TX_R50_OFFSET_D_MASK, reg);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG0, 0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG1, 0);
+
+	/* Enable tx slew control */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0185, 0x0001);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00fb, 0x0000);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x0);
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6, 0x0);
+
+	/* Restore BMCR */
+	phy_write(phydev, MII_BMCR, bmcr_tmp);
+
+	/* Restore MDI */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0145, dev1e_145_tmp);
+
+	return 0;
+}
+
+static int airoha_rx_offset_cal_sw(struct phy_device *phydev)
+{
+	/* Hw bypass, Fw cal */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x96, 0x8000);
+	/* tx/rx_cal_criteria_value */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x37, 0x0033);
+
+	/* rx offset cal by Hw setup */
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39,
+			   BIT(14) | BIT(11));
+
+	/* disable rtune calibration */
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, 0x107,
+			   BIT(12));
+
+	/* bypass tx/rx dc offset cancellation process */
+	phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171,
+			 BIT(8) | BIT(7));
+
+	/* rx offset calibration start */
+	phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39,
+			 BIT(13));
+
+	/* rx offset calibration stop */
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x39,
+			   BIT(13));
+
+	mdelay(10);
+
+	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171,
+			   BIT(8) | BIT(7));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6,
+		      0x1);
+
+	return 0;
+}
+
+static int airoha_cal_sw(struct phy_device *phydev, enum CAL_ITEM cal_item,
+			 u8 start_pair, u8 end_pair)
+{
+	u8 pair_n;
+	int ret;
+
+	for (pair_n = start_pair; pair_n <= end_pair; pair_n++) {
+		switch (cal_item) {
+		case REXT:
+			ret = airoha_rext_cal_sw(phydev);
+			break;
+		case TX_OFFSET:
+			ret = airoha_tx_offset_cal_sw(phydev, pair_n);
+			break;
+		case TX_AMP:
+			ret = airoha_tx_amp_cal_sw(phydev, pair_n);
+			break;
+		case TX_R50:
+			ret = airoha_tx_r50_cal_sw(phydev, pair_n);
+			break;
+		case RX_OFFSET:
+			ret = airoha_rx_offset_cal_sw(phydev);
+			break;
+		default:
+			return -EINVAL;
+		}
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int airoha_start_cal(struct phy_device *phydev, enum CAL_ITEM cal_item,
+			    enum CAL_MODE cal_mode, u8 start_pair,
+			    u8 end_pair, u32 *buf)
+{
+	int ret;
+
+	switch (cal_mode) {
+	case SW_M:
+		ret = airoha_cal_sw(phydev, cal_item, start_pair, end_pair);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ret) {
+		phydev_err(phydev, "cal %d failed\n", cal_item);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int airoha_phy_calib(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+
+	/* PreCalibrate Set */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x5c, 0x6666);
+
+	if (!shared->rext_sw_calib_done) {
+		ret = airoha_start_cal(phydev, REXT, SW_M, NO_PAIR, NO_PAIR, NULL);
+		if (ret)
+			return ret;
+
+		shared->rext_sw_calib_done = true;
+	}
+
+	ret = airoha_start_cal(phydev, TX_R50, SW_M, PAIR_A, PAIR_D, NULL);
+	if (ret)
+		return ret;
+
+	ret = airoha_start_cal(phydev, TX_OFFSET, SW_M, PAIR_A, PAIR_D, NULL);
+	if (ret)
+		return ret;
+
+	ret = airoha_start_cal(phydev, TX_AMP, SW_M, PAIR_A, PAIR_D, NULL);
+	if (ret)
+		return ret;
+
+	ret = airoha_start_cal(phydev, RX_OFFSET, SW_M, NO_PAIR, NO_PAIR, NULL);
+	if (ret)
+		return ret;
+
+	/* Gating, short with other pair */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x15, 0x0);
+
+	phy_write_mmd(phydev_p0, MDIO_MMD_VEND2, 0x100, 0x0);
+
+	return 0;
+}
+
+static int airoha_phy_auto_select_transformer(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u8 phy_offset;
+	u16 bmcr_tmp;
+	u16 val;
+	int i;
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	/* Force 1G full duplex for calibration */
+	bmcr_tmp = phy_read(phydev, MII_BMCR);
+	phy_write(phydev, MII_BMCR, BMCR_FULLDPLX | BMCR_SPEED1000);
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x271, GENMASK(4, 0),
+		       FIELD_PREP(GENMASK(4, 0), 0x3));
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x269, GENMASK(15, 12),
+		       FIELD_PREP(GENMASK(15, 12), 0x2));
+
+	phy_modify_mmd(phydev, MDIO_MMD_VEND2, 0x26f, GENMASK(14, 12),
+		       FIELD_PREP(GENMASK(14, 12), 0x0));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, BIT(12));
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D,
+		      0x8000 | 0xf0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D,
+		      0x8000 | 0xf0);
+
+	/* Force pGA gain */
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x10,
+			GENMASK(20, 15),
+			FIELD_PREP(GENMASK(20, 15), 0x1f));
+
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x11,
+			GENMASK(23, 18) | GENMASK(15, 10) | GENMASK(7, 2),
+			FIELD_PREP(GENMASK(23, 18), 0x1f) |
+			FIELD_PREP(GENMASK(15, 10), 0x1f) |
+			FIELD_PREP(GENMASK(7, 2), 0x1f));
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xc9, 0xffff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x151, 0x12);
+
+	switch (phy_offset) {
+	case 0:
+		val = 0x1;
+		break;
+	case 1:
+		val = 0x3;
+		break;
+	case 2:
+		val = 0x5;
+		break;
+	case 3:
+		val = 0x7;
+		break;
+	default:
+		return -EINVAL;
+	}
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x15,
+		      FIELD_PREP(GENMASK(14, 12), val) | 0x161);
+	mdelay(500);
+
+	for (i = 0; i < 3; i++)
+		val = phy_read_mmd(phydev, MDIO_MMD_VEND2, 0x1a);
+
+	/* Check the value and report transformer type for later */
+	shared->transformer_type[phy_offset] = val < 0xe0;
+
+	/* Switch to Default */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x15, 0x1000);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xdd, 0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_A,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_A,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_B,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_B,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_C,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_C,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN0_D,
+		      0x0);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_DASN_DAC_IN1_D,
+		      0x0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xc9, 0xfff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x151, 0x0);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x10,
+			GENMASK(20, 15),
+			FIELD_PREP(GENMASK(20, 15), 0x4));
+
+	__mtk_tr_modify(phydev, 0x1, 0xf, 0x11,
+			GENMASK(23, 18) | GENMASK(15, 10) | GENMASK(7, 2),
+			FIELD_PREP(GENMASK(23, 18), 0x0) |
+			FIELD_PREP(GENMASK(15, 10), 0x0) |
+			FIELD_PREP(GENMASK(7, 2), 0x0));
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	/* Restore BMCR */
+	phy_write(phydev, MII_BMCR, bmcr_tmp);
+
+	return 0;
+}
+
+static int airoha_phy_tx_amp_compensation(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+
+	u16 reg_1e_12_read;
+	u16 reg_1e_16_read;
+	u16 reg_1e_17_read;
+	u16 reg_1e_18_read;
+	u16 reg_1e_19_read;
+	u16 reg_1e_20_read;
+	u16 reg_1e_21_read;
+	u16 reg_1e_22_read;
+	u16 reg_1e_174_read;
+	u16 reg_1e_175_read;
+
+	int reg_1e_12_header;
+	int reg_1e_16_header;
+	int reg_1e_17_header;
+	int reg_1e_18_header;
+	int reg_1e_19_header;
+	int reg_1e_21_header;
+	int reg_1e_174_header;
+	int reg_1e_175_header;
+
+	int reg_1e_12_end;
+	int reg_1e_16_end;
+	int reg_1e_17_end;
+	int reg_1e_18_end;
+	int reg_1e_20_end;
+	int reg_1e_22_end;
+	int reg_1e_174_end;
+	int reg_1e_175_end;
+
+	struct phy_device *phydev_p0;
+	u8 phy_offset;
+
+	int txamp_low_limit = -3;
+	int txamp_high_limit = 0x42;
+
+	int r50_low_limit = -8;
+	int r50_high_limit = 0x88;
+
+	bool overflow;
+	int (*tx_amp_table)[CALIB_CONST_TYPE_MAX][4];
+	int transformer_type, mdi_resister_type;
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	transformer_type = shared->transformer_type[phy_offset];
+	mdi_resister_type = shared->mdi_resister_type;
+	tx_amp_table = shared->tx_amp_compensation_tbl[transformer_type][mdi_resister_type];
+
+	reg_1e_12_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x12);
+	reg_1e_16_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x16);
+	reg_1e_17_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x17);
+	reg_1e_18_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x18);
+	reg_1e_19_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x19);
+	reg_1e_20_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x20);
+	reg_1e_21_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x21);
+	reg_1e_22_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x22);
+	reg_1e_174_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x174);
+	reg_1e_175_read = phy_read_mmd(phydev, MDIO_MMD_VEND1, 0x175);
+
+	reg_1e_12_header = ((reg_1e_12_read & 0xfc00) / 1024) + *tx_amp_table[TX_AMP_1G_A][phy_offset];
+	reg_1e_17_header = ((reg_1e_17_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_1G_B][phy_offset];
+	reg_1e_19_header = ((reg_1e_19_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_1G_C][phy_offset];
+	reg_1e_21_header = ((reg_1e_21_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_1G_D][phy_offset];
+
+	overflow = false;
+	if (reg_1e_12_header < txamp_low_limit ||
+	    reg_1e_12_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_17_header < txamp_low_limit ||
+	    reg_1e_17_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_19_header < txamp_low_limit ||
+	    reg_1e_19_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_21_header < txamp_low_limit ||
+	    reg_1e_21_header > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_12_header = 0x20;
+		reg_1e_17_header = 0x20;
+		reg_1e_19_header = 0x20;
+		reg_1e_21_header = 0x20;
+	} else {
+		if (reg_1e_12_header < 0)
+			reg_1e_12_header = 0;
+		else if (reg_1e_12_header > 0x3f)
+			reg_1e_12_header = 0x3f;
+		if (reg_1e_17_header < 0)
+			reg_1e_17_header = 0;
+		else if (reg_1e_17_header > 0x3f)
+			reg_1e_17_header = 0x3f;
+		if (reg_1e_19_header < 0)
+			reg_1e_19_header = 0;
+		else if (reg_1e_19_header > 0x3f)
+			reg_1e_19_header = 0x3f;
+		if (reg_1e_21_header < 0)
+			reg_1e_21_header = 0;
+		else if (reg_1e_21_header > 0x3f)
+			reg_1e_21_header = 0x3f;
+	}
+
+	reg_1e_16_end = (reg_1e_16_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_A][phy_offset];
+	reg_1e_18_end = (reg_1e_18_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_B][phy_offset];
+	reg_1e_20_end = (reg_1e_20_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_C][phy_offset];
+	reg_1e_22_end = (reg_1e_22_read & 0x003f) + *tx_amp_table[TX_AMP_TEST_D][phy_offset];
+
+	overflow = false;
+	if (reg_1e_16_end < txamp_low_limit ||
+	    reg_1e_16_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_18_end < txamp_low_limit ||
+	    reg_1e_18_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_20_end < txamp_low_limit ||
+	    reg_1e_20_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_22_end < txamp_low_limit ||
+	    reg_1e_22_end > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_16_end = 0x20;
+		reg_1e_18_end = 0x20;
+		reg_1e_20_end = 0x20;
+		reg_1e_22_end = 0x20;
+	} else {
+		if (reg_1e_16_end < 0)
+			reg_1e_16_end = 0;
+		else if (reg_1e_16_end > 0x3f)
+			reg_1e_16_end = 0x3f;
+		if (reg_1e_18_end < 0)
+			reg_1e_18_end = 0;
+		else if (reg_1e_18_end > 0x3f)
+			reg_1e_18_end = 0x3f;
+		if (reg_1e_20_end < 0)
+			reg_1e_20_end = 0;
+		else if (reg_1e_20_end > 0x3f)
+			reg_1e_20_end = 0x3f;
+		if (reg_1e_22_end < 0)
+			reg_1e_22_end = 0;
+		else if (reg_1e_22_end > 0x3f)
+			reg_1e_22_end = 0x3f;
+	}
+
+	reg_1e_16_header = ((reg_1e_16_read & 0xfc00) / 1024) + *tx_amp_table[TX_AMP_100M_A][phy_offset];
+	reg_1e_18_header = ((reg_1e_18_read & 0x3f00) / 256) + *tx_amp_table[TX_AMP_100M_B][phy_offset];
+
+	overflow = false;
+	if (reg_1e_16_header < txamp_low_limit ||
+	    reg_1e_16_header > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_18_header < txamp_low_limit ||
+	    reg_1e_18_header > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_16_header = 0x20;
+		reg_1e_18_header = 0x20;
+	} else {
+		if (reg_1e_16_header < 0)
+			reg_1e_16_header = 0;
+		else if (reg_1e_16_header > 0x3f)
+			reg_1e_16_header = 0x3f;
+		if (reg_1e_18_header < 0)
+			reg_1e_18_header = 0;
+		else if (reg_1e_18_header > 0x3f)
+			reg_1e_18_header = 0x3f;
+	}
+
+	reg_1e_12_end = (reg_1e_12_read & 0x003f) + *tx_amp_table[TX_AMP_10M_A][phy_offset];
+	reg_1e_17_end = (reg_1e_17_read & 0x003f) + *tx_amp_table[TX_AMP_10M_B][phy_offset];
+
+	overflow = false;
+	if (reg_1e_12_end < txamp_low_limit ||
+	    reg_1e_12_end > txamp_high_limit)
+		overflow = true;
+	if (reg_1e_17_end < txamp_low_limit ||
+	    reg_1e_17_end > txamp_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_12_end = 0x20;
+		reg_1e_17_end = 0x20;
+	} else {
+		if (reg_1e_12_end < 0)
+			reg_1e_12_end = 0;
+		else if (reg_1e_12_end > 0x3f)
+			reg_1e_12_end = 0x3f;
+		if (reg_1e_17_end < 0)
+			reg_1e_17_end = 0;
+		else if (reg_1e_17_end > 0x3f)
+			reg_1e_17_end = 0x3f;
+	}
+
+	reg_1e_174_header = ((reg_1e_174_read & 0x7f00) / 256) + *tx_amp_table[R50_A][phy_offset];
+	reg_1e_174_end = (reg_1e_174_read & 0x007f) + *tx_amp_table[R50_B][phy_offset];
+	reg_1e_175_header = ((reg_1e_175_read & 0x7f00) / 256) + *tx_amp_table[R50_C][phy_offset];
+	reg_1e_175_end = (reg_1e_175_read & 0x007f) + *tx_amp_table[R50_D][phy_offset];
+
+	overflow = false;
+	if (reg_1e_174_header < r50_low_limit ||
+	    reg_1e_174_header > r50_high_limit)
+		overflow = true;
+	if (reg_1e_174_end < r50_low_limit ||
+	    reg_1e_174_end > r50_high_limit)
+		overflow = true;
+	if (reg_1e_175_header < r50_low_limit ||
+	    reg_1e_175_header > r50_high_limit)
+		overflow = true;
+	if (reg_1e_175_end < r50_low_limit ||
+	    reg_1e_175_end > r50_high_limit)
+		overflow = true;
+
+	if (overflow) {
+		reg_1e_174_header = 0x34;
+		reg_1e_174_end = 0x34;
+		reg_1e_175_header = 0x34;
+		reg_1e_175_end = 0x34;
+	} else {
+		if (reg_1e_174_header < 0)
+			reg_1e_174_header = 0;
+		else if (reg_1e_174_header > 0x7f)
+			reg_1e_174_header = 0x7f;
+		if (reg_1e_174_end < 0)
+			reg_1e_174_end = 0;
+		else if (reg_1e_174_end > 0x7f)
+			reg_1e_174_end = 0x7f;
+		if (reg_1e_175_header < 0)
+			reg_1e_175_header = 0;
+		else if (reg_1e_175_header > 0x7f)
+			reg_1e_175_header = 0x7f;
+		if (reg_1e_175_end < 0)
+			reg_1e_175_end = 0;
+		else if (reg_1e_175_end > 0x7f)
+			reg_1e_175_end = 0x7f;
+	}
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x12,
+		      (reg_1e_12_header * 1024) + reg_1e_12_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x16,
+		      (reg_1e_16_header * 1024) + reg_1e_16_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x17,
+		      (reg_1e_17_header * 256) + reg_1e_17_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x18,
+		      (reg_1e_18_header * 256) + reg_1e_18_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x19,
+		      (reg_1e_19_read & 0x00ff) + (reg_1e_19_header * 256));
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x20,
+		      (reg_1e_20_read & 0xff00) + reg_1e_20_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x21,
+		      (reg_1e_21_read & 0x00ff) + (reg_1e_21_header * 256));
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x22,
+		      (reg_1e_22_read & 0xff00) + reg_1e_22_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x174,
+		      0x8080 + (reg_1e_174_header * 256) + reg_1e_174_end);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x175,
+		      0x8080 + (reg_1e_175_header * 256) + reg_1e_175_end);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x176, 0x4400);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x177, 0x0044);
+
+	return 0;
+}
+
+static int an7581_phy_config_init(struct phy_device *phydev)
+{
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u32 soc_pdidr;
+	u8 phy_offset;
+	int ret;
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	/* FIXME: Read SoC PDIDR if available or default to 1 */
+	soc_pdidr = 1;
+
+	shared->mdi_resister_type = MDI_5R;
+	if (soc_pdidr == 1)
+		shared->tx_amp_compensation_tbl = &an7581_tx_amp_compensation_tbl[0];
+	else
+		shared->tx_amp_compensation_tbl = &an7581_tx_amp_compensation_tbl[1];
+	shared->r50_cal_tbl = an7581_zcal_to_r45ohm;
+
+	ret = airoha_phy_calib(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_auto_select_transformer(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_tx_amp_compensation(phydev);
+	if (ret)
+		return ret;
+
+	/* RX setting for 5R_TXMR before AN setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe6, 0x1111);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe7, 0x5555);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe9, 0x0001);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xfe, 0x0000);
+
+	/* Long Loop Reach setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x11, 0xff00);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x202, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x203, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x204, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x205, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x206, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x207, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x208, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x209, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20a, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20b, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20e, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x20f, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x210, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x211, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x212, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x213, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x214, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x215, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x216, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x217, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21a, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21b, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21c, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21d, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21e, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x21f, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x220, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x221, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x222, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x223, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x226, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x227, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x228, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x229, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22a, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22b, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22c, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22d, 0x0025);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22e, 0x2310);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x22f, 0x0025);
+
+	/* RX Setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3c, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3d, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+
+	/* EEE setting */
+	phy_write_mmd(phydev, MDIO_MMD_AN, 0x3c, 0x0000);
+
+	/* RX */
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x41, 0x3333);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x44, 0x00c0);
+
+	/* 10M settings */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a3, 0x00d2);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a4, 0x010e);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27b, 0x1177);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27c, 0x0808);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1);
+	__phy_write(phydev, 0x14, 0x3a18);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write(phydev, 0x9, 0x1e00);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x2114);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26a, 0x1113);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26f, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x2c63);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x0c2b);
+
+	/* EEE set because power down can't set tokenring */
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+	__mtk_tr_write(phydev, 0x0, 0x7, 0x15, 0x0055a0);
+	__mtk_tr_write(phydev, 0x0, 0x7, 0x17, 0x07ff3f);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x0, 0x00001e);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x1, 0x6fb90a);
+	__mtk_tr_write(phydev, 0x1, 0xf, 0x18, 0x0e2f00);
+	__mtk_tr_write(phydev, 0x1, 0xd, 0x26, 0x444444);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x3, 0x000004);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x6, 0x2ebaef);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x8, 0x00000b);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0xc, 0x00504d);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0xd, 0x02314f);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0xf, 0x003028);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x10, 0x00000a);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x11, 0x040001);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x14, 0x00024a);
+	__mtk_tr_write(phydev, 0x2, 0xd, 0x1c, 0x003210);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x120, 0x8014);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x122, 0xffff);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x144, 0x0200);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x14a, 0xee20);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x19b, 0x0111);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x234, 0x1181);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x238, 0x0120);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x239, 0x0117);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x2d1, 0x0733);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x323, 0x0011);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x324, 0x013f);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x326, 0x0037);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x268, 0x07f4);
+
+	if (shared->transformer_type[phy_offset] == TXMR) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0187);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cc);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0109);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x020b);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0202);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03c5);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c2);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0309);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x000e);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0002);
+
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0880);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0880);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0880);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0880);
+	} else {
+		if (soc_pdidr == 2) {
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x269, 0x1114);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26a, 0x1113);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x26f, 0x2000);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x4c88);
+			phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x272, 0x0c2b);
+
+			/* 100M sharp */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0190);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01c8);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0030);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0208);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0000);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0318);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03c8);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0230);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x0008);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0200);
+
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0885);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0885);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0885);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0885);
+		} else {
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0190);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cf);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0030);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x020f);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0000);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0318);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03cf);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c0);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x09, 0x0230);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x000f);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0200);
+
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0003);
+		}
+	}
+
+	return 0;
+}
+
 static int an7581_phy_probe(struct phy_device *phydev)
 {
+	struct airoha_socphy_shared *shared;
 	struct mtk_socphy_priv *priv;
 	struct pinctrl *pinctrl;
+	int ret;
 
 	/* Toggle pinctrl to enable PHY LED */
 	pinctrl = devm_pinctrl_get_select(&phydev->mdio.dev, "gbe-led");
@@ -18,10 +1495,18 @@ static int an7581_phy_probe(struct phy_device *phydev)
 		dev_err(&phydev->mdio.bus->dev,
 			"Failed to setup PHY LED pinctrl\n");
 
+	ret = devm_phy_package_join(&phydev->mdio.dev, phydev, 0,
+				    sizeof(struct airoha_socphy_shared));
+	if (ret)
+		return ret;
+
 	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
 		return -ENOMEM;
 
+	if (phydev->mdio.addr == AIROHA_DEFAULT_PORT0_ADDR)
+		shared->phydev_p0 = phydev;
+
 	phydev->priv = priv;
 
 	return 0;
@@ -63,6 +1548,7 @@ static struct phy_driver mtk_socphy_driver[] = {
 	{
 		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7581),
 		.name		= "Airoha AN7581 PHY",
+		.config_init	= an7581_phy_config_init,
 		.config_intr	= genphy_no_config_intr,
 		.handle_interrupt = genphy_handle_interrupt_no_ack,
 		.probe		= an7581_phy_probe,
@@ -72,6 +1558,8 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.led_hw_control_set = mt798x_phy_led_hw_control_set,
 		.led_hw_control_get = mt798x_phy_led_hw_control_get,
 		.led_polarity_set = an7581_phy_led_polarity_set,
+		.read_page	= mtk_phy_read_page,
+		.write_page	= mtk_phy_write_page,
 	},
 	{
 		PHY_ID_MATCH_EXACT(AIROHA_GPHY_ID_AN7583),
@@ -98,5 +1586,6 @@ static const struct mdio_device_id __maybe_unused airoha_socphy_tbl[] = {
 MODULE_DESCRIPTION("Airoha SoC Gigabit Ethernet PHY driver");
 MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
 MODULE_LICENSE("GPL");
+MODULE_VERSION("01.01.20260306");
 
 MODULE_DEVICE_TABLE(mdio, airoha_socphy_tbl);
diff --git a/drivers/net/phy/mediatek/mtk-ge-soc.h b/drivers/net/phy/mediatek/mtk-ge-soc.h
index 507e68343576..5e9d24a9833c 100644
--- a/drivers/net/phy/mediatek/mtk-ge-soc.h
+++ b/drivers/net/phy/mediatek/mtk-ge-soc.h
@@ -12,6 +12,7 @@
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_STANDARD			0x0000
+#define MTK_PHY_PAGE_EXTENDED_1			0x0001
 #define MTK_PHY_PAGE_EXTENDED_3			0x0003
 
 #define MTK_PHY_LPI_REG_14			0x14
@@ -177,6 +178,7 @@
 #define MTK_PHY_RG_ANA_CAL_RG0			0xdb
 #define   MTK_PHY_RG_CAL_CKINV			BIT(12)
 #define   MTK_PHY_RG_ANA_CALEN			BIT(8)
+#define   MTK_PHY_RG_REXT_CALEN			BIT(4)
 #define   MTK_PHY_RG_ZCALEN_A			BIT(0)
 
 #define MTK_PHY_RG_ANA_CAL_RG1			0xdc
@@ -187,6 +189,9 @@
 
 #define MTK_PHY_RG_ANA_CAL_RG5			0xe0
 #define   MTK_PHY_RG_REXT_TRIM_MASK		GENMASK(13, 8)
+#define   MTK_PHY_RG_REXT_ZCAL_CTRL_MASK	GENMASK(5, 0)
+
+#define MTK_PHY_RG_ANA_CAL_RG6			0xe1
 
 #define MTK_PHY_RG_TX_FILTER			0xfe
 
@@ -208,6 +213,14 @@
 #define   MTK_PHY_CR_TX_AMP_OFFSET_C_MASK	GENMASK(13, 8)
 #define   MTK_PHY_CR_TX_AMP_OFFSET_D_MASK	GENMASK(6, 0)
 
+#define MTK_PHY_RG_CR_TX_R50_OFFSET_A_B		0x174
+#define   MTK_PHY_CR_TX_R50_OFFSET_A_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_R50_OFFSET_B_MASK	GENMASK(6, 0)
+
+#define MTK_PHY_RG_CR_TX_R50_OFFSET_C_D		0x175
+#define   MTK_PHY_CR_TX_R50_OFFSET_C_MASK	GENMASK(13, 8)
+#define   MTK_PHY_CR_TX_R50_OFFSET_D_MASK	GENMASK(6, 0)
+
 #define MTK_PHY_RG_AD_CAL_COMP			0x17a
 #define   MTK_PHY_AD_CAL_COMP_OUT_MASK		GENMASK(8, 8)
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH net-next 5/5] net: phy: mediatek: add calibration logic for AN7583
From: Christian Marangi @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Christian Marangi,
	Daniel Golle, Qingfang Deng, SkyLake Huang, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-kernel, netdev,
	linux-arm-kernel, linux-mediatek
In-Reply-To: <20260708102341.53919-1-ansuelsmth@gmail.com>

Similar to Airoha AN7581 Switch PHY also the Airoha AN7583 SoC
requires dedicated calibration and tuneup to make EEE correctly work.

Airoha AN7583 differ from AN7581 by having a dedicated table for
calibration and additional EEE logic tuneup.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
---
 drivers/net/phy/mediatek/airoha-ge-soc.c | 227 ++++++++++++++++++++++-
 drivers/net/phy/mediatek/mtk.h           |   1 +
 2 files changed, 225 insertions(+), 3 deletions(-)

diff --git a/drivers/net/phy/mediatek/airoha-ge-soc.c b/drivers/net/phy/mediatek/airoha-ge-soc.c
index bc487b093cfa..601a64d83365 100644
--- a/drivers/net/phy/mediatek/airoha-ge-soc.c
+++ b/drivers/net/phy/mediatek/airoha-ge-soc.c
@@ -159,6 +159,106 @@ static u8 an7581_zcal_to_r45ohm[64] = {
 	 52,  50,  48,  46,  44,  42,  40,  39,  36,  35,  32,  32,  30,  28,  27,  25
 };
 
+/*
+ * 2 chip revision
+ * TXMR or discrete
+ * 2 MDI type
+ * TX AMP test/TX AMP 1G/TX AMP 100M/TX AMP 10M/R50
+ * 4 PHY
+ */
+static int an7583_tx_amp_compensation_tbl[TRANSFORMER_TYPE_MAX][MDI_TYPE_MAX][CALIB_CONST_TYPE_MAX][4] = {
+	[TXMR] = {
+		[MDI_0R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { 4, 4, 0, 0 },
+			[R50_B] = { 4, 4, 0, 0 },
+			[R50_C] = { 5, 5, 3, 4 },
+			[R50_D] = { 4, 4, 4, 0 },
+		},
+		[MDI_5R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { -4, -4, -4, -4 },
+			[R50_B] = { -4, -4, -4, -4 },
+			[R50_C] = { -2, -2, -2, -2 },
+			[R50_D] = { -3, -3, -2, -2 },
+		},
+	},
+	[DISCRETE] = {
+		[MDI_0R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { 4, 4, 4, 4 },
+			[R50_B] = { 4, 4, 4, 4 },
+			[R50_C] = { 6, 6, 6, 6 },
+			[R50_D] = { 4, 4, 4, 4 },
+		},
+		[MDI_5R] = {
+			[TX_AMP_TEST_A] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_B] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_C] = { -3, -3, -3, -3 },
+			[TX_AMP_TEST_D] = { -3, -3, -3, -3 },
+			[TX_AMP_1G_A] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_B] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_C] = { 0, 0, 0, 0 },
+			[TX_AMP_1G_D] = { 0, 0, 0, 0 },
+			[TX_AMP_100M_A] = { 4, 4, 4, 4 },
+			[TX_AMP_100M_B] = { 4, 4, 4, 4 },
+			[TX_AMP_10M_A] = { 0, 0, 0, 0 },
+			[TX_AMP_10M_B] = { 0, 0, 0, 0 },
+			[R50_A] = { 0, 0, 0, 0 },
+			[R50_B] = { 0, 0, 0, 0 },
+			[R50_C] = { 0, 0, 0, 0 },
+			[R50_D] = { 0, 0, 0, 0 },
+		},
+	},
+};
+
+static u8 an7583_zcal_to_r50ohm_0R[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 122, 118, 114, 110, 106, 102,  98,  96,  92,  88,  85,  82,  80,
+	 76,  72,  70,  68,  64,  62,  60,  57,  55,  52,  50,  48,  46,  44,  41,  40,
+	 38,  36,  33,  32,  30,  28,  26,  24,  24,  22,  20,  18,  16,  16,  14,  12,
+};
+
+static u8 an7583_zcal_to_r50ohm_5R[64] = {
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+	127, 127, 127, 127, 127, 127, 127, 127, 127, 124, 120, 116, 112, 110, 106, 102,
+	 99,  96,  93,  90,  88,  84,  81,  79,  76,  73,  71,  68,  66,  64,  61,  59,
+	 56,  54,  52,  50,  48,  46,  44,  42,  40,  39,  37,  36,  33,  32,  31,  29,
+};
+
 static int airoha_cal_cycle(struct phy_device *phydev, int devad,
 			    u32 regnum, u16 mask, u16 cal_val)
 {
@@ -766,8 +866,9 @@ static int airoha_rx_offset_cal_sw(struct phy_device *phydev)
 	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, 0x171,
 			   BIT(8) | BIT(7));
 
-	phy_write_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_RG_ANA_CAL_RG6,
-		      0x1);
+	if (phydev->drv->phy_id == AIROHA_GPHY_ID_AN7581)
+		phy_write_mmd(phydev, MDIO_MMD_VEND1,
+			      MTK_PHY_RG_ANA_CAL_RG6, 0x1);
 
 	return 0;
 }
@@ -1540,8 +1641,126 @@ static int an7581_phy_led_polarity_set(struct phy_device *phydev, int index,
 
 static int an7583_phy_config_init(struct phy_device *phydev)
 {
+	struct airoha_socphy_shared *shared = phy_package_get_priv(phydev);
+	struct phy_device *phydev_p0;
+	u8 phy_offset;
+	int ret;
+
 	/* BMCR_PDOWN is enabled by default */
-	return phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+	phy_clear_bits(phydev, MII_BMCR, BMCR_PDOWN);
+
+	phydev_p0 = shared->phydev_p0;
+	phy_offset = phydev->mdio.addr - phydev_p0->mdio.addr;
+
+	/* FIXME: Read SoC MDI Resister Type if available or default to 5R */
+	shared->mdi_resister_type = MDI_5R;
+
+	shared->mdi_resister_type = MDI_5R;
+	if (shared->mdi_resister_type == MDI_0R)
+		shared->r50_cal_tbl = an7583_zcal_to_r50ohm_0R;
+	if (shared->mdi_resister_type == MDI_5R)
+		shared->r50_cal_tbl = an7583_zcal_to_r50ohm_5R;
+	shared->tx_amp_compensation_tbl = &an7583_tx_amp_compensation_tbl;
+
+	ret = airoha_phy_calib(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_auto_select_transformer(phydev);
+	if (ret)
+		return ret;
+
+	ret = airoha_phy_tx_amp_compensation(phydev);
+	if (ret)
+		return ret;
+
+	/* Enable Idle Mode Power Setting */
+	if (phy_offset == 0) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x268, 0x07F1);
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x703, 0x3111);
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x271, 0x3C24);
+		phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x701, 0x1023);
+	}
+
+	if (shared->transformer_type[phy_offset] == TXMR) {
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0881);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0881);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0881);
+		phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0881);
+
+		if (shared->mdi_resister_type == MDI_0R)
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0205);
+		else
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0200);
+	} else {
+		if (shared->mdi_resister_type == MDI_0R) {
+			/* RX setting for 5R_TXMR before AN setting */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe7, 0x6666);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe9, 0x0003);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xfe, 0x0006);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xe6, 0x1111);
+			/* 100M sharp */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0c86);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cb);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0108);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0211);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0205);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03ce);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c8);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0005);
+		} else {
+			/* 100M sharp */
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x23, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x24, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x25, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x26, 0x0886);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x00, 0x0195);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x01, 0x01cb);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x02, 0x01c2);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x03, 0x0108);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x04, 0x0211);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x05, 0x0205);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x06, 0x0387);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x07, 0x03ce);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x08, 0x03c3);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0a, 0x0010);
+			phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x0b, 0x0005);
+		}
+	}
+
+	/* RX Setting */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3c, 0xc000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3d, 0x0000);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x3e, 0xc000);
+
+	/* EEE setting */
+	phy_write_mmd(phydev, MDIO_MMD_AN, 0x3c, 0x0000);
+
+	/* 10M settings */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a3, 0x00d2);
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x1a4, 0x010e);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27b, 0x1177);
+	phy_write_mmd(phydev, MDIO_MMD_VEND2, 0x27c, 0x0808);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_3);
+	__phy_write(phydev, 0x14, 0x190);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1);
+	__phy_write(phydev, 0x14, 0x3a18);
+	phy_restore_page(phydev, MTK_PHY_PAGE_STANDARD, 0);
+
+	phy_write(phydev, 0x9, 0x0600);
+
+	/* EEE keep only Pair A ON */
+	phy_write_mmd(phydev, MDIO_MMD_VEND1, 0x630, 0x006E);
+
+	return 0;
 }
 
 static struct phy_driver mtk_socphy_driver[] = {
@@ -1572,6 +1791,8 @@ static struct phy_driver mtk_socphy_driver[] = {
 		.led_hw_control_set = mt798x_phy_led_hw_control_set,
 		.led_hw_control_get = mt798x_phy_led_hw_control_get,
 		.led_polarity_set = an7581_phy_led_polarity_set,
+		.read_page	= mtk_phy_read_page,
+		.write_page	= mtk_phy_write_page,
 	},
 };
 
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
index c690aaf38124..cd9d4c5550c3 100644
--- a/drivers/net/phy/mediatek/mtk.h
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -13,6 +13,7 @@
 
 #define MTK_EXT_PAGE_ACCESS			0x1f
 #define MTK_PHY_PAGE_EXTENDED_1			0x0001
+#define MTK_PHY_PAGE_EXTENDED_3			0x0003
 #define MTK_PHY_PAGE_STANDARD			0x0000
 #define MTK_PHY_PAGE_EXTENDED_52B5		0x52b5
 
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH net-next v6 3/3] net: libwx: support vf per-queue statistics via ethtool -S
From: mengyuanlou @ 2026-07-08 10:23 UTC (permalink / raw)
  To: Paolo Abeni; +Cc: netdev, jiawenwu, duanqiangwen, horms, kuba
In-Reply-To: <20260707095046.104606-1-pabeni@redhat.com>



> 2026年7月7日 17:50,Paolo Abeni <pabeni@redhat.com> 写道:
> 
> From: AI Reviewer <ai@example.com>
> 
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/
> ---
> Now I have enough information to produce the report.
> 
> commit e59c58bb080afb34cac34c91b8c320a1f05134e6
> Author: Mengyuan Lou <mengyuanlou@net-swift.com>
> 
> net: libwx: support vf per-queue statistics via ethtool -S
> 
> The patch wires wx_get_sset_count, wx_get_strings, and wx_get_ethtool_stats
> into wx_ethtool_ops_vf, adds a small vf-only global stats table, and starts
> calling wx_update_stats() from wxvf_service_task. It also adds NULL checks
> for wx->rx_ring[i] / wx->tx_ring[i] inside wx_update_stats.
> 
>> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
>> index 22037f015ded..f72908630ea4 100644
>> --- a/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
>> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ethtool.c
> 
> [ ... ]
> 
>> @@ -114,7 +133,7 @@ void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
>> ethtool_sprintf(&p, "tx_queue_%u_packets", i);
>> ethtool_sprintf(&p, "tx_queue_%u_bytes", i);
>> }
>> - for (i = 0; i < WX_NUM_RX_QUEUES; i++) {
>> + for (i = 0; i < WX_NUM_RX_QUEUES(netdev); i++) {
>> ethtool_sprintf(&p, "rx_queue_%u_packets", i);
>> ethtool_sprintf(&p, "rx_queue_%u_bytes", i);
>> }
> 
> [ ... ]
> 
>> @@ -852,6 +878,9 @@ static const struct ethtool_ops wx_ethtool_ops_vf = {
>> .set_coalesce = wx_set_coalesce,
>> .get_ts_info = ethtool_op_get_ts_info,
>> .get_link_ksettings = wx_get_link_ksettings_vf,
>> + .get_sset_count = wx_get_sset_count,
>> + .get_strings = wx_get_strings,
>> + .get_ethtool_stats = wx_get_ethtool_stats,
>> };
> 
> [Medium]
> Before this change wx_ethtool_ops_vf had no get_sset_count / get_strings /
> get_ethtool_stats callbacks, so the VF exposed no ethtool -S output at all.
> Wiring these in also picks up the shared per-queue string loop that emits
> tx_queue_%u_packets / tx_queue_%u_bytes and rx_queue_%u_packets /
> rx_queue_%u_bytes.
> 
> Per-queue TX/RX packet and byte counters are a category that already has a
> standard uAPI (netdev_stat_ops with queue_stats_rx / queue_stats_tx,
> surfaced by netlink and ynl). Documentation/networking/statistics.rst and
> the ethtool -S guidance state that new counters covering values that have a
> standard uAPI should not be added to ethtool -S even when the driver does
> not yet implement the standard interface.
> 
> The wangxun tree does not implement netdev_stat_ops (no
> get_base_stats / get_queue_stats_rx / get_queue_stats_tx callbacks are
> present under drivers/net/ethernet/wangxun/).

Ok,I will remove patch3(support vf per-queue statistics via ethtool -S).
> 
> Would it be preferable to expose the per-queue counters on the VF via
> netdev_stat_ops instead of adding them to ethtool -S here?

netdev_stat_ops support is deferred to a follow-up patch; this one only extends ethtool ops.
> 
>> void wx_set_ethtool_ops_vf(struct net_device *netdev)
>> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_hw.c b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
>> index 260e14d5d541..349afbe60132 100644
>> --- a/drivers/net/ethernet/wangxun/libwx/wx_hw.c
>> +++ b/drivers/net/ethernet/wangxun/libwx/wx_hw.c
>> @@ -2886,6 +2886,8 @@ void wx_update_stats(struct wx *wx)
>> for (i = 0; i < wx->num_rx_queues; i++) {
>> struct wx_ring *rx_ring = wx->rx_ring[i];
>> 
>> + if (!rx_ring)
>> + continue;
>> non_eop_descs += rx_ring->rx_stats.non_eop_descs;
> 
> [ ... ]
> 
>> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c
>> index 26de78e9a69e..1d84fe35d7d9 100644
>> --- a/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c
>> +++ b/drivers/net/ethernet/wangxun/libwx/wx_vf_common.c
>> @@ -409,6 +410,7 @@ static void wxvf_service_task(struct work_struct *work)
>> 
>> wxvf_link_config_subtask(wx);
>> wxvf_reset_subtask(wx);
>> + wx_update_stats(wx);
>> wx_service_event_complete(wx);
>> }
> 
> [High]
> Can this call race with VF PCI suspend and shutdown to produce a
> use-after-free on wx->rx_ring[i] / wx->tx_ring[i]?
> 
> Both ngbevf and txgbevf use wxvf_suspend as their PM suspend callback and
> wxvf_shutdown (which calls wxvf_suspend) as their pci_driver ->shutdown
> callback:
> 
>    static DEFINE_SIMPLE_DEV_PM_OPS(ngbevf_pm_ops, wxvf_suspend, wxvf_resume);
>    ...
>    .shutdown = wxvf_shutdown,
> 
> wxvf_suspend() in wx_vf_common.c does:
> 
>    int wxvf_suspend(struct device *dev_d)
>    {
>        struct pci_dev *pdev = to_pci_dev(dev_d);
>        struct wx *wx = pci_get_drvdata(pdev);
> 
>        netif_device_detach(wx->netdev);
>        wx_clear_interrupt_scheme(wx);
>        pci_disable_device(pdev);
> 
>        return 0;
>    }
> 
> wx_clear_interrupt_scheme() ends up freeing the q_vectors (via
> kfree_rcu), and the ring allocations are embedded in the q_vector, so
> wx->rx_ring[i] / wx->tx_ring[i] end up pointing at memory that will be
> freed after an RCU grace period.
> 
> wxvf_suspend / wxvf_shutdown do not call timer_shutdown_sync on
> wx->service_timer or cancel_work_sync on wx->service_task, and they do
> not set WX_STATE_DOWN.
> 
> With this hunk in place, the service_task now walks wx->rx_ring[] and
> wx->tx_ring[]:
> 
>    void wx_update_stats(struct wx *wx)
>    {
>        ...
>        for (i = 0; i < wx->num_rx_queues; i++) {
>            struct wx_ring *rx_ring = wx->rx_ring[i];
> 
>            if (!rx_ring)
>                continue;
>            non_eop_descs += rx_ring->rx_stats.non_eop_descs;
>            ...
>        }
>        ...
>        for (i = 0; i < wx->num_tx_queues; i++) {
>            struct wx_ring *tx_ring = wx->tx_ring[i];
> 
>            if (!tx_ring)
>                continue;
>            restart_queue += tx_ring->tx_stats.restart_queue;
>            ...
>        }
> 
> If the service_task latches a non-NULL rx_ring / tx_ring pointer just
> before wx_clear_interrupt_scheme runs, the added NULL check does not
> help, and the subsequent ->rx_stats / ->tx_stats reads touch memory
> that has been kfree_rcu'd. wxvf_remove() covers this by calling
> timer_shutdown_sync + cancel_work_sync before wx_clear_interrupt_scheme,
> but wxvf_suspend / wxvf_shutdown do not.
> 
> Should wxvf_suspend (and hence wxvf_shutdown) quiesce the service_timer
> and service_task, or set WX_STATE_DOWN, before wx_clear_interrupt_scheme
> now that wx_update_stats is on the service_task's call path?
> -- 
> This is an AI-generated review.
> 
> 


^ permalink raw reply

* [PATCH iwl-next v3] ixgbe: E610: force phy link to get down when interface is down
From: Jedrzej Jagielski @ 2026-07-08 10:06 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: anthony.l.nguyen, netdev, pmenzel, Jedrzej Jagielski,
	Aleksandr Loktionov

For the E610 family, similarly to the E8xx adapters, the default behavior
is for the PHY link to remain up even when the corresponding OS interface
is down.

Add function setting down the PHY config IXGBE_ACI_PHY_ENA_LINK bit
what leads to disabling PHY link.

Now ixgbe_close() needs to share some of the ixgbe_watchdog_link_is_down
code so move the common part into the separate function.

Align functionality with the implementation of the ice driver.

Let user to configure link-down-on-close enablement through ethtool.

Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Signed-off-by: Jedrzej Jagielski <jedrzej.jagielski@intel.com>
---
v2: apply Paul's notes
v3: use e_err
---
 drivers/net/ethernet/intel/ixgbe/ixgbe.h      |  1 +
 drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c | 35 ++++++++++++++++++-
 drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h |  1 +
 .../net/ethernet/intel/ixgbe/ixgbe_ethtool.c  | 15 ++++++++
 drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 27 +++++++++++---
 5 files changed, 73 insertions(+), 6 deletions(-)

diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
index 30f62174acf2..7bbb82dd962c 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
@@ -685,6 +685,7 @@ struct ixgbe_adapter {
 #define IXGBE_FLAG2_MOD_POWER_UNSUPPORTED	BIT(22)
 #define IXGBE_FLAG2_API_MISMATCH		BIT(23)
 #define IXGBE_FLAG2_FW_ROLLBACK			BIT(24)
+#define IXGBE_FLAG2_LINK_DOWN_ON_CLOSE		BIT(25)
 
 	/* Tx fast path data */
 	int num_tx_queues;
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
index 831cfe9a4697..02bc0dac5123 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
@@ -1923,6 +1923,33 @@ void ixgbe_fc_autoneg_e610(struct ixgbe_hw *hw)
 	hw->fc.current_mode = hw->fc.requested_mode;
 }
 
+/**
+ * ixgbe_disable_phy_link - force phy link to get down
+ * @hw: pointer to hardware structure
+ *
+ * Send 0x0601 with the IXGBE_ACI_PHY_ENA_LINK bit set down.
+ *
+ * Return: the exit code of the operation.
+ */
+int ixgbe_disable_phy_link(struct ixgbe_hw *hw)
+{
+	struct ixgbe_aci_cmd_get_phy_caps_data pcaps = {};
+	struct ixgbe_aci_cmd_set_phy_cfg_data pcfg = {};
+	int err;
+
+	err = ixgbe_aci_get_phy_caps(hw, false, IXGBE_ACI_REPORT_ACTIVE_CFG,
+				     &pcaps);
+	if (err)
+		return err;
+
+	ixgbe_copy_phy_caps_to_cfg(&pcaps, &pcfg);
+
+	pcfg.caps &= ~IXGBE_ACI_PHY_ENA_LINK;
+	pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
+
+	return ixgbe_aci_set_phy_cfg(hw, &pcfg);
+}
+
 /**
  * ixgbe_disable_rx_e610 - Disable RX unit
  * @hw: pointer to hardware structure
@@ -2207,6 +2234,7 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
 	u8 rmode = IXGBE_ACI_REPORT_TOPO_CAP_MEDIA;
 	u64 sup_phy_type_low, sup_phy_type_high;
 	u64 phy_type_low = 0, phy_type_high = 0;
+	bool force_on_required;
 	int err;
 
 	err = ixgbe_aci_get_link_info(hw, false, NULL);
@@ -2272,6 +2300,11 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
 		phy_type_high |= IXGBE_PHY_TYPE_HIGH_10G_USXGMII;
 	}
 
+	/* If IXGBE_ACI_PHY_ENA_LINK has been explicitly disabled that means
+	 * we need to force PHY link UP state during PHY link setup
+	 */
+	force_on_required = !(pcfg.caps & IXGBE_ACI_PHY_ENA_LINK);
+
 	/* Mask the set values to avoid requesting unsupported link types. */
 	phy_type_low &= sup_phy_type_low;
 	pcfg.phy_type_low = cpu_to_le64(phy_type_low);
@@ -2280,7 +2313,7 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
 
 	if (pcfg.phy_type_high != pcaps.phy_type_high ||
 	    pcfg.phy_type_low != pcaps.phy_type_low ||
-	    pcfg.caps != pcaps.caps) {
+	    pcfg.caps != pcaps.caps || force_on_required) {
 		pcfg.caps |= IXGBE_ACI_PHY_ENA_LINK;
 		pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
 
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
index 2cb76a3d30ae..59044d67ebeb 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
@@ -50,6 +50,7 @@ int ixgbe_cfg_phy_fc(struct ixgbe_hw *hw,
 		     enum ixgbe_fc_mode req_mode);
 int ixgbe_setup_fc_e610(struct ixgbe_hw *hw);
 void ixgbe_fc_autoneg_e610(struct ixgbe_hw *hw);
+int ixgbe_disable_phy_link(struct ixgbe_hw *hw);
 void ixgbe_disable_rx_e610(struct ixgbe_hw *hw);
 int ixgbe_init_phy_ops_e610(struct ixgbe_hw *hw);
 int ixgbe_identify_phy_e610(struct ixgbe_hw *hw);
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
index b8e85bc91a27..16e26d54f3be 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
@@ -139,6 +139,8 @@ static const char ixgbe_priv_flags_strings[][ETH_GSTRING_LEN] = {
 	"vf-ipsec",
 #define IXGBE_PRIV_FLAGS_AUTO_DISABLE_VF	BIT(2)
 	"mdd-disable-vf",
+#define IXGBE_PRIV_LINK_DOWN_ON_CLOSE	BIT(3)
+	"link-down-on-close",
 };
 
 #define IXGBE_PRIV_FLAGS_STR_LEN ARRAY_SIZE(ixgbe_priv_flags_strings)
@@ -3822,6 +3824,9 @@ static u32 ixgbe_get_priv_flags(struct net_device *netdev)
 	if (adapter->flags2 & IXGBE_FLAG2_AUTO_DISABLE_VF)
 		priv_flags |= IXGBE_PRIV_FLAGS_AUTO_DISABLE_VF;
 
+	if (adapter->flags2 & IXGBE_FLAG2_LINK_DOWN_ON_CLOSE)
+		priv_flags |= IXGBE_PRIV_LINK_DOWN_ON_CLOSE;
+
 	return priv_flags;
 }
 
@@ -3859,6 +3864,16 @@ static int ixgbe_set_priv_flags(struct net_device *netdev, u32 priv_flags)
 		}
 	}
 
+	flags2 &= ~IXGBE_FLAG2_LINK_DOWN_ON_CLOSE;
+	if (priv_flags & IXGBE_PRIV_LINK_DOWN_ON_CLOSE) {
+		if (adapter->hw.mac.type == ixgbe_mac_e610) {
+			flags2 |= IXGBE_FLAG2_LINK_DOWN_ON_CLOSE;
+		} else {
+			e_info(probe, "Cannot set private flags: Feature supported only for E610 devices\n");
+			return -EOPNOTSUPP;
+		}
+	}
+
 	if (flags2 != adapter->flags2) {
 		adapter->flags2 = flags2;
 
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 7a0783c1abc1..d0bba3e202ca 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -7551,6 +7551,17 @@ static void ixgbe_close_suspend(struct ixgbe_adapter *adapter)
 	ixgbe_free_all_rx_resources(adapter);
 }
 
+static void ixgbe_handle_link_down(struct ixgbe_adapter *adapter)
+{
+	struct net_device *netdev = adapter->netdev;
+
+	if (test_bit(__IXGBE_PTP_RUNNING, &adapter->state))
+		ixgbe_ptp_start_cyclecounter(adapter);
+
+	e_info(drv, "NIC Link is Down\n");
+	netif_carrier_off(netdev);
+}
+
 /**
  * ixgbe_close - Disables a network interface
  * @netdev: network interface device structure
@@ -7573,6 +7584,16 @@ int ixgbe_close(struct net_device *netdev)
 
 	ixgbe_fdir_filter_exit(adapter);
 
+	if (adapter->flags2 & IXGBE_FLAG2_LINK_DOWN_ON_CLOSE) {
+		int err;
+
+		err = ixgbe_disable_phy_link(&adapter->hw);
+		if (err)
+			e_err(drv, "Cannot set PHY link down\n");
+
+		ixgbe_handle_link_down(adapter);
+	}
+
 	ixgbe_release_hw_control(adapter);
 
 	return 0;
@@ -8251,11 +8272,7 @@ static void ixgbe_watchdog_link_is_down(struct ixgbe_adapter *adapter)
 	if (ixgbe_is_sfp(hw) && hw->mac.type == ixgbe_mac_82598EB)
 		adapter->flags2 |= IXGBE_FLAG2_SEARCH_FOR_SFP;
 
-	if (test_bit(__IXGBE_PTP_RUNNING, &adapter->state))
-		ixgbe_ptp_start_cyclecounter(adapter);
-
-	e_info(drv, "NIC Link is Down\n");
-	netif_carrier_off(netdev);
+	ixgbe_handle_link_down(adapter);
 }
 
 static bool ixgbe_ring_tx_pending(struct ixgbe_adapter *adapter)
-- 
2.31.1


^ permalink raw reply related

* Re: [PATCH net-next v3 07/15] net: macb: introduce macb_context struct for buffer management
From: Paolo Abeni @ 2026-07-08 10:27 UTC (permalink / raw)
  To: theo.lebrun
  Cc: conor.dooley, andrew+netdev, davem, edumazet, kuba, pabeni,
	richardcochran, linux, netdev, linux-kernel, nicolas.ferre,
	claudiu.beznea, pvalerio, nb, vladimir.kondratiev,
	gregory.clement, benoit.monin, tawfik.bayouk, thomas.petazzoni,
	maxime.chevallier
In-Reply-To: <20260701-macb-context-v3-7-00268d5b1502@bootlin.com>

From: AI Reviewer <ai@example.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
net: macb: introduce macb_context struct for buffer management

This patch moves per-queue ring buffers and ring-size parameters out of
struct macb / struct macb_queue into a new struct macb_context that is
allocated on open and freed on close, so future patches can swap in a
new context while the interface is running.

> diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
> index 951a7f080225..dfb1d49d841c 100644
> --- a/drivers/net/ethernet/cadence/macb_main.c
> +++ b/drivers/net/ethernet/cadence/macb_main.c

[ ... ]

> @@ -1257,6 +1288,7 @@ static void macb_tx_error_task(struct work_struct *work)
>  	struct macb_queue *queue = container_of(work, struct macb_queue,
>  						tx_error_task);
>  	unsigned int q = queue - queue->bp->queues;
> +	struct macb_txq *txq = macb_txq(queue);
>  	struct macb *bp = queue->bp;

[High]
Can macb_tx_error_task() dereference a freed context?

macb_tx_error_task is scheduled from macb_interrupt():

    schedule_work(&queue->tx_error_task);

After this patch, the very first thing the worker does is:

    struct macb_txq *txq = macb_txq(queue);

which expands to &bp->ctx->txq[q] and dereferences bp->ctx.

Looking at macb_close():

    macb_reset_hw(bp);
    ...
    macb_free(bp);
    kfree(bp->ctx);
    bp->ctx = NULL;

There is no cancel_work_sync(&queue->tx_error_task), and macb_reset_hw()
does not drain the workqueue. If an interrupt scheduled the work just
before close, or the work was already pending, the worker can run after
kfree(bp->ctx) and hit a NULL dereference (and a use-after-free if
macb_open() re-allocates ctx concurrently via ethtool ring reconfig).

The same reasoning applies to bp->hresp_err_bh_work, which is queued
from macb_interrupt() and whose handler calls
mog_init_rings()/macb_init_buffers() that read bp->ctx->tx_ring_size and
bp->ctx->{tx,rx}q[q]. cancel_work_sync(&bp->hresp_err_bh_work) is only
called from macb_remove(), not macb_close().

Pre-patch these fields lived inside the embedded struct macb_queue so a
late worker would touch stale-but-addressable memory; post-patch the
first indirection through bp->ctx faults.

Would adding cancel_work_sync(&queue->tx_error_task) per queue and
cancel_work_sync(&bp->hresp_err_bh_work) before kfree(bp->ctx) in
macb_close() close this window?

[ ... ]

> @@ -3690,8 +3778,8 @@ static int macb_set_ringparam(struct net_device *netdev,
>  		macb_close(bp->netdev);
>  	}
>  
> -	bp->rx_ring_size = new_rx_size;
> -	bp->tx_ring_size = new_tx_size;
> +	bp->configured_rx_ring_size = new_rx_size;
> +	bp->configured_tx_ring_size = new_tx_size;
>  
>  	if (reset)
>  		macb_open(bp->netdev);

[Medium]
Should the return value of macb_open() be checked here?

This patch adds a new failure point inside macb_open():

    bp->ctx = kzalloc_obj(*bp->ctx);
    if (!bp->ctx) {
        err = -ENOMEM;
        goto pm_exit;
    }

On failure, bp->ctx remains NULL and the interface stays down, but
macb_set_ringparam() ignores the return value and reports success to
userspace. The commit message says the motivation is to improve
reliability under memory pressure, but this caller silently swallows the
new allocation failure.

[ ... ]

> @@ -5039,6 +5125,12 @@ static int at91ether_open(struct net_device *netdev)
>  	if (ret < 0)
>  		return ret;
>  
> +	bp->ctx = kzalloc_obj(*bp->ctx);
> +	if (!bp->ctx) {
> +		ret = -ENOMEM;
> +		goto pm_exit;
> +	}
> +
>  	/* Clear internal statistics */
>  	ctl = macb_readl(bp, NCR);
>  	macb_writel(bp, NCR, ctl | MACB_BIT(CLRSTAT));

[Low]
Should bp->ctx->rx_ring_size, tx_ring_size and rx_buffer_size be
initialized on this path?

macb_open() sets them from configured_{rx,tx}_ring_size and calls
macb_init_rx_buffer_size(), but at91ether_open() leaves them at zero
after kzalloc_obj().

Helpers such as macb_rx_ring_wrap() compute:

    return index & (bp->ctx->rx_ring_size - 1);

which becomes index & 0xFFFFFFFF when rx_ring_size is 0. It happens to
be harmless today because the at91ether path constrains indices to
[0, AT91ETHER_MAX_RX_DESCR-1] independently and macb_get_regs() reports
zeroed head/tail for the tx-less at91ether, but pre-patch macb_init_dflt()
unconditionally set bp->rx_ring_size = DEFAULT_RX_RING_SIZE.

Any future shared helper that used tx_ring_size as a stride or divisor
would misbehave on at91ether.

[ ... ]

> @@ -5076,6 +5171,9 @@ static int at91ether_close(struct net_device *netdev)
>  
>  	at91ether_stop(bp);
>  
> +	kfree(bp->ctx);
> +	bp->ctx = NULL;
> +
>  	pm_runtime_put(&bp->pdev->dev);
>  
>  	return 0;

[High]
Is there IRQ synchronization missing before freeing bp->ctx on this
path?

at91ether_stop() disables MAC RX/TX and writes IDR to mask further
interrupts, but does not synchronize with a handler already running or
already dispatched on another CPU. at91ether_interrupt() calls
at91ether_rx(), which after this patch does:

    struct macb_rxq *rxq = &bp->ctx->rxq[0];
    ...
    desc = macb_rx_desc(queue, rxq->tail);
    ...
    p_recv = rxq->buffers + rxq->tail * AT91ETHER_MAX_RBUFF_SZ;

If the handler is in flight when kfree(bp->ctx); bp->ctx = NULL runs,
the next access through bp->ctx faults. The macb_open()/macb_close()
path is somewhat protected by napi_disable() before macb_free(), but
at91ether does not use NAPI and has no equivalent barrier.

Would a synchronize_irq(bp->netdev->irq) between at91ether_stop() and
kfree(bp->ctx) close this?
-- 
This is an AI-generated review.


^ permalink raw reply

* [PATCH net v2 1/2] vsock/virtio: collapse receive queue under memory pressure
From: Stefano Garzarella @ 2026-07-08 10:29 UTC (permalink / raw)
  To: netdev
  Cc: Jason Wang, Stefano Garzarella, Xuan Zhuo, Eric Dumazet,
	Eugenio Pérez, Simon Horman, Stefan Hajnoczi,
	David S. Miller, linux-kernel, Michael S. Tsirkin, kvm,
	Paolo Abeni, virtualization, Jakub Kicinski, Jason Wang, stable,
	Brien Oberstein
In-Reply-To: <20260708102904.50732-1-sgarzare@redhat.com>

From: Stefano Garzarella <sgarzare@redhat.com>

When many small packets accumulate in the receive queue, the skb overhead
can exceed buf_alloc even while the payload is within bounds. This causes
virtio_transport_inc_rx_pkt() to reject packets, leading to connection
resets during large transfers under backpressure.

The issue was reported by Brien, who has a reproducer, but it is also
easily reproducible with iperf-vsock [1] using a small packet size:

  iperf3 --vsock -c $CID -l 129

which fails immediately without this patch but with commit 059b7dbd20a6
("vsock/virtio: fix potential unbounded skb queue").

Inspired by TCP's tcp_collapse() which solves a similar problem, add
virtio_transport_collapse_rx_queue() that walks the receive queue and
re-copies data into compact linear skbs to reduce the overhead.

The collapse is triggered proactively from when the number of skb queued
is close to exceeding the overhead budget.

A pre-scan counts the eligible bytes to size each allocation precisely,
avoiding waste for isolated small packets. Partially consumed skbs are
kept as-is to preserve buf_used/fwd_cnt accounting, EOM-marked skbs to
maintain SEQPACKET message boundaries, and skbs already larger than the
collapse target because they already have a good data-to-overhead ratio.

Walking a large queue may take a significant amount of time and cache
misses, causing traffic burstiness. To limit this, the collapse stops
once enough room is freed for this packet and the next one, but may
opportunistically free more to fill each collapsed skb to capacity.

[1] https://github.com/stefano-garzarella/iperf-vsock

Fixes: 059b7dbd20a6 ("vsock/virtio: fix potential unbounded skb queue")
Cc: stable@vger.kernel.org
Reported-by: Brien Oberstein <brienpub@gmail.com>
Closes: https://lore.kernel.org/netdev/618701dd023e$063de350$12b9a9f0$@gmail.com/
Tested-by: Brien Oberstein <brienpub@gmail.com>
Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
---
v2:
- defined MAX_COLLAPSE_LEN macro instead of using a variable [Paolo]
- added a threshold to avoid walking all the queue while collapsing
  [Paolo]
- collapsed the queue before calling virtio_transport_inc_rx_pkt().
  While working on the threshold, I figured out that the check I was
  introducing can also be used to proactively trigger the collapse, so I
  moved the call to virtio_transport_collapse_rx_queue() before acquiring
  the rx_lock to have also a better diff to simplify backports
- improved code readability (removed `out` label, `keep` initialization,
  etc.) [Paolo + other small stuff]
- Brien kindly retested this version as well (thank you so much)
---
 net/vmw_vsock/virtio_transport_common.c | 165 +++++++++++++++++++++++-
 1 file changed, 164 insertions(+), 1 deletion(-)

diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 09475007165b..8becad81279c 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -26,6 +26,13 @@
 /* Threshold for detecting small packets to copy */
 #define GOOD_COPY_LEN  128
 
+/* Max payload that can be collapsed into a single linear skb, using the same
+ * allocation threshold as virtio_vsock_alloc_skb() to avoid adding pressure
+ * on the page allocator.
+ */
+#define MAX_COLLAPSE_LEN \
+	SKB_MAX_ORDER(VIRTIO_VSOCK_SKB_HEADROOM, PAGE_ALLOC_COSTLY_ORDER)
+
 static void virtio_transport_cancel_close_work(struct vsock_sock *vsk,
 					       bool cancel_timeout);
 static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs);
@@ -420,6 +427,145 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
 	return ret;
 }
 
+static bool virtio_transport_can_collapse(struct sk_buff *skb)
+{
+	/* skbs that are partially consumed, mark a SEQPACKET message boundary,
+	 * or are already large enough should not be collapsed: they either
+	 * need special accounting, carry protocol state, or already have a
+	 * good data-to-overhead ratio.
+	 */
+	if (VIRTIO_VSOCK_SKB_CB(skb)->offset)
+		return false;
+	if (le32_to_cpu(virtio_vsock_hdr(skb)->flags) & VIRTIO_VSOCK_SEQ_EOM)
+		return false;
+	if (skb->len >= MAX_COLLAPSE_LEN)
+		return false;
+	return true;
+}
+
+/* Iterate through the packets in the queue starting from the current skb to
+ * count the number of bytes we can collapse.
+ */
+static unsigned int
+virtio_transport_collapse_size(struct sk_buff *skb, struct sk_buff_head *queue)
+{
+	unsigned int target = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset;
+
+	while ((skb = skb_peek_next(skb, queue)) &&
+	       virtio_transport_can_collapse(skb)) {
+		unsigned int len = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset;
+
+		if (len > MAX_COLLAPSE_LEN - target)
+			return target;
+
+		target += len;
+	}
+
+	return target;
+}
+
+/* Called under lock_sock to compact the receive queue by merging small skbs.
+ * @min_to_free: minimum number of skbs to eliminate from the queue. May free
+ *               more to fill each collapsed skb to capacity.
+ */
+static void
+virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
+				   u32 min_to_free)
+{
+	struct sk_buff *skb, *next_skb, *new_skb = NULL;
+	struct sk_buff_head new_queue;
+	u32 saved = 0;
+
+	__skb_queue_head_init(&new_queue);
+
+	skb_queue_walk_safe(&vvs->rx_queue, skb, next_skb) {
+		struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+		u32 src_off = VIRTIO_VSOCK_SKB_CB(skb)->offset;
+		u32 src_len = skb->len - src_off;
+		bool keep;
+
+		keep = !virtio_transport_can_collapse(skb);
+		if (keep) {
+			/* Finalize pending collapsed skb to preserve packet
+			 * ordering.
+			 */
+			if (new_skb) {
+				__skb_queue_tail(&new_queue, new_skb);
+				new_skb = NULL;
+				saved--;
+			}
+			goto next;
+		}
+
+		/* Finalize if this packet won't fit in the remaining tailroom,
+		 * so we can allocate a right-sized new_skb.
+		 */
+		if (new_skb && src_len > skb_tailroom(new_skb)) {
+			__skb_queue_tail(&new_queue, new_skb);
+			new_skb = NULL;
+			saved--;
+		}
+
+		if (!new_skb) {
+			unsigned int alloc_size;
+
+			/* Check after finalizing to opportunistically fill
+			 * each collapsed skb to capacity, merging more skbs
+			 * than strictly required.
+			 */
+			if (saved >= min_to_free)
+				break;
+
+			alloc_size = virtio_transport_collapse_size(skb, &vvs->rx_queue);
+
+			/* Only this skb's data is eligible, nothing to merge
+			 * with. Keep as-is.
+			 */
+			if (alloc_size <= src_len) {
+				keep = true;
+				goto next;
+			}
+
+			new_skb = virtio_vsock_alloc_linear_skb(alloc_size +
+					VIRTIO_VSOCK_SKB_HEADROOM, GFP_KERNEL);
+			if (!new_skb)
+				break;
+
+			memcpy(virtio_vsock_hdr(new_skb), hdr,
+			       sizeof(struct virtio_vsock_hdr));
+			virtio_vsock_hdr(new_skb)->len = 0;
+		}
+
+		/* Cannot fail since src_off/src_len are within bounds, but if
+		 * it does, discard new_skb to avoid queuing corrupted data.
+		 */
+		if (WARN_ON_ONCE(skb_copy_bits(skb, src_off,
+					       skb_put(new_skb, src_len),
+					       src_len))) {
+			kfree_skb(new_skb);
+			new_skb = NULL;
+			break;
+		}
+
+		le32_add_cpu(&virtio_vsock_hdr(new_skb)->len, src_len);
+		virtio_vsock_hdr(new_skb)->flags |= hdr->flags;
+
+next:
+		__skb_unlink(skb, &vvs->rx_queue);
+		if (keep) {
+			__skb_queue_tail(&new_queue, skb);
+		} else {
+			consume_skb(skb);
+			saved++;
+		}
+	}
+
+	if (new_skb)
+		__skb_queue_tail(&new_queue, new_skb);
+
+	skb_queue_splice(&new_queue, &vvs->rx_queue);
+}
+
 static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
 					u32 len)
 {
@@ -1354,12 +1500,29 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk,
 {
 	struct virtio_vsock_sock *vvs = vsk->trans;
 	bool can_enqueue, free_pkt = false;
+	u32 len, queue_max, queue_len;
 	struct virtio_vsock_hdr *hdr;
-	u32 len;
 
 	hdr = virtio_vsock_hdr(skb);
 	len = le32_to_cpu(hdr->len);
 
+	/* virtio_transport_inc_rx_pkt() rejects packets when the per-skb
+	 * overhead (skb_queue_len * SKB_TRUESIZE(0)) exceeds buf_alloc.
+	 * Proactively collapse the queue before that happens.
+	 * No rx_lock needed: lock_sock is held by caller, preventing
+	 * concurrent enqueue or dequeue.
+	 */
+	queue_max = vvs->buf_alloc / SKB_TRUESIZE(0);
+	queue_len = skb_queue_len(&vvs->rx_queue);
+	if (queue_len >= queue_max) {
+		/* Walking a large queue may take a significant amount of time
+		 * and cache misses, causing traffic burstiness. Limit the
+		 * collapse to freeing room for this packet and the next one.
+		 * It may free more to fill each collapsed skb to capacity.
+		 */
+		virtio_transport_collapse_rx_queue(vvs, queue_len + 2 - queue_max);
+	}
+
 	spin_lock_bh(&vvs->rx_lock);
 
 	can_enqueue = virtio_transport_inc_rx_pkt(vvs, len);
-- 
2.55.0


^ permalink raw reply related

* [PATCH net v2 0/2] vsock/virtio: collapse receive queue under memory pressure
From: Stefano Garzarella @ 2026-07-08 10:29 UTC (permalink / raw)
  To: netdev
  Cc: Jason Wang, Stefano Garzarella, Xuan Zhuo, Eric Dumazet,
	Eugenio Pérez, Simon Horman, Stefan Hajnoczi,
	David S. Miller, linux-kernel, Michael S. Tsirkin, kvm,
	Paolo Abeni, virtualization, Jakub Kicinski, Jason Wang

This series contains a patch (the first one) that is part of work I'm
doing to improve the tracking of memory used by AF_VSOCK sockets.
The second patch is a test for our suite that highlights the issue.

Since Brien reported an issue with his environment (based on Linux 6.12.y)
related to the work I’m doing, I extracted this patch and tried to make it
as easy as possible to backport. Brien tested it by backporting it to
6.12.y, which now contains the backport of the 059b7dbd20a6
("vsock/virtio: fix potential unbounded skb queue").

This patch primarily fixes STREAM sockets, but also partially fixes
SEQPACKET (with the exception of EOMs, which are kept in separate skbs to
avoid overcomplicating the code).

The rest of the work, I feel, is more net-next material and still needs
some work to be completed.

Changelog
---------

v2:
- defined MAX_COLLAPSE_LEN macro instead of using a variable [Paolo]
- added a threshold to avoid walking all the queue while collapsing
  [Paolo]
- collapsed the queue before calling virtio_transport_inc_rx_pkt().
  While working on the threshold, I figured out that the check I was
  introducing can also be used to proactively trigger the collapse, so I
  moved the call to virtio_transport_collapse_rx_queue() before acquiring
  the rx_lock to have also a better diff to simplify backports
- improved code readability (removed `out` label, `keep` initialization,
  etc.) [Paolo + other small stuff]
- Brien kindly retested this version as well (thank you so much)

v1: https://lore.kernel.org/netdev/20260626134823.206676-1-sgarzare@redhat.com/

Thanks,
Stefano

Stefano Garzarella (2):
  vsock/virtio: collapse receive queue under memory pressure
  vsock/test: add test for small packets under pressure

 net/vmw_vsock/virtio_transport_common.c | 165 +++++++++++++++++++++++-
 tools/testing/vsock/vsock_test.c        |  87 +++++++++++++
 2 files changed, 251 insertions(+), 1 deletion(-)

-- 
2.55.0


^ permalink raw reply

* [PATCH net v2 2/2] vsock/test: add test for small packets under pressure
From: Stefano Garzarella @ 2026-07-08 10:29 UTC (permalink / raw)
  To: netdev
  Cc: Jason Wang, Stefano Garzarella, Xuan Zhuo, Eric Dumazet,
	Eugenio Pérez, Simon Horman, Stefan Hajnoczi,
	David S. Miller, linux-kernel, Michael S. Tsirkin, kvm,
	Paolo Abeni, virtualization, Jakub Kicinski, Jason Wang
In-Reply-To: <20260708102904.50732-1-sgarzare@redhat.com>

From: Stefano Garzarella <sgarzare@redhat.com>

Add a test that sends 2 MB of data using randomly sized small packets
(129-512 bytes) over a SOCK_STREAM connection. Packets above
GOOD_COPY_LEN (128) bypass the in-place coalescing in recv_enqueue(),
forcing each one into its own skb.

Without receive queue collapsing, the per-skb overhead eventually
exceeds buf_alloc and the connection is reset. The test verifies
that all data arrives and that content integrity is preserved.

Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>
---
 tools/testing/vsock/vsock_test.c | 87 ++++++++++++++++++++++++++++++++
 1 file changed, 87 insertions(+)

diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
index 76be0e4a7f0e..b4ff9f946565 100644
--- a/tools/testing/vsock/vsock_test.c
+++ b/tools/testing/vsock/vsock_test.c
@@ -2347,6 +2347,88 @@ static void test_stream_tx_credit_bounds_server(const struct test_opts *opts)
 	close(fd);
 }
 
+/* Test that many small packets don't cause a connection reset under pressure
+ * and that data integrity is preserved.  Packet sizes vary randomly between
+ * 129 and 512 bytes, above GOOD_COPY_LEN (128) to bypass in-place coalescing
+ * in recv_enqueue, forcing each one into its own skb.  Without receive queue
+ * collapsing, the per-skb overhead eventually exceeds buf_alloc and the
+ * connection is reset.
+ */
+#define COLLAPSE_PKT_MIN 129
+#define COLLAPSE_PKT_MAX 512
+#define COLLAPSE_TOTAL (2 * 1024 * 1024)
+
+static void test_stream_collapse_client(const struct test_opts *opts)
+{
+	unsigned char *data;
+	unsigned long hash;
+	size_t offset = 0;
+	int i, fd;
+
+	data = malloc(COLLAPSE_TOTAL);
+	if (!data) {
+		perror("malloc");
+		exit(EXIT_FAILURE);
+	}
+
+	for (i = 0; i < COLLAPSE_TOTAL; i++)
+		data[i] = rand() & 0xff;
+
+	fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+	if (fd < 0) {
+		perror("connect");
+		exit(EXIT_FAILURE);
+	}
+
+	while (offset < COLLAPSE_TOTAL) {
+		size_t pkt_size = COLLAPSE_PKT_MIN +
+			rand() % (COLLAPSE_PKT_MAX - COLLAPSE_PKT_MIN + 1);
+
+		pkt_size = min(pkt_size, COLLAPSE_TOTAL - offset);
+
+		send_buf(fd, data + offset, pkt_size, 0, pkt_size);
+		offset += pkt_size;
+	}
+
+	hash = hash_djb2(data, COLLAPSE_TOTAL);
+	control_writeulong(hash);
+
+	free(data);
+	close(fd);
+}
+
+static void test_stream_collapse_server(const struct test_opts *opts)
+{
+	unsigned long hash, remote_hash;
+	unsigned char *data;
+	int fd;
+
+	data = malloc(COLLAPSE_TOTAL);
+	if (!data) {
+		perror("malloc");
+		exit(EXIT_FAILURE);
+	}
+
+	fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
+	if (fd < 0) {
+		perror("accept");
+		exit(EXIT_FAILURE);
+	}
+
+	recv_buf(fd, data, COLLAPSE_TOTAL, 0, COLLAPSE_TOTAL);
+
+	hash = hash_djb2(data, COLLAPSE_TOTAL);
+	remote_hash = control_readulong();
+	if (hash != remote_hash) {
+		fprintf(stderr, "hash mismatch: local %lu remote %lu\n",
+			hash, remote_hash);
+		exit(EXIT_FAILURE);
+	}
+
+	free(data);
+	close(fd);
+}
+
 static struct test_case test_cases[] = {
 	{
 		.name = "SOCK_STREAM connection reset",
@@ -2546,6 +2628,11 @@ static struct test_case test_cases[] = {
 		.run_client = test_stream_msg_peek_client,
 		.run_server = test_stream_peek_after_recv_server,
 	},
+	{
+		.name = "SOCK_STREAM small packets backpressure",
+		.run_client = test_stream_collapse_client,
+		.run_server = test_stream_collapse_server,
+	},
 	{},
 };
 
-- 
2.55.0


^ permalink raw reply related

* Re: [PATCH nf-next v4 5/6] net: netfilter: Add SIT tunnel flowtable acceleration
From: Lorenzo Bianconi @ 2026-07-08 10:33 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Felix Fietkau, Matthias Brugger,
	AngeloGioacchino Del Regno, Simon Horman, David Ahern,
	Ido Schimmel, Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
	Shuah Khan
  Cc: linux-arm-kernel, linux-mediatek, netdev, netfilter-devel,
	coreteam, linux-kselftest
In-Reply-To: <20260703-b4-flowtable-sw-accel-ip6ip-v4-5-00398cd12382@kernel.org>

[-- Attachment #1: Type: text/plain, Size: 20415 bytes --]

commenting on sashiko's report:
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260703-b4-flowtable-sw-accel-ip6ip-v4-0-00398cd12382%40kernel.org

> Introduce sw flowtable acceleration for the TX/RX paths of
> SIT tunnels, relying on the netfilter flowtable infrastructure.
> The feature can be tested with a forwarding scenario between two
> NICs (eth0 and eth1), where a SIT tunnel is used to reach a remote
> site via eth1 as the underlay device:
> 
>     ETH0 -- TUN0 <==> ETH1 -- [IP network] -- TUN1 (192.168.2.2)
> 
> [IP configuration]
> 
> 6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
>     link/ether 00:00:22:33:11:55 brd ff:ff:ff:ff:ff:ff
>     inet6 2001:db8:1::2/64 scope global nodad
>        valid_lft forever preferred_lft forever
> 7: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
>     link/ether 00:11:22:33:11:55 brd ff:ff:ff:ff:ff:ff
>     inet 192.168.2.1/24 scope global eth1
>        valid_lft forever preferred_lft forever
> 8: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
>     link/sit 192.168.2.1 peer 192.168.2.2
>     inet6 2001:db8:200::1/64 scope global nodad
>        valid_lft forever preferred_lft forever
> 
> $ ip route show
> 192.168.2.0/24 dev eth1 proto kernel scope link src 192.168.2.1
> 
> $ ip -6 route show
> 2001:db8:1::/64 dev eth0 proto kernel metric 256 pref medium
> 2001:db8:200::/64 dev tun0 proto kernel metric 256 pref medium
> default via 2001:db8:200::2 dev tun0 metric 1024 pref medium
> 
> $ nft list ruleset
> table inet filter {
>     flowtable ft {
>         hook ingress priority filter
>         devices = { eth0, eth1 }
>     }
> 
>     chain forward {
>         type filter hook forward priority filter; policy accept;
>         meta l4proto { tcp, udp } flow add @ft
>     }
> }
> 
> When reproducing this scenario using veth interfaces, the following
> results were observed:
> 
> - TCP stream received from SIT tunnel:
>   - net-next (baseline):                ~118 Gbps
>   - net-next + SIT flowtable support: ~148 Gbps
> 
> - TCP stream transmitted to SIT tunnel:
>   - net-next (baseline):                ~131 Gbps
>   - net-next + SIT flowtable support: ~147 Gbps
> 
> Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
>  net/ipv6/sit.c                   |  26 ++++
>  net/netfilter/nf_flow_table_ip.c | 290 +++++++++++++++++++++------------------
>  2 files changed, 182 insertions(+), 134 deletions(-)
> 
> diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
> index a38b24fb8384..0ac6f7839878 100644
> --- a/net/ipv6/sit.c
> +++ b/net/ipv6/sit.c
> @@ -1365,6 +1365,31 @@ ipip6_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
>  	}
>  }
>  
> +static int ipip6_tunnel_fill_forward_path(struct net_device_path_ctx *ctx,
> +					  struct net_device_path *path)
> +{
> +	struct ip_tunnel *tunnel = netdev_priv(ctx->dev);
> +	const struct iphdr *tiph = &tunnel->parms.iph;
> +	struct rtable *rt;
> +
> +	rt = ip_route_output(dev_net(ctx->dev), tiph->daddr, 0, 0, 0,
> +			     RT_SCOPE_UNIVERSE);
> +	if (IS_ERR(rt))
> +		return PTR_ERR(rt);
> +
> +	path->type = DEV_PATH_TUN;
> +	path->tun.src_v4.s_addr = tiph->saddr;
> +	path->tun.dst_v4.s_addr = tiph->daddr;
> +	path->tun.l3_proto = IPPROTO_IPV6;
> +	path->tun.encap_proto = AF_INET;
> +	path->dev = ctx->dev;
> +
> +	ctx->dev = rt->dst.dev;
> +	ip_rt_put(rt);
> +
> +	return 0;

- Is tiph->daddr always meaningful here?  SIT supports several modes where
  tunnel->parms.iph.daddr is either zero or a placeholder and the real
  outer IPv4 endpoint is derived per-packet from the inner IPv6
  destination:
    - ISATAP (dev->priv_flags & IFF_ISATAP), resolved by
      ipip6_tunnel_dst_find(..., true)
    - 6rd / 6to4, resolved per-packet via try_6rd() / check_6rd()
    - NBMA / point-to-multipoint (tiph->daddr == 0), resolved by
      ipip6_tunnel_dst_find(..., false)
  In these cases ip_route_output(daddr=0) will fall through to the default
  route, ctx->dev gets resolved to the wrong underlay, and every
  accelerated packet is encapsulated toward 0.0.0.0 (or a fixed unicast
  that does not match the flow) regardless of the actual inner
  destination.
  Compare ip6_tnl_fill_forward_path() which returns -EOPNOTSUPP when it
  detects unsupported tunnel features; would something equivalent make
  sense here, at least for !tiph->daddr, IFF_ISATAP, and 6rd configured
  tunnels?
  Does this path also need to consider tunnel->encap?  SIT supports FOU
  and GUE via ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4) in the
  slow path, and there is no rejection here when tunnel->encap.type is
  not TUNNEL_ENCAP_NONE.  Once a SIT tunnel that was configured with
  encap fou/gue is attached to a flowtable, the fast path pushes a plain
  IPv4 outer header via nf_flow_tunnel_ipip_push() and skips the FOU/GUE
  encapsulation entirely.  ip6_tnl_fill_forward_path() has an
  -EOPNOTSUPP guard for its own analogous case (encaplimit); would a
  similar guard work here?
  The slow path in ipip6_tunnel_xmit() uses flowi4_init_output() with
  tunnel->parms.link (oif), tunnel->fwmark, tos & INET_DSCP_MASK, and
  tiph->saddr:
      flowi4_init_output(&fl4, tunnel->parms.link, tunnel->fwmark,
                         tos & INET_DSCP_MASK, RT_SCOPE_UNIVERSE,
                         IPPROTO_IPV6, 0, dst, tiph->saddr, ...);
  The call above passes 0 for saddr, dscp and oif and does not pass a
  fwmark.  For a SIT tunnel bound to a specific link, using fwmark-based
  policy routing, or configured with a source address, can the fast-path
  route lookup diverge from what the slow path would pick, so that
  rt->dst.dev (which becomes ctx->dev and drives the flowtable egress)
  ends up on a different underlay device than slow-path packets on the
  same tunnel?
  - I will fix it in v5.

- Is it safe to hardcode IPPROTO_IPV6 here?  sit_tunnel_xmit() dispatches
  on skb->protocol and legitimately supports ETH_P_IP (IPv4-in-IPv4 via
  IPPROTO_IPIP) and, if CONFIG_MPLS is set, ETH_P_MPLS_UC (IPPROTO_MPLS);
  ipip6_valid_ip_proto() also accepts both on the receive side.
  If flowtable acceleration engages for a flow whose inner payload is
  IPv4 or MPLS through a SIT device, the outer iph->protocol that
  nf_flow_tunnel_ipip_push() synthesises from tuple->tun.l3_proto will be
  IPPROTO_IPV6, and the receiver will drop or misparse the packet.
  ip6_tnl_fill_forward_path() selects l3_proto from ctx->ether_type
  (ETH_P_IP => IPPROTO_IPIP, else IPPROTO_IPV6) — would something similar
  be appropriate here?
  - I will fix it in v5.

Regards,
Lorenzo

> +}
> +
>  static int
>  ipip6_tunnel_siocdevprivate(struct net_device *dev, struct ifreq *ifr,
>  			    void __user *data, int cmd)
> @@ -1401,6 +1426,7 @@ static const struct net_device_ops ipip6_netdev_ops = {
>  	.ndo_siocdevprivate = ipip6_tunnel_siocdevprivate,
>  	.ndo_get_iflink = ip_tunnel_get_iflink,
>  	.ndo_tunnel_ctl = ipip6_tunnel_ctl,
> +	.ndo_fill_forward_path = ipip6_tunnel_fill_forward_path,
>  };
>  
>  static void ipip6_dev_free(struct net_device *dev)
> diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
> index 4b6de16bd4f3..f02e71ae5448 100644
> --- a/net/netfilter/nf_flow_table_ip.c
> +++ b/net/netfilter/nf_flow_table_ip.c
> @@ -338,7 +338,7 @@ static bool nf_flow_ip4_tunnel_proto(struct nf_flowtable_ctx *ctx,
>  	if (iph->ttl <= 1)
>  		return false;
>  
> -	if (iph->protocol == IPPROTO_IPIP) {
> +	if (iph->protocol == IPPROTO_IPIP || iph->protocol == IPPROTO_IPV6) {
>  		ctx->tun.proto = iph->protocol;
>  		ctx->tun.hdr_size = size;
>  		ctx->offset += ctx->tun.hdr_size;
> @@ -464,21 +464,6 @@ static void nf_flow_encap_pop(struct nf_flowtable_ctx *ctx,
>  		nf_flow_ip_tunnel_pop(ctx, skb);
>  }
>  
> -static struct flow_offload_tuple_rhash *
> -nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
> -		       struct nf_flowtable *flow_table, struct sk_buff *skb)
> -{
> -	struct flow_offload_tuple tuple = {};
> -
> -	if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP)))
> -		return NULL;
> -
> -	if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0)
> -		return NULL;
> -
> -	return flow_offload_lookup(flow_table, &tuple);
> -}
> -
>  static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
>  				   struct nf_flowtable *flow_table,
>  				   struct flow_offload_tuple_rhash *tuplehash,
> @@ -606,19 +591,33 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
>  				    struct flow_offload_tuple *tuple,
>  				    __be32 *ip_daddr)
>  {
> -	struct iphdr *iph = (struct iphdr *)skb_network_header(skb);
>  	struct rtable *rt = dst_rtable(tuple->dst_cache);
> -	u8 tos = iph->tos, ttl = iph->ttl;
> -	__be16 frag_off = iph->frag_off;
> -	u32 headroom = sizeof(*iph);
> +	__be16 frag_off = 0;
> +	struct iphdr *iph;
> +	u8 tos = 0, ttl;
> +	u32 headroom;
>  	int err;
>  
> +	if (tuple->tun.l3_proto == IPPROTO_IPV6) {
> +		struct ipv6hdr *ip6h;
> +
> +		ip6h = (struct ipv6hdr *)skb_network_header(skb);
> +		tos = ipv6_get_dsfield(ip6h);
> +		ttl = ip6h->hop_limit;
> +	} else {
> +		iph = (struct iphdr *)skb_network_header(skb);
> +		frag_off = iph->frag_off;
> +		tos = iph->tos;
> +		ttl = iph->ttl;
> +	}
> +
>  	err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4);
>  	if (err)
>  		return err;
>  
> -	skb_set_inner_ipproto(skb, IPPROTO_IPIP);
> -	headroom += LL_RESERVED_SPACE(rt->dst.dev) + rt->dst.header_len;
> +	skb_set_inner_ipproto(skb, tuple->tun.l3_proto);
> +	headroom = sizeof(*iph) + LL_RESERVED_SPACE(rt->dst.dev) +
> +		   rt->dst.header_len;
>  	err = skb_cow_head(skb, headroom);
>  	if (err)
>  		return err;
> @@ -629,6 +628,7 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
>  	/* Push down and install the IP header. */
>  	skb_push(skb, sizeof(*iph));
>  	skb_reset_network_header(skb);
> +	skb->protocol = htons(ETH_P_IP);
>  
>  	iph = ip_hdr(skb);
>  	iph->version	= 4;
> @@ -723,16 +723,6 @@ static int nf_flow_tunnel_push(struct net *net, struct sk_buff *skb,
>  	}
>  }
>  
> -static int nf_flow_tunnel_v6_push(struct net *net, struct sk_buff *skb,
> -				  struct flow_offload_tuple *tuple,
> -				  struct in6_addr **ip6_daddr)
> -{
> -	if (tuple->tun_num)
> -		return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, ip6_daddr);
> -
> -	return 0;
> -}
> -
>  static int nf_flow_encap_push(struct sk_buff *skb,
>  			      struct flow_offload_tuple *tuple,
>  			      struct net_device *outdev)
> @@ -830,103 +820,6 @@ static unsigned int nf_flow_queue_xmit(struct net *net, struct sk_buff *skb,
>  	return NF_STOLEN;
>  }
>  
> -unsigned int
> -nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
> -			const struct nf_hook_state *state)
> -{
> -	struct flow_offload_tuple_rhash *tuplehash;
> -	struct nf_flowtable *flow_table = priv;
> -	struct flow_offload_tuple *other_tuple;
> -	enum flow_offload_tuple_dir dir;
> -	struct nf_flowtable_ctx ctx = {
> -		.in	= state->in,
> -	};
> -	struct nf_flow_xmit xmit = {};
> -	struct in6_addr *ip6_daddr;
> -	struct flow_offload *flow;
> -	struct neighbour *neigh;
> -	struct rtable *rt;
> -	__be32 ip_daddr;
> -	int ret;
> -
> -	tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb);
> -	if (!tuplehash)
> -		return NF_ACCEPT;
> -
> -	ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb);
> -	if (ret < 0)
> -		return NF_DROP;
> -	else if (ret == 0)
> -		return NF_ACCEPT;
> -
> -	if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) {
> -		rt = dst_rtable(tuplehash->tuple.dst_cache);
> -		memset(skb->cb, 0, sizeof(struct inet_skb_parm));
> -		IPCB(skb)->iif = skb->dev->ifindex;
> -		IPCB(skb)->flags = IPSKB_FORWARDED;
> -		return nf_flow_xmit_xfrm(skb, state, &rt->dst);
> -	}
> -
> -	dir = tuplehash->tuple.dir;
> -	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
> -	other_tuple = &flow->tuplehash[!dir].tuple;
> -	ip_daddr = other_tuple->src_v4.s_addr;
> -
> -	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
> -				&ip6_daddr) < 0)
> -		return NF_DROP;
> -
> -	switch (tuplehash->tuple.xmit_type) {
> -	case FLOW_OFFLOAD_XMIT_NEIGH: {
> -		struct dst_entry *dst;
> -
> -		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
> -		if (!xmit.outdev) {
> -			flow_offload_teardown(flow);
> -			return NF_DROP;
> -		}
> -		if (other_tuple->tun.encap_proto == AF_INET6 ||
> -		    ctx.tun.proto == IPPROTO_IPV6) {
> -			struct rt6_info *rt6;
> -
> -			rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
> -			neigh = ip_neigh_gw6(rt6->dst.dev,
> -					     rt6_nexthop(rt6, ip6_daddr));
> -			dst = &rt6->dst;
> -		} else {
> -			rt = dst_rtable(tuplehash->tuple.dst_cache);
> -			neigh = ip_neigh_gw4(rt->dst.dev,
> -					     rt_nexthop(rt, ip_daddr));
> -			dst = &rt->dst;
> -		}
> -		if (IS_ERR(neigh)) {
> -			flow_offload_teardown(flow);
> -			return NF_DROP;
> -		}
> -		xmit.dest = neigh->ha;
> -		skb_dst_set_noref(skb, dst);
> -		break;
> -	}
> -	case FLOW_OFFLOAD_XMIT_DIRECT:
> -		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
> -		if (!xmit.outdev) {
> -			flow_offload_teardown(flow);
> -			return NF_DROP;
> -		}
> -		xmit.dest = tuplehash->tuple.out.h_dest;
> -		xmit.source = tuplehash->tuple.out.h_source;
> -		break;
> -	default:
> -		WARN_ON_ONCE(1);
> -		return NF_DROP;
> -	}
> -	xmit.tuple = other_tuple;
> -	xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment;
> -
> -	return nf_flow_queue_xmit(state->net, skb, &xmit);
> -}
> -EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
> -
>  static void nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff,
>  				 struct in6_addr *addr,
>  				 struct in6_addr *new_addr,
> @@ -1111,8 +1004,16 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
>  	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
>  
>  	mtu = flow->tuplehash[dir].tuple.mtu + ctx->offset;
> -	if (flow->tuplehash[!dir].tuple.tun_num)
> +	switch (flow->tuplehash[!dir].tuple.tun.encap_proto) {
> +	case AF_INET:
> +		mtu -= sizeof(struct iphdr);
> +		break;
> +	case AF_INET6:
>  		mtu -= sizeof(*ip6h);
> +		break;
> +	default:
> +		break;
> +	}
>  
>  	if (unlikely(nf_flow_exceeds_mtu(skb, mtu)))
>  		return 0;
> @@ -1146,6 +1047,25 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
>  	return 1;
>  }
>  
> +static struct flow_offload_tuple_rhash *
> +nf_flow_offload_lookup(struct nf_flowtable_ctx *ctx,
> +		       struct nf_flowtable *flow_table, struct sk_buff *skb)
> +{
> +	struct flow_offload_tuple tuple = {};
> +
> +	if (!nf_flow_skb_encap_protocol(ctx, skb, htons(ETH_P_IP)))
> +		return NULL;
> +
> +	if (ctx->tun.proto == IPPROTO_IPV6) {
> +		if (nf_flow_tuple_ipv6(ctx, skb, &tuple) < 0)
> +			return NULL;
> +	} else if (nf_flow_tuple_ip(ctx, skb, &tuple) < 0) {
> +		return NULL;
> +	}
> +
> +	return flow_offload_lookup(flow_table, &tuple);
> +}
> +
>  static struct flow_offload_tuple_rhash *
>  nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
>  			    struct nf_flowtable *flow_table,
> @@ -1166,6 +1086,108 @@ nf_flow_offload_ipv6_lookup(struct nf_flowtable_ctx *ctx,
>  	return flow_offload_lookup(flow_table, &tuple);
>  }
>  
> +unsigned int
> +nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
> +			const struct nf_hook_state *state)
> +{
> +	struct flow_offload_tuple_rhash *tuplehash;
> +	struct nf_flowtable *flow_table = priv;
> +	struct flow_offload_tuple *other_tuple;
> +	enum flow_offload_tuple_dir dir;
> +	struct nf_flowtable_ctx ctx = {
> +		.in	= state->in,
> +	};
> +	struct nf_flow_xmit xmit = {};
> +	struct in6_addr *ip6_daddr;
> +	struct flow_offload *flow;
> +	struct neighbour *neigh;
> +	struct rtable *rt;
> +	__be32 ip_daddr;
> +	int ret;
> +
> +	tuplehash = nf_flow_offload_lookup(&ctx, flow_table, skb);
> +	if (!tuplehash)
> +		return NF_ACCEPT;
> +
> +	if (ctx.tun.proto == IPPROTO_IPV6)
> +		ret = nf_flow_offload_ipv6_forward(&ctx, flow_table, tuplehash,
> +						   skb);
> +	else
> +		ret = nf_flow_offload_forward(&ctx, flow_table, tuplehash, skb);
> +	if (ret < 0)
> +		return NF_DROP;
> +	else if (ret == 0)
> +		return NF_ACCEPT;
> +
> +	if (unlikely(tuplehash->tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)) {
> +		rt = dst_rtable(tuplehash->tuple.dst_cache);
> +		memset(skb->cb, 0, sizeof(struct inet_skb_parm));
> +		IPCB(skb)->iif = skb->dev->ifindex;
> +		IPCB(skb)->flags = IPSKB_FORWARDED;
> +		return nf_flow_xmit_xfrm(skb, state, &rt->dst);
> +	}
> +
> +	dir = tuplehash->tuple.dir;
> +	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
> +	other_tuple = &flow->tuplehash[!dir].tuple;
> +	ip_daddr = other_tuple->src_v4.s_addr;
> +	ip6_daddr = &other_tuple->src_v6;
> +
> +	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
> +				&ip6_daddr) < 0)
> +		return NF_DROP;
> +
> +	switch (tuplehash->tuple.xmit_type) {
> +	case FLOW_OFFLOAD_XMIT_NEIGH: {
> +		struct dst_entry *dst;
> +
> +		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.ifidx);
> +		if (!xmit.outdev) {
> +			flow_offload_teardown(flow);
> +			return NF_DROP;
> +		}
> +		if (other_tuple->tun.encap_proto == AF_INET6 ||
> +		    ctx.tun.proto == IPPROTO_IPV6) {
> +			struct rt6_info *rt6;
> +
> +			rt6 = dst_rt6_info(tuplehash->tuple.dst_cache);
> +			neigh = ip_neigh_gw6(rt6->dst.dev,
> +					     rt6_nexthop(rt6, ip6_daddr));
> +			dst = &rt6->dst;
> +		} else {
> +			rt = dst_rtable(tuplehash->tuple.dst_cache);
> +			neigh = ip_neigh_gw4(rt->dst.dev,
> +					     rt_nexthop(rt, ip_daddr));
> +			dst = &rt->dst;
> +		}
> +		if (IS_ERR(neigh)) {
> +			flow_offload_teardown(flow);
> +			return NF_DROP;
> +		}
> +		xmit.dest = neigh->ha;
> +		skb_dst_set_noref(skb, dst);
> +		break;
> +	}
> +	case FLOW_OFFLOAD_XMIT_DIRECT:
> +		xmit.outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.out.ifidx);
> +		if (!xmit.outdev) {
> +			flow_offload_teardown(flow);
> +			return NF_DROP;
> +		}
> +		xmit.dest = tuplehash->tuple.out.h_dest;
> +		xmit.source = tuplehash->tuple.out.h_source;
> +		break;
> +	default:
> +		WARN_ON_ONCE(1);
> +		return NF_DROP;
> +	}
> +	xmit.tuple = other_tuple;
> +	xmit.needs_gso_segment = tuplehash->tuple.needs_gso_segment;
> +
> +	return nf_flow_queue_xmit(state->net, skb, &xmit);
> +}
> +EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook);
> +
>  unsigned int
>  nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
>  			  const struct nf_hook_state *state)
> @@ -1182,6 +1204,7 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
>  	struct flow_offload *flow;
>  	struct neighbour *neigh;
>  	struct rt6_info *rt;
> +	__be32 ip_daddr;
>  	int ret;
>  
>  	tuplehash = nf_flow_offload_ipv6_lookup(&ctx, flow_table, skb);
> @@ -1209,10 +1232,11 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
>  	dir = tuplehash->tuple.dir;
>  	flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]);
>  	other_tuple = &flow->tuplehash[!dir].tuple;
> +	ip_daddr = other_tuple->src_v4.s_addr;
>  	ip6_daddr = &other_tuple->src_v6;
>  
> -	if (nf_flow_tunnel_v6_push(state->net, skb, other_tuple,
> -				   &ip6_daddr) < 0)
> +	if (nf_flow_tunnel_push(state->net, skb, other_tuple, &ip_daddr,
> +				&ip6_daddr) < 0)
>  		return NF_DROP;
>  
>  	switch (tuplehash->tuple.xmit_type) {
> @@ -1226,10 +1250,8 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
>  		}
>  		if (other_tuple->tun.encap_proto == AF_INET ||
>  		    ctx.tun.proto == IPPROTO_IPIP) {
> -			__be32 ip_daddr = other_tuple->src_v4.s_addr;
>  			struct rtable *rt4;
>  
> -			skb->protocol = htons(ETH_P_IP);
>  			rt4 = dst_rtable(tuplehash->tuple.dst_cache);
>  			neigh = ip_neigh_gw4(rt4->dst.dev,
>  					     rt_nexthop(rt4, ip_daddr));
> 
> -- 
> 2.55.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply

* Re: [PATCH net-next] gtp: annotate PDP lookups under RTNL
From: Simon Horman @ 2026-07-08 10:35 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: Runyu Xiao, laforge, andrew+netdev, davem, edumazet, kuba, pabeni,
	osmocom-net-gprs, netdev, linux-kernel, jianhao.xu
In-Reply-To: <ak0SYEOvw6K_8C_w@chamomile>

On Tue, Jul 07, 2026 at 04:51:12PM +0200, Pablo Neira Ayuso wrote:
> Hi Simon,
> 
> On Tue, Jul 07, 2026 at 03:28:20PM +0100, Simon Horman wrote:
> > On Wed, Jul 01, 2026 at 08:39:25PM +0800, Runyu Xiao wrote:
> > > The GTP PDP lookup helpers are shared by RCU-protected data and report
> > > paths and RTNL-protected control paths such as gtp_genl_new_pdp(). The
> > > helpers walk RCU hlists, but they do not currently pass the RTNL
> > > condition for the control-path lookups.
> > > 
> > > Pass lockdep_rtnl_is_held() to the PDP hlist iterators. Existing
> > > RCU-reader callers remain valid because the RCU-list macros also accept
> > > an active RCU read-side section; the added condition only documents the
> > > non-RCU protection already used by RTNL control paths.
> > > 
> > > This was found by our static analysis tool and then manually reviewed
> > > against the current tree. The dynamic triage evidence is a
> > > target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
> > > to documenting the existing protection contract.
> > > 
> > > This is a lockdep annotation cleanup. It does not change PDP lifetime or
> > > hash updates.
> > > 
> > > Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
> > 
> > Reviewed-by: Simon Horman <horms@kernel.org>
> > 
> > There is an AI-generated review of this patch available on sashko.dev.
> > While I don't believe that the issues raised there should impede progress
> > of this patch you may want to look into them as possible follow-up.
> 
> This patch refers to the rtnl_lock, but it is the genetlink mutex that
> protects updates on the PDP context list.
> 
> Then, from packet path, rcu lookups are performed.
> 
> I think this patch is not correct.

Hi Pablo,

Of course you are correct.
Sorry for not realising this earlier.

^ permalink raw reply

* Re: [PATCH net-next 0/2] selftests: drv-net: run XDP tests with both IP versions
From: patchwork-bot+netdevbpf @ 2026-07-08 10:40 UTC (permalink / raw)
  To: Nimrod Oren
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, shuah, ast, daniel,
	hawk, john.fastabend, sdf, bobbyeshleman, willemb, weibunny, dw,
	cratiu, netdev, linux-kselftest, linux-kernel, bpf
In-Reply-To: <20260702062348.2123960-1-noren@nvidia.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Paolo Abeni <pabeni@redhat.com>:

On Thu, 2 Jul 2026 09:23:46 +0300 you wrote:
> The driver selftest environment picks a single address family for tests
> which use the generic address fields. When both IPv4 and IPv6 are
> available it prefers IPv6, which can leave IPv4 paths untested and hide
> IPv4-only bugs.
> 
> This happened recently in the XDP selftest, where an IPv4 checksum bug
> was missed when IPv6 was also configured. XDP traffic handling has
> IP-version-specific code paths, and the follow-up discussion concluded
> that the XDP tests should run against both IP versions [1].
> 
> [...]

Here is the summary with links:
  - [net-next,1/2] selftests: drv-net: allow switching env IP version
    https://git.kernel.org/netdev/net-next/c/432f4bab1ada
  - [net-next,2/2] selftests: drv-net: xdp: run with both IP versions
    https://git.kernel.org/netdev/net-next/c/47467501cb88

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [RFC] VEGA: a syzbot-like workflow for LLM-found kernel bugs
From: Laurent Pinchart @ 2026-07-08 10:47 UTC (permalink / raw)
  To: Yuan Tan
  Cc: linux-kernel, workflows, jhs, gregkh, sven, netdev,
	netfilter-devel, linux-crypto
In-Reply-To: <20260708092247.4188498-1-yuantan098@gmail.com>

On Wed, Jul 08, 2026 at 02:22:47AM -0700, Yuan Tan wrote:
> Hi all,
> 
> We would like to ask for feedback on a proposed workflow for reporting Linux
> kernel bugs found by an LLM-assisted code auditing tool that we have
> been developing since earlier this year.
> 
> Since February, we have been developing an LLM-driven kernel code auditing
> tool called VEGA. It started as a side project, but the results became much
> substantial than we expected: VEGA has found hundreds of valid bugs in Linux
> kernel.
> 
> That immediately created a practical problem: we do not want to dump a large
> pile of bug reports onto mail lists and annoy the maintainers.
> 
> The first thing we tried was to fix as many as we could ourselves. We
> started working with a group of student volunteers. Most of them are
> college students, so we have been training them, reviewing their patches,
> and trying to build an internal review process before anything is sent to
> the mailing list. The goal is to turn these findings into useful fixes, and
> also to help new contributors grow into people who can reduce maintainer
> workload instead of adding to it.
> 
> The process was not perfect. Some patches were not good enough, and we also
> made some mistakes early on when deciding what should be called a security
> issue.  Our internal review process has been improving with the help of the
> community.
> 
> Since March, we picked up non-root triggerable bug first and have worked on
> fixes for more than 100 validated kernel bugs. we especially want to thank
> the students and professor who have helped a lot with this effort.
> 
> But the remaining queue is still too large for us to handle.
> 
> Recently Jamal pointed out problems around our tags. That made me realize
> that we should probably stop treating this as an ad-hoc patch effort and
> build something closer to syzbot: public, reproducible, trackable,
> deduplicated, and useful to maintainers.
> 
> So this mail is an RFC for a VEGA reporting workflow.
> 
> The rough idea
> ==============
> 
> VEGA would have a public dashboard, similar to syzbot, and would
> send selected bug reports to the relevant kernel mailing lists.
> 
> The goal is to send reports that contain enough information for maintainers
> or other developers to pick up, understand, reproduce and fix the issue.
> 
> For each public report, we expect to include:
> 
>   - a description of the bug
>   - the tested kernel tree and commit
>   - the kernel config and environment
>   - the crash log
>   - a minimized user-space reproducer
>   - the suspected introducing commit
>   - a suggested fix patch
> 
> The suggested fix patch is meant to reduce maintainer burden. It still need
> human review, but hopefully it can save a lot time from building a patch
> from scratch.

Will the information included in the public report (including the
suggested fix) be written by a human or an LLM ? In the latter case I
don't see how you could reasonably claim to reduce maintainer burden, so
that would be a big NACK as far as I'm concerned.

> What will be public
> ===================
> 
> All VEGA findings that we have evaluated as not having major security
> impact can be published on the VEGA dashboard. The dashboard would make it
> possible to see what VEGA found, whether the issue was reproduced, whether
> a fix exists, whether it was reported to a mailing list, and whether it has
> been fixed upstream.
> 
> For issues that we have validated as having possible serious security
> impact, we will not publish it on the public dashboard before going through
> the appropriate kernel security process.
> 
> Dumping everything onto the mailing list may be annoying. During the initial
> stage, reports will be rate-limited and sent manually. We will check for
> duplicates against lore/upstream, and make sure the issue is not already
> fixed or reported.
> 
> Report identity and tags
> ========================
> 
> Each public VEGA report will have a stable identity, similar to
> syzbot reports.
> 
> One possible format is:
> 
>   Reported-by: VEGA <vega+HASH@DOMAIN>
>   Closes: <public dashboard URL>
> 
> =========
> 
> We would like to hear what maintainers think about this before we start
> sending these reports.
> 
> We do not want VEGA to become another source of mailing list noise. The goal
> is to make LLM-based bug finding transparent and useful, and to make sure
> the reports come with enough context, reproducers, suggested fixes, and
> tracking so that they reduce work rather than create more.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply

* Re: [PATCH net-next v4 1/3] net: devmem: allow rx-buf-size > PAGE_SIZE per dmabuf binding
From: Paolo Abeni @ 2026-07-08 10:50 UTC (permalink / raw)
  To: Mina Almasry, Bobby Eshleman
  Cc: Donald Hunter, Jakub Kicinski, David S. Miller, Eric Dumazet,
	Simon Horman, Andrew Lunn, Gerd Hoffmann, Vivek Kasireddy,
	Sumit Semwal, Christian König, Shuah Khan, netdev,
	linux-kernel, dri-devel, linux-media, linaro-mm-sig,
	linux-kselftest, sdf, razor, daniel, matttbe, skhawaja, dw,
	Joe Damato, Bobby Eshleman
In-Reply-To: <CAHS8izNdJ1LTOr_pLjXef6Yv-=JOFPe1GcZtcbStD93Tkpy1XQ@mail.gmail.com>

On 7/7/26 10:36 PM, Mina Almasry wrote:
> On Wed, Jul 1, 2026 at 12:22 PM Bobby Eshleman <bobbyeshleman@gmail.com> wrote:
>>
>> From: Bobby Eshleman <bobbyeshleman@meta.com>
>>
>> Every devmem dmabuf binding today hands the page_pool PAGE_SIZE niovs.
>> This caps a single RX descriptor at PAGE_SIZE, burning CPU on buffer
>> churn for large flows.
>>
>> Add a bind-time netlink attribute, NETDEV_A_DMABUF_RX_BUF_SIZE, that
>> lets userspace request a larger niov size. The value must be a power of
>> two >= PAGE_SIZE.
>>
>> Measurements
>> ------------

Checkpatch complains about this separator usage:

ERROR: Invalid commit separator - some tools may have problems applying this
#15:
------------

Please replace or remove it in the next revision

>> @@ -90,16 +90,17 @@ net_devmem_alloc_dmabuf(struct net_devmem_dmabuf_binding *binding)
>>         struct dmabuf_genpool_chunk_owner *owner;
>>         unsigned long dma_addr;
>>         struct net_iov *niov;
>> -       ssize_t offset;
>> -       ssize_t index;
>> +       size_t offset;
>> +       size_t index;
>>
> 
> nit: I would keep this signed. Some of the most frustrating issues I
> ran into is some of the underflowing and then passing a > check or
> something. Although if the LLM is not complaining about this
> particular case, there is probably no issue with it. I also notice a
> lot of existing code that deals with indexes and offsets goes for
> signed.

At very least the above change should go in a separate patch, as is
quite unrelated from the rest.

/P


^ permalink raw reply

* Re: [PATCH] dt-bindings: net: convert microchip,lan78xx.txt to YAML schema
From: Krzysztof Kozlowski @ 2026-07-08 10:56 UTC (permalink / raw)
  To: Mikhail Lukianchikov
  Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Rengarajan Sundararajan, UNGLinuxDriver, netdev, devicetree,
	linux-kernel
In-Reply-To: <20260707165840.107409-1-avermoal@gmail.com>

On Tue, Jul 07, 2026 at 10:58:40PM +0600, Mikhail Lukianchikov wrote:
> Convert the Microchip LAN78xx family (LAN7800, LAN7801, LAN7850) binding
> documentation from plain text to DT schema format using YAML.

Subject: there is no YAML schema

https://elixir.bootlin.com/linux/v7.1-rc7/source/Documentation/devicetree/bindings/submitting-patches.rst#L23

> 
> The conversion was validated with 'make dt_binding_check'

Drop, irrelevant and not even true. There is no point in writing in
commit msg that you performed the absolute minimal expected build
testing. It is expected.


> 
> Signed-off-by: Mikhail Lukianchikov <avermoal@gmail.com>

...

You left stale maintainer entry. Checkpatch told you that, no? Did you
run checkpatch?

If this is your first contribution then you need to read carefully
guidelines how to contribute and then actually follow these guidelines.
What does evry guideline speak about? Checkpatch.

> diff --git a/Documentation/devicetree/bindings/net/microchip,lan78xx.yaml b/Documentation/devicetree/bindings/net/microchip,lan78xx.yaml
> new file mode 100644
> index 000000000000..743667c1e761
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/microchip,lan78xx.yaml


microchip,lan7800.yaml

> @@ -0,0 +1,113 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/microchip,lan78xx.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Microchip LAN78xx Gigabit Ethernet controller
> +
> +maintainers:
> +  - Rengarajan Sundararajan <Rengarajan.S@microchip.com>
> +  - UNGLinuxDriver <UNGLinuxDriver@microchip.com>

Drop last email.

> +
> +description:
> +  The LAN78XX devices are usually configured by programming their OTP or with
> +  an external EEPROM, but some platforms (e.g. Raspberry Pi 3 B+) have neither.
> +  The Device Tree properties, if present, override the OTP and EEPROM.
> +
> +allOf:
> +  - $ref: /schemas/usb/usb-device.yaml#
> +  - $ref: /schemas/net/ethernet-controller.yaml#
> +
> +properties:
> +  compatible:
> +    enum:
> +      - usb424,7800
> +      - usb424,7801
> +      - usb424,7850

Odd format. Missing blank lines. Look at existing code to understand how
this is supposed to look like.

> +  reg:
> +    maxItems: 1
> +    description: USB port number
> +  local-mac-address:
> +    $ref: /schemas/types.yaml#/definitions/uint8-array
> +    minItems: 6
> +    maxItems: 6
> +    description:
> +      MAC address to use if not stored in OTP or EEPROM. If present,
> +      overrides OTP/EEPROM.
> +  mdio:
> +    $ref: /schemas/net/mdio.yaml#
> +    unevaluatedProperties: false
> +
> +patternProperties:
> +  "^ethernet-phy(@[0-9a-f]+)?$":
> +    type: object
> +    description: |
> +      PHY node for the embedded or external PHY. The PHY address is
> +      given by the 'reg' property.
> +    properties:
> +      reg:
> +        maxItems: 1
> +        description: PHY address.
> +      microchip,led-modes:
> +        $ref: /schemas/types.yaml#/definitions/uint32-array
> +        minItems: 1
> +        maxItems: 4
> +        description:
> +          Array of LED mode values for each of up to 4 LEDs.
> +          Omitted LEDs are turned off. Allowed values are defined
> +          in include/dt-bindings/net/microchip-lan78xx.h.
> +    required:
> +      - reg
> +    additionalProperties: false
> +
> +required:
> +  - compatible
> +  - reg
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/net/microchip-lan78xx.h>
> +    / {
> +        usb: usb {
> +            compatible = "usb-host";
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +        };
> +    };
> +    &usb {

Drop all this, irrelevant and not even close to expected style.

> +        usb-port@1 {
> +            compatible = "usb424,2514";

Drop node

> +            reg = <1>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            usb-port@1 {
> +                compatible = "usb424,2514";

Drop node, not relevant to this binding.

Again, look at other bindings to understand what to write. Or read
DTS101 slides.

> +                reg = <1>;
> +                #address-cells = <1>;
> +                #size-cells = <0>;

Best regards,
Krzysztof


^ permalink raw reply

* Re: [PATCH net v3 1/2] net: mana: Validate the packet length reported by the NIC
From: Paolo Abeni @ 2026-07-08 10:57 UTC (permalink / raw)
  To: Dexuan Cui, kys, haiyangz, wei.liu, longli, andrew+netdev, davem,
	edumazet, kuba, kotaranov, horms, ernis, dipayanroy, kees,
	jacob.e.keller, ssengar, linux-hyperv, netdev, linux-kernel,
	linux-rdma
  Cc: stable
In-Reply-To: <20260702041237.617719-2-decui@microsoft.com>

On 7/2/26 6:12 AM, Dexuan Cui wrote:
> Validate the packet length reported in the RX CQE before passing it
> to skb processing. The CQE is supplied by the NIC device and should
> not be blindly trusted.
> 
> Cc: stable@vger.kernel.org

This need a Fixes: tag, to help stable team backport.

No need to repost: just reply here, and I'll add it while applying the
patch.

Thanks,

Paolo


^ permalink raw reply

* Re: [PATCH net v2 2/2] vsock/test: add test for small packets under pressure
From: Michael S. Tsirkin @ 2026-07-08 10:59 UTC (permalink / raw)
  To: Stefano Garzarella
  Cc: netdev, Jason Wang, Xuan Zhuo, Eric Dumazet, Eugenio Pérez,
	Simon Horman, Stefan Hajnoczi, David S. Miller, linux-kernel, kvm,
	Paolo Abeni, virtualization, Jakub Kicinski, Jason Wang
In-Reply-To: <20260708102904.50732-3-sgarzare@redhat.com>

On Wed, Jul 08, 2026 at 12:29:04PM +0200, Stefano Garzarella wrote:
> From: Stefano Garzarella <sgarzare@redhat.com>
> 
> Add a test that sends 2 MB of data using randomly sized small packets
> (129-512 bytes) over a SOCK_STREAM connection. Packets above
> GOOD_COPY_LEN (128) bypass the in-place coalescing in recv_enqueue(),
> forcing each one into its own skb.
> 
> Without receive queue collapsing, the per-skb overhead eventually
> exceeds buf_alloc and the connection is reset. The test verifies
> that all data arrives and that content integrity is preserved.
> 
> Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>

maybe cut down SO_VM_SOCKETS_BUFFER_SIZE? will make it easier to
trigger?

anyway

Acked-by: Michael S. Tsirkin <mst@redhat.com>

> ---
>  tools/testing/vsock/vsock_test.c | 87 ++++++++++++++++++++++++++++++++
>  1 file changed, 87 insertions(+)
> 
> diff --git a/tools/testing/vsock/vsock_test.c b/tools/testing/vsock/vsock_test.c
> index 76be0e4a7f0e..b4ff9f946565 100644
> --- a/tools/testing/vsock/vsock_test.c
> +++ b/tools/testing/vsock/vsock_test.c
> @@ -2347,6 +2347,88 @@ static void test_stream_tx_credit_bounds_server(const struct test_opts *opts)
>  	close(fd);
>  }
>  
> +/* Test that many small packets don't cause a connection reset under pressure
> + * and that data integrity is preserved.  Packet sizes vary randomly between
> + * 129 and 512 bytes, above GOOD_COPY_LEN (128) to bypass in-place coalescing
> + * in recv_enqueue, forcing each one into its own skb.  Without receive queue
> + * collapsing, the per-skb overhead eventually exceeds buf_alloc and the
> + * connection is reset.
> + */
> +#define COLLAPSE_PKT_MIN 129
> +#define COLLAPSE_PKT_MAX 512
> +#define COLLAPSE_TOTAL (2 * 1024 * 1024)
> +
> +static void test_stream_collapse_client(const struct test_opts *opts)
> +{
> +	unsigned char *data;
> +	unsigned long hash;
> +	size_t offset = 0;
> +	int i, fd;
> +
> +	data = malloc(COLLAPSE_TOTAL);
> +	if (!data) {
> +		perror("malloc");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	for (i = 0; i < COLLAPSE_TOTAL; i++)
> +		data[i] = rand() & 0xff;
> +
> +	fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
> +	if (fd < 0) {
> +		perror("connect");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	while (offset < COLLAPSE_TOTAL) {
> +		size_t pkt_size = COLLAPSE_PKT_MIN +
> +			rand() % (COLLAPSE_PKT_MAX - COLLAPSE_PKT_MIN + 1);
> +
> +		pkt_size = min(pkt_size, COLLAPSE_TOTAL - offset);
> +
> +		send_buf(fd, data + offset, pkt_size, 0, pkt_size);
> +		offset += pkt_size;
> +	}
> +
> +	hash = hash_djb2(data, COLLAPSE_TOTAL);
> +	control_writeulong(hash);
> +
> +	free(data);
> +	close(fd);
> +}
> +
> +static void test_stream_collapse_server(const struct test_opts *opts)
> +{
> +	unsigned long hash, remote_hash;
> +	unsigned char *data;
> +	int fd;
> +
> +	data = malloc(COLLAPSE_TOTAL);
> +	if (!data) {
> +		perror("malloc");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
> +	if (fd < 0) {
> +		perror("accept");
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	recv_buf(fd, data, COLLAPSE_TOTAL, 0, COLLAPSE_TOTAL);
> +
> +	hash = hash_djb2(data, COLLAPSE_TOTAL);
> +	remote_hash = control_readulong();
> +	if (hash != remote_hash) {
> +		fprintf(stderr, "hash mismatch: local %lu remote %lu\n",
> +			hash, remote_hash);
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	free(data);
> +	close(fd);
> +}
> +
>  static struct test_case test_cases[] = {
>  	{
>  		.name = "SOCK_STREAM connection reset",
> @@ -2546,6 +2628,11 @@ static struct test_case test_cases[] = {
>  		.run_client = test_stream_msg_peek_client,
>  		.run_server = test_stream_peek_after_recv_server,
>  	},
> +	{
> +		.name = "SOCK_STREAM small packets backpressure",
> +		.run_client = test_stream_collapse_client,
> +		.run_server = test_stream_collapse_server,
> +	},
>  	{},
>  };
>  
> -- 
> 2.55.0


^ permalink raw reply

* Re: [PATCH net v2 1/2] vsock/virtio: collapse receive queue under memory pressure
From: Michael S. Tsirkin @ 2026-07-08 11:00 UTC (permalink / raw)
  To: Stefano Garzarella
  Cc: netdev, Jason Wang, Xuan Zhuo, Eric Dumazet, Eugenio Pérez,
	Simon Horman, Stefan Hajnoczi, David S. Miller, linux-kernel, kvm,
	Paolo Abeni, virtualization, Jakub Kicinski, Jason Wang, stable,
	Brien Oberstein
In-Reply-To: <20260708102904.50732-2-sgarzare@redhat.com>

On Wed, Jul 08, 2026 at 12:29:03PM +0200, Stefano Garzarella wrote:
> From: Stefano Garzarella <sgarzare@redhat.com>
> 
> When many small packets accumulate in the receive queue, the skb overhead
> can exceed buf_alloc even while the payload is within bounds. This causes
> virtio_transport_inc_rx_pkt() to reject packets, leading to connection
> resets during large transfers under backpressure.
> 
> The issue was reported by Brien, who has a reproducer, but it is also
> easily reproducible with iperf-vsock [1] using a small packet size:
> 
>   iperf3 --vsock -c $CID -l 129
> 
> which fails immediately without this patch but with commit 059b7dbd20a6
> ("vsock/virtio: fix potential unbounded skb queue").
> 
> Inspired by TCP's tcp_collapse() which solves a similar problem, add
> virtio_transport_collapse_rx_queue() that walks the receive queue and
> re-copies data into compact linear skbs to reduce the overhead.
> 
> The collapse is triggered proactively from when the number of skb queued
> is close to exceeding the overhead budget.
> 
> A pre-scan counts the eligible bytes to size each allocation precisely,
> avoiding waste for isolated small packets. Partially consumed skbs are
> kept as-is to preserve buf_used/fwd_cnt accounting, EOM-marked skbs to
> maintain SEQPACKET message boundaries, and skbs already larger than the
> collapse target because they already have a good data-to-overhead ratio.
> 
> Walking a large queue may take a significant amount of time and cache
> misses, causing traffic burstiness. To limit this, the collapse stops
> once enough room is freed for this packet and the next one, but may
> opportunistically free more to fill each collapsed skb to capacity.
> 
> [1] https://github.com/stefano-garzarella/iperf-vsock
> 
> Fixes: 059b7dbd20a6 ("vsock/virtio: fix potential unbounded skb queue")
> Cc: stable@vger.kernel.org
> Reported-by: Brien Oberstein <brienpub@gmail.com>
> Closes: https://lore.kernel.org/netdev/618701dd023e$063de350$12b9a9f0$@gmail.com/
> Tested-by: Brien Oberstein <brienpub@gmail.com>
> Signed-off-by: Stefano Garzarella <sgarzare@redhat.com>


this is the right approach

Acked-by: Michael S. Tsirkin <mst@redhat.com>

> ---
> v2:
> - defined MAX_COLLAPSE_LEN macro instead of using a variable [Paolo]
> - added a threshold to avoid walking all the queue while collapsing
>   [Paolo]
> - collapsed the queue before calling virtio_transport_inc_rx_pkt().
>   While working on the threshold, I figured out that the check I was
>   introducing can also be used to proactively trigger the collapse, so I
>   moved the call to virtio_transport_collapse_rx_queue() before acquiring
>   the rx_lock to have also a better diff to simplify backports
> - improved code readability (removed `out` label, `keep` initialization,
>   etc.) [Paolo + other small stuff]
> - Brien kindly retested this version as well (thank you so much)
> ---
>  net/vmw_vsock/virtio_transport_common.c | 165 +++++++++++++++++++++++-
>  1 file changed, 164 insertions(+), 1 deletion(-)
> 
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index 09475007165b..8becad81279c 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -26,6 +26,13 @@
>  /* Threshold for detecting small packets to copy */
>  #define GOOD_COPY_LEN  128
>  
> +/* Max payload that can be collapsed into a single linear skb, using the same
> + * allocation threshold as virtio_vsock_alloc_skb() to avoid adding pressure
> + * on the page allocator.
> + */
> +#define MAX_COLLAPSE_LEN \
> +	SKB_MAX_ORDER(VIRTIO_VSOCK_SKB_HEADROOM, PAGE_ALLOC_COSTLY_ORDER)
> +
>  static void virtio_transport_cancel_close_work(struct vsock_sock *vsk,
>  					       bool cancel_timeout);
>  static s64 virtio_transport_has_space(struct virtio_vsock_sock *vvs);
> @@ -420,6 +427,145 @@ static int virtio_transport_send_pkt_info(struct vsock_sock *vsk,
>  	return ret;
>  }
>  
> +static bool virtio_transport_can_collapse(struct sk_buff *skb)
> +{
> +	/* skbs that are partially consumed, mark a SEQPACKET message boundary,
> +	 * or are already large enough should not be collapsed: they either
> +	 * need special accounting, carry protocol state, or already have a
> +	 * good data-to-overhead ratio.
> +	 */
> +	if (VIRTIO_VSOCK_SKB_CB(skb)->offset)
> +		return false;
> +	if (le32_to_cpu(virtio_vsock_hdr(skb)->flags) & VIRTIO_VSOCK_SEQ_EOM)
> +		return false;
> +	if (skb->len >= MAX_COLLAPSE_LEN)
> +		return false;
> +	return true;
> +}
> +
> +/* Iterate through the packets in the queue starting from the current skb to
> + * count the number of bytes we can collapse.
> + */
> +static unsigned int
> +virtio_transport_collapse_size(struct sk_buff *skb, struct sk_buff_head *queue)
> +{
> +	unsigned int target = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset;
> +
> +	while ((skb = skb_peek_next(skb, queue)) &&
> +	       virtio_transport_can_collapse(skb)) {
> +		unsigned int len = skb->len - VIRTIO_VSOCK_SKB_CB(skb)->offset;
> +
> +		if (len > MAX_COLLAPSE_LEN - target)
> +			return target;
> +
> +		target += len;
> +	}
> +
> +	return target;
> +}
> +
> +/* Called under lock_sock to compact the receive queue by merging small skbs.
> + * @min_to_free: minimum number of skbs to eliminate from the queue. May free
> + *               more to fill each collapsed skb to capacity.
> + */
> +static void
> +virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
> +				   u32 min_to_free)
> +{
> +	struct sk_buff *skb, *next_skb, *new_skb = NULL;
> +	struct sk_buff_head new_queue;
> +	u32 saved = 0;
> +
> +	__skb_queue_head_init(&new_queue);
> +
> +	skb_queue_walk_safe(&vvs->rx_queue, skb, next_skb) {
> +		struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
> +		u32 src_off = VIRTIO_VSOCK_SKB_CB(skb)->offset;
> +		u32 src_len = skb->len - src_off;
> +		bool keep;
> +
> +		keep = !virtio_transport_can_collapse(skb);
> +		if (keep) {
> +			/* Finalize pending collapsed skb to preserve packet
> +			 * ordering.
> +			 */
> +			if (new_skb) {
> +				__skb_queue_tail(&new_queue, new_skb);
> +				new_skb = NULL;
> +				saved--;
> +			}
> +			goto next;
> +		}
> +
> +		/* Finalize if this packet won't fit in the remaining tailroom,
> +		 * so we can allocate a right-sized new_skb.
> +		 */
> +		if (new_skb && src_len > skb_tailroom(new_skb)) {
> +			__skb_queue_tail(&new_queue, new_skb);
> +			new_skb = NULL;
> +			saved--;
> +		}
> +
> +		if (!new_skb) {
> +			unsigned int alloc_size;
> +
> +			/* Check after finalizing to opportunistically fill
> +			 * each collapsed skb to capacity, merging more skbs
> +			 * than strictly required.
> +			 */
> +			if (saved >= min_to_free)
> +				break;
> +
> +			alloc_size = virtio_transport_collapse_size(skb, &vvs->rx_queue);
> +
> +			/* Only this skb's data is eligible, nothing to merge
> +			 * with. Keep as-is.
> +			 */
> +			if (alloc_size <= src_len) {
> +				keep = true;
> +				goto next;
> +			}
> +
> +			new_skb = virtio_vsock_alloc_linear_skb(alloc_size +
> +					VIRTIO_VSOCK_SKB_HEADROOM, GFP_KERNEL);
> +			if (!new_skb)
> +				break;
> +
> +			memcpy(virtio_vsock_hdr(new_skb), hdr,
> +			       sizeof(struct virtio_vsock_hdr));
> +			virtio_vsock_hdr(new_skb)->len = 0;
> +		}
> +
> +		/* Cannot fail since src_off/src_len are within bounds, but if
> +		 * it does, discard new_skb to avoid queuing corrupted data.
> +		 */
> +		if (WARN_ON_ONCE(skb_copy_bits(skb, src_off,
> +					       skb_put(new_skb, src_len),
> +					       src_len))) {
> +			kfree_skb(new_skb);
> +			new_skb = NULL;
> +			break;
> +		}
> +
> +		le32_add_cpu(&virtio_vsock_hdr(new_skb)->len, src_len);
> +		virtio_vsock_hdr(new_skb)->flags |= hdr->flags;
> +
> +next:
> +		__skb_unlink(skb, &vvs->rx_queue);
> +		if (keep) {
> +			__skb_queue_tail(&new_queue, skb);
> +		} else {
> +			consume_skb(skb);
> +			saved++;
> +		}
> +	}
> +
> +	if (new_skb)
> +		__skb_queue_tail(&new_queue, new_skb);
> +
> +	skb_queue_splice(&new_queue, &vvs->rx_queue);
> +}
> +
>  static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
>  					u32 len)
>  {
> @@ -1354,12 +1500,29 @@ virtio_transport_recv_enqueue(struct vsock_sock *vsk,
>  {
>  	struct virtio_vsock_sock *vvs = vsk->trans;
>  	bool can_enqueue, free_pkt = false;
> +	u32 len, queue_max, queue_len;
>  	struct virtio_vsock_hdr *hdr;
> -	u32 len;
>  
>  	hdr = virtio_vsock_hdr(skb);
>  	len = le32_to_cpu(hdr->len);
>  
> +	/* virtio_transport_inc_rx_pkt() rejects packets when the per-skb
> +	 * overhead (skb_queue_len * SKB_TRUESIZE(0)) exceeds buf_alloc.
> +	 * Proactively collapse the queue before that happens.
> +	 * No rx_lock needed: lock_sock is held by caller, preventing
> +	 * concurrent enqueue or dequeue.
> +	 */
> +	queue_max = vvs->buf_alloc / SKB_TRUESIZE(0);
> +	queue_len = skb_queue_len(&vvs->rx_queue);
> +	if (queue_len >= queue_max) {
> +		/* Walking a large queue may take a significant amount of time
> +		 * and cache misses, causing traffic burstiness. Limit the
> +		 * collapse to freeing room for this packet and the next one.
> +		 * It may free more to fill each collapsed skb to capacity.
> +		 */
> +		virtio_transport_collapse_rx_queue(vvs, queue_len + 2 - queue_max);
> +	}
> +
>  	spin_lock_bh(&vvs->rx_lock);
>  
>  	can_enqueue = virtio_transport_inc_rx_pkt(vvs, len);
> -- 
> 2.55.0


^ permalink raw reply

* Re: [PATCH v1 net-next] net: phy: Drop #inclusion of <linux/mod_devicetable.h> from <linux/mdio.h>
From: Uwe Kleine-König (The Capable Hub) @ 2026-07-08 11:02 UTC (permalink / raw)
  To: Andrew Lunn; +Cc: Heiner Kallweit, Russell King, netdev, linux-kernel
In-Reply-To: <2f07f731-0b97-4a23-8ccd-f01638fc1c7e@lunn.ch>

[-- Attachment #1: Type: text/plain, Size: 1791 bytes --]

On Tue, Jul 07, 2026 at 02:10:56PM +0200, Andrew Lunn wrote:
> On Tue, Jul 07, 2026 at 09:15:35AM +0200, Uwe Kleine-König (The Capable Hub) wrote:
> > Hello Andrew,
> > 
> > On Mon, Jul 06, 2026 at 11:28:33PM +0200, Andrew Lunn wrote:
> > > > That's exactly my thought, and there is commit ad428f5811bd
> > > > ("mod_devicetable.h: Split into per subsystem headers") that is part of
> > > > v7.2-rc2 and that does exactly that (apart from the filename).
> > > 
> > > Ah, O.K. net-next tends to great dragged along the -rcX versions. So
> > > you can probably submit the networking changes in a weeks time. Just
> > > make sure they apply to
> > > 
> > > https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git
> > 
> > Note that while I created the patch under discussion on top of -rc2, the
> > justification is also valid on -rc1 and the patch doesn't use the new
> > headers, so it can be applied to the above repo already now.
> 
> Yes, our patchwork instance and its CI agree:
> 
> https://patchwork.kernel.org/project/netdevbpf/patch/ca270a534d0f230a939a3fb4a661808b35d6436d.1783329817.git.u.kleine-koenig@baylibre.com/

That's great.

> but i was actually meaning the patches which swaps to the broken out
> header with only struct mdio_device_id. I assume that patch is the one
> which really reduces the number of files which get recompiled on
> unrelated changes. That patch can be submitted to netdev probably next
> week.

Ah, indeed. After 995832b2cebe6969d1b42635db698803ee31294d and its
parent there are not many left affecting netdev. But ack, I will wait a
bit before tackling drivers/net/wireless/intel/iwlwifi/iwl-config.h
(which is the only remaining user of <linux/mod_devicetable.h> in
drivers/net).

Best regards
Uwe

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply

* Re: [PATCH net] nfc: pn533: fix use-after-free in pn533_recv_frame
From: Simon Horman @ 2026-07-07 18:06 UTC (permalink / raw)
  To: dddddd
  Cc: Simon Horman, netdev, david, error27, krzk, kees, kuba, sameo,
	michael.thalmeier, dzm91, hust-os-kernel-patches
In-Reply-To: <20260627131345.1548407-1-dddddd@hust.edu.cn>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://sashiko.dev
---
nfc: pn533: fix use-after-free in pn533_recv_frame

This commit adds a spinlock cmd_state_lock to serialize access to dev->cmd 
in pn533_recv_frame() and the command completion worker. It aims to prevent 
a use-after-free when a command is detached and freed while being 
concurrently received.

> diff --git a/drivers/nfc/pn533/pn533.c b/drivers/nfc/pn533/pn533.c
> index d7bdbc82e2ba2..921e93a5f16f5 100644
> --- a/drivers/nfc/pn533/pn533.c
> +++ b/drivers/nfc/pn533/pn533.c

[ ... ]

> @@ -458,10 +477,10 @@ static int __pn533_send_async(struct pn533 *dev, u8 cmd_code,
>  	mutex_lock(&dev->cmd_lock);
>  
>  	if (!dev->cmd_pending) {
> -		dev->cmd = cmd;
> +		pn533_set_current_cmd(dev, cmd);
>  		rc = dev->phy_ops->send_frame(dev, req);
>  		if (rc) {
> -			dev->cmd = NULL;
> +			pn533_set_current_cmd(dev, NULL);

[Severity: High]
This is a pre-existing issue, but if dev->phy_ops->send_frame() fails, it
jumps to the error label and frees cmd. If a concurrent receive scheduled 
the complete work, could that work also free cmd, causing a double free?

>  			goto error;
>  		}

[ ... ]

> @@ -569,10 +588,10 @@ static void pn533_wq_cmd(struct work_struct *work)
>  
>  	mutex_unlock(&dev->cmd_lock);
>  
> -	dev->cmd = cmd;
> +	pn533_set_current_cmd(dev, cmd);
>  	rc = dev->phy_ops->send_frame(dev, cmd->req);
>  	if (rc < 0) {
> -		dev->cmd = NULL;
> +		pn533_set_current_cmd(dev, NULL);

[Severity: High]
This isn't a bug introduced by this patch, but if dev->phy_ops->send_frame() 
fails in pn533_wq_cmd(), cmd is freed. Does a race condition exist here 
similar to __pn533_send_async(), where concurrent complete work could also 
free cmd?

[Severity: High]
Additionally, this is also a pre-existing issue, but does this error path 
drop the command without calling complete_cb and without clearing 
dev->cmd_pending? Could this leave the command queue permanently hung 
upon transient bus errors?

>  		dev_kfree_skb(cmd->req);
>  		kfree(cmd);
>  		return;
>  	}

[ ... ]

> @@ -2165,6 +2184,15 @@ static int pn533_data_exchange_complete(struct pn533 *dev, void *_arg,
>   */
>  void pn533_recv_frame(struct pn533 *dev, struct sk_buff *skb, int status)
>  {
> +	unsigned long flags;
> +
> +	/*
> +	 * Hold cmd_state_lock across the whole receive path so the current
> +	 * command cannot be freed by pn533_send_async_complete() between the
> +	 * dev->cmd check and the stores into it.
> +	 */
> +	spin_lock_irqsave(&dev->cmd_state_lock, flags);
> +
>  	if (!dev->cmd)
>  		goto sched_wq;

[Severity: Medium]
This isn't a bug introduced by this patch, but if there is no active command 
(!dev->cmd), this jumps to sched_wq without freeing the skb. Does this leak 
the skb if an unexpected frame arrives?

>  
>  	dev->cmd->status = status;

[ ... ]

> @@ -2182,6 +2210,7 @@ void pn533_recv_frame(struct pn533 *dev, struct sk_buff *skb, int status)
>  
>  	if (pn533_rx_frame_is_ack(skb->data)) {
>  		dev_dbg(dev->dev, "%s: Received ACK frame\n", __func__);
> +		spin_unlock_irqrestore(&dev->cmd_state_lock, flags);
>  		dev_kfree_skb(skb);
>  		return;
>  	}

[Severity: High]
This is a pre-existing issue, but does pn533_rx_frame_is_ack() safely read 
from skb->data here? Since there doesn't appear to be a check for skb->len 
before calling it, could this result in an out-of-bounds read if a malformed 
or short frame is received?

^ permalink raw reply

* Re: [PATCH net-next] gtp: annotate PDP lookups under RTNL
From: Paolo Abeni @ 2026-07-08 11:10 UTC (permalink / raw)
  To: Simon Horman, Pablo Neira Ayuso
  Cc: Runyu Xiao, laforge, andrew+netdev, davem, edumazet, kuba,
	osmocom-net-gprs, netdev, linux-kernel, jianhao.xu
In-Reply-To: <20260708103525.GJ1364329@horms.kernel.org>

On 7/8/26 12:35 PM, Simon Horman wrote:
> On Tue, Jul 07, 2026 at 04:51:12PM +0200, Pablo Neira Ayuso wrote:
>> On Tue, Jul 07, 2026 at 03:28:20PM +0100, Simon Horman wrote:
>>> On Wed, Jul 01, 2026 at 08:39:25PM +0800, Runyu Xiao wrote:
>>>> The GTP PDP lookup helpers are shared by RCU-protected data and report
>>>> paths and RTNL-protected control paths such as gtp_genl_new_pdp(). The
>>>> helpers walk RCU hlists, but they do not currently pass the RTNL
>>>> condition for the control-path lookups.
>>>>
>>>> Pass lockdep_rtnl_is_held() to the PDP hlist iterators. Existing
>>>> RCU-reader callers remain valid because the RCU-list macros also accept
>>>> an active RCU read-side section; the added condition only documents the
>>>> non-RCU protection already used by RTNL control paths.
>>>>
>>>> This was found by our static analysis tool and then manually reviewed
>>>> against the current tree. The dynamic triage evidence is a
>>>> target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
>>>> to documenting the existing protection contract.
>>>>
>>>> This is a lockdep annotation cleanup. It does not change PDP lifetime or
>>>> hash updates.
>>>>
>>>> Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
>>>
>>> Reviewed-by: Simon Horman <horms@kernel.org>
>>>
>>> There is an AI-generated review of this patch available on sashko.dev.
>>> While I don't believe that the issues raised there should impede progress
>>> of this patch you may want to look into them as possible follow-up.
>>
>> This patch refers to the rtnl_lock, but it is the genetlink mutex that
>> protects updates on the PDP context list.
>>
>> Then, from packet path, rcu lookups are performed.
>>
>> I think this patch is not correct.
> 
> Hi Pablo,
> 
> Of course you are correct.
> Sorry for not realising this earlier.

Human slop here made me wrongly apply this patch. Could either of you
please share a formal revert?

Thanks!

Paolo


^ permalink raw reply

* Re: [PATCH v3 05/20] driver core: update kerneldoc for platform_device_alloc()
From: Andy Shevchenko @ 2026-07-08 11:11 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Lee Jones, Mark Brown, Thierry Reding, Sebastian Hesselbarth,
	Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Srinivas Kandagatla, Greg Kroah-Hartman, Vinod Koul,
	Rafael J. Wysocki, Danilo Krummrich, Rob Herring, Saravana Kannan,
	Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
	Christophe Leroy (CS GROUP), Andi Shyti, Joerg Roedel,
	Will Deacon, Robin Murphy, Doug Berger, Florian Fainelli,
	Broadcom internal kernel review list, Ulf Hansson, Frank Li,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	Matthew Brost, Thomas Hellström, Rodrigo Vivi, David Airlie,
	Simona Vetter, Peter Chen, Paul Cercueil, Bin Liu, Philipp Zabel,
	Maximilian Luz, Hans de Goede, Ilpo Järvinen,
	Krzysztof Kozlowski, Benjamin Herrenschmidt, linux-kernel, netdev,
	linux-arm-msm, linux-sound, driver-core, devicetree, linuxppc-dev,
	linux-i2c, iommu, linux-pm, imx, linux-arm-kernel, intel-xe,
	dri-devel, linux-usb, linux-mips, platform-driver-x86, mfd,
	Manuel Ebner, Bartosz Golaszewski
In-Reply-To: <CAMRc=Md7-XMBKpdPuZ4=epfTn6F-PXKJBZNPGuz5xbVy58BHsg@mail.gmail.com>

On Wed, Jul 08, 2026 at 12:42:58AM -0700, Bartosz Golaszewski wrote:
> On Tue, 7 Jul 2026 18:05:29 +0200, Andy Shevchenko
> <andriy.shevchenko@linux.intel.com> said:
> > On Mon, Jul 06, 2026 at 02:44:17PM +0200, Bartosz Golaszewski wrote:
> >> Users of platform_device_alloc() + platform_device_add() must not modify
> >> certain fields of the dynamically created platform device object. Update
> >> the kernel doc to say which fields are affected and which functions to
> >> use.
> >
> > Consider using __private checker attribute for them as well. It will make
> > sparse scream.
> 
> Sure but let's leave it for another series.

Sure, just a side note comment that you know that we have some facilities for
that type of problems.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* [PATCH] dpaa2-switch: put MAC endpoint device on disconnect
From: Guangshuo Li @ 2026-07-08 11:10 UTC (permalink / raw)
  To: Ioana Ciornei, Andrew Lunn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
  Cc: Guangshuo Li

fsl_mc_get_endpoint() returns the MAC endpoint device with a reference
taken through device_find_child(). The switch port connect path stores
that device in mac->mc_dev and keeps it for the lifetime of the connected
MAC object.

However, the disconnect path only closes the MAC and frees the dpaa2_mac
object. It does not drop the endpoint device reference stored in
mac->mc_dev, so every successful connect leaks that device reference when
the MAC is later disconnected.

Drop the endpoint device reference before freeing the dpaa2_mac object.

Fixes: 84cba72956fd ("dpaa2-switch: integrate the MAC endpoint support")
Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
---
 drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
index 858ba844ac51..dd4f60031d0c 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c
@@ -1560,6 +1560,7 @@ static void dpaa2_switch_port_disconnect_mac(struct ethsw_port_priv *port_priv)
 		dpaa2_mac_disconnect(mac);
 
 	dpaa2_mac_close(mac);
+	put_device(&mac->mc_dev->dev);
 	kfree(mac);
 }
 
-- 
2.43.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox