* [PATCH net-next v4 0/2] net: dsa: realtek: rtl8365mb: add SGMII/HSGMII support for RTL8367S
From: Johan Alvarado @ 2026-07-02 20:47 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux
Cc: luizluca, maxime.chevallier, namiltd, netdev, linux-kernel,
contact
The RTL8367S is a 5+2 port switch from the same family as the
RTL8365MB-VC already supported by this driver. Its chip info table
entry declares SGMII and HSGMII on external interface 1, but the
driver so far only implements RGMII, leaving boards that wire the
switch to the CPU over the SerDes without a working CPU port.
This series implements both modes. The configuration sequence and the
SerDes tuning parameters are derived from the GPL-licensed Realtek
rtl8367c vendor driver, as distributed in the Mercusys MR80X GPL code
drop, and cross-checked against the real register sequence captured at
runtime by chainloading a custom U-Boot ahead of the stock firmware
and logging the live SerDes accesses on hardware.
The vendor driver brings up the SerDes by loading firmware into the
switch's embedded DW8051 microcontroller. Analysis of that firmware
(by Luiz Angelo Daros de Luca) showed it only performs a SerDes
data-path reset right after the SerDes reset is deasserted, and then
runs a link-polling loop that writes the external interface force
registers -- duplicating, and racing with, the link management phylink
already performs. This series therefore keeps the DW8051 disabled and
performs the one necessary action (the data-path reset via the SerDes
BMCR register) directly in the driver, avoiding both the race and a
dependency on a redistributable firmware blob.
The SerDes is modelled as a phylink PCS: mac_select_pcs() hands the
SerDes interfaces to a phylink_pcs whose pcs_config()/pcs_link_up()
ops own the SerDes register sequence, keeping it out of the MAC
operations. In-band autonegotiation is not implemented; the link is
forced (fixed-link or conventional PHY), as for RGMII, and the PCS
reports this to phylink through pcs_inband_caps().
Patch 1 adds the SerDes indirect access helpers, the PCS and SGMII
(1 Gbps) support. Patch 2 extends the PCS to HSGMII (2.5 Gbps), which
phylink represents as 2500base-x.
Tested on a Mercusys MR80X v2.20 (RTL8367S wired to the SoC over the
SerDes), in both SGMII and HSGMII modes with a fixed-link device tree
description: link bring-up verified across cold boots, warm reboots,
module reloads and link down/up cycles, with sustained traffic and no
CRC/symbol errors. The HSGMII link is confirmed running at 2.5G at the
register level (SoC uniphy mode and gmac clocks); per-direction
throughput could not be pushed past ~1 Gbps on this board because the
SoC side is driven by the IPQ5018 SSDK and the user-facing PHY is 1G,
so full 2.5G line-rate throughput remains unverified on my hardware.
The RTL8367SB also declares SGMII and HSGMII in its chip info entry
and therefore gains both modes as well. The vendor driver drives the
two chips through the same code path, keyed only on the chip option
register (both report chip id 0x6367), so this is expected to work
there too, but I have no RTL8367SB hardware to confirm it.
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
v4:
- Drop the chip model name from the driver's NOTE comment; which
interfaces a given chip exposes is described by its chip_info entry,
not the file header. Pointed out by Luiz Angelo Daros de Luca.
- Build the SerDes BMCR data-path-reset values from the standard
BMCR_ANENABLE | BMCR_ISOLATE bits instead of a bare magic number, so
the meaning is in the code rather than only in a comment. Pointed
out by Luiz Angelo Daros de Luca.
- Use a temporary for the DIGITAL_INTERFACE_SELECT value instead of
wrapping the expression across the regmap_update_bits() arguments.
Pointed out by Luiz Angelo Daros de Luca.
- Reject the untested SerDes tuning variant. The vendor driver keeps
two sets of SerDes tuning parameters and selects between them based
on the chip option register (0x13C1); the tables in this series are
the variant for a non-zero option, which is what the RTL8367S parts
seen so far report. The option is probed once at setup and the
SerDes interface modes are only advertised to phylink when the
tuning parameters match, so an unsupported variant fails at phylink
validation time instead of when configuring the link. Thanks to
Luiz Angelo Daros de Luca for pointing out the conditional.
- Express the external interface line rate bypass bit through a
parametric macro keyed on the port number (with port 5 as the base),
instead of an open-coded BIT(interface id) that only matched by
coincidence; other RTL8367 families index this register differently.
Suggested by Luiz Angelo Daros de Luca.
- Drop the arbitrary usleep_range() after each SerDes indirect access.
SerDes writes are now fire-and-forget and reads poll the self-clearing
BUSY bit with regmap_read_poll_timeout(), matching the vendor driver,
which never sleeps. On the MR80X the BUSY bit is never even observed
set: the access completes within the register transaction. Pointed out
by Luiz Angelo Daros de Luca; poll approach suggested by Mieczyslaw
Nalewaj.
- Drop the always-zero SerDes index argument from the SerDes indirect
access helpers, along with the INDACS command index field whose
width was questioned during review; this chip has a single SerDes
block reachable through this window, so the index served no purpose.
Raised by Luiz Angelo Daros de Luca.
- Stop hardcoding external interface 1 with an early -EOPNOTSUPP in
the SerDes configuration path. The SerDes interface modes are now
advertised in phylink_get_caps() from the chip_info
supported_interfaces, and mac_select_pcs() returns the PCS only for
those modes. Pointed out by Luiz Angelo Daros de Luca.
- Keep the new register definitions as raw hex masks, matching the
prevailing style of the file. A file-wide GENMASK/BIT conversion,
raised by Luiz Angelo Daros de Luca during review, is left for a
separate cleanup patch so this series stays focused on the feature.
- Convert the SerDes path to a phylink_pcs, as suggested by Maxime
Chevallier. The SGMII/HSGMII SerDes handling now lives in
pcs_config()/pcs_get_state()/pcs_link_up() selected via
mac_select_pcs(), instead of being driven from the MAC
mac_config()/mac_link_up()/mac_link_down() operations. This
separates the MAC and SerDes layers and makes future in-band
autonegotiation an additive change. No functional change intended
for the forced-link path; retested on the MR80X v2.20. In-band
autonegotiation remains unimplemented and is left for a follow-up,
once hardware is available to validate it.
- Implement pcs_inband_caps(), returning LINK_INBAND_DISABLE so that
phylink knows this PCS cannot do in-band autonegotiation and never
selects an in-band-enabled negotiation mode for it. pcs_config()
rejects PHYLINK_PCS_NEG_INBAND_ENABLED with -EOPNOTSUPP instead of
the previous warn-and-force.
- Program the SerDes pause controls in SDS_MISC from the resolved
pause modes when forcing the MAC external interface, as the vendor
driver does, instead of leaving whatever state the boot firmware
left there. Done in mac_link_up() because pcs_link_up() carries no
pause information.
- Set the PCS poll flag: the SerDes has no link interrupt wired up,
so phylink must poll pcs_get_state() when it tracks the link
through the PCS (in-band mode with autonegotiation disabled).
- Report link down from pcs_get_state() if reading back the forced
speed/duplex fails, rather than reporting link up with a stale
state.
- Reword the misleading "disable in-band aneg" comment.
v3: https://lore.kernel.org/netdev/20260613232136.24246-1-contact@c127.dev/
- Drop the DW8051 firmware loading entirely. Analysis of the vendor
firmware showed it only duplicates the link management phylink
already does; the one needed action (SerDes data-path reset via
the BMCR register) is now performed directly in the driver, with
the DW8051 kept disabled. This removes the dependency on the
rtl8367s-sgmii.bin firmware blob, which could not be redistributed
via linux-firmware (the GPL vendor source ships it as a byte array
without the corresponding microcode source). Thanks to Luiz Angelo
Daros de Luca for the firmware analysis.
v2: https://lore.kernel.org/netdev/0100019eb0b1822e-ffc5626c-1b9f-4c8a-8a1a-759a9e665f4f-000000@email.amazonses.com/
- No code changes; resend because the SMTP provider used for v1
corrupted the mails and patch 1/2 never reached the list.
v1: https://lore.kernel.org/netdev/aebccaad-eca3-4ea4-99dd-ae7edbc8981b@smtp-relay.sendinblue.com/
Johan Alvarado (2):
net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
drivers/net/dsa/realtek/rtl8365mb_main.c | 563 ++++++++++++++++++++++-
1 file changed, 558 insertions(+), 5 deletions(-)
base-commit: d6e81529749190123aa0040626c7e5dbc20fdc9a
--
2.55.0
^ permalink raw reply
* [PATCH net-next v4 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Johan Alvarado @ 2026-07-02 20:47 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux
Cc: luizluca, maxime.chevallier, namiltd, netdev, linux-kernel,
contact
In-Reply-To: <20260702204648.276112-1-contact@c127.dev>
The RTL8367S can mux its embedded SerDes to external interface 1,
which is typically used to connect the switch to a CPU port. The chip
info table already declares SGMII as a supported interface mode for
this chip, but the driver only implements RGMII so far.
Implement SGMII support as a phylink PCS, with the configuration
sequence derived from the GPL-licensed Realtek rtl8367c vendor driver
as distributed in the Mercusys MR80X GPL code drop:
- Add accessors for the SerDes indirect access registers (SDS_INDACS),
through which the SerDes internal registers are reached.
- Register a phylink_pcs for the SerDes, selected from mac_select_pcs
for the SGMII interface, so the SerDes handling lives in the PCS
operations rather than in the MAC operations.
- Probe the SerDes tuning variant from the chip option register once
at setup. The vendor driver keeps two sets of SerDes tuning
parameters and selects between them based on this option; only the
variant for a non-zero option (which all RTL8367S parts seen so far
report) has been validated on hardware, so the SerDes interface
modes are only advertised in that case. An unsupported variant thus
fails at phylink validation time instead of at link configuration
time.
- Keep the embedded DW8051 microcontroller in reset and disabled. The
vendor driver loads firmware into it to manage the SerDes link, but
analysis of that firmware shows it only duplicates the link
management phylink already performs: it polls the port status and
writes the external interface force registers behind the driver's
back.
- Clear the line rate bypass bit for the external interface, tune the
SerDes with the vendor-prescribed parameters, mux the SerDes to MAC8
in SGMII mode and only then take the SerDes out of reset, as the
vendor driver does.
- After deasserting the SerDes reset, reset the SerDes data path via
the SerDes BMCR register to flush the FIFOs and resync the PLL.
This mirrors what the vendor firmware does right after deasserting
the SerDes reset, and ensures a clean link state from cold boot.
- Force the SGMII link parameters (link, speed, duplex) in the SDS_MISC
register from pcs_link_up(). SGMII in-band autonegotiation is not
implemented, so only fixed-link and conventional PHY setups are
supported, just like RGMII. This is reported to phylink through
pcs_inband_caps() returning LINK_INBAND_DISABLE, so phylink never
selects an in-band-enabled negotiation mode for this PCS.
- Program the SerDes pause controls in SDS_MISC from the resolved
pause modes when forcing the MAC external interface in mac_link_up,
as the vendor driver does, rather than leaving whatever state the
boot firmware left there. This is done in the MAC layer because
pcs_link_up() carries no pause information.
- Implement pcs_get_state() by reading the link status from the
SerDes, with the forced speed and duplex read back from SDS_MISC.
Although the supported fixed-link and conventional PHY setups do not
use it, the PCS owns the SerDes link state, and phylink consults
pcs_get_state() to track the physical link when operating in in-band
mode with autonegotiation disabled. The SerDes has no link interrupt
wired up, so the PCS sets its poll flag.
Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to
the SoC over SGMII.
Suggested-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Suggested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Suggested-by: Mieczyslaw Nalewaj <namiltd@yahoo.com>
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 518 ++++++++++++++++++++++-
1 file changed, 514 insertions(+), 4 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 5ac091bf93c9..2d202120cfd9 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -40,7 +40,8 @@
* driver has only been tested with a fixed-link, but in principle it should not
* matter.
*
- * NOTE: Currently, only the RGMII interface is implemented in this driver.
+ * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this
+ * driver.
*
* The interrupt line is asserted on link UP/DOWN events. The driver creates a
* custom irqchip to handle this interrupt and demultiplex the events by reading
@@ -94,11 +95,13 @@
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
+#include <linux/mii.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
+#include <linux/phylink.h>
#include "realtek.h"
#include "realtek-smi.h"
@@ -129,6 +132,7 @@
/* Chip reset register */
#define RTL8365MB_CHIP_RESET_REG 0x1322
+#define RTL8365MB_CHIP_RESET_DW8051_MASK 0x0010
#define RTL8365MB_CHIP_RESET_SW_MASK 0x0002
#define RTL8365MB_CHIP_RESET_HW_MASK 0x0001
@@ -238,6 +242,76 @@
#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK 0x0007
#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK 0x0008
+/* External interface line rate bypass register - one bit per external
+ * interface, indexed by the external port number with port 5 (the first
+ * external port) as the base. Other RTL8367 families index this register
+ * differently (e.g. the RTL8367R uses (id + 1) % 2), so this mapping only
+ * holds for the RTL8367C-style parts this driver supports.
+ */
+#define RTL8365MB_BYPASS_LINE_RATE_REG 0x03F7
+#define RTL8365MB_BYPASS_LINE_RATE_MASK(_port) BIT((_port) - 5)
+
+/* SerDes indirect access registers */
+#define RTL8365MB_SDS_INDACS_CMD_REG 0x6600
+#define RTL8365MB_SDS_INDACS_CMD_BUSY_MASK 0x0100
+#define RTL8365MB_SDS_INDACS_CMD_RUN_MASK 0x0080
+#define RTL8365MB_SDS_INDACS_CMD_WR_MASK 0x0040
+#define RTL8365MB_SDS_INDACS_ADR_REG 0x6601
+#define RTL8365MB_SDS_INDACS_DATA_REG 0x6602
+
+/* SerDes miscellaneous configuration register */
+#define RTL8365MB_SDS_MISC_REG 0x1D11
+#define RTL8365MB_SDS_MISC_SGMII_RXFC_MASK 0x4000
+#define RTL8365MB_SDS_MISC_SGMII_TXFC_MASK 0x2000
+#define RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK 0x0800
+#define RTL8365MB_SDS_MISC_SGMII_FDUP_MASK 0x0400
+#define RTL8365MB_SDS_MISC_SGMII_LINK_MASK 0x0200
+#define RTL8365MB_SDS_MISC_SGMII_SPD_MASK 0x0180
+#define RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK 0x0040
+
+/* SerDes internal registers, accessed via the SDS_INDACS registers. The BMCR
+ * data path reset holds BMCR_ANENABLE | BMCR_ISOLATE while toggling the
+ * vendor-specific low bits from phase 1 to phase 2, which triggers a data path
+ * reset and PLL resync.
+ */
+#define RTL8365MB_SDS_REG_BMCR 0x0000
+#define RTL8365MB_SDS_BMCR_DPRST_PHASE1 (BMCR_ANENABLE | BMCR_ISOLATE | 0x1)
+#define RTL8365MB_SDS_BMCR_DPRST_PHASE2 (BMCR_ANENABLE | BMCR_ISOLATE | 0x3)
+#define RTL8365MB_SDS_REG_NWAY 0x0002
+#define RTL8365MB_SDS_NWAY_EN_MASK 0x0200
+#define RTL8365MB_SDS_NWAY_RESTART_MASK 0x0100
+#define RTL8365MB_SDS_REG_RESET 0x0003
+#define RTL8365MB_SDS_RESET_DEASSERT 0x7106
+#define RTL8365MB_SDS_REG_LINK_STATUS 0x003d
+#define RTL8365MB_SDS_LINK_STATUS_LINK_MASK 0x0010
+
+/* The embedded SerDes can only be muxed to external interface 1 (MAC8),
+ * which is port 6.
+ */
+#define RTL8365MB_SDS_EXT_INTERFACE_ID 1
+#define RTL8365MB_SDS_EXT_INTERFACE_PORT 6
+
+/* Line rate bypass bit for the SerDes external interface */
+#define RTL8365MB_SDS_BYPASS_LINE_RATE_MASK \
+ RTL8365MB_BYPASS_LINE_RATE_MASK(RTL8365MB_SDS_EXT_INTERFACE_PORT)
+
+/* SerDes tuning parameter variant selector. The vendor driver picks between
+ * two sets of SerDes tuning parameters based on this chip option. Reading it
+ * requires first arming the read by writing a magic key to the arm register,
+ * then disarming it afterwards.
+ */
+#define RTL8365MB_SDS_OPTION_ARM_REG 0x13C0
+#define RTL8365MB_SDS_OPTION_ARM_KEY 0x0249
+#define RTL8365MB_SDS_OPTION_REG 0x13C1
+
+/* Embedded DW8051 microcontroller control registers. The microcontroller
+ * can run firmware to manage the SerDes link, but this driver keeps it in
+ * reset and disabled: phylink already performs the link management that
+ * the firmware would otherwise do.
+ */
+#define RTL8365MB_MISC_CFG0_REG 0x130C
+#define RTL8365MB_MISC_CFG0_DW8051_EN_MASK 0x0020
+
/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */
#define RTL8365MB_PORT_SPEED_10M 0
#define RTL8365MB_PORT_SPEED_100M 1
@@ -551,6 +625,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] = {
{ 0x1D32, 0x0002 },
};
+/* SGMII SerDes tuning parameters, lifted from the vendor driver sources. The
+ * vendor driver keeps two variants of this table and selects between them
+ * based on the chip option register; these are the values for a non-zero
+ * option, which is what RTL8367S parts seen so far report. See
+ * rtl8365mb_sds_probe_option().
+ */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = {
+ { 0x0480, 0x04D7 }, { 0x0481, 0xF994 }, { 0x0482, 0x2420 },
+ { 0x0483, 0x6960 }, { 0x0484, 0x9728 }, { 0x0423, 0x9D85 },
+ { 0x0424, 0xD810 }, { 0x002E, 0x83F2 },
+};
+
enum rtl8365mb_phy_interface_mode {
RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -730,6 +816,9 @@ struct rtl8365mb_port {
* @cpu: CPU tagging and CPU port configuration for this chip
* @mib_lock: prevent concurrent reads of MIB counters
* @ports: per-port data
+ * @pcs: PCS for the SerDes external interface
+ * @sds_supported: SerDes tuning parameters match the chip option, so the
+ * SerDes interface modes can be advertised
*
* Private data for this driver.
*/
@@ -740,8 +829,12 @@ struct rtl8365mb {
struct rtl8365mb_cpu cpu;
struct mutex mib_lock;
struct rtl8365mb_port ports[RTL8365MB_MAX_NUM_PORTS];
+ struct phylink_pcs pcs;
+ bool sds_supported;
};
+#define pcs_to_rtl8365mb(_pcs) container_of((_pcs), struct rtl8365mb, pcs)
+
static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
{
u32 val;
@@ -1042,6 +1135,342 @@ static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
return 0;
}
+static int rtl8365mb_sds_write(struct realtek_priv *priv, u16 addr, u16 data)
+{
+ int ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, data);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
+ if (ret)
+ return ret;
+
+ /* The SerDes indirect access engine completes the command within the
+ * register write transaction, so there is no need to wait or poll for
+ * completion before the next access, matching the vendor driver.
+ */
+ return regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK |
+ RTL8365MB_SDS_INDACS_CMD_WR_MASK);
+}
+
+static int rtl8365mb_sds_read(struct realtek_priv *priv, u16 addr, u16 *data)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_ADR_REG, addr);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ RTL8365MB_SDS_INDACS_CMD_RUN_MASK);
+ if (ret)
+ return ret;
+
+ /* Wait for the indirect read to complete: the engine clears the BUSY
+ * bit once the data register holds the result.
+ */
+ ret = regmap_read_poll_timeout(priv->map, RTL8365MB_SDS_INDACS_CMD_REG,
+ val,
+ !(val & RTL8365MB_SDS_INDACS_CMD_BUSY_MASK),
+ 10, 1000);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_SDS_INDACS_DATA_REG, &val);
+ if (ret)
+ return ret;
+
+ *data = val;
+
+ return 0;
+}
+
+/* The vendor driver selects between two sets of SerDes tuning parameters based
+ * on the chip option register. Only the variant for a non-zero option has been
+ * tested on real hardware - the RTL8367S parts seen so far all report 1. The
+ * variant for option 0 uses different tuning values that cannot be verified,
+ * so probe the option once at setup and only advertise the SerDes interface
+ * modes when the tuning parameters are known to match, so that an unsupported
+ * variant fails at phylink validation time rather than when configuring the
+ * link.
+ */
+static int rtl8365mb_sds_probe_option(struct realtek_priv *priv)
+{
+ struct rtl8365mb *mb = priv->chip_data;
+ const struct rtl8365mb_extint *extint;
+ u32 option;
+ int ret;
+ int i;
+
+ /* Nothing to probe if no external interface is wired to the SerDes */
+ for (i = 0; i < RTL8365MB_MAX_NUM_EXTINTS; i++) {
+ extint = &mb->chip_info->extints[i];
+
+ if (extint->supported_interfaces &
+ (RTL8365MB_PHY_INTERFACE_MODE_SGMII |
+ RTL8365MB_PHY_INTERFACE_MODE_HSGMII))
+ break;
+ }
+ if (i == RTL8365MB_MAX_NUM_EXTINTS)
+ return 0;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG,
+ RTL8365MB_SDS_OPTION_ARM_KEY);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->map, RTL8365MB_SDS_OPTION_REG, &option);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->map, RTL8365MB_SDS_OPTION_ARM_REG, 0);
+ if (ret)
+ return ret;
+
+ if (option == 0) {
+ dev_warn(priv->dev,
+ "unsupported SerDes tuning variant (chip option 0), disabling SerDes interface modes\n");
+ return 0;
+ }
+
+ mb->sds_supported = true;
+
+ return 0;
+}
+
+static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+ struct realtek_priv *priv;
+ u16 val;
+ int ret;
+ int i;
+
+ priv = mb->priv;
+
+ /* This driver does not implement SGMII in-band autonegotiation yet, so
+ * the link parameters are forced from rtl8365mb_pcs_link_up() instead.
+ * rtl8365mb_pcs_inband_caps() reports this to phylink, which should
+ * therefore never select an in-band-enabled negotiation mode.
+ */
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
+ return -EOPNOTSUPP;
+
+ /* Hold the embedded DW8051 microcontroller in reset and keep it
+ * disabled. The vendor driver loads firmware into it to manage the
+ * SerDes link, but the firmware only duplicates work that phylink
+ * already does: it polls the port status and forces the external
+ * interface configuration in the very registers this driver manages.
+ * Letting it run would race with phylink.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_CHIP_RESET_REG,
+ RTL8365MB_CHIP_RESET_DW8051_MASK,
+ RTL8365MB_CHIP_RESET_DW8051_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->map, RTL8365MB_MISC_CFG0_REG,
+ RTL8365MB_MISC_CFG0_DW8051_EN_MASK, 0);
+ if (ret)
+ return ret;
+
+ /* The vendor driver clears the line rate bypass for all interface
+ * modes except TMII.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_BYPASS_LINE_RATE_REG,
+ RTL8365MB_SDS_BYPASS_LINE_RATE_MASK, 0);
+ if (ret)
+ return ret;
+
+ /* Tune the SerDes with vendor-prescribed parameters */
+ for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) {
+ ret = rtl8365mb_sds_write(priv,
+ rtl8365mb_sds_jam_sgmii[i].reg,
+ rtl8365mb_sds_jam_sgmii[i].val);
+ if (ret)
+ return ret;
+ }
+
+ /* Mux the SerDes to MAC8 in SGMII mode */
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
+ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
+ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK);
+ if (ret)
+ return ret;
+
+ val = RTL8365MB_EXT_PORT_MODE_SGMII
+ << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
+ RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
+ val);
+ if (ret)
+ return ret;
+
+ /* Take the SerDes out of reset. The vendor driver does this only
+ * after the SerDes mux and the interface mode are configured.
+ */
+ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_RESET,
+ RTL8365MB_SDS_RESET_DEASSERT);
+ if (ret)
+ return ret;
+
+ /* Reset the SerDes data path and resync its PLL, mirroring what the
+ * vendor firmware does right after deasserting the SerDes reset.
+ * This flushes the FIFOs and ensures a clean state for the link,
+ * preventing silent drops and CRC errors.
+ */
+ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+ RTL8365MB_SDS_BMCR_DPRST_PHASE1);
+ if (ret)
+ return ret;
+
+ ret = rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_BMCR,
+ RTL8365MB_SDS_BMCR_DPRST_PHASE2);
+ if (ret)
+ return ret;
+
+ /* Keep SGMII in-band autonegotiation disabled: the link parameters are
+ * forced from rtl8365mb_pcs_link_up() instead.
+ */
+ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_NWAY, &val);
+ if (ret)
+ return ret;
+
+ val &= ~RTL8365MB_SDS_NWAY_EN_MASK;
+ val |= RTL8365MB_SDS_NWAY_RESTART_MASK;
+
+ return rtl8365mb_sds_write(priv, RTL8365MB_SDS_REG_NWAY, val);
+}
+
+static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
+{
+ return interface == PHY_INTERFACE_MODE_SGMII;
+}
+
+static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ /* In-band autonegotiation is not implemented; the link is always
+ * forced. Report that to phylink so that it never selects an
+ * in-band-enabled negotiation mode for this PCS.
+ */
+ return LINK_INBAND_DISABLE;
+}
+
+static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+ struct realtek_priv *priv = mb->priv;
+ u16 status;
+ u32 val;
+ int ret;
+
+ /* In-band autonegotiation is not implemented, so the link parameters are
+ * forced from rtl8365mb_pcs_link_up(). The real link state must still be
+ * read from the SerDes itself: the embedded DW8051 microcontroller that
+ * the vendor firmware uses to poll the SerDes is kept disabled (see
+ * rtl8365mb_pcs_config()), so the link status register can be read
+ * directly through the SDS_INDACS window without racing the auto-poll.
+ */
+ ret = rtl8365mb_sds_read(priv, RTL8365MB_SDS_REG_LINK_STATUS, &status);
+ if (ret) {
+ state->link = false;
+ return;
+ }
+
+ state->link = !!(status & RTL8365MB_SDS_LINK_STATUS_LINK_MASK);
+ state->an_complete = state->link;
+ if (!state->link)
+ return;
+
+ /* The speed and duplex are forced; read them back from the values
+ * programmed into the SerDes MISC register.
+ */
+ ret = regmap_read(priv->map, RTL8365MB_SDS_MISC_REG, &val);
+ if (ret) {
+ state->link = false;
+ return;
+ }
+
+ state->duplex = (val & RTL8365MB_SDS_MISC_SGMII_FDUP_MASK) ?
+ DUPLEX_FULL : DUPLEX_HALF;
+
+ switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) {
+ case RTL8365MB_PORT_SPEED_1000M:
+ state->speed = SPEED_1000;
+ break;
+ case RTL8365MB_PORT_SPEED_100M:
+ state->speed = SPEED_100;
+ break;
+ case RTL8365MB_PORT_SPEED_10M:
+ state->speed = SPEED_10;
+ break;
+ }
+}
+
+static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+{
+ struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
+ struct realtek_priv *priv = mb->priv;
+ u32 mask = RTL8365MB_SDS_MISC_SGMII_FDUP_MASK |
+ RTL8365MB_SDS_MISC_SGMII_LINK_MASK |
+ RTL8365MB_SDS_MISC_SGMII_SPD_MASK;
+ u32 val = RTL8365MB_SDS_MISC_SGMII_LINK_MASK;
+ u32 r_speed;
+ int ret;
+
+ if (speed == SPEED_1000) {
+ r_speed = RTL8365MB_PORT_SPEED_1000M;
+ } else if (speed == SPEED_100) {
+ r_speed = RTL8365MB_PORT_SPEED_100M;
+ } else if (speed == SPEED_10) {
+ r_speed = RTL8365MB_PORT_SPEED_10M;
+ } else {
+ dev_err(priv->dev, "unsupported SerDes speed %s\n",
+ phy_speed_to_str(speed));
+ return;
+ }
+
+ val |= FIELD_PREP(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, r_speed);
+
+ if (duplex == DUPLEX_FULL)
+ val |= RTL8365MB_SDS_MISC_SGMII_FDUP_MASK;
+
+ /* pcs_link_up() carries no pause information, so the SerDes flow
+ * control bits are programmed together with the MAC external interface
+ * force from rtl8365mb_phylink_mac_link_up(), where the resolved pause
+ * modes are known.
+ */
+ ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG, mask, val);
+ if (ret) {
+ dev_err(priv->dev, "failed to force SerDes link: %pe\n",
+ ERR_PTR(ret));
+ return;
+ }
+}
+
+static const struct phylink_pcs_ops rtl8365mb_pcs_ops = {
+ .pcs_inband_caps = rtl8365mb_pcs_inband_caps,
+ .pcs_config = rtl8365mb_pcs_config,
+ .pcs_get_state = rtl8365mb_pcs_get_state,
+ .pcs_link_up = rtl8365mb_pcs_link_up,
+};
+
static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
bool link, int speed, int duplex,
bool tx_pause, bool rx_pause)
@@ -1118,6 +1547,8 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
{
const struct rtl8365mb_extint *extint =
rtl8365mb_get_port_extint(ds->priv, port);
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
config->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE |
MAC_10 | MAC_100 | MAC_1000FD;
@@ -1141,6 +1572,25 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_RGMII)
phy_interface_set_rgmii(config->supported_interfaces);
+
+ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_SGMII &&
+ mb->sds_supported)
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+}
+
+static struct phylink_pcs *
+rtl8365mb_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct realtek_priv *priv = dp->ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
+
+ if (rtl8365mb_interface_is_serdes(interface))
+ return &mb->pcs;
+
+ return NULL;
}
static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
@@ -1168,6 +1618,12 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
return;
}
+ /* SGMII is handled by the SerDes PCS, configured through the
+ * phylink_pcs ops, so there is nothing to do here for it.
+ */
+ if (rtl8365mb_interface_is_serdes(state->interface))
+ return;
+
/* TODO: Implement MII and RMII modes, which the RTL8365MB-VC also
* supports
*/
@@ -1188,7 +1644,13 @@ static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
p = &mb->ports[port];
cancel_delayed_work_sync(&p->mib_work);
- if (phy_interface_mode_is_rgmii(interface)) {
+ /* phylink has no pcs_link_down callback, so on the SerDes path only the
+ * MAC external interface force is reset here. Clearing the MAC force is
+ * enough to bring the link down; the SerDes keeps presenting its last
+ * forced state until the next pcs_link_up() reprograms it.
+ */
+ if (phy_interface_mode_is_rgmii(interface) ||
+ rtl8365mb_interface_is_serdes(interface)) {
ret = rtl8365mb_ext_config_forcemode(priv, port, false, 0, 0,
false, false);
if (ret)
@@ -1218,14 +1680,46 @@ static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
p = &mb->ports[port];
schedule_delayed_work(&p->mib_work, 0);
- if (phy_interface_mode_is_rgmii(interface)) {
+ /* The SerDes forced link state is programmed by the PCS in
+ * rtl8365mb_pcs_link_up(); here only the MAC external interface force
+ * is configured, for both RGMII and SerDes.
+ */
+ if (phy_interface_mode_is_rgmii(interface) ||
+ rtl8365mb_interface_is_serdes(interface)) {
ret = rtl8365mb_ext_config_forcemode(priv, port, true, speed,
duplex, tx_pause,
rx_pause);
- if (ret)
+ if (ret) {
dev_err(priv->dev,
"failed to force mode on port %d: %pe\n", port,
ERR_PTR(ret));
+ return;
+ }
+
+ /* The SerDes has its own pause controls; program them from
+ * the resolved pause modes, as the vendor driver does when
+ * forcing the link on a SerDes external interface. This is
+ * done here rather than in rtl8365mb_pcs_link_up() because
+ * pcs_link_up() carries no pause information.
+ */
+ if (rtl8365mb_interface_is_serdes(interface)) {
+ u32 val = 0;
+
+ if (tx_pause)
+ val |= RTL8365MB_SDS_MISC_SGMII_TXFC_MASK;
+ if (rx_pause)
+ val |= RTL8365MB_SDS_MISC_SGMII_RXFC_MASK;
+
+ ret = regmap_update_bits(priv->map,
+ RTL8365MB_SDS_MISC_REG,
+ RTL8365MB_SDS_MISC_SGMII_TXFC_MASK |
+ RTL8365MB_SDS_MISC_SGMII_RXFC_MASK,
+ val);
+ if (ret)
+ dev_err(priv->dev,
+ "failed to force SerDes pause modes on port %d: %pe\n",
+ port, ERR_PTR(ret));
+ }
return;
}
@@ -2419,6 +2913,14 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
mb = priv->chip_data;
cpu = &mb->cpu;
+ mb->pcs.ops = &rtl8365mb_pcs_ops;
+
+ /* The SerDes has no link interrupt wired up, so phylink must poll the
+ * PCS for link changes when it tracks the link through pcs_get_state()
+ * (in-band mode with autonegotiation disabled).
+ */
+ mb->pcs.poll = true;
+
ret = rtl8365mb_reset_chip(priv);
if (ret) {
dev_err(priv->dev, "failed to reset chip: %pe\n",
@@ -2426,6 +2928,13 @@ static int rtl8365mb_setup(struct dsa_switch *ds)
goto out_error;
}
+ ret = rtl8365mb_sds_probe_option(priv);
+ if (ret) {
+ dev_err(priv->dev, "failed to probe SerDes chip option: %pe\n",
+ ERR_PTR(ret));
+ goto out_error;
+ }
+
/* Configure switch to vendor-defined initial state */
ret = rtl8365mb_switch_init(priv);
if (ret) {
@@ -2658,6 +3167,7 @@ static int rtl8365mb_detect(struct realtek_priv *priv)
}
static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops = {
+ .mac_select_pcs = rtl8365mb_phylink_mac_select_pcs,
.mac_config = rtl8365mb_phylink_mac_config,
.mac_link_down = rtl8365mb_phylink_mac_link_down,
.mac_link_up = rtl8365mb_phylink_mac_link_up,
base-commit: d6e81529749190123aa0040626c7e5dbc20fdc9a
--
2.55.0
^ permalink raw reply related
* [PATCH net-next v4 2/2] net: dsa: realtek: rtl8365mb: add HSGMII support for RTL8367S
From: Johan Alvarado @ 2026-07-02 20:47 UTC (permalink / raw)
To: linusw, alsi, andrew, olteanv, kuba, davem, edumazet, pabeni,
linux
Cc: luizluca, maxime.chevallier, namiltd, netdev, linux-kernel,
contact
In-Reply-To: <20260702204648.276112-1-contact@c127.dev>
In addition to SGMII, the RTL8367S SerDes also supports HSGMII, which
carries 2.5 Gbps with the same signaling as SGMII at 2.5x clock rate.
The chip info table already declares HSGMII as a supported interface
mode for external interface 1.
Extend the SerDes PCS to handle HSGMII, which phylink represents as
2500base-x:
- Select the HSGMII SerDes tuning parameters and external interface
mode, and mux the SerDes to MAC8 in HSGMII mode, from pcs_config()
according to the interface. The parameters are again lifted from the
GPL-licensed Realtek rtl8367c vendor driver, and again only cover
the tuning variant for a non-zero chip option, so the mode is gated
on the option probed at setup.
- Advertise 2500base-x and MAC_2500FD on ports whose external
interface supports HSGMII.
- Accept SPEED_2500 in the forced link configuration. The MAC speed
field has no 2.5 Gbps value: the rate is determined by the HSGMII
SerDes configuration, and the vendor driver programs the 1 Gbps
value here, so do the same.
Tested on a Mercusys MR80X v2.20, where the RTL8367S is connected to
the SoC over HSGMII.
Suggested-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
Signed-off-by: Johan Alvarado <contact@c127.dev>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 75 +++++++++++++++++++-----
1 file changed, 59 insertions(+), 16 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index 2d202120cfd9..38e5804f9ff6 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -40,8 +40,8 @@
* driver has only been tested with a fixed-link, but in principle it should not
* matter.
*
- * NOTE: Currently, only the RGMII and SGMII interfaces are implemented in this
- * driver.
+ * NOTE: Currently, only the RGMII, SGMII and HSGMII interfaces are implemented
+ * in this driver.
*
* The interrupt line is asserted on link UP/DOWN events. The driver creates a
* custom irqchip to handle this interrupt and demultiplex the events by reading
@@ -637,6 +637,18 @@ static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_sgmii[] = {
{ 0x0424, 0xD810 }, { 0x002E, 0x83F2 },
};
+/* HSGMII SerDes tuning parameters, lifted from the vendor driver sources. As
+ * with the SGMII table, the vendor driver keeps several variants and selects
+ * one based on the chip option register; these are the values for a non-zero
+ * option, which is what RTL8367S parts seen so far report. See
+ * rtl8365mb_sds_probe_option().
+ */
+static const struct rtl8365mb_jam_tbl_entry rtl8365mb_sds_jam_hsgmii[] = {
+ { 0x0500, 0x82F0 }, { 0x0501, 0xF195 }, { 0x0502, 0x31A2 },
+ { 0x0503, 0x7960 }, { 0x0504, 0x9728 }, { 0x0423, 0x9D85 },
+ { 0x0424, 0xD810 }, { 0x0001, 0x0F80 }, { 0x002E, 0x83F2 },
+};
+
enum rtl8365mb_phy_interface_mode {
RTL8365MB_PHY_INTERFACE_MODE_INVAL = 0,
RTL8365MB_PHY_INTERFACE_MODE_INTERNAL = BIT(0),
@@ -1247,9 +1259,12 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
+ const struct rtl8365mb_jam_tbl_entry *sds_jam;
const int id = RTL8365MB_SDS_EXT_INTERFACE_ID;
struct rtl8365mb *mb = pcs_to_rtl8365mb(pcs);
struct realtek_priv *priv;
+ size_t sds_jam_size;
+ u32 mode;
u16 val;
int ret;
int i;
@@ -1264,6 +1279,16 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
return -EOPNOTSUPP;
+ if (interface == PHY_INTERFACE_MODE_2500BASEX) {
+ sds_jam = rtl8365mb_sds_jam_hsgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_hsgmii);
+ mode = RTL8365MB_EXT_PORT_MODE_HSGMII;
+ } else {
+ sds_jam = rtl8365mb_sds_jam_sgmii;
+ sds_jam_size = ARRAY_SIZE(rtl8365mb_sds_jam_sgmii);
+ mode = RTL8365MB_EXT_PORT_MODE_SGMII;
+ }
+
/* Hold the embedded DW8051 microcontroller in reset and keep it
* disabled. The vendor driver loads firmware into it to manage the
* SerDes link, but the firmware only duplicates work that phylink
@@ -1291,24 +1316,24 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
return ret;
/* Tune the SerDes with vendor-prescribed parameters */
- for (i = 0; i < ARRAY_SIZE(rtl8365mb_sds_jam_sgmii); i++) {
- ret = rtl8365mb_sds_write(priv,
- rtl8365mb_sds_jam_sgmii[i].reg,
- rtl8365mb_sds_jam_sgmii[i].val);
+ for (i = 0; i < sds_jam_size; i++) {
+ ret = rtl8365mb_sds_write(priv, sds_jam[i].reg,
+ sds_jam[i].val);
if (ret)
return ret;
}
- /* Mux the SerDes to MAC8 in SGMII mode */
+ /* Mux the SerDes to MAC8 in the requested mode */
ret = regmap_update_bits(priv->map, RTL8365MB_SDS_MISC_REG,
RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK |
RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK,
- RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK);
+ mode == RTL8365MB_EXT_PORT_MODE_SGMII ?
+ RTL8365MB_SDS_MISC_MAC8_SEL_SGMII_MASK :
+ RTL8365MB_SDS_MISC_MAC8_SEL_HSGMII_MASK);
if (ret)
return ret;
- val = RTL8365MB_EXT_PORT_MODE_SGMII
- << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
+ val = mode << RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(id);
ret = regmap_update_bits(priv->map,
RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(id),
RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(id),
@@ -1354,7 +1379,8 @@ static int rtl8365mb_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
static bool rtl8365mb_interface_is_serdes(phy_interface_t interface)
{
- return interface == PHY_INTERFACE_MODE_SGMII;
+ return interface == PHY_INTERFACE_MODE_SGMII ||
+ interface == PHY_INTERFACE_MODE_2500BASEX;
}
static unsigned int rtl8365mb_pcs_inband_caps(struct phylink_pcs *pcs,
@@ -1409,7 +1435,9 @@ static void rtl8365mb_pcs_get_state(struct phylink_pcs *pcs,
switch (FIELD_GET(RTL8365MB_SDS_MISC_SGMII_SPD_MASK, val)) {
case RTL8365MB_PORT_SPEED_1000M:
- state->speed = SPEED_1000;
+ state->speed =
+ state->interface == PHY_INTERFACE_MODE_2500BASEX ?
+ SPEED_2500 : SPEED_1000;
break;
case RTL8365MB_PORT_SPEED_100M:
state->speed = SPEED_100;
@@ -1434,7 +1462,11 @@ static void rtl8365mb_pcs_link_up(struct phylink_pcs *pcs,
u32 r_speed;
int ret;
- if (speed == SPEED_1000) {
+ /* The speed field has no value for 2.5 Gbps: the rate is determined by
+ * the HSGMII SerDes configuration, and the vendor driver programs the
+ * 1 Gbps value here.
+ */
+ if (speed == SPEED_2500 || speed == SPEED_1000) {
r_speed = RTL8365MB_PORT_SPEED_1000M;
} else if (speed == SPEED_100) {
r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1494,7 +1526,11 @@ static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
r_rx_pause = rx_pause ? 1 : 0;
r_tx_pause = tx_pause ? 1 : 0;
- if (speed == SPEED_1000) {
+ /* The speed field has no value for 2.5 Gbps: the rate is
+ * determined by the HSGMII SerDes configuration, and the
+ * vendor driver programs the 1 Gbps value here.
+ */
+ if (speed == SPEED_2500 || speed == SPEED_1000) {
r_speed = RTL8365MB_PORT_SPEED_1000M;
} else if (speed == SPEED_100) {
r_speed = RTL8365MB_PORT_SPEED_100M;
@@ -1577,6 +1613,13 @@ static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
mb->sds_supported)
__set_bit(PHY_INTERFACE_MODE_SGMII,
config->supported_interfaces);
+
+ if (extint->supported_interfaces & RTL8365MB_PHY_INTERFACE_MODE_HSGMII &&
+ mb->sds_supported) {
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ }
}
static struct phylink_pcs *
@@ -1618,8 +1661,8 @@ static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
return;
}
- /* SGMII is handled by the SerDes PCS, configured through the
- * phylink_pcs ops, so there is nothing to do here for it.
+ /* SGMII and 2500base-x are handled by the SerDes PCS, configured
+ * through the phylink_pcs ops, so nothing to do here for them.
*/
if (rtl8365mb_interface_is_serdes(state->interface))
return;
--
2.55.0
^ permalink raw reply related
* Re: [PATCH v2] selftests: Open /dev/udmabuf O_RDONLY
From: Bobby Eshleman @ 2026-07-02 20:53 UTC (permalink / raw)
To: T.J. Mercier
Cc: kraxel, vivek.kasireddy, kuba, Shuah Khan, Andrew Lunn,
David S. Miller, Eric Dumazet, Paolo Abeni, linux-kselftest,
linux-kernel, netdev, bpf
In-Reply-To: <20260701192210.2997769-1-tjmercier@google.com>
On Wed, Jul 01, 2026 at 12:22:08PM -0700, T.J. Mercier wrote:
> Write permissions on the /dev/udmabuf device file are not required to
> issue ioctls and allocate udmabufs. Applications should be opening this
> file as O_RDONLY. The BPF dmabuf_iter selftest already does this. [1]
>
> Users are pointing to these selftests as examples of how use udmabuf,
> and encountering permission errors on systems where write permissions
> are not available on /dev/udmabuf. Apply the principle of least
> privilege to selftests which use udmabuf by removing the write access
> mode from drivers/dma-buf/udmabuf.c and drivers/net/hw/ncdevmem.c.
>
> [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/selftests/bpf/prog_tests/dmabuf_iter.c?h=v7.1#n49
>
> Signed-off-by: T.J. Mercier <tjmercier@google.com>
> ---
> tools/testing/selftests/drivers/dma-buf/udmabuf.c | 2 +-
> tools/testing/selftests/drivers/net/hw/ncdevmem.c | 2 +-
> 2 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/tools/testing/selftests/drivers/dma-buf/udmabuf.c b/tools/testing/selftests/drivers/dma-buf/udmabuf.c
> index d78aec662586..ced0b95c876c 100644
> --- a/tools/testing/selftests/drivers/dma-buf/udmabuf.c
> +++ b/tools/testing/selftests/drivers/dma-buf/udmabuf.c
> @@ -140,7 +140,7 @@ int main(int argc, char *argv[])
> ksft_print_header();
> ksft_set_plan(7);
>
> - devfd = open("/dev/udmabuf", O_RDWR);
> + devfd = open("/dev/udmabuf", O_RDONLY);
> if (devfd < 0) {
> ksft_print_msg(
> "%s: [skip,no-udmabuf: Unable to access DMA buffer device file]\n",
> diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
> index e098d6534c3c..8114a29692fd 100644
> --- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
> +++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
> @@ -149,7 +149,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
>
> ctx->size = size;
>
> - ctx->devfd = open("/dev/udmabuf", O_RDWR);
> + ctx->devfd = open("/dev/udmabuf", O_RDONLY);
> if (ctx->devfd < 0) {
> pr_err("[skip,no-udmabuf: Unable to access DMA buffer device file]");
> goto err_free_ctx;
>
> base-commit: fbb7ad31ab376c5101b2ac7205fad0344fd2de60
> --
> 2.55.0.rc0.799.gd6f94ed593-goog
>
Reviewed-by: Bobby Eshleman <bobbyeshleman@meta.com>
^ permalink raw reply
* RE: [PATCH net-next v6 12/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY
From: Selvamani Rajagopal @ 2026-07-02 21:23 UTC (permalink / raw)
To: Julian Braha, Andrew Lunn, Piergiorgio Beruto, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Andrew Lunn, Parthiban Veerasooran, Richard Cochran,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman,
Jonathan Corbet, Shuah Khan
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org, linux-doc@vger.kernel.org, Jerry Ray
In-Reply-To: <d6a56d05-0c6f-49a5-9281-1194b62ab86e@gmail.com>
> -----Original Message-----
> From: Julian Braha <julian.braha@gmail.com> On Behalf Of Julian Braha
> Subject: Re: [PATCH net-next v6 12/15] onsemi: s2500: Add driver support for TS2500 MAC-PHY
>
> > +endif # NET_VENDOR_ONSEMI
>
> S2500_MACPHY still has that duplicate dependency from being inside two
> of these:
> 'if NET_VENDOR_ONSEMI..endif'
>
> And I already pointed it out on v5:
Somehow, I missed your feedback. Sorry about that. Will remove the duplication.
(Also will go over all the responses to see if I missed anything else)
> :(
>
> - Julian Braha
^ permalink raw reply
* Re: [PATCH net] net/tls: Consume empty data records in tls_sw_read_sock()
From: Sabrina Dubroca @ 2026-07-02 21:50 UTC (permalink / raw)
To: Chuck Lever
Cc: john.fastabend, Jakub Kicinski, davem, edumazet, Paolo Abeni,
Simon Horman, netdev
In-Reply-To: <0a03d16e-d4ce-422d-9492-3e31d910d8e5@app.fastmail.com>
2026-07-02, 15:52:49 -0400, Chuck Lever wrote:
>
>
> On Thu, Jul 2, 2026, at 2:05 PM, Sabrina Dubroca wrote:
> > 2026-06-30, 15:15:51 -0400, Chuck Lever wrote:
> >> A peer may send a zero-length TLS application_data record; TLS 1.3
> >> explicitly permits these as a traffic-analysis countermeasure (RFC
> >> 8446, Section 5.1). After decryption such a record has full_len ==
> >> 0. tls_sw_read_sock() hands it to the read_actor, which has no
> >> payload to consume and returns zero. The loop treats a zero return
> >> as backpressure (used <= 0), requeues the skb at the head of
> >> rx_list, and stops. rx_list is serviced head-first on the next
> >> call, so the empty record is dequeued, fails the same way, and is
> >> requeued again; every later record on the connection is blocked
> >> behind it.
> >>
> >> tls_sw_recvmsg() does not stall on this: a zero-length data record
> >> copies nothing and falls through to consume_skb(). Mirror that in
> >> the read_sock() path by recognizing an empty data record before
> >> the actor runs, consuming it, and continuing.
> >>
> >> Fixes: 662fbcec32f4 ("net/tls: implement ->read_sock()")
> >> Signed-off-by: Chuck Lever <cel@kernel.org>
> >> ---
> >> net/tls/tls_sw.c | 11 +++++++++++
> >> 1 file changed, 11 insertions(+)
> >
> > Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
> >
> > I think tls_sw_splice_read() suffers from a similar issue (returning 0
> > even though more data may be available). Sashiko agrees, and also
> > found a few more pre-existing issues.
>
> Do you want a v2 series with those issues addressed?
I'd be ok with this patch going in on its own, and the other issues
being addressed separately. If you have time to look into those,
that'd be great.
--
Sabrina
^ permalink raw reply
* Re: [syzbot ci] Re: net: Support per-netns device unregistration
From: Kuniyuki Iwashima @ 2026-07-02 21:59 UTC (permalink / raw)
To: syzbot+ci052b96c9bf56ca1d
Cc: andrew, davem, edumazet, horms, kuba, kuni1840, kuniyu, netdev,
pabeni, syzbot, syzkaller-bugs
In-Reply-To: <6a461706.52c20a74.1f8b39.0008.GAE@google.com>
From: syzbot ci <syzbot+ci052b96c9bf56ca1d@syzkaller.appspotmail.com>
Date: Thu, 02 Jul 2026 00:45:10 -0700
> syzbot ci has tested the following series
>
> [v1] net: Support per-netns device unregistration
> https://lore.kernel.org/all/20260701214334.266991-1-kuniyu@google.com
> * [PATCH v1 net-next 01/14] rtnetlink: Lock sock_net(skb->sk) in rtnl_newlink().
> * [PATCH v1 net-next 02/14] rtnetlink: Call unregister_netdevice_many() only once in rtnl_link_unregister().
> * [PATCH v1 net-next 03/14] rtnetlink: Add per-netns rtnl_work.
> * [PATCH v1 net-next 04/14] net: Wrap default_device_exit_net() with __rtnl_net_lock().
> * [PATCH v1 net-next 05/14] net: Hold __rtnl_net_lock() in netdev_wait_allrefs_any().
> * [PATCH v1 net-next 06/14] net: Add per-netns netdev unregistration infra.
> * [PATCH v1 net-next 07/14] net: Call unregister_netdevice_many() per netns.
> * [PATCH v1 net-next 08/14] veth: Support per-netns device unregistration.
> * [PATCH v1 net-next 09/14] bareudp: Protect bareudp_list with mutex.
> * [PATCH v1 net-next 10/14] bareudp: Support per-netns netdev unregistration.
> * [PATCH v1 net-next 11/14] ipvlan: Convert ipvl_port.count to refcount_t.
> * [PATCH v1 net-next 12/14] ipvlan: Synchronise ipvlan_init() and ipvlan_uninit() for the same lower dev.
> * [PATCH v1 net-next 13/14] ipvlan: Protect ipvl_port.ipvlans with mutex.
> * [PATCH v1 net-next 14/14] ipvlan: Support per-netns netdev unregistration.
>
> and found the following issue:
> possible deadlock in __dev_change_net_namespace
>
> Full report is available here:
> https://ci.syzbot.org/series/a744b257-d741-4780-8a53-f156b2a7afc9
>
> ***
>
> possible deadlock in __dev_change_net_namespace
>
> tree: net-next
> URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/netdev/net-next.git
> base: d6e81529749190123aa0040626c7e5dbc20fdc9a
> arch: amd64
> compiler: Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
> config: https://ci.syzbot.org/builds/243cd0ec-28f9-4d21-8f16-3d2fbad8388d/config
> syz repro: https://ci.syzbot.org/findings/a8a0740d-fdec-4a20-9aa5-7cb955707913/syz_repro
>
> veth0_macvtap: left promiscuous mode
> ============================================
> WARNING: possible recursive locking detected
> syzkaller #0 Not tainted
> --------------------------------------------
> syz.1.18/5814 is trying to acquire lock:
> ffffffff9a9b0418 (&net->dev_unreg_lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock.h:342 [inline]
> ffffffff9a9b0418 (&net->dev_unreg_lock){+.+.}-{3:3}, at: unregister_netdevice_move_net net/core/dev.c:-1 [inline]
> ffffffff9a9b0418 (&net->dev_unreg_lock){+.+.}-{3:3}, at: __dev_change_net_namespace+0x1479/0x2200 net/core/dev.c:12768
>
> but task is already holding lock:
> ffff88810b6cf8d8 (&net->dev_unreg_lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock.h:342 [inline]
> ffff88810b6cf8d8 (&net->dev_unreg_lock){+.+.}-{3:3}, at: unregister_netdevice_move_net net/core/dev.c:-1 [inline]
> ffff88810b6cf8d8 (&net->dev_unreg_lock){+.+.}-{3:3}, at: __dev_change_net_namespace+0x146a/0x2200 net/core/dev.c:12768
>
> other info that might help us debug this:
> Possible unsafe locking scenario:
>
> CPU0
> ----
> lock(&net->dev_unreg_lock);
> lock(&net->dev_unreg_lock);
>
> *** DEADLOCK ***
>
> May be due to missing lock nesting notation
Oh right, I'll squash this to patch 6.
---8<---
diff --git a/net/core/dev.c b/net/core/dev.c
index 57fb4741d0ac..fcd58c2aa030 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12574,10 +12574,10 @@ static void unregister_netdevice_move_net(struct net *net_old,
{
if (net_old > net) {
spin_lock(&net->dev_unreg_lock);
- spin_lock(&net_old->dev_unreg_lock);
+ spin_lock_nested(&net_old->dev_unreg_lock, SINGLE_DEPTH_NESTING);
} else {
spin_lock(&net_old->dev_unreg_lock);
- spin_lock(&net->dev_unreg_lock);
+ spin_lock_nested(&net->dev_unreg_lock, SINGLE_DEPTH_NESTING);
}
if (!list_empty(&dev->unreg_list_net)) {
---8<---
^ permalink raw reply related
* [PATCH net-next v5] net: mana: Add Interrupt Moderation support
From: Haiyang Zhang @ 2026-07-02 22:01 UTC (permalink / raw)
To: linux-hyperv, netdev, K. Y. Srinivasan, Haiyang Zhang, Wei Liu,
Dexuan Cui, Long Li, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Konstantin Taranov, Simon Horman,
Erni Sri Satya Vennela, Dipayaan Roy, Shradha Gupta, Aditya Garg,
Stanislav Fomichev, Breno Leitao, linux-kernel, linux-rdma
Cc: paulros
From: Haiyang Zhang <haiyangz@microsoft.com>
Add Static and Dynamic Interrupt Moderation (DIM) support for
Rx and Tx.
Update queue creation procedure with new data struct with the related
settings.
Add functions to collect stat for DIM, and workers to update DIM data
and settings.
Update ethtool handler to get/set the moderation settings from a user.
To avoid detach/re-attach ops, ring DIM doorbell to change settings
at run time.
By default, adaptive-rx/tx (DIM) are enabled if supported by HW.
Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Reviewed-by: Simon Horman <horms@kernel.org>
---
v5:
Updated function return type and comments suggested by Paolo Abeni.
v4:
Fixed tx stat, concurrency, and mb issues from Simon's review.
v3:
Updated to avoid detach/re-attach ops as suggested by Paolo.
v2:
Updated with comments from Jedrzej.
---
drivers/net/ethernet/microsoft/Kconfig | 1 +
.../net/ethernet/microsoft/mana/gdma_main.c | 29 +++
drivers/net/ethernet/microsoft/mana/mana_en.c | 175 ++++++++++++++++++
.../ethernet/microsoft/mana/mana_ethtool.c | 167 ++++++++++++++++-
include/net/mana/gdma.h | 24 ++-
include/net/mana/mana.h | 54 ++++++
6 files changed, 441 insertions(+), 9 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/Kconfig b/drivers/net/ethernet/microsoft/Kconfig
index 3f36ee6a8ece..e9be18c92ca5 100644
--- a/drivers/net/ethernet/microsoft/Kconfig
+++ b/drivers/net/ethernet/microsoft/Kconfig
@@ -21,6 +21,7 @@ config MICROSOFT_MANA
depends on X86_64 || (ARM64 && !CPU_BIG_ENDIAN)
depends on PCI_HYPERV
select AUXILIARY_BUS
+ select DIMLIB
select PAGE_POOL
select NET_SHAPER
help
diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c
index e8b7ffb47eb9..aef3b77229c1 100644
--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
+++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2021, Microsoft Corporation. */
+#include <linux/bitfield.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/pci.h>
@@ -466,6 +467,7 @@ static int mana_gd_disable_queue(struct gdma_queue *queue)
#define DOORBELL_OFFSET_RQ 0x400
#define DOORBELL_OFFSET_CQ 0x800
#define DOORBELL_OFFSET_EQ 0xFF8
+#define DOORBELL_OFFSET_DIM 0x820
static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index,
enum gdma_queue_type q_type, u32 qid,
@@ -506,6 +508,16 @@ static void mana_gd_ring_doorbell(struct gdma_context *gc, u32 db_index,
addr += DOORBELL_OFFSET_SQ;
break;
+ case GDMA_DIM:
+ e.dim.id = qid;
+ e.dim.mod_usec = FIELD_GET(MANA_INTR_MODR_USEC_MAX, tail_ptr);
+ e.dim.mod_usec_vld = !!(tail_ptr & MANA_INTR_MODR_USEC_VLD);
+ e.dim.mod_comps = FIELD_GET(MANA_INTR_MODR_COMP_MASK, tail_ptr);
+ e.dim.mod_comps_vld = num_req;
+
+ addr += DOORBELL_OFFSET_DIM;
+ break;
+
default:
WARN_ON(1);
return;
@@ -540,6 +552,23 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit)
}
EXPORT_SYMBOL_NS(mana_gd_ring_cq, "NET_MANA");
+void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld,
+ u32 mod_comps, bool mod_comps_vld)
+{
+ struct gdma_context *gc = cq->gdma_dev->gdma_context;
+ u32 dim_val;
+
+ /* Convert the DIM values to doorbell parameters */
+ dim_val = FIELD_PREP(MANA_INTR_MODR_USEC_MAX, mod_usec) |
+ FIELD_PREP(MANA_INTR_MODR_COMP_MASK, mod_comps);
+ if (mod_usec_vld)
+ dim_val |= MANA_INTR_MODR_USEC_VLD;
+
+ mana_gd_ring_doorbell(gc, cq->gdma_dev->doorbell, GDMA_DIM, cq->id,
+ dim_val, mod_comps_vld);
+}
+EXPORT_SYMBOL_NS(mana_gd_ring_dim, "NET_MANA");
+
#define MANA_SERVICE_PERIOD 10
static void mana_serv_rescan(struct pci_dev *pdev)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 7438ea6b3f26..5ce0b96c50f6 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -1591,6 +1591,15 @@ int mana_create_wq_obj(struct mana_port_context *apc,
mana_gd_init_req_hdr(&req.hdr, MANA_CREATE_WQ_OBJ,
sizeof(req), sizeof(resp));
+
+ /* Our driver uses different message versions for request and
+ * response in this case.
+ * Our firmware is forward compatible with newer message versions, so
+ * the old firmware still properly handles this message, just the new
+ * feature fields are ignored, and queue creation will be successful.
+ */
+ req.hdr.req.msg_version = GDMA_MESSAGE_V3;
+ req.hdr.resp.msg_version = GDMA_MESSAGE_V2;
req.vport = vport;
req.wq_type = wq_type;
req.wq_gdma_region = wq_spec->gdma_region;
@@ -1599,6 +1608,9 @@ int mana_create_wq_obj(struct mana_port_context *apc,
req.cq_size = cq_spec->queue_size;
req.cq_moderation_ctx_id = cq_spec->modr_ctx_id;
req.cq_parent_qid = cq_spec->attached_eq;
+ req.req_cq_moderation = cq_spec->req_cq_moderation;
+ req.cq_moderation_comp = cq_spec->cq_moderation_comp;
+ req.cq_moderation_usec = cq_spec->cq_moderation_usec;
err = mana_send_request(apc->ac, &req, sizeof(req), &resp,
sizeof(resp));
@@ -1856,6 +1868,7 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
struct gdma_posted_wqe_info *wqe_info;
unsigned int pkt_transmitted = 0;
unsigned int wqe_unit_cnt = 0;
+ unsigned int tx_bytes = 0;
struct mana_txq *txq = cq->txq;
struct mana_port_context *apc;
struct netdev_queue *net_txq;
@@ -1937,6 +1950,8 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
mana_unmap_skb(skb, apc);
+ tx_bytes += skb->len;
+
napi_consume_skb(skb, cq->budget);
pkt_transmitted++;
@@ -1967,6 +1982,10 @@ static void mana_poll_tx_cq(struct mana_cq *cq)
if (atomic_sub_return(pkt_transmitted, &txq->pending_sends) < 0)
WARN_ON_ONCE(1);
+ /* Feed DIM with the completion rate observed here, in NAPI context. */
+ cq->tx_dim_pkts += pkt_transmitted;
+ cq->tx_dim_bytes += tx_bytes;
+
cq->work_done = pkt_transmitted;
}
@@ -2318,6 +2337,117 @@ static void mana_poll_rx_cq(struct mana_cq *cq)
xdp_do_flush();
}
+static void mana_rx_dim_work(struct work_struct *work)
+{
+ struct dim *dim = container_of(work, struct dim, work);
+ struct dim_cq_moder cur_moder;
+ struct mana_cq *cq;
+
+ cur_moder = net_dim_get_rx_moderation(dim->mode, dim->profile_ix);
+ cq = container_of(dim, struct mana_cq, dim);
+
+ cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX);
+ cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX);
+
+ mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true,
+ cur_moder.pkts, true);
+
+ dim->state = DIM_START_MEASURE;
+}
+
+static void mana_tx_dim_work(struct work_struct *work)
+{
+ struct dim *dim = container_of(work, struct dim, work);
+ struct dim_cq_moder cur_moder;
+ struct mana_cq *cq;
+
+ cur_moder = net_dim_get_tx_moderation(dim->mode, dim->profile_ix);
+ cq = container_of(dim, struct mana_cq, dim);
+
+ cur_moder.usec = min_t(u16, cur_moder.usec, MANA_INTR_MODR_USEC_MAX);
+ cur_moder.pkts = min_t(u16, cur_moder.pkts, MANA_INTR_MODR_COMP_MAX);
+
+ mana_gd_ring_dim(cq->gdma_cq, cur_moder.usec, true,
+ cur_moder.pkts, true);
+
+ dim->state = DIM_START_MEASURE;
+}
+
+/* The caller must update apc->rx/tx_dim_enabled before disabling and
+ * after enabling. And synchronize_net() before draining the DIM work,
+ * so that NAPI cannot observe a stale flag.
+ */
+void mana_dim_change(struct mana_cq *cq, bool enable)
+{
+ bool is_rx = cq->type == MANA_CQ_TYPE_RX;
+ struct mana_port_context *apc;
+ work_func_t work_func;
+ u32 usec, comp;
+
+ if (is_rx) {
+ apc = netdev_priv(cq->rxq->ndev);
+ usec = apc->intr_modr_rx_usec;
+ comp = apc->intr_modr_rx_comp;
+ work_func = mana_rx_dim_work;
+ } else {
+ apc = netdev_priv(cq->txq->ndev);
+ usec = apc->intr_modr_tx_usec;
+ comp = apc->intr_modr_tx_comp;
+ work_func = mana_tx_dim_work;
+ }
+
+ /* On enable, zero the DIM state so net_dim() starts measuring from
+ * scratch.
+ * On disable, drain any pending DIM work and restore the static
+ * moderation values.
+ */
+ if (enable) {
+ memset(&cq->dim, 0, sizeof(cq->dim));
+ cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE;
+ INIT_WORK(&cq->dim.work, work_func);
+ } else {
+ cancel_work_sync(&cq->dim.work);
+ mana_gd_ring_dim(cq->gdma_cq, usec, true, comp, true);
+ }
+}
+
+static void mana_update_rx_dim(struct mana_cq *cq)
+{
+ struct mana_port_context *apc = netdev_priv(cq->rxq->ndev);
+ struct dim_sample dim_sample = {};
+ struct mana_rxq *rxq = cq->rxq;
+
+ /* Pairs with smp_store_release() in mana_set_coalesce(): observing the
+ * enable flag set guarantees the DIM (re)initialization is visible.
+ */
+ if (!smp_load_acquire(&apc->rx_dim_enabled))
+ return;
+
+ dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats.packets,
+ rxq->stats.bytes, &dim_sample);
+ net_dim(&cq->dim, &dim_sample);
+}
+
+static void mana_update_tx_dim(struct mana_cq *cq)
+{
+ struct mana_port_context *apc = netdev_priv(cq->txq->ndev);
+ struct dim_sample dim_sample = {};
+
+ /* Pairs with smp_store_release() in mana_set_coalesce(): observing the
+ * enable flag set guarantees the DIM (re)initialization is visible.
+ */
+ if (!smp_load_acquire(&apc->tx_dim_enabled))
+ return;
+
+ /* cq->tx_dim_pkts/bytes are accumulated in mana_poll_tx_cq(), in the
+ * same NAPI context as this read, so they track the hardware
+ * completion rate and need no u64_stats_sync protection.
+ */
+ dim_update_sample(READ_ONCE(cq->dim_event_ctr), cq->tx_dim_pkts,
+ cq->tx_dim_bytes, &dim_sample);
+ net_dim(&cq->dim, &dim_sample);
+}
+
static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue)
{
struct mana_cq *cq = context;
@@ -2336,6 +2466,15 @@ static int mana_cq_handler(void *context, struct gdma_queue *gdma_queue)
if (w < cq->budget) {
mana_gd_ring_cq(gdma_queue, SET_ARM_BIT);
cq->work_done_since_doorbell = 0;
+
+ /* Update DIM before napi_complete_done() to prevent running
+ * net_dim() concurrently.
+ */
+ if (cq->type == MANA_CQ_TYPE_RX)
+ mana_update_rx_dim(cq);
+ else
+ mana_update_tx_dim(cq);
+
napi_complete_done(&cq->napi, w);
} else if (cq->work_done_since_doorbell >=
(cq->gdma_cq->queue_size / COMP_ENTRY_SIZE) * 4) {
@@ -2368,6 +2507,7 @@ static void mana_schedule_napi(void *context, struct gdma_queue *gdma_queue)
{
struct mana_cq *cq = context;
+ WRITE_ONCE(cq->dim_event_ctr, cq->dim_event_ctr + 1);
napi_schedule_irqoff(&cq->napi);
}
@@ -2410,6 +2550,7 @@ static void mana_destroy_txq(struct mana_port_context *apc)
if (apc->tx_qp[i]->txq.napi_initialized) {
napi_synchronize(napi);
napi_disable_locked(napi);
+ cancel_work_sync(&apc->tx_qp[i]->tx_cq.dim.work);
netif_napi_del_locked(napi);
apc->tx_qp[i]->txq.napi_initialized = false;
}
@@ -2543,6 +2684,11 @@ static int mana_create_txq(struct mana_port_context *apc,
cq_spec.modr_ctx_id = 0;
cq_spec.attached_eq = cq->gdma_cq->cq.parent->id;
+ /* DIM setting can be changed at runtime */
+ cq_spec.req_cq_moderation = true;
+ cq_spec.cq_moderation_usec = apc->intr_modr_tx_usec;
+ cq_spec.cq_moderation_comp = apc->intr_modr_tx_comp;
+
err = mana_create_wq_obj(apc, apc->port_handle, GDMA_SQ,
&wq_spec, &cq_spec,
&apc->tx_qp[i]->tx_object);
@@ -2573,6 +2719,13 @@ static int mana_create_txq(struct mana_port_context *apc,
set_bit(NAPI_STATE_NO_BUSY_POLL, &cq->napi.state);
netif_napi_add_locked(net, &cq->napi, mana_poll);
+
+ /* Initialize the DIM work before enabling NAPI, so that a poll
+ * cannot reach net_dim() with an uninitialized cq->dim.work.
+ */
+ INIT_WORK(&cq->dim.work, mana_tx_dim_work);
+ cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE;
+
napi_enable_locked(&cq->napi);
txq->napi_initialized = true;
@@ -2610,6 +2763,7 @@ static void mana_destroy_rxq(struct mana_port_context *apc,
napi_synchronize(napi);
napi_disable_locked(napi);
+ cancel_work_sync(&rxq->rx_cq.dim.work);
netif_napi_del_locked(napi);
}
@@ -2848,6 +3002,11 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
cq_spec.modr_ctx_id = 0;
cq_spec.attached_eq = cq->gdma_cq->cq.parent->id;
+ /* DIM setting can be changed at runtime */
+ cq_spec.req_cq_moderation = true;
+ cq_spec.cq_moderation_usec = apc->intr_modr_rx_usec;
+ cq_spec.cq_moderation_comp = apc->intr_modr_rx_comp;
+
err = mana_create_wq_obj(apc, apc->port_handle, GDMA_RQ,
&wq_spec, &cq_spec, &rxq->rxobj);
if (err)
@@ -2880,6 +3039,12 @@ static struct mana_rxq *mana_create_rxq(struct mana_port_context *apc,
WARN_ON(xdp_rxq_info_reg_mem_model(&rxq->xdp_rxq, MEM_TYPE_PAGE_POOL,
rxq->page_pool));
+ /* Initialize the DIM work before enabling NAPI, so that a poll
+ * cannot reach net_dim() with an uninitialized cq->dim.work.
+ */
+ INIT_WORK(&cq->dim.work, mana_rx_dim_work);
+ cq->dim.mode = DIM_CQ_PERIOD_MODE_START_FROM_EQE;
+
napi_enable_locked(&cq->napi);
mana_gd_ring_cq(cq->gdma_cq, SET_ARM_BIT);
@@ -3546,6 +3711,16 @@ static int mana_probe_port(struct mana_context *ac, int port_idx,
apc->link_cfg_error = 1;
apc->cqe_coalescing_enable = 0;
+ /* Initialize interrupt moderation settings if supported by HW */
+ if (gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION) {
+ apc->intr_modr_rx_usec = MANA_INTR_MODR_USEC_DEF;
+ apc->intr_modr_rx_comp = MANA_INTR_MODR_COMP_DEF;
+ apc->intr_modr_tx_usec = MANA_INTR_MODR_USEC_DEF;
+ apc->intr_modr_tx_comp = MANA_INTR_MODR_COMP_DEF;
+ apc->rx_dim_enabled = MANA_ADAPTIVE_RX_DEF;
+ apc->tx_dim_enabled = MANA_ADAPTIVE_TX_DEF;
+ }
+
mutex_init(&apc->vport_mutex);
apc->vport_use_count = 0;
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 881df597d7f9..9e31e2595ae3 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -419,6 +419,15 @@ static int mana_get_coalesce(struct net_device *ndev,
!kernel_coal->rx_cqe_nsecs)
kernel_coal->rx_cqe_nsecs = MANA_RX_CQE_NSEC_DEF;
+ ec->rx_coalesce_usecs = apc->intr_modr_rx_usec;
+ ec->rx_max_coalesced_frames = apc->intr_modr_rx_comp;
+
+ ec->tx_coalesce_usecs = apc->intr_modr_tx_usec;
+ ec->tx_max_coalesced_frames = apc->intr_modr_tx_comp;
+
+ ec->use_adaptive_rx_coalesce = apc->rx_dim_enabled;
+ ec->use_adaptive_tx_coalesce = apc->tx_dim_enabled;
+
return 0;
}
@@ -428,9 +437,34 @@ static int mana_set_coalesce(struct net_device *ndev,
struct netlink_ext_ack *extack)
{
struct mana_port_context *apc = netdev_priv(ndev);
- u8 saved_cqe_coalescing_enable;
+ struct {
+ u16 intr_modr_rx_usec;
+ u16 intr_modr_rx_comp;
+ u16 intr_modr_tx_usec;
+ u16 intr_modr_tx_comp;
+ u8 cqe_coalescing_enable;
+ bool rx_dim_enabled;
+ bool tx_dim_enabled;
+ } saved;
+ bool modr_changed = false;
+ bool dim_changed = false;
+ struct gdma_context *gc;
int err;
+ gc = apc->ac->gdma_dev->gdma_context;
+
+ /* Both static and dynamic interrupt moderation (DIM) rely on the
+ * same HW capability advertised by the PF.
+ */
+ if ((ec->use_adaptive_rx_coalesce || ec->use_adaptive_tx_coalesce ||
+ ec->rx_coalesce_usecs || ec->tx_coalesce_usecs ||
+ ec->rx_max_coalesced_frames || ec->tx_max_coalesced_frames) &&
+ !(gc->pf_cap_flags1 & GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION)) {
+ NL_SET_ERR_MSG(extack,
+ "Interrupt Moderation is not supported by HW");
+ return -EOPNOTSUPP;
+ }
+
if (kernel_coal->rx_cqe_frames != 1 &&
kernel_coal->rx_cqe_frames != MANA_RXCOMP_OOB_NUM_PPI) {
NL_SET_ERR_MSG_FMT(extack,
@@ -440,18 +474,129 @@ static int mana_set_coalesce(struct net_device *ndev,
return -EINVAL;
}
- saved_cqe_coalescing_enable = apc->cqe_coalescing_enable;
+ if (ec->rx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX ||
+ ec->tx_coalesce_usecs > MANA_INTR_MODR_USEC_MAX) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "coalesce usecs must be <= %lu",
+ MANA_INTR_MODR_USEC_MAX);
+ return -EINVAL;
+ }
+
+ if (ec->rx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX ||
+ ec->tx_max_coalesced_frames > MANA_INTR_MODR_COMP_MAX) {
+ NL_SET_ERR_MSG_FMT(extack,
+ "coalesce frames must be <= %lu",
+ MANA_INTR_MODR_COMP_MAX);
+ return -EINVAL;
+ }
+
+ if (ec->rx_coalesce_usecs != apc->intr_modr_rx_usec ||
+ ec->rx_max_coalesced_frames != apc->intr_modr_rx_comp ||
+ ec->tx_coalesce_usecs != apc->intr_modr_tx_usec ||
+ ec->tx_max_coalesced_frames != apc->intr_modr_tx_comp)
+ modr_changed = true;
+
+ saved.intr_modr_rx_usec = apc->intr_modr_rx_usec;
+ saved.intr_modr_rx_comp = apc->intr_modr_rx_comp;
+ saved.intr_modr_tx_usec = apc->intr_modr_tx_usec;
+ saved.intr_modr_tx_comp = apc->intr_modr_tx_comp;
+
+ apc->intr_modr_rx_usec = ec->rx_coalesce_usecs;
+ apc->intr_modr_rx_comp = ec->rx_max_coalesced_frames;
+ apc->intr_modr_tx_usec = ec->tx_coalesce_usecs;
+ apc->intr_modr_tx_comp = ec->tx_max_coalesced_frames;
+
+ if (!!ec->use_adaptive_rx_coalesce != apc->rx_dim_enabled ||
+ !!ec->use_adaptive_tx_coalesce != apc->tx_dim_enabled)
+ dim_changed = true;
+
+ saved.rx_dim_enabled = apc->rx_dim_enabled;
+ saved.tx_dim_enabled = apc->tx_dim_enabled;
+
+ saved.cqe_coalescing_enable = apc->cqe_coalescing_enable;
apc->cqe_coalescing_enable =
kernel_coal->rx_cqe_frames == MANA_RXCOMP_OOB_NUM_PPI;
- if (!apc->port_is_up)
+ if (!apc->port_is_up) {
+ WRITE_ONCE(apc->rx_dim_enabled, !!ec->use_adaptive_rx_coalesce);
+ WRITE_ONCE(apc->tx_dim_enabled, !!ec->use_adaptive_tx_coalesce);
return 0;
+ }
- err = mana_config_rss(apc, TRI_STATE_TRUE, false, false);
- if (err)
- apc->cqe_coalescing_enable = saved_cqe_coalescing_enable;
+ if (apc->cqe_coalescing_enable != saved.cqe_coalescing_enable) {
+ /* CQE coalescing setting is applied via RSS configuration. */
+ err = mana_config_rss(apc, TRI_STATE_TRUE, false, false);
+ if (err) {
+ netdev_err(ndev, "Change CQE coalescing failed: %d\n",
+ err);
+ apc->cqe_coalescing_enable =
+ saved.cqe_coalescing_enable;
+ apc->intr_modr_rx_usec = saved.intr_modr_rx_usec;
+ apc->intr_modr_rx_comp = saved.intr_modr_rx_comp;
+ apc->intr_modr_tx_usec = saved.intr_modr_tx_usec;
+ apc->intr_modr_tx_comp = saved.intr_modr_tx_comp;
+ return err;
+ }
+ }
- return err;
+ if (modr_changed || dim_changed) {
+ bool new_rx_dim = !!ec->use_adaptive_rx_coalesce;
+ bool new_tx_dim = !!ec->use_adaptive_tx_coalesce;
+ bool disable_rx_dim = saved.rx_dim_enabled && !new_rx_dim;
+ bool disable_tx_dim = saved.tx_dim_enabled && !new_tx_dim;
+ bool enable_rx_dim = !saved.rx_dim_enabled && new_rx_dim;
+ bool enable_tx_dim = !saved.tx_dim_enabled && new_tx_dim;
+ int q;
+
+ /* On disable: clear the per-port flag first and
+ * synchronize_net() so any in-flight NAPI poll observes
+ * the new value and will not schedule further DIM work;
+ * then drain pending work and restore the static
+ * moderation values.
+ */
+ if (disable_rx_dim)
+ WRITE_ONCE(apc->rx_dim_enabled, false);
+ if (disable_tx_dim)
+ WRITE_ONCE(apc->tx_dim_enabled, false);
+ if (disable_rx_dim || disable_tx_dim)
+ synchronize_net();
+
+ for (q = 0; q < apc->num_queues; q++) {
+ struct mana_cq *rx_cq = &apc->rxqs[q]->rx_cq;
+ struct mana_cq *tx_cq = &apc->tx_qp[q]->tx_cq;
+
+ if (disable_rx_dim)
+ mana_dim_change(rx_cq, false);
+ else if (enable_rx_dim)
+ mana_dim_change(rx_cq, true);
+ else if (!new_rx_dim && modr_changed)
+ mana_gd_ring_dim(rx_cq->gdma_cq,
+ apc->intr_modr_rx_usec, true,
+ apc->intr_modr_rx_comp, true);
+
+ if (disable_tx_dim)
+ mana_dim_change(tx_cq, false);
+ else if (enable_tx_dim)
+ mana_dim_change(tx_cq, true);
+ else if (!new_tx_dim && modr_changed)
+ mana_gd_ring_dim(tx_cq->gdma_cq,
+ apc->intr_modr_tx_usec, true,
+ apc->intr_modr_tx_comp, true);
+ }
+
+ /* Publish the enable flag with release semantics so a
+ * concurrent NAPI poll that observes it set also sees the DIM
+ * (re)init done by mana_dim_change() above.
+ */
+ if (enable_rx_dim)
+ /* pairs with smp_load_acquire() in mana_update_rx_dim() */
+ smp_store_release(&apc->rx_dim_enabled, true);
+ if (enable_tx_dim)
+ /* pairs with smp_load_acquire() in mana_update_tx_dim() */
+ smp_store_release(&apc->tx_dim_enabled, true);
+ }
+
+ return 0;
}
/* mana_set_channels - change the number of queues on a port
@@ -595,7 +740,13 @@ static int mana_get_link_ksettings(struct net_device *ndev,
}
const struct ethtool_ops mana_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES,
+ .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES |
+ ETHTOOL_COALESCE_RX_USECS |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES |
+ ETHTOOL_COALESCE_TX_USECS |
+ ETHTOOL_COALESCE_TX_MAX_FRAMES |
+ ETHTOOL_COALESCE_USE_ADAPTIVE_RX |
+ ETHTOOL_COALESCE_USE_ADAPTIVE_TX,
.op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS |
ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM |
ETHTOOL_OP_NEEDS_RTNL_GLINK,
diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h
index 0c395917b214..8529cef0d7c4 100644
--- a/include/net/mana/gdma.h
+++ b/include/net/mana/gdma.h
@@ -47,6 +47,7 @@ enum gdma_queue_type {
GDMA_RQ,
GDMA_CQ,
GDMA_EQ,
+ GDMA_DIM,
};
enum gdma_work_request_flags {
@@ -126,6 +127,17 @@ union gdma_doorbell_entry {
u64 tail_ptr : 31;
u64 arm : 1;
} eq;
+
+ struct {
+ u64 id : 24;
+ u64 reserved : 8;
+ u64 mod_usec : 10;
+ u64 reserve1 : 5;
+ u64 mod_usec_vld : 1;
+ u64 mod_comps : 8;
+ u64 reserve2 : 7;
+ u64 mod_comps_vld: 1;
+ } dim;
}; /* HW DATA */
struct gdma_msg_hdr {
@@ -502,6 +514,9 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit);
int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type);
+void mana_gd_ring_dim(struct gdma_queue *cq, u32 mod_usec, bool mod_usec_vld,
+ u32 mod_comps, bool mod_comps_vld);
+
struct gdma_wqe {
u32 reserved :24;
u32 last_vbytes :8;
@@ -650,6 +665,9 @@ enum {
/* Driver supports self recovery on Hardware Channel timeouts */
#define GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY BIT(25)
+/* Driver supports dynamic interrupt moderation - DIM */
+#define GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(28)
+
#define GDMA_DRV_CAP_FLAGS1 \
(GDMA_DRV_CAP_FLAG_1_EQ_SHARING_MULTI_VPORT | \
GDMA_DRV_CAP_FLAG_1_NAPI_WKDONE_FIX | \
@@ -665,7 +683,8 @@ enum {
GDMA_DRV_CAP_FLAG_1_PROBE_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \
GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \
- GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT)
+ GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT | \
+ GDMA_DRV_CAP_FLAG_1_DYN_INTERRUPT_MODERATION)
#define GDMA_DRV_CAP_FLAGS2 0
@@ -701,6 +720,9 @@ struct gdma_verify_ver_req {
u8 os_ver_str4[128];
}; /* HW DATA */
+/* HW supports dynamic interrupt moderation - DIM */
+#define GDMA_PF_CAP_FLAG_1_DYN_INTERRUPT_MODERATION BIT(15)
+
struct gdma_verify_ver_resp {
struct gdma_resp_hdr hdr;
u64 gdma_protocol_ver;
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 13c87baf018e..48f4445aa87a 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -4,6 +4,7 @@
#ifndef _MANA_H
#define _MANA_H
+#include <linux/dim.h>
#include <net/xdp.h>
#include <net/net_shaper.h>
@@ -64,6 +65,19 @@ enum TRI_STATE {
/* Maximum number of packets per coalesced CQE */
#define MANA_RXCOMP_OOB_NUM_PPI 4
+/* Default/max interrupt moderation settings */
+#define MANA_INTR_MODR_USEC_DEF 0
+#define MANA_INTR_MODR_COMP_DEF 0
+
+#define MANA_ADAPTIVE_RX_DEF true
+#define MANA_ADAPTIVE_TX_DEF true
+
+/* DIM doorbell value field layout */
+#define MANA_INTR_MODR_USEC_MAX GENMASK(9, 0)
+#define MANA_INTR_MODR_USEC_VLD BIT(15)
+#define MANA_INTR_MODR_COMP_MAX GENMASK(7, 0)
+#define MANA_INTR_MODR_COMP_MASK GENMASK(23, 16)
+
/* Update this count whenever the respective structures are changed */
#define MANA_STATS_RX_COUNT (6 + MANA_RXCOMP_OOB_NUM_PPI - 1)
#define MANA_STATS_TX_COUNT 11
@@ -297,6 +311,17 @@ struct mana_cq {
int work_done;
int work_done_since_doorbell;
int budget;
+
+ /* DIM - Dynamic Interrupt Moderation */
+ struct dim dim;
+ u16 dim_event_ctr;
+
+ /* Cumulative TX completions fed to DIM. Updated and read only in
+ * NAPI context (mana_poll_tx_cq() / mana_update_tx_dim()), so they
+ * measure the hardware completion rate and need no u64_stats_sync.
+ */
+ u64 tx_dim_pkts;
+ u64 tx_dim_bytes;
};
struct mana_recv_buf_oob {
@@ -573,6 +598,15 @@ struct mana_port_context {
u8 cqe_coalescing_enable;
u32 cqe_coalescing_timeout_ns;
+ /* Interrupt moderation settings */
+ u16 intr_modr_rx_usec;
+ u16 intr_modr_rx_comp;
+ u16 intr_modr_tx_usec;
+ u16 intr_modr_tx_comp;
+
+ bool rx_dim_enabled;
+ bool tx_dim_enabled;
+
struct mana_ethtool_stats eth_stats;
struct mana_ethtool_phy_stats phy_stats;
@@ -598,6 +632,8 @@ int mana_alloc_queues(struct net_device *ndev);
int mana_attach(struct net_device *ndev);
int mana_detach(struct net_device *ndev, bool from_close);
+void mana_dim_change(struct mana_cq *cq, bool enable);
+
int mana_probe(struct gdma_dev *gd, bool resuming);
void mana_remove(struct gdma_dev *gd, bool suspending);
@@ -633,6 +669,9 @@ struct mana_obj_spec {
u32 queue_size;
u32 attached_eq;
u32 modr_ctx_id;
+ u8 req_cq_moderation;
+ u16 cq_moderation_comp;
+ u16 cq_moderation_usec;
};
enum mana_command_code {
@@ -764,6 +803,15 @@ struct mana_create_wqobj_req {
u32 cq_size;
u32 cq_moderation_ctx_id;
u32 cq_parent_qid;
+
+ /* V2 */
+ u8 allow_rqwqe_chain;
+
+ /* V3 */
+ u8 req_cq_moderation;
+ u16 cq_moderation_comp;
+ u16 cq_moderation_usec;
+ u8 reserved2[2];
}; /* HW DATA */
struct mana_create_wqobj_resp {
@@ -771,6 +819,12 @@ struct mana_create_wqobj_resp {
u32 wq_id;
u32 cq_id;
mana_handle_t wq_obj;
+
+ /* V2 */
+ u16 cq_moderation_comp;
+ u16 cq_moderation_usec;
+ u8 cq_moderation_enabled;
+ u8 reserved1[3];
}; /* HW DATA */
/* Destroy WQ Object */
--
2.34.1
^ permalink raw reply related
* Re: [PATCH ipsec] xfrm: policy: use hlist_del_init_rcu in xfrm_hash_rebuild to avoid bydst poison
From: Xiang Mei @ 2026-07-02 22:11 UTC (permalink / raw)
To: Florian Westphal
Cc: steffen.klassert, herbert, davem, netdev, horms, edumazet, kuba,
pabeni, AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <aka5pwAGGI56QrrS@strlen.de>
On Thu, Jul 2, 2026 at 12:19 PM Florian Westphal <fw@strlen.de> wrote:
>
> Xiang Mei (Microsoft) <xmei5@asu.edu> wrote:
> > xfrm_hash_rebuild() unlinks each policy from its bydst chain with
> > hlist_del_rcu() and re-inserts it. For an inexact policy the re-insert goes
> > through xfrm_policy_inexact_insert(), which can fail on a GFP_ATOMIC
> > allocation; on failure the error path only WARN_ONCE()s and continues, so the
> > policy is left with a poisoned bydst node (LIST_POISON2). The next rebuild
> > calls hlist_del_rcu() on that node again, dereferences the poison, and takes a
> > general protection fault.
> >
> > Use hlist_del_init_rcu() instead, so a failed-reinsert node is left unhashed
> > (pprev == NULL) rather than poisoned. The next rebuild's hlist_del_init_rcu()
> > is then a no-op for it, and the non-failing case is unchanged.
> >
> > The reinsert allocation is GFP_ATOMIC (it runs under xfrm_policy_lock), so in
> > practice this is only reached under memory pressure; the crash below was
> > reproduced deterministically by forcing that allocation to fail with fault
> > injection (failslab).
> >
> > Crash:
> > Oops: general protection fault, probably for non-canonical address
> > 0xfbd59c0000000024: 0000 [#1] SMP KASAN NOPTI
> > KASAN: maybe wild-memory-access in range [0xdead000000000120-0xdead000000000127]
> > ...
> > Workqueue: events xfrm_hash_rebuild
> > RIP: 0010:xfrm_hash_rebuild+0x5b3/0x1190
> > RAX: dead000000000122 (LIST_POISON2 + offset)
> > ...
> > Call Trace:
> > hlist_del_rcu (include/linux/rculist.h:599)
> > xfrm_hash_rebuild (net/xfrm/xfrm_policy.c:1365)
> > process_one_work (kernel/workqueue.c:3322)
> > worker_thread (kernel/workqueue.c:3486)
> > kthread (kernel/kthread.c:436)
> > ret_from_fork (arch/x86/kernel/process.c:158)
> > ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
> > ...
> > Kernel panic - not syncing: Fatal exception in interrupt
> >
> > Fixes: 563d5ca93e88 ("xfrm: switch migrate to xfrm_policy_lookup_bytype")
> > Reported-by: AutonomousCodeSecurity@microsoft.com
> > Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
> > ---
> > net/xfrm/xfrm_policy.c | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
> > index 7ef861a0e823..2612a405542b 100644
> > --- a/net/xfrm/xfrm_policy.c
> > +++ b/net/xfrm/xfrm_policy.c
> > @@ -1362,7 +1362,7 @@ static void xfrm_hash_rebuild(struct work_struct *work)
> > if (xfrm_policy_is_dead_or_sk(policy))
> > continue;
> >
> > - hlist_del_rcu(&policy->bydst);
> > + hlist_del_init_rcu(&policy->bydst);
>
> This patch is dubious. I looks to me as if it papers over the
> actual bug.
>
> Why is there a memory allocation error?
>
> The first loop -- before unlink -- is supposed to preallocate the new
> bins and chain heads.
>
Agreed, and my patch hides it instead of avoiding it. The problem is
the prep loop's guard is inverted:
if (policy->selector.prefixlen_d < dbits ||
policy->selector.prefixlen_s < sbits)
continue;
That skips exactly the policies reinserted via the tree (prefixlen <
threshold => policy_hash_bysel() NULL => xfrm_policy_inexact_insert()), and
preallocates for the exact ones instead, which never allocate and get
pruned again at out_unlock. So the inexact bin/node is allocated GFP_ATOMIC
after the hlist_del_rcu(), and that's the failure the WARN catches.
The reproducer lowers then raises the threshold so those bins are pruned
and reallocated during reinsert; failslab just makes the failure
deterministic, OOM would do the same.
v2 inverts the guard so prep prepares the set that's actually reinserted:
- if (policy->selector.prefixlen_d < dbits ||
- policy->selector.prefixlen_s < sbits)
+ if (policy->selector.prefixlen_d >= dbits &&
+ policy->selector.prefixlen_s >= sbits)
continue;
Then inexact_insert() finds bin+node present and allocates nothing, so the
reinsert can't fail; a prep failure still hits goto out_unlock before any
unlink. hlist_del_rcu vs _init_ then no longer matters.
---
net/xfrm/xfrm_policy.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c
index 7ef861a0e823..932a313b9460 100644
--- a/net/xfrm/xfrm_policy.c
+++ b/net/xfrm/xfrm_policy.c
@@ -1329,8 +1329,8 @@ static void xfrm_hash_rebuild(struct work_struct *work)
}
}
- if (policy->selector.prefixlen_d < dbits ||
- policy->selector.prefixlen_s < sbits)
+ if (policy->selector.prefixlen_d >= dbits &&
+ policy->selector.prefixlen_s >= sbits)
continue;
bin = xfrm_policy_inexact_alloc_bin(policy, dir);
---
I checked the new patch on the reproducer, and the crash can't be triggered.
If you agree with this new patch, I'll send this as v2. I can also share
the reproducer if you need that to do further checks.
Xiang
Xiang
> This is also why there is a WARN. No memory allocations are supposed to
> occur after the hlist_del_rcu(), there is supposed to be a guarantee
> that the insertion succeeds.
>
^ permalink raw reply related
* [PATCH net v2] net/mlx5: free mlx5_st_idx_data on final dealloc
From: Zhiping Zhang @ 2026-07-02 22:24 UTC (permalink / raw)
To: Jason Gunthorpe, Leon Romanovsky, Saeed Mahameed Michael,
Tariq Toukan, Mark Bloch
Cc: Michael Guralnik, netdev, linux-rdma, linux-kernel, Zhiping Zhang,
stable
Workloads that repeatedly allocate and release mkeys carrying TPH
steering-tag hints (e.g. churning RDMA MRs) leak one
struct mlx5_st_idx_data per cycle; kmemleak flags it as unreferenced
and the kmalloc slab grows over time.
When the last reference to an ST table entry is dropped,
mlx5_st_dealloc_index() removed the entry from idx_xa but the backing
mlx5_st_idx_data allocation was never freed.
Free idx_data after the xa_erase() so the lifetime of the bookkeeping
struct matches the lifetime of the ST entry it tracks.
Cc: stable@vger.kernel.org
Fixes: 888a7776f4fb ("net/mlx5: Add support for device steering tag")
Reviewed-by: Michael Gur <michaelgur@nvidia.com>
Signed-off-by: Zhiping Zhang <zhipingz@meta.com>
---
v2: respin per maintainer-netdev.rst; no code change.
v1: https://lore.kernel.org/linux-rdma/20260612170406.3339093-1-zhipingz@meta.com/
drivers/net/ethernet/mellanox/mlx5/core/lib/st.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c
index 997be91f0a13..7cedc348790d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/st.c
@@ -175,6 +175,7 @@ int mlx5_st_dealloc_index(struct mlx5_core_dev *dev, u16 st_index)
if (refcount_dec_and_test(&idx_data->usecount)) {
xa_erase(&st->idx_xa, st_index);
+ kfree(idx_data);
/* We leave PCI config space as was before, no mkey will refer to it */
}
--
2.53.0-Meta
^ permalink raw reply related
* Re: [PATCH net-next] macsec: no longer rely on RTNL in macsec_fill_info()
From: Sabrina Dubroca @ 2026-07-02 22:30 UTC (permalink / raw)
To: Eric Dumazet
Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kuniyuki Iwashima, netdev, eric.dumazet, Andrew Lunn
In-Reply-To: <20260701094341.3218199-1-edumazet@google.com>
2026-07-01, 09:43:41 +0000, Eric Dumazet wrote:
> Add READ_ONCE()/WRITE_ONCE() annotations on fields that can be
> changed concurrently in macsec_changelink() and macsec_update_offload():
>
> - secy->key_len
> - secy->xpn
Those can't be changed, macsec_changelink() rejects
IFLA_MACSEC_CIPHER_SUITE (as well as IFLA_MACSEC_ICV_LEN and
IFLA_MACSEC_SCI) and key_len/xpn are fully linked to the cipher suite.
But I don't mind the extra READ/WRITE_ONCE if you don't want to
resend.
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
> - tx_sc->encoding_sa
> - tx_sc->encrypt
> - secy->protect_frames
> - tx_sc->send_sci
> - tx_sc->end_station
> - tx_sc->scb
> - secy->replay_protect
> - secy->validate_frames
> - secy->replay_window
> - macsec->offload
>
> This allows macsec_fill_info() to run locklessly without RTNL.
And at some point, the whole datapath probably needs some READ_ONCE()
sprinkled for those fields, too.
> @@ -3928,13 +3929,14 @@ static int macsec_changelink_common(struct net_device *dev,
> }
>
> if (data[IFLA_MACSEC_WINDOW]) {
> - secy->replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]);
> + u32 replay_window = nla_get_u32(data[IFLA_MACSEC_WINDOW]);
>
> /* IEEE 802.1AEbw-2013 10.7.8 - maximum replay window
> * for XPN cipher suites */
> if (secy->xpn &&
> - secy->replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW)
> + replay_window > MACSEC_XPN_MAX_REPLAY_WINDOW)
nit: no longer > 80 chars
--
Sabrina
^ permalink raw reply
* [PATCH net-next v2 0/6] net: hold instance lock around NETDEV_DOWN and NETDEV_GOING_DOWN
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
NETDEV_UP and NETDEV_REGISTER already run under the per-device
instance lock. The teardown side does not. Make it symmetric so
ops-locked drivers can rely on the lock being held in both
directions.
v2:
- reword NETDEV_UNREGISTER unlocked rationale (Jakub)
Stanislav Fomichev (6):
net: hold instance lock around NETDEV_DOWN/GOING_DOWN
net: dsa: hold instance lock on close-on-shutdown paths
net: mtk_eth_soc: hold instance lock around DMA-device-swap close
net: rtnetlink: take instance lock inside rtnl_configure_link
net: require instance lock for NETDEV_DOWN/GOING_DOWN notifiers
net: document NETDEV_UNREGISTER unlocked rationale
Documentation/networking/netdevices.rst | 7 +++++++
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 5 +++++
net/core/dev.c | 5 +++++
net/core/lock_debug.c | 4 ++--
net/core/rtnetlink.c | 17 ++++++++++-------
net/dsa/dsa.c | 20 +++++++++++++++++---
net/dsa/user.c | 19 +++++++++++++++++--
7 files changed, 63 insertions(+), 14 deletions(-)
--
2.53.0-Meta
^ permalink raw reply
* [PATCH net-next v2 1/6] net: hold instance lock around NETDEV_DOWN/GOING_DOWN
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260702224150.3730033-1-sdf@fomichev.me>
Mirror what call_netdevice_register_net_notifiers does but for the
teardown. Cover only DOWN and GOING_DOWN. UNREGISTER is still unlocked
because of the SW devices using dev_xxx methods.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/core/dev.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e0..9d49493f4fb5 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1912,9 +1912,11 @@ static void call_netdevice_unregister_notifiers(struct notifier_block *nb,
struct net_device *dev)
{
if (dev->flags & IFF_UP) {
+ netdev_lock_ops(dev);
call_netdevice_notifier(nb, NETDEV_GOING_DOWN,
dev);
call_netdevice_notifier(nb, NETDEV_DOWN, dev);
+ netdev_unlock_ops(dev);
}
call_netdevice_notifier(nb, NETDEV_UNREGISTER, dev);
}
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v2 2/6] net: dsa: hold instance lock on close-on-shutdown paths
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260702224150.3730033-1-sdf@fomichev.me>
netif_close_many will soon assert ops lock (for locked DOWN/GOING_DOWN).
Update dsa_switch_shutdown to manually grab and release the ops lock.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/dsa/dsa.c | 20 +++++++++++++++++---
net/dsa/user.c | 19 +++++++++++++++++--
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 9cb732f6b1e3..da53a666d4b8 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -18,6 +18,7 @@
#include <linux/of.h>
#include <linux/of_net.h>
#include <net/dsa_stubs.h>
+#include <net/netdev_lock.h>
#include <net/sch_generic.h>
#include "conduit.h"
@@ -1620,10 +1621,23 @@ void dsa_switch_shutdown(struct dsa_switch *ds)
rtnl_lock();
- dsa_switch_for_each_cpu_port(dp, ds)
- list_add(&dp->conduit->close_list, &close_list);
+ dsa_switch_for_each_cpu_port(dp, ds) {
+ if (!(dp->conduit->flags & IFF_UP))
+ continue;
+ list_add_tail(&dp->conduit->close_list, &close_list);
+ netdev_lock_ops(dp->conduit);
+ }
+
+ netif_close_many(&close_list, false);
- netif_close_many(&close_list, true);
+ while (!list_empty(&close_list)) {
+ struct net_device *conduit;
+
+ conduit = list_first_entry(&close_list, struct net_device,
+ close_list);
+ netdev_unlock_ops(conduit);
+ list_del_init(&conduit->close_list);
+ }
dsa_switch_for_each_user_port(dp, ds) {
conduit = dsa_port_to_conduit(dp);
diff --git a/net/dsa/user.c b/net/dsa/user.c
index 8704c1a3a5b7..8ea47444d6d5 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -13,6 +13,7 @@
#include <linux/of_net.h>
#include <linux/of_mdio.h>
#include <linux/mdio.h>
+#include <net/netdev_lock.h>
#include <net/rtnetlink.h>
#include <net/pkt_cls.h>
#include <net/selftests.h>
@@ -3600,10 +3601,24 @@ static int dsa_user_netdevice_event(struct notifier_block *nb,
if (dp->cpu_dp != cpu_dp)
continue;
- list_add(&dp->user->close_list, &close_list);
+ if (!(dp->user->flags & IFF_UP))
+ continue;
+
+ list_add_tail(&dp->user->close_list, &close_list);
+ netdev_lock_ops(dp->user);
}
- netif_close_many(&close_list, true);
+ netif_close_many(&close_list, false);
+
+ while (!list_empty(&close_list)) {
+ struct net_device *user_dev;
+
+ user_dev = list_first_entry(&close_list,
+ struct net_device,
+ close_list);
+ netdev_unlock_ops(user_dev);
+ list_del_init(&user_dev->close_list);
+ }
return NOTIFY_OK;
}
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v2 3/6] net: mtk_eth_soc: hold instance lock around DMA-device-swap close
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260702224150.3730033-1-sdf@fomichev.me>
netif_close_many will soon assert ops lock (for locked DOWN/GOING_DOWN).
Update mtk_eth_set_dma_device to manually grab and release the ops lock.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
drivers/net/ethernet/mediatek/mtk_eth_soc.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 5d291e50a47b..fe7610c42e5d 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -26,6 +26,7 @@
#include <linux/bitfield.h>
#include <net/dsa.h>
#include <net/dst_metadata.h>
+#include <net/netdev_lock.h>
#include <net/page_pool/helpers.h>
#include <linux/genalloc.h>
@@ -5030,10 +5031,14 @@ void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev)
continue;
list_add_tail(&dev->close_list, &dev_list);
+ netdev_lock_ops(dev);
}
netif_close_many(&dev_list, false);
+ list_for_each_entry(dev, &dev_list, close_list)
+ netdev_unlock_ops(dev);
+
eth->dma_dev = dma_dev;
list_for_each_entry_safe(dev, tmp, &dev_list, close_list) {
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v2 4/6] net: rtnetlink: take instance lock inside rtnl_configure_link
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260702224150.3730033-1-sdf@fomichev.me>
rtnl_configure_link calls __dev_change_flags() and __dev_notify_flags,
both need the instance lock. rtnl_newlink_create grabs it but stacked
devices do not. Move the lock inside rtnl_configure_link.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/core/rtnetlink.c | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 12aa3aa1688b..1b7d6f6b8b68 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -3660,14 +3660,16 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm,
u32 portid, const struct nlmsghdr *nlh)
{
unsigned int old_flags, changed;
- int err;
+ int err = 0;
+
+ netdev_lock_ops(dev);
old_flags = dev->flags;
if (ifm && (ifm->ifi_flags || ifm->ifi_change)) {
err = __dev_change_flags(dev, rtnl_dev_combine_flags(dev, ifm),
NULL);
if (err < 0)
- return err;
+ goto out;
}
changed = old_flags ^ dev->flags;
@@ -3677,7 +3679,10 @@ int rtnl_configure_link(struct net_device *dev, const struct ifinfomsg *ifm,
}
__dev_notify_flags(dev, old_flags, changed, portid, nlh);
- return 0;
+
+out:
+ netdev_unlock_ops(dev);
+ return err;
}
EXPORT_SYMBOL(rtnl_configure_link);
@@ -3918,22 +3923,20 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
goto out;
}
- netdev_lock_ops(dev);
-
err = rtnl_configure_link(dev, ifm, portid, nlh);
if (err < 0)
goto out_unregister;
if (tb[IFLA_MASTER]) {
+ netdev_lock_ops(dev);
err = do_set_master(dev, nla_get_u32(tb[IFLA_MASTER]), extack);
+ netdev_unlock_ops(dev);
if (err)
goto out_unregister;
}
- netdev_unlock_ops(dev);
out:
return err;
out_unregister:
- netdev_unlock_ops(dev);
if (ops->newlink) {
LIST_HEAD(list_kill);
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v2 5/6] net: require instance lock for NETDEV_DOWN/GOING_DOWN notifiers
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260702224150.3730033-1-sdf@fomichev.me>
Sprinkle a few asserts about ops lock: netif_close_many and __dev_notify_flags
should now consistently run under the lock
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/netdevices.rst | 2 ++
net/core/dev.c | 3 +++
net/core/lock_debug.c | 4 ++--
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index d2a238f8cc8b..1bb68a73bb67 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -421,6 +421,8 @@ For devices with locked ops, currently only the following notifiers are
* ``NETDEV_CHANGENAME``
* ``NETDEV_REGISTER``
* ``NETDEV_UP``
+* ``NETDEV_DOWN``
+* ``NETDEV_GOING_DOWN``
The following notifiers are running without the lock:
* ``NETDEV_UNREGISTER``
diff --git a/net/core/dev.c b/net/core/dev.c
index 9d49493f4fb5..714d05283500 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1802,6 +1802,7 @@ void netif_close_many(struct list_head *head, bool unlink)
__dev_close_many(head);
list_for_each_entry_safe(dev, tmp, head, close_list) {
+ netdev_assert_locked_ops_compat(dev);
rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP | IFF_RUNNING, GFP_KERNEL, 0, NULL);
call_netdevice_notifiers(NETDEV_DOWN, dev);
if (unlink)
@@ -9787,6 +9788,8 @@ void __dev_notify_flags(struct net_device *dev, unsigned int old_flags,
{
unsigned int changes = dev->flags ^ old_flags;
+ netdev_assert_locked_ops_compat(dev);
+
if (gchanges)
rtmsg_ifinfo(RTM_NEWLINK, dev, gchanges, GFP_ATOMIC, portid, nlh);
diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c
index 8a81c5430705..abc4c00728b1 100644
--- a/net/core/lock_debug.c
+++ b/net/core/lock_debug.c
@@ -24,15 +24,15 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event,
case NETDEV_CHANGE:
case NETDEV_REGISTER:
case NETDEV_UP:
+ case NETDEV_DOWN:
+ case NETDEV_GOING_DOWN:
netdev_assert_locked_ops_compat(dev);
fallthrough;
- case NETDEV_DOWN:
case NETDEV_REBOOT:
case NETDEV_UNREGISTER:
case NETDEV_CHANGEMTU:
case NETDEV_CHANGEADDR:
case NETDEV_PRE_CHANGEADDR:
- case NETDEV_GOING_DOWN:
case NETDEV_FEAT_CHANGE:
case NETDEV_BONDING_FAILOVER:
case NETDEV_PRE_UP:
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v2 6/6] net: document NETDEV_UNREGISTER unlocked rationale
From: Stanislav Fomichev @ 2026-07-02 22:41 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni
In-Reply-To: <20260702224150.3730033-1-sdf@fomichev.me>
The lock-state table marks UNREGISTER as unlocked without saying
why. Add a short note that many handlers release the lowers via
dev_close().
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/netdevices.rst | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index 1bb68a73bb67..c8b15f6926ed 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -427,6 +427,11 @@ For devices with locked ops, currently only the following notifiers are
The following notifiers are running without the lock:
* ``NETDEV_UNREGISTER``
+Many SW devices (uppers) catch their lower's ``NETDEV_UNREGISTER``
+events and may interact with them via ``dev_*()`` handlers, which take
+the instance lock. Until we convert these devices to ``netif_*()`` variants,
+``NETDEV_UNREGISTER`` stays unlocked.
+
There are no clear expectations for the remaining notifiers. Notifiers not on
the list may run with or without the instance lock, potentially even invoking
the same notifier type with and without the lock from different code paths.
--
2.53.0-Meta
^ permalink raw reply related
* Re: [REGRESSION][BISECTED] tun/tap & vhost-net: multi-threaded network performance
From: Michael S. Tsirkin @ 2026-07-02 22:44 UTC (permalink / raw)
To: Brett Sheffield
Cc: Simon Schippers, regressions, netdev, Jakub Kicinski, Tim Gebauer,
Willem de Bruijn, Jason Wang, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, linux-kernel
In-Reply-To: <akZGg96-Xu9VeGrw@karahi.librecast.net>
On Thu, Jul 02, 2026 at 01:07:47PM +0200, Brett Sheffield wrote:
> On 2026-07-02 09:24, Simon Schippers wrote:
> > On 7/1/26 22:56, Michael S. Tsirkin wrote:
> > > On Wed, Jul 01, 2026 at 09:16:48PM +0200, Brett Sheffield wrote:
> > >> TL;DR - Commit 1d6e569b7d0c0b2736636749e4be0a27f3cefcb3 causes
> > >> significant performance regressions with TAP interfaces and multithreaded
> > >> network code. Please revert.
> > >>
> > >>
> > >> Librecast is an IPv6 multicast library. One of the tests (0055) fails under
> > >> Linux 7.2-rc1. The test performs data synchronization over IPv6 multicast using a TAP
> > >> interface. This test has run successfully on every stable, LTS and mainline RC
> > >> released in the past year. Every kernel with my Tested-by has run this test.
> > >>
> > >> There have been a bunch of changes to MLDv2 so I started bisecting there, but
> > >> the culprit is actually 1d6e569b7d0c0b2736636749e4be0a27f3cefcb3 "tun/tap &
> > >> vhost-net: avoid ptr_ring tail-drop when a qdisc is present"
> > >>
> > >> Reverting this commit fixes the test.
> > >>
> > >> To eliminate my code and any multicast weirdness, I ran tests with iperf3
> > >> comparing the same host running 7.2-rc1 both with and without 1d6e569b7d0
> > >> reverted.
> >
> > Thank you very much for your bisect!
> >
> > As the author, I am sorry for that regression!
>
> No worries. That's why we test :-)
>
> > > - does it help to increase the tun queue size?
> >
> > I agree, this would be great to know.
> >
> > However, even then we must act. I am considering IFF_BACKPRESSURE
> > as a feature flag, defaulting to off. It would just enable/disable
> > the stopping logic in tun_net_xmit() and the waking logic
> > in __tun_wake_queue(). If disabled, it would result in the same logic
> > as before.
> >
> > I could provide such a patch as [net] material.
>
> I'm going to make myself a strong cup of tea and dig into it a bit more here and
> will let you know if I find anything worth reporting.
>
> If you need me to try re-testing with specific settings or test a patch I'm
> happy to do so.
>
> Cheers,
>
>
> Brett
> --
> Brett Sheffield (he/him)
> Librecast - Decentralising the Internet with Multicast
> https://librecast.net/
> https://blog.brettsheffield.com/
Well, the issue was with host to guest right?
Then testing what does bql do might be interesting.
Might help.
Something like this? Lightly tested.
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index bfa49fa9e3a1..abc46354c107 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -1076,6 +1076,7 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
queue = netdev_get_tx_queue(dev, txq);
spin_lock(&tfile->tx_ring.producer_lock);
+ netdev_tx_sent_queue(queue, len);
ret = __ptr_ring_produce(&tfile->tx_ring, skb);
if (!qdisc_txq_has_no_queue(queue) &&
__ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
@@ -1088,6 +1089,7 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
spin_unlock(&tfile->tx_ring.producer_lock);
if (ret) {
+ netdev_tx_completed_queue(queue, 1, len);
/* This should be a rare case if a qdisc is present, but
* can happen due to lltx.
* Since skb_tx_timestamp(), skb_orphan(),
@@ -2148,15 +2150,19 @@ static ssize_t tun_put_user(struct tun_struct *tun,
/* Callers must hold ring.consumer_lock */
static void __tun_wake_queue(struct tun_struct *tun,
- struct tun_file *tfile, int consumed)
+ struct tun_file *tfile,
+ unsigned int pkts, unsigned int bytes)
{
struct netdev_queue *txq = netdev_get_tx_queue(tun->dev,
tfile->queue_index);
+ if (bytes)
+ netdev_tx_completed_queue(txq, pkts, bytes);
+
/* Paired with smp_mb__after_atomic() in tun_net_xmit() */
smp_mb();
if (netif_tx_queue_stopped(txq)) {
- tfile->cons_cnt += consumed;
+ tfile->cons_cnt += pkts;
if (tfile->cons_cnt >= tfile->tx_ring.size / 2 ||
__ptr_ring_empty(&tfile->tx_ring)) {
netif_tx_wake_queue(txq);
@@ -2167,12 +2173,16 @@ static void __tun_wake_queue(struct tun_struct *tun,
static void *tun_ring_consume(struct tun_struct *tun, struct tun_file *tfile)
{
+ unsigned int bytes = 0;
void *ptr;
spin_lock(&tfile->tx_ring.consumer_lock);
ptr = __ptr_ring_consume(&tfile->tx_ring);
- if (ptr)
- __tun_wake_queue(tun, tfile, 1);
+ if (ptr) {
+ if (!tun_is_xdp_frame(ptr))
+ bytes = ((struct sk_buff *)ptr)->len;
+ __tun_wake_queue(tun, tfile, 1, bytes);
+ }
spin_unlock(&tfile->tx_ring.consumer_lock);
return ptr;
@@ -3805,7 +3815,7 @@ struct ptr_ring *tun_get_tx_ring(struct file *file)
EXPORT_SYMBOL_GPL(tun_get_tx_ring);
/* Callers must hold ring.consumer_lock */
-void tun_wake_queue(struct file *file, int consumed)
+void tun_wake_queue(struct file *file, unsigned int pkts, unsigned int bytes)
{
struct tun_file *tfile;
struct tun_struct *tun;
@@ -3821,7 +3831,7 @@ void tun_wake_queue(struct file *file, int consumed)
tun = rcu_dereference(tfile->tun);
if (tun)
- __tun_wake_queue(tun, tfile, consumed);
+ __tun_wake_queue(tun, tfile, pkts, bytes);
rcu_read_unlock();
}
diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index db341c922673..5267b323bd59 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -181,14 +181,23 @@ static int vhost_net_buf_produce(struct sock *sk,
{
struct file *file = sk->sk_socket->file;
struct vhost_net_buf *rxq = &nvq->rxq;
+ unsigned int bytes = 0;
+ int i;
rxq->head = 0;
spin_lock(&nvq->rx_ring->consumer_lock);
rxq->tail = __ptr_ring_consume_batched(nvq->rx_ring, rxq->queue,
VHOST_NET_BATCH);
- if (rxq->tail)
- tun_wake_queue(file, rxq->tail);
+ if (rxq->tail) {
+ for (i = 0; i < rxq->tail; i++) {
+ void *ptr = rxq->queue[i];
+
+ if (!tun_is_xdp_frame(ptr))
+ bytes += ((struct sk_buff *)ptr)->len;
+ }
+ tun_wake_queue(file, rxq->tail, bytes);
+ }
spin_unlock(&nvq->rx_ring->consumer_lock);
return rxq->tail;
diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h
index 5f3e206c7a73..49b85bf4f828 100644
--- a/include/linux/if_tun.h
+++ b/include/linux/if_tun.h
@@ -22,7 +22,7 @@ struct tun_msg_ctl {
#if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE)
struct socket *tun_get_socket(struct file *);
struct ptr_ring *tun_get_tx_ring(struct file *file);
-void tun_wake_queue(struct file *file, int consumed);
+void tun_wake_queue(struct file *file, unsigned int pkts, unsigned int bytes);
static inline bool tun_is_xdp_frame(void *ptr)
{
@@ -56,7 +56,8 @@ static inline struct ptr_ring *tun_get_tx_ring(struct file *f)
return ERR_PTR(-EINVAL);
}
-static inline void tun_wake_queue(struct file *f, int consumed) {}
+static inline void tun_wake_queue(struct file *f,
+ unsigned int pkts, unsigned int bytes) {}
static inline bool tun_is_xdp_frame(void *ptr)
{
^ permalink raw reply related
* [PATCH bpf] bpf: reject mini-sockets in bpf_sock_destroy()
From: Xiang Mei (Microsoft) @ 2026-07-02 22:45 UTC (permalink / raw)
To: ast, daniel, andrii, eddyz87, memxor, bpf
Cc: netdev, martin.lau, song, yonghong.song, jolsa, emil,
john.fastabend, sdf, aditi.ghag, AutonomousCodeSecurity,
tgopinath, kys, linux-kernel, Xiang Mei (Microsoft)
bpf_sock_destroy() casts its struct sock_common * argument to a full
struct sock and reads sk->sk_protocol. The BPF tcp iterator can pass a
TIME_WAIT or NEW_SYN_RECV mini-socket, which only embeds a sock_common
prefix. Unlike sk_prot (which aliases skc_prot inside sock_common),
sk_protocol lives beyond that prefix, so the read goes out of bounds of
the small tw_sock_TCP object (type confusion).
Reject non-full sockets with sk_fullsock() before touching any
full-sock field. sk_fullsock() only reads sk_state (in sock_common),
and these mini-sockets have no ->diag_destroy(), This matches the other
sock_common consumers in this file, e.g. bpf_skc_to_tcp_sock(), which
already sk_fullsock() before reading sk_protocol.
BUG: KASAN: slab-out-of-bounds in bpf_sock_destroy (net/core/filter.c:12673)
Read of size 2 at addr ffff888013ffc71c by task exploit/143
Call Trace:
kasan_report (mm/kasan/report.c:595)
bpf_sock_destroy (net/core/filter.c:12673)
bpf_prog_8b5bd55c189cabc9_sock_destroy_tw+0x31/0x3e
bpf_iter_run_prog (kernel/bpf/bpf_iter.c:697)
bpf_iter_tcp_seq_show (net/ipv4/tcp_ipv4.c:3247)
bpf_seq_read (kernel/bpf/bpf_iter.c:184)
vfs_read (fs/read_write.c:572)
ksys_read (fs/read_write.c:716)
do_syscall_64 (arch/x86/entry/syscall_64.c:94)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
...
The buggy address belongs to the object at ffff888013ffc640
which belongs to the cache tw_sock_TCP of size 256
The buggy address is located 220 bytes inside of
allocated 256-byte region [ffff888013ffc640, ffff888013ffc740)
Fixes: 4ddbcb886268 ("bpf: Add bpf_sock_destroy kfunc")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
---
net/core/filter.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/core/filter.c b/net/core/filter.c
index 69ec1a4c0f9d..a0fcafb08b96 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -12665,6 +12665,9 @@ __bpf_kfunc int bpf_sock_destroy(struct sock_common *sock)
{
struct sock *sk = (struct sock *)sock;
+ if (!sk_fullsock(sk))
+ return -EOPNOTSUPP;
+
/* The locking semantics that allow for synchronous execution of the
* destroy handlers are only supported for TCP and UDP.
* Supporting protocols will need to acquire sock lock in the BPF context
--
2.43.0
^ permalink raw reply related
* [PATCH] net: phy: marvell: Add soft reset for 88E1510
From: Ben Brown @ 2026-07-02 22:50 UTC (permalink / raw)
To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni
Cc: linux-kernel, netdev, chris.packham, Ben Brown
When bringing down then up the link on a 88e1512 phy a link is not
getting established. This is because the phy is coming out of reset then
immediately getting configured. During configuration the page is
unsuccessfully updated causing writes to the wrong registers.
Add the soft reset function that does a reset then polling read waiting
for the phy to come back online, at which stage the page register can be
updated successfully.
This was tested on a 88E1512 phy, using ip link to bring up/down the
link.
Signed-off-by: Ben Brown <ben.brown@alliedtelesis.co.nz>
---
drivers/net/phy/marvell.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c
index 7a578b5aa2ed..1ae75141408a 100644
--- a/drivers/net/phy/marvell.c
+++ b/drivers/net/phy/marvell.c
@@ -3922,6 +3922,7 @@ static struct phy_driver marvell_drivers[] = {
.features = PHY_GBIT_FIBRE_FEATURES,
.flags = PHY_POLL_CABLE_TEST,
.probe = marvell_probe,
+ .soft_reset = genphy_soft_reset,
.config_init = m88e1510_config_init,
.config_aneg = m88e1510_config_aneg,
.read_status = marvell_read_status,
--
2.54.0
^ permalink raw reply related
* Re: [REGRESSION][BISECTED] tun/tap & vhost-net: multi-threaded network performance
From: Michael S. Tsirkin @ 2026-07-02 22:55 UTC (permalink / raw)
To: Brett Sheffield
Cc: Simon Schippers, regressions, netdev, Jakub Kicinski, Tim Gebauer,
Willem de Bruijn, Jason Wang, Andrew Lunn, David S. Miller,
Eric Dumazet, Paolo Abeni, linux-kernel
In-Reply-To: <akZGg96-Xu9VeGrw@karahi.librecast.net>
On Thu, Jul 02, 2026 at 01:07:47PM +0200, Brett Sheffield wrote:
> On 2026-07-02 09:24, Simon Schippers wrote:
> > On 7/1/26 22:56, Michael S. Tsirkin wrote:
> > > On Wed, Jul 01, 2026 at 09:16:48PM +0200, Brett Sheffield wrote:
> > >> TL;DR - Commit 1d6e569b7d0c0b2736636749e4be0a27f3cefcb3 causes
> > >> significant performance regressions with TAP interfaces and multithreaded
> > >> network code. Please revert.
> > >>
> > >>
> > >> Librecast is an IPv6 multicast library. One of the tests (0055) fails under
> > >> Linux 7.2-rc1. The test performs data synchronization over IPv6 multicast using a TAP
> > >> interface. This test has run successfully on every stable, LTS and mainline RC
> > >> released in the past year. Every kernel with my Tested-by has run this test.
> > >>
> > >> There have been a bunch of changes to MLDv2 so I started bisecting there, but
> > >> the culprit is actually 1d6e569b7d0c0b2736636749e4be0a27f3cefcb3 "tun/tap &
> > >> vhost-net: avoid ptr_ring tail-drop when a qdisc is present"
> > >>
> > >> Reverting this commit fixes the test.
> > >>
> > >> To eliminate my code and any multicast weirdness, I ran tests with iperf3
> > >> comparing the same host running 7.2-rc1 both with and without 1d6e569b7d0
> > >> reverted.
> >
> > Thank you very much for your bisect!
> >
> > As the author, I am sorry for that regression!
>
> No worries. That's why we test :-)
>
> > > - does it help to increase the tun queue size?
> >
> > I agree, this would be great to know.
> >
> > However, even then we must act. I am considering IFF_BACKPRESSURE
> > as a feature flag, defaulting to off. It would just enable/disable
> > the stopping logic in tun_net_xmit() and the waking logic
> > in __tun_wake_queue(). If disabled, it would result in the same logic
> > as before.
> >
> > I could provide such a patch as [net] material.
>
> I'm going to make myself a strong cup of tea and dig into it a bit more here and
> will let you know if I find anything worth reporting.
>
> If you need me to try re-testing with specific settings or test a patch I'm
> happy to do so.
>
> Cheers,
>
>
> Brett
> --
> Brett Sheffield (he/him)
> Librecast - Decentralising the Internet with Multicast
> https://librecast.net/
> https://blog.brettsheffield.com/
Maybe it's the supposedly rare case? Does this change anything
for you?
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index bfa49fa9e3a1..bacd89460078 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -1018,7 +1018,6 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
struct netdev_queue *queue;
struct tun_file *tfile;
int len = skb->len;
- int ret;
rcu_read_lock();
tfile = rcu_dereference(tun->tfiles[txq]);
@@ -1064,19 +1063,24 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
goto drop;
}
- skb_tx_timestamp(skb);
-
- /* Orphan the skb - required as we might hang on to it
- * for indefinite time.
- */
- skb_orphan(skb);
-
- nf_reset_ct(skb);
-
queue = netdev_get_tx_queue(dev, txq);
spin_lock(&tfile->tx_ring.producer_lock);
- ret = __ptr_ring_produce(&tfile->tx_ring, skb);
+ if (__ptr_ring_check_produce(&tfile->tx_ring)) {
+ spin_unlock(&tfile->tx_ring.producer_lock);
+ netif_tx_stop_queue(queue);
+ smp_mb__after_atomic();
+ if (!__ptr_ring_check_produce(&tfile->tx_ring))
+ netif_tx_wake_queue(queue);
+ rcu_read_unlock();
+ return NETDEV_TX_BUSY;
+ }
+
+ skb_tx_timestamp(skb);
+ skb_orphan(skb);
+ nf_reset_ct(skb);
+
+ __ptr_ring_produce(&tfile->tx_ring, skb);
if (!qdisc_txq_has_no_queue(queue) &&
__ptr_ring_check_produce(&tfile->tx_ring) == -ENOSPC) {
netif_tx_stop_queue(queue);
@@ -1087,18 +1091,6 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
}
spin_unlock(&tfile->tx_ring.producer_lock);
- if (ret) {
- /* This should be a rare case if a qdisc is present, but
- * can happen due to lltx.
- * Since skb_tx_timestamp(), skb_orphan(),
- * run_ebpf_filter() and pskb_trim() could have tinkered
- * with the SKB, returning NETDEV_TX_BUSY is unsafe and
- * we must drop instead.
- */
- drop_reason = SKB_DROP_REASON_FULL_RING;
- goto drop;
- }
-
/* dev->lltx requires to do our own update of trans_start */
txq_trans_cond_update(queue);
^ permalink raw reply related
* Re: [PATCH] net: phy: marvell: Add soft reset for 88E1510
From: Andrew Lunn @ 2026-07-02 23:04 UTC (permalink / raw)
To: Ben Brown
Cc: hkallweit1, linux, davem, edumazet, kuba, pabeni, linux-kernel,
netdev, chris.packham
In-Reply-To: <20260702225034.1062457-1-ben.brown@alliedtelesis.co.nz>
On Fri, Jul 03, 2026 at 10:50:34AM +1200, Ben Brown wrote:
> When bringing down then up the link on a 88e1512 phy a link is not
> getting established. This is because the phy is coming out of reset then
> immediately getting configured. During configuration the page is
> unsuccessfully updated causing writes to the wrong registers.
>
> Add the soft reset function that does a reset then polling read waiting
> for the phy to come back online, at which stage the page register can be
> updated successfully.
>
> This was tested on a 88E1512 phy, using ip link to bring up/down the
> link.
What makes the 88E1512 special that it needs this, but no other
Marvell PHY does?
Andrew
^ permalink raw reply
* Re: [PATCH net] af_unix: fix listen() succeeding on sockets in the wrong state
From: Kuniyuki Iwashima @ 2026-07-02 23:17 UTC (permalink / raw)
To: John Ericson
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
John Ericson, Simon Horman, Christian Brauner, David Rheinsberg,
Cong Wang, Sergei Zimmerman, netdev, linux-kernel
In-Reply-To: <20260702202018.2280336-1-John.Ericson@Obsidian.Systems>
On Thu, Jul 2, 2026 at 1:20 PM John Ericson
<John.Ericson@obsidian.systems> wrote:
>
> From: John Ericson <mail@johnericson.me>
>
> Commit fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for
> reaped sk->sk_peer_pid") inserted a `prepare_peercred()` call between
> `err = -EINVAL` and the socket-state check in `unix_listen()`. Since
> `prepare_peercred()` leaves `err` at 0 on success, `listen()` on an
> AF_UNIX socket that is not in `TCP_CLOSE` or `TCP_LISTEN` state (e.g.
> one that is already connected) now silently returns success without
> doing anything, instead of failing with `EINVAL` as it did before.
>
> To fix this bug, and avoid such bugs in the future, switch to a style
> where `err = -E...;` instead happens right before the `goto`. (`err =
> other_function(...);` is not changed.) Then there is no spooky-action-
> at-a-distance between the `err` initialization and the `goto`, something
> which is easier to slip by code review.
>
> Fixes: fd0a109a0f6b ("net, pidfs: prepare for handing out pidfds for reaped sk->sk_peer_pid")
> Assisted-by: Claude:claude-fable-5
> Signed-off-by: John Ericson <mail@johnericson.me>
> ---
> net/unix/af_unix.c | 14 +++++++++-----
> 1 file changed, 9 insertions(+), 5 deletions(-)
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index f7a9d55eee8a..7878b27bbaf8 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -813,18 +813,22 @@ static int unix_listen(struct socket *sock, int backlog)
> struct unix_sock *u = unix_sk(sk);
> struct unix_peercred peercred = {};
>
> - err = -EOPNOTSUPP;
> - if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET)
> + if (sock->type != SOCK_STREAM && sock->type != SOCK_SEQPACKET) {
> + err = -EOPNOTSUPP;
> goto out; /* Only stream/seqpacket sockets accept */
> - err = -EINVAL;
> - if (!READ_ONCE(u->addr))
> + }
> + if (!READ_ONCE(u->addr)) {
> + err = -EINVAL;
> goto out; /* No listens on an unbound socket */
> + }
> err = prepare_peercred(&peercred);
> if (err)
> goto out;
> unix_state_lock(sk);
> - if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN)
> + if (sk->sk_state != TCP_CLOSE && sk->sk_state != TCP_LISTEN) {
> + err = -EINVAL;
Could you keep this part only for net.git and follow up on the
cleanup part in net-next later ?
> goto out_unlock;
> + }
> if (backlog > sk->sk_max_ack_backlog)
> wake_up_interruptible_all(&u->peer_wait);
> sk->sk_max_ack_backlog = backlog;
> --
> 2.54.0
>
^ permalink raw reply
* [PATCH net] net: emac: mal: fix W1C write race in ICINTSTAT clearing
From: Rosen Penev @ 2026-07-02 23:49 UTC (permalink / raw)
To: netdev
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Jeff Garzik, David Gibson, open list
The ICINTSTAT register is write-1-to-clear (W1C). The read-modify-write
pattern in both mal_txeob() and mal_rxeob() can lose interrupts: if a bit
that should not be cleared is already asserted when mfdcri() reads the
register, it is included in the read value, retained by the bitwise OR, and
then written back as 1 - inadvertently clearing a pending but unhandled
interrupt.
Fix by writing only the specific bit to clear (ICINTSTAT_ICTX for TXEOB,
ICINTSTAT_ICRX for RXEOB). W1C semantics guarantee that writing 0 to the
other bits has no effect.
Fixes: 1d3bb996481e ("Device tree aware EMAC driver")
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/ethernet/ibm/emac/mal.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/ibm/emac/mal.c b/drivers/net/ethernet/ibm/emac/mal.c
index 4025bc36ae16..eab7a487bf08 100644
--- a/drivers/net/ethernet/ibm/emac/mal.c
+++ b/drivers/net/ethernet/ibm/emac/mal.c
@@ -282,8 +282,7 @@ static irqreturn_t mal_txeob(int irq, void *dev_instance)
#ifdef CONFIG_PPC_DCR_NATIVE
if (mal_has_feature(mal, MAL_FTR_CLEAR_ICINTSTAT))
- mtdcri(SDR0, DCRN_SDR_ICINTSTAT,
- (mfdcri(SDR0, DCRN_SDR_ICINTSTAT) | ICINTSTAT_ICTX));
+ mtdcri(SDR0, DCRN_SDR_ICINTSTAT, ICINTSTAT_ICTX);
#endif
return IRQ_HANDLED;
@@ -302,8 +301,7 @@ static irqreturn_t mal_rxeob(int irq, void *dev_instance)
#ifdef CONFIG_PPC_DCR_NATIVE
if (mal_has_feature(mal, MAL_FTR_CLEAR_ICINTSTAT))
- mtdcri(SDR0, DCRN_SDR_ICINTSTAT,
- (mfdcri(SDR0, DCRN_SDR_ICINTSTAT) | ICINTSTAT_ICRX));
+ mtdcri(SDR0, DCRN_SDR_ICINTSTAT, ICINTSTAT_ICRX);
#endif
return IRQ_HANDLED;
--
2.55.0
^ permalink raw reply related
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