All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "Cédric Le Goater" <clg@kaod.org>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Steven Lee" <steven_lee@aspeedtech.com>,
	"Troy Lee" <leetroy@gmail.com>,
	"Kane Chen" <kane_chen@aspeedtech.com>,
	"Andrew Jeffery" <andrew@codeconstruct.com.au>,
	"Joel Stanley" <joel@jms.id.au>,
	"open list:ASPEED BMCs" <qemu-arm@nongnu.org>,
	"open list:All patches CC here" <qemu-devel@nongnu.org>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>, Troy Lee <troy_lee@aspeedtech.com>
Subject: [PATCH v2 07/11] hw/usb/aspeed-udc: Support single-stage IN transfers
Date: Mon, 5 Oct 2026 06:44:27 +0000	[thread overview]
Message-ID: <20261005064418.3060881-8-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20261005064418.3060881-1-jamin_lin@aspeedtech.com>

The controller can send an IN endpoint's data from a descriptor ring or
from a single buffer. Only the descriptor ring was modelled, so an IN
endpoint in single stage mode sent nothing.

Add the path: one buffer at EP_DMA_BUFF, its length in PKT_SIZE, queued
by setting the write pointer. How to tell whether anything is queued
also depends on the mode. A descriptor ring is empty when its two
pointers are equal. A single stage buffer is queued by the write
pointer alone.

The buffer can hold more than the host asks for in one request, so keep
an offset into it. The rest goes out when the host asks again, and the
guest is told only once all of it has gone.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 include/hw/usb/aspeed-udc.h |   2 +
 hw/usb/aspeed-udc.c         | 108 ++++++++++++++++++++++++++++++++++--
 2 files changed, 106 insertions(+), 4 deletions(-)

diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
index 42916c0bd6..6665609501 100644
--- a/include/hw/usb/aspeed-udc.h
+++ b/include/hw/usb/aspeed-udc.h
@@ -53,6 +53,8 @@ typedef struct AspeedUDCEP {
     USBPacket *pkt;
     /* bytes of the current IN descriptor already served */
     uint32_t desc_off;
+    /* bytes of the single-stage IN buffer already sent */
+    uint32_t single_buf_off;
 } AspeedUDCEP;
 
 struct AspeedUDCGadget {
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index c48e2c5f1b..dcced1b8fb 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -566,6 +566,60 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_desc(AspeedUDCState *s,
     return done ? ASPEED_UDC_XFER_DONE : ASPEED_UDC_XFER_MORE;
 }
 
+/*
+ * IN transfer: the guest gadget driver put one buffer in EP_DMA_BUFF, its
+ * length in PKT_SIZE, and kicked it with the write pointer. Copy that
+ * buffer into the host's IN packet.
+ *
+ * The buffer can hold more than the host asked for. Copy what fits and
+ * remember how far we got in single_buf_off, so the rest goes out when
+ * the host asks again.
+ *
+ * A buffer smaller than the packet makes a short packet, and in USB a
+ * short packet ends the transfer.
+ */
+static AspeedUDCXferResult aspeed_udc_ep_xfer_in_single(AspeedUDCState *s,
+                                                        int ep, USBPacket *p)
+{
+    QEMUIOVector *pktiov = p->combined ? &p->combined->iov : &p->iov;
+    AspeedUDCEP *e = &s->ep[ep];
+    uint32_t buf_len = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, PKT_SIZE);
+    uint32_t left = buf_len > e->single_buf_off ?
+                    buf_len - e->single_buf_off : 0;
+    uint32_t pkt_space = pktiov->size > (uint32_t)p->actual_length ?
+                         pktiov->size - (uint32_t)p->actual_length : 0;
+    uint32_t addr = e->regs[R_EP_DMA_BUFF];
+    uint32_t len = MIN(left, pkt_space);
+
+    if (len && !aspeed_udc_ep_copy_to_pkt(s, ep, addr + e->single_buf_off,
+                                          len, p)) {
+        return ASPEED_UDC_XFER_ERROR;
+    }
+
+    e->single_buf_off += len;
+
+    /*
+     * The packet is full, but the buffer is not finished. Complete the
+     * packet. Do not ACK, and leave WPTR set: the guest still owns the
+     * buffer, and the next IN request sends the rest.
+     */
+    if (e->single_buf_off < buf_len) {
+        return ASPEED_UDC_XFER_DONE;
+    }
+
+    /* All of the buffer went out. Retire it and tell the driver. */
+    e->single_buf_off = 0;
+    e->regs[R_EP_DMA_STS] = FIELD_DP32(e->regs[R_EP_DMA_STS], EP_DMA_STS,
+                                       PKT_SIZE, buf_len);
+    e->regs[R_EP_DMA_STS] = FIELD_DP32(e->regs[R_EP_DMA_STS], EP_DMA_STS,
+                                       WPTR, 0);
+    e->regs[R_EP_DMA_CTRL] = FIELD_DP32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL,
+                                        PROC_STS, EP_DMA_CTRL_STS_TX_IDLE);
+    aspeed_udc_raise_ep_ack(s, ep);
+
+    return ASPEED_UDC_XFER_DONE;
+}
+
 /*
  * OUT transfer: receive data from the host by copying its OUT packet into the
  * buffer the guest gadget driver set up.
@@ -667,6 +721,40 @@ static void aspeed_udc_ep_in_kick_desc(AspeedUDCState *s, int ep,
     }
 }
 
+/*
+ * IN kick: the guest gadget driver wrote EP_DMA_STS to queue one buffer to
+ * send to the host. If a host IN request is already waiting (parked because
+ * there was no data before), send the buffer now and finish the request.
+ */
+static void aspeed_udc_ep_in_kick_single(AspeedUDCState *s, int ep)
+{
+    AspeedUDCEP *e = &s->ep[ep];
+    USBPacket *p = e->pkt;
+
+    /* Do nothing if no host request is waiting, or no buffer was queued. */
+    if (!p || !FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR)) {
+        return;
+    }
+
+    /* A kick serves a newly queued buffer, so start from its beginning. */
+    e->single_buf_off = 0;
+
+    switch (aspeed_udc_ep_xfer_in_single(s, ep, p)) {
+    case ASPEED_UDC_XFER_DONE:
+        e->pkt = NULL;
+        p->status = USB_RET_SUCCESS;
+        usb_packet_complete(USB_DEVICE(s->usbgadget), p);
+        break;
+    case ASPEED_UDC_XFER_ERROR:
+        e->pkt = NULL;
+        p->status = USB_RET_IOERROR;
+        usb_packet_complete(USB_DEVICE(s->usbgadget), p);
+        break;
+    case ASPEED_UDC_XFER_MORE:
+        break;
+    }
+}
+
 /*
  * OUT kick: the guest gadget driver wrote EP_DMA_STS to give us a buffer for
  * OUT data. If an OUT packet is already waiting (parked because there was no
@@ -739,6 +827,8 @@ static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data,
         } else {
             if (FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN)) {
                 aspeed_udc_ep_in_kick_desc(s, e->index, old_val);
+            } else {
+                aspeed_udc_ep_in_kick_single(s, e->index);
             }
         }
         break;
@@ -774,6 +864,7 @@ static void aspeed_udc_reset_hold(Object *obj, ResetType type)
         memset(s->ep[i].regs, 0, sizeof(s->ep[i].regs));
         s->ep[i].pkt = NULL;
         s->ep[i].desc_off = 0;
+        s->ep[i].single_buf_off = 0;
     }
 
     /* Device-reset default: root, DMA and EP-pool soft-reset bits set */
