> 
> Hello,
> 
> Who could help review this patch ?
> 

I am reworking this patch to make it easier to review and will send a v2 soon.

Thanks,
Jamin

> Thanks,
> 
> C.
> 
> On 7/3/26 09:43, Jamin Lin wrote:
> > Present the UDC gadget side to a USB host controller as a USB device
> > (TYPE_ASPEED_UDC_GADGET). This is a normal QEMU USB device, so it can
> > be attached to any USB host controller bus, not only the BMC's own EHCI.
> >
> > It forwards host transactions to the guest gadget driver by raising
> > the matching controller interrupts, and completes them once the driver
> > answers through the device-controller MMIO registers:
> >
> >    - EP0 control: the SETUP packet is mirrored to the SETUP data buffer,
> >      and the driver's IN/OUT data is copied to/from the host packet.
> >    - Bulk endpoints: IN uses the descriptor-list DMA ring, OUT uses the
> >      single-stage DMA buffer. A transfer larger than one host packet is
> >      served across several polls and the packet is parked until the gadget
> >      queues more data.
> >
> > The gadget connects to / disconnects from the host bus when the driver
> > sets or clears the upstream-enable bit (pull-up), and is detached on
> > reset so a guest reboot does not leave the host enumerating a gadget
> > with no driver behind it.
> >
> > Signed-off-by: Jamin Lin <[email protected]>
> > ---
> >   hw/usb/aspeed-udc.c         | 678
> +++++++++++++++++++++++++++++++++++-
> >   hw/usb/trace-events         |   9 +
> >   include/hw/usb/aspeed-udc.h |  30 ++
> >   3 files changed, 711 insertions(+), 6 deletions(-)
> >
> > diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c index
> > 9be9cfcf13..3d69e33516 100644
> > --- a/hw/usb/aspeed-udc.c
> > +++ b/hw/usb/aspeed-udc.c
> > @@ -9,18 +9,30 @@
> >    * "aspeed_udc" gadget driver. It implements one control endpoint (EP0)
> and
> >    * 4 programmable endpoints.
> >    *
> > - * This file is the system-bus side of the controller: the MMIO
> > register map,
> > - * the interrupt and the soft reset. The gadget USB device presented
> > to a host
> > - * controller (and the endpoint data path) is added on top of this.
> > + * The model has two faces:
> > + *   - a SysBus device exposing the MMIO register interface, the interrupt
> and
> > + *     the integrated DMA engine to the guest gadget driver (the "system
> bus
> > + *     device" section below);
> > + *   - a USBDevice presented on a host controller's bus, which forwards
> host
> > + *     transactions to the gadget by raising the matching controller
> interrupts
> > + *     and completes them once the gadget responds via MMIO (the "USB
> device"
> > + *     section below).
> >    */
> >
> >   #include "qemu/osdep.h"
> > +#include "qemu/error-report.h"
> >   #include "hw/core/irq.h"
> >   #include "hw/core/registerfields.h"
> > +#include "hw/core/qdev-properties.h"
> >   #include "hw/usb/aspeed-udc.h"
> >   #include "qemu/module.h"
> > +#include "qapi/error.h"
> > +#include "system/dma.h"
> > +#include "system/address-spaces.h"
> >   #include "trace.h"
> >
> > +#define AST_UDC_EP0_MAXPKT      64
> > +
> >   /* Root / Global registers (offset from the controller base) */
> >   REG32(UDC_FUNC_CTRL, 0x00)
> >       FIELD(UDC_FUNC_CTRL, UPSTREAM_EN,       0, 1)
> > @@ -90,11 +102,29 @@ REG32(EP_DMA_STS, 0x0C)
> >       FIELD(EP_DMA_STS, RPTR,                 8, 8)
> >       FIELD(EP_DMA_STS, TX_SIZE,             16, 11)
> >
> > +/* DMA descriptor ring (256-stage mode) and descriptor data limits */
> > +#define ASPEED_UDC_DESCS_COUNT  256 #define
> ASPEED_UDC_DESC_MAX_LEN
> > +4096
> > +
> > +/* DMA processing-status idle codes (EP_DMA_CTRL PROC_STS field) */
> > +#define EP_DMA_CTRL_STS_RX_IDLE 0x0 #define
> EP_DMA_CTRL_STS_TX_IDLE
> > +0x8
> > +
> > +/* DMA descriptor (DES1) fields, in guest memory */
> > +#define AST_EP_DESC1_IN_LEN(w1)     ((w1) & 0x1fff)
> > +/* interrupt-on-completion */
> > +#define AST_EP_DESC1_INTR           BIT(31)
> > +
> >   /* Device-reset default: root, DMA and EP-pool soft-reset bits set (0x301)
> */
> >   #define UDC_DEV_RESET_DEFAULT \
> >       (R_UDC_DEV_RESET_ROOT_MASK |
> R_UDC_DEV_RESET_DMA_MASK | \
> >        R_UDC_DEV_RESET_EP_POOL_MASK)
> >
> > +static bool aspeed_udc_ep_serve_in(AspeedUDCState *s, unsigned ep,
> > +                                   USBPacket *p, bool *complete);
> > +static bool aspeed_udc_ep_deliver_out(AspeedUDCState *s, unsigned ep,
> > +                                      USBPacket *p);
> > +
> >   static void aspeed_udc_update_irq(AspeedUDCState *s)
> >   {
> >       bool level;
> > @@ -107,6 +137,159 @@ static void
> aspeed_udc_update_irq(AspeedUDCState *s)
> >       qemu_set_irq(s->irq, level);
> >   }
> >
> > +static void aspeed_udc_raise_isr(AspeedUDCState *s, uint32_t mask) {
> > +    s->regs[R_UDC_ISR] |= mask;
> > +    aspeed_udc_update_irq(s);
> > +}
> > +
> > +/* Signal completion of a programmable-endpoint transaction to the
> > +gadget */ static void aspeed_udc_raise_ep_ack(AspeedUDCState *s,
> > +unsigned ep) {
> > +    trace_aspeed_udc_ep_ack(ep);
> > +    s->regs[R_UDC_EP_ACK_ISR] |= BIT(ep);
> > +    s->regs[R_UDC_ISR] |= R_UDC_ISR_EP_POOL_ACK_MASK;
> > +    aspeed_udc_update_irq(s);
> > +}
> > +
> > +/*
> > + * System bus device: MMIO register interface (guest gadget-driver
> > +facing)  */
> > +
> > +/* Connect/disconnect the gadget from the host bus (UBD00 upstream
> > +enable) */ static void aspeed_udc_set_pullup(AspeedUDCState *s, bool
> > +on) {
> > +    USBDevice *udev;
> > +    Error *err = NULL;
> > +
> > +    if (!s->usbgadget) {
> > +        /* no gadget device bound to this controller */
> > +        return;
> > +    }
> > +
> > +    udev = USB_DEVICE(s->usbgadget);
> > +    if (!udev->port) {
> > +        /* not attached to a host controller bus */
> > +        return;
> > +    }
> > +
> > +    trace_aspeed_udc_pullup(on, udev->attached);
> > +    if (on && !udev->attached) {
> > +        usb_device_attach(udev, &err);
> > +        if (err) {
> > +            warn_report_err(err);
> > +        }
> > +    } else if (!on && udev->attached) {
> > +        usb_device_detach(udev);
> > +    }
> > +}
> > +
> > +/* Complete the in-flight EP0 control transfer back to the host */
> > +static void aspeed_udc_ep0_complete(AspeedUDCState *s, uint32_t len)
> > +{
> > +    USBPacket *p = s->ep0_packet;
> > +
> > +    if (!p) {
> > +        return;
> > +    }
> > +
> > +    s->ep0_packet = NULL;
> > +    p->actual_length = s->ep0_dir_in ? MIN(len, s->ep0_setup_len)
> > +                                     : s->ep0_setup_len;
> > +    p->status = USB_RET_SUCCESS;
> > +    trace_aspeed_udc_ep0_complete(s->ep0_dir_in, p->actual_length);
> > +    usb_generic_async_ctrl_complete(USB_DEVICE(s->usbgadget), p); }
> > +
> > +/*
> > + * The gadget driver drives EP0 by writing UBD30. Translate those
> > +writes into
> > + * data movement to/from the deferred host control packet plus the
> > +matching
> > + * ACK interrupts the gadget expects.
> > + */
> > +static void aspeed_udc_ep0_ctrl_write(AspeedUDCState *s, uint32_t
> > +val) {
> > +    uint32_t buf_addr = s->regs[R_UDC_EP0_DATA_BUFF];
> > +    uint32_t store = val & R_UDC_EP0_CTRL_STALL_MASK;
> > +    uint32_t txlen;
> > +    USBPacket *p;
> > +    uint32_t n;
> > +
> > +    trace_aspeed_udc_ep0_ctrl_write(val, s->ep0_dir_in,
> > + s->ep0_offset);
> > +
> > +    /* Gadget stalled EP0: fail the pending control transfer */
> > +    if (val & R_UDC_EP0_CTRL_STALL_MASK) {
> > +        p = s->ep0_packet;
> > +
> > +        if (p) {
> > +            s->ep0_packet = NULL;
> > +            p->status = USB_RET_STALL;
> > +
> usb_generic_async_ctrl_complete(USB_DEVICE(s->usbgadget), p);
> > +        }
> > +        s->regs[R_UDC_EP0_CTRL] = store;
> > +        return;
> > +    }
> > +
> > +    if (val & R_UDC_EP0_CTRL_TX_RDY_MASK) {
> > +        txlen = FIELD_EX32(val, UDC_EP0_CTRL, TX_LEN);
> > +
> > +        if (s->ep0_dir_in && s->ep0_packet) {
> > +            /* IN data stage: copy a chunk from the gadget's DMA buffer
> */
> > +            n = MIN(txlen, s->ep0_setup_len - s->ep0_offset);
> > +
> > +            if (n) {
> > +                dma_memory_read(&address_space_memory,
> buf_addr,
> > +                                s->ep0_data + s->ep0_offset, n,
> > +                                MEMTXATTRS_UNSPECIFIED);
> > +            }
> > +            s->ep0_offset += n;
> > +            aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_IN_ACK_MASK);
> > +
> > +            if (txlen < AST_UDC_EP0_MAXPKT ||
> > +                s->ep0_offset >= s->ep0_setup_len) {
> > +                aspeed_udc_ep0_complete(s, s->ep0_offset);
> > +            }
> > +        } else {
> > +            /* Zero-length status IN for an OUT / no-data control transfer
> */
> > +            aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_IN_ACK_MASK);
> > +            aspeed_udc_ep0_complete(s, s->ep0_offset);
> > +        }
> > +
> > +    } else if (val & R_UDC_EP0_CTRL_RX_RDY_MASK) {
> > +        if (s->ep0_dir_in) {
> > +            /* Status stage OUT (zero length) following IN data */
> > +            aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_OUT_ACK_MASK);
> > +        } else if (s->ep0_packet) {
> > +            /* OUT data stage: hand a chunk of host data to the gadget
> */
> > +            n = MIN(s->ep0_setup_len - s->ep0_offset,
> > +                    AST_UDC_EP0_MAXPKT);
> > +
> > +            if (n) {
> > +                dma_memory_write(&address_space_memory,
> buf_addr,
> > +                                 s->ep0_data + s->ep0_offset, n,
> > +                                 MEMTXATTRS_UNSPECIFIED);
> > +            }
> > +            s->ep0_offset += n;
> > +            store = FIELD_DP32(store, UDC_EP0_CTRL, RX_LEN, n);
> > +            aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_OUT_ACK_MASK);
> > +        }
> > +    }
> > +
> > +    s->regs[R_UDC_EP0_CTRL] = store;
> > +}
> > +
> > +/* The upstream-enable bit connects/disconnects the gadget from the
> > +host bus */ static void aspeed_udc_func_ctrl_write(AspeedUDCState *s,
> > +uint32_t val) {
> > +    bool was_on = FIELD_EX32(s->regs[R_UDC_FUNC_CTRL],
> > +                             UDC_FUNC_CTRL, UPSTREAM_EN);
> > +    bool now_on = FIELD_EX32(val, UDC_FUNC_CTRL, UPSTREAM_EN);
> > +
> > +    s->regs[R_UDC_FUNC_CTRL] = val;
> > +    if (now_on != was_on) {
> > +        aspeed_udc_set_pullup(s, now_on);
> > +    }
> > +}
> > +
> >   static uint64_t aspeed_udc_read(void *opaque, hwaddr offset, unsigned
> size)
> >   {
> >       AspeedUDCState *s = ASPEED_UDC(opaque); @@ -132,6 +315,12
> @@
> > static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t data,
> >           /* Status registers are write-1-to-clear */
> >           s->regs[reg] &= ~val;
> >           break;
> > +    case R_UDC_FUNC_CTRL:
> > +        aspeed_udc_func_ctrl_write(s, val);
> > +        break;
> > +    case R_UDC_EP0_CTRL:
> > +        aspeed_udc_ep0_ctrl_write(s, val);
> > +        return;
> >       default:
> >           s->regs[reg] = val;
> >           break;
> > @@ -154,6 +343,70 @@ static const MemoryRegionOps aspeed_udc_ops = {
> >       },
> >   };
> >
> > +/*
> > + * Descriptor-mode IN kick: the gadget wrote EP_DMA_STS to advance
> > +its write
> > + * pointer (queue more IN data). Adopt the write pointer, keep our
> > +read pointer
> > + * (the DMA engine owns it), then serve and complete a parked IN poll if
> any.
> > + */
> > +static void aspeed_udc_ep_dma_kick(AspeedUDCState *s, unsigned ep,
> > +uint32_t val) {
> > +    AspeedUDCEP *e = &s->ep[ep];
> > +    uint32_t rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS,
> RPTR);
> > +    uint32_t rptr_w = FIELD_EX32(val, EP_DMA_STS, RPTR);
> > +    uint32_t wptr = FIELD_EX32(val, EP_DMA_STS, WPTR);
> > +    uint32_t sts = e->regs[R_EP_DMA_STS];
> > +    USBPacket *p = e->pkt;
> > +    bool complete;
> > +
> > +    sts = FIELD_DP32(sts, EP_DMA_STS, WPTR, wptr);
> > +
> > +    /*
> > +     * A normal kick writes only the write pointer (bits[7:0]); the
> read-pointer
> > +     * field is zero and is ours to advance. Only an explicit non-zero read
> > +     * pointer equal to the write pointer (the driver draining the ring, 
> > e.g.
> on
> > +     * reset) resets our read pointer. Testing rptr==wptr alone would
> wrongly
> > +     * match a normal kick whose write pointer just wrapped to 0,
> resetting the
> > +     * ring and stranding a parked packet until the host times out.
> > +     */
> > +    if (rptr_w != 0 && rptr_w == wptr) {
> > +        rptr = rptr_w;
> > +        e->desc_off = 0;
> > +    }
> > +    e->regs[R_EP_DMA_STS] = FIELD_DP32(sts, EP_DMA_STS, RPTR, rptr);
> > +
> > +    if (p && rptr != wptr) {
> > +        if (aspeed_udc_ep_serve_in(s, ep, p, &complete)) {
> > +            aspeed_udc_raise_ep_ack(s, ep);
> > +        }
> > +        if (complete) {
> > +            e->pkt = NULL;
> > +            p->status = USB_RET_SUCCESS;
> > +            usb_packet_complete(USB_DEVICE(s->usbgadget), p);
> > +        }
> > +    }
> > +}
> > +
> > +/*
> > + * Single-stage OUT arm: the gadget wrote EP_DMA_STS to offer a
> > +receive
> > + * buffer. If a host OUT packet is parked waiting for it,
> > + * deliver into the buffer now and complete it (or keep it parked if
> > +the host
> > + * data phase is larger than this one armed chunk).
> > + */
> > +static void aspeed_udc_ep_out_kick(AspeedUDCState *s, unsigned ep) {
> > +    AspeedUDCEP *e = &s->ep[ep];
> > +    USBPacket *p = e->pkt;
> > +
> > +    if (!p || !FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR))
> {
> > +        return;
> > +    }
> > +    if (aspeed_udc_ep_deliver_out(s, ep, p)) {
> > +        e->pkt = NULL;
> > +        p->status = USB_RET_SUCCESS;
> > +        usb_packet_complete(USB_DEVICE(s->usbgadget), p);
> > +    }
> > +}
> > +
> >   static uint64_t aspeed_udc_ep_read(void *opaque, hwaddr offset,
> unsigned size)
> >   {
> >       AspeedUDCEP *e = opaque;
> > @@ -167,9 +420,33 @@ static void aspeed_udc_ep_write(void *opaque,
> hwaddr offset, uint64_t data,
> >                                   unsigned size)
> >   {
> >       AspeedUDCEP *e = opaque;
> > -
> > -    trace_aspeed_udc_ep_write(e->index, offset, data);
> > -    e->regs[offset >> 2] = data;
> > +    AspeedUDCState *s = e->udc;
> > +    uint32_t reg = offset >> 2;
> > +    uint32_t val = data;
> > +    uint32_t ctrl;
> > +
> > +    trace_aspeed_udc_ep_write(e->index, offset, val);
> > +
> > +    /*
> > +     * A write to EP_DMA_STS is a DMA kick. In descriptor mode (IN) it
> advances
> > +     * the ring and resumes a parked IN poll; in single-stage mode (OUT)
> it
> > +     * arms a receive buffer and delivers a parked OUT packet. Every
> other
> > +     * EP register is plain storage.
> > +     */
> > +    if (reg == R_EP_DMA_STS) {
> > +        ctrl = e->regs[R_EP_DMA_CTRL];
> > +
> > +        if (FIELD_EX32(ctrl, EP_DMA_CTRL, DESC_OP_EN)) {
> > +            /* descriptor-mode IN */
> > +            aspeed_udc_ep_dma_kick(s, e->index, val);
> > +        } else {
> > +            /* single-stage OUT */
> > +            e->regs[reg] = val;
> > +            aspeed_udc_ep_out_kick(s, e->index);
> > +        }
> > +    } else {
> > +        e->regs[reg] = val;
> > +    }
> >   }
> >
> >   static const MemoryRegionOps aspeed_udc_ep_ops = { @@ -206,6
> +483,7
> > @@ static void aspeed_udc_realize(DeviceState *dev, Error **errp)
> >       for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
> >           g_autofree char *name = g_strdup_printf(TYPE_ASPEED_UDC
> > ".ep%d", i);
> >
> > +        s->ep[i].udc = s;
> >           s->ep[i].index = i;
> >           s->ep[i].regs = g_new0(uint32_t, ASPEED_UDC_EP_NR_REGS);
> >           memory_region_init_io(&s->ep[i].mr, OBJECT(s),
> > &aspeed_udc_ep_ops, @@ -226,8 +504,26 @@ static void
> aspeed_udc_reset_hold(Object *obj, ResetType type)
> >       memset(s->regs, 0, ASPEED_UDC_NR_REGS * sizeof(uint32_t));
> >       for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
> >           memset(s->ep[i].regs, 0, ASPEED_UDC_EP_NR_REGS *
> > sizeof(uint32_t));
> > +        s->ep[i].pkt = NULL;
> > +        s->ep[i].desc_off = 0;
> >       }
> >       s->regs[R_UDC_DEV_RESET] = UDC_DEV_RESET_DEFAULT;
> > +    s->ep0_packet = NULL;
> > +
> > +    /*
> > +     * A machine reset (e.g. a guest reboot) wipes the gadget driver, so a
> > +     * device still attached to the host bus from before the reset is now
> > +     * dead. Detach it, otherwise the rebooted host tries to re-enumerate
> a
> > +     * gadget with no driver behind it and fails with -110. It re-attaches
> > +     * when the new gadget asserts pull-up (UBD00 upstream-enable).
> > +     */
> > +    if (s->usbgadget) {
> > +        USBDevice *udev = USB_DEVICE(s->usbgadget);
> > +
> > +        if (udev->attached) {
> > +            usb_device_detach(udev);
> > +        }
> > +    }
> >   }
> >
> >   static void aspeed_udc_unrealize(DeviceState *dev) @@ -252,6
> > +548,370 @@ static void aspeed_udc_class_init(ObjectClass *klass, const
> void *data)
> >       rc->phases.hold = aspeed_udc_reset_hold;
> >   }
> >
> > +/*
> > + * USB device: gadget presented on a host controller's bus
> > + *
> > + * These callbacks run in the context of the host controller. They
> > +translate
> > + * host transactions into the controller interrupts/state the gadget
> > +driver
> > + * expects, then defer (USB_RET_ASYNC) until the driver responds
> > +through the
> > + * MMIO register interface above.
> > + */
> > +
> > +/* Locate the programmable endpoint matching a host transaction */
> > +static int aspeed_udc_find_ep(AspeedUDCState *s, int ep_nr, bool
> > +is_out) {
> > +    uint32_t cfg;
> > +    int i;
> > +
> > +    for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
> > +        cfg = s->ep[i].regs[R_EP_CONFIG];
> > +
> > +        if (!FIELD_EX32(cfg, EP_CONFIG, ENABLE) ||
> > +            FIELD_EX32(cfg, EP_CONFIG, EP_NUM) != ep_nr) {
> > +            continue;
> > +        }
> > +        if (!!FIELD_EX32(cfg, EP_CONFIG, DIR_OUT) == is_out) {
> > +            return i;
> > +        }
> > +    }
> > +    return -1;
> > +}
> > +
> > +/*
> > + * IN endpoint, descriptor-list mode: serve the descriptors the
> > +gadget queued
> > + * (read pointer .. write pointer) into the host IN packet.
> > + *
> > + * The host controller coalesces a multi-packet bulk IN into one
> > +packet whose
> > + * iovec may span several descriptors, so drain as many queued
> > +descriptors as
> > + * fit. A descriptor bigger than the remaining room is served
> > +partially and
> > + * resumed on the next poll via desc_off; the read pointer only
> > +advances once a
> > + * descriptor is fully consumed (so the gadget driver, which reads
> > +the read
> > + * pointer back, learns exactly what completed).
> > + *
> > + * *complete is set true when the host transaction is finished
> > +(packet full, or
> > + * a short/zero-length descriptor ended the gadget's transfer) and
> > +false when
> > + * the ring ran dry with the packet still short - the caller then
> > +keeps the
> > + * packet parked and resumes on the next kick, rather than completing
> > +it short
> > + * (a maxpacket-multiple short read is not seen as end-of-transfer by
> > +the host,
> > + * which would then wait forever for data the gadget considers already
> sent).
> > + *
> > + * Returns true when the gadget should be signalled (EP ACK): the
> > +ring drained
> > + * (its descriptor handler waits for read==write) or a descriptor
> > +asked for an
> > + * interrupt.
> > + */
> > +static bool aspeed_udc_ep_serve_in(AspeedUDCState *s, unsigned ep,
> > +                                   USBPacket *p, bool *complete) {
> > +    QEMUIOVector *pktiov = p->combined ? &p->combined->iov : &p->iov;
> > +    AspeedUDCEP *e = &s->ep[ep];
> > +    uint32_t mps = FIELD_EX32(e->regs[R_EP_CONFIG], EP_CONFIG,
> MAX_PKT);
> > +    uint32_t base = e->regs[R_EP_DMA_BUFF];
> > +    uint32_t sts = e->regs[R_EP_DMA_STS];
> > +    uint32_t wptr = FIELD_EX32(sts, EP_DMA_STS, WPTR);
> > +    uint32_t rptr = FIELD_EX32(sts, EP_DMA_STS, RPTR);
> > +    uint32_t remaining;
> > +    uint8_t buf[1024];
> > +    /* des_0: buffer base, des_1: control/len */
> > +    uint32_t desc[2];
> > +    uint32_t copied;
> > +    uint32_t chunk;
> > +    uint32_t dlen;
> > +    uint32_t room;
> > +    uint32_t seg;
> > +    uint32_t off;
> > +    uint32_t w0;
> > +    uint32_t w1;
> > +    bool done = false;
> > +    bool ack = false;
> > +
> > +    if (mps == 0) {
> > +        /* a MAX_PKT field of 0 means 1024 bytes */
> > +        mps = 1024;
> > +    }
> > +
> > +    trace_aspeed_udc_ep_data_in(ep, rptr, wptr, pktiov->size);
> > +
> > +    while (rptr != wptr) {
> > +        copied = 0;
> > +        dma_memory_read(&address_space_memory, base + rptr *
> sizeof(desc),
> > +                        desc, sizeof(desc),
> MEMTXATTRS_UNSPECIFIED);
> > +        w0 = le32_to_cpu(desc[0]);
> > +        w1 = le32_to_cpu(desc[1]);
> > +        dlen = AST_EP_DESC1_IN_LEN(w1);
> > +        off = e->desc_off;
> > +        remaining = dlen > off ? dlen - off : 0;
> > +        room = pktiov->size > (uint32_t)p->actual_length ?
> > +               pktiov->size - (uint32_t)p->actual_length : 0;
> > +        chunk = MIN(remaining, room);
> > +
> > +        while (copied < chunk) {
> > +            seg = MIN(chunk - copied, sizeof(buf));
> > +            dma_memory_read(&address_space_memory, w0 + off +
> copied, buf, seg,
> > +                            MEMTXATTRS_UNSPECIFIED);
> > +            usb_packet_copy(p, buf, seg);
> > +            copied += seg;
> > +        }
> > +        e->desc_off = off + chunk;
> > +
> > +        if (e->desc_off < dlen) {
> > +            /* Room exhausted mid-descriptor: the packet is full */
> > +            done = true;
> > +            break;
> > +        }
> > +
> > +        /* Descriptor fully served: advance the read pointer */
> > +        rptr = (rptr + 1) % ASPEED_UDC_DESCS_COUNT;
> > +        e->desc_off = 0;
> > +        if (w1 & AST_EP_DESC1_INTR) {
> > +            ack = true;
> > +        }
> > +        /* short/zero-length stage ends the transfer */
> > +        if (dlen < mps) {
> > +            done = true;
> > +            break;
> > +        }
> > +        if ((uint32_t)p->actual_length >= pktiov->size) {
> > +            done = true;
> > +            break;
> > +        }
> > +    }
> > +
> > +    e->regs[R_EP_DMA_STS] = FIELD_DP32(sts, EP_DMA_STS, RPTR, rptr);
> > +    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);
> > +    /* The gadget driver completes its request when the ring drains */
> > +    if (rptr == wptr) {
> > +        ack = true;
> > +    }
> > +
> > +    *complete = done;
> > +    return ack;
> > +}
> > +
> > +static void aspeed_udc_ep_data_in(AspeedUDCState *s, unsigned ep,
> > +USBPacket *p) {
> > +    AspeedUDCEP *e = &s->ep[ep];
> > +    uint32_t sts = e->regs[R_EP_DMA_STS];
> > +    uint32_t rptr = FIELD_EX32(sts, EP_DMA_STS, RPTR);
> > +    uint32_t wptr = FIELD_EX32(sts, EP_DMA_STS, WPTR);
> > +    bool complete;
> > +
> > +    if (rptr == wptr) {
> > +        /*
> > +         * Nothing queued yet. Park the packet as ASYNC rather than
> NAKing: the
> > +         * gadget will kick shortly and the kick handler serves and
> completes
> > +         * it, waking the host immediately. NAK would leave the host to
> re-poll
> > +         * on its much slower async-schedule timer, collapsing bulk
> throughput.
> > +         */
> > +        e->pkt = p;
> > +        p->status = USB_RET_ASYNC;
> > +        return;
> > +    }
> > +
> > +    if (aspeed_udc_ep_serve_in(s, ep, p, &complete)) {
> > +        aspeed_udc_raise_ep_ack(s, ep);
> > +    }
> > +    if (complete) {
> > +        p->status = USB_RET_SUCCESS;
> > +    } else {
> > +        /*
> > +         * Ring ran dry before the request was satisfied: keep it parked
> and
> > +         * resume from the kick that queues the next chunk.
> > +         */
> > +        e->pkt = p;
> > +        p->status = USB_RET_ASYNC;
> > +    }
> > +}
> > +
> > +/*
> > + * OUT endpoint, single-stage mode: deliver host OUT data into the
> > +buffer the
> > + * gadget armed (EP_DMA_BUFF + EP_DMA_STS). The host controller may
> > + * hand the whole OUT data phase as one packet larger than the armed
> > +chunk, so
> > + * copy up to TX_SIZE bytes starting where the previous call left off
> > + * (p->actual_length); the caller keeps the packet parked across
> > +re-arms until
> > + * it is fully drained. Returns true once the packet is fully delivered.
> > + */
> > +static bool aspeed_udc_ep_deliver_out(AspeedUDCState *s, unsigned ep,
> > +                                      USBPacket *p) {
> > +    AspeedUDCEP *e = &s->ep[ep];
> > +    uint32_t buf_addr = e->regs[R_EP_DMA_BUFF];
> > +    uint32_t chunk = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS,
> TX_SIZE);
> > +    uint32_t remaining = p->iov.size - (uint32_t)p->actual_length;
> > +    uint32_t len = MIN(remaining, chunk);
> > +    uint8_t buf[ASPEED_UDC_DESC_MAX_LEN];
> > +
> > +    if (buf_addr && len) {
> > +        len = MIN(len, sizeof(buf));
> > +        usb_packet_copy(p, buf, len);
> > +        dma_memory_write(&address_space_memory, buf_addr, buf,
> len,
> > +                         MEMTXATTRS_UNSPECIFIED);
> > +    }
> > +
> > +    /* Report the received length; clearing WPTR disarms the receive
> buffer */
> > +    e->regs[R_EP_DMA_STS] = FIELD_DP32(0, EP_DMA_STS, TX_SIZE, len);
> > +    e->regs[R_EP_DMA_CTRL] = FIELD_DP32(e->regs[R_EP_DMA_CTRL],
> EP_DMA_CTRL,
> > +                                        PROC_STS,
> EP_DMA_CTRL_STS_RX_IDLE);
> > +    aspeed_udc_raise_ep_ack(s, ep);
> > +
> > +    return (uint32_t)p->actual_length >= p->iov.size; }
> > +
> > +static void aspeed_udc_ep_data_out(AspeedUDCState *s, unsigned ep,
> > +USBPacket *p) {
> > +    AspeedUDCEP *e = &s->ep[ep];
> > +    uint32_t sts = e->regs[R_EP_DMA_STS];
> > +
> > +    trace_aspeed_udc_ep_data_out(ep, FIELD_EX32(sts, EP_DMA_STS,
> WPTR),
> > +                                 FIELD_EX32(sts, EP_DMA_STS,
> TX_SIZE),
> > +                                 p->iov.size);
> > +    if (!FIELD_EX32(sts, EP_DMA_STS, WPTR)) {
> > +        /*
> > +         * No receive buffer armed yet. Park the packet as ASYNC rather
> than
> > +         * NAKing: delivering into a stale buffer would lose the packet
> (e.g. a
> > +         * mass-storage CBW), and NAK would fall back to the slow async
> re-poll.
> > +         * aspeed_udc_ep_out_kick() delivers it once the gadget arms a
> buffer.
> > +         */
> > +        e->pkt = p;
> > +        p->status = USB_RET_ASYNC;
> > +        return;
> > +    }
> > +
> > +    if (aspeed_udc_ep_deliver_out(s, ep, p)) {
> > +        p->status = USB_RET_SUCCESS;
> > +    } else {
> > +        e->pkt = p;
> > +        p->status = USB_RET_ASYNC;
> > +    }
> > +}
> > +
> > +static void aspeed_udc_gadget_handle_reset(USBDevice *udev) {
> > +    AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc;
> > +
> > +    s->ep0_packet = NULL;
> > +    s->ep0_offset = 0;
> > +    /* EHCI is high speed; let the gadget read the link speed from UBD24
> */
> > +    s->regs[R_UDC_STS] = R_UDC_STS_HIGHSPEED_MASK;
> > +    trace_aspeed_udc_reset(s->regs[R_UDC_IER]);
> > +    aspeed_udc_raise_isr(s, R_UDC_ISR_BUS_RESET_MASK); }
> > +
> > +static void aspeed_udc_gadget_handle_control(USBDevice *udev,
> USBPacket *p,
> > +                                          int request, int value,
> int index,
> > +                                          int length, uint8_t
> *data)
> > +{
> > +    AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc;
> > +    uint8_t type = request >> 8;
> > +    uint8_t req = request & 0xff;
> > +
> > +    /*
> > +     * Reconstruct the 8-byte SETUP packet into the SETUP data buffer
> where
> > +     * the gadget driver reads it from (controller offset 0x80).
> > +     */
> > +    s->regs[R_UDC_SETUP0] = type | (req << 8) | ((value & 0xffff) << 16);
> > +    s->regs[R_UDC_SETUP1] = (index & 0xffff) | ((length & 0xffff) <<
> > + 16);
> > +
> > +    s->ep0_packet = p;
> > +    s->ep0_data = data;
> > +    s->ep0_setup_len = length;
> > +    s->ep0_offset = 0;
> > +    s->ep0_dir_in = (type & USB_DIR_IN);
> > +
> > +    trace_aspeed_udc_ep0_setup(type, req, value, index, length,
> > +                               s->ep0_dir_in, udev->addr);
> > +
> > +    /*
> > +     * SET_ADDRESS: adopt the address and complete the (zero-length)
> status
> > +     * synchronously, exactly like QEMU's generic control handler. The
> gadget
> > +     * is still notified so its own state machine advances, but the host
> > +     * transfer must not depend on that asynchronous round-trip.
> > +     */
> > +    if (type == 0 && req == USB_REQ_SET_ADDRESS) {
> > +        udev->addr = value;
> > +        s->ep0_packet = NULL;
> > +        aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_SETUP_MASK);
> > +        p->status = USB_RET_SUCCESS;
> > +        return;
> > +    }
> > +
> > +    aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_SETUP_MASK);
> > +    p->status = USB_RET_ASYNC;
> > +}
> > +
> > +static void aspeed_udc_gadget_handle_data(USBDevice *udev, USBPacket
> > +*p) {
> > +    AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc;
> > +    bool is_out = (p->pid == USB_TOKEN_OUT);
> > +    int ep = aspeed_udc_find_ep(s, p->ep->nr, is_out);
> > +
> > +    trace_aspeed_udc_handle_data(p->ep->nr, is_out ? "OUT" : "IN",
> > +                                 p->iov.size, ep);
> > +    if (ep < 0) {
> > +        p->status = USB_RET_STALL;
> > +        return;
> > +    }
> > +
> > +    if (is_out) {
> > +        aspeed_udc_ep_data_out(s, ep, p);
> > +    } else {
> > +        aspeed_udc_ep_data_in(s, ep, p);
> > +    }
> > +}
> > +
> > +static void aspeed_udc_gadget_cancel_packet(USBDevice *udev,
> > +USBPacket *p) {
> > +    AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc;
> > +    int i;
> > +
> > +    if (s->ep0_packet == p) {
> > +        s->ep0_packet = NULL;
> > +    }
> > +    for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
> > +        if (s->ep[i].pkt == p) {
> > +            s->ep[i].pkt = NULL;
> > +        }
> > +    }
> > +}
> > +
> > +static void aspeed_udc_gadget_realize(USBDevice *udev, Error **errp)
> > +{
> > +    AspeedUDCGadget *dev = ASPEED_UDC_GADGET(udev);
> > +
> > +    if (!dev->udc) {
> > +        error_setg(errp, "aspeed-udc-gadget: 'udc' link is not set");
> > +        return;
> > +    }
> > +    /* Bind this gadget to its controller */
> > +    dev->udc->usbgadget = dev;
> > +
> > +    /* Connection to the host is driven by the gadget via UBD00
> upstream-en */
> > +    udev->auto_attach = 0;
> > +    /* The ASPEED UDC is USB 2.0, so it only runs at High-Speed for now
> */
> > +    udev->speed = USB_SPEED_HIGH;
> > +    udev->speedmask = USB_SPEED_MASK_HIGH; }
> > +
> > +static const Property aspeed_udc_gadget_props[] = {
> > +    DEFINE_PROP_LINK("udc", AspeedUDCGadget, udc,
> TYPE_ASPEED_UDC,
> > +                     AspeedUDCState *), };
> > +
> > +static void aspeed_udc_gadget_class_init(ObjectClass *klass, const
> > +void *data) {
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +    USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
> > +
> > +    dc->desc           = "ASPEED UDC gadget device";
> > +    uc->product_desc   = "ASPEED UDC gadget";
> > +    uc->realize        = aspeed_udc_gadget_realize;
> > +    uc->handle_reset   = aspeed_udc_gadget_handle_reset;
> > +    uc->handle_control = aspeed_udc_gadget_handle_control;
> > +    uc->handle_data    = aspeed_udc_gadget_handle_data;
> > +    uc->cancel_packet  = aspeed_udc_gadget_cancel_packet;
> > +    device_class_set_props(dc, aspeed_udc_gadget_props); }
> > +
> >   static const TypeInfo aspeed_udc_types[] = {
> >       {
> >           .name          = TYPE_ASPEED_UDC,
> > @@ -259,6 +919,12 @@ static const TypeInfo aspeed_udc_types[] = {
> >           .instance_size = sizeof(AspeedUDCState),
> >           .class_init    = aspeed_udc_class_init,
> >       },
> > +    {
> > +        .name          = TYPE_ASPEED_UDC_GADGET,
> > +        .parent        = TYPE_USB_DEVICE,
> > +        .instance_size = sizeof(AspeedUDCGadget),
> > +        .class_init    = aspeed_udc_gadget_class_init,
> > +    },
> >   };
> >
> >   DEFINE_TYPES(aspeed_udc_types)
> > diff --git a/hw/usb/trace-events b/hw/usb/trace-events index
> > fb79e24fba..77958fe79a 100644
> > --- a/hw/usb/trace-events
> > +++ b/hw/usb/trace-events
> > @@ -383,4 +383,13 @@ aspeed_udc_read(uint64_t offset, uint64_t value)
> "offset 0x%" PRIx64 " value 0x%
> >   aspeed_udc_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 "
> value 0x%" PRIx64
> >   aspeed_udc_ep_read(int ep, uint64_t offset, uint64_t value) "ep %d,
> offset 0x%" PRIx64 " value 0x%" PRIx64
> >   aspeed_udc_ep_write(int ep, uint64_t offset, uint64_t value) "ep %d,
> > offset 0x%" PRIx64 " value 0x%" PRIx64
> > +aspeed_udc_pullup(int on, int attached) "on %d, attached %d"
> >   aspeed_udc_irq(uint32_t isr, uint32_t ier, int level) "isr 0x%x, ier 0x%x,
> level %d"
> > +aspeed_udc_reset(uint32_t ier) "bus reset, ier 0x%x"
> > +aspeed_udc_ep0_setup(uint8_t type, uint8_t req, uint16_t value, uint16_t
> index, uint16_t length, int dir_in, int addr) "bmRequestType 0x%02x, bRequest
> 0x%02x, wValue 0x%04x, wIndex 0x%04x, wLength %d, dir_in %d, addr %d"
> > +aspeed_udc_ep0_ctrl_write(uint32_t val, int dir_in, uint32_t offset) "val
> 0x%x, dir_in %d, off %u"
> > +aspeed_udc_ep0_complete(int dir_in, int actual) "dir_in %d, actual %d"
> > +aspeed_udc_handle_data(int ep_nr, const char *dir, uint32_t iov, int 
> > ep_idx)
> "ep_nr %d, %s, iov %u, ep_idx %d"
> > +aspeed_udc_ep_data_in(unsigned ep, uint32_t rptr, uint32_t wptr, uint32_t
> iov) "ep %u, rptr %u, wptr %u, iov %u"
> > +aspeed_udc_ep_data_out(unsigned ep, uint32_t wptr, uint32_t avail,
> uint32_t iov) "ep %u, wptr %u, avail %u, iov %u"
> > +aspeed_udc_ep_ack(unsigned ep) "ep %u"
> > diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
> > index eb279dd9c3..cbfd1e30fb 100644
> > --- a/include/hw/usb/aspeed-udc.h
> > +++ b/include/hw/usb/aspeed-udc.h
> > @@ -10,11 +10,19 @@
> >   #define HW_USB_ASPEED_UDC_H
> >
> >   #include "hw/core/sysbus.h"
> > +#include "hw/usb/usb.h"
> >   #include "qom/object.h"
> >
> >   #define TYPE_ASPEED_UDC "aspeed.udc"
> >   OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCState, ASPEED_UDC)
> >
> > +/*
> > + * The gadget side of the controller is presented to a USB host
> > +controller's
> > + * bus as a single USB device that delegates back to the AspeedUDCState.
> > + */
> > +#define TYPE_ASPEED_UDC_GADGET "aspeed.udc-gadget"
> > +OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCGadget,
> ASPEED_UDC_GADGET)
> > +
> >   /*
> >    * EP0 (control) is served through the root registers (UDC_EP0_*), so only
> >    * the 4 programmable endpoints get their own register bank / ep[] entry.
> > @@ -36,10 +44,20 @@ OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCState,
> > ASPEED_UDC)
> >
> >   typedef struct AspeedUDCEP {
> >       MemoryRegion mr;
> > +    AspeedUDCState *udc;
> >       int index;
> >       uint32_t *regs;
> > +    /* host packet parked until the gadget queues (IN) or arms (OUT) data
> */
> > +    USBPacket *pkt;
> > +    /* bytes of the current IN descriptor already served */
> > +    uint32_t desc_off;
> >   } AspeedUDCEP;
> >
> > +struct AspeedUDCGadget {
> > +    USBDevice parent_obj;
> > +    AspeedUDCState *udc;
> > +};
> > +
> >   struct AspeedUDCState {
> >       SysBusDevice parent_obj;
> >
> > @@ -49,6 +67,18 @@ struct AspeedUDCState {
> >       qemu_irq irq;
> >       uint32_t *regs;
> >       AspeedUDCEP ep[ASPEED_UDC_NUM_EP];
> > +    /* gadget USB device bound to this controller (set at its realize) */
> > +    AspeedUDCGadget *usbgadget;
> > +
> > +    /*
> > +     * In-flight EP0 control transfer (host side), deferred until the 
> > gadget
> > +     * driver responds via MMIO.
> > +     */
> > +    USBPacket *ep0_packet;
> > +    uint8_t *ep0_data;
> > +    uint32_t ep0_setup_len;
> > +    uint32_t ep0_offset;
> > +    bool ep0_dir_in;
> >   };
> >
> >   #endif /* HW_USB_ASPEED_UDC_H */

Reply via email to