forked from mirrors/linux
		
	spi: spi-dw: Add lock protect dw_spi rx/tx to prevent concurrent calls
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>
This commit is contained in:
		
							parent
							
								
									ca59d5a516
								
							
						
					
					
						commit
						19b61392c5
					
				
					 2 changed files with 13 additions and 3 deletions
				
			
		|  | @ -172,9 +172,11 @@ static inline u32 rx_max(struct dw_spi *dws) | ||||||
| 
 | 
 | ||||||
| static void dw_writer(struct dw_spi *dws) | static void dw_writer(struct dw_spi *dws) | ||||||
| { | { | ||||||
| 	u32 max = tx_max(dws); | 	u32 max; | ||||||
| 	u16 txw = 0; | 	u16 txw = 0; | ||||||
| 
 | 
 | ||||||
|  | 	spin_lock(&dws->buf_lock); | ||||||
|  | 	max = tx_max(dws); | ||||||
| 	while (max--) { | 	while (max--) { | ||||||
| 		/* Set the tx word if the transfer's original "tx" is not null */ | 		/* Set the tx word if the transfer's original "tx" is not null */ | ||||||
| 		if (dws->tx_end - dws->len) { | 		if (dws->tx_end - dws->len) { | ||||||
|  | @ -186,13 +188,16 @@ static void dw_writer(struct dw_spi *dws) | ||||||
| 		dw_write_io_reg(dws, DW_SPI_DR, txw); | 		dw_write_io_reg(dws, DW_SPI_DR, txw); | ||||||
| 		dws->tx += dws->n_bytes; | 		dws->tx += dws->n_bytes; | ||||||
| 	} | 	} | ||||||
|  | 	spin_unlock(&dws->buf_lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void dw_reader(struct dw_spi *dws) | static void dw_reader(struct dw_spi *dws) | ||||||
| { | { | ||||||
| 	u32 max = rx_max(dws); | 	u32 max; | ||||||
| 	u16 rxw; | 	u16 rxw; | ||||||
| 
 | 
 | ||||||
|  | 	spin_lock(&dws->buf_lock); | ||||||
|  | 	max = rx_max(dws); | ||||||
| 	while (max--) { | 	while (max--) { | ||||||
| 		rxw = dw_read_io_reg(dws, DW_SPI_DR); | 		rxw = dw_read_io_reg(dws, DW_SPI_DR); | ||||||
| 		/* Care rx only if the transfer's original "rx" is not null */ | 		/* Care rx only if the transfer's original "rx" is not null */ | ||||||
|  | @ -204,6 +209,7 @@ static void dw_reader(struct dw_spi *dws) | ||||||
| 		} | 		} | ||||||
| 		dws->rx += dws->n_bytes; | 		dws->rx += dws->n_bytes; | ||||||
| 	} | 	} | ||||||
|  | 	spin_unlock(&dws->buf_lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void int_error_stop(struct dw_spi *dws, const char *msg) | static void int_error_stop(struct dw_spi *dws, const char *msg) | ||||||
|  | @ -276,18 +282,20 @@ static int dw_spi_transfer_one(struct spi_controller *master, | ||||||
| { | { | ||||||
| 	struct dw_spi *dws = spi_controller_get_devdata(master); | 	struct dw_spi *dws = spi_controller_get_devdata(master); | ||||||
| 	struct chip_data *chip = spi_get_ctldata(spi); | 	struct chip_data *chip = spi_get_ctldata(spi); | ||||||
|  | 	unsigned long flags; | ||||||
| 	u8 imask = 0; | 	u8 imask = 0; | ||||||
| 	u16 txlevel = 0; | 	u16 txlevel = 0; | ||||||
| 	u32 cr0; | 	u32 cr0; | ||||||
| 	int ret; | 	int ret; | ||||||
| 
 | 
 | ||||||
| 	dws->dma_mapped = 0; | 	dws->dma_mapped = 0; | ||||||
| 
 | 	spin_lock_irqsave(&dws->buf_lock, flags); | ||||||
| 	dws->tx = (void *)transfer->tx_buf; | 	dws->tx = (void *)transfer->tx_buf; | ||||||
| 	dws->tx_end = dws->tx + transfer->len; | 	dws->tx_end = dws->tx + transfer->len; | ||||||
| 	dws->rx = transfer->rx_buf; | 	dws->rx = transfer->rx_buf; | ||||||
| 	dws->rx_end = dws->rx + transfer->len; | 	dws->rx_end = dws->rx + transfer->len; | ||||||
| 	dws->len = transfer->len; | 	dws->len = transfer->len; | ||||||
|  | 	spin_unlock_irqrestore(&dws->buf_lock, flags); | ||||||
| 
 | 
 | ||||||
| 	spi_enable_chip(dws, 0); | 	spi_enable_chip(dws, 0); | ||||||
| 
 | 
 | ||||||
|  | @ -470,6 +478,7 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws) | ||||||
| 	dws->type = SSI_MOTO_SPI; | 	dws->type = SSI_MOTO_SPI; | ||||||
| 	dws->dma_inited = 0; | 	dws->dma_inited = 0; | ||||||
| 	dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR); | 	dws->dma_addr = (dma_addr_t)(dws->paddr + DW_SPI_DR); | ||||||
|  | 	spin_lock_init(&dws->buf_lock); | ||||||
| 
 | 
 | ||||||
| 	spi_controller_set_devdata(master, dws); | 	spi_controller_set_devdata(master, dws); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -119,6 +119,7 @@ struct dw_spi { | ||||||
| 	size_t			len; | 	size_t			len; | ||||||
| 	void			*tx; | 	void			*tx; | ||||||
| 	void			*tx_end; | 	void			*tx_end; | ||||||
|  | 	spinlock_t		buf_lock; | ||||||
| 	void			*rx; | 	void			*rx; | ||||||
| 	void			*rx_end; | 	void			*rx_end; | ||||||
| 	int			dma_mapped; | 	int			dma_mapped; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 wuxu.wu
						wuxu.wu