はじめに
筆者は Linux でユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバとして u-dma-buf を開発して github に公開しています。(諸事情によりgithub 上のリポジトリ名が udmabuf になっています)
また、現在の Linux では、 Cache Coherence Hardware を持っていない一部の組み込み系コンピューターシステムの場合は DMA Buffer をmmap する際に CPU Cache が無効になることがわかっています。
この件に関しては次の記事で詳細に説明しています。
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Linux Kernel の Cache 問題の扱い)』 @Qiita
- 『Linux では Cache Coherence Hardware を持っていないとDMA Buffer をmmap する際に CPU Cache が無効になる』 @Qiita
上の記事では、「Cache Coherence Hardware を持っていないコンピューターシステムの場合は、例え Cache Aliasing 問題が起きない場合でも、 DMA Buffer を mmap する際に CPU Cache が無効になる」ことを説明しています。
u-dma-buf は、「Cache Coherence Hardware を持っていないが Cache Aliasing 問題が起きないコンピューターシステムでも、 DMA Buffer を mmap する際にCPU Cache が有効になる」仕組みを導入しています。この記事ではこの仕組みについて説明します。
QUIRK_MMAP
名前の由来
QUIRK_MMAP とは、その名の通り、QUIRK (変則的なとか風変わりなという意味)な MMAP 方式です。通常、linux では DMA Buffer を mmap する際には DMA Mapping API で定義された関数を使うのがルールですが、u-dma-buf では独自の mmap を導入しています。u-dma-buf では「変則的な」 mmap であることを強調するために、あえて QUIRK_MMAP と呼んでいます。
ソースコードの説明
u-dma-buf にて、確保した DMA Buffer を mmap する関数は udmabuf_object_mmap() です。udmabuf_object_mmap() は3つのパートに分かれています。
/**
* udmabuf_object_mmap() - udmabuf object memory map operation.
* @this: Pointer to the udmabuf object.
* @vma: Pointer to the vm area structure.
* @force_sync Force sync flag.
* Return: Success(=0) or error status(<0).
*/
static int udmabuf_object_mmap(struct udmabuf_object* this, struct vm_area_struct* vma, bool force_sync)
{
/* 事前準備 */
/* QUIRK_MMAP の場合の mmap 処理 */
/* QUIRK_MMAP の場合ではないmmap 処理 */
}
事前準備部
udmabuf_object_mmap() の事前準備のパートは次のようになっています。
static int udmabuf_object_mmap(struct udmabuf_object* this, struct vm_area_struct* vma, bool force_sync)
{
if (vma->vm_pgoff + vma_pages(vma) > (this->alloc_size >> PAGE_SHIFT))
return -EINVAL;
if ((force_sync == true) | (this->sync_mode & SYNC_ALWAYS)) {
switch (this->sync_mode & SYNC_MODE_MASK) {
case SYNC_MODE_NONCACHED :
vm_flags_set(vma, VM_IO);
vma->vm_page_prot = _PGPROT_NONCACHED(vma->vm_page_prot);
break;
case SYNC_MODE_WRITECOMBINE :
vm_flags_set(vma, VM_IO);
vma->vm_page_prot = _PGPROT_WRITECOMBINE(vma->vm_page_prot);
break;
case SYNC_MODE_DMACOHERENT :
vm_flags_set(vma, VM_IO);
vma->vm_page_prot = _PGPROT_DMACOHERENT(vma->vm_page_prot);
break;
default :
break;
}
}
/* QUIRK_MMAP の場合の mmap 処理 */
/* QUIRK_MMAP の場合ではないmmap 処理 */
}
まず、割り当てる仮想空間の大きさが確保した DMA Buffer の大きさを超えていないかチェックしています。
その後、各種フラグを vma 構造体にセットしています。その際に、 vma 構造体の vm_page_prot に sync モードに応じた値を上書きしていることに注目してください。
QUIRK_MMAP の場合の mmap 処理
QUIRK_MMAP の場合の mmap 処理は次のようにしています。
static int udmabuf_object_mmap(struct udmabuf_object* this, struct vm_area_struct* vma, bool force_sync)
{
/* 事前準備 */
#if (USE_QUIRK_MMAP == 1)
if (udmabuf_quirk_mmap_enable(this))
{
unsigned long page_frame_num = (this->phys_addr >> PAGE_SHIFT) + vma->vm_pgoff;
if (pfn_valid(page_frame_num)) {
vm_flags_set(vma, VM_PFNMAP);
vma->vm_ops = &udmabuf_mmap_vm_ops;
vma->vm_private_data = this;
udmabuf_mmap_vma_open(vma);
return 0;
}
}
#endif
/* QUIRK_MMAP の場合ではないmmap 処理 */
}
まず、QUIRK_MMAP が有効かどうかを判定してます。QUIRK_MMAP が有効かどうかは、u-dma-buf をロードした際に指定されたモジュールパラメータやデバイスツリーに指定されたプロパティで判定します。詳細はソースコードを参照してください。
ここで重要なのは、vma 構造体の vm_ops フィールドにセットする udmabuf_mmap_vm_ops です。udmabuf_mmap_vm_ops は次のようになっています。
/**
* udmabuf device file mmap vm operation table.
*/
static const struct vm_operations_struct udmabuf_mmap_vm_ops = {
.open = udmabuf_mmap_vma_open ,
.close = udmabuf_mmap_vma_close,
.fault = udmabuf_mmap_vma_fault,
};
vm_operations_struct 構造体の fault フィールドに udmabuf_mmap_vma_fault() のポインタが指定されています。これはオンデマンドページングという手法です。オンデマンドページングでは、mmap 等で物理メモリ領域を仮想メモリ領域に割り当てる際に、ひとまず「何もしません」。そして、CPU が実際に割り当てられた仮想アドレスをアクセスした時に初めて物理メモリ領域を割り当てます。
具体的には、CPU が割り当てられた仮想アドレスをアクセスした時、もしまだ物理メモリ領域が割り当てられていなかった場合は、CPU に対して例外(割り込み)が発生します。Linux Kernel はその例外をキャッチして、対応する処理を行います。その処理を行うのが、vm_operations_struct 構造体の fault フィールドに設定されている関数です。
vm_operations_struct 構造体の fault フィールドに設定されている udmabuf_mmap_vma_fault() は次のようになっています。Linux Kernel のバージョンによって、戻り値の型や引数が違うのでラッパーをかましています。この関数の本体は _udmabuf_mmap_vma_fault() です。
/**
* VM_FAULT_RETURN_TYPE - Type of udmabuf_mmap_vma_fault() return value.
*/
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0))
typedef vm_fault_t VM_FAULT_RETURN_TYPE;
#else
typedef int VM_FAULT_RETURN_TYPE;
#endif
static inline VM_FAULT_RETURN_TYPE _udmabuf_mmap_vma_fault(struct vm_area_struct* vma, struct vm_fault* vmf){ /* 後述*/ }
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0))
/**
* udmabuf_mmap_vma_fault() - udmabuf device file mmap vm area fault operation.
* @vfm: Pointer to the vm fault structure.
* Return: VM_FAULT_RETURN_TYPE (Success(=0) or error status(!=0)).
*/
static VM_FAULT_RETURN_TYPE udmabuf_mmap_vma_fault(struct vm_fault* vmf)
{
return _udmabuf_mmap_vma_fault(vmf->vma, vmf);
}
#else
/**
* udmabuf_mmap_vma_fault() - udmabuf device file mmap vm area fault operation.
* @vma: Pointer to the vm area structure.
* @vfm: Pointer to the vm fault structure.
* Return: VM_FAULT_RETURN_TYPE (Success(=0) or error status(!=0)).
*/
static VM_FAULT_RETURN_TYPE udmabuf_mmap_vma_fault(struct vm_area_struct* vma, struct vm_fault* vmf)
{
return _udmabuf_mmap_vma_fault(vma, vmf);
}
#endif
_udmabuf_mmap_vma_fault() は次のようになっています。
/**
* _udmabuf_mmap_vma_fault() - udmabuf device file mmap vm area fault operation.
* @vma: Pointer to the vm area structure.
* @vfm: Pointer to the vm fault structure.
* Return: VM_FAULT_RETURN_TYPE (Success(=0) or error status(!=0)).
*/
static inline VM_FAULT_RETURN_TYPE _udmabuf_mmap_vma_fault(struct vm_area_struct* vma, struct vm_fault* vmf)
{
struct udmabuf_object* this = vma->vm_private_data;
unsigned long offset = vmf->pgoff << PAGE_SHIFT;
unsigned long phys_addr = this->phys_addr + offset;
unsigned long page_frame_num = phys_addr >> PAGE_SHIFT;
unsigned long request_size = 1 << PAGE_SHIFT;
unsigned long available_size = this->alloc_size -offset;
unsigned long virt_addr;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0))
virt_addr = vmf->address;
#else
virt_addr = (unsigned long)vmf->virtual_address;
#endif
if (UDMABUF_VMA_DEBUG(this))
dev_info(this->dma_dev,
"vma_fault(virt_addr=%pad, phys_addr=%pad)\n", &virt_addr, &phys_addr
);
if (request_size > available_size)
return VM_FAULT_SIGBUS;
if (!pfn_valid(page_frame_num))
return VM_FAULT_SIGBUS;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0))
return vmf_insert_pfn(vma, virt_addr, page_frame_num);
#else
{
int err = vm_insert_pfn(vma, virt_addr, page_frame_num);
if (err == -ENOMEM)
return VM_FAULT_OOM;
if (err < 0 && err != -EBUSY)
return VM_FAULT_SIGBUS;
return VM_FAULT_NOPAGE;
}
#endif
}
引数の妥当性をチェックしたあと、vmf_insert_pfn() または vm_insert_pfn() を使って引数で指定されたオフセットに対応した物理ページ番号を仮想アドレスに割り当てています。どちらの関数を使うかは Linux Kernel のバージョンによって異なります。
QUIRK_MMAP の場合ではないmmap 処理
udmabuf_object_mmap() の最後に QUIRK_MMAP の場合ではない場合の mmap 処理を示します。この場合は単純に Linux Kernel が提供している DMA Mapping API の dma_mmap_coherent() を呼び出しています。
static int udmabuf_object_mmap(struct udmabuf_object* this, struct vm_area_struct* vma, bool force_sync)
{
/* 事前準備 */
/* QUIRK_MMAP の場合の mmap 処理 */
return dma_mmap_coherent(this->dma_dev, vma, this->virt_addr, this->phys_addr, this->alloc_size);
}
目的
このように u-dma-buf の QUIRK_MMAP は、Linux Kernel が提供する DMA Mapping API の mmap を使わずに独自の mmap を用意しています。
その理由は、Linux Kernel の DMA Mapping API の mmap では、Cache Coherence Hardware を持っていないコンピューターシステムの場合は、例え Cache Aliasing 問題が起きない場合でも、 dma_pgprot() によって vma 構造体の vm_page_prot フィールドを CPU Cache が無効になるように再設定してしまうからです。詳細は以下の記事を参照してください。
そこで、 u-dma-buf では、Linux Kernel が提供する DMA Mapping API の mmap を使わない独自の mmap を用意することで、Cache Coherence Hardware を持っていないが Cache Aliasing 問題が起きないコンピューターシステムでも、 DMA Buffer を mmap する際にCPU Cache が有効に出来るようにしています。
補足
QUIRK_MMAP という名前は u-dma-buf のバージョン 4.4.0 にて付けられました。もともと u-dma-buf の初期のバージョンからすでに同様の機能は実装していましたが、特に名前はつけていませんでした。4.4.0 にてあらためて「変則的な」 mmap であることを強調するために、QUIRK_MMAP と命名しました。
Cache Sync
u-dma-buf には Cache Coherence 問題をソフトウェアで解決するための機構が用意されています。Cache Coherence 問題とは何か、この問題をソフトウェアで解決するとはどういうことかは、次の Qiita の記事を参照してください。
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Coherence 問題とは何か)』 @Qiita
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Coherence 問題の解決方法)』 @Qiita
u-dma-buf のソースコードの説明
udmabuf_sync_for_cpu()
sync_for_cpu は、これ以降は、DMA Buffer へのアクセスは CPU に限定されて、DEVICE からはアクセスされないことを示します。u-dma-buf では sync_for_cpu は次のようになっています。
/**
* udmabuf_sync_for_cpu() - call dma_sync_single_for_cpu() when (sync_for_cpu != 0)
* @this: Pointer to the udmabuf object.
* Return: Success(=0) or error status(<0).
*/
static int udmabuf_sync_for_cpu(struct udmabuf_object* this)
{
int status = 0;
if (this->sync_for_cpu) {
dma_addr_t phys_addr;
size_t size;
enum dma_data_direction direction;
status = udmabuf_sync_command_argments(this, this->sync_for_cpu, &phys_addr, &size, &direction);
if (status == 0) {
dma_sync_single_for_cpu(this->dma_dev, phys_addr, size, direction);
this->sync_for_cpu = 0;
this->sync_owner = 0;
}
}
return status;
}
u-dma-buf では、単純にLinux Kernel が提供している DMA Mapping API の dma_sync_single_for_cpu() を呼び出しています。 ここでは確保した DMA Buffer の物理アドレスを引数として渡していることに注意してください。
udmabuf_sync_for_device()
sync_for_device は、これ以降は、DMA Buffer へのアクセスは DEVICE に限定されて、CPU からはアクセスされないことを示します。u-dma-buf では sync_for_device は次のようになっています。
/**
* udmabuf_sync_for_device() - call dma_sync_single_for_device() when (sync_for_device != 0)
* @this: Pointer to the udmabuf object.
* Return: Success(=0) or error status(<0).
*/
static int udmabuf_sync_for_device(struct udmabuf_object* this)
{
int status = 0;
if (this->sync_for_device) {
dma_addr_t phys_addr;
size_t size;
enum dma_data_direction direction;
status = udmabuf_sync_command_argments(this, this->sync_for_device, &phys_addr, &size, &direction);
if (status == 0) {
dma_sync_single_for_device(this->dma_dev, phys_addr, size, direction);
this->sync_for_device = 0;
this->sync_owner = 1;
}
}
return status;
}
u-dma-buf では、単純にLinux Kernel が提供している DMA Mapping API の dma_sync_single_for_device() を呼び出しています。 ここでは確保した DMA Buffer の物理アドレスを引数として渡していることに注意してください。
u-dma-buf は Cache Alasing 問題は解決しない
Cache Aliasing 問題とは、仮想記憶をサポートしたコンピューターシステムにおいて、同じ物理アドレスに複数の異なる仮想アドレスを割り当てた際に発生するキャッシュの問題のことです。詳細は、以下の記事を参照してください。
u-dma-buf は、Cache Coherence Hardware を持っていないが Cache Aliasing 問題が起きないコンピューターシステムでも、 DMA Buffer を mmap する際にCPU Cache が有効になるようにしています。しかし、Cache Aliasing 問題が起こるコンピューターシステムでは、キャッシュの制御が上手くいきません。この節ではその理由を説明します。
sync_for_cpu/sync_for_device で指定するアドレスは物理アドレス
Linux Kernel が提供する DMA Mapping API の dma_sync_single_for_cpu() および dma_sync_single_for_device() で指定する DMA Buffer のアドレスは物理アドレスです。
void dma_sync_single_for_device(struct device *dev, dma_addr_t addr,
size_t size, enum dma_data_direction dir)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
BUG_ON(!valid_dma_direction(dir));
if (dma_map_direct(dev, ops))
dma_direct_sync_single_for_device(dev, addr, size, dir);
else if (ops->sync_single_for_device)
ops->sync_single_for_device(dev, addr, size, dir);
debug_dma_sync_single_for_device(dev, addr, size, dir);
}
EXPORT_SYMBOL(dma_sync_single_for_device);
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg,
int nelems, enum dma_data_direction dir)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
BUG_ON(!valid_dma_direction(dir));
if (dma_map_direct(dev, ops))
dma_direct_sync_sg_for_cpu(dev, sg, nelems, dir);
else if (ops->sync_sg_for_cpu)
ops->sync_sg_for_cpu(dev, sg, nelems, dir);
debug_dma_sync_sg_for_cpu(dev, sg, nelems, dir);
}
EXPORT_SYMBOL(dma_sync_sg_for_cpu);
dma-direct の場合は各々次に示す関数を呼び出します。
static inline void dma_direct_sync_single_for_device(struct device *dev,
dma_addr_t addr, size_t size, enum dma_data_direction dir)
{
phys_addr_t paddr = dma_to_phys(dev, addr);
if (unlikely(is_swiotlb_buffer(dev, paddr)))
swiotlb_sync_single_for_device(dev, paddr, size, dir);
if (!dev_is_dma_coherent(dev))
arch_sync_dma_for_device(paddr, size, dir);
}
static inline void dma_direct_sync_single_for_cpu(struct device *dev,
dma_addr_t addr, size_t size, enum dma_data_direction dir)
{
phys_addr_t paddr = dma_to_phys(dev, addr);
if (!dev_is_dma_coherent(dev)) {
arch_sync_dma_for_cpu(paddr, size, dir);
arch_sync_dma_for_cpu_all();
}
if (unlikely(is_swiotlb_buffer(dev, paddr)))
swiotlb_sync_single_for_cpu(dev, paddr, size, dir);
if (dir == DMA_FROM_DEVICE)
arch_dma_mark_clean(paddr, size);
}
『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Linux Kernel の Cache 問題の扱い)』 @Qiita で説明したように、dev_is_dma_coherent() が false の場合は、各々アーキテクチャ毎に定義されている arch_sync_dma_for_cpu() や arch_sync_dma_for_device() が呼ばれます。
arm64 のキャッシュ制御命令で指定するアドレスは仮想アドレス
arm64 の場合、arch_sync_dma_for_device() や arch_sync_dma_for_cpu() は次のようになっています。
void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,
enum dma_data_direction dir)
{
unsigned long start = (unsigned long)phys_to_virt(paddr);
dcache_clean_poc(start, start + size);
}
void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
enum dma_data_direction dir)
{
unsigned long start = (unsigned long)phys_to_virt(paddr);
if (dir == DMA_TO_DEVICE)
return;
dcache_inval_poc(start, start + size);
}
ここでの注目点は、物理アドレスを仮想アドレスに変換していることです。というのも arm64 は命令セットにキャッシュを制御する命令を持っています。しかしその命令に必要なのは物理アドレスではなく、CPU から見た仮想アドレスです。したがって、ここで物理アドレスを仮想アドレスに変換する必要があるのです。
では、どうやって物理アドレスを仮想アドレスに変換しているのでしょうか。arm64 の場合は次のようにしています。
#define phys_to_virt phys_to_virt
static inline void *phys_to_virt(phys_addr_t x)
{
return (void *)(__phys_to_virt(x));
}
#define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET) | PAGE_OFFSET)
extern s64 memstart_addr;
/* PHYS_OFFSET - the physical address of the start of memory. */
#define PHYS_OFFSET ({ VM_BUG_ON(memstart_addr & 1); memstart_addr; })
/*
* PAGE_OFFSET - the virtual address of the start of the linear map, at the
* start of the TTBR1 address space.
* PAGE_END - the end of the linear map, where all other kernel mappings begin.
* KIMAGE_VADDR - the virtual address of the start of the kernel image.
* VA_BITS - the maximum number of bits for virtual addresses.
*/
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define _PAGE_OFFSET(va) (-(UL(1) << (va)))
#define PAGE_OFFSET (_PAGE_OFFSET(VA_BITS))
arm64 で Linux を動作させる時は、次のように、Linux Kernel をロードした物理メモリ領域をそのまま Kernel 空間の仮想アドレスに割り当てているようです。
Fig.1 Mapping between Physical Memory and Virtual Address of linux Kernel (arm64)
arm64 のキャッシュ制御命令で指定する DMA Buffer のアドレスは Linux Kernel に割りてられた仮想アドレス
ということは、arm64 アーキテクチャの場合、 DMA Buffer に対して Cache の Coherence 問題をソフトウェアで解決する場合、Kernel 空間にマッピングした仮想アドレスに対して、キャッシュ操作を行うことになります。
Cache Aliasing 問題との関連
ここで u-dma-buf によって DMA Buffer をユーザー空間に mmap したとします。
コンピューターシステムが Cache Aliasing 問題が起きないアーキテクチャの場合は、次図に示すように、カーネル空間に割り当てられた仮想アドレスからのアクセスもユーザー空間に mmap された仮想アドレスからのアクセスも、どちらも同じキャッシュラインを指します。
したがって、カーネル空間に割り当てられた仮想アドレスを使ってキャッシュ操作を行うのと、ユーザー空間に mmap された仮想アドレスに対してキャッシュ操作を行うのは同じことです。
Fig.2 Architecture in which Cache Aliasing problems do not occur
一方、コンピューターシステムが Cache Aliasing 問題が起きるアーキテクチャの場合は、次図に示すように、カーネル空間に割り当てられた仮想アドレスからのアクセスとユーザー空間に mmap された仮想アドレスからのアクセスとでは異なるキャッシュラインを指す可能性があります。
したがって、カーネル空間に割り当てられた仮想アドレスを使ってキャッシュ操作を行っても、ユーザー空間に mmap された仮想アドレスに対してキャッシュ操作を行うことにはなりません。
Fig.3 Architecture in which Cache Aliasing problems do occur
これが、u-dma-buf が Cache Alasing 問題が発生するようなコンピューターシステムでは正常に動作しない理由です。
まとめ
この記事では u-dma-buf の「Cache Coherence Hardware を持っていないが Cache Aliasing 問題が起きないコンピューターシステムでも、 DMA Buffer を mmap する際にCPU Cache が有効になる」仕組みである QUIRK_MMAP を説明しました。
また「Cache Aliasing 問題が起きるコンピューターシステム」では正常にキャッシュ操作が行われないことも説明しました。
u-dma-buf でユーザー空間に DMA Buffer を mmap する場合や、キャッシュ操作を行う場合は、これらの点に注意してください。
参考
u-dma-buf のリポジトリ
u-dma-buf に関する Qiita の記事
- 『Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ』 @Qiita
https://qiita.com/ikwzm/items/cc1bb33ff43a491440ea
Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合に関するQiita の記事
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (はじめに)』 @Qiita
https://qiita.com/ikwzm/items/aa7e2373705872ccf764 - 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Coherence 問題とは何か)』 @Qiita
https://qiita.com/ikwzm/items/f60281491cc83f29c019 - 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Coherence 問題の解決方法)』 @Qiita
https://qiita.com/ikwzm/items/6d3225e4cbdbbabcabd5 - 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Aliasing 問題とは何か)』@Qiita
https://qiita.com/ikwzm/items/1b8dc72e04d0fa031a0f - 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Linux Kernel の Cache 問題の扱い)』 @Qiita
https://qiita.com/ikwzm/items/f32985890b2013ef5528 - 『Linux では Cache Coherence Hardware を持っていないとDMA Buffer をmmap する際に CPU Cache が無効になる』 @Qiita
https://qiita.com/ikwzm/items/0f77072158ce842018fc
Linux Kernel の DMA Mapping API に関する資料
- 「Dynamic DMA mapping using the generic device」
https://docs.kernel.org/core-api/dma-api.html - 「Linux Kernel 6.1.97 source」
https://elixir.bootlin.com/linux/v6.1.97/source