* [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind
@ 2026-09-18 1:50 Aleksei Sviridkin
2026-09-18 1:50 ` [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 Aleksei Sviridkin
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Aleksei Sviridkin @ 2026-09-18 1:50 UTC (permalink / raw)
To: netdev
Cc: chester.a.unal, daniel, andrew, olteanv, gerg, davem, edumazet,
kuba, pabeni, matthias.bgg, angelogioacchino.delregno,
linux-kernel, linux-arm-kernel, linux-mediatek,
Aleksei Sviridkin
Unbinding the MT7530 driver from an MT7531 dereferences NULL in
regulator_disable(). On a Netcraze NC-1012 (MT7981B + MT7531, 6.18.44):
# echo mdio-bus:1f > /sys/bus/mdio_bus/drivers/mt7530-mdio/unbind
oopses there, and the build it was found on sets CONFIG_PANIC_ON_OOPS, so
the board goes down with it. Fix that and the same command gets as far as
mt7530_remove_common(), which disposes interrupt mappings the switch's own
regmap-irq chip still owns; the regmap-irq thread then faults in
handle_nested_irq() later in the same teardown. rmmod reaches both, since
mdio_module_driver() calls .remove on module exit.
Patch 1 is the regulator one. mt7530_probe() requests the core and io
supplies only for ID_MT7530 and mt7530_setup() enables them under the same
test, but mt7530_remove() disables them unconditionally, so on an MT7621 or
an MT7531 both pointers are still NULL from devm_kzalloc(). It reaches the
MDIO front end only.
Patch 2 is the interrupt one, and it reaches further.
mt7530_remove_common() disposes the per-PHY interrupt mappings by hand from
.remove, while the regmap-irq chip that owns the domain is devm-registered
and its parent interrupt is only freed once .remove has returned.
regmap_del_irq_chip() disposes the same mappings itself, in an order that
cannot race, so the driver's call adds nothing but a window. That helper is
called from both front ends, so the defect also covers the MMIO parts -
MT7988, EN7581, AN7583 and EN7528 - which have no regulators and never meet
the first defect at all.
The order is not arbitrary. On an MT7531 the regulator fault happens in the
first thing mt7530_remove() does with the switch, so execution never
reaches the interrupt defect. The second only became visible once the first
was fixed, which is also how both came to be found on one board.
Found and verified there. Without patch 1 the unbind panics in
regulator_disable(); with patch 1 alone the panic moves on to
handle_nested_irq(); with both, three unbind/bind cycles run, two back to
back and a third after a pause. In the two whose dmesg was captured, each
unbind removes the switch from the driver directory and takes lan1 to lan4
with it, each bind brings them back, and lan1 relinks at 1Gbps/full after
both binds, lan4 after the second. uptime rose from 58 to 202 seconds
across the three without resetting and pstore gained no new record. The
third cycle stayed unbound long enough to read the descriptors: no mt7530
line in /proc/interrupts and no irq/79, irq/80 or irq/81 directory, and the
next bind reuses those three numbers - regmap-irq freeing and disposing
what the driver no longer touches. The kernel under test was identified by
the sha256 of its ELF notes section, read from /sys/kernel/notes on the
running board and computed in advance from the flashed image.
What hardware could not answer here. There is no MT7530 or MT7621 part on
this bench, so the ID_MT7530 branch that patch 1 adds was checked by
reading the generated code rather than by running it, and no MMIO part was
available to exercise patch 2 on that front end either. One unrelated WARN
remains across the unbind, from sysfs_remove_link() under
dsa_user_destroy(); it is a separate DSA teardown-ordering defect and is
not addressed here.
v2:
- patch 2 changes shape. Moving dsa_unregister_switch() in front of the
dispose, as v1 did, only covers the phylib side: the regmap-irq chip is
devm-registered, so its parent interrupt outlives .remove and the thread
can still dispatch on a mapping the driver has just disposed. Dropping
the call is what closes that: regmap_del_irq_chip() walks every hwirq
below chip->num_irqs and disposes each one that maps, which covers
whatever the driver created - the driver's own set is the user ports
below MT7530_NUM_PHYS, hwirq 0 to 2 on the board below - and it does so
after freeing the parent interrupt and before removing the domain.
- patch 2 points Fixes: at commit 254f6b272e3b ("dsa: mt7530: Utilize
REGMAP_IRQ for interrupt handling") instead of commit ba751e28d442 ("net:
dsa: mt7530: add interrupt support"). Before the regmap-irq conversion
the driver owned the domain and removed it by hand, where
irq_domain_remove() disposes nothing, so the call was required there; it
became redundant when regmap-irq took the domain over.
- patch 1: same diff; the trailers are reordered and the message now names
devm_kzalloc() as where the NULL comes from.
- v1: https://lore.kernel.org/netdev/20260914202421.2737079-1-f@lex.la/
Aleksei Sviridkin (2):
net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq
drivers/net/dsa/mt7530-mdio.c | 18 ++++++++++--------
drivers/net/dsa/mt7530.c | 3 ---
2 files changed, 10 insertions(+), 11 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
2026-09-18 1:50 [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind Aleksei Sviridkin
@ 2026-09-18 1:50 ` Aleksei Sviridkin
2026-09-18 1:58 ` Andrew Lunn
2026-09-22 2:15 ` netdev-bot+sashiko
2026-09-18 1:50 ` [PATCH net v2 2/2] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq Aleksei Sviridkin
2026-09-24 16:20 ` [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind patchwork-bot+netdevbpf
2 siblings, 2 replies; 7+ messages in thread
From: Aleksei Sviridkin @ 2026-09-18 1:50 UTC (permalink / raw)
To: netdev
Cc: chester.a.unal, daniel, andrew, olteanv, gerg, davem, edumazet,
kuba, pabeni, matthias.bgg, angelogioacchino.delregno,
linux-kernel, linux-arm-kernel, linux-mediatek,
Aleksei Sviridkin
The core and io supplies are only requested for ID_MT7530: both the
devm_regulator_get() in probe and the regulator_enable() in
mt7530_setup() are guarded by the switch id, but mt7530_remove()
disables them unconditionally. On an MT7621 or an MT7531 both pointers
are still NULL from devm_kzalloc(), so rmmod or a sysfs unbind calls
regulator_disable() on NULL.
Fixes: ddda1ac116c8 ("net: dsa: mt7530: support the 7530 switch on the Mediatek MT7621 SoC")
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
Found by accident on a Netcraze NC-1012 (MT7981B + MT7531, 6.18.44) while
looking for a way to tear a DSA port down at runtime:
# echo mdio-bus:1f > /sys/bus/mdio_bus/drivers/mt7530-mdio/unbind
Unable to handle kernel access to user memory outside uaccess routines
at virtual address 0000000000000078
pc : regulator_disable+0x14/0x48
lr : mt7530_remove+0x1c/0x80
...
x0 : 0000000000000000
Call trace:
regulator_disable+0x14/0x48 (P)
mt7530_remove+0x1c/0x80
mdio_remove+0x20/0x40
device_remove+0x68/0x80
device_release_driver_internal+0x1cc/0x220
device_driver_detach+0x14/0x20
unbind_store+0xac/0xb0
...
Kernel panic - not syncing: Oops: Fatal exception
The oops itself is a process-context oops that kills the writing task. It
became a panic and a reboot because OpenWrt's generic kernel config sets
CONFIG_PANIC_ON_OOPS=y and this target does not override it, not because of
anything local to this bench. With CONFIG_REGULATOR=n the stub
regulator_disable() returns 0 and nothing is dereferenced at all -
NET_DSA_MT7530 neither selects nor depends on REGULATOR - so the severity
is config-dependent, and the commit message states the mechanism rather
than an outcome.
x0 is the regulator pointer and regulator_disable() reads regulator->rdev
straight away, so the NULL comes from the field never being assigned rather
than from an error pointer: with CONFIG_REGULATOR=y devm_regulator_get()
hands back a valid pointer or an ERR_PTR, and on anything but ID_MT7530 it
is never called at all.
The same shape applies to MT7621, which mt7530_of_match also binds. The
MMIO driver is unaffected: it makes no regulator calls at all, though it
does still carry the include.
The id test is used rather than a NULL check because the driver already
says "these supplies belong to ID_MT7530" that way in the other two places
it matters: the devm_regulator_get() pair in mt7530_probe() and the
regulator_set_voltage()/regulator_enable() pair in mt7530_setup(). A NULL
check would be a third spelling of the same condition.
Tested on the board above. Without the patch the unbind panics as shown;
both pointers come out of devm_kzalloc() and are never assigned on an
MT7531, so the fault is structural rather than timing-dependent. With this
patch plus patch 2, the same unbind runs to completion: mdio-bus:1f leaves
/sys/bus/mdio_bus/drivers/mt7530-mdio/, lan1-lan4 disappear, the kernel
prints "DSA: tree 0 torn down", and uptime does not reset. The kernel under
test was identified by the sha256 of its ELF notes section, read from
/sys/kernel/notes on the running board and computed in advance from the
image that was flashed.
dmesg is not silent across that unbind. It gains one WARN - a single cut
here/WARNING/end trace block - from sysfs_remove_link() under
dsa_user_destroy() reaching an already-removed netdev directory: "kernfs:
can not remove 'phydev', no directory". That is a DSA teardown-ordering
defect rather than a regulator one, and in this run it fired on the first
unbind after boot. dmesg is where it shows: pstore gained no new record
across the run, but pstore records only oopses and panics and could not
have caught a WARN.
That run needs patch 2 on top, a different defect in the same teardown:
mt7530_remove_common() disposes the per-PHY interrupt mappings from .remove
while the regmap-irq chip that owns the domain is still live, and the board
dies in handle_nested_irq() later in the same teardown. With this patch
alone, the panic moves from regulator_disable+0x14 to that second defect,
and mt7530_remove is reached at +0x24/0x90 rather than +0x1c/0x80 - which
is the point: the NULL dereference is gone and execution now gets past it.
The disable stays where it is, ahead of mt7530_remove_common() and so ahead
of the register writes dsa_unregister_switch() makes on the way down. On a
real MT7530 that is teardown talking to a switch whose rails are already
off, which is a separate and pre-existing ordering question, not addressed
here; moving it would change when the supplies drop, which is more than a
NULL-pointer fix should do.
Not tested: the ID_MT7530 branch, which must still disable both rails.
There is no MT7530 or MT7621 hardware here, so the disassembly stands in
for it - before the change mt7530_remove() falls from the priv NULL check
straight into ldr x0, [x19, #40] / bl regulator_disable; after it, ldr w0,
[x19, #72] (priv->id) / cbz w0 gates both calls, and ID_MT7530 is 0. Built
with W=1, no warnings; checkpatch --strict clean.
drivers/net/dsa/mt7530-mdio.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c
index 784dd58a7158..de42f70afcfa 100644
--- a/drivers/net/dsa/mt7530-mdio.c
+++ b/drivers/net/dsa/mt7530-mdio.c
@@ -227,15 +227,17 @@ mt7530_remove(struct mdio_device *mdiodev)
if (!priv)
return;
- ret = regulator_disable(priv->core_pwr);
- if (ret < 0)
- dev_err(priv->dev,
- "Failed to disable core power: %d\n", ret);
+ if (priv->id == ID_MT7530) {
+ ret = regulator_disable(priv->core_pwr);
+ if (ret < 0)
+ dev_err(priv->dev,
+ "Failed to disable core power: %d\n", ret);
- ret = regulator_disable(priv->io_pwr);
- if (ret < 0)
- dev_err(priv->dev, "Failed to disable io pwr: %d\n",
- ret);
+ ret = regulator_disable(priv->io_pwr);
+ if (ret < 0)
+ dev_err(priv->dev, "Failed to disable io pwr: %d\n",
+ ret);
+ }
mt7530_remove_common(priv);
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v2 2/2] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq
2026-09-18 1:50 [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind Aleksei Sviridkin
2026-09-18 1:50 ` [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 Aleksei Sviridkin
@ 2026-09-18 1:50 ` Aleksei Sviridkin
2026-09-24 16:20 ` [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind patchwork-bot+netdevbpf
2 siblings, 0 replies; 7+ messages in thread
From: Aleksei Sviridkin @ 2026-09-18 1:50 UTC (permalink / raw)
To: netdev
Cc: chester.a.unal, daniel, andrew, olteanv, gerg, davem, edumazet,
kuba, pabeni, matthias.bgg, angelogioacchino.delregno,
linux-kernel, linux-arm-kernel, linux-mediatek,
Aleksei Sviridkin
mt7530_remove_common() disposes the per-PHY interrupt mappings from
.remove, but the regmap-irq chip that owns the domain is devm-registered,
so its parent interrupt is only freed once .remove has returned. The
switch's own regmap-irq thread can therefore still dispatch on a mapping
that is already gone: irq_find_mapping() returns 0, irq_to_desc() returns
NULL and handle_nested_irq() locks desc->lock without checking it. The
attached PHYs have not given those interrupts back yet either, which the
kernel warns about a moment before the fault.
regmap_del_irq_chip() disposes the same mappings itself, after freeing the
parent interrupt and before removing the domain, so there is nothing left
for the driver to do here. Until it runs the descriptors stay alive, and a
late dispatch on one of them is harmless: dsa_unregister_switch() has freed
the PHY handlers by then, so handle_nested_irq() finds no action and
returns.
Fixes: 254f6b272e3b ("dsa: mt7530: Utilize REGMAP_IRQ for interrupt handling")
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
Found on a Netcraze NC-1012 (MT7981B + MT7531, 6.18.44) directly behind the
regulator fix in patch 1: with that one applied the unbind stops faulting
in mt7530_remove() and reaches the teardown, where the kernel says what is
wrong in words before it dies.
# echo mdio-bus:1f > /sys/bus/mdio_bus/drivers/mt7530-mdio/unbind
remove_proc_entry: removing non-empty directory 'irq/81', leaking at least
'mt7530-0:02'
WARNING: CPU: 0 PID: 4629 at remove_proc_entry+0x1d0/0x1f0
...
Call trace:
remove_proc_entry+0x1d0/0x1f0 (P)
unregister_irq_proc+0xd0/0x104
free_desc+0x38/0xa0
irq_free_descs+0x64/0x98
irq_dispose_mapping+0x70/0x14c
mt7530_free_mdio_irq+0x5c/0x60
mt7530_remove_common+0x1c/0x30
mt7530_remove+0x24/0x90
mdio_remove+0x20/0x40
device_remove+0x68/0x80
device_release_driver_internal+0x1cc/0x220
device_driver_detach+0x14/0x20
unbind_store+0xac/0xb0
...
Unable to handle kernel read from unreadable memory at virtual address
00000000000000ac
pc : handle_nested_irq+0x28/0x168
...
Call trace:
handle_nested_irq+0x28/0x168 (P)
regmap_irq_thread+0x19c/0x2e8
irq_thread_fn+0x28/0x88
irq_thread+0x18c/0x28c
kthread+0xe4/0x1ac
ret_from_fork+0x10/0x20
Kernel panic - not syncing: Oops: Fatal exception
The WARN comes from unregister_irq_proc() under irq_free_descs(), fired for
a mapping a PHY still holds. The captured record shows one, for
mt7530-0:02, and already carries the W taint bit, so at least one earlier
WARN fell outside the ramoops window. Later in the same teardown, and in
the same ramoops record, the switch's own regmap-irq thread - PID 627, Comm
irq/53-mt7530 - dispatches for a mapping that is already gone:
irq_find_mapping() returns 0, irq_to_desc() returns NULL and
handle_nested_irq() takes desc->lock on it, which is the read at virtual
address 0xac in the trace.
The mappings regmap-irq disposes are a superset of the driver's.
mt7530_setup_mdio_irq() maps hwirq p for each user port p below
MT7530_NUM_PHYS - at most 0 to 4, and 0 to 2 on the board below, since the
loop tests ds->phys_mii_mask. regmap_del_irq_chip() walks hwirq 0 to
chip->num_irqs and skips only entries whose mask is zero; mt7530_irqs[] is
written with designated initialisers up to [31], so num_irqs is 32 with 12
zero-mask holes, none of them below 5 - hwirq 0 to 4 carry masks 0x1 to
0x10. A devicetree that gives the PHYs their own interrupts lands on the
same hwirqs, since regmap_domain_ops uses irq_domain_xlate_onetwocell;
today the driver disposes those too without ever having created them, and
after this patch the remove path no longer does. mt7530_free_mdio_irq()
does nothing but dispose - it neither removes the domain nor clears
bus->irq[] - so the call is the whole of what goes away.
The devres order is the right way round as well: mt7530_setup_irq()
registers the chip before mt7530_setup_mdio() registers the bus, so the bus
is released first and the chip after, and regmap_del_irq_chip() frees the
parent interrupt before it disposes anything.
Fixes names the regmap-irq conversion rather than the 2021 commit that put
this call in .remove. Before 254f6b272e3b the driver created the domain
with irq_domain_add_linear() and tore it down in mt7530_free_irq_common(),
where irq_domain_remove() disposes nothing, so mt7530_free_mdio_irq() was
required there. The conversion handed both the parent interrupt and the
domain to regmap-irq and left the call behind.
The two remaining callers are error paths in mt7530_setup_mdio() and
mt753x_setup(), reached before probe completes, and only one of them can
run in a given probe: a failing mt7530_setup_mdio() returns from
mt753x_setup() before the second is reached. An early dispose there costs
nothing anyway, because regmap_del_irq_chip() looks each hwirq up again and
only disposes the ones that still map. Dropping those calls is a cleanup,
not a fix, so they stay.
Tested on the board above with both patches applied, on a kernel identified
by the sha256 of its ELF notes section - read from /sys/kernel/notes on the
running board and computed in advance from the flashed image. Three
unbind/bind cycles. In the two whose dmesg was captured, each unbind
dropped mdio-bus:1f from the driver directory and took lan1 to lan4 with
it, each bind brought them back, lan1 relinked at 1Gbps/full after both and
lan4 after the second; the third logged interrupt descriptors instead, as
below. uptime rose from 58 to 202 seconds across the three cycles without
resetting and pstore gained no record. At the end of the two logged cycles
dmesg carried no handle_nested_irq, no Oops and no remove_proc_entry line,
against 82 lines mentioning mt7530 in that same dmesg, so those zeros are
absences and not a broken grep; the third cycle re-read the first two
counters, still zero, against 91. One unrelated WARN remains, on the first
unbind only: sysfs_remove_link() under dsa_user_destroy(), a separate DSA
teardown-ordering defect.
The third cycle was left unbound for a moment to look at the descriptors.
/proc/interrupts then had no mt7530 line at all - the parent 53 gone along
with the per-PHY 79, 80 and 81 - and /proc/irq had lost those three
directories; the next bind came back on the same three numbers. That is
regmap_del_irq_chip() doing both the free and the dispose once the driver
stopped doing half of it by hand. Had it not, the directories would have
stayed behind and the rebind would have taken the next free virqs.
What this board cannot show is the race itself. The window is narrow, and
reordering the two calls instead of removing one ran just as clean here.
The panic quoted above is what the unfixed path does, captured on the same
board and the same base with only patch 1 applied. Both kernels also
carried a local debug msleep() in phy_remove(), left over from unrelated
work in the same tree. It only widens the window this patch closes:
phy_remove() runs after .remove has returned and before
regmap_del_irq_chip() frees the parent interrupt, which is exactly the span
an early dispose leaves open. The clean runs, the WARN and the descriptor
readings do not depend on it; the panic quoted above was captured with it
in place.
Not tested: any MMIO part - there is no MT7988, EN7581, AN7583 or EN7528
hardware here. The object file was read instead: mt7530_remove_common() now
compiles to a single call to dsa_unregister_switch(), and
mt7530_free_mdio_irq() keeps its two remaining callers. Built with W=1, no
warnings; checkpatch --strict clean.
drivers/net/dsa/mt7530.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
index 3e61eb3c2b1e..96832852c65a 100644
--- a/drivers/net/dsa/mt7530.c
+++ b/drivers/net/dsa/mt7530.c
@@ -3593,9 +3593,6 @@ EXPORT_SYMBOL_GPL(mt7530_probe_common);
void
mt7530_remove_common(struct mt7530_priv *priv)
{
- if (priv->irq_domain)
- mt7530_free_mdio_irq(priv);
-
dsa_unregister_switch(priv->ds);
mutex_destroy(&priv->reg_mutex);
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
2026-09-18 1:50 ` [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 Aleksei Sviridkin
@ 2026-09-18 1:58 ` Andrew Lunn
2026-09-18 8:20 ` Aleksei Sviridkin
2026-09-22 2:15 ` netdev-bot+sashiko
1 sibling, 1 reply; 7+ messages in thread
From: Andrew Lunn @ 2026-09-18 1:58 UTC (permalink / raw)
To: Aleksei Sviridkin
Cc: netdev, chester.a.unal, daniel, olteanv, gerg, davem, edumazet,
kuba, pabeni, matthias.bgg, angelogioacchino.delregno,
linux-kernel, linux-arm-kernel, linux-mediatek
On Fri, Sep 18, 2026 at 04:50:19AM +0300, Aleksei Sviridkin wrote:
> The core and io supplies are only requested for ID_MT7530: both the
> devm_regulator_get() in probe and the regulator_enable() in
> mt7530_setup() are guarded by the switch id, but mt7530_remove()
> disables them unconditionally. On an MT7621 or an MT7531 both pointers
> are still NULL from devm_kzalloc(), so rmmod or a sysfs unbind calls
> regulator_disable() on NULL.
Did you consider using devm_regulator_get_enable()?
Andrew
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
2026-09-18 1:58 ` Andrew Lunn
@ 2026-09-18 8:20 ` Aleksei Sviridkin
0 siblings, 0 replies; 7+ messages in thread
From: Aleksei Sviridkin @ 2026-09-18 8:20 UTC (permalink / raw)
To: andrew
Cc: netdev, chester.a.unal, daniel, olteanv, davem, edumazet, kuba,
pabeni, matthias.bgg, angelogioacchino.delregno, gerg,
linux-kernel, linux-arm-kernel, linux-mediatek
> Did you consider using devm_regulator_get_enable()?
No. Looking at it now, it is the better shape, but not for this patch.
Two things stop it from being a swap. The driver sets the voltages before
it enables, 1.0 V on core and 3.3 V on io, and get_enable returns an int,
so no handle is left for regulator_set_voltage(). On the BPI-R2, which is
also the example in the binding, both supplies are MT6323 regulators with
a range, vpa 0.5 to 3.65 V and vemc3v3 3.0 to 3.3 V, so those calls are
not decoration. The other is placement: the get is in probe and the enable
is in mt7530_setup(), which DSA calls much later and which resets the chip
on the next lines, so get_enable would bring the rails up at a different
point.
It is still worth doing, for a reason this patch deliberately leaves
alone. mt7530_remove() disables the supplies before
mt7530_remove_common() calls dsa_unregister_switch(), which still writes
switch registers over MDIO, so on a board where those rails really drop
the teardown is talking to a switch that is already off. A devres disable
runs after .remove returns and would fix that too.
So I would keep this one as the NULL check with a Fixes tag, and send the
conversion to net-next, where the voltage handling and the power-up
ordering can be argued on their own.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
2026-09-18 1:50 ` [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 Aleksei Sviridkin
2026-09-18 1:58 ` Andrew Lunn
@ 2026-09-22 2:15 ` netdev-bot+sashiko
1 sibling, 0 replies; 7+ messages in thread
From: netdev-bot+sashiko @ 2026-09-22 2:15 UTC (permalink / raw)
To: f
Cc: netdev, chester.a.unal, daniel, andrew, olteanv, gerg, davem,
edumazet, kuba, pabeni, matthias.bgg, angelogioacchino.delregno,
linux-kernel, linux-arm-kernel, linux-mediatek
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 (2) · Low: 0 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The regulator enable reference is taken in the DSA `.setup`
callback (mt7530_setup(), drivers/net/dsa/mt7530.c:2493-2509) but…
Pre-existing issues:
- [Medium] On the ID_MT7530 path the patch keeps
regulator_disable(core_pwr)/regulator_disable(io_pwr) ahead of…
- [Medium] In mt7530_setup() (drivers/net/dsa/mt7530.c:2494 and :2502)
the return values of `regulator_set_voltage(priv->core_pwr, 1000000,…
- [Low] Binding/driver disagreement about which compatibles the `core-
supply`/`io-supply` properties apply to.
--
net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
The core and io supplies are only requested for ID_MT7530, but
mt7530_remove() disabled them unconditionally, so unbinding an MT7531 or
MT7621 called regulator_disable() on a NULL pointer. The patch gates the
two regulator_disable() calls on priv->id == ID_MT7530.
> diff --git a/drivers/net/dsa/mt7530-mdio.c b/drivers/net/dsa/mt7530-mdio.c
> index 784dd58a71589..de42f70afcfa1 100644
> --- a/drivers/net/dsa/mt7530-mdio.c
> +++ b/drivers/net/dsa/mt7530-mdio.c
> @@ -227,15 +227,17 @@ mt7530_remove(struct mdio_device *mdiodev)
> if (!priv)
> return;
>
> - ret = regulator_disable(priv->core_pwr);
> - if (ret < 0)
> - dev_err(priv->dev,
> - "Failed to disable core power: %d\n", ret);
> -
> - ret = regulator_disable(priv->io_pwr);
> - if (ret < 0)
> - dev_err(priv->dev, "Failed to disable io pwr: %d\n",
> - ret);
> + if (priv->id == ID_MT7530) {
[Severity: Low]
This isn't a bug introduced by this patch, but the id test here is now the
third place that encodes "these supplies belong to ID_MT7530", while the
binding does not say that.
In Documentation/devicetree/bindings/net/dsa/mediatek,mt7530.yaml both
core-supply and io-supply are defined as generic top-level properties and
are only listed under required: for the mediatek,mt7530 branch. The
mediatek,mt7531 branch disallows other properties explicitly:
properties:
gpio-controller: false
mediatek,mcm: false
but leaves the two supplies permitted, and the mediatek,mt7621 branch only
adds mediatek,mcm to required:.
So a schema-valid DT with core-supply on compatible = "mediatek,mt7531" has
that phandle silently ignored, since mt7530_probe() never calls
devm_regulator_get() for any other id:
if (priv->id == ID_MT7530) {
priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core");
...
}
Should the binding set core-supply: false / io-supply: false on the
mt7531 and mt7621 branches so the two sides agree?
> + ret = regulator_disable(priv->core_pwr);
> + if (ret < 0)
> + dev_err(priv->dev,
> + "Failed to disable core power: %d\n", ret);
> +
> + ret = regulator_disable(priv->io_pwr);
> + if (ret < 0)
> + dev_err(priv->dev, "Failed to disable io pwr: %d\n",
> + ret);
> + }
[Severity: Medium]
Is priv->id the right condition for the disable side? It records that the
supplies were requested in mt7530_probe(), not that they were ever enabled.
The enable happens in a different callback, mt7530_setup() in mt7530.c,
reached through ds->ops->setup.
Can this produce an unbalanced disable on an ID_MT7530 board?
mt7530_probe() -> dsa_register_switch() -> dsa_tree_setup() can return
success without ever running ds->ops->setup:
net/dsa/dsa.c:dsa_tree_setup() {
...
complete = dsa_tree_setup_routing_table(dst);
if (!complete)
return 0;
...
}
A later unbind then takes this new ID_MT7530 branch with
regulator->enable_count still zero:
drivers/regulator/core.c:_regulator_disable() {
if (WARN(regulator->enable_count == 0,
"unbalanced disables for %s\n", rdev_get_name(rdev)))
return -EIO;
...
}
which also means the dev_err() above prints -EIO. A tree teardown and
re-setup cycle, e.g. a peer switch in the same tree unbinding and binding
again, runs mt7530_setup() and therefore regulator_enable() more than once
against this single disable.
Does the opposite direction leak the enable reference? Every error return
in mt7530_setup() after the core rail is up:
if (priv->id == ID_MT7530) {
regulator_set_voltage(priv->core_pwr, 1000000, 1000000);
ret = regulator_enable(priv->core_pwr);
...
regulator_set_voltage(priv->io_pwr, 3300000, 3300000);
ret = regulator_enable(priv->io_pwr);
if (ret < 0) {
dev_err(priv->dev, "Failed to enable io pwr: %d\n",
ret);
return ret;
}
}
/* Waiting for MT7530 got to stable */
INIT_MT7530_DUMMY_POLL(&p, priv, MT753X_TRAP);
ret = readx_poll_timeout(_mt7530_read, &p, val, val != 0,
20, 1000000);
if (ret < 0) {
dev_err(priv->dev, "reset timeout\n");
return ret;
propagates out through mt753x_setup() and dsa_register_switch() to
mt7530_probe(), so .remove is never called. The devm release path only
puts the regulator:
drivers/regulator/core.c:_regulator_put() {
...
/* Docs say you must disable before calling regulator_put() */
WARN_ON(regulator->enable_count);
...
}
so the 1.0 V core and 3.3 V I/O rails stay enabled for the lifetime of the
system, with a WARN. The MT7530_CREV id mismatch and the 20 MHz XTAL
rejection reach the same point.
Would pairing the enable with the existing symmetric hook,
mt753x_teardown() in mt7530.c, or tracking an explicit "enabled" flag and
unwinding the mt7530_setup() error paths, cover the NULL case this patch
targets as well as these two?
>
> mt7530_remove_common(priv);
>
[Severity: Medium]
This is a pre-existing ordering issue rather than something this patch
introduces, and the cover text says as much, but it may be worth recording
here since the ID_MT7530 path keeps it.
Everything mt7530_remove_common() still does runs against a die whose rails
have just been switched off:
drivers/net/dsa/mt7530.c:mt7530_remove_common() {
dsa_unregister_switch(priv->ds);
mutex_destroy(&priv->reg_mutex);
}
dsa_unregister_switch() reaches mt753x_teardown(), the per-port
mt7530_port_disable() PCR read-modify-writes such as
mt7530_rmw(priv, MT7530_PCR_P(port), PCR_MATRIX_MASK, PCR_MATRIX_CLR);
the mac_link_down() PMCR writes and the internal-PHY suspend writes that go
through the switch's own MDIO bus. Are those writes simply lost at that
point, leaving the driver's bookkeeping describing a state the hardware
never reached?
Can the MIB poller also read an unpowered die? mt753x_teardown() is the
only place the self-rearming stats work is cancelled:
drivers/net/dsa/mt7530.c:mt753x_teardown() {
struct mt7530_priv *priv = ds->priv;
if (priv->bus)
cancel_delayed_work_sync(&priv->stats_work);
}
and it runs from inside dsa_unregister_switch(), i.e. after the disables
above. mt7530_stats_poll() re-arms itself unconditionally:
mt7530_stats_refresh(priv);
schedule_delayed_work(&priv->stats_work,
MT7530_STATS_POLL_INTERVAL);
so it can be doing MDIO reads while mt7530_remove() drops the rails,
folding all-ones reads into the cached per-port counters.
[Severity: Medium]
This is also pre-existing and untouched by the patch, but while in this
area: mt7530_setup() in drivers/net/dsa/mt7530.c discards the return value
of both regulator_set_voltage() calls:
if (priv->id == ID_MT7530) {
regulator_set_voltage(priv->core_pwr, 1000000, 1000000);
ret = regulator_enable(priv->core_pwr);
...
regulator_set_voltage(priv->io_pwr, 3300000, 3300000);
ret = regulator_enable(priv->io_pwr);
If machine constraints do not permit the requested range, or
REGULATOR_CHANGE_VOLTAGE is not allowed, or a second consumer holds a
shared rail at another voltage, the rail is still enabled and the driver
goes on to reset the die, poll MT753X_TRAP and read MT7530_CREV as if the
1.0 V core / 3.3 V I/O operating points had been applied. Should these two
calls be checked so the failure surfaces as an error instead of as
intermittent register access failures?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260918015020.2518315-1-f%40lex.la
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind
2026-09-18 1:50 [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind Aleksei Sviridkin
2026-09-18 1:50 ` [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 Aleksei Sviridkin
2026-09-18 1:50 ` [PATCH net v2 2/2] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq Aleksei Sviridkin
@ 2026-09-24 16:20 ` patchwork-bot+netdevbpf
2 siblings, 0 replies; 7+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-24 16:20 UTC (permalink / raw)
To: Aleksei Sviridkin
Cc: netdev, chester.a.unal, daniel, andrew, olteanv, gerg, davem,
edumazet, kuba, pabeni, matthias.bgg, angelogioacchino.delregno,
linux-kernel, linux-arm-kernel, linux-mediatek
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Fri, 18 Sep 2026 04:50:18 +0300 you wrote:
> Unbinding the MT7530 driver from an MT7531 dereferences NULL in
> regulator_disable(). On a Netcraze NC-1012 (MT7981B + MT7531, 6.18.44):
>
> # echo mdio-bus:1f > /sys/bus/mdio_bus/drivers/mt7530-mdio/unbind
>
> oopses there, and the build it was found on sets CONFIG_PANIC_ON_OOPS, so
> the board goes down with it. Fix that and the same command gets as far as
> mt7530_remove_common(), which disposes interrupt mappings the switch's own
> regmap-irq chip still owns; the regmap-irq thread then faults in
> handle_nested_irq() later in the same teardown. rmmod reaches both, since
> mdio_module_driver() calls .remove on module exit.
>
> [...]
Here is the summary with links:
- [net,v2,1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621
https://git.kernel.org/netdev/net/c/c2cdef41e0b4
- [net,v2,2/2] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq
https://git.kernel.org/netdev/net/c/0d80ba0a204c
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-24 16:21 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 1:50 [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind Aleksei Sviridkin
2026-09-18 1:50 ` [PATCH net v2 1/2] net: dsa: mt7530: fix NULL dereference on unbind of MT7531 and MT7621 Aleksei Sviridkin
2026-09-18 1:58 ` Andrew Lunn
2026-09-18 8:20 ` Aleksei Sviridkin
2026-09-22 2:15 ` netdev-bot+sashiko
2026-09-18 1:50 ` [PATCH net v2 2/2] net: dsa: mt7530: leave the MDIO IRQ mappings to regmap-irq Aleksei Sviridkin
2026-09-24 16:20 ` [PATCH net v2 0/2] net: dsa: mt7530: fix two crashes on driver unbind patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®