Skip to main content

Crate dma_api

Crate dma_api 

Source
Expand description

§DMA API

用于 Rust 的 DMA(直接内存访问)抽象 API,提供安全的 DMA 内存操作接口,适用于嵌入式和裸机环境。

§目录


§快速开始

§1. 实现 DmaOp trait

首先需要为你的平台实现 DmaOp trait,提供底层 DMA 操作支持:

use dma_api::{DmaOp, DmaDirection, DmaHandle, DmaError};
use core::{alloc::Layout, ptr::NonNull, num::NonZeroUsize};

struct MyDmaImpl;

impl DmaOp for MyDmaImpl {
    fn page_size(&self) -> usize {
        4096 // 返回系统页大小
    }

    unsafe fn map_single(
        &self,
        dma_mask: u64,
        addr: NonNull<u8>,
        size: NonZeroUsize,
        align: usize,
        direction: DmaDirection,
    ) -> Result<DmaHandle, DmaError> {
        // 实现虚拟地址到 DMA 地址的映射
        // 返回 DmaHandle
        todo!()
    }

    unsafe fn unmap_single(&self, handle: DmaHandle) {
        // 解除 DMA 映射
        todo!()
    }

    unsafe fn alloc_coherent(
        &self,
        dma_mask: u64,
        layout: Layout,
    ) -> Option<DmaHandle> {
        // 分配 DMA 一致性内存
        todo!()
    }

    unsafe fn dealloc_coherent(&self, handle: DmaHandle) {
        // 释放 DMA 内存
        todo!()
    }
}

§2. 选择合适的 DMA 容器

根据你的使用场景选择:

需求推荐容器特点
需要数组类型的 DMA 缓冲区DArray<T>自动缓存同步,固定大小
需要单个结构体的 DMA 缓冲区DBox<T>自动缓存同步,单个值
映射现有缓冲区用于 DMASArrayPtr<T>手动缓存同步,单个连续内存区域映射

§核心概念

§DMA 传输方向 (DmaDirection)

DMA 操作有三种方向,决定了缓存同步的行为:

pub enum DmaDirection {
    ToDevice,       // CPU → 设备:CPU 写数据,设备读
    FromDevice,     // 设备 → CPU:设备写数据,CPU 读
    Bidirectional,  // 双向:CPU 和设备都可能读写
}

选择指南

  • ToDevice: 用于发送数据到设备(如网卡发送缓冲区)
  • FromDevice: 用于接收设备数据(如网卡接收缓冲区)
  • Bidirectional: 用于双向通信(如驱动程序与设备共享内存)

§缓存同步

DMA 操作需要处理 CPU 缓存和内存之间的数据一致性:

