linux驱动-DMA

来源:互联网 发布:c语言 string类 编辑:程序博客网 时间:2024/06/18 16:44

DMA概述:

  • DMA(Direct Memory Access)就是(外部设备)直接存取(访问)内存(RAM)。
  • DMA映射,就是将内存一段空间,做特殊处理后,把物理地址告诉外设,外设部设备可以直接存取内存。如camera支持DMA,在camera控制器中,一般会有设置DMA物理地址的寄存器,按照本章下面介绍的映射方法,获取的总线地址(物理地址),将这个物理地址写到camera寄存器,就完成了camera的DMA配置,当然具体情况还有其他很多配置。

DMA数据传输概况:

DMA数据传输两种情况:

  • 软件请求数据:驱动完成DMA映射后,进程请求访问这个映射缓存(RAM)。

    • 进程读数据:当DMA映射的缓存,没有满时,进程阻塞(是否阻塞等待,还是不等,根据具体情况定),当外设填满缓存时,产生中断,中断服务唤醒进程,进程读数据。
    • 写数据 :进程向DMA映射的缓存写数据,写完时,产生一个完成动作,然后引发数据传输到外不设备。
  • 硬件主动中断请求数据传输:

    • 硬件产生中断,告诉系统要传输数据。
    • 中断服务程序完成DMA映射后,告诉硬件数据存放缓存地址。
    • 外部设备写数据到缓存 ,完成时,产生中断告诉系统,数据传输完成。
    • 系统决定数据由那个进程来取出。

DMA总线地址:

DMA总线地址,也就是物理地址。与内核虚拟地址转换用include/asg-generic/io.h中定义的:

unsigned long virt_to_bus(volatile void *address);void *bus_to_virt(unsigned long address);

DMA总线地址,一般用dma_addr_t表示(虽然也是个u32类型),定义在include/linux/types.h中:

#ifdef CONFIG_ARCH_DMA_ADDR_T_64BITtypedef u64 dma_addr_t;#elsetypedef u32 dma_addr_t;#endif

DMA特殊硬件:

有些32/64位体系处理器上,有些DMA不支持32/64地址,如只支持24位,那么这个时候,我们需要调用dma_set_mask(dev,0xffffff)告诉系统(也需要,系统支持),其中0xffffff就是告诉系统DMA是24位的。定义在include/linux/dma-mapping.h(如果这个路径找不到,就到具体CPU体系下面找,如是ARM,则arch/arm/include/asm/dam-mapping.h)中:

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

返回值是0表示失败,不可以用DMA,返回非0表示成功,可以用DMA。
dma_set_mask早期,DMA控制器没有集成到CPU里面时,现在基本都集成到CPU里面,都是32位,现在很少使用了。

DMA三种映射方式:

