From cc5e8c5af5e3dd4fb12c76e2f3a16f5c0a772a88 Mon Sep 17 00:00:00 2001 From: Carlo Szelinsky Date: Tue, 16 Jun 2026 21:12:23 +0200 Subject: [PATCH] generic: 6.18: backport SFP I2C presence detection without MOD_DEF0 GPIO Backport upstream net-next commit 8ac44d24c3a1. An SFP cage whose MOD_DEF0 signal is not wired to a readable GPIO currently uses sff_gpio_get_state(), which always reports the module present: an empty cage gets stuck in MOD_ERROR, hot-insertion is never detected and empty cages spam -EIO at boot. Derive presence from a throttled single-byte I2C read of the module EEPROM instead, so hot-plug works and the boot spam stops. A soldered-down "sff,sff" module stays always-present. This helps RTL93xx SFP boards that route no cage presence signal to a GPIO. Remaining sfp.c patches refreshed. Link: https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=8ac44d24c3a1 Signed-off-by: Carlo Szelinsky Link: https://github.com/openwrt/openwrt/pull/23836 Signed-off-by: Jonas Jelonek --- ...esence-via-I2C-when-no-MOD_DEF0-GPIO.patch | 156 ++++++++++++++++++ ...k-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch | 2 +- ...net-sfp-add-quirk-for-TP-LINK-SM410U.patch | 2 +- ...ze-i2c_block_size-at-adapter-configu.patch | 2 +- ...C-adapter-quirks-to-limit-block-size.patch | 2 +- ...02-v7.2-net-sfp-extend-SMBus-support.patch | 4 +- ...14-net-phy-sfp-add-support-for-SMBus.patch | 4 +- 7 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 target/linux/generic/backport-6.18/711-v7.2-net-phy-sfp-detect-presence-via-I2C-when-no-MOD_DEF0-GPIO.patch diff --git a/target/linux/generic/backport-6.18/711-v7.2-net-phy-sfp-detect-presence-via-I2C-when-no-MOD_DEF0-GPIO.patch b/target/linux/generic/backport-6.18/711-v7.2-net-phy-sfp-detect-presence-via-I2C-when-no-MOD_DEF0-GPIO.patch new file mode 100644 index 00000000000..aca784e753a --- /dev/null +++ b/target/linux/generic/backport-6.18/711-v7.2-net-phy-sfp-detect-presence-via-I2C-when-no-MOD_DEF0-GPIO.patch @@ -0,0 +1,156 @@ +From 8ac44d24c3a148c4177bd3ad790c377279f4674f Mon Sep 17 00:00:00 2001 +From: Greg Patrick +Date: Thu, 11 Jun 2026 17:53:41 +0000 +Subject: [PATCH] net: phy: sfp: detect presence via I2C when no MOD_DEF0 GPIO + +An SFP cage (compatible "sff,sfp") whose MOD_DEF0 signal is not wired to a +GPIO currently falls back to sff_gpio_get_state(), which unconditionally +reports the module as present. An empty cage therefore fails its probe and +is parked in SFP_MOD_ERROR forever; because SFP_F_PRESENT never deasserts +there is no REMOVE event to recover the state machine, so a module inserted +after boot is never detected, and empty cages spam -EIO at boot. + +This affects boards that route none of the cage presence signal to a +software-readable input. On the NicGiga S100-0800S-M (RTL9303, 8x SFP+) the +cage I2C bus is the switch's SMBus master; TX_DISABLE is driven via a +PCA9534 I/O expander, but no MOD_ABS/MOD_DEF0 line reaches a readable GPIO +(the RTL9303 gpio0 lines read stuck-low, the single PCA9534 is fully +consumed by TX_DISABLE, and there is no RTL8231). The Horaco ZX-SW82TS-L2P +(RTL9302D, 2x SFP+) is independently affected in the same way. + +For such an SFP cage, derive presence from a throttled single-byte I2C read +of the module EEPROM instead: a successful read asserts SFP_F_PRESENT, +R_PROBE_ABSENT consecutive failures clear it (to ride out a transient error +on a live module). The existing poll then emits SFP_E_INSERT / SFP_E_REMOVE +normally, giving working hot-plug and silencing the boot-time -EIO spam on +empty cages. Presence is re-probed every T_PROBE_PRESENT, so insertion is +detected within that interval and removal within +T_PROBE_PRESENT * R_PROBE_ABSENT. + +A soldered-down module (compatible "sff,sff") has no presence signal and is +genuinely always present, so it continues to use sff_gpio_get_state(); the +new path is gated on the cage type advertising SFP_F_PRESENT. + +Signed-off-by: Greg Patrick +Tested-by: Manuel Stocker +Reviewed-by: Maxime Chevallier +Tested-by: Maxime Chevallier +Link: https://patch.msgid.link/20260611175341.2223184-1-gregspatrick@hotmail.com +Signed-off-by: Jakub Kicinski +--- + drivers/net/phy/sfp.c | 83 +++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 80 insertions(+), 3 deletions(-) + +--- a/drivers/net/phy/sfp.c ++++ b/drivers/net/phy/sfp.c +@@ -206,6 +206,16 @@ static const enum gpiod_flags gpio_flags + #define T_PROBE_RETRY_SLOW msecs_to_jiffies(5000) + #define R_PROBE_RETRY_SLOW 12 + ++/* Polling interval and consecutive-failure threshold for the I2C presence ++ * probe used on boards without a MOD_DEF0 GPIO (see sfp_i2c_get_state()). ++ * A single successful read asserts presence immediately; R_PROBE_ABSENT ++ * consecutive failures are required to declare a live module removed, to ride ++ * out a transient I2C error. Insertion is thus detected within ++ * T_PROBE_PRESENT and removal within T_PROBE_PRESENT * R_PROBE_ABSENT. ++ */ ++#define T_PROBE_PRESENT msecs_to_jiffies(500) ++#define R_PROBE_ABSENT 3 ++ + /* SFP modules appear to always have their PHY configured for bus address + * 0x56 (which with mdio-i2c, translates to a PHY address of 22). + * RollBall SFPs access phy via SFP Enhanced Digital Diagnostic Interface +@@ -249,6 +259,13 @@ struct sfp { + + bool need_poll; + ++ /* I2C-probed presence, for boards without a MOD_DEF0 GPIO. ++ * Access rules: st_mutex held (updated from the poll/state machine). ++ */ ++ bool i2c_present; ++ u8 i2c_present_nak; ++ unsigned long i2c_present_next; ++ + /* Access rules: + * state_hw_drive: st_mutex held + * state_hw_mask: st_mutex held +@@ -860,6 +877,45 @@ static int sfp_read(struct sfp *sfp, boo + return sfp->read(sfp, a2, addr, buf, len); + } + ++/* Probe whether a module is physically present by attempting a single-byte ++ * I2C read of the EEPROM identifier (an empty cage NAKs). Used as the presence ++ * source on boards that do not wire MOD_DEF0 to a GPIO. ++ */ ++static bool sfp_module_present_i2c(struct sfp *sfp) ++{ ++ u8 id; ++ ++ return sfp_read(sfp, false, SFP_PHYS_ID, &id, sizeof(id)) == sizeof(id); ++} ++ ++/* get_state variant for boards without a MOD_DEF0 GPIO. Instead of assuming ++ * the module is always present, derive SFP_F_PRESENT from a throttled I2C ++ * probe so that hot-insertion and removal are detected. A single ACK asserts ++ * presence; R_PROBE_ABSENT consecutive failures clear it, to ride out a ++ * transient I2C error on a live module. ++ */ ++static unsigned int sfp_i2c_get_state(struct sfp *sfp) ++{ ++ unsigned int state = sfp_gpio_get_state(sfp); ++ ++ if (time_after_eq(jiffies, sfp->i2c_present_next)) { ++ if (sfp_module_present_i2c(sfp)) { ++ sfp->i2c_present = true; ++ sfp->i2c_present_nak = 0; ++ } else if (sfp->i2c_present && ++ ++sfp->i2c_present_nak >= R_PROBE_ABSENT) { ++ sfp->i2c_present = false; ++ sfp->i2c_present_nak = 0; ++ } ++ sfp->i2c_present_next = jiffies + T_PROBE_PRESENT; ++ } ++ ++ if (sfp->i2c_present) ++ state |= SFP_F_PRESENT; ++ ++ return state; ++} ++ + static int sfp_write(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) + { + return sfp->write(sfp, a2, addr, buf, len); +@@ -3158,9 +3214,30 @@ static int sfp_probe(struct platform_dev + sfp->get_state = sfp_gpio_get_state; + sfp->set_state = sfp_gpio_set_state; + +- /* Modules that have no detect signal are always present */ +- if (!(sfp->gpio[GPIO_MODDEF0])) +- sfp->get_state = sff_gpio_get_state; ++ /* An SFP cage with no MOD_DEF0 GPIO has no hardware presence signal. ++ * Assuming the module is always present traps an empty cage in ++ * MOD_ERROR and never detects hot-insertion, so derive presence from a ++ * throttled I2C probe and poll for changes instead. sfp_i2c_configure() ++ * has already set i2c_max_block_size; seed i2c_block_size so the ++ * presence read does not issue a zero-length transfer before the first ++ * EEPROM read. Seed i2c_present_next to jiffies so the first probe ++ * happens immediately (a zero value would be in the past relative to ++ * the negative INITIAL_JIFFIES at boot and delay detection). ++ * ++ * A soldered-down module (sff,sff) has no presence signal and is ++ * genuinely always present, so it keeps the always-present behaviour; ++ * the I2C probe is gated on the cage type advertising SFP_F_PRESENT. ++ */ ++ if (!sfp->gpio[GPIO_MODDEF0]) { ++ if (sff->gpios & SFP_F_PRESENT) { ++ sfp->get_state = sfp_i2c_get_state; ++ sfp->i2c_block_size = sfp->i2c_max_block_size; ++ sfp->i2c_present_next = jiffies; ++ sfp->need_poll = true; ++ } else { ++ sfp->get_state = sff_gpio_get_state; ++ } ++ } + + device_property_read_u32(&pdev->dev, "maximum-power-milliwatt", + &sfp->max_power_mW); diff --git a/target/linux/generic/pending-6.18/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch b/target/linux/generic/pending-6.18/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch index 21ad584fdb2..b09463fcddb 100644 --- a/target/linux/generic/pending-6.18/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch +++ b/target/linux/generic/pending-6.18/750-net-sfp-add-quirk-for-QINIYEK-BJ-SFP-10G-T-copper-SF.patch @@ -16,7 +16,7 @@ Signed-off-by: Daniel Golle --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c -@@ -585,6 +585,7 @@ static const struct sfp_quirk sfp_quirks +@@ -602,6 +602,7 @@ static const struct sfp_quirk sfp_quirks SFP_QUIRK_S("OEM", "SFP-2.5G-BX10-U", sfp_quirk_2500basex), SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc), SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc), diff --git a/target/linux/generic/pending-6.18/751-net-sfp-add-quirk-for-TP-LINK-SM410U.patch b/target/linux/generic/pending-6.18/751-net-sfp-add-quirk-for-TP-LINK-SM410U.patch index b0b71cb9174..c1f17ae69b8 100644 --- a/target/linux/generic/pending-6.18/751-net-sfp-add-quirk-for-TP-LINK-SM410U.patch +++ b/target/linux/generic/pending-6.18/751-net-sfp-add-quirk-for-TP-LINK-SM410U.patch @@ -14,7 +14,7 @@ Signed-off-by: Daniel Golle --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c -@@ -591,6 +591,7 @@ static const struct sfp_quirk sfp_quirks +@@ -608,6 +608,7 @@ static const struct sfp_quirk sfp_quirks SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball), SFP_QUIRK_S("ZOERAX", "SFP-2.5G-T", sfp_quirk_oem_2_5g), diff --git a/target/linux/realtek/patches-6.18/705-v7.1-net-sfp-initialize-i2c_block_size-at-adapter-configu.patch b/target/linux/realtek/patches-6.18/705-v7.1-net-sfp-initialize-i2c_block_size-at-adapter-configu.patch index c252aa95a05..b1cac719cb2 100644 --- a/target/linux/realtek/patches-6.18/705-v7.1-net-sfp-initialize-i2c_block_size-at-adapter-configu.patch +++ b/target/linux/realtek/patches-6.18/705-v7.1-net-sfp-initialize-i2c_block_size-at-adapter-configu.patch @@ -31,7 +31,7 @@ Signed-off-by: Jakub Kicinski --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c -@@ -824,6 +824,7 @@ static int sfp_i2c_configure(struct sfp +@@ -841,6 +841,7 @@ static int sfp_i2c_configure(struct sfp return -EINVAL; } diff --git a/target/linux/realtek/patches-6.18/706-01-v7.2-net-sfp-apply-I2C-adapter-quirks-to-limit-block-size.patch b/target/linux/realtek/patches-6.18/706-01-v7.2-net-sfp-apply-I2C-adapter-quirks-to-limit-block-size.patch index 75164506462..d2ce503e810 100644 --- a/target/linux/realtek/patches-6.18/706-01-v7.2-net-sfp-apply-I2C-adapter-quirks-to-limit-block-size.patch +++ b/target/linux/realtek/patches-6.18/706-01-v7.2-net-sfp-apply-I2C-adapter-quirks-to-limit-block-size.patch @@ -24,7 +24,7 @@ Signed-off-by: Jakub Kicinski --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c -@@ -809,21 +809,29 @@ static int sfp_smbus_byte_write(struct s +@@ -826,21 +826,29 @@ static int sfp_smbus_byte_write(struct s static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) { diff --git a/target/linux/realtek/patches-6.18/706-02-v7.2-net-sfp-extend-SMBus-support.patch b/target/linux/realtek/patches-6.18/706-02-v7.2-net-sfp-extend-SMBus-support.patch index e0924437072..c00c0d85803 100644 --- a/target/linux/realtek/patches-6.18/706-02-v7.2-net-sfp-extend-SMBus-support.patch +++ b/target/linux/realtek/patches-6.18/706-02-v7.2-net-sfp-extend-SMBus-support.patch @@ -75,7 +75,7 @@ Signed-off-by: Jakub Kicinski #include #include "sfp.h" -@@ -758,50 +759,113 @@ static int sfp_i2c_write(struct sfp *sfp +@@ -775,50 +776,113 @@ static int sfp_i2c_write(struct sfp *sfp return ret == ARRAY_SIZE(msgs) ? len : 0; } @@ -213,7 +213,7 @@ Signed-off-by: Jakub Kicinski } return data - (u8 *)buf; -@@ -817,10 +881,29 @@ static int sfp_i2c_configure(struct sfp +@@ -834,10 +898,29 @@ static int sfp_i2c_configure(struct sfp sfp->read = sfp_i2c_read; sfp->write = sfp_i2c_write; max_block_size = SFP_EEPROM_BLOCK_SIZE; diff --git a/target/linux/realtek/patches-6.18/714-net-phy-sfp-add-support-for-SMBus.patch b/target/linux/realtek/patches-6.18/714-net-phy-sfp-add-support-for-SMBus.patch index cc6283f0a7c..ceeaae4c75a 100644 --- a/target/linux/realtek/patches-6.18/714-net-phy-sfp-add-support-for-SMBus.patch +++ b/target/linux/realtek/patches-6.18/714-net-phy-sfp-add-support-for-SMBus.patch @@ -10,7 +10,7 @@ Signed-off-by: Antoine Tenart --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c -@@ -942,6 +942,29 @@ static int sfp_i2c_mdiobus_create(struct +@@ -959,6 +959,29 @@ static int sfp_i2c_mdiobus_create(struct return 0; } @@ -40,7 +40,7 @@ Signed-off-by: Antoine Tenart static void sfp_i2c_mdiobus_destroy(struct sfp *sfp) { mdiobus_unregister(sfp->i2c_mii); -@@ -2116,9 +2139,15 @@ static void sfp_sm_fault(struct sfp *sfp +@@ -2172,9 +2195,15 @@ static void sfp_sm_fault(struct sfp *sfp static int sfp_sm_add_mdio_bus(struct sfp *sfp) { -- 2.47.3