动态DMA映射使用通用设备 【ChatGPT】

发布时间 2023-12-09 16:38:34作者: 摩斯电码

动态DMA映射使用通用设备

作者

James E.J. Bottomley James.Bottomley@HansenPartnership.com

本文档描述了DMA API。要了解API的更详细介绍(以及实际示例),请参阅动态DMA映射指南。

该API分为两部分。第一部分描述了基本API。第二部分描述了支持非一致内存机器的扩展。除非您确定您的驱动程序绝对必须支持非一致平台(这通常只是传统平台),否则您应该只使用第一部分中描述的API。

第一部分 - dma_API

要获取dma_API,您必须包含 <linux/dma-mapping.h>。这提供了dma_addr_t和下面描述的接口。

dma_addr_t可以保存平台上任何有效的DMA地址。它可以被提供给设备,用作DMA源或目标。CPU不能直接引用dma_addr_t,因为它的物理地址空间和DMA地址空间之间可能需要进行转换。

第一部分a - 使用大型DMA一致性缓冲区

void *
dma_alloc_coherent(struct device *dev, size_t size,
                   dma_addr_t *dma_handle, gfp_t flag)

一致性内存是指无论是设备还是处理器的写入,都可以立即被设备或处理器读取,而无需担心缓存效应。(但您可能需要确保在告知设备读取该内存之前刷新处理器的写入缓冲区。)

此例程分配了一个大小为<size>字节的一致性内存区域。

它返回分配区域的指针(在处理器的虚拟地址空间中),如果分配失败则返回NULL。

它还返回一个<dma_handle>,可以将其转换为与总线宽度相同的无符号整数,并将其作为该区域的DMA地址基址提供给设备。

注意:在某些平台上,一致性内存可能很昂贵,并且最小分配长度可能与页面大小一样大,因此您应尽可能合并对一致性内存的请求。最简单的方法是使用dma_pool调用(参见下文)。

flag参数(仅适用于dma_alloc_coherent())允许调用者指定GFP_标志(参见kmalloc())进行分配(实现可能选择忽略影响返回内存位置的标志,如GFP_DMA)。

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

释放先前分配的一致性内存区域。dev、size和dma_handle必须与传递给dma_alloc_coherent()的相同。cpu_addr必须是dma_alloc_coherent()返回的虚拟地址。

请注意,与其兄弟分配调用不同,这些例程只能在启用IRQ时调用。

第一部分b - 使用小型DMA一致性缓冲区

要获取dma_API的这一部分,您必须包含 <linux/dmapool.h>

许多驱动程序需要大量小型DMA一致性内存区域,用于DMA描述符或I/O缓冲区。与使用dma_alloc_coherent()以页面或更大单位分配不同,您可以使用DMA池。这些工作方式类似于struct kmem_cache,但它们使用DMA一致性分配器,而不是__get_free_pages()。此外,它们了解对齐的常见硬件约束,比如队列头需要在N字节边界上对齐。

struct dma_pool *
dma_pool_create(const char *name, struct device *dev,
                size_t size, size_t align, size_t alloc)

dma_pool_create()初始化了一个DMA一致性缓冲区池,用于给定设备的使用。它必须在可以睡眠的上下文中调用。

"name"用于诊断(类似于struct kmem_cache名称);dev和size类似于传递给dma_alloc_coherent()的内容。该类型数据的设备硬件对齐要求为"align"(以字节表示,必须是2的幂)。如果您的设备没有边界交叉限制,请传递0给alloc;传递4096表示从该池分配的内存不得跨越4KB边界。

void *
dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags,
                dma_addr_t *handle)

包装dma_pool_alloc(),如果分配尝试成功,则还会将返回的内存清零。

void *
dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags,
               dma_addr_t *dma_handle)

从池中分配内存;返回的内存将满足在创建时指定的大小和对齐要求。传递GFP_ATOMIC以防止阻塞,或者如果允许(不在中断中,不持有SMP锁),则传递GFP_KERNEL以允许阻塞。与dma_alloc_coherent()一样,这将返回两个值:CPU可用的地址和池设备可用的DMA地址。

void
dma_pool_free(struct dma_pool *pool, void *vaddr,
              dma_addr_t addr)

将内存放回池中。池是传递给dma_pool_alloc()的内容;CPU(vaddr)和DMA地址是该例程分配的内存被释放时返回的内容。

void
dma_pool_destroy(struct dma_pool *pool)

