#include <linux/atomic.h>
#include <linux/cleanup.h>
#include <linux/lockdep.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/iio/buffer_impl.h>
#include <linux/iio/buffer-dma.h>
#include <linux/dma-buf.h>
#include <linux/dma-fence.h>
#include <linux/dma-mapping.h>
#include <linux/sizes.h>
static void iio_buffer_block_release(struct kref *kref)
{
struct iio_dma_buffer_block *block = container_of(kref,
struct iio_dma_buffer_block, kref);
struct iio_dma_buffer_queue *queue = block->queue;
WARN_ON(block->fileio && block->state != IIO_BLOCK_STATE_DEAD);
if (block->fileio) {
dma_free_coherent(queue->dev, PAGE_ALIGN(block->size),
block->vaddr, block->phys_addr);
} else {
atomic_dec(&queue->num_dmabufs);
}
iio_buffer_put(&queue->buffer);
kfree(block);
}
static void iio_buffer_block_get(struct iio_dma_buffer_block *block)
{
kref_get(&block->kref);
}
static void iio_buffer_block_put(struct iio_dma_buffer_block *block)
{
kref_put(&block->kref, iio_buffer_block_release);
}
static LIST_HEAD(iio_dma_buffer_dead_blocks);
static DEFINE_SPINLOCK(iio_dma_buffer_dead_blocks_lock);
static void iio_dma_buffer_cleanup_worker(struct work_struct *work)
{
struct iio_dma_buffer_block *block, *_block;
LIST_HEAD(block_list);
scoped_guard(spinlock_irq, &iio_dma_buffer_dead_blocks_lock)
list_splice_tail_init(&iio_dma_buffer_dead_blocks, &block_list);
list_for_each_entry_safe(block, _block, &block_list, head)
iio_buffer_block_release(&block->kref);
}
static DECLARE_WORK(iio_dma_buffer_cleanup_work, iio_dma_buffer_cleanup_worker);
static void iio_buffer_block_release_atomic(struct kref *kref)
{
struct iio_dma_buffer_block *block;
block = container_of(kref, struct iio_dma_buffer_block, kref);
scoped_guard(spinlock_irqsave, &iio_dma_buffer_dead_blocks_lock)
list_add_tail(&block->head, &iio_dma_buffer_dead_blocks);
schedule_work(&iio_dma_buffer_cleanup_work);
}
static void iio_buffer_block_put_atomic(struct iio_dma_buffer_block *block)
{
kref_put(&block->kref, iio_buffer_block_release_atomic);
}
static struct iio_dma_buffer_queue *iio_buffer_to_queue(struct iio_buffer *buf)
{
return container_of(buf, struct iio_dma_buffer_queue, buffer);
}
static struct iio_dma_buffer_block *
iio_dma_buffer_alloc_block(struct iio_dma_buffer_queue *queue, size_t size,
bool fileio)
{
struct iio_dma_buffer_block *block __free(kfree) =
kzalloc_obj(*block);
if (!block)
return NULL;
if (fileio) {
block->vaddr = dma_alloc_coherent(queue->dev, PAGE_ALIGN(size),
&block->phys_addr, GFP_KERNEL);
if (!block->vaddr)
return NULL;
}
block->fileio = fileio;
block->size = size;
block->state = IIO_BLOCK_STATE_DONE;
block->queue = queue;
INIT_LIST_HEAD(&block->head);
kref_init(&block->kref);
iio_buffer_get(&queue->buffer);
if (!fileio)
atomic_inc(&queue->num_dmabufs);
return_ptr(block);
}
static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
{
if (block->state != IIO_BLOCK_STATE_DEAD)
block->state = IIO_BLOCK_STATE_DONE;
}
static void iio_dma_buffer_queue_wake(struct iio_dma_buffer_queue *queue)
{
__poll_t flags;
if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN)
flags = EPOLLIN | EPOLLRDNORM;
else
flags = EPOLLOUT | EPOLLWRNORM;
wake_up_interruptible_poll(&queue->buffer.pollq, flags);
}
void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
{
struct iio_dma_buffer_queue *queue = block->queue;
bool cookie;
cookie = dma_fence_begin_signalling();
scoped_guard(spinlock_irqsave, &queue->list_lock)
_iio_dma_buffer_block_done(block);
if (!block->fileio)
iio_buffer_signal_dmabuf_done(block->fence, 0);
iio_buffer_block_put_atomic(block);
iio_dma_buffer_queue_wake(queue);
dma_fence_end_signalling(cookie);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_block_done, "IIO_DMA_BUFFER");
void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue,
struct list_head *list)
{
struct iio_dma_buffer_block *block, *_block;
bool cookie;
cookie = dma_fence_begin_signalling();
scoped_guard(spinlock_irqsave, &queue->list_lock) {
list_for_each_entry_safe(block, _block, list, head) {
list_del(&block->head);
block->bytes_used = 0;
_iio_dma_buffer_block_done(block);
if (!block->fileio)
iio_buffer_signal_dmabuf_done(block->fence,
-EINTR);
iio_buffer_block_put_atomic(block);
}
}
if (queue->fileio.enabled)
queue->fileio.enabled = false;
iio_dma_buffer_queue_wake(queue);
dma_fence_end_signalling(cookie);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_block_list_abort, "IIO_DMA_BUFFER");
static bool iio_dma_block_reusable(struct iio_dma_buffer_block *block)
{
switch (block->state) {
case IIO_BLOCK_STATE_QUEUED:
case IIO_BLOCK_STATE_DONE:
return true;
default:
return false;
}
}
static bool iio_dma_buffer_can_use_fileio(struct iio_dma_buffer_queue *queue)
{
return queue->fileio.enabled || !atomic_read(&queue->num_dmabufs);
}
int iio_dma_buffer_request_update(struct iio_buffer *buffer)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
struct iio_dma_buffer_block *block;
bool try_reuse = false;
size_t size;
int i;
size = DIV_ROUND_UP(queue->buffer.bytes_per_datum *
queue->buffer.length, 2);
guard(mutex)(&queue->lock);
queue->fileio.enabled = iio_dma_buffer_can_use_fileio(queue);
if (!queue->fileio.enabled)
return 0;
if (PAGE_ALIGN(queue->fileio.block_size) == PAGE_ALIGN(size))
try_reuse = true;
queue->fileio.block_size = size;
queue->fileio.active_block = NULL;
scoped_guard(spinlock_irq, &queue->list_lock) {
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
block = queue->fileio.blocks[i];
if (block && (!iio_dma_block_reusable(block) || !try_reuse))
block->state = IIO_BLOCK_STATE_DEAD;
}
}
INIT_LIST_HEAD(&queue->incoming);
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
if (queue->fileio.blocks[i]) {
block = queue->fileio.blocks[i];
if (block->state == IIO_BLOCK_STATE_DEAD) {
iio_buffer_block_put(block);
block = NULL;
} else {
block->size = size;
}
} else {
block = NULL;
}
if (!block) {
block = iio_dma_buffer_alloc_block(queue, size, true);
if (!block)
return -ENOMEM;
queue->fileio.blocks[i] = block;
}
block->bytes_used = block->size;
if (queue->buffer.direction == IIO_BUFFER_DIRECTION_IN) {
block->state = IIO_BLOCK_STATE_QUEUED;
list_add_tail(&block->head, &queue->incoming);
} else {
block->state = IIO_BLOCK_STATE_DONE;
}
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_request_update, "IIO_DMA_BUFFER");
static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue)
{
unsigned int i;
scoped_guard(spinlock_irq, &queue->list_lock) {
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
if (!queue->fileio.blocks[i])
continue;
queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD;
}
}
INIT_LIST_HEAD(&queue->incoming);
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
if (!queue->fileio.blocks[i])
continue;
iio_buffer_block_put(queue->fileio.blocks[i]);
queue->fileio.blocks[i] = NULL;
}
queue->fileio.active_block = NULL;
}
static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue,
struct iio_dma_buffer_block *block)
{
int ret;
if (!queue->ops)
return;
block->state = IIO_BLOCK_STATE_ACTIVE;
iio_buffer_block_get(block);
ret = queue->ops->submit(queue, block);
if (ret) {
if (!block->fileio)
iio_buffer_signal_dmabuf_done(block->fence, ret);
iio_buffer_block_put(block);
}
}
int iio_dma_buffer_enable(struct iio_buffer *buffer, struct iio_dev *indio_dev)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
struct iio_dma_buffer_block *block, *_block;
guard(mutex)(&queue->lock);
queue->active = true;
list_for_each_entry_safe(block, _block, &queue->incoming, head) {
list_del(&block->head);
iio_dma_buffer_submit_block(queue, block);
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_enable, "IIO_DMA_BUFFER");
int iio_dma_buffer_disable(struct iio_buffer *buffer, struct iio_dev *indio_dev)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
guard(mutex)(&queue->lock);
queue->active = false;
if (queue->ops && queue->ops->abort)
queue->ops->abort(queue);
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_disable, "IIO_DMA_BUFFER");
static void iio_dma_buffer_enqueue(struct iio_dma_buffer_queue *queue,
struct iio_dma_buffer_block *block)
{
if (block->state == IIO_BLOCK_STATE_DEAD) {
iio_buffer_block_put(block);
} else if (queue->active) {
iio_dma_buffer_submit_block(queue, block);
} else {
block->state = IIO_BLOCK_STATE_QUEUED;
list_add_tail(&block->head, &queue->incoming);
}
}
static struct iio_dma_buffer_block *
iio_dma_buffer_dequeue(struct iio_dma_buffer_queue *queue)
{
struct iio_dma_buffer_block *block;
unsigned int idx;
guard(spinlock_irq)(&queue->list_lock);
idx = queue->fileio.next_dequeue;
block = queue->fileio.blocks[idx];
if (block->state != IIO_BLOCK_STATE_DONE)
return NULL;
idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks);
queue->fileio.next_dequeue = idx;
return block;
}
static int iio_dma_buffer_io(struct iio_buffer *buffer, size_t n,
char __user *user_buffer, bool is_from_user)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
struct iio_dma_buffer_block *block;
void *addr;
int ret;
if (n < buffer->bytes_per_datum)
return -EINVAL;
guard(mutex)(&queue->lock);
if (!queue->fileio.active_block) {
block = iio_dma_buffer_dequeue(queue);
if (!block)
return 0;
queue->fileio.pos = 0;
queue->fileio.active_block = block;
} else {
block = queue->fileio.active_block;
}
n = rounddown(n, buffer->bytes_per_datum);
if (n > block->bytes_used - queue->fileio.pos)
n = block->bytes_used - queue->fileio.pos;
addr = block->vaddr + queue->fileio.pos;
if (is_from_user)
ret = copy_from_user(addr, user_buffer, n);
else
ret = copy_to_user(user_buffer, addr, n);
if (ret)
return -EFAULT;
queue->fileio.pos += n;
if (queue->fileio.pos == block->bytes_used) {
queue->fileio.active_block = NULL;
iio_dma_buffer_enqueue(queue, block);
}
return n;
}
int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
char __user *user_buffer)
{
return iio_dma_buffer_io(buffer, n, user_buffer, false);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_read, "IIO_DMA_BUFFER");
int iio_dma_buffer_write(struct iio_buffer *buffer, size_t n,
const char __user *user_buffer)
{
return iio_dma_buffer_io(buffer, n,
(__force __user char *)user_buffer, true);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_write, "IIO_DMA_BUFFER");
size_t iio_dma_buffer_usage(struct iio_buffer *buf)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf);
struct iio_dma_buffer_block *block;
size_t data_available = 0;
unsigned int i;
guard(mutex)(&queue->lock);
if (queue->fileio.active_block)
data_available += queue->fileio.active_block->size;
guard(spinlock_irq)(&queue->list_lock);
for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
block = queue->fileio.blocks[i];
if (block != queue->fileio.active_block && block->state == IIO_BLOCK_STATE_DONE)
data_available += block->size;
}
return data_available;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_usage, "IIO_DMA_BUFFER");
struct iio_dma_buffer_block *
iio_dma_buffer_attach_dmabuf(struct iio_buffer *buffer,
struct dma_buf_attachment *attach)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
struct iio_dma_buffer_block *block;
guard(mutex)(&queue->lock);
if (queue->fileio.enabled)
return ERR_PTR(-EBUSY);
block = iio_dma_buffer_alloc_block(queue, attach->dmabuf->size, false);
if (!block)
return ERR_PTR(-ENOMEM);
iio_dma_buffer_fileio_free(queue);
return block;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_attach_dmabuf, "IIO_DMA_BUFFER");
void iio_dma_buffer_detach_dmabuf(struct iio_buffer *buffer,
struct iio_dma_buffer_block *block)
{
block->state = IIO_BLOCK_STATE_DEAD;
iio_buffer_block_put_atomic(block);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_detach_dmabuf, "IIO_DMA_BUFFER");
static int iio_dma_can_enqueue_block(struct iio_dma_buffer_block *block)
{
struct iio_dma_buffer_queue *queue = block->queue;
if (queue->fileio.enabled)
return -EBUSY;
switch (block->state) {
case IIO_BLOCK_STATE_QUEUED:
return -EPERM;
case IIO_BLOCK_STATE_ACTIVE:
case IIO_BLOCK_STATE_DEAD:
return -EBUSY;
case IIO_BLOCK_STATE_DONE:
break;
}
return 0;
}
int iio_dma_buffer_enqueue_dmabuf(struct iio_buffer *buffer,
struct iio_dma_buffer_block *block,
struct dma_fence *fence,
struct sg_table *sgt,
size_t size, bool cyclic)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
bool cookie;
int ret;
lockdep_assert_held(&queue->lock);
cookie = dma_fence_begin_signalling();
ret = iio_dma_can_enqueue_block(block);
if (ret < 0)
goto out_end_signalling;
block->bytes_used = size;
block->cyclic = cyclic;
block->sg_table = sgt;
block->fence = fence;
iio_dma_buffer_enqueue(queue, block);
out_end_signalling:
dma_fence_end_signalling(cookie);
return ret;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_enqueue_dmabuf, "IIO_DMA_BUFFER");
struct device *iio_dma_buffer_get_dma_dev(struct iio_buffer *buffer)
{
return iio_buffer_to_queue(buffer)->dev;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_get_dma_dev, "IIO_DMA_BUFFER");
void iio_dma_buffer_lock_queue(struct iio_buffer *buffer)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
mutex_lock(&queue->lock);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_lock_queue, "IIO_DMA_BUFFER");
void iio_dma_buffer_unlock_queue(struct iio_buffer *buffer)
{
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buffer);
mutex_unlock(&queue->lock);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_unlock_queue, "IIO_DMA_BUFFER");
int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd)
{
buffer->bytes_per_datum = bpd;
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_set_bytes_per_datum, "IIO_DMA_BUFFER");
int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length)
{
if (length < 2)
length = 2;
buffer->length = length;
buffer->watermark = length / 2;
return 0;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_set_length, "IIO_DMA_BUFFER");
void iio_dma_buffer_init(struct iio_dma_buffer_queue *queue, struct device *dev,
const struct iio_dma_buffer_ops *ops)
{
iio_buffer_init(&queue->buffer);
queue->buffer.length = PAGE_SIZE;
queue->buffer.watermark = queue->buffer.length / 2;
queue->dev = dev;
queue->ops = ops;
INIT_LIST_HEAD(&queue->incoming);
mutex_init(&queue->lock);
spin_lock_init(&queue->list_lock);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_init, "IIO_DMA_BUFFER");
void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue)
{
guard(mutex)(&queue->lock);
iio_dma_buffer_fileio_free(queue);
queue->ops = NULL;
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_exit, "IIO_DMA_BUFFER");
void iio_dma_buffer_release(struct iio_dma_buffer_queue *queue)
{
mutex_destroy(&queue->lock);
}
EXPORT_SYMBOL_NS_GPL(iio_dma_buffer_release, "IIO_DMA_BUFFER");
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("DMA buffer for the IIO framework");
MODULE_LICENSE("GPL v2");