操作ToDeviceFromDeviceBidirectional
写数据前 (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 = DeviceDma::new(0xFFFFFFFF, &DMA_IMPL);

// 创建 100 个 u32 的 DMA 数组
let mut dma_array = device.array_zero_with_align::<u32>(100, 64, DmaDirection::FromDevice)
    .expect("Failed to allocate");

dma_array.set(0, 0x12345678);  // 写入(自动刷新缓存)
let value = dma_array.read(0); // 读取(自动失效缓存)

let dma_addr = dma_array.dma_addr(); // 获取 DMA 地址给硬件

适用场景

  • 网卡数据包缓冲区
  • 音频采样缓冲区
  • 图像帧缓冲区

§场景 2: DMA 单值容器

用途:需要单个结构体的 DMA 缓冲区

推荐DBox<T>

特点

  • ✅ 自动缓存同步(每次 read/write/modify 时)
  • ✅ 适合配置寄存器、描述符等
  • ✅ 类型安全

示例

#[derive(Default)]
struct Descriptor {
    addr: u64,
    length: u32,
    flags: u32,
}

let mut dma_desc = device.box_zero_with_align::<Descriptor>(64, DmaDirection::ToDevice)
    .expect("Failed to allocate");

dma_desc.modify(|d| d.length = 4096); // 修改(自动刷新缓存)
let desc = dma_desc.read();           // 读取(自动失效缓存)

适用场景

  • DMA 描述符
  • 设备配置结构
  • 状态寄存器

§场景 3: 映射现有缓冲区

用途:将已存在的缓冲区映射用于 DMA

推荐SingleMap

特点

  • ⚠️ 手动缓存同步
  • ✅ 临时映射,RAII 自动解映射
  • ✅ 适用于栈分配或静态缓冲区

示例

let mut buffer = [0u8; 4096];

// 映射现有缓冲区
let mapping = device.map_single_array(&buffer, 64, DmaDirection::ToDevice)
    .expect("Mapping failed");

// ⚠️ 重要:使用前必须手动同步缓存
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 BoxDBox<T>自动
box_zero_with_align<T>(align, dir)创建指定对齐的 DMA BoxDBox<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_api::{DeviceDma, Direction};

// 创建 DMA 设备
let device = DeviceDma::new(0xFFFFFFFF, &DMA_IMPL);

// 分配接收缓冲区(1500 字节数据包)
let mut rx_buffer = device.array_zero_with_align::<u8>(1500, 64, DmaDirection::FromDevice)
    .expect("Failed to allocate RX buffer");

// 配置网卡使用这个 DMA 地址
let dma_addr = rx_buffer.dma_addr();
nic.set_rx_address(dma_addr.as_u64());

// ... 网卡接收数据 ...

// 读取数据(自动使 CPU 缓存失效)
let data = rx_buffer.read(0).unwrap();

§示例 2: DMA 描述符配置

#[derive(Default)]
struct DmaDescriptor {
    buffer_addr: u64,
    length: u32,
    control: u32,
}

// 分配描述符
let mut desc = device.box_zero_with_align::<DmaDescriptor>(64, DmaDirection::ToDevice)
    .expect("Failed to allocate descriptor");

// 配置描述符(自动刷新缓存)
desc.write(DmaDescriptor {
    buffer_addr: 0x12345000,
    length: 4096,
    control: 0x01,
});

// 修改描述符(自动失效 → 修改 → 刷新)
desc.modify(|d| d.length = 2048);

// 获取 DMA 地址给硬件
let desc_addr = desc.dma_addr();

§示例 3: 临时映射栈缓冲区

// 栈上的临时缓冲区
let mut temp_buf = [0u8; 256];

// 映射用于 DMA 写入
let mapping = device.map_single_array(&temp_buf, 64, DmaDirection::ToDevice)
    .expect("Failed to map");

// 准备数据写入设备
temp_buf[0] = 0xAA;
temp_buf[1] = 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(&buffer, 64, DmaDirection::ToDevice)?;

// 写入前准备
mapping.confirm_write_all();  // 将 CPU 数据刷到内存

// ... DMA 传输 ...

// 读取前准备
mapping.prepare_read_all();   // 使 CPU 缓存失效

// Drop 时只会解除映射,不会自动同步

为什么这样设计

  • 与 Linux DMA API 语义一致
  • 让用户精确控制缓存同步时机
  • 避免不必要的缓存操作

§缓存同步规则

根据 DMA 方向选择同步操作:

DMA 方向写入设备前读取设备后
ToDeviceconfirm_write_all()❌ 无需操作
FromDevice❌ 无需操作prepare_read_all()
Bidirectionalconfirm_write_all()prepare_read_all()

§完整 API 参考

§核心类型

§DeviceDma

DMA 设备操作接口。

构造函数

pub fn new(dma_mask: u64, osal: &impl DmaOp) -> Self
  • 用途:创建 DMA 设备实例
  • 参数
    • dma_mask: 设备可寻址的地址掩码(如 0xFFFFFFFF
    • osal: 实现 DmaOp trait 的操作系统抽象层
  • 示例
let device = DeviceDma::new(0xFFFFFFFF, &DMA_IMPL);
§数组创建方法
pub fn array_zero<T: Sized + Default>(
    &self,
    len: usize,
    direction: DmaDirection,
) -> Result<DArray<T>, DmaError>

pub fn array_zero_with_align<T: Sized + Default>(
    &self,
    len: usize,
    align: usize,
    direction: DmaDirection,
) -> Result<DArray<T>, DmaError>
  • 用途:创建 DMA 可访问的数组,初始化为零
  • 参数
    • len: 数组长度(元素个数)
    • align: 对齐字节数(array_zero 默认为 core::mem::align_of::<T>()
    • direction: DMA 传输方向
  • 返回DArray<T> 容器
  • 缓存同步:自动
  • 示例
let array = device.array_zero_with_align::<u32>(100, 64, DmaDirection::ToDevice)?;
§Box 创建方法
pub fn box_zero<T: Sized + Default>(
    &self,
    direction: DmaDirection,
) -> Result<DBox<T>, DmaError>

pub fn box_zero_with_align<T: Sized + Default>(
    &self,
    align: usize,
    direction: DmaDirection,
) -> Result<DBox<T>, DmaError>
  • 用途:创建 DMA 可访问的单值容器,初始化为零
  • 参数
    • align: 对齐字节数(box_zero 默认为 core::mem::align_of::<T>()
    • direction: DMA 传输方向
  • 返回DBox<T> 容器
  • 缓存同步:自动
  • 示例
let box_val = device.box_zero_with_align::<MyStruct>(64, DmaDirection::Bidirectional)?;
§映射方法
pub fn map_single_array<T: Sized>(
    &self,
    buff: &[T],
    align: usize,
    direction: DmaDirection,
) -> Result<SArrayPtr<T>, DmaError>
  • 用途:将现有缓冲区映射为 DMA 可访问
  • 参数
    • buff: 要映射的缓冲区切片
    • align: 对齐字节数
    • direction: DMA 传输方向
  • 返回SArrayPtr<T> 映射句柄
  • 缓存同步手动(必须使用 to_vec()copy_from_slice()
  • 示例
let buf = [0u8; 4096];
let mapping = device.map_single_array(&buf, 64, DmaDirection::ToDevice)?;

§DArray<T>

DMA 可访问的数组容器,支持自动缓存同步。

§访问方法
pub fn read(&self, index: usize) -> Option<T>
  • 用途:读取数组中指定索引的元素
  • 缓存同步:读取前自动失效
  • 返回Some(T) 如果索引有效,否则 None
pub fn set(&mut self, index: usize, value: T) -> Option<()>
  • 用途:写入数组中指定索引的元素
  • 缓存同步:写入后自动刷新
  • 返回Some(()) 如果索引有效,否则 None
pub fn copy_from_slice(&mut self, src: &[T]) where T: Copy
  • 用途:从 slice 复制数据到数组
  • 缓存同步:写入后自动刷新整个范围
  • 要求src.len() <= self.len()
§信息方法
pub fn dma_addr(&self) -> DmaAddr
  • 用途:获取 DMA 地址,用于配置硬件
  • 返回:DMA 地址
pub fn len(&self) -> usize
  • 用途:获取数组长度
  • 返回:元素个数

§DBox<T>

DMA 可访问的单值容器,支持自动缓存同步。

§访问方法
pub fn read(&self) -> T
  • 用途:读取存储的值
  • 缓存同步:读取前自动失效
  • 返回:存储的值
pub fn write(&mut self, value: T)
  • 用途:写入新值
  • 缓存同步:写入后自动刷新
pub fn modify<F: FnOnce(&mut T)>(&mut self, f: F)
  • 用途:修改值(read-modify-write 模式)
  • 缓存同步:读取前失效,写入后刷新
  • 示例
dma_box.modify(|v| v.field += 10);
§信息方法
pub fn dma_addr(&self) -> DmaAddr
  • 用途:获取 DMA 地址,用于配置硬件
  • 返回:DMA 地址

§SArrayPtr<T>

映射单个连续内存区域的 DMA 数组,RAII 风格自动清理。

注意:此类型提供手动缓存同步控制,与 DArray<T> 不同,它不会在每次访问时自动同步缓存。

§访问方法
pub fn read(&self, index: usize) -> Option<T>
  • 用途:读取指定索引的元素(不自动同步缓存)
  • 返回Some(T) 如果索引有效,否则 None
  • 注意:读取前需手动使用 to_vec() 方法
pub fn set(&mut self, index: usize, value: T)
  • 用途:写入指定索引的元素(不自动同步缓存)
  • 注意:写入后需使用 copy_from_slice() 刷新缓存
pub fn copy_from_slice(&mut self, src: &[T])
  • 用途:从 slice 复制数据到数组
  • 缓存同步:写入后自动刷新整个范围
pub fn to_vec(&self) -> Vec<T>
  • 用途:将整个数组转换为 Vec
  • 缓存同步:读取前自动失效整个范围
§信息方法
pub fn dma_addr(&self) -> DmaAddr
  • 用途:获取 DMA 地址,用于配置硬件
  • 返回:DMA 地址
pub fn len(&self) -> usize
  • 用途:获取数组长度(元素个数)
  • 返回:元素个数
pub fn is_empty(&self) -> bool
  • 用途:检查数组是否为空
  • 返回:如果长度为 0 返回 true

§类型定义

§DmaDirection

DMA 传输方向枚举:

pub enum DmaDirection {
    ToDevice,       // DMA_TO_DEVICE: CPU 写入,设备读取
    FromDevice,     // DMA_FROM_DEVICE: 设备写入,CPU 读取
    Bidirectional,  // DMA_BIDIRECTIONAL: 双向传输
}
§DmaError

DMA 操作错误类型:

pub enum DmaError {
    NoMemory,                           // DMA 分配失败
    LayoutError(LayoutError),           // 无效的内存布局
    DmaMaskNotMatch { addr, mask },     // DMA 地址超出设备掩码
    AlignMismatch { required, address }, // 地址对齐不满足要求
    NullPointer,                        // 提供了空指针
    ZeroSizedBuffer,                    // 零大小缓冲区不能用于 DMA
}
§DmaAddr

DMA 地址类型:

pub struct DmaAddr(u64);

impl DmaAddr {
    pub fn as_u64(&self) -> u64;  // 转换为 u64
    pub fn checked_add(&self, rhs: u64) -> Option<Self>;  // 安全加法
}

§Linux 等价 API

Rust APILinux 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

Structs§

DArray
DMA 可访问的数组容器。
DArrayIter
DBox
DMA 可访问的单值容器。
DeviceDma
DMA 设备操作接口。
DmaAddr
DmaHandle
Handle for DMA memory allocation.
DmaMapHandle
PhysAddr
物理地址类型
SArrayPtr
映射单个连续内存区域的 DMA 数组。

Enums§

DmaDirection
DMA 传输方向
DmaError
DMA 错误类型

Traits§

DmaOp