教程 43:从 ETH_IRQHandler() 到 HAL_ETH_Transmit()——STM32H7 Ethernet DMA、Descriptor、Cache 与 Zero-copy 边界
摘要:沿 STM32H750 Ethernet RX/TX 真实源码追踪 descriptor OWN、buffer 生命周期、D-Cache maintenance、pbuf copy 与当前 TX zero-copy 边界。
[TOC]
DMA(Direct Memory Access,直接内存访问)允许 STM32 Ethernet 外设在不经过 CPU 逐字节搬运的情况下直接读写 SRAM。Descriptor(DMA 描述符) 是 DMA 使用的控制记录,保存 buffer 地址、长度和状态;本文反复出现的 OWN bit 表示某个 descriptor 当前由 DMA 还是 CPU 处理。STM32H7 的 Cortex-M7 又带有 D-Cache(数据缓存),因此当 CPU 和 DMA 访问同一块 cacheable 内存时必须处理 cache coherency(缓存一致性)。所谓 zero-copy(零拷贝) 也不是“源码里没有 memcpy()”这么简单,而是 buffer 的所有权与生命周期能否在 lwIP、Driver 与 DMA 之间安全移交。S4S5S6
Stage 20/21 已经建立通用 DMA/zero-copy 与 descriptor ring 模型;Stage 43 只回答当前 STM32H750 Art-Pi 具体实现:RX frame 如何从 DMA descriptor 进入 pbuf,TX pbuf 又如何直接成为 DMA source,以及 D-Cache 与 ownership 在这两条路径上分别在哪里处理。
阅读源码前:建议提前阅读
- STM32H742/H743/H750 Reference Manual RM0433
- 用途:查 Ethernet DMA descriptor、OWN、buffer address 与 tail pointer(用于通知 DMA 环中可继续处理位置的尾指针/寄存器语义)。S5
- ST AN4839 — Level 1 cache on STM32F7/H7
- 用途:理解 Cortex-M7 D-Cache 与 DMA 共享内存时为什么需要 Clean(把 cache 中已修改数据写回内存)、Invalidate(丢弃 cache 中可能过期的副本),或使用 MPU(Memory Protection Unit,内存保护单元)把 DMA 区域设成 non-cacheable。S6
- ST UM2217 — STM32H7 HAL and Low-Layer Drivers
- 用途:把 HAL ETH API contract 与 RT-Thread 当前 buffer/线程策略区分开。S7
- RT-Thread
drv_eth.c(固定 commit)- 用途:本文真正使用的 RX buffer pool、cache helper、
rt_stm32_eth_rx()与rt_stm32_eth_tx()实现。S1
- 用途:本文真正使用的 RX buffer pool、cache helper、
进入 RX/TX 源码前:先认清三个对象和两个方向
| 对象 | 谁主要使用 | 作用 | 什么时候会改变 ownership |
|---|---|---|---|
| DMA descriptor | CPU + Ethernet DMA | 保存 buffer 地址、长度、状态/OWN | CPU 准备完交给 DMA;DMA 完成后交回 CPU |
| DMA buffer | Ethernet DMA + Driver | 承载真实 Ethernet frame bytes | RX/TX 生命周期中跟随 descriptor 被复用 |
lwIP pbuf |
lwIP + RT-Thread Port/Driver | 承载协议栈 packet view 与引用计数 | 是否能直接交给 DMA 取决于当前 TX/RX 策略 |
当前 Art-Pi 的 RX 与 TX 并不是对称的:RX 会把 DMA frame copy 到新分配的 pbuf;TX 则把 pbuf payload 地址直接交给 HAL/DMA,但 HAL_ETH_Transmit() 会阻塞到 descriptor OWN 清零后才返回,因此它不是异步 ownership-transfer 型 zero-copy。S1S4
1 | flowchart TD |
这张图只建立 ownership 心智模型。下面仍从真实 RX interrupt 入口开始逐函数追踪;到 TX 部分再从 lwIP pbuf 的发送入口进入,不会用这张图替代源码链。
1. RX 的真实入口:ETH_IRQHandler() 只把执行权交给 HAL
Stage 42 已经建立初始化链。进入运行期后,一帧 Ethernet frame 被 MAC/DMA 收下并产生 RX interrupt,CPU 首先进入具体 STM32 ISR:S1
1 | void ETH_IRQHandler(void) |
这个 ISR 不碰 pbuf,也不遍历 descriptor;它只建立 RT-Thread interrupt context,然后调用 ST HAL 的 HAL_ETH_IRQHandler()。
进入 HAL_ETH_IRQHandler() 后,RX interrupt 分支检查 RI 与 RX interrupt enable,清 pending bits,再调用 RX complete callback:S4
1 | /* Packet received */ |
当前 RT-Thread driver 使用 weak callback 方式,因此下一站是 HAL_ETH_RxCpltCallback()。
2. HAL_ETH_RxCpltCallback() 不读数据,只把工作从 ISR 交给 erx thread
RT-Thread 实现的 callback 只调用 eth_device_ready():S1
1 | void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth) |
进入 RT-Thread ethernetif.c 后,eth_device_ready() 用 rx_notice 避免在已有 pending RX notification 时反复向 mailbox 塞同一 device:S3
1 | rt_err_t eth_device_ready(struct eth_device* dev) |
于是 RX 上半部到此结束:
1 | flowchart LR |
这条异步边界非常重要:descriptor walk、buffer allocation、cache maintenance、pbuf allocation 和 memcpy 全部发生在线程上下文,不在 ETH ISR 中。
3. eth_rx_thread_entry() 才真正调用 rt_stm32_eth_rx()
erx thread 收到 eth_device * 后,会清掉 rx_notice,然后循环调用 device->eth_rx(),直到 driver 返回 NULL。S3
继续阅读 eth_rx_thread_entry() 的接收循环:
1 | /* receive all of buffer */ |
Stage 42 已经证明 device->eth_rx = rt_stm32_eth_rx,而 device->netif->input = tcpip_input。因此现在可以把注意力完全放到中间这一步:rt_stm32_eth_rx() 怎样把 DMA buffers 变成 lwIP pbuf。
4. 进入 rt_stm32_eth_rx():第一步不是分配 pbuf,而是 HAL_ETH_ReadData()
函数先取得 MAC mutex,确认 MAC 已启动,然后调用 HAL 读取已经完成的 RX descriptors:S1
1 | struct pbuf *rt_stm32_eth_rx(rt_device_t dev) |
这里第一次遇到 descriptor ownership。STM32H7 RX descriptor 中的 OWN bit 表示 descriptor 当前归 DMA 还是 CPU/HAL:DMA 拥有时 CPU 不能把它当成已完成 packet;DMA 收完 frame 后清除 OWN,HAL 才开始消费。S4S5
5. HAL_ETH_ReadData():只处理 OWN 已经回到 CPU 的 RX descriptor
进入 HAL_ETH_ReadData() 后,HAL 从 RxDescIdx 取得当前 descriptor,并计算本轮最多能处理多少已完成 descriptor:S4
1 | descidx = heth->RxDescList.RxDescIdx; |
这段条件直接给出 RX 的核心所有权规则:
1 | OWN = 1 -> DMA 仍拥有 descriptor |
进入循环后,HAL 用 FD/LD 识别 frame first/last descriptor,计算当前 buffer 对应的 frame 片段长度。当 descriptor 对应 packet data 时,它调用 Rx Link Callback,把 DMA buffer 交给上层组织成一个 packet chain:S4
1 | /* Link data */ |
这里 BackupAddr0 被清零很关键:这个 descriptor 原来使用的 RX buffer 已经被移交给当前 packet chain,descriptor 接下来必须拿到另一个可用 buffer 才能重新交给 DMA。
继续阅读 HAL_ETH_ReadData():HAL 随后推进 descriptor index,并累计需要 rebuild 的 descriptor 数量:S4
1 | /* Increment current rx descriptor index */ |
这就是接下来必须进入 ETH_UpdateDescriptor() 的直接 call site。
6. HAL_ETH_RxLinkCallback() 把 DMA buffer 串起来,但还没有创建 pbuf
在进入 descriptor rebuild 前,先看刚才同步调用的 HAL_ETH_RxLinkCallback()。RT-Thread driver 没有在这里分配 pbuf,而是把 buff 映射回自己的 Rx_Buff_Info[index]:S1
1 | void HAL_ETH_RxLinkCallback(void **pStart, void **pEnd, uint8_t *buff, uint16_t length) |
继续阅读 HAL_ETH_RxLinkCallback() 的后半段,它把一个 frame 的多个 DMA buffer metadata 串成链:S1
1 | if (*pStart == RT_NULL) |
因此此时存在三类不同对象:
| 对象 | 保存什么 | 当前 owner/使用者 |
|---|---|---|
| DMA descriptor | buffer address、status、OWN | HAL/DMA ring |
Rx_Buff_Storage[] |
真正 Ethernet frame bytes | 已完成 frame 暂时占用 |
Rx_Buff_Info[] |
allocated/length/next metadata |
RT-Thread driver |
Rx_Buff_Info 不是 descriptor,也不是 lwIP pbuf。 它只是 driver 在 HAL callback 和最终 pbuf copy 之间建立的一层软件 ownership metadata。
7. 为什么 RX buffer pool 是 descriptor 数量的两倍
当前 driver 定义:S1
1 |
并为每个 RX buffer 保存 allocated 状态:S1
1 | struct rt_stm32_eth_rx_buffer |
把这个数量关系放回 ETH_UpdateDescriptor() 的运行时路径,可以确定它产生的直接效果:HAL 在 HAL_ETH_ReadData() 返回给 rt_stm32_eth_rx() 之前,就尝试重新给刚消费的 descriptors 补 buffer 并把 OWN 交回 DMA;但当前 frame 占用的旧 buffers 还要等 driver 完成 invalidate/copy 才能释放。因此额外的 free slots 允许“旧 frame 仍被 CPU 使用”和“RX ring 获得替换 buffer”发生重叠。
源码没有单独注释 * 2U 的设计理由,因此这里不把该倍数写成协议要求或作者明确声明的设计意图;能够由当前实现直接确认的是上述生命周期效果。S1S4
8. 进入 ETH_UpdateDescriptor():重新分配 buffer,再把 OWN 交回 DMA
HAL_ETH_ReadData() 在退出 descriptor scan 前调用 ETH_UpdateDescriptor()。函数从 RxBuildDescIdx 开始,为需要 rebuild 的 descriptor 请求新 buffer:S4
1 | while ((desccount > 0U) && (allocStatus != 0U)) |
当前 callback HAL_ETH_RxAllocateCallback() 在 Rx_Buff_Info[] 中找 allocated == RT_FALSE 的 slot,标记占用并返回真实 DMA buffer 地址:S1
1 | void HAL_ETH_RxAllocateCallback(uint8_t **buff) |
回到 ETH_UpdateDescriptor()。拿到 buffer 后,当前 HAL 实现先把地址写进 BackupAddr0/DESC0,随后才写 DESC3 的 OWN/BUF1V。这个先后顺序可以直接从当前源码确认;源码本身没有在这里单独注释其设计理由,因此这里只把它作为当前实现的 ownership handoff 顺序来解释:S4
1 | if (allocStatus != 0U) |
只要本轮至少 rebuild 一个 descriptor,HAL 还会先执行 __DMB(),再更新 RX tail pointer:S4
1 | if (heth->RxDescList.RxBuildDescCnt != desccount) |
到这里,刚才被 CPU 消费的 RX descriptor 已经获得新 buffer 并重新进入 DMA 可用 ring;而旧 frame buffers 仍由 rx_buffer metadata chain 暂时持有。
9. HAL_ETH_ReadData() 返回 packet chain 后,driver 才处理 Cache 与 pbuf
descriptor rebuild 完成后,HAL_ETH_ReadData() 把 pRxStart 返回给调用者,然后清空该 pointer:S4
1 | if (rxdataready == 1U) |
执行回到 rt_stm32_eth_rx()。此时 rx_buffer 指向的是 RT-Thread metadata chain。driver 先统计总 frame length,同时对每一段真实 DMA buffer 调用 eth_invalidate_cache():S1
1 | for (current = rx_buffer; current != RT_NULL; current = current->next) |
Cache coherency 在这里第一次真正参与 RX 数据面:DMA 已经把新 frame 写到内存,CPU 在读取这些 bytes 之前不能继续使用可能存在的 stale cache line。当前 generic helper 会把维护范围扩到完整 32-byte cache line,并在 D-Cache 实际开启时调用 CMSIS invalidate:S1
1 | static void eth_invalidate_cache(const void *buffer, rt_size_t length) |
Art-Pi 当前把 descriptor 与 Rx_Buff_Storage 所在 0x30040000 region 配成 non-cacheable/shareable,所以这块固定 DMA memory 的一致性主要由 MPU memory attribute 保证;driver 仍保留通用 cache helper,使同一 driver 代码能覆盖其他 cache policy。S2S6 不能把这一板级策略泛化成所有 STM32H7 Ethernet Port 都必须使用同样地址或 MPU region。
10. 当前 RX 不是 zero-copy:pbuf_alloc() 后发生一次完整 frame copy
继续阅读 rt_stm32_eth_rx()。获得总长度后,driver 分配新的 PBUF_RAM,再调用 eth_rx_copy():S1
1 | p = pbuf_alloc(PBUF_RAW, frame_length, PBUF_RAM); |
eth_rx_copy() 同时遍历 DMA buffer metadata chain 和 lwIP pbuf chain,用 rt_memcpy() 把 bytes 搬过去:S1
1 | static rt_err_t eth_rx_copy(struct pbuf *p, struct rt_stm32_eth_rx_buffer *rx_buffer) |
继续阅读同一个 eth_rx_copy(),真正的 copy 发生在这里:S1
1 | rx_remaining = (rt_size_t)rx_buffer->length - rx_offset; |
因此当前 RX 明确是:
1 | flowchart LR |
这里不能称为 RX zero-copy。
11. eth_rx_release_buffers() 才把旧 DMA buffers 重新放回软件 free pool
pbuf copy 完成以后,driver 才释放本 frame 使用过的 Rx_Buff_Info slots:S1
1 | static void eth_rx_release_buffers(struct rt_stm32_eth_rx_buffer *rx_buffer) |
注意这个“释放”不是 free() DMA memory;它只是把固定 buffer pool 中的 slot 重新标成可分配。下一次 HAL_ETH_RxAllocateCallback() 才可能重新把这些 buffers 绑定到 descriptors。
所以一块 RX buffer 的完整软件生命周期是:
1 | stateDiagram-v2 |
这张图才解释了为什么“descriptor 已经重新给 DMA”与“旧 frame buffer 仍被 CPU 使用”可以同时成立:rebuild 时 descriptor 获得的是另一个 free buffer。
12. RX zero-copy 的真正变化是 buffer ownership
当前 PBUF_RAM + memcpy 把 DMA buffer 与 lwIP pbuf 生命周期彻底解耦。若移除 eth_rx_copy(),必须改成 custom pbuf/free callback 或等价机制,让 pbuf 最后一个引用释放之后 才把 DMA buffer 归还 pool,并保证 descriptor 不会提前重新绑定同一 buffer;多 descriptor frame、cache-line alignment 与 invalidate 也必须纳入同一个 ownership contract。Stage 20 已经建立这些通用原则,这里不再重复展开。
13. RX Buffer Unavailable 为什么也会唤醒 erx
RX ring 可能因为没有可用 buffer/descriptor 而出现 ETH_DMA_RX_BUFFER_UNAVAILABLE_FLAG。当前 HAL_ETH_ErrorCallback() 对这个条件不直接 reset MAC,而是再次调用 eth_device_ready():S1
1 | void HAL_ETH_ErrorCallback(ETH_HandleTypeDef *heth) |
注释已经说明当前恢复策略:让 erx 再次进入 HAL_ETH_ReadData(),由其 ETH_UpdateDescriptor() 尝试 replenish descriptor 并更新 tail pointer。这里再次体现 ISR/error callback 只负责通知,复杂恢复仍放到 thread context。
14. TX 从 pbuf chain 开始:driver 直接把 payload 地址交给 HAL
现在切换到 TX。Stage 42 已经证明:
1 | lwIP netif->linkoutput |
进入 rt_stm32_eth_tx() 后,driver 在栈上建立 ETH_BufferTypeDef tx_buffer[ETH_TX_DESC_CNT],然后遍历 lwIP pbuf chain。每个 element 直接保存 q->payload 地址与 q->len,没有先 memcpy 到独立 TX DMA buffer:S1
1 | rt_err_t rt_stm32_eth_tx(rt_device_t dev, struct pbuf *p) |
因此当前 TX 的第一层 boundary 是:
1 | lwIP pbuf payload |
这意味着 payload 本身没有发生 driver-side copy。
15. TX 为什么必须在交给 DMA 前 Clean D-Cache
继续阅读 rt_stm32_eth_tx() 的同一个 pbuf loop。每个 payload 地址填入 HAL buffer chain 后立即执行 eth_clean_cache():S1
1 | if (index > 0U) |
RX 固定 buffers 位于 Art-Pi non-cacheable region,但 TX q->payload 来自 lwIP 内存体系,不保证位于那个 region。CPU 可能已经修改 payload,而最新 bytes 只存在 dirty D-Cache line 中;DMA 直接读 SRAM 就可能拿到旧数据。因此当前 driver 在 DMA 使用 payload 前做 clean。S1S6
eth_clean_cache() 与 RX invalidate 使用同一 32-byte 对齐策略:S1
1 | static void eth_clean_cache(const void *buffer, rt_size_t length) |
Clean 与 Invalidate 的方向不能记反:
| 方向 | 谁产生最新数据 | CPU cache 风险 | 当前动作 |
|---|---|---|---|
| TX | CPU | dirty line 尚未写回 SRAM | Clean,先让 DMA 能读到最新 bytes |
| RX | DMA | CPU 可能持有旧 line | Invalidate,CPU 随后重新从内存读取 |
当前 helper 对起止地址做 cache-line 扩展,也意味着 DMA buffer/payload 与其他可写对象混在同一 cache line 时要谨慎;cache maintenance 的粒度不是 Ethernet frame 字节,而是 cache line。S6
16. 回到 rt_stm32_eth_tx():HAL_ETH_Transmit() 前还有 link/MAC 状态门槛
pbuf chain 转成 HAL buffer chain 后,driver 获取 MAC mutex,并再次检查 link 与 mac_started,避免 Link 状态在准备 TX 过程中改变:S1
1 | if (rt_mutex_take(&stm32_eth_device.mac_lock, RT_WAITING_FOREVER) != RT_EOK) |
现在进入 ST HAL 的 blocking transmit path。
17. HAL_ETH_Transmit() 先调用 ETH_Prepare_Tx_Descriptors()
HAL_ETH_Transmit() 要求 HAL state 已经 STARTED;然后把 TxConfig 交给 ETH_Prepare_Tx_Descriptors():S4
1 | if (heth->gState == HAL_ETH_STATE_STARTED) |
进入 ETH_Prepare_Tx_Descriptors() 后,HAL 首先检查当前 descriptor 是否仍由 DMA 拥有,或者 software 仍记录 packet address;任一成立都不能复用这个 descriptor:S4
1 | /* Current Tx Descriptor Owned by DMA: cannot be used by the application */ |
这就是 TX 的 ownership gate:CPU/HAL 只有在 descriptor 不归 DMA 时才能重新填写。
18. ETH_Prepare_Tx_Descriptors() 把 q->payload 地址写入 descriptor,再设置 OWN
当前 TxConfig 只启用 CRC/PAD,并可能启用 checksum offload,不使用 VLAN/TSO context descriptor。因此主线进入 normal descriptor configuration。HAL 把 txbuffer->buffer 和 length 直接写进 descriptor:S4
1 | /* Set header or buffer 1 address */ |
函数继续设置 frame length/checksum/CRC policy,再标记 first descriptor。最关键的顺序是:先写完 descriptor 内容,执行 __DMB(),最后才设置 OWN。S4
1 | /* Mark it as First Descriptor */ |
这个 barrier/OWN 顺序是硬件 ownership handoff 的核心:DMA 看到 OWN 之前,descriptor 中用于本次 packet 的 buffer address、length 和 control fields 必须已经对系统可见。S4S5
对于更长的 buffer chain,函数会推进 descriptor index、检查下一个 descriptor 仍不属于 DMA,然后继续把后续 buffer 地址写入 ring;最后一个 descriptor 被标记 LD,并更新 CurTxDesc。S4
19. 回到 HAL_ETH_Transmit():Tail Pointer 真正通知 DMA,然后阻塞等待 OWN 清零
ETH_Prepare_Tx_Descriptors() 返回成功以后,HAL_ETH_Transmit() 取得当前 packet 的最后 descriptor,推进软件 index,然后写 DMACTDTPR:S4
1 | dmatxdesc = (ETH_DMADescTypeDef *)(&heth->TxDescList)->TxDesc[heth->TxDescList.CurTxDesc]; |
继续阅读 HAL_ETH_Transmit():这个 API 不是立即返回,而是轮询 dmatxdesc->DESC3 & OWN,直到 DMA 释放 descriptor 或出现 DMA error/timeout:S4
1 | /* Wait for data to be transmitted or timeout occurred */ |
这解释了当前 driver 为什么可以让 ETH_BufferTypeDef tx_buffer[] 放在 rt_stm32_eth_tx() 栈上,也解释了 pbuf payload 的 lifetime:HAL 已经把 buffer 地址复制进 DMA descriptors,而且 HAL_ETH_Transmit() 在 DMA 清 OWN 前不会正常返回。
20. 当前 TX 是“payload 不复制”,但不是异步 ownership-transfer zero-copy
从 lwIP 到 DMA 的 payload path 是:
1 | flowchart LR |
因此当前 driver 没有像 RX 那样做 pbuf -> DMA TX buffer memcpy,可以称为 TX payload no-copy / zero-copy at the driver payload boundary。
但它不是一个完全异步的 ownership-transfer 模型。RT-Thread ethernetif_linkoutput() 把 pbuf pointer 发给 TX thread后等待 completion,TX thread 又要等 rt_stm32_eth_tx() 返回才 rt_completion_done();而 rt_stm32_eth_tx() 内部的 HAL_ETH_Transmit() 本身阻塞到 DMA 清掉 OWN。于是原 pbuf 生命周期被同步调用链自然 pin 住:S1S3S4
1 | sequenceDiagram |
如果未来换成完全异步 HAL_ETH_Transmit_IT() 并立即返回,就必须新增 pbuf ref/pin 与 TxComplete 后释放的 ownership contract,不能直接照搬当前 stack-local tx_buffer[] 与同步 completion 设计。
21. Art-Pi 为什么把 descriptor/RX buffer 放进 non-cacheable region
当前 H750 driver 为 descriptor 和 RX buffers 使用专门 section;Art-Pi mpu_init() 把 0x30040000 起 32 KB region 设置为 non-cacheable、shareable:S1S2
1 |
|
这是一项明确的 board memory-policy 选择:固定 descriptor/RX-buffer 区域通过 MPU 设为 non-cacheable,避免 CPU/DMA 对 OWN、地址和 RX 数据产生 cache alias;普通 lwIP TX payload 不享有该 MPU 属性,所以 Driver 仍必须在 DMA 读取前显式 clean cache。S2S5S6
22. RX 与 TX 两条生命周期现在可以完整闭环
RX:
1 | flowchart TD |
TX:
1 | flowchart TD |
这两条链把 Stage 20/21 的抽象边界落到真实 STM32H750 Port:descriptor 管 ownership/status,buffer 承载 DMA 数据,pbuf 承载 lwIP packet。zero-copy 是否成立最终取决于 buffer 生命周期能否跨 DMA 与协议栈安全移交,而不是只看有没有 memcpy()。
资料来源
[S1] RT-Thread STM32 HAL Ethernet Driver
- 类型:RT-Thread 官方仓库源码
- 版本:commit
dc8aaa73f2dbea255325ec058a083aeeb5381d0a,2026-09-28 - 定位:
bsp/stm32/libraries/HAL_Drivers/drivers/drv_eth.c:RX buffer pool、cache helpers、rt_stm32_eth_rx()、rt_stm32_eth_tx()、IRQ callbacks、DMA memory allocation - URL/文档:RT-Thread drv_eth.c
- 使用位置:Stage 43 RX/TX 数据面主线
- 支撑内容:证明当前 driver 的 buffer ownership、RX copy、TX direct payload、cache maintenance 与 error recovery 方式
[S2] STM32H750 Art-Pi linker/MPU/Kconfig
- 类型:RT-Thread 官方板级源码
- 版本:同上
- 定位:
board/linker_scripts/link.lds、board/port/drv_mpu.c、board/Kconfig - URL/文档:Art-Pi link.lds、Art-Pi drv_mpu.c、Art-Pi Kconfig
- 使用位置:“DMA memory placement”“D-Cache 与 non-cacheable region”
- 支撑内容:证明 descriptor/RX buffer 的固定地址、MPU 属性和 Art-Pi D-Cache 配置背景
[S3] RT-Thread lwIP Ethernet Port
- 类型:RT-Thread 官方仓库源码
- 版本:同上
- 定位:
components/net/lwip/port/ethernetif.c:eth_device_ready()、eth_rx_thread_entry()、ethernetif_linkoutput()、eth_tx_thread_entry() - URL/文档:RT-Thread ethernetif.c
- 使用位置:“ISR 到 erx”“TX pbuf lifetime bridge”“pbuf 进入 tcpip_input”
- 支撑内容:说明具体 STM32 DMA driver 与 lwIP pbuf 数据路径之间的 RTOS bridge
[S4] ST STM32H7 HAL Ethernet Driver
- 类型:ST 官方 HAL 源码
- 版本:commit
7e541d92019e18f98d211fc4ab9197ec8e8105f6,2026-09-29 - 定位:
Src/stm32h7xx_hal_eth.c:HAL_ETH_IRQHandler()、HAL_ETH_ReadData()、ETH_UpdateDescriptor()、HAL_ETH_Transmit()、ETH_Prepare_Tx_Descriptors() - URL/文档:STM32H7 HAL ETH source
- 使用位置:“RX descriptor 回收/重建”“TX descriptor OWN 与 blocking transmit”
- 支撑内容:证明 HAL 怎样读取 CPU-owned RX descriptors、补 buffer、更新 tail pointer,以及怎样准备 TX descriptors 并等待 DMA 释放 OWN
[S5] STM32H742/H743/H750 Reference Manual RM0433
- 类型:ST 官方参考手册
- 版本:RM0433 Rev 8
- URL/文档:RM0433
- 使用位置:“DMA descriptor/OWN/tail pointer 的硬件语义”
- 支撑内容:说明 RX/TX DMA、descriptor ring、ownership、buffer address 与 tail pointer 驱动模型
[S6] ST AN4839 — Level 1 cache on STM32F7/H7
- 类型:ST 官方 Application Note
- 版本:AN4839 Rev 2
- URL/文档:AN4839
- 使用位置:“CPU/DMA cache coherency”“Clean/Invalidate 与 non-cacheable MPU 方案”
- 支撑内容:说明 Cortex-M7 cache 与 DMA 共享 SRAM 时的数据一致性风险,以及软件 cache maintenance/MPU memory attribute 的处理方式
[S7] ST UM2217 — STM32H7 HAL and Low-Layer Drivers
- 类型:芯片厂商官方 HAL 用户手册
- 版本:UM2217 Rev 6,访问日期 2026-10-03
- URL/文档:Description of STM32H7 HAL and low-layer drivers
- 使用位置:开篇 HAL ETH contract、RX/TX HAL API 边界
- 支撑内容:定义
HAL_ETH_Start[_IT]、Ethernet MAC/DMA 配置以及 HAL ETH 收发接口,用于区分 ST HAL API contract 与当前 RT-Thread Driver 的具体 buffer/线程策略









