Skip to content

arch/arm/stm32h7: poll MDIO completion in microseconds, not 5 ms steps - #20067

Merged
acassis merged 1 commit into
apache:masterfrom
dakejahl:dakejahl/stm32h7-mdio-poll
Sep 7, 2026
Merged

arch/arm/stm32h7: poll MDIO completion in microseconds, not 5 ms steps#20067
acassis merged 1 commit into
apache:masterfrom
dakejahl:dakejahl/stm32h7-mdio-poll

Conversation

@dakejahl

@dakejahl dakejahl commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

stm32_c22_read() / stm32_c22_write() in arch/arm/src/stm32h7/stm32_mdio.c wait for the MACMDIOAR busy bit with up_mdelay(5) between checks. A Clause 22 frame is ~30 us at a 2.5 MHz MDC, so the first check always sees the bus busy and every PHY register access costs a 5 ms busy-wait. Before the MDIO bus refactor (95efa6f) stm32_phyread() polled the busy bit in a tight loop.

The cost shows up in stm32_phyinit(), which waits for link-up with PHY_RETRY_TIMEOUT (6552) MSR reads when STM32H7_AUTONEG is set. With no cable attached, ifup spends ~33 s of CPU in up_mdelay() with the network lock held. On an STM32H753 running PX4 the netinit monitor thread pinned the core at 44% for the first 65 s after boot (priority-inherited to 100 because a UDP sender was blocked on the net lock), and every socket operation on other threads stalled until the link wait timed out.

This polls every 10 us instead, with the timeout expressed in microseconds so the total bound stays at 10 ms, and reports a timeout from the result rather than the loop counter so a transfer completing on the last iteration is not logged as timed out.

Impact

STM32H7 boards with STM32H7_ETHMAC. PHY register access goes from a fixed 5 ms to ~30 us; the autonegotiation link wait with no cable drops from ~33 s of CPU to under a second of mostly sleeping. No functional change when the PHY responds.

Testing

Host: Linux, GCC 13.2.1. Board: ARK FMU-V6X (STM32H753, LAN8742A PHY, CONFIG_STM32H7_AUTONEG=y, CONFIG_STM32H7_PHY_POLLING=y, CONFIG_NETINIT_MONITOR=y), no ethernet cable, running PX4 on NuttX 12.12.0 with this change cherry-picked. top once sampled every 2 s from power-on over the debug console.

Before (netinit at 44% until ~65 s uptime, priority 100 inherited from a blocked UDP sender, then 0.44% = one 5 ms MDIO read per 2 s poll):

[ 23.0]    6 netinit                       537 44.715   704/ 2032 100 ( 49)  READY  4
[ 49.0]    6 netinit                       538 44.770   704/ 2032 100 ( 49)  READY  4
[ 75.1]    6 netinit                       529 44.076   704/ 2032 100 ( 49)  READY  4
[ 77.1]    6 netinit                       157 13.072   704/ 2032  49 ( 49)  w:sem  4 | WARN  [mavlink] no broadcasting address found
[ 79.1]    6 netinit                         5  0.431   704/ 2032  49 ( 49)  w:sem  4
[ 99.1]    6 netinit                         5  0.440   704/ 2032  49 ( 49)  w:sem  4

After:

[ 13.0]    6 netinit                        19  1.654   704/ 2032 100 ( 49)  w:sig  4
[ 15.0]    6 netinit                        11  0.940   704/ 2032  49 ( 49)  w:sem  4 | WARN  [mavlink] no broadcasting address found
[ 17.0]    6 netinit                         0  0.004   704/ 2032  49 ( 49)  w:sem  4
[ 69.1]    6 netinit                         0  0.005   704/ 2032  49 ( 49)  w:sem  4

Timestamps are seconds since power-on; the board reaches the shell at ~12 s. The UDP MAVLink instance that was blocked on the net lock for 65 s now proceeds at ~2 s. tools/checkpatch.sh -c -u -m -g origin/master..HEAD passes.

stm32_c22_read() and stm32_c22_write() waited for the MACMDIOAR busy bit
with up_mdelay(5) between checks. A Clause 22 frame takes about 30 us,
so the first check always sees the bus busy and every PHY register
access costs a 5 ms busy-wait, roughly 150 times the transfer.

stm32_phyinit() waits for link-up with PHY_RETRY_TIMEOUT (6552) MSR
reads. With no cable attached that is 33 s of CPU spent in
up_mdelay() inside ifup, with the network lock held: on an STM32H753
the netinit thread pinned the core at 44% for the first 65 s after
boot and every socket operation on other threads blocked until it gave
up. Before the MDIO bus refactor, stm32_phyread() polled the busy bit
in a tight loop.

Poll every 10 us instead, with the timeout expressed in microseconds so
the total bound stays at 10 ms, and report the timeout from the result
rather than the loop counter so a transfer that completes on the last
iteration is not logged as timed out.

Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
@github-actions github-actions Bot added Arch: arm Issues related to ARM (32-bit) architecture Size: S The size of the change in this PR is small labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

@acassis
acassis merged commit 304cbb1 into apache:master Sep 7, 2026
38 checks passed
dakejahl added a commit to PX4/PX4-Autopilot that referenced this pull request Sep 7, 2026
Every PHY register access on STM32H7 spent 5 ms in up_mdelay(), so the
boot-time autonegotiation link wait ran netinit at 44% CPU for 65 s with
the net lock held whenever no ethernet cable was attached (PX4/NuttX#414,
apache/nuttx#20067). Also picks up the two upstream stm32_mdio fixes for
the lower-half cast and the Clause 22 write register field.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Arch: arm Issues related to ARM (32-bit) architecture Size: S The size of the change in this PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants