Re: [RFC] mipi-i3c-hci: Support for DMA Ring Pipelining / High-throughput Streaming
Frank Li <[email protected]>
| Newsgroups | org.infradead.lists.linux-i3c |
|---|---|
| Message-ID | <aiBxKRj6Aaw4RodS@lizhi-Precision-Tower-5810> |
On Wed, Jun 03, 2026 at 12:43:40AM -0700, Sam Agazaryan wrote: > On Sat, May 23, 2026 at 7:48 AM Frank Li <[email protected]> wrote: > > > > On Fri, May 22, 2026 at 03:37:35PM -0700, Sam Agazaryan wrote: > > > Hello all, > > > > > > I am working on a project using the mipi-i3c-hci driver that involves > > > large packet bursts (exceeding the physical hardware DMA ring size, > > > such as large MCTP-over-I3C payloads). > > > > How large to exceed DMA ring size, can you increase ring size? > > > > And if large transfer, it will defer IBI handle for while. IBI check only > > happen at every START phase. > > > > Right now we are working with the DMA ring maxed out at 255 entries. > The mipi i3c hci > controller I'm working with allows up to 128 bytes per transaction due > to hardware limitations of > the SoC. > > To my understanding with the driver implementation right now if I send > down 255 entries, TOC gets > set on the final 255th transaction, and according to the MIPI HCi > standard v1.2 section 6.12 this > means that the STOP signal will be sent on the bus after that final > transaction. My > design proposes a change to this: > > For transfers involving transfer descriptors less than or equal to the > maximum ring size the driver > functions exactly the same. > > For transfers involving more than the ring size, say ring size = 255 > and we send down 500 descriptors > to the driver - We begin setting up TOC = 1 on the 127th descriptor > and the last descriptor in the buffer, > this is so we can wake up the thread responsible for sending data down > the bus to queue up another 127 Assume left 128 entry, each entry only 1 byte, about 128 * (10 + 8)'s SCL, If SCL is 12M, only have 192us to fill new descriptor. time of wake up a thread may be bigger than this time. Frank > descriptors and then go back to waiting for completion again - until > the 127 descriptors the DMA controller > is currently handling is done. This is repeated until all 500 > descriptors are transferred. In theory, we should > still have relatively good IBI responsiveness compared to the current > state of the driver. > > > > > > > I noticed that the current driver implementation treats these as > > > discrete batches. > > > > > > I am considering implementing a ring pipelining or DMA streaming > > > mechanism to allow for asynchronous refills while the ring is running. > > > This would leverage the > > > standard ENQ_PTR doorbell mechanism (per MIPI HCI v1.2, Section 6.8.2) > > > to continuously feed the hardware. I figured in that case it may be > > > worth while to see how the upstream community feels about this > > > feature. > > > > > > Before I dive into the implementation for upstream, I wanted to check: > > > 1. Is there any existing work or a roadmap for DMA > > > streaming/pipelining in the HCI driver? > > > 2. Is a generic dma streaming mechanism for large transfers something > > > you would be interested in seeing as a contribution to the mainline > > > driver? > > > > Usb\network\storage is async. The I3C's framework is sync. > > > > I think I made this sound like an asynchronous operation where we > return before finishing all > transactions; the driver would still be synchronous in this case. > > > > > > > Currently, my proof-of-concept handles the pipelining at the core > > > transfer level, but I > > > > Can you send your patch as RFC to check what you already did? > > > > Yes, below is a implementation of the streaming logic > implemented in core.c. > > I initially considered moving this into dma.c to support multiple Ring > Bundles. However, because dma.c expects pre-translated hci_xfer structs, > moving the logic there would require core.c to allocate the > entire transfer array upfront (e.g., a massive kmalloc for 500+ structs > or however big the transfers end up being). This leads to heap exhaustion and > "Sleep while Atomic" crashes if an IBI triggers a readback > concurrently. By keeping > the sliding window in core.c, we can reuse a single, pre-allocated 255-entry > hci_xfer array. > > > Signed-off-by: Sam Agazaryan <[email protected]> > --- > drivers/i3c/master/mipi-i3c-hci/core.c | 133 +++++++++++++++++++------ > drivers/i3c/master/mipi-i3c-hci/dma.c | 1 - > drivers/i3c/master/mipi-i3c-hci/hci.h | 6 ++ > 3 files changed, 106 insertions(+), 34 deletions(-) > > diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c > b/drivers/i3c/master/mipi-i3c-hci/core.c > index b781dbed2165..77e04585150b 100644 > --- a/drivers/i3c/master/mipi-i3c-hci/core.c > +++ b/drivers/i3c/master/mipi-i3c-hci/core.c > @@ -290,9 +290,12 @@ static int i3c_hci_send_ccc_cmd(struct > i3c_master_controller *m, > dev_dbg(&hci->master.dev, "cmd=%#x rnw=%d ndests=%d data[0].len=%d", > ccc->id, ccc->rnw, ccc->ndests, ccc->dests[0].payload.len); > + mutex_lock(&hci->xfer_lock); > xfer = hci_alloc_xfer(nxfers); > - if (!xfer) > + if (!xfer) { > + mutex_unlock(&hci->xfer_lock); > return -ENOMEM; > + } > if (prefixed) { > xfer->data = NULL; > @@ -346,6 +349,7 @@ static int i3c_hci_send_ccc_cmd(struct > i3c_master_controller *m, > ccc->dests[0].payload.len, ccc->dests[0].payload.data); > out: > + mutex_unlock(&hci->xfer_lock); > hci_free_xfer(xfer, nxfers); > return ret; > } > @@ -363,53 +367,100 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev, > { > struct i3c_master_controller *m = i3c_dev_get_master(dev); > struct i3c_hci *hci = to_i3c_hci(m); > - struct hci_xfer *xfer; > - DECLARE_COMPLETION_ONSTACK(done); > + struct hci_xfer *xfer = hci->xfer_ring; > unsigned int size_limit; > - int i, last, ret = 0; > + int i, ret = 0; > + int processed = 0; > + int queued = 0; > + int ring_size = XFER_RING_ENTRIES; > + int chunk_limit = (ring_size - 1) / 2; > + int sw_ring_size = chunk_limit * 2; > dev_dbg(&hci->master.dev, "nxfers = %d", nxfers); > - xfer = hci_alloc_xfer(nxfers); > if (!xfer) > - return -ENOMEM; > + return -ENODEV; > + > + mutex_lock(&hci->xfer_lock); > + reinit_completion(&hci->xfer_done); > size_limit = 1U << (16 + FIELD_GET(HC_CAP_MAX_DATA_LENGTH, hci->caps)); > - for (i = 0; i < nxfers; i++) { > - xfer[i].data_len = i3c_xfers[i].len; > - ret = -EFBIG; > - if (xfer[i].data_len >= size_limit) > - goto out; > - xfer[i].rnw = i3c_xfers[i].rnw; > - if (i3c_xfers[i].rnw) { > - xfer[i].data = i3c_xfers[i].data.in; > - } else { > - /* silence the const qualifier warning with a cast */ > - xfer[i].data = (void *) i3c_xfers[i].data.out; > + /* 1. Prime the ring */ > + while (queued < nxfers && (queued - processed) < sw_ring_size) { > + int n = min(nxfers - queued, chunk_limit); > + struct hci_xfer *chunk_start = xfer + (queued % sw_ring_size); > + > + for (i = 0; i < n; i++) { > + int idx = queued + i; > + chunk_start[i].data_len = i3c_xfers[idx].len; > + chunk_start[i].rnw = i3c_xfers[idx].rnw; > + chunk_start[i].data = i3c_xfers[idx].rnw ? > + i3c_xfers[idx].data.in : > + (void *)i3c_xfers[idx].data.out; > + hci->cmd->prep_i3c_xfer(hci, dev, &chunk_start[i]); > + chunk_start[i].cmd_desc[0] |= CMD_0_ROC; > + chunk_start[i].completion = NULL; > } > - hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]); > - xfer[i].cmd_desc[0] |= CMD_0_ROC; > + chunk_start[n - 1].cmd_desc[0] |= CMD_0_TOC; > + chunk_start[n - 1].completion = &hci->xfer_done; > + > + ret = hci->io->queue_xfer(hci, chunk_start, n); > + if (ret) > + goto out; > + queued += n; > } > - last = i - 1; > - xfer[last].cmd_desc[0] |= CMD_0_TOC; > - xfer[last].completion = &done; > - xfer[last].timeout = HZ; > - ret = i3c_hci_process_xfer(hci, xfer, nxfers); > - if (ret) > - goto out; > - for (i = 0; i < nxfers; i++) { > - if (i3c_xfers[i].rnw) > - i3c_xfers[i].len = RESP_DATA_LENGTH(xfer[i].response); > - if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) { > - ret = -EIO; > + /* 2. Sliding Window Loop (Counting Semaphore) */ > + while (processed < nxfers) { > + if (!wait_for_completion_timeout(&hci->xfer_done, HZ)) { > + hci->io->dequeue_xfer(hci, xfer, ring_size); > + ret = -ETIME; > goto out; > } > + > + int n_done = min(nxfers - processed, chunk_limit); > + struct hci_xfer *done_chunk = xfer + (processed % sw_ring_size); > + > + for (i = 0; i < n_done; i++) { > + int idx = processed + i; > + if (i3c_xfers[idx].rnw) > + i3c_xfers[idx].len = RESP_DATA_LENGTH(done_chunk[i].response); > + if (RESP_STATUS(done_chunk[i].response) != RESP_SUCCESS) { > + ret = -EIO; > + goto out; > + } > + } > + processed += n_done; > + > + /* 3. Refill the ring */ > + if (queued < nxfers) { > + int n_next = min(nxfers - queued, chunk_limit); > + struct hci_xfer *next_chunk = xfer + (queued % sw_ring_size); > + > + for (i = 0; i < n_next; i++) { > + int idx = queued + i; > + next_chunk[i].data_len = i3c_xfers[idx].len; > + next_chunk[i].rnw = i3c_xfers[idx].rnw; > + next_chunk[i].data = i3c_xfers[idx].rnw ? > + i3c_xfers[idx].data.in : > + (void *)i3c_xfers[idx].data.out; > + hci->cmd->prep_i3c_xfer(hci, dev, &next_chunk[i]); > + next_chunk[i].cmd_desc[0] |= CMD_0_ROC; > + next_chunk[i].completion = NULL; > + } > + next_chunk[n_next - 1].cmd_desc[0] |= CMD_0_TOC; > + next_chunk[n_next - 1].completion = &hci->xfer_done; > + > + ret = hci->io->queue_xfer(hci, next_chunk, n_next); > + if (ret) > + goto out; > + queued += n_next; > + } > } > out: > - hci_free_xfer(xfer, nxfers); > + mutex_unlock(&hci->xfer_lock); > return ret; > } > @@ -424,9 +475,12 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, > dev_dbg(&hci->master.dev, "nxfers = %d", nxfers); > + mutex_lock(&hci->xfer_lock); > xfer = hci_alloc_xfer(nxfers); > - if (!xfer) > + if (!xfer) { > + mutex_unlock(&hci->xfer_lock); > return -ENOMEM; > + } > for (i = 0; i < nxfers; i++) { > xfer[i].data = i2c_xfers[i].buf; > @@ -451,6 +505,7 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, > } > out: > + mutex_unlock(&hci->xfer_lock); > hci_free_xfer(xfer, nxfers); > return ret; > } > @@ -1019,6 +1074,18 @@ static int i3c_hci_probe(struct platform_device *pdev) > if (hci->quirks & HCI_QUIRK_RPM_IBI_ALLOWED) > hci->master.rpm_ibi_allowed = true; > + /* Pre-allocate ring for high-throughput sliding window transfers */ > + > + hci->xfer_ring = devm_kcalloc(&pdev->dev, XFER_RING_ENTRIES, > sizeof(struct hci_xfer), GFP_KERNEL); > + > + if (!hci->xfer_ring) > + > + return -ENOMEM; > + > + init_completion(&hci->xfer_done); > + > + mutex_init(&hci->xfer_lock); > + > return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false); > } > diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c > b/drivers/i3c/master/mipi-i3c-hci/dma.c > index e4daaa612055..2cfd6ff25040 100644 > --- a/drivers/i3c/master/mipi-i3c-hci/dma.c > +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c > @@ -26,7 +26,6 @@ > */ > #define XFER_RINGS 1 /* max: 8 */ > -#define XFER_RING_ENTRIES 16 /* max: 255 */ > #define IBI_RINGS 1 /* max: 8 */ > #define IBI_STATUS_RING_ENTRIES 32 /* max: 255 */ > diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h > b/drivers/i3c/master/mipi-i3c-hci/hci.h > index f17f43494c1b..c6c8eabbcba8 100644 > --- a/drivers/i3c/master/mipi-i3c-hci/hci.h > +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h > @@ -37,6 +37,8 @@ struct dat_words { > }; > /* Our main structure */ > +#define XFER_RING_ENTRIES 255 /* max: 255 */ > + > struct i3c_hci { > struct i3c_master_controller master; > void __iomem *base_regs; > @@ -70,6 +72,10 @@ struct i3c_hci { > u32 vendor_version_id; > u32 vendor_product_id; > void *vendor_data; > + /* High-throughput sliding window support */ > + struct hci_xfer *xfer_ring; > + struct completion xfer_done; > + struct mutex xfer_lock; > }; > /* > -- > > -- > linux-i3c mailing list > [email protected] > http://lists.infradead.org/mailman/listinfo/linux-i3c -- linux-i3c mailing list [email protected] http://lists.infradead.org/mailman/listinfo/linux-i3c