dma_pool_destroy()释放了池的资源。它必须在可以睡眠的上下文中调用。在销毁池之前,请确保已将所有分配的内存释放回池中。

第一部分c - DMA寻址限制

int
dma_set_mask_and_coherent(struct device *dev, u64 mask)

检查是否可能使用该掩码,并更新设备流式和一致性DMA掩码参数(如果可能)。

返回:如果成功则返回0,否则返回负错误。

int
dma_set_mask(struct device *dev, u64 mask)

检查是否可能使用该掩码,并更新设备参数(如果可能)。

返回:如果成功则返回0,否则返回负错误。

int
dma_set_coherent_mask(struct device *dev, u64 mask)

检查是否可能使用该掩码,并更新设备参数(如果可能)。

返回:如果成功则返回0,否则返回负错误。

u64
dma_get_required_mask(struct device *dev)

此API返回平台要求的掩码,通常意味着返回的掩码是覆盖所有内存所需的最小掩码。检查所需的掩码使具有可变描述符大小的驱动程序有机会根据需要使用较小的描述符。

请求所需的掩码不会更改当前掩码。如果您希望利用它,应发出dma_set_mask()调用,将掩码设置为返回的值。

size_t
dma_max_mapping_size(struct device *dev)

返回设备的映射最大大小。映射函数的大小参数,如dma_map_single()、dma_map_page()等,不应大于返回的值。

size_t
dma_opt_mapping_size(struct device *dev)

返回设备的最大最优映射大小。

在某些情况下,映射更大的缓冲区可能需要更长的时间。此外,对于高速短暂的流式映射,用于映射的前期时间可能占总请求寿命的相当大部分。因此,如果分割较大的请求不会带来显著的性能损失,那么建议设备驱动程序将总DMA流式映射长度限制为返回的值。

bool
dma_need_sync(struct device *dev, dma_addr_t dma_addr)

如果需要进行dma_sync_single_for_{device,cpu}调用来传输内存所有权,则返回%true。如果可以跳过这些调用,则返回%false。

unsigned long
dma_get_merge_boundary(struct device *dev)

返回DMA合并边界。如果设备无法合并任何DMA地址段,则该函数返回0。

第一部分d - 流式DMA映射

dma_map_single

dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction direction)

将一段处理器虚拟内存映射,以便设备可以访问,并返回内存的DMA地址。

这两个API的方向可以自由转换,但是DMA_API使用强类型的枚举器来表示方向:

  • DMA_NONE:无方向(用于调试)
  • DMA_TO_DEVICE:数据从内存传输到设备
  • DMA_FROM_DEVICE:数据从设备传输到内存
  • DMA_BIDIRECTIONAL:方向未知

注意:

  • 并非所有机器上的内存区域都可以通过此API进行映射。
  • 进一步,连续的内核虚拟空间可能不连续作为物理内存。
  • 由于此API不提供任何分散/聚集功能,如果用户尝试映射非物理上连续的内存片段,映射将失败。
  • 因此,应该从能够保证其物理上连续的来源(如kmalloc)获取要由此API映射的内存。

警告:

  • 内存一致性以缓存行宽度为粒度进行操作。为了确保由此API映射的内存正确操作,映射区域必须恰好从缓存行边界开始并恰好结束在缓存行边界上(以防止两个分别映射的区域共享单个缓存行)。
  • 由于缓存行大小可能在编译时不可知,API不会强制执行此要求。因此,建议未特别关注确定运行时缓存行大小的驱动程序编写者,只映射从页边界开始和结束的虚拟区域(这也保证了它们是缓存行边界)。

同步说明:

  • DMA_TO_DEVICE同步必须在软件最后修改内存区域之后,并在将其交给设备之前进行。一旦使用此原语,此原语覆盖的内存应被设备视为只读。如果设备可能随时对其进行写入,则应该使用DMA_BIDIRECTIONAL(见下文)。
  • DMA_FROM_DEVICE同步必须在驱动程序访问可能被设备更改的数据之前进行。驱动程序应将此内存视为只读。如果驱动程序需要随时对其进行写入,则应该使用DMA_BIDIRECTIONAL(见下文)。
  • DMA_BIDIRECTIONAL需要特殊处理:这意味着驱动程序不确定内存在交给设备之前是否已被修改,也不确定设备是否也会修改它。因此,必须始终对双向内存进行两次同步:一次是在将内存交给设备之前(以确保所有内存更改已从处理器刷新),另一次是在设备使用后可能访问数据之前(以确保设备可能已更改的数据已更新到处理器缓存行中)。

