Device driver for programs and hardware running in user space on Linux to share memory (V4L2_MEMORY_USERPTR)

Introduction

The author publishes udmabuf as open source.

This article describes how to use the buffer allocated by udmabuf as a V4L2 buffer.

Background

In the next article, I showed the problem that the cache is not enabled when mmaping the V4L2 buffer with a V4L2 driver using Xilinx VDMA etc.

As a countermeasure, instead of V4L2_MEMORY_MMAP, you can allocate a buffer on the user application side and pass it to the V4L2 driver with V4L2_MEMORY_USERPTR. However, the Xilinx forums have shown that this method does not work.

This is obvious when you think about it, as Xilinx VDMA does not support Scatter Gather, so the V4L2 buffer must be contiguous in physical memory. If a buffer is allocated in user space, there is no guarantee that it will be contiguous in physical memory. I got stuck in this check.

So one tried using udmabuf to allocate a contiguous buffer in physical memory, mapping that buffer to userspace with mmap and passing it to the V4L2 driver with V4L2_MEMORY_USERPTR. Unfortunately, it didn't work at first and I gave an issue to udmabuf.

In the end, this attempt seems to have been successful. It took 24msec for V4L2_MEMORY_MMAP to transfer the image data of 1920x1080p NV12, but it took 1.6msec for udmabuf + V4L2_MEMORY_USERPTR.

Example of use

Here, we will explain how to use the buffer allocated by udmabuf as a V4L2 buffer based on an example.

Preparing udmabuf

udmabuf is a Linux kernel module. For building and installing udmabuf, refer to the materials such as Readm.md of udmabuf.

udmabuf open

Open udmabuf.

udmabuf.hpp


	name  = device_name;
	sprintf(file_name, "/dev/%s", name.c_str());
	if ((_device_file = open(file_name, O_RDWR)) < 0) {
 		printf("Can not open %s\\n", file_name);
		_status = false;
		goto done;
	}

udmabuf mmap

Map udmabuf to userspace with mmap.

udmabuf.hpp


	buf = mmap(NULL, buf_size, PROT_READ|PROT_WRITE, MAP_SHARED, _device_file, 0);
	if (buf == MAP_FAILED) {
		printf("Can not mmap %s\\n", file_name);
		_status = false;
		goto done;
	}

Clear udmabuf

Actually, this process is very important and indispensable. That's because udmabuf maps physical memory to virtual addresses through on-demand paging. To elaborate a bit more, udmabuf does not immediately map physical memory to virtual addresses with mmap (). When you make some access to a virtual address, a page fault will occur if the physical memory is not mapped. udmabuf maps physical memory only when a page fault occurs.

In the case of udmabuf, the virtual address returned by mmap () has not yet been mapped to a physical address, so if you pass this virtual address to a V4L2 device, as shown in [Buffer from UDMABUF and V4L2_MEMORY_USERPTR # 38] Error occurs.

To prevent this, it's a good idea to clear the buffer immediately after mmap (). By writing to the buffer, it causes a page fault and maps physical memory to the virtual address.

udmabuf.hpp


	// Write once to allocate physical memory to u-dma-buf virtual space.
	// Note: Do not use memset() for this.
	//       Because it does not work as expected.
	{
		uint64_t*	word_ptr = reinterpret_cast<uint64_t*>(buf);
		size_t    	words    = buf_size/sizeof(uint64_t);
		for(int i = 0 ; i < words; i++) {
			word_ptr[i] = 0;
		}
	}

Do not use memset () to clear the buffer. memset () is doing very complicated things and I can't get the results I want.

In the future, we are considering doing this inside udmabuf. Until then, explicitly clear the buffer as in this use case.

Request V4L2 buffer from V4L2 driver

Request a V4L2 buffer for the V4L2 driver. In doing so, specify V4L2_MEMORY_USERPTR for req.memory to indicate that the V4L2 buffer should be allocated by the user program rather than allocated by the V4L2 driver.

example.cpp


	v4l2_requestbuffers req ;
	memset(&req, 0, sizeof(req));
	req.count = num_buf;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_USERPTR;
	ret = xioctl(fd, VIDIOC_REQBUFS, &req);
	if (ret < 0) {
		perror("ioctl(VIDIOC_REQBUFS)");
		return -1;
	}

Enqueue the V4L2 buffer to the V4L2 driver

Enqueue the V4L2 buffer into the V4L2 driver's queue prior to starting streaming. The virtual address that maps the physical memory of the buffer allocated by udmabuf is set in buf.m.userptr.

example.cpp


	for (i = 0; i < num_buf; i++) {
		v4l2_buffer buf;
		memset(&buf, 0, sizeof(buf));
		buf.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_USERPTR;
		buf.index = i;
		buf.length = buf_size;
		buf.m.userptr = reinterpret_cast<unsigned long>(udmabuf.buf + i*buf_size);
		ret = xioctl(fd, VIDIOC_QBUF, &buf);
		if (ret < 0) {
			perror("ioctl(VIDIOC_QBUF)");
			return -1;
		}
	}

reference

["Device driver for programs running in user space on Linux and hardware sharing memory" @Qiita]: https://qiita.com/ikwzm/items/cc1bb33ff43a491440ea "" Programs running in user space on Linux Device driver for hardware to share memory with @Qiita " ["Problem with no performance with V4L2 streaming I / O (V4L2_MEMORY_MMAP)" @Qiita]: https://qiita.com/ikwzm/items/8f1c98efe6fd4ae2490a "No performance with V4L2 streaming I / O (V4L2_MEMORY_MMAP)" 』@Qiita" [『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 "『V4l2 V4L2_MEMORY_USERPTR:contiguous mapping is too small 4096/1228800』@XilinxForum" [『Buffer from UDMABUF and V4L2_MEMORY_USERPTR #38』]: https://github.com/ikwzm/udmabuf/issues/38 "『Buffer from UDMABUF and V4L2_MEMORY_USERPTR #38』@gitub.com"

Recommended Posts

Device driver for programs and hardware running in user space on Linux to share memory (V4L2_MEMORY_USERPTR)
Device driver (NumPy compatible) for programs and hardware that run in user space on Linux to share memory
Add words to MeCab's user dictionary on Ubuntu for use in Python