Create the Octeon CPUs and clock, map the split DRAM layout and
per-core CVMSEG storage, and provide the EBB7304 boot flash windows.
Load firmware into the flash reset alias and restore the boot PC during
machine reset.
---
 hw/mips/Kconfig           |   1 +
 hw/mips/octeon.c          | 308 ++++++++++++++++++++++++++++++++++++++
 hw/mips/octeon_internal.h |  28 ++++
 3 files changed, 337 insertions(+)

diff --git a/hw/mips/Kconfig b/hw/mips/Kconfig
index 2e71e6dee6..cb6057fe38 100644
--- a/hw/mips/Kconfig
+++ b/hw/mips/Kconfig
@@ -71,6 +71,7 @@ config MIPS_OCTEON
     bool
     default y
     depends on MIPS64 && TARGET_BIG_ENDIAN
+    select PFLASH_CFI02
 
 config MIPS_CPS
     bool
diff --git a/hw/mips/octeon.c b/hw/mips/octeon.c
index 2ecc464579..0e02d3693d 100644
--- a/hw/mips/octeon.c
+++ b/hw/mips/octeon.c
@@ -8,30 +8,337 @@
 
 #include "qemu/osdep.h"
 #include "qemu/units.h"
+#include "qemu/datadir.h"
+#include "qapi/error.h"
+#include "hw/core/clock.h"
 #include "hw/core/boards.h"
+#include "hw/core/qdev-clock.h"
 #include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-properties-system.h"
+#include "hw/core/sysbus.h"
+#include "hw/block/flash.h"
+#include "hw/mips/mips.h"
+#include "system/blockdev.h"
+#include "system/address-spaces.h"
+#include "system/memory.h"
+#include "system/reset.h"
+#include "qemu/error-report.h"
 #include "hw/mips/octeon_internal.h"
 #include "target/mips/cpu.h"
 
 #define TYPE_OCTEON_MACHINE MACHINE_TYPE_NAME("octeon3")
 OBJECT_DECLARE_SIMPLE_TYPE(OcteonMachineState, OCTEON_MACHINE)
 
