Skip to content

Commit 0dedf90

Browse files
vladimirolteanbroonie
authored andcommitted
spi: spi-fsl-dspi: Protect against races on dspi->words_in_flight
dspi->words_in_flight is a variable populated in the *_write functions and used in the dspi_fifo_read function. It is also used in dspi_fifo_write, immediately after transmission, to update the message->actual_length variable used by higher layers such as spi-mem for integrity checking. But it may happen that the IRQ which calls dspi_fifo_read to be triggered before the updating of message->actual_length takes place. In that case, dspi_fifo_read will decrement dspi->words_in_flight to -1, and that will cause an invalid modification of message->actual_length. For that, we make the simplest fix possible: to not decrement the actual shared variable in dspi->words_in_flight from dspi_fifo_read, but actually a copy of it which is on stack. But even if dspi_fifo_read from the next IRQ does not interfere with the dspi_fifo_write of the current chunk, the *next* dspi_fifo_write still can. So we must assume that everything after the last write to the TX FIFO can be preempted by the "TX complete" IRQ, and the dspi_fifo_write function must be safe against that. This means refactoring the 2 flavours of FIFO writes (for EOQ and XSPI) such that the calculation of the number of words to be written is common and happens a priori. This way, the code for updating the message->actual_length variable works with a copy and not with the volatile dspi->words_in_flight. After some interior debate, the dspi->progress variable used for software timestamping was *not* backed up against preemption in a copy on stack. Because if preemption does occur between spi_take_timestamp_pre and spi_take_timestamp_post, there's really no point in trying to save anything. The first-in-time spi_take_timestamp_post call with a dspi->progress higher than the requested xfer->ptp_sts_word_post will trigger xfer->timestamped = true anyway and will close the deal. To understand the above a bit better, consider a transfer with xfer->ptp_sts_word_pre = xfer->ptp_sts_word_post = 3, and xfer->bits_per_words = 8 (so byte 3 needs to be timestamped). The DSPI controller timestamps in chunks of 4 bytes at a time, and preemption occurs in the middle of timestamping the first chunk: spi_take_timestamp_pre(0) . . (preemption) . . spi_take_timestamp_pre(4) . . spi_take_timestamp_post(7) . spi_take_timestamp_post(3) So the reason I'm not bothering to back up dspi->progress for that spi_take_timestamp_post(3) is that spi_take_timestamp_post(7) is going to (a) be more honest, (b) provide better accuracy and (c) already render the spi_take_timestamp_post(3) into a noop by setting xfer->timestamped = true anyway. Fixes: d59c90a ("spi: spi-fsl-dspi: Convert TCFQ users to XSPI FIFO mode") Reported-by: Michael Walle <[email protected]> Signed-off-by: Vladimir Oltean <[email protected]> Tested-by: Michael Walle <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mark Brown <[email protected]>
1 parent c6c1e30 commit 0dedf90

File tree

1 file changed

+52
-59
lines changed

1 file changed

+52
-59
lines changed

drivers/spi/spi-fsl-dspi.c

Lines changed: 52 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -669,17 +669,26 @@ static void dspi_pushr_txdata_write(struct fsl_dspi *dspi, u16 txdata)
669669
regmap_write(dspi->regmap_pushr, dspi->pushr_tx, txdata);
670670
}
671671

672-
static void dspi_xspi_write(struct fsl_dspi *dspi, int cnt, bool eoq)
672+
static void dspi_xspi_fifo_write(struct fsl_dspi *dspi, int num_words)
673673
{
674+
int num_bytes = num_words * dspi->oper_word_size;
674675
u16 tx_cmd = dspi->tx_cmd;
675676

676-
if (eoq)
677+
/*
678+
* If the PCS needs to de-assert (i.e. we're at the end of the buffer
679+
* and cs_change does not want the PCS to stay on), then we need a new
680+
* PUSHR command, since this one (for the body of the buffer)
681+
* necessarily has the CONT bit set.
682+
* So send one word less during this go, to force a split and a command
683+
* with a single word next time, when CONT will be unset.
684+
*/
685+
if (!(dspi->tx_cmd & SPI_PUSHR_CMD_CONT) && num_bytes == dspi->len)
677686
tx_cmd |= SPI_PUSHR_CMD_EOQ;
678687

679688
/* Update CTARE */
680689
regmap_write(dspi->regmap, SPI_CTARE(0),
681690
SPI_FRAME_EBITS(dspi->oper_bits_per_word) |
682-
SPI_CTARE_DTCP(cnt));
691+
SPI_CTARE_DTCP(num_words));
683692

684693
/*
685694
* Write the CMD FIFO entry first, and then the two
@@ -688,7 +697,7 @@ static void dspi_xspi_write(struct fsl_dspi *dspi, int cnt, bool eoq)
688697
dspi_pushr_cmd_write(dspi, tx_cmd);
689698

690699
/* Fill TX FIFO with as many transfers as possible */
691-
while (cnt--) {
700+
while (num_words--) {
692701
u32 data = dspi_pop_tx(dspi);
693702

694703
dspi_pushr_txdata_write(dspi, data & 0xFFFF);
@@ -697,58 +706,15 @@ static void dspi_xspi_write(struct fsl_dspi *dspi, int cnt, bool eoq)
697706
}
698707
}
699708

