DMA API
用于 Rust 的 DMA(直接内存访问)抽象 API,提供安全的 DMA 内存操作接口,适用于嵌入式和裸机环境。
目录
快速开始
1. 实现 DmaOp trait
首先需要为你的平台实现 DmaOp trait,提供底层 DMA 操作支持:
use ;
use ;
;
2. 选择合适的 DMA 容器
根据你的使用场景选择:
| 需求 | 推荐容器 | 特点 |
|---|---|---|
| 需要数组类型的 DMA 缓冲区 | DArray<T> |
自动缓存同步,固定大小 |
| 需要单个结构体的 DMA 缓冲区 | DBox<T> |
自动缓存同步,单个值 |
| 映射现有缓冲区用于 DMA | SArrayPtr<T> |
手动缓存同步,单个连续内存区域映射 |
核心概念
DMA 传输方向 (DmaDirection)
DMA 操作有三种方向,决定了缓存同步的行为:
选择指南:
- ToDevice: 用于发送数据到设备(如网卡发送缓冲区)
- FromDevice: 用于接收设备数据(如网卡接收缓冲区)
- Bidirectional: 用于双向通信(如驱动程序与设备共享内存)
缓存同步
DMA 操作需要处理 CPU 缓存和内存之间的数据一致性:
| 操作 | ToDevice | FromDevice | Bidirectional |
|---|---|---|---|
写数据前 (confirm_write) |
✅ 刷新缓存 | ❌ 无操作 | ✅ 刷新缓存 |
读数据前 (prepare_read) |
❌ 无操作 | ✅ 使缓存失效 | ✅ 使缓存失效 |
自动同步 vs 手动同步:
DArray<T>和DBox<T>:每次访问(read/set/write/modify)自动同步对应元素的缓存SingleMap:不自动同步,用户必须手动调用prepare_read_all()和confirm_write_all()
DMA 地址掩码 (dma_mask)
dma_mask 指定设备可寻址的地址范围:
0xFFFFFFFF(32 位设备,最多 4GB)0xFFFFFFFFFFFFFFFF(64 位设备,全地址空间)- 其他值根据设备硬件限制
使用场景指南
场景 1: DMA 数组缓冲区
用途:需要固定大小的数组类型 DMA 缓冲区
推荐:DArray<T>
特点:
- ✅ 自动缓存同步(每次
read/set时) - ✅ 固定大小,随机访问
- ✅ 类型安全
示例:
let device = new;
// 创建 100 个 u32 的 DMA 数组
let mut dma_array = device.
.expect;
dma_array.set; // 写入(自动刷新缓存)
let value = dma_array.read; // 读取(自动失效缓存)
let dma_addr = dma_array.dma_addr; // 获取 DMA 地址给硬件
适用场景:
- 网卡数据包缓冲区
- 音频采样缓冲区
- 图像帧缓冲区
场景 2: DMA 单值容器
用途:需要单个结构体的 DMA 缓冲区
推荐:DBox<T>
特点:
- ✅ 自动缓存同步(每次
read/write/modify时) - ✅ 适合配置寄存器、描述符等
- ✅ 类型安全
示例:
let mut dma_desc = device.
.expect;
dma_desc.modify; // 修改(自动刷新缓存)
let desc = dma_desc.read; // 读取(自动失效缓存)
适用场景:
- DMA 描述符
- 设备配置结构
- 状态寄存器
场景 3: 映射现有缓冲区
用途:将已存在的缓冲区映射用于 DMA
推荐:SingleMap
特点:
- ⚠️ 手动缓存同步
- ✅ 临时映射,RAII 自动解映射
- ✅ 适用于栈分配或静态缓冲区
示例:
let mut buffer = ;
// 映射现有缓冲区
let mapping = device.map_single_array
.expect;
// ⚠️ 重要:使用前必须手动同步缓存
mapping.confirm_write_all; // 将 CPU 数据刷到内存
// ... DMA 传输 ...
mapping.prepare_read_all; // 使 CPU 缓存失效,准备接收设备数据
let dma_addr = mapping.dma_addr;
// 映射在离开作用域时自动解除(不会自动同步缓存)
适用场景:
- 临时 DMA 操作
- 栈上的小缓冲区
- 重用已分配的内存
API 方法目录
📦 创建方法
| 方法 | 用途 | 返回类型 | 缓存同步 |
|---|---|---|---|
array_zero<T>(len, dir) |
创建默认对齐的 DMA 数组 | DArray<T> |
自动 |
array_zero_with_align<T>(len, align, dir) |
创建指定对齐的 DMA 数组 | DArray<T> |
自动 |
box_zero<T>(align, dir) |
创建默认对齐的 DMA Box | DBox<T> |
自动 |
box_zero_with_align<T>(align, dir) |
创建指定对齐的 DMA Box | DBox<T> |
自动 |
map_single_array<T>(buff, align, dir) |
映射现有缓冲区 | SArrayPtr<T> |
手动 |
🔍 访问方法(DArray)
| 方法 | 用途 | 缓存同步 | 返回值 |
|---|---|---|---|
read(index) |
读取元素 | 自动失效 | Option<T> |
set(index, value) |
写入元素 | 自动刷新 | () |
copy_from_slice(slice) |
批量复制 | 自动刷新整个范围 | () |
🔍 访问方法(DBox)
| 方法 | 用途 | 缓存同步 | 返回值 |
|---|---|---|---|
read() |
读取值 | 自动失效 | T |
write(value) |
写入值 | 自动刷新 | () |
modify(f) |
修改值(read-modify-write) | 先失效后刷新 | () |
🔄 同步方法(SingleMap)
| 方法 | 用途 | 适用方向 |
|---|---|---|
prepare_read_all() |
使 CPU 缓存失效 | FromDevice, Bidirectional |
confirm_write_all() |
刷新 CPU 缓存到内存 | ToDevice, Bidirectional |
📊 信息方法
| 方法 | 用途 | 返回值 |
|---|---|---|
dma_addr() |
获取 DMA 地址 | DmaAddr |
len() |
获取数组长度 | usize |
完整示例
示例 1: 网卡接收缓冲区
use ;
// 创建 DMA 设备
let device = new;
// 分配接收缓冲区(1500 字节数据包)
let mut rx_buffer = device.
.expect;
// 配置网卡使用这个 DMA 地址
let dma_addr = rx_buffer.dma_addr;
nic.set_rx_address;
// ... 网卡接收数据 ...
// 读取数据(自动使 CPU 缓存失效)
let data = rx_buffer.read.unwrap;
示例 2: DMA 描述符配置
// 分配描述符
let mut desc = device.
.expect;
// 配置描述符(自动刷新缓存)
desc.write;
// 修改描述符(自动失效 → 修改 → 刷新)
desc.modify;
// 获取 DMA 地址给硬件
let desc_addr = desc.dma_addr;
示例 3: 临时映射栈缓冲区
// 栈上的临时缓冲区
let mut temp_buf = ;
// 映射用于 DMA 写入
let mapping = device.map_single_array
.expect;
// 准备数据写入设备
temp_buf = 0xAA;
temp_buf = 0xBB;
// ⚠️ 必须手动刷新缓存
mapping.confirm_write_all;
// ... 启动 DMA 传输 ...
// 映射在作用域结束时自动解映射
缓存同步详解
自动同步(DArray 和 DBox)
DArray<T> 和 DBox<T> 在以下操作时自动处理缓存同步:
| 操作 | 缓存同步行为 |
|---|---|
DArray::set(i, v) |
写入后刷新单个元素 |
DArray::read(i) |
读取前失效单个元素 |
DArray::copy_from_slice(s) |
写入后刷新整个范围 |
DBox::write(v) |
写入后刷新 |
DBox::read() |
读取前失效 |
DBox::modify(f) |
失效 → 执行闭包 → 刷新 |
优点:使用简单,不会出错 缺点:频繁同步可能影响性能
手动同步(SingleMap)
SingleMap 不会在 Drop 时自动同步缓存,必须显式调用:
let mapping = device.map_single_array?;
// 写入前准备
mapping.confirm_write_all; // 将 CPU 数据刷到内存
// ... DMA 传输 ...
// 读取前准备
mapping.prepare_read_all; // 使 CPU 缓存失效
// Drop 时只会解除映射,不会自动同步
为什么这样设计:
- 与 Linux DMA API 语义一致
- 让用户精确控制缓存同步时机
- 避免不必要的缓存操作
缓存同步规则
根据 DMA 方向选择同步操作:
| DMA 方向 | 写入设备前 | 读取设备后 |
|---|---|---|
ToDevice |
confirm_write_all() ✅ |
❌ 无需操作 |
FromDevice |
❌ 无需操作 | prepare_read_all() ✅ |
Bidirectional |
confirm_write_all() ✅ |
prepare_read_all() ✅ |
完整 API 参考
核心类型
DeviceDma
DMA 设备操作接口。
构造函数:
- 用途:创建 DMA 设备实例
- 参数:
dma_mask: 设备可寻址的地址掩码(如0xFFFFFFFF)osal: 实现DmaOptrait 的操作系统抽象层
- 示例:
let device = new;
数组创建方法
- 用途:创建 DMA 可访问的数组,初始化为零
- 参数:
len: 数组长度(元素个数)align: 对齐字节数(array_zero默认为core::mem::align_of::<T>())direction: DMA 传输方向
- 返回:
DArray<T>容器 - 缓存同步:自动
- 示例:
let array = device.?;
Box 创建方法
- 用途:创建 DMA 可访问的单值容器,初始化为零
- 参数:
align: 对齐字节数(box_zero默认为core::mem::align_of::<T>())direction: DMA 传输方向
- 返回:
DBox<T>容器 - 缓存同步:自动
- 示例:
let box_val = device.?;
映射方法
- 用途:将现有缓冲区映射为 DMA 可访问
- 参数:
buff: 要映射的缓冲区切片align: 对齐字节数direction: DMA 传输方向
- 返回:
SArrayPtr<T>映射句柄 - 缓存同步:手动(必须使用
to_vec()和copy_from_slice()) - 示例:
let buf = ;
let mapping = device.map_single_array?;
DArray<T>
DMA 可访问的数组容器,支持自动缓存同步。
访问方法
- 用途:读取数组中指定索引的元素
- 缓存同步:读取前自动失效
- 返回:
Some(T)如果索引有效,否则None
- 用途:写入数组中指定索引的元素
- 缓存同步:写入后自动刷新
- 返回:
Some(())如果索引有效,否则None
- 用途:从 slice 复制数据到数组
- 缓存同步:写入后自动刷新整个范围
- 要求:
src.len() <= self.len()
信息方法
- 用途:获取 DMA 地址,用于配置硬件
- 返回:DMA 地址
- 用途:获取数组长度
- 返回:元素个数
DBox<T>
DMA 可访问的单值容器,支持自动缓存同步。
访问方法
- 用途:读取存储的值
- 缓存同步:读取前自动失效
- 返回:存储的值
- 用途:写入新值
- 缓存同步:写入后自动刷新
- 用途:修改值(read-modify-write 模式)
- 缓存同步:读取前失效,写入后刷新
- 示例:
dma_box.modify;
信息方法
- 用途:获取 DMA 地址,用于配置硬件
- 返回:DMA 地址
SArrayPtr<T>
映射单个连续内存区域的 DMA 数组,RAII 风格自动清理。
注意:此类型提供手动缓存同步控制,与 DArray<T> 不同,它不会在每次访问时自动同步缓存。
访问方法
- 用途:读取指定索引的元素(不自动同步缓存)
- 返回:
Some(T)如果索引有效,否则None - 注意:读取前需手动使用
to_vec()方法
- 用途:写入指定索引的元素(不自动同步缓存)
- 注意:写入后需使用
copy_from_slice()刷新缓存
- 用途:从 slice 复制数据到数组
- 缓存同步:写入后自动刷新整个范围
- 用途:将整个数组转换为 Vec
- 缓存同步:读取前自动失效整个范围
信息方法
- 用途:获取 DMA 地址,用于配置硬件
- 返回:DMA 地址
- 用途:获取数组长度(元素个数)
- 返回:元素个数
- 用途:检查数组是否为空
- 返回:如果长度为 0 返回
true
类型定义
DmaDirection
DMA 传输方向枚举:
DmaError
DMA 操作错误类型:
DmaAddr
DMA 地址类型:
;
Linux 等价 API
| Rust API | Linux Equivalent |
|---|---|
DeviceDma::map_single_array() |
dma_map_single() |
SArrayPtr<T>::drop() |
dma_unmap_single() |
DmaOp::alloc_coherent() |
dma_alloc_coherent() |
DmaOp::dealloc_coherent() |
dma_free_coherent() |
DmaOp::flush() |
dma_cache_sync() (DMA_TO_DEVICE) |
DmaOp::invalidate() |
dma_cache_sync() (DMA_FROM_DEVICE) |
对齐要求
DMA 操作通常需要对齐到特定的边界:
- 常见对齐值:64、128、256、512、4096
array_zero_with_align()/box_zero_with_align()的align参数指定对齐字节数map_single_array()的align参数也指定对齐要求- 确保返回的 DMA 地址满足对齐要求,否则返回
DmaError::AlignMismatch
DMA Mask
DeviceDma::new() 的 dma_mask 参数指定设备可寻址的地址范围:
0xFFFFFFFF(32 位设备,最多 4GB)0xFFFFFFFFFFFFFFFF(64 位设备,全地址空间)- 其他值根据设备硬件限制
- 如果分配的 DMA 地址超出掩码范围,返回
DmaError::DmaMaskNotMatch