dma_unmap_single

void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction)

取消先前映射的区域。传递的所有参数必须与映射API中传递的(和返回的)参数相同。

dma_map_page 和 dma_unmap_page

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_address, size_t size, enum dma_data_direction direction)

用于页面的映射和取消映射的API。所有其他映射API的注意事项和警告也适用于此处。尽管提供了<offset><size>参数以进行部分页面映射,但建议除非真正了解缓存宽度,否则不要使用这些参数。

dma_map_resource 和 dma_unmap_resource

dma_addr_t dma_map_resource(struct device *dev, phys_addr_t phys_addr, size_t size, enum dma_data_direction dir, unsigned long attrs)

void dma_unmap_resource(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir, unsigned long attrs)

用于映射和取消映射MMIO资源的API。所有其他映射API的注意事项和警告也适用于此处。该API只能用于映射设备MMIO资源,不允许映射RAM。

dma_mapping_error

int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)

在某些情况下,dma_map_single()、dma_map_page()和dma_map_resource()将无法创建映射。驱动程序可以通过使用dma_mapping_error()测试返回的DMA地址来检查这些错误。非零返回值表示无法创建映射,驱动程序应采取适当的措施(例如减少当前DMA映射使用或延迟并稍后重试)。

dma_map_sg 和 dma_unmap_sg

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 nents, enum dma_data_direction direction)

返回:映射的DMA地址段数(如果散射/聚集列表的某些元素在物理上或虚拟上相邻,并且IOMMU使用单个条目映射它们,则此数可能小于传入的<nents>)。

请注意,如果已经映射了sg,则无法再次映射。映射过程允许破坏sg中的信息。

与其他映射接口一样,dma_map_sg()可能会失败。失败时返回0,驱动程序必须采取适当的措施。驱动程序必须采取行动是至关重要的,在块驱动程序的情况下,中止请求甚至是发生故障也比不采取任何行动和损坏文件系统要好。

使用散射列表时,可以像这样使用生成的映射:

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
        hw_address[i] = sg_dma_address(sg);
        hw_len[i] = sg_dma_len(sg);
}

其中nents是sglist中条目的数量。

实现可以自由地将几个连续的sglist条目合并为一个(例如,使用IOMMU,或者如果几个页面恰好是物理上连续的),并返回它们映射到的实际sg条目数。失败时返回0。

然后,应循环count次(注意:这可以少于nents次),并在先前访问sg->address和sg->length的地方使用sg_dma_address()和sg_dma_len()宏。

dma_sync_single_for_cpu、dma_sync_single_for_device、dma_sync_sg_for_cpu 和 dma_sync_sg_for_device

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction)

void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction)

void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)

void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)

为CPU和设备同步单个连续或散射/聚集映射。对于sync_sg API,所有参数必须与传递给单个映射API的参数相同。对于sync_single API,可以使用与传递给单个映射API不同的dma_handle和size参数来执行部分同步。

注意:

  • 在从设备读取已由DMA写入的值之前(使用DMA_FROM_DEVICE方向)之前必须执行同步。
  • 在使用DMA写入将要写入设备的值之后(使用DMA_TO_DEVICE方向)之前必须执行同步。
  • 如果内存是DMA_BIDIRECTIONAL,则在将内存交给设备之前和之后必须执行同步。

dma_map_single_attrs、dma_unmap_single_attrs、dma_map_sg_attrs 和 dma_unmap_sg_attrs

dma_addr_t dma_map_single_attrs(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction dir, unsigned long attrs)

void dma_unmap_single_attrs(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir, unsigned long attrs)

int dma_map_sg_attrs(struct device *dev, struct scatterlist *sgl, int nents, enum dma_data_direction dir, unsigned long attrs)

void dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sgl, int nents, enum dma_data_direction dir, unsigned long attrs)

上述四个函数与没有_attrs后缀的对应函数相似,只是它们传递了可选的dma_attrs。

DMA属性的解释是特定于体系结构的,每个属性应在DMA属性中进行文档化。

如果dma_attrs为0,则这些函数的语义与相应的没有_attrs后缀的函数完全相同。因此,dma_map_single_attrs()通常可以替换dma_map_single()等。

以下是使用*_attrs函数的示例,这是如何在为DMA映射内存时传递DMA_ATTR_FOO属性的方法:

