aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/spi
diff options
context:
space:
mode:
authorIan Abbott <abbotti@mev.co.uk>2015-02-16 10:00:47 -0500
committerMark Brown <broonie@kernel.org>2015-02-23 20:38:25 -0500
commit9a12bff7c34635c329079858460e9705d819c500 (patch)
treef058a4f6f230d635a5cd400d51222f3ca0d3e44b /drivers/spi
parentc517d838eb7d07bbe9507871fab3931deccff539 (diff)
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>
Diffstat (limited to 'drivers/spi')
-rw-r--r--drivers/spi/spidev.c28
1 files changed, 23 insertions, 5 deletions
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index 4eb7a980e670..bb6b3abd116c 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -223,7 +223,7 @@ static int spidev_message(struct spidev_data *spidev,
223 struct spi_transfer *k_xfers; 223 struct spi_transfer *k_xfers;
224 struct spi_transfer *k_tmp; 224 struct spi_transfer *k_tmp;
225 struct spi_ioc_transfer *u_tmp; 225 struct spi_ioc_transfer *u_tmp;
226 unsigned n, total; 226 unsigned n, total, tx_total, rx_total;
227 u8 *tx_buf, *rx_buf; 227 u8 *tx_buf, *rx_buf;
228 int status = -EFAULT; 228 int status = -EFAULT;
229 229
@@ -239,33 +239,51 @@ static int spidev_message(struct spidev_data *spidev,
239 tx_buf = spidev->tx_buffer; 239 tx_buf = spidev->tx_buffer;
240 rx_buf = spidev->rx_buffer; 240 rx_buf = spidev->rx_buffer;
241 total = 0; 241 total = 0;
242 tx_total = 0;
243 rx_total = 0;
242 for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; 244 for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
243 n; 245 n;
244 n--, k_tmp++, u_tmp++) { 246 n--, k_tmp++, u_tmp++) {
245 k_tmp->len = u_tmp->len; 247 k_tmp->len = u_tmp->len;
246 248
247 total += k_tmp->len; 249 total += k_tmp->len;
248 if (total > bufsiz) { 250 /* Since the function returns the total length of transfers
251 * on success, restrict the total to positive int values to
252 * avoid the return value looking like an error.
253 */
254 if (total > INT_MAX) {
249 status = -EMSGSIZE; 255 status = -EMSGSIZE;
250 goto done; 256 goto done;
251 } 257 }
252 258
253 if (u_tmp->rx_buf) { 259 if (u_tmp->rx_buf) {
260 /* this transfer needs space in RX bounce buffer */
261 rx_total += k_tmp->len;
262 if (rx_total > bufsiz) {
263 status = -EMSGSIZE;
264 goto done;
265 }
254 k_tmp->rx_buf = rx_buf; 266 k_tmp->rx_buf = rx_buf;
255 if (!access_ok(VERIFY_WRITE, (u8 __user *) 267 if (!access_ok(VERIFY_WRITE, (u8 __user *)
256 (uintptr_t) u_tmp->rx_buf, 268 (uintptr_t) u_tmp->rx_buf,
257 u_tmp->len)) 269 u_tmp->len))
258 goto done; 270 goto done;
271 rx_buf += k_tmp->len;
259 } 272 }
260 if (u_tmp->tx_buf) { 273 if (u_tmp->tx_buf) {
274 /* this transfer needs space in TX bounce buffer */
275 tx_total += k_tmp->len;
276 if (tx_total > bufsiz) {
277 status = -EMSGSIZE;
278 goto done;
279 }
261 k_tmp->tx_buf = tx_buf; 280 k_tmp->tx_buf = tx_buf;
262 if (copy_from_user(tx_buf, (const u8 __user *) 281 if (copy_from_user(tx_buf, (const u8 __user *)
263 (uintptr_t) u_tmp->tx_buf, 282 (uintptr_t) u_tmp->tx_buf,
264 u_tmp->len)) 283 u_tmp->len))
265 goto done; 284 goto done;
285 tx_buf += k_tmp->len;
266 } 286 }
267 tx_buf += k_tmp->len;
268 rx_buf += k_tmp->len;
269 287
270 k_tmp->cs_change = !!u_tmp->cs_change; 288 k_tmp->cs_change = !!u_tmp->cs_change;
271 k_tmp->tx_nbits = u_tmp->tx_nbits; 289 k_tmp->tx_nbits = u_tmp->tx_nbits;
@@ -303,8 +321,8 @@ static int spidev_message(struct spidev_data *spidev,
303 status = -EFAULT; 321 status = -EFAULT;
304 goto done; 322 goto done;
305 } 323 }
324 rx_buf += u_tmp->len;
306 } 325 }
307 rx_buf += u_tmp->len;
308 } 326 }
309 status = total; 327 status = total;
310 328