Commit 9e23897b authored by Jarkko Nikula's avatar Jarkko Nikula Committed by Alexandre Belloni
Browse files

i3c: mipi-i3c-hci: Use physical device pointer with DMA API



DMA transfer faults on Intel hardware when the IOMMU is enabled and
driver initialization will fail when attempting to do the first transfer:

	DMAR: DRHD: handling fault status reg 2
	DMAR: [DMA Read NO_PASID] Request device [00:11.0] fault addr 0x676e3000 [fault reason 0x71] SM: Present bit in first-level paging entry is clear
 	i3c mipi-i3c-hci.0: ring 0: Transfer Aborted
	mipi-i3c-hci mipi-i3c-hci.0: probe with driver mipi-i3c-hci failed with error -62

Reason for this is that the IOMMU setup is done for the physical devices
only and not for the virtual I3C Controller device object.

Therefore use the pointer to a physical device object with the DMA API.

Due to a data corruption observation when the device DMA is IOMMU
mapped, a properly sized receive bounce buffer is required if transfer
length is not a multiple of DWORDs.

Reported-by:
Signed-off-by: default avatarJarkko Nikula <jarkko.nikula@linux.intel.com>
Reviewed-by: default avatarFrank Li <Frank.Li@nxp.com>
Link: https://lore.kernel.org/r/20250822105630.2820009-4-jarkko.nikula@linux.intel.com


Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@bootlin.com>
parent 1c46bfc4
Loading
Loading
Loading
Loading
+33 −13
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <linux/errno.h>
#include <linux/i3c/master.h>
#include <linux/io.h>
#include <linux/pci.h>

#include "hci.h"
#include "cmd.h"
@@ -138,6 +139,7 @@ struct hci_rh_data {
};

struct hci_rings_data {
	struct device *sysdev;
	unsigned int total;
	struct hci_rh_data headers[] __counted_by(total);
};
@@ -165,20 +167,20 @@ static void hci_dma_cleanup(struct i3c_hci *hci)
		rh_reg_write(IBI_SETUP, 0);

		if (rh->xfer)
			dma_free_coherent(&hci->master.dev,
			dma_free_coherent(rings->sysdev,
					  rh->xfer_struct_sz * rh->xfer_entries,
					  rh->xfer, rh->xfer_dma);
		if (rh->resp)
			dma_free_coherent(&hci->master.dev,
			dma_free_coherent(rings->sysdev,
					  rh->resp_struct_sz * rh->xfer_entries,
					  rh->resp, rh->resp_dma);
		kfree(rh->src_xfers);
		if (rh->ibi_status)
			dma_free_coherent(&hci->master.dev,
			dma_free_coherent(rings->sysdev,
					  rh->ibi_status_sz * rh->ibi_status_entries,
					  rh->ibi_status, rh->ibi_status_dma);
		if (rh->ibi_data_dma)
			dma_unmap_single(&hci->master.dev, rh->ibi_data_dma,
			dma_unmap_single(rings->sysdev, rh->ibi_data_dma,
					 rh->ibi_chunk_sz * rh->ibi_chunks_total,
					 DMA_FROM_DEVICE);
		kfree(rh->ibi_data);
@@ -194,11 +196,23 @@ static int hci_dma_init(struct i3c_hci *hci)
{
	struct hci_rings_data *rings;
	struct hci_rh_data *rh;
	struct device *sysdev;
	u32 regval;
	unsigned int i, nr_rings, xfers_sz, resps_sz;
	unsigned int ibi_status_ring_sz, ibi_data_ring_sz;
	int ret;

	/*
	 * Set pointer to a physical device that does DMA and has IOMMU setup
	 * done for it in case of enabled IOMMU and use it with the DMA API.
	 * Here such device is either
	 * "mipi-i3c-hci" platform device (OF/ACPI enumeration) parent or
	 * grandparent (PCI enumeration).
	 */
	sysdev = hci->master.dev.parent;
	if (sysdev->parent && dev_is_pci(sysdev->parent))
		sysdev = sysdev->parent;

	regval = rhs_reg_read(CONTROL);
	nr_rings = FIELD_GET(MAX_HEADER_COUNT_CAP, regval);
	dev_info(&hci->master.dev, "%d DMA rings available\n", nr_rings);
@@ -213,6 +227,7 @@ static int hci_dma_init(struct i3c_hci *hci)
		return -ENOMEM;
	hci->io_data = rings;
	rings->total = nr_rings;
	rings->sysdev = sysdev;

	regval = FIELD_PREP(MAX_HEADER_COUNT, rings->total);
	rhs_reg_write(CONTROL, regval);
@@ -239,9 +254,9 @@ static int hci_dma_init(struct i3c_hci *hci)
		xfers_sz = rh->xfer_struct_sz * rh->xfer_entries;
		resps_sz = rh->resp_struct_sz * rh->xfer_entries;

		rh->xfer = dma_alloc_coherent(&hci->master.dev, xfers_sz,
		rh->xfer = dma_alloc_coherent(rings->sysdev, xfers_sz,
					      &rh->xfer_dma, GFP_KERNEL);
		rh->resp = dma_alloc_coherent(&hci->master.dev, resps_sz,
		rh->resp = dma_alloc_coherent(rings->sysdev, resps_sz,
					      &rh->resp_dma, GFP_KERNEL);
		rh->src_xfers =
			kmalloc_array(rh->xfer_entries, sizeof(*rh->src_xfers),
@@ -295,16 +310,16 @@ static int hci_dma_init(struct i3c_hci *hci)
		ibi_data_ring_sz = rh->ibi_chunk_sz * rh->ibi_chunks_total;

		rh->ibi_status =
			dma_alloc_coherent(&hci->master.dev, ibi_status_ring_sz,
			dma_alloc_coherent(rings->sysdev, ibi_status_ring_sz,
					   &rh->ibi_status_dma, GFP_KERNEL);
		rh->ibi_data = kmalloc(ibi_data_ring_sz, GFP_KERNEL);
		ret = -ENOMEM;
		if (!rh->ibi_status || !rh->ibi_data)
			goto err_out;
		rh->ibi_data_dma =
			dma_map_single(&hci->master.dev, rh->ibi_data,
			dma_map_single(rings->sysdev, rh->ibi_data,
				       ibi_data_ring_sz, DMA_FROM_DEVICE);
		if (dma_mapping_error(&hci->master.dev, rh->ibi_data_dma)) {
		if (dma_mapping_error(rings->sysdev, rh->ibi_data_dma)) {
			rh->ibi_data_dma = 0;
			ret = -ENOMEM;
			goto err_out;
@@ -372,6 +387,7 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci,
		u32 *ring_data = rh->xfer + rh->xfer_struct_sz * enqueue_ptr;
		enum dma_data_direction dir = xfer->rnw ? DMA_FROM_DEVICE :
							  DMA_TO_DEVICE;
		bool need_bounce;

		/* store cmd descriptor */
		*ring_data++ = xfer->cmd_desc[0];
@@ -390,10 +406,13 @@ static int hci_dma_queue_xfer(struct i3c_hci *hci,

		/* 2nd and 3rd words of Data Buffer Descriptor Structure */
		if (xfer->data) {
			xfer->dma = i3c_master_dma_map_single(&hci->master.dev,
			need_bounce = device_iommu_mapped(rings->sysdev) &&
				      xfer->rnw &&
				      xfer->data_len != ALIGN(xfer->data_len, 4);
			xfer->dma = i3c_master_dma_map_single(rings->sysdev,
							      xfer->data,
							      xfer->data_len,
							      false,
							      need_bounce,
							      dir);
			if (!xfer->dma) {
				hci_dma_unmap_xfer(hci, xfer_list, i);
@@ -581,6 +600,7 @@ static void hci_dma_recycle_ibi_slot(struct i3c_hci *hci,

static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
{
	struct hci_rings_data *rings = hci->io_data;
	struct i3c_dev_desc *dev;
	struct i3c_hci_dev_data *dev_data;
	struct hci_dma_dev_ibi_data *dev_ibi;
@@ -691,7 +711,7 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
			* rh->ibi_chunk_sz;
	if (first_part > ibi_size)
		first_part = ibi_size;
	dma_sync_single_for_cpu(&hci->master.dev, ring_ibi_data_dma,
	dma_sync_single_for_cpu(rings->sysdev, ring_ibi_data_dma,
				first_part, DMA_FROM_DEVICE);
	memcpy(slot->data, ring_ibi_data, first_part);

@@ -700,7 +720,7 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
		/* we wrap back to the start and copy remaining data */
		ring_ibi_data = rh->ibi_data;
		ring_ibi_data_dma = rh->ibi_data_dma;
		dma_sync_single_for_cpu(&hci->master.dev, ring_ibi_data_dma,
		dma_sync_single_for_cpu(rings->sysdev, ring_ibi_data_dma,
					ibi_size - first_part, DMA_FROM_DEVICE);
		memcpy(slot->data + first_part, ring_ibi_data,
		       ibi_size - first_part);