Re: [PATCH v2 1/3] iio: core: Add mmap interface infrastructure
From: Jonathan Cameron
Date: Sun Feb 14 2021 - 10:25:48 EST
On Fri, 12 Feb 2021 12:11:41 +0200
Alexandru Ardelean <alexandru.ardelean@xxxxxxxxxx> wrote:
> From: Lars-Peter Clausen <lars@xxxxxxxxxx>
>
> Add the necessary infrastructure to the IIO core to support an mmap based
> interface to access the capture data.
>
> The advantage of the mmap based interface compared to the read() based
> interface is that it avoids an extra copy of the data between kernel and
> userspace. This is particular useful for high-speed devices which produce
> several megabytes or even gigabytes of data per second.
>
> The data for the mmap interface is managed at the granularity of so called
> blocks. A block is a contiguous region of memory (at the moment both
> physically and virtually contiguous). Reducing the granularity from byte
> level to block level is done to reduce the userspace-kernelspace
> synchronization overhead since performing syscalls for each byte at a
> data-rate of a few megabytes is not feasible.
>
> This of course leads to a slightly increased latency. For this reason an
> application can choose the size of the blocks as well as how many blocks it
> allocates. E.g. two blocks would be a traditional double buffering scheme.
> But using a higher number might be necessary to avoid underflow/overflow
> situations in the presence of scheduling latencies.
>
> A block can either be owned by kernel space or userspace. When owned by
> userspace it save to access the data in the block and process it. When
> owned by kernel space the block can be in one of 3 states.
>
> It can be in the incoming queue where all blocks submitted from userspace
> are placed and are waiting to be processed by the kernel driver.
>
> It can be currently being processed by the kernel driver, this means it is
> actively placing capturing data in it (usually using DMA).
>
> Or it can be in the outgoing queue where all blocks that have been
> processed by the kernel are placed. Userspace can dequeue the blocks as
> necessary.
>
> As part of the interface 5 new IOCTLs to manage the blocks and exchange
> them between userspace and kernelspace. The IOCTLs can be accessed through
> a open file descriptor to a IIO device.
>
> IIO_BUFFER_BLOCK_ALLOC_IOCTL(struct iio_buffer_block_alloc_req *):
> Allocates new blocks. Can be called multiple times if necessary. A newly
> allocated block is initially owned by userspace.
>
> IIO_BUFFER_BLOCK_FREE_IOCTL(void):
> Frees all previously allocated blocks. If the backing memory of a block is
> still in use by a kernel driver (i.e. active DMA transfer) it will be
> freed once the kernel driver has released it.
>
> IIO_BUFFER_BLOCK_QUERY_IOCTL(struct iio_buffer_block *):
> Queries information about a block. The id of the block about which
> information is to be queried needs to be set by userspace.
>
> IIO_BUFFER_BLOCK_ENQUEUE_IOCTL(struct iio_buffer_block *):
> Places a block on the incoming queue. This transfers ownership of the
> block from userspace to kernelspace. Userspace must populate the id field
> of the block to indicate which block to enqueue.
>
> IIO_BUFFER_BLOCK_DEQUEUE_IOCTL(struct iio_buffer_block *):
> Removes the first block from the outgoing queue. This transfers ownership
> of the block from kernelspace to userspace. Kernelspace will populate all
> fields of the block. If the queue is empty and the file descriptor is set
> to blocking the IOCTL will block until a new block is available on the
> outgoing queue.
>
> To access the data stored in a block by userspace the block must be mapped
> to the process's memory. This is done by calling mmap() on the IIO device
> file descriptor. Each block has a unique offset assigned to it which should
> be passed to the mmap interface. E.g.
>
> mmap(0, block.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
> block.offset);
>
> A typical workflow for the new interface is:
>
> BLOCK_ALLOC
>
> foreach block
> BLOCK_QUERY block
> mmap block.data.offset
> BLOCK_ENQUEUE block
>
> enable buffer
>
> while !done
> BLOCK_DEQUEUE block
> process data
> BLOCK_ENQUEUE block
>
> disable buffer
>
> BLOCK_FREE
Rather feels like some of this info should be in the formal docs somewhere rather
than just burried in this patch description.
Also good to have a bit more in the way of docs in the uapi header.
>
> Signed-off-by: Lars-Peter Clausen <lars@xxxxxxxxxx>
> Signed-off-by: Alexandru Ardelean <alexandru.ardelean@xxxxxxxxxx>
Few trivial things inline, but otherwise seems like straightforwards
wrappers.
Jonathan
> ---
> drivers/iio/industrialio-buffer.c | 157 ++++++++++++++++++++++++++++++
> include/linux/iio/buffer-dma.h | 5 -
> include/linux/iio/buffer_impl.h | 23 +++++
> include/uapi/linux/iio/buffer.h | 26 +++++
> 4 files changed, 206 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
> index 3aa6702a5811..50228df0b09f 100644
> --- a/drivers/iio/industrialio-buffer.c
> +++ b/drivers/iio/industrialio-buffer.c
> @@ -16,6 +16,7 @@
> #include <linux/fs.h>
> #include <linux/cdev.h>
> #include <linux/slab.h>
> +#include <linux/mm.h>
> #include <linux/poll.h>
> #include <linux/sched/signal.h>
>
> @@ -1370,6 +1371,12 @@ static void iio_buffer_unregister_legacy_sysfs_groups(struct iio_dev *indio_dev)
> kfree(iio_dev_opaque->legacy_scan_el_group.attrs);
> }
>
> +static void iio_buffer_free_blocks(struct iio_buffer *buffer)
> +{
> + if (buffer->access->free_blocks)
> + buffer->access->free_blocks(buffer);
> +}
> +
> static int iio_buffer_chrdev_release(struct inode *inode, struct file *filep)
> {
> struct iio_dev_buffer_pair *ib = filep->private_data;
> @@ -1378,18 +1385,24 @@ static int iio_buffer_chrdev_release(struct inode *inode, struct file *filep)
>
> wake_up(&buffer->pollq);
> clear_bit(IIO_BUSY_BIT_POS, &buffer->flags);
> + iio_buffer_free_blocks(buffer);
> iio_device_put(indio_dev);
> kfree(ib);
>
> return 0;
> }
>
> +static long iio_buffer_ioctl(struct file *filep, unsigned int cmd, unsigned long arg);
> +static int iio_buffer_mmap(struct file *filep, struct vm_area_struct *vma);
> +
> static const struct file_operations iio_buffer_chrdev_fileops = {
> .owner = THIS_MODULE,
> .llseek = noop_llseek,
> .read = iio_buffer_read,
> .poll = iio_buffer_poll,
> + .unlocked_ioctl = iio_buffer_ioctl,
> .compat_ioctl = compat_ptr_ioctl,
Why do we have an existing compat_ioctl here?
Seems odd without the unlocked_ioctl. I see introduced in the multi buffer
series whereas I think it should in this one.
It has a test against being there without the unlocked_ioctl though so
no great disaster.
> + .mmap = iio_buffer_mmap,
> .release = iio_buffer_chrdev_release,
> };
>
> @@ -1762,6 +1775,150 @@ void iio_buffer_put(struct iio_buffer *buffer)
> }
> EXPORT_SYMBOL_GPL(iio_buffer_put);
>
> +static int iio_buffer_query_block(struct iio_buffer *buffer,
> + struct iio_buffer_block __user *user_block)
> +{
> + struct iio_buffer_block block;
> + int ret;
> +
> + if (!buffer->access->query_block)
> + return -ENOSYS;
> +
> + if (copy_from_user(&block, user_block, sizeof(block)))
> + return -EFAULT;
> +
> + ret = buffer->access->query_block(buffer, &block);
> + if (ret)
> + return ret;
> +
> + if (copy_to_user(user_block, &block, sizeof(block)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int iio_buffer_dequeue_block(struct iio_dev *indio_dev,
> + struct iio_buffer *buffer,
> + struct iio_buffer_block __user *user_block,
> + bool non_blocking)
> +{
> + struct iio_buffer_block block;
> + int ret;
> +
> + if (!buffer->access->dequeue_block)
> + return -ENOSYS;
> +
> + do {
> + if (!iio_buffer_data_available(buffer)) {
> + if (non_blocking)
> + return -EAGAIN;
> +
> + ret = wait_event_interruptible(buffer->pollq,
> + iio_buffer_data_available(buffer) ||
> + indio_dev->info == NULL);
> + if (ret)
> + return ret;
> + if (indio_dev->info == NULL)
> + return -ENODEV;
> + }
> +
> + ret = buffer->access->dequeue_block(buffer, &block);
> + if (ret == -EAGAIN && non_blocking)
> + ret = 0;
> + } while (ret);
> +
> + if (ret)
> + return ret;
> +
> + if (copy_to_user(user_block, &block, sizeof(block)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int iio_buffer_enqueue_block(struct iio_buffer *buffer,
> + struct iio_buffer_block __user *user_block)
> +{
> + struct iio_buffer_block block;
> +
> + if (!buffer->access->enqueue_block)
> + return -ENOSYS;
> +
> + if (copy_from_user(&block, user_block, sizeof(block)))
> + return -EFAULT;
> +
> + return buffer->access->enqueue_block(buffer, &block);
> +}
> +
> +static int iio_buffer_alloc_blocks(struct iio_buffer *buffer,
> + struct iio_buffer_block_alloc_req __user *user_req)
> +{
> + struct iio_buffer_block_alloc_req req;
> + int ret;
> +
> + if (!buffer->access->alloc_blocks)
> + return -ENOSYS;
> +
> + if (copy_from_user(&req, user_req, sizeof(req)))
> + return -EFAULT;
> +
> + ret = buffer->access->alloc_blocks(buffer, &req);
> + if (ret)
> + return ret;
> +
> + if (copy_to_user(user_req, &req, sizeof(req)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static long iio_buffer_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
> +{
> + bool non_blocking = filep->f_flags & O_NONBLOCK;
> + struct iio_dev_buffer_pair *ib = filep->private_data;
> + struct iio_dev *indio_dev = ib->indio_dev;
> + struct iio_buffer *buffer = ib->buffer;
> +
> + if (!buffer || !buffer->access)
> + return -ENODEV;
> +
> + switch (cmd) {
> + case IIO_BUFFER_BLOCK_ALLOC_IOCTL:
> + return iio_buffer_alloc_blocks(buffer,
> + (struct iio_buffer_block_alloc_req __user *)arg);
> + case IIO_BUFFER_BLOCK_FREE_IOCTL:
> + iio_buffer_free_blocks(buffer);
> + return 0;
> + case IIO_BUFFER_BLOCK_QUERY_IOCTL:
> + return iio_buffer_query_block(buffer,
> + (struct iio_buffer_block __user *)arg);
> + case IIO_BUFFER_BLOCK_ENQUEUE_IOCTL:
> + return iio_buffer_enqueue_block(buffer,
> + (struct iio_buffer_block __user *)arg);
> + case IIO_BUFFER_BLOCK_DEQUEUE_IOCTL:
> + return iio_buffer_dequeue_block(indio_dev, buffer,
> + (struct iio_buffer_block __user *)arg, non_blocking);
> + }
> + return -EINVAL;
> +}
> +
> +static int iio_buffer_mmap(struct file *filep, struct vm_area_struct *vma)
> +{
> + struct iio_dev_buffer_pair *ib = filep->private_data;
> + struct iio_buffer *buffer = ib->buffer;
> +
> + if (!buffer->access || !buffer->access->mmap)
> + return -ENODEV;
> +
> + if (!(vma->vm_flags & VM_SHARED))
> + return -EINVAL;
> +
> + if (!(vma->vm_flags & VM_READ))
> + return -EINVAL;
> +
> + return buffer->access->mmap(buffer, vma);
> +}
> +
> /**
> * iio_device_attach_buffer - Attach a buffer to a IIO device
> * @indio_dev: The device the buffer should be attached to
> diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h
> index ff15c61bf319..6564bdcdac66 100644
> --- a/include/linux/iio/buffer-dma.h
> +++ b/include/linux/iio/buffer-dma.h
> @@ -17,11 +17,6 @@ struct iio_dma_buffer_queue;
> struct iio_dma_buffer_ops;
> struct device;
>
> -struct iio_buffer_block {
> - u32 size;
> - u32 bytes_used;
> -};
> -
> /**
> * enum iio_block_state - State of a struct iio_dma_buffer_block
> * @IIO_BLOCK_STATE_DEQUEUED: Block is not queued
> diff --git a/include/linux/iio/buffer_impl.h b/include/linux/iio/buffer_impl.h
> index 245b32918ae1..1d57dc7ccb4f 100644
> --- a/include/linux/iio/buffer_impl.h
> +++ b/include/linux/iio/buffer_impl.h
> @@ -34,6 +34,18 @@ struct iio_buffer;
> * device stops sampling. Calles are balanced with @enable.
> * @release: called when the last reference to the buffer is dropped,
> * should free all resources allocated by the buffer.
> + * @alloc_blocks: called from userspace via ioctl to allocate blocks
> + * that will be used via the mmap interface.
> + * @free_blocks: called from userspace via ioctl to free all blocks
> + * allocated for this buffer.
> + * @enqueue_block: called from userspace via ioctl to queue this block
> + * to this buffer. Requires a valid block id.
> + * @dequeue_block: called from userspace via ioctl to dequeue this block
> + * from this buffer. Requires a valid block id.
> + * @query_block: called from userspace via ioctl to query the attributes
> + * of this block. Requires a valid block id.
> + * @mmap: mmap hook for this buffer. Userspace mmap() calls will
> + * get routed to this.
> * @modes: Supported operating modes by this buffer type
> * @flags: A bitmask combination of INDIO_BUFFER_FLAG_*
> *
> @@ -60,6 +72,17 @@ struct iio_buffer_access_funcs {
>
> void (*release)(struct iio_buffer *buffer);
>
> + int (*alloc_blocks)(struct iio_buffer *buffer,
> + struct iio_buffer_block_alloc_req *req);
> + int (*free_blocks)(struct iio_buffer *buffer);
> + int (*enqueue_block)(struct iio_buffer *buffer,
> + struct iio_buffer_block *block);
> + int (*dequeue_block)(struct iio_buffer *buffer,
> + struct iio_buffer_block *block);
> + int (*query_block)(struct iio_buffer *buffer,
> + struct iio_buffer_block *block);
> + int (*mmap)(struct iio_buffer *buffer, struct vm_area_struct *vma);
> +
> unsigned int modes;
> unsigned int flags;
> };
> diff --git a/include/uapi/linux/iio/buffer.h b/include/uapi/linux/iio/buffer.h
> index 13939032b3f6..70ad3aea01ea 100644
> --- a/include/uapi/linux/iio/buffer.h
> +++ b/include/uapi/linux/iio/buffer.h
> @@ -5,6 +5,32 @@
> #ifndef _UAPI_IIO_BUFFER_H_
> #define _UAPI_IIO_BUFFER_H_
>
> +struct iio_buffer_block_alloc_req {
> + __u32 type;
> + __u32 size;
> + __u32 count;
> + __u32 id;
> +};
> +
> +#define IIO_BUFFER_BLOCK_FLAG_TIMESTAMP_VALID (1 << 0)
> +
Add some docs for the elements of these structures.
> +struct iio_buffer_block {
> + __u32 id;
> + __u32 size;
> + __u32 bytes_used;
> + __u32 type;
> + __u32 flags;
> + union {
> + __u32 offset;
> + } data;
> + __u64 timestamp;
> +};
> +
> #define IIO_BUFFER_GET_FD_IOCTL _IOWR('i', 0x91, int)
> +#define IIO_BUFFER_BLOCK_ALLOC_IOCTL _IOWR('i', 0x92, struct iio_buffer_block_alloc_req)
> +#define IIO_BUFFER_BLOCK_FREE_IOCTL _IO('i', 0x93)
> +#define IIO_BUFFER_BLOCK_QUERY_IOCTL _IOWR('i', 0x93, struct iio_buffer_block)
> +#define IIO_BUFFER_BLOCK_ENQUEUE_IOCTL _IOWR('i', 0x94, struct iio_buffer_block)
> +#define IIO_BUFFER_BLOCK_DEQUEUE_IOCTL _IOWR('i', 0x95, struct iio_buffer_block)
>
> #endif /* _UAPI_IIO_BUFFER_H_ */