* [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete
@ 2026-10-22 22:29 Christian Marangi
2026-10-22 22:29 ` [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling Christian Marangi
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Christian Marangi @ 2026-10-22 22:29 UTC (permalink / raw)
To: Christian Marangi, Andrew Lunn, Heiner Kallweit, Russell King,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
netdev, linux-kernel
Cc: Maxime Chevallier
Add missing case in custom read_link, when autoneg is started, autoneg
complete bit is reset but link is still not up.
Fixes: 830877d89edc ("net: phy: Add support for Aeonsemi AS21xxx PHYs")
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
Changes v2:
- Add Review tag
drivers/net/phy/as21xxx.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c
index d5738117eca6..0db82da8dbdf 100644
--- a/drivers/net/phy/as21xxx.c
+++ b/drivers/net/phy/as21xxx.c
@@ -658,6 +658,13 @@ static int as21xxx_read_link(struct phy_device *phydev, int *bmcr)
return status;
phydev->link = !!(status & MDIO_STAT1_LSTATUS);
+ phydev->autoneg_complete = !!(status & MDIO_AN_STAT1_COMPLETE);
+
+ /* Consider the case that autoneg was started and "aneg complete"
+ * bit has been reset, but "link up" bit not yet.
+ */
+ if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete)
+ phydev->link = 0;
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling 2026-10-22 22:29 [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete Christian Marangi @ 2026-10-22 22:29 ` Christian Marangi 2026-10-26 23:07 ` netdev-bot+sashiko 2026-10-22 22:29 ` [PATCH net v2 3/4] net: phy: as21xxx: force C45 OPs for AUTONEG Christian Marangi ` (2 subsequent siblings) 3 siblings, 1 reply; 8+ messages in thread From: Christian Marangi @ 2026-10-22 22:29 UTC (permalink / raw) To: Christian Marangi, Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel With further test with 2.5G NIC it was discovered that phy_resolve_aneg_linkmode is not enough to detect speed higher that 1G when autoneg is enabled. Also in the switch case there is a typo where the speed mask is AND with VEND1_SPEED_STATUS instead of the correct mask VEND1_SPEED_MASK. Rework the read_status code to always read the speed from the vendor register and parse the generic bit only for the pause frame. Fixes: 830877d89edc ("net: phy: Add support for Aeonsemi AS21xxx PHYs") Signed-off-by: Christian Marangi <ansuelsmth@gmail.com> --- Changes v2: - Handle half duplex on 10 and 100mbps - Handle >1G advertise drivers/net/phy/as21xxx.c | 104 +++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index 0db82da8dbdf..519b78d3579d 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -671,7 +671,7 @@ static int as21xxx_read_link(struct phy_device *phydev, int *bmcr) static int as21xxx_read_c22_lpa(struct phy_device *phydev) { - int lpagb; + int lpagb, lpa; /* MII_STAT1000 are only filled in the mapped C22 * in C45, use that to fill lpagb values and check. @@ -698,13 +698,22 @@ static int as21xxx_read_c22_lpa(struct phy_device *phydev) mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, lpagb); + lpa = phy_read_mmd(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_LPA); + if (lpa < 0) + return lpa; + + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, lpa); + return 0; } static int as21xxx_read_status(struct phy_device *phydev) { int bmcr, old_link = phydev->link; + int speed; int ret; + int val; ret = as21xxx_read_link(phydev, &bmcr); if (ret) @@ -720,58 +729,73 @@ static int as21xxx_read_status(struct phy_device *phydev) phydev->asym_pause = 0; if (phydev->autoneg == AUTONEG_ENABLE) { - ret = genphy_c45_read_lpa(phydev); - if (ret) - return ret; + if (!phydev->autoneg_complete) { + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, + 0); + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0); + mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0); + return 0; + } ret = as21xxx_read_c22_lpa(phydev); if (ret) return ret; - phy_resolve_aneg_linkmode(phydev); - } else { - int speed; + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, val); + } else { linkmode_zero(phydev->lp_advertising); + } - speed = phy_read_mmd(phydev, MDIO_MMD_VEND1, - VEND1_SPEED_STATUS); - if (speed < 0) - return speed; - - switch (speed & VEND1_SPEED_STATUS) { - case VEND1_SPEED_10000: - phydev->speed = SPEED_10000; - phydev->duplex = DUPLEX_FULL; - break; - case VEND1_SPEED_5000: - phydev->speed = SPEED_5000; - phydev->duplex = DUPLEX_FULL; - break; - case VEND1_SPEED_2500: - phydev->speed = SPEED_2500; + speed = phy_read_mmd(phydev, MDIO_MMD_VEND1, + VEND1_SPEED_STATUS); + if (speed < 0) + return speed; + + switch (speed & VEND1_SPEED_MASK) { + case VEND1_SPEED_10000: + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_5000: + phydev->speed = SPEED_5000; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_2500: + phydev->speed = SPEED_2500; + phydev->duplex = DUPLEX_FULL; + break; + case VEND1_SPEED_1000: + phydev->speed = SPEED_1000; + if (bmcr & BMCR_FULLDPLX) phydev->duplex = DUPLEX_FULL; - break; - case VEND1_SPEED_1000: - phydev->speed = SPEED_1000; - if (bmcr & BMCR_FULLDPLX) - phydev->duplex = DUPLEX_FULL; - else - phydev->duplex = DUPLEX_HALF; - break; - case VEND1_SPEED_100: - phydev->speed = SPEED_100; + else + phydev->duplex = DUPLEX_HALF; + break; + case VEND1_SPEED_100: + phydev->speed = SPEED_100; + if (bmcr & BMCR_FULLDPLX) phydev->duplex = DUPLEX_FULL; - break; - case VEND1_SPEED_10: - phydev->speed = SPEED_10; + else + phydev->duplex = DUPLEX_HALF; + break; + case VEND1_SPEED_10: + phydev->speed = SPEED_10; + if (bmcr & BMCR_FULLDPLX) phydev->duplex = DUPLEX_FULL; - break; - default: - return -EINVAL; - } + else + phydev->duplex = DUPLEX_HALF; + break; + default: + return -EINVAL; } + if (phydev->autoneg == AUTONEG_ENABLE) + phy_resolve_aneg_pause(phydev); + return 0; } -- 2.55.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling 2026-10-22 22:29 ` [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling Christian Marangi @ 2026-10-26 23:07 ` netdev-bot+sashiko 0 siblings, 0 replies; 8+ messages in thread From: netdev-bot+sashiko @ 2026-10-26 23:07 UTC (permalink / raw) To: ansuelsmth Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev, linux-kernel Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 2 · Low: 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] With autoneg enabled, as21xxx_read_status() (drivers/net/phy/as21xxx.c) now takes the duplex for 1000/100/10 links… - [Medium] With autoneg enabled, as21xxx_read_status() now reads and decodes VEND1_SPEED_STATUS (MMD VEND1 0x4002) whenever the cached… - [Low] The commit message does not match several behaviour changes in the patch. -- net: phy: as21xxx: fix read_status speed handling This commit reworks as21xxx_read_status() so that the speed always comes from the vendor VEND1_SPEED_STATUS register, including when autoneg is enabled. It also changes the switch to mask with VEND1_SPEED_MASK. The generic link partner registers are now used only to resolve pause, through phy_resolve_aneg_pause(). > With further test with 2.5G NIC it was discovered that > phy_resolve_aneg_linkmode is not enough to detect speed higher that 1G > when autoneg is enabled. [ ... ] > Rework the read_status code to always read the speed from the vendor > register and parse the generic bit only for the pause frame. [Severity: Low] Could the commit message explain why phy_resolve_aneg_linkmode() was not enough? The removed genphy_c45_read_lpa() already read MDIO_AN_10GBT_STAT and set the partner's 2.5G/5G/10G bits before resolution. The new code reads the same register to fill lp_advertising: val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); ... mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, val); The message doesn't say whether that register is reliable on this PHY. If it isn't, the partner modes reported to userspace would still be wrong. The message also leaves out a few behaviour changes: - In as21xxx_read_c22_lpa(), the base page partner advertisement now comes from the mapped C22 MII_LPA instead of C45 MDIO_AN_LPA. The partner Autoneg bit therefore comes from LPA_LPACK instead of MDIO_AN_STAT1_LPABLE. - Forced 10/100 used to always report DUPLEX_FULL and now reports the BMCR_FULLDPLX bit. Only the v2 changelog below the --- line mentions this, and that part is dropped when the patch is applied. - The default -EINVAL return in the vendor speed switch can now be reached with autoneg enabled. Since the patch carries a Fixes: tag, could the commit message describe these changes too? > diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c > index 0db82da8dbdf3..519b78d3579d3 100644 > --- a/drivers/net/phy/as21xxx.c > +++ b/drivers/net/phy/as21xxx.c [ ... ] > @@ -720,58 +729,73 @@ static int as21xxx_read_status(struct phy_device *phydev) > phydev->asym_pause = 0; > > if (phydev->autoneg == AUTONEG_ENABLE) { > - ret = genphy_c45_read_lpa(phydev); > - if (ret) > - return ret; > + if (!phydev->autoneg_complete) { > + mii_stat1000_mod_linkmode_lpa_t(phydev->lp_advertising, > + 0); > + mii_lpa_mod_linkmode_lpa_t(phydev->lp_advertising, 0); > + mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0); > + return 0; > + } [Severity: Medium] Can phydev->autoneg_complete be stale when this guard checks it? as21xxx_read_link() returns early when ANRESTART is set, before it updates autoneg_complete: if (*bmcr & BMCR_ANRESTART) { phydev->link = 0; return 0; } If the previous poll saw the link up, autoneg_complete is still 1, so renegotiation skips this guard. One example is _phy_start_aneg()->phy_config_aneg()->phy_check_link_status() running before ANRESTART self-clears. The removed genphy_c45_read_lpa() re-read MDIO_STAT1 on every call. The code also falls through when AN complete is still set but LSTATUS has dropped. In both cases the code goes on to decode VEND1_SPEED_STATUS while the link is down or renegotiating, which can cause two problems. First, VEND1_SPEED_10 is encoded as 0, so a zero or unresolved register value is reported as a 10 Mbps link. If the register lags behind link up, the "why bother the PHY if nothing can have changed" shortcut keeps that speed until the link drops. Second, any value outside the six documented codes hits the default -EINVAL. phy_state_machine() turns that into phy_error_precise() and PHY_ERROR. Before this patch, that path could only be reached with autoneg disabled. Should this guard use a fresh AN status read, or should the vendor speed read be skipped when phydev->link is 0? The later patches in the series don't seem to change this code. > > ret = as21xxx_read_c22_lpa(phydev); > if (ret) > return ret; > > - phy_resolve_aneg_linkmode(phydev); > - } else { > - int speed; [ ... ] > + switch (speed & VEND1_SPEED_MASK) { [ ... ] > + case VEND1_SPEED_1000: > + phydev->speed = SPEED_1000; > + if (bmcr & BMCR_FULLDPLX) > phydev->duplex = DUPLEX_FULL; [ ... ] > + else > + phydev->duplex = DUPLEX_HALF; > + break; [Severity: Medium] With autoneg enabled, does BMCR_FULLDPLX report the negotiated duplex for the 1000/100/10 cases? Before this patch, the autoneg duplex came from phy_resolve_aneg_linkmode(), which intersects advertising with lp_advertising. BMCR bit 8 is a forced mode control bit (IEEE 802.3 22.2.4.1.8). genphy_read_status_fixed() reads it only for AUTONEG_DISABLE. Nothing on the autoneg path writes this bit. genphy_restart_aneg() only sets BMCR_ANENABLE | BMCR_ANRESTART, while genphy_setup_forced() rewrites the whole BMCR. For example, if the user forces 100/half and then turns autoneg back on, BMCR_FULLDPLX stays clear. Every later autonegotiated 10/100/1000 link would then be reported as half duplex. A 1000/half result also looks impossible here, because 1000baseT_Half doesn't seem to be advertised. The reverse also applies: if the bit is left at 1, a link that negotiated half duplex would be reported as full. A wrong duplex also disables pause, because phy_resolve_aneg_pause() only resolves it for full duplex: if (phydev->duplex == DUPLEX_FULL) { phydev->pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, ... Unless the vendor mapped C22 BMCR mirrors the resolved duplex, should the autoneg case get the duplex from the advertisement intersection, as it did before? The later patches in the series don't seem to change this either. [ ... ] > + if (phydev->autoneg == AUTONEG_ENABLE) > + phy_resolve_aneg_pause(phydev); > + > return 0; > } -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261022222955.698722-1-ansuelsmth%40gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net v2 3/4] net: phy: as21xxx: force C45 OPs for AUTONEG 2026-10-22 22:29 [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete Christian Marangi 2026-10-22 22:29 ` [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling Christian Marangi @ 2026-10-22 22:29 ` Christian Marangi 2026-10-26 23:07 ` netdev-bot+sashiko 2026-10-22 22:29 ` [PATCH net v2 4/4] net: phy: as21xxx: handle odd firmware on loading Christian Marangi 2026-10-26 23:07 ` [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete netdev-bot+sashiko 3 siblings, 1 reply; 8+ messages in thread From: Christian Marangi @ 2026-10-22 22:29 UTC (permalink / raw) To: Christian Marangi, Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel With further testing with 2.5G NIC, it was discovered that the PHY require the C45 OPs to configure and restart ANEG or speed higher than 1G doesn't function correctly. To force C45 OPs with generic PHY function, clear the C22 bit from devices_in_package bitmask and implement a custom .config_aneg. Fixes: 830877d89edc ("net: phy: Add support for Aeonsemi AS21xxx PHYs") Signed-off-by: Christian Marangi <ansuelsmth@gmail.com> --- Changes v2: - Implement custom config_aneg drivers/net/phy/as21xxx.c | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index 519b78d3579d..e2988cb10c04 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -616,6 +616,13 @@ static int as21xxx_probe(struct phy_device *phydev) if (ret) return ret; + /* Even if PHY declare support for Clause 22 register, + * Clause 45 register should be used for ANEG configuration + * and restart. Clear the C22 bit for devices_in_package to + * force C45 generic OPs in generic PHY ANGE OPs. + */ + phydev->c45_ids.devices_in_package &= ~BIT(0); + ret = aeon_ipc_sync_parity(phydev, priv); if (ret) return ret; @@ -633,6 +640,32 @@ static int as21xxx_probe(struct phy_device *phydev) return aeon_dpc_ra_enable(phydev); } +static int as21xxx_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + int ret; + u16 adv; + + ret = genphy_c45_an_config_aneg(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, + AS21XXX_MDIO_AN_C22 + MII_CTRL1000, + ADVERTISE_1000FULL | + ADVERTISE_1000HALF, adv); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return genphy_c45_check_and_restart_aneg(phydev, changed); +} + static int as21xxx_read_link(struct phy_device *phydev, int *bmcr) { int status; @@ -990,6 +1023,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21011JB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1002,6 +1036,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21011PB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1014,6 +1049,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21010PB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1026,6 +1062,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21010JB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1038,6 +1075,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21210PB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1050,6 +1088,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21510JB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1062,6 +1101,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21510PB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1074,6 +1114,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21511JB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1086,6 +1127,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21210JB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, @@ -1098,6 +1140,7 @@ static struct phy_driver as21xxx_drivers[] = { .name = "Aeonsemi AS21511PB1", .probe = as21xxx_probe, .match_phy_device = as21xxx_match_phy_device, + .config_aneg = as21xxx_config_aneg, .read_status = as21xxx_read_status, .led_brightness_set = as21xxx_led_brightness_set, .led_hw_is_supported = as21xxx_led_hw_is_supported, -- 2.55.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 3/4] net: phy: as21xxx: force C45 OPs for AUTONEG 2026-10-22 22:29 ` [PATCH net v2 3/4] net: phy: as21xxx: force C45 OPs for AUTONEG Christian Marangi @ 2026-10-26 23:07 ` netdev-bot+sashiko 0 siblings, 0 replies; 8+ messages in thread From: netdev-bot+sashiko @ 2026-10-26 23:07 UTC (permalink / raw) To: ansuelsmth Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev, linux-kernel Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 2 · Low: 0 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 new as21xxx_config_aneg() in drivers/net/phy/as21xxx.c never checks phydev->autoneg, so a forced speed/duplex request is… - [Medium] Installing the custom as21xxx_config_aneg() also drops master/slave (timing role) programming. -- net: phy: as21xxx: force C45 OPs for AUTONEG This patch clears the C22 bit from devices_in_package in as21xxx_probe(). It adds a custom as21xxx_config_aneg() that uses the generic C45 AN helpers and also writes the 1000BASE-T advertisement through the mapped C22 CTRL1000 register in MMD 7. The new callback is hooked into all of the post-firmware driver entries. > diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c > index 519b78d3579d3..e2988cb10c047 100644 > --- a/drivers/net/phy/as21xxx.c > +++ b/drivers/net/phy/as21xxx.c [ ... ] > @@ -633,6 +640,32 @@ static int as21xxx_probe(struct phy_device *phydev) > return aeon_dpc_ra_enable(phydev); > } > > +static int as21xxx_config_aneg(struct phy_device *phydev) > +{ > + bool changed = false; > + int ret; > + u16 adv; > + > + ret = genphy_c45_an_config_aneg(phydev); [Severity: Medium] What happens here when phydev->autoneg == AUTONEG_DISABLE? There is no forced-mode branch, so a request like "ethtool -s ethX autoneg off speed 100 duplex full" goes through: phy_ethtool_ksettings_set() _phy_start_aneg() phy_config_aneg() as21xxx_config_aneg() genphy_c45_an_config_aneg() genphy_c45_check_and_restart_aneg() genphy_c45_restart_aneg() genphy_c45_restart_aneg() sets MDIO_AN_CTRL1_ENABLE | MDIO_AN_CTRL1_RESTART. So autoneg stays enabled, or gets turned back on, and no forced speed or duplex is programmed. Before this patch there was no .config_aneg, so phy_config_aneg() used genphy_config_aneg()->__genphy_config_aneg(). That path handled forced mode with genphy_setup_forced() below 1000, and for 1000 it advertised only the requested mode. genphy_c45_config_aneg(), which this code seems to be based on, also starts with: if (phydev->autoneg == AUTONEG_DISABLE) return genphy_c45_pma_setup_forced(phydev); The config_aneg kernel-doc in include/linux/phy.h says the callback "forces the speed to the current settings in phydev if phydev->autoneg is off". as21xxx_read_status() also still has an AUTONEG_DISABLE branch. Is this a regression for forced link modes? Against a partner forced to 10/100 full duplex, this side would fall back to parallel detection and end up at half duplex. A forced 1000 request would also no longer restrict the advertisement, so the link could come up at 2.5G or higher. Would it make sense to add an AUTONEG_DISABLE branch at the top of as21xxx_config_aneg(), for example returning genphy_c45_pma_setup_forced(phydev)? > + if (ret < 0) > + return ret; > + if (ret > 0) > + changed = true; > + > + adv = linkmode_adv_to_mii_ctrl1000_t(phydev->advertising); > + > + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, > + AS21XXX_MDIO_AN_C22 + MII_CTRL1000, > + ADVERTISE_1000FULL | > + ADVERTISE_1000HALF, adv); [Severity: Medium] Does this drop master/slave (timing role) programming? The mask only covers ADVERTISE_1000FULL | ADVERTISE_1000HALF. genphy_c45_an_config_aneg() only writes the advertisement registers, so nothing in this callback uses phydev->master_slave_set. Before this patch, __genphy_config_aneg() called genphy_setup_master_slave(). That function does: return phy_modify_changed(phydev, MII_CTRL1000, (CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER | CTL1000_PREFER_MASTER), ctl); Its result was also counted toward the AN restart decision. genphy_c45_config_aneg() does the same through genphy_c45_an_setup_master_slave(). The ethtool paths can't reach this for this PHY, because master_slave_get is never set and stays MASTER_SLAVE_CFG_UNSUPPORTED. A DT "timing-role" property still sets master_slave_set through phy_probe()->of_set_phy_timing_role(). With this patch that role is no longer written to the PHY. A role change on its own also no longer causes an AN restart. If the link partner is forced to the same role, master/slave resolution would fail and the link would not come up. as21xxx_read_c22_lpa() still prints "Master/Slave resolution failed, maybe conflicting manual settings?", which suggests manual roles are meant to work. Should the CTL1000 role bits be programmed in the mapped register as well, or genphy_c45_an_setup_master_slave() be called, with the result folded into changed? > + if (ret < 0) > + return ret; > + if (ret > 0) > + changed = true; > + > + return genphy_c45_check_and_restart_aneg(phydev, changed); > +} > + [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261022222955.698722-1-ansuelsmth%40gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net v2 4/4] net: phy: as21xxx: handle odd firmware on loading 2026-10-22 22:29 [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete Christian Marangi 2026-10-22 22:29 ` [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling Christian Marangi 2026-10-22 22:29 ` [PATCH net v2 3/4] net: phy: as21xxx: force C45 OPs for AUTONEG Christian Marangi @ 2026-10-22 22:29 ` Christian Marangi 2026-10-26 23:07 ` netdev-bot+sashiko 2026-10-26 23:07 ` [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete netdev-bot+sashiko 3 siblings, 1 reply; 8+ messages in thread From: Christian Marangi @ 2026-10-22 22:29 UTC (permalink / raw) To: Christian Marangi, Andrew Lunn, Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel Even if firmware for AS21xxx should be always u16 aligned, we can't assume this will be the same forever. Better address this by simply validate the index before reading out of bound from the data buffer. A zero padded buffer will be passed to the AS21xxx mailbox in such case. Fixes: 830877d89edc ("net: phy: Add support for Aeonsemi AS21xxx PHYs") Signed-off-by: Christian Marangi <ansuelsmth@gmail.com> --- Changes v2: - Add this patch drivers/net/phy/as21xxx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c index e2988cb10c04..db1a7cd68cef 100644 --- a/drivers/net/phy/as21xxx.c +++ b/drivers/net/phy/as21xxx.c @@ -325,7 +325,9 @@ static int aeon_firmware_boot(struct phy_device *phydev, const u8 *data, /* Firmware is always aligned to u16 */ for (i = 0; i < size; i += 2) { - val = data[i + 1] << 8 | data[i]; + val = data[i]; + if (i + 1 < size) + val |= data[i + 1] << 8; ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLB_REG_MDIO_INDIRECT_LOAD, val); -- 2.55.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 4/4] net: phy: as21xxx: handle odd firmware on loading 2026-10-22 22:29 ` [PATCH net v2 4/4] net: phy: as21xxx: handle odd firmware on loading Christian Marangi @ 2026-10-26 23:07 ` netdev-bot+sashiko 0 siblings, 0 replies; 8+ messages in thread From: netdev-bot+sashiko @ 2026-10-26 23:07 UTC (permalink / raw) To: ansuelsmth Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev, linux-kernel Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin. Low priority / nit picks for your consideration: - [Low] In aeon_firmware_boot() (drivers/net/phy/as21xxx.c), the comment directly above the firmware upload loop still reads "/* Firmware is… -- net: phy: as21xxx: handle odd firmware on loading This commit changes the firmware upload loop in aeon_firmware_boot() so it checks the index before reading data[i + 1]. If the firmware image has an odd size, the last byte is now sent to the PHY mailbox as a zero-padded 16-bit word. > diff --git a/drivers/net/phy/as21xxx.c b/drivers/net/phy/as21xxx.c > index e2988cb10c047..db1a7cd68cef1 100644 > --- a/drivers/net/phy/as21xxx.c > +++ b/drivers/net/phy/as21xxx.c > @@ -325,7 +325,9 @@ static int aeon_firmware_boot(struct phy_device *phydev, const u8 *data, > > /* Firmware is always aligned to u16 */ > for (i = 0; i < size; i += 2) { > - val = data[i + 1] << 8 | data[i]; > + val = data[i]; > + if (i + 1 < size) > + val |= data[i + 1] << 8; [Severity: Low] This isn't a bug, but should the comment above the loop be updated too? It still says "Firmware is always aligned to u16". The new i + 1 < size check exists only because size can be odd. The commit message also says "we can't assume this will be the same forever". Nothing enforces an even size either. aeon_firmware_load() passes the size from request_firmware() straight through: ret = aeon_firmware_boot(phydev, fw->data, fw->size); Someone who trusts the comment later could remove the guard as redundant. That would bring back the out-of-bounds read of data[size] that this patch fixes. Maybe the comment could say something like "Firmware should be u16 aligned, zero-pad a trailing odd byte"? > > ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, > VEND1_GLB_REG_MDIO_INDIRECT_LOAD, val); -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261022222955.698722-1-ansuelsmth%40gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete 2026-10-22 22:29 [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete Christian Marangi ` (2 preceding siblings ...) 2026-10-22 22:29 ` [PATCH net v2 4/4] net: phy: as21xxx: handle odd firmware on loading Christian Marangi @ 2026-10-26 23:07 ` netdev-bot+sashiko 3 siblings, 0 replies; 8+ messages in thread From: netdev-bot+sashiko @ 2026-10-26 23:07 UTC (permalink / raw) To: ansuelsmth Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni, netdev, linux-kernel Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin. Low priority / nit picks for your consideration: - [Low] The commit message for "net: phy: as21xxx: handle corner case with link and autoneg complete" describes the wrong condition. -- net: phy: as21xxx: handle corner case with link and autoneg complete This commit makes as21xxx_read_link() store the autoneg complete bit from MDIO_STAT1 in phydev->autoneg_complete. When autoneg is enabled but has not completed, it reports the link as down. This is the same approach genphy_update_link() takes. > Add missing case in custom read_link, when autoneg is started, autoneg > complete bit is reset but link is still not up. [Severity: Low] Is this the condition the new code actually handles? If the link is not up, MDIO_STAT1_LSTATUS is already clear. That means phydev->link is already 0 before the new check runs: phydev->link = !!(status & MDIO_STAT1_LSTATUS); phydev->autoneg_complete = !!(status & MDIO_AN_STAT1_COMPLETE); ... if (phydev->autoneg == AUTONEG_ENABLE && !phydev->autoneg_complete) phydev->link = 0; The check only changes anything when MDIO_STAT1_LSTATUS is still set and MDIO_AN_STAT1_COMPLETE has already been cleared. The in-code comment, copied from genphy_update_link(), describes it that way: /* Consider the case that autoneg was started and "aneg complete" * bit has been reset, but "link up" bit not yet. */ Could the commit message be reworded to match? The case being handled is that the link up bit has not been reset yet, not that the link is not up. It could also help stable backporters to state the user-visible effect. Without this check, as21xxx_read_status() can move from link down to link up while genphy_c45_read_lpa() is taking its !MDIO_AN_STAT1_COMPLETE branch: if (!(val & MDIO_AN_STAT1_COMPLETE)) { linkmode_clear_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->lp_advertising); mii_10gbt_stat_mod_linkmode_lpa_t(phydev->lp_advertising, 0); mii_adv_mod_linkmode_adv_t(phydev->lp_advertising, 0); ... return 0; } In that case phy_resolve_aneg_linkmode() resolves speed and duplex after the base page and 10G link partner bits have been cleared. The link can then be reported up with an unknown or stale speed. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261022222955.698722-1-ansuelsmth%40gmail.com ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-26 23:07 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-22 22:29 [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete Christian Marangi 2026-10-22 22:29 ` [PATCH net v2 2/4] net: phy: as21xxx: fix read_status speed handling Christian Marangi 2026-10-26 23:07 ` netdev-bot+sashiko 2026-10-22 22:29 ` [PATCH net v2 3/4] net: phy: as21xxx: force C45 OPs for AUTONEG Christian Marangi 2026-10-26 23:07 ` netdev-bot+sashiko 2026-10-22 22:29 ` [PATCH net v2 4/4] net: phy: as21xxx: handle odd firmware on loading Christian Marangi 2026-10-26 23:07 ` netdev-bot+sashiko 2026-10-26 23:07 ` [PATCH net v2 1/4] net: phy: as21xxx: handle corner case with link and autoneg complete netdev-bot+sashiko
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®