#include <linux/dma-mapping.h>
/* DMA_ATTR_FOO应该在linux/dma-mapping.h中定义,并在Documentation/core-api/dma-attributes.rst中进行文档化 */
...

unsigned long attr;
attr |= DMA_ATTR_FOO;
....
n = dma_map_sg_attrs(dev, sg, nents, DMA_TO_DEVICE, attr);
....

关心DMA_ATTR_FOO的体系结构将在其映射和取消映射例程的实现中检查其是否存在,例如:

void whizco_dma_map_sg_attrs(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir, unsigned long attrs)
{
        ....
        if (attrs & DMA_ATTR_FOO)
                /* twizzle the frobnozzle */
        ....
}

第二部分 - 非一致性 DMA 分配

这些 API 允许分配页面,保证由传入设备进行 DMA 寻址,但需要对内存所有权进行显式管理,以便内核与设备之间进行内存所有权的管理。

如果你不理解处理器和 I/O 设备之间的缓存行一致性是如何工作的,你不应该使用这个 API 的这一部分。

struct page *
dma_alloc_pages(struct device *dev, size_t size, dma_addr_t *dma_handle,
                enum dma_data_direction dir, gfp_t gfp)

这个函数分配了一个大小为 <size> 字节的非一致性内存区域。它返回该区域的第一个 struct page 的指针,如果分配失败则返回 NULL。返回的 struct page 可以用于所有适合 struct page 的操作。

它还返回一个 <dma_handle>,可以将其转换为与总线宽度相同的无符号整数,并将其作为该区域的 DMA 地址基址传递给设备。

dir 参数指定设备是读取和/或写入数据,详细信息请参阅 dma_map_single()。

gfp 参数允许调用者指定 GFP_ 标志(参见 kmalloc())进行分配,但拒绝用于指定内存区域的标志,如 GFP_DMA 或 GFP_HIGHMEM。

在将内存提供给设备之前,需要调用 dma_sync_single_for_device(),在读取设备写入的内存之前,需要调用 dma_sync_single_for_cpu(),就像重用的流式 DMA 映射一样。

void
dma_free_pages(struct device *dev, size_t size, struct page *page,
                dma_addr_t dma_handle, enum dma_data_direction dir)

释放先前使用 dma_alloc_pages() 分配的内存区域。dev、size、dma_handle 和 dir 必须与传递给 dma_alloc_pages() 的参数相同。page 必须是 dma_alloc_pages() 返回的指针。

int
dma_mmap_pages(struct device *dev, struct vm_area_struct *vma,
               size_t size, struct page *page)

将从 dma_alloc_pages() 返回的分配映射到用户地址空间。dev 和 size 必须与传递给 dma_alloc_pages() 的参数相同。page 必须是 dma_alloc_pages() 返回的指针。

void *
dma_alloc_noncoherent(struct device *dev, size_t size,
                dma_addr_t *dma_handle, enum dma_data_direction dir,
                gfp_t gfp)

这个函数是 dma_alloc_pages 的一个方便封装,它返回分配内存的内核虚拟地址,而不是页面结构。

void
dma_free_noncoherent(struct device *dev, size_t size, void *cpu_addr,
                dma_addr_t dma_handle, enum dma_data_direction dir)

释放先前使用 dma_alloc_noncoherent() 分配的内存区域。dev、size、dma_handle 和 dir 必须与传递给 dma_alloc_noncoherent() 的参数相同。cpu_addr 必须是 dma_alloc_noncoherent() 返回的虚拟地址。

struct sg_table *
dma_alloc_noncontiguous(struct device *dev, size_t size,
                        enum dma_data_direction dir, gfp_t gfp,
                        unsigned long attrs)

这个函数分配 <size> 字节的非一致性和可能非连续的内存。它返回一个指向描述分配和 DMA 映射内存的 struct sg_table 的指针,如果分配失败则返回 NULL。返回的内存可以用于 struct page 映射到 scatterlist 中适合的操作。

返回的 sg_table 保证有 1 个单一的 DMA 映射段,如 sgt->nents 所示,但可能有多个 CPU 端段,如 sgt->orig_nents 所示。

dir 参数指定设备是读取和/或写入数据,详细信息请参阅 dma_map_single()。

gfp 参数允许调用者指定 GFP_ 标志(参见 kmalloc())进行分配,但拒绝用于指定内存区域的标志,如 GFP_DMA 或 GFP_HIGHMEM。