+/*
+ * 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. 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)
+
+/*
+ * 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_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
 
 struct OcteonMachineState {
     MachineState parent_obj;
+    OcteonState *board;
     uint64_t cpu_hz;
     uint64_t ref_hz;
     uint64_t io_hz;
     uint64_t ddr_hz;
 };
 
+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_cpu_after_reset(OcteonCPUState *cs)
+{
+    CPUMIPSState *env = &cs->cpu->env;
+
+    env->CP0_CvmMemCtl = 0x100;
+    env->CP0_CvmCtl = 0;
+
+    if (cs->boot_cpu && 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;
+    for (i = 0; i < s->cpu_count; i++) {
+        DeviceState *dev = qdev_new(s->machine->cpu_type);
+
+        s->cpu[i].board = s;
+        s->cpu[i].boot_cpu = i == 0;
+        object_property_set_bool(OBJECT(dev), "big-endian", TARGET_BIG_ENDIAN,
+                                 &error_abort);
+        object_property_set_bool(OBJECT(dev), "start-powered-off", i != 0,
+                                 &error_abort);
+        qdev_connect_clock_in(dev, "clk-in", s->cpuclk);
+        qdev_realize(dev, NULL, &error_abort);
+        s->cpu[i].cpu = MIPS_CPU(dev);
+
+        cpu_mips_irq_init_cpu(s->cpu[i].cpu);
+        cpu_mips_clock_init(s->cpu[i].cpu);
+        qemu_register_resettable(OBJECT(dev));
+    }
+}
+
+static uint64_t octeon_map_ram_alias(OcteonState *s, MemoryRegion *alias,
+                                     const char *name, hwaddr base,
+                                     uint64_t offset, uint64_t size)
+{
+    MemoryRegion *sysmem = get_system_memory();
+
+    if (!size) {
+        return offset;
+    }
+
+    memory_region_init_alias(alias, NULL, name, s->machine->ram, offset, size);
+    memory_region_add_subregion(sysmem, base, alias);
+    return offset + size;
+}
+
+static void octeon_map_ram(OcteonState *s)
+{
+    MemoryRegion *sysmem = get_system_memory();
+    uint64_t offset = 0;
+    uint64_t remaining = s->machine->ram_size;
+    uint64_t size;
+
+    size = MIN(remaining, (uint64_t)OCTEON_DR0_SIZE);
+    offset = octeon_map_ram_alias(s, &s->dr0, "octeon.dr0",
+                                  OCTEON_DR0_BASE, offset, size);
+    remaining -= size;
+
+    size = MIN(remaining, (uint64_t)OCTEON_DR1_SIZE);
+    octeon_map_ram_alias(s, &s->dr1, "octeon.dr1",
+                         OCTEON_DR1_BASE, offset, size);
+
+    memory_region_init_ram(&s->cvmseg, NULL, "octeon.cvmseg",
+                           OCTEON_CVMSEG_TOTAL_SIZE, &error_fatal);
+    memory_region_add_subregion(sysmem, OCTEON_CVMSEG_BASE, &s->cvmseg);
+}
+
+static void octeon_load_firmware(OcteonState *s)
+{
+    g_autofree char *filename = NULL;
+    g_autofree gchar *contents = NULL;
+    g_autoptr(GError) err = NULL;
+    gsize len;
+    uint8_t *flash;
+
+    if (!s->machine->firmware) {
+        return;
+    }
+    if (!s->flash) {
+        error_report("Octeon boot flash is not initialized");
+        exit(1);
+    }
+
+    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->machine->firmware);
+    if (!filename) {
+        error_report("Could not find Octeon firmware '%s'",
+                     s->machine->firmware);
+        exit(1);
+    }
+
+    if (!g_file_get_contents(filename, &contents, &len, &err)) {
+        error_report("Could not load Octeon firmware '%s': %s",
+                     filename, err->message);
+        exit(1);
+    }
+
+    if (len == 0 || len > OCTEON_FLASH_ENV_OFFSET) {
+        error_report("Octeon firmware '%s' has invalid size %" G_GSIZE_FORMAT,
+                     filename, len);
+        exit(1);
+    }
+
+    flash = memory_region_get_ram_ptr(s->flash);
+    memcpy(flash, contents, len);
+    s->firmware_entry = cpu_mips_phys_to_kseg1(NULL, OCTEON_FLASH_RESET_BASE);
+}
+
+static MemTxResult octeon_boot_flash_read(void *opaque, hwaddr addr,
+                                          uint64_t *data, unsigned size,
+                                          MemTxAttrs attrs)
+{
+    OcteonState *s = opaque;
+    uint8_t buf[8];
+    MemTxResult result = MEMTX_OK;
+    unsigned int i;
+
+    for (i = 0; i < size; i++) {
+        uint64_t value;
+
+        result |= memory_region_dispatch_read(s->flash, addr + i, &value,
+                                              MO_8, attrs);
+        buf[i] = value;
+    }
+    *data = ldn_be_p(buf, size);
+    return result;
+}
+
+static MemTxResult octeon_boot_flash_write(void *opaque, hwaddr addr,
+                                           uint64_t value, unsigned size,
+                                           MemTxAttrs attrs)
+{
+    OcteonState *s = opaque;
+    unsigned access_size = MIN(size, 4);
+
+    if (size > access_size) {
+        access_size = OCTEON_FLASH_WIDTH;
+    }
+    return memory_region_dispatch_write(s->flash, addr, value,
+                                        size_memop(access_size) | MO_BE,
+                                        attrs);
+}
+
+static const MemoryRegionOps octeon_boot_flash_ops = {
+    .read_with_attrs = octeon_boot_flash_read,
+    .write_with_attrs = octeon_boot_flash_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+};
+
+static void octeon_init_flash(OcteonState *s)
+{
+    DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0);
+    DeviceState *dev;
+    uint8_t *flash;
+
+    dev = qdev_new(TYPE_PFLASH_CFI02);
+    if (dinfo) {
+        qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(dinfo));
+    }
+    qdev_prop_set_uint32(dev, "num-blocks0", OCTEON_FLASH_MAIN_SECTORS);
+    qdev_prop_set_uint32(dev, "sector-length0",
+                         OCTEON_FLASH_MAIN_SECTOR_SIZE);
+    qdev_prop_set_uint32(dev, "num-blocks1", OCTEON_FLASH_ENV_SECTORS);
+    qdev_prop_set_uint32(dev, "sector-length1",
+                         OCTEON_FLASH_ENV_SECTOR_SIZE);
+    qdev_prop_set_uint8(dev, "width", OCTEON_FLASH_WIDTH);
+    qdev_prop_set_uint8(dev, "mappings", 1);
+    qdev_prop_set_uint8(dev, "big-endian", TARGET_BIG_ENDIAN);
+    qdev_prop_set_uint16(dev, "id0", 0x01);
+    qdev_prop_set_uint16(dev, "id1", 0x7e);
+    qdev_prop_set_uint16(dev, "id2", 0x00);
+    qdev_prop_set_uint16(dev, "id3", 0x00);
+    qdev_prop_set_uint16(dev, "unlock-addr0", 0x555);
+    qdev_prop_set_uint16(dev, "unlock-addr1", 0x2aa);
+    qdev_prop_set_string(dev, "name", "octeon.bootbus-flash");
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+    s->flash = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+    if (!dinfo) {
+        flash = memory_region_get_ram_ptr(s->flash);
+        memset(flash, 0xff, OCTEON_FLASH_SIZE);
+    }
+    memory_region_init_io(&s->boot_flash, NULL, &octeon_boot_flash_ops, s,
+                          "octeon.bootbus-flash-window", OCTEON_FLASH_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_FLASH_BASE,
+                                &s->boot_flash);
+
+    memory_region_init_alias(&s->boot_flash_alias, NULL,
+                             "octeon.bootbus-flash-reset-alias",
+                             &s->boot_flash, 0, OCTEON_FLASH_RESET_SIZE);
+    memory_region_add_subregion(get_system_memory(), OCTEON_FLASH_RESET_BASE,
+                                &s->boot_flash_alias);
+}
+
 static void mips_octeon_init(MachineState *machine)
 {
+    OcteonMachineState *oms = OCTEON_MACHINE(machine);
+    OcteonState *s = g_new0(OcteonState, 1);
+
+    octeon_validate_clocks(oms);
+
+    oms->board = s;
+    s->machine = machine;
+    s->cpu_hz = oms->cpu_hz;
+    s->ref_hz = oms->ref_hz;
+    s->io_hz = oms->io_hz;
+    s->ddr_hz = oms->ddr_hz;
+
+    if (machine->kernel_filename) {
+        error_report("-kernel is not implemented for octeon3; "
+                     "boot via -bios and U-Boot");
+        exit(1);
+    }
+
+    octeon_map_ram(s);
+    octeon_init_flash(s);
+    octeon_load_firmware(s);
+    octeon_create_cpus(s);
+
+}
+
+static void octeon_machine_reset(MachineState *machine, ResetType type)
+{
+    OcteonState *s = OCTEON_MACHINE(machine)->board;
+    unsigned int i;
+
+    qemu_devices_reset(type);
+    for (i = 0; i < s->cpu_count; i++) {
+        octeon_cpu_after_reset(&s->cpu[i]);
+    }
 }
 
 static void octeon_machine_instance_init(Object *obj)
@@ -70,6 +377,7 @@ static void octeon_machine_class_init(ObjectClass *oc, const 
void *data)
 
     mc->desc = "Cavium Octeon III / EBB7304 EVK";
     mc->init = mips_octeon_init;
+    mc->reset = octeon_machine_reset;
     mc->default_cpu_type = MIPS_CPU_TYPE_NAME("OcteonCN73XX");
     mc->default_ram_size = OCTEON_DEFAULT_RAM_SIZE;
     mc->default_ram_id = "octeon.ram";
diff --git a/hw/mips/octeon_internal.h b/hw/mips/octeon_internal.h
index 7cab7790bd..2e568c29ea 100644
--- a/hw/mips/octeon_internal.h
+++ b/hw/mips/octeon_internal.h
@@ -10,7 +10,35 @@
 #define HW_MIPS_OCTEON_INTERNAL_H
 
 #include "qemu/typedefs.h"
+#include "system/memory.h"
+#include "target/mips/cpu.h"
 
 #define OCTEON_MAX_CPUS             16
+typedef struct OcteonState OcteonState;
+
+typedef struct OcteonCPUState {
+    OcteonState *board;
+    MIPSCPU *cpu;
+    bool boot_cpu;
+} OcteonCPUState;
+
+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 cvmseg;
+};
 
 #endif
-- 
2.54.0


Reply via email to