dma_addr_t与流式映射和一致性映射

DMA映射详解:一致性与流式
DMA映射在设备驱动中至关重要,涉及到一致性映射和流式映射。dma_alloc_coherent用于一致性映射,dma_map_single用于流式映射。一致性映射的内存分配和释放涉及dma_pool_create和dma_pool_destroy。流式映射需要指定数据流向,如DMA_TO_DEVICE或DMA_FROM_DEVICE。此外,针对不同场景,如单页和分散/聚集映射,有不同的映射和解映射函数。驱动程序在使用DMA时需要注意缓冲区的访问权限和同步问题。

DMA映射

       一个DMA映射是要分配的DMA缓冲区与为该缓冲区生成的、设备可访问地址的组合。DMA映射建立了一个新的结构类型---dma_addr_t来表示总线地址dma_addr_t类型的变量对驱动程序是不透明的,唯一允许的操作是将它们传递给DMA支持例程以及设备本身。

       根据DMA缓冲区期望保留的时间长短,PCI代码有两种DMA映射:
1)一致性映射
2)流式DMA映射(推荐)

********************************************************************************************************

建立一致性DMA映射

********************************************************************************************************

void *dma_alloc_coherent(struct device *dev,size_t size,dma_addr_t *dma_handle,int flag);
该函数处理了缓冲区的分配和映射。

前两个参数是device结构和所需缓冲区的大小。
函数在两处返回结果:
1)函数的返回值是缓冲区的内核虚拟地址,可以被驱动程序使用。
2)相关的总线地址则保存在dma_handle中。

向系统返回缓冲区--解除一致性映射
void dma_free_coherent(struct device *dev,size_t size,void *vaddr,dma_addr_t dma_handle);

---------
DMA池

DMA池是一个生成小型、一致性DMA映射的机制。调用dma_alloc_coherent函数获得的映射,可能其最小大小为单个页。如果设备需要的DMA区域比这还小,就要用DMA池了。

<linux/dmapool.h>

struct dma_pool *dma_pool_create(const char *name,struct device *dev,size_t size,size_t align,size_t allocation);
name是DMA池的名字,dev是device结构,size是从该池中分配的缓冲区大小,
align是该池分配操作所必须遵守的硬件对齐原则。
销毁DMA池
void dma_pool_destroy(struct dma_pool *pool);

DMA池分配内存
void *dma_pool_alloc(struct dma_pool *pool,int mem_flags,dma_addr_t *handle);
释放内存
void dma_pool_free(struct dma_pool *pool,void *vaddr,dma_addr_t addr);

 

 

********************************************************************************************************

建立流式DMA映射

********************************************************************************************************

       流式映射具有比一致性映射更为复杂的接口。这些映射希望能与已经由驱动程序分配的缓冲区协同工作,因而不得不处理那些不是它们选择的地址。

当建立流式映射时,必须告诉内核数据流动的方向。
枚举类型dma_data_direction:
DMA_TO_DEVICE 数据发送到设备(如write系统调用)
DMA_FROM_DEVICE 数据被发送到CPU
DMA_BIDIRECTIONAL 数据可双向移动
DMA_NONE 出于调试目的。

当只有一个缓冲区要被传输的时候,使用dma_map_single函数映射它:
dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction);
返回值是总线地址,可以把它传递给设备。

当传输完毕后,使用dma_unmap_single函数删除映射:
void dma_unmap_single(struct device *dev,dma_addr_t dma_addr,size_t size,enum dma_data_direction direction);

 

流式DMA映射的几条原则:
*)缓冲区只能用于这样的传送,即其传送方向匹配于映射时给定的方向。
*)一旦缓冲区被映射,它将属于设备,而不是处理器。
*)直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。
*)在DMA处于活动期间内,不能撤销对缓冲区映射,否则会严重破坏系统的稳定性。

 

驱动程序需要不经过撤销映射就访问流式DMA缓冲区的内容,有如下调用:
void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr,size_t size,enum dma_data_direction direction);
将缓冲区所有权交还给设备:
void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr,size_t size,enum dma_data_direction direction);

--------------------------
单页流式映射

有时候,要为page结构指针指向的缓冲区建立映射,比如
为get_user_pages获得的用户空间缓冲区。

dma_addr_t dma_map_page(struct device *dev,struct page *page,unsigned long offset,size_t size,enum dma_data_direction direction);
void dma_unmap_page(struct device *dev,dma_addr_t dma_address,size_t size,enum dma_data_direction direction);

 

---------------------------
分散/聚集映射

       有几个缓冲区,它们需要与设备双向传输数据。可以简单地依次映射每一个缓冲区并且执行请求的操作,但是一次映射整个缓冲区表还是很有利的。

映射分散表的第一步是建立并填充一个描述被传输缓冲区的scatterlist结构的数组。
<linux/scatterlist.h>

scatterlist结构的成员:
struct page *page;
unsigned int length;
unsigned int offset;

