* [PATCH net-next v3 5/8] net: mdio: realtek-rtl9300: Add page tracking
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>
The hardware polling unit of the Realtek switches has a very special
handling for PHY register 31 (aka Realtek page register) in place.
- On the RTL838x it is permanently reset to zero.
- On other devices there is some magic saving/restoring (aka parking)
in the background in place.
This makes access to PHYs a gamble.
As of now all known existing hardware designs have Realtek PHYs for 1G
connectivity. Otherwise the polling engine and the MAC status update
will not work at all and the vendor SDK would fail totally.
This driver differentiates clearly between C22 and C45 buses. During
probing it enables only one of the protocols for a bus. So it is safe
to assume that any C22 access will only target a Realtek based 1G PHY.
For safety reasons block any non-Realtek PHY on a C22 bus.
Intercept access to register 31 and handle it internally. Store the
desired value for each port in the driver. When issuing hardware access
to other registers add the page to the command towards the controller.
This given, the hardware will run two consecutive c22 commands that are
not interrupted by polling.
... hardware poll ...
phy_write(phy, 31, page)
phy_write(phy, reg, value)
... hardware poll ...
Remark! To keep this simple, writes to register 31 are only accepted if
they are lower than the device specific raw page - 0..4094/8190.
Otherwise -EINVAL is returned. Under the above assumption (Only 1G
Realtek PHYs on c22 bus) this is no limitation.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 34 ++++++++++++++++++++-----
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index c36244cd9a66..a5cac0d04114 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -171,6 +171,7 @@
#define PHY_CTRL_CMD BIT(0)
#define PHY_CTRL_MMD_DEVAD GENMASK(20, 16)
#define PHY_CTRL_MMD_REG GENMASK(15, 0)
+#define PHY_VENDOR_REALTEK 0x001cc800
#define MAP_ADDRS_PER_REG 6
#define MAP_BITS_PER_ADDR 5
@@ -198,6 +199,7 @@ struct otto_emdio_priv {
struct mutex lock; /* protect HW access */
DECLARE_BITMAP(phy_poll, MAX_PORTS);
DECLARE_BITMAP(valid_ports, MAX_PORTS);
+ u16 page[MAX_PORTS];
u8 smi_bus[MAX_PORTS];
u8 smi_addr[MAX_PORTS];
bool smi_bus_is_c45[MAX_SMI_BUSSES];
@@ -351,7 +353,7 @@ static int otto_emdio_9300_read_c22(struct mii_bus *bus, int port, int regnum, u
struct otto_emdio_cmd_regs cmd_data = {
.c22_data = FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
.io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, port),
};
@@ -365,7 +367,7 @@ static int otto_emdio_9300_write_c22(struct mii_bus *bus, int port, int regnum,
struct otto_emdio_cmd_regs cmd_data = {
.c22_data = FIELD_PREP(RTL9300_PHY_CTRL_REG_ADDR, regnum) |
FIELD_PREP(RTL9300_PHY_CTRL_PARK_PAGE, 0x1f) |
- FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9300_PHY_CTRL_MAIN_PAGE, priv->page[port]),
.io_data = FIELD_PREP(RTL9300_PHY_CTRL_INDATA, value),
.port_mask_low = BIT(port),
};
@@ -405,7 +407,7 @@ static int otto_emdio_9310_read_c22(struct mii_bus *bus, int port, int regnum, u
struct otto_emdio_cmd_regs cmd_data = {
.broadcast = FIELD_PREP(RTL9310_BC_PORT_ID, port),
.c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
- FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
};
return otto_emdio_read_cmd(bus, RTL9310_PHY_CTRL_TYPE_C22, &cmd_data,
@@ -417,7 +419,7 @@ static int otto_emdio_9310_write_c22(struct mii_bus *bus, int port, int regnum,
struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(bus);
struct otto_emdio_cmd_regs cmd_data = {
.c22_data = FIELD_PREP(RTL9310_PHY_CTRL_REG_ADDR, regnum) |
- FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, RAW_PAGE(priv)),
+ FIELD_PREP(RTL9310_PHY_CTRL_MAIN_PAGE, priv->page[port]),
.io_data = FIELD_PREP(RTL9310_PHY_CTRL_INDATA, value),
.port_mask_high = (u32)(BIT_ULL(port) >> 32),
.port_mask_low = (u32)(BIT_ULL(port)),
@@ -463,8 +465,12 @@ static int otto_emdio_read_c22(struct mii_bus *bus, int phy_id, int regnum)
if (port < 0)
return port;
- scoped_guard(mutex, &priv->lock)
+ scoped_guard(mutex, &priv->lock) {
+ if (regnum == 31)
+ return priv->page[port];
+
ret = priv->info->read_c22(bus, port, regnum, &value);
+ }
return ret ? ret : value;
}
@@ -478,8 +484,17 @@ static int otto_emdio_write_c22(struct mii_bus *bus, int phy_id, int regnum, u16
if (port < 0)
return port;
- scoped_guard(mutex, &priv->lock)
+ scoped_guard(mutex, &priv->lock) {
+ if (regnum == 31) {
+ if (value >= RAW_PAGE(priv))
+ return -EINVAL;
+
+ priv->page[port] = value;
+ return 0;
+ }
+
ret = priv->info->write_c22(bus, port, regnum, value);
+ }
return ret;
}
@@ -592,6 +607,7 @@ static int otto_emdio_notify_phy_attach(struct phy_device *phydev)
{
struct otto_emdio_priv *priv = otto_emdio_bus_to_priv(phydev->mdio.bus);
int port = otto_emdio_phy_to_port(phydev->mdio.bus, phydev->mdio.addr);
+ struct otto_emdio_chan *chan = phydev->mdio.bus->priv;
int ret;
if (port < 0)
@@ -600,6 +616,12 @@ static int otto_emdio_notify_phy_attach(struct phy_device *phydev)
if (test_bit(port, priv->phy_poll))
return 0;
+ if (!priv->smi_bus_is_c45[chan->mdio_bus] &&
+ (!phy_id_compare_vendor(phydev->phy_id, PHY_VENDOR_REALTEK))) {
+ phydev_err(phydev, "Only Realtek PHYs allowed on C22 bus\n");
+ return -EOPNOTSUPP;
+ }
+
scoped_guard(mutex, &priv->lock) {
ret = otto_emdio_set_port_polling(priv, port, true);
if (!ret)
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v3 1/8] dt-bindings: net: realtek,rtl9301-mdio: Add RTL83xx series
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>
The lower end Realtek Otto switches provide 1G only and are divided into
two series:
- Maple : RTL838x up to 28 ports
- Cypress: RTL839x up to 56 ports
The Maple based devices have 3 different SoCs: RTL8380, RTL8381 and
RTL8382. The Cypress series consists of the RTL8391, RTL8392 and
RTL8393 SoCs. The MDIO controller of these switches works like the
existing RTL93xx logic but has different characteristics and different
registers. Add new compatibles in the device tree.
With the extended compatibility list change the title to better reflect
the scope of. Especially add the "Ethernet" tag as these devices have
multiple MDIO controllers.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
.../bindings/net/realtek,rtl9301-mdio.yaml | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
index 271e05bae9c5..67e0b23a8470 100644
--- a/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
+++ b/Documentation/devicetree/bindings/net/realtek,rtl9301-mdio.yaml
@@ -4,7 +4,7 @@
$id: http://devicetree.org/schemas/net/realtek,rtl9301-mdio.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
-title: Realtek RTL9300 MDIO Controller
+title: Realtek Otto Switches Ethernet MDIO Controller
maintainers:
- Chris Packham <chris.packham@alliedtelesis.co.nz>
@@ -12,6 +12,16 @@ maintainers:
properties:
compatible:
oneOf:
+ - items:
+ - enum:
+ - realtek,rtl8381-mdio
+ - realtek,rtl8382-mdio
+ - const: realtek,rtl8380-mdio
+ - items:
+ - enum:
+ - realtek,rtl8392-mdio
+ - realtek,rtl8393-mdio
+ - const: realtek,rtl8391-mdio
- items:
- enum:
- realtek,rtl9302b-mdio
@@ -24,6 +34,8 @@ properties:
- realtek,rtl9313-mdio
- const: realtek,rtl9311-mdio
- enum:
+ - realtek,rtl8380-mdio
+ - realtek,rtl8391-mdio
- realtek,rtl9301-mdio
- realtek,rtl9311-mdio
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v3 2/8] net: mdio: realtek-rtl9300: Add polling documentation
From: Markus Stockhausen @ 2026-07-05 16:35 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev,
chris.packham, daniel, robh, krzk+dt, conor+dt, devicetree
Cc: Markus Stockhausen
In-Reply-To: <20260705163532.2853959-1-markus.stockhausen@gmx.de>
Add a detailed explanation how the hardware polling unit in the
Realtek Otto switches works. This simplifies developing future
patches and reviewing them.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
drivers/net/mdio/mdio-realtek-rtl9300.c | 68 +++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/drivers/net/mdio/mdio-realtek-rtl9300.c b/drivers/net/mdio/mdio-realtek-rtl9300.c
index 892ed3780a65..562f9c7f2895 100644
--- a/drivers/net/mdio/mdio-realtek-rtl9300.c
+++ b/drivers/net/mdio/mdio-realtek-rtl9300.c
@@ -35,6 +35,74 @@
*
* The driver works out the mapping based on the MDIO bus described in device tree and phandles on
* the ethernet-ports property.
+ *
+ * The devices have a hardware polling unit that runs in the background without any CPU load. It
+ * constantly scans the MDIO bus and the attached PHYs and updates the MAC status registers.
+ *
+ * How does the polling work?
+ *
+ * Each device has a SMI_POLL_CTRL register. A per-port bitmask decides if the hardware polling of
+ * the associated bus/address is active or not. The hardware runs a tight loop over this and for
+ * each set polling bit it issues a status check for the PHY. Attaching a logic analyzer to the
+ * MDIO bus of an RTL8380 and RTL8393 gives the following commands (in kernel notation):
+ *
+ * RTL8380 RTL8393
+ * --------------------------- ---------------------------
+ * phy_write(phy, 31, 0x0); phy_read(phy, 0);
+ * phy_write(phy, 13, 0x7); phy_read(phy, 1);
+ * phy_write(phy, 14, 0x3c); phy_read(phy, 4);
+ * phy_write(phy, 13, 0x8007); phy_read(phy, 5);
+ * phy_read(phy, 14); phy_read(phy, 6);
+ * phy_write(phy, 13, 0x7); phy_read(phy, 9);
+ * phy_write(phy, 14, 0x3d); phy_read(phy, 10);
+ * phy_write(phy, 13, 0x8007); phy_read(phy, 15);
+ * phy_read(phy, 14); phy_write(phy, 13, 0x7);
+ * phy_read(phy, 9); phy_write(phy, 14, 0x3c);
+ * phy_read(phy, 10); phy_write(phy, 13, 0x4007);
+ * phy_read(phy, 15); phy_read(phy, 14);
+ * phy_read(phy, 0); phy_write(phy, 13, 0x7);
+ * phy_read(phy, 1); phy_write(phy, 14, 0x3d);
+ * phy_read(phy, 4); phy_write(phy, 13, 0x4007);
+ * phy_read(phy, 5); phy_read(phy, 14);
+ * phy_read(phy, 6);
+ *
+ * From the above snippets it polls MDIO_AN_EEE_ADV and MDIO_AN_EEE_LPABLE via C45 over C22. As
+ * of now it is unclear how the hardware decides if a PHY supports these registers. After one PHY
+ * status is read, the polling engine goes over to the next PHY. Basically the bus is always busy
+ * and the MAC status is updated in real-time.
+ *
+ * How does MDIO access from kernel work?
+ *
+ * When issuing MDIO accesses via an MMIO based interface the final write to the command register
+ * sets a "run command now" bit. Between two polling sequences for different PHYs the hardware
+ * checks if a user command needs to run and sends it onto the bus. Afterwards it simply continues
+ * its polling work. Inspecting the command sequence for a paged read on the logic analyzer gives:
+ *
+ * RTL8380 RTL8393
+ * --------------------------- ---------------------------
+ * phy_write(phy, 31, page); phy_write(phy, 31, page);
+ * phy_write(phy, reg, value); phy_write(phy, reg, value);
+ * phy_write(phy, 31, 0);
+ *
+ * What does this mean?
+ *
+ * There are slight differences in polling and PHY access between the models but the challenge
+ * stays the same. On the one hand that greatly simplifies the MAC layer, on the other hand it
+ * has some implications for the kernel PHY subsystem.
+ *
+ * - Without the polling and a proper MAC status, some of the link handling features do not work.
+ * Especially an unpopulated MAC_LINK_STS register cancels operations to other MAC registers.
+ * - The Realtek page register 31 is magically modified in the background so that polling will
+ * read the right data. On the RTL838x polling simply resets it to zero. Other devices seem
+ * to track the page access "magically" in the background.
+ * - A C45 over C22 kernel access sequence is most likely to fail because chances are high that
+ * the polling engine overwrites registers 13/14 in between.
+ * - PHY firmware loading can have issues. Especially if a PHY is designed to expect a clean
+ * sequence of registers and values without deviation.
+ * - An access to one PHY will need to wait for the next free slot of the polling engine.
+ *
+ * Conclusion: The Realtek MDIO bus driver PHY access must know and handle any interference that
+ * arises from the above described hardware polling.
*/
#include <linux/bitfield.h>
--
2.54.0
^ permalink raw reply related
* [PATCH net-next] net: ethernet: qualcomm: remove unneeded 'fast_io' parameter in regmap_config
From: Wolfram Sang @ 2026-07-05 16:41 UTC (permalink / raw)
To: linux-kernel
Cc: Wolfram Sang, Luo Jie, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, netdev
When using MMIO with regmap, fast_io is implied. No need to set it
again.
Signed-off-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
---
No dependencies, can be applied directly to the subsystem tree. Buildbot is
happy, too.
drivers/net/ethernet/qualcomm/ppe/ppe.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/drivers/net/ethernet/qualcomm/ppe/ppe.c b/drivers/net/ethernet/qualcomm/ppe/ppe.c
index be747510d947..3c301e609d3e 100644
--- a/drivers/net/ethernet/qualcomm/ppe/ppe.c
+++ b/drivers/net/ethernet/qualcomm/ppe/ppe.c
@@ -106,7 +106,6 @@ static const struct regmap_config regmap_config_ipq9574 = {
.rd_table = &ppe_reg_table,
.wr_table = &ppe_reg_table,
.max_register = 0xbef800,
- .fast_io = true,
};
static int ppe_clock_init_and_reset(struct ppe_device *ppe_dev)
--
2.51.0
^ permalink raw reply related
* Re: [PATCH net-next v2] ipv4: hold a consistent view of rt->dst.dev under RCU
From: Ido Schimmel @ 2026-07-05 16:46 UTC (permalink / raw)
To: xuanqiang.luo
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
David Ahern, Simon Horman, Kuniyuki Iwashima, netdev,
linux-kernel, Xuanqiang Luo
In-Reply-To: <20260701032434.17500-1-xuanqiang.luo@linux.dev>
On Wed, Jul 01, 2026 at 11:24:34AM +0800, xuanqiang.luo@linux.dev wrote:
> From: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
>
> rt_flush_dev() walks the per-CPU uncached route list and rewrites
> rt->dst.dev in-place to blackhole_netdev under spin_lock_bh().
> This lock does not exclude RCU readers, which may load rt->dst.dev
> multiple times within a single rcu_read_lock() region.
>
> ip_rt_send_redirect() is a typical example: it reads rt->dst.dev
> three times to obtain in_dev, the L3 master ifindex, and net.
> A concurrent device unregistration can repoint rt->dst.dev to
> blackhole_netdev between those reads, making the reader combine
> state from two different net_devices — for instance, an in_dev
> from the real device but a netns and peer lookup from the blackhole
> device. ip_rt_get_source() has the same problem: it reads
> rt->dst.dev four times to obtain the output ifindex, the netns,
> and the source address, so a concurrent flush can cause the source
> selection to mix state from different devices.
Why only change ip_rt_send_redirect() and ip_rt_get_source() when the
patch is titled "ipv4: hold a consistent view of rt->dst.dev under RCU"?
What is the criterion?
>
> Take a single dst_dev_rcu() snapshot of rt->dst.dev at the start
> of each affected RCU reader and use that snapshot throughout, so
> concurrent flushes cannot cause mid-function inconsistency.
> Publish the in-place write in rt_flush_dev() with rcu_assign_pointer()
> to match the readers.
The rt_flush_dev() change should be a separate change. Note that
dst_dev_put() was already converted to use rcu_assign_pointer().
>
> Fixes: caacf05e5ad1a ("ipv4: Properly purge netdev references on uncached routes.")
Please remove the Fixes tag given you are targeting net-next.
> Signed-off-by: Xuanqiang Luo <luoxuanqiang@kylinos.cn>
> ---
> v2:
> - Use dst_dev_rcu() and dev_net_rcu() for the RCU readers.
> - Use rcu_assign_pointer() when publishing the uncached route device
> replacement.
> - Slightly adjust the commit message wording because this issue was found
> by inspection, not from an observed user-visible failure.
>
> v1: https://lore.kernel.org/all/20260630094250.29386-1-xuanqiang.luo@linux.dev/
>
> net/ipv4/route.c | 29 +++++++++++++++++------------
> 1 file changed, 17 insertions(+), 12 deletions(-)
>
> diff --git a/net/ipv4/route.c b/net/ipv4/route.c
> index 3f3de5164d6e5..57f38467e6d0c 100644
> --- a/net/ipv4/route.c
> +++ b/net/ipv4/route.c
> @@ -873,6 +873,7 @@ static void ipv4_negative_advice(struct sock *sk,
> void ip_rt_send_redirect(struct sk_buff *skb)
> {
> struct rtable *rt = skb_rtable(skb);
> + struct net_device *dev;
https://docs.kernel.org/process/maintainer-netdev.html#local-variable-ordering-reverse-xmas-tree-rcs
Same in other places.
^ permalink raw reply
* Re: [PATCH net-next v11 0/2] net: mana: add ethtool private flag for full-page RX buffers
From: Dipayaan Roy @ 2026-07-05 17:17 UTC (permalink / raw)
To: Maciej Fijalkowski
Cc: kys, haiyangz, wei.liu, decui, andrew+netdev, davem, edumazet,
kuba, pabeni, leon, longli, kotaranov, horms, shradhagupta,
ssengar, ernis, shirazsaleem, linux-hyperv, netdev, linux-kernel,
linux-rdma, stephen, jacob.e.keller, dipayanroy, leitao, kees,
john.fastabend, hawk, bpf, daniel, ast, sdf, yury.norov,
pavan.chebbi
In-Reply-To: <akUn+nMBrjbUts2N@boxer>
On Wed, Jul 01, 2026 at 04:45:14PM +0200, Maciej Fijalkowski wrote:
> On Wed, Jul 01, 2026 at 07:15:44AM -0700, Dipayaan Roy wrote:
> > On some ARM64 platforms with 4K PAGE_SIZE, utilizing page_pool
> > fragments for allocation in the RX refill path (~2kB buffer per fragment)
> > causes 15-20% throughput regression under high connection counts
> > (>16 TCP streams at 180+ Gbps). Using full-page buffers on these
> > platforms shows no regression and restores line-rate performance.
> >
> > This behavior is observed on a single platform; other platforms
> > perform better with page_pool fragments, indicating this is not a
> > page_pool issue but platform-specific.
> >
> > This series adds an ethtool private flag "full-page-rx" to let the
> > user opt in to one RX buffer per page:
> >
> > ethtool --set-priv-flags eth0 full-page-rx on
> >
> > There is no behavioral change by default. The flag can be persisted
> > via udev rule for affected platforms.
>
> Were you able to track down what is the actual bottleneck on the 'broken'
> platform? What is the performance of full-page approach on healthy
> platforms? On changelog below you mention the frag approach 'outperforms'
> the full-page one.
>
Hi Maciej,
The HW team identified a PCIE root port stall occurring due to a PCIe
errata in a HW IP used for this platform. Using full pages increases the
time in packet refill path and indirectly helping to reduce the back pressure
in NIC pipeline caused due to the stall in root port. As per them it
will be fixed in next version of this hw which is not anytime soon.
On various other healthy platforms with 4k base page size we tested, we see
improvements using page fragments than full pages around anywhere between 5 to 15%.
Regards
Dipayaan Roy
> >
> > This series depends on the following fixes now merged in net-next:
> > commit 17bfe0a8c014 ("net: mana: Add NULL guards in teardown path to prevent panic on attach failure")
> > commit 5b05aa36ee24 ("net: mana: Skip redundant detach on already-detached port")
> >
> > Changes in v11:
> > - Rebased on net-next
> > Changes in v10:
> > - Rebased on net-next which now includes the prerequisite fixes.
> > - Recovery logic in mana_set_priv_flags() leverages the idempotent
> > mana_detach() from the merged fixes.
> > Changes in v9:
> > - Added correct tree.
> > Changes in v8:
> > - Fixed queue_reset_work recovery by restoring port_is_up before
> > scheduling reset so the handler can properly re-attach.
> > - Simplified "err && schedule_port_reset" to "schedule_port_reset".
> > Changes in v7:
> > - Rebased onto net-next.
> > - Retained private flag approach after David Wei's testing on
> > Grace (ARM64) confirmed that fragment mode outperforms
> > full-page mode on other platforms, validating this is a
> > single-platform workaround rather than a generic issue.
> > Changes in v6:
> > - Added missed maintainers.
> > Changes in v5:
> > - Split prep refactor into separate patch (patch 1/2)
> > Changes in v4:
> > - Dropping the smbios string parsing and add ethtool priv flag
> > to reconfigure the queues with full page rx buffers.
> > Changes in v3:
> > - changed u8* to char*
> > Changes in v2:
> > - separate reading string index and the string, remove inline.
> >
> > Dipayaan Roy (2):
> > net: mana: refactor mana_get_strings() and mana_get_sset_count() to
> > use switch
> > net: mana: force full-page RX buffers via ethtool private flag
> >
> > drivers/net/ethernet/microsoft/mana/mana_en.c | 22 ++-
> > .../ethernet/microsoft/mana/mana_ethtool.c | 178 +++++++++++++++---
> > include/net/mana/mana.h | 8 +
> > 3 files changed, 177 insertions(+), 31 deletions(-)
> >
> > --
> > 2.43.0
> >
> >
^ permalink raw reply
* Re: [PATCH nf] ipvs: skip IPv6 extension headers in SCTP state lookup
From: Julian Anastasov @ 2026-07-05 17:35 UTC (permalink / raw)
To: Yizhou Zhao
Cc: netdev, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
Phil Sutter, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, lvs-devel, netfilter-devel, coreteam, linux-kernel,
Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu, stable
In-Reply-To: <20260705123040.35755-1-zhaoyz24@mails.tsinghua.edu.cn>
Hello,
On Sun, 5 Jul 2026, Yizhou Zhao wrote:
> set_sctp_state() reads the SCTP chunk header again in order to drive the
> IPVS SCTP state table. For IPv6 it computes the offset with
> sizeof(struct ipv6hdr), while the surrounding IPVS code uses iph->len from
> ip_vs_fill_iph_skb(), where ipv6_find_hdr() has already skipped extension
> headers and found the real transport header.
>
> This makes the state machine read from the wrong offset for IPv6 SCTP
> packets that carry extension headers. For example, an INIT packet with an
> 8-byte destination options header can be scheduled correctly by
> sctp_conn_schedule(), but set_sctp_state() reads the first byte of the SCTP
> verification tag as a DATA chunk type. The connection then moves from NONE
> to ESTABLISHED instead of INIT1, gets the longer established timeout, and
> updates the active/inactive destination counters incorrectly. This happens
> even though the SCTP handshake has not completed.
>
> Use ip_vs_fill_iph_skb() in set_sctp_state() and base the chunk-header
> offset on iph.len, matching sctp_conn_schedule() and the SCTP NAT handlers.
> For IPv4 and IPv6 packets without extension headers this preserves the
> existing offset.
>
> Fixes: 2906f66a5682 ("ipvs: SCTP Trasport Loadbalancing Support")
> Cc: stable@vger.kernel.org
> Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
> Reported-by: Ao Wang <wangao@seu.edu.cn>
> Reported-by: Xuewei Feng <fengxw06@126.com>
> Reported-by: Qi Li <qli01@tsinghua.edu.cn>
> Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
> Assisted-by: Claude-Code:GLM-5.2
> Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> ---
> net/netfilter/ipvs/ip_vs_proto_sctp.c | 12 +++++-------
> 1 file changed, 5 insertions(+), 7 deletions(-)
>
> diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
> index 63c78a1f3918..6e0fc23be305 100644
> --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
> +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
> @@ -375,17 +375,15 @@ set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
> int direction, const struct sk_buff *skb)
> {
> struct sctp_chunkhdr _sctpch, *sch;
> unsigned char chunk_type;
> + struct ip_vs_iphdr iph;
> int event, next_state;
> - int ihl, cofs;
> + int cofs;
>
> -#ifdef CONFIG_IP_VS_IPV6
> - ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
> -#else
> - ihl = ip_hdrlen(skb);
> -#endif
> + if (!ip_vs_fill_iph_skb(cp->af, skb, false, &iph))
> + return;
May be it is better starting from ip_vs_set_state()
to provide new arg 'int iph_len/offset' (set to iph.len), down to
state_transition(), sctp_state_transition() and set_sctp_state().
Same for all protos. It should cost less stack and ipv6_find_hdr()
calls and what matters most, correct iph context in case we
have IP+ICMP+TCP (with just two ports or even with TCP flags)
and are scheduling ICMP, i.e. not IP+TCP as usually.
But what I see is that ip_vs_in_icmp*() are missing
the ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd) call just
after ip_vs_in_stats() and before ip_vs_icmp_xmit() where
we should provide ciph.len. That is why we don't reach the
set_tcp_state() calls to set correct cp->state and timeout
when scheduling related ICMP. So, this should be fixed too.
> - cofs = ihl + sizeof(struct sctphdr);
> + cofs = iph.len + sizeof(struct sctphdr);
> sch = skb_header_pointer(skb, cofs, sizeof(_sctpch), &_sctpch);
> if (sch == NULL)
> return;
> --
> 2.47.3
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply
* [PATCH v2 net 0/3] ipv4/ipv6: Fix UAF and memory leak in IGMP/MLD
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
This series addresses two potential UAF vulnerabilities
and memory leaks in the IPv4 IGMP and IPv6 MLD subsystems.
The first two patches fix a UAF where the packet receive path races with
device teardown. If the device refcount has already hit 0 (but the memory
is still held by RCU), incoming IGMP/MLD packets trying to schedule delayed
work or timers would call refcount_inc() on the 0 refcount, triggering a
warning and eventually leading to a UAF when the work runs after the device
has been freed. This is fixed by introducing safe hold helpers using
refcount_inc_not_zero(). In MLD, we also ensure we only enqueue the skb
if we successfully acquired the device reference, to avoid leaking skbs
when the device is being destroyed.
The third patch fixes memory leaks in IPv4 IGMP when timers are deleted or
stopped. When a timer is deleted (in igmp_mod_timer) or stopped (in
igmp_stop_timer) and not re-armed, the code dropped the group refcount using
refcount_dec(). However, if the group was concurrently removed from the list,
this decrement could drop the refcount to 0 without triggering the
cleanup/free path, leaking the group structure. This is fixed by using
ip_ma_put() instead, and deferring the put until after the lock is released.
Eric Dumazet (3):
ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
ipv6: mcast: Fix potential UAF in MLD delayed work
ipv4: igmp: Fix potential memory leaks in igmp_mod_timer() and
igmp_stop_timer()
include/linux/inetdevice.h | 5 +++++
include/net/addrconf.h | 5 +++++
net/ipv4/igmp.c | 28 +++++++++++++++++++-------
net/ipv6/mcast.c | 40 ++++++++++++++++++++++++++------------
4 files changed, 59 insertions(+), 19 deletions(-)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply
* [PATCH v2 net 1/3] ipv4: igmp: Fix potential UAF in igmp_gq_start_timer()
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet, Zero Day Initiative
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
A race condition exists between device teardown (inetdev_destroy) and
incoming IGMP query processing (igmp_rcv), leading to a Use-After-Free
in the IGMP timer callback.
During device destruction, inetdev_destroy() drops the primary reference
to in_device, which can drop its refcount to 0. The actual freeing of
in_device memory is deferred via RCU (using call_rcu()).
Concurrently, igmp_rcv() runs under RCU read lock and obtains the
in_device pointer. Because the memory is RCU-protected, CPU-0 can safely
dereference in_device even if its refcount has hit 0.
However, if CPU-0 calls igmp_gq_start_timer() and re-arms the timer, it
attempts to acquire a reference using in_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the in_device memory is still scheduled to be freed after the RCU
grace period (as the free callback does not check the refcount again),
the device is freed while the timer is still armed. When the timer
expires, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not arm the timer.
A similar issue in IPv6 MLD is fixed in a subsequent patch.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Zero Day Initiative <zdi-disclosures@trendmicro.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/linux/inetdevice.h | 5 +++++
net/ipv4/igmp.c | 14 +++++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h
index dccbeb25f70141982160c776f3cc727296def2b7..6032eea2539a60d0476d85667bda3bfcad8f1425 100644
--- a/include/linux/inetdevice.h
+++ b/include/linux/inetdevice.h
@@ -293,6 +293,11 @@ static inline void in_dev_put(struct in_device *idev)
#define __in_dev_put(idev) refcount_dec(&(idev)->refcnt)
#define in_dev_hold(idev) refcount_inc(&(idev)->refcnt)
+static inline bool in_dev_hold_safe(struct in_device *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
#endif /* __KERNEL__ */
static __inline__ __be32 inet_make_mask(int logmask)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index b6337a47c1418589bf8ef31e00fd98b6187f5271..f5f9763895641bf86bfcf9fd7fd7b06012fa4ece 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -248,16 +248,20 @@ static void igmp_gq_start_timer(struct in_device *in_dev)
return;
in_dev->mr_gq_running = 1;
- if (!mod_timer(&in_dev->mr_gq_timer, exp))
- in_dev_hold(in_dev);
+ if (in_dev_hold_safe(in_dev)) {
+ if (mod_timer(&in_dev->mr_gq_timer, exp))
+ in_dev_put(in_dev);
+ }
}
static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
{
- int tv = get_random_u32_below(delay);
+ if (in_dev_hold_safe(in_dev)) {
+ int tv = get_random_u32_below(delay);
- if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
- in_dev_hold(in_dev);
+ if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2))
+ in_dev_put(in_dev);
+ }
}
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net 2/3] ipv6: mcast: Fix potential UAF in MLD delayed work
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
A race condition exists between device teardown and incoming MLD query
processing, leading to a Use-After-Free in the MLD delayed work.
During device destruction, the primary reference to inet6_dev is dropped,
which can drop its refcount to 0. The actual freeing of inet6_dev memory
is deferred via RCU.
Concurrently, the packet receive path runs under RCU read lock and obtains
the inet6_dev pointer. Because the memory is RCU-protected, CPU-0 can
safely dereference inet6_dev even if its refcount has hit 0.
However, if CPU-0 calls igmp6_event_query() and schedules delayed work, it
attempts to acquire a reference using in6_dev_hold(). This increments the
refcount from 0 to 1, triggering a "refcount_t: addition on 0" warning.
Since the inet6_dev memory is still scheduled to be freed after the RCU
grace period, the device is freed while the work is still scheduled.
When the work runs, it accesses the freed memory, causing a kernel panic.
Fix this by using refcount_inc_not_zero() (via a new helper
in6_dev_hold_safe()) to prevent acquiring a reference if the device is
already being destroyed. If the refcount is 0, we do not schedule the work.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/addrconf.h | 5 +++++
net/ipv6/mcast.c | 40 ++++++++++++++++++++++++++++------------
2 files changed, 33 insertions(+), 12 deletions(-)
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 539bbbe54b14e8108ff7304d7a08bc605655cc31..8ced27a8229b6e0580f934be2223676cc123307b 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -446,6 +446,11 @@ static inline void in6_dev_hold(struct inet6_dev *idev)
refcount_inc(&idev->refcnt);
}
+static inline bool in6_dev_hold_safe(struct inet6_dev *idev)
+{
+ return refcount_inc_not_zero(&idev->refcnt);
+}
+
/* called with rcu_read_lock held */
static inline bool ip6_ignore_linkdown(const struct net_device *dev)
{
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 04b811b3be978e36b0fd4ecd8312313d73ed2ed9..4d2b9377ba2de5ad2b6d503bb4b285bfd1307a55 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -1083,8 +1083,10 @@ static void mld_gq_start_work(struct inet6_dev *idev)
mc_assert_locked(idev);
idev->mc_gq_running = 1;
- if (!mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_gq_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_gq_stop_work(struct inet6_dev *idev)
@@ -1102,8 +1104,10 @@ static void mld_ifc_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_ifc_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_ifc_stop_work(struct inet6_dev *idev)
@@ -1121,8 +1125,10 @@ static void mld_dad_start_work(struct inet6_dev *idev, unsigned long delay)
mc_assert_locked(idev);
- if (!mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
- in6_dev_hold(idev);
+ if (in6_dev_hold_safe(idev)) {
+ if (mod_delayed_work(mld_wq, &idev->mc_dad_work, tv + 2))
+ in6_dev_put(idev);
+ }
}
static void mld_dad_stop_work(struct inet6_dev *idev)
@@ -1395,18 +1401,23 @@ static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
void igmp6_event_query(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
spin_lock_bh(&idev->mc_query_lock);
- if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS) {
+ if (skb_queue_len(&idev->mc_query_queue) < MLD_MAX_SKBS &&
+ in6_dev_hold_safe(idev)) {
__skb_queue_tail(&idev->mc_query_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
- in6_dev_hold(idev);
+ if (mod_delayed_work(mld_wq, &idev->mc_query_work, 0))
+ put = true;
skb = NULL;
}
spin_unlock_bh(&idev->mc_query_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
@@ -1570,18 +1581,23 @@ static void mld_query_work(struct work_struct *work)
void igmp6_event_report(struct sk_buff *skb)
{
struct inet6_dev *idev = __in6_dev_get(skb->dev);
+ bool put = false;
if (!idev || idev->dead)
goto out;
spin_lock_bh(&idev->mc_report_lock);
- if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS) {
+ if (skb_queue_len(&idev->mc_report_queue) < MLD_MAX_SKBS &&
+ in6_dev_hold_safe(idev)) {
__skb_queue_tail(&idev->mc_report_queue, skb);
- if (!mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
- in6_dev_hold(idev);
+ if (mod_delayed_work(mld_wq, &idev->mc_report_work, 0))
+ put = true;
skb = NULL;
}
spin_unlock_bh(&idev->mc_report_lock);
+
+ if (put)
+ in6_dev_put(idev);
out:
kfree_skb(skb);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH v2 net 3/3] ipv4: igmp: Fix potential memory leaks in igmp_mod_timer() and igmp_stop_timer()
From: Eric Dumazet @ 2026-07-05 18:17 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Kuniyuki Iwashima, Ido Schimmel, David Ahern,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260705181756.963063-1-edumazet@google.com>
When a timer is deleted and not re-armed in igmp_mod_timer(), or stopped
in igmp_stop_timer(), the code currently decrements the reference counter
of the multicast list entry @im using refcount_dec(&im->refcnt).
However, both functions can be called from the RCU reader path:
- igmp_mod_timer() via igmp_heard_query() -> for_each_pmc_rcu()
- igmp_stop_timer() via igmp_rcv() -> igmp_heard_report()
If the group im was concurrently removed from the list by ip_mc_dec_group(),
its reference count might have already been decremented to 1.
In this case, timer_delete() succeeds, and refcount_dec() decrements
the refcount from 1 to 0. Since refcount_dec() does not free the object
when it hits 0 (unlike ip_ma_put()), the im structure is leaked.
Fix this by using ip_ma_put(im) instead of refcount_dec(&im->refcnt),
and deferring the put until after the spinlock is released.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
net/ipv4/igmp.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index f5f9763895641bf86bfcf9fd7fd7b06012fa4ece..957013e7f7a25f32d848822caab322147db3dc6e 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -217,13 +217,18 @@ static void ip_sf_list_clear_all(struct ip_sf_list *psf)
static void igmp_stop_timer(struct ip_mc_list *im)
{
+ bool put = false;
+
spin_lock_bh(&im->lock);
if (timer_delete(&im->timer))
- refcount_dec(&im->refcnt);
+ put = true;
WRITE_ONCE(im->tm_running, 0);
WRITE_ONCE(im->reporter, 0);
im->unsolicit_count = 0;
spin_unlock_bh(&im->lock);
+
+ if (put)
+ ip_ma_put(im);
}
/* It must be called with locked im->lock */
@@ -266,6 +271,8 @@ static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
{
+ bool put = false;
+
spin_lock_bh(&im->lock);
im->unsolicit_count = 0;
if (timer_delete(&im->timer)) {
@@ -275,10 +282,13 @@ static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
spin_unlock_bh(&im->lock);
return;
}
- refcount_dec(&im->refcnt);
+ put = true;
}
igmp_start_timer(im, max_delay);
spin_unlock_bh(&im->lock);
+
+ if (put)
+ ip_ma_put(im);
}
--
2.55.0.rc0.799.gd6f94ed593-goog
^ permalink raw reply related
* [PATCH net-next 0/2] net: sfp: quirk support for XGS-PON ONT sticks with unclean EEPROMs
From: Martino Dell'Ambrogio @ 2026-07-05 18:54 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Heiner Kallweit, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel,
Martino Dell'Ambrogio
Some clone XGS-PON ONT sticks ship EEPROMs where the vendor PN field is
filled with non-printable garbage past the legitimate string instead of
the SFF-8472 mandated space padding. sfp_strlen() then can't trim the
field, the exact-length check in sfp_match() rejects the quirk entry
before the string comparison runs, and the quirk silently never applies
— so the kernel honors the module's spurious TX_FAULT and eventually
disables it.
Patch 1 adds an opt-in prefix-matching flag to the quirk table so such
modules can still be matched; existing exact-match entries behave
exactly as before. Patch 2 adds the two entries that need it: the
"OEM" XGSPONST2001 and the Fiberstore XGS-SFP-ONT-MACI, both wired to
the existing potron fixup.
Both quirks are in production use on a Bananapi BPI-R4 (MT7988A)
router on an XGS-PON uplink, backported onto 6.12.
Martino Dell'Ambrogio (2):
net: sfp: allow prefix matching in quirk lookup
net: sfp: add quirks for OEM XGSPONST2001 and FS XGS-SFP-ONT-MACI
drivers/net/phy/sfp.c | 37 ++++++++++++++++++++++++++++++++-----
drivers/net/phy/sfp.h | 1 +
2 files changed, 33 insertions(+), 5 deletions(-)
--
2.47.3
^ permalink raw reply
* [PATCH net-next 2/2] net: sfp: add quirks for OEM XGSPONST2001 and FS XGS-SFP-ONT-MACI
From: Martino Dell'Ambrogio @ 2026-07-05 18:54 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Heiner Kallweit, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel,
Martino Dell'Ambrogio
In-Reply-To: <20260705185440.136496-1-tillo@tillo.ch>
Cheap XGS-PON ONT sticks identifying as vendor "OEM", PN "XGSPONST2001"
have broken TX_FAULT and LOS indicators (driven by the ONU serial
passthrough wires) and need a longer T_START_UP than the SFF-8472
default. The Fiberstore XGS-SFP-ONT-MACI MAC-mode ONT stick has the
same ONT-class TX_FAULT/LOS wiring and startup behaviour. Apply the
existing sfp_fixup_potron handler to both, which masks both signals
and bumps T_START_UP to T_START_UP_BAD_GPON.
Both modules fail to space-pad the EEPROM vendor PN field past the
legitimate string as SFF-8472 mandates (the XGSPONST2001 fills it with
non-printable garbage), which defeats exact-length matching:
sfp_strlen() cannot trim the field, so a plain SFP_QUIRK_F entry would
silently never apply and the kernel would honor the spurious TX_FAULT
and eventually disable the module. Match both entries as prefixes
using SFP_QUIRK_F_PREFIX.
Signed-off-by: Martino Dell'Ambrogio <tillo@tillo.ch>
---
drivers/net/phy/sfp.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index e7ba642..eae0699 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -555,6 +555,13 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK("FS", "GPON-ONU-34-20BI", sfp_quirk_2500basex,
sfp_fixup_ignore_tx_fault),
+ // Fiberstore XGS-SFP-ONT-MACI is a MAC-mode XGS-PON ONT stick with
+ // ONT-class serial-passthrough TX_FAULT/LOS wiring and slow startup;
+ // mask both signals and extend T_START_UP via the potron fixup. The
+ // EEPROM vendor PN field is not space-padded past the legitimate
+ // string, so match it as a prefix.
+ SFP_QUIRK_F_PREFIX("FS", "XGS-SFP-ONT-MACI", sfp_fixup_potron),
+
SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
SFP_QUIRK_F("H-COM", "SPP425H-GAB4", sfp_fixup_potron),
@@ -612,6 +619,14 @@ static const struct sfp_quirk sfp_quirks[] = {
SFP_QUIRK_S("OEM", "SFP-2.5G-LH20-A", sfp_quirk_2500basex),
SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
+
+ // OEM XGSPONST2001 is an XGS-PON ONT stick with broken TX_FAULT and
+ // LOS indicators and slow startup, just like potron. The EEPROM
+ // vendor PN field is filled with non-printable garbage past the
+ // legitimate string instead of space padding, so match it as a
+ // prefix.
+ SFP_QUIRK_F_PREFIX("OEM", "XGSPONST2001", sfp_fixup_potron),
+
SFP_QUIRK_F("Turris", "RTSFP-2.5G", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
--
2.47.3
^ permalink raw reply related
* [PATCH net-next 1/2] net: sfp: allow prefix matching in quirk lookup
From: Martino Dell'Ambrogio @ 2026-07-05 18:54 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Heiner Kallweit, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel,
Martino Dell'Ambrogio
In-Reply-To: <20260705185440.136496-1-tillo@tillo.ch>
Some clone SFP modules (notably XGS-PON ONT sticks) ship malformed
EEPROMs where the vendor PN field is filled with non-printable garbage
past the trailing legitimate characters instead of SFF-8472 mandated
space padding. The current sfp_match() requires an exact full-field
length match: sfp_strlen() returns 16 (no trailing spaces or NULs to
strip), but strlen() of the quirk string is shorter, so the length
comparison rejects the entry before strncmp() is even called and the
quirk silently never applies. The kernel then honors the module's
spurious TX_FAULT signal and the SFP state machine eventually disables
the module.
Add a prefix_match flag to struct sfp_quirk and a SFP_QUIRK_F_PREFIX
macro. When set, sfp_match() compares only strlen() leading bytes of
the quirk string, ignoring trailing field bytes. Existing exact-match
quirks are unaffected (prefix_match defaults to false via zero-init in
the existing SFP_QUIRK macros).
Signed-off-by: Martino Dell'Ambrogio <tillo@tillo.ch>
---
drivers/net/phy/sfp.c | 22 +++++++++++++++++-----
drivers/net/phy/sfp.h | 1 +
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
index f520206..e7ba642 100644
--- a/drivers/net/phy/sfp.c
+++ b/drivers/net/phy/sfp.c
@@ -516,6 +516,13 @@ static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
{ .vendor = _v, .part = _p, .support = _s, .fixup = _f, }
#define SFP_QUIRK_S(_v, _p, _s) SFP_QUIRK(_v, _p, _s, NULL)
#define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f)
+/* Like SFP_QUIRK_F, but matches as a prefix. Use for clone modules
+ * that fill EEPROM trailing bytes with garbage instead of the
+ * SFF-8472-mandated space padding, so sfp_strlen can't trim the
+ * field down to the legitimate length.
+ */
+#define SFP_QUIRK_F_PREFIX(_v, _p, _f) \
+ { .vendor = _v, .part = _p, .support = NULL, .fixup = _f, .prefix_match = true }
static const struct sfp_quirk sfp_quirks[] = {
// Alcatel Lucent G-010S-P can operate at 2500base-X, but incorrectly
@@ -626,13 +633,16 @@ static size_t sfp_strlen(const char *str, size_t maxlen)
return size;
}
-static bool sfp_match(const char *qs, const char *str, size_t len)
+static bool sfp_match(const char *qs, const char *str, size_t len, bool prefix)
{
+ size_t qs_len;
+
if (!qs)
return true;
- if (strlen(qs) != len)
+ qs_len = strlen(qs);
+ if (prefix ? qs_len > len : qs_len != len)
return false;
- return !strncmp(qs, str, len);
+ return !strncmp(qs, str, qs_len);
}
static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
@@ -645,8 +655,10 @@ static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
- if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
- sfp_match(q->part, id->base.vendor_pn, ps))
+ if (sfp_match(q->vendor, id->base.vendor_name, vs,
+ q->prefix_match) &&
+ sfp_match(q->part, id->base.vendor_pn, ps,
+ q->prefix_match))
return q;
return NULL;
diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
index 879dff7..867e45e 100644
--- a/drivers/net/phy/sfp.h
+++ b/drivers/net/phy/sfp.h
@@ -12,6 +12,7 @@ struct sfp_quirk {
void (*support)(const struct sfp_eeprom_id *id,
struct sfp_module_caps *caps);
void (*fixup)(struct sfp *sfp);
+ bool prefix_match;
};
struct sfp_socket_ops {
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v2 0/2] nfc: s3fwrn5: support the S3NRN4V variant
From: Jorijn van der Graaf @ 2026-07-05 19:06 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Jorijn van der Graaf, David Heidelberg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
linux-kernel
This adds support for the Samsung S3NRN4V, an S3FWRN5-family NFC
controller found e.g. on the Fairphone 6 (SM7635), to the s3fwrn5
driver.
The S3NRN4V differs from the already-supported parts in three ways: it
ships with working firmware behind a bootloader protocol the driver
does not implement (so firmware download is skipped), it loads its RF
registers through a different proprietary command (DUAL_OPTION), and it
gates its reference clock through a CLK_REQ line that the driver must
service for the chip to be able to generate the 13.56 MHz poll carrier.
Patch 1 adds the compatible and the clk-req-gpios property to the
binding; patch 2 implements the variant in the driver.
Tested on a Fairphone 6 running a milos-mainline kernel: reader mode polls
and reads ISO 14443-4 tags reliably, both from a fresh boot and across
driver reloads.
Changes in v2:
- Drop the -i2c bus suffix from the new compatible: it is now plain
samsung,s3nrn4v (Requested by: Conor Dooley).
- Close a race in the probe-time CLK_REQ seeding by reading the GPIO
level under clk_lock, so a stale level can never overwrite a fresher
state applied by the irq thread (found by the Sashiko AI review of
v1).
- Binding completeness: document the PVDD supply (required for the
S3NRN4V), add an S3NRN4V example exercising the new properties, make
clk-req-gpios depend on clocks, and describe the CLK_REQ pin in
hardware terms.
- Rework the binding commit message: justify the GPIO modelling in
hardware terms and explain why no fallback compatible applies.
- Add an s3nrn4v i2c_device_id entry carrying the variant so both
match paths agree, and describe the of_match_ptr() removal in the
driver commit message.
- Reject malformed rfreg blobs (word alignment, single-byte section
index bound) up front instead of failing at STOP_UPDATE.
- Handle gpiod_get_value_cansleep() failure in the CLK_REQ sync
instead of treating an error as "clock off".
v1: https://lore.kernel.org/20260703202601.78563-1-jorijnvdgraaf@catcrafts.net
Jorijn van der Graaf (2):
dt-bindings: net: nfc: samsung,s3fwrn5: add S3NRN4V and clk-req-gpios
nfc: s3fwrn5: support the S3NRN4V variant
.../bindings/net/nfc/samsung,s3fwrn5.yaml | 65 ++++++++-
drivers/nfc/s3fwrn5/core.c | 40 ++++-
drivers/nfc/s3fwrn5/i2c.c | 138 ++++++++++++++++--
drivers/nfc/s3fwrn5/nci.c | 119 ++++++++++++++-
drivers/nfc/s3fwrn5/nci.h | 32 +++-
drivers/nfc/s3fwrn5/s3fwrn5.h | 14 +-
drivers/nfc/s3fwrn5/uart.c | 2 +-
7 files changed, 394 insertions(+), 16 deletions(-)
base-commit: 805185b7c7a1069e407b6f7b3bc98e44d415f484
--
2.55.0
^ permalink raw reply
* [PATCH net-next v2 1/2] dt-bindings: net: nfc: samsung,s3fwrn5: add S3NRN4V and clk-req-gpios
From: Jorijn van der Graaf @ 2026-07-05 19:06 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Jorijn van der Graaf, David Heidelberg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
linux-kernel
In-Reply-To: <20260705190621.128257-1-jorijnvdgraaf@catcrafts.net>
The S3NRN4V is an S3FWRN5-family NCI NFC controller found e.g. on the
Fairphone 6 (SM7635). Its host interface is NCI over I2C like the
S3FWRN5, but it is not compatible with any of the existing parts, so no
fallback compatible applies: its bootloader speaks a different protocol
and its RF configuration is loaded through a different proprietary
command set.
Document the optional clk-req-gpios property: the controller's CLK_REQ
output is asserted for as long as the chip needs its external reference
clock, notably while generating the 13.56 MHz poll carrier. The pin is
a level signal reflecting the chip's current clock demand, not a
one-shot event, so it is modelled as a GPIO. No user of the handshake
is known on the already-supported parts, so the property is restricted
to the S3NRN4V (easily relaxed should one appear), and it depends on
clocks, as its purpose is gating an external clock.
Also document the PVDD supply, the externally switched rail powering
the controller (boards feed it from a PMIC LDO). It is required for the
new device; deployed DTs for the existing parts never described a
supply, so for those it stays optional. Add an example for the new
device exercising the new properties.
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
Changes in v2:
- Drop the -i2c bus suffix from the new compatible; the parent bus
node already implies the interface (Requested by: Conor Dooley).
- Document the PVDD supply; required for the S3NRN4V, optional for the
existing parts whose deployed DTs never described a supply.
- Add an S3NRN4V example exercising clk-req-gpios, clocks and
pvdd-supply.
- Make clk-req-gpios depend on clocks.
- Describe the CLK_REQ pin in hardware terms (what the pin is, not
what the OS does with it).
- Rework the commit message: justify the GPIO modelling in hardware
terms and explain why no fallback compatible applies.
v1: https://lore.kernel.org/20260703202601.78563-2-jorijnvdgraaf@catcrafts.net
.../bindings/net/nfc/samsung,s3fwrn5.yaml | 65 ++++++++++++++++++-
1 file changed, 64 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
index 12baee45752c..1e784a90d015 100644
--- a/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
+++ b/Documentation/devicetree/bindings/net/nfc/samsung,s3fwrn5.yaml
@@ -14,12 +14,20 @@ properties:
enum:
- samsung,s3fwrn5-i2c
- samsung,s3fwrn82
+ - samsung,s3nrn4v
en-gpios:
maxItems: 1
description:
Output GPIO pin used for enabling/disabling the chip
+ clk-req-gpios:
+ maxItems: 1
+ description:
+ Input GPIO pin connected to the controller's CLK_REQ output, which the
+ controller asserts for as long as it requires its external reference
+ clock.
+
interrupts:
maxItems: 1
@@ -29,6 +37,9 @@ properties:
clocks:
maxItems: 1
+ pvdd-supply:
+ description: PVDD power supply
+
wake-gpios:
maxItems: 1
description:
@@ -53,17 +64,45 @@ required:
- en-gpios
- wake-gpios
+# The clock-request handshake gates an external clock, so it needs one.
+dependencies:
+ clk-req-gpios: [ clocks ]
+
allOf:
- if:
properties:
compatible:
contains:
- const: samsung,s3fwrn5-i2c
+ enum:
+ - samsung,s3fwrn5-i2c
+ - samsung,s3nrn4v
then:
required:
- interrupts
- reg
+ # No user of the clock-request handshake is known on the other parts.
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: samsung,s3nrn4v
+ then:
+ properties:
+ clk-req-gpios: false
+
+ # Deployed DTs for the older parts never described a supply, so PVDD is
+ # only required for the new device.
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: samsung,s3nrn4v
+ then:
+ required:
+ - pvdd-supply
+
examples:
- |
#include <dt-bindings/gpio/gpio.h>
@@ -97,3 +136,27 @@ examples:
};
};
+ # S3NRN4V with a clock-request gated external clock
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ nfc@27 {
+ compatible = "samsung,s3nrn4v";
+ reg = <0x27>;
+
+ interrupt-parent = <&tlmm>;
+ interrupts = <31 IRQ_TYPE_EDGE_RISING>;
+
+ en-gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>;
+ wake-gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>;
+ clk-req-gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>;
+
+ clocks = <&rpmhcc 4>;
+ pvdd-supply = <&nfc_pvdd>;
+ };
+ };
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v2 2/2] nfc: s3fwrn5: support the S3NRN4V variant
From: Jorijn van der Graaf @ 2026-07-05 19:06 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Jorijn van der Graaf, David Heidelberg, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Conor Dooley, oe-linux-nfc, netdev, devicetree,
linux-kernel
In-Reply-To: <20260705190621.128257-1-jorijnvdgraaf@catcrafts.net>
The S3NRN4V (e.g. on the Fairphone 6, SM7635) is an S3FWRN5-family NFC
controller that needs different bring-up, selected with a new
samsung,s3nrn4v compatible:
- It ships with working firmware behind a bootloader protocol this
driver does not implement (GET_BOOTINFO times out), so the firmware
download step is skipped. Its RF registers are (re)loaded with the
proprietary DUAL_OPTION command (the HW and SW register blobs merged
into a single stream) instead of the START/SET/STOP_RFREG sequence.
- Its reference clock speed is configured with the single-byte FW_CFG
form, sent from the ->setup hook (after CORE_RESET, before CORE_INIT).
The selector value (0x11) is taken from the vendor configuration for
this part; its encoding is not documented.
- It gates its XI clock through a CLK_REQ line: the chip drives it high
when it needs the clock, notably to synthesise the 13.56 MHz poll
carrier. Left always-on, the free-running clock never lets the chip's
TX PLL lock on a fresh start and it cannot poll (it falls back to
listen only). Service the handshake when a clk-req GPIO is described,
gating the clock on it; without one the clock stays always-on.
The variant is carried as match data by both the OF and the I2C device
id tables so the two match paths agree, and the OF table is now
referenced unconditionally for its match data, so drop the
of_match_ptr()/__maybe_unused annotations from it.
The error policy differs between the two configuration steps on purpose:
a clock misconfiguration is fatal (a ->setup failure aborts CORE_INIT),
whereas an RF-register update failure is only warned about and bring-up
continues, since the chip falls back to the RF registers programmed in
its flash and NFC may still work.
Unlike the host-endian word read in the legacy rfreg path, the
DUAL_OPTION checksum is accumulated with get_unaligned_le32() and emitted
little-endian explicitly, so it is correct regardless of CPU endianness.
Existing S3FWRN5 / S3FWRN82 setups keep the firmware-download path and
the always-on clock, unchanged.
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
Changes in v2:
- Rename the new compatible to samsung,s3nrn4v, matching the binding
change (Requested by: Conor Dooley).
- Close a race in the probe-time CLK_REQ seeding: the GPIO level is
now read under clk_lock (new s3fwrn5_i2c_clk_sync(), used by both
the irq thread and probe), so a level read before the irq fired can
never overwrite the fresher state the irq thread applied (found by
the Sashiko AI review of v1).
- Reject malformed rfreg blobs (word alignment, single-byte section
index bound) up front instead of failing at STOP_UPDATE.
- Handle gpiod_get_value_cansleep() failure instead of gating the
clock off on error.
- Add an s3nrn4v i2c_device_id entry carrying the variant so both
match paths agree.
- Describe the of_match_ptr()/__maybe_unused removal in the commit
message.
v1: https://lore.kernel.org/20260703202601.78563-3-jorijnvdgraaf@catcrafts.net
drivers/nfc/s3fwrn5/core.c | 40 +++++++++-
drivers/nfc/s3fwrn5/i2c.c | 138 +++++++++++++++++++++++++++++++---
drivers/nfc/s3fwrn5/nci.c | 119 ++++++++++++++++++++++++++++-
drivers/nfc/s3fwrn5/nci.h | 32 +++++++-
drivers/nfc/s3fwrn5/s3fwrn5.h | 14 +++-
drivers/nfc/s3fwrn5/uart.c | 2 +-
6 files changed, 330 insertions(+), 15 deletions(-)
diff --git a/drivers/nfc/s3fwrn5/core.c b/drivers/nfc/s3fwrn5/core.c
index af0fa8bd970b..59317eaad7ac 100644
--- a/drivers/nfc/s3fwrn5/core.c
+++ b/drivers/nfc/s3fwrn5/core.c
@@ -122,11 +122,47 @@ static int s3fwrn5_nci_send(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
+static int s3fwrn5_nci_setup(struct nci_dev *ndev)
+{
+ struct s3fwrn5_info *info = nci_get_drvdata(ndev);
+
+ /*
+ * Runs after CORE_RESET, before CORE_INIT. The S3NRN4V needs its
+ * reference clock configured here (the downstream stack does it in the
+ * bootloader, before CORE_RESET, but this is the earliest hook the NCI
+ * core offers and the chip accepts it).
+ */
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V)
+ return s3fwrn5_nci_clk_cfg(info);
+
+ return 0;
+}
+
static int s3fwrn5_nci_post_setup(struct nci_dev *ndev)
{
struct s3fwrn5_info *info = nci_get_drvdata(ndev);
int ret;
+ if (info->variant == S3FWRN5_VARIANT_S3NRN4V) {
+ /*
+ * The S3NRN4V ships with working firmware behind a bootloader
+ * protocol this driver does not implement, so there is no
+ * download step; the NCI core has already done CORE_RESET +
+ * CORE_INIT. Just (re)load the RF registers via DUAL_OPTION.
+ */
+ ret = s3fwrn5_nci_rf_configure_dual(info, "sec_s3nrn4v_hwreg.bin",
+ "sec_s3nrn4v_swreg.bin");
+ /*
+ * Keep going even if the blobs could not be loaded: the chip
+ * still enumerates and falls back to the RF registers programmed
+ * in its flash, so NFC may work anyway.
+ */
+ if (ret < 0)
+ dev_warn(&ndev->nfc_dev->dev,
+ "rfreg configure failed (%d)\n", ret);
+ return 0;
+ }
+
if (s3fwrn5_firmware_init(info)) {
//skip bootloader mode
return 0;
@@ -152,13 +188,14 @@ static const struct nci_ops s3fwrn5_nci_ops = {
.open = s3fwrn5_nci_open,
.close = s3fwrn5_nci_close,
.send = s3fwrn5_nci_send,
+ .setup = s3fwrn5_nci_setup,
.post_setup = s3fwrn5_nci_post_setup,
.prop_ops = s3fwrn5_nci_prop_ops,
.n_prop_ops = ARRAY_SIZE(s3fwrn5_nci_prop_ops),
};
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops)
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant)
{
struct s3fwrn5_info *info;
int ret;
@@ -170,6 +207,7 @@ int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
info->phy_id = phy_id;
info->pdev = pdev;
info->phy_ops = phy_ops;
+ info->variant = variant;
mutex_init(&info->mutex);
s3fwrn5_set_mode(info, S3FWRN5_MODE_COLD);
diff --git a/drivers/nfc/s3fwrn5/i2c.c b/drivers/nfc/s3fwrn5/i2c.c
index e9a34d27a369..7d20e737e402 100644
--- a/drivers/nfc/s3fwrn5/i2c.c
+++ b/drivers/nfc/s3fwrn5/i2c.c
@@ -23,9 +23,76 @@ struct s3fwrn5_i2c_phy {
struct i2c_client *i2c_dev;
struct clk *clk;
+ /*
+ * Optional hardware clock-request handshake. When a CLK_REQ GPIO is
+ * wired, the chip drives it high while it needs its XI clock -- notably
+ * to generate the poll/reader carrier -- and the clock is gated on it
+ * instead of being left always-on (which never lets the chip's TX PLL
+ * lock on a fresh clock start, leaving it unable to poll).
+ */
+ struct gpio_desc *gpio_clk_req;
+ bool clk_on;
+ struct mutex clk_lock; /* serialises clk_on against the CLK_REQ irq */
+
unsigned int irq_skip:1;
};
+static void s3fwrn5_i2c_clk_set_locked(struct s3fwrn5_i2c_phy *phy, bool on)
+{
+ lockdep_assert_held(&phy->clk_lock);
+
+ if (on && !phy->clk_on) {
+ int ret = clk_prepare_enable(phy->clk);
+
+ if (ret == 0)
+ phy->clk_on = true;
+ else
+ dev_warn_once(&phy->i2c_dev->dev,
+ "failed to enable clock (%d); NFC may not poll\n",
+ ret);
+ } else if (!on && phy->clk_on) {
+ clk_disable_unprepare(phy->clk);
+ phy->clk_on = false;
+ }
+}
+
+/*
+ * Apply the current CLK_REQ level. Reading the GPIO under clk_lock makes
+ * concurrent callers (the CLK_REQ irq thread and the probe-time seeding)
+ * safe: whoever runs last applies a level read after the earlier update,
+ * never a stale one.
+ */
+static void s3fwrn5_i2c_clk_sync(struct s3fwrn5_i2c_phy *phy)
+{
+ int level;
+
+ mutex_lock(&phy->clk_lock);
+ level = gpiod_get_value_cansleep(phy->gpio_clk_req);
+ if (level >= 0)
+ s3fwrn5_i2c_clk_set_locked(phy, level > 0);
+ else
+ dev_warn_once(&phy->i2c_dev->dev,
+ "failed to read CLK_REQ (%d); keeping clock state\n",
+ level);
+ mutex_unlock(&phy->clk_lock);
+}
+
+static void s3fwrn5_i2c_clk_disable_action(void *data)
+{
+ struct s3fwrn5_i2c_phy *phy = data;
+
+ mutex_lock(&phy->clk_lock);
+ s3fwrn5_i2c_clk_set_locked(phy, false);
+ mutex_unlock(&phy->clk_lock);
+}
+
+static irqreturn_t s3fwrn5_i2c_clk_req_thread(int irq, void *phy_id)
+{
+ s3fwrn5_i2c_clk_sync(phy_id);
+
+ return IRQ_HANDLED;
+}
+
static void s3fwrn5_i2c_set_mode(void *phy_id, enum s3fwrn5_mode mode)
{
struct s3fwrn5_i2c_phy *phy = phy_id;
@@ -146,6 +213,7 @@ static irqreturn_t s3fwrn5_i2c_irq_thread_fn(int irq, void *phy_id)
static int s3fwrn5_i2c_probe(struct i2c_client *client)
{
+ enum s3fwrn5_variant variant;
struct s3fwrn5_i2c_phy *phy;
int ret;
@@ -172,15 +240,61 @@ static int s3fwrn5_i2c_probe(struct i2c_client *client)
* S3FWRN5 depends on a clock input ("XI" pin) to function properly.
* Depending on the hardware configuration this could be an always-on
* oscillator or some external clock that must be explicitly enabled.
- * Make sure the clock is running before starting S3FWRN5.
+ *
+ * If a CLK_REQ GPIO is wired, the chip gates the clock itself (driving
+ * CLK_REQ high when it needs XI); service that handshake. Otherwise just
+ * make sure the clock is running before starting S3FWRN5.
*/
- phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
- if (IS_ERR(phy->clk))
- return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
- "failed to get clock\n");
+ mutex_init(&phy->clk_lock);
+ phy->gpio_clk_req = devm_gpiod_get_optional(&client->dev, "clk-req",
+ GPIOD_IN);
+ if (IS_ERR(phy->gpio_clk_req))
+ return PTR_ERR(phy->gpio_clk_req);
+
+ if (phy->gpio_clk_req) {
+ int clk_req_irq;
+
+ phy->clk = devm_clk_get_optional(&client->dev, NULL);
+ if (IS_ERR(phy->clk))
+ return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
+ "failed to get clock\n");
+
+ /*
+ * Unlike the always-on branch below, this clock is enabled by
+ * hand from the CLK_REQ handler, so devm will not disable it on
+ * unbind. Gate it off explicitly if it is still on at teardown.
+ */
+ ret = devm_add_action_or_reset(&client->dev,
+ s3fwrn5_i2c_clk_disable_action,
+ phy);
+ if (ret)
+ return ret;
+
+ clk_req_irq = gpiod_to_irq(phy->gpio_clk_req);
+ if (clk_req_irq < 0)
+ return clk_req_irq;
+
+ ret = devm_request_threaded_irq(&client->dev, clk_req_irq, NULL,
+ s3fwrn5_i2c_clk_req_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "s3fwrn5_clk_req", phy);
+ if (ret)
+ return ret;
+
+ /* Seed the clock state from the current CLK_REQ level. */
+ s3fwrn5_i2c_clk_sync(phy);
+ } else {
+ phy->clk = devm_clk_get_optional_enabled(&client->dev, NULL);
+ if (IS_ERR(phy->clk))
+ return dev_err_probe(&client->dev, PTR_ERR(phy->clk),
+ "failed to get clock\n");
+ }
+ variant = (uintptr_t)i2c_get_match_data(client);
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->i2c_dev->dev,
- &i2c_phy_ops);
+ &i2c_phy_ops, variant);
if (ret < 0)
return ret;
@@ -205,13 +319,17 @@ static void s3fwrn5_i2c_remove(struct i2c_client *client)
}
static const struct i2c_device_id s3fwrn5_i2c_id_table[] = {
- { .name = S3FWRN5_I2C_DRIVER_NAME },
+ { .name = S3FWRN5_I2C_DRIVER_NAME, .driver_data = S3FWRN5_VARIANT_FWDL },
+ { .name = "s3nrn4v", .driver_data = S3FWRN5_VARIANT_S3NRN4V },
{ }
};
MODULE_DEVICE_TABLE(i2c, s3fwrn5_i2c_id_table);
-static const struct of_device_id of_s3fwrn5_i2c_match[] __maybe_unused = {
- { .compatible = "samsung,s3fwrn5-i2c", },
+static const struct of_device_id of_s3fwrn5_i2c_match[] = {
+ { .compatible = "samsung,s3fwrn5-i2c",
+ .data = (void *)S3FWRN5_VARIANT_FWDL, },
+ { .compatible = "samsung,s3nrn4v",
+ .data = (void *)S3FWRN5_VARIANT_S3NRN4V, },
{}
};
MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
@@ -219,7 +337,7 @@ MODULE_DEVICE_TABLE(of, of_s3fwrn5_i2c_match);
static struct i2c_driver s3fwrn5_i2c_driver = {
.driver = {
.name = S3FWRN5_I2C_DRIVER_NAME,
- .of_match_table = of_match_ptr(of_s3fwrn5_i2c_match),
+ .of_match_table = of_s3fwrn5_i2c_match,
},
.probe = s3fwrn5_i2c_probe,
.remove = s3fwrn5_i2c_remove,
diff --git a/drivers/nfc/s3fwrn5/nci.c b/drivers/nfc/s3fwrn5/nci.c
index 5a9de11bbece..7034fb810e18 100644
--- a/drivers/nfc/s3fwrn5/nci.c
+++ b/drivers/nfc/s3fwrn5/nci.c
@@ -8,6 +8,9 @@
#include <linux/completion.h>
#include <linux/firmware.h>
+#include <linux/minmax.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
#include "s3fwrn5.h"
#include "nci.h"
@@ -20,7 +23,7 @@ static int s3fwrn5_nci_prop_rsp(struct nci_dev *ndev, struct sk_buff *skb)
return 0;
}
-const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
+const struct nci_driver_ops s3fwrn5_nci_prop_ops[5] = {
{
.opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
NCI_PROP_SET_RFREG),
@@ -41,6 +44,11 @@ const struct nci_driver_ops s3fwrn5_nci_prop_ops[4] = {
NCI_PROP_FW_CFG),
.rsp = s3fwrn5_nci_prop_rsp,
},
+ {
+ .opcode = nci_opcode_pack(NCI_GID_PROPRIETARY,
+ NCI_PROP_DUAL_OPTION),
+ .rsp = s3fwrn5_nci_prop_rsp,
+ },
};
#define S3FWRN5_RFREG_SECTION_SIZE 252
@@ -117,3 +125,112 @@ int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name)
release_firmware(fw);
return ret;
}
+
+/*
+ * Configure the reference clock. The S3NRN4V expects the single-byte FW_CFG
+ * form (just the clock-speed selector). The downstream stack sends this in the
+ * bootloader before CORE_RESET; the earliest the mainline NCI core lets us in
+ * is the ->setup hook (after CORE_RESET, before CORE_INIT), which works.
+ */
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info)
+{
+ u8 clk_speed = NCI_PROP_FW_CFG_CLK_SPEED;
+
+ return nci_prop_cmd(info->ndev, NCI_PROP_FW_CFG, 1, &clk_speed);
+}
+
+/*
+ * S3NRN4V RF register update. The HW and SW register blobs are merged into a
+ * single stream (HW first) and pushed via the DUAL_OPTION command:
+ * START_UPDATE, one SET_OPTION per 252-byte section, then STOP_UPDATE carrying
+ * a 16-bit checksum (running sum of the merged stream as 32-bit words).
+ */
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name)
+{
+ const struct firmware *hw_fw = NULL, *sw_fw = NULL;
+ struct nci_prop_dual_set_option_cmd set_option;
+ struct device *dev = &info->ndev->nfc_dev->dev;
+ size_t merged_size, i, len;
+ u8 *merged = NULL;
+ u8 stop_cmd[3];
+ u32 checksum;
+ u8 sub_oid;
+ int ret;
+
+ ret = request_firmware(&hw_fw, hw_name, dev);
+ if (ret < 0)
+ return ret;
+ ret = request_firmware(&sw_fw, sw_name, dev);
+ if (ret < 0)
+ goto out_hw;
+
+ merged_size = hw_fw->size + sw_fw->size;
+
+ /*
+ * The stream is checksummed as 32-bit words and pushed in at most 256
+ * sections (the section index is a single byte); reject blobs that
+ * would silently break either.
+ */
+ if (merged_size % 4 ||
+ merged_size > 256 * NCI_PROP_DUAL_SECTION_SIZE) {
+ dev_err(dev, "invalid rfreg blob size (%zu)\n", merged_size);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ merged = kmalloc(merged_size, GFP_KERNEL);
+ if (!merged) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ memcpy(merged, hw_fw->data, hw_fw->size);
+ memcpy(merged + hw_fw->size, sw_fw->data, sw_fw->size);
+
+ /* Running sum of the merged stream as little-endian 32-bit words. */
+ checksum = 0;
+ for (i = 0; i + 4 <= merged_size; i += 4)
+ checksum += get_unaligned_le32(merged + i);
+
+ dev_dbg(dev, "rfreg dual-option update: %s + %s\n", hw_name, sw_name);
+
+ /* START_UPDATE */
+ sub_oid = NCI_PROP_DUAL_SUB_START_UPDATE;
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 1, &sub_oid);
+ if (ret < 0) {
+ dev_err(dev, "Unable to start rfreg update\n");
+ goto out;
+ }
+
+ /* SET_OPTION per section */
+ set_option.sub_oid = NCI_PROP_DUAL_SUB_SET_OPTION;
+ set_option.index = 0;
+ for (i = 0; i < merged_size; i += NCI_PROP_DUAL_SECTION_SIZE) {
+ len = min_t(size_t, merged_size - i, NCI_PROP_DUAL_SECTION_SIZE);
+ memcpy(set_option.data, merged + i, len);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION,
+ len + 2, (__u8 *)&set_option);
+ if (ret < 0) {
+ dev_err(dev, "rfreg update error (code=%d)\n", ret);
+ goto out;
+ }
+ set_option.index++;
+ }
+
+ /* STOP_UPDATE with checksum */
+ stop_cmd[0] = NCI_PROP_DUAL_SUB_STOP_UPDATE;
+ put_unaligned_le16(checksum, &stop_cmd[1]);
+ ret = nci_prop_cmd(info->ndev, NCI_PROP_DUAL_OPTION, 3, stop_cmd);
+ if (ret < 0) {
+ dev_err(dev, "Unable to stop rfreg update\n");
+ goto out;
+ }
+
+ dev_dbg(dev, "rfreg dual-option update: success\n");
+out:
+ kfree(merged);
+ release_firmware(sw_fw);
+out_hw:
+ release_firmware(hw_fw);
+ return ret;
+}
diff --git a/drivers/nfc/s3fwrn5/nci.h b/drivers/nfc/s3fwrn5/nci.h
index bc4bce2bbc4d..23179ba095a1 100644
--- a/drivers/nfc/s3fwrn5/nci.h
+++ b/drivers/nfc/s3fwrn5/nci.h
@@ -40,6 +40,13 @@ struct nci_prop_stop_rfreg_rsp {
#define NCI_PROP_FW_CFG 0x28
+/*
+ * Single-byte FW_CFG payload (clock-speed selector) for the S3NRN4V reference
+ * clock. Taken from the vendor configuration for this part (the encoding is
+ * not documented).
+ */
+#define NCI_PROP_FW_CFG_CLK_SPEED 0x11
+
struct nci_prop_fw_cfg_cmd {
__u8 clk_type;
__u8 clk_speed;
@@ -50,7 +57,30 @@ struct nci_prop_fw_cfg_rsp {
__u8 status;
};
-extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[4];
+/*
+ * The S3NRN4V updates its RF registers through a single "dual option" command
+ * (a sub-OID selects the operation) instead of the START/SET/STOP_RFREG
+ * opcodes above, and expects the HW and SW register blobs merged into one
+ * stream.
+ */
+#define NCI_PROP_DUAL_OPTION 0x2a
+
+#define NCI_PROP_DUAL_SUB_START_UPDATE 0x01
+#define NCI_PROP_DUAL_SUB_SET_OPTION 0x02
+#define NCI_PROP_DUAL_SUB_STOP_UPDATE 0x03
+
+#define NCI_PROP_DUAL_SECTION_SIZE 252
+
+struct nci_prop_dual_set_option_cmd {
+ __u8 sub_oid; /* NCI_PROP_DUAL_SUB_SET_OPTION */
+ __u8 index;
+ __u8 data[NCI_PROP_DUAL_SECTION_SIZE];
+};
+
+extern const struct nci_driver_ops s3fwrn5_nci_prop_ops[5];
int s3fwrn5_nci_rf_configure(struct s3fwrn5_info *info, const char *fw_name);
+int s3fwrn5_nci_rf_configure_dual(struct s3fwrn5_info *info,
+ const char *hw_name, const char *sw_name);
+int s3fwrn5_nci_clk_cfg(struct s3fwrn5_info *info);
#endif /* __LOCAL_S3FWRN5_NCI_H_ */
diff --git a/drivers/nfc/s3fwrn5/s3fwrn5.h b/drivers/nfc/s3fwrn5/s3fwrn5.h
index 2b492236090b..2d8c12091fba 100644
--- a/drivers/nfc/s3fwrn5/s3fwrn5.h
+++ b/drivers/nfc/s3fwrn5/s3fwrn5.h
@@ -21,6 +21,17 @@ enum s3fwrn5_mode {
S3FWRN5_MODE_FW,
};
+enum s3fwrn5_variant {
+ /* S3FWRN5 / S3FWRN82: firmware is downloaded by this driver */
+ S3FWRN5_VARIANT_FWDL,
+ /*
+ * S3NRN4V: ships with working firmware behind a bootloader protocol
+ * this driver does not implement; skip the download, configure the
+ * clock (FW_CFG) and update the RF registers via the DUAL_OPTION cmd.
+ */
+ S3FWRN5_VARIANT_S3NRN4V,
+};
+
struct s3fwrn5_phy_ops {
void (*set_wake)(void *id, bool sleep);
void (*set_mode)(void *id, enum s3fwrn5_mode);
@@ -36,6 +47,7 @@ struct s3fwrn5_info {
const struct s3fwrn5_phy_ops *phy_ops;
struct s3fwrn5_fw_info fw_info;
+ enum s3fwrn5_variant variant;
struct mutex mutex;
};
@@ -78,7 +90,7 @@ static inline int s3fwrn5_write(struct s3fwrn5_info *info, struct sk_buff *skb)
}
int s3fwrn5_probe(struct nci_dev **ndev, void *phy_id, struct device *pdev,
- const struct s3fwrn5_phy_ops *phy_ops);
+ const struct s3fwrn5_phy_ops *phy_ops, enum s3fwrn5_variant variant);
void s3fwrn5_remove(struct nci_dev *ndev);
int s3fwrn5_recv_frame(struct nci_dev *ndev, struct sk_buff *skb,
diff --git a/drivers/nfc/s3fwrn5/uart.c b/drivers/nfc/s3fwrn5/uart.c
index 540a4ddb0b05..47172d739a41 100644
--- a/drivers/nfc/s3fwrn5/uart.c
+++ b/drivers/nfc/s3fwrn5/uart.c
@@ -137,7 +137,7 @@ static int s3fwrn82_uart_probe(struct serdev_device *serdev)
}
ret = s3fwrn5_probe(&phy->common.ndev, phy, &phy->ser_dev->dev,
- &uart_phy_ops);
+ &uart_phy_ops, S3FWRN5_VARIANT_FWDL);
if (ret < 0)
goto err_serdev;
--
2.55.0
^ permalink raw reply related
* Re: [PATCH net] sctp: validate STALE_COOKIE cause length before reading staleness
From: Xin Long @ 2026-07-05 19:12 UTC (permalink / raw)
To: Weiming Shi
Cc: linux-sctp, Marcelo Ricardo Leitner, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, Xiang Mei,
stable
In-Reply-To: <20260704033545.2438373-2-bestswngs@gmail.com>
On Fri, Jul 3, 2026 at 11:35 PM Weiming Shi <bestswngs@gmail.com> wrote:
>
> When an ERROR chunk with a STALE_COOKIE cause is received in the
> COOKIE_ECHOED state, sctp_sf_do_5_2_6_stale() reads the 4-byte Measure
> of Staleness that follows the cause header:
>
> err = (struct sctp_errhdr *)(chunk->skb->data);
> stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
>
> err is the first cause in the chunk, not the STALE_COOKIE cause that
> caused the dispatch, and nothing guarantees the staleness field is
> present. sctp_walk_errors() only requires a cause to be as long as the
> 4-byte header, so for a STALE_COOKIE cause of length 4 the read runs
> past the cause, and for a minimal ERROR chunk past skb->tail. The value
> is echoed to the peer in the Cookie Preservative of the reply INIT,
> leaking uninitialized memory.
>
> sctp_sf_cookie_echoed_err() already walks to the STALE_COOKIE cause, so
> check its length there and pass it to sctp_sf_do_5_2_6_stale(), which
> reads that cause instead of the first one. A STALE_COOKIE cause too
> short to hold the staleness field is discarded.
>
> The read is reachable by any peer that can drive an association into
> COOKIE_ECHOED, including an unprivileged process using a raw SCTP socket
> in a user and network namespace.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Reported-by: Xiang Mei <xmei5@asu.edu>
> Assisted-by: Claude:claude-opus-4-8
> Cc: stable@vger.kernel.org
> Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Acked-by: Xin Long <lucien.xin@gmail.com>
^ permalink raw reply
* Re: [PATCH net] sctp: validate the body of a STALE_COOKIE error before reading it
From: Xin Long @ 2026-07-05 19:13 UTC (permalink / raw)
To: Xiang Mei
Cc: Marcelo Ricardo Leitner, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-sctp, netdev,
linux-kernel, bestswngs
In-Reply-To: <20260705003013.1134430-1-xmei5@asu.edu>
On Sat, Jul 4, 2026 at 8:31 PM Xiang Mei <xmei5@asu.edu> wrote:
>
> sctp_sf_do_5_2_6_stale() reads the 32-bit Measure of Staleness that
> follows the error header:
>
> stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
>
> without checking that the STALE_COOKIE cause actually carries that
> 4-byte body. sctp_walk_errors() in the caller only requires
> err->length >= sizeof(struct sctp_errhdr), so a peer can send an 8-byte
> ERROR chunk whose sole STALE_COOKIE cause has length == 4 and no body.
> It passes sctp_chunk_length_valid() (>= 8) and the error walk, yet the
> staleness read reaches past the validated cause.
>
> When that is the only chunk in the packet the cause ends exactly at
> skb_tail (sctp_inq_pop() discards only when chunk_end > skb_tail), so
> the read stays in-bounds of the skb head slab object but past the packet
> data. The value is folded into the COOKIE_PRESERVATIVE parameter of the
> retransmitted INIT and reflected to the peer, leaking adjacent kernel
> slab bytes.
>
> Discard the chunk when the staleness field falls outside the validated
> chunk data.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> net/sctp/sm_statefuns.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
> index d23d935e128e..e4b4b63162cf 100644
> --- a/net/sctp/sm_statefuns.c
> +++ b/net/sctp/sm_statefuns.c
> @@ -2592,6 +2592,9 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale(
>
> err = (struct sctp_errhdr *)(chunk->skb->data);
>
> + if ((u8 *)err + sizeof(*err) + sizeof(__be32) > chunk->chunk_end)
> + return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
> +
> /* When calculating the time extension, an implementation
> * SHOULD use the RTT information measured based on the
> * previous COOKIE ECHO / ERROR exchange, and should add no
> --
> 2.43.0
>
I think this is a dup of
https://lore.kernel.org/netdev/20260704033545.2438373-2-bestswngs@gmail.com/.
Thanks.
^ permalink raw reply
* [PATCH ethtool-next 1/2] sfpid: print all implemented options
From: Aleksander Jan Bajkowski @ 2026-07-05 20:06 UTC (permalink / raw)
To: mkubecek, andrew, danieller, kuba, davem, edumazet, pabeni,
netdev
Cc: Aleksander Jan Bajkowski
SFP modules implement multiple options. Before the “json” option was
instroduced, all options were listed. Currently, only the last option
is listed. This commit fixes this bug. Options are represented as array.
Before:
$ ethtool -m sfp-wan
...
Option values : 0x00 0x32
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-wan
[ {
...
"option_values": [ 0,50 ],
"option": "RATE_SELECT implemented",
...
} ]
After:
$ ethtool -m sfp-lan
...
Option values : 0x00 0x32
Option : RX_LOS implemented
Option : TX_DISABLE implemented
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-lan
[ {
..
"option_values": [ 0,50 ],
"option": [ "RX_LOS implemented","TX_DISABLE implemented","RATE_SELECT implemented" ],
} ]
..
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
module-common.c | 8 ++++++++
module-common.h | 1 +
sfpid.c | 39 +++++++++++++++++++++++----------------
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/module-common.c b/module-common.c
index 42fccf6..d736c7c 100644
--- a/module-common.c
+++ b/module-common.c
@@ -258,6 +258,14 @@ void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
printf("\t%-41s : %s\n", fn, str_value);
}
+void module_print_array_string(const char *fn, const char *value)
+{
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", value);
+ else
+ printf("\t%-41s : %s\n", fn, value);
+}
+
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit)
diff --git a/module-common.h b/module-common.h
index 4063448..6f9da5f 100644
--- a/module-common.h
+++ b/module-common.h
@@ -281,6 +281,7 @@ void module_print_any_string(const char *fn, const char *value);
void module_print_any_float(const char *fn, float value, const char *unit);
void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
const char *str_value);
+void module_print_array_string(const char *fn, const char *value);
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit);
diff --git a/sfpid.c b/sfpid.c
index 74a6f51..d6636ba 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -396,7 +396,6 @@ static void sff8079_show_wavelength_or_copper_compliance(const __u8 *id)
static void sff8079_show_options(const __u8 *id)
{
static const char *pfx = "Option";
- char value[64] = "";
if (is_json_context()) {
open_json_array("option_values", "");
@@ -407,35 +406,43 @@ static void sff8079_show_options(const __u8 *id)
printf("\t%-41s : 0x%02x 0x%02x\n", "Option values", id[64],
id[65]);
}
+
+ if (is_json_context())
+ open_json_array("option", "");
+
if (id[65] & (1 << 1))
- sprintf(value, "%s", "RX_LOS implemented");
+ module_print_array_string(pfx, "RX_LOS implemented");
if (id[65] & (1 << 2))
- sprintf(value, "%s", "RX_LOS implemented, inverted");
+ module_print_array_string(pfx, "RX_LOS implemented, inverted");
if (id[65] & (1 << 3))
- sprintf(value, "%s", "TX_FAULT implemented");
+ module_print_array_string(pfx, "TX_FAULT implemented");
if (id[65] & (1 << 4))
- sprintf(value, "%s", "TX_DISABLE implemented");
+ module_print_array_string(pfx, "TX_DISABLE implemented");
if (id[65] & (1 << 5))
- sprintf(value, "%s", "RATE_SELECT implemented");
+ module_print_array_string(pfx, "RATE_SELECT implemented");
if (id[65] & (1 << 6))
- sprintf(value, "%s", "Tunable transmitter technology");
+ module_print_array_string(pfx,
+ "Tunable transmitter technology");
if (id[65] & (1 << 7))
- sprintf(value, "%s", "Receiver decision threshold implemented");
+ module_print_array_string(pfx,
+ "Receiver decision threshold implemented");
if (id[64] & (1 << 0))
- sprintf(value, "%s", "Linear receiver output implemented");
+ module_print_array_string(pfx,
+ "Linear receiver output implemented");
if (id[64] & (1 << 1))
- sprintf(value, "%s", "Power level 2 requirement");
+ module_print_array_string(pfx, "Power level 2 requirement");
if (id[64] & (1 << 2))
- sprintf(value, "%s", "Cooled transceiver implemented");
+ module_print_array_string(pfx,
+ "Cooled transceiver implemented");
if (id[64] & (1 << 3))
- sprintf(value, "%s", "Retimer or CDR implemented");
+ module_print_array_string(pfx, "Retimer or CDR implemented");
if (id[64] & (1 << 4))
- sprintf(value, "%s", "Paging implemented");
+ module_print_array_string(pfx, "Paging implemented");
if (id[64] & (1 << 5))
- sprintf(value, "%s", "Power level 3 requirement");
+ module_print_array_string(pfx, "Power level 3 requirement");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_all_common(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* [PATCH ethtool-next 2/2] sfpid: print all compliance codes
From: Aleksander Jan Bajkowski @ 2026-07-05 20:06 UTC (permalink / raw)
To: mkubecek, andrew, danieller, kuba, davem, edumazet, pabeni,
netdev
Cc: Aleksander Jan Bajkowski
In-Reply-To: <20260705200637.2092534-1-olek2@wp.pl>
SFP modules implement multiple compliance codes. This is common for
dual-rate modules. Before the `json` option was introduced, all
compliance codes were displayed. Currently, only the last code is
displayed. This commit fixes that bug. Compliance codes are
represented as array.
Before:
$ ethtool -m sfp-lan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : FC: 100 MBytes/sec</pre>
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": "FC: 100 MBytes/sec",
...
} ]
After:
$ ethtool -m sfp-wan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : Ethernet: 1000BASE-SX
Transceiver type : FC: intermediate distance (I)
Transceiver type : FC: Shortwave laser w/o OFC (SN)
Transceiver type : FC: Multimode, 62.5um (M6)
Transceiver type : FC: Multimode, 50um (M5)
Transceiver type : FC: 400 MBytes/sec
Transceiver type : FC: 200 MBytes/sec
Transceiver type : FC: 100 MBytes/sec
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": [ "Ethernet: 1000BASE-SX","FC: intermediate distance (I)","FC: Shortwave laser w/o OFC (SN)","FC: Multimode, 62.5um (M6)","FC: Multimode, 50um (M5)","FC: 400 MBytes/sec","FC: 200 MBytes/sec","FC: 100 MBytes/sec" ],
...
} ]
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
sfpid.c | 231 +++++++++++++++++++++++++++++++-------------------------
1 file changed, 127 insertions(+), 104 deletions(-)
diff --git a/sfpid.c b/sfpid.c
index d6636ba..72c44cf 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -50,7 +50,6 @@ static void sff8079_show_connector(const __u8 *id)
static void sff8079_show_transceiver(const __u8 *id)
{
static const char *pfx = "Transceiver type";
- char value[140] = "";
if (is_json_context()) {
open_json_array("transceiver_codes", "");
@@ -70,242 +69,266 @@ static void sff8079_show_transceiver(const __u8 *id)
"Transceiver codes", id[3], id[4], id[5], id[6],
id[7], id[8], id[9], id[10], id[36]);
}
+
+ if (is_json_context())
+ open_json_array("transceiver_type", "");
+
/* 10G Ethernet Compliance Codes */
if (id[3] & (1 << 7))
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"10G Ethernet: 10G Base-ER [SFF-8472 rev10.4 onwards]");
if (id[3] & (1 << 6))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LRM");
+ module_print_array_string(pfx, "10G Ethernet: 10G Base-LRM");
if (id[3] & (1 << 5))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LR");
+ module_print_array_string(pfx, "10G Ethernet: 10G Base-LR");
if (id[3] & (1 << 4))
- sprintf(value, "%s", "10G Ethernet: 10G Base-SR");
+ module_print_array_string(pfx, "10G Ethernet: 10G Base-SR");
/* Infiniband Compliance Codes */
if (id[3] & (1 << 3))
- sprintf(value, "%s", "Infiniband: 1X SX");
+ module_print_array_string(pfx, "Infiniband: 1X SX");
if (id[3] & (1 << 2))
- sprintf(value, "%s", "Infiniband: 1X LX");
+ module_print_array_string(pfx, "Infiniband: 1X LX");
if (id[3] & (1 << 1))
- sprintf(value, "%s", "Infiniband: 1X Copper Active");
+ module_print_array_string(pfx, "Infiniband: 1X Copper Active");
if (id[3] & (1 << 0))
- sprintf(value, "%s", "Infiniband: 1X Copper Passive");
+ module_print_array_string(pfx,
+ "Infiniband: 1X Copper Passive");
/* ESCON Compliance Codes */
if (id[4] & (1 << 7))
- sprintf(value, "%s", "ESCON: ESCON MMF, 1310nm LED");
+ module_print_array_string(pfx, "ESCON: ESCON MMF, 1310nm LED");
if (id[4] & (1 << 6))
- sprintf(value, "%s", "ESCON: ESCON SMF, 1310nm Laser");
+ module_print_array_string(pfx,
+ "ESCON: ESCON SMF, 1310nm Laser");
/* SONET Compliance Codes */
if (id[4] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-192, short reach");
+ module_print_array_string(pfx, "SONET: OC-192, short reach");
if (id[4] & (1 << 4))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 1");
+ module_print_array_string(pfx,
+ "SONET: SONET reach specifier bit 1");
if (id[4] & (1 << 3))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 2");
+ module_print_array_string(pfx,
+ "SONET: SONET reach specifier bit 2");
if (id[4] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-48, long reach");
+ module_print_array_string(pfx, "SONET: OC-48, long reach");
if (id[4] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-48, intermediate reach");
+ module_print_array_string(pfx,
+ "SONET: OC-48, intermediate reach");
if (id[4] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-48, short reach");
+ module_print_array_string(pfx, "SONET: OC-48, short reach");
if (id[5] & (1 << 6))
- sprintf(value, "%s", "SONET: OC-12, single mode, long reach");
+ module_print_array_string(pfx,
+ "SONET: OC-12, single mode, long reach");
if (id[5] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-12, single mode, inter. reach");
+ module_print_array_string(pfx,
+ "SONET: OC-12, single mode, inter. reach");
if (id[5] & (1 << 4))
- sprintf(value, "%s", "SONET: OC-12, short reach");
+ module_print_array_string(pfx, "SONET: OC-12, short reach");
if (id[5] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-3, single mode, long reach");
+ module_print_array_string(pfx,
+ "SONET: OC-3, single mode, long reach");
if (id[5] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-3, single mode, inter. reach");
+ module_print_array_string(pfx,
+ "SONET: OC-3, single mode, inter. reach");
if (id[5] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-3, short reach");
+ module_print_array_string(pfx, "SONET: OC-3, short reach");
/* Ethernet Compliance Codes */
if (id[6] & (1 << 7))
- sprintf(value, "%s", "Ethernet: BASE-PX");
+ module_print_array_string(pfx, "Ethernet: BASE-PX");
if (id[6] & (1 << 6))
- sprintf(value, "%s", "Ethernet: BASE-BX10");
+ module_print_array_string(pfx, "Ethernet: BASE-BX10");
if (id[6] & (1 << 5))
- sprintf(value, "%s", "Ethernet: 100BASE-FX");
+ module_print_array_string(pfx, "Ethernet: 100BASE-FX");
if (id[6] & (1 << 4))
- sprintf(value, "%s", "Ethernet: 100BASE-LX/LX10");
+ module_print_array_string(pfx, "Ethernet: 100BASE-LX/LX10");
if (id[6] & (1 << 3))
- sprintf(value, "%s", "Ethernet: 1000BASE-T");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-T");
if (id[6] & (1 << 2))
- sprintf(value, "%s", "Ethernet: 1000BASE-CX");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-CX");
if (id[6] & (1 << 1))
- sprintf(value, "%s", "Ethernet: 1000BASE-LX");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-LX");
if (id[6] & (1 << 0))
- sprintf(value, "%s", "Ethernet: 1000BASE-SX");
+ module_print_array_string(pfx, "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (id[7] & (1 << 7))
- sprintf(value, "%s", "FC: very long distance (V)");
+ module_print_array_string(pfx, "FC: very long distance (V)");
if (id[7] & (1 << 6))
- sprintf(value, "%s", "FC: short distance (S)");
+ module_print_array_string(pfx, "FC: short distance (S)");
if (id[7] & (1 << 5))
- sprintf(value, "%s", "FC: intermediate distance (I)");
+ module_print_array_string(pfx,
+ "FC: intermediate distance (I)");
if (id[7] & (1 << 4))
- sprintf(value, "%s", "FC: long distance (L)");
+ module_print_array_string(pfx, "FC: long distance (L)");
if (id[7] & (1 << 3))
- sprintf(value, "%s", "FC: medium distance (M)");
+ module_print_array_string(pfx, "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (id[7] & (1 << 2))
- sprintf(value, "%s", "FC: Shortwave laser, linear Rx (SA)");
+ module_print_array_string(pfx,
+ "FC: Shortwave laser, linear Rx (SA)");
if (id[7] & (1 << 1))
- sprintf(value, "%s", "FC: Longwave laser (LC)");
+ module_print_array_string(pfx, "FC: Longwave laser (LC)");
if (id[7] & (1 << 0))
- sprintf(value, "%s", "FC: Electrical inter-enclosure (EL)");
+ module_print_array_string(pfx,
+ "FC: Electrical inter-enclosure (EL)");
if (id[8] & (1 << 7))
- sprintf(value, "%s", "FC: Electrical intra-enclosure (EL)");
+ module_print_array_string(pfx,
+ "FC: Electrical intra-enclosure (EL)");
if (id[8] & (1 << 6))
- sprintf(value, "%s", "FC: Shortwave laser w/o OFC (SN)");
+ module_print_array_string(pfx,
+ "FC: Shortwave laser w/o OFC (SN)");
if (id[8] & (1 << 5))
- sprintf(value, "%s", "FC: Shortwave laser with OFC (SL)");
+ module_print_array_string(pfx,
+ "FC: Shortwave laser with OFC (SL)");
if (id[8] & (1 << 4))
- sprintf(value, "%s", "FC: Longwave laser (LL)");
+ module_print_array_string(pfx, "FC: Longwave laser (LL)");
if (id[8] & (1 << 3))
- sprintf(value, "%s", "Active Cable");
+ module_print_array_string(pfx, "Active Cable");
if (id[8] & (1 << 2))
- sprintf(value, "%s", "Passive Cable");
+ module_print_array_string(pfx, "Passive Cable");
if (id[8] & (1 << 1))
- sprintf(value, "%s", "FC: Copper FC-BaseT");
+ module_print_array_string(pfx, "FC: Copper FC-BaseT");
/* Fibre Channel transmission media */
if (id[9] & (1 << 7))
- sprintf(value, "%s", "FC: Twin Axial Pair (TW)");
+ module_print_array_string(pfx, "FC: Twin Axial Pair (TW)");
if (id[9] & (1 << 6))
- sprintf(value, "%s", "FC: Twisted Pair (TP)");
+ module_print_array_string(pfx, "FC: Twisted Pair (TP)");
if (id[9] & (1 << 5))
- sprintf(value, "%s", "FC: Miniature Coax (MI)");
+ module_print_array_string(pfx, "FC: Miniature Coax (MI)");
if (id[9] & (1 << 4))
- sprintf(value, "%s", "FC: Video Coax (TV)");
+ module_print_array_string(pfx, "FC: Video Coax (TV)");
if (id[9] & (1 << 3))
- sprintf(value, "%s", "FC: Multimode, 62.5um (M6)");
+ module_print_array_string(pfx, "FC: Multimode, 62.5um (M6)");
if (id[9] & (1 << 2))
- sprintf(value, "%s", "FC: Multimode, 50um (M5)");
+ module_print_array_string(pfx, "FC: Multimode, 50um (M5)");
if (id[9] & (1 << 0))
- sprintf(value, "%s", "FC: Single Mode (SM)");
+ module_print_array_string(pfx, "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (id[10] & (1 << 7))
- sprintf(value, "%s", "FC: 1200 MBytes/sec");
+ module_print_array_string(pfx, "FC: 1200 MBytes/sec");
if (id[10] & (1 << 6))
- sprintf(value, "%s", "FC: 800 MBytes/sec");
+ module_print_array_string(pfx, "FC: 800 MBytes/sec");
if (id[10] & (1 << 5))
- sprintf(value, "%s", "FC: 1600 MBytes/sec");
+ module_print_array_string(pfx, "FC: 1600 MBytes/sec");
if (id[10] & (1 << 4))
- sprintf(value, "%s", "FC: 400 MBytes/sec");
+ module_print_array_string(pfx, "FC: 400 MBytes/sec");
if (id[10] & (1 << 3))
- sprintf(value, "%s", "FC: 3200 MBytes/sec");
+ module_print_array_string(pfx, "FC: 3200 MBytes/sec");
if (id[10] & (1 << 2))
- sprintf(value, "%s", "FC: 200 MBytes/sec");
+ module_print_array_string(pfx, "FC: 200 MBytes/sec");
if (id[10] & (1 << 0))
- sprintf(value, "%s", "FC: 100 MBytes/sec");
+ module_print_array_string(pfx, "FC: 100 MBytes/sec");
/* Extended Specification Compliance Codes from SFF-8024 */
if (id[36] == 0x1)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
if (id[36] == 0x2)
- sprintf(value, "%s", "Extended: 100G Base-SR4 or 25GBase-SR");
+ module_print_array_string(pfx,
+ "Extended: 100G Base-SR4 or 25GBase-SR");
if (id[36] == 0x3)
- sprintf(value, "%s", "Extended: 100G Base-LR4 or 25GBase-LR");
+ module_print_array_string(pfx,
+ "Extended: 100G Base-LR4 or 25GBase-LR");
if (id[36] == 0x4)
- sprintf(value, "%s", "Extended: 100G Base-ER4 or 25GBase-ER");
+ module_print_array_string(pfx,
+ "Extended: 100G Base-ER4 or 25GBase-ER");
if (id[36] == 0x8)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
if (id[36] == 0xb)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G Base-CR4 or 25G Base-CR CA-L");
if (id[36] == 0xc)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-S");
+ module_print_array_string(pfx, "Extended: 25G Base-CR CA-S");
if (id[36] == 0xd)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-N");
+ module_print_array_string(pfx, "Extended: 25G Base-CR CA-N");
if (id[36] == 0x16)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 10Gbase-T with SFI electrical interface");
if (id[36] == 0x18)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
if (id[36] == 0x19)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
if (id[36] == 0x1a)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
if (id[36] == 0x1b)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G 1550nm WDM (4 wavelengths)");
if (id[36] == 0x1c)
- sprintf(value, "%s", "Extended: 10Gbase-T Short Reach");
+ module_print_array_string(pfx,
+ "Extended: 10Gbase-T Short Reach");
if (id[36] == 0x1d)
- sprintf(value, "%s", "Extended: 5GBASE-T");
+ module_print_array_string(pfx, "Extended: 5GBASE-T");
if (id[36] == 0x1e)
- sprintf(value, "%s", "Extended: 2.5GBASE-T");
+ module_print_array_string(pfx, "Extended: 2.5GBASE-T");
if (id[36] == 0x1f)
- sprintf(value, "%s", "Extended: 40G SWDM4");
+ module_print_array_string(pfx, "Extended: 40G SWDM4");
if (id[36] == 0x20)
- sprintf(value, "%s", "Extended: 100G SWDM4");
+ module_print_array_string(pfx, "Extended: 100G SWDM4");
if (id[36] == 0x21)
- sprintf(value, "%s", "Extended: 100G PAM4 BiDi");
+ module_print_array_string(pfx, "Extended: 100G PAM4 BiDi");
if (id[36] == 0x22)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
if (id[36] == 0x23)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
if (id[36] == 0x24)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
if (id[36] == 0x25)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100GBASE-DR (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x26)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x27)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x30)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x31)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x32)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x33)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x40)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
if (id[36] == 0x41)
- sprintf(value, "%s",
+ module_print_array_string(pfx,
"Extended: 50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
if (id[36] == 0x42)
- sprintf(value, "%s", "Extended: 50GBASE-FR or 200GBASE-DR4");
+ module_print_array_string(pfx,
+ "Extended: 50GBASE-FR or 200GBASE-DR4");
if (id[36] == 0x43)
- sprintf(value, "%s", "Extended: 200GBASE-FR4");
+ module_print_array_string(pfx, "Extended: 200GBASE-FR4");
if (id[36] == 0x44)
- sprintf(value, "%s", "Extended: 200G 1550 nm PSM4");
+ module_print_array_string(pfx, "Extended: 200G 1550 nm PSM4");
if (id[36] == 0x45)
- sprintf(value, "%s", "Extended: 50GBASE-LR");
+ module_print_array_string(pfx, "Extended: 50GBASE-LR");
if (id[36] == 0x46)
- sprintf(value, "%s", "Extended: 200GBASE-LR4");
+ module_print_array_string(pfx, "Extended: 200GBASE-LR4");
if (id[36] == 0x50)
- sprintf(value, "%s", "Extended: 64GFC EA");
+ module_print_array_string(pfx, "Extended: 64GFC EA");
if (id[36] == 0x51)
- sprintf(value, "%s", "Extended: 64GFC SW");
+ module_print_array_string(pfx, "Extended: 64GFC SW");
if (id[36] == 0x52)
- sprintf(value, "%s", "Extended: 64GFC LW");
+ module_print_array_string(pfx, "Extended: 64GFC LW");
if (id[36] == 0x53)
- sprintf(value, "%s", "Extended: 128GFC EA");
+ module_print_array_string(pfx, "Extended: 128GFC EA");
if (id[36] == 0x54)
- sprintf(value, "%s", "Extended: 128GFC SW");
+ module_print_array_string(pfx, "Extended: 128GFC SW");
if (id[36] == 0x55)
- sprintf(value, "%s", "Extended: 128GFC LW");
+ module_print_array_string(pfx, "Extended: 128GFC LW");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_encoding(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* [PATCH net] selftests: netfilter: conntrack_resize.sh: fix skip exit code
From: Dharmik Parmar @ 2026-07-05 20:42 UTC (permalink / raw)
To: netdev; +Cc: Dharmik Parmar
In-Reply-To: <20260705204252.630729-1-dharmikparmar2004.ref@yahoo.com>
When conntrack sysctls are unavailable, the test prints SKIP but exits
with $KSFT_SKIP. lib.sh defines ksft_skip instead, so the kselftest
runner did not see a proper skip.
Use ksft_skip.
Fixes: d33f889fd80c ("selftests: netfilter: add conntrack stress test")
Signed-off-by: Dharmik Parmar <dharmikparmar2004@yahoo.com>
---
tools/testing/selftests/net/netfilter/conntrack_resize.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/netfilter/conntrack_resize.sh b/tools/testing/selftests/net/netfilter/conntrack_resize.sh
index 615fe3c6f405..51564092cec4 100755
--- a/tools/testing/selftests/net/netfilter/conntrack_resize.sh
+++ b/tools/testing/selftests/net/netfilter/conntrack_resize.sh
@@ -22,7 +22,7 @@ insert_count=2000
modprobe -q nf_conntrack
if ! sysctl -q net.netfilter.nf_conntrack_max >/dev/null;then
echo "SKIP: conntrack sysctls not available"
- exit $KSFT_SKIP
+ exit $ksft_skip
fi
init_net_max=$(sysctl -n net.netfilter.nf_conntrack_max) || exit 1
--
2.43.0
^ permalink raw reply related
* [PATCH nf] netfilter: ebtables: terminate table name before find_table_lock()
From: Xiang Mei @ 2026-07-05 21:58 UTC (permalink / raw)
To: Pablo Neira Ayuso, Florian Westphal, Phil Sutter,
Nikolay Aleksandrov, Ido Schimmel, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: netfilter-devel, coreteam, bridge, netdev, linux-kernel,
Xiang Mei, Weiming Shi
update_counters() and compat_update_counters() forward a user-supplied
32-byte table name to find_table_lock() without NUL-terminating it. On a
lookup miss, find_inlist_lock() calls try_then_request_module(..., "%s%s",
"ebtable_", name), and vsnprintf() reads past the name field and the
stack object until it hits a zero byte.
BUG: KASAN: stack-out-of-bounds in string (lib/vsprintf.c:648 lib/vsprintf.c:730)
Read of size 1 at addr ffff8880119dfb20 by task exploit/147
Call Trace:
...
string (lib/vsprintf.c:648 lib/vsprintf.c:730)
vsnprintf (lib/vsprintf.c:2945)
__request_module (kernel/module/kmod.c:150)
do_update_counters.isra.0 (net/bridge/netfilter/ebtables.c:371 net/bridge/netfilter/ebtables.c:380)
update_counters (net/bridge/netfilter/ebtables.c:1440)
do_ebt_set_ctl (net/bridge/netfilter/ebtables.c:2573)
nf_setsockopt (net/netfilter/nf_sockopt.c:101)
ip_setsockopt (net/ipv4/ip_sockglue.c:1424)
raw_setsockopt (net/ipv4/raw.c:847)
__sys_setsockopt (net/socket.c:2393)
...
compat_do_replace() shares the same unterminated name via
compat_copy_ebt_replace_from_user(); terminate it there too so all
find_table_lock() callers behave alike. The other callers already
terminate the name after the copy.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 81e675c227ec ("netfilter: ebtables: add CONFIG_COMPAT support")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/bridge/netfilter/ebtables.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c
index f20c039e44c8..5b74ff827493 100644
--- a/net/bridge/netfilter/ebtables.c
+++ b/net/bridge/netfilter/ebtables.c
@@ -1434,6 +1434,8 @@ static int update_counters(struct net *net, sockptr_t arg, unsigned int len)
if (copy_from_sockptr(&hlp, arg, sizeof(hlp)))
return -EFAULT;
+ hlp.name[sizeof(hlp.name) - 1] = '\0';
+
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
return -EINVAL;
@@ -2273,6 +2275,8 @@ static int compat_copy_ebt_replace_from_user(struct ebt_replace *repl,
memcpy(repl, &tmp, offsetof(struct ebt_replace, hook_entry));
+ repl->name[sizeof(repl->name) - 1] = '\0';
+
/* starting with hook_entry, 32 vs. 64 bit structures are different */
for (i = 0; i < NF_BR_NUMHOOKS; i++)
repl->hook_entry[i] = compat_ptr(tmp.hook_entry[i]);
@@ -2395,6 +2399,8 @@ static int compat_update_counters(struct net *net, sockptr_t arg,
if (copy_from_sockptr(&hlp, arg, sizeof(hlp)))
return -EFAULT;
+ hlp.name[sizeof(hlp.name) - 1] = '\0';
+
/* try real handler in case userland supplied needed padding */
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
return update_counters(net, arg, len);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net] sctp: validate the body of a STALE_COOKIE error before reading it
From: Xiang Mei @ 2026-07-05 22:29 UTC (permalink / raw)
To: Xin Long
Cc: Marcelo Ricardo Leitner, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-sctp, netdev,
linux-kernel, bestswngs
In-Reply-To: <CADvbK_cWUT2ygfBSW6qjgx9_y=bvksqG87Lp8edQcFT+3p9fOA@mail.gmail.com>
Thanks for the reminder. I didn't notice that Weiming has patched it.
Sorry about that.
Xiang
On Sun, Jul 5, 2026 at 12:13 PM Xin Long <lucien.xin@gmail.com> wrote:
>
> On Sat, Jul 4, 2026 at 8:31 PM Xiang Mei <xmei5@asu.edu> wrote:
> >
> > sctp_sf_do_5_2_6_stale() reads the 32-bit Measure of Staleness that
> > follows the error header:
> >
> > stale = ntohl(*(__be32 *)((u8 *)err + sizeof(*err)));
> >
> > without checking that the STALE_COOKIE cause actually carries that
> > 4-byte body. sctp_walk_errors() in the caller only requires
> > err->length >= sizeof(struct sctp_errhdr), so a peer can send an 8-byte
> > ERROR chunk whose sole STALE_COOKIE cause has length == 4 and no body.
> > It passes sctp_chunk_length_valid() (>= 8) and the error walk, yet the
> > staleness read reaches past the validated cause.
> >
> > When that is the only chunk in the packet the cause ends exactly at
> > skb_tail (sctp_inq_pop() discards only when chunk_end > skb_tail), so
> > the read stays in-bounds of the skb head slab object but past the packet
> > data. The value is folded into the COOKIE_PRESERVATIVE parameter of the
> > retransmitted INIT and reflected to the peer, leaking adjacent kernel
> > slab bytes.
> >
> > Discard the chunk when the staleness field falls outside the validated
> > chunk data.
> >
> > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> > Reported-by: Weiming Shi <bestswngs@gmail.com>
> > Assisted-by: Claude:claude-opus-4-8
> > Signed-off-by: Xiang Mei <xmei5@asu.edu>
> > ---
> > net/sctp/sm_statefuns.c | 3 +++
> > 1 file changed, 3 insertions(+)
> >
> > diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
> > index d23d935e128e..e4b4b63162cf 100644
> > --- a/net/sctp/sm_statefuns.c
> > +++ b/net/sctp/sm_statefuns.c
> > @@ -2592,6 +2592,9 @@ static enum sctp_disposition sctp_sf_do_5_2_6_stale(
> >
> > err = (struct sctp_errhdr *)(chunk->skb->data);
> >
> > + if ((u8 *)err + sizeof(*err) + sizeof(__be32) > chunk->chunk_end)
> > + return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
> > +
> > /* When calculating the time extension, an implementation
> > * SHOULD use the RTT information measured based on the
> > * previous COOKIE ECHO / ERROR exchange, and should add no
> > --
> > 2.43.0
> >
>
> I think this is a dup of
> https://lore.kernel.org/netdev/20260704033545.2438373-2-bestswngs@gmail.com/.
>
> Thanks.
^ permalink raw reply
* [PATCH net v3 0/3] Fix to possible skb leak due to race condtion in tx path
From: Selvamani Rajagopal via B4 Relay @ 2026-07-05 22:59 UTC (permalink / raw)
To: Parthiban Veerasooran, Andrew Lunn, Piergiorgio Beruto,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: netdev, linux-kernel, Andrew Lunn, Parthiban Veerasooran,
Selvamani Rajagopal
Now the traffic is handled in threaded IRQ, and the
disable_traffic flag is checked before handling the
data, new race condition is exposed, in which
buffer may leak, if threaded IRQ interrupts the
trasmit path midway.
With this change, disable_traffic and waiting_tx_skb
pointer are protected by spin lock/unlock pair.
This is highlighted in Sashiko review
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260611-level-trigger-v5-0-4533a9e85ce2%40onsemi.com
Also on buffer overrun condition, probably due to loss of
SPI data chunks, receive path doesn't see the expected
data chunk with end_valid bit set. As a result, driver
keeps adding data chunks to the skb before running out
of space and kernel panic is seen.
With this change, before adding data to the skb, if there
is no space, skb is freed and driver starts looking for
new frame by looking for a data chunk with start_valid
bit set.
[ 705.405490] skbuff: skb_over_panic: text:ffffffd2eb72a264 len:1600 put:64 head:ffffff804e5cdc40 data:ffffff804e5cdc80 tail:0x680 end:0x640 dev:eth1
[ 705.405569] ------------[ cut here ]------------
[ 705.405575] kernel BUG at net/core/skbuff.c:214!
[ 705.405589] Internal error: Oops - BUG: 00000000f2000800 [#1] SMP
[ 6703.427690] Call trace:
[ 705.925157] skb_panic+0x58/0x68 (P)
[ 705.928726] skb_put+0x74/0x80
[ 705.931772] oa_tc6_update_rx_skb+0x44/0x98 [oa_tc6_mod]
[ 705.937084] oa_tc6_macphy_threaded_irq+0x3f4/0x900 [oa_tc6_mod]
[ 705.943084] irq_thread_fn+0x34/0xb8
[ 705.946654] irq_thread+0x1a0/0x300
[ 705.950134] kthread+0x138/0x150
[ 705.953356] ret_from_fork+0x10/0x20
Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
Changes in v3:
- Cover all the instances of disable_traffic flag with
spin lock to serialize the access
- Disabling the tx queue and mark the carrier off when
disable_traffic is set.
- Continue processing received chunks on buffer overflow
error and "out of skb" error.
- Link to v2: https://lore.kernel.org/r/20260626-fix-race-condition-and-crash-v2-0-b6c5c10e604f@onsemi.com
Changes in v2:
- Improvment to how error -EAGAIN is handled. Took care of
couple of use cases where start_bit and end_bit may be missing or
repeated due to lost data chunks.
- Protected handling of waiting_tx_skb pointer with spin lock
- Link to v1: https://lore.kernel.org/r/20260621-fix-race-condition-and-crash-v1-0-87e290d9357f@onsemi.com
---
Selvamani Rajagopal (3):
net: ethernet: oa_tc6: Protect skb pointer used by two different kernel instances
net: ethernet: oa_tc6: Improvement in buffer overflow handling
net: ethernet: oa_tc6: Carrier off when disable_traffic is set
drivers/net/ethernet/oa_tc6.c | 219 ++++++++++++++++++++++++++++++------------
1 file changed, 158 insertions(+), 61 deletions(-)
---
base-commit: d7a8d500d7e42837bd8dce40cb52c97c6e8706a9
change-id: 20260621-fix-race-condition-and-crash-94d055a665c4
Best regards,
--
Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox