display:Driver porting: DMA changes-2003

23 篇文章 45 订阅
8 篇文章 2 订阅

 

直接内存访问(DMA)支持层在2.6中已进行了广泛更改,但是在许多情况下,设备驱动程序应保持不变。对于使用新驱动程序的开发人员,或希望使用最新API保持其代码最新的开发人员,有很多更改需要注意。

最明显的变化是创建了新的通用DMA层。大多数驱动程序程序员都知道pci_* DMA支持函数;SPARC程序员可能也遇到过类似的sbus_*函数集。从2.5.53开始,添加了一组新的通用DMA函数,目的是提供一个DMA支持API,而不是特定于任何特定总线。新的功能看起来很像旧的功能;从一个API切换到另一个API是一项相当自动的工作。

下面的讨论将注意到DMA API的变化,而不会查看每个新的dma_*函数。请参阅我们的DMA API快速参考页,以获得从旧的PCI接口到新通用函数的映射的简明摘要。

Allocating DMA regions

新的和旧的DMA api都区分了“一致”(或“一致”)"consistent" (or "coherent") 和“流”"streaming"内存。一致的内存保证在处理器和支持dma的设备上看起来是一样的,没有缓存造成的问题;它最常用于长期的双向I/O缓冲区。流存储可能具有缓存效果,通常用于单个传输。

用于分配一致内存的PCI函数从2.4起没有改变:

    void *pci_alloc_consistent(struct pci_dev *dev, size_t size,
			       dma_addr_t *dma_handle);
    void pci_free_consistent(struct pci_dev *dev, size_t size,
			     void *cpu_addr, dma_addr_t dma_handle);

通用版本有一点不同,对这种类型的内存采用了术语“连贯”,并添加了一个分配标志:

    void *dma_alloc_coherent(struct device *dev, size_t size,
			     dma_addr_t *dma_handle, int flag);
    void dma_free_coherent(struct device *dev, size_t size,
			   void *cpu_addr, dma_addr_t dma_handle);

这里添加的flag参数是通常的内存分配标志。pci_alloc_consistent()被认为具有隐式的GFP_ATOMIC标志。

对于单缓冲区流分配,PCI接口同样是不变的,而且通用DMA接口与PCI版本是同构的。现在有一个枚举类型来描述映射的方向:

    enum dma_data_direction {
        DMA_BIDIRECTIONAL = 0,
        DMA_TO_DEVICE = 1,
        DMA_FROM_DEVICE = 2,
        DMA_NONE = 3,
    };

实际的映射和解映射函数是:

    dma_addr_t dma_map_single(struct device *dev, void *addr,
	                      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);

    dma_addr_t dma_map_page(struct device *dev, struct page *page,
	                    unsigned long offset, size_t size,
			    enum dma_data_direction direction);
    void dma_unmap_page(struct device *dev, dma_addr_t dma_addr, 
                        size_t size,
			enum dma_data_direction direction);

就像这些函数的PCI版本一样,除非您真正知道自己在做什么,否则不鼓励使用offset和size参数。

在创建scatter/gather streamingDMA映射方面有一个显著的变化。struct scatterlist的2.4版本为要映射的缓冲区使用了一个char *指针(称为address),而struct页面指针只用于高内存地址。在2.6中,地址指针没有了,所有的散列列表都必须使用struct页面指针来构建。

scatter/gather函数的通用版本是:

    int dma_map_sg(struct device *dev, struct scatterlist *sg, 
                   int nents, enum dma_data_direction direction);
    void dma_unmap_sg(struct device *dev, struct scatterlist *sg, 
                      int nhwentries, enum dma_data_direction direction);

分散/收集I/O允许系统在分散在物理内存中的缓冲区上执行DMA I/O操作。例如,考虑在用户空间中创建一个大型(多页)缓冲区的情况。应用程序可以看到一个连续的虚拟地址范围,但是这些地址后面的物理页面几乎肯定不会相邻。

如果该缓冲区要在一个单一的I/O操作中写入设备,必须做两件事之一:

(1)数据必须复制到一个物理连续的缓冲区。

或者(2)设备必须能够处理物理地址和长度的列表,从每个段中抓取适当数量的数据。分散/聚集I/O,通过消除将数据复制到相邻缓冲区的需要,可以极大地提高I/O操作的效率,同时解决了创建大型的、物理上相邻的缓冲区可能首先是一个问题。

https://lwn.net/Articles/256368/

Noncoherent DMA mappings

2.6中的通用DMA层包括一组用于创建显式非一致映射的函数。很少有驱动程序需要使用这个接口;它主要用于那些必须在不能创建一致映射的旧平台上工作的代码。请注意,对于这些函数没有PCI等价物;您必须使用通用变体。

非一致映射的函数:

    void *dma_alloc_noncoherent(struct device *dev, size_t size,
			        dma_addr_t *dma_handle, int flag);

这个函数的行为与dma_alloc_coherent()相同,除了返回的映射可能不在一致的内存中。使用该内存的驱动程序必须小心遵守所有权规则,并在需要时调用相应的dma_sync_*函数。一个额外的功能:

    void dma_sync_single_range(struct device *dev, dma_addr_t dma_handle,
		               unsigned long offset, size_t size,
			       enum dma_data_direction direction);

将只同步(较大的)非一致映射的一部分。

当你的驱动程序完成映射后,它应该以如下方式返回到系统:

    void dma_free_noncoherent(struct device *dev, size_t size, 
                              void *cpu_addr, dma_addr_t dma_handle);

Double address cycle addressing

一种总线标准总线能够“双地址周期”(DAC)的操作模式。DAC允许使用64位DMA地址,极大地扩展了在没有I/O内存映射单元的系统上可访问的内存范围。然而,DAC也很昂贵,并不是所有设备和总线都适当支持它。因此DMA支持例程通常会避免创建需要DAC的映射—即使驱动程序已经设置了允许它的地址掩码。

但是,在某些情况下DAC是有用的。特别是,在正常的单周期地址范围内,可能不可能有非常大的DMA映射。对于这些罕见的情况,PCI层(而不是通用的DMA层)提供了一组特殊的函数。注意,使用DAC函数可能非常昂贵;除非绝对必要,否则一般应避免使用。这些功能并不是严格意义上的2.6特性;它们也被添加到2.4.13中。

支持dac的驱动程序必须先设置一个单独的地址掩码:

    int pci_dac_set_dma_mask(struct pci_dev *dev, u64 mask);

掩码描述您的设备可以支持的地址范围。如果函数返回非零,则不能使用DAC寻址,也不应该尝试。

DAC映射通过以下方式创建:

   dma64_addr_t pci_dac_page_to_dma(struct pci_dev *dev,
				     struct page *page,
				     unsigned long offset,
				     int direction);

关于DAC映射有几点需要注意。它们只能使用结构体页面指针和偏移量创建;DAC映射,就其本质而言,将位于高内存中,因此不会有内核虚拟地址。DAC映射是一种直接的地址转换,不需要外部资源,因此在使用后不需要显式地解除它们的映射。最后,所有的DAC映射都是不一致的(非一致的)映射,因此需要显式的同步,以确保设备和CPU看到相同的内存。对于DAC映射,使用:

    void pci_dac_dma_sync_single(struct pci_dev *dev,
				 dma64_addr_t dma_addr,
				 size_t len, int direction);

Some other details

在许多架构中,DMA映射不会消耗任何资源,因此不需要解除它们的映射。在这些体系结构上,各种unmap函数被设置为no-ops,但一些程序员显然不喜欢不必要地记住DMA映射地址和长度。所以2.6(和2.4.18的2.4)有一个相当复杂的预处理器滥用,可以用它来节省几个字的内存。如果有兴趣,请参阅源代码树中的Documentation/DMA-mapping.txt。

“PCI pool”接口绝对不是一个特定于2.5版本的特性,因为它是在2.4.4中首次出现的。这是足够新的,然而,一些参考(如Linux设备驱动程序,第二版)没有涵盖它们。PCI池接口允许使用非常小的DMA缓冲区。在过去,这样的缓冲区通常保存在设备特定的结构中。然而,当DMA缓冲区与相同结构的其他成员共享高速缓存线时,一些用户遇到了麻烦。创建PCI池接口是为了帮助将微小的DMA缓冲区移动到它们自己的空间中,并避免这种内存损坏。再次,参见DMA-mapping.txt了解详细信息。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值