映射
int dma_map_sg(struct device *dev,struct scatterlist *sg,int nents,enum dma_data_direction direction);
解除
void dma_unmap_sg(struct device *dev,struct scatterlsit *list,int nents,enum dma_data_direction direction);

 

 

--------------------------
PCI双重地址周期映射

通用DMA支持层使用32位总线地址,然而PCI总线还支持64位地址模式,即双重地址周期(DAC)。

<linux/pci.h>
通用DMA层并不支持该模式。

要使用PCI总线的DAC,必须设置一个单独的DMA掩码:
int pci_dac_set_dma_mask(struct pci_dev *pdev,u64 mask);

建立映射
dma64_addr_t pci_dac_page_to_dma(struct pci_dev *pdev,struct page *page,unsigned long offset,int direction);

 

-----------------------------
一个简单的PCI DMA例子

这里提供了一个PCI设备的DMA例子dad(DMA Acquisition Device)的一部分,说明如何使用DMA映射:

int dad_transfer(struct dad_dev *dev,int write,void *buffer,size_t count)
{
dma_addr_t bus_addr;

dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
dev->dma_size = count;

/*映射DMA需要的缓冲区*/
bus_addr = dma_map_single(&dev->pci_dev->dev,buffer,count,dev->dma_dir);

writeb(dev->registers.command,DAD_CMD_DISABLEDMA);
writeb(dev->registers.command,write ? DAD_CMD_WR : DAD_CMD_RD);
writeb(dev->registers.addr,cpu_to_le32(bus_addr)); /*设置*/
writeb(dev->registers.len,cpu_to_le32(count));

/*开始操作*/
writeb(dev->registers.command,DAD_CMD_ENABLEDMA);

return 0;
}
该函数映射了准备进行传输的缓冲区并且启动设备操作。

另一半工作必须在中断服务例程中完成,如:

void dad_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
struct dad_dev *dev = (struct dad_dev *)dev_id;

/* 确定中断是由对应的设备发来的*/

dma_unmap_single(dev->pci_dev->dev,dev->dma_addr,
dev->dma_size,dev->dma_dir);

/* 释放之后,才能访问缓冲区,把它拷贝给用户 */
...
}

 

-------------------------------------------------------------------
ISA设备的DMA

ISA总线允许两种DMA传输:本地DMA和ISA总线控制DMA。

只讨论本地(native)DMA。***********************非常重要!!!!!

本地DMA使用主板上的标准DMA控制器电路来驱动ISA总线上的信号线。

本地DMA,要关注三种实体:
*8237 DMA控制器(DMAC)
控制器保存了有关DMA传输的信息,如方向、内存地址、传输数据量大小等。
还包含了一个跟踪传送状态的计数器。
当控制器接收到一个DMA请求信号时,它将获得总线控制权并驱动信号线,
这样设备就能读写数据了。
*外围设备
当设备准备传送数据时,必须激活DMA请求信号。
DMAC负责管理实际的传输工作;当控制器选通设备后,
硬件设备就可以顺序地读/写总线上的数据。
当传输结束时,设备通常会产生一个中断。
*设备驱动程序
设备驱动程序完成的工作很少,
它只是负责提供DMA控制器的方向、总线地址、传输量的大小等。
它还与外围设备通信,做好传输数据的准备,当DMA传输完毕后,响应中断。

在PC中使用的早期DMA控制器能够管理四个“通道”,
每个通道都与一套DMA寄存器相关联。

DMA控制器是系统资源,因此,内核协助处理这一资源。

内核使用DMA注册表为DMA通道提供了请求/释放机制,
并且提供了一组函数在DMA控制器中配置通道信息。

