Re: [PATCH 09/20] bus: mhi: ep: Add support for ring management
From: Manivannan Sadhasivam
Date: Fri Feb 04 2022 - 04:21:55 EST
On Wed, Jan 05, 2022 at 06:30:17PM -0600, Alex Elder wrote:
> On 12/2/21 5:35 AM, Manivannan Sadhasivam wrote:
> > Add support for managing the MHI ring. The MHI ring is a circular queue
> > of data structures used to pass the information between host and the
> > endpoint.
> >
> > MHI support 3 types of rings:
> >
> > 1. Transfer ring
> > 2. Event ring
> > 3. Command ring
> >
> > All rings reside inside the host memory and the MHI EP device maps it to
> > the device memory using blocks like PCIe iATU. The mapping is handled in
> > the MHI EP controller driver itself.
>
> A few more comments here. And with that, I'm done for today.
>
> -Alex
>
> > Signed-off-by: Manivannan Sadhasivam <manivannan.sadhasivam@xxxxxxxxxx>
> > ---
> > drivers/bus/mhi/ep/Makefile | 2 +-
> > drivers/bus/mhi/ep/internal.h | 23 +++
> > drivers/bus/mhi/ep/main.c | 53 +++++-
> > drivers/bus/mhi/ep/ring.c | 314 ++++++++++++++++++++++++++++++++++
> > include/linux/mhi_ep.h | 11 ++
> > 5 files changed, 401 insertions(+), 2 deletions(-)
> > create mode 100644 drivers/bus/mhi/ep/ring.c
> >
> > diff --git a/drivers/bus/mhi/ep/Makefile b/drivers/bus/mhi/ep/Makefile
> > index a1555ae287ad..7ba0e04801eb 100644
> > --- a/drivers/bus/mhi/ep/Makefile
> > +++ b/drivers/bus/mhi/ep/Makefile
> > @@ -1,2 +1,2 @@
> > obj-$(CONFIG_MHI_BUS_EP) += mhi_ep.o
> > -mhi_ep-y := main.o mmio.o
> > +mhi_ep-y := main.o mmio.o ring.o
> > diff --git a/drivers/bus/mhi/ep/internal.h b/drivers/bus/mhi/ep/internal.h
> > index 39eeb5f384e2..a7a4e6934f7d 100644
> > --- a/drivers/bus/mhi/ep/internal.h
> > +++ b/drivers/bus/mhi/ep/internal.h
> > @@ -97,6 +97,18 @@ enum mhi_ep_execenv {
> > MHI_EP_UNRESERVED
> > };
> > +/* Transfer Ring Element macros */
> > +#define MHI_EP_TRE_PTR(ptr) (ptr)
> > +#define MHI_EP_TRE_DWORD0(len) (len & MHI_MAX_MTU)
> > +#define MHI_EP_TRE_DWORD1(bei, ieot, ieob, chain) ((2 << 16) | (bei << 10) \
> > + | (ieot << 9) | (ieob << 8) | chain)
> > +#define MHI_EP_TRE_GET_PTR(tre) ((tre)->ptr)
> > +#define MHI_EP_TRE_GET_LEN(tre) ((tre)->dword[0] & 0xffff)
> > +#define MHI_EP_TRE_GET_CHAIN(tre) FIELD_GET(BIT(0), (tre)->dword[1])
> > +#define MHI_EP_TRE_GET_IEOB(tre) FIELD_GET(BIT(8), (tre)->dword[1])
> > +#define MHI_EP_TRE_GET_IEOT(tre) FIELD_GET(BIT(9), (tre)->dword[1])
> > +#define MHI_EP_TRE_GET_BEI(tre) FIELD_GET(BIT(10), (tre)->dword[1])
> > +
> > enum mhi_ep_ring_state {
> > RING_STATE_UINT = 0,
> > RING_STATE_IDLE,
> > @@ -161,6 +173,17 @@ struct mhi_ep_chan {
> > bool skip_td;
> > };
> > +/* MHI Ring related functions */
> > +void mhi_ep_ring_init(struct mhi_ep_ring *ring, enum mhi_ep_ring_type type, u32 id);
> > +void mhi_ep_ring_stop(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring);
> > +size_t mhi_ep_ring_addr2offset(struct mhi_ep_ring *ring, u64 ptr);
> > +int mhi_ep_ring_start(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring,
> > + union mhi_ep_ring_ctx *ctx);
> > +int mhi_ep_process_ring(struct mhi_ep_ring *ring);
> > +int mhi_ep_ring_add_element(struct mhi_ep_ring *ring, struct mhi_ep_ring_element *element,
> > + int evt_offset);
> > +void mhi_ep_ring_inc_index(struct mhi_ep_ring *ring);
> > +
> > /* MMIO related functions */
> > void mhi_ep_mmio_read(struct mhi_ep_cntrl *mhi_cntrl, u32 offset, u32 *regval);
> > void mhi_ep_mmio_write(struct mhi_ep_cntrl *mhi_cntrl, u32 offset, u32 val);
> > diff --git a/drivers/bus/mhi/ep/main.c b/drivers/bus/mhi/ep/main.c
> > index fddf75dfb9c7..6d448d42f527 100644
> > --- a/drivers/bus/mhi/ep/main.c
> > +++ b/drivers/bus/mhi/ep/main.c
> > @@ -18,6 +18,42 @@
> > static DEFINE_IDA(mhi_ep_cntrl_ida);
> > +static void mhi_ep_ring_worker(struct work_struct *work)
> > +{
> > + struct mhi_ep_cntrl *mhi_cntrl = container_of(work,
> > + struct mhi_ep_cntrl, ring_work);
> > + struct device *dev = &mhi_cntrl->mhi_dev->dev;
> > + struct mhi_ep_ring *ring;
> > + struct list_head *cp, *q;
> > + unsigned long flags;
> > + int ret = 0;
> > +
> > + /* Process the command ring first */
> > + ret = mhi_ep_process_ring(&mhi_cntrl->mhi_cmd->ring);
> > + if (ret) {
> > + dev_err(dev, "Error processing command ring\n");
> > + goto err_unlock;
> > + }
> > +
> > + spin_lock_irqsave(&mhi_cntrl->list_lock, flags);
> > + /* Process the channel rings now */
>
> Use list_for_each_entry_safe() here.
>
> But actually, rather than doing this, you can do the
> trick of grabbing the whole list under lock, then
> handling processing the entries outside of it. You'll
> have to judge whether that can be done, but basically:
>
> struct mhi_ep_ring *ring, *tmp;
> LIST_HEAD(list);
>
> spin_lock_irqsave(&mhi_cntrl->list_lock, flags);
>
> list_splice_init(&mhi_cntrl->ch_db_list, &list);
>
> spin_unlock_irqrestore(&mhi_cntrl->list_lock, flags);
>
> list_for_each_entry_safe(ring, tmp, &list, list) {
> list_del(&ring->list);
> ret = mhi_ep_process_ring(ring);
> ...
> }
>
Yes, I made this change while doing the bringup on SM8450.
> > + list_for_each_safe(cp, q, &mhi_cntrl->ch_db_list) {
> > + ring = list_entry(cp, struct mhi_ep_ring, list);
> > + list_del(cp);
> > + ret = mhi_ep_process_ring(ring);
> > + if (ret) {
> > + dev_err(dev, "Error processing channel ring: %d\n", ring->ch_id);
> > + goto err_unlock;
> > + }
> > +
> > + /* Re-enable channel interrupt */
> > + mhi_ep_mmio_enable_chdb_a7(mhi_cntrl, ring->ch_id);
> > + }
> > +
> > +err_unlock:
> > + spin_unlock_irqrestore(&mhi_cntrl->list_lock, flags);
> > +}
> > +
> > static void mhi_ep_release_device(struct device *dev)
> > {
> > struct mhi_ep_device *mhi_dev = to_mhi_ep_device(dev);
>
> . . .
>
> > +void mhi_ep_ring_inc_index(struct mhi_ep_ring *ring)
> > +{
> > + ring->rd_offset++;
> > + if (ring->rd_offset == ring->ring_size)
> > + ring->rd_offset = 0;
> > +}
> > +
> > +static int __mhi_ep_cache_ring(struct mhi_ep_ring *ring, size_t end)
> > +{
> > + struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
> > + struct device *dev = &mhi_cntrl->mhi_dev->dev;
> > + size_t start, copy_size;
> > + struct mhi_ep_ring_element *ring_shadow;
> > + phys_addr_t ring_shadow_phys;
> > + size_t size = ring->ring_size * sizeof(struct mhi_ep_ring_element);
>
> Do you cache the entire ring just in case you need to wrap
> around the end of it?
>
Actually caching the whole ring is not needed. I've now modified this code to
cache only the ring elements those need to be cached.
> > + int ret;
> > +
> > + /* No need to cache event rings */
> > + if (ring->type == RING_TYPE_ER)
> > + return 0;
> > +
> > + /* No need to cache the ring if write pointer is unmodified */
> > + if (ring->wr_offset == end)
> > + return 0;
> > +
> > + start = ring->wr_offset;
> > +
> > + /* Allocate memory for host ring */
> > + ring_shadow = mhi_cntrl->alloc_addr(mhi_cntrl, &ring_shadow_phys,
> > + size);
> > + if (!ring_shadow) {
> > + dev_err(dev, "Failed to allocate memory for ring_shadow\n");
> > + return -ENOMEM;
> > + }
> > +
> > + /* Map host ring */
> > + ret = mhi_cntrl->map_addr(mhi_cntrl, ring_shadow_phys,
> > + ring->ring_ctx->generic.rbase, size);
> > + if (ret) {
> > + dev_err(dev, "Failed to map ring_shadow\n\n");
> > + goto err_ring_free;
> > + }
> > +
> > + dev_dbg(dev, "Caching ring: start %d end %d size %d", start, end, copy_size);
> > +
> > + if (start < end) {
> > + copy_size = (end - start) * sizeof(struct mhi_ep_ring_element);
> > + memcpy_fromio(&ring->ring_cache[start], &ring_shadow[start], copy_size);
> > + } else {
> > + copy_size = (ring->ring_size - start) * sizeof(struct mhi_ep_ring_element);
> > + memcpy_fromio(&ring->ring_cache[start], &ring_shadow[start], copy_size);
> > + if (end)
> > + memcpy_fromio(&ring->ring_cache[0], &ring_shadow[0],
> > + end * sizeof(struct mhi_ep_ring_element));
> > + }
> > +
> > + /* Now unmap and free host ring */
> > + mhi_cntrl->unmap_addr(mhi_cntrl, ring_shadow_phys);
> > + mhi_cntrl->free_addr(mhi_cntrl, ring_shadow_phys, ring_shadow, size);
> > +
> > + return 0;
> > +
> > +err_ring_free:
> > + mhi_cntrl->free_addr(mhi_cntrl, ring_shadow_phys, &ring_shadow, size);
> > +
> > + return ret;
> > +}
> > +
>
> . . .
>
> > +static int mhi_ep_process_ring_element(struct mhi_ep_ring *ring, size_t offset)
> > +{
> > + struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
> > + struct device *dev = &mhi_cntrl->mhi_dev->dev;
> > + struct mhi_ep_ring_element *el;
> > + int ret = -ENODEV;
> > +
> > + /* Get the element and invoke the respective callback */
> > + el = &ring->ring_cache[offset];
> > +
>
> You already know that the ring_cb function pointer is non-null (you set
> it in mhi_ep_ring_init(), below). At least you *should* be able to
> be sure of that...
>
ack
> > + if (ring->ring_cb)
> > + ret = ring->ring_cb(ring, el);
> > + else
> > + dev_err(dev, "No callback registered for ring\n");
> > +
> > + return ret;
> > +}
> > +
> > +int mhi_ep_process_ring(struct mhi_ep_ring *ring)
> > +{
> > + struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
> > + struct device *dev = &mhi_cntrl->mhi_dev->dev;
> > + int ret = 0;
> > +
> > + /* Event rings should not be processed */
> > + if (ring->type == RING_TYPE_ER)
> > + return -EINVAL;
> > +
> > + dev_dbg(dev, "Processing ring of type: %d\n", ring->type);
> > +
> > + /* Update the write offset for the ring */
> > + ret = mhi_ep_update_wr_offset(ring);
> > + if (ret) {
> > + dev_err(dev, "Error updating write offset for ring\n");
> > + return ret;
> > + }
> > +
> > + /* Sanity check to make sure there are elements in the ring */
> > + if (ring->rd_offset == ring->wr_offset)
> > + return 0;
> > +
> > + /* Process channel ring first */
> > + if (ring->type == RING_TYPE_CH) {
> > + ret = mhi_ep_process_ring_element(ring, ring->rd_offset);
> > + if (ret)
> > + dev_err(dev, "Error processing ch ring element: %d\n", ring->rd_offset);
> > +
> > + return ret;
> > + }
> > +
> > + /* Process command ring now */
> > + while (ring->rd_offset != ring->wr_offset) {
> > + ret = mhi_ep_process_ring_element(ring, ring->rd_offset);
> > + if (ret) {
> > + dev_err(dev, "Error processing cmd ring element: %d\n", ring->rd_offset);
> > + return ret;
> > + }
> > +
> > + mhi_ep_ring_inc_index(ring);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/* TODO: Support for adding multiple ring elements to the ring */
> > +int mhi_ep_ring_add_element(struct mhi_ep_ring *ring, struct mhi_ep_ring_element *el, int size)
>
> I'm pretty sure the size argument is unused, so should be eliminated.
>
done
> > +{
> > + struct mhi_ep_cntrl *mhi_cntrl = ring->mhi_cntrl;
> > + struct device *dev = &mhi_cntrl->mhi_dev->dev;
> > + struct mhi_ep_ring_element *ring_shadow;
> > + size_t ring_size = ring->ring_size * sizeof(struct mhi_ep_ring_element);
>
> Use sizeof(*el) in the line above.
>
> > + phys_addr_t ring_shadow_phys;
> > + size_t old_offset = 0;
> > + u32 num_free_elem;
> > + int ret;
> > +
> > + ret = mhi_ep_update_wr_offset(ring);
> > + if (ret) {
> > + dev_err(dev, "Error updating write pointer\n");
> > + return ret;
> > + }
> > +
> > + if (ring->rd_offset < ring->wr_offset)
> > + num_free_elem = (ring->wr_offset - ring->rd_offset) - 1;
> > + else
> > + num_free_elem = ((ring->ring_size - ring->rd_offset) + ring->wr_offset) - 1;
> > +
> > + /* Check if there is space in ring for adding at least an element */
> > + if (num_free_elem < 1) {
>
> if (!num_free_elem) {
>
> > + dev_err(dev, "No space left in the ring\n");
> > + return -ENOSPC;
> > + }
> > +
> > + old_offset = ring->rd_offset;
> > + mhi_ep_ring_inc_index(ring);
> > +
> > + dev_dbg(dev, "Adding an element to ring at offset (%d)\n", ring->rd_offset);
> > +
> > + /* Update rp in ring context */
> > + ring->ring_ctx->generic.rp = (ring->rd_offset * sizeof(struct mhi_ep_ring_element)) +
> > + ring->ring_ctx->generic.rbase;
> > +
> > + /* Allocate memory for host ring */
> > + ring_shadow = mhi_cntrl->alloc_addr(mhi_cntrl, &ring_shadow_phys, ring_size);
> > + if (!ring_shadow) {
> > + dev_err(dev, "failed to allocate ring_shadow\n");
> > + return -ENOMEM;
> > + }
> > +
> > + /* Map host ring */
> > + ret = mhi_cntrl->map_addr(mhi_cntrl, ring_shadow_phys,
> > + ring->ring_ctx->generic.rbase, ring_size);
> > + if (ret) {
> > + dev_err(dev, "failed to map ring_shadow\n\n");
> > + goto err_ring_free;
> > + }
> > +
> > + /* Copy the element to ring */
>
> Use sizeof(*el) in the memcpy_toio() call.
>
> > + memcpy_toio(&ring_shadow[old_offset], el, sizeof(struct mhi_ep_ring_element));
> > +
> > + /* Now unmap and free host ring */
> > + mhi_cntrl->unmap_addr(mhi_cntrl, ring_shadow_phys);
> > + mhi_cntrl->free_addr(mhi_cntrl, ring_shadow_phys, ring_shadow, ring_size);
> > +
> > + return 0;
> > +
> > +err_ring_free:
> > + mhi_cntrl->free_addr(mhi_cntrl, ring_shadow_phys, ring_shadow, ring_size);
> > +
> > + return ret;
> > +}
> > +
> > +void mhi_ep_ring_init(struct mhi_ep_ring *ring, enum mhi_ep_ring_type type, u32 id)
> > +{
> > + ring->state = RING_STATE_UINT;
> > + ring->type = type;
> > + if (ring->type == RING_TYPE_CMD) {
> > + ring->db_offset_h = CRDB_HIGHER;
> > + ring->db_offset_l = CRDB_LOWER;
> > + } else if (ring->type == RING_TYPE_CH) {
> > + ring->db_offset_h = CHDB_HIGHER_n(id);
> > + ring->db_offset_l = CHDB_LOWER_n(id);
> > + ring->ch_id = id;
> > + } else if (ring->type == RING_TYPE_ER) {
> > + ring->db_offset_h = ERDB_HIGHER_n(id);
> > + ring->db_offset_l = ERDB_LOWER_n(id);
> > + }
>
> There is no other case, right? If you hit it, you should report
> it. Otherwise there's not really any need to check for ring->type
> RING_TYPE_ER...
>
Ack.
Thanks,
Mani
> -Alex
>
> > +}
> > +
> > +int mhi_ep_ring_start(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring,
> > + union mhi_ep_ring_ctx *ctx)
> > +{
> > + struct device *dev = &mhi_cntrl->mhi_dev->dev;
> > + int ret;
> > +
> > + ring->mhi_cntrl = mhi_cntrl;
> > + ring->ring_ctx = ctx;
> > + ring->ring_size = mhi_ep_ring_num_elems(ring);
> > +
> > + /* During ring init, both rp and wp are equal */
> > + ring->rd_offset = mhi_ep_ring_addr2offset(ring, ring->ring_ctx->generic.rp);
> > + ring->wr_offset = mhi_ep_ring_addr2offset(ring, ring->ring_ctx->generic.rp);
> > + ring->state = RING_STATE_IDLE;
> > +
> > + /* Allocate ring cache memory for holding the copy of host ring */
> > + ring->ring_cache = kcalloc(ring->ring_size, sizeof(struct mhi_ep_ring_element),
> > + GFP_KERNEL);
> > + if (!ring->ring_cache)
> > + return -ENOMEM;
> > +
> > + ret = mhi_ep_cache_ring(ring, ring->ring_ctx->generic.wp);
> > + if (ret) {
> > + dev_err(dev, "Failed to cache ring\n");
> > + kfree(ring->ring_cache);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +void mhi_ep_ring_stop(struct mhi_ep_cntrl *mhi_cntrl, struct mhi_ep_ring *ring)
> > +{
> > + ring->state = RING_STATE_UINT;
> > + kfree(ring->ring_cache);
> > +}
> > diff --git a/include/linux/mhi_ep.h b/include/linux/mhi_ep.h
> > index 902c8febd856..729f4b802b74 100644
> > --- a/include/linux/mhi_ep.h
> > +++ b/include/linux/mhi_ep.h
> > @@ -62,6 +62,11 @@ struct mhi_ep_db_info {
> > * @ch_ctx_host_pa: Physical address of host channel context data structure
> > * @ev_ctx_host_pa: Physical address of host event context data structure
> > * @cmd_ctx_host_pa: Physical address of host command context data structure
> > + * @ring_wq: Dedicated workqueue for processing MHI rings
> > + * @ring_work: Ring worker
> > + * @ch_db_list: List of queued channel doorbells
> > + * @st_transition_list: List of state transitions
> > + * @list_lock: Lock for protecting state transition and channel doorbell lists
> > * @chdb: Array of channel doorbell interrupt info
> > * @raise_irq: CB function for raising IRQ to the host
> > * @alloc_addr: CB function for allocating memory in endpoint for storing host context
> > @@ -90,6 +95,12 @@ struct mhi_ep_cntrl {
> > u64 ev_ctx_host_pa;
> > u64 cmd_ctx_host_pa;
> > + struct workqueue_struct *ring_wq;
> > + struct work_struct ring_work;
> > +
> > + struct list_head ch_db_list;
> > + struct list_head st_transition_list;
> > + spinlock_t list_lock;
> > struct mhi_ep_db_info chdb[4];
> > void (*raise_irq)(struct mhi_ep_cntrl *mhi_cntrl);
> >
>