V4L2 ストリーミングI/O(V4L2_MEMORY_MMAP) で性能が出ない問題 (2024年改訂版)







# はじめに


V4L2 のストリーミングI/O(V4L2_MEMORY_MMAP) はV4L2 ストリーミング I/O の方式の一つで、V4L2 ドライバ内(カーネル内)で確保した V4L2 バッファを mmap 機構を使ってユーザー空間にマッピングすることで、ユーザープログラムが V4L2 バッファにアクセスできるようにします。V4L2バッファをユーザー空間から直接アクセス出来るため、この方式は比較的よく使われます。



しかし、ある種の V4L2 ドライバでは、mmap でユーザー空間にマッピングする際にキャッシュがオフになってしまってメモリアクセスが非常に遅くなり性能が出ない問題がありました。

この問題を起こす V4L2 ドライバの一つとして、Xilinx の Video DMA があります。



この記事では、そのメカニズムを説明します。



なお、この記事は、以前投稿した次記事の改訂版です。

* [『V4L2 ストリーミングI/O(V4L2_MEMORY_MMAP) で性能が出ない問題)』 @Qiita](https://qiita.com/ikwzm/items/8f1c98efe6fd4ae2490a)






# キャッシュがオフになるメカニズム



## V4L2 バッファのメモリアロケータ−


V4L2 バッファのメモリアロケータ−には次の３種類があります。

  * vmalloc			: DMA を伴わない V4L2 ドライバ用
  * dma-sg			: Scatter Gather に対応した DMA デバイス用
  * dma-contig	: Scatter Gather に対応していない DMA デバイス用

このうち、問題となるのは最後の dma-contig です。


### vmalloc


vmalloc はDMAを伴わない V4L2 ドライバのためのメモリアロケータ−です。例えば、USB Camera のV4L2 ドライバがこれにあたります。USB の場合は USB のデバイスドライバが USB デバイスとのデータ転送を行い、V4L2 ドライバ自体は直接 USB デバイスとのデータ転送を行いません。そのため、カーネルが普通に使っている vmalloc を使ってメモリを確保します。


### dma-sg


dma-sg は Scatter Gather に対応した DMA を持つデバイスのためのメモリアロケータ−です。Scatter Gather に対応しているため、バッファが物理メモリ空間上に連続していなくても DMA 転送が可能です。 dma-sg は Linux Kernel の DMA Mapping API を使ってメモリを確保します。しかし、mmap は DMA Mapping API を使っていません。


### dma-contig


dma-contig は Scatter Gather に対応していない DMA を持つデバイスのためのメモリアロケータ−です。Scatter Gather に対応していないため、バッファは物理メモリ空間上に連続していなければなりません。dma-contig は Linux  Kernel の DMA Mapping API を使ってメモリを確保します。そして mmap も DMA Mapping API を使います。実は Linux Kernel の DMA Mapping API が提供する mmap は、Cache Coherence Hardware を持っていないと CPU Cache が無効になります。

したがって、この dma-contig を採用した V4L2 ドライバでV4L2_MEMORY_MMAP を使うと、Cache Coherence Hardware を持っていない場合は CPU Cache が無効になります。


## dma-contig の mmap


dma-contig の mmap は次のようになっています。


```C:https://elixir.bootlin.com/linux/v6.1.97/source/drivers/media/common/videobuf2/videobuf2-dma-contig.c
static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
	struct vb2_dc_buf *buf = buf_priv;
	int ret;
	if (!buf) {
		printk(KERN_ERR "No buffer to map\n");
		return -EINVAL;
	}
	if (buf->non_coherent_mem)
		ret = dma_mmap_noncontiguous(buf->dev, vma, buf->size,
					     buf->dma_sgt);
	else
		ret = dma_mmap_attrs(buf->dev, vma, buf->cookie, buf->dma_addr,
				     buf->size, buf->attrs);
	if (ret) {
		pr_err("Remapping memory failed, error: %d\n", ret);
		return ret;
	}
	vma->vm_flags		|= VM_DONTEXPAND | VM_DONTDUMP;
	vma->vm_private_data	= &buf->handler;
	vma->vm_ops		= &vb2_common_vm_ops;
	vma->vm_ops->open(vma);
	pr_debug("%s: mapped dma addr 0x%08lx at 0x%08lx, size %lu\n",
		 __func__, (unsigned long)buf->dma_addr, vma->vm_start,
		 buf->size);
	return 0;
}
```


実際に mmap を行うのはLinux Kernel  の DMA Mapping API で提供される dma_mmap_attrs() です。なお、non_coherent_mem の場合は dma_mmap_noncontiguous() が呼ばれていますが、これは別の DMA Buffer をImport した時に使われるものなので、ここでは説明しません。



Linux Kernel 5.0 以降の DMA Mapping API は dma-direct という実装が使われています。この dma-direct の説明と、CPU Cache が無効になるメカニズムについては、次の記事を参照してください。

  * [『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)



結論だけを述べると、dma-contig の mmap は、Cache Coherence Hardware を持っていないと CPU Cache が無効になります。


# 対処方法


~~こちらに udmabuf を使って対処する方法を投稿しました。~~(2024年改定 この記事の内容は古くて現在は使えません。

* [「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(V4L2_MEMORY_USERPTR編)」@Qiita](https://qiita.com/ikwzm/items/c81e8b749576300fceee)



別の方法として、dma-heap を使った解決方法をこちらに投稿しました。

* [「V4L2 ストリーミングI/Oで性能が出ない問題を dma-heap で解決する」@Qiita](https://qiita.com/ikwzm/items/00359768a7c982a4ddc0)


# 参考



## 「V4l2 V4L2_MEMORY_USERPTR:contiguous mapping is too small 4096/1228800」


Xilinx のフォーラムに次のスレッドがありました。



* 「V4l2 V4L2_MEMORY_USERPTR:contiguous mapping is too small 4096/1228800」   
https://forums.xilinx.com/t5/Embedded-Linux/V4l2-V4L2-MEMORY-USERPTR-contiguous-mapping-is-too-small-4096/td-p/825067



Xilinx の VDMA を使ってキャプチャーしようとしたら mmap によるデータ転送が遅いので、mmap の代わりにユーザー空間にバッファを確保して、それをV4L2 ドライバに渡したら、今度は連続した空間が小さすぎてエラーになったという話です。

これは考えてみれば当たり前な話で、Xilinx の VDMA は Scatter Gather に対応していないのでバッファは連続したメモリ空間になければなりません。




## 「Buffer from UDMABUF and V4L2_MEMORY_USERPTR」


もともと私がこの問題に興味を持ったのは、udmabuf に次の issue があげられたからです。



* 「Buffer from UDMABUF and V4L2_MEMORY_USERPTR」    
https://github.com/ikwzm/udmabuf/issues/38



 mmap によるデータ転送が遅いので、mmap の代わりに udmabuf で連続した物理メモリを確保してユーザー空間にマッピングして、それを V4L2 ドライバに渡せばデータ転送が速くしようという試みでした。

最初は上手くいかなくて udmabuf に issue があげられたのですが、最終的にはこの試みは成功したようです。



残念ながら、この方法は Linux Kernel 5.10 以降は使えません。詳細は次の記事の「注意(2024年4月3日追記)」参照してください。

* [『Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(V4L2_MEMORY_USERPTR編)』　@Qiita](https://qiita.com/ikwzm/items/c81e8b749576300fceee)




## 「Ultra96 で Julia set をぐりぐり動かせるやつを作った」


これは、ある方のブログの記事です。



* 「Ultra96 で Julia set をぐりぐり動かせるやつを作った」   
https://blog.myon.info/entry/2019/05/15/ultra96-julia-set-explorer/



この記事では Vivado-HLS で記述したJulia set を  Ultra96 の FPGA に実装してディスプレイに表示しています。その際、FPGA との通信に Xilinx の VDMA を使っていて、最初は V4L2 の mmap を使っていたけど全然性能が出なくて、dma-buf というバッファを共有する方法にしたという話です。




## 「imx6ull v4l2 slow memcpy for captured memory」


NXP社のフォーラムでも同じような投稿がありました。



* 「imx6ull v4l2 slow memcpy for captured memory」   
https://community.nxp.com/thread/483135