> > 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 */