@@ -896,22 +987,31 @@ static int aspeed_udc_find_ep(AspeedUDCState *s, int ep_nr, bool is_out)
 static void aspeed_udc_ep_data_in(AspeedUDCState *s, int ep, USBPacket *p)
 {
     AspeedUDCEP *e = &s->ep[ep];
+    bool is_desc = FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN);
     uint32_t rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR);
     uint32_t wptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR);
+    AspeedUDCXferResult res;
 
-    if (rptr == wptr) {
+    /*
+     * In descriptor mode the ring is empty when the pointers meet;
+     * in single-stage mode one buffer is queued by setting the write pointer.
+     */
+    if (is_desc ? (rptr == wptr) : (wptr == 0)) {
         /*
          * No IN data is queued yet. Save the packet and return ASYNC
          * instead of NAK. A NAK would make the host retry slowly.
-         * aspeed_udc_ep_in_kick_desc() serves and completes this packet later,
-         * once the guest gadget driver queues descriptors.
+         * The kick from the guest gadget driver serves and completes this
+         * packet later, once it queues something to send.
          */
         e->pkt = p;
         p->status = USB_RET_ASYNC;
         return;
     }
 
-    switch (aspeed_udc_ep_xfer_in_desc(s, ep, p)) {
+    res = is_desc ? aspeed_udc_ep_xfer_in_desc(s, ep, p)
+                  : aspeed_udc_ep_xfer_in_single(s, ep, p);
+
+    switch (res) {
     case ASPEED_UDC_XFER_DONE:
         p->status = USB_RET_SUCCESS;
         break;
-- 
2.43.0


  parent reply	other threads:[~2026-10-05  6:47 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-05  6:44 [PATCH v2 00/11] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
2026-10-05  6:44 ` [PATCH v2 01/11] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable Jamin Lin
2026-10-12  6:27   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 02/11] hw/usb/aspeed-udc: Mask the reserved bits on register writes Jamin Lin
2026-10-12  6:27   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 03/11] hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left Jamin Lin
2026-10-12  6:28   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 04/11] hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode Jamin Lin
2026-10-12  6:28   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 05/11] hw/usb/aspeed-udc: Store the EP_DMA_STS write first Jamin Lin
2026-10-12  6:28   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 06/11] hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction Jamin Lin
2026-10-12  6:29   ` Cédric Le Goater
2026-10-05  6:44 ` Jamin Lin [this message]
2026-10-05  6:44 ` [PATCH v2 08/11] hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers Jamin Lin
2026-10-05  6:44 ` [PATCH v2 09/11] hw/usb/aspeed-udc: Add trace events for the transfer paths Jamin Lin
2026-10-12  6:29   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 10/11] hw/arm/aspeed_ast10x0: Add the USB device controller Jamin Lin
2026-10-12  6:29   ` Cédric Le Goater
2026-10-05  6:44 ` [PATCH v2 11/11] tests/functional/arm/test_aspeed_ast1030: Test the usbredir server transport Jamin Lin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261005064418.3060881-8-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=andrew@codeconstruct.com.au \
    --cc=clg@kaod.org \
    --cc=joel@jms.id.au \
    --cc=kane_chen@aspeedtech.com \
    --cc=leetroy@gmail.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=steven_lee@aspeedtech.com \
    --cc=troy_lee@aspeedtech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.