Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

spi: spidev: fix lock inversion between spi_lock and buf_lock

The spidev driver previously used two mutexes, spi_lock and buf_lock,
but acquired them in different orders depending on the code path:

write()/read(): buf_lock -> spi_lock
ioctl(): spi_lock -> buf_lock

This AB-BA locking pattern triggers lockdep warnings and can
cause real deadlocks:

WARNING: possible circular locking dependency detected
spidev_ioctl() -> mutex_lock(&spidev->buf_lock)
spidev_sync_write() -> mutex_lock(&spidev->spi_lock)
*** DEADLOCK ***

The issue is reproducible with a simple userspace program that
performs write() and SPI_IOC_WR_MAX_SPEED_HZ ioctl() calls from
separate threads on the same spidev file descriptor.

Fix this by simplifying the locking model and removing the lock
inversion entirely. spidev_sync() no longer performs any locking,
and all callers serialize access using spi_lock.

buf_lock is removed since its functionality is fully covered by
spi_lock, eliminating the possibility of lock ordering issues.

This removes the lock inversion and prevents deadlocks without
changing userspace ABI or behaviour.

Signed-off-by: Fabian Godehardt <fg@emlix.com>
Link: https://patch.msgid.link/20260211072616.489522-1-fg@emlix.com
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Fabian Godehardt and committed by
Mark Brown
40534d19 888a0a80

+22 -41
+22 -41
drivers/spi/spidev.c
··· 74 74 struct list_head device_entry; 75 75 76 76 /* TX/RX buffers are NULL unless this device is open (users > 0) */ 77 - struct mutex buf_lock; 78 77 unsigned users; 79 78 u8 *tx_buffer; 80 79 u8 *rx_buffer; ··· 101 102 return status; 102 103 } 103 104 104 - static ssize_t 105 - spidev_sync(struct spidev_data *spidev, struct spi_message *message) 106 - { 107 - ssize_t status; 108 - struct spi_device *spi; 109 - 110 - mutex_lock(&spidev->spi_lock); 111 - spi = spidev->spi; 112 - 113 - if (spi == NULL) 114 - status = -ESHUTDOWN; 115 - else 116 - status = spidev_sync_unlocked(spi, message); 117 - 118 - mutex_unlock(&spidev->spi_lock); 119 - return status; 120 - } 121 - 122 105 static inline ssize_t 123 106 spidev_sync_write(struct spidev_data *spidev, size_t len) 124 107 { ··· 113 132 114 133 spi_message_init(&m); 115 134 spi_message_add_tail(&t, &m); 116 - return spidev_sync(spidev, &m); 135 + 136 + return spidev_sync_unlocked(spidev->spi, &m); 117 137 } 118 138 119 139 static inline ssize_t ··· 129 147 130 148 spi_message_init(&m); 131 149 spi_message_add_tail(&t, &m); 132 - return spidev_sync(spidev, &m); 150 + 151 + return spidev_sync_unlocked(spidev->spi, &m); 133 152 } 134 153 135 154 /*-------------------------------------------------------------------------*/ ··· 140 157 spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) 141 158 { 142 159 struct spidev_data *spidev; 143 - ssize_t status; 160 + ssize_t status = -ESHUTDOWN; 144 161 145 162 /* chipselect only toggles at start or end of operation */ 146 163 if (count > bufsiz) ··· 148 165 149 166 spidev = filp->private_data; 150 167 151 - mutex_lock(&spidev->buf_lock); 168 + mutex_lock(&spidev->spi_lock); 169 + 170 + if (spidev->spi == NULL) 171 + goto err_spi_removed; 172 + 152 173 status = spidev_sync_read(spidev, count); 153 174 if (status > 0) { 154 175 unsigned long missing; ··· 163 176 else 164 177 status = status - missing; 165 178 } 166 - mutex_unlock(&spidev->buf_lock); 179 + 180 + err_spi_removed: 181 + mutex_unlock(&spidev->spi_lock); 167 182 168 183 return status; 169 184 } ··· 176 187 size_t count, loff_t *f_pos) 177 188 { 178 189 struct spidev_data *spidev; 179 - ssize_t status; 190 + ssize_t status = -ESHUTDOWN; 180 191 unsigned long missing; 181 192 182 193 /* chipselect only toggles at start or end of operation */ ··· 185 196 186 197 spidev = filp->private_data; 187 198 188 - mutex_lock(&spidev->buf_lock); 199 + mutex_lock(&spidev->spi_lock); 200 + 201 + if (spidev->spi == NULL) 202 + goto err_spi_removed; 203 + 189 204 missing = copy_from_user(spidev->tx_buffer, buf, count); 190 205 if (missing == 0) 191 206 status = spidev_sync_write(spidev, count); 192 207 else 193 208 status = -EFAULT; 194 - mutex_unlock(&spidev->buf_lock); 209 + 210 + err_spi_removed: 211 + mutex_unlock(&spidev->spi_lock); 195 212 196 213 return status; 197 214 } ··· 374 379 375 380 ctlr = spi->controller; 376 381 377 - /* use the buffer lock here for triple duty: 378 - * - prevent I/O (from us) so calling spi_setup() is safe; 379 - * - prevent concurrent SPI_IOC_WR_* from morphing 380 - * data fields while SPI_IOC_RD_* reads them; 381 - * - SPI_IOC_MESSAGE needs the buffer locked "normally". 382 - */ 383 - mutex_lock(&spidev->buf_lock); 384 - 385 382 switch (cmd) { 386 383 /* read requests */ 387 384 case SPI_IOC_RD_MODE: ··· 497 510 break; 498 511 } 499 512 500 - mutex_unlock(&spidev->buf_lock); 501 513 spi_dev_put(spi); 502 514 mutex_unlock(&spidev->spi_lock); 503 515 return retval; ··· 527 541 return -ESHUTDOWN; 528 542 } 529 543 530 - /* SPI_IOC_MESSAGE needs the buffer locked "normally" */ 531 - mutex_lock(&spidev->buf_lock); 532 - 533 544 /* Check message and copy into scratch area */ 534 545 ioc = spidev_get_ioc_message(cmd, u_ioc, &n_ioc); 535 546 if (IS_ERR(ioc)) { ··· 547 564 kfree(ioc); 548 565 549 566 done: 550 - mutex_unlock(&spidev->buf_lock); 551 567 spi_dev_put(spi); 552 568 mutex_unlock(&spidev->spi_lock); 553 569 return retval; ··· 784 802 /* Initialize the driver data */ 785 803 spidev->spi = spi; 786 804 mutex_init(&spidev->spi_lock); 787 - mutex_init(&spidev->buf_lock); 788 805 789 806 INIT_LIST_HEAD(&spidev->device_entry); 790 807