【转】scatterlist && DMA

原文:scatterlist && DMA

 

DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以是系统CPU从实际的IO数据传输过程中摆脱出来,从而大大提

供系统的吞吐率。DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发地执行其他任务,当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后续处理。

在内存中用于与外设交互数据的一块区域被称作DMA缓冲区,在设备不支持scatter/gatherCSG,分散/聚集操作的情况下,DMA缓冲区必须是物理上联系的。

对于ISA设备而言,其DMA操作只能在16MB以下的内存进行,因此,在使用kmalloc()和__get_free_pages()及其类似函数申请DMA缓冲区时应使用GFP_DMA标志,这样能保证获得的内存是具备DMA能力的。

 

DMA的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的内存地址,物理地址是从CPU角度上看到的未经转换的内存地址(经过转换的那叫虚拟地址)。

在PC上,对于ISA和PCI而言,总线即为物理地址,但并非每个平台都是如此。由于有时候接口总线是通过桥接电路被连接,桥接电路会将IO地址映射为不同的物理地址。

 

设备不一定能在所有的内存地址上执行DMA操作,在这种情况下应该通过下列函数执行DMA地址掩码:

 

int dma_set_mask(struct device *dev, u64 mask);

 

 

DMA映射包括两个方面的工作:

  • 分配一片DMA缓冲区;
  • 为这片缓冲区产生设备可访问的地址。

 

内核中提供了一下函数用于分配一个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);

以下函数用于分配一个写合并(writecombinbing)的DMA缓冲区:

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

与之对应的是释放函数:dma_free_writecombine(),它其实就是dma_free_conherent,只不过是用了#define重命名而已。

 

 

对于单个已经分配的缓冲区而言,使用dma_map_single()可实现流式DMA映射:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);  
如果映射成功,返回的是总线地址,否则返回NULL.最后一个参数DMA的方向,可能取DMA_TO_DEVICE, DMA_FORM_DEVICE, DMA_BIDIRECTIONAL和DMA_NONE;
与之对应的反函数是:
void dma_unmap_single(struct device *dev,dma_addr_t *dma_addrp,size_t size,enum dma_data_direction direction);

 

 

MMC的scatter list相关操作

 

MMC作为块设备,它的存储空间,最小单位由struct bio_vec 描述,它代表一段物理地址范围。

 

struct bio_vec {
	struct page	*bv_page;
	unsigned int	bv_len;
	unsigned int	bv_offset;
};


一次块设备传输请求,会涉及到很多个这样的不连续的物理空间。不连续的物理空间,不能直接使用DMA

 

这时,可以利用sg操作,让每个bio_vec结构,对应一个scatterlist结构:

struct scatterlist {
    unsigned long    page_link;
    unsigned int    offset;        /* buffer offset         */
    dma_addr_t    dma_address;    /* dma address             */
    unsigned int    length;        /* length             */
};

 

在MMC的请求处理函数中,遍历每一request中所有bio_vec结构,对应一个scatterlis结构描述:

 

  rq_for_each_segment(bvec, rq, iter) {
        ... ...
        sg = sg_next(sg);           //指向sg链表中的下一个scatterlist                                 
        sg_set_page(sg, bvec->bv_page, bvec->bv_len, bvec->bv_offset);    //使用sg描述一个页   
        ... ...        
  } 
  ... ...
  sg_mark_end(sg); //标志sg链表到此sg节点就结束了

 

上面sg这个链表初始化代码如下:

 

sg_init_table(mq->bounce_sg, bouncesz / 512);
第一个参数为链表头,第二个为成员数量。

 

上边所涉及到的几个函数:

 

void sg_init_table(struct scatterlist *sg, unsigned int nents);

sg是sg链表(数组)的表头,nents是要分配的数组的个数。

 

 

 

void sg_set_page(struct scatterlist *sg, struct page *page,
		     unsigned int len, unsigned int offset);
void sg_set_buf(struct scatterlist *sg, const void *buf,
	      	    unsigned int buflen);

使用指定的参数,填充sg结构。第一个以页为页地址,偏移量,长度为接线;第二个以地址和长度为界限。

 

 

 

struct scatterlist *sg_next(struct scatterlist *sg);

返回下一个sg成员地址。

 

 

也有一个宏,来遍历sg链表上的所有sg结构,它的使用方法通常如下:

 

int i;
    struct scatterlist *list, *sgentry;

    /* Fill in list and pass it to dma_map_sg().  Then... */
    for_each_sg(i, list, sgentry, nentries) {
	program_hw(device, sg_dma_address(sgentry), sg_dma_len(sgentry));
    }

 

 

sgentry为sg链表入口,nentryies是sg数组(链表)总长。

posted @ 2016-04-19 09:54  L_free  阅读(1206)  评论(0编辑  收藏  举报