| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
The RX programmable burst length was left at its reset value of zero, so the RX DMA transferred received frames from the MTL RX FIFO to memory in minimal bursts and could not keep up with 1Gbit line rate: under a sustained TCP RX stream the 4KB MTL RX FIFO overflows (the MTL RX overflow counter increments) even while free RX descriptors are available, capping TCP RX throughput at ~50Mbit/s on an OpenMV N6. The TX path already programs a burst length; set the RX side to 32 beats to match the FIFO drain rate to line rate. Verified on an OpenMV N6 (RGMII, 1Gbit link) with the MTL RX queue overflow counter (MTLRXQ0MPOCR) and iperf-style Python benchmarks: this change and the AXI outstanding-request limit change together stop the FIFO overflows at line rate. Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
The DMA system bus mode register's read and write outstanding-request limit fields reset to zero (one outstanding AXI request), which serialises the RX DMA's memory writes and, combined with the burst length, determines how fast the MTL RX FIFO can drain. At 1Gbit line rate one outstanding request is not enough: the 4KB FIFO fills in a few frame times whenever a burst arrives and the MTL drops frames (RX overflow counter increments) even though free RX descriptors are available. Set both limits to their maximum of 4 outstanding requests. Together with programming the RX burst length this lets the RX DMA sustain line rate on an OpenMV N6 (RGMII, 1Gbit link), verified with the MTL RX overflow counter and Python socket benchmarks. Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
The STM32N6 MAC is configured with checksum offload enabled (MACCR.IPC), so the hardware verifies incoming IPv4 header and TCP/UDP/ICMP payload checksums, and because the MTL RX queue operates in store-and-forward mode with forward-error-packets left disabled, frames that fail those checks are dropped before they reach the driver. lwip was nevertheless re-verifying every RX checksum in software, and because this driver runs lwip's input path directly in the ETH IRQ handler, that work is done at interrupt priority for every received frame. Keep only the software ICMP checks (cheap and rare) and drop the redundant IP/UDP/TCP ones on STM32N6. Other MCUs are unchanged, as they do not enable MACCR.IPC. On an OpenMV N6 (RGMII, 1Gbit link) this raises sustained TCP RX throughput measured from a Python socket benchmark by a further ~30%, with TCP TX, UDP TX and UDP RX rates unchanged. Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
|
Code size report: Reference: lib/tinyusb: Update tinyusb submodule to MicroPython's fork. [791ba6e]
Comparison: stm32/mpu: Reset the MPU to a clean state before configuring it. [merge of 9e7ba11]
mpy-cross: +0 +0.000%
bare-arm: +0 +0.000%
minimal x86: +0 +0.000%
unix x64: +0 +0.000% standard
stm32: +0 +0.000% PYBV10
esp32: +0 +0.000% ESP32_GENERIC
mimxrt: +0 +0.000% TEENSY40
rp2: +0 +0.000% RPI_PICO_W
samd: +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
qemu rv32: +0 +0.000% VIRT_RV32
|
Sorry, something went wrong.
On ARMv8-M parts the bootloader can hand control to the application with the MPU still enabled: the OpenMV N6 bootloader was observed to do so, with CTRL=ENABLE|HFNMIENA|PRIVDEFENA and its own MAIR attributes still in place. mpu_init() then rewrote MAIR0, region 0 and finally CTRL while the MPU was live and code was executing from external XSPI flash. Reconfiguring a live MPU under XIP raises a spurious MemManage fault (IACCVIOL) on the Cortex-M55 within a few instructions of the CTRL write, but only when an instruction fetch happens to fall inside the reconfiguration window -- so whether a given firmware boots depended on its code layout. Several OPENMV_N6 builds of current master fault on every boot (parked in MemManage_Handler at reset, CFSR=IACCVIOL, before reaching main), while other builds of the same source boot cleanly. Fix by putting the MPU into a defined state first: disable it (with DSB/ISB), clear every region, then configure MAIR0 and region 0 and enable it. With this change previously non-booting layouts boot reliably (verified over repeated resets on an OpenMV N6), and layouts that already worked are unaffected. Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
| Back | FazBrowse Home | New Git URL |
Summary
Sustained TCP receive on the STM32N6 tops out at ~50Mbit/s on a 1Gbit RGMII
link (OpenMV N6), while TCP transmit runs at ~620Mbit/s on the same setup.
The bottleneck is the RX DMA: the 4KB MTL RX FIFO overflows (the MTL RX
queue overflow counter increments) even while free RX descriptors are
available, i.e. the DMA cannot drain the FIFO at line rate.
Three eth changes, one commit each, all scoped to STM32N6:
reset value of 0, so the RX DMA moves frames out of the FIFO in minimal
bursts. The TX path already programs a burst length. Set RX to 32 beats.
outstanding-request limit fields reset to one outstanding AXI request,
which serialises the RX DMA's memory writes. Raise both to the maximum.
configured with checksum offload (MACCR.IPC), and the MTL — in
store-and-forward mode with forward-error-packets left clear — drops
checksum-errored frames before the driver sees them. lwip was
nevertheless re-verifying every RX checksum in software, and since this
driver runs lwip's input path inside the ETH IRQ handler, that work
happens at interrupt priority for every frame. Keep only the software
ICMP checks on STM32N6. Other MCUs don't enable MACCR.IPC and are
unchanged.
Each register fix was first validated by poking the register live from
Python and watching the MTL overflow counter and throughput respond, then
implemented and re-measured.
A fourth commit fixes a boot-reliability bug found while validating the
above on hardware: some OPENMV_N6 builds of current master fail to boot,
parked in MemManage_Handler before reaching main() with CFSR=IACCVIOL, and
whether a given build boots depends on its code layout. Root cause: this
board boots via a bootloader (mboot or OpenMV's — both supported per the
board's mpconfigboard.mk), and both hand control to the application with
the MPU still enabled (mboot calls mpu_init() itself and
branch_to_application() never disables it; the OpenMV bootloader was
observed handing over with CTRL=ENABLE|HFNMIENA|PRIVDEFENA). mpu_init()
then reprograms MAIR0, region 0 and CTRL while the MPU is live and code is
executing from external XSPI flash — an instruction fetch landing inside
that reconfiguration window takes a spurious MemManage on the Cortex-M55.
The fix disables the MPU (with barriers) and clears every region before
configuring it, so initialization starts from a defined state.
Performance on an OpenMV N6 (1Gbit RGMII link, Python socket benchmark
with preallocated buffers and 64KB readinto, same board/host/switch,
8 runs across two build layouts):
Testing
Tested on an OpenMV N6 (STM32N657, RGMII, 1Gbit link) running this
branch's OPENMV_N6 build: Python socket benchmarks for TCP/UDP in both
directions (numbers above), plus hardware-counter verification that the
MTL RX FIFO overflow counter stops incrementing under sustained TCP RX
after the register changes.
The MPU fix was verified by flashing a master build that faults on every
boot (MemManage before main) and confirming the same source with this fix
boots cleanly across repeated reset cycles; the full branch was also
boot-cycled and re-benchmarked on top of the fix.
An STM32H747 board (Arduino Portenta H7, 100Mbit RMII link) was
runtime-tested with the same benchmark suite: ~line rate (TCP RX 91.1 /
TCP TX 80.9 / UDP TX 87.5 / UDP RX ~95 Mbit/s), identical with and without
the checksum change — consistent with the H7 not being RX-CPU-bound at
100Mbit. The eth changes are all N6-scoped: the H7 (like F4/F7) does not
enable MACCR.IPC, so its software checksum verification is retained.
Build-tested: OPENMV_N6, NUCLEO_H563ZI (shares the ARMv8-M
mpu_init()), NUCLEO_H743ZI, NUCLEO_F767ZI (the second eth commit
restructures a #if shared with the H5/H7 path).
Trade-offs and Alternatives
No RAM and negligible code-size cost — the eth changes are register
configuration and a compile-time lwip checksum setting; the MPU change
adds a short clear-all-regions loop at init.
The remaining gap to line rate on TCP RX is the RX ring depth: lwip's
input path runs under the lwip lock (which masks the ETH IRQ during
Python-side socket operations), and when the 16-entry RX ring fills during
those windows the FIFO overflows again. Deepening the ring from 16 to 48
entries was measured at ~234 Mbit/s TCP RX (~4.7×) but costs 48KB of RAM,
so it is deliberately not included; it could be exposed later as a
board-configurable option (e.g. MICROPY_HW_ETH_RX_BUF_NUM) for boards
willing to spend the RAM.
The MPU commit is independent of the eth commits and can be split into its
own PR if preferred.
Generative AI
I used generative AI tools when creating this PR, but a human has checked
the code and is responsible for the code and the description above.