Commit 25d5cb4b authored by David Brownell's avatar David Brownell Committed by Linus Torvalds

spi: remove some spidev oops-on-rmmod paths

Somehow the spidev code forgot to include a critical mechanism: when the
underlying device is removed (e.g.  spi_master rmmod), open file
descriptors must be prevented from issuing new I/O requests to that
device.  On penalty of the oopsing reported by Sebastian Siewior
<bigeasy@tglx.de> ...

This is a partial fix, adding handshaking between the lower level (SPI
messaging) and the file operations using the spi_dev.  (It also fixes an
issue where reads and writes didn't return the number of bytes sent or
received.)

There's still a refcounting issue to be addressed (separately).
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Reported-by: default avatarSebastian Siewior <bigeasy@tglx.de>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 5c02b575
...@@ -68,6 +68,7 @@ static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG]; ...@@ -68,6 +68,7 @@ static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG];
struct spidev_data { struct spidev_data {
struct device dev; struct device dev;
spinlock_t spi_lock;
struct spi_device *spi; struct spi_device *spi;
struct list_head device_entry; struct list_head device_entry;
...@@ -85,12 +86,75 @@ MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); ...@@ -85,12 +86,75 @@ MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/*
* We can't use the standard synchronous wrappers for file I/O; we
* need to protect against async removal of the underlying spi_device.
*/
static void spidev_complete(void *arg)
{
complete(arg);
}
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);
int status;
message->complete = spidev_complete;
message->context = &done;
spin_lock_irq(&spidev->spi_lock);
if (spidev->spi == NULL)
status = -ESHUTDOWN;
else
status = spi_async(spidev->spi, message);
spin_unlock_irq(&spidev->spi_lock);
if (status == 0) {
wait_for_completion(&done);
status = message->status;
if (status == 0)
status = message->actual_length;
}
return status;
}
static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.tx_buf = spidev->buffer,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidev_sync(spidev, &m);
}
static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->buffer,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidev_sync(spidev, &m);
}
/*-------------------------------------------------------------------------*/
/* Read-only message with current device setup */ /* Read-only message with current device setup */
static ssize_t static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{ {
struct spidev_data *spidev; struct spidev_data *spidev;
struct spi_device *spi;
ssize_t status = 0; ssize_t status = 0;
/* chipselect only toggles at start or end of operation */ /* chipselect only toggles at start or end of operation */
...@@ -98,10 +162,9 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) ...@@ -98,10 +162,9 @@ spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
return -EMSGSIZE; return -EMSGSIZE;
spidev = filp->private_data; spidev = filp->private_data;
spi = spidev->spi;
mutex_lock(&spidev->buf_lock); mutex_lock(&spidev->buf_lock);
status = spi_read(spi, spidev->buffer, count); status = spidev_sync_read(spidev, count);
if (status == 0) { if (status == 0) {
unsigned long missing; unsigned long missing;
...@@ -122,7 +185,6 @@ spidev_write(struct file *filp, const char __user *buf, ...@@ -122,7 +185,6 @@ spidev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos) size_t count, loff_t *f_pos)
{ {
struct spidev_data *spidev; struct spidev_data *spidev;
struct spi_device *spi;
ssize_t status = 0; ssize_t status = 0;
unsigned long missing; unsigned long missing;
...@@ -131,12 +193,11 @@ spidev_write(struct file *filp, const char __user *buf, ...@@ -131,12 +193,11 @@ spidev_write(struct file *filp, const char __user *buf,
return -EMSGSIZE; return -EMSGSIZE;
spidev = filp->private_data; spidev = filp->private_data;
spi = spidev->spi;
mutex_lock(&spidev->buf_lock); mutex_lock(&spidev->buf_lock);
missing = copy_from_user(spidev->buffer, buf, count); missing = copy_from_user(spidev->buffer, buf, count);
if (missing == 0) { if (missing == 0) {
status = spi_write(spi, spidev->buffer, count); status = spidev_sync_write(spidev, count);
if (status == 0) if (status == 0)
status = count; status = count;
} else } else
...@@ -153,7 +214,6 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -153,7 +214,6 @@ static int spidev_message(struct spidev_data *spidev,
struct spi_transfer *k_xfers; struct spi_transfer *k_xfers;
struct spi_transfer *k_tmp; struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp; struct spi_ioc_transfer *u_tmp;
struct spi_device *spi = spidev->spi;
unsigned n, total; unsigned n, total;
u8 *buf; u8 *buf;
int status = -EFAULT; int status = -EFAULT;
...@@ -215,7 +275,7 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -215,7 +275,7 @@ static int spidev_message(struct spidev_data *spidev,
spi_message_add_tail(k_tmp, &msg); spi_message_add_tail(k_tmp, &msg);
} }
status = spi_sync(spi, &msg); status = spidev_sync(spidev, &msg);
if (status < 0) if (status < 0)
goto done; goto done;
...@@ -269,8 +329,16 @@ spidev_ioctl(struct inode *inode, struct file *filp, ...@@ -269,8 +329,16 @@ spidev_ioctl(struct inode *inode, struct file *filp,
if (err) if (err)
return -EFAULT; return -EFAULT;
/* guard against device removal before, or while,
* we issue this ioctl.
*/
spidev = filp->private_data; spidev = filp->private_data;
spi = spidev->spi; spin_lock_irq(&spidev->spi_lock);
spi = spi_dev_get(spidev->spi);
spin_unlock_irq(&spidev->spi_lock);
if (spi == NULL)
return -ESHUTDOWN;
switch (cmd) { switch (cmd) {
/* read requests */ /* read requests */
...@@ -356,8 +424,10 @@ spidev_ioctl(struct inode *inode, struct file *filp, ...@@ -356,8 +424,10 @@ spidev_ioctl(struct inode *inode, struct file *filp,
default: default:
/* segmented and/or full-duplex I/O request */ /* segmented and/or full-duplex I/O request */
if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
|| _IOC_DIR(cmd) != _IOC_WRITE) || _IOC_DIR(cmd) != _IOC_WRITE) {
return -ENOTTY; retval = -ENOTTY;
break;
}
tmp = _IOC_SIZE(cmd); tmp = _IOC_SIZE(cmd);
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {
...@@ -385,6 +455,7 @@ spidev_ioctl(struct inode *inode, struct file *filp, ...@@ -385,6 +455,7 @@ spidev_ioctl(struct inode *inode, struct file *filp,
kfree(ioc); kfree(ioc);
break; break;
} }
spi_dev_put(spi);
return retval; return retval;
} }
...@@ -488,6 +559,7 @@ static int spidev_probe(struct spi_device *spi) ...@@ -488,6 +559,7 @@ static int spidev_probe(struct spi_device *spi)
/* Initialize the driver data */ /* Initialize the driver data */
spidev->spi = spi; spidev->spi = spi;
spin_lock_init(&spidev->spi_lock);
mutex_init(&spidev->buf_lock); mutex_init(&spidev->buf_lock);
INIT_LIST_HEAD(&spidev->device_entry); INIT_LIST_HEAD(&spidev->device_entry);
...@@ -526,13 +598,17 @@ static int spidev_remove(struct spi_device *spi) ...@@ -526,13 +598,17 @@ static int spidev_remove(struct spi_device *spi)
{ {
struct spidev_data *spidev = dev_get_drvdata(&spi->dev); struct spidev_data *spidev = dev_get_drvdata(&spi->dev);
mutex_lock(&device_list_lock); /* make sure ops on existing fds can abort cleanly */
spin_lock_irq(&spidev->spi_lock);
spidev->spi = NULL;
spin_unlock_irq(&spidev->spi_lock);
/* prevent new opens */
mutex_lock(&device_list_lock);
list_del(&spidev->device_entry); list_del(&spidev->device_entry);
dev_set_drvdata(&spi->dev, NULL); dev_set_drvdata(&spi->dev, NULL);
clear_bit(MINOR(spidev->dev.devt), minors); clear_bit(MINOR(spidev->dev.devt), minors);
device_unregister(&spidev->dev); device_unregister(&spidev->dev);
mutex_unlock(&device_list_lock); mutex_unlock(&device_list_lock);
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment