[PATCH v3 5/6] iio: buffer-dma: Add output buffer support

From: Alexandru Ardelean
Date: Fri Feb 19 2021 - 07:41:54 EST


From: Lars-Peter Clausen <lars@xxxxxxxxxx>

Add support for output buffers to the dma buffer implementation.

Signed-off-by: Lars-Peter Clausen <lars@xxxxxxxxxx>
Signed-off-by: Alexandru Ardelean <alexandru.ardelean@xxxxxxxxxx>
---
drivers/iio/adc/adi-axi-adc.c | 3 +-
drivers/iio/buffer/industrialio-buffer-dma.c | 116 ++++++++++++++++--
.../buffer/industrialio-buffer-dmaengine.c | 43 +++++--
include/linux/iio/buffer-dma.h | 6 +
include/linux/iio/buffer-dmaengine.h | 6 +-
5 files changed, 151 insertions(+), 23 deletions(-)

diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index bfa00100a631..98cc1e7caa69 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -104,6 +104,7 @@ static unsigned int adi_axi_adc_read(struct adi_axi_adc_state *st,
static int adi_axi_adc_config_dma_buffer(struct device *dev,
struct iio_dev *indio_dev)
{
+ enum iio_buffer_direction dir = IIO_BUFFER_DIRECTION_IN;
const char *dma_name;

if (!device_property_present(dev, "dmas"))
@@ -113,7 +114,7 @@ static int adi_axi_adc_config_dma_buffer(struct device *dev,
dma_name = "rx";

return devm_iio_dmaengine_buffer_setup(indio_dev->dev.parent,
- indio_dev, dma_name,
+ indio_dev, dir, dma_name,
NULL, NULL);
}

diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c
index aa56c10418d0..83074d060535 100644
--- a/drivers/iio/buffer/industrialio-buffer-dma.c
+++ b/drivers/iio/buffer/industrialio-buffer-dma.c
@@ -223,7 +223,8 @@ void iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
spin_unlock_irqrestore(&queue->list_lock, flags);

iio_buffer_block_put_atomic(block);
- wake_up_interruptible_poll(&queue->buffer.pollq, EPOLLIN | EPOLLRDNORM);
+ wake_up_interruptible_poll(&queue->buffer.pollq,
+ (uintptr_t)queue->poll_wakup_flags);
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_block_done);

@@ -252,7 +253,8 @@ void iio_dma_buffer_block_list_abort(struct iio_dma_buffer_queue *queue,
}
spin_unlock_irqrestore(&queue->list_lock, flags);

- wake_up_interruptible_poll(&queue->buffer.pollq, EPOLLIN | EPOLLRDNORM);
+ wake_up_interruptible_poll(&queue->buffer.pollq,
+ (uintptr_t)queue->poll_wakup_flags);
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_block_list_abort);

@@ -353,9 +355,6 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer)
}

block->block.id = i;
-
- block->state = IIO_BLOCK_STATE_QUEUED;
- list_add_tail(&block->head, &queue->incoming);
}

