2019-05-27 06:55:06 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2007-05-08 07:32:15 +00:00
|
|
|
/*
|
2011-06-06 07:16:30 +00:00
|
|
|
* Simple synchronous userspace interface to SPI devices
|
2007-05-08 07:32:15 +00:00
|
|
|
*
|
|
|
|
* Copyright (C) 2006 SWAPP
|
|
|
|
* Andrea Paterniani <a.paterniani@swapp-eng.it>
|
|
|
|
* Copyright (C) 2007 David Brownell (simplification, cleanup)
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/ioctl.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/device.h>
|
2008-06-06 05:45:50 +00:00
|
|
|
#include <linux/err.h>
|
2007-05-08 07:32:15 +00:00
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/errno.h>
|
2022-03-23 14:02:14 +00:00
|
|
|
#include <linux/mod_devicetable.h>
|
|
|
|
#include <linux/module.h>
|
2007-05-08 07:32:15 +00:00
|
|
|
#include <linux/mutex.h>
|
2022-03-23 14:02:14 +00:00
|
|
|
#include <linux/property.h>
|
2007-05-08 07:32:15 +00:00
|
|
|
#include <linux/slab.h>
|
2011-02-03 08:37:18 +00:00
|
|
|
#include <linux/compat.h>
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
#include <linux/spi/spi.h>
|
|
|
|
#include <linux/spi/spidev.h>
|
|
|
|
|
2013-10-14 01:36:54 +00:00
|
|
|
#include <linux/uaccess.h>
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
tree-wide: fix comment/printk typos
"gadget", "through", "command", "maintain", "maintain", "controller", "address",
"between", "initiali[zs]e", "instead", "function", "select", "already",
"equal", "access", "management", "hierarchy", "registration", "interest",
"relative", "memory", "offset", "already",
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2010-11-01 19:38:34 +00:00
|
|
|
* This supports access to SPI devices using normal userspace I/O calls.
|
2007-05-08 07:32:15 +00:00
|
|
|
* Note that while traditional UNIX/POSIX I/O semantics are half duplex,
|
|
|
|
* and often mask message boundaries, full SPI support requires full duplex
|
2009-10-22 19:34:29 +00:00
|
|
|
* transfers. There are several kinds of internal message boundaries to
|
2007-05-08 07:32:15 +00:00
|
|
|
* handle chipselect management and other protocol options.
|
|
|
|
*
|
|
|
|
* SPI has a character major number assigned. We allocate minor numbers
|
|
|
|
* dynamically using a bitmask. You must use hotplug tools, such as udev
|
|
|
|
* (or mdev with busybox) to create and destroy the /dev/spidevB.C device
|
|
|
|
* nodes, since there is no fixed association of minor numbers with any
|
|
|
|
* particular SPI bus or device.
|
|
|
|
*/
|
|
|
|
#define SPIDEV_MAJOR 153 /* assigned */
|
|
|
|
#define N_SPI_MINORS 32 /* ... up to 256 */
|
|
|
|
|
2009-12-14 22:20:23 +00:00
|
|
|
static DECLARE_BITMAP(minors, N_SPI_MINORS);
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2022-03-23 14:02:13 +00:00
|
|
|
static_assert(N_SPI_MINORS > 0 && N_SPI_MINORS <= 256);
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2007-07-31 07:38:43 +00:00
|
|
|
/* Bit masks for spi_device.mode management. Note that incorrect
|
2009-06-30 18:41:26 +00:00
|
|
|
* settings for some settings can cause *lots* of trouble for other
|
|
|
|
* devices on a shared bus:
|
2007-07-31 07:38:43 +00:00
|
|
|
*
|
2009-06-30 18:41:26 +00:00
|
|
|
* - CS_HIGH ... this device will be active when it shouldn't be
|
|
|
|
* - 3WIRE ... when active, it won't behave as it should
|
|
|
|
* - NO_CS ... there will be no explicit message boundaries; this
|
|
|
|
* is completely incompatible with the shared bus model
|
|
|
|
* - READY ... transfers may proceed when they shouldn't.
|
|
|
|
*
|
|
|
|
* REVISIT should changing those flags be privileged?
|
2007-07-31 07:38:43 +00:00
|
|
|
*/
|
2021-05-10 13:12:12 +00:00
|
|
|
#define SPI_MODE_MASK (SPI_MODE_X_MASK | SPI_CS_HIGH \
|
2009-06-30 18:41:26 +00:00
|
|
|
| SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \
|
2014-02-25 10:40:17 +00:00
|
|
|
| SPI_NO_CS | SPI_READY | SPI_TX_DUAL \
|
2020-04-16 10:18:34 +00:00
|
|
|
| SPI_TX_QUAD | SPI_TX_OCTAL | SPI_RX_DUAL \
|
2022-04-11 18:45:28 +00:00
|
|
|
| SPI_RX_QUAD | SPI_RX_OCTAL \
|
|
|
|
| SPI_RX_CPHA_FLIP)
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
struct spidev_data {
|
2008-06-06 05:45:50 +00:00
|
|
|
dev_t devt;
|
2023-01-06 10:07:18 +00:00
|
|
|
struct mutex spi_lock;
|
2007-05-08 07:32:15 +00:00
|
|
|
struct spi_device *spi;
|
|
|
|
struct list_head device_entry;
|
|
|
|
|
2014-10-09 18:19:25 +00:00
|
|
|
/* TX/RX buffers are NULL unless this device is open (users > 0) */
|
2007-05-08 07:32:15 +00:00
|
|
|
struct mutex buf_lock;
|
|
|
|
unsigned users;
|
2014-10-09 18:19:25 +00:00
|
|
|
u8 *tx_buffer;
|
|
|
|
u8 *rx_buffer;
|
2014-11-08 10:28:10 +00:00
|
|
|
u32 speed_hz;
|
2007-05-08 07:32:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static LIST_HEAD(device_list);
|
|
|
|
static DEFINE_MUTEX(device_list_lock);
|
|
|
|
|
|
|
|
static unsigned bufsiz = 4096;
|
|
|
|
module_param(bufsiz, uint, S_IRUGO);
|
|
|
|
MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
2023-01-16 14:41:49 +00:00
|
|
|
static ssize_t
|
|
|
|
spidev_sync_unlocked(struct spi_device *spi, struct spi_message *message)
|
|
|
|
{
|
|
|
|
ssize_t status;
|
|
|
|
|
|
|
|
status = spi_sync(spi, message);
|
|
|
|
if (status == 0)
|
|
|
|
status = message->actual_length;
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2008-05-23 20:05:03 +00:00
|
|
|
static ssize_t
|
|
|
|
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
|
|
|
|
{
|
2023-01-16 14:41:49 +00:00
|
|
|
ssize_t status;
|
2015-04-23 07:56:01 +00:00
|
|
|
struct spi_device *spi;
|
2008-05-23 20:05:03 +00:00
|
|
|
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_lock(&spidev->spi_lock);
|
2015-04-23 07:56:01 +00:00
|
|
|
spi = spidev->spi;
|
|
|
|
|
|
|
|
if (spi == NULL)
|
2008-05-23 20:05:03 +00:00
|
|
|
status = -ESHUTDOWN;
|
|
|
|
else
|
2023-01-16 14:41:49 +00:00
|
|
|
status = spidev_sync_unlocked(spi, message);
|
2015-04-23 07:56:01 +00:00
|
|
|
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ssize_t
|
|
|
|
spidev_sync_write(struct spidev_data *spidev, size_t len)
|
|
|
|
{
|
|
|
|
struct spi_transfer t = {
|
2014-10-09 18:19:25 +00:00
|
|
|
.tx_buf = spidev->tx_buffer,
|
2008-05-23 20:05:03 +00:00
|
|
|
.len = len,
|
2014-11-08 10:28:10 +00:00
|
|
|
.speed_hz = spidev->speed_hz,
|
2008-05-23 20:05:03 +00:00
|
|
|
};
|
|
|
|
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 = {
|
2014-10-09 18:19:25 +00:00
|
|
|
.rx_buf = spidev->rx_buffer,
|
2008-05-23 20:05:03 +00:00
|
|
|
.len = len,
|
2014-11-08 10:28:10 +00:00
|
|
|
.speed_hz = spidev->speed_hz,
|
2008-05-23 20:05:03 +00:00
|
|
|
};
|
|
|
|
struct spi_message m;
|
|
|
|
|
|
|
|
spi_message_init(&m);
|
|
|
|
spi_message_add_tail(&t, &m);
|
|
|
|
return spidev_sync(spidev, &m);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
/* Read-only message with current device setup */
|
|
|
|
static ssize_t
|
|
|
|
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
|
|
|
|
{
|
|
|
|
struct spidev_data *spidev;
|
2020-09-09 06:08:24 +00:00
|
|
|
ssize_t status;
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
/* chipselect only toggles at start or end of operation */
|
|
|
|
if (count > bufsiz)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
spidev = filp->private_data;
|
|
|
|
|
|
|
|
mutex_lock(&spidev->buf_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
status = spidev_sync_read(spidev, count);
|
2008-07-04 16:59:56 +00:00
|
|
|
if (status > 0) {
|
2007-05-08 07:32:15 +00:00
|
|
|
unsigned long missing;
|
|
|
|
|
2014-10-09 18:19:25 +00:00
|
|
|
missing = copy_to_user(buf, spidev->rx_buffer, status);
|
2008-07-04 16:59:56 +00:00
|
|
|
if (missing == status)
|
2007-05-08 07:32:15 +00:00
|
|
|
status = -EFAULT;
|
|
|
|
else
|
2008-07-04 16:59:56 +00:00
|
|
|
status = status - missing;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
mutex_unlock(&spidev->buf_lock);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write-only message with current device setup */
|
|
|
|
static ssize_t
|
|
|
|
spidev_write(struct file *filp, const char __user *buf,
|
|
|
|
size_t count, loff_t *f_pos)
|
|
|
|
{
|
|
|
|
struct spidev_data *spidev;
|
2020-09-09 06:08:24 +00:00
|
|
|
ssize_t status;
|
2007-05-08 07:32:15 +00:00
|
|
|
unsigned long missing;
|
|
|
|
|
|
|
|
/* chipselect only toggles at start or end of operation */
|
|
|
|
if (count > bufsiz)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
spidev = filp->private_data;
|
|
|
|
|
|
|
|
mutex_lock(&spidev->buf_lock);
|
2014-10-09 18:19:25 +00:00
|
|
|
missing = copy_from_user(spidev->tx_buffer, buf, count);
|
2013-10-14 01:36:54 +00:00
|
|
|
if (missing == 0)
|
2008-05-23 20:05:03 +00:00
|
|
|
status = spidev_sync_write(spidev, count);
|
2013-10-14 01:36:54 +00:00
|
|
|
else
|
2007-05-08 07:32:15 +00:00
|
|
|
status = -EFAULT;
|
|
|
|
mutex_unlock(&spidev->buf_lock);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int spidev_message(struct spidev_data *spidev,
|
|
|
|
struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
|
|
|
|
{
|
|
|
|
struct spi_message msg;
|
|
|
|
struct spi_transfer *k_xfers;
|
|
|
|
struct spi_transfer *k_tmp;
|
|
|
|
struct spi_ioc_transfer *u_tmp;
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
unsigned n, total, tx_total, rx_total;
|
2014-10-09 18:19:25 +00:00
|
|
|
u8 *tx_buf, *rx_buf;
|
2007-05-08 07:32:15 +00:00
|
|
|
int status = -EFAULT;
|
|
|
|
|
|
|
|
spi_message_init(&msg);
|
|
|
|
k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
|
|
|
|
if (k_xfers == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* Construct spi_message, copying any tx data to bounce buffer.
|
|
|
|
* We walk the array of user-provided transfers, using each one
|
|
|
|
* to initialize a kernel version of the same transfer.
|
|
|
|
*/
|
2014-10-09 18:19:25 +00:00
|
|
|
tx_buf = spidev->tx_buffer;
|
|
|
|
rx_buf = spidev->rx_buffer;
|
2007-05-08 07:32:15 +00:00
|
|
|
total = 0;
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
tx_total = 0;
|
|
|
|
rx_total = 0;
|
2007-05-08 07:32:15 +00:00
|
|
|
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
|
|
|
|
n;
|
|
|
|
n--, k_tmp++, u_tmp++) {
|
2020-07-28 10:08:32 +00:00
|
|
|
/* Ensure that also following allocations from rx_buf/tx_buf will meet
|
|
|
|
* DMA alignment requirements.
|
|
|
|
*/
|
|
|
|
unsigned int len_aligned = ALIGN(u_tmp->len, ARCH_KMALLOC_MINALIGN);
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
k_tmp->len = u_tmp->len;
|
|
|
|
|
2007-05-23 20:57:39 +00:00
|
|
|
total += k_tmp->len;
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
/* Since the function returns the total length of transfers
|
|
|
|
* on success, restrict the total to positive int values to
|
2015-03-23 17:50:27 +00:00
|
|
|
* avoid the return value looking like an error. Also check
|
|
|
|
* each transfer length to avoid arithmetic overflow.
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
*/
|
2015-03-23 17:50:27 +00:00
|
|
|
if (total > INT_MAX || k_tmp->len > INT_MAX) {
|
2007-05-23 20:57:39 +00:00
|
|
|
status = -EMSGSIZE;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
if (u_tmp->rx_buf) {
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
/* this transfer needs space in RX bounce buffer */
|
2020-07-28 10:08:32 +00:00
|
|
|
rx_total += len_aligned;
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
if (rx_total > bufsiz) {
|
|
|
|
status = -EMSGSIZE;
|
|
|
|
goto done;
|
|
|
|
}
|
2014-10-09 18:19:25 +00:00
|
|
|
k_tmp->rx_buf = rx_buf;
|
2020-07-28 10:08:32 +00:00
|
|
|
rx_buf += len_aligned;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
if (u_tmp->tx_buf) {
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
/* this transfer needs space in TX bounce buffer */
|
2020-07-28 10:08:32 +00:00
|
|
|
tx_total += len_aligned;
|
spi: spidev: only use up TX/RX bounce buffer space when needed
This patch changes the way space is reserved in spidev's pre-allocated
TX and RX bounce buffers to avoid wasting space in the buffers for an
SPI message consisting of multiple, half-duplex transfers in different
directions.
Background:
spidev data structures have separate, pre-allocated TX and RX bounce
buffers (`spidev->tx_buffer` and `spidev->rx_buffer`) of fixed size
(`bufsiz`). The `SPI_IOC_MESSAGE(N)` ioctl processing uses a kernel
copy of the N `struct spi_ioc_transfer` elements copied from the
userspace ioctl arg pointer. In these elements: `.len` is the length of
transfer in bytes; `.rx_buf` is either a userspace pointer to a buffer
to copy the RX data to or is set to 0 to discard the data; and `.tx_buf`
is either a userspace pointer to TX data supplied by the user or is set
to 0 to transmit zeros for this transfer.
`spidev_message()` uses the array of N `struct spi_ioc_transfer`
elements to construct a kernel SPI message consisting of a `struct
spi_message` containing a linked list (allocated as an array) of N
`struct spi_transfer` elements. This involves iterating through the
`struct spi_ioc_transfer` and `struct spi_transfer` elements (variables
`u_tmp` and `k_tmp` respectively). Before the first iteration,
variables `tx_buf` and `rx_buf` point to the start of the TX and RX
bounce buffers `spidev->tx_buffer` and `spidev->rx_buffer` and variable
`total` is set to 0. These variables keep track of the next available
space in the bounce buffers and the total length of the SPI message.
Each iteration checks that there is enough room left in the buffers for
the transfer. If `u_tmp->rx_buf` is non-zero, `k_tmp->rx_buf` is set to
`rx_buf`, otherwise it remains set to NULL. If `u_tmp->tx_buf` is
non-zero, `k_tmp->tx_buf` is set to `tx_buf` and the userspace TX data
copied there, otherwise it remains set to NULL. The variables `total`,
`rx_buf` and `tx_buf` are advanced by the length of the transfer.
The "problem":
While iterating through the transfers, the local bounce buffer "free
space" pointer variables `tx_buf` and `rx_buf` are always advanced by
the length of the transfer. If `u_tmp->rx_buf` is 0 (so `k_tmp->rx_buf`
is NULL), then `rx_buf` is advanced unnecessarily and that part of
`spidev->rx_buffer` is wasted. Similarly, if `u_tmp->tx_buf` is 0 (so
`k_tmp->tx_buf` is NULL), part of `spidev->tx_buffer` is wasted.
What this patch does:
To avoid wasting space unnecessarily in the RX bounce buffer, only
advance `rx_buf` by the transfer length if `u_tmp->rx_buf` is non-zero.
Similarly, to avoid wasting space unnecessarily in the TX bounce buffer,
only advance `tx_buf` if `u_tmp->tx_buf is non-zero. To avoid pointer
subtraction, use new variables `rx_total` and `tx_total` to keep track
of the amount of space allocated in each of the bounce buffers. If
these exceed the available space, a `-EMSGSIZE` error will be returned.
Limit the total length of the transfers (tracked by variable `total`) to
`INT_MAX` instead of `bufsiz`, returning an `-EMSGSIZE` error if
exceeded. The total length is returned by `spidev_message()` on success
and we want that to be non-negative. The message size limits for the
`SPI_IOC_MESSAGE(N)` ioctl are now as follows:
(a) total length of transfers is <= INTMAX;
(b) total length of transfers with non-NULL rx_buf is <= bufsiz;
(c) total length of transfers with non-NULL tx_buf is <= bufsiz.
Some transfers may have NULL rx_buf and NULL tx_buf.
If the transfer is completed successfully by the SPI core,
`spidev_message()` iterates through the transfers to copy any RX data
from the bounce buffer back to userspace on those transfers where
`u_tmp->rx_buf` is non-zero. The variable `rx_buf` is again used to
keep track of the corresponding positions in the bounce buffer. Now it
is only advanced for those transfers that use the RX bounce buffer.
Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Mark Brown <broonie@kernel.org>
2015-02-16 15:00:47 +00:00
|
|
|
if (tx_total > bufsiz) {
|
|
|
|
status = -EMSGSIZE;
|
|
|
|
goto done;
|
|
|
|
}
|
2014-10-09 18:19:25 +00:00
|
|
|
k_tmp->tx_buf = tx_buf;
|
|
|
|
if (copy_from_user(tx_buf, (const u8 __user *)
|
2007-10-29 05:11:28 +00:00
|
|
|
(uintptr_t) u_tmp->tx_buf,
|
2007-05-08 07:32:15 +00:00
|
|
|
u_tmp->len))
|
|
|
|
goto done;
|
2020-07-28 10:08:32 +00:00
|
|
|
tx_buf += len_aligned;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
k_tmp->cs_change = !!u_tmp->cs_change;
|
2014-02-25 10:40:17 +00:00
|
|
|
k_tmp->tx_nbits = u_tmp->tx_nbits;
|
|
|
|
k_tmp->rx_nbits = u_tmp->rx_nbits;
|
2007-05-08 07:32:15 +00:00
|
|
|
k_tmp->bits_per_word = u_tmp->bits_per_word;
|
2019-09-26 10:51:41 +00:00
|
|
|
k_tmp->delay.value = u_tmp->delay_usecs;
|
|
|
|
k_tmp->delay.unit = SPI_DELAY_UNIT_USECS;
|
2007-05-08 07:32:15 +00:00
|
|
|
k_tmp->speed_hz = u_tmp->speed_hz;
|
2019-09-26 10:51:34 +00:00
|
|
|
k_tmp->word_delay.value = u_tmp->word_delay_usecs;
|
|
|
|
k_tmp->word_delay.unit = SPI_DELAY_UNIT_USECS;
|
2014-11-08 10:28:10 +00:00
|
|
|
if (!k_tmp->speed_hz)
|
|
|
|
k_tmp->speed_hz = spidev->speed_hz;
|
2007-05-08 07:32:15 +00:00
|
|
|
#ifdef VERBOSE
|
2009-12-07 14:28:43 +00:00
|
|
|
dev_dbg(&spidev->spi->dev,
|
2019-03-07 14:29:42 +00:00
|
|
|
" xfer len %u %s%s%s%dbits %u usec %u usec %uHz\n",
|
2020-02-29 16:18:40 +00:00
|
|
|
k_tmp->len,
|
|
|
|
k_tmp->rx_buf ? "rx " : "",
|
|
|
|
k_tmp->tx_buf ? "tx " : "",
|
|
|
|
k_tmp->cs_change ? "cs " : "",
|
|
|
|
k_tmp->bits_per_word ? : spidev->spi->bits_per_word,
|
|
|
|
k_tmp->delay.value,
|
|
|
|
k_tmp->word_delay.value,
|
|
|
|
k_tmp->speed_hz ? : spidev->spi->max_speed_hz);
|
2007-05-08 07:32:15 +00:00
|
|
|
#endif
|
|
|
|
spi_message_add_tail(k_tmp, &msg);
|
|
|
|
}
|
|
|
|
|
2023-01-16 14:41:49 +00:00
|
|
|
status = spidev_sync_unlocked(spidev->spi, &msg);
|
2007-05-08 07:32:15 +00:00
|
|
|
if (status < 0)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
/* copy any rx data out of bounce buffer */
|
2020-07-28 10:08:32 +00:00
|
|
|
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
|
|
|
|
n;
|
|
|
|
n--, k_tmp++, u_tmp++) {
|
2007-05-08 07:32:15 +00:00
|
|
|
if (u_tmp->rx_buf) {
|
2017-04-20 19:47:34 +00:00
|
|
|
if (copy_to_user((u8 __user *)
|
2020-07-28 10:08:32 +00:00
|
|
|
(uintptr_t) u_tmp->rx_buf, k_tmp->rx_buf,
|
2007-05-08 07:32:15 +00:00
|
|
|
u_tmp->len)) {
|
|
|
|
status = -EFAULT;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
status = total;
|
|
|
|
|
|
|
|
done:
|
|
|
|
kfree(k_xfers);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2015-01-30 18:43:33 +00:00
|
|
|
static struct spi_ioc_transfer *
|
|
|
|
spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc,
|
|
|
|
unsigned *n_ioc)
|
|
|
|
{
|
|
|
|
u32 tmp;
|
|
|
|
|
|
|
|
/* Check type, command number and direction */
|
|
|
|
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC
|
|
|
|
|| _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
|
|
|
|
|| _IOC_DIR(cmd) != _IOC_WRITE)
|
|
|
|
return ERR_PTR(-ENOTTY);
|
|
|
|
|
|
|
|
tmp = _IOC_SIZE(cmd);
|
|
|
|
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
*n_ioc = tmp / sizeof(struct spi_ioc_transfer);
|
|
|
|
if (*n_ioc == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* copy into scratch area */
|
2017-05-06 15:42:17 +00:00
|
|
|
return memdup_user(u_ioc, tmp);
|
2015-01-30 18:43:33 +00:00
|
|
|
}
|
|
|
|
|
2008-07-24 04:29:55 +00:00
|
|
|
static long
|
|
|
|
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
2007-05-08 07:32:15 +00:00
|
|
|
{
|
|
|
|
int retval = 0;
|
|
|
|
struct spidev_data *spidev;
|
|
|
|
struct spi_device *spi;
|
|
|
|
u32 tmp;
|
|
|
|
unsigned n_ioc;
|
|
|
|
struct spi_ioc_transfer *ioc;
|
|
|
|
|
|
|
|
/* Check type and command number */
|
|
|
|
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
|
|
|
|
return -ENOTTY;
|
|
|
|
|
2008-05-23 20:05:03 +00:00
|
|
|
/* guard against device removal before, or while,
|
|
|
|
* we issue this ioctl.
|
|
|
|
*/
|
2007-05-08 07:32:15 +00:00
|
|
|
spidev = filp->private_data;
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_lock(&spidev->spi_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
spi = spi_dev_get(spidev->spi);
|
2023-01-06 10:07:18 +00:00
|
|
|
if (spi == NULL) {
|
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
return -ESHUTDOWN;
|
2023-01-06 10:07:18 +00:00
|
|
|
}
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2008-07-24 04:29:55 +00:00
|
|
|
/* use the buffer lock here for triple duty:
|
|
|
|
* - prevent I/O (from us) so calling spi_setup() is safe;
|
|
|
|
* - prevent concurrent SPI_IOC_WR_* from morphing
|
|
|
|
* data fields while SPI_IOC_RD_* reads them;
|
|
|
|
* - SPI_IOC_MESSAGE needs the buffer locked "normally".
|
|
|
|
*/
|
|
|
|
mutex_lock(&spidev->buf_lock);
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
switch (cmd) {
|
|
|
|
/* read requests */
|
|
|
|
case SPI_IOC_RD_MODE:
|
2014-02-25 10:40:17 +00:00
|
|
|
case SPI_IOC_RD_MODE32:
|
2022-11-30 16:29:27 +00:00
|
|
|
tmp = spi->mode;
|
|
|
|
|
|
|
|
{
|
|
|
|
struct spi_controller *ctlr = spi->controller;
|
|
|
|
|
|
|
|
if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
|
2023-03-10 17:32:03 +00:00
|
|
|
ctlr->cs_gpiods[spi_get_chipselect(spi, 0)])
|
2022-11-30 16:29:27 +00:00
|
|
|
tmp &= ~SPI_CS_HIGH;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmd == SPI_IOC_RD_MODE)
|
|
|
|
retval = put_user(tmp & SPI_MODE_MASK,
|
|
|
|
(__u8 __user *)arg);
|
|
|
|
else
|
|
|
|
retval = put_user(tmp & SPI_MODE_MASK,
|
|
|
|
(__u32 __user *)arg);
|
2014-02-25 10:40:17 +00:00
|
|
|
break;
|
2007-05-08 07:32:15 +00:00
|
|
|
case SPI_IOC_RD_LSB_FIRST:
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
|
2007-05-08 07:32:15 +00:00
|
|
|
(__u8 __user *)arg);
|
|
|
|
break;
|
|
|
|
case SPI_IOC_RD_BITS_PER_WORD:
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = put_user(spi->bits_per_word, (__u8 __user *)arg);
|
2007-05-08 07:32:15 +00:00
|
|
|
break;
|
|
|
|
case SPI_IOC_RD_MAX_SPEED_HZ:
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = put_user(spidev->speed_hz, (__u32 __user *)arg);
|
2007-05-08 07:32:15 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
/* write requests */
|
|
|
|
case SPI_IOC_WR_MODE:
|
2014-02-25 10:40:17 +00:00
|
|
|
case SPI_IOC_WR_MODE32:
|
|
|
|
if (cmd == SPI_IOC_WR_MODE)
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = get_user(tmp, (u8 __user *)arg);
|
2014-02-25 10:40:17 +00:00
|
|
|
else
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = get_user(tmp, (u32 __user *)arg);
|
2007-05-08 07:32:15 +00:00
|
|
|
if (retval == 0) {
|
2020-02-18 12:08:00 +00:00
|
|
|
struct spi_controller *ctlr = spi->controller;
|
2014-02-25 10:40:16 +00:00
|
|
|
u32 save = spi->mode;
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
if (tmp & ~SPI_MODE_MASK) {
|
|
|
|
retval = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-02-18 12:08:00 +00:00
|
|
|
if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
|
2023-03-10 17:32:03 +00:00
|
|
|
ctlr->cs_gpiods[spi_get_chipselect(spi, 0)])
|
2020-02-18 12:08:00 +00:00
|
|
|
tmp |= SPI_CS_HIGH;
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
tmp |= spi->mode & ~SPI_MODE_MASK;
|
2021-11-22 17:52:45 +00:00
|
|
|
spi->mode = tmp & SPI_MODE_USER_MASK;
|
2007-05-08 07:32:15 +00:00
|
|
|
retval = spi_setup(spi);
|
|
|
|
if (retval < 0)
|
|
|
|
spi->mode = save;
|
|
|
|
else
|
2014-02-25 10:40:17 +00:00
|
|
|
dev_dbg(&spi->dev, "spi mode %x\n", tmp);
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SPI_IOC_WR_LSB_FIRST:
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = get_user(tmp, (__u8 __user *)arg);
|
2007-05-08 07:32:15 +00:00
|
|
|
if (retval == 0) {
|
2014-02-25 10:40:16 +00:00
|
|
|
u32 save = spi->mode;
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
if (tmp)
|
|
|
|
spi->mode |= SPI_LSB_FIRST;
|
|
|
|
else
|
|
|
|
spi->mode &= ~SPI_LSB_FIRST;
|
|
|
|
retval = spi_setup(spi);
|
|
|
|
if (retval < 0)
|
|
|
|
spi->mode = save;
|
|
|
|
else
|
|
|
|
dev_dbg(&spi->dev, "%csb first\n",
|
|
|
|
tmp ? 'l' : 'm');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SPI_IOC_WR_BITS_PER_WORD:
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = get_user(tmp, (__u8 __user *)arg);
|
2007-05-08 07:32:15 +00:00
|
|
|
if (retval == 0) {
|
|
|
|
u8 save = spi->bits_per_word;
|
|
|
|
|
|
|
|
spi->bits_per_word = tmp;
|
|
|
|
retval = spi_setup(spi);
|
|
|
|
if (retval < 0)
|
|
|
|
spi->bits_per_word = save;
|
|
|
|
else
|
|
|
|
dev_dbg(&spi->dev, "%d bits per word\n", tmp);
|
|
|
|
}
|
|
|
|
break;
|
2022-01-25 06:52:02 +00:00
|
|
|
case SPI_IOC_WR_MAX_SPEED_HZ: {
|
|
|
|
u32 save;
|
|
|
|
|
2017-04-20 19:47:34 +00:00
|
|
|
retval = get_user(tmp, (__u32 __user *)arg);
|
2022-01-25 06:52:02 +00:00
|
|
|
if (retval)
|
|
|
|
break;
|
|
|
|
if (tmp == 0) {
|
|
|
|
retval = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2022-01-25 06:52:02 +00:00
|
|
|
save = spi->max_speed_hz;
|
|
|
|
|
|
|
|
spi->max_speed_hz = tmp;
|
|
|
|
retval = spi_setup(spi);
|
|
|
|
if (retval == 0) {
|
|
|
|
spidev->speed_hz = tmp;
|
|
|
|
dev_dbg(&spi->dev, "%d Hz (max)\n", spidev->speed_hz);
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
|
2022-01-25 06:52:02 +00:00
|
|
|
spi->max_speed_hz = save;
|
|
|
|
break;
|
|
|
|
}
|
2007-05-08 07:32:15 +00:00
|
|
|
default:
|
|
|
|
/* segmented and/or full-duplex I/O request */
|
2015-01-30 18:43:33 +00:00
|
|
|
/* Check message and copy into scratch area */
|
|
|
|
ioc = spidev_get_ioc_message(cmd,
|
|
|
|
(struct spi_ioc_transfer __user *)arg, &n_ioc);
|
|
|
|
if (IS_ERR(ioc)) {
|
|
|
|
retval = PTR_ERR(ioc);
|
2007-05-08 07:32:15 +00:00
|
|
|
break;
|
|
|
|
}
|
2015-01-30 18:43:33 +00:00
|
|
|
if (!ioc)
|
|
|
|
break; /* n_ioc is also 0 */
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
/* translate to spi_message, execute */
|
|
|
|
retval = spidev_message(spidev, ioc, n_ioc);
|
|
|
|
kfree(ioc);
|
|
|
|
break;
|
|
|
|
}
|
2008-07-24 04:29:55 +00:00
|
|
|
|
|
|
|
mutex_unlock(&spidev->buf_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
spi_dev_put(spi);
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2007-05-08 07:32:15 +00:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2011-02-03 08:37:18 +00:00
|
|
|
#ifdef CONFIG_COMPAT
|
2015-01-30 18:43:33 +00:00
|
|
|
static long
|
|
|
|
spidev_compat_ioc_message(struct file *filp, unsigned int cmd,
|
|
|
|
unsigned long arg)
|
|
|
|
{
|
|
|
|
struct spi_ioc_transfer __user *u_ioc;
|
|
|
|
int retval = 0;
|
|
|
|
struct spidev_data *spidev;
|
|
|
|
struct spi_device *spi;
|
|
|
|
unsigned n_ioc, n;
|
|
|
|
struct spi_ioc_transfer *ioc;
|
|
|
|
|
|
|
|
u_ioc = (struct spi_ioc_transfer __user *) compat_ptr(arg);
|
|
|
|
|
|
|
|
/* guard against device removal before, or while,
|
|
|
|
* we issue this ioctl.
|
|
|
|
*/
|
|
|
|
spidev = filp->private_data;
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_lock(&spidev->spi_lock);
|
2015-01-30 18:43:33 +00:00
|
|
|
spi = spi_dev_get(spidev->spi);
|
2023-01-06 10:07:18 +00:00
|
|
|
if (spi == NULL) {
|
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2015-01-30 18:43:33 +00:00
|
|
|
return -ESHUTDOWN;
|
2023-01-06 10:07:18 +00:00
|
|
|
}
|
2015-01-30 18:43:33 +00:00
|
|
|
|
|
|
|
/* SPI_IOC_MESSAGE needs the buffer locked "normally" */
|
|
|
|
mutex_lock(&spidev->buf_lock);
|
|
|
|
|
|
|
|
/* Check message and copy into scratch area */
|
|
|
|
ioc = spidev_get_ioc_message(cmd, u_ioc, &n_ioc);
|
|
|
|
if (IS_ERR(ioc)) {
|
|
|
|
retval = PTR_ERR(ioc);
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if (!ioc)
|
|
|
|
goto done; /* n_ioc is also 0 */
|
|
|
|
|
|
|
|
/* Convert buffer pointers */
|
|
|
|
for (n = 0; n < n_ioc; n++) {
|
|
|
|
ioc[n].rx_buf = (uintptr_t) compat_ptr(ioc[n].rx_buf);
|
|
|
|
ioc[n].tx_buf = (uintptr_t) compat_ptr(ioc[n].tx_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* translate to spi_message, execute */
|
|
|
|
retval = spidev_message(spidev, ioc, n_ioc);
|
|
|
|
kfree(ioc);
|
|
|
|
|
|
|
|
done:
|
|
|
|
mutex_unlock(&spidev->buf_lock);
|
|
|
|
spi_dev_put(spi);
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2015-01-30 18:43:33 +00:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2011-02-03 08:37:18 +00:00
|
|
|
static long
|
|
|
|
spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
2015-01-30 18:43:33 +00:00
|
|
|
if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC
|
|
|
|
&& _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0))
|
|
|
|
&& _IOC_DIR(cmd) == _IOC_WRITE)
|
|
|
|
return spidev_compat_ioc_message(filp, cmd, arg);
|
|
|
|
|
2011-02-03 08:37:18 +00:00
|
|
|
return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define spidev_compat_ioctl NULL
|
|
|
|
#endif /* CONFIG_COMPAT */
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
static int spidev_open(struct inode *inode, struct file *filp)
|
|
|
|
{
|
2022-03-24 07:25:34 +00:00
|
|
|
struct spidev_data *spidev = NULL, *iter;
|
2007-05-08 07:32:15 +00:00
|
|
|
int status = -ENXIO;
|
|
|
|
|
|
|
|
mutex_lock(&device_list_lock);
|
|
|
|
|
2022-03-24 07:25:34 +00:00
|
|
|
list_for_each_entry(iter, &device_list, device_entry) {
|
|
|
|
if (iter->devt == inode->i_rdev) {
|
2007-05-08 07:32:15 +00:00
|
|
|
status = 0;
|
2022-03-24 07:25:34 +00:00
|
|
|
spidev = iter;
|
2007-05-08 07:32:15 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-10-09 18:19:25 +00:00
|
|
|
|
2022-03-24 07:25:34 +00:00
|
|
|
if (!spidev) {
|
2014-10-09 18:19:25 +00:00
|
|
|
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) {
|
2015-07-31 12:42:29 +00:00
|
|
|
status = -ENOMEM;
|
2014-10-09 18:19:25 +00:00
|
|
|
goto err_find_dev;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
2015-07-31 12:42:29 +00:00
|
|
|
}
|
2014-10-09 18:19:25 +00:00
|
|
|
|
|
|
|
if (!spidev->rx_buffer) {
|
|
|
|
spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
|
|
|
|
if (!spidev->rx_buffer) {
|
|
|
|
status = -ENOMEM;
|
|
|
|
goto err_alloc_rx_buf;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
2014-10-09 18:19:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
spidev->users++;
|
|
|
|
filp->private_data = spidev;
|
2019-03-26 20:51:19 +00:00
|
|
|
stream_open(inode, filp);
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2014-10-09 18:19:25 +00:00
|
|
|
mutex_unlock(&device_list_lock);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_alloc_rx_buf:
|
|
|
|
kfree(spidev->tx_buffer);
|
|
|
|
spidev->tx_buffer = NULL;
|
|
|
|
err_find_dev:
|
2007-05-08 07:32:15 +00:00
|
|
|
mutex_unlock(&device_list_lock);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int spidev_release(struct inode *inode, struct file *filp)
|
|
|
|
{
|
|
|
|
struct spidev_data *spidev;
|
2020-06-18 03:21:25 +00:00
|
|
|
int dofree;
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
mutex_lock(&device_list_lock);
|
|
|
|
spidev = filp->private_data;
|
|
|
|
filp->private_data = NULL;
|
2008-06-06 05:45:50 +00:00
|
|
|
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_lock(&spidev->spi_lock);
|
2020-06-18 03:21:25 +00:00
|
|
|
/* ... after we unbound from the underlying device? */
|
|
|
|
dofree = (spidev->spi == NULL);
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2020-06-18 03:21:25 +00:00
|
|
|
|
2008-06-06 05:45:50 +00:00
|
|
|
/* last close? */
|
2007-05-08 07:32:15 +00:00
|
|
|
spidev->users--;
|
|
|
|
if (!spidev->users) {
|
2008-06-06 05:45:50 +00:00
|
|
|
|
2014-10-09 18:19:25 +00:00
|
|
|
kfree(spidev->tx_buffer);
|
|
|
|
spidev->tx_buffer = NULL;
|
|
|
|
|
|
|
|
kfree(spidev->rx_buffer);
|
|
|
|
spidev->rx_buffer = NULL;
|
2008-06-06 05:45:50 +00:00
|
|
|
|
|
|
|
if (dofree)
|
|
|
|
kfree(spidev);
|
2020-06-18 03:21:25 +00:00
|
|
|
else
|
|
|
|
spidev->speed_hz = spidev->spi->max_speed_hz;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
2019-09-25 09:11:42 +00:00
|
|
|
#ifdef CONFIG_SPI_SLAVE
|
2020-06-18 03:21:25 +00:00
|
|
|
if (!dofree)
|
|
|
|
spi_slave_abort(spidev->spi);
|
2019-09-25 09:11:42 +00:00
|
|
|
#endif
|
2007-05-08 07:32:15 +00:00
|
|
|
mutex_unlock(&device_list_lock);
|
|
|
|
|
2015-05-09 15:57:19 +00:00
|
|
|
return 0;
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
|
2009-10-01 22:43:56 +00:00
|
|
|
static const struct file_operations spidev_fops = {
|
2007-05-08 07:32:15 +00:00
|
|
|
.owner = THIS_MODULE,
|
|
|
|
/* REVISIT switch to aio primitives, so that userspace
|
|
|
|
* gets more complete API coverage. It'll simplify things
|
|
|
|
* too, except for the locking.
|
|
|
|
*/
|
|
|
|
.write = spidev_write,
|
|
|
|
.read = spidev_read,
|
2008-07-24 04:29:55 +00:00
|
|
|
.unlocked_ioctl = spidev_ioctl,
|
2011-02-03 08:37:18 +00:00
|
|
|
.compat_ioctl = spidev_compat_ioctl,
|
2007-05-08 07:32:15 +00:00
|
|
|
.open = spidev_open,
|
|
|
|
.release = spidev_release,
|
llseek: automatically add .llseek fop
All file_operations should get a .llseek operation so we can make
nonseekable_open the default for future file operations without a
.llseek pointer.
The three cases that we can automatically detect are no_llseek, seq_lseek
and default_llseek. For cases where we can we can automatically prove that
the file offset is always ignored, we use noop_llseek, which maintains
the current behavior of not returning an error from a seek.
New drivers should normally not use noop_llseek but instead use no_llseek
and call nonseekable_open at open time. Existing drivers can be converted
to do the same when the maintainer knows for certain that no user code
relies on calling seek on the device file.
The generated code is often incorrectly indented and right now contains
comments that clarify for each added line why a specific variant was
chosen. In the version that gets submitted upstream, the comments will
be gone and I will manually fix the indentation, because there does not
seem to be a way to do that using coccinelle.
Some amount of new code is currently sitting in linux-next that should get
the same modifications, which I will do at the end of the merge window.
Many thanks to Julia Lawall for helping me learn to write a semantic
patch that does all this.
===== begin semantic patch =====
// This adds an llseek= method to all file operations,
// as a preparation for making no_llseek the default.
//
// The rules are
// - use no_llseek explicitly if we do nonseekable_open
// - use seq_lseek for sequential files
// - use default_llseek if we know we access f_pos
// - use noop_llseek if we know we don't access f_pos,
// but we still want to allow users to call lseek
//
@ open1 exists @
identifier nested_open;
@@
nested_open(...)
{
<+...
nonseekable_open(...)
...+>
}
@ open exists@
identifier open_f;
identifier i, f;
identifier open1.nested_open;
@@
int open_f(struct inode *i, struct file *f)
{
<+...
(
nonseekable_open(...)
|
nested_open(...)
)
...+>
}
@ read disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ read_no_fpos disable optional_qualifier exists @
identifier read_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off)
{
... when != off
}
@ write @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
expression E;
identifier func;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
<+...
(
*off = E
|
*off += E
|
func(..., off, ...)
|
E = *off
)
...+>
}
@ write_no_fpos @
identifier write_f;
identifier f, p, s, off;
type ssize_t, size_t, loff_t;
@@
ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off)
{
... when != off
}
@ fops0 @
identifier fops;
@@
struct file_operations fops = {
...
};
@ has_llseek depends on fops0 @
identifier fops0.fops;
identifier llseek_f;
@@
struct file_operations fops = {
...
.llseek = llseek_f,
...
};
@ has_read depends on fops0 @
identifier fops0.fops;
identifier read_f;
@@
struct file_operations fops = {
...
.read = read_f,
...
};
@ has_write depends on fops0 @
identifier fops0.fops;
identifier write_f;
@@
struct file_operations fops = {
...
.write = write_f,
...
};
@ has_open depends on fops0 @
identifier fops0.fops;
identifier open_f;
@@
struct file_operations fops = {
...
.open = open_f,
...
};
// use no_llseek if we call nonseekable_open
////////////////////////////////////////////
@ nonseekable1 depends on !has_llseek && has_open @
identifier fops0.fops;
identifier nso ~= "nonseekable_open";
@@
struct file_operations fops = {
... .open = nso, ...
+.llseek = no_llseek, /* nonseekable */
};
@ nonseekable2 depends on !has_llseek @
identifier fops0.fops;
identifier open.open_f;
@@
struct file_operations fops = {
... .open = open_f, ...
+.llseek = no_llseek, /* open uses nonseekable */
};
// use seq_lseek for sequential files
/////////////////////////////////////
@ seq depends on !has_llseek @
identifier fops0.fops;
identifier sr ~= "seq_read";
@@
struct file_operations fops = {
... .read = sr, ...
+.llseek = seq_lseek, /* we have seq_read */
};
// use default_llseek if there is a readdir
///////////////////////////////////////////
@ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier readdir_e;
@@
// any other fop is used that changes pos
struct file_operations fops = {
... .readdir = readdir_e, ...
+.llseek = default_llseek, /* readdir is present */
};
// use default_llseek if at least one of read/write touches f_pos
/////////////////////////////////////////////////////////////////
@ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read.read_f;
@@
// read fops use offset
struct file_operations fops = {
... .read = read_f, ...
+.llseek = default_llseek, /* read accesses f_pos */
};
@ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write.write_f;
@@
// write fops use offset
struct file_operations fops = {
... .write = write_f, ...
+ .llseek = default_llseek, /* write accesses f_pos */
};
// Use noop_llseek if neither read nor write accesses f_pos
///////////////////////////////////////////////////////////
@ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
identifier write_no_fpos.write_f;
@@
// write fops use offset
struct file_operations fops = {
...
.write = write_f,
.read = read_f,
...
+.llseek = noop_llseek, /* read and write both use no f_pos */
};
@ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier write_no_fpos.write_f;
@@
struct file_operations fops = {
... .write = write_f, ...
+.llseek = noop_llseek, /* write uses no f_pos */
};
@ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
identifier read_no_fpos.read_f;
@@
struct file_operations fops = {
... .read = read_f, ...
+.llseek = noop_llseek, /* read uses no f_pos */
};
@ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @
identifier fops0.fops;
@@
struct file_operations fops = {
...
+.llseek = noop_llseek, /* no read or write fn */
};
===== End semantic patch =====
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Cc: Julia Lawall <julia@diku.dk>
Cc: Christoph Hellwig <hch@infradead.org>
2010-08-15 16:52:59 +00:00
|
|
|
.llseek = no_llseek,
|
2007-05-08 07:32:15 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* The main reason to have this class is to make mdev/udev create the
|
|
|
|
* /dev/spidevB.C character device nodes exposing our userspace API.
|
|
|
|
* It also simplifies memory management.
|
|
|
|
*/
|
|
|
|
|
2008-06-06 05:45:50 +00:00
|
|
|
static struct class *spidev_class;
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2021-09-23 17:00:23 +00:00
|
|
|
static const struct spi_device_id spidev_spi_ids[] = {
|
|
|
|
{ .name = "dh2228fv" },
|
|
|
|
{ .name = "ltc2488" },
|
|
|
|
{ .name = "sx1301" },
|
|
|
|
{ .name = "bk4" },
|
|
|
|
{ .name = "dhcom-board" },
|
|
|
|
{ .name = "m53cpld" },
|
|
|
|
{ .name = "spi-petra" },
|
|
|
|
{ .name = "spi-authenta" },
|
2022-12-27 02:35:48 +00:00
|
|
|
{ .name = "em3581" },
|
2022-12-27 14:10:08 +00:00
|
|
|
{ .name = "si3210" },
|
2021-09-23 17:00:23 +00:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(spi, spidev_spi_ids);
|
|
|
|
|
2022-03-23 14:02:15 +00:00
|
|
|
/*
|
|
|
|
* spidev should never be referenced in DT without a specific compatible string,
|
|
|
|
* it is a Linux implementation thing rather than a description of the hardware.
|
|
|
|
*/
|
|
|
|
static int spidev_of_check(struct device *dev)
|
|
|
|
{
|
|
|
|
if (device_property_match_string(dev, "compatible", "spidev") < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
dev_err(dev, "spidev listed directly in DT is not supported\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-03-27 23:36:04 +00:00
|
|
|
static const struct of_device_id spidev_dt_ids[] = {
|
2023-01-20 07:56:51 +00:00
|
|
|
{ .compatible = "cisco,spi-petra", .data = &spidev_of_check },
|
|
|
|
{ .compatible = "dh,dhcom-board", .data = &spidev_of_check },
|
2022-03-23 14:02:15 +00:00
|
|
|
{ .compatible = "lineartechnology,ltc2488", .data = &spidev_of_check },
|
|
|
|
{ .compatible = "lwn,bk4", .data = &spidev_of_check },
|
|
|
|
{ .compatible = "menlo,m53cpld", .data = &spidev_of_check },
|
|
|
|
{ .compatible = "micron,spi-authenta", .data = &spidev_of_check },
|
2023-01-20 07:56:51 +00:00
|
|
|
{ .compatible = "rohm,dh2228fv", .data = &spidev_of_check },
|
|
|
|
{ .compatible = "semtech,sx1301", .data = &spidev_of_check },
|
2022-12-27 02:35:48 +00:00
|
|
|
{ .compatible = "silabs,em3581", .data = &spidev_of_check },
|
2022-12-27 14:10:08 +00:00
|
|
|
{ .compatible = "silabs,si3210", .data = &spidev_of_check },
|
2015-03-27 23:36:04 +00:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
|
|
|
|
|
2016-07-04 14:18:15 +00:00
|
|
|
/* Dummy SPI devices not to be used in production systems */
|
2022-03-23 14:02:14 +00:00
|
|
|
static int spidev_acpi_check(struct device *dev)
|
|
|
|
{
|
|
|
|
dev_warn(dev, "do not use this driver in production systems!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2016-07-04 14:18:15 +00:00
|
|
|
|
|
|
|
static const struct acpi_device_id spidev_acpi_ids[] = {
|
|
|
|
/*
|
|
|
|
* The ACPI SPT000* devices are only meant for development and
|
|
|
|
* testing. Systems used in production should have a proper ACPI
|
|
|
|
* description of the connected peripheral and they should also use
|
|
|
|
* a proper driver instead of poking directly to the SPI bus.
|
|
|
|
*/
|
2022-03-23 14:02:14 +00:00
|
|
|
{ "SPT0001", (kernel_ulong_t)&spidev_acpi_check },
|
|
|
|
{ "SPT0002", (kernel_ulong_t)&spidev_acpi_check },
|
|
|
|
{ "SPT0003", (kernel_ulong_t)&spidev_acpi_check },
|
2016-07-04 14:18:15 +00:00
|
|
|
{},
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, spidev_acpi_ids);
|
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
2012-12-07 16:57:14 +00:00
|
|
|
static int spidev_probe(struct spi_device *spi)
|
2007-05-08 07:32:15 +00:00
|
|
|
{
|
2022-03-23 14:02:14 +00:00
|
|
|
int (*match)(struct device *dev);
|
2007-05-08 07:32:15 +00:00
|
|
|
struct spidev_data *spidev;
|
|
|
|
int status;
|
|
|
|
unsigned long minor;
|
|
|
|
|
2022-03-23 14:02:14 +00:00
|
|
|
match = device_get_match_data(&spi->dev);
|
|
|
|
if (match) {
|
|
|
|
status = match(&spi->dev);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
}
|
2016-07-04 14:18:15 +00:00
|
|
|
|
2007-05-08 07:32:15 +00:00
|
|
|
/* Allocate driver data */
|
|
|
|
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
|
|
|
|
if (!spidev)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* Initialize the driver data */
|
|
|
|
spidev->spi = spi;
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_init(&spidev->spi_lock);
|
2007-05-08 07:32:15 +00:00
|
|
|
mutex_init(&spidev->buf_lock);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&spidev->device_entry);
|
|
|
|
|
|
|
|
/* If we can allocate a minor number, hook up this device.
|
|
|
|
* Reusing minors is fine so long as udev or mdev is working.
|
|
|
|
*/
|
|
|
|
mutex_lock(&device_list_lock);
|
2007-05-16 06:57:05 +00:00
|
|
|
minor = find_first_zero_bit(minors, N_SPI_MINORS);
|
2007-05-08 07:32:15 +00:00
|
|
|
if (minor < N_SPI_MINORS) {
|
2008-06-06 05:45:50 +00:00
|
|
|
struct device *dev;
|
|
|
|
|
|
|
|
spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
|
2008-07-22 03:03:34 +00:00
|
|
|
dev = device_create(spidev_class, &spi->dev, spidev->devt,
|
|
|
|
spidev, "spidev%d.%d",
|
2023-03-10 17:32:03 +00:00
|
|
|
spi->master->bus_num, spi_get_chipselect(spi, 0));
|
2013-07-15 01:50:32 +00:00
|
|
|
status = PTR_ERR_OR_ZERO(dev);
|
2007-05-08 07:32:15 +00:00
|
|
|
} else {
|
|
|
|
dev_dbg(&spi->dev, "no minor number available!\n");
|
|
|
|
status = -ENODEV;
|
|
|
|
}
|
|
|
|
if (status == 0) {
|
|
|
|
set_bit(minor, minors);
|
|
|
|
list_add(&spidev->device_entry, &device_list);
|
|
|
|
}
|
|
|
|
mutex_unlock(&device_list_lock);
|
|
|
|
|
2014-11-08 10:28:10 +00:00
|
|
|
spidev->speed_hz = spi->max_speed_hz;
|
|
|
|
|
2008-12-01 21:13:52 +00:00
|
|
|
if (status == 0)
|
|
|
|
spi_set_drvdata(spi, spidev);
|
|
|
|
else
|
2007-05-08 07:32:15 +00:00
|
|
|
kfree(spidev);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2022-01-23 17:52:01 +00:00
|
|
|
static void spidev_remove(struct spi_device *spi)
|
2007-05-08 07:32:15 +00:00
|
|
|
{
|
2008-06-06 05:45:50 +00:00
|
|
|
struct spidev_data *spidev = spi_get_drvdata(spi);
|
2007-05-08 07:32:15 +00:00
|
|
|
|
2020-06-18 03:21:24 +00:00
|
|
|
/* prevent new opens */
|
|
|
|
mutex_lock(&device_list_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
/* make sure ops on existing fds can abort cleanly */
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_lock(&spidev->spi_lock);
|
2008-05-23 20:05:03 +00:00
|
|
|
spidev->spi = NULL;
|
2023-01-06 10:07:18 +00:00
|
|
|
mutex_unlock(&spidev->spi_lock);
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
list_del(&spidev->device_entry);
|
2008-06-06 05:45:50 +00:00
|
|
|
device_destroy(spidev_class, spidev->devt);
|
|
|
|
clear_bit(MINOR(spidev->devt), minors);
|
|
|
|
if (spidev->users == 0)
|
|
|
|
kfree(spidev);
|
2007-05-08 07:32:15 +00:00
|
|
|
mutex_unlock(&device_list_lock);
|
|
|
|
}
|
|
|
|
|
2009-12-14 22:20:22 +00:00
|
|
|
static struct spi_driver spidev_spi_driver = {
|
2007-05-08 07:32:15 +00:00
|
|
|
.driver = {
|
|
|
|
.name = "spidev",
|
2022-03-23 14:02:15 +00:00
|
|
|
.of_match_table = spidev_dt_ids,
|
2022-03-23 14:02:14 +00:00
|
|
|
.acpi_match_table = spidev_acpi_ids,
|
2007-05-08 07:32:15 +00:00
|
|
|
},
|
|
|
|
.probe = spidev_probe,
|
2012-12-07 16:57:14 +00:00
|
|
|
.remove = spidev_remove,
|
2021-09-23 17:00:23 +00:00
|
|
|
.id_table = spidev_spi_ids,
|
2007-05-08 07:32:15 +00:00
|
|
|
|
|
|
|
/* NOTE: suspend/resume methods are not necessary here.
|
|
|
|
* We don't do anything except pass the requests to/from
|
|
|
|
* the underlying controller. The refrigerator handles
|
|
|
|
* most issues; the controller driver handles the rest.
|
|
|
|
*/
|
|
|
|
};
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static int __init spidev_init(void)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
/* Claim our 256 reserved device numbers. Then register a class
|
|
|
|
* that will key udev/mdev to add/remove /dev nodes. Last, register
|
|
|
|
* the driver which manages those device numbers.
|
|
|
|
*/
|
|
|
|
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
2023-03-13 18:18:35 +00:00
|
|
|
spidev_class = class_create("spidev");
|
2008-06-06 05:45:50 +00:00
|
|
|
if (IS_ERR(spidev_class)) {
|
2009-12-14 22:20:22 +00:00
|
|
|
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
|
2008-06-06 05:45:50 +00:00
|
|
|
return PTR_ERR(spidev_class);
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
|
2009-12-14 22:20:22 +00:00
|
|
|
status = spi_register_driver(&spidev_spi_driver);
|
2007-05-08 07:32:15 +00:00
|
|
|
if (status < 0) {
|
2008-06-06 05:45:50 +00:00
|
|
|
class_destroy(spidev_class);
|
2009-12-14 22:20:22 +00:00
|
|
|
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
module_init(spidev_init);
|
|
|
|
|
|
|
|
static void __exit spidev_exit(void)
|
|
|
|
{
|
2009-12-14 22:20:22 +00:00
|
|
|
spi_unregister_driver(&spidev_spi_driver);
|
2008-06-06 05:45:50 +00:00
|
|
|
class_destroy(spidev_class);
|
2009-12-14 22:20:22 +00:00
|
|
|
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
|
2007-05-08 07:32:15 +00:00
|
|
|
}
|
|
|
|
module_exit(spidev_exit);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>");
|
|
|
|
MODULE_DESCRIPTION("User mode SPI device interface");
|
|
|
|
MODULE_LICENSE("GPL");
|
2009-09-22 23:46:08 +00:00
|
|
|
MODULE_ALIAS("spi:spidev");
|