* [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support
@ 2026-11-10 8:29 Jamin Lin
2026-11-10 8:29 ` [PATCH v3 01/12] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable Jamin Lin
` (11 more replies)
0 siblings, 12 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee
This series adds USB Device Controller (UDC) support for the ASPEED
AST1030 and extends the existing ASPEED UDC model with the transfer
handling required by the AST1030.
The v1 series included both the usbredir server transport and the ASPEED
AST1030 UDC support. Following review feedback, the two parts have been
split into separate series. The usbredir server transport is now sent
separately, while this series contains the ASPEED AST1030 UDC changes.
Dependency
==========
The AST1030 UDC implementation itself does not depend on the usbredir
server transport series. However, the functional test in this series
does depend on it, because the AST1030 has no USB host controller and
requires x-usb-redir-server to export the emulated USB gadget to a USB
host controller in another QEMU instance.
The functional test therefore depends on:
[v9,00/19] hw/usb: Add a usbredir server transport
https://patchwork.kernel.org/project/qemu-devel/cover/20261110072818.2883349-1-jamin_lin@aspeedtech.com/
Background
==========
This work is part of a larger plan to model USB device-side support on
ASPEED BMC/BIC SoCs, which has three goals:
1. Model the ASPEED UDC (AST2600 / AST1030). The AST2600 also has USB host
(EHCI) controllers, so that work targets the AST2600 UDC: its gadget can
be attached to the SoC's own EHCI bus, letting the guest enumerate its
own gadget and exercise the UDC end-to-end. [Done]
https://lore.kernel.org/qemu-devel/20260902021542.3194812-1-jamin_lin@aspeedtech.com/
2. AST1030 UDC. The AST1030 has no USB host controller, so testing its UDC
needs a second QEMU instance. The UDC gadget is redirected out of the
guest with libusbredir and attached to another QEMU that runs a USB host
(a VMM, or an AST2600 / AST2700 guest). [this series]
3. ASPEED vHub, as a longer-term goal towards BMC KVM / Virtual Media
support in QEMU. [future]
This series implements goal 2.
AST1030 gadget to an AST2700 host
=================================
QEMU #1 -M ast1030-evb QEMU #2 -M ast2700a2-evb
+-----------------------------+ +-----------------------------+
| Zephyr usb_dc_aspeed | | Linux ehci-platform |
+--------------+--------------+ +--------------+--------------+
| MMIO | MMIO
+--------------v--------------+ +--------------v--------------+
| aspeed.udc | | EHCI3 (usb-bus.3) |
+--------------+--------------+ +--------------+--------------+
| USBPacket | USBPacket
+--------------v--------------+ +--------------v--------------+
| aspeed.udc-gadget | | usb-redir |
+--------------+--------------+ +--------------+--------------+
| USBPacket |
+--------------v--------------+ |
| x-usb-redir-server | |
+--------------+--------------+ |
| |
+=========== unix socket ============+
usbredir protocol
Testing
=======
The AST1030 UDC was tested with a Zephyr CDC-ACM gadget. The AST1030
side exports the gadget through x-usb-redir-server:
$ qemu-system-arm -M ast1030-evb -nographic
-kernel zephyr.bin
-chardev socket,id=u,path=/tmp/usbredir.sock,server=on,wait=off
-device x-usb-redir-server,id=udcredir,chardev=u
-device aspeed.udc-gadget,bus=udcredir.0,udc=/machine/soc/udc
uart:~$ usb enable
uart:~$ usb print 1
The gadget is attached to EHCI3 of an AST2700 guest through the
usb-redir client:
$ qemu-system-aarch64 -M ast2700a2-evb -nographic
-drive file=image-bmc,if=mtd,format=raw -snapshot
-net nic -net user
-chardev socket,id=r,path=/tmp/usbredir.sock
-device usb-redir,chardev=r,bus=usb-bus.3
The AST2700 guest enumerates the AST1030 gadget:
root@ast2700-default:~# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux 6.18.36 ehci_hcd EHCI Host Controller
Bus 001 Device 002: ID 2245:1030 ZEPHYR Zephyr DFU sample
Data transfer was also checked:
2700 -> 1030
echo '1234567890' > /dev/ttyACM0
1030 console: Print Data: 0x31 0x32 ... 0x30
A functional test is added to exercise the AST1030 UDC through this
usbredir setup.
v1:
1. Make the SETUP buffer byte addressable
2. Support single-stage IN transfers
3. Clear EP_POOL_ACK when no endpoint ack is left
4. Mask the reserved bits on register writes
5. Use the endpoint max packet size for OUT transfers
6. Test the usbredir server transport
7. Add the USB device controller for AST1030
v2:
1. Split the usbredir server transport into a separate series as
suggested during review.
2. Keep this series focused on the ASPEED UDC changes and AST1030
UDC support.
3. Add the AST1030 functional test using the usbredir server transport.
v3:
1. Finish an EP0 IN transfer at the status stage to fix an AST1030 UDC crash.
2. Update the functional test to scan for the AST1030 USB device from AST2600
U-Boot to reduce testing time.
Jamin Lin (12):
hw/usb/aspeed-udc: Make the SETUP buffer byte addressable
hw/usb/aspeed-udc: Finish an EP0 IN transfer at the status stage
hw/usb/aspeed-udc: Mask the reserved bits on register writes
hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left
hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode
hw/usb/aspeed-udc: Store the EP_DMA_STS write first
hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction
hw/usb/aspeed-udc: Support single-stage IN transfers
hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers
hw/usb/aspeed-udc: Add trace events for the transfer paths
hw/arm/aspeed_ast10x0: Add the USB device controller
tests/functional/arm/test_aspeed_ast1030: Test the usbredir server
transport
include/hw/arm/aspeed_soc.h | 1 +
include/hw/usb/aspeed-udc.h | 14 +-
hw/arm/aspeed_ast10x0.c | 13 +-
hw/usb/aspeed-udc.c | 309 ++++++++++++++++----
hw/usb/trace-events | 9 +
tests/functional/arm/test_aspeed_ast1030.py | 51 ++++
6 files changed, 343 insertions(+), 54 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3 01/12] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 02/12] hw/usb/aspeed-udc: Finish an EP0 IN transfer at the status stage Jamin Lin
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
The controller registers need a 4 byte access, but the 8 bytes at 0x80
are not registers. They are a SRAM buffer holding the EP0 SETUP packet.
A read or a write that is not 4 bytes returned the wrong data, so the
driver saw a broken SETUP packet.
Introduce a separate SETUP MMIO region that handles 1, 2 and 4 byte
reads and writes.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
include/hw/usb/aspeed-udc.h | 11 ++++++--
hw/usb/aspeed-udc.c | 56 +++++++++++++++++++++++++++++++++----
hw/usb/trace-events | 2 ++
3 files changed, 61 insertions(+), 8 deletions(-)
diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
index 7701c1aa34..42916c0bd6 100644
--- a/include/hw/usb/aspeed-udc.h
+++ b/include/hw/usb/aspeed-udc.h
@@ -24,11 +24,14 @@ OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCState, ASPEED_UDC)
OBJECT_DECLARE_SIMPLE_TYPE(AspeedUDCGadget, ASPEED_UDC_GADGET)
/*
- * Register map: root/global block at 0x000 - 0x087, then one 0x10 byte bank
- * per programmable endpoint from 0x200.
+ * Register map: root/global block at 0x000 - 0x07f, the SETUP buffer at
+ * 0x080 - 0x087, then one 0x10 byte bank per programmable endpoint from
+ * 0x200.
*/
#define ASPEED_UDC_MEM_SIZE 0x300
-#define ASPEED_UDC_ROOT_NR_REGS (0x88 >> 2)
+#define ASPEED_UDC_ROOT_NR_REGS (0x80 >> 2)
+#define ASPEED_UDC_SETUP_BASE 0x80
+#define ASPEED_UDC_SETUP_SIZE 0x08
#define ASPEED_UDC_EP_REG_BASE 0x200
#define ASPEED_UDC_EP_NR_REGS (0x10 >> 2)
@@ -62,9 +65,11 @@ struct AspeedUDCState {
MemoryRegion udc_container;
MemoryRegion root_mr;
+ MemoryRegion setup_mr;
MemoryRegion *dram_mr;
AddressSpace dram_as;
uint32_t regs[ASPEED_UDC_ROOT_NR_REGS];
+ uint8_t setup_buf[ASPEED_UDC_SETUP_SIZE];
AspeedUDCEP ep[ASPEED_UDC_NUM_EP];
qemu_irq irq;
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 4c90b4f4c2..355512822b 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -58,9 +58,6 @@ REG32(UDC_EP0_CTRL, 0x30)
FIELD(UDC_EP0_CTRL, STALL, 0, 1)
REG32(UDC_EP0_DATA_BUFF, 0x34)
FIELD(UDC_EP0_DATA_BUFF, BASE_ADDR, 0, 31)
-/* EP0 SETUP packet buffer: SETUP0 = bytes 0...3, SETUP1 = bytes 4...7 */
-REG32(UDC_SETUP0, 0x80)
-REG32(UDC_SETUP1, 0x84)
/* Per programmable-endpoint registers (offset from the EP register base) */
REG32(EP_CONFIG, 0x00)
@@ -363,6 +360,45 @@ static const MemoryRegionOps aspeed_udc_ops = {
},
};
+static uint64_t aspeed_udc_setup_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ AspeedUDCState *s = ASPEED_UDC(opaque);
+ uint64_t value = 0;
+ int i;
+
+ for (i = 0; i < size; i++) {
+ value |= (uint64_t)s->setup_buf[offset + i] << (8 * i);
+ }
+
+ trace_aspeed_udc_setup_read(offset, size, value);
+
+ return value;
+}
+
+static void aspeed_udc_setup_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ AspeedUDCState *s = ASPEED_UDC(opaque);
+ int i;
+
+ trace_aspeed_udc_setup_write(offset, size, value);
+
+ for (i = 0; i < size; i++) {
+ s->setup_buf[offset + i] = value >> (8 * i);
+ }
+}
+
+static const MemoryRegionOps aspeed_udc_setup_ops = {
+ .read = aspeed_udc_setup_read,
+ .write = aspeed_udc_setup_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
/*
* Copy len bytes from guest memory at addr into the IN packet, going through
* a bounce buffer one buf-full at a time. Returns false on DMA failure.
@@ -708,6 +744,7 @@ static void aspeed_udc_reset_hold(Object *obj, ResetType type)
int i;
memset(s->regs, 0, sizeof(s->regs));
+ memset(s->setup_buf, 0, sizeof(s->setup_buf));
for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
memset(s->ep[i].regs, 0, sizeof(s->ep[i].regs));
s->ep[i].pkt = NULL;
@@ -754,6 +791,12 @@ static void aspeed_udc_realize(DeviceState *dev, Error **errp)
ASPEED_UDC_ROOT_NR_REGS << 2);
memory_region_add_subregion(&s->udc_container, 0, &s->root_mr);
+ memory_region_init_io(&s->setup_mr, OBJECT(s), &aspeed_udc_setup_ops,
+ s, TYPE_ASPEED_UDC ".setup",
+ ASPEED_UDC_SETUP_SIZE);
+ memory_region_add_subregion(&s->udc_container,
+ ASPEED_UDC_SETUP_BASE, &s->setup_mr);
+
/* Each programmable endpoint has its own register bank */
for (i = 0; i < ASPEED_UDC_NUM_EP; i++) {
g_autofree char *name = g_strdup_printf(TYPE_ASPEED_UDC ".ep%d", i);
@@ -918,8 +961,11 @@ static void aspeed_udc_gadget_handle_control(USBDevice *udev, USBPacket *p,
* Reconstruct the 8-byte SETUP packet into the SETUP data buffer where
* the guest gadget driver reads it from.
*/
- s->regs[R_UDC_SETUP0] = type | (req << 8) | ((value & 0xffff) << 16);
- s->regs[R_UDC_SETUP1] = (index & 0xffff) | ((length & 0xffff) << 16);
+ s->setup_buf[0] = type;
+ s->setup_buf[1] = req;
+ stw_le_p(&s->setup_buf[2], value);
+ stw_le_p(&s->setup_buf[4], index);
+ stw_le_p(&s->setup_buf[6], length);
/* A new SETUP clears the EP0 STALL condition */
s->regs[R_UDC_EP0_CTRL] &= ~R_UDC_EP0_CTRL_STALL_MASK;
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 36a870689f..7a84786fd0 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -381,6 +381,8 @@ canokey_unrealize(void)
# aspeed-udc.c
aspeed_udc_read(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%x"
aspeed_udc_write(uint64_t offset, uint32_t value) "offset 0x%" PRIx64 " value 0x%x"
+aspeed_udc_setup_read(uint64_t offset, unsigned size, uint64_t value) "offset 0x%" PRIx64 " size %u value 0x%" PRIx64
+aspeed_udc_setup_write(uint64_t offset, unsigned size, uint64_t value) "offset 0x%" PRIx64 " size %u value 0x%" PRIx64
aspeed_udc_ep_read(int ep, uint64_t offset, uint32_t value) "ep %d, offset 0x%" PRIx64 " value 0x%x"
aspeed_udc_ep_write(int ep, uint64_t offset, uint32_t value) "ep %d, offset 0x%" PRIx64 " value 0x%x"
aspeed_udc_pullup(int on, int attached) "on %d, attached %d"
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 02/12] hw/usb/aspeed-udc: Finish an EP0 IN transfer at the status stage
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
2026-11-10 8:29 ` [PATCH v3 01/12] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 03/12] hw/usb/aspeed-udc: Mask the reserved bits on register writes Jamin Lin
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee
In a control IN transfer, the data goes from the device to the host.
At the end, the host sends an empty OUT packet to the device. The
driver gets an OUT ACK interrupt for it.
The model sent the OUT ACK as soon as the driver set RX_RDY in
EP0_CTRL. This could happen before the IN data was sent. Real hardware
sends it only after all the IN data. This can break an IN transfer of
more than one packet.
Send the OUT ACK only after all the IN data is sent, and complete the
packet then. In a control OUT transfer, the data goes from the host to
the device. After all the OUT data is received, send no more OUT ACK.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
include/hw/usb/aspeed-udc.h | 1 +
hw/usb/aspeed-udc.c | 19 +++++++++++++++----
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/include/hw/usb/aspeed-udc.h b/include/hw/usb/aspeed-udc.h
index 42916c0bd6..1cfef6dba9 100644
--- a/include/hw/usb/aspeed-udc.h
+++ b/include/hw/usb/aspeed-udc.h
@@ -84,6 +84,7 @@ struct AspeedUDCState {
uint32_t ep0_setup_len;
uint32_t ep0_offset;
uint8_t *ep0_data;
+ bool ep0_in_done;
bool ep0_dir_in;
};
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 355512822b..6a5d05ce1f 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -209,10 +209,10 @@ static void aspeed_udc_ep0_tx_ready(AspeedUDCState *s, uint32_t val)
return;
}
s->ep0_offset += n;
- aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_IN_ACK_MASK);
if (txlen < ASPEED_UDC_EP0_MAXPKT || s->ep0_offset >= s->ep0_setup_len) {
- aspeed_udc_ep0_complete(s, s->ep0_offset);
+ s->ep0_in_done = true;
}
+ aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_IN_ACK_MASK);
}
static void aspeed_udc_ep0_rx_ready(AspeedUDCState *s)
@@ -222,14 +222,24 @@ static void aspeed_udc_ep0_rx_ready(AspeedUDCState *s)
uint32_t n;
if (s->ep0_dir_in) {
- /* Status stage OUT (zero length) for an IN transfer */
- aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_OUT_ACK_MASK);
+ /*
+ * Status stage OUT (zero length) for an IN transfer.
+ * Send the OUT ACK only after all the IN data is sent.
+ */
+ if (s->ep0_packet && s->ep0_in_done) {
+ aspeed_udc_raise_isr(s, R_UDC_ISR_EP0_OUT_ACK_MASK);
+ aspeed_udc_ep0_complete(s, s->ep0_offset);
+ }
return;
}
/* no control transfer is waiting: nothing to receive */
if (!s->ep0_packet) {
return;
}
+ /* All the OUT data is received. Do not send another OUT ACK. */
+ if (s->ep0_offset >= s->ep0_setup_len) {
+ return;
+ }
/* OUT data stage: hand host data to the guest gadget driver */
n = MIN(s->ep0_setup_len - s->ep0_offset, ASPEED_UDC_EP0_MAXPKT);
@@ -975,6 +985,7 @@ static void aspeed_udc_gadget_handle_control(USBDevice *udev, USBPacket *p,
s->ep0_setup_len = length;
s->ep0_offset = 0;
s->ep0_dir_in = (type & USB_DIR_IN);
+ s->ep0_in_done = false;
trace_aspeed_udc_ep0_setup(type, req, value, index, length,
s->ep0_dir_in, udev->addr);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 03/12] hw/usb/aspeed-udc: Mask the reserved bits on register writes
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
2026-11-10 8:29 ` [PATCH v3 01/12] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable Jamin Lin
2026-11-10 8:29 ` [PATCH v3 02/12] hw/usb/aspeed-udc: Finish an EP0 IN transfer at the status stage Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 04/12] hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left Jamin Lin
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
Writes to the interrupt registers were not masked, so a guest could set
a reserved bit and read it back. The hardware returns 0 for those.
Mask each one to the bits the datasheet marks read/write:
UDC_IER 17, 16, 8:6, 4:0
UDC_ISR 8:6, 4:0
UDC_EP_ACK_IER 3:0
UDC_EP_NAK_IER 3:0
UDC_EP_ACK_ISR 3:0
UDC_EP_NAK_ISR 3:0
UDC_ISR bits 17 and 16 summarise the endpoint pool. They are read only,
so a write cannot clear them.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
hw/usb/aspeed-udc.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 6a5d05ce1f..945940cc99 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -329,14 +329,24 @@ static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t data,
s->regs[R_UDC_FUNC_CTRL] = val;
break;
case R_UDC_IER:
+ val &= 0x000301df;
+ s->regs[reg] = val;
+ aspeed_udc_update_irq(s);
+ break;
+ case R_UDC_ISR:
+ val &= 0x000001df;
+ s->regs[reg] &= ~val;
+ aspeed_udc_update_irq(s);
+ break;
case R_UDC_EP_ACK_IER:
case R_UDC_EP_NAK_IER:
+ val &= 0x0000000f;
s->regs[reg] = val;
aspeed_udc_update_irq(s);
break;
- case R_UDC_ISR:
case R_UDC_EP_ACK_ISR:
case R_UDC_EP_NAK_ISR:
+ val &= 0x0000000f;
s->regs[reg] &= ~val;
aspeed_udc_update_irq(s);
break;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 04/12] hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (2 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 03/12] hw/usb/aspeed-udc: Mask the reserved bits on register writes Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 05/12] hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode Jamin Lin
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
UDC_ISR.EP_POOL_ACK says that at least one endpoint in UDC_EP_ACK_ISR
has an ack. It is read only, so a write cannot clear it.
Clear it when bits 3:0 of UDC_EP_ACK_ISR are all 0, which means no
programmable endpoint has an ack.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
hw/usb/aspeed-udc.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 945940cc99..2d024533e7 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -345,6 +345,13 @@ static void aspeed_udc_write(void *opaque, hwaddr offset, uint64_t data,
aspeed_udc_update_irq(s);
break;
case R_UDC_EP_ACK_ISR:
+ val &= 0x0000000f;
+ s->regs[reg] &= ~val;
+ if (!s->regs[R_UDC_EP_ACK_ISR]) {
+ s->regs[R_UDC_ISR] &= ~R_UDC_ISR_EP_POOL_ACK_MASK;
+ }
+ aspeed_udc_update_irq(s);
+ break;
case R_UDC_EP_NAK_ISR:
val &= 0x0000000f;
s->regs[reg] &= ~val;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 05/12] hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (3 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 04/12] hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 06/12] hw/usb/aspeed-udc: Store the EP_DMA_STS write first Jamin Lin
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
The controller can run an endpoint in descriptor list mode or in single
stage mode. Put the mode in the helper names. That makes the code
easier to read.
No functional change.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
hw/usb/aspeed-udc.c | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 2d024533e7..33a197af54 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -469,8 +469,8 @@ static bool aspeed_udc_ep_copy_to_pkt(AspeedUDCState *s, int ep, uint32_t addr,
* This function raises the endpoint ACK by itself when the ring becomes empty
* or when a descriptor asks for an interrupt.
*/
-static AspeedUDCXferResult aspeed_udc_ep_xfer_in(AspeedUDCState *s, int ep,
- USBPacket *p)
+static AspeedUDCXferResult aspeed_udc_ep_xfer_in_desc(AspeedUDCState *s,
+ int ep, USBPacket *p)
{
QEMUIOVector *pktiov = p->combined ? &p->combined->iov : &p->iov;
AspeedUDCEP *e = &s->ep[ep];
@@ -578,15 +578,15 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in(AspeedUDCState *s, int ep,
/*
* OUT transfer: receive data from the host by copying its OUT packet into the
- * buffer the guest gadget driver set up (single-stage mode).
+ * buffer the guest gadget driver set up.
*
* A host packet can be bigger than one buffer, so we copy at most PKT_SIZE
* bytes per call, continuing from where the last call stopped
* (p->actual_length). The caller keeps the packet parked until it is fully
* copied.
*/
-static AspeedUDCXferResult aspeed_udc_ep_xfer_out(AspeedUDCState *s, int ep,
- USBPacket *p)
+static AspeedUDCXferResult aspeed_udc_ep_xfer_out_single(AspeedUDCState *s,
+ int ep, USBPacket *p)
{
AspeedUDCEP *e = &s->ep[ep];
uint32_t chunk = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, PKT_SIZE);
@@ -629,7 +629,7 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_out(AspeedUDCState *s, int ep,
* If the request needs more data than was queued, keep it parked and wait for
* the next kick.
*/
-static void aspeed_udc_ep_in_kick(AspeedUDCState *s, int ep, uint32_t val)
+static void aspeed_udc_ep_in_kick_desc(AspeedUDCState *s, int ep, uint32_t val)
{
AspeedUDCEP *e = &s->ep[ep];
uint32_t cur_rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR);
@@ -659,7 +659,7 @@ static void aspeed_udc_ep_in_kick(AspeedUDCState *s, int ep, uint32_t val)
return;
}
- switch (aspeed_udc_ep_xfer_in(s, ep, p)) {
+ switch (aspeed_udc_ep_xfer_in_desc(s, ep, p)) {
case ASPEED_UDC_XFER_DONE:
e->pkt = NULL;
p->status = USB_RET_SUCCESS;
@@ -681,7 +681,7 @@ static void aspeed_udc_ep_in_kick(AspeedUDCState *s, int ep, uint32_t val)
* buffer before), copy its data into the buffer now and finish it. If the
* packet has more data than fits, keep it parked and wait for the next buffer.
*/
-static void aspeed_udc_ep_out_kick(AspeedUDCState *s, int ep)
+static void aspeed_udc_ep_out_kick_single(AspeedUDCState *s, int ep)
{
AspeedUDCEP *e = &s->ep[ep];
USBPacket *p = e->pkt;
@@ -691,7 +691,7 @@ static void aspeed_udc_ep_out_kick(AspeedUDCState *s, int ep)
return;
}
- switch (aspeed_udc_ep_xfer_out(s, ep, p)) {
+ switch (aspeed_udc_ep_xfer_out_single(s, ep, p)) {
case ASPEED_UDC_XFER_DONE:
e->pkt = NULL;
p->status = USB_RET_SUCCESS;
@@ -737,11 +737,11 @@ static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data,
val &= 0x77ffffff;
if (FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN)) {
/* IN, descriptor-list mode */
- aspeed_udc_ep_in_kick(s, e->index, val);
+ aspeed_udc_ep_in_kick_desc(s, e->index, val);
} else {
/* OUT, single-stage mode */
e->regs[reg] = val;
- aspeed_udc_ep_out_kick(s, e->index);
+ aspeed_udc_ep_out_kick_single(s, e->index);
}
break;
default:
@@ -905,7 +905,7 @@ static void aspeed_udc_ep_data_in(AspeedUDCState *s, int ep, USBPacket *p)
/*
* 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() serves and completes this packet later,
+ * aspeed_udc_ep_in_kick_desc() serves and completes this packet later,
* once the guest gadget driver queues descriptors.
*/
e->pkt = p;
@@ -913,7 +913,7 @@ static void aspeed_udc_ep_data_in(AspeedUDCState *s, int ep, USBPacket *p)
return;
}
- switch (aspeed_udc_ep_xfer_in(s, ep, p)) {
+ switch (aspeed_udc_ep_xfer_in_desc(s, ep, p)) {
case ASPEED_UDC_XFER_DONE:
p->status = USB_RET_SUCCESS;
break;
@@ -941,7 +941,7 @@ static void aspeed_udc_ep_data_out(AspeedUDCState *s, int ep, USBPacket *p)
* No OUT buffer is ready yet. Save the packet and return ASYNC
* instead of NAK. Writing now could use an old buffer address and
* lose the data (for example a mass-storage CBW). A NAK would make
- * the host retry slowly. aspeed_udc_ep_out_kick() delivers this
+ * the host retry slowly. aspeed_udc_ep_out_kick_single() delivers this
* packet later, once the guest gadget driver sets up a buffer.
*/
e->pkt = p;
@@ -949,7 +949,7 @@ static void aspeed_udc_ep_data_out(AspeedUDCState *s, int ep, USBPacket *p)
return;
}
- switch (aspeed_udc_ep_xfer_out(s, ep, p)) {
+ switch (aspeed_udc_ep_xfer_out_single(s, ep, p)) {
case ASPEED_UDC_XFER_DONE:
p->status = USB_RET_SUCCESS;
break;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 06/12] hw/usb/aspeed-udc: Store the EP_DMA_STS write first
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (4 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 05/12] hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 07/12] hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction Jamin Lin
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
The other helpers run after EP_DMA_STS is written. The descriptor mode
kick did not: it had to run before the write, because it stored the
value itself.
Write the register first in every case, then call the helper. The order
is now the same everywhere, so a later change cannot go wrong on it,
and the code is easier to maintain.
No functional change.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
hw/usb/aspeed-udc.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 33a197af54..5f7b9f94f0 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -629,12 +629,13 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_out_single(AspeedUDCState *s,
* If the request needs more data than was queued, keep it parked and wait for
* the next kick.
*/
-static void aspeed_udc_ep_in_kick_desc(AspeedUDCState *s, int ep, uint32_t val)
+static void aspeed_udc_ep_in_kick_desc(AspeedUDCState *s, int ep,
+ uint32_t old_val)
{
AspeedUDCEP *e = &s->ep[ep];
- uint32_t cur_rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR);
- uint32_t new_rptr = FIELD_EX32(val, EP_DMA_STS, RPTR);
- uint32_t new_wptr = FIELD_EX32(val, EP_DMA_STS, WPTR);
+ uint32_t new_rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR);
+ uint32_t new_wptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR);
+ uint32_t cur_rptr = FIELD_EX32(old_val, EP_DMA_STS, RPTR);
USBPacket *p = e->pkt;
/*
@@ -651,8 +652,9 @@ static void aspeed_udc_ep_in_kick_desc(AspeedUDCState *s, int ep, uint32_t val)
cur_rptr = new_rptr;
e->desc_off = 0;
}
- /* store the guest's write, but keep our own read pointer */
- e->regs[R_EP_DMA_STS] = FIELD_DP32(val, EP_DMA_STS, RPTR, cur_rptr);
+ /* keep our own read pointer */
+ e->regs[R_EP_DMA_STS] = FIELD_DP32(e->regs[R_EP_DMA_STS], EP_DMA_STS,
+ RPTR, cur_rptr);
/* nothing to do unless an IN packet is waiting and the ring has data */
if (!p || cur_rptr == new_wptr) {
@@ -725,6 +727,7 @@ static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data,
AspeedUDCEP *e = opaque;
AspeedUDCState *s = container_of(e - e->index, AspeedUDCState, ep[0]);
uint32_t reg = offset >> 2;
+ uint32_t old_val = e->regs[reg];
uint32_t val = data;
trace_aspeed_udc_ep_write(e->index, offset, val);
@@ -735,12 +738,12 @@ static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data,
break;
case R_EP_DMA_STS:
val &= 0x77ffffff;
+ e->regs[reg] = val;
if (FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN)) {
/* IN, descriptor-list mode */
- aspeed_udc_ep_in_kick_desc(s, e->index, val);
+ aspeed_udc_ep_in_kick_desc(s, e->index, old_val);
} else {
/* OUT, single-stage mode */
- e->regs[reg] = val;
aspeed_udc_ep_out_kick_single(s, e->index);
}
break;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 07/12] hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (5 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 06/12] hw/usb/aspeed-udc: Store the EP_DMA_STS write first Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 08/12] hw/usb/aspeed-udc: Support single-stage IN transfers Jamin Lin
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
DESC_OP_EN is the DMA mode. It does not tell the direction. The write
handler used it as the direction anyway: set meant IN, clear meant OUT.
Both directions support both modes. So take the direction from
EP_CONFIG.DIR_OUT first, then take the mode from DESC_OP_EN.
The model does not support OUT descriptor mode. Log it as not
implemented. It does not support IN single stage mode yet either, so
that path does nothing until the next patch.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
hw/usb/aspeed-udc.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 5f7b9f94f0..6231b96487 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -739,12 +739,17 @@ static void aspeed_udc_ep_write(void *opaque, hwaddr offset, uint64_t data,
case R_EP_DMA_STS:
val &= 0x77ffffff;
e->regs[reg] = val;
- if (FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN)) {
- /* IN, descriptor-list mode */
- aspeed_udc_ep_in_kick_desc(s, e->index, old_val);
- } else {
- /* OUT, single-stage mode */
+ if (FIELD_EX32(e->regs[R_EP_CONFIG], EP_CONFIG, DIR_OUT)) {
+ if (FIELD_EX32(e->regs[R_EP_DMA_CTRL], EP_DMA_CTRL, DESC_OP_EN)) {
+ qemu_log_mask(LOG_UNIMP, "%s: ep %d OUT descriptor mode is not"
+ " implemented\n", __func__, e->index);
+ break;
+ }
aspeed_udc_ep_out_kick_single(s, e->index);
+ } 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);
+ }
}
break;
default:
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 08/12] hw/usb/aspeed-udc: Support single-stage IN transfers
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (6 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 07/12] hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 09/12] hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers Jamin Lin
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee
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 1cfef6dba9..e55c53d17b 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 6231b96487..1302b19a89 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -576,6 +576,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.
@@ -677,6 +731,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
@@ -749,6 +837,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;
@@ -784,6 +874,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 */
@@ -906,22 +997,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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 09/12] hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (7 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 08/12] hw/usb/aspeed-udc: Support single-stage IN transfers Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 10/12] hw/usb/aspeed-udc: Add trace events for the transfer paths Jamin Lin
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee
In single mode EP_DMA_STS.PKT_SIZE on an OUT endpoint is where the
controller reports the length it received. It is not a buffer size. The
model took the copy length from it, so a zero in that field meant no
data was copied at all.
One maximum packet is the most that can arrive, so copy at most
EP_CONFIG.MAX_PKT bytes per call instead. That field cannot hold 1024,
so a value of 0 there means 1024 bytes. The IN path decoded that by
hand; move it into one helper and use it from both paths.
Take the packet size from the combined packet when there is one, as the
IN paths do. A combined packet holds more than p->iov says, so reading
p->iov.size cut the transfer short.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/usb/aspeed-udc.c | 32 ++++++++++++++++++--------------
1 file changed, 18 insertions(+), 14 deletions(-)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 1302b19a89..4ba55fd86a 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -426,6 +426,13 @@ static const MemoryRegionOps aspeed_udc_setup_ops = {
},
};
+static uint32_t aspeed_udc_ep_get_max_packet(AspeedUDCEP *e)
+{
+ uint32_t mps = FIELD_EX32(e->regs[R_EP_CONFIG], EP_CONFIG, MAX_PKT);
+
+ return mps ? mps : ASPEED_UDC_EP_MAXPKT;
+}
+
/*
* Copy len bytes from guest memory at addr into the IN packet, going through
* a bounce buffer one buf-full at a time. Returns false on DMA failure.
@@ -474,10 +481,10 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_desc(AspeedUDCState *s,
{
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 wptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR);
uint32_t rptr = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, RPTR);
uint32_t desc_base = e->regs[R_EP_DMA_BUFF];
+ uint32_t mps = aspeed_udc_ep_get_max_packet(e);
uint32_t desc_addr;
uint32_t remaining;
uint32_t desc_ctrl;
@@ -490,11 +497,6 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_desc(AspeedUDCState *s,
bool done = false;
bool ack = false;
- if (mps == 0) {
- /* a MAX_PKT field of 0 means the maximum packet size */
- mps = ASPEED_UDC_EP_MAXPKT;
- }
-
trace_aspeed_udc_ep_data_in(ep, rptr, wptr, pktiov->size);
/* walk the queued descriptors, filling the packet */
@@ -634,19 +636,21 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_single(AspeedUDCState *s,
* OUT transfer: receive data from the host by copying its OUT packet into the
* buffer the guest gadget driver set up.
*
- * A host packet can be bigger than one buffer, so we copy at most PKT_SIZE
- * bytes per call, continuing from where the last call stopped
- * (p->actual_length). The caller keeps the packet parked until it is fully
- * copied.
+ * In single mode PKT_SIZE is where the controller reports the length it
+ * received, so it is not a buffer size. One maximum packet is the most that
+ * can arrive, so copy at most that much per call, continuing from where the
+ * last call stopped (p->actual_length). The caller keeps the packet parked
+ * until it is fully copied.
*/
static AspeedUDCXferResult aspeed_udc_ep_xfer_out_single(AspeedUDCState *s,
int ep, USBPacket *p)
{
+ QEMUIOVector *pktiov = p->combined ? &p->combined->iov : &p->iov;
AspeedUDCEP *e = &s->ep[ep];
- uint32_t chunk = FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, PKT_SIZE);
- uint32_t remaining = p->iov.size - (uint32_t)p->actual_length;
+ uint32_t remaining = pktiov->size > (uint32_t)p->actual_length ?
+ pktiov->size - (uint32_t)p->actual_length : 0;
+ uint32_t len = MIN(remaining, aspeed_udc_ep_get_max_packet(e));
uint32_t data_buf_addr = e->regs[R_EP_DMA_BUFF];
- uint32_t len = MIN(remaining, chunk);
g_autofree uint8_t *buf = g_malloc(len);
if (data_buf_addr && len) {
@@ -669,7 +673,7 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_out_single(AspeedUDCState *s,
PROC_STS, EP_DMA_CTRL_STS_RX_IDLE);
aspeed_udc_raise_ep_ack(s, ep);
- if ((uint32_t)p->actual_length >= p->iov.size) {
+ if ((uint32_t)p->actual_length >= pktiov->size) {
return ASPEED_UDC_XFER_DONE;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 10/12] hw/usb/aspeed-udc: Add trace events for the transfer paths
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (8 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 09/12] hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 11/12] hw/arm/aspeed_ast10x0: Add the USB device controller Jamin Lin
2026-11-10 8:29 ` [PATCH v3 12/12] tests/functional/arm/test_aspeed_ast1030: Test the usbredir server transport Jamin Lin
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
The register traces show what the guest wrote, but not which transfer
path the write chose, how much each call moved, or when the host gave up
on a packet.
Add one event per path, so the name says which path ran and a single
path can be enabled on its own: a kick, a transfer result, and a
cancelled packet.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
hw/usb/aspeed-udc.c | 23 +++++++++++++++++++++++
hw/usb/trace-events | 7 +++++++
2 files changed, 30 insertions(+)
diff --git a/hw/usb/aspeed-udc.c b/hw/usb/aspeed-udc.c
index 4ba55fd86a..b7aff1b31b 100644
--- a/hw/usb/aspeed-udc.c
+++ b/hw/usb/aspeed-udc.c
@@ -575,6 +575,9 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_desc(AspeedUDCState *s,
aspeed_udc_raise_ep_ack(s, ep);
}
+ trace_aspeed_udc_ep_xfer_in_desc(ep, (uint32_t)p->actual_length,
+ done ? "done" : "more");
+
return done ? ASPEED_UDC_XFER_DONE : ASPEED_UDC_XFER_MORE;
}
@@ -616,6 +619,9 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_single(AspeedUDCState *s,
* buffer, and the next IN request sends the rest.
*/
if (e->single_buf_off < buf_len) {
+ trace_aspeed_udc_ep_xfer_in_single(ep,
+ (uint32_t)p->actual_length,
+ "buffer left");
return ASPEED_UDC_XFER_DONE;
}
@@ -629,6 +635,9 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_in_single(AspeedUDCState *s,
PROC_STS, EP_DMA_CTRL_STS_TX_IDLE);
aspeed_udc_raise_ep_ack(s, ep);
+ trace_aspeed_udc_ep_xfer_in_single(ep, (uint32_t)p->actual_length,
+ "done");
+
return ASPEED_UDC_XFER_DONE;
}
@@ -674,9 +683,15 @@ static AspeedUDCXferResult aspeed_udc_ep_xfer_out_single(AspeedUDCState *s,
aspeed_udc_raise_ep_ack(s, ep);
if ((uint32_t)p->actual_length >= pktiov->size) {
+ trace_aspeed_udc_ep_xfer_out_single(ep,
+ (uint32_t)p->actual_length,
+ "done");
return ASPEED_UDC_XFER_DONE;
}
+ trace_aspeed_udc_ep_xfer_out_single(ep, (uint32_t)p->actual_length,
+ "more");
+
return ASPEED_UDC_XFER_MORE;
}
@@ -696,6 +711,8 @@ static void aspeed_udc_ep_in_kick_desc(AspeedUDCState *s, int ep,
uint32_t cur_rptr = FIELD_EX32(old_val, EP_DMA_STS, RPTR);
USBPacket *p = e->pkt;
+ trace_aspeed_udc_ep_in_kick_desc(ep);
+
/*
* A normal kick only sets the write pointer and leaves the read-pointer
* field 0 (the read pointer is ours to advance). The guest resets the ring
@@ -745,6 +762,8 @@ static void aspeed_udc_ep_in_kick_single(AspeedUDCState *s, int ep)
AspeedUDCEP *e = &s->ep[ep];
USBPacket *p = e->pkt;
+ trace_aspeed_udc_ep_in_kick_single(ep);
+
/* 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;
@@ -780,6 +799,8 @@ static void aspeed_udc_ep_out_kick_single(AspeedUDCState *s, int ep)
AspeedUDCEP *e = &s->ep[ep];
USBPacket *p = e->pkt;
+ trace_aspeed_udc_ep_out_kick_single(ep);
+
/* nothing to do unless an OUT packet is waiting and a buffer is ready */
if (!p || !FIELD_EX32(e->regs[R_EP_DMA_STS], EP_DMA_STS, WPTR)) {
return;
@@ -1166,6 +1187,8 @@ static void aspeed_udc_gadget_cancel_packet(USBDevice *udev, USBPacket *p)
AspeedUDCState *s = ASPEED_UDC_GADGET(udev)->udc;
int i;
+ trace_aspeed_udc_cancel_packet(p->ep->nr);
+
if (s->ep0_packet == p) {
s->ep0_packet = NULL;
}
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 7a84786fd0..95548eda67 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -395,6 +395,13 @@ aspeed_udc_handle_data(int ep_nr, const char *dir, uint32_t iov, int ep_idx) "ep
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"
+aspeed_udc_ep_in_kick_desc(int ep) "ep %d"
+aspeed_udc_ep_in_kick_single(int ep) "ep %d"
+aspeed_udc_ep_out_kick_single(int ep) "ep %d"
+aspeed_udc_ep_xfer_in_desc(int ep, uint32_t sent, const char *res) "ep %d, sent %u, %s"
+aspeed_udc_ep_xfer_in_single(int ep, uint32_t sent, const char *res) "ep %d, sent %u, %s"
+aspeed_udc_ep_xfer_out_single(int ep, uint32_t received, const char *res) "ep %d, received %u, %s"
+aspeed_udc_cancel_packet(int ep) "ep %d"
# redirect-server.c
usbredir_server_chardev_open(void) "chardev opened"
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 11/12] hw/arm/aspeed_ast10x0: Add the USB device controller
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (9 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 10/12] hw/usb/aspeed-udc: Add trace events for the transfer paths Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
2026-11-10 8:29 ` [PATCH v3 12/12] tests/functional/arm/test_aspeed_ast1030: Test the usbredir server transport Jamin Lin
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee, Cédric Le Goater
Add the USB UDC device at 0x7E6A2000 with IRQ 9. The AST10x0 UDC
controller is compatible with the AST2600 one, so reuse TYPE_ASPEED_UDC.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
Reviewed-by: Cédric Le Goater <clg@redhat.com>
---
include/hw/arm/aspeed_soc.h | 1 +
hw/arm/aspeed_ast10x0.c | 13 ++++++++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 338496a421..fc1adcb6c1 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -157,6 +157,7 @@ struct Aspeed10x0SoCState {
AspeedSoCState parent;
ARMv7MState armv7m;
+ AspeedUDCState udc;
};
#define TYPE_ASPEED10X0_SOC "aspeed10x0-soc"
diff --git a/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c
index 0790859735..04e7650e7e 100644
--- a/hw/arm/aspeed_ast10x0.c
+++ b/hw/arm/aspeed_ast10x0.c
@@ -101,7 +101,6 @@ static const UnimplementedDeviceConfig aspeed_soc_ast10x0_unimp_devs[] = {
{ "iomem", "aspeed.io", 0x7E600000, 0x00200000, -1000 },
{ "pwm", "aspeed.pwm", 0x7E610000, 0x100, -1000 },
{ "espi", "aspeed.espi", 0x7E6EE000, 0x800, -1000 },
- { "udc", "aspeed.udc", 0x7E6A2000, 0x1000, -1000 },
{ "sgpiom", "aspeed.sgpiom", 0x7E780500, 0x100, -1000 },
{ "jtag[0]", "aspeed.jtag0", 0x7E6E4000, 0x20, -1000 },
{ "jtag[1]", "aspeed.jtag1", 0x7E6E4100, 0x20, -1000 },
@@ -170,6 +169,7 @@ static void aspeed_soc_ast10x0_init(Object *obj, const char *socname)
snprintf(typename, sizeof(typename), "aspeed.hace-%s", socname);
object_initialize_child(obj, "hace", &s->hace, typename);
+ object_initialize_child(obj, "udc", &a->udc, TYPE_ASPEED_UDC);
}
static void aspeed_soc_ast1030_init(Object *obj)
@@ -389,6 +389,17 @@ static bool aspeed_soc_ast10x0_realize(Aspeed10x0SoCState *a, Error **errp)
sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio), 0,
aspeed_soc_ast1030_get_irq(s, ASPEED_DEV_GPIO));
+ /* UDC - USB 2.0 Device Controller */
+ object_property_set_link(OBJECT(&a->udc), "dram",
+ OBJECT(&s->sram[0]), &error_abort);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&a->udc), errp)) {
+ return false;
+ }
+ aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&a->udc), 0,
+ sc->memmap[ASPEED_DEV_UDC]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&a->udc), 0,
+ aspeed_soc_ast1030_get_irq(s, ASPEED_DEV_UDC));
+
return true;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 12/12] tests/functional/arm/test_aspeed_ast1030: Test the usbredir server transport
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
` (10 preceding siblings ...)
2026-11-10 8:29 ` [PATCH v3 11/12] hw/arm/aspeed_ast10x0: Add the USB device controller Jamin Lin
@ 2026-11-10 8:29 ` Jamin Lin
11 siblings, 0 replies; 13+ messages in thread
From: Jamin Lin @ 2026-11-10 8:29 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Kane Chen,
open list:ASPEED BMCs, open list:All patches CC here
Cc: Jamin Lin, Troy Lee
Export the AST1030 UDC gadget with x-usb-redir-server and import it on an
AST2600 with usb-redir, then check that the AST2600 enumerates it. That
covers the announce, control and enumeration paths of the transport, and
the UDC model behind it.
The two machines have to come from the same QEMU binary, because a
functional test gets only one. ast1030-evb and ast2600-evb are both in
qemu-system-arm.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
tests/functional/arm/test_aspeed_ast1030.py | 51 +++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/tests/functional/arm/test_aspeed_ast1030.py b/tests/functional/arm/test_aspeed_ast1030.py
index 83a96ec322..57d1233b6d 100755
--- a/tests/functional/arm/test_aspeed_ast1030.py
+++ b/tests/functional/arm/test_aspeed_ast1030.py
@@ -6,6 +6,8 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later
+import os
+
from aspeed import AspeedTest
from qemu_test import Asset, exec_command_and_wait_for_pattern
@@ -68,6 +70,55 @@ def test_arm_ast1030_zephyros_1_07(self):
'kernel uptime',
]: exec_command_and_wait_for_pattern(self, shell_cmd, "uart:~$")
+ ASSET_SDK_V1103_AST2600 = Asset(
+ 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz',
+ '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165')
+
+ def test_arm_ast1030_usbredir_to_ast2600(self):
+ self.require_device('x-usb-redir-server')
+ self.require_device('usb-redir')
+ self.set_machine('ast2600-evb')
+ self.set_machine('ast1030-evb')
+
+ ast1030_kernel_file = self.archive_extract(
+ self.ASSET_ZEPHYR_3_08, member="ast1030-evb-demo/zephyr.elf")
+
+ ast2600_image_file = self.archive_extract(
+ self.ASSET_SDK_V1103_AST2600,
+ member="ast2600-default-image/image-bmc")
+ sock = os.path.join(self.socket_dir().name, 'usbredir.sock')
+
+ udc = self.get_vm(name='udc')
+ udc.set_console()
+ udc.add_args('-kernel', ast1030_kernel_file, '-nographic',
+ '-chardev',
+ f'socket,id=usbredir0,path={sock},server=on,wait=off',
+ '-device',
+ 'x-usb-redir-server,id=udcredir,chardev=usbredir0',
+ '-device', 'aspeed.udc-gadget,bus=udcredir.0,'
+ 'udc=/machine/soc/udc')
+ udc.launch()
+ self.wait_for_console_pattern('Booting Zephyr OS', vm=udc)
+ exec_command_and_wait_for_pattern(self, 'usb enable', 'cmd_usb_enable',
+ vm=udc)
+ self.wait_for_console_pattern('uart:~$', vm=udc)
+
+ host = self.get_vm(name='host')
+ host.set_machine('ast2600-evb')
+ host.set_console()
+ host.add_args('-drive',
+ f'file={ast2600_image_file},if=mtd,format=raw',
+ '-snapshot',
+ '-chardev', f'socket,id=usbredir0,path={sock}',
+ '-device', 'usb-redir,chardev=usbredir0,bus=usb-bus.1')
+ host.launch()
+ self.wait_for_console_pattern('U-Boot 2019.04', vm=host)
+ self.wait_for_console_pattern('Hit any key to stop autoboot', vm=host)
+ exec_command_and_wait_for_pattern(self, '\012', 'ast#', vm=host)
+ exec_command_and_wait_for_pattern(self, 'usb start', 'ast#', vm=host)
+ exec_command_and_wait_for_pattern(self, 'usb tree',
+ 'ZEPHYR Zephyr DFU sample', vm=host)
+
def test_arm_ast1030_otp_blockdev_device(self):
self.vm.set_machine("ast1030-evb")
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-11-10 8:31 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 8:29 [PATCH v3 00/12] hw/usb/aspeed-udc: Add AST1030 UDC support Jamin Lin
2026-11-10 8:29 ` [PATCH v3 01/12] hw/usb/aspeed-udc: Make the SETUP buffer byte addressable Jamin Lin
2026-11-10 8:29 ` [PATCH v3 02/12] hw/usb/aspeed-udc: Finish an EP0 IN transfer at the status stage Jamin Lin
2026-11-10 8:29 ` [PATCH v3 03/12] hw/usb/aspeed-udc: Mask the reserved bits on register writes Jamin Lin
2026-11-10 8:29 ` [PATCH v3 04/12] hw/usb/aspeed-udc: Clear EP_POOL_ACK when no endpoint ack is left Jamin Lin
2026-11-10 8:29 ` [PATCH v3 05/12] hw/usb/aspeed-udc: Rename the transfer helpers after their DMA mode Jamin Lin
2026-11-10 8:29 ` [PATCH v3 06/12] hw/usb/aspeed-udc: Store the EP_DMA_STS write first Jamin Lin
2026-11-10 8:29 ` [PATCH v3 07/12] hw/usb/aspeed-udc: Pick the EP_DMA_STS path by direction Jamin Lin
2026-11-10 8:29 ` [PATCH v3 08/12] hw/usb/aspeed-udc: Support single-stage IN transfers Jamin Lin
2026-11-10 8:29 ` [PATCH v3 09/12] hw/usb/aspeed-udc: Use the endpoint max packet size for OUT transfers Jamin Lin
2026-11-10 8:29 ` [PATCH v3 10/12] hw/usb/aspeed-udc: Add trace events for the transfer paths Jamin Lin
2026-11-10 8:29 ` [PATCH v3 11/12] hw/arm/aspeed_ast10x0: Add the USB device controller Jamin Lin
2026-11-10 8:29 ` [PATCH v3 12/12] tests/functional/arm/test_aspeed_ast1030: Test the usbredir server transport Jamin Lin
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.