out_unlock:
@@ -437,7 +436,29 @@ int iio_dma_buffer_enable(struct iio_buffer *buffer,
struct iio_dma_buffer_block *block, *_block;

mutex_lock(&queue->lock);
+
+ if (buffer->direction == IIO_BUFFER_DIRECTION_IN)
+ queue->poll_wakup_flags = POLLIN | POLLRDNORM;
+ else
+ queue->poll_wakup_flags = POLLOUT | POLLWRNORM;
+
queue->fileio.enabled = !queue->num_blocks;
+ if (queue->fileio.enabled) {
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
+ struct iio_dma_buffer_block *block =
+ queue->fileio.blocks[i];
+ if (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_DEQUEUED;
+ list_add_tail(&block->head, &queue->outgoing);
+ }
+ }
+ }
+
queue->active = true;
list_for_each_entry_safe(block, _block, &queue->incoming, head) {
list_del(&block->head);
@@ -567,6 +588,61 @@ int iio_dma_buffer_read(struct iio_buffer *buffer, size_t n,
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_read);

+int iio_dma_buffer_write(struct iio_buffer *buf, size_t n,
+ const char __user *user_buffer)
+{
+ struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf);
+ struct iio_dma_buffer_block *block;
+ int ret;
+
+ if (n < buf->bytes_per_datum)
+ return -EINVAL;
+
+ mutex_lock(&queue->lock);
+
+ if (!queue->fileio.enabled) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ if (!queue->fileio.active_block) {
+ block = iio_dma_buffer_dequeue(queue);
+ if (block == NULL) {
+ ret = 0;
+ goto out_unlock;
+ }
+ queue->fileio.pos = 0;
+ queue->fileio.active_block = block;
+ } else {
+ block = queue->fileio.active_block;
+ }
+
+ n = rounddown(n, buf->bytes_per_datum);
+ if (n > block->block.size - queue->fileio.pos)
+ n = block->block.size - queue->fileio.pos;
+
+ if (copy_from_user(block->vaddr + queue->fileio.pos, user_buffer, n)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ queue->fileio.pos += n;
+
+ if (queue->fileio.pos == block->block.size) {
+ queue->fileio.active_block = NULL;
+ block->block.bytes_used = block->block.size;
+ iio_dma_buffer_enqueue(queue, block);
+ }
+
+ ret = n;
+
+out_unlock:
+ mutex_unlock(&queue->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iio_dma_buffer_write);
+
/**
* iio_dma_buffer_data_available() - DMA buffer data_available callback
* @buf: Buffer to check for data availability
@@ -588,12 +664,14 @@ size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
*/

mutex_lock(&queue->lock);
- if (queue->fileio.active_block)
- data_available += queue->fileio.active_block->block.size;
+ if (queue->fileio.active_block) {
+ data_available += queue->fileio.active_block->block.bytes_used -
+ queue->fileio.pos;
+ }

spin_lock_irq(&queue->list_lock);
list_for_each_entry(block, &queue->outgoing, head)
- data_available += block->block.size;
+ data_available += block->block.bytes_used;
spin_unlock_irq(&queue->list_lock);
mutex_unlock(&queue->lock);

@@ -601,6 +679,28 @@ size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_data_available);

+size_t iio_dma_buffer_space_available(struct iio_buffer *buf)
+{
+ struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf);
+ struct iio_dma_buffer_block *block;
+ size_t space_available = 0;
+
+ mutex_lock(&queue->lock);
+ if (queue->fileio.active_block) {
+ space_available += queue->fileio.active_block->block.size -
+ queue->fileio.pos;
+ }
+
+ spin_lock_irq(&queue->list_lock);
+ list_for_each_entry(block, &queue->outgoing, head)
+ space_available += block->block.size;
+ spin_unlock_irq(&queue->list_lock);
+ mutex_unlock(&queue->lock);
+
+ return space_available;
+}
+EXPORT_SYMBOL_GPL(iio_dma_buffer_space_available);
+
int iio_dma_buffer_alloc_blocks(struct iio_buffer *buffer,
struct iio_buffer_block_alloc_req *req)
{
diff --git a/drivers/iio/buffer/industrialio-buffer-dmaengine.c b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
index a4e7b97ce239..65458a6cc81a 100644
--- a/drivers/iio/buffer/industrialio-buffer-dmaengine.c
+++ b/drivers/iio/buffer/industrialio-buffer-dmaengine.c
@@ -37,6 +37,8 @@ struct dmaengine_buffer {

u32 align;
u32 max_size;
+
+ bool is_tx;
};

static struct dmaengine_buffer *iio_buffer_to_dmaengine_buffer(
@@ -64,9 +66,12 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
struct dmaengine_buffer *dmaengine_buffer =
iio_buffer_to_dmaengine_buffer(&queue->buffer);
struct dma_async_tx_descriptor *desc;
+ enum dma_transfer_direction direction;
dma_cookie_t cookie;

- block->block.bytes_used = min(block->block.size,
+ if (!dmaengine_buffer->is_tx)
+ block->block.bytes_used = block->block.size;
+ block->block.bytes_used = min(block->block.bytes_used,
dmaengine_buffer->max_size);
block->block.bytes_used = rounddown(block->block.bytes_used,
dmaengine_buffer->align);
@@ -75,8 +80,10 @@ static int iio_dmaengine_buffer_submit_block(struct iio_dma_buffer_queue *queue,
return 0;
}

+ direction = dmaengine_buffer->is_tx ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+
desc = dmaengine_prep_slave_single(dmaengine_buffer->chan,
- block->phys_addr, block->block.bytes_used, DMA_DEV_TO_MEM,
+ block->phys_addr, block->block.bytes_used, direction,
DMA_PREP_INTERRUPT);
if (!desc)
return -ENOMEM;
@@ -117,12 +124,14 @@ static void iio_dmaengine_buffer_release(struct iio_buffer *buf)

static const struct iio_buffer_access_funcs iio_dmaengine_buffer_ops = {
.read = iio_dma_buffer_read,
+ .write = iio_dma_buffer_write,
.set_bytes_per_datum = iio_dma_buffer_set_bytes_per_datum,
.set_length = iio_dma_buffer_set_length,
.request_update = iio_dma_buffer_request_update,
.enable = iio_dma_buffer_enable,
.disable = iio_dma_buffer_disable,
.data_available = iio_dma_buffer_data_available,
+ .space_available = iio_dma_buffer_space_available,
.release = iio_dmaengine_buffer_release,

.alloc_blocks = iio_dma_buffer_alloc_blocks,
@@ -162,6 +171,7 @@ static const struct attribute *iio_dmaengine_buffer_attrs[] = {
/**
* iio_dmaengine_buffer_alloc() - Allocate new buffer which uses DMAengine
* @dev: Parent device for the buffer
+ * @direction: Set the direction of the data.
* @channel: DMA channel name, typically "rx".
* @ops: Custom iio_dma_buffer_ops, if NULL default ops will be used
* @driver_data: Driver data to be passed to custom iio_dma_buffer_ops
@@ -174,11 +184,12 @@ static const struct attribute *iio_dmaengine_buffer_attrs[] = {
* release it.
*/
static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
- const char *channel, const struct iio_dma_buffer_ops *ops,
- void *driver_data)
+ enum iio_buffer_direction direction, const char *channel,
+ const struct iio_dma_buffer_ops *ops, void *driver_data)
{
struct dmaengine_buffer *dmaengine_buffer;
unsigned int width, src_width, dest_width;
+ bool is_tx = (direction == IIO_BUFFER_DIRECTION_OUT);
struct dma_slave_caps caps;
struct dma_chan *chan;
int ret;
@@ -187,6 +198,9 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
if (!dmaengine_buffer)
return ERR_PTR(-ENOMEM);

+ if (!channel)
+ channel = is_tx ? "tx" : "rx";
+
chan = dma_request_chan(dev, channel);
if (IS_ERR(chan)) {
ret = PTR_ERR(chan);
@@ -212,6 +226,7 @@ static struct iio_buffer *iio_dmaengine_buffer_alloc(struct device *dev,
dmaengine_buffer->chan = chan;
dmaengine_buffer->align = width;
dmaengine_buffer->max_size = dma_get_max_seg_size(chan->device->dev);
+ dmaengine_buffer->is_tx = is_tx;

iio_dma_buffer_init(&dmaengine_buffer->queue, chan->device->dev,
ops ? ops : &iio_dmaengine_default_ops, driver_data);
@@ -251,7 +266,8 @@ static void __devm_iio_dmaengine_buffer_free(struct device *dev, void *res)
/**
* devm_iio_dmaengine_buffer_alloc() - Resource-managed iio_dmaengine_buffer_alloc()
* @dev: Parent device for the buffer
- * @channel: DMA channel name, typically "rx".
+ * @direction: Direction of the data stream (in/out).
+ * @channel: DMA channel name, typically "rx" for input, "tx" for output.
* @ops: Custom iio_dma_buffer_ops, if NULL default ops will be used
* @driver_data: Driver data to be passed to custom iio_dma_buffer_ops
*
@@ -262,8 +278,8 @@ static void __devm_iio_dmaengine_buffer_free(struct device *dev, void *res)
* The buffer will be automatically de-allocated once the device gets destroyed.
*/
static struct iio_buffer *devm_iio_dmaengine_buffer_alloc(struct device *dev,
- const char *channel, const struct iio_dma_buffer_ops *ops,
- void *driver_data)
+ enum iio_buffer_direction direction, const char *channel,
+ const struct iio_dma_buffer_ops *ops, void *driver_data)
{
struct iio_buffer **bufferp, *buffer;

@@ -272,7 +288,8 @@ static struct iio_buffer *devm_iio_dmaengine_buffer_alloc(struct device *dev,
if (!bufferp)
return ERR_PTR(-ENOMEM);

- buffer = iio_dmaengine_buffer_alloc(dev, channel, ops, driver_data);
+ buffer = iio_dmaengine_buffer_alloc(dev, direction, channel, ops,
+ driver_data);
if (IS_ERR(buffer)) {
devres_free(bufferp);
return buffer;
@@ -288,7 +305,8 @@ static struct iio_buffer *devm_iio_dmaengine_buffer_alloc(struct device *dev,
* devm_iio_dmaengine_buffer_setup() - Setup a DMA buffer for an IIO device
* @dev: Parent device for the buffer
* @indio_dev: IIO device to which to attach this buffer.
- * @channel: DMA channel name, typically "rx".
+ * @direction: Direction of the data stream (in/out).
+ * @channel: DMA channel name, typically "rx" for input, "tx" for output.
* @ops: Custom iio_dma_buffer_ops, if NULL default ops will be used
* @driver_data: Driver data to be passed to custom iio_dma_buffer_ops
*
@@ -298,14 +316,15 @@ static struct iio_buffer *devm_iio_dmaengine_buffer_alloc(struct device *dev,
* IIO device.
*/
int devm_iio_dmaengine_buffer_setup(struct device *dev,
- struct iio_dev *indio_dev, const char *channel,
- const struct iio_dma_buffer_ops *ops,
+ struct iio_dev *indio_dev, enum iio_buffer_direction direction,
+ const char *channel, const struct iio_dma_buffer_ops *ops,
void *driver_data)
{
struct iio_buffer *buffer;

buffer = devm_iio_dmaengine_buffer_alloc(indio_dev->dev.parent,
- channel, ops, driver_data);
+ direction, channel, ops,
+ driver_data);
if (IS_ERR(buffer))
return PTR_ERR(buffer);

diff --git a/include/linux/iio/buffer-dma.h b/include/linux/iio/buffer-dma.h
index 1eec7efe44cf..9a99c74fab16 100644
--- a/include/linux/iio/buffer-dma.h
+++ b/include/linux/iio/buffer-dma.h
@@ -112,6 +112,8 @@ struct iio_dma_buffer_queue {

void *driver_data;

+ unsigned int poll_wakup_flags;
+
unsigned int num_blocks;
struct iio_dma_buffer_block **blocks;
unsigned int max_offset;
@@ -145,6 +147,10 @@ int iio_dma_buffer_set_bytes_per_datum(struct iio_buffer *buffer, size_t bpd);
int iio_dma_buffer_set_length(struct iio_buffer *buffer, unsigned int length);
int iio_dma_buffer_request_update(struct iio_buffer *buffer);

+int iio_dma_buffer_write(struct iio_buffer *buf, size_t n,
+ const char __user *user_buffer);
+size_t iio_dma_buffer_space_available(struct iio_buffer *buf);
+
int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue,
struct device *dma_dev, const struct iio_dma_buffer_ops *ops,
void *driver_data);
diff --git a/include/linux/iio/buffer-dmaengine.h b/include/linux/iio/buffer-dmaengine.h
index 1fca8cdbf14e..1d70d35946b1 100644
--- a/include/linux/iio/buffer-dmaengine.h
+++ b/include/linux/iio/buffer-dmaengine.h
@@ -7,13 +7,15 @@
#ifndef __IIO_DMAENGINE_H__
#define __IIO_DMAENGINE_H__

+#include <linux/iio/buffer.h>
+
struct iio_dev;
struct iio_dma_buffer_ops;
struct device;

int devm_iio_dmaengine_buffer_setup(struct device *dev,
- struct iio_dev *indio_dev, const char *channel,
- const struct iio_dma_buffer_ops *ops,
+ struct iio_dev *indio_dev, enum iio_buffer_direction direction,
+ const char *channel, const struct iio_dma_buffer_ops *ops,
void *driver_data);

#endif
--
2.27.0