aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwuxu.wu <wuxu.wu@huawei.com>2020-01-01 11:39:41 +0800
committerBen Hutchings <ben@decadent.org.uk>2020-05-22 21:19:11 +0100
commitf358118d34cdd7e898685205bb62e41e9a3246aa (patch)
treed277c5ffe678c5670b56ed042ec6a5b48c1495c0
parent42f443179444258caf10b7f4f9f4e75201150918 (diff)
downloadlinux-stable-f358118d34cdd7e898685205bb62e41e9a3246aa.tar.gz
spi: spi-dw: Add lock protect dw_spi rx/tx to prevent concurrent calls
commit 19b61392c5a852b4e8a0bf35aecb969983c5932d upstream. dw_spi_irq() and dw_spi_transfer_one concurrent calls. I find a panic in dw_writer(): txw = *(u8 *)(dws->tx), when dw->tx==null, dw->len==4, and dw->tx_end==1. When tpm driver's message overtime dw_spi_irq() and dw_spi_transfer_one may concurrent visit dw_spi, so I think dw_spi structure lack of protection. Otherwise dw_spi_transfer_one set dw rx/tx buffer and then open irq, store dw rx/tx instructions and other cores handle irq load dw rx/tx instructions may out of order. [ 1025.321302] Call trace: ... [ 1025.321319] __crash_kexec+0x98/0x148 [ 1025.321323] panic+0x17c/0x314 [ 1025.321329] die+0x29c/0x2e8 [ 1025.321334] die_kernel_fault+0x68/0x78 [ 1025.321337] __do_kernel_fault+0x90/0xb0 [ 1025.321346] do_page_fault+0x88/0x500 [ 1025.321347] do_translation_fault+0xa8/0xb8 [ 1025.321349] do_mem_abort+0x68/0x118 [ 1025.321351] el1_da+0x20/0x8c [ 1025.321362] dw_writer+0xc8/0xd0 [ 1025.321364] interrupt_transfer+0x60/0x110 [ 1025.321365] dw_spi_irq+0x48/0x70 ... Signed-off-by: wuxu.wu <wuxu.wu@huawei.com> Link: https://lore.kernel.org/r/1577849981-31489-1-git-send-email-wuxu.wu@huawei.com Signed-off-by: Mark Brown <broonie@kernel.org> [iwamatsu: Backported to 3.16: adjut context] Signed-off-by: Nobuhiro Iwamatsu (CIP) <nobuhiro1.iwamatsu@toshiba.co.jp> Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
-rw-r--r--drivers/spi/spi-dw.c14
-rw-r--r--drivers/spi/spi-dw.h1
2 files changed, 13 insertions, 2 deletions
diff --git a/drivers/spi/spi-dw.c b/drivers/spi/spi-dw.c
index 66e9e5196c8c5e..b3e697d5b5966b 100644
--- a/drivers/spi/spi-dw.c
+++ b/drivers/spi/spi-dw.c
@@ -182,9 +182,11 @@ static inline u32 rx_max(struct dw_spi *dws)
static void dw_writer(struct dw_spi *dws)
{
- u32 max = tx_max(dws);
+ u32 max;
u16 txw = 0;
+ spin_lock(&dws->buf_lock);
+ max = tx_max(dws);
while (max--) {
/* Set the tx word if the transfer's original "tx" is not null */
if (dws->tx_end - dws->len) {
@@ -196,13 +198,16 @@ static void dw_writer(struct dw_spi *dws)
dw_writew(dws, DW_SPI_DR, txw);
dws->tx += dws->n_bytes;
}
+ spin_unlock(&dws->buf_lock);
}
static void dw_reader(struct dw_spi *dws)
{
- u32 max = rx_max(dws);
+ u32 max;
u16 rxw;
+ spin_lock(&dws->buf_lock);
+ max = rx_max(dws);
while (max--) {
rxw = dw_readw(dws, DW_SPI_DR);
/* Care rx only if the transfer's original "rx" is not null */
@@ -214,6 +219,7 @@ static void dw_reader(struct dw_spi *dws)
}
dws->rx += dws->n_bytes;
}
+ spin_unlock(&dws->buf_lock);
}
static void *next_transfer(struct dw_spi *dws)
@@ -368,6 +374,7 @@ static void pump_transfers(unsigned long data)
struct spi_transfer *previous = NULL;
struct spi_device *spi = NULL;
struct chip_data *chip = NULL;
+ unsigned long flags;
u8 bits = 0;
u8 imask = 0;
u8 cs_change = 0;
@@ -406,6 +413,7 @@ static void pump_transfers(unsigned long data)
dws->dma_width = chip->dma_width;
dws->cs_control = chip->cs_control;
+ spin_lock_irqsave(&dws->buf_lock, flags);
dws->rx_dma = transfer->rx_dma;
dws->tx_dma = transfer->tx_dma;
dws->tx = (void *)transfer->tx_buf;
@@ -415,6 +423,7 @@ static void pump_transfers(unsigned long data)
dws->len = dws->cur_transfer->len;
if (chip != dws->prev_chip)
cs_change = 1;
+ spin_unlock_irqrestore(&dws->buf_lock, flags);
cr0 = chip->cr0;
@@ -651,6 +660,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
dws->dma_addr = (dma_addr_t)(dws->paddr + 0x60);
snprintf(dws->name, sizeof(dws->name), "dw_spi%d",
dws->bus_num);
+ spin_lock_init(&dws->buf_lock);
ret = request_irq(dws->irq, dw_spi_irq, IRQF_SHARED, dws->name, dws);
if (ret < 0) {
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index 6d2acad34f64f5..be4119a2159bf3 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -116,6 +116,7 @@ struct dw_spi {
size_t len;
void *tx;
void *tx_end;
+ spinlock_t buf_lock;
void *rx;
void *rx_end;
int dma_mapped;