6
votes

I have a kernel driver which allocates several buffers in kernel space (physically contiguous, aligned to page boundaries, and consisting of integral number of pages). Next, I need to make my driver able to mmap some of these buffers to userspace (one buffer per mmap() call, of course). The driver registers single character device for that purpose. Userspace program must be able to tell kernel which buffer it wants to mmap (for example, by specifying its index or unique ID, or physical address previously resolved through ioctl()).

I want to do so by using mmap()'s offset parameter, for example (from userspace):

mapped_ptr = mmap(NULL, buf_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (MAGIC + buffer_id) * PAGE_SIZE);

Where "MAGIC" is some magic number, and buffer_id is the buffer ID which I want to mmap. Next, in the kernel part there will be something like this:

static int my_dev_mmap(struct file *filp, struct vm_area_struct *vma)
{
  int bufferID = vma->vm_pgoff - MAGIC;
  /* 
   * Convert bufferID to PFN by looking through driver's buffer descriptors
   * Check length = vma->vm_end - vma->vm_start
   * Call remap_pfn_range()
   */
}

But I think it is some sort of dirty way, because "offset" in the mmap() is not supposed to specify index or identifier, its role is to provide number of skipped bytes (or pages) from the beginning of mmap-ed device(or file) memory (which is supposed to be contiguous, right?).

However, i've already seen some drivers in mainline which use "offset" to distinguish between mmap-ed buffers.

Are there any alternative solutions to this?

P.S. I need all this just because I'm dealing with some unusual SoC' graphics controller, which can operate only on physically contiguous, aligned to 8-byte boundary memory buffers. So, I can only allocate such buffers in kernel space and pass them to user space via mmap().

The most part of controller' programming (composing instruction batches and pushing them to kernel driver) is performed in user space. Also, I can't just allocate single big chunk of physically contiguous memory, because in that case it needs to be really big (for ex., 16+ MiB) and alloc_pages_exact() will fail.

2

2 Answers

1
votes

I don't see anything wrong with using the offset to pass the index in from userspace to your driver. If it bugs you, then just look at your driver as assembling a large buffer out of individual pages that it wants to present to userspace as virtually contiguous, so that the offset really is an offset into this buffer. But really in my opinion there's nothing wrong with doing things this way.

Another alternative, if you can use kernel 3.5 or newer, might be to use the "Contiguous Memory Allocator" (CMA) -- look at <linux/dma-contiguous.h> and drivers/base/dma-contiguous.c for more information. There's also https://lwn.net/Articles/486301/ as a reference but I don't know how much (if anything) changed between that article and getting the code merged into mainline.

1
votes

Finally, I've chosen to mmap exactly one buffer per one opened device file descriptor (struct file in kernel) and implement control through ioctl(): one IOCTL for allocating new buffer, one for attaching to already allocated buffer with known ID, and another one to get information about buffer. Usually, userspace will mmap() about 10..20 buffers at the same time, so it is nice and clean solution for this case.