Re: [PATCH 4/4] net: dsa: soce: Add initial driver support for MRS switches
Vasilij Strassheim <[email protected]> Mon, 03 Aug 2026 18:44:56 +0200
| Newsgroups | dev.linux.lists.sashiko-reviews,org.kernel.vger.linux-devicetree |
|---|---|
| Organization | Linutronix GmbH |
| Message-ID | <[email protected]> |
On Thu, 2026-07-30 at 16:37 +0000, [email protected] wrote: > Thank you for your contribution! Sashiko AI review found 6 potential issu= e(s) to consider: > - [High] Incorrect error check for `devm_of_iomap()` leads to potential N= ULL pointer dereference on mapping failure. > - [High] The `ds->phys_mii_mask` is never initialized, causing the driver= 's custom MDIO bus to silently ignore all PHY reads and writes. > - [High] Concurrent MDIO accesses via `slave_mii_bus` and `user_mii_bus` = race on shared hardware registers, causing state corruption. > - [Medium] Missing bitmasks on input parameters in MDIO hardware register= construction allows out-of-bounds values to corrupt adjacent register fiel= ds. > - [Medium] The driver unconditionally issues a flush command to the hardw= are FDB controller even if the previous command is still being processed. > - [Medium] Asynchronous, switch-wide FDB flush is performed instead of a = synchronous, per-port flush. > -- >=20 > --- Patch [4]: [PATCH 4/4] net: dsa: soce: Add initial driver support for= MRS switches --- > commit 4ad10a20f72f216349a16415f424608c5d3e5c86 > Author: Vasilij Strassheim <[email protected]> >=20 > net: dsa: soce: Add initial driver support for MRS switches >=20 > This commit adds initial support for SoC-e switches based on FPGA IP core= s. > It provides MDIO access helpers for supported IP register layouts and com= mon > STP/FDB handling. >=20 > > diff --git a/drivers/net/dsa/soce/soce_common.c b/drivers/net/dsa/soce/= soce_common.c > > index 0000000000000..f7f345a690e46 > > --- /dev/null > > +++ b/drivers/net/dsa/soce/soce_common.c >=20 > [ ... ] >=20 > > +static void soce_fast_age(struct dsa_switch *ds, int port, > > + const struct soce_layout *layout) > > +{ > > + struct soce_priv *priv =3D ds->priv; > > + struct soce_dsa_local *local; > > + void __iomem *p_mes_ctrl; > > + u32 val; > > + > > + local =3D &priv->local; > > + if (!layout || !local->base_addr) > > + return; > > + > > + p_mes_ctrl =3D local->base_addr + layout->fdb_ctrl; > > + if (readl_poll_timeout(p_mes_ctrl, val, > > + !(val & SOCE_FDB_CLEAR_UNDER_PROCESS), > > + SOCE_FAST_AGE_POLL_US, SOCE_FAST_AGE_TIMEOUT_US)) > > + dev_warn(ds->dev, "SoC-e fast_age timeout on port %d\n", port); > > + > > + writel(SOCE_FDB_CLEAR_FLUSH_MSTID, p_mes_ctrl); >=20 > [Severity: Medium] > Does this code continue to issue the flush command even if the hardware i= s > still processing the previous one? If readl_poll_timeout() fails, it prin= ts > a warning but unconditionally executes the writel() below it. This code will be reworked or removed in next version. >=20 > [Severity: Medium] > Will this flush the entire MSTID globally rather than just the specified = port? > The command ignores the port argument. Additionally, it appears to return > immediately without waiting for the clear operation to complete. >=20 This code will be reworked or removed in next version. > > +} >=20 > [ ... ] >=20 > > diff --git a/drivers/net/dsa/soce/soce_dsa_core.c b/drivers/net/dsa/soc= e/soce_dsa_core.c > > index 0000000000000..b88d00c56d718 > > --- /dev/null > > +++ b/drivers/net/dsa/soce/soce_dsa_core.c >=20 > [ ... ] >=20 > > +static int soce_phy_read(struct dsa_switch *ds, int port, int regnum) > > +{ > > + struct soce_priv *priv =3D ds->priv; > > + struct soce_dsa_local *local; > > + > > + local =3D &priv->local; > > + if (!local->mdio_ops || !local->mdio_ops->phy_read) > > + return 0xffff; > > + > > + return local->mdio_ops->phy_read(ds, port, regnum); > > +} >=20 > [Severity: High] > Can concurrent MDIO accesses race on the shared hardware registers here? > The DSA core implicitly registers the slave_mii_bus, and the driver expli= citly > registers a user_mii_bus. Both buses have their own independent mdio_lock > mutex, but they share the same underlying MMIO routines. >=20 This will be reworked based on other review comments. I'll check afterward = if the problem still exists. > [ ... ] >=20 > > +static int soce_sw_probe(struct mdio_device *mdiodev) > > +{ > [ ... ] > > + local->base_addr =3D devm_of_iomap(dev, switch_node, 0, NULL); > > + if (IS_ERR(local->base_addr)) { > > + dev_err(dev, "failed to map switch register space for %pOF\n", > > + switch_node); > > + of_node_put(switch_node); > > + return PTR_ERR(local->base_addr); > > + } >=20 > [Severity: High] > Does devm_of_iomap() return NULL on failure rather than an error pointer? > Checking IS_ERR() here will evaluate to false if it returns NULL, leading= to a > potential NULL pointer dereference later when accessing the MMIO base. >=20 No, this pattern is used in other files and proposed in devres.c: \* Usage example: \* \* base =3D devm_of_iomap(&pdev->dev, node, 0, NULL); \* if (IS_ERR(base)) \* return PTR_ERR(base); > [ ... ] >=20 > > diff --git a/drivers/net/dsa/soce/soce_mdio.c b/drivers/net/dsa/soce/so= ce_mdio.c > > index 0000000000000..241fc373de8b8 > > --- /dev/null > > +++ b/drivers/net/dsa/soce/soce_mdio.c >=20 > [ ... ] >=20 > > +static int soce_user_phy_read(struct mii_bus *bus, int addr, int reg) > > +{ > > + struct dsa_switch *ds =3D bus->priv; > > + > > + if (ds->phys_mii_mask & BIT(addr)) > > + return ds->ops->phy_read(ds, addr, reg); > > + > > + return 0xffff; > > +} >=20 > [Severity: High] > Is ds->phys_mii_mask ever initialized? It appears to default to 0 when > allocated in soce_sw_probe(). If it is always 0, this check will fail and > all user PHY reads will silently return 0xffff. >=20 Yes, ds->phys_mii_mask is initialized in dsa.c dsa_switch_setup(): /\* Initialize ds->phys_mii_mask before registering the user MDIO bus =20 \* driver and before ops->setup() has run, since the switch drivers and = =20 \* the user MDIO bus driver rely on these values for probing PHY =20 \* devices or not =20 \*/ =20 ds->phys_mii_mask |=3D dsa_user_ports(ds); > [ ... ] >=20 > > +int soce_user_mii_bus_register(struct dsa_switch *ds) > > +{ > [ ... ] > > + ds->user_mii_bus->parent =3D ds->dev; > > + ds->user_mii_bus->phy_mask =3D ~ds->phys_mii_mask; >=20 > [Severity: High] > Since ds->phys_mii_mask defaults to 0, will setting phy_mask to the inver= ted > value instruct the MDIO core to ignore all PHYs and prevent probing entir= ely? >=20 ds->phys_mii_mask is valid here, see the comment above. > [ ... ] >=20 > > +int soce_mdio_20_03_write(struct dsa_switch *ds, int portnumber, int r= egnum, > > + u16 val) > > +{ > [ ... ] > > + /* Then write control */ > > + regvalue =3D (mdio->phy_addr << SOCE_MDIO_20_03_CTRL_PHY_ADDR_SHIFT) = | > > + (regnum << SOCE_MDIO_20_03_CTRL_REG_ADDR_SHIFT) | > > + SOCE_MDIO_20_03_CTRL_OP_WRITE | > > + SOCE_MDIO_20_03_CTRL_START | > > + mdio->mdio_output; > > + writel(regvalue, ctrl); >=20 > [Severity: Medium] > Should the phy_addr and regnum inputs be bounded or masked before shiftin= g? > If an oversized regnum is passed in via userspace ioctls, or a malformed = DT > provides a large soce,phy-addr, the shifted bits could overflow into adja= cent > control register fields. >=20 Good point, I will fix that. > [ ... ] >=20 > > +static int soce_mdio_23_02_read_c22(struct dsa_switch *ds, int portnum= ber, > > + int regnum) > > +{ > [ ... ] > > + regvalue =3D (regnum << SOCE_MDIO_23_02_CTRL_REGADDRDEVTYPE_OFFSET) + > > + (mdio->phy_addr << SOCE_MDIO_23_02_CTRL_PHYADDR_OFFSET); > > + writel(regvalue, params); >=20 > [Severity: Medium] > Similarly here, can the addition of unmasked values risk arithmetic overf= low > into adjacent bit ranges if the inputs are out-of-bounds? >=20 I will also fix that in the next version.