Skip to content

net: phy: as21xxx: stop probing foreign PHYs (root cause of the MxL862xx relay error flood) - #207

Merged
frank-w merged 2 commits into
frank-w:7.1-mainfrom
meehien:bpi/as21xxx_prob_foreign_phy
Aug 2, 2026
Merged

net: phy: as21xxx: stop probing foreign PHYs (root cause of the MxL862xx relay error flood)#207
frank-w merged 2 commits into
frank-w:7.1-mainfrom
meehien:bpi/as21xxx_prob_foreign_phy

Conversation

@meehien

Copy link
Copy Markdown

Follow-up to #201, which just silenced the symptom.

aeon_gen1_match_phy_device() calls aeon_gen1_read_pid(), which uses
aeon_cl45_read(). That accessor ends with:

staticvoidaeon_mdio_patch(structphy_device*phydev)
{
mutex_lock(&bus->mdio_lock);
__mdiobus_c45_write(bus, 30, 0x1, 0x1, 0x1);
mutex_unlock(&bus->mdio_lock);
}

A write to PHY address 30 — nothing is there.

phylib calls match_phy_device() for every registered driver entry against
every phydev on every bus. That includes the four internal GPY cores of the
MxL86252C at mdio-bus:10-mii:00..03, whose bus is the switch firmware's MDIO
relay. There the patch write becomes an INT_GPHY_WRITE mailbox command for
PHY 30, which the firmware rejects:

mxl862xx mdio-bus:10: CMD 1802 returned error -19

CMD 1802isINT_GPHY_WRITE. The error is the patch write itself.

Arithmetic

N_cores × 12 match entries (11 gen1 + 1 gen2) × 2 PID reads

Measured on a BPI-R4 Pro boot where three of the four cores were present:
3 × 12 × 2 = 72. Exactly 72 observed.

Timing on my device confirms it is the match path

[14.302844] mxl862xx mdio-bus:10: switch ready after 2390ms, firmware 1.0.70
[14.348624] mxl862xx mdio-bus:10: CMD 1801 returned error -19
[14.393903] mxl862xx mdio-bus:10: CMD 1802 returned error -19 <- first
... 24 errors, 6.4 ms apart ...
[14.557880] MaxLinear Ethernet MxL86252 mdio-bus:10-mii:01: Firmware Version: 0.77
[14.584632] mxl862xx mdio-bus:10: CMD 1802 returned error -19 <- continues

The errors start 46 ms after the switch reports ready and run interleaved
with
PHY binding. They cannot come from mxl-gpy's config_init, which is
what #201 claimed — that runs after a PHY has bound.

Comparison with mainline

drivers/net/phy/as21xxx.c in this same tree has no __mdiobus_* calls, no
mdio_lock manipulation and no flush of any kind — all 24 register accesses go
through phy_{read,write,modify}_mmd(). It loads the same firmware and
identifies the same silicon without a patch write anywhere, which is good
evidence the flush is not a hardware requirement of this PHY.

Mainline also gates its match function before touching the bus:

if (!phy_id_compare_vendor(phydev->c45_ids.device_ids[MDIO_MMD_PCS],
PHY_VENDOR_AEONSEMI))
returngenphy_match_phy_device(phydev, phydrv);

The 1.9.2 vendor driver has neither protection, though it already defines
PHY_VENDOR_AEONSEMI (unused).

The patches

1/2 — don't write to a nonexistent PHY after C45 reads. A read needs no
flush: __mdiobus_c45_read() has already completed and dropped the bus lock
when the patch write is issued. aeon_mdio_patch() is kept in
aeon_cl45_write(), where the original workaround was reported to matter.
After this change every remaining caller is reached only once a device is
confirmed to be an AS21xxx, so no foreign bus is touched.

2/2 — only probe Aeonsemi PHYs when matching. Brings the vendor driver in
line with mainline. This removes the PID reads themselves — each one a full
mailbox round-trip (write LEN_RET, write CTRL, poll BUSY, read back).

