Linux DMA分析

来源:互联网 发布:驱动人生网卡版无网络 编辑:程序博客网 时间:2024/04/29 20:46

DMA
~~~~~~~


传输过程
~~~~~~~~~
两种方式
1.软件请求
2.硬件中断请求

方式一:
1)App调用read函数,驱动分配DMA缓冲区,硬件设备将数据传输到缓冲区。进程休眠。
2) 当硬件写完数据,产生中断。
3)中断处理程序获得数据,应答中断,唤醒休眠的进程。

方式二:
1)数据采集硬件产生中断,通知有数据要来。
2)中断处理程序分配缓冲区,告诉设备向哪里传数据。
3)设备写完数据后产生中断。
4)中断处理程序分发数据,唤醒相关的进程,然后做清理工作。


缓冲区分配
~~~~~~~~~~

必须使用连续的物理地址空间

方法:
1)启动内核是在启动参数中添加“mem=255M”那么对256M的内存,就会留下最后1M空闲空间。再使用
    dmabuf = ioremap(0xFF00000, 0x100000);
分配内存。

2)get_free_pages(),最多11M缓冲区,容易失败
3)采用分散/聚集 I/O


通用DMA层 —— by kernel
~~~~~~~~~~~~~~~~~~~~~~~

虚拟DMA总线

对于常见32位DMA采用
   int dam_set_mask()来定义其寻址范围。


DMA映射
~~~~~~~~~
映射建立了新的结构类型 —— dma_addr_t 表示总线地址

两种映射类型:
1)一致性DMA映射
   一致性映射的缓冲区必须可以同时被CPU和外围设备访问。
2)流式DMA映射
    性能较佳,建议使用

一致性DMA映射
~~~~~~~~~~~~~~
 可能其最小大小为单个页

 void *dma_alloc_coherent(struct device *dev, size_t size,
                    dma_addr_t *dma_handle, int flag)
返回值为缓冲区内核虚拟地址。总线地址保存在dma_handle中

  void dma_free_coherent();


DMA池 —— 一致性
~~~~~~~~~~~~~~~
分配的区域大小比页还小
<linux/dmapool.h>

1.创建
struct dma_pool *dma_pool_create()
2.释放
void dma_pool_destroy()
3.分配
void dma_pool_alloc()
返回的DMA缓冲区地址为虚拟地址
4.回收
void dma_pool_free()


流式DMA映射
~~~~~~~~~~~~
建立映射时,必须告诉内核数据流动方向。(enum dma_data_direction)
DMA_TO_DEVICE
DMA_FROM_DEVICE
DMA_BIDIRECTIONAL (双向,耗费性能)

只有一个缓冲区要传输时,使用:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size,
                     enum dma_data_direction direction)
返回值为总线地址

删除映射:
void dma_unmap_single()

*****************************
*****************************
原则:
1、缓冲区只属于设备,驱动程序不能以任何方式访问其中的内容。在缓冲区包含了所有要写入的数据之前,不能进行映射
2、在DMA处于活动期间,不能撤销缓冲区映射,否则会严重破坏系统稳定

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

不删除映射访问缓冲区
void dma_sync_single_for_cpu()
void dma_sync_single_for_device()


单页流式映射
~~~~~~~~~~~~
尽量避免使用
为page结构的指针指向的缓冲区建立映射
dma_addr_t dma_map_page(struct device *dev, struct page *page, ... )
dma_addr_t dma_unmap_page()


分散/聚集映射
~~~~~~~~~~~~
特殊的流式映射
( readv() writev() 从非连续缓冲区中读写 )

struct scatterlist {
#ifdef CONFIG_DEBUG_SG
 unsigned long sg_magic;
#endif
 unsigned long page_link;
 unsigned int offset;
 unsigned int length;
 dma_addr_t dma_address;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
 unsigned int dma_length;
#endif
};

分配:
int dma_map_sg(struct device * dev, struct scatterlist *sg, int nents,
               enum dma_data_direction direction)

nents 是传入的分散表入口的数量。返回值是要传送的DMA缓冲区数,它可能小于nents

宏:
dma_addr_t sg_dma_address(struct scatterlist *sg)
从分散表的入口项中返回总线地址

unsigned int sg_dma_len(stcuct scatterlist *sg)
返回缓冲区长度

删除映射:
void dma_umap_sg(struct device *dev, struct scatterlist *list
                 int nents, enum dma_data_direction direction)
nents不是返回值。

可用:
void dma_sync_single_for_cpu()
void dma_sync_single_for_device()


PCI 双重(64位)地址周期映射 (DAC)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<linux/pic.h>
先设置mask
int pci_dac_set_dma_mask(struct pci_dev *pdev, u64 mask)
只有当该函数返回0时,才能使用DAC

建立映射:
dma64_addr_t pci_dac_page_to_dma()


DMA Driver
~~~~~~~~~~~~~~

int my_dma_prepare(int channel, int mode, unsigned int buf
                   , unsinged int count)
{
   unsigned long flags;
   flags = claim_dma_lock();
   disable_dma(channel);
   clear_dma_ff(channel);
   set_dma_mode(channel);
   set_dma_addr(channel, virt_to_bus(buf));
   set_dma_count(channel, count);
   enable_dma(channel);
   release_dma(channel);
  
   return 0;
}

int my_dma_is_done(int channel)
{
    int residue;
    unsigned long flags = claim_dma_lock();
    residue = get_dma_residue(channel);
    release_dma_lock(flags);
    return(residue == 0);
 
}

 

 

原创粉丝点击