DMA三种映射方式分别为:一致映射、流映射、发散/汇聚映射。

  • 一致DMA映射
  • 普通一致DMA映射
    普通一致DMA映射,内存空间地址连续,并且空间大小是页的整数倍。一般实现一致DMA映射函数内部都是调用__get_free_pages 来分配空间的。

    • 映射函数:

      • 定义头文件:include/linux/dma-mapping.h
      • 映射函数:
        void *dma_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag)
        第一个参数:设备指针
        第二个参数:要映射缓存大小,单位字节
        第三个参数:映射完成后,总线地址(物理地址)存在dma_handle,属于函数返回的。
        第五个参数:标志,和kmalloc()、__get_free_pages()的flag一样,常用GFP_KERNEL和GFP_ATOMIC。
        返回值:返回内核逻辑地址,0表示失败。dma_alloc_coherent内部也是调用__get_free_pages分配空间。
        • 释放映射:
          void dma_free_coherent(struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_handle)
          第一个参数:设备指针
          第二个参数:映射的大小,也就是dma_alloc_coherent的大小。
          第三个参数:是dma_alloc_coherent返回的内核逻辑地址。
          第四个参数:是dma_alloc_coherent的第三个参数。
          返回值:没有,不管。

      dma-mapping.h里面还声明了其他接口,如dmam_alloc_coherent映射缓存会清零。

  • DMA池
    当映射缓存小于一个页时,首先考虑用DMA池。

    • 创建DMA池
      • 定义头文件:include/linux/dmapool.h
      • 创建函数:
        struct dma_pool *dma_pool_create(const char *name, struct device *dev,
        size_t size, size_t align, size_t allocation);
        第一个参数:DMA池名字,name是字符串
        第二个参数:设备指针
        第三个参数:DMA池,从池分配缓存的大小,这个size是指用dma_pool_alloc分配缓存大小,而不是指整个池的大小。
        第四个参数:对齐方式,如字对齐,字节对齐,0表示默认对齐方式(跟随系统)
        第五个参数:内存边界限制。如果设备没有边界限制,可以设置该参数为0。如果设置为4096,则表示从内存池分配的内存不能超过4K字节的边界。
        返回值:返回DMA池dma_pool 结构体指针
    • 分配函数:
      DMA池创建完成后,就是从池分配缓存了。分配缓存空间大小是dma_pool_create第三个参数size设置的。

      • 定义头文件:include/linux/dmapool.h
      • 创建函数:
        void *dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags,
        dma_addr_t *handle);
        第一个参数:由dma_pool_create返回的DMA池dma_pool结构体指针
        第二个参数:分配标志,同kmalloc()
        第三个参数:分配返回给我们的总线地址(物理地址)
        返回值:内核逻辑地址。
    • 释放从DMA池分配的缓存:
      不用DMA池映射的缓存时,要释放掉缓存 。

      • 定义头文件:include/linux/dmapool.h
      • 释放函数:
        void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr)
        第一个参数:由dma_pool_create返回的DMA池dma_pool结构体指针
        第二个参数:dma_pool_alloc返回的内核逻辑地址。
        第三个参数:dma_pool_alloc第三个参数返回给用户的总线地址(物理地址)
    • 销毁DMA池
      当模块卸载时,应销毁DMA池

      • 定义头文件:include/linux/dmapool.h
      • 销毁函数:
        void dma_pool_destroy(struct dma_pool *pool)
        第一个参数:由dma_pool_create返回的DMA池dma_pool结构体指针
  • DMA流映射
    DMA流映射,指映射的缓存有方向,可以映射成单向和双向。单向效率高,单不方便。双向方便,效率没单向高。

    • 流方向标记:

      • 头文件:include/linux/dma-direction.h
      • 方向标记说明:
        • DMA_TO_DEVICE
          写,数据从缓存流向设备。
        • DMA_FROM_DEVICE
          读,数据从设备流向缓存。
        • DMA_BIDIRECTIONAL
          读写,双向,数据可流向设备,可从设备流向缓存。用得比较多。
        • DMA_NONE
          这个符号只作为一个调试辅助而提供. 试图使用带这个方向的缓冲导致内核崩溃.
    • 建立流映射:

      • 定义头文件:include/linux/dma-mapping.h
      • 宏:
        dma_map_single(d, a, s, r)宏展开是函数dma_map_single_attrs(d, a, s, r, NULL):
        dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,
        size_t size,
        enum dma_data_direction dir,
        struct dma_attrs *attrs);
        使用宏 dma_map_single的形参,我们看函数dma_map_single_attrs的参数
        dev:设备指针
        ptr:分配的缓存内核逻辑地址。用户自己,用kmalloc()、__get_free_pages()分配完成后。
        dir:流的方向标记
        attrs:为NULL
        返回值:总线地址(物理地址)
        注意:一但用dma_map_single映射后,CPU不能再对这段缓存操作,否则会导致系统不稳定。只有当dma_unmap_single取消映射过后,才能访问,或者通过下面介绍函数dma_sync_single_for_cpu处理后,也可以访问。
    • 取消流映射:

      • 定义头文件:include/linux/dma-mapping.h
      • 宏:
        void dma_unmap_single(d, a, s, r)宏展开是调用函数dma_unmap_single_attrs(d, a, s, r, NULL):
        void dma_unmap_single_attrs(struct device *dev, dma_addr_t addr,
        size_t size,
        enum dma_data_direction dir,
        struct dma_attrs *attrs)
        使用宏dma_unmap_single的形参,我们看函数dma_unmap_single_attrs的参数:
        dev:设备指针
        addr:dma_map_single返回的总线地址(物理地址)
        size:映射缓存的大小
        dir:与dma_map_single相同的流方向标记。
        attrs:为NULL
    • CPU访问流映射缓存,临时处理函数:

      • 定义头文件:include/linux/dma-mapping.h
      • CPU访问前处理函数:
        void dma_sync_single_for_cpu(struct device *dev, dma_addr_t addr,
        size_t size,
        enum dma_data_direction dir)
        第一个参数:dev设备指针
        第二个参数: addr是dma_map_single返回的总线地址(物理地址)
        第三个参数:size缓存大小,dma_map_single第三个参数
        第四个参数:流方向标记。
        用dma_sync_single_for_cpu处理后,CPU可以访问只段缓存,访问完成后要恢复。
      • CPU访问完成后处理函数:
        void dma_sync_single_for_device(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir)
        第一个参数:dev设备指针
        第二个参数: addr是dma_map_single返回的总线地址(物理地址)
        第三个参数:size缓存大小,dma_map_single第三个参数
        第四个参数:流方向标记。
        用dma_sync_single_for_device处理后,恢复给设备访问缓存,CPU不能再访问缓存。
    • 单页流映射:
      单页流映射,就是只映射一页缓存。

      • 映射函数

        • 定义头文件:include/linux/dma-mapping.h
        • 函数:
          dma_addr_t dma_map_page(struct device *dev, struct page *page,
          size_t offset, size_t size,enum dma_data_direction dir)
          第一个参数:dev设备指针
          第二个参数:页指针,用__get_free_page分配
          第三个参数:页偏移量
          第四个参数:只映射一页的部分,字节单位,size小于PAGE_SIZE
          第五个参数:流方向标记
          返回值:返回总线地址(物理地址)
          offset和size被用来映射页的部分,但尽量少用(不用,应该都是0)。
      • 取消映射函数

        • 定义头文件:include/linux/dma-mapping.h
        • 函数:
          void dma_unmap_page(struct device *dev, dma_addr_t addr,
          size_t size, enum dma_data_direction dir)
          第一个参数:dev设备指针
          第二个参数:addr总线地址(物理地址),由dma_map_page返回的
          第三个参数:缓存大小,同dma_map_page第四个参数
          第四个参数:流方向标志。
  • 发散/汇聚DMA映射
    发散/汇聚DMA映射,当有多个buff(单个buff是连续的),需要映射时,我没不用再一个buff,一个buff的调用上面流映射接口去映射,而是一次调用一个接口映射全部buffer。

    • scatterlist结构体
      映射前,需要将这几个分散的buff汇聚到,scatterlist结构数组里面,buff个数和数组长度想同。

      • scatterlist定义头文件:include/linux/scatterlist.h
      • 部分成员描述:
        struct page 指针, 对应在发散/汇聚操作中使用的缓冲.
        • unsigned int length
          缓存的长度
        • unsigned int offset
          缓存页偏移量,一般为0
        • dma_addr_t dma_address
          单个buff映射后的总线地址(物理地址),这个是调用接口一次映射后,这个值才有。
    • buff添加到scatterlist上:

      • 定义头文件:include/linux/scatterlist.h
      • 操作函数:
        void sg_init_one(struct scatterlist , const void , unsigned int)
        第一个参数:scatterlist 结构体指针
        第二个参数:要映射的buff,可以是定义的数组,也可以是kmalloc等分配的
        第三个参数:buff大小,单位字节
    • 映射函数

      • 定义头文件:include/linux/dma-mapping.h
      • 函数
        宏dma_map_sg(d, s, n, r)展开是调用dma_map_sg_attrs(d, s, n, r, NULL):
        int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction dir,
        struct dma_attrs *attrs)
        第一个参数:设备指针
        第二个参数:scatterlist 结构体的数组指针。
        第三个参数:要映射的buff个数
        第四个参数:流方向标记
        第五个参数:NULL
        返回值:映射成功buff的个数,一般和第三个参数相等。
        • 映射完成后,取映射buff的总线地址(物理地址)用函数sg_dma_address(&sg[n]),取长度用sg_dma_len(&sg[n]).
        • 同样,映射完成后,CPU要访问映射的空间,必须取消后访问,如果不想取消映射,临时用函数void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
          int nelems, enum dma_data_direction dir)处理后,CPU才能访问,函数的参数到这里一看就不都知道了。CPU访问完成后,需要恢复处理
          void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
          int nelems, enum dma_data_direction dir),恢复完成后,设备就可以访问了。
    • 取消映射函数

      • 定义头文件:include/linux/dma-mapping.h
      • 函数
        宏dma_unmap_sg(d, s, n, r)展开dma_unmap_sg_attrs(d, s, n, r, NULL):
        void dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction dir,
        struct dma_attrs *attrs)
        第一个参数:设备指针
        第二个参数:scatterlist 结构体的数组指针
        第三个参数:取消映射的buff个数,同dma_map_sg第三个参数
        第四个参数:流方向标记
        第五个参数:为NULL

发散/汇聚映射用法例子:

#include <linux/scatterlist.h>#include <linux/dma-mapping.h>#include <linux/dma-direction.h>#include <linux/device.h>char bufA[100];int  bufB[200];struct scatterlist sg[3];struct device dev;int sginit(){    int ret = -1;    void *p = NULL;    P = kmalloc(1024,GFP_KERNEL);    sg_init_one(&sg[0],(void *)bufA,100);    sg_init_one(&sg[1],(void *)bufB,200);    sg_init_one(&sg[2],P,100);    ret = dma_map_sg(&dev,sg,3,DMA_FROM_DEVICE );    if(ret == 3)        return 3;    else        return -1;}

这样后面要用到那个映射,用sg_dma_address(&sg[n])取出物理地址。

0 0
原创粉丝点击