700-
static void dspi_xspi_fifo_write(struct fsl_dspi *dspi)
701-
{
702-
int num_fifo_entries = dspi->devtype_data->fifo_size;
703-
int bytes_in_flight;
704-
bool eoq = false;
705-
706-
/* In XSPI mode each 32-bit word occupies 2 TX FIFO entries */
707-
if (dspi->oper_word_size == 4)
708-
num_fifo_entries /= 2;
709-
710-
/*
711-
* Integer division intentionally trims off odd (or non-multiple of 4)
712-
* numbers of bytes at the end of the buffer, which will be sent next
713-
* time using a smaller oper_word_size.
714-
*/
715-
dspi->words_in_flight = dspi->len / dspi->oper_word_size;
716-
717-
if (dspi->words_in_flight > num_fifo_entries)
718-
dspi->words_in_flight = num_fifo_entries;
719-
720-
bytes_in_flight = dspi->words_in_flight * dspi->oper_word_size;
721-
722-
/*
723-
* If the PCS needs to de-assert (i.e. we're at the end of the buffer
724-
* and cs_change does not want the PCS to stay on), then we need a new
725-
* PUSHR command, since this one (for the body of the buffer)
726-
* necessarily has the CONT bit set.
727-
* So send one word less during this go, to force a split and a command
728-
* with a single word next time, when CONT will be unset.
729-
*/
730-
if (!(dspi->tx_cmd & SPI_PUSHR_CMD_CONT) &&
731-
bytes_in_flight == dspi->len)
732-
eoq = true;
733-
734-
dspi_xspi_write(dspi, dspi->words_in_flight, eoq);
735-
}
736-
737-
static void dspi_eoq_fifo_write(struct fsl_dspi *dspi)
709+
static void dspi_eoq_fifo_write(struct fsl_dspi *dspi, int num_words)
738710
{
739-
int num_fifo_entries = dspi->devtype_data->fifo_size;
740711
u16 xfer_cmd = dspi->tx_cmd;
741712

742-
if (num_fifo_entries * dspi->oper_word_size > dspi->len)
743-
num_fifo_entries = dspi->len / dspi->oper_word_size;
744-
745-
dspi->words_in_flight = num_fifo_entries;
746-
747713
/* Fill TX FIFO with as many transfers as possible */
748-
while (num_fifo_entries--) {
714+
while (num_words--) {
749715
dspi->tx_cmd = xfer_cmd;
750716
/* Request EOQF for last transfer in FIFO */
751-
if (num_fifo_entries == 0)
717+
if (num_words == 0)
752718
dspi->tx_cmd |= SPI_PUSHR_CMD_EOQ;
753719
/* Write combined TX FIFO and CMD FIFO entry */
754720
dspi_pushr_write(dspi);
@@ -765,8 +731,10 @@ static u32 dspi_popr_read(struct fsl_dspi *dspi)
765731

766732
static void dspi_fifo_read(struct fsl_dspi *dspi)
767733
{
734+
int num_fifo_entries = dspi->words_in_flight;
735+
768736
/* Read one FIFO entry and push to rx buffer */
769-
while (dspi->words_in_flight--)
737+
while (num_fifo_entries--)
770738
dspi_push_rx(dspi, dspi_popr_read(dspi));
771739
}
772740

@@ -832,23 +800,48 @@ static void dspi_setup_accel(struct fsl_dspi *dspi)
832800

833801
static void dspi_fifo_write(struct fsl_dspi *dspi)
834802
{
803+
int num_fifo_entries = dspi->devtype_data->fifo_size;
835804
struct spi_transfer *xfer = dspi->cur_transfer;
836805
struct spi_message *msg = dspi->cur_msg;
837-
int bytes_sent;
806+
int num_words, num_bytes;
838807

839808
dspi_setup_accel(dspi);
840809

810+
/* In XSPI mode each 32-bit word occupies 2 TX FIFO entries */
811+
if (dspi->oper_word_size == 4)
812+
num_fifo_entries /= 2;
813+
814+
/*
815+
* Integer division intentionally trims off odd (or non-multiple of 4)
816+
* numbers of bytes at the end of the buffer, which will be sent next
817+
* time using a smaller oper_word_size.
818+
*/
819+
num_words = dspi->len / dspi->oper_word_size;
820+
if (num_words > num_fifo_entries)
821+
num_words = num_fifo_entries;
822+
823+
/* Update total number of bytes that were transferred */
824+
num_bytes = num_words * dspi->oper_word_size;
825+
msg->actual_length += num_bytes;
826+
dspi->progress += num_bytes / DIV_ROUND_UP(xfer->bits_per_word, 8);
827+
828+
/*
829+
* Update shared variable for use in the next interrupt (both in
830+
* dspi_fifo_read and in dspi_fifo_write).
831+
*/
832+
dspi->words_in_flight = num_words;
833+
841834
spi_take_timestamp_pre(dspi->ctlr, xfer, dspi->progress, !dspi->irq);
842835

843836
if (dspi->devtype_data->trans_mode == DSPI_EOQ_MODE)
844-
dspi_eoq_fifo_write(dspi);
837+
dspi_eoq_fifo_write(dspi, num_words);
845838
else
846-
dspi_xspi_fifo_write(dspi);
847-
848-
/* Update total number of bytes that were transferred */
849-
bytes_sent = dspi->words_in_flight * dspi->oper_word_size;
850-
msg->actual_length += bytes_sent;
851-
dspi->progress += bytes_sent / DIV_ROUND_UP(xfer->bits_per_word, 8);
839+
dspi_xspi_fifo_write(dspi, num_words);
840+
/*
841+
* Everything after this point is in a potential race with the next
842+
* interrupt, so we must never use dspi->words_in_flight again since it
843+
* might already be modified by the next dspi_fifo_write.
844+
*/
852845

853846
spi_take_timestamp_post(dspi->ctlr, dspi->cur_transfer,
854847
dspi->progress, !dspi->irq);

0 commit comments

Comments
 (0)