attrs 参数必须是 0 或 DMA_ATTR_ALLOC_SINGLE_PAGES。

在将内存提供给设备之前,需要调用 dma_sync_sgtable_for_device(),在读取设备写入的内存之前,需要调用 dma_sync_sgtable_for_cpu(),就像重用的流式 DMA 映射一样。

void
dma_free_noncontiguous(struct device *dev, size_t size,
                       struct sg_table *sgt,
                       enum dma_data_direction dir)

释放先前使用 dma_alloc_noncontiguous() 分配的内存。dev、size 和 dir 必须与传递给 dma_alloc_noncontiguous() 的参数相同。sgt 必须是 dma_alloc_noncontiguous() 返回的指针。

void *
dma_vmap_noncontiguous(struct device *dev, size_t size,
        struct sg_table *sgt)

返回从 dma_alloc_noncontiguous() 返回的分配的连续内核映射。dev 和 size 必须与传递给 dma_alloc_noncontiguous() 的参数相同。sgt 必须是 dma_alloc_noncontiguous() 返回的指针。

一旦使用此函数映射了非连续分配,必须使用 flush_kernel_vmap_range() 和 invalidate_kernel_vmap_range() API 来管理内核映射、设备和用户空间映射(如果有)之间的一致性。

void
dma_vunmap_noncontiguous(struct device *dev, void *vaddr)

取消映射由 dma_vmap_noncontiguous() 返回的内核映射。dev 必须与传递给 dma_alloc_noncontiguous() 的参数相同。vaddr 必须是 dma_vmap_noncontiguous() 返回的指针。

int
dma_mmap_noncontiguous(struct device *dev, struct vm_area_struct *vma,
                       size_t size, struct sg_table *sgt)

将从 dma_alloc_noncontiguous() 返回的分配映射到用户地址空间。dev 和 size 必须与传递给 dma_alloc_noncontiguous() 的参数相同。sgt 必须是 dma_alloc_noncontiguous() 返回的指针。

int
dma_get_cache_alignment(void)

返回处理器的缓存对齐方式。这是在映射内存或进行部分刷新时必须遵守的绝对最小对齐和宽度。

注意

这个 API 可能返回一个大于实际缓存行的数字,但它将保证一个或多个缓存行完全适合于此调用返回的宽度。它也总是 2 的幂,以便轻松对齐。

以上是关于 DMA API 的详细介绍,希望对你有所帮助。

第三部分 - 调试DMA-API的驱动程序使用

如上所述,DMA-API具有一些约束。例如,DMA地址必须使用相同大小的相应函数释放。随着硬件IOMMU的出现,驱动程序不违反这些约束变得越来越重要。在最坏的情况下,这种违反可能导致数据损坏,甚至破坏文件系统。

为了调试驱动程序并找到DMA-API使用中的错误,可以将检查代码编译到内核中,该代码将向开发人员报告这些违规行为。如果您的架构支持,您可以在内核配置中选择“启用DMA-API使用调试”选项。启用此选项会对性能产生影响。不要在生产内核中启用它。

如果您启动生成的内核,该内核将包含一些关于为哪个设备分配了DMA内存的记录。如果此代码检测到错误,它将在内核日志中打印警告消息及一些详细信息。例如,警告消息可能如下所示:

警告:位于 /data2/repos/linux-2.6-iommu/lib/dma-debug.c:448
        check_unmap+0x203/0x490()
硬件名称:
forcedeth 0000:00:08.0: DMA-API:设备驱动程序使用错误的函数释放DMA内存
        [设备地址=0x00000000640444be] [大小=66字节] [映射为单页] [取消映射为页面]
