DMA数据传输:
DMA允许外围设备和主内存之间直接传输 I/O 数据, DMA 依赖于系统。每一种体系结构DMA传输不同,编程接口也不同。
数据传输可以以两种方式触发:一种软件请求数据,另一种由硬件异步传输。
在第一种情况下,调用的步骤可以概括如下(以read为例):
(1)在进程调用 read 时,驱动程序的方法分配一个 DMA 缓冲区,随后指示硬件传送它的数据。进程进入睡眠。
(2)硬件将数据写入 DMA 缓冲区并在完成时产生一个中断。
(3)中断处理程序获得输入数据,应答中断,最后唤醒进程,该进程现在可以读取数据了。
第二种情形是在 DMA 被异步使用时发生的。以数据采集设备为例:
(1)硬件发出中断来通知新的数据已经到达。
(2)中断处理程序分配一个DMA缓冲区。
(3)外围设备将数据写入缓冲区,然后在完成时发出另一个中断。
(4)处理程序利用DMA分发新的数据,唤醒任何相关进程。
网卡传输也是如此,网卡有一个循环缓冲区(通常叫做 DMA 环形缓冲区)建立在与处理器共享的内存中。每一个输入数据包被放置在环形缓冲区中下一个可用缓冲区,并且发出中断。然后驱动程序将网络数据包传给内核的其它部分处理,并在环形缓冲区中放置一个新的 DMA 缓冲区。驱动程序在初始化时分配DMA缓冲区,并使用它们直到停止运行
分配DMA缓冲区:
1.使用DMA缓冲区的主要问题是:大于一页时,他们必须占据连续的物理页,因为设备使用ISA或者PCI系统总线传输数据,而这两种方式使用的都是物理地址。
2.实际操作中,一些设备和一些系统中的高端内存不能用于DMA,这是因为外围设备不能使用高端内存地址。应使用GFP_DMA标志调用kmalloc或者get_free_pages从DMA区间分配内存。
3.使用DMA的设备驱动程序将于连接到总线接口上的硬件通信,这些硬件通常使用的是总线地址。
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);
这些函数在虚拟地址和总线地址见做了简单的转化,但对于必须使用I/O内存管理单元或者必须使用回弹缓冲区的情况下,将不能工作。
通用DMA层:
内核提供了一个与总线--体系架构无关的DMA层,它会隐藏大多数问题,为DMA编写驱动时,使用该层。
使用些列函数的驱动程序都要包含头文件
1.int dma_set_mask (struct device *dev , u64 mask);用于判断给定寻址范围受限的设备在当前主机上是否具备DMA操作的能力。
如果设备支持常见的32位DMA操作,则没有必要调用dma_set_mask。
2.DMA映射是分配一个DMA缓冲区和为该缓冲区生成设备可访问地址的操作的组合。
DMA映射建立了一个新的结构类型--dma_addr_t来表示总线地址,dma_addr_t类型的变量对驱动是不透明的;如果cpu直接使用了该结构,将会导致发生不可
预期的问题。
根据DMA缓冲区期望保留的时间长短,PCI代码区分两种类型的DMA映射
一致性DMA映射:必须可以同时被cpu和外围设备访问。必须保存在一致性缓存中。
流式DMA映射:通常为单独的操作建立流式映射,开发者建议尽量使用流式映射。
2.1 内核中提供了一下函数用于分配一个DMA一致性的内存区域:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
这个函数的返回值为申请到的DMA缓冲区的虚拟地址。此外,该函数还通过参数handle返回DMA缓冲区的总线地址。与之对应的释放函数为:
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);
此外,Linux内核还提供了PCI设备申请DMA缓冲区的函数pci_alloc_consistent(),原型为:
void *pci_alloc_consistent(struct pci_dev *dev, size_t size, dma_addr_t *dma_addrp); 对应的释放函数为:
void pci_free_consistent(struct pci_dev *pdev, size_t size, void *cpu_addr, dma_addr_t dma_addr);
2.2DMA池
DMA池是一个生成小型、一致性DMA映射的机制。调用dma_alloc_coherent获得的映射,可能其最小大小为单个页。如果设备需要的DMA缓冲
区比这还小,就要使用DMA池了。
struct dma_pool *dma_pool_create(const char *name,struct device *dev,size_t size,size_t align,size_t allocation);
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);
void dma_pool_destroy(struct dma_pool *pool);
2.3建立流式DMA映射
2.3.1建立流式映射时,必须告诉内核数据流动的方向。dma_data_direction枚举类型:
DMA_TO_DEVICE
DMA_FROM_DEVICE
DMA_BIDIRECTIONAL
DMA_NONE
2.3.2当只有一个缓冲区需要被传输时:
dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction);
返回值时总线地址,可以把返回值传递给设备。
void dma_unmap_single(struct device *dev,dma_addr_t dma_addr,size_t size,enum dma_data_direction direction);
size和direction参数必须与映射缓冲区的参数相匹配。
2.3.3流式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);
2.4单页流式映射
2.5分散/聚集映射
3. ISA设备的DMA
3.1
ISA总线语序两种DMA传输:本地DMA和ISA总线控制DMA.这里主要说一下本地DMA
有三种实体涉及到ISA总线上的DMA数据传输:
DMA控制器:DMAC
外围设备
设备驱动程序
在pc中,早期DMA控制器能够管理四个"通道",每个通道与一套DMA寄存器相关联.
注册DMA:
int request_dma(unsigned int channel,const char *name);
channel是0到7的整数,其中4不可用,通道4在内部用来将从属控制器级联到主控制器上。返回0表示成功。cascade入口是一个占位符,表示4通道不可用
void free_dma(unsigned int channel);
在open操作时请求通道比在模块初始化函数中请求通道更好一些。同时还建议在请求中断信号线以后请求DMA通道,在释放中断前释放DMA通道。
3.2与DMA控制器通信
DMA通道时一个可共享的资源,在配置通道的时候需要加锁。
unsigned long claim_dma_lock()
void release_dma_lock(unsigned lon;g flags);
配置DMA通道的信息包含三部分:RAM地址,被传输的原子项个数(以字节为单位),传输的方向
void set_dma_mode(unsigned int channel,char mode);
void set_dma_addr(unsigned int channel,unsigned int addr);
void set_dma_count(unsigned int channel,unsigned int count);
除此之外,当处理DMA设备时,还有许多用于管理设备的函数:
void disable_dma(unsigned int channel);
void enable_dma(unsigned int channel);
int get_dma_residue(unsigned int channel); 该函数返回还未传输的字节数,用于查看DMA传输是否已经结束。
void clear_dma_ff
程序员必须在访问DMA寄存器前清除触发器,触发器用于控制对16位寄存器的访问,我们可以通过两个连续的8位操作访问该寄存器
当清零时被用来选择低字节,置位选择高字节。