https://patchew.org/QEMU/20260715134447.56007-1-kirill%40korins.ky/

Index: hw/mips/octeon.c
--- hw/mips/octeon.c.orig
+++ hw/mips/octeon.c
@@ -0,0 +1,3035 @@
+/*
+ * QEMU Cavium Octeon board model.
+ *
+ * Copyright (c) 2026 Kirill A. Korinsky
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/datadir.h"
+#include "qemu/host-utils.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "hw/char/serial-mm.h"
+#include "hw/core/clock.h"
+#include "hw/core/irq.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-properties-system.h"
+#include "hw/core/sysbus.h"
+#include "hw/block/flash.h"
+#include "hw/mips/mips.h"
+#include "system/blockdev.h"
+#include "system/address-spaces.h"
+#include "system/cpus.h"
+#include "system/memory.h"
+#include "system/reset.h"
+#include "system/runstate.h"
+#include "system/system.h"
+#include "exec/cpu-interrupt.h"
+#include "qemu/guest-random.h"
+#include "qemu/error-report.h"
+#include "qemu/atomic.h"
+#include "hw/mips/octeon_internal.h"
+#include "target/mips/cpu.h"
+
+#define TYPE_OCTEON_MACHINE MACHINE_TYPE_NAME("octeon3")
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonMachineState, OCTEON_MACHINE)
+
+#define TYPE_OCTEON_PERIPHERAL "octeon-peripheral"
+OBJECT_DECLARE_TYPE(OcteonPeripheralState, OcteonPeripheralClass,
+                    OCTEON_PERIPHERAL)
+
+#define TYPE_OCTEON_MIO "octeon-mio"
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonMioState, OCTEON_MIO)
+#define TYPE_OCTEON_EMM "octeon-emm"
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonEmmState, OCTEON_EMM)
+#define TYPE_OCTEON_RST "octeon-rst"
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonRstState, OCTEON_RST)
+#define TYPE_OCTEON_INTC "octeon-intc"
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonIntcState, OCTEON_INTC)
+#define TYPE_OCTEON_LMC "octeon-lmc"
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonLmcState, OCTEON_LMC)
+#define TYPE_OCTEON_CSR_BANK "octeon-csr-bank"
+OBJECT_DECLARE_SIMPLE_TYPE(OcteonCsrBankState, OCTEON_CSR_BANK)
+
+#define OCTEON_CSR_32BIT_SIZE       4
+#define OCTEON_CSR_64BIT_SIZE       8
+#define OCTEON_CSR_HI32_OFFSET      0
+#define OCTEON_CSR_LO32_OFFSET      4
+
+/*
+ * Clock defaults follow a Ubiquiti E1000 EBB7304 U-Boot banner.
+ * DDR follows the upstream EBB7304 U-Boot board default.
+ */
+#define OCTEON_DEFAULT_CPU_HZ       1800000000
+#define OCTEON_DEFAULT_REF_HZ       50000000
+#define OCTEON_DEFAULT_IO_HZ        1000000000
+#define OCTEON_DEFAULT_DDR_HZ       800000000
+
+/*
+ * The physical layout follows U-Boot's generic Octeon III
+ * OCTEON_MAX_PHY_MEM_SIZE value. The synthesized DDR4 SPD below accepts
+ * power-of-two RAM sizes from 256 MiB through 32 GiB. Keep 1 GiB as the
+ * tested default.
+ */
+#define OCTEON_DEFAULT_RAM_SIZE     (1 * GiB)
+#define OCTEON_MAX_PHY_MEM_SIZE     (512 * GiB)
+
+/*
+ * The EBB7304 FDT exposes DRAM below and above the
+ * 0x10000000..0x1fffffff boot-bus/EJTAG hole.
+ */
+#define OCTEON_DR0_BASE             0x000000000ULL
+#define OCTEON_DR0_SIZE             (256 * MiB)
+#define OCTEON_DR1_BASE             0x020000000ULL
+#define OCTEON_DR1_SIZE             (OCTEON_MAX_PHY_MEM_SIZE - OCTEON_DR0_SIZE)
+
+#define OCTEON_CIU_BASE             0x1070000000000ULL
+#define OCTEON_CIU_LEGACY_PAGE_SIZE (4 * KiB)
+#define OCTEON_CIU_SIZE             (1 * MiB + OCTEON_CIU_LEGACY_PAGE_SIZE)
+#define OCTEON_CIU_FUSE             0x728
+#define OCTEON_CIU_SUM_IPI          0x0000000100000000ULL
+#define OCTEON_CIU_SUM_UART         0x0000000800000000ULL
+#define OCTEON_CIU_SUM_PCI          0x0000001000000000ULL
+#define OCTEON_CIU_ENABLE0          0x88006f1800000000ULL
+#define OCTEON_CIU_IPI_SUM_BASE     0x008
+#define OCTEON_CIU_IPI_SUM_STRIDE   0x10
+#define OCTEON_CIU_IPI_EN_BASE      0x210
+#define OCTEON_CIU_IPI_EN_STRIDE    0x20
+#define OCTEON_CIU_MBOX_SET_BASE    0x600
+#define OCTEON_CIU_MBOX_CLR_BASE    0x680
+#define OCTEON_CIU_MBOX_SET_STRIDE  8
+#define OCTEON_CIU_GPIO_RX_DAT      0x880
+#define OCTEON_CIU_GPIO_TX_SET      0x888
+#define OCTEON_CIU_GPIO_TX_CLR      0x890
+#define OCTEON_CIU_GPIO_BIT_CFG_STRIDE OCTEON_CSR_64BIT_SIZE
+#define OCTEON_CIU_GPIO_BIT_CFGX(x) \
+    (0x900 + (x) * OCTEON_CIU_GPIO_BIT_CFG_STRIDE)
+#define OCTEON_CIU_GPIO_INPUTS      ((1ULL << OCTEON_CIU_GPIO_COUNT) - 1)
+#define OCTEON_CIU_GPIO_BIT_CFG_TX_OE (1ULL << 0)
+
+#define OCTEON_CIU3_BASE            0x1010000000000ULL
+#define OCTEON_CIU3_SIZE            0xb0000000ULL
+#define OCTEON_CIU3_PP_RST          0x100
+#define OCTEON_CIU3_PP_RST_PENDING  0x110
+#define OCTEON_CIU3_NMI             0x160
+#define OCTEON_CIU3_FUSE            0x1a0
+#define OCTEON_CIU3_IDT_CTL(idt)    ((idt) * 8 + 0x110000U)
+#define OCTEON_CIU3_IDT_PP(idt)     ((idt) * 32 + 0x120000U)
+#define OCTEON_CIU3_IDT_IO(idt)     ((idt) * 8 + 0x130000U)
+#define OCTEON_CIU3_DEST_PP_INT(cpu) ((cpu) * 8 + 0x200000U)
+#define OCTEON_CIU3_DEST_PP_INT_INTSN_SHIFT 32
+#define OCTEON_CIU3_DEST_PP_INT_INTR 0x0000000000000001ULL
+#define OCTEON_CIU3_IDT_CTL_IP_NUM  0x7
+#define OCTEON_CIU3_CP0_IRQ_BASE    2
+#define OCTEON_CIU3_ISC_CTL_BASE    0x80000000U
+#define OCTEON_CIU3_ISC_W1C_BASE    0x90000000U
+#define OCTEON_CIU3_ISC_W1S_BASE    0xa0000000U
+#define OCTEON_CIU3_ISC_CTL_IDT     0x0000000000ff0000ULL
+#define OCTEON_CIU3_ISC_CTL_IDT_SHIFT 16
+#define OCTEON_CIU3_ISC_CTL_IMP     0x0000000000008000ULL
+#define OCTEON_CIU3_ISC_CTL_EN      0x0000000000000002ULL
+#define OCTEON_CIU3_ISC_CTL_RAW     0x0000000000000001ULL
+#define OCTEON_CIU3_SRC_LEVEL       0x00000001U
+#define OCTEON_CIU3_SRC_RAW         0x00000002U
+#define OCTEON_CIU3_NINTSN          (1U << 20)
+#define OCTEON_CIU3_ISC_SIZE        (OCTEON_CIU3_NINTSN * 8)
+#define OCTEON_CIU3_PEM_INTSN_INTA(port) (((0xc0 + (port)) << 12) + 60)
+#define OCTEON_CIU3_MBOX_INTSN(cpu) ((cpu) + 0x4000U)
+#define OCTEON_CIU3_UART0_INTSN     0x8000
+#define OCTEON_CSR_BASE             0x1180000000000ULL
+#define OCTEON_CSR_SIZE             0x100000000ULL
+#define OCTEON_DPI_BASE             0x1df0000000000ULL
+#define OCTEON_DPI_SIZE             0x10000
+#define OCTEON_MIO_BOOT_REG_CFGX(x) \
+    (0x0000000 + (x) * OCTEON_CSR_64BIT_SIZE)
+#define OCTEON_MIO_BOOT_REG_CFG_INDEX(reg) \
+    ((reg) / OCTEON_CSR_64BIT_SIZE)
+#define OCTEON_MIO_BOOT_REG_CFG_BASE 0x000000000000ffffULL
+#define OCTEON_MIO_BOOT_LOC_CFGX(x) \
+    (0x0000080 + ((x) & 1) * OCTEON_CSR_64BIT_SIZE)
+#define OCTEON_MIO_BOOT_LOC_CFG_INDEX(reg) \
+    (((reg) - OCTEON_MIO_BOOT_LOC_CFGX(0)) / OCTEON_CSR_64BIT_SIZE)
+#define OCTEON_MIO_BOOT_LOC_ADR     0x0000090
+#define OCTEON_MIO_BOOT_LOC_DAT     0x0000098
+#define OCTEON_MIO_BOOT_LOC_CFG_BASE 0x000000000ffffff8ULL
+#define OCTEON_MIO_BOOT_LOC_CFG_BASE_SHIFT 3
+#define OCTEON_MIO_BOOT_LOC_CFG_EN  0x0000000080000000ULL
+#define OCTEON_MIO_BOOT_LOC_ADR_MASK 0xf8
+/* Fuses are left unprogrammed; firmware only needs reads to complete. */
+#define OCTEON_MIO_FUS_DAT2         0x0001410
+#define OCTEON_MIO_FUS_RCMD         0x0001500
+#define OCTEON_MIO_FUS_RCMD_PEND    0x0000000000001000ULL
+#define OCTEON_MIO_FUS_RCMD_DAT     0x0000000000ff0000ULL
+#define OCTEON_MIO_EMM_DMA_FIFO_CFG 0x0000160
+#define OCTEON_MIO_EMM_DMA_FIFO_ADR 0x0000170
+#define OCTEON_MIO_EMM_DMA_FIFO_CMD 0x0000178
+#define OCTEON_MIO_EMM_DMA_CFG      0x0000180
+#define OCTEON_MIO_EMM_DMA_ADR      0x0000188
+#define OCTEON_MIO_EMM_DMA_INT      0x0000190
+#define OCTEON_MIO_EMM_CFG          0x0002000
+#define OCTEON_MIO_EMM_MODEX(x) \
+    (0x0002008 + (x) * OCTEON_CSR_64BIT_SIZE)
+#define OCTEON_MIO_EMM_MODE_COUNT   4
+#define OCTEON_MIO_EMM_INT_EN_OLD   0x0002040
+#define OCTEON_MIO_EMM_SWITCH       0x0002048
+#define OCTEON_MIO_EMM_DMA          0x0002050
+#define OCTEON_MIO_EMM_CMD          0x0002058
+#define OCTEON_MIO_EMM_RSP_STS      0x0002060
+#define OCTEON_MIO_EMM_RSP_LO       0x0002068
+#define OCTEON_MIO_EMM_RSP_HI       0x0002070
+#define OCTEON_MIO_EMM_INT          0x0002078
+#define OCTEON_MIO_EMM_WDOG         0x0002088
+#define OCTEON_MIO_EMM_SAMPLE       0x0002090
+#define OCTEON_MIO_EMM_STS_MASK     0x0002098
+#define OCTEON_MIO_EMM_RCA          0x00020a0
+#define OCTEON_MIO_EMM_BUF_IDX      0x00020e0
+#define OCTEON_MIO_EMM_BUF_DAT      0x00020e8
+#define OCTEON_MIO_EMM_DEBUG        0x00020f8
+#define OCTEON_MIO_EMM_NODE_OFFSET  0x0002000
+#define OCTEON_MIO_EMM_CMD_VAL      (1ULL << 59)
+#define OCTEON_MIO_EMM_DMA_VAL      (1ULL << 59)
+#define OCTEON_MIO_EMM_DMA_BLOCK_CNT_SHIFT 32
+#define OCTEON_MIO_EMM_DMA_BLOCK_CNT_MASK \
+    (0xffffULL << OCTEON_MIO_EMM_DMA_BLOCK_CNT_SHIFT)
+#define OCTEON_MIO_EMM_SWITCH_EXE   (1ULL << 59)
+#define OCTEON_MIO_EMM_CMD_IDX_SHIFT 32
+#define OCTEON_MIO_EMM_CMD_IDX_BITS 6
+#define OCTEON_MIO_EMM_CMD_IDX_MASK \
+    ((1U << OCTEON_MIO_EMM_CMD_IDX_BITS) - 1)
+#define OCTEON_MIO_EMM_CMD_BUS_ID_SHIFT 60
+#define OCTEON_MIO_EMM_BUS_ID_MASK  (OCTEON_MIO_EMM_MODE_COUNT - 1)
+#define OCTEON_MIO_EMM_RSP_STS_CMD_DONE (1ULL << 0)
+#define OCTEON_MIO_EMM_RSP_STS_CMD_IDX_SHIFT 1
+#define OCTEON_MIO_EMM_RSP_STS_RSP_TIMEOUT (1ULL << 13)
+#define OCTEON_MIO_EMM_RSP_STS_DMA_VAL (1ULL << 57)
+#define OCTEON_MIO_EMM_RSP_STS_DMA_PEND (1ULL << 56)
+#define OCTEON_MIO_EMM_RSP_STS_BUS_ID_SHIFT 60
+#define OCTEON_MIO_EMM_DMA_INT_DONE (1ULL << 0)
+#define OCTEON_MIO_EMM_INT_CMD_DONE (1ULL << 1)
+#define OCTEON_MIO_EMM_INT_CMD_ERR  (1ULL << 3)
+#define OCTEON_MIO_EMM_INT_DMA_DONE (1ULL << 2)
+#define OCTEON_MIO_EMM_INT_DMA_ERR  (1ULL << 4)
+#define OCTEON_MIO_EMM_INT_SWITCH_DONE (1ULL << 5)
+#define OCTEON_MIO_EMM_INT_SWITCH_ERR  (1ULL << 6)
+#define OCTEON_MIO_EMM_SWITCH_MODE_MASK ((1ULL << 49) - 1)
+#define OCTEON_MIO_EMM_CFG_BUS_EN_MASK \
+    ((1ULL << OCTEON_MIO_EMM_MODE_COUNT) - 1)
+#define OCTEON_MIO_EMM_LOW_SPEED_HZ    400000
+#define OCTEON_MIO_EMM_MODE_CLK_LO_MASK 0x000000000000ffffULL
+#define OCTEON_MIO_EMM_MODE_CLK_HI_MASK 0x00000000ffff0000ULL
+#define OCTEON_MIO_EMM_MODE_CLK_HI_SHIFT 16
+#define OCTEON_MIO_EMM_BUF_IDX_INC  (1ULL << 16)
+#define OCTEON_MIO_EMM_BUF_WORD_SIZE OCTEON_CSR_64BIT_SIZE
+#define OCTEON_MIO_EMM_BUF_ENTRY_COUNT \
+    (OCTEON_MIO_EMM_BUF_SIZE / OCTEON_MIO_EMM_BUF_WORD_SIZE)
+#define OCTEON_MIO_EMM_BUF_IDX_OFF_MASK \
+    (OCTEON_MIO_EMM_BUF_ENTRY_COUNT - 1)
+#define OCTEON_MMC_CMD_GO_IDLE_STATE 0
+#define OCTEON_MMC_CMD_BUSTEST_R    14
+#define OCTEON_MMC_CMD_BUSTEST_W    19
+/* SD/MMC CMD19 request patterns and CMD14 response patterns. */
+#define OCTEON_MMC_BUSTEST_8_REQ0   0x55
+#define OCTEON_MMC_BUSTEST_8_REQ1   0xaa
+#define OCTEON_MMC_BUSTEST_8_RESP0  0xaa
+#define OCTEON_MMC_BUSTEST_8_RESP1  0x55
+#define OCTEON_MMC_BUSTEST_4_REQ0   0x5a
+#define OCTEON_MMC_BUSTEST_4_REQ4   0x99
+#define OCTEON_MMC_BUSTEST_4_REQ5   0x50
+#define OCTEON_MMC_BUSTEST_4_REQ6   0x0f
+#define OCTEON_MMC_BUSTEST_4_RESP0  0xa5
+#define OCTEON_MMC_BUSTEST_1_REQ0   0x80
+#define OCTEON_MMC_BUSTEST_1_REQ1   0x70
+#define OCTEON_MMC_BUSTEST_1_REQ2   0x78
+#define OCTEON_MMC_BUSTEST_1_REQ3   0x01
+#define OCTEON_MMC_BUSTEST_1_RESP0  0x40
+#define OCTEON_RST_BOOT             0x6001600
+#define OCTEON_RST_SOFT_RST         0x6001680
+/* U-Boot uses the reset controller to park and release secondary cores. */
+#define OCTEON_RST_PP_POWER         0x6001700
+#define OCTEON_LMC_BASE             0x88000000
+#define OCTEON_LMC_SEQ_CTL          0x48
+#define OCTEON_LMC_SEQ_COMPLETE     (1ULL << 5)
+#define OCTEON_LMC_MPR_DATA0        0x70
+#define OCTEON_LMC_MPR_DATA1        0x78
+#define OCTEON_LMC_DCLK_CNT         0x1e0
+#define OCTEON_LMC_PHY_CTL          0x210
+#define OCTEON_LMC_PHY_DSK_COMPLETE (1ULL << 49)
+#define OCTEON_LMC_RLEVEL_RANK      0x280
+#define OCTEON_LMC_RLEVEL_STATUS    (3ULL << 54)
+#define OCTEON_LMC_RLEVEL_DBG       0x2a8
+#define OCTEON_LMC_RLEVEL_MASK      0x3f0
+#define OCTEON_LMC_WLEVEL_RANK      0x2c0
+#define OCTEON_LMC_RANK_COUNT       4
+#define OCTEON_LMC_WLEVEL_STATUS    (3ULL << 45)
+#define OCTEON_LMC_WLEVEL_DBG       0x308
+#define OCTEON_LMC_WLEVEL_MASK      (0x0fULL << 4)
+#define OCTEON_LMC_STRIDE           0x1000000
+#define OCTEON_RST_BOOT_C_MUL_SHIFT 30
+#define OCTEON_RST_BOOT_PNR_MUL_SHIFT 24
+
+#define OCTEON_FPA_BASE             0x1280000000000ULL
+#define OCTEON_FPA_SIZE             0x1000
+#define OCTEON_FPA_CLK_COUNT        0xf0
+
+#define OCTEON_IPD_BASE             0x14f0000000000ULL
+#define OCTEON_IPD_SIZE             0x1000
+#define OCTEON_IPD_CLK_COUNT        0x338
+
+#define OCTEON_PKO_BASE             0x1540000000000ULL
+#define OCTEON_PKO_SIZE             0x1000000
+
+#define OCTEON_RNG_BASE             0x1400000000000ULL
+#define OCTEON_RNG_SIZE             0x8
+
+#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
+#define OCTEON_UART_TX_HI32_OFFSET  OCTEON_CSR_HI32_OFFSET
+#define OCTEON_UART_TX_SIZE         OCTEON_CSR_64BIT_SIZE
+
+/*
+ * QEMU-only backing for per-core CVMSEG. Keep it outside guest DRAM;
+ * the guest sees the virtual CVMSEG window, not this physical address.
+ */
+#define OCTEON_CVMSEG_BASE          0x10000000000ULL
+#define OCTEON_CVMSEG_SIZE          0x4000
+#define OCTEON_CVMSEG_TOTAL_SIZE    (OCTEON_MAX_CPUS * OCTEON_CVMSEG_SIZE)
+
+#define OCTEON_GSERX_CFG0           0x90000080
+#define OCTEON_GSERX_QLM_STAT0      0x900000a0
+#define OCTEON_RST_CTLX0            0x6001640
+#define OCTEON_RST_SOFT_PRSTX0      0x60016c0
+#define OCTEON_PEMX_CFG_RD0         0xc0000030
+#define OCTEON_PEMX_CFG_WR0         0xc0000028
+#define OCTEON_PEMX_BIST_STATUS0    0xc0000440
+#define OCTEON_PEMX_ON0             0xc0000420
+#define OCTEON_PEMX_STRAP0          0xc0000408
+
+#define OCTEON_RST_CTL_HOST_MODE    (1ULL << 6)
+#define OCTEON_RST_CTL_RST_DONE     (1ULL << 8)
+#define OCTEON_FLASH_BASE           0x1f400000ULL
+#define OCTEON_FLASH_RESET_BASE     0x1fc00000ULL
+#define OCTEON_FLASH_RESET_SIZE     (4 * MiB)
+#define OCTEON_FLASH_MAIN_SECTORS   127
+#define OCTEON_FLASH_MAIN_SECTOR_SIZE (64 * KiB)
+#define OCTEON_FLASH_ENV_SECTORS    8
+#define OCTEON_FLASH_ENV_SECTOR_SIZE 0x2000
+#define OCTEON_FLASH_SIZE           \
+    (OCTEON_FLASH_MAIN_SECTORS * OCTEON_FLASH_MAIN_SECTOR_SIZE + \
+     OCTEON_FLASH_ENV_SECTORS * OCTEON_FLASH_ENV_SECTOR_SIZE)
+#define OCTEON_FLASH_ENV_OFFSET     \
+    (OCTEON_FLASH_SIZE - OCTEON_FLASH_ENV_SECTOR_SIZE)
+#define OCTEON_FLASH_WIDTH          2
+#define OCTEON_MIO_BOOT_REG_CFG_RESET_BASE (OCTEON_FLASH_RESET_BASE >> 16)
+#define OCTEON_BOOTBUS_LED_CS       4
+#define OCTEON_BOOTBUS_LED_SIZE     (64 * KiB)
+
+struct OcteonMachineState {
+    MachineState parent_obj;
+    OcteonState *board;
+    uint64_t cpu_hz;
+    uint64_t ref_hz;
+    uint64_t io_hz;
+    uint64_t ddr_hz;
+};
+
+struct OcteonPeripheralState {
+    SysBusDevice parent_obj;
+    OcteonState *board;
+    unsigned int index;
+};
+
+struct OcteonPeripheralClass {
+    SysBusDeviceClass parent_class;
+    ResettablePhases parent_phases;
+};
+
+struct OcteonMioState {
+    OcteonPeripheralState parent_obj;
+    MemoryRegion bootbus_led;
+    MemoryRegion mio_boot_loc;
+    uint64_t mio_boot_reg_cfg[OCTEON_MIO_BOOT_REG_CFG_COUNT];
+    uint64_t mio_boot_loc_cfg[2];
+    uint64_t mio_boot_loc_adr;
+    uint64_t mio_fus_rcmd;
+    hwaddr mio_boot_loc_base;
+    uint8_t mio_boot_loc_data[OCTEON_MIO_BOOT_LOC_SIZE];
+    bool mio_boot_loc_mapped;
+    hwaddr bootbus_led_base;
+    bool bootbus_led_mapped;
+};
+
+struct OcteonEmmState {
+    OcteonPeripheralState parent_obj;
+    GHashTable *regs;
+    uint64_t mio_emm_rsp_sts;
+    uint64_t mio_emm_rsp_lo;
+    uint64_t mio_emm_rsp_hi;
+    uint64_t mio_emm_int;
+    uint64_t mio_emm_dma_int;
+    uint16_t mio_emm_buf_idx;
+    bool mio_emm_buf_inc;
+    uint8_t mio_emm_buf[OCTEON_MIO_EMM_BUF_SIZE];
+};
+
+struct OcteonRstState {
+    OcteonPeripheralState parent_obj;
+    GHashTable *regs;
+    uint64_t rst_pp_power;
+};
+
+struct OcteonIntcState {
+    OcteonPeripheralState parent_obj;
+    MemoryRegion ciu;
+    MemoryRegion ciu3;
+    uint32_t ciu_mbox[OCTEON_MAX_CPUS];
+    uint64_t ciu_ipi_en[OCTEON_MAX_CPUS];
+    uint64_t ciu_gpio_rx;
+    uint64_t ciu_gpio_tx;
+    uint64_t ciu_gpio_bit_cfg[OCTEON_CIU_GPIO_COUNT];
+    bool uart_pending;
+    bool pci_pending;
+    uint64_t ciu3_pp_rst;
+    uint32_t ciu3_src_state[OCTEON_CIU3_SRC_COUNT];
+    uint64_t ciu3_src_ctl[OCTEON_CIU3_SRC_COUNT];
+    uint64_t ciu3_idt_ctl[OCTEON_CIU3_IDT_COUNT];
+    uint64_t ciu3_idt_pp[OCTEON_CIU3_IDT_COUNT];
+    uint64_t ciu3_idt_io[OCTEON_CIU3_IDT_COUNT];
+    uint16_t ciu3_src_cursor[OCTEON_MAX_CPUS][OCTEON_CIU3_CP0_IRQ_COUNT];
+    uint8_t ciu3_irq_line[OCTEON_MAX_CPUS][OCTEON_CIU3_CP0_IRQ_COUNT];
+};
+
+struct OcteonLmcState {
+    OcteonPeripheralState parent_obj;
+    GHashTable *regs;
+};
+
+struct OcteonCsrBankState {
+    OcteonPeripheralState parent_obj;
+    MemoryRegion csr;
+    GHashTable *values;
+};
+
+guint octeon_uint64_hash(gconstpointer v)
+{
+    uint64_t value = *(const uint64_t *)v;
+
+    return value ^ (value >> 32);
+}
+
+gboolean octeon_uint64_equal(gconstpointer a, gconstpointer b)
+{
+    return *(const uint64_t *)a == *(const uint64_t *)b;
+}
+
+uint64_t octeon_read64(uint64_t value, hwaddr addr, unsigned size)
+{
+    if (size == 4) {
+        if (addr & 4) {
+            return value & 0xffffffffU;
+        }
+        return value >> 32;
+    }
+
+    return value;
+}
+
+uint64_t octeon_write64(uint64_t old, hwaddr addr,
+                        uint64_t value, unsigned size)
+{
+    if (size == 4) {
+        if (addr & 4) {
+            return (old & 0xffffffff00000000ULL) | (value & 0xffffffffU);
+        }
+        return (old & 0xffffffffULL) | ((value & 0xffffffffU) << 32);
+    }
+
+    return value;
+}
+
+static uint64_t octeon_atomic_write64(uint64_t *ptr, hwaddr addr,
+                                      uint64_t value, unsigned size,
+                                      uint64_t and_mask, uint64_t or_mask,
+                                      uint64_t *oldp)
+{
+    uint64_t old;
+    uint64_t new;
+    uint64_t cmp = qatomic_read(ptr);
+
+    do {
+        old = cmp;
+        new = (octeon_write64(old, addr, value, size) & and_mask) | or_mask;
+        cmp = qatomic_cmpxchg(ptr, old, new);
+    } while (old != cmp);
+
+    if (oldp) {
+        *oldp = old;
+    }
+
+    return new;
+}
+
+static uint64_t octeon_clock_count(uint64_t hz)
+{
+    uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+    return muldiv64(now, hz, NANOSECONDS_PER_SECOND);
+}
+
+static void octeon_validate_clocks(OcteonMachineState *oms)
+{
+    if (!oms->cpu_hz || !oms->ref_hz || !oms->io_hz || !oms->ddr_hz) {
+        error_report("octeon3 clock frequencies must be non-zero");
+        exit(1);
+    }
+
+    if (oms->cpu_hz % oms->ref_hz || oms->io_hz % oms->ref_hz) {
+        error_report("octeon3 CPU and IO clocks must be multiples of "
+                     "ref-clock-hz");
+        exit(1);
+    }
+}
+
+bool octeon_reg_lookup(GHashTable *regs, uint64_t reg, uint64_t *value)
+{
+    uint64_t *stored = g_hash_table_lookup(regs, &reg);
+
+    if (!stored) {
+        return false;
+    }
+
+    *value = *stored;
+    return true;
+}
+
+void octeon_reg_store(GHashTable *regs, uint64_t reg, uint64_t value)
+{
+    uint64_t *key = g_new(uint64_t, 1);
+    uint64_t *stored = g_new(uint64_t, 1);
+
+    *key = reg;
+    *stored = value;
+    g_hash_table_replace(regs, key, stored);
+}
+
+static bool octeon_mio_emm_decode(hwaddr reg, hwaddr *ereg);
+
+bool octeon_csr_lookup(OcteonState *s, uint64_t reg, uint64_t *value)
+{
+    hwaddr ereg;
+
+    if (octeon_mio_emm_decode(reg, &ereg)) {
+        return octeon_reg_lookup(s->emm->regs, ereg, value);
+    }
+    if (reg == OCTEON_RST_CTLX0 || reg == OCTEON_RST_SOFT_PRSTX0 ||
+        reg == OCTEON_RST_SOFT_RST) {
+        return octeon_reg_lookup(s->rst->regs, reg, value);
+    }
+    return octeon_reg_lookup(s->csr_bank->values, reg, value);
+}
+
+static void octeon_csr_store(OcteonState *s, uint64_t reg, uint64_t value)
+{
+    hwaddr ereg;
+
+    if (octeon_mio_emm_decode(reg, &ereg)) {
+        octeon_reg_store(s->emm->regs, ereg, value);
+        return;
+    }
+    if (reg == OCTEON_RST_CTLX0 || reg == OCTEON_RST_SOFT_PRSTX0 ||
+        reg == OCTEON_RST_SOFT_RST) {
+        octeon_reg_store(s->rst->regs, reg, value);
+        return;
+    }
+    octeon_reg_store(s->csr_bank->values, reg, value);
+}
+
+static hwaddr octeon_mio_boot_loc_base(uint64_t cfg)
+{
+    uint64_t base = (cfg & OCTEON_MIO_BOOT_LOC_CFG_BASE) >>
+                    OCTEON_MIO_BOOT_LOC_CFG_BASE_SHIFT;
+
+    return base << 7;
+}
+
+static void octeon_mio_boot_loc_update(OcteonState *s)
+{
+    MemoryRegion *sysmem = get_system_memory();
+    bool enabled = s->mio->mio_boot_loc_cfg[0] & OCTEON_MIO_BOOT_LOC_CFG_EN;
+    hwaddr base = octeon_mio_boot_loc_base(s->mio->mio_boot_loc_cfg[0]);
+
+    if (s->mio->mio_boot_loc_mapped) {
+        if (enabled && base == s->mio->mio_boot_loc_base) {
+            return;
+        }
+        memory_region_del_subregion(sysmem, &s->mio->mio_boot_loc);
+        s->mio->mio_boot_loc_mapped = false;
+    }
+
+    if (enabled) {
+        memory_region_add_subregion_overlap(sysmem, base,
+                                            &s->mio->mio_boot_loc, 1);
+        s->mio->mio_boot_loc_base = base;
+        s->mio->mio_boot_loc_mapped = true;
+    }
+}
+
+static hwaddr octeon_mio_boot_reg_cfg_base(uint64_t cfg)
+{
+    return (cfg & OCTEON_MIO_BOOT_REG_CFG_BASE) << 16;
+}
+
+static void octeon_bootbus_led_update(OcteonState *s)
+{
+    MemoryRegion *sysmem = get_system_memory();
+    hwaddr base = octeon_mio_boot_reg_cfg_base(
+        s->mio->mio_boot_reg_cfg[OCTEON_BOOTBUS_LED_CS]);
+
+    if (s->mio->bootbus_led_mapped) {
+        if (base == s->mio->bootbus_led_base) {
+            return;
+        }
+        memory_region_del_subregion(sysmem, &s->mio->bootbus_led);
+        s->mio->bootbus_led_mapped = false;
+    }
+
+    if (base) {
+        memory_region_add_subregion(sysmem, base, &s->mio->bootbus_led);
+        s->mio->bootbus_led_base = base;
+        s->mio->bootbus_led_mapped = true;
+    }
+}
+
+static uint64_t octeon_mio_boot_loc_dat_read(OcteonState *s)
+{
+    unsigned int offset = s->mio->mio_boot_loc_adr &
+                          OCTEON_MIO_BOOT_LOC_ADR_MASK;
+    uint64_t value = ldq_be_p(s->mio->mio_boot_loc_data + offset);
+
+    s->mio->mio_boot_loc_adr =
+        (s->mio->mio_boot_loc_adr + OCTEON_CSR_64BIT_SIZE) &
+        OCTEON_MIO_BOOT_LOC_ADR_MASK;
+    return value;
+}
+
+static void octeon_mio_boot_loc_dat_write(OcteonState *s, hwaddr addr,
+                                          uint64_t value, unsigned size)
+{
+    unsigned int offset = s->mio->mio_boot_loc_adr &
+                          OCTEON_MIO_BOOT_LOC_ADR_MASK;
+    uint64_t old = ldq_be_p(s->mio->mio_boot_loc_data + offset);
+
+    value = octeon_write64(old, addr, value, size);
+    stq_be_p(s->mio->mio_boot_loc_data + offset, value);
+    memory_region_set_dirty(&s->mio->mio_boot_loc, offset, sizeof(value));
+    s->mio->mio_boot_loc_adr =
+        (s->mio->mio_boot_loc_adr + OCTEON_CSR_64BIT_SIZE) &
+        OCTEON_MIO_BOOT_LOC_ADR_MASK;
+}
+
+static bool octeon_mio_emm_valid_reg(hwaddr reg)
+{
+    if (reg >= OCTEON_MIO_EMM_MODEX(0) &&
+        reg < OCTEON_MIO_EMM_MODEX(OCTEON_MIO_EMM_MODE_COUNT) &&
+        (reg & (OCTEON_CSR_64BIT_SIZE - 1)) == 0) {
+        return true;
+    }
+
+    switch (reg) {
+    case OCTEON_MIO_EMM_DMA_INT:
+    case OCTEON_MIO_EMM_CFG:
+    case OCTEON_MIO_EMM_SWITCH:
+    case OCTEON_MIO_EMM_DMA:
+    case OCTEON_MIO_EMM_CMD:
+    case OCTEON_MIO_EMM_RSP_STS:
+    case OCTEON_MIO_EMM_INT:
+    case OCTEON_MIO_EMM_DMA_FIFO_CFG:
+    case OCTEON_MIO_EMM_DMA_FIFO_ADR:
+    case OCTEON_MIO_EMM_DMA_FIFO_CMD:
+    case OCTEON_MIO_EMM_DMA_CFG:
+    case OCTEON_MIO_EMM_DMA_ADR:
+    case OCTEON_MIO_EMM_INT_EN_OLD:
+    case OCTEON_MIO_EMM_RSP_LO:
+    case OCTEON_MIO_EMM_RSP_HI:
+    case OCTEON_MIO_EMM_WDOG:
+    case OCTEON_MIO_EMM_SAMPLE:
+    case OCTEON_MIO_EMM_STS_MASK:
+    case OCTEON_MIO_EMM_RCA:
+    case OCTEON_MIO_EMM_BUF_IDX:
+    case OCTEON_MIO_EMM_BUF_DAT:
+    case OCTEON_MIO_EMM_DEBUG:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool octeon_mio_emm_decode(hwaddr reg, hwaddr *ereg)
+{
+    if (octeon_mio_emm_valid_reg(reg)) {
+        *ereg = reg;
+        return true;
+    }
+
+    reg -= OCTEON_MIO_EMM_NODE_OFFSET;
+    if (octeon_mio_emm_valid_reg(reg)) {
+        *ereg = reg;
+        return true;
+    }
+
+    return false;
+}
+
+static bool octeon_mio_emm_execute_write(hwaddr addr, unsigned size)
+{
+    return size == OCTEON_CSR_64BIT_SIZE ||
+           (size == OCTEON_CSR_32BIT_SIZE &&
+            (addr & OCTEON_CSR_LO32_OFFSET));
+}
+
+static void octeon_mio_emm_set_low_speed_mode(OcteonState *s,
+                                               unsigned int bus_id)
+{
+    uint64_t period = DIV_ROUND_UP(s->io_hz, OCTEON_MIO_EMM_LOW_SPEED_HZ);
+    uint64_t value;
+
+    period = MIN(period, UINT16_MAX);
+    if (!octeon_csr_lookup(s, OCTEON_MIO_EMM_MODEX(bus_id), &value)) {
+        value = 0;
+    }
+
+    value &= ~(OCTEON_MIO_EMM_MODE_CLK_LO_MASK |
+               OCTEON_MIO_EMM_MODE_CLK_HI_MASK);
+    value |= period | (period << OCTEON_MIO_EMM_MODE_CLK_HI_SHIFT);
+    octeon_csr_store(s, OCTEON_MIO_EMM_MODEX(bus_id), value);
+}
+
+static void octeon_mio_emm_set_bustest(OcteonState *s)
+{
+    const uint8_t *buf = s->emm->mio_emm_buf;
+    bool bus8 = buf[0] == OCTEON_MMC_BUSTEST_8_REQ0 &&
+                buf[1] == OCTEON_MMC_BUSTEST_8_REQ1;
+    bool bus4 = buf[0] == OCTEON_MMC_BUSTEST_4_REQ0 &&
+                buf[4] == OCTEON_MMC_BUSTEST_4_REQ4 &&
+                buf[5] == OCTEON_MMC_BUSTEST_4_REQ5 &&
+                buf[6] == OCTEON_MMC_BUSTEST_4_REQ6;
+    bool bus1 = buf[0] == OCTEON_MMC_BUSTEST_1_REQ0 &&
+                buf[1] == OCTEON_MMC_BUSTEST_1_REQ1 &&
+                buf[2] == OCTEON_MMC_BUSTEST_1_REQ2 &&
+                buf[3] == OCTEON_MMC_BUSTEST_1_REQ3;
+
+    memset(s->emm->mio_emm_buf, 0, sizeof(s->emm->mio_emm_buf));
+    if (bus8) {
+        s->emm->mio_emm_buf[0] = OCTEON_MMC_BUSTEST_8_RESP0;
+        s->emm->mio_emm_buf[1] = OCTEON_MMC_BUSTEST_8_RESP1;
+    } else if (bus4) {
+        s->emm->mio_emm_buf[0] = OCTEON_MMC_BUSTEST_4_RESP0;
+    } else if (bus1) {
+        s->emm->mio_emm_buf[0] = OCTEON_MMC_BUSTEST_1_RESP0;
+    }
+}
+
+static void octeon_mio_emm_complete_dma(OcteonState *s, uint64_t dma)
+{
+    dma &= ~(OCTEON_MIO_EMM_DMA_VAL | OCTEON_MIO_EMM_DMA_BLOCK_CNT_MASK);
+    octeon_csr_store(s, OCTEON_MIO_EMM_DMA, dma);
+    s->emm->mio_emm_rsp_sts &= ~(OCTEON_MIO_EMM_RSP_STS_DMA_VAL |
+                            OCTEON_MIO_EMM_RSP_STS_DMA_PEND);
+    s->emm->mio_emm_dma_int |= OCTEON_MIO_EMM_DMA_INT_DONE;
+    s->emm->mio_emm_int |= OCTEON_MIO_EMM_INT_DMA_DONE;
+}
+
+static void octeon_mio_emm_command(OcteonState *s, uint64_t cmd)
+{
+    unsigned int cmd_idx = (cmd >> OCTEON_MIO_EMM_CMD_IDX_SHIFT) &
+                           OCTEON_MIO_EMM_CMD_IDX_MASK;
+    unsigned int bus_id = (cmd >> OCTEON_MIO_EMM_CMD_BUS_ID_SHIFT) &
+                          OCTEON_MIO_EMM_BUS_ID_MASK;
+
+    s->emm->mio_emm_rsp_lo = 0;
+    s->emm->mio_emm_rsp_hi = 0;
+    s->emm->mio_emm_rsp_sts =
+        OCTEON_MIO_EMM_RSP_STS_CMD_DONE |
+        ((uint64_t)cmd_idx << OCTEON_MIO_EMM_RSP_STS_CMD_IDX_SHIFT) |
+        ((uint64_t)bus_id << OCTEON_MIO_EMM_RSP_STS_BUS_ID_SHIFT);
+
+    switch (cmd_idx) {
+    case OCTEON_MMC_CMD_GO_IDLE_STATE:
+    case OCTEON_MMC_CMD_BUSTEST_W:
+        break;
+    case OCTEON_MMC_CMD_BUSTEST_R:
+        octeon_mio_emm_set_bustest(s);
+        break;
+    default:
+        s->emm->mio_emm_rsp_sts |= OCTEON_MIO_EMM_RSP_STS_RSP_TIMEOUT;
+        break;
+    }
+
+    s->emm->mio_emm_int |= OCTEON_MIO_EMM_INT_CMD_DONE;
+    if (s->emm->mio_emm_rsp_sts & OCTEON_MIO_EMM_RSP_STS_RSP_TIMEOUT) {
+        s->emm->mio_emm_int |= OCTEON_MIO_EMM_INT_CMD_ERR;
+    }
+}
+
+static uint64_t octeon_mio_emm_read(OcteonState *s, hwaddr reg,
+                                    hwaddr addr, unsigned size)
+{
+    uint64_t value;
+
+    switch (reg) {
+    case OCTEON_MIO_EMM_RSP_STS:
+        value = s->emm->mio_emm_rsp_sts;
+        break;
+    case OCTEON_MIO_EMM_RSP_LO:
+        value = s->emm->mio_emm_rsp_lo;
+        break;
+    case OCTEON_MIO_EMM_RSP_HI:
+        value = s->emm->mio_emm_rsp_hi;
+        break;
+    case OCTEON_MIO_EMM_BUF_DAT:
+        value = ldq_be_p(s->emm->mio_emm_buf + s->emm->mio_emm_buf_idx);
+        if (s->emm->mio_emm_buf_inc) {
+            s->emm->mio_emm_buf_idx =
+                (s->emm->mio_emm_buf_idx + OCTEON_MIO_EMM_BUF_WORD_SIZE) %
+                OCTEON_MIO_EMM_BUF_SIZE;
+        }
+        break;
+    case OCTEON_MIO_EMM_INT:
+        value = s->emm->mio_emm_int;
+        break;
+    case OCTEON_MIO_EMM_DMA_INT:
+        value = s->emm->mio_emm_dma_int;
+        break;
+    case OCTEON_MIO_EMM_CMD:
+    case OCTEON_MIO_EMM_SWITCH:
+    case OCTEON_MIO_EMM_DMA:
+        if (!octeon_csr_lookup(s, reg, &value)) {
+            value = 0;
+        }
+        break;
+    case OCTEON_MIO_EMM_MODEX(0):
+    case OCTEON_MIO_EMM_MODEX(1):
+    case OCTEON_MIO_EMM_MODEX(2):
+    case OCTEON_MIO_EMM_MODEX(3):
+        if (!octeon_csr_lookup(s, reg, &value)) {
+            value = 0;
+        }
+        break;
+    default:
+        if (!octeon_csr_lookup(s, reg, &value)) {
+            value = 0;
+        }
+        break;
+    }
+
+    return octeon_read64(value, addr, size);
+}
+
+static void octeon_mio_emm_write(OcteonState *s, hwaddr reg,
+                                 hwaddr addr, uint64_t value, unsigned size)
+{
+    uint64_t old;
+    uint64_t clear;
+
+    switch (reg) {
+    case OCTEON_MIO_EMM_CMD:
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        if (!(value & OCTEON_MIO_EMM_CMD_VAL)) {
+            return;
+        }
+        if (!octeon_mio_emm_execute_write(addr, size)) {
+            return;
+        }
+
+        octeon_csr_store(s, reg, value & ~OCTEON_MIO_EMM_CMD_VAL);
+        octeon_mio_emm_command(s, value);
+        return;
+    case OCTEON_MIO_EMM_SWITCH:
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        if (!(value & OCTEON_MIO_EMM_SWITCH_EXE)) {
+            return;
+        }
+        if (!octeon_mio_emm_execute_write(addr, size)) {
+            return;
+        }
+        octeon_csr_store(s, reg, value & ~OCTEON_MIO_EMM_SWITCH_EXE);
+        octeon_csr_store(s, OCTEON_MIO_EMM_MODEX(
+                         (value >> OCTEON_MIO_EMM_CMD_BUS_ID_SHIFT) &
+                         OCTEON_MIO_EMM_BUS_ID_MASK),
+                         value & OCTEON_MIO_EMM_SWITCH_MODE_MASK);
+        s->emm->mio_emm_int |= OCTEON_MIO_EMM_INT_SWITCH_DONE;
+        return;
+    case OCTEON_MIO_EMM_DMA:
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        if (!(value & OCTEON_MIO_EMM_DMA_VAL)) {
+            return;
+        }
+        if (!octeon_mio_emm_execute_write(addr, size)) {
+            return;
+        }
+        octeon_mio_emm_complete_dma(s, value);
+        return;
+    case OCTEON_MIO_EMM_INT:
+        clear = octeon_write64(0, addr, value, size);
+        s->emm->mio_emm_int &= ~clear;
+        return;
+    case OCTEON_MIO_EMM_DMA_INT:
+        clear = octeon_write64(0, addr, value, size);
+        s->emm->mio_emm_dma_int &= ~clear;
+        return;
+    case OCTEON_MIO_EMM_MODEX(0):
+    case OCTEON_MIO_EMM_MODEX(1):
+    case OCTEON_MIO_EMM_MODEX(2):
+    case OCTEON_MIO_EMM_MODEX(3):
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        return;
+    case OCTEON_MIO_EMM_CFG:
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        if (octeon_mio_emm_execute_write(addr, size)) {
+            uint64_t enabled = (value & ~old) & OCTEON_MIO_EMM_CFG_BUS_EN_MASK;
+            unsigned int i;
+
+            for (i = 0; i < OCTEON_MIO_EMM_MODE_COUNT; i++) {
+                if (enabled & (1U << i)) {
+                    octeon_mio_emm_set_low_speed_mode(s, i);
+                }
+            }
+        }
+        return;
+    case OCTEON_MIO_EMM_BUF_IDX:
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        s->emm->mio_emm_buf_idx = (value & OCTEON_MIO_EMM_BUF_IDX_OFF_MASK) *
+                             OCTEON_MIO_EMM_BUF_WORD_SIZE;
+        s->emm->mio_emm_buf_inc = value & OCTEON_MIO_EMM_BUF_IDX_INC;
+        return;
+    case OCTEON_MIO_EMM_BUF_DAT:
+        old = ldq_be_p(s->emm->mio_emm_buf + s->emm->mio_emm_buf_idx);
+        value = octeon_write64(old, addr, value, size);
+        stq_be_p(s->emm->mio_emm_buf + s->emm->mio_emm_buf_idx, value);
+        if (s->emm->mio_emm_buf_inc) {
+            s->emm->mio_emm_buf_idx =
+                (s->emm->mio_emm_buf_idx + OCTEON_MIO_EMM_BUF_WORD_SIZE) %
+                OCTEON_MIO_EMM_BUF_SIZE;
+        }
+        return;
+    default:
+        if (!octeon_csr_lookup(s, reg, &old)) {
+            old = 0;
+        }
+        value = octeon_write64(old, addr, value, size);
+        octeon_csr_store(s, reg, value);
+        return;
+    }
+}
+
+static bool octeon_lmc_decode(hwaddr reg, OcteonLmcState **lmc, hwaddr *lreg,
+                              OcteonState *s)
+{
+    hwaddr offset;
+    unsigned int index;
+
+    if (reg < OCTEON_LMC_BASE) {
+        return false;
+    }
+
+    offset = reg - OCTEON_LMC_BASE;
+    index = offset / OCTEON_LMC_STRIDE;
+    if (index >= OCTEON_LMC_COUNT) {
+        return false;
+    }
+
+    *lmc = s->lmc[index];
+    *lreg = offset % OCTEON_LMC_STRIDE;
+    return true;
+}
+
+static uint64_t octeon_lmc_get(OcteonLmcState *lmc, hwaddr reg)
+{
+    uint64_t value;
+
+    if (!octeon_reg_lookup(lmc->regs, reg, &value)) {
+        return 0;
+    }
+    return value;
+}
+
+static bool octeon_lmc_rank_reg(hwaddr reg, hwaddr base)
+{
+    return reg >= base &&
+           reg < base + OCTEON_LMC_RANK_COUNT * 8 &&
+           ((reg - base) & 7) == 0;
+}
+
+static uint64_t octeon_lmc_read(OcteonState *s, OcteonLmcState *lmc,
+                                hwaddr reg, hwaddr addr, unsigned size)
+{
+    uint64_t value;
+
+    switch (reg) {
+    case OCTEON_LMC_SEQ_CTL:
+        value = octeon_lmc_get(lmc, reg) | OCTEON_LMC_SEQ_COMPLETE;
+        break;
+    case OCTEON_LMC_MPR_DATA0:
+        value = ~0ULL;
+        break;
+    case OCTEON_LMC_MPR_DATA1:
+        value = 0xff;
+        break;
+    case OCTEON_LMC_DCLK_CNT:
+        value = octeon_clock_count(s->ddr_hz);
+        break;
+    case OCTEON_LMC_PHY_CTL:
+        value = octeon_lmc_get(lmc, reg) | OCTEON_LMC_PHY_DSK_COMPLETE;
+        break;
+    case OCTEON_LMC_RLEVEL_DBG:
+        value = OCTEON_LMC_RLEVEL_MASK;
+        break;
+    case OCTEON_LMC_WLEVEL_DBG:
+        value = OCTEON_LMC_WLEVEL_MASK;
+        break;
+    default:
+        if (octeon_lmc_rank_reg(reg, OCTEON_LMC_RLEVEL_RANK)) {
+            value = octeon_lmc_get(lmc, reg) | OCTEON_LMC_RLEVEL_STATUS;
+        } else if (octeon_lmc_rank_reg(reg, OCTEON_LMC_WLEVEL_RANK)) {
+            value = octeon_lmc_get(lmc, reg) | OCTEON_LMC_WLEVEL_STATUS;
+        } else {
+            value = octeon_lmc_get(lmc, reg);
+        }
+        break;
+    }
+
+    return octeon_read64(value, addr, size);
+}
+
+static void octeon_lmc_write(OcteonLmcState *lmc, hwaddr reg,
+                             hwaddr addr, uint64_t value, unsigned size)
+{
+    uint64_t old = octeon_lmc_get(lmc, reg);
+
+    value = octeon_write64(old, addr, value, size);
+    octeon_reg_store(lmc->regs, reg, value);
+}
+
+static bool octeon_ciu3_source_from_intsn(uint32_t intsn,
+                                          unsigned int *source)
+{
+    if (intsn == OCTEON_CIU3_PEM_INTSN_INTA(0)) {
+        *source = OCTEON_CIU3_SRC_PCI_INTA;
+        return true;
+    }
+    if (intsn == OCTEON_CIU3_UART0_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 +
+                  intsn - OCTEON_CIU3_MBOX_INTSN(0);
+        return true;
+    }
+
+    return false;
+}
+
+static uint32_t octeon_ciu3_source_intsn(unsigned int source)
+{
+    switch (source) {
+    case OCTEON_CIU3_SRC_PCI_INTA:
+        return OCTEON_CIU3_PEM_INTSN_INTA(0);
+    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 &&
+        source < OCTEON_CIU3_SRC_COUNT) {
+        return OCTEON_CIU3_MBOX_INTSN(source - OCTEON_CIU3_SRC_MBOX0);
+    }
+
+    g_assert_not_reached();
+}
+
+static bool octeon_ciu3_source_is_level(unsigned int source)
+{
+    switch (source) {
+    case OCTEON_CIU3_SRC_PCI_INTA:
+    case OCTEON_CIU3_SRC_UART0:
+    case OCTEON_CIU3_SRC_USB0:
+    case OCTEON_CIU3_SRC_USB1:
+        return true;
+    }
+
+    return false;
+}
+
+static bool octeon_ciu3_set_level(OcteonState *s, unsigned int source,
+                                  int level)
+{
+    uint32_t old;
+    uint32_t new;
+    uint32_t cmp = qatomic_read(&s->intc->ciu3_src_state[source]);
+
+    do {
+        old = cmp;
+        if (level) {
+            new = old | OCTEON_CIU3_SRC_LEVEL | OCTEON_CIU3_SRC_RAW;
+        } else {
+            new = old & ~(OCTEON_CIU3_SRC_LEVEL | OCTEON_CIU3_SRC_RAW);
+        }
+        if (new == old) {
+            return false;
+        }
+        cmp = qatomic_cmpxchg(&s->intc->ciu3_src_state[source], old, new);
+    } while (old != cmp);
+
+    return true;
+}
+
+static void octeon_ciu3_clear_raw(OcteonState *s, unsigned int source)
+{
+    uint32_t old;
+    uint32_t new;
+    uint32_t cmp = qatomic_read(&s->intc->ciu3_src_state[source]);
+
+    do {
+        old = cmp;
+        if (octeon_ciu3_source_is_level(source) &&
+            (old & OCTEON_CIU3_SRC_LEVEL)) {
+            new = old | OCTEON_CIU3_SRC_RAW;
+        } else {
+            new = old & ~OCTEON_CIU3_SRC_RAW;
+        }
+        if (new == old) {
+            return;
+        }
+        cmp = qatomic_cmpxchg(&s->intc->ciu3_src_state[source], old, new);
+    } while (old != cmp);
+}
+
+static bool octeon_ciu3_set_mbox(OcteonState *s, unsigned int cpu)
+{
+    return octeon_ciu3_set_level(s, OCTEON_CIU3_SRC_MBOX0 + cpu,
+                                 qatomic_read(&s->intc->ciu_mbox[cpu]) != 0);
+}
+
+static bool octeon_ciu3_isc_decode(hwaddr reg, uint32_t base,
+                                   uint32_t *intsn)
+{
+    if (reg < base || reg >= base + OCTEON_CIU3_ISC_SIZE) {
+        return false;
+    }
+
+    *intsn = (reg - base) >> 3;
+    return true;
+}
+
+static uint64_t octeon_ciu3_isc_read(OcteonState *s, uint32_t intsn)
+{
+    unsigned int src;
+    uint64_t value;
+
+    if (!octeon_ciu3_source_from_intsn(intsn, &src)) {
+        return 0;
+    }
+
+    value = qatomic_read(&s->intc->ciu3_src_ctl[src]) | OCTEON_CIU3_ISC_CTL_IMP;
+    if (qatomic_read(&s->intc->ciu3_src_state[src]) & OCTEON_CIU3_SRC_RAW) {
+        value |= OCTEON_CIU3_ISC_CTL_RAW;
+    }
+    return value;
+}
+
+static bool octeon_ciu3_source_pending(OcteonState *s, unsigned int cpu_index,
+                                       unsigned int irq, unsigned int src)
+{
+    uint64_t ctl = qatomic_read(&s->intc->ciu3_src_ctl[src]);
+    unsigned int idt;
+    unsigned int output;
+
+    if (!(qatomic_read(&s->intc->ciu3_src_state[src]) & OCTEON_CIU3_SRC_RAW) ||
+        !(ctl & OCTEON_CIU3_ISC_CTL_EN)) {
+        return false;
+    }
+
+    idt = (ctl & OCTEON_CIU3_ISC_CTL_IDT) >>
+          OCTEON_CIU3_ISC_CTL_IDT_SHIFT;
+    if (idt >= OCTEON_CIU3_IDT_COUNT) {
+        return false;
+    }
+
+    output = qatomic_read(&s->intc->ciu3_idt_ctl[idt]) &
+             OCTEON_CIU3_IDT_CTL_IP_NUM;
+    if (output >= OCTEON_CIU3_CP0_IRQ_COUNT ||
+        irq != OCTEON_CIU3_CP0_IRQ_BASE + output) {
+        return false;
+    }
+
+    return qatomic_read(&s->intc->ciu3_idt_pp[idt]) & (1ULL << cpu_index);
+}
+
+static bool octeon_ciu3_pending_intsn(OcteonState *s, unsigned int cpu_index,
+                                      unsigned int irq, uint32_t *intsn)
+{
+    unsigned int src;
+
+    for (src = 0; src < OCTEON_CIU3_SRC_COUNT; src++) {
+        if (octeon_ciu3_source_pending(s, cpu_index, irq, src)) {
+            *intsn = octeon_ciu3_source_intsn(src);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool octeon_ciu3_pending_intsn_from(OcteonState *s,
+                                           unsigned int cpu_index,
+                                           unsigned int irq,
+                                           unsigned int first_src,
+                                           uint32_t *intsn,
+                                           unsigned int *source)
+{
+    unsigned int i;
+    unsigned int src;
+
+    for (i = 0; i < OCTEON_CIU3_SRC_COUNT; i++) {
+        src = (first_src + i) % OCTEON_CIU3_SRC_COUNT;
+        if (octeon_ciu3_source_pending(s, cpu_index, irq, src)) {
+            *intsn = octeon_ciu3_source_intsn(src);
+            *source = src;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool octeon_ciu3_pending_dest_intsn(OcteonState *s,
+                                           unsigned int cpu_index,
+                                           uint32_t *intsn)
+{
+    unsigned int irq;
+    unsigned int irq_index;
+    unsigned int src;
+
+    for (irq = OCTEON_CIU3_CP0_IRQ_BASE;
+         irq < OCTEON_CIU3_CP0_IRQ_BASE + OCTEON_CIU3_CP0_IRQ_COUNT;
+         irq++) {
+        irq_index = irq - OCTEON_CIU3_CP0_IRQ_BASE;
+        if (octeon_ciu3_pending_intsn_from(s, cpu_index, irq,
+                qatomic_read(&s->intc->ciu3_src_cursor[cpu_index][irq_index]),
+                intsn, &src)) {
+            qatomic_set(&s->intc->ciu3_src_cursor[cpu_index][irq_index],
+                        (src + 1) % OCTEON_CIU3_SRC_COUNT);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static void octeon_ciu3_set_cpu_irq(OcteonState *s, unsigned int cpu_index,
+                                    unsigned int irq, bool level)
+{
+    CPUMIPSState *env = &s->cpu[cpu_index].cpu->env;
+    unsigned int irq_index = irq - OCTEON_CIU3_CP0_IRQ_BASE;
+    uint8_t new_level = level;
+    uint8_t old_level;
+
+    old_level = qatomic_xchg(&s->intc->ciu3_irq_line[cpu_index][irq_index],
+                             new_level);
+    if (old_level == new_level) {
+        return;
+    }
+
+    qemu_set_irq(env->irq[irq], level);
+}
+
+static void octeon_intc_update_cpu(OcteonState *s, unsigned int cpu_index)
+{
+    uint32_t intsn;
+    unsigned int irq;
+    bool level;
+
+    if (cpu_index >= s->cpu_count || !s->cpu[cpu_index].cpu) {
+        return;
+    }
+
+    if (qatomic_read(&s->intc->ciu3_pp_rst) & (1ULL << cpu_index)) {
+        for (irq = OCTEON_CIU3_CP0_IRQ_BASE;
+             irq < OCTEON_CIU3_CP0_IRQ_BASE + OCTEON_CIU3_CP0_IRQ_COUNT;
+             irq++) {
+            octeon_ciu3_set_cpu_irq(s, cpu_index, irq, false);
+        }
+        return;
+    }
+
+    for (irq = OCTEON_CIU3_CP0_IRQ_BASE;
+         irq < OCTEON_CIU3_CP0_IRQ_BASE + OCTEON_CIU3_CP0_IRQ_COUNT;
+         irq++) {
+        level = false;
+        if (irq == 3) {
+            level = qatomic_read(&s->intc->ciu_mbox[cpu_index]) &&
+                    (qatomic_read(&s->intc->ciu_ipi_en[cpu_index]) &
+                     OCTEON_CIU_SUM_IPI);
+        }
+
+        if (octeon_ciu3_pending_intsn(s, cpu_index, irq, &intsn)) {
+            level = true;
+        }
+
+        octeon_ciu3_set_cpu_irq(s, cpu_index, irq, level);
+    }
+}
+
+static void octeon_intc_request_cpu_update(OcteonState *s,
+                                           unsigned int cpu_index)
+{
+    if (cpu_index >= s->cpu_count || !s->cpu[cpu_index].cpu) {
+        return;
+    }
+
+    octeon_intc_update_cpu(s, cpu_index);
+}
+
+static void octeon_intc_update_cpu_mask(OcteonState *s, uint64_t cpu_mask)
+{
+    unsigned int cpu;
+
+    for (cpu = 0; cpu < s->cpu_count; cpu++) {
+        if (cpu_mask & (1ULL << cpu)) {
+            octeon_intc_request_cpu_update(s, cpu);
+        }
+    }
+}
+
+static void octeon_intc_update_all_cpus(OcteonState *s)
+{
+    unsigned int cpu;
+
+    for (cpu = 0; cpu < s->cpu_count; cpu++) {
+        octeon_intc_request_cpu_update(s, cpu);
+    }
+}
+
+static uint64_t octeon_present_cpu_mask(OcteonState *s)
+{
+    return (1ULL << s->cpu_count) - 1;
+}
+
+static uint64_t octeon_secondary_cpu_mask(OcteonState *s)
+{
+    return octeon_present_cpu_mask(s) & ~1ULL;
+}
+
+static uint64_t octeon_ciu3_ctl_cpu_mask(OcteonState *s, uint64_t ctl)
+{
+    unsigned int idt = (ctl & OCTEON_CIU3_ISC_CTL_IDT) >>
+                       OCTEON_CIU3_ISC_CTL_IDT_SHIFT;
+
+    if (idt >= OCTEON_CIU3_IDT_COUNT) {
+        return 0;
+    }
+
+    return qatomic_read(&s->intc->ciu3_idt_pp[idt]);
+}
+
+static uint64_t octeon_ciu3_source_cpu_mask(OcteonState *s, unsigned int src)
+{
+    return octeon_ciu3_ctl_cpu_mask(
+        s, qatomic_read(&s->intc->ciu3_src_ctl[src]));
+}
+
+static uint64_t octeon_ciu_gpio_rx_value(OcteonState *s)
+{
+    uint64_t value = 0;
+    unsigned int i;
+
+    for (i = 0; i < OCTEON_CIU_GPIO_COUNT; i++) {
+        uint64_t mask = 1ULL << i;
+
+        if (qatomic_read(&s->intc->ciu_gpio_bit_cfg[i]) &
+            OCTEON_CIU_GPIO_BIT_CFG_TX_OE) {
+            if (qatomic_read(&s->intc->ciu_gpio_tx) & mask) {
+                value |= mask;
+            }
+        } else if (qatomic_read(&s->intc->ciu_gpio_rx) & mask) {
+            value |= mask;
+        }
+    }
+
+    return value;
+}
+
+static uint64_t octeon_ciu_read(void *opaque, hwaddr addr, unsigned size)
+{
+    OcteonState *s = opaque;
+    uint64_t value = 0;
+    hwaddr reg = addr & ~7ULL;
+    unsigned int cpu;
+
+    if (reg == 0) {
+        if (qatomic_read(&s->intc->uart_pending)) {
+            value |= OCTEON_CIU_SUM_UART;
+        }
+        if (qatomic_read(&s->intc->pci_pending)) {
+            value |= OCTEON_CIU_SUM_PCI;
+        }
+        octeon_intc_request_cpu_update(s, 0);
+        return octeon_read64(value, addr, size);
+    }
+
+    if (reg == OCTEON_CIU_IPI_EN_BASE) {
+        return octeon_read64(OCTEON_CIU_ENABLE0, addr, size);
+    }
+
+    if (reg == OCTEON_CIU_FUSE) {
+        value = octeon_present_cpu_mask(s);
+        return octeon_read64(value, addr, size);
+    }
+
+    if (reg == OCTEON_CIU_GPIO_RX_DAT) {
+        return octeon_read64(octeon_ciu_gpio_rx_value(s), addr, size);
+    }
+
+    if (reg == OCTEON_CIU_GPIO_TX_SET ||
+        reg == OCTEON_CIU_GPIO_TX_CLR) {
+        return octeon_read64(qatomic_read(&s->intc->ciu_gpio_tx), addr, size);
+    }
+
+    if (reg >= OCTEON_CIU_GPIO_BIT_CFGX(0) &&
+        reg < OCTEON_CIU_GPIO_BIT_CFGX(OCTEON_CIU_GPIO_COUNT)) {
+        unsigned int index = (reg - OCTEON_CIU_GPIO_BIT_CFGX(0)) /
+                             OCTEON_CIU_GPIO_BIT_CFG_STRIDE;
+
+        return octeon_read64(qatomic_read(&s->intc->ciu_gpio_bit_cfg[index]),
+                             addr, size);
+    }
+
+    if (reg >= OCTEON_CIU_IPI_SUM_BASE &&
+        reg < OCTEON_CIU_IPI_SUM_BASE +
+              OCTEON_MAX_CPUS * OCTEON_CIU_IPI_SUM_STRIDE &&
+        ((reg - OCTEON_CIU_IPI_SUM_BASE) &
+         (OCTEON_CIU_IPI_SUM_STRIDE - 1)) == 0) {
+        cpu = (reg - OCTEON_CIU_IPI_SUM_BASE) / OCTEON_CIU_IPI_SUM_STRIDE;
+        if (cpu < s->cpu_count && qatomic_read(&s->intc->ciu_mbox[cpu])) {
+            return octeon_read64(OCTEON_CIU_SUM_IPI, addr, size);
+        }
+    }
+
+    if (reg >= OCTEON_CIU_IPI_EN_BASE &&
+        reg < OCTEON_CIU_IPI_EN_BASE +
+              OCTEON_MAX_CPUS * OCTEON_CIU_IPI_EN_STRIDE &&
+        ((reg - OCTEON_CIU_IPI_EN_BASE) &
+         (OCTEON_CIU_IPI_EN_STRIDE - 1)) == 0) {
+        cpu = (reg - OCTEON_CIU_IPI_EN_BASE) / OCTEON_CIU_IPI_EN_STRIDE;
+        if (cpu < s->cpu_count) {
+            return octeon_read64(qatomic_read(&s->intc->ciu_ipi_en[cpu]),
+                                 addr, size);
+        }
+    }
+
+    if (reg >= OCTEON_CIU_MBOX_CLR_BASE &&
+        reg < OCTEON_CIU_MBOX_CLR_BASE +
+              OCTEON_MAX_CPUS * OCTEON_CIU_MBOX_SET_STRIDE &&
+        ((reg - OCTEON_CIU_MBOX_CLR_BASE) &
+         (OCTEON_CIU_MBOX_SET_STRIDE - 1)) == 0) {
+        cpu = (reg - OCTEON_CIU_MBOX_CLR_BASE) / OCTEON_CIU_MBOX_SET_STRIDE;
+        if (cpu < s->cpu_count) {
+            return octeon_read64(qatomic_read(&s->intc->ciu_mbox[cpu]),
+                                 addr, size);
+        }
+    }
+
+    return 0;
+}
+
+static void octeon_ciu_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+    OcteonState *s = opaque;
+    hwaddr reg = addr & ~7ULL;
+    uint64_t old;
+    unsigned int cpu;
+
+    if (reg >= OCTEON_CIU_IPI_EN_BASE &&
+        reg < OCTEON_CIU_IPI_EN_BASE +
+              OCTEON_MAX_CPUS * OCTEON_CIU_IPI_EN_STRIDE &&
+        ((reg - OCTEON_CIU_IPI_EN_BASE) &
+         (OCTEON_CIU_IPI_EN_STRIDE - 1)) == 0) {
+        cpu = (reg - OCTEON_CIU_IPI_EN_BASE) / OCTEON_CIU_IPI_EN_STRIDE;
+        if (cpu < s->cpu_count) {
+            value = octeon_atomic_write64(&s->intc->ciu_ipi_en[cpu], addr,
+                                          value, size, UINT64_MAX, 0, &old);
+            if (old != value) {
+                octeon_intc_request_cpu_update(s, cpu);
+            }
+        }
+        return;
+    }
+
+    if (reg >= OCTEON_CIU_MBOX_SET_BASE &&
+        reg < OCTEON_CIU_MBOX_SET_BASE +
+              OCTEON_MAX_CPUS * OCTEON_CIU_MBOX_SET_STRIDE &&
+        ((reg - OCTEON_CIU_MBOX_SET_BASE) &
+         (OCTEON_CIU_MBOX_SET_STRIDE - 1)) == 0) {
+        cpu = (reg - OCTEON_CIU_MBOX_SET_BASE) / OCTEON_CIU_MBOX_SET_STRIDE;
+        if (cpu < s->cpu_count) {
+            old = qatomic_fetch_or(&s->intc->ciu_mbox[cpu], value & UINT32_MAX);
+            if ((old | (value & UINT32_MAX)) != old &&
+                octeon_ciu3_set_mbox(s, cpu)) {
+                octeon_intc_request_cpu_update(s, cpu);
+            }
+        }
+        return;
+    }
+
+    if (reg >= OCTEON_CIU_MBOX_CLR_BASE &&
+        reg < OCTEON_CIU_MBOX_CLR_BASE +
+              OCTEON_MAX_CPUS * OCTEON_CIU_MBOX_SET_STRIDE &&
+        ((reg - OCTEON_CIU_MBOX_CLR_BASE) &
+         (OCTEON_CIU_MBOX_SET_STRIDE - 1)) == 0) {
+        cpu = (reg - OCTEON_CIU_MBOX_CLR_BASE) / OCTEON_CIU_MBOX_SET_STRIDE;
+        if (cpu < s->cpu_count) {
+            old = qatomic_fetch_and(&s->intc->ciu_mbox[cpu],
+                                    ~(value & UINT32_MAX));
+            if ((old & (value & UINT32_MAX)) &&
+                octeon_ciu3_set_mbox(s, cpu)) {
+                octeon_intc_request_cpu_update(s, cpu);
+            }
+        }
+        return;
+    }
+
+    if (reg == OCTEON_CIU_GPIO_TX_SET) {
+        value = octeon_write64(0, addr, value, size) &
+                OCTEON_CIU_GPIO_INPUTS;
+        qatomic_or(&s->intc->ciu_gpio_tx, value);
+        return;
+    }
+
+    if (reg == OCTEON_CIU_GPIO_TX_CLR) {
+        value = octeon_write64(0, addr, value, size) &
+                OCTEON_CIU_GPIO_INPUTS;
+        qatomic_and(&s->intc->ciu_gpio_tx, ~value);
+        return;
+    }
+
+    if (reg >= OCTEON_CIU_GPIO_BIT_CFGX(0) &&
+        reg < OCTEON_CIU_GPIO_BIT_CFGX(OCTEON_CIU_GPIO_COUNT)) {
+        unsigned int index = (reg - OCTEON_CIU_GPIO_BIT_CFGX(0)) /
+                             OCTEON_CIU_GPIO_BIT_CFG_STRIDE;
+
+        octeon_atomic_write64(&s->intc->ciu_gpio_bit_cfg[index], addr, value,
+                              size, UINT64_MAX, 0, NULL);
+        return;
+    }
+}
+
+static void octeon_cpu_park_now(MIPSCPU *cpu)
+{
+    CPUMIPSState *env = &cpu->env;
+    CPUState *cs = CPU(cpu);
+
+    env->active_tc.CP0_TCHalt = 1;
+    env->tcs[0].CP0_TCHalt = 1;
+    cs->halted = 1;
+    cpu_reset_interrupt(cs, CPU_INTERRUPT_WAKE);
+}
+
+static void octeon_cpu_park_work(CPUState *cs, run_on_cpu_data data)
+{
+    octeon_cpu_park_now(MIPS_CPU(cs));
+}
+
+static void octeon_cpu_start_work(CPUState *cs, run_on_cpu_data data)
+{
+    MIPSCPU *cpu = MIPS_CPU(cs);
+    CPUMIPSState *env = &cpu->env;
+
+    cpu->mvp->CP0_MVPControl |= (1 << CP0MVPCo_EVP);
+    env->CP0_VPEConf0 |= (1 << CP0VPEC0_MVP) | (1 << CP0VPEC0_VPA);
+    env->active_tc.CP0_TCHalt = 0;
+    env->tcs[0].CP0_TCHalt = 0;
+    env->active_tc.CP0_TCStatus = (1 << CP0TCSt_A);
+    env->tcs[0].CP0_TCStatus = (1 << CP0TCSt_A);
+    env->active_tc.PC = cpu_mips_phys_to_kseg1(NULL, OCTEON_FLASH_RESET_BASE);
+    cs->halted = 0;
+    cpu_interrupt(cs, CPU_INTERRUPT_WAKE | CPU_INTERRUPT_EXITTB);
+}
+
+static void octeon_start_cpu(OcteonState *s, unsigned int cpu_index)
+{
+    MIPSCPU *cpu;
+
+    if (cpu_index >= s->cpu_count || !s->cpu[cpu_index].cpu) {
+        return;
+    }
+
+    cpu = s->cpu[cpu_index].cpu;
+    async_run_on_cpu(CPU(cpu), octeon_cpu_start_work, RUN_ON_CPU_NULL);
+}
+
+static void octeon_ciu3_start_cpu(OcteonState *s, unsigned int cpu_index)
+{
+    if (qatomic_read(&s->intc->ciu3_pp_rst) & (1ULL << cpu_index)) {
+        return;
+    }
+
+    octeon_start_cpu(s, cpu_index);
+}
+
+static void octeon_ciu3_nmi(OcteonState *s, uint64_t mask)
+{
+    unsigned int cpu;
+
+    for (cpu = 1; cpu < s->cpu_count; cpu++) {
+        if (mask & (1ULL << cpu)) {
+            octeon_ciu3_start_cpu(s, cpu);
+        }
+    }
+}
+
+static void octeon_rst_pp_power_write(OcteonState *s, hwaddr addr,
+                                      uint64_t value, unsigned size)
+{
+    uint64_t old = s->rst->rst_pp_power;
+    uint64_t changed;
+    unsigned int cpu;
+
+    value = octeon_write64(old, addr, value, size) &
+            octeon_present_cpu_mask(s);
+    s->rst->rst_pp_power = value;
+    changed = old ^ value;
+
+    for (cpu = 1; cpu < s->cpu_count; cpu++) {
+        uint64_t mask = 1ULL << cpu;
+
+        if (!(changed & mask)) {
+            continue;
+        }
+        if (value & mask) {
+            async_run_on_cpu(CPU(s->cpu[cpu].cpu), octeon_cpu_park_work,
+                             RUN_ON_CPU_NULL);
+        } else {
+            octeon_start_cpu(s, cpu);
+        }
+    }
+}
+
+static const MemoryRegionOps octeon_ciu_ops = {
+    .read = octeon_ciu_read,
+    .write = octeon_ciu_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_ciu3_read(void *opaque, hwaddr addr, unsigned size)
+{
+    OcteonState *s = opaque;
+    hwaddr reg = addr & ~7ULL;
+    uint64_t value;
+    uint32_t intsn;
+    unsigned int cpu;
+    unsigned int idt;
+
+    if (reg == OCTEON_CIU3_PP_RST) {
+        return octeon_read64(qatomic_read(&s->intc->ciu3_pp_rst), addr, size);
+    }
+
+    if (reg == OCTEON_CIU3_PP_RST_PENDING) {
+        return 0;
+    }
+
+    if (reg == OCTEON_CIU3_NMI) {
+        return 0;
+    }
+
+    if (reg == OCTEON_CIU3_FUSE) {
+        value = octeon_present_cpu_mask(s);
+        return octeon_read64(value, addr, size);
+    }
+
+    if (reg >= OCTEON_CIU3_IDT_CTL(0) &&
+        reg < OCTEON_CIU3_IDT_CTL(OCTEON_CIU3_IDT_COUNT)) {
+        idt = (reg - OCTEON_CIU3_IDT_CTL(0)) >> 3;
+        return octeon_read64(qatomic_read(&s->intc->ciu3_idt_ctl[idt]),
+                             addr, size);
+    }
+
+    if (reg >= OCTEON_CIU3_IDT_PP(0) &&
+        reg < OCTEON_CIU3_IDT_PP(OCTEON_CIU3_IDT_COUNT)) {
+        idt = (reg - OCTEON_CIU3_IDT_PP(0)) >> 5;
+        return octeon_read64(qatomic_read(&s->intc->ciu3_idt_pp[idt]),
+                             addr, size);
+    }
+
+    if (reg >= OCTEON_CIU3_IDT_IO(0) &&
+        reg < OCTEON_CIU3_IDT_IO(OCTEON_CIU3_IDT_COUNT)) {
+        idt = (reg - OCTEON_CIU3_IDT_IO(0)) >> 3;
+        return octeon_read64(qatomic_read(&s->intc->ciu3_idt_io[idt]),
+                             addr, size);
+    }
+
+    if (reg >= OCTEON_CIU3_DEST_PP_INT(0) &&
+        reg < OCTEON_CIU3_DEST_PP_INT(OCTEON_MAX_CPUS)) {
+        cpu = (reg - OCTEON_CIU3_DEST_PP_INT(0)) >> 3;
+        value = 0;
+        if (cpu < s->cpu_count) {
+            if (!(qatomic_read(&s->intc->ciu3_pp_rst) & (1ULL << cpu)) &&
+                octeon_ciu3_pending_dest_intsn(s, cpu, &intsn)) {
+                value = OCTEON_CIU3_DEST_PP_INT_INTR |
+                        ((uint64_t)intsn <<
+                         OCTEON_CIU3_DEST_PP_INT_INTSN_SHIFT);
+            }
+        }
+        return octeon_read64(value, addr, size);
+    }
+
+    if (octeon_ciu3_isc_decode(reg, OCTEON_CIU3_ISC_CTL_BASE, &intsn)) {
+        return octeon_read64(octeon_ciu3_isc_read(s, intsn), addr, size);
+    }
+
+    if (octeon_ciu3_isc_decode(reg, OCTEON_CIU3_ISC_W1C_BASE, &intsn) ||
+        octeon_ciu3_isc_decode(reg, OCTEON_CIU3_ISC_W1S_BASE, &intsn)) {
+        return octeon_read64(octeon_ciu3_isc_read(s, intsn), addr, size);
+    }
+
+    return 0;
+}
+
+static void octeon_ciu3_write(void *opaque, hwaddr addr,
+                              uint64_t value, unsigned size)
+{
+    OcteonState *s = opaque;
+    hwaddr reg = addr & ~7ULL;
+    uint64_t old;
+    uint32_t intsn;
+    unsigned int cpu;
+    unsigned int idt;
+    unsigned int src;
+
+    if (reg == OCTEON_CIU3_PP_RST) {
+        value = octeon_atomic_write64(&s->intc->ciu3_pp_rst, addr, value, size,
+                                      octeon_present_cpu_mask(s), 0, &old);
+
+        for (cpu = 1; cpu < s->cpu_count; cpu++) {
+            uint64_t mask = 1ULL << cpu;
+
+            if (!((old ^ value) & mask)) {
+                continue;
+            }
+            if (value & mask) {
+                async_run_on_cpu(CPU(s->cpu[cpu].cpu), octeon_cpu_park_work,
+                                 RUN_ON_CPU_NULL);
+            } else {
+                octeon_ciu3_start_cpu(s, cpu);
+            }
+        }
+        octeon_intc_update_all_cpus(s);
+        return;
+    }
+
+    if (reg == OCTEON_CIU3_NMI) {
+        octeon_ciu3_nmi(s, octeon_write64(0, addr, value, size));
+        octeon_intc_update_all_cpus(s);
+        return;
+    }
+
+    if (reg >= OCTEON_CIU3_IDT_CTL(0) &&
+        reg < OCTEON_CIU3_IDT_CTL(OCTEON_CIU3_IDT_COUNT)) {
+        idt = (reg - OCTEON_CIU3_IDT_CTL(0)) >> 3;
+        octeon_atomic_write64(&s->intc->ciu3_idt_ctl[idt], addr, value, size,
+                              UINT64_MAX, 0, NULL);
+        octeon_intc_update_cpu_mask(
+            s, qatomic_read(&s->intc->ciu3_idt_pp[idt]));
+        return;
+    }
+
+    if (reg >= OCTEON_CIU3_IDT_PP(0) &&
+        reg < OCTEON_CIU3_IDT_PP(OCTEON_CIU3_IDT_COUNT)) {
+        idt = (reg - OCTEON_CIU3_IDT_PP(0)) >> 5;
+        value = octeon_atomic_write64(&s->intc->ciu3_idt_pp[idt], addr, value,
+                                      size, UINT64_MAX, 0, &old);
+        octeon_intc_update_cpu_mask(s, old | value);
+        return;
+    }
+
+    if (reg >= OCTEON_CIU3_IDT_IO(0) &&
+        reg < OCTEON_CIU3_IDT_IO(OCTEON_CIU3_IDT_COUNT)) {
+        idt = (reg - OCTEON_CIU3_IDT_IO(0)) >> 3;
+        octeon_atomic_write64(&s->intc->ciu3_idt_io[idt], addr, value, size,
+                              UINT64_MAX, 0, NULL);
+        octeon_intc_update_cpu_mask(
+            s, qatomic_read(&s->intc->ciu3_idt_pp[idt]));
+        return;
+    }
+
+    if (octeon_ciu3_isc_decode(reg, OCTEON_CIU3_ISC_CTL_BASE, &intsn)) {
+        if (octeon_ciu3_source_from_intsn(intsn, &src)) {
+            value = octeon_atomic_write64(&s->intc->ciu3_src_ctl[src], addr,
+                                          value, size,
+                                          ~OCTEON_CIU3_ISC_CTL_RAW,
+                                          0, &old);
+            octeon_intc_update_cpu_mask(s,
+                                        octeon_ciu3_ctl_cpu_mask(s, old) |
+                                        octeon_ciu3_ctl_cpu_mask(s, value));
+        }
+        return;
+    }
+
+    if (octeon_ciu3_isc_decode(reg, OCTEON_CIU3_ISC_W1C_BASE, &intsn)) {
+        if (octeon_ciu3_source_from_intsn(intsn, &src)) {
+            old = qatomic_read(&s->intc->ciu3_src_ctl[src]);
+            if (value & OCTEON_CIU3_ISC_CTL_EN) {
+                qatomic_and(&s->intc->ciu3_src_ctl[src],
+                            ~OCTEON_CIU3_ISC_CTL_EN);
+            }
+            if (value & OCTEON_CIU3_ISC_CTL_RAW) {
+                octeon_ciu3_clear_raw(s, src);
+            }
+            octeon_intc_update_cpu_mask(s,
+                                        octeon_ciu3_ctl_cpu_mask(s, old) |
+                                        octeon_ciu3_source_cpu_mask(s, src));
+        }
+        return;
+    }
+
+    if (octeon_ciu3_isc_decode(reg, OCTEON_CIU3_ISC_W1S_BASE, &intsn)) {
+        if (octeon_ciu3_source_from_intsn(intsn, &src)) {
+            old = qatomic_read(&s->intc->ciu3_src_ctl[src]);
+            if (value & OCTEON_CIU3_ISC_CTL_EN) {
+                qatomic_or(&s->intc->ciu3_src_ctl[src], OCTEON_CIU3_ISC_CTL_EN);
+            }
+            if (value & OCTEON_CIU3_ISC_CTL_RAW) {
+                qatomic_or(&s->intc->ciu3_src_state[src], OCTEON_CIU3_SRC_RAW);
+            }
+            octeon_intc_update_cpu_mask(s,
+                                        octeon_ciu3_ctl_cpu_mask(s, old) |
+                                        octeon_ciu3_source_cpu_mask(s, src));
+        }
+    }
+}
+
+static const MemoryRegionOps octeon_ciu3_ops = {
+    .read = octeon_ciu3_read,
+    .write = octeon_ciu3_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_csr_read(void *opaque, hwaddr addr, unsigned size)
+{
+    OcteonState *s = opaque;
+    OcteonLmcState *lmc;
+    hwaddr reg = addr & ~7ULL;
+    hwaddr lreg;
+    hwaddr ereg;
+    uint64_t value;
+
+    if (reg >= OCTEON_MIO_BOOT_REG_CFGX(0) &&
+        reg < OCTEON_MIO_BOOT_REG_CFGX(OCTEON_MIO_BOOT_REG_CFG_COUNT)) {
+        unsigned int index = OCTEON_MIO_BOOT_REG_CFG_INDEX(reg);
+
+        return octeon_read64(s->mio->mio_boot_reg_cfg[index], addr, size);
+    }
+
+    switch (reg) {
+    case OCTEON_MIO_BOOT_LOC_CFGX(0):
+    case OCTEON_MIO_BOOT_LOC_CFGX(1): {
+        unsigned int index = OCTEON_MIO_BOOT_LOC_CFG_INDEX(reg);
+
+        return octeon_read64(s->mio->mio_boot_loc_cfg[index], addr, size);
+    }
+    case OCTEON_MIO_BOOT_LOC_ADR:
+        return octeon_read64(s->mio->mio_boot_loc_adr, addr, size);
+    case OCTEON_MIO_BOOT_LOC_DAT:
+        return octeon_read64(octeon_mio_boot_loc_dat_read(s), addr, size);
+    case OCTEON_MIO_FUS_DAT2:
+        return 0;
+    case OCTEON_MIO_FUS_RCMD:
+        return octeon_read64(s->mio->mio_fus_rcmd, addr, size);
+    case OCTEON_RST_BOOT:
+        value = ((uint64_t)(s->cpu_hz / s->ref_hz) <<
+                 OCTEON_RST_BOOT_C_MUL_SHIFT) |
+                ((uint64_t)(s->io_hz / s->ref_hz) <<
+                 OCTEON_RST_BOOT_PNR_MUL_SHIFT);
+        return octeon_read64(value, addr, size);
+    case OCTEON_RST_PP_POWER:
+        return octeon_read64(s->rst->rst_pp_power, addr, size);
+    case OCTEON_GSERX_CFG0:
+        return octeon_read64(1, addr, size);
+    case OCTEON_GSERX_QLM_STAT0:
+        return octeon_read64(3, addr, size);
+    case OCTEON_RST_CTLX0:
+        if (!octeon_csr_lookup(s, reg, &value)) {
+            value = OCTEON_RST_CTL_HOST_MODE | OCTEON_RST_CTL_RST_DONE;
+        }
+        return octeon_read64(value, addr, size);
+    case OCTEON_RST_SOFT_PRSTX0:
+        if (!octeon_csr_lookup(s, reg, &value)) {
+            value = 1;
+        }
+        return octeon_read64(value, addr, size);
+    case OCTEON_PEMX_CFG_RD0:
+    case OCTEON_PEMX_CFG_WR0:
+        return octeon_pci_pem_read(s, reg, addr, size);
+    case OCTEON_PEMX_BIST_STATUS0:
+        return 0;
+    case OCTEON_PEMX_ON0:
+        return octeon_read64(3, addr, size);
+    case OCTEON_PEMX_STRAP0:
+        return octeon_read64(3, addr, size);
+    default:
+        break;
+    }
+
+    if (octeon_lmc_decode(reg, &lmc, &lreg, s)) {
+        return octeon_lmc_read(s, lmc, lreg, addr, size);
+    }
+
+    if (octeon_mio_emm_decode(reg, &ereg)) {
+        return octeon_mio_emm_read(s, ereg, addr, size);
+    }
+
+    if (!octeon_csr_lookup(s, reg, &value)) {
+        value = 0;
+    }
+    return octeon_read64(value, addr, size);
+}
+
+static void octeon_csr_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+    OcteonState *s = opaque;
+    OcteonLmcState *lmc;
+    hwaddr reg = addr & ~7ULL;
+    hwaddr lreg;
+    hwaddr ereg;
+    uint64_t old;
+
+    if (octeon_lmc_decode(reg, &lmc, &lreg, s)) {
+        octeon_lmc_write(lmc, lreg, addr, value, size);
+        return;
+    }
+
+    if (octeon_mio_emm_decode(reg, &ereg)) {
+        octeon_mio_emm_write(s, ereg, addr, value, size);
+        return;
+    }
+
+    if (reg == OCTEON_RST_SOFT_RST && value) {
+        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+        return;
+    }
+
+    if (reg == OCTEON_MIO_BOOT_LOC_CFGX(0) ||
+        reg == OCTEON_MIO_BOOT_LOC_CFGX(1)) {
+        unsigned int index = OCTEON_MIO_BOOT_LOC_CFG_INDEX(reg);
+
+        old = s->mio->mio_boot_loc_cfg[index];
+        s->mio->mio_boot_loc_cfg[index] =
+            octeon_write64(old, addr, value, size);
+        if (index == 0) {
+            octeon_mio_boot_loc_update(s);
+        }
+        return;
+    }
+
+    if (reg == OCTEON_MIO_BOOT_LOC_ADR) {
+        old = s->mio->mio_boot_loc_adr;
+        value = octeon_write64(old, addr, value, size);
+        s->mio->mio_boot_loc_adr = value & OCTEON_MIO_BOOT_LOC_ADR_MASK;
+        return;
+    }
+
+    if (reg == OCTEON_MIO_BOOT_LOC_DAT) {
+        octeon_mio_boot_loc_dat_write(s, addr, value, size);
+        return;
+    }
+
+    if (reg == OCTEON_MIO_FUS_RCMD) {
+        value = octeon_write64(s->mio->mio_fus_rcmd, addr, value, size);
+        s->mio->mio_fus_rcmd = value & ~(OCTEON_MIO_FUS_RCMD_PEND |
+                                    OCTEON_MIO_FUS_RCMD_DAT);
+        return;
+    }
+
+    if (reg == OCTEON_RST_PP_POWER) {
+        octeon_rst_pp_power_write(s, addr, value, size);
+        return;
+    }
+
+    if (reg == OCTEON_PEMX_CFG_RD0 || reg == OCTEON_PEMX_CFG_WR0) {
+        octeon_pci_pem_write(s, reg, addr, value, size);
+        return;
+    }
+
+    if (reg >= OCTEON_MIO_BOOT_REG_CFGX(0) &&
+        reg < OCTEON_MIO_BOOT_REG_CFGX(OCTEON_MIO_BOOT_REG_CFG_COUNT)) {
+        unsigned int index = OCTEON_MIO_BOOT_REG_CFG_INDEX(reg);
+
+        old = s->mio->mio_boot_reg_cfg[index];
+        s->mio->mio_boot_reg_cfg[index] =
+            octeon_write64(old, addr, value, size);
+        if (index == OCTEON_BOOTBUS_LED_CS) {
+            octeon_bootbus_led_update(s);
+        }
+        return;
+    }
+
+    if (!octeon_csr_lookup(s, reg, &old)) {
+        old = 0;
+    }
+    value = octeon_write64(old, addr, value, size);
+    octeon_csr_store(s, reg, value);
+}
+
+static const MemoryRegionOps octeon_csr_ops = {
+    .read = octeon_csr_read,
+    .write = octeon_csr_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_stateful_io_read(OcteonState *s, uint64_t base,
+                                        hwaddr addr, unsigned size)
+{
+    uint64_t value;
+    uint64_t reg = base + (addr & ~7ULL);
+
+    if (!octeon_csr_lookup(s, reg, &value)) {
+        value = 0;
+    }
+
+    return octeon_read64(value, addr, size);
+}
+
+static void octeon_stateful_io_write(OcteonState *s, uint64_t base,
+                                     hwaddr addr, uint64_t value,
+                                     unsigned size)
+{
+    uint64_t reg = base + (addr & ~7ULL);
+    uint64_t old;
+
+    if (!octeon_csr_lookup(s, reg, &old)) {
+        old = 0;
+    }
+
+    octeon_csr_store(s, reg, octeon_write64(old, addr, value, size));
+}
+
+static uint64_t octeon_pexp_read(void *opaque, hwaddr addr, unsigned size)
+{
+    return octeon_pci_pexp_read(opaque, addr, size);
+}
+
+static void octeon_pexp_write(void *opaque, hwaddr addr,
+                              uint64_t value, unsigned size)
+{
+    octeon_pci_pexp_write(opaque, addr, value, size);
+}
+
+static const MemoryRegionOps octeon_pexp_ops = {
+    .read = octeon_pexp_read,
+    .write = octeon_pexp_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_dpi_read(void *opaque, hwaddr addr, unsigned size)
+{
+    return octeon_stateful_io_read(opaque, OCTEON_DPI_BASE, addr, size);
+}
+
+static void octeon_dpi_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+    octeon_stateful_io_write(opaque, OCTEON_DPI_BASE, addr, value, size);
+}
+
+static const MemoryRegionOps octeon_dpi_ops = {
+    .read = octeon_dpi_read,
+    .write = octeon_dpi_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_fpa_read(void *opaque, hwaddr addr, unsigned size)
+{
+    OcteonState *s = opaque;
+    hwaddr reg = addr & ~7ULL;
+
+    if (reg == OCTEON_FPA_CLK_COUNT) {
+        return octeon_read64(octeon_clock_count(s->io_hz), addr, size);
+    }
+
+    return 0;
+}
+
+static void octeon_fpa_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+}
+
+static const MemoryRegionOps octeon_fpa_ops = {
+    .read = octeon_fpa_read,
+    .write = octeon_fpa_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_ipd_read(void *opaque, hwaddr addr, unsigned size)
+{
+    OcteonState *s = opaque;
+    hwaddr reg = addr & ~7ULL;
+
+    if (reg == OCTEON_IPD_CLK_COUNT) {
+        return octeon_read64(octeon_clock_count(s->io_hz), addr, size);
+    }
+
+    return 0;
+}
+
+static void octeon_ipd_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+}
+
+static const MemoryRegionOps octeon_ipd_ops = {
+    .read = octeon_ipd_read,
+    .write = octeon_ipd_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_pko_read(void *opaque, hwaddr addr, unsigned size)
+{
+    return octeon_stateful_io_read(opaque, OCTEON_PKO_BASE, addr, size);
+}
+
+static void octeon_pko_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+    octeon_stateful_io_write(opaque, OCTEON_PKO_BASE, addr, value, size);
+}
+
+static const MemoryRegionOps octeon_pko_ops = {
+    .read = octeon_pko_read,
+    .write = octeon_pko_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_rng_read(void *opaque, hwaddr addr, unsigned size)
+{
+    uint64_t value;
+
+    qemu_guest_getrandom_nofail(&value, sizeof(value));
+    return octeon_read64(value, addr, size);
+}
+
+static void octeon_rng_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+}
+
+static const MemoryRegionOps octeon_rng_ops = {
+    .read = octeon_rng_read,
+    .write = octeon_rng_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static uint64_t octeon_pow_read(void *opaque, hwaddr addr, unsigned size)
+{
+    return octeon_stateful_io_read(opaque, OCTEON_POW_BASE, addr, size);
+}
+
+static void octeon_pow_write(void *opaque, hwaddr addr,
+                             uint64_t value, unsigned size)
+{
+    octeon_stateful_io_write(opaque, OCTEON_POW_BASE, addr, value, size);
+}
+
+static const MemoryRegionOps octeon_pow_ops = {
+    .read = octeon_pow_read,
+    .write = octeon_pow_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+
+void octeon_irq_set(void *opaque, int irq, int level)
+{
+    OcteonState *s = opaque;
+    unsigned int src;
+
+    switch (irq) {
+    case OCTEON_IRQ_UART:
+        src = OCTEON_CIU3_SRC_UART0;
+        if (!octeon_ciu3_set_level(s, src, level)) {
+            return;
+        }
+        qatomic_set(&s->intc->uart_pending, level);
+        break;
+    case OCTEON_IRQ_PCI:
+        src = OCTEON_CIU3_SRC_PCI_INTA;
+        if (!octeon_ciu3_set_level(s, src, level)) {
+            return;
+        }
+        qatomic_set(&s->intc->pci_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;
+    }
+
+    octeon_intc_update_cpu_mask(s, octeon_ciu3_source_cpu_mask(s, src));
+}
+
+
+static void octeon_cpu_after_reset(OcteonCPUState *cs)
+{
+    CPUMIPSState *env = &cs->cpu->env;
+
+    env->CP0_CvmMemCtl = 0x100;
+    env->CP0_CvmCtl = 0;
+
+    if (!cs->boot_cpu) {
+        /* Secondary cores are released by the Octeon reset controller. */
+        octeon_cpu_park_now(cs->cpu);
+        return;
+    }
+
+    if (cs->board->firmware_entry) {
+        env->active_tc.PC = cs->board->firmware_entry;
+    }
+}
+
+static void octeon_create_cpus(OcteonState *s)
+{
+    unsigned int i;
+
+    s->cpuclk = clock_new(OBJECT(s->machine), "cpu-refclk");
+    clock_set_hz(s->cpuclk, s->cpu_hz);
+    s->cpu_count = s->machine->smp.cpus;
+    qatomic_set(&s->intc->ciu3_pp_rst, octeon_secondary_cpu_mask(s));
+
+    for (i = 0; i < s->cpu_count; i++) {
+        DeviceState *dev = qdev_new(s->machine->cpu_type);
+
+        s->cpu[i].board = s;
+        s->cpu[i].boot_cpu = i == 0;
+        object_property_set_bool(OBJECT(dev), "big-endian", TARGET_BIG_ENDIAN,
+                                 &error_abort);
+        object_property_set_bool(OBJECT(dev), "start-powered-off", i != 0,
+                                 &error_abort);
+        qdev_connect_clock_in(dev, "clk-in", s->cpuclk);
+        qdev_realize(dev, NULL, &error_abort);
+        s->cpu[i].cpu = MIPS_CPU(dev);
+
+        cpu_mips_irq_init_cpu(s->cpu[i].cpu);
+        cpu_mips_clock_init(s->cpu[i].cpu);
+        qemu_register_resettable(OBJECT(dev));
+    }
+}
+
+static uint64_t octeon_map_ram_alias(OcteonState *s, MemoryRegion *alias,
+                                     const char *name, hwaddr base,
+                                     uint64_t offset, uint64_t size)
+{
+    MemoryRegion *sysmem = get_system_memory();
+
+    if (!size) {
+        return offset;
+    }
+
+    memory_region_init_alias(alias, NULL, name, s->machine->ram, offset, size);
+    memory_region_add_subregion(sysmem, base, alias);
+    return offset + size;
+}
+
+static void octeon_map_ram(OcteonState *s)
+{
+    MemoryRegion *sysmem = get_system_memory();
+    uint64_t offset = 0;
+    uint64_t remaining = s->machine->ram_size;
+    uint64_t size;
+
+    size = MIN(remaining, (uint64_t)OCTEON_DR0_SIZE);
+    offset = octeon_map_ram_alias(s, &s->dr0, "octeon.dr0",
+                                  OCTEON_DR0_BASE, offset, size);
+    remaining -= size;
+
+    size = MIN(remaining, (uint64_t)OCTEON_DR1_SIZE);
+    octeon_map_ram_alias(s, &s->dr1, "octeon.dr1",
+                         OCTEON_DR1_BASE, offset, size);
+
+    memory_region_init_ram(&s->cvmseg, NULL, "octeon.cvmseg",
+                           OCTEON_CVMSEG_TOTAL_SIZE, &error_fatal);
+    memory_region_add_subregion(sysmem, OCTEON_CVMSEG_BASE, &s->cvmseg);
+}
+
+static void octeon_load_firmware(OcteonState *s)
+{
+    g_autofree char *filename = NULL;
+    g_autofree gchar *contents = NULL;
+    g_autoptr(GError) err = NULL;
+    gsize len;
+    uint8_t *flash;
+
+    if (!s->machine->firmware) {
+        return;
+    }
+    if (!s->flash) {
+        error_report("Octeon boot flash is not initialized");
+        exit(1);
+    }
+
+    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->machine->firmware);
+    if (!filename) {
+        error_report("Could not find Octeon firmware '%s'",
+                     s->machine->firmware);
+        exit(1);
+    }
+
+    if (!g_file_get_contents(filename, &contents, &len, &err)) {
+        error_report("Could not load Octeon firmware '%s': %s",
+                     filename, err->message);
+        exit(1);
+    }
+
+    if (len == 0 || len > OCTEON_FLASH_ENV_OFFSET) {
+        error_report("Octeon firmware '%s' has invalid size %" G_GSIZE_FORMAT,
+                     filename, len);
+        exit(1);
+    }
+
+    flash = memory_region_get_ram_ptr(s->flash);
+    memcpy(flash, contents, len);
+    s->firmware_entry = cpu_mips_phys_to_kseg1(NULL, OCTEON_FLASH_RESET_BASE);
+}
+
+static MemTxResult octeon_boot_flash_read(void *opaque, hwaddr addr,
+                                          uint64_t *data, unsigned size,
+                                          MemTxAttrs attrs)
+{
+    OcteonState *s = opaque;
+    uint8_t buf[8];
+    MemTxResult result = MEMTX_OK;
+    unsigned int i;
+
+    for (i = 0; i < size; i++) {
+        uint64_t value;
+
+        result |= memory_region_dispatch_read(s->flash, addr + i, &value,
+                                              MO_8, attrs);
+        buf[i] = value;
+    }
+    *data = ldn_be_p(buf, size);
+    return result;
+}
+
+static MemTxResult octeon_boot_flash_write(void *opaque, hwaddr addr,
+                                           uint64_t value, unsigned size,
+                                           MemTxAttrs attrs)
+{
+    OcteonState *s = opaque;
+    unsigned access_size = MIN(size, 4);
+
+    if (size > access_size) {
+        access_size = OCTEON_FLASH_WIDTH;
+    }
+    return memory_region_dispatch_write(s->flash, addr, value,
+                                        size_memop(access_size) | MO_BE,
+                                        attrs);
+}
+
+static const MemoryRegionOps octeon_boot_flash_ops = {
+    .read_with_attrs = octeon_boot_flash_read,
+    .write_with_attrs = octeon_boot_flash_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+};
+
+static void octeon_init_flash(OcteonState *s)
+{
+    DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0);
+    DeviceState *dev;
+    uint8_t *flash;
+
+    dev = qdev_new(TYPE_PFLASH_CFI02);
+    if (dinfo) {
+        qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(dinfo));
+    }
+    qdev_prop_set_uint32(dev, "num-blocks0", OCTEON_FLASH_MAIN_SECTORS);
+    qdev_prop_set_uint32(dev, "sector-length0",
+                         OCTEON_FLASH_MAIN_SECTOR_SIZE);
+    qdev_prop_set_uint32(dev, "num-blocks1", OCTEON_FLASH_ENV_SECTORS);
+    qdev_prop_set_uint32(dev, "sector-length1",
+                         OCTEON_FLASH_ENV_SECTOR_SIZE);
+    qdev_prop_set_uint8(dev, "width", OCTEON_FLASH_WIDTH);
+    qdev_prop_set_uint8(dev, "mappings", 1);
+    qdev_prop_set_uint8(dev, "big-endian", TARGET_BIG_ENDIAN);
+    qdev_prop_set_uint16(dev, "id0", 0x01);
+    qdev_prop_set_uint16(dev, "id1", 0x7e);
+    qdev_prop_set_uint16(dev, "id2", 0x00);
+    qdev_prop_set_uint16(dev, "id3", 0x00);
+    qdev_prop_set_uint16(dev, "unlock-addr0", 0x555);
+    qdev_prop_set_uint16(dev, "unlock-addr1", 0x2aa);
+    qdev_prop_set_string(dev, "name", "octeon.bootbus-flash");
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+    s->flash = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+    if (!dinfo) {
+        flash = memory_region_get_ram_ptr(s->flash);
+        memset(flash, 0xff, OCTEON_FLASH_SIZE);
+    }
+    memory_region_init_io(&s->boot_flash, NULL, &octeon_boot_flash_ops, s,
+                          "octeon.bootbus-flash-window", OCTEON_FLASH_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_FLASH_BASE,
+                                &s->boot_flash);
+
+    memory_region_init_alias(&s->boot_flash_alias, NULL,
+                             "octeon.bootbus-flash-reset-alias",
+                             &s->boot_flash, 0, OCTEON_FLASH_RESET_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_FLASH_RESET_BASE,
+                                &s->boot_flash_alias);
+}
+
+static void octeon_init_mio_boot_loc(OcteonState *s)
+{
+    memory_region_init_ram(&s->mio->bootbus_led, OBJECT(s->mio),
+                           "octeon.bootbus-led",
+                           OCTEON_BOOTBUS_LED_SIZE, &error_fatal);
+    memory_region_init_ram_ptr(&s->mio->mio_boot_loc, OBJECT(s->mio),
+                               "octeon.mio-boot-loc",
+                               OCTEON_MIO_BOOT_LOC_SIZE,
+                               s->mio->mio_boot_loc_data);
+}
+
+
+static uint64_t octeon_uart_tx_read(void *opaque, hwaddr addr, unsigned size)
+{
+    return 0;
+}
+
+static void octeon_uart_tx_write(void *opaque, hwaddr addr,
+                                 uint64_t value, unsigned size)
+{
+    OcteonState *s = opaque;
+
+    if (size == OCTEON_CSR_32BIT_SIZE &&
+        addr == OCTEON_UART_TX_HI32_OFFSET) {
+        return;
+    }
+
+    serial_io_ops.write(&s->uart->serial, 0, value & 0xff, 1);
+}
+
+static const MemoryRegionOps octeon_uart_tx_ops = {
+    .read = octeon_uart_tx_read,
+    .write = octeon_uart_tx_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+};
+
+static void octeon_init_uart(OcteonState *s)
+{
+    SerialMM *uart;
+    qemu_irq uart_irq;
+    MemoryRegion *uart_mmio;
+    int baudbase = s->io_hz / 16;
+
+    uart_irq = qemu_allocate_irq(octeon_irq_set, s, OCTEON_IRQ_UART);
+    uart = serial_mm_init(get_system_memory(), OCTEON_UART0_BASE, 3, uart_irq,
+                          baudbase, serial_hd(0), DEVICE_BIG_ENDIAN);
+    s->uart = uart;
+
+    uart_mmio = sysbus_mmio_get_region(SYS_BUS_DEVICE(uart), 0);
+    memory_region_init_alias(&s->uart_alias, NULL, "octeon.uart0-alias",
+                             uart_mmio, 0, memory_region_size(uart_mmio));
+    memory_region_add_subregion(get_system_memory(), OCTEON_UART0_ALIAS_BASE,
+                                &s->uart_alias);
+
+    memory_region_init_io(&s->uart_tx, NULL, &octeon_uart_tx_ops, s,
+                          "octeon.uart0-tx", OCTEON_UART_TX_SIZE);
+    memory_region_add_subregion(get_system_memory(),
+                                OCTEON_UART0_BASE + OCTEON_UART_TX_REG,
+                                &s->uart_tx);
+    memory_region_init_io(&s->uart_alias_tx, NULL, &octeon_uart_tx_ops, s,
+                          "octeon.uart0-alias-tx", OCTEON_UART_TX_SIZE);
+    memory_region_add_subregion(get_system_memory(),
+                                OCTEON_UART0_ALIAS_BASE + OCTEON_UART_TX_REG,
+                                &s->uart_alias_tx);
+}
+
+
+static void octeon_mio_reset_hold(Object *obj, ResetType type)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_GET_CLASS(obj);
+    OcteonMioState *mio = OCTEON_MIO(obj);
+    OcteonState *s = mio->parent_obj.board;
+
+    if (opc->parent_phases.hold) {
+        opc->parent_phases.hold(obj, type);
+    }
+
+    if (mio->bootbus_led_mapped) {
+        memory_region_del_subregion(get_system_memory(), &mio->bootbus_led);
+        mio->bootbus_led_mapped = false;
+    }
+    memset(mio->mio_boot_reg_cfg, 0, sizeof(mio->mio_boot_reg_cfg));
+    mio->mio_boot_reg_cfg[0] = OCTEON_MIO_BOOT_REG_CFG_RESET_BASE;
+    mio->mio_boot_loc_cfg[0] = 0;
+    mio->mio_boot_loc_cfg[1] = 0;
+    mio->mio_boot_loc_adr = 0;
+    mio->mio_fus_rcmd = 0;
+    memset(mio->mio_boot_loc_data, 0, sizeof(mio->mio_boot_loc_data));
+    octeon_mio_boot_loc_update(s);
+}
+
+static void octeon_emm_reset_hold(Object *obj, ResetType type)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_GET_CLASS(obj);
+    OcteonEmmState *emm = OCTEON_EMM(obj);
+
+    if (opc->parent_phases.hold) {
+        opc->parent_phases.hold(obj, type);
+    }
+
+    g_hash_table_remove_all(emm->regs);
+    emm->mio_emm_rsp_sts = 0;
+    emm->mio_emm_rsp_lo = 0;
+    emm->mio_emm_rsp_hi = 0;
+    emm->mio_emm_int = 0;
+    emm->mio_emm_dma_int = 0;
+    emm->mio_emm_buf_idx = 0;
+    emm->mio_emm_buf_inc = false;
+    memset(emm->mio_emm_buf, 0, sizeof(emm->mio_emm_buf));
+}
+
+static void octeon_rst_reset_hold(Object *obj, ResetType type)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_GET_CLASS(obj);
+    OcteonRstState *rst = OCTEON_RST(obj);
+
+    if (opc->parent_phases.hold) {
+        opc->parent_phases.hold(obj, type);
+    }
+
+    g_hash_table_remove_all(rst->regs);
+    rst->rst_pp_power = octeon_secondary_cpu_mask(rst->parent_obj.board);
+}
+
+static void octeon_intc_reset_hold(Object *obj, ResetType type)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_GET_CLASS(obj);
+    OcteonIntcState *intc = OCTEON_INTC(obj);
+    OcteonState *s = intc->parent_obj.board;
+
+    if (opc->parent_phases.hold) {
+        opc->parent_phases.hold(obj, type);
+    }
+
+    memset(intc->ciu_mbox, 0, sizeof(intc->ciu_mbox));
+    memset(intc->ciu_ipi_en, 0, sizeof(intc->ciu_ipi_en));
+    qatomic_set(&intc->ciu_gpio_rx, OCTEON_CIU_GPIO_INPUTS);
+    qatomic_set(&intc->ciu_gpio_tx, 0);
+    memset(intc->ciu_gpio_bit_cfg, 0, sizeof(intc->ciu_gpio_bit_cfg));
+    qatomic_set(&intc->uart_pending, false);
+    qatomic_set(&intc->pci_pending, false);
+    qatomic_set(&intc->ciu3_pp_rst, octeon_secondary_cpu_mask(s));
+    memset(intc->ciu3_src_state, 0, sizeof(intc->ciu3_src_state));
+    memset(intc->ciu3_src_ctl, 0, sizeof(intc->ciu3_src_ctl));
+    memset(intc->ciu3_idt_ctl, 0, sizeof(intc->ciu3_idt_ctl));
+    memset(intc->ciu3_idt_pp, 0, sizeof(intc->ciu3_idt_pp));
+    memset(intc->ciu3_idt_io, 0, sizeof(intc->ciu3_idt_io));
+    memset(intc->ciu3_src_cursor, 0, sizeof(intc->ciu3_src_cursor));
+    memset(intc->ciu3_irq_line, 0, sizeof(intc->ciu3_irq_line));
+    octeon_intc_update_all_cpus(s);
+}
+
+static void octeon_lmc_reset_hold(Object *obj, ResetType type)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_GET_CLASS(obj);
+    OcteonLmcState *lmc = OCTEON_LMC(obj);
+
+    if (opc->parent_phases.hold) {
+        opc->parent_phases.hold(obj, type);
+    }
+
+    g_hash_table_remove_all(lmc->regs);
+}
+
+static void octeon_csr_bank_reset_hold(Object *obj, ResetType type)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_GET_CLASS(obj);
+    OcteonCsrBankState *bank = OCTEON_CSR_BANK(obj);
+
+    if (opc->parent_phases.hold) {
+        opc->parent_phases.hold(obj, type);
+    }
+
+    g_hash_table_remove_all(bank->values);
+}
+
+static void octeon_peripheral_class_init(ObjectClass *klass,
+                                          const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->user_creatable = false;
+}
+
+static GHashTable *octeon_reg_table_new(void)
+{
+    return g_hash_table_new_full(octeon_uint64_hash,
+                                 octeon_uint64_equal,
+                                 g_free, g_free);
+}
+
+static void octeon_emm_init(Object *obj)
+{
+    OCTEON_EMM(obj)->regs = octeon_reg_table_new();
+}
+
+static void octeon_emm_finalize(Object *obj)
+{
+    g_hash_table_destroy(OCTEON_EMM(obj)->regs);
+}
+
+static void octeon_rst_init(Object *obj)
+{
+    OCTEON_RST(obj)->regs = octeon_reg_table_new();
+}
+
+static void octeon_rst_finalize(Object *obj)
+{
+    g_hash_table_destroy(OCTEON_RST(obj)->regs);
+}
+
+static void octeon_lmc_init(Object *obj)
+{
+    OCTEON_LMC(obj)->regs = octeon_reg_table_new();
+}
+
+static void octeon_lmc_finalize(Object *obj)
+{
+    g_hash_table_destroy(OCTEON_LMC(obj)->regs);
+}
+
+static void octeon_csr_bank_init(Object *obj)
+{
+    OCTEON_CSR_BANK(obj)->values = octeon_reg_table_new();
+}
+
+static void octeon_csr_bank_finalize(Object *obj)
+{
+    g_hash_table_destroy(OCTEON_CSR_BANK(obj)->values);
+}
+
+static void octeon_mio_class_init(ObjectClass *klass, const void *data)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    resettable_class_set_parent_phases(rc, NULL, octeon_mio_reset_hold,
+                                       NULL, &opc->parent_phases);
+}
+
+static void octeon_emm_class_init(ObjectClass *klass, const void *data)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    resettable_class_set_parent_phases(rc, NULL, octeon_emm_reset_hold,
+                                       NULL, &opc->parent_phases);
+}
+
+static void octeon_rst_class_init(ObjectClass *klass, const void *data)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    resettable_class_set_parent_phases(rc, NULL, octeon_rst_reset_hold,
+                                       NULL, &opc->parent_phases);
+}
+
+static void octeon_intc_class_init(ObjectClass *klass, const void *data)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    resettable_class_set_parent_phases(rc, NULL, octeon_intc_reset_hold,
+                                       NULL, &opc->parent_phases);
+}
+
+static void octeon_lmc_class_init(ObjectClass *klass, const void *data)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    resettable_class_set_parent_phases(rc, NULL, octeon_lmc_reset_hold,
+                                       NULL, &opc->parent_phases);
+}
+
+static void octeon_csr_bank_class_init(ObjectClass *klass, const void *data)
+{
+    OcteonPeripheralClass *opc = OCTEON_PERIPHERAL_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    resettable_class_set_parent_phases(rc, NULL,
+                                       octeon_csr_bank_reset_hold, NULL,
+                                       &opc->parent_phases);
+}
+
+static OcteonPeripheralState *octeon_new_peripheral(OcteonState *s,
+                                                     const char *type,
+                                                     unsigned int index)
+{
+    DeviceState *qdev = qdev_new(type);
+    OcteonPeripheralState *dev = OCTEON_PERIPHERAL(qdev);
+
+    dev->board = s;
+    dev->index = index;
+    return dev;
+}
+
+static void octeon_create_peripherals(OcteonState *s)
+{
+    unsigned int i;
+
+    s->mio = OCTEON_MIO(octeon_new_peripheral(s, TYPE_OCTEON_MIO, 0));
+    s->emm = OCTEON_EMM(octeon_new_peripheral(s, TYPE_OCTEON_EMM, 0));
+    s->rst = OCTEON_RST(octeon_new_peripheral(s, TYPE_OCTEON_RST, 0));
+    s->intc = OCTEON_INTC(octeon_new_peripheral(s, TYPE_OCTEON_INTC, 0));
+    for (i = 0; i < OCTEON_LMC_COUNT; i++) {
+        s->lmc[i] = OCTEON_LMC(octeon_new_peripheral(s, TYPE_OCTEON_LMC,
+                                                      i));
+    }
+    s->csr_bank = OCTEON_CSR_BANK(octeon_new_peripheral(
+                                      s, TYPE_OCTEON_CSR_BANK, 0));
+}
+
+static void octeon_realize_peripheral(OcteonPeripheralState *dev)
+{
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+}
+
+static void octeon_realize_peripherals(OcteonState *s)
+{
+    int i;
+
+    octeon_realize_peripheral(&s->csr_bank->parent_obj);
+    for (i = OCTEON_LMC_COUNT - 1; i >= 0; i--) {
+        octeon_realize_peripheral(&s->lmc[i]->parent_obj);
+    }
+    octeon_realize_peripheral(&s->intc->parent_obj);
+    octeon_realize_peripheral(&s->rst->parent_obj);
+    octeon_realize_peripheral(&s->emm->parent_obj);
+    octeon_realize_peripheral(&s->mio->parent_obj);
+}
+
+static void mips_octeon_init(MachineState *machine)
+{
+    OcteonMachineState *oms = OCTEON_MACHINE(machine);
+    OcteonState *s = g_new0(OcteonState, 1);
+
+    octeon_validate_clocks(oms);
+
+    oms->board = s;
+    s->machine = machine;
+    s->cpu_hz = oms->cpu_hz;
+    s->ref_hz = oms->ref_hz;
+    s->io_hz = oms->io_hz;
+    s->ddr_hz = oms->ddr_hz;
+    octeon_create_peripherals(s);
+
+    if (machine->kernel_filename) {
+        error_report("-kernel is not implemented for octeon3; "
+                     "boot via -bios and U-Boot");
+        exit(1);
+    }
+
+    octeon_validate_ram_size(machine->ram_size);
+    octeon_init_spd(s);
+    octeon_map_ram(s);
+    octeon_init_flash(s);
+    octeon_init_mio_boot_loc(s);
+    octeon_load_firmware(s);
+    octeon_create_cpus(s);
+
+    memory_region_init_io(&s->intc->ciu, OBJECT(s->intc), &octeon_ciu_ops, s,
+                          "octeon.ciu", OCTEON_CIU_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_CIU_BASE,
+                                &s->intc->ciu);
+
+    memory_region_init_io(&s->intc->ciu3, OBJECT(s->intc),
+                          &octeon_ciu3_ops, s,
+                          "octeon.ciu3", OCTEON_CIU3_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_CIU3_BASE,
+                                &s->intc->ciu3);
+
+    octeon_init_uart(s);
+    octeon_init_twsi(s);
+
+    memory_region_init_io(&s->csr_bank->csr, OBJECT(s->csr_bank),
+                          &octeon_csr_ops, s,
+                          "octeon.csr", OCTEON_CSR_SIZE);
+    memory_region_add_subregion_overlap(get_system_memory(), OCTEON_CSR_BASE,
+                                        &s->csr_bank->csr, -1);
+
+    memory_region_init_io(&s->pexp, NULL, &octeon_pexp_ops, s,
+                          "octeon.pexp", OCTEON_PEXP_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_PEXP_BASE,
+                                &s->pexp);
+
+    memory_region_init_io(&s->dpi, NULL, &octeon_dpi_ops, s,
+                          "octeon.dpi", OCTEON_DPI_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_DPI_BASE,
+                                &s->dpi);
+
+    memory_region_init_io(&s->fpa, NULL, &octeon_fpa_ops, s,
+                          "octeon.fpa", OCTEON_FPA_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_FPA_BASE,
+                                &s->fpa);
+
+    memory_region_init_io(&s->ipd, NULL, &octeon_ipd_ops, s,
+                          "octeon.ipd", OCTEON_IPD_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_IPD_BASE,
+                                &s->ipd);
+
+    memory_region_init_io(&s->pko, NULL, &octeon_pko_ops, s,
+                          "octeon.pko", OCTEON_PKO_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_PKO_BASE,
+                                &s->pko);
+
+    memory_region_init_io(&s->rng, NULL, &octeon_rng_ops, s,
+                          "octeon.rng", OCTEON_RNG_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_RNG_BASE,
+                                &s->rng);
+
+    memory_region_init_io(&s->pow, NULL, &octeon_pow_ops, s,
+                          "octeon.pow", OCTEON_POW_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_POW_BASE,
+                                &s->pow);
+
+    octeon_init_usb(s);
+    octeon_init_pci(s);
+    octeon_realize_peripherals(s);
+}
+
+static void octeon_machine_reset(MachineState *machine, ResetType type)
+{
+    OcteonState *s = OCTEON_MACHINE(machine)->board;
+    unsigned int i;
+
+    qemu_devices_reset(type);
+    for (i = 0; i < s->cpu_count; i++) {
+        octeon_cpu_after_reset(&s->cpu[i]);
+    }
+}
+
+static void octeon_machine_instance_init(Object *obj)
+{
+    OcteonMachineState *oms = OCTEON_MACHINE(obj);
+
+    oms->cpu_hz = OCTEON_DEFAULT_CPU_HZ;
+    oms->ref_hz = OCTEON_DEFAULT_REF_HZ;
+    oms->io_hz = OCTEON_DEFAULT_IO_HZ;
+    oms->ddr_hz = OCTEON_DEFAULT_DDR_HZ;
+
+    object_property_add_uint64_ptr(obj, "cpu-clock-hz", &oms->cpu_hz,
+                                   OBJ_PROP_FLAG_READWRITE);
+    object_property_set_description(obj, "cpu-clock-hz",
+                                    "CPU clock frequency in Hz");
+
+    object_property_add_uint64_ptr(obj, "ref-clock-hz", &oms->ref_hz,
+                                   OBJ_PROP_FLAG_READWRITE);
+    object_property_set_description(obj, "ref-clock-hz",
+                                    "Reference clock frequency in Hz");
+
+    object_property_add_uint64_ptr(obj, "io-clock-hz", &oms->io_hz,
+                                   OBJ_PROP_FLAG_READWRITE);
+    object_property_set_description(obj, "io-clock-hz",
+                                    "IO clock frequency in Hz");
+
+    object_property_add_uint64_ptr(obj, "ddr-clock-hz", &oms->ddr_hz,
+                                   OBJ_PROP_FLAG_READWRITE);
+    object_property_set_description(obj, "ddr-clock-hz",
+                                    "DDR clock frequency in Hz");
+}
+
+static void octeon_machine_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->desc = "Cavium Octeon III / EBB7304 EVK";
+    mc->init = mips_octeon_init;
+    mc->reset = octeon_machine_reset;
+    mc->default_cpu_type = MIPS_CPU_TYPE_NAME("OcteonCN73XX");
+    mc->default_ram_size = OCTEON_DEFAULT_RAM_SIZE;
+    mc->default_ram_id = "octeon.ram";
+    mc->max_cpus = OCTEON_MAX_CPUS;
+    mc->pci_allow_0_address = true;
+}
+
+static const TypeInfo octeon_machine_types[] = {
+    {
+        .name = TYPE_OCTEON_PERIPHERAL,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(OcteonPeripheralState),
+        .class_size = sizeof(OcteonPeripheralClass),
+        .class_init = octeon_peripheral_class_init,
+        .abstract = true,
+    },
+    {
+        .name = TYPE_OCTEON_MIO,
+        .parent = TYPE_OCTEON_PERIPHERAL,
+        .instance_size = sizeof(OcteonMioState),
+        .class_init = octeon_mio_class_init,
+    },
+    {
+        .name = TYPE_OCTEON_EMM,
+        .parent = TYPE_OCTEON_PERIPHERAL,
+        .instance_size = sizeof(OcteonEmmState),
+        .instance_init = octeon_emm_init,
+        .instance_finalize = octeon_emm_finalize,
+        .class_init = octeon_emm_class_init,
+    },
+    {
+        .name = TYPE_OCTEON_RST,
+        .parent = TYPE_OCTEON_PERIPHERAL,
+        .instance_size = sizeof(OcteonRstState),
+        .instance_init = octeon_rst_init,
+        .instance_finalize = octeon_rst_finalize,
+        .class_init = octeon_rst_class_init,
+    },
+    {
+        .name = TYPE_OCTEON_INTC,
+        .parent = TYPE_OCTEON_PERIPHERAL,
+        .instance_size = sizeof(OcteonIntcState),
+        .class_init = octeon_intc_class_init,
+    },
+    {
+        .name = TYPE_OCTEON_LMC,
+        .parent = TYPE_OCTEON_PERIPHERAL,
+        .instance_size = sizeof(OcteonLmcState),
+        .instance_init = octeon_lmc_init,
+        .instance_finalize = octeon_lmc_finalize,
+        .class_init = octeon_lmc_class_init,
+    },
+    {
+        .name = TYPE_OCTEON_CSR_BANK,
+        .parent = TYPE_OCTEON_PERIPHERAL,
+        .instance_size = sizeof(OcteonCsrBankState),
+        .instance_init = octeon_csr_bank_init,
+        .instance_finalize = octeon_csr_bank_finalize,
+        .class_init = octeon_csr_bank_class_init,
+    },
+    {
+        .name = TYPE_OCTEON_MACHINE,
+        .parent = TYPE_MACHINE,
+        .instance_size = sizeof(OcteonMachineState),
+        .instance_init = octeon_machine_instance_init,
+        .class_init = octeon_machine_class_init,
+    }
+};
+
+DEFINE_TYPES(octeon_machine_types)
