使用通用设备的动态 DMA 映射¶
- 作者:
James E.J. Bottomley <James.Bottomley@HansenPartnership.com>
本文档描述了 DMA API。有关 API 的更温和的介绍(和实际示例),请参阅动态 DMA 映射指南。
此 API 分为两部分。第一部分描述了基本 API。第二部分描述了支持非一致性内存机器的扩展。除非您知道您的驱动程序绝对必须支持非一致性平台(这通常只适用于旧平台),否则您应该只使用第一部分中描述的 API。
第一部分 - dma_API¶
要获取 dma_API,您必须 #include <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 调用(见下文)。
标志参数(仅限于 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 的这一部分,您必须 #include <linux/dmapool.h>
许多驱动程序需要大量小型 DMA 一致性内存区域用于 DMA 描述符或 I/O 缓冲区。您可以使用 DMA 池,而不是使用 dma_alloc_coherent() 以页面或更大的单位进行分配。它们的工作方式与 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 的幂)。如果您的设备没有跨边界限制,则将 alloc 设为 0;传递 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);
这会将内存放回池中。pool 是传递给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_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 不提供任何分散/聚集能力,如果用户尝试映射非物理连续的内存,它将失败。因此,要通过此 API 映射的内存应从保证物理连续的源(如 kmalloc)获取。
此外,内存的 DMA 地址必须在设备的 dma_mask 范围内(dma_mask 是设备可寻址区域的位掩码,即,如果内存的 DMA 地址与 dma_mask 进行 AND 运算后仍等于 DMA 地址,则设备可以对该内存执行 DMA)。为确保 kmalloc 分配的内存位于 dma_mask 范围内,驱动程序可以指定各种平台相关标志来限制分配的 DMA 地址范围(例如,在 x86 上,GFP_DMA 保证在可用 DMA 地址的前 16MB 范围内,这是 ISA 设备所要求的)。
另请注意,如果平台具有 IOMMU(将 I/O DMA 地址映射到物理内存地址的设备),则上述关于物理连续性和 dma_mask 的约束可能不适用。但是,为了可移植性,设备驱动程序编写者不得假设存在这样的 IOMMU。
警告
内存一致性以称为缓存行宽度的粒度运行。为了使此 API 映射的内存正常工作,映射区域必须正好从缓存行边界开始并在缓存行边界结束(以防止两个单独映射的区域共享单个缓存行)。由于缓存行大小在编译时可能未知,API 不会强制执行此要求。因此,建议不特别注意在运行时确定缓存行大小的驱动程序编写者仅映射在页面边界开始和结束的虚拟区域(这些区域也保证是缓存行边界)。
DMA_TO_DEVICE 同步必须在软件对内存区域进行最后修改之后,并在将其交给设备之前完成。一旦使用此原语,此原语覆盖的内存应被设备视为只读。如果设备可能在任何时候写入它,则它应该是 DMA_BIDIRECTIONAL(见下文)。
DMA_FROM_DEVICE 同步必须在驱动程序访问可能被设备更改的数据之前完成。此内存应被驱动程序视为只读。如果驱动程序在任何时候需要写入它,则它应该是 DMA_BIDIRECTIONAL(见下文)。
DMA_BIDIRECTIONAL 需要特殊处理:这意味着驱动程序不确定内存是否在交给设备之前被修改,也不确定设备是否也会修改它。因此,您必须始终对双向内存进行两次同步:一次在内存交给设备之前(以确保所有内存更改都从处理器中刷新),另一次在数据被设备使用后可能被访问之前(以确保任何处理器缓存行都用设备可能已更改的数据进行更新)。
void
dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
enum dma_data_direction direction)
解除映射先前映射的区域。传入的所有参数必须与映射 API 传入(并返回)的参数相同。
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_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。
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 映射使用或延迟并稍后重试)。
int
dma_map_sg(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction)
返回:映射的 DMA 地址段数(如果分散/聚集列表的某些元素在物理或虚拟上相邻且 IOMMU 将它们映射为单个条目,则此数字可能短于传入的 <nents>)。
请注意,sg 一旦被映射,就不能再次映射。映射过程允许破坏 sg 中的信息。
与其他映射接口一样,dma_map_sg() 可能会失败。当它失败时,返回 0,驱动程序必须采取适当的行动。驱动程序采取行动至关重要,对于块设备驱动程序,中止请求甚至 oops 也比什么都不做并损坏文件系统要好。
对于分散列表,您可以像这样使用结果映射
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。
然后,您应该循环计数次(注意:这可以少于 nents 次),并使用 sg_dma_address() 和 sg_dma_len() 宏,而您之前访问的是 sg->address 和 sg->length,如上所示。
void
dma_unmap_sg(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction)
解除映射先前映射的分散/聚集列表。所有参数必须与传入分散/聚集映射 API 的参数相同。
注意:<nents> 必须是您传入的数字,而不是返回的 DMA 地址条目数。
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,所有参数必须与传递给 sg 映射 API 的参数相同。对于 sync_single API,您可以使用与传递给单个映射 API 的参数不完全相同的 dma_handle 和 size 参数来执行部分同步。
注意
您必须这样做
在读取设备通过 DMA 写入的值之前(使用 DMA_FROM_DEVICE 方向)
在写入将使用 DMA 写入设备的值之后(使用 DMA_TO_DEVICE 方向)
如果内存是 DMA_BIDIRECTIONAL,则在将内存交给设备之前和之后
另请参见 dma_map_single()。
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 should be defined in linux/dma-mapping.h and
* documented in 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 */
....
}
第一部分e - 基于 IOVA 的 DMA 映射¶
这些 API 在使用 IOMMU 时允许非常高效的映射。它们是可选路径,需要额外的代码,并且仅建议用于 DMA 映射性能或存储 DMA 地址的空间使用量很重要的驱动程序。上一节中的所有考虑因素也适用于此处。
bool dma_iova_try_alloc(struct device *dev, struct dma_iova_state *state,
phys_addr_t phys, size_t size);
用于尝试为映射操作分配 IOVA 空间。如果它返回 false,则此 API 不能用于给定设备,应使用正常的流式 DMA 映射 API。struct dma_iova_state
由驱动程序分配,并且必须保留直到解除映射。
static inline bool dma_use_iova(struct dma_iova_state *state)
在调用 dma_iova_try_alloc 后,驱动程序可以使用它来检查是否使用了基于 IOVA 的 API。这在解除映射路径中很有用。
int dma_iova_link(struct device *dev, struct dma_iova_state *state,
phys_addr_t phys, size_t offset, size_t size,
enum dma_data_direction dir, unsigned long attrs);
用于将范围链接到先前分配的 IOVA。除第一次调用 dma_iova_link 之外,给定状态的所有后续调用的起始必须对齐到 dma_get_merge_boundary()
返回的 DMA 合并边界,并且除最后一个范围之外的所有范围的大小也必须对齐到 DMA 合并边界。
int dma_iova_sync(struct device *dev, struct dma_iova_state *state,
size_t offset, size_t size);
必须调用以同步 IOMMU 页表,用于一个或多个 dma_iova_link()
调用映射的 IOVA 范围。
对于使用一次性映射的驱动程序,可以通过调用以下函数解除映射所有范围并释放 IOVA:
void dma_iova_destroy(struct device *dev, struct dma_iova_state *state,
size_t mapped_len, enum dma_data_direction dir,
unsigned long attrs);
或者,驱动程序可以通过解除映射和映射单独的区域来动态管理 IOVA 空间。在这种情况下,
void dma_iova_unlink(struct device *dev, struct dma_iova_state *state,
size_t offset, size_t size, enum dma_data_direction dir,
unsigned long attrs);
用于解除映射先前映射的范围,并且
void dma_iova_free(struct device *dev, struct dma_iova_state *state);
用于释放 IOVA 空间。在调用 dma_iova_free()
之前,所有区域都必须已使用 dma_iova_unlink()
解除映射。
第二部分 - 非一致性 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 保证有一个单一的 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 地址必须以相同的大小用对应的函数释放。随着硬件 IOMMU 的出现,驱动程序不违反这些限制变得越来越重要。在最坏的情况下,这种违反可能导致数据损坏,甚至文件系统被破坏。
为了调试驱动程序并发现 DMA-API 使用中的错误,可以将检查代码编译到内核中,这将告知开发人员这些违规行为。如果您的体系结构支持,您可以在内核配置中选择“启用 DMA-API 使用调试”选项。启用此选项会对性能产生影响。请勿在生产内核中启用它。
如果您启动,生成的内核将包含一些关于为哪个设备分配了哪些 DMA 内存的簿记代码。如果此代码检测到错误,它会将警告消息和一些详细信息打印到您的内核日志中。示例警告消息可能如下所示
WARNING: at /data2/repos/linux-2.6-iommu/lib/dma-debug.c:448
check_unmap+0x203/0x490()
Hardware name:
forcedeth 0000:00:08.0: DMA-API: device driver frees DMA memory with wrong
function [device address=0x00000000640444be] [size=66 bytes] [mapped as
single] [unmapped as page]
Modules linked in: nfsd exportfs bridge stp llc r8169
Pid: 0, comm: swapper Tainted: G W 2.6.28-dmatest-09289-g8bb99c0 #1
Call Trace:
<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=<您期望的数字>”启动以覆盖默认值。请注意,代码是分批分配条目的,因此预分配条目的确切数量可能大于实际请求的数量。代码每次动态分配与最初预分配的条目一样多的条目时,都会打印到内核日志。这表明可能需要更大的预分配大小,或者如果这种情况持续发生,则表明驱动程序可能正在泄漏映射。
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 映射错误检查调试。