Add a minimal Cavium/Marvell Octeon III machine model for the EBB7304 evaluation board. This work is based on Amir Mehmood's original Octeon host.patch; the basic machine approach and several hardware model ideas come from that work.
The model targets modern U-Boot's EBB7304 firmware and OpenBSD/octeon. The same machine also boots the U-Boot image shipped in the ER-8-XG firmware upgrade package. It provides the RAM layout, boot flash aliasing, CIU/CIU3 interrupt state, UART, TWSI/SPD, LMC state, RNG, POW, DWC3 USB, and minimal PCIe support needed to boot with USB storage and an attached PCI e1000. Add a functional test that boots the OpenBSD/octeon ramdisk through U-Boot using QEMU's user mode TFTP server. Signed-off-by: Kirill A. Korinsky <[email protected]> --- hw/mips/Kconfig | 10 + hw/mips/meson.build | 1 + hw/mips/octeon.c | 3829 +++++++++++++++++++++++ tests/functional/mips64/meson.build | 2 + tests/functional/mips64/test_octeon3.py | 54 + 5 files changed, 3896 insertions(+) create mode 100644 hw/mips/octeon.c create mode 100644 tests/functional/mips64/test_octeon3.py diff --git a/hw/mips/Kconfig b/hw/mips/Kconfig index b59cb2f111..8636003f53 100644 --- a/hw/mips/Kconfig +++ b/hw/mips/Kconfig @@ -67,6 +67,16 @@ config LOONGSON3V select FW_CFG_MIPS select UNIMP +config MIPS_OCTEON + bool + default y + depends on MIPS64 && TARGET_BIG_ENDIAN + imply E1000_PCI + imply PCI_DEVICES + select PFLASH_CFI02 + select SERIAL_MM + select USB_DWC3 + config MIPS_CPS bool select MIPS_ITU if TCG diff --git a/hw/mips/meson.build b/hw/mips/meson.build index 390f0fd7f9..f11dcdf794 100644 --- a/hw/mips/meson.build +++ b/hw/mips/meson.build @@ -10,6 +10,7 @@ if 'CONFIG_TCG' in config_all_accel mips_ss.add(when: 'CONFIG_JAZZ', if_true: files('jazz.c')) mips_ss.add(when: 'CONFIG_FULOONG', if_true: files('fuloong2e.c')) mips_ss.add(when: 'CONFIG_MIPS_BOSTON', if_true: files('boston.c')) +mips_ss.add(when: 'CONFIG_MIPS_OCTEON', if_true: files('octeon.c')) endif hw_arch += {'mips': mips_ss} diff --git a/hw/mips/octeon.c b/hw/mips/octeon.c new file mode 100644 index 0000000000..20b4c16525 --- /dev/null +++ b/hw/mips/octeon.c @@ -0,0 +1,3829 @@ +/* + * QEMU Cavium Octeon board model. + * + * Copyright (c) 2026 Kirill A. Korinsky + * + * This work is based on Amir Mehmood's original Octeon host.patch. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/datadir.h" +#include "qemu/bswap.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 "hw/pci/pci.h" +#include "hw/pci/pci_host.h" +#include "hw/usb/hcd-dwc3.h" +#include "system/blockdev.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 "target/mips/cpu.h" + +#define TYPE_OCTEON_MACHINE MACHINE_TYPE_NAME("octeon3") +OBJECT_DECLARE_SIMPLE_TYPE(OcteonMachineState, OCTEON_MACHINE) + +#define TYPE_OCTEON_PCI_HOST "octeon-pci-host" +OBJECT_DECLARE_SIMPLE_TYPE(OcteonPCIHostState, OCTEON_PCI_HOST) + +#define OCTEON_CSR_32BIT_SIZE 4 +#define OCTEON_CSR_64BIT_SIZE 8 +#define OCTEON_CSR_HI32_OFFSET 0 +#define OCTEON_CSR_LO32_OFFSET 4 + +#define OCTEON_MAX_CPUS 16 +/* + * 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_COUNT 32 +#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_CP0_IRQ_COUNT 4 +#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_IDT_COUNT (OCTEON_MAX_CPUS * 4) +#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_PEXP_BASE 0x11f0000000000ULL +#define OCTEON_PEXP_SIZE 0x40000 +#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_COUNT 8 +#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_SIZE 0x100 +#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_SIZE 512 +#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_COUNT 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_USB_COUNT 2 +#define OCTEON_UCTL_SIZE 0x100 +#define OCTEON_USB0_UCTL_BASE 0x1180068000000ULL +#define OCTEON_USB1_UCTL_BASE 0x1180069000000ULL +#define OCTEON_USB0_DWC3_BASE 0x1680000000000ULL +#define OCTEON_USB1_DWC3_BASE 0x1690000000000ULL +#define OCTEON_UCTL_SHIM_CFG 0xe8 +#define OCTEON_UCTL_SHIM_CFG_CSR_BYTE_SWAP 0x3 +#define OCTEON_UCTL_SHIM_CFG_CSR_NATIVE 0x3 +#define OCTEON_CIU3_USB0_INTSN 0x68080 +#define OCTEON_CIU3_USB1_INTSN 0x69080 + +#define OCTEON_TWSI_COUNT 2 +#define OCTEON_TWSI_SIZE 0x200 +#define OCTEON_TWSI0_BASE 0x1180000001000ULL +#define OCTEON_TWSI1_BASE 0x1180000001200ULL +#define OCTEON_TWSI_SW_TWSI 0x00 +#define OCTEON_TWSI_INT 0x10 +#define OCTEON_TWSI_SW_DATA_MASK 0xffffffffULL +#define OCTEON_TWSI_SW_EOP_IA_SHIFT 32 +#define OCTEON_TWSI_SW_R (1ULL << 56) +#define OCTEON_TWSI_SW_OP_SHIFT 57 +#define OCTEON_TWSI_SW_V (1ULL << 63) +#define OCTEON_TWSI_OP_EOP_IA 6 +#define OCTEON_TWSI_EOP_DATA 1 +#define OCTEON_TWSI_EOP_CTL 2 +#define OCTEON_TWSI_EOP_STAT 3 +#define OCTEON_TWSI_CTL_AAK 0x04 +#define OCTEON_TWSI_CTL_IFLG 0x08 +#define OCTEON_TWSI_CTL_STP 0x10 +#define OCTEON_TWSI_CTL_STA 0x20 +#define OCTEON_TWSI_STAT_START 0x08 +#define OCTEON_TWSI_STAT_TXADDR_ACK 0x18 +#define OCTEON_TWSI_STAT_TXADDR_NAK 0x20 +#define OCTEON_TWSI_STAT_TXDATA_ACK 0x28 +#define OCTEON_TWSI_STAT_TXDATA_NAK 0x30 +#define OCTEON_TWSI_STAT_RXADDR_ACK 0x40 +#define OCTEON_TWSI_STAT_RXADDR_NAK 0x48 +#define OCTEON_TWSI_STAT_RXDATA_ACK 0x50 +#define OCTEON_TWSI_STAT_RXDATA_NAK 0x58 +#define OCTEON_TWSI_STAT_IDLE 0xf8 +#define OCTEON_SPD_EEPROM_SIZE 139 +#define OCTEON_SPD_EEPROM_COUNT 1 +#define OCTEON_SPD_BUS 1 +#define OCTEON_SPD_ADDR 0x50 +#define OCTEON_SPD_MIN_ROW_BITS 12 +#define OCTEON_SPD_MAX_ROW_BITS 18 +#define OCTEON_SPD_MIN_COL_BITS 9 +#define OCTEON_SPD_MAX_COL_BITS 10 +#define OCTEON_SPD_BANK_BITS 4 +#define OCTEON_SPD_MIN_RAM_SIZE \ + (1ULL << (OCTEON_SPD_MIN_ROW_BITS + OCTEON_SPD_MIN_COL_BITS + \ + OCTEON_SPD_BANK_BITS + 3)) +#define OCTEON_SPD_MAX_RAM_SIZE \ + (1ULL << (OCTEON_SPD_MAX_ROW_BITS + OCTEON_SPD_MAX_COL_BITS + \ + OCTEON_SPD_BANK_BITS + 3)) +#define OCTEON_SPD_DDR4_TCKMIN 18 +#define OCTEON_SPD_DDR4_FINE_TCKMIN 125 +#define OCTEON_SPD_DDR4_MTB_PS 125 +#define OCTEON_SPD_DDR4_FTB_PS 1 + +#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_PCIE_CFG_BASE 0x1190400000000ULL +#define OCTEON_PCIE_CFG_SIZE 0x20000000ULL +#define OCTEON_PCIE_SLI_CFG_BASE 0x1190c00000000ULL +#define OCTEON_PCIE_SLI_CFG_PORTS 4 +#define OCTEON_PCIE_SLI_CFG_PORT_SIZE (1ULL << 32) +#define OCTEON_PCIE_SLI_CFG_SIZE \ + (OCTEON_PCIE_SLI_CFG_PORTS * OCTEON_PCIE_SLI_CFG_PORT_SIZE) +#define OCTEON_PCIE0_IO_BASE 0x11a0400000000ULL +#define OCTEON_PCIE0_IO_SIZE (1ULL << 32) +#define OCTEON_PCIE0_MEM_BASE 0x11b0000000000ULL +#define OCTEON_PCIE0_MEM_SIZE (1ULL << 32) +#define OCTEON_SLI_MEM_ACCESS_SUBID_BASE 0x000000e0 +#define OCTEON_SLI_MEM_ACCESS_SUBID_STRIDE 16 +#define OCTEON_SLI_MEM_ACCESS_SUBID_ESR_M 0x0000003000000000ULL +#define OCTEON_SLI_MEM_ACCESS_SUBID_ESR_S 36 +#define OCTEON_SLI_MEM_ACCESS_SUBID_ESW_M 0x0000000c00000000ULL +#define OCTEON_SLI_MEM_ACCESS_SUBID_ESW_S 34 +#define OCTEON_SLI_MEM_ACCESS_SWAP 3 +#define OCTEON_PCIE0_ENDPOINT_DEVFN PCI_DEVFN(0x0b, 0) + +#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_PCIE_CFG006 0x018 +#define OCTEON_PCIE_CFG031 0x07c +#define OCTEON_PCIE_CFG032 0x080 +#define OCTEON_PCIE_CFG068 0x110 +#define OCTEON_PCIE_CFG_STORE_BASE (1ULL << 63) + +#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) + +typedef enum OcteonIRQ { + OCTEON_IRQ_UART, + OCTEON_IRQ_PCI, + OCTEON_IRQ_USB0, + OCTEON_IRQ_USB1, + OCTEON_IRQ_COUNT, +} OcteonIRQ; + +typedef enum OcteonCiu3Source { + OCTEON_CIU3_SRC_PCI_INTA, + OCTEON_CIU3_SRC_UART0, + OCTEON_CIU3_SRC_USB0, + OCTEON_CIU3_SRC_USB1, + OCTEON_CIU3_SRC_MBOX0, + OCTEON_CIU3_SRC_COUNT = OCTEON_CIU3_SRC_MBOX0 + OCTEON_MAX_CPUS, +} OcteonCiu3Source; + +typedef struct OcteonState OcteonState; + +struct OcteonMachineState { + MachineState parent_obj; + uint64_t cpu_hz; + uint64_t ref_hz; + uint64_t io_hz; + uint64_t ddr_hz; +}; + +struct OcteonPCIHostState { + PCIHostState parent_obj; + MemoryRegion mem; + MemoryRegion io; +}; + +typedef struct OcteonCPUState { + OcteonState *board; + MIPSCPU *cpu; + bool boot_cpu; +} OcteonCPUState; + +typedef struct OcteonSpdEepromState { + unsigned int bus; + uint8_t addr; + uint16_t offset; + uint8_t data[OCTEON_SPD_EEPROM_SIZE]; +} OcteonSpdEepromState; + +typedef struct OcteonLmcState { + GHashTable *regs; +} OcteonLmcState; + +typedef struct OcteonTWSIState { + OcteonState *board; + MemoryRegion mmio; + unsigned int bus; + uint64_t sw_twsi; + uint64_t int_reg; + uint8_t ctl; + uint8_t stat; + uint8_t data; + uint8_t slave_addr; + uint8_t write_buf[2]; + unsigned int write_len; + bool have_slave; + bool read_transfer; + bool addr_phase; +} OcteonTWSIState; + +typedef struct OcteonUsbState { + OcteonState *board; + USBDWC3 *dwc3; + unsigned int index; + MemoryRegion uctl; + MemoryRegion dwc3_window; + AddressSpace dwc3_as; + GHashTable *regs; +} OcteonUsbState; + +struct OcteonState { + MachineState *machine; + Clock *cpuclk; + uint64_t cpu_hz; + uint64_t ref_hz; + uint64_t io_hz; + uint64_t ddr_hz; + OcteonCPUState cpu[OCTEON_MAX_CPUS]; + unsigned int cpu_count; + uint64_t firmware_entry; + + MemoryRegion dr0; + MemoryRegion dr1; + MemoryRegion *flash; + MemoryRegion boot_flash; + MemoryRegion boot_flash_alias; + MemoryRegion bootbus_led; + MemoryRegion mio_boot_loc; + MemoryRegion cvmseg; + MemoryRegion ciu; + MemoryRegion ciu3; + MemoryRegion csr; + MemoryRegion pexp; + MemoryRegion dpi; + MemoryRegion fpa; + MemoryRegion ipd; + MemoryRegion pko; + MemoryRegion rng; + MemoryRegion pow; + OcteonLmcState lmc[OCTEON_LMC_COUNT]; + OcteonTWSIState twsi[OCTEON_TWSI_COUNT]; + OcteonUsbState usb[OCTEON_USB_COUNT]; + OcteonSpdEepromState spd[OCTEON_SPD_EEPROM_COUNT]; + SerialMM *uart; + MemoryRegion uart_alias; + MemoryRegion uart_tx; + MemoryRegion uart_alias_tx; + MemoryRegion pcie_cfg; + MemoryRegion pcie_sli_cfg; + MemoryRegion pcie0_io; + MemoryRegion pcie0_mem; + AddressSpace pcie0_mem_as; + + 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; + 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]; + uint64_t rst_pp_power; + 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; + uint32_t pcie_cfg_rd_addr; + 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]; + + PCIBus *pci_bus; + GHashTable *csr_values; +}; + +static const uint8_t octeon_spd_eeprom_template[OCTEON_SPD_EEPROM_SIZE] = { + [0] = 0x23, [1] = 0x11, [2] = 0x0c, [3] = 0x02, + [4] = 0x82, [5] = 0x19, [11] = 0x03, [12] = 0x01, + [13] = 0x03, [18] = 0x0a, [19] = 0x20, [20] = 0x78, + [24] = 0x6e, [25] = 0x6e, [26] = 0x6e, [27] = 0x11, + [29] = 0x6e, [31] = 0x05, [32] = 0x70, [33] = 0x03, + [34] = 0xd0, [35] = 0x02, [37] = 0xa8, [38] = 0x20, + [39] = 0x27, [40] = 0x28, [126] = 0x16, [127] = 0x2f, +}; + +static guint octeon_uint64_hash(gconstpointer v) +{ + uint64_t value = *(const uint64_t *)v; + + return value ^ (value >> 32); +} + +static gboolean octeon_uint64_equal(gconstpointer a, gconstpointer b) +{ + return *(const uint64_t *)a == *(const uint64_t *)b; +} + +static 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; +} + +static 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 uint16_t octeon_spd_crc16(const uint8_t *ptr, int count) +{ + int crc = 0; + int i; + + while (--count >= 0) { + crc ^= *ptr++ << 8; + for (i = 0; i < 8; i++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; + } else { + crc <<= 1; + } + } + } + + return crc & 0xffff; +} + +static bool octeon_spd_geometry(uint64_t ram_size, int *row_bits, + int *col_bits, int *density_code) +{ + uint64_t ram_mb; + int pbank_lsb; + int row_col_bits; + int col; + int row; + int density; + + if (ram_size < OCTEON_SPD_MIN_RAM_SIZE || + ram_size > OCTEON_SPD_MAX_RAM_SIZE || + !is_power_of_2(ram_size)) { + return false; + } + + ram_mb = ram_size / MiB; + pbank_lsb = ctz64(ram_size); + row_col_bits = pbank_lsb - 3 - OCTEON_SPD_BANK_BITS; + + col = MIN(OCTEON_SPD_MAX_COL_BITS, + row_col_bits - OCTEON_SPD_MIN_ROW_BITS); + col = MAX(OCTEON_SPD_MIN_COL_BITS, col); + row = row_col_bits - col; + density = ctz64(ram_mb) - 8; + + if (row < OCTEON_SPD_MIN_ROW_BITS || + row > OCTEON_SPD_MAX_ROW_BITS || + density < 0 || density > 7) { + return false; + } + + if (row_bits) { + *row_bits = row; + } + if (col_bits) { + *col_bits = col; + } + if (density_code) { + *density_code = density; + } + return true; +} + +static void octeon_validate_ram_size(uint64_t ram_size) +{ + if (!octeon_spd_geometry(ram_size, NULL, NULL, NULL)) { + error_report("octeon3 RAM size must be a power of two from " + "%" PRIu64 " MB to %" PRIu64 " GB", + (uint64_t)OCTEON_SPD_MIN_RAM_SIZE / MiB, + (uint64_t)OCTEON_SPD_MAX_RAM_SIZE / GiB); + exit(1); + } +} + +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); + } +} + +static void octeon_spd_set_tckmin(OcteonState *s, uint8_t *spd) +{ + uint64_t tck_ps = (1000000000000ULL + s->ddr_hz - 1) / s->ddr_hz; + uint64_t mtb = tck_ps / OCTEON_SPD_DDR4_MTB_PS; + uint64_t fine = tck_ps % OCTEON_SPD_DDR4_MTB_PS; + + if (mtb == 0 || mtb > UINT8_MAX || fine > INT8_MAX) { + error_report("octeon3 DDR clock cannot be represented in DDR4 SPD"); + exit(1); + } + + spd[OCTEON_SPD_DDR4_TCKMIN] = mtb; + spd[OCTEON_SPD_DDR4_FINE_TCKMIN] = fine / OCTEON_SPD_DDR4_FTB_PS; +} + +static void octeon_init_spd(OcteonState *s) +{ + OcteonSpdEepromState *eeprom = &s->spd[0]; + uint64_t ram_size = s->machine->ram_size; + int col_bits; + int row_bits; + int density_code; + uint16_t crc; + + eeprom->bus = OCTEON_SPD_BUS; + eeprom->addr = OCTEON_SPD_ADDR; + memcpy(eeprom->data, octeon_spd_eeprom_template, + sizeof(eeprom->data)); + + g_assert(octeon_spd_geometry(ram_size, &row_bits, &col_bits, + &density_code)); + + eeprom->data[4] = 0x80 | density_code; + eeprom->data[5] = ((row_bits - OCTEON_SPD_MIN_ROW_BITS) << 3) | + (col_bits - OCTEON_SPD_MIN_COL_BITS); + octeon_spd_set_tckmin(s, eeprom->data); + + crc = octeon_spd_crc16(eeprom->data, + (eeprom->data[0] & 0x80) ? 117 : 126); + eeprom->data[126] = crc & 0xff; + eeprom->data[127] = crc >> 8; +} + +static bool octeon_reg_lookup(GHashTable *regs, uint64_t reg, uint64_t *value) +{ + uint64_t *stored = g_hash_table_lookup(regs, ®); + + if (!stored) { + return false; + } + + *value = *stored; + return true; +} + +static 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_csr_lookup(OcteonState *s, uint64_t reg, uint64_t *value) +{ + return octeon_reg_lookup(s->csr_values, reg, value); +} + +static void octeon_csr_store(OcteonState *s, uint64_t reg, uint64_t value) +{ + octeon_reg_store(s->csr_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_boot_loc_cfg[0] & OCTEON_MIO_BOOT_LOC_CFG_EN; + hwaddr base = octeon_mio_boot_loc_base(s->mio_boot_loc_cfg[0]); + + if (s->mio_boot_loc_mapped) { + if (enabled && base == s->mio_boot_loc_base) { + return; + } + memory_region_del_subregion(sysmem, &s->mio_boot_loc); + s->mio_boot_loc_mapped = false; + } + + if (enabled) { + memory_region_add_subregion_overlap(sysmem, base, + &s->mio_boot_loc, 1); + s->mio_boot_loc_base = base; + s->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_boot_reg_cfg[OCTEON_BOOTBUS_LED_CS]); + + if (s->bootbus_led_mapped) { + if (base == s->bootbus_led_base) { + return; + } + memory_region_del_subregion(sysmem, &s->bootbus_led); + s->bootbus_led_mapped = false; + } + + if (base) { + memory_region_add_subregion(sysmem, base, &s->bootbus_led); + s->bootbus_led_base = base; + s->bootbus_led_mapped = true; + } +} + +static uint64_t octeon_mio_boot_loc_dat_read(OcteonState *s) +{ + unsigned int offset = s->mio_boot_loc_adr & OCTEON_MIO_BOOT_LOC_ADR_MASK; + uint64_t value = ldq_be_p(s->mio_boot_loc_data + offset); + + s->mio_boot_loc_adr = + (s->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_boot_loc_adr & OCTEON_MIO_BOOT_LOC_ADR_MASK; + uint64_t old = ldq_be_p(s->mio_boot_loc_data + offset); + + value = octeon_write64(old, addr, value, size); + stq_be_p(s->mio_boot_loc_data + offset, value); + memory_region_set_dirty(&s->mio_boot_loc, offset, sizeof(value)); + s->mio_boot_loc_adr = + (s->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->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->mio_emm_buf, 0, sizeof(s->mio_emm_buf)); + if (bus8) { + s->mio_emm_buf[0] = OCTEON_MMC_BUSTEST_8_RESP0; + s->mio_emm_buf[1] = OCTEON_MMC_BUSTEST_8_RESP1; + } else if (bus4) { + s->mio_emm_buf[0] = OCTEON_MMC_BUSTEST_4_RESP0; + } else if (bus1) { + s->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->mio_emm_rsp_sts &= ~(OCTEON_MIO_EMM_RSP_STS_DMA_VAL | + OCTEON_MIO_EMM_RSP_STS_DMA_PEND); + s->mio_emm_dma_int |= OCTEON_MIO_EMM_DMA_INT_DONE; + s->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->mio_emm_rsp_lo = 0; + s->mio_emm_rsp_hi = 0; + s->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->mio_emm_rsp_sts |= OCTEON_MIO_EMM_RSP_STS_RSP_TIMEOUT; + break; + } + + s->mio_emm_int |= OCTEON_MIO_EMM_INT_CMD_DONE; + if (s->mio_emm_rsp_sts & OCTEON_MIO_EMM_RSP_STS_RSP_TIMEOUT) { + s->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->mio_emm_rsp_sts; + break; + case OCTEON_MIO_EMM_RSP_LO: + value = s->mio_emm_rsp_lo; + break; + case OCTEON_MIO_EMM_RSP_HI: + value = s->mio_emm_rsp_hi; + break; + case OCTEON_MIO_EMM_BUF_DAT: + value = ldq_be_p(s->mio_emm_buf + s->mio_emm_buf_idx); + if (s->mio_emm_buf_inc) { + s->mio_emm_buf_idx = + (s->mio_emm_buf_idx + OCTEON_MIO_EMM_BUF_WORD_SIZE) % + OCTEON_MIO_EMM_BUF_SIZE; + } + break; + case OCTEON_MIO_EMM_INT: + value = s->mio_emm_int; + break; + case OCTEON_MIO_EMM_DMA_INT: + value = s->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->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->mio_emm_int &= ~clear; + return; + case OCTEON_MIO_EMM_DMA_INT: + clear = octeon_write64(0, addr, value, size); + s->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->mio_emm_buf_idx = (value & OCTEON_MIO_EMM_BUF_IDX_OFF_MASK) * + OCTEON_MIO_EMM_BUF_WORD_SIZE; + s->mio_emm_buf_inc = value & OCTEON_MIO_EMM_BUF_IDX_INC; + return; + case OCTEON_MIO_EMM_BUF_DAT: + old = ldq_be_p(s->mio_emm_buf + s->mio_emm_buf_idx); + value = octeon_write64(old, addr, value, size); + stq_be_p(s->mio_emm_buf + s->mio_emm_buf_idx, value); + if (s->mio_emm_buf_inc) { + s->mio_emm_buf_idx = + (s->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 uint32_t octeon_pcie_root_cfg_default(uint32_t reg) +{ + switch (reg) { + case OCTEON_PCIE_CFG006: + return 0x00010101; + case OCTEON_PCIE_CFG031: + return 0x00000011; + case OCTEON_PCIE_CFG032: + return 0x20110000; + default: + return 0; + } +} + +static uint32_t octeon_pcie_root_cfg_read(OcteonState *s, uint32_t reg) +{ + uint64_t value; + + reg &= ~3U; + switch (reg) { + case OCTEON_PCIE_CFG006: + case OCTEON_PCIE_CFG031: + case OCTEON_PCIE_CFG032: + return octeon_pcie_root_cfg_default(reg); + default: + break; + } + + if (octeon_csr_lookup(s, OCTEON_PCIE_CFG_STORE_BASE | reg, &value)) { + return value; + } + + return octeon_pcie_root_cfg_default(reg); +} + +static void octeon_pcie_root_cfg_write(OcteonState *s, uint32_t reg, + uint32_t value) +{ + reg &= ~3U; + if (reg == OCTEON_PCIE_CFG068) { + value = 0; + } + + octeon_csr_store(s, OCTEON_PCIE_CFG_STORE_BASE | reg, value); +} + +static void octeon_init_lmcs(OcteonState *s) +{ + unsigned int i; + + for (i = 0; i < OCTEON_LMC_COUNT; i++) { + s->lmc[i].regs = g_hash_table_new_full(octeon_uint64_hash, + octeon_uint64_equal, + g_free, g_free); + } +} + +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->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->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->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->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->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->ciu3_src_ctl[src]) | OCTEON_CIU3_ISC_CTL_IMP; + if (qatomic_read(&s->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->ciu3_src_ctl[src]); + unsigned int idt; + unsigned int output; + + if (!(qatomic_read(&s->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->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->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->ciu3_src_cursor[cpu_index][irq_index]), + intsn, &src)) { + qatomic_set(&s->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->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->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->ciu_mbox[cpu_index]) && + (qatomic_read(&s->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->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->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->ciu_gpio_bit_cfg[i]) & + OCTEON_CIU_GPIO_BIT_CFG_TX_OE) { + if (qatomic_read(&s->ciu_gpio_tx) & mask) { + value |= mask; + } + } else if (qatomic_read(&s->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->uart_pending)) { + value |= OCTEON_CIU_SUM_UART; + } + if (qatomic_read(&s->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->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->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->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->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->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->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->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->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->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->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->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->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_pp_power; + uint64_t changed; + unsigned int cpu; + + value = octeon_write64(old, addr, value, size) & + octeon_present_cpu_mask(s); + s->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_NATIVE_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->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->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->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->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->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->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->ciu3_idt_ctl[idt], addr, value, size, + UINT64_MAX, 0, NULL); + octeon_intc_update_cpu_mask(s, qatomic_read(&s->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->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->ciu3_idt_io[idt], addr, value, size, + UINT64_MAX, 0, NULL); + octeon_intc_update_cpu_mask(s, qatomic_read(&s->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->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->ciu3_src_ctl[src]); + if (value & OCTEON_CIU3_ISC_CTL_EN) { + qatomic_and(&s->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->ciu3_src_ctl[src]); + if (value & OCTEON_CIU3_ISC_CTL_EN) { + qatomic_or(&s->ciu3_src_ctl[src], OCTEON_CIU3_ISC_CTL_EN); + } + if (value & OCTEON_CIU3_ISC_CTL_RAW) { + qatomic_or(&s->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_NATIVE_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_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_boot_loc_cfg[index], addr, size); + } + case OCTEON_MIO_BOOT_LOC_ADR: + return octeon_read64(s->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_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_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: + value = ((uint64_t)octeon_pcie_root_cfg_read(s, + s->pcie_cfg_rd_addr) << + 32) | s->pcie_cfg_rd_addr; + return octeon_read64(value, 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_boot_loc_cfg[index]; + s->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_boot_loc_adr; + value = octeon_write64(old, addr, value, size); + s->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_fus_rcmd, addr, value, size); + s->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) { + if (!octeon_csr_lookup(s, reg, &old)) { + old = 0; + } + value = octeon_write64(old, addr, value, size); + s->pcie_cfg_rd_addr = value; + return; + } + + if (reg == OCTEON_PEMX_CFG_WR0) { + if (!octeon_csr_lookup(s, reg, &old)) { + old = 0; + } + value = octeon_write64(old, addr, value, size); + octeon_pcie_root_cfg_write(s, value, value >> 32); + octeon_csr_store(s, reg, value); + 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_boot_reg_cfg[index]; + s->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_NATIVE_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_stateful_io_read(opaque, OCTEON_PEXP_BASE, addr, size); +} + +static void octeon_pexp_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + octeon_stateful_io_write(opaque, OCTEON_PEXP_BASE, addr, value, size); +} + +static const MemoryRegionOps octeon_pexp_ops = { + .read = octeon_pexp_read, + .write = octeon_pexp_write, + .endianness = DEVICE_NATIVE_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_NATIVE_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_NATIVE_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_NATIVE_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_NATIVE_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_NATIVE_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_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, +}; + +static uint64_t octeon_uctl_read(void *opaque, hwaddr addr, unsigned size) +{ + OcteonUsbState *usb = opaque; + hwaddr reg = addr & ~7ULL; + uint64_t value; + + if (!octeon_reg_lookup(usb->regs, reg, &value)) { + value = 0; + } + + return octeon_read64(value, addr, size); +} + +static void octeon_uctl_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + OcteonUsbState *usb = opaque; + hwaddr reg = addr & ~7ULL; + uint64_t old; + + if (!octeon_reg_lookup(usb->regs, reg, &old)) { + old = 0; + } + + octeon_reg_store(usb->regs, reg, octeon_write64(old, addr, value, size)); +} + +static const MemoryRegionOps octeon_uctl_ops = { + .read = octeon_uctl_read, + .write = octeon_uctl_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, +}; + +static bool octeon_usb_csr_needs_swap(OcteonUsbState *usb) +{ +#if TARGET_BIG_ENDIAN + uint64_t value; + + if (!octeon_reg_lookup(usb->regs, OCTEON_UCTL_SHIM_CFG, &value)) { + return true; + } + + return (value & OCTEON_UCTL_SHIM_CFG_CSR_BYTE_SWAP) != + OCTEON_UCTL_SHIM_CFG_CSR_NATIVE; +#else + return false; +#endif +} + +static uint64_t octeon_usb_swap(uint64_t value, unsigned size) +{ + switch (size) { + case 2: + return bswap16(value); + case 4: + return bswap32(value); + case 8: + return bswap64(value); + default: + return value; + } +} + +static uint64_t octeon_dwc3_window_read(void *opaque, hwaddr addr, + unsigned size) +{ + OcteonUsbState *usb = opaque; + uint64_t value; + + switch (size) { + case 1: + value = address_space_ldub(&usb->dwc3_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 2: + value = address_space_lduw_le(&usb->dwc3_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 4: + value = address_space_ldl_le(&usb->dwc3_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 8: + value = address_space_ldq_le(&usb->dwc3_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + default: + g_assert_not_reached(); + } + + if (octeon_usb_csr_needs_swap(usb)) { + value = octeon_usb_swap(value, size); + } + + return value; +} + +static void octeon_dwc3_window_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + OcteonUsbState *usb = opaque; + + if (octeon_usb_csr_needs_swap(usb)) { + value = octeon_usb_swap(value, size); + } + + switch (size) { + case 1: + address_space_stb(&usb->dwc3_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 2: + address_space_stw_le(&usb->dwc3_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 4: + address_space_stl_le(&usb->dwc3_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 8: + address_space_stq_le(&usb->dwc3_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps octeon_dwc3_window_ops = { + .read = octeon_dwc3_window_read, + .write = octeon_dwc3_window_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static OcteonSpdEepromState *octeon_twsi_find_spd(OcteonTWSIState *t) +{ + OcteonState *s = t->board; + unsigned int i; + + for (i = 0; i < OCTEON_SPD_EEPROM_COUNT; i++) { + if (s->spd[i].bus == t->bus && s->spd[i].addr == t->slave_addr) { + return &s->spd[i]; + } + } + + return NULL; +} + +static uint8_t octeon_spd_eeprom_read(OcteonSpdEepromState *eeprom) +{ + uint8_t value = 0xff; + + if (eeprom->offset < sizeof(eeprom->data)) { + value = eeprom->data[eeprom->offset]; + } + eeprom->offset++; + return value; +} + +static void octeon_twsi_write_ctl(OcteonTWSIState *t, uint8_t value) +{ + OcteonSpdEepromState *eeprom; + + t->ctl = value | OCTEON_TWSI_CTL_IFLG; + + if (value & OCTEON_TWSI_CTL_STA) { + t->have_slave = false; + t->addr_phase = false; + t->write_len = 0; + t->stat = OCTEON_TWSI_STAT_START; + } else if (value & OCTEON_TWSI_CTL_STP) { + t->have_slave = false; + t->addr_phase = false; + t->stat = OCTEON_TWSI_STAT_IDLE; + } else if (t->addr_phase) { + eeprom = octeon_twsi_find_spd(t); + t->addr_phase = false; + if (eeprom) { + t->stat = t->read_transfer ? OCTEON_TWSI_STAT_RXADDR_ACK : + OCTEON_TWSI_STAT_TXADDR_ACK; + } else { + t->stat = t->read_transfer ? OCTEON_TWSI_STAT_RXADDR_NAK : + OCTEON_TWSI_STAT_TXADDR_NAK; + } + } else if (t->read_transfer) { + t->stat = (value & OCTEON_TWSI_CTL_AAK) ? + OCTEON_TWSI_STAT_RXDATA_ACK : OCTEON_TWSI_STAT_RXDATA_NAK; + } else { + t->stat = octeon_twsi_find_spd(t) ? OCTEON_TWSI_STAT_TXDATA_ACK : + OCTEON_TWSI_STAT_TXDATA_NAK; + } +} + +static void octeon_twsi_write_data(OcteonTWSIState *t, uint8_t value) +{ + OcteonSpdEepromState *eeprom; + + t->data = value; + + if (!t->have_slave) { + t->slave_addr = value >> 1; + t->read_transfer = value & 1; + t->have_slave = true; + t->addr_phase = true; + t->write_len = 0; + return; + } + + if (!t->read_transfer && t->write_len < ARRAY_SIZE(t->write_buf)) { + t->write_buf[t->write_len++] = value; + eeprom = octeon_twsi_find_spd(t); + if (eeprom) { + if (t->write_len == 1) { + eeprom->offset = value; + } else if (t->write_len == ARRAY_SIZE(t->write_buf)) { + eeprom->offset = (t->write_buf[0] << 8) | t->write_buf[1]; + } + } + } +} + +static uint8_t octeon_twsi_read_reg(OcteonTWSIState *t, unsigned int reg) +{ + switch (reg) { + case OCTEON_TWSI_EOP_DATA: + if (t->read_transfer) { + OcteonSpdEepromState *eeprom = octeon_twsi_find_spd(t); + + t->data = eeprom ? octeon_spd_eeprom_read(eeprom) : 0xff; + } + return t->data; + case OCTEON_TWSI_EOP_CTL: + return t->ctl; + case OCTEON_TWSI_EOP_STAT: + return t->stat; + default: + return 0; + } +} + +static void octeon_twsi_write_reg(OcteonTWSIState *t, unsigned int reg, + uint8_t value) +{ + switch (reg) { + case OCTEON_TWSI_EOP_DATA: + octeon_twsi_write_data(t, value); + break; + case OCTEON_TWSI_EOP_CTL: + octeon_twsi_write_ctl(t, value); + break; + default: + break; + } +} + +static uint64_t octeon_twsi_exec(OcteonTWSIState *t, uint64_t value) +{ + unsigned int op = (value >> OCTEON_TWSI_SW_OP_SHIFT) & 0xf; + unsigned int reg = (value >> OCTEON_TWSI_SW_EOP_IA_SHIFT) & 0x7; + + if (!(value & OCTEON_TWSI_SW_V)) { + return value; + } + + if (op == OCTEON_TWSI_OP_EOP_IA) { + if (value & OCTEON_TWSI_SW_R) { + value &= ~OCTEON_TWSI_SW_DATA_MASK; + value |= octeon_twsi_read_reg(t, reg); + } else { + octeon_twsi_write_reg(t, reg, value & 0xff); + } + } + + return value & ~OCTEON_TWSI_SW_V; +} + +static uint64_t octeon_twsi_read(void *opaque, hwaddr addr, unsigned size) +{ + OcteonTWSIState *t = opaque; + hwaddr reg = addr & ~7ULL; + + switch (reg) { + case OCTEON_TWSI_SW_TWSI: + return octeon_read64(t->sw_twsi, addr, size); + case OCTEON_TWSI_INT: + return octeon_read64(t->int_reg, addr, size); + default: + return 0; + } +} + +static void octeon_twsi_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + OcteonTWSIState *t = opaque; + hwaddr reg = addr & ~7ULL; + uint64_t old = 0; + + switch (reg) { + case OCTEON_TWSI_SW_TWSI: + old = t->sw_twsi; + value = octeon_write64(old, addr, value, size); + if (size == 4 && !(addr & 4)) { + t->sw_twsi = value; + } else { + t->sw_twsi = octeon_twsi_exec(t, value); + } + break; + case OCTEON_TWSI_INT: + old = t->int_reg; + t->int_reg = octeon_write64(old, addr, value, size); + break; + default: + break; + } +} + +static const MemoryRegionOps octeon_twsi_ops = { + .read = octeon_twsi_read, + .write = octeon_twsi_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 8, + }, +}; + +static 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->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->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 uint64_t octeon_pci_absent(unsigned size) +{ + return size == 1 ? 0xff : size == 2 ? 0xffff : 0xffffffffU; +} + +static bool octeon_pcie_config_addr(hwaddr addr, uint32_t *pci_addr, + bool sli_cfg) +{ + unsigned int bus = (addr >> 20) & 0xff; + unsigned int dev = (addr >> 15) & 0x1f; + unsigned int fn = (addr >> 12) & 0x7; + unsigned int reg = addr & 0xfff; + + if (sli_cfg) { + if (addr >= OCTEON_PCIE_SLI_CFG_PORT_SIZE || + bus != 0 || dev != 0 || fn != 0 || + reg >= PCI_CONFIG_SPACE_SIZE) { + return false; + } + } else if (bus != 1 || dev != 0 || fn != 0 || + reg >= PCI_CONFIG_SPACE_SIZE) { + return false; + } + + *pci_addr = 0x80000000U | (OCTEON_PCIE0_ENDPOINT_DEVFN << 8) | reg; + return true; +} + +static uint64_t octeon_pcie_cfg_read(void *opaque, hwaddr addr, unsigned size) +{ + OcteonState *s = opaque; + uint32_t pci_addr; + + if (!octeon_pcie_config_addr(addr, &pci_addr, false)) { + return octeon_pci_absent(size); + } + + return pci_data_read(s->pci_bus, pci_addr, size); +} + +static void octeon_pcie_cfg_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + OcteonState *s = opaque; + uint32_t pci_addr; + + if (octeon_pcie_config_addr(addr, &pci_addr, false)) { + pci_data_write(s->pci_bus, pci_addr, value, size); + } +} + +static const MemoryRegionOps octeon_pcie_cfg_ops = { + .read = octeon_pcie_cfg_read, + .write = octeon_pcie_cfg_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static uint64_t octeon_pcie_sli_cfg_read(void *opaque, hwaddr addr, + unsigned size) +{ + OcteonState *s = opaque; + uint32_t pci_addr; + + if (!octeon_pcie_config_addr(addr, &pci_addr, true)) { + return octeon_pci_absent(size); + } + + return pci_data_read(s->pci_bus, pci_addr, size); +} + +static void octeon_pcie_sli_cfg_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + OcteonState *s = opaque; + uint32_t pci_addr; + + if (octeon_pcie_config_addr(addr, &pci_addr, true)) { + pci_data_write(s->pci_bus, pci_addr, value, size); + } +} + +static const MemoryRegionOps octeon_pcie_sli_cfg_ops = { + .read = octeon_pcie_sli_cfg_read, + .write = octeon_pcie_sli_cfg_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static uint64_t octeon_pcie_bswap(uint64_t value, unsigned size) +{ + switch (size) { + case 1: + return value; + case 2: + return bswap16(value); + case 4: + return bswap32(value); + case 8: + return bswap64(value); + default: + g_assert_not_reached(); + } +} + +static uint64_t octeon_pcie_mem_subid_reg(hwaddr addr) +{ + unsigned int subid = (addr >> 28) & 3; + + return OCTEON_PEXP_BASE + OCTEON_SLI_MEM_ACCESS_SUBID_BASE + + subid * OCTEON_SLI_MEM_ACCESS_SUBID_STRIDE; +} + +static bool octeon_pcie_mem_swap_enabled(OcteonState *s, hwaddr addr, + bool write) +{ + uint64_t value; + uint64_t mask = write ? OCTEON_SLI_MEM_ACCESS_SUBID_ESW_M : + OCTEON_SLI_MEM_ACCESS_SUBID_ESR_M; + unsigned int shift = write ? OCTEON_SLI_MEM_ACCESS_SUBID_ESW_S : + OCTEON_SLI_MEM_ACCESS_SUBID_ESR_S; + + if (!octeon_csr_lookup(s, octeon_pcie_mem_subid_reg(addr), &value)) { + return false; + } + + return ((value & mask) >> shift) == OCTEON_SLI_MEM_ACCESS_SWAP; +} + +static uint64_t octeon_pcie0_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + OcteonState *s = opaque; + uint64_t value; + + switch (size) { + case 1: + value = address_space_ldub(&s->pcie0_mem_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 2: + value = address_space_lduw_le(&s->pcie0_mem_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 4: + value = address_space_ldl_le(&s->pcie0_mem_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 8: + value = address_space_ldq_le(&s->pcie0_mem_as, addr, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + default: + g_assert_not_reached(); + } + + if (TARGET_BIG_ENDIAN && !octeon_pcie_mem_swap_enabled(s, addr, false)) { + value = octeon_pcie_bswap(value, size); + } + + return value; +} + +static void octeon_pcie0_mem_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + OcteonState *s = opaque; + + if (TARGET_BIG_ENDIAN && !octeon_pcie_mem_swap_enabled(s, addr, true)) { + value = octeon_pcie_bswap(value, size); + } + + switch (size) { + case 1: + address_space_stb(&s->pcie0_mem_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 2: + address_space_stw_le(&s->pcie0_mem_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 4: + address_space_stl_le(&s->pcie0_mem_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + case 8: + address_space_stq_le(&s->pcie0_mem_as, addr, value, + MEMTXATTRS_UNSPECIFIED, NULL); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps octeon_pcie0_mem_ops = { + .read = octeon_pcie0_mem_read, + .write = octeon_pcie0_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + }, +}; + +static int octeon_pci_map_irq(PCIDevice *pci_dev, int irq_num) +{ + return 0; +} + +static void octeon_pci_set_irq(void *opaque, int irq_num, int level) +{ + octeon_irq_set(opaque, OCTEON_IRQ_PCI, level); +} + +static void octeon_pci_host_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->fw_name = "pci"; + dc->user_creatable = false; +} + +static const TypeInfo octeon_pci_host_info = { + .name = TYPE_OCTEON_PCI_HOST, + .parent = TYPE_PCI_HOST_BRIDGE, + .instance_size = sizeof(OcteonPCIHostState), + .class_init = octeon_pci_host_class_init, +}; + +static void octeon_pci_host_register_types(void) +{ + type_register_static(&octeon_pci_host_info); +} + +type_init(octeon_pci_host_register_types) + +static void octeon_cpu_reset(void *opaque) +{ + OcteonCPUState *cs = opaque; + CPUMIPSState *env = &cs->cpu->env; + CPUState *cpu = CPU(cs->cpu); + + cpu_reset(cpu); + + 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->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_reset(octeon_cpu_reset, &s->cpu[i]); + } +} + +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->bootbus_led, NULL, "octeon.bootbus-led", + OCTEON_BOOTBUS_LED_SIZE, &error_fatal); + memory_region_init_ram_ptr(&s->mio_boot_loc, NULL, + "octeon.mio-boot-loc", + OCTEON_MIO_BOOT_LOC_SIZE, + s->mio_boot_loc_data); +} + +static void octeon_init_pci(OcteonState *s) +{ + DeviceState *dev; + OcteonPCIHostState *host; + PCIHostState *phb; + + dev = qdev_new(TYPE_OCTEON_PCI_HOST); + host = OCTEON_PCI_HOST(dev); + phb = PCI_HOST_BRIDGE(dev); + + memory_region_init(&host->mem, OBJECT(dev), "octeon.pci-mem", + OCTEON_PCIE0_MEM_SIZE); + memory_region_init(&host->io, OBJECT(dev), "octeon.pci-io", + OCTEON_PCIE0_IO_SIZE); + + phb->bus = pci_root_bus_new(dev, "pci", &host->mem, &host->io, + OCTEON_PCIE0_ENDPOINT_DEVFN, TYPE_PCI_BUS); + s->pci_bus = phb->bus; + pci_bus_irqs(s->pci_bus, octeon_pci_set_irq, s, 1); + pci_bus_map_irqs(s->pci_bus, octeon_pci_map_irq); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + memory_region_init_io(&s->pcie_cfg, NULL, &octeon_pcie_cfg_ops, s, + "octeon.pcie-config", OCTEON_PCIE_CFG_SIZE); + memory_region_add_subregion(get_system_memory(), OCTEON_PCIE_CFG_BASE, + &s->pcie_cfg); + + memory_region_init_io(&s->pcie_sli_cfg, NULL, &octeon_pcie_sli_cfg_ops, + s, "octeon.pcie-sli-config", + OCTEON_PCIE_SLI_CFG_SIZE); + memory_region_add_subregion(get_system_memory(), + OCTEON_PCIE_SLI_CFG_BASE, + &s->pcie_sli_cfg); + + memory_region_init_alias(&s->pcie0_io, NULL, "octeon.pcie0-io", + &host->io, 0, + OCTEON_PCIE0_IO_SIZE); + memory_region_add_subregion(get_system_memory(), OCTEON_PCIE0_IO_BASE, + &s->pcie0_io); + + address_space_init(&s->pcie0_mem_as, &host->mem, + "octeon.pcie0-mem-as"); + memory_region_init_io(&s->pcie0_mem, NULL, &octeon_pcie0_mem_ops, s, + "octeon.pcie0-mem", OCTEON_PCIE0_MEM_SIZE); + memory_region_add_subregion(get_system_memory(), OCTEON_PCIE0_MEM_BASE, + &s->pcie0_mem); +} + +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_NATIVE_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_NATIVE_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_init_twsi(OcteonState *s) +{ + static const uint64_t base[OCTEON_TWSI_COUNT] = { + OCTEON_TWSI0_BASE, + OCTEON_TWSI1_BASE, + }; + static const char *const name[OCTEON_TWSI_COUNT] = { + "octeon.twsi0", + "octeon.twsi1", + }; + unsigned int i; + + for (i = 0; i < OCTEON_TWSI_COUNT; i++) { + s->twsi[i].board = s; + s->twsi[i].bus = i; + s->twsi[i].stat = OCTEON_TWSI_STAT_IDLE; + + memory_region_init_io(&s->twsi[i].mmio, NULL, &octeon_twsi_ops, + &s->twsi[i], name[i], OCTEON_TWSI_SIZE); + memory_region_add_subregion(get_system_memory(), base[i], + &s->twsi[i].mmio); + } +} + +static void octeon_init_usb(OcteonState *s) +{ + static const uint64_t uctl_base[OCTEON_USB_COUNT] = { + OCTEON_USB0_UCTL_BASE, + OCTEON_USB1_UCTL_BASE, + }; + static const uint64_t dwc3_base[OCTEON_USB_COUNT] = { + OCTEON_USB0_DWC3_BASE, + OCTEON_USB1_DWC3_BASE, + }; + static const char * const uctl_name[OCTEON_USB_COUNT] = { + "octeon.usb0-uctl", + "octeon.usb1-uctl", + }; + static const char * const dwc3_window_name[OCTEON_USB_COUNT] = { + "octeon.usb0-dwc3-window", + "octeon.usb1-dwc3-window", + }; + static const char * const dwc3_as_name[OCTEON_USB_COUNT] = { + "octeon.usb0-dwc3-as", + "octeon.usb1-dwc3-as", + }; + static const OcteonIRQ irq[OCTEON_USB_COUNT] = { + OCTEON_IRQ_USB0, + OCTEON_IRQ_USB1, + }; + unsigned int i; + + for (i = 0; i < OCTEON_USB_COUNT; i++) { + OcteonUsbState *usb = &s->usb[i]; + DeviceState *dev = qdev_new(TYPE_USB_DWC3); + USBDWC3 *dwc3 = USB_DWC3(dev); + MemoryRegion *dwc3_mr; + + usb->board = s; + usb->dwc3 = dwc3; + usb->index = i; + usb->regs = g_hash_table_new_full(octeon_uint64_hash, + octeon_uint64_equal, + g_free, g_free); + memory_region_init_io(&usb->uctl, NULL, &octeon_uctl_ops, usb, + uctl_name[i], OCTEON_UCTL_SIZE); + memory_region_add_subregion(get_system_memory(), uctl_base[i], + &usb->uctl); + + object_property_set_link(OBJECT(&dwc3->sysbus_xhci), "dma", + OBJECT(get_system_memory()), &error_abort); + qdev_prop_set_uint32(DEVICE(&dwc3->sysbus_xhci), "intrs", 1); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + dwc3_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); + address_space_init(&usb->dwc3_as, dwc3_mr, dwc3_as_name[i]); + memory_region_init_io(&usb->dwc3_window, NULL, + &octeon_dwc3_window_ops, usb, + dwc3_window_name[i], DWC3_SIZE); + memory_region_add_subregion(get_system_memory(), dwc3_base[i], + &usb->dwc3_window); + sysbus_connect_irq(SYS_BUS_DEVICE(&dwc3->sysbus_xhci), 0, + qemu_allocate_irq(octeon_irq_set, s, irq[i])); + } +} + +static void octeon_board_reset(void *opaque) +{ + OcteonState *s = opaque; + unsigned int i; + + if (s->bootbus_led_mapped) { + memory_region_del_subregion(get_system_memory(), &s->bootbus_led); + s->bootbus_led_mapped = false; + } + memset(s->mio_boot_reg_cfg, 0, sizeof(s->mio_boot_reg_cfg)); + s->mio_boot_reg_cfg[0] = OCTEON_MIO_BOOT_REG_CFG_RESET_BASE; + s->mio_boot_loc_cfg[0] = 0; + s->mio_boot_loc_cfg[1] = 0; + s->mio_boot_loc_adr = 0; + s->mio_fus_rcmd = 0; + s->mio_emm_rsp_sts = 0; + s->mio_emm_rsp_lo = 0; + s->mio_emm_rsp_hi = 0; + s->mio_emm_int = 0; + s->mio_emm_dma_int = 0; + s->mio_emm_buf_idx = 0; + s->mio_emm_buf_inc = false; + memset(s->mio_emm_buf, 0, sizeof(s->mio_emm_buf)); + s->rst_pp_power = octeon_secondary_cpu_mask(s); + memset(s->mio_boot_loc_data, 0, sizeof(s->mio_boot_loc_data)); + octeon_mio_boot_loc_update(s); + + s->pcie_cfg_rd_addr = 0; + memset(s->ciu_mbox, 0, sizeof(s->ciu_mbox)); + memset(s->ciu_ipi_en, 0, sizeof(s->ciu_ipi_en)); + qatomic_set(&s->ciu_gpio_rx, OCTEON_CIU_GPIO_INPUTS); + qatomic_set(&s->ciu_gpio_tx, 0); + memset(s->ciu_gpio_bit_cfg, 0, sizeof(s->ciu_gpio_bit_cfg)); + qatomic_set(&s->uart_pending, false); + qatomic_set(&s->pci_pending, false); + qatomic_set(&s->ciu3_pp_rst, octeon_secondary_cpu_mask(s)); + memset(s->ciu3_src_state, 0, sizeof(s->ciu3_src_state)); + memset(s->ciu3_src_ctl, 0, sizeof(s->ciu3_src_ctl)); + memset(s->ciu3_idt_ctl, 0, sizeof(s->ciu3_idt_ctl)); + memset(s->ciu3_idt_pp, 0, sizeof(s->ciu3_idt_pp)); + memset(s->ciu3_idt_io, 0, sizeof(s->ciu3_idt_io)); + memset(s->ciu3_src_cursor, 0, sizeof(s->ciu3_src_cursor)); + memset(s->ciu3_irq_line, 0, sizeof(s->ciu3_irq_line)); + + for (i = 0; i < OCTEON_LMC_COUNT; i++) { + g_hash_table_remove_all(s->lmc[i].regs); + } + for (i = 0; i < OCTEON_TWSI_COUNT; i++) { + OcteonTWSIState *t = &s->twsi[i]; + + t->sw_twsi = 0; + t->int_reg = 0; + t->ctl = 0; + t->stat = OCTEON_TWSI_STAT_IDLE; + t->data = 0; + t->slave_addr = 0; + t->write_len = 0; + t->have_slave = false; + t->read_transfer = false; + t->addr_phase = false; + memset(t->write_buf, 0, sizeof(t->write_buf)); + } + for (i = 0; i < OCTEON_USB_COUNT; i++) { + g_hash_table_remove_all(s->usb[i].regs); + } + for (i = 0; i < OCTEON_SPD_EEPROM_COUNT; i++) { + s->spd[i].offset = 0; + } + + g_hash_table_remove_all(s->csr_values); + octeon_intc_update_all_cpus(s); +} + +static void mips_octeon_init(MachineState *machine) +{ + OcteonMachineState *oms = OCTEON_MACHINE(machine); + OcteonState *s = g_new0(OcteonState, 1); + + octeon_validate_clocks(oms); + + 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; + s->csr_values = g_hash_table_new_full(octeon_uint64_hash, + octeon_uint64_equal, + g_free, g_free); + + if (machine->kernel_filename) { + error_report("-kernel is not implemented for octeon3; " + "boot via -bios and U-Boot"); + exit(1); + } + + octeon_init_lmcs(s); + 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->ciu, NULL, &octeon_ciu_ops, s, + "octeon.ciu", OCTEON_CIU_SIZE); + memory_region_add_subregion(get_system_memory(), OCTEON_CIU_BASE, + &s->ciu); + + memory_region_init_io(&s->ciu3, NULL, &octeon_ciu3_ops, s, + "octeon.ciu3", OCTEON_CIU3_SIZE); + memory_region_add_subregion(get_system_memory(), OCTEON_CIU3_BASE, + &s->ciu3); + + octeon_init_uart(s); + octeon_init_twsi(s); + + memory_region_init_io(&s->csr, NULL, &octeon_csr_ops, s, + "octeon.csr", OCTEON_CSR_SIZE); + memory_region_add_subregion_overlap(get_system_memory(), OCTEON_CSR_BASE, + &s->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); + qemu_register_reset(octeon_board_reset, s); +} + +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->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_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) diff --git a/tests/functional/mips64/meson.build b/tests/functional/mips64/meson.build index 3ff2118987..98b7c491a1 100644 --- a/tests/functional/mips64/meson.build +++ b/tests/functional/mips64/meson.build @@ -2,9 +2,11 @@ test_mips64_timeouts = { 'malta' : 240, + 'octeon3' : 300, } tests_mips64_system_thorough = [ 'malta', + 'octeon3', 'tuxrun', ] diff --git a/tests/functional/mips64/test_octeon3.py b/tests/functional/mips64/test_octeon3.py new file mode 100644 index 0000000000..9c2bc67ac4 --- /dev/null +++ b/tests/functional/mips64/test_octeon3.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# +# Functional test for the Cavium Octeon III / EBB7304 machine. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import shutil + +from qemu_test import QemuSystemTest, Asset +from qemu_test import wait_for_console_pattern +from qemu_test import exec_command_and_wait_for_pattern as ec_and_wait + + +class Octeon3Machine(QemuSystemTest): + + ASSET_UBOOT = Asset( + 'https://kirill.korins.ky/pub/qemu-mips64-openbsd/u-boot.bin', + '7e519b2e52ba44b27bfafd16738c8f485e9eda5438b4899cf6a70520a6d34396') + + ASSET_BSD_RD = Asset( + 'https://kirill.korins.ky/pub/qemu-mips64-openbsd/bsd.rd', + '907f26f0ff67390dd21dc85e2c854fc0e65db295b15d47ae92e1927f98ac5872') + + def test_openbsd_ramdisk_tftp(self): + self.require_netdev('user') + self.require_device('e1000') + self.set_machine('octeon3') + + tftp_dir = self.scratch_file('tftp') + os.mkdir(tftp_dir) + shutil.copy(self.ASSET_BSD_RD.fetch(), + os.path.join(tftp_dir, 'bsd.rd')) + + self.vm.set_console() + self.vm.add_args('-bios', self.ASSET_UBOOT.fetch(), + '-m', '1G', + '-smp', '4', + '-no-reboot', + '-netdev', + f'user,id=net0,tftp={tftp_dir},bootfile=bsd.rd', + '-device', 'e1000,netdev=net0') + self.vm.launch() + + wait_for_console_pattern(self, '=> ') + ec_and_wait(self, 'pci enum', '=> ') + ec_and_wait(self, 'dhcp', 'Bytes transferred') + wait_for_console_pattern(self, '=> ') + ec_and_wait(self, 'bootoctlinux ${loadaddr} numcores=4', + 'Welcome to the OpenBSD/octeon 7.9 installation program.') + + +if __name__ == '__main__': + QemuSystemTest.main() -- 2.54.0
