On Thu, May 16, 2024 at 05:11:21PM -0400, Nícolas F. R. A. Prado wrote: > On Thu, May 16, 2024 at 08:46:23PM +0300, Andy Shevchenko wrote: > > On Thu, May 16, 2024 at 12:25:19PM -0400, Nícolas F. R. A. Prado wrote: > > > On Thu, May 16, 2024 at 04:25:35PM +0300, Andy Shevchenko wrote: > > > > On Thu, May 16, 2024 at 01:18:04PM +0300, Andy Shevchenko wrote: > > > > > On Wed, May 15, 2024 at 05:09:33PM -0400, Nícolas F. R. A. Prado wrote: > > > > > > On Tue, May 07, 2024 at 11:10:27PM +0300, Andy Shevchenko wrote: > > > > > > > Both dma_unmap_sgtable() and sg_free_table() in spi_unmap_buf_attrs() > > > > > > > have checks for orig_nents against 0. No need to duplicate this. > > > > > > > All the same applies to other DMA mapping API calls. > > > > > > > > > > > > > > Also note, there is no other user in the kernel that does this kind of > > > > > > > checks. > > > > > > > > > > > > > > Signed-off-by: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx> > > > > > > > > > > > > this commit caused a regression which I reported here: > > > > > > > > > > > > https://lore.kernel.org/all/d3679496-2e4e-4a7c-97ed-f193bd53af1d@notapiano > > > > > > > > > > > > along with some thoughts on the cause and a possible solution, though I'm not > > > > > > familiar with this code base at all and would really appreciate any feedback you > > > > > > may have. > > > > > > > > > > Thanks for the report and preliminary analysis! > > > > > I'll look at it hopefully sooner than later. > > > > > > > > > > But at least what I think now is that my change revealed a problem somewhere > > > > > else, because that's how DMA mapping / streaming APIs designed, it's extremely > > > > > rare to check orig_nents field. > > > > > > > > Can you test the below patch? > > > > > > > > diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c > > > > index b2efd4964f7c..51811f04e463 100644 > > > > --- a/drivers/spi/spi.c > > > > +++ b/drivers/spi/spi.c > > > > @@ -1243,6 +1243,7 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) > > > > else > > > > rx_dev = ctlr->dev.parent; > > > > > > > > + ret = -ENOMSG; > > > > list_for_each_entry(xfer, &msg->transfers, transfer_list) { > > > > /* The sync is done before each transfer. */ > > > > unsigned long attrs = DMA_ATTR_SKIP_CPU_SYNC; > > > > @@ -1272,6 +1273,9 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) > > > > } > > > > } > > > > } > > > > + /* No transfer has been mapped, bail out with success */ > > > > + if (ret) > > > > + return 0; > > > > > > > > ctlr->cur_rx_dma_dev = rx_dev; > > > > ctlr->cur_tx_dma_dev = tx_dev; > > > > > > Hi Andy, > > > > > > thank you for the patch. Unfortunately it didn't completely solve the issue. Now > > > the stack trace is slightly different and points at the next line: > > > > > > dma_sync_sgtable_for_device(rx_dev, &xfer->rx_sg, DMA_FROM_DEVICE); > > > > > > So now we're hitting the case where only the tx buffer was DMA mapped, but the > > > rx is still uninitialized, though the cur_msg_mapped flag is set to true, since > > > it is shared between them. The original code checked for the initialization of > > > each scatterlist individually, which is why it worked. (So the above patch is okay, the below is wrong, but read at the bottom as well) > > I was kinda expecting that, and already have another patch to try (should > > applied on top): > > > > diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c > > index 51811f04e463..5c607dd21fe7 100644 > > --- a/drivers/spi/spi.c > > +++ b/drivers/spi/spi.c > > @@ -1258,6 +1258,8 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) > > attrs); > > if (ret != 0) > > return ret; > > + } else { > > + memset(&xfer->tx_sg, 0, sizeof(xfer->tx_sg)); > > } > > > > if (xfer->rx_buf != NULL) { > > @@ -1271,6 +1273,8 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) > > > > return ret; > > } > > + } else { > > + memset(&xfer->rx_sg, 0, sizeof(xfer->rx_sg)); > > } > > } > > /* No transfer has been mapped, bail out with success */ > > Still the same issue. I've attached the backtrace at the end for reference. But > I don't see how a memset would help here. As far as I can see, there's nothing > in the DMA API protecting it from a null pointer to be passed in. So when > > dma_sync_sgtable_for_device(tx_dev, &xfer->tx_sg, DMA_TO_DEVICE); > > is called with xfer->tx_sg.sgl being null, that will get passed all the way to > iommu_dma_sync_sg_for_device() and sg_dma_is_swiotlb(), where it'll be > dereferenced and cause the issue. Right, sorry I was missing that piece. > So it seems to me that either the DMA API > functions should check for the null pointer, or if the API doesn't want to > handle those cases (like sync being called before the buffer has been mapped), > then the caller needs to do the check, as was done in the original code. The dma-api.rst seems to imply that sync calls done after the mapping: "With the sync_sg API, all the parameters must be the same as those passed into the sg mapping API." The dma-api-howto.rst is clearer on this: "So, firstly, just map it with dma_map_{single,sg}(), and after each DMA transfer call either:: dma_sync_single_for_cpu(dev, dma_handle, size, direction); or:: dma_sync_sg_for_cpu(dev, sglist, nents, direction); as appropriate." So, it means the calling sync APIs on unprepared resources is a shooting in a foot. OTOH > The same applies for the change in spi_unmap_buf_attrs(). I see sg_free_table() > does handle a null sgl, but dma_unmap_sgtable() doesn't (and indeed I verified > null pointer dereference happens there too if I avoid this one). Taking into account the above, I think those memset()'s has actually to be paired with a dummy SG table, which is empty. --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -1220,6 +1220,11 @@ void spi_unmap_buf(struct spi_controller *ctlr, struct device *dev, spi_unmap_buf_attrs(ctlr, dev, sgt, dir, 0); } +/* Dummy SG for unidirect transfers */ +static struct scatterlist dummy_sg = { + .page_link = SG_END, +}; + static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) { struct device *tx_dev, *rx_dev; @@ -1260,6 +1265,7 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) return ret; } else { memset(&xfer->tx_sg, 0, sizeof(xfer->tx_sg)); + xfer->tx_sg.sgl = &dummy_sg; } if (xfer->rx_buf != NULL) { @@ -1275,6 +1281,7 @@ static int __spi_map_msg(struct spi_controller *ctlr, struct spi_message *msg) } } else { memset(&xfer->rx_sg, 0, sizeof(xfer->rx_sg)); + xfer->rx_sg.sgl = &dummy_sg; } } /* No transfer has been mapped, bail out with success */ But the best shot is to fix IOMMU for nents == 0 case in my opinion. Neglecting nents before accessing the SG is not a good idea. Catalin? The commit in question here is this one 861370f49ce4 ("iommu/dma: force bouncing if the size is not cacheline-aligned"). -- With Best Regards, Andy Shevchenko