<think> 我们正在讨论一致性DMA映射流式DMA映射的区别。根据引用资料,我们可以总结如下: 1. **一致性DMA映射(Coherent DMA Mapping)**: - 在驱动初始化时进行映射,在驱动关闭时解除映射(长期存在)[^3]。 - 硬件保证外设CPU可以并行访问同一块数据,并且在软件没有显式刷新操作的情况下,双方都能看到对方对数据的更新(即具有一致性)[^3]。 - 通常通过`dma_alloc_coherent()`函数申请一段非缓存(uncached)的内存,或者将已有的内存设置为非缓存属性。 - 适用于小范围且频繁访问的内存区域,因为关闭缓存后,虽然每次访问都要从内存读取(而不是缓存),但避免了频繁的缓存刷新/无效操作[^1]。 2. **流式DMA映射(Streaming DMA Mapping)**: - 用于一次性的DMA传输,传输完成后立即解除映射(短期存在)[^2]。 - 需要软件显式管理缓存一致性:在DMA传输前,如果设备要读取内存,则必须将CPU缓存中的数据刷回内存(flush);在DMA传输后,如果设备写入了内存,则必须使CPU缓存中相应的数据失效(invalidate)[^2]。 - 使用`dma_map_single()`等函数进行映射,传输结束后使用`dma_unmap_single()`解除映射。 - 有严格的使用规则:缓冲区只能用于映射时给定方向匹配的传输;映射后缓冲区属于设备,CPU不能访问;在DMA活动期间不能解除映射[^2]。 **主要区别总结**: | 特性 | 一致性DMA映射 | 流式DMA映射 | |--------------|----------------------------------------|------------------------------------------| | **生命周期** | 长期(驱动初始化到关闭)[^3] | 短期(单次传输)[^2] | | **缓存管理** | 硬件自动维护一致性(或使用非缓存内存) | 软件显式刷新/无效缓存[^2] | | **性能影响** | 避免缓存维护开销,但内存访问无缓存加速 | 需要缓存维护操作,但允许使用缓存加速访问 | | **适用场景** | 小范围、频繁访问 | 大数据传输、一次性传输 | | **申请函数** | `dma_alloc_coherent()` | `dma_map_single()` | **场景选择建议**: - 如果数据区域小且需要被频繁访问(例如设备控制结构),使用一致性映射,避免频繁缓存维护操作[^1]。 - 如果传输数据量大或是一次性传输(例如网络数据包),使用流式映射,利用缓存加速CPU访问,并通过显式缓存维护确保一致性。 **代码示例**: ```c /* 一致性DMA映射示例 */ void *coherent_buf; dma_addr_t coherent_dma_handle; coherent_buf = dma_alloc_coherent(dev, size, &coherent_dma_handle, GFP_KERNEL); /* 直接读写coherent_buf,无需缓存维护 */ dma_free_coherent(dev, size, coherent_buf, coherent_dma_handle); /* 流式DMA映射示例 */ void *streaming_buf = kmalloc(size, GFP_KERNEL); dma_addr_t streaming_dma_handle = dma_map_single(dev, streaming_buf, size, DMA_TO_DEVICE); /* DMA传输期间,CPU不能访问streaming_buf */ dma_unmap_single(dev, streaming_dma_handle, size, DMA_TO_DEVICE); kfree(streaming_buf); ``` §§相关问题§§ 1. 在Linux驱动中如何正确选择一致性DMA映射流式DMA映射? 2. 使用流式DMA映射时,如何避免缓存一致性导致的错误? 3. `dma_alloc_coherent()`函数内部是如何实现内存分配缓存设置的? </think> ### 一致性DMA映射流式DMA映射的区别 #### 1. **生命周期使用模式** - **一致性映射(Coherent DMA)** 在驱动初始化时建立映射,驱动关闭时解除映射[^3],属于**长期持续性映射**。 示例: ```c void *buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); // 长期使用该映射 dma_free_coherent(dev, size, buf, dma_handle); ``` - **流式映射(Streaming DMA)** 针对单次DMA传输建立映射,传输完成后立即解除[^2][^4],属于**短期一次性映射**。 示例: ```c dma_addr_t dma_handle = dma_map_single(dev, buf, size, direction); // 单次传输后解除 dma_unmap_single(dev, dma_handle, size, direction); ``` #### 2. **缓存一致性机制** - **一致性映射** 硬件自动维护缓存一致性(如总线监听)[^3],无需软件干预。内存区域通常标记为**非缓存(Uncached)**或**写合并(Write-Combining)**。 $$ \text{CPU外设并行访问数据} \xrightarrow{\text{自动同步}} \text{无显式刷存} $$ - **流式映射** 需**显式软件维护**缓存一致性[^2]: - DMA读前:刷存CPU缓存 (`dma_sync_single_for_device()`) - DMA写后:失效CPU缓存 (`dma_sync_single_for_cpu()`) #### 3. **内存所有权规则** - **一致性映射** CPU外设可**同时访问**映射区域[^3]。 - **流式映射** 严格的所有权转移[^2]: 1. 映射后内存**归属设备**,CPU禁止访问 2. 传输完成并解除映射后,所有权才返回CPU[^4] ```mermaid graph LR A[CPU准备数据] --> B[映射所有权转设备] B --> C[DMA传输] C --> D[解除映射所有权转回CPU] ``` #### 4. **性能适用场景** | 特性 | 一致性映射 | 流式映射 | |--------------------|-------------------------------|-----------------------------------| | **缓存状态** | 非缓存 | 可缓存 | | **软件开销** | 零开销(硬件维护) | 需显式刷存/失效操作 | | **最佳场景** | 小内存+高频访问(如寄存器)[^1] | 大数据块+低频传输(如网络包)[^4] | | **实时性** | 确定性强 | 受缓存维护延迟影响 | #### 5. **典型应用对比 - **一致性映射案例** 硬件控制寄存器映射(如GPIO寄存器),需实时响应外设状态变化[^1]。 - **流式映射案例** 网络驱动收发数据包: ```c // 收包流程 dma_map_single(dev, skb->data, len, DMA_FROM_DEVICE); // 映射 netif_rx(skb); // 传输数据 dma_unmap_single(...); // 解除映射[^4] ``` > **总结**:一致性映射通过硬件特殊内存属性简化开发,适合长期共享数据;流式映射通过显式缓存控制优化性能,适合短期大数据传输[^2][^3]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值