// SPDX-License-Identifier: LGPL-2.1-or-later /* * libiio - Library for interfacing industrial I/O (IIO) devices * * Copyright (C) 2022 Analog Devices, Inc. * Author: Paul Cercueil <paul.cercueil@analog.com> */ #include "iio-private.h" #include "local.h" #include <errno.h> #include <iio/iio.h> #include <iio/iio-backend.h> #include <iio/iio-debug.h> #include <poll.h> #include <sys/ioctl.h> #include <stdatomic.h> #include <stddef.h> #include <stdint.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <time.h> #define container_of(ptr, type, member) \ ((type *)(void *)((uintptr_t)(ptr) - offsetof(type, member))) #define BLOCK_ALLOC_IOCTL _IOWR('i', 0xa0, struct block_alloc_req) #define BLOCK_FREE_IOCTL _IO('i', 0xa1) #define BLOCK_QUERY_IOCTL _IOWR('i', 0xa2, struct block) #define BLOCK_ENQUEUE_IOCTL _IOWR('i', 0xa3, struct block) #define BLOCK_DEQUEUE_IOCTL _IOWR('i', 0xa4, struct block) #define BLOCK_FLAG_CYCLIC BIT(1) struct block_alloc_req { uint32_t type, size, count, id; }; struct block { uint32_t id, size, bytes_used, type, flags, offset; uint64_t timestamp; }; struct iio_buffer_impl_pdata { atomic_uint_fast64_t mmap_block_mask; atomic_uint_fast64_t mmap_enqueued_blocks_mask; bool mmap_check_done; bool cyclic_buffer_enqueued; unsigned int nb_blocks; }; struct iio_block_impl_pdata { struct iio_block_pdata pdata; struct block block; unsigned int idx; bool enqueued; }; static struct iio_block_impl_pdata * iio_block_get_impl(struct iio_block_pdata *pdata) { return container_of(pdata, struct iio_block_impl_pdata, pdata); } static bool local_is_mmap_api_supported(int fd) { /* * For the BLOCK_ALLOC_IOCTL ioctl it is not possible to distinguish * between an error during the allocation (e.g. incorrect size) or * whether the high-speed interface is not supported. BLOCK_FREE_IOCTL does * never fail if the device supports the high-speed interface, so we use it * here. Calling it when no blocks are allocated the ioctl has no effect. */ return !ioctl_nointr(fd, BLOCK_FREE_IOCTL, NULL); } struct iio_block_pdata * local_create_mmap_block(struct iio_buffer_pdata *pdata, size_t size, void **data) { struct iio_buffer_impl_pdata *ppdata = pdata->pdata; struct iio_block_impl_pdata *priv; struct block_alloc_req req; int ret; if (!ppdata->mmap_check_done) { pdata->mmap_supported = local_is_mmap_api_supported(pdata->fd); ppdata->mmap_check_done = true; } if (!pdata->mmap_supported) return iio_ptr(-ENOSYS); priv = zalloc(sizeof(*priv)); if (!priv) return iio_ptr(-ENOMEM); if (ppdata->mmap_block_mask == (uint64_t) -1) { /* 64 blocks is the maximum */ dev_err(pdata->dev, "64 blocks is the maximum with the MMAP API.\n"); ret = -EINVAL; goto out_free_priv; } if (__builtin_popcountl(ppdata->mmap_block_mask) == ppdata->nb_blocks) { /* All our allocated blocks are used; we need to create one more. */ priv->idx = ppdata->nb_blocks; req.id = 0; req.type = 0; req.size = size; req.count = 1; ret = ioctl_nointr(pdata->fd, BLOCK_ALLOC_IOCTL, &req); if (ret < 0) goto out_free_priv; ppdata->nb_blocks++; } else { /* One of our previously allocated blocks has been freed; * reuse it now. */ /* XXX: This only works if the blocks are the same size... */ priv->idx = __builtin_ffsl(~ppdata->mmap_block_mask) - 1; } priv->block.id = priv->idx; ret = ioctl_nointr(pdata->fd, BLOCK_QUERY_IOCTL, &priv->block); if (ret < 0) goto out_free_priv; priv->pdata.data = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, pdata->fd, priv->block.offset); if (priv->pdata.data == MAP_FAILED) { ret = -errno; goto out_free_priv; } priv->pdata.size = size; priv->pdata.buf = pdata; *data = priv->pdata.data; ppdata->mmap_block_mask |= BIT(priv->idx); return &priv->pdata; out_free_priv: free(priv); return iio_ptr(ret); } void local_free_mmap_block(struct iio_block_pdata *pdata) { struct iio_block_impl_pdata *priv = iio_block_get_impl(pdata); struct iio_buffer_pdata *buf = pdata->buf; munmap(pdata->data, pdata->size); if (!atomic_fetch_xor(&buf->pdata->mmap_block_mask, BIT(priv->idx))) { ioctl_nointr(pdata->buf->fd, BLOCK_FREE_IOCTL, 0); pdata->buf->pdata->nb_blocks = 0; } free(priv); } int local_enqueue_mmap_block(struct iio_block_pdata *pdata, size_t bytes_used, bool cyclic) { struct iio_block_impl_pdata *priv = iio_block_get_impl(pdata); struct iio_buffer_pdata *buf = pdata->buf; int ret, fd = buf->fd; uint_fast64_t mask; if (cyclic) { if (buf->pdata->cyclic_buffer_enqueued) return -EBUSY; priv->block.flags |= BLOCK_FLAG_CYCLIC; } if (bytes_used != priv->block.size && !iio_device_is_tx(buf->dev)) { /* MMAP interface only supports bytes_used on TX */ return -EINVAL; } if (priv->enqueued) { /* Already enqueued */ return -EPERM; } mask = atomic_fetch_or(&buf->pdata->mmap_enqueued_blocks_mask, BIT(priv->idx)); if (mask & BIT(priv->idx)) { /* The block was marked as dequeued, but has been enqueued to * the kernel? This case should never happen. */ priv->enqueued = true; return 0; } priv->block.bytes_used = (uint32_t) bytes_used; ret = ioctl_nointr(fd, BLOCK_ENQUEUE_IOCTL, &priv->block); if (ret < 0) { atomic_fetch_xor(&buf->pdata->mmap_enqueued_blocks_mask, BIT(priv->idx)); return ret; } if (cyclic) buf->pdata->cyclic_buffer_enqueued = true; priv->enqueued = true; return 0; } int local_dequeue_mmap_block(struct iio_block_pdata *pdata, bool nonblock) { struct iio_block_impl_pdata *priv = iio_block_get_impl(pdata); struct iio_buffer_pdata *buf = pdata->buf; struct timespec start, *time_ptr = NULL; struct block block; int ret, fd = buf->fd; if (!priv->enqueued) { /* Already dequeued */ return -EPERM; } if (!nonblock) { clock_gettime(CLOCK_MONOTONIC, &start); time_ptr = &start; } for (;;) { if (!(atomic_load(&buf->pdata->mmap_enqueued_blocks_mask) & BIT(priv->idx))) { /* The block has been dequeued by a previous call to * local_dequeue_mmap_block() for a different block. */ break; } ret = buffer_check_ready(buf, fd, POLLIN | POLLOUT, time_ptr); if (ret < 0) return ret; ret = ioctl_nointr(fd, BLOCK_DEQUEUE_IOCTL, &block); if (ret < 0) return ret; atomic_fetch_xor(&buf->pdata->mmap_enqueued_blocks_mask, BIT(block.id)); if (block.id == priv->idx) break; } priv->enqueued = false; return 0; } struct iio_buffer_impl_pdata * local_alloc_mmap_buffer_impl(void) { struct iio_buffer_impl_pdata *pdata; pdata = zalloc(sizeof(*pdata)); if (!pdata) return iio_ptr(-ENOMEM); return pdata; }