aeon_cl45_read() ends with aeon_mdio_patch(), which issues
__mdiobus_c45_write(bus, 30, 0x1, 0x1, 0x1);
a write to PHY address 30 on whatever bus the phydev happens to live
on. Nothing is at address 30.
phylib calls match_phy_device() for every registered driver entry
against every phydev, so aeon_gen1_read_pid() runs against PHYs that
are not Aeonsemi parts - including the four internal GPY cores of an
MxL86252C DSA switch, whose bus is the switch firmware's MDIO relay.
There the write becomes an INT_GPHY_WRITE mailbox command for PHY 30,
which the firmware rejects:
mxl862xx mdio-bus:10: CMD 1802 returned error -19
On a BPI-R4 Pro that is 4 GPY cores x 12 match entries x 2 PID reads =
96 rejected commands on every boot.
A read needs no flush: __mdiobus_c45_read() has already completed and
dropped the bus lock when the patch write is issued. The mainline
as21xxx driver performs the same identification and firmware load with
no such write at all. Drop it from the read path.
aeon_mdio_patch() is kept in aeon_cl45_write(), where the original
workaround was reported to matter. After this change every remaining
caller is reached only once a device has been confirmed to be an
AS21xxx, so no foreign bus is touched.
Signed-off-by: Mihai Ordean <research@mihaiordean.com>
aeon_gen1_match_phy_device() and aeon_gen2_match_phy_device() read the
PHY ID out of every phydev phylib offers them, with no check that the
device is an Aeonsemi part at all. phylib calls match_phy_device() for
every registered driver entry against every phydev on every bus, so the
driver issues C45 vendor register accesses to foreign PHYs.
On a BPI-R4 Pro the four internal GPY cores of the MxL86252C sit on the
switch firmware's MDIO relay, where each of those reads is a full
mailbox transaction: write LEN_RET, write CTRL, poll for BUSY to clear,
read CTRL and LEN_RET back. With 12 match entries (11 gen1, 1 gen2)
and two PID reads each, that is 96 pointless mailbox commands - several
thousand SMDIO frames - during PHY probing, delaying the switch setup
that follows.
The mainline as21xxx driver guards its match function with a vendor
comparison and returns genphy_match_phy_device() for everything else.
Do the same here. PHY_VENDOR_AEONSEMI is already defined and was
previously unused.
Signed-off-by: Mihai Ordean <research@mihaiordean.com>
@@ -1484,7 +1480,17 @@
static int aeon_gen1_match_phy_device(struct phy_device *phydev,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, this seems the rootcause of these phyad 30 errors on mxl bus which looked really strange. Thanks for it

@frank-wfrank-wAug 2, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like i catched the wrong line...meant the line with the read_pid before checking for aeonsemi phy

	u32 phy_id = aeon_gen1_read_pid(phydev);

}

ret = __mdiobus_c45_read(bus, phy_addr, dev_addr, phy_reg);
__mdiobus_c45_write(bus, 30, 0x1, 0x1, 0x1);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this could be dropped too?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. this can be removed too. good find. i can submit a patch later. want me to do it to this PR?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can do it on my own, just wanted your feedback here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. i've tested and works fine for me:

diff --git a/drivers/net/phy/as21xx_1.9.2/as21xxx.c b/drivers/net/phy/as21xx_1.9.2/as21xxx.c
index ed001d3..e595e10 100644
--- a/drivers/net/phy/as21xx_1.9.2/as21xxx.c
+++ b/drivers/net/phy/as21xx_1.9.2/as21xxx.c
@@ -539,7 +539,6 @@ static int aeon_mdio_read(struct phy_device *phydev, int dev_addr,
}
ret = __mdiobus_c45_read(bus, phy_addr, dev_addr, phy_reg);
-	__mdiobus_c45_write(bus, 30, 0x1, 0x1, 0x1);
return ret;
}

Comment threaddrivers/net/phy/as21xx_1.9.2/as21xxx.c
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@meehien@frank-w