Skip to content

Commit

Permalink
spi: spidev: Use separate TX and RX bounce buffers
Browse files Browse the repository at this point in the history
By using separate TX and RX bounce buffers, we avoid potential cache
flush and invalidation sequence issue that may be encountered when a
single bounce buffer is shared between TX and RX

Signed-off-by: Ray Jui <rjui@broadcom.com>
Reviewed-by: JD (Jiandong) Zheng <jdzheng@broadcom.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
  • Loading branch information
Ray Jui authored and Mark Brown committed Oct 13, 2014
1 parent a2285b8 commit 865f6d1
Showing 1 changed file with 52 additions and 27 deletions.
79 changes: 52 additions & 27 deletions drivers/spi/spidev.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,11 @@ struct spidev_data {
struct spi_device *spi;
struct list_head device_entry;

/* buffer is NULL unless this device is open (users > 0) */
/* TX/RX buffers are NULL unless this device is open (users > 0) */
struct mutex buf_lock;
unsigned users;
u8 *buffer;
u8 *tx_buffer;
u8 *rx_buffer;
};

static LIST_HEAD(device_list);
Expand Down Expand Up @@ -135,7 +136,7 @@ static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.tx_buf = spidev->buffer,
.tx_buf = spidev->tx_buffer,
.len = len,
};
struct spi_message m;
Expand All @@ -149,7 +150,7 @@ static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->buffer,
.rx_buf = spidev->rx_buffer,
.len = len,
};
struct spi_message m;
Expand Down Expand Up @@ -179,7 +180,7 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
if (status > 0) {
unsigned long missing;

missing = copy_to_user(buf, spidev->buffer, status);
missing = copy_to_user(buf, spidev->rx_buffer, status);
if (missing == status)
status = -EFAULT;
else
Expand All @@ -206,7 +207,7 @@ spidev_write(struct file *filp, const char __user *buf,
spidev = filp->private_data;

mutex_lock(&spidev->buf_lock);
missing = copy_from_user(spidev->buffer, buf, count);
missing = copy_from_user(spidev->tx_buffer, buf, count);
if (missing == 0)
status = spidev_sync_write(spidev, count);
else
Expand All @@ -224,7 +225,7 @@ static int spidev_message(struct spidev_data *spidev,
struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp;
unsigned n, total;
u8 *buf;
u8 *tx_buf, *rx_buf;
int status = -EFAULT;

spi_message_init(&msg);
Expand All @@ -236,7 +237,8 @@ static int spidev_message(struct spidev_data *spidev,
* We walk the array of user-provided transfers, using each one
* to initialize a kernel version of the same transfer.
*/
buf = spidev->buffer;
tx_buf = spidev->tx_buffer;
rx_buf = spidev->rx_buffer;
total = 0;
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
n;
Expand All @@ -250,20 +252,21 @@ static int spidev_message(struct spidev_data *spidev,
}

if (u_tmp->rx_buf) {
k_tmp->rx_buf = buf;
k_tmp->rx_buf = rx_buf;
if (!access_ok(VERIFY_WRITE, (u8 __user *)
(uintptr_t) u_tmp->rx_buf,
u_tmp->len))
goto done;
}
if (u_tmp->tx_buf) {
k_tmp->tx_buf = buf;
if (copy_from_user(buf, (const u8 __user *)
k_tmp->tx_buf = tx_buf;
if (copy_from_user(tx_buf, (const u8 __user *)
(uintptr_t) u_tmp->tx_buf,
u_tmp->len))
goto done;
}
buf += k_tmp->len;
tx_buf += k_tmp->len;
rx_buf += k_tmp->len;

k_tmp->cs_change = !!u_tmp->cs_change;
k_tmp->tx_nbits = u_tmp->tx_nbits;
Expand All @@ -290,17 +293,17 @@ static int spidev_message(struct spidev_data *spidev,
goto done;

/* copy any rx data out of bounce buffer */
buf = spidev->buffer;
rx_buf = spidev->rx_buffer;
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
if (u_tmp->rx_buf) {
if (__copy_to_user((u8 __user *)
(uintptr_t) u_tmp->rx_buf, buf,
(uintptr_t) u_tmp->rx_buf, rx_buf,
u_tmp->len)) {
status = -EFAULT;
goto done;
}
}
buf += u_tmp->len;
rx_buf += u_tmp->len;
}
status = total;

Expand Down Expand Up @@ -508,22 +511,41 @@ static int spidev_open(struct inode *inode, struct file *filp)
break;
}
}
if (status == 0) {
if (!spidev->buffer) {
spidev->buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->buffer) {

if (status) {
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
goto err_find_dev;
}

if (!spidev->tx_buffer) {
spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->tx_buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM;
goto err_find_dev;
}
}
if (status == 0) {
spidev->users++;
filp->private_data = spidev;
nonseekable_open(inode, filp);

if (!spidev->rx_buffer) {
spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->rx_buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM;
goto err_alloc_rx_buf;
}
} else
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
}

spidev->users++;
filp->private_data = spidev;
nonseekable_open(inode, filp);

mutex_unlock(&device_list_lock);
return 0;

err_alloc_rx_buf:
kfree(spidev->tx_buffer);
spidev->tx_buffer = NULL;
err_find_dev:
mutex_unlock(&device_list_lock);
return status;
}
Expand All @@ -542,8 +564,11 @@ static int spidev_release(struct inode *inode, struct file *filp)
if (!spidev->users) {
int dofree;

kfree(spidev->buffer);
spidev->buffer = NULL;
kfree(spidev->tx_buffer);
spidev->tx_buffer = NULL;

kfree(spidev->rx_buffer);
spidev->rx_buffer = NULL;

/* ... after we unbound from the underlying device? */
spin_lock_irq(&spidev->spi_lock);
Expand Down

0 comments on commit 865f6d1

Please sign in to comment.