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


Reply via email to