• 【转】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数组(链表)总长。

  • 相关阅读:
    mysql 主从配置
    doGet和doPost的区别
    我的第一个MVC项目
    c3p0xml配置详解
    c3p0连接数据库
    java加载资源文件
    Windows上部署Python
    Windows上部署Python
    NagiosQL安装
    Nagios Windows客户端NSClient++ 0.4.x安装配置
  • 原文地址:https://www.cnblogs.com/losing-1216/p/5406995.html
Copyright © 2020-2023  润新知