已链接模块:nfsd exportfs bridge stp llc r8169
Pid: 0, comm: swapper Tainted: G        W  2.6.28-dmatest-09289-g8bb99c0 #1
调用跟踪:
<IRQ>  [<ffffffff80240b22>] warn_slowpath+0xf2/0x130
[<ffffffff80647b70>] _spin_unlock+0x10/0x30
[<ffffffff80537e75>] usb_hcd_link_urb_to_ep+0x75/0xc0
[<ffffffff80647c22>] _spin_unlock_irqrestore+0x12/0x40
[<ffffffff8055347f>] ohci_urb_enqueue+0x19f/0x7c0
[<ffffffff80252f96>] queue_work+0x56/0x60
[<ffffffff80237e10>] enqueue_task_fair+0x20/0x50
[<ffffffff80539279>] usb_hcd_submit_urb+0x379/0xbc0
[<ffffffff803b78c3>] cpumask_next_and+0x23/0x40
[<ffffffff80235177>] find_busiest_group+0x207/0x8a0
[<ffffffff8064784f>] _spin_lock_irqsave+0x1f/0x50
[<ffffffff803c7ea3>] check_unmap+0x203/0x490
[<ffffffff803c8259>] debug_dma_unmap_page+0x49/0x50
[<ffffffff80485f26>] nv_tx_done_optimized+0xc6/0x2c0
[<ffffffff80486c13>] nv_nic_irq_optimized+0x73/0x2b0
[<ffffffff8026df84>] handle_IRQ_event+0x34/0x70
[<ffffffff8026ffe9>] handle_edge_irq+0xc9/0x150
[<ffffffff8020e3ab>] do_IRQ+0xcb/0x1c0
[<ffffffff8020c093>] ret_from_intr+0x0/0xa
<EOI> <4>---[ end trace f6435a98e2a38c0e ]---

驱动程序开发人员可以找到引发此警告的驱动程序和设备,包括导致此警告的DMA-API调用的堆栈跟踪。

默认情况下,只有第一个错误会导致警告消息。所有其他错误将只被默默地计数。这个限制存在是为了防止代码淹没内核日志。为了支持调试设备驱动程序,可以通过debugfs禁用此功能。有关详细信息,请参阅下面的debugfs接口文档。

DMA-API调试代码的debugfs目录称为dma-api/。在此目录中,当前可以找到以下文件:

  • dma-api/all_errors:此文件包含一个数字值。如果此值不等于零,则调试代码将为它找到的每个错误打印警告消息到内核日志中。请注意,此选项可能会轻易地淹没您的日志。
  • dma-api/disabled:此只读文件包含字符'Y',如果调试代码已禁用。当它耗尽内存或在引导时禁用时,会发生这种情况。
  • dma-api/dump:此只读文件包含当前的DMA映射。
  • dma-api/error_count:此文件是只读的,显示找到的错误总数。
  • dma-api/num_errors:此文件中的数字显示在停止之前将打印到内核日志中的警告数量。此数字在系统启动时初始化为一,并且可以通过向此文件写入来设置。
  • dma-api/min_free_entries:此只读文件可读取以获取分配器曾经看到的最小数量的空闲dma_debug_entries。如果此值降至零,代码将尝试增加nr_total_entries以补偿。
  • dma-api/num_free_entries:分配器中当前的空闲dma_debug_entries数量。
  • dma-api/nr_total_entries:分配器中dma_debug_entries的总数,包括空闲和已使用的。
  • dma-api/driver_filter:您可以将驱动程序的名称写入此文件,以将调试输出限制为来自特定驱动程序的请求。将空字符串写入该文件以禁用过滤器并再次查看所有错误。

如果将此代码编译到内核中,它将默认启用。如果您想要在没有记录的情况下启动,可以在引导参数中提供'dma_debug=off'。这将禁用DMA-API调试。请注意,您无法在运行时重新启用它。您必须重新启动才能这样做。

如果您只想为特定设备驱动程序查看调试消息,可以指定dma_debug_driver=<drivername>参数。这将在引导时启用驱动程序过滤器。调试代码之后只会为该驱动程序打印错误。此过滤器可以在以后使用debugfs禁用或更改。

当代码在运行时禁用自身时,这很可能是因为它耗尽了dma_debug_entries并且无法按需分配更多。在引导时,将预分配65536个条目 - 如果这对您来说太低,请使用'dma_debug_entries=<your_desired_number>'来覆盖默认值。请注意,代码会批量分配条目,因此预分配条目的确切数量可能大于实际请求的数量。每次动态分配与最初预分配的条目数量相同时,代码将在内核日志中打印。这是为了指示可能需要更大的预分配大小,或者如果驱动程序持续泄漏映射。

void
debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr);

dma-debug接口debug_dma_mapping_error()用于调试未能检查由dma_map_single()和dma_map_page()接口返回的地址的DMA映射错误的驱动程序。此接口清除由debug_dma_map_page()设置的标志,以指示驱动程序已调用dma_mapping_error()。当驱动程序取消映射时,debug_dma_unmap()会检查标志,如果此标志仍然设置,则打印警告消息,其中包括导致取消映射的调用跟踪。此接口可以从dma_mapping_error()例程中调用,以启用DMA映射错误检查调试。