Re: [PATCH v3 1/4] i2c: add helpers to ease DMA handling

From: Jonathan Cameron
Date: Sun Jul 23 2017 - 07:23:12 EST


On Tue, 18 Jul 2017 12:23:36 +0200
Wolfram Sang <wsa+renesas@xxxxxxxxxxxxxxxxxxxx> wrote:

> One helper checks if DMA is suitable and optionally creates a bounce
> buffer, if not. The other function returns the bounce buffer and makes
> sure the data is properly copied back to the message.
>
> Signed-off-by: Wolfram Sang <wsa+renesas@xxxxxxxxxxxxxxxxxxxx>
My knowledge of the exact requirements for dma on all platforms isn't
all that good. Mostly it's based around the assumption that if one
forces a heap allocation and makes sure nothing else is in the
cacheline all is good.

I like the basic idea of this patch set a lot btw!

Jonathan
> ---
> Changes since v2:
>
> * rebased to v4.13-rc1
> * helper functions are not inlined anymore but moved to i2c core
> * __must_check has been added to the buffer check helper
> * the release function has been renamed to contain 'dma' as well
>
> drivers/i2c/i2c-core-base.c | 68 +++++++++++++++++++++++++++++++++++++++++++++
> include/linux/i2c.h | 5 ++++
> 2 files changed, 73 insertions(+)
>
> diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
> index c89dac7fd2e7b7..7326a9d2e4eb69 100644
> --- a/drivers/i2c/i2c-core-base.c
> +++ b/drivers/i2c/i2c-core-base.c
> @@ -34,6 +34,7 @@
> #include <linux/irqflags.h>
> #include <linux/jump_label.h>
> #include <linux/kernel.h>
> +#include <linux/mm.h>
> #include <linux/module.h>
> #include <linux/mutex.h>
> #include <linux/of_device.h>
> @@ -44,6 +45,7 @@
> #include <linux/pm_wakeirq.h>
> #include <linux/property.h>
> #include <linux/rwsem.h>
> +#include <linux/sched/task_stack.h>
> #include <linux/slab.h>
>
> #include "i2c-core.h"
> @@ -2240,6 +2242,72 @@ void i2c_put_adapter(struct i2c_adapter *adap)
> }
> EXPORT_SYMBOL(i2c_put_adapter);
>
> +/**
> + * i2c_check_msg_for_dma() - check if a message is suitable for DMA
> + * @msg: the message to be checked
> + * @threshold: the amount of byte from which using DMA makes sense
> + * @ptr_for_bounce_buf: if not NULL, a bounce buffer will be attached to this
> + * ptr, if needed. The bounce buffer must be freed by the
> + * caller using i2c_release_dma_bounce_buf().
> + *
> + * Return: -ERANGE if message is smaller than threshold
> + * -EFAULT if message buffer is not DMA capable and no bounce buffer
> + * was requested
> + * -ENOMEM if a bounce buffer could not be created
> + * 0 if message is suitable for DMA
> + *
> + * The return value must be checked.
> + *
> + * Note: This function should only be called from process context! It uses
> + * helper functions which work on the 'current' task.
> + */
> +int i2c_check_msg_for_dma(struct i2c_msg *msg, unsigned int threshold,
> + u8 **ptr_for_bounce_buf)
> +{
> + if (ptr_for_bounce_buf)
> + *ptr_for_bounce_buf = NULL;
> +
> + if (msg->len < threshold)
> + return -ERANGE;
> +
> + if (!virt_addr_valid(msg->buf) || object_is_on_stack(msg->buf)) {
> + pr_debug("msg buffer to 0x%04x is not DMA safe%s\n", msg->addr,
> + ptr_for_bounce_buf ? ", trying bounce buffer" : "");
> + if (ptr_for_bounce_buf) {
> + if (msg->flags & I2C_M_RD)
> + *ptr_for_bounce_buf = kzalloc(msg->len, GFP_KERNEL);
> + else
> + *ptr_for_bounce_buf = kmemdup(msg->buf, msg->len,
> + GFP_KERNEL);
> + if (!*ptr_for_bounce_buf)
> + return -ENOMEM;
> + } else {
> + return -EFAULT;
> + }
> + }
I'm trying to get my head around whether this is a sufficient set of conditions
for a dma safe buffer and I'm not sure it is.

We go to a lot of effort with SPI buffers to ensure they are in their own cache
lines. Do we not need to do the same here? The issue would be the
classic case of embedding a buffer inside a larger structure which is on
the stack. Need the magic of __cacheline_aligned to force it into it's
own line for devices with dma-incoherent caches.
DMA-API-HOWTO.txt (not read that for a while at it is a rather good
at describing this stuff these days!)

So that one is easy enough to check by checking if it is cache line aligned,
but what do you do to know there is nothing much after it?

Not sure there is a way around this short of auditing i2c drivers to
change any that do this.
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(i2c_check_msg_for_dma);
> +
> +/**
> + * i2c_release_bounce_buf - copy data back from bounce buffer and release it
> + * @msg: the message to be copied back to
> + * @bounce_buf: the bounce buffer obtained from i2c_check_msg_for_dma().
> + * May be NULL.
> + */
> +void i2c_release_dma_bounce_buf(struct i2c_msg *msg, u8 *bounce_buf)
> +{
> + if (!bounce_buf)
> + return;
> +
> + if (msg->flags & I2C_M_RD)
> + memcpy(msg->buf, bounce_buf, msg->len);
> +
> + kfree(bounce_buf);
> +}
> +EXPORT_SYMBOL_GPL(i2c_release_bounce_buf);
> +
> MODULE_AUTHOR("Simon G. Vogl <simon@xxxxxxxxxxxxxxxxx>");
> MODULE_DESCRIPTION("I2C-Bus main module");
> MODULE_LICENSE("GPL");
> diff --git a/include/linux/i2c.h b/include/linux/i2c.h
> index 00ca5b86a753f8..ac02287b6c0d8f 100644
> --- a/include/linux/i2c.h
> +++ b/include/linux/i2c.h
> @@ -766,6 +766,11 @@ static inline u8 i2c_8bit_addr_from_msg(const struct i2c_msg *msg)
> return (msg->addr << 1) | (msg->flags & I2C_M_RD ? 1 : 0);
> }
>
> +int __must_check i2c_check_msg_for_dma(struct i2c_msg *msg, unsigned int threshold,
> + u8 **ptr_for_bounce_buf);
> +
> +void i2c_release_dma_bounce_buf(struct i2c_msg *msg, u8 *bounce_buf);
> +
> int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr);
> /**
> * module_i2c_driver() - Helper macro for registering a modular I2C driver