Add the Octeon UCTL wrapper around the DWC3 controller, including the
big endian register window, byte swapping, DMA mapping and interrupt
routing. Instantiate both EBB7304 USB controllers.
---
hw/mips/Kconfig | 1 +
hw/mips/octeon.c | 31 ++++
hw/mips/octeon_internal.h | 6 +
hw/usb/meson.build | 5 +
hw/usb/octeon_usb.c | 319 ++++++++++++++++++++++++++++++++++++++
5 files changed, 362 insertions(+)
create mode 100644 hw/usb/octeon_usb.c
diff --git a/hw/mips/Kconfig b/hw/mips/Kconfig
index 5fe5f885e3..2566df6da6 100644
--- a/hw/mips/Kconfig
+++ b/hw/mips/Kconfig
@@ -73,6 +73,7 @@ config MIPS_OCTEON
depends on MIPS64 && TARGET_BIG_ENDIAN
select PFLASH_CFI02
select SERIAL_MM
+ select USB_DWC3
config MIPS_CPS
bool
diff --git a/hw/mips/octeon.c b/hw/mips/octeon.c
index 7e78511b8f..94717b6c89 100644
--- a/hw/mips/octeon.c
+++ b/hw/mips/octeon.c
@@ -287,6 +287,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(OcteonCsrBankState,
OCTEON_CSR_BANK)
#define OCTEON_POW_BASE 0x1670000000000ULL
#define OCTEON_POW_SIZE 0x2000
+#define OCTEON_CIU3_USB0_INTSN 0x68080
+#define OCTEON_CIU3_USB1_INTSN 0x69080
+
#define OCTEON_UART0_BASE 0x1180000000800ULL
#define OCTEON_UART0_ALIAS_BASE 0x1180000000c00ULL
#define OCTEON_UART_TX_REG 0x40
@@ -1032,6 +1035,15 @@ static bool octeon_ciu3_source_from_intsn(uint32_t intsn,
*source = OCTEON_CIU3_SRC_UART0;
return true;
}
+ if (intsn == OCTEON_CIU3_USB0_INTSN) {
+ *source = OCTEON_CIU3_SRC_USB0;
+ return true;
+ }
+ if (intsn == OCTEON_CIU3_USB1_INTSN) {
+ *source = OCTEON_CIU3_SRC_USB1;
+ return true;
+ }
+
if (intsn >= OCTEON_CIU3_MBOX_INTSN(0) &&
intsn < OCTEON_CIU3_MBOX_INTSN(OCTEON_MAX_CPUS)) {
*source = OCTEON_CIU3_SRC_MBOX0 +
@@ -1047,6 +1059,10 @@ static uint32_t octeon_ciu3_source_intsn(unsigned int
source)
switch (source) {
case OCTEON_CIU3_SRC_UART0:
return OCTEON_CIU3_UART0_INTSN;
+ case OCTEON_CIU3_SRC_USB0:
+ return OCTEON_CIU3_USB0_INTSN;
+ case OCTEON_CIU3_SRC_USB1:
+ return OCTEON_CIU3_USB1_INTSN;
}
if (source >= OCTEON_CIU3_SRC_MBOX0 &&
@@ -1061,6 +1077,8 @@ static bool octeon_ciu3_source_is_level(unsigned int
source)
{
switch (source) {
case OCTEON_CIU3_SRC_UART0:
+ case OCTEON_CIU3_SRC_USB0:
+ case OCTEON_CIU3_SRC_USB1:
return true;
}
@@ -2156,6 +2174,18 @@ void octeon_irq_set(void *opaque, int irq, int level)
}
qatomic_set(&s->intc->uart_pending, level);
break;
+ case OCTEON_IRQ_USB0:
+ src = OCTEON_CIU3_SRC_USB0;
+ if (!octeon_ciu3_set_level(s, src, level)) {
+ return;
+ }
+ break;
+ case OCTEON_IRQ_USB1:
+ src = OCTEON_CIU3_SRC_USB1;
+ if (!octeon_ciu3_set_level(s, src, level)) {
+ return;
+ }
+ break;
default:
return;
}
@@ -2791,6 +2821,7 @@ static void mips_octeon_init(MachineState *machine)
memory_region_add_subregion(get_system_memory(), OCTEON_POW_BASE,
&s->pow);
+ octeon_init_usb(s);
octeon_realize_peripherals(s);
}
diff --git a/hw/mips/octeon_internal.h b/hw/mips/octeon_internal.h
index f2daa35cdb..6c93c957de 100644
--- a/hw/mips/octeon_internal.h
+++ b/hw/mips/octeon_internal.h
@@ -22,6 +22,7 @@
#define OCTEON_MIO_BOOT_LOC_SIZE 0x100
#define OCTEON_MIO_EMM_BUF_SIZE 512
#define OCTEON_LMC_COUNT 4
+#define OCTEON_USB_COUNT 2
#define OCTEON_TWSI_COUNT 2
#define OCTEON_TWSI_STAT_IDLE 0xf8
#define OCTEON_SPD_EEPROM_SIZE 139
@@ -29,11 +30,15 @@
typedef enum OcteonIRQ {
OCTEON_IRQ_UART,
+ OCTEON_IRQ_USB0,
+ OCTEON_IRQ_USB1,
OCTEON_IRQ_COUNT,
} OcteonIRQ;
typedef enum OcteonCiu3Source {
OCTEON_CIU3_SRC_UART0,
+ OCTEON_CIU3_SRC_USB0,
+ OCTEON_CIU3_SRC_USB1,
OCTEON_CIU3_SRC_MBOX0,
OCTEON_CIU3_SRC_COUNT = OCTEON_CIU3_SRC_MBOX0 + OCTEON_MAX_CPUS,
} OcteonCiu3Source;
@@ -103,5 +108,6 @@ void octeon_irq_set(void *opaque, int irq, int level);
void octeon_validate_ram_size(uint64_t ram_size);
void octeon_init_spd(OcteonState *s);
void octeon_init_twsi(OcteonState *s);
+void octeon_init_usb(OcteonState *s);
#endif
diff --git a/hw/usb/meson.build b/hw/usb/meson.build
index ba55c28ef6..051140fa57 100644
--- a/hw/usb/meson.build
+++ b/hw/usb/meson.build
@@ -28,6 +28,11 @@ system_ss.add(when: 'CONFIG_USB_DWC2', if_true:
files('hcd-dwc2.c'))
system_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c'))
system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.c'))
+if 'CONFIG_TCG' in config_all_accel
+ mips_ss.add(when: 'CONFIG_MIPS_OCTEON',
+ if_true: files('octeon_usb.c'))
+endif
+
system_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c'))
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c'))
system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true:
files('xlnx-versal-usb2-ctrl-regs.c'))
diff --git a/hw/usb/octeon_usb.c b/hw/usb/octeon_usb.c
new file mode 100644
index 0000000000..48e9ec234f
--- /dev/null
+++ b/hw/usb/octeon_usb.c
@@ -0,0 +1,319 @@
+/*
+ * QEMU Cavium Octeon USB controller model.
+ *
+ * Copyright (c) 2026 Kirill A. Korinsky
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "qapi/error.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/resettable.h"
+#include "hw/core/sysbus.h"
+#include "hw/mips/octeon_internal.h"
+#include "hw/usb/hcd-dwc3.h"
+#include "system/address-spaces.h"
+#include "system/memory.h"
+
+#define OCTEON_UCTL_SIZE 0x100
+#define OCTEON_USB0_UCTL_BASE 0x1180068000000ULL
+#define OCTEON_USB1_UCTL_BASE 0x1180069000000ULL
+#define OCTEON_USB0_DWC3_BASE 0x1680000000000ULL
+#define OCTEON_USB1_DWC3_BASE 0x1690000000000ULL
+#define OCTEON_UCTL_SHIM_CFG 0xe8
+#define OCTEON_UCTL_SHIM_CFG_CSR_BYTE_SWAP 0x3
+#define OCTEON_UCTL_SHIM_CFG_CSR_NATIVE 0x3
+
+#define TYPE_OCTEON_USB "octeon-usb"
+OBJECT_DECLARE_TYPE(OcteonUsbState, OcteonUsbClass, OCTEON_USB)
+
+struct OcteonUsbState {
+ SysBusDevice parent_obj;
+ USBDWC3 dwc3;
+ unsigned int index;
+ MemoryRegion uctl;
+ MemoryRegion dwc3_window;
+ AddressSpace dwc3_as;
+ GHashTable *regs;
+ bool dwc3_as_initialized;
+};
+
+struct OcteonUsbClass {
+ SysBusDeviceClass parent_class;
+ ResettablePhases parent_phases;
+};
+
+static uint64_t octeon_uctl_read(void *opaque, hwaddr addr, unsigned size)
+{
+ OcteonUsbState *usb = opaque;
+ hwaddr reg = addr & ~7ULL;
+ uint64_t value;
+
+ if (!octeon_reg_lookup(usb->regs, reg, &value)) {
+ value = 0;
+ }
+
+ return octeon_read64(value, addr, size);
+}
+
+static void octeon_uctl_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ OcteonUsbState *usb = opaque;
+ hwaddr reg = addr & ~7ULL;
+ uint64_t old;
+
+ if (!octeon_reg_lookup(usb->regs, reg, &old)) {
+ old = 0;
+ }
+
+ octeon_reg_store(usb->regs, reg, octeon_write64(old, addr, value, size));
+}
+
+static const MemoryRegionOps octeon_uctl_ops = {
+ .read = octeon_uctl_read,
+ .write = octeon_uctl_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static bool octeon_usb_csr_needs_swap(OcteonUsbState *usb)
+{
+#if TARGET_BIG_ENDIAN
+ uint64_t value;
+
+ if (!octeon_reg_lookup(usb->regs, OCTEON_UCTL_SHIM_CFG, &value)) {
+ return true;
+ }
+
+ return (value & OCTEON_UCTL_SHIM_CFG_CSR_BYTE_SWAP) !=
+ OCTEON_UCTL_SHIM_CFG_CSR_NATIVE;
+#else
+ return false;
+#endif
+}
+
+static uint64_t octeon_usb_swap(uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 2:
+ return bswap16(value);
+ case 4:
+ return bswap32(value);
+ case 8:
+ return bswap64(value);
+ default:
+ return value;
+ }
+}
+
+static uint64_t octeon_dwc3_window_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ OcteonUsbState *usb = opaque;
+ uint64_t value;
+
+ switch (size) {
+ case 1:
+ value = address_space_ldub(&usb->dwc3_as, addr,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 2:
+ value = address_space_lduw_le(&usb->dwc3_as, addr,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 4:
+ value = address_space_ldl_le(&usb->dwc3_as, addr,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 8:
+ value = address_space_ldq_le(&usb->dwc3_as, addr,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ if (octeon_usb_csr_needs_swap(usb)) {
+ value = octeon_usb_swap(value, size);
+ }
+
+ return value;
+}
+
+static void octeon_dwc3_window_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ OcteonUsbState *usb = opaque;
+
+ if (octeon_usb_csr_needs_swap(usb)) {
+ value = octeon_usb_swap(value, size);
+ }
+
+ switch (size) {
+ case 1:
+ address_space_stb(&usb->dwc3_as, addr, value,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 2:
+ address_space_stw_le(&usb->dwc3_as, addr, value,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 4:
+ address_space_stl_le(&usb->dwc3_as, addr, value,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ case 8:
+ address_space_stq_le(&usb->dwc3_as, addr, value,
+ MEMTXATTRS_UNSPECIFIED, NULL);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static const MemoryRegionOps octeon_dwc3_window_ops = {
+ .read = octeon_dwc3_window_read,
+ .write = octeon_dwc3_window_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 8,
+ },
+};
+
+static void octeon_usb_reset_hold(Object *obj, ResetType type)
+{
+ OcteonUsbClass *ouc = OCTEON_USB_GET_CLASS(obj);
+ OcteonUsbState *usb = OCTEON_USB(obj);
+
+ if (ouc->parent_phases.hold) {
+ ouc->parent_phases.hold(obj, type);
+ }
+
+ g_hash_table_remove_all(usb->regs);
+}
+
+static void octeon_usb_realize(DeviceState *dev, Error **errp)
+{
+ OcteonUsbState *usb = OCTEON_USB(dev);
+ static const char * const uctl_name[OCTEON_USB_COUNT] = {
+ "octeon.usb0-uctl",
+ "octeon.usb1-uctl",
+ };
+ static const char * const dwc3_window_name[OCTEON_USB_COUNT] = {
+ "octeon.usb0-dwc3-window",
+ "octeon.usb1-dwc3-window",
+ };
+ static const char * const dwc3_as_name[OCTEON_USB_COUNT] = {
+ "octeon.usb0-dwc3-as",
+ "octeon.usb1-dwc3-as",
+ };
+ USBDWC3 *dwc3 = &usb->dwc3;
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ MemoryRegion *dwc3_mr;
+
+ object_property_set_link(OBJECT(&dwc3->sysbus_xhci), "dma",
+ OBJECT(get_system_memory()), &error_abort);
+ qdev_prop_set_uint32(DEVICE(&dwc3->sysbus_xhci), "intrs", 1);
+ if (!sysbus_realize(SYS_BUS_DEVICE(dwc3), errp)) {
+ return;
+ }
+
+ dwc3_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dwc3), 0);
+ address_space_init(&usb->dwc3_as, dwc3_mr,
+ dwc3_as_name[usb->index]);
+ usb->dwc3_as_initialized = true;
+ memory_region_init_io(&usb->uctl, OBJECT(dev), &octeon_uctl_ops, usb,
+ uctl_name[usb->index], OCTEON_UCTL_SIZE);
+ sysbus_init_mmio(sbd, &usb->uctl);
+ memory_region_init_io(&usb->dwc3_window, OBJECT(dev),
+ &octeon_dwc3_window_ops, usb,
+ dwc3_window_name[usb->index], DWC3_SIZE);
+ sysbus_init_mmio(sbd, &usb->dwc3_window);
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&dwc3->sysbus_xhci));
+}
+
+static void octeon_usb_init(Object *obj)
+{
+ OcteonUsbState *usb = OCTEON_USB(obj);
+
+ usb->regs = g_hash_table_new_full(octeon_uint64_hash,
+ octeon_uint64_equal,
+ g_free, g_free);
+ object_initialize_child(obj, "dwc3", &usb->dwc3, TYPE_USB_DWC3);
+}
+
+static void octeon_usb_finalize(Object *obj)
+{
+ OcteonUsbState *usb = OCTEON_USB(obj);
+
+ if (usb->dwc3_as_initialized) {
+ address_space_destroy(&usb->dwc3_as);
+ }
+ g_hash_table_destroy(usb->regs);
+}
+
+static void octeon_usb_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ OcteonUsbClass *ouc = OCTEON_USB_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = octeon_usb_realize;
+ dc->user_creatable = false;
+ resettable_class_set_parent_phases(rc, NULL, octeon_usb_reset_hold, NULL,
+ &ouc->parent_phases);
+}
+
+static const TypeInfo octeon_usb_type_info = {
+ .name = TYPE_OCTEON_USB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(OcteonUsbState),
+ .instance_init = octeon_usb_init,
+ .instance_finalize = octeon_usb_finalize,
+ .class_size = sizeof(OcteonUsbClass),
+ .class_init = octeon_usb_class_init,
+};
+
+static void octeon_usb_register_types(void)
+{
+ type_register_static(&octeon_usb_type_info);
+}
+
+type_init(octeon_usb_register_types)
+
+void octeon_init_usb(OcteonState *s)
+{
+ static const uint64_t uctl_base[OCTEON_USB_COUNT] = {
+ OCTEON_USB0_UCTL_BASE,
+ OCTEON_USB1_UCTL_BASE,
+ };
+ static const uint64_t dwc3_base[OCTEON_USB_COUNT] = {
+ OCTEON_USB0_DWC3_BASE,
+ OCTEON_USB1_DWC3_BASE,
+ };
+ static const OcteonIRQ irq[OCTEON_USB_COUNT] = {
+ OCTEON_IRQ_USB0,
+ OCTEON_IRQ_USB1,
+ };
+ unsigned int i;
+
+ for (i = 0; i < OCTEON_USB_COUNT; i++) {
+ DeviceState *dev = qdev_new(TYPE_OCTEON_USB);
+ OcteonUsbState *usb = OCTEON_USB(dev);
+
+ usb->index = i;
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, uctl_base[i]);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, dwc3_base[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+ qemu_allocate_irq(octeon_irq_set, s, irq[i]));
+ }
+}
--
2.54.0