--- /dev/null
+From 56d0885514491e5ed8f7593400879ab77c52504c Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Thu, 28 May 2026 20:52:40 +0000
+Subject: [PATCH] net: sfp: initialize i2c_block_size at adapter configure time
+
+sfp->i2c_block_size is only assigned in sfp_sm_mod_probe(), which runs
+from the state machine timer after SFP_F_PRESENT has been set. Between
+those two points, sfp_module_eeprom() (the ethtool -m callback) gates
+only on SFP_F_PRESENT and can be entered with i2c_block_size still at
+its kzalloc'd value of 0.
+
+On a pure-I2C adapter, sfp_i2c_read() then issues an i2c_transfer()
+with msgs[1].len = 0 inside a loop that subtracts this_len from len
+each iteration; on adapters that succeed a zero-length read the loop
+never advances, spinning while holding rtnl_lock.
+
+This was previously addressed by initializing i2c_block_size in
+sfp_alloc() (commit 813c2dd78618), but the initialization was dropped
+when i2c_block_size was split from i2c_max_block_size.
+
+Initialize sfp->i2c_block_size from sfp->i2c_max_block_size in
+sfp_i2c_configure(), so the field is valid as soon as the adapter is
+known. sfp_sm_mod_probe() still reassigns it on each module insertion
+to recover from a per-module clamp to 1 (sfp_id_needs_byte_io).
+
+Fixes: 7662abf4db94 ("net: phy: sfp: Add support for SMBus module access")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Link: https://patch.msgid.link/20260528205242.971410-2-jelonek.jonas@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -824,6 +824,7 @@ static int sfp_i2c_configure(struct sfp
+ return -EINVAL;
+ }
+
++ sfp->i2c_block_size = sfp->i2c_max_block_size;
+ return 0;
+ }
+
-From 572304faf8c1d30f142060c76a3e731438fc5975 Mon Sep 17 00:00:00 2001
+From f2a138abfb719a3bfd370ca79f00055ec81e4f59 Mon Sep 17 00:00:00 2001
From: Jonas Jelonek <jelonek.jonas@gmail.com>
-Date: Thu, 22 Jan 2026 10:59:20 +0000
-Subject: [PATCH net-next v6 1/2] net: sfp: apply I2C adapter quirks to limit
- block size
+Date: Sun, 14 Jun 2026 13:34:17 +0000
+Subject: [PATCH 1/2] net: sfp: apply I2C adapter quirks to limit block size
The SFP driver assumes all I2C adapters support reading and writing the
pre-defined block size SFP_EEPROM_BLOCK_SIZE of 16 bytes. This constant
to further limit the maximum block size if needed.
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
----
- drivers/net/phy/sfp.c | 12 ++++++++++--
- 1 file changed, 10 insertions(+), 2 deletions(-)
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Link: https://patch.msgid.link/20260614133418.2068201-2-jelonek.jonas@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
+ max_block_size = min(max_block_size, i2c->quirks->max_write_len);
+
+ sfp->i2c_max_block_size = max_block_size;
+ sfp->i2c_block_size = sfp->i2c_max_block_size;
return 0;
}
-
--- /dev/null
+From 58b29bdf6186a8c3f2d725619c0b17cf602ac4e0 Mon Sep 17 00:00:00 2001
+From: Jonas Jelonek <jelonek.jonas@gmail.com>
+Date: Sun, 14 Jun 2026 13:34:18 +0000
+Subject: [PATCH 2/2] net: sfp: extend SMBus support
+
+Commit 7662abf4db94 ("net: phy: sfp: Add support for SMBus module access")
+added SMBus access for SFP modules, but limited it to single-byte
+transfers. As a side effect, hwmon is disabled (16-bit reads cannot be
+guaranteed atomic) and a warning is printed.
+
+Many SMBus-only I2C controllers in the wild support more than just
+byte access, and SFP cages are often wired to such controllers
+rather than to a full-featured I2C controller -- e.g. the SMBus
+controllers in the Realtek longan and mango SoCs, which advertise
+word access and I2C block reads. Today, they cannot drive an SFP at
+all without falling back to the byte-only path.
+
+Extend sfp_smbus_read()/sfp_smbus_write() so that, in addition to
+the existing byte access, they also use SMBus word access and SMBus
+I2C block access whenever the adapter advertises them. Both
+directions are handled in a single read and a single write helper
+that pick the largest supported transfer per chunk and fall back as
+needed.
+
+I2C-block is preferred unconditionally when available: the protocol
+carries any length 1..32, so it can serve every chunk -- including
+the 1- and 2-byte tails -- without help from word or byte access.
+Note that this requires I2C_FUNC_SMBUS_I2C_BLOCK, which reads a
+caller-specified number of bytes. This deviates from the official
+SMBus Block Read (length is supplied by the slave) but is widely
+supported by Linux I2C controllers/drivers.
+
+Capability matrix this implementation supports:
+
+ - BYTE only: works (unchanged behaviour); 1-byte
+ xfers, hwmon disabled.
+ - BYTE + WORD: word for >=2-byte chunks, byte for
+ trailing odd byte.
+ - I2C_BLOCK present (with or
+ without BYTE/WORD): block as the universal transport for
+ every chunk.
+ - WORD only (no BYTE/BLOCK): accepted with WARN_ONCE. Even-length
+ transfers work; odd-length transfers
+ (e.g. the 3-byte cotsworks fixup
+ write) hit the BYTE branch which the
+ adapter does not implement, so the
+ xfer returns an error and the
+ operation is aborted. No mainline
+ I2C driver was found to advertise
+ WORD without BYTE; the warning lets
+ us learn about it if it ever shows
+ up.
+
+Adapters with asymmetric R/W capabilities (e.g. only READ_I2C_BLOCK
+but not WRITE_I2C_BLOCK) remain functionally correct -- the
+per-iteration fallback uses the direction-specific bits -- but the
+shared i2c_max_block_size is sized by the all-bits-set check, so a
+transfer in the better-supported direction is not upgraded. None of
+the mainline I2C bus drivers surveyed during review advertise such
+asymmetry; promoting i2c_max_block_size to per-direction sizes can
+be revisited if needed.
+
+Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
+Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
+Link: https://patch.msgid.link/20260614133418.2068201-3-jelonek.jonas@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -14,6 +14,7 @@
+ #include <linux/platform_device.h>
+ #include <linux/rtnetlink.h>
+ #include <linux/slab.h>
++#include <linux/unaligned.h>
+ #include <linux/workqueue.h>
+
+ #include "sfp.h"
+@@ -758,50 +759,113 @@ static int sfp_i2c_write(struct sfp *sfp
+ return ret == ARRAY_SIZE(msgs) ? len : 0;
+ }
+
+-static int sfp_smbus_byte_read(struct sfp *sfp, bool a2, u8 dev_addr,
+- void *buf, size_t len)
++static int sfp_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
++ size_t len)
+ {
+- union i2c_smbus_data smbus_data;
++ union i2c_smbus_data smbus_data = {0};
+ u8 bus_addr = a2 ? 0x51 : 0x50;
++ size_t this_len, transferred;
++ u32 functionality;
+ u8 *data = buf;
+ int ret;
+
+- while (len) {
+- ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
+- I2C_SMBUS_READ, dev_addr,
+- I2C_SMBUS_BYTE_DATA, &smbus_data);
+- if (ret < 0)
+- return ret;
++ functionality = i2c_get_functionality(sfp->i2c);
+
+- *data = smbus_data.byte;
++ while (len) {
++ this_len = min(len, sfp->i2c_block_size);
+
+- len--;
+- data++;
+- dev_addr++;
++ if (functionality & I2C_FUNC_SMBUS_READ_I2C_BLOCK) {
++ smbus_data.block[0] = this_len;
++ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
++ I2C_SMBUS_READ, dev_addr,
++ I2C_SMBUS_I2C_BLOCK_DATA, &smbus_data);
++ if (ret < 0)
++ return ret;
++
++ transferred = min_t(size_t, smbus_data.block[0], this_len);
++ if (!transferred)
++ return -EIO;
++
++ memcpy(data, &smbus_data.block[1], transferred);
++ } else if (this_len >= 2 &&
++ (functionality & I2C_FUNC_SMBUS_READ_WORD_DATA)) {
++ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
++ I2C_SMBUS_READ, dev_addr,
++ I2C_SMBUS_WORD_DATA, &smbus_data);
++ if (ret < 0)
++ return ret;
++
++ put_unaligned_le16(smbus_data.word, data);
++ transferred = 2;
++ } else {
++ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
++ I2C_SMBUS_READ, dev_addr,
++ I2C_SMBUS_BYTE_DATA, &smbus_data);
++ if (ret < 0)
++ return ret;
++
++ *data = smbus_data.byte;
++ transferred = 1;
++ }
++
++ data += transferred;
++ len -= transferred;
++ dev_addr += transferred;
+ }
+
+ return data - (u8 *)buf;
+ }
+
+-static int sfp_smbus_byte_write(struct sfp *sfp, bool a2, u8 dev_addr,
+- void *buf, size_t len)
++static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
++ size_t len)
+ {
+ union i2c_smbus_data smbus_data;
+ u8 bus_addr = a2 ? 0x51 : 0x50;
++ size_t this_len, transferred;
++ u32 functionality;
+ u8 *data = buf;
+ int ret;
+
++ functionality = i2c_get_functionality(sfp->i2c);
++
+ while (len) {
+- smbus_data.byte = *data;
+- ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
+- I2C_SMBUS_WRITE, dev_addr,
+- I2C_SMBUS_BYTE_DATA, &smbus_data);
+- if (ret)
+- return ret;
++ this_len = min(len, sfp->i2c_block_size);
+
+- len--;
+- data++;
+- dev_addr++;
++ if (functionality & I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) {
++ smbus_data.block[0] = this_len;
++ memcpy(&smbus_data.block[1], data, this_len);
++
++ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
++ I2C_SMBUS_WRITE, dev_addr,
++ I2C_SMBUS_I2C_BLOCK_DATA, &smbus_data);
++ if (ret < 0)
++ return ret;
++
++ transferred = this_len;
++ } else if (this_len >= 2 &&
++ (functionality & I2C_FUNC_SMBUS_WRITE_WORD_DATA)) {
++ smbus_data.word = get_unaligned_le16(data);
++ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
++ I2C_SMBUS_WRITE, dev_addr,
++ I2C_SMBUS_WORD_DATA, &smbus_data);
++ if (ret < 0)
++ return ret;
++
++ transferred = 2;
++ } else {
++ smbus_data.byte = *data;
++ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
++ I2C_SMBUS_WRITE, dev_addr,
++ I2C_SMBUS_BYTE_DATA, &smbus_data);
++ if (ret < 0)
++ return ret;
++
++ transferred = 1;
++ }
++
++ data += transferred;
++ len -= transferred;
++ dev_addr += transferred;
+ }
+
+ return data - (u8 *)buf;
+@@ -817,10 +881,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;
+- } else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
+- sfp->read = sfp_smbus_byte_read;
+- sfp->write = sfp_smbus_byte_write;
+- max_block_size = 1;
++ } else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA) ||
++ i2c_check_functionality(i2c, I2C_FUNC_SMBUS_I2C_BLOCK)) {
++ /* Either protocol alone covers any length: I2C-block carries
++ * 1..32 bytes per xfer, byte iterates one byte at a time.
++ */
++ sfp->read = sfp_smbus_read;
++ sfp->write = sfp_smbus_write;
++
++ if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_I2C_BLOCK))
++ max_block_size = SFP_EEPROM_BLOCK_SIZE;
++ else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_WORD_DATA))
++ max_block_size = 2;
++ else
++ max_block_size = 1;
++ } else if (WARN_ONCE(i2c_check_functionality(i2c, I2C_FUNC_SMBUS_WORD_DATA),
++ "SMBus word-only adapter; odd-length transfers will fail\n")) {
++ /* Word-only: even-length xfers work; odd-length xfers fall
++ * to BYTE, which the adapter does not advertise and will
++ * likely fail.
++ */
++ sfp->read = sfp_smbus_read;
++ sfp->write = sfp_smbus_write;
++ max_block_size = 2;
+ } else {
+ sfp->i2c = NULL;
+ return -EINVAL;
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
-@@ -850,6 +850,29 @@ static int sfp_i2c_mdiobus_create(struct
+@@ -942,6 +942,29 @@ static int sfp_i2c_mdiobus_create(struct
return 0;
}
static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
{
mdiobus_unregister(sfp->i2c_mii);
-@@ -2024,9 +2047,15 @@ static void sfp_sm_fault(struct sfp *sfp
+@@ -2116,9 +2139,15 @@ static void sfp_sm_fault(struct sfp *sfp
static int sfp_sm_add_mdio_bus(struct sfp *sfp)
{
+++ /dev/null
-From 475b0dd976de6db975bfef7d6fdb978d2d64c535 Mon Sep 17 00:00:00 2001
-From: Jonas Jelonek <jelonek.jonas@gmail.com>
-Date: Fri, 9 Jan 2026 21:04:02 +0000
-Subject: [PATCH net-next v6 2/2] net: sfp: extend SMBus support
-
-Commit 7662abf4db94 ("net: phy: sfp: Add support for SMBus module access")
-added support for SMBus-only controllers for module access. However, this
-is restricted to single-byte accesses and has the implication that hwmon
-is disabled (due to missing atomicity of 16-bit accesses) and warnings
-are printed.
-
-There are probably a lot of SMBus-only I2C controllers out in the wild
-which support more than just byte access. And it also seems that in
-several devices, SFP slots are attached to these SMBus controllers
-instead of full-featured I2C controllers. Right now, they don't work
-with SFP modules. This applies - amongst others - to I2C/SMBus-only
-controllers in Realtek longan and mango SoCs. They also support word
-access and I2C block reads.
-
-Extend the current read/write SMBus operations to support SMBus I2C
-block and SMBus word access. To avoid having dedicated operations for
-each kind of transfer, provide generic read and write operations that
-covers all kinds of access depending on whats supported.
-
-For block access, this requires I2C_FUNC_SMBUS_I2C_BLOCK to be
-supported as it relies on reading a pre-defined amount of bytes.
-This isn't intended by the official SMBus Block Read but supported by
-several I2C controllers/drivers.
-
-Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
----
- drivers/net/phy/sfp.c | 117 +++++++++++++++++++++++++++++++++---------
- 1 file changed, 92 insertions(+), 25 deletions(-)
-
---- a/drivers/net/phy/sfp.c
-+++ b/drivers/net/phy/sfp.c
-@@ -14,6 +14,7 @@
- #include <linux/platform_device.h>
- #include <linux/rtnetlink.h>
- #include <linux/slab.h>
-+#include <linux/unaligned.h>
- #include <linux/workqueue.h>
-
- #include "sfp.h"
-@@ -758,50 +759,101 @@ static int sfp_i2c_write(struct sfp *sfp
- return ret == ARRAY_SIZE(msgs) ? len : 0;
- }
-
--static int sfp_smbus_byte_read(struct sfp *sfp, bool a2, u8 dev_addr,
-- void *buf, size_t len)
-+static int sfp_smbus_read(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
-+ size_t len)
- {
- union i2c_smbus_data smbus_data;
- u8 bus_addr = a2 ? 0x51 : 0x50;
-+ size_t this_len, transferred;
-+ u32 functionality;
- u8 *data = buf;
- int ret;
-
-+ functionality = i2c_get_functionality(sfp->i2c);
-+
- while (len) {
-- ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-- I2C_SMBUS_READ, dev_addr,
-- I2C_SMBUS_BYTE_DATA, &smbus_data);
-+ this_len = min(len, sfp->i2c_max_block_size);
-+
-+ if (functionality & I2C_FUNC_SMBUS_READ_I2C_BLOCK) {
-+ smbus_data.block[0] = this_len;
-+ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-+ I2C_SMBUS_READ, dev_addr,
-+ I2C_SMBUS_I2C_BLOCK_DATA, &smbus_data);
-+
-+ memcpy(data, &smbus_data.block[1], this_len);
-+ transferred = this_len;
-+ } else if (this_len >= 2 &&
-+ functionality & I2C_FUNC_SMBUS_READ_WORD_DATA) {
-+ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-+ I2C_SMBUS_READ, dev_addr,
-+ I2C_SMBUS_WORD_DATA, &smbus_data);
-+
-+ put_unaligned_le16(smbus_data.word, data);
-+ transferred = 2;
-+ } else {
-+ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-+ I2C_SMBUS_READ, dev_addr,
-+ I2C_SMBUS_BYTE_DATA, &smbus_data);
-+
-+ *data = smbus_data.byte;
-+ transferred = 1;
-+ }
-+
- if (ret < 0)
- return ret;
-
-- *data = smbus_data.byte;
--
-- len--;
-- data++;
-- dev_addr++;
-+ data += transferred;
-+ len -= transferred;
-+ dev_addr += transferred;
- }
-
- return data - (u8 *)buf;
- }
-
--static int sfp_smbus_byte_write(struct sfp *sfp, bool a2, u8 dev_addr,
-- void *buf, size_t len)
-+static int sfp_smbus_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
-+ size_t len)
- {
- union i2c_smbus_data smbus_data;
- u8 bus_addr = a2 ? 0x51 : 0x50;
-+ size_t this_len, transferred;
-+ u32 functionality;
- u8 *data = buf;
- int ret;
-
-+ functionality = i2c_get_functionality(sfp->i2c);
-+
- while (len) {
-- smbus_data.byte = *data;
-- ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-- I2C_SMBUS_WRITE, dev_addr,
-- I2C_SMBUS_BYTE_DATA, &smbus_data);
-- if (ret)
-+ this_len = min(len, sfp->i2c_max_block_size);
-+
-+ if (functionality & I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) {
-+ smbus_data.block[0] = this_len;
-+ memcpy(&smbus_data.block[1], data, this_len);
-+
-+ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-+ I2C_SMBUS_WRITE, dev_addr,
-+ I2C_SMBUS_I2C_BLOCK_DATA, &smbus_data);
-+ transferred = this_len;
-+ } else if (this_len >= 2 &&
-+ functionality & I2C_FUNC_SMBUS_WRITE_WORD_DATA) {
-+ smbus_data.word = get_unaligned_le16(data);
-+ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-+ I2C_SMBUS_WRITE, dev_addr,
-+ I2C_SMBUS_WORD_DATA, &smbus_data);
-+ transferred = 2;
-+ } else {
-+ smbus_data.byte = *data;
-+ ret = i2c_smbus_xfer(sfp->i2c, bus_addr, 0,
-+ I2C_SMBUS_WRITE, dev_addr,
-+ I2C_SMBUS_BYTE_DATA, &smbus_data);
-+ transferred = 1;
-+ }
-+
-+ if (ret < 0)
- return ret;
-
-- len--;
-- data++;
-- dev_addr++;
-+ data += transferred;
-+ len -= transferred;
-+ dev_addr += transferred;
- }
-
- return data - (u8 *)buf;
-@@ -810,17 +862,32 @@ static int sfp_smbus_byte_write(struct s
- static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
- {
- size_t max_block_size;
-+ u32 functionality;
-
- sfp->i2c = i2c;
-+ functionality = i2c_get_functionality(i2c);
-
-- if (i2c_check_functionality(i2c, I2C_FUNC_I2C)) {
-+ if (functionality & I2C_FUNC_I2C) {
- sfp->read = sfp_i2c_read;
- sfp->write = sfp_i2c_write;
- max_block_size = SFP_EEPROM_BLOCK_SIZE;
-- } else if (i2c_check_functionality(i2c, I2C_FUNC_SMBUS_BYTE_DATA)) {
-- sfp->read = sfp_smbus_byte_read;
-- sfp->write = sfp_smbus_byte_write;
-- max_block_size = 1;
-+ } else if ((functionality & I2C_FUNC_SMBUS_BYTE_DATA) == I2C_FUNC_SMBUS_BYTE_DATA ||
-+ (functionality & I2C_FUNC_SMBUS_I2C_BLOCK) == I2C_FUNC_SMBUS_I2C_BLOCK) {
-+ sfp->read = sfp_smbus_read;
-+ sfp->write = sfp_smbus_write;
-+
-+ if ((functionality & I2C_FUNC_SMBUS_I2C_BLOCK) == I2C_FUNC_SMBUS_I2C_BLOCK)
-+ max_block_size = SFP_EEPROM_BLOCK_SIZE;
-+ else if ((functionality & I2C_FUNC_SMBUS_WORD_DATA) == I2C_FUNC_SMBUS_WORD_DATA)
-+ max_block_size = 2;
-+ else
-+ max_block_size = 1;
-+ } else if ((functionality & I2C_FUNC_SMBUS_WORD_DATA) == I2C_FUNC_SMBUS_WORD_DATA) {
-+ WARN(1, "SMBus adapter only supports word access, odd reads/writes will fail!\n");
-+
-+ sfp->read = sfp_smbus_read;
-+ sfp->write = sfp_smbus_write;
-+ max_block_size = 2;
- } else {
- sfp->i2c = NULL;
- return -EINVAL;