-bugfix: fix qmp command migrate-set-parameters -some bugfixs about ARM hot-plugged CPUs -hw/core/machine:Fix the missing consideration of cluster-id -test/tcg:Fix target-specific Makefile variable path for user-mode -tests:add (riscv virt) machine mapping to testenv -Make a litte improvement in curl and hw/riscv -qemu support for loongarch -hw/pvrdma: Protect against buggy or malious guest driver -hw/audio/intel-hda:fix stream reset -dsoundaudio:fix crackling audio recordings -add notify-vm-exit support for i386 -blok-backend: prevent dangling BDS pointers across aio_poll() -net:Fix uninitialized data usage -net/eth:Don't consider ESP to be an IPv6 option header -hw/net/vmxnet3:Log guest-triggerable errors using LOG_GUEST_ERROR Signed-off-by: FeiXu <xufei30@huawei.com>
6182 lines
203 KiB
Diff
6182 lines
203 KiB
Diff
From 810369434538d64a4118c3ec70d4c3daf479bd52 Mon Sep 17 00:00:00 2001
|
||
From: lixianglai <lixianglai@loongson.cn>
|
||
Date: Tue, 7 Feb 2023 07:14:21 -0500
|
||
Subject: [PATCH] Add loongarch machine.
|
||
|
||
1.Add loongarch ACPI table support.
|
||
2.Add loongarch apic support.
|
||
3.Add loongarch ipi support.
|
||
4.Add loongarch hotplug support.
|
||
5.Add loongarch board simulation.
|
||
6.Add loongarch interrupt support.
|
||
7.Add loongarch north bridge simulation.
|
||
8.Add loongarch iocsr device simulation.
|
||
9.Add loongarch system bus support.
|
||
|
||
Signed-off-by: lixianglai <lixianglai@loongson.cn>
|
||
---
|
||
hw/loongarch/Kconfig | 17 +
|
||
hw/loongarch/acpi-build.c | 827 ++++++++++++
|
||
hw/loongarch/acpi-build.h | 32 +
|
||
hw/loongarch/apic.c | 689 ++++++++++
|
||
hw/loongarch/ioapic.c | 419 ++++++
|
||
hw/loongarch/iocsr.c | 227 ++++
|
||
hw/loongarch/ipi.c | 284 ++++
|
||
hw/loongarch/larch_3a.c | 2063 +++++++++++++++++++++++++++++
|
||
hw/loongarch/larch_hotplug.c | 377 ++++++
|
||
hw/loongarch/larch_int.c | 87 ++
|
||
hw/loongarch/ls7a_nb.c | 289 ++++
|
||
hw/loongarch/meson.build | 15 +
|
||
hw/loongarch/sysbus-fdt.c | 178 +++
|
||
include/disas/dis-asm.h | 3 +-
|
||
include/elf.h | 2 +
|
||
include/hw/loongarch/bios.h | 24 +
|
||
include/hw/loongarch/cpudevs.h | 71 +
|
||
include/hw/loongarch/larch.h | 164 +++
|
||
include/hw/loongarch/ls7a.h | 167 +++
|
||
include/hw/loongarch/sysbus-fdt.h | 33 +
|
||
include/qemu/osdep.h | 4 +
|
||
21 files changed, 5971 insertions(+), 1 deletion(-)
|
||
create mode 100644 hw/loongarch/Kconfig
|
||
create mode 100644 hw/loongarch/acpi-build.c
|
||
create mode 100644 hw/loongarch/acpi-build.h
|
||
create mode 100644 hw/loongarch/apic.c
|
||
create mode 100644 hw/loongarch/ioapic.c
|
||
create mode 100644 hw/loongarch/iocsr.c
|
||
create mode 100644 hw/loongarch/ipi.c
|
||
create mode 100644 hw/loongarch/larch_3a.c
|
||
create mode 100644 hw/loongarch/larch_hotplug.c
|
||
create mode 100644 hw/loongarch/larch_int.c
|
||
create mode 100644 hw/loongarch/ls7a_nb.c
|
||
create mode 100644 hw/loongarch/meson.build
|
||
create mode 100644 hw/loongarch/sysbus-fdt.c
|
||
create mode 100644 include/hw/loongarch/bios.h
|
||
create mode 100644 include/hw/loongarch/cpudevs.h
|
||
create mode 100644 include/hw/loongarch/larch.h
|
||
create mode 100644 include/hw/loongarch/ls7a.h
|
||
create mode 100644 include/hw/loongarch/sysbus-fdt.h
|
||
|
||
diff --git a/hw/loongarch/Kconfig b/hw/loongarch/Kconfig
|
||
new file mode 100644
|
||
index 0000000000..3fe2677fda
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/Kconfig
|
||
@@ -0,0 +1,17 @@
|
||
+config LS7A_APIC
|
||
+ bool
|
||
+
|
||
+config LS7A_RTC
|
||
+ bool
|
||
+
|
||
+config LOONGSON3A
|
||
+ bool
|
||
+
|
||
+config MEM_HOTPLUG
|
||
+ bool
|
||
+
|
||
+config ACPI_LOONGARCH
|
||
+ bool
|
||
+
|
||
+config E1000E_PCI
|
||
+ bool
|
||
diff --git a/hw/loongarch/acpi-build.c b/hw/loongarch/acpi-build.c
|
||
new file mode 100644
|
||
index 0000000000..4dd128a05e
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/acpi-build.c
|
||
@@ -0,0 +1,827 @@
|
||
+/*
|
||
+ * Support for generating ACPI tables and passing them to Guests
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qapi/error.h"
|
||
+#include "qapi/qmp/qnum.h"
|
||
+#include "acpi-build.h"
|
||
+#include "qemu-common.h"
|
||
+#include "qemu/bitmap.h"
|
||
+#include "qemu/error-report.h"
|
||
+#include "hw/pci/pci.h"
|
||
+#include "hw/boards.h"
|
||
+#include "hw/core/cpu.h"
|
||
+#include "target/loongarch64/cpu.h"
|
||
+#include "hw/misc/pvpanic.h"
|
||
+#include "hw/timer/hpet.h"
|
||
+#include "hw/acpi/acpi-defs.h"
|
||
+#include "hw/acpi/acpi.h"
|
||
+#include "hw/acpi/cpu.h"
|
||
+#include "hw/nvram/fw_cfg.h"
|
||
+#include "hw/acpi/bios-linker-loader.h"
|
||
+#include "hw/loader.h"
|
||
+#include "hw/isa/isa.h"
|
||
+#include "hw/block/fdc.h"
|
||
+#include "hw/acpi/memory_hotplug.h"
|
||
+#include "sysemu/tpm.h"
|
||
+#include "hw/acpi/tpm.h"
|
||
+#include "hw/acpi/vmgenid.h"
|
||
+#include "sysemu/tpm_backend.h"
|
||
+#include "hw/rtc/mc146818rtc_regs.h"
|
||
+#include "sysemu/numa.h"
|
||
+#include "sysemu/runstate.h"
|
||
+#include "sysemu/reset.h"
|
||
+#include "migration/vmstate.h"
|
||
+#include "hw/mem/memory-device.h"
|
||
+#include "hw/acpi/utils.h"
|
||
+#include "hw/acpi/pci.h"
|
||
+/* Supported chipsets: */
|
||
+#include "hw/acpi/aml-build.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+#include "hw/loongarch/ls7a.h"
|
||
+#include "hw/platform-bus.h"
|
||
+
|
||
+#include "hw/acpi/ipmi.h"
|
||
+#include "hw/acpi/ls7a.h"
|
||
+
|
||
+/*
|
||
+ * These are used to size the ACPI tables for -M pc-i440fx-1.7 and
|
||
+ * -M pc-i440fx-2.0. Even if the actual amount of AML generated grows
|
||
+ * a little bit, there should be plenty of free space since the DSDT
|
||
+ * shrunk by ~1.5k between QEMU 2.0 and QEMU 2.1.
|
||
+ */
|
||
+#define ACPI_BUILD_ALIGN_SIZE 0x1000
|
||
+#define ACPI_BUILD_TABLE_SIZE 0x20000
|
||
+
|
||
+/* #define DEBUG_ACPI_BUILD */
|
||
+#ifdef DEBUG_ACPI_BUILD
|
||
+#define ACPI_BUILD_DPRINTF(fmt, ...) \
|
||
+ do { \
|
||
+ printf("ACPI_BUILD: " fmt, ##__VA_ARGS__); \
|
||
+ } while (0)
|
||
+#else
|
||
+#define ACPI_BUILD_DPRINTF(fmt, ...)
|
||
+#endif
|
||
+
|
||
+/* Default IOAPIC ID */
|
||
+#define ACPI_BUILD_IOAPIC_ID 0x0
|
||
+
|
||
+/* PCI fw r3.0 MCFG table. */
|
||
+/* Subtable */
|
||
+
|
||
+typedef struct AcpiMiscInfo {
|
||
+ bool is_piix4;
|
||
+ bool has_hpet;
|
||
+ TPMVersion tpm_version;
|
||
+ const unsigned char *dsdt_code;
|
||
+ unsigned dsdt_size;
|
||
+ uint16_t pvpanic_port;
|
||
+ uint16_t applesmc_io_base;
|
||
+} AcpiMiscInfo;
|
||
+
|
||
+typedef struct AcpiBuildPciBusHotplugState {
|
||
+ GArray *device_table;
|
||
+ GArray *notify_table;
|
||
+ struct AcpiBuildPciBusHotplugState *parent;
|
||
+ bool pcihp_bridge_en;
|
||
+} AcpiBuildPciBusHotplugState;
|
||
+
|
||
+static void init_common_fadt_data(AcpiFadtData *data)
|
||
+{
|
||
+ AmlAddressSpace as = AML_AS_SYSTEM_MEMORY;
|
||
+ uint64_t base = LS7A_ACPI_REG_BASE;
|
||
+ AcpiFadtData fadt = {
|
||
+ .rev = 3,
|
||
+ .flags = (1 << ACPI_FADT_F_WBINVD) | (1 << ACPI_FADT_F_PROC_C1) |
|
||
+ (1 << ACPI_FADT_F_SLP_BUTTON) |
|
||
+ (1 << ACPI_FADT_F_TMR_VAL_EXT) |
|
||
+ (1 << ACPI_FADT_F_RESET_REG_SUP),
|
||
+ /* C2 state not supported */
|
||
+ .plvl2_lat = 0xfff,
|
||
+ /* C3 state not supported */
|
||
+ .plvl3_lat = 0xfff,
|
||
+ .smi_cmd = 0x00,
|
||
+ .sci_int = ACPI_SCI_IRQ,
|
||
+ .acpi_enable_cmd = 0x00,
|
||
+ .acpi_disable_cmd = 0x00,
|
||
+ .pm1a_evt = { .space_id = as,
|
||
+ .bit_width = 8 * 8,
|
||
+ .address = base + LS7A_PM_EVT_BLK },
|
||
+ .pm1a_cnt = { .space_id = as,
|
||
+ .bit_width = 4 * 8,
|
||
+ .address = base + LS7A_PM_CNT_BLK },
|
||
+ .pm_tmr = { .space_id = as,
|
||
+ .bit_width = 4 * 8,
|
||
+ .address = base + LS7A_PM_TMR_BLK },
|
||
+ .gpe0_blk = { .space_id = as,
|
||
+ .bit_width = 8 * 8,
|
||
+ .address = base + LS7A_GPE0_STS_REG },
|
||
+ .reset_reg = { .space_id = as,
|
||
+ .bit_width = 4 * 8,
|
||
+ .address = base + LS7A_GPE0_RESET_REG },
|
||
+ .reset_val = 0x1,
|
||
+ };
|
||
+ *data = fadt;
|
||
+}
|
||
+
|
||
+static void acpi_align_size(GArray *blob, unsigned align)
|
||
+{
|
||
+ /*
|
||
+ * Align size to multiple of given size. This reduces the chance
|
||
+ * we need to change size in the future (breaking cross version migration).
|
||
+ */
|
||
+ g_array_set_size(blob, ROUND_UP(acpi_data_len(blob), align));
|
||
+}
|
||
+
|
||
+/* FACS */
|
||
+static void build_facs(GArray *table_data)
|
||
+{
|
||
+ const char *sig = "FACS";
|
||
+ const uint8_t reserved[40] = {};
|
||
+
|
||
+ g_array_append_vals(table_data, sig, 4); /* Signature */
|
||
+ build_append_int_noprefix(table_data, 64, 4); /* Length */
|
||
+ build_append_int_noprefix(table_data, 0, 4); /* Hardware Signature */
|
||
+ build_append_int_noprefix(table_data, 0, 4); /* Firmware Waking Vector */
|
||
+ build_append_int_noprefix(table_data, 0, 4); /* Global Lock */
|
||
+ build_append_int_noprefix(table_data, 0, 4); /* Flags */
|
||
+ g_array_append_vals(table_data, reserved, 40); /* Reserved */
|
||
+}
|
||
+
|
||
+void ls7a_madt_cpu_entry(AcpiDeviceIf *adev, int uid,
|
||
+ const CPUArchIdList *apic_ids, GArray *entry,
|
||
+ bool force_enabled)
|
||
+{
|
||
+ uint32_t apic_id = apic_ids->cpus[uid].arch_id;
|
||
+ /* Flags – Local APIC Flags */
|
||
+ uint32_t flags = apic_ids->cpus[uid].cpu != NULL || force_enabled ? 1 : 0;
|
||
+
|
||
+ /* Rev 1.0b, Table 5-13 Processor Local APIC Structure */
|
||
+ build_append_int_noprefix(entry, 0, 1); /* Type */
|
||
+ build_append_int_noprefix(entry, 8, 1); /* Length */
|
||
+ build_append_int_noprefix(entry, uid, 1); /* ACPI Processor ID */
|
||
+ build_append_int_noprefix(entry, apic_id, 1); /* APIC ID */
|
||
+ build_append_int_noprefix(entry, flags, 4); /* Flags */
|
||
+}
|
||
+
|
||
+static void build_ioapic(GArray *entry, uint8_t id, uint32_t addr,
|
||
+ uint32_t irq)
|
||
+{
|
||
+ /* Rev 1.0b, 5.2.8.2 IO APIC */
|
||
+ build_append_int_noprefix(entry, 1, 1); /* Type */
|
||
+ build_append_int_noprefix(entry, 12, 1); /* Length */
|
||
+ build_append_int_noprefix(entry, id, 1); /* IO APIC ID */
|
||
+ build_append_int_noprefix(entry, 0, 1); /* Reserved */
|
||
+ build_append_int_noprefix(entry, addr, 4); /* IO APIC Address */
|
||
+ build_append_int_noprefix(entry, irq, 4); /* System Vector Base */
|
||
+}
|
||
+
|
||
+static void build_madt(GArray *table_data, BIOSLinker *linker,
|
||
+ LoongarchMachineState *lsms)
|
||
+{
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(lsms);
|
||
+ const CPUArchIdList *apic_ids = mc->possible_cpu_arch_ids(MACHINE(lsms));
|
||
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(lsms->acpi_dev);
|
||
+ AcpiDeviceIf *adev = ACPI_DEVICE_IF(lsms->acpi_dev);
|
||
+ int i;
|
||
+ AcpiTable table = { .sig = "APIC",
|
||
+ .rev = 1,
|
||
+ .oem_id = lsms->oem_id,
|
||
+ .oem_table_id = lsms->oem_table_id };
|
||
+
|
||
+ acpi_table_begin(&table, table_data);
|
||
+
|
||
+ /* Local APIC Address */
|
||
+ build_append_int_noprefix(table_data, 0, 4);
|
||
+ build_append_int_noprefix(table_data, 1 /* PCAT_COMPAT */, 4); /* Flags */
|
||
+
|
||
+ for (i = 0; i < apic_ids->len; i++) {
|
||
+ adevc->madt_cpu(adev, i, apic_ids, table_data, false);
|
||
+ }
|
||
+
|
||
+ build_ioapic(table_data, ACPI_BUILD_IOAPIC_ID, lsmc->ls7a_ioapic_reg_base,
|
||
+ LOONGARCH_PCH_IRQ_BASE);
|
||
+
|
||
+ /* Rev 1.0b, 5.2.8.3.3 Local APIC NMI */
|
||
+ build_append_int_noprefix(table_data, 3, 1); /* Type */
|
||
+ build_append_int_noprefix(table_data, 6, 1); /* Length */
|
||
+ /* ACPI Processor ID */
|
||
+ build_append_int_noprefix(table_data, 0xFF, 1); /* all processors */
|
||
+ build_append_int_noprefix(table_data, 0, 2); /* Flags */
|
||
+ /* Local APIC INTI# */
|
||
+ build_append_int_noprefix(table_data, 1, 1); /* ACPI_LINT1 */
|
||
+
|
||
+ /* Rev 1.0b, 5.2.8.3.3 Local APIC NMI */
|
||
+ build_append_int_noprefix(table_data, 4, 1); /* Type */
|
||
+ build_append_int_noprefix(table_data, 6, 1); /* Length */
|
||
+ /* ACPI Processor ID */
|
||
+ build_append_int_noprefix(table_data, 0xFF, 1); /* all processors */
|
||
+ build_append_int_noprefix(table_data, 0, 2); /* Flags */
|
||
+ /* Local APIC INTI# */
|
||
+ build_append_int_noprefix(table_data, 1, 1); /* ACPI_LINT1 */
|
||
+
|
||
+ acpi_table_end(linker, &table);
|
||
+}
|
||
+
|
||
+static void build_srat(GArray *table_data, BIOSLinker *linker,
|
||
+ MachineState *machine)
|
||
+{
|
||
+ uint64_t i, mem_len, mem_base;
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ const CPUArchIdList *apic_ids = mc->possible_cpu_arch_ids(machine);
|
||
+ int nb_numa_nodes = machine->numa_state->num_nodes;
|
||
+ NodeInfo *numa_info = machine->numa_state->nodes;
|
||
+ AcpiTable table = { .sig = "SRAT",
|
||
+ .rev = 1,
|
||
+ .oem_id = lsms->oem_id,
|
||
+ .oem_table_id = lsms->oem_table_id };
|
||
+
|
||
+ acpi_table_begin(&table, table_data);
|
||
+ build_append_int_noprefix(table_data, 1, 4); /* Reserved */
|
||
+ build_append_int_noprefix(table_data, 0, 8); /* Reserved */
|
||
+
|
||
+ for (i = 0; i < apic_ids->len; ++i) {
|
||
+ /* 5.2.15.1 Processor Local APIC/SAPIC Affinity Structure */
|
||
+ build_append_int_noprefix(table_data, 0, 1); /* Type */
|
||
+ build_append_int_noprefix(table_data, 16, 1); /* Length */
|
||
+ /* Proximity Domain [7:0] */
|
||
+ build_append_int_noprefix(table_data, apic_ids->cpus[i].props.node_id,
|
||
+ 1);
|
||
+ build_append_int_noprefix(table_data, apic_ids->cpus[i].arch_id,
|
||
+ 1); /* APIC ID */
|
||
+ /* Flags, Table 5-36 */
|
||
+ build_append_int_noprefix(table_data, 1, 4);
|
||
+ build_append_int_noprefix(table_data, 0, 1); /* Local SAPIC EID */
|
||
+ /* Proximity Domain [31:8] */
|
||
+ build_append_int_noprefix(table_data, 0, 3);
|
||
+ build_append_int_noprefix(table_data, 0, 4); /* Reserved */
|
||
+ }
|
||
+
|
||
+ /* node0 */
|
||
+ mem_base = (uint64_t)0;
|
||
+ mem_len = 0x10000000;
|
||
+ build_srat_memory(table_data, mem_base, mem_len, 0, MEM_AFFINITY_ENABLED);
|
||
+ mem_base = 0x90000000;
|
||
+ if (!nb_numa_nodes) {
|
||
+ mem_len = machine->ram_size - 0x10000000;
|
||
+ } else {
|
||
+ mem_len = numa_info[0].node_mem - 0x10000000;
|
||
+ }
|
||
+
|
||
+ build_srat_memory(table_data, mem_base, mem_len, 0, MEM_AFFINITY_ENABLED);
|
||
+ mem_base += mem_len;
|
||
+
|
||
+ /* node1 ~ nodemax */
|
||
+ for (i = 1; i < nb_numa_nodes; ++i) {
|
||
+ mem_len = numa_info[i].node_mem;
|
||
+ build_srat_memory(table_data, mem_base, mem_len, i,
|
||
+ MEM_AFFINITY_ENABLED);
|
||
+ mem_base += mem_len;
|
||
+ }
|
||
+
|
||
+ if (lsms->hotplug_memory_size) {
|
||
+ build_srat_memory(table_data, machine->device_memory->base,
|
||
+ lsms->hotplug_memory_size, 0,
|
||
+ MEM_AFFINITY_HOTPLUGGABLE | MEM_AFFINITY_ENABLED);
|
||
+ }
|
||
+
|
||
+ acpi_table_end(linker, &table);
|
||
+}
|
||
+
|
||
+typedef struct AcpiBuildState {
|
||
+ /* Copy of table in RAM (for patching). */
|
||
+ MemoryRegion *table_mr;
|
||
+ /* Is table patched? */
|
||
+ uint8_t patched;
|
||
+ void *rsdp;
|
||
+ MemoryRegion *rsdp_mr;
|
||
+ MemoryRegion *linker_mr;
|
||
+} AcpiBuildState;
|
||
+
|
||
+static void build_ls7a_pci0_int(Aml *table)
|
||
+{
|
||
+ Aml *sb_scope = aml_scope("_SB");
|
||
+ Aml *pci0_scope = aml_scope("PCI0");
|
||
+ Aml *prt_pkg = aml_varpackage(128);
|
||
+ int slot, pin;
|
||
+
|
||
+ for (slot = 0; slot < PCI_SLOT_MAX; slot++) {
|
||
+ for (pin = 0; pin < PCI_NUM_PINS; pin++) {
|
||
+ Aml *pkg = aml_package(4);
|
||
+ aml_append(pkg, aml_int((slot << 16) | 0xFFFF));
|
||
+ aml_append(pkg, aml_int(pin));
|
||
+ aml_append(pkg, aml_int(0));
|
||
+ aml_append(pkg, aml_int(LOONGARCH_PCH_IRQ_BASE + 16 +
|
||
+ (slot * 4 + pin) % 16));
|
||
+ aml_append(prt_pkg, pkg);
|
||
+ }
|
||
+ }
|
||
+ aml_append(pci0_scope, aml_name_decl("_PRT", prt_pkg));
|
||
+
|
||
+ aml_append(sb_scope, pci0_scope);
|
||
+
|
||
+ aml_append(table, sb_scope);
|
||
+}
|
||
+
|
||
+static void build_dbg_aml(Aml *table)
|
||
+{
|
||
+ Aml *field;
|
||
+ Aml *method;
|
||
+ Aml *while_ctx;
|
||
+ Aml *scope = aml_scope("\\");
|
||
+ Aml *buf = aml_local(0);
|
||
+ Aml *len = aml_local(1);
|
||
+ Aml *idx = aml_local(2);
|
||
+
|
||
+ aml_append(scope, aml_operation_region("DBG", AML_SYSTEM_IO,
|
||
+ aml_int(0x0402), 0x01));
|
||
+ field = aml_field("DBG", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
|
||
+ aml_append(field, aml_named_field("DBGB", 8));
|
||
+ aml_append(scope, field);
|
||
+
|
||
+ method = aml_method("DBUG", 1, AML_NOTSERIALIZED);
|
||
+
|
||
+ aml_append(method, aml_to_hexstring(aml_arg(0), buf));
|
||
+ aml_append(method, aml_to_buffer(buf, buf));
|
||
+ aml_append(method, aml_subtract(aml_sizeof(buf), aml_int(1), len));
|
||
+ aml_append(method, aml_store(aml_int(0), idx));
|
||
+
|
||
+ while_ctx = aml_while(aml_lless(idx, len));
|
||
+ aml_append(while_ctx,
|
||
+ aml_store(aml_derefof(aml_index(buf, idx)), aml_name("DBGB")));
|
||
+ aml_append(while_ctx, aml_increment(idx));
|
||
+ aml_append(method, while_ctx);
|
||
+
|
||
+ aml_append(method, aml_store(aml_int(0x0A), aml_name("DBGB")));
|
||
+ aml_append(scope, method);
|
||
+
|
||
+ aml_append(table, scope);
|
||
+}
|
||
+
|
||
+static Aml *build_ls7a_osc_method(void)
|
||
+{
|
||
+ Aml *if_ctx;
|
||
+ Aml *if_ctx2;
|
||
+ Aml *else_ctx;
|
||
+ Aml *method;
|
||
+ Aml *a_cwd1 = aml_name("CDW1");
|
||
+ Aml *a_ctrl = aml_local(0);
|
||
+
|
||
+ method = aml_method("_OSC", 4, AML_NOTSERIALIZED);
|
||
+ aml_append(method, aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1"));
|
||
+
|
||
+ if_ctx = aml_if(aml_equal(
|
||
+ aml_arg(0), aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766")));
|
||
+ aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2"));
|
||
+ aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3"));
|
||
+
|
||
+ aml_append(if_ctx, aml_store(aml_name("CDW3"), a_ctrl));
|
||
+
|
||
+ /*
|
||
+ * Always allow native PME, AER (no dependencies)
|
||
+ * Allow SHPC (PCI bridges can have SHPC controller)
|
||
+ */
|
||
+ aml_append(if_ctx, aml_and(a_ctrl, aml_int(0x1F), a_ctrl));
|
||
+
|
||
+ if_ctx2 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(1))));
|
||
+ /* Unknown revision */
|
||
+ aml_append(if_ctx2, aml_or(a_cwd1, aml_int(0x08), a_cwd1));
|
||
+ aml_append(if_ctx, if_ctx2);
|
||
+
|
||
+ if_ctx2 = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), a_ctrl)));
|
||
+ /* Capabilities bits were masked */
|
||
+ aml_append(if_ctx2, aml_or(a_cwd1, aml_int(0x10), a_cwd1));
|
||
+ aml_append(if_ctx, if_ctx2);
|
||
+
|
||
+ /* Update DWORD3 in the buffer */
|
||
+ aml_append(if_ctx, aml_store(a_ctrl, aml_name("CDW3")));
|
||
+ aml_append(method, if_ctx);
|
||
+
|
||
+ else_ctx = aml_else();
|
||
+ /* Unrecognized UUID */
|
||
+ aml_append(else_ctx, aml_or(a_cwd1, aml_int(4), a_cwd1));
|
||
+ aml_append(method, else_ctx);
|
||
+
|
||
+ aml_append(method, aml_return(aml_arg(3)));
|
||
+ return method;
|
||
+}
|
||
+
|
||
+static void build_ls7a_rtc_device_aml(Aml *table)
|
||
+{
|
||
+ Aml *dev;
|
||
+ Aml *crs;
|
||
+ uint32_t rtc_irq = LS7A_RTC_IRQ;
|
||
+
|
||
+ Aml *scope = aml_scope("_SB");
|
||
+ dev = aml_device("RTC");
|
||
+ aml_append(dev, aml_name_decl("_HID", aml_string("LOON0001")));
|
||
+ crs = aml_resource_template();
|
||
+ aml_append(crs, aml_qword_memory(AML_POS_DECODE, AML_MIN_FIXED,
|
||
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
|
||
+ AML_READ_WRITE, 0, LS7A_RTC_REG_BASE,
|
||
+ LS7A_RTC_REG_BASE + LS7A_RTC_LEN - 1, 0,
|
||
+ LS7A_RTC_LEN));
|
||
+ aml_append(crs, aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
|
||
+ AML_EXCLUSIVE, &rtc_irq, 1));
|
||
+
|
||
+ aml_append(dev, aml_name_decl("_CRS", crs));
|
||
+ aml_append(scope, dev);
|
||
+ aml_append(table, scope);
|
||
+}
|
||
+
|
||
+static void build_ls7a_uart_device_aml(Aml *table)
|
||
+{
|
||
+ Aml *dev;
|
||
+ Aml *crs;
|
||
+ Aml *pkg0, *pkg1, *pkg2;
|
||
+ uint32_t uart_irq = LS7A_UART_IRQ;
|
||
+
|
||
+ Aml *scope = aml_scope("_SB");
|
||
+ dev = aml_device("COMA");
|
||
+ aml_append(dev, aml_name_decl("_HID", aml_string("PNP0501")));
|
||
+ aml_append(dev, aml_name_decl("_UID", aml_int(0)));
|
||
+ aml_append(dev, aml_name_decl("_CCA", aml_int(1)));
|
||
+ crs = aml_resource_template();
|
||
+ aml_append(crs, aml_qword_memory(
|
||
+ AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
|
||
+ AML_NON_CACHEABLE, AML_READ_WRITE, 0, LS7A_UART_BASE,
|
||
+ LS7A_UART_BASE + LS7A_UART_LEN - 1, 0, 0x8));
|
||
+ aml_append(crs, aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
|
||
+ AML_EXCLUSIVE, &uart_irq, 1));
|
||
+ aml_append(dev, aml_name_decl("_CRS", crs));
|
||
+ pkg0 = aml_package(0x2);
|
||
+ aml_append(pkg0, aml_int(0x01F78A40));
|
||
+ aml_append(pkg0, aml_string("clock-frenquency"));
|
||
+ pkg1 = aml_package(0x1);
|
||
+ aml_append(pkg1, pkg0);
|
||
+ pkg2 = aml_package(0x2);
|
||
+ aml_append(pkg2, aml_touuid("DAFFD814-6EBA-4D8C-8A91-BC9BBF4AA301"));
|
||
+ aml_append(pkg2, pkg1);
|
||
+
|
||
+ aml_append(dev, aml_name_decl("_DSD", pkg2));
|
||
+
|
||
+ aml_append(scope, dev);
|
||
+ aml_append(table, scope);
|
||
+}
|
||
+
|
||
+#ifdef CONFIG_TPM
|
||
+static void acpi_dsdt_add_tpm(Aml *scope, LoongarchMachineState *vms)
|
||
+{
|
||
+ PlatformBusDevice *pbus = PLATFORM_BUS_DEVICE(vms->platform_bus_dev);
|
||
+ hwaddr pbus_base = VIRT_PLATFORM_BUS_BASEADDRESS;
|
||
+ SysBusDevice *sbdev = SYS_BUS_DEVICE(tpm_find());
|
||
+ MemoryRegion *sbdev_mr;
|
||
+ hwaddr tpm_base;
|
||
+
|
||
+ if (!sbdev) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ tpm_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
|
||
+ assert(tpm_base != -1);
|
||
+
|
||
+ tpm_base += pbus_base;
|
||
+
|
||
+ sbdev_mr = sysbus_mmio_get_region(sbdev, 0);
|
||
+
|
||
+ Aml *dev = aml_device("TPM0");
|
||
+ aml_append(dev, aml_name_decl("_HID", aml_string("MSFT0101")));
|
||
+ aml_append(dev, aml_name_decl("_STR", aml_string("TPM 2.0 Device")));
|
||
+ aml_append(dev, aml_name_decl("_UID", aml_int(0)));
|
||
+
|
||
+ Aml *crs = aml_resource_template();
|
||
+ aml_append(crs, aml_memory32_fixed(tpm_base,
|
||
+ (uint32_t)memory_region_size(sbdev_mr),
|
||
+ AML_READ_WRITE));
|
||
+ aml_append(dev, aml_name_decl("_CRS", crs));
|
||
+ aml_append(scope, dev);
|
||
+}
|
||
+#endif
|
||
+
|
||
+static void build_dsdt(GArray *table_data, BIOSLinker *linker,
|
||
+ MachineState *machine)
|
||
+{
|
||
+ Aml *dsdt, *sb_scope, *scope, *dev, *crs, *pkg;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ uint32_t nr_mem = machine->ram_slots;
|
||
+ uint64_t base = LS7A_ACPI_REG_BASE;
|
||
+ int root_bus_limit = PCIE_MMCFG_BUS(LS_PCIECFG_SIZE - 1);
|
||
+ AcpiTable table = { .sig = "DSDT",
|
||
+ .rev = 1,
|
||
+ .oem_id = lsms->oem_id,
|
||
+ .oem_table_id = lsms->oem_table_id };
|
||
+
|
||
+ acpi_table_begin(&table, table_data);
|
||
+ dsdt = init_aml_allocator();
|
||
+
|
||
+ build_dbg_aml(dsdt);
|
||
+
|
||
+ sb_scope = aml_scope("_SB");
|
||
+ dev = aml_device("PCI0");
|
||
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A08")));
|
||
+ aml_append(dev, aml_name_decl("_CID", aml_eisaid("PNP0A03")));
|
||
+ aml_append(dev, aml_name_decl("_ADR", aml_int(0)));
|
||
+ aml_append(dev, aml_name_decl("_BBN", aml_int(0)));
|
||
+ aml_append(dev, aml_name_decl("_UID", aml_int(1)));
|
||
+ aml_append(dev, build_ls7a_osc_method());
|
||
+ aml_append(sb_scope, dev);
|
||
+
|
||
+#ifdef CONFIG_TPM
|
||
+ acpi_dsdt_add_tpm(sb_scope, lsms);
|
||
+#endif
|
||
+ aml_append(dsdt, sb_scope);
|
||
+
|
||
+ build_ls7a_pci0_int(dsdt);
|
||
+ build_ls7a_rtc_device_aml(dsdt);
|
||
+ build_ls7a_uart_device_aml(dsdt);
|
||
+
|
||
+ if (lsms->acpi_dev) {
|
||
+ CPUHotplugFeatures opts = { .acpi_1_compatible = true,
|
||
+ .has_legacy_cphp = false };
|
||
+ build_cpus_aml(dsdt, machine, opts, CPU_HOTPLUG_BASE, "\\_SB.PCI0",
|
||
+ "\\_GPE._E02", AML_SYSTEM_MEMORY);
|
||
+
|
||
+ build_memory_hotplug_aml(dsdt, nr_mem, "\\_SB.PCI0", "\\_GPE._E03",
|
||
+ AML_SYSTEM_MEMORY, MEMORY_HOTPLUG_BASE);
|
||
+ }
|
||
+
|
||
+ scope = aml_scope("_GPE");
|
||
+ {
|
||
+ aml_append(scope, aml_name_decl("_HID", aml_string("ACPI0006")));
|
||
+ }
|
||
+ aml_append(dsdt, scope);
|
||
+
|
||
+ scope = aml_scope("\\_SB.PCI0");
|
||
+ /* build PCI0._CRS */
|
||
+ crs = aml_resource_template();
|
||
+ aml_append(crs, aml_word_bus_number(
|
||
+ AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE, 0x0000,
|
||
+ 0x0, root_bus_limit, 0x0000, root_bus_limit + 1));
|
||
+ aml_append(crs, aml_word_io(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE,
|
||
+ AML_ENTIRE_RANGE, 0x0000, 0x4000, 0xFFFF,
|
||
+ 0x0000, 0xC000));
|
||
+ aml_append(crs,
|
||
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
|
||
+ AML_CACHEABLE, AML_READ_WRITE, 0, 0x40000000,
|
||
+ 0x7FFFFFFF, 0, 0x40000000));
|
||
+ aml_append(scope, aml_name_decl("_CRS", crs));
|
||
+
|
||
+ /* reserve GPE0 block resources */
|
||
+ dev = aml_device("GPE0");
|
||
+ aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06")));
|
||
+ aml_append(dev, aml_name_decl("_UID", aml_string("GPE0 resources")));
|
||
+ /* device present, functioning, decoding, not shown in UI */
|
||
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
|
||
+ crs = aml_resource_template();
|
||
+ aml_append(crs,
|
||
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
|
||
+ AML_CACHEABLE, AML_READ_WRITE, 0,
|
||
+ base + LS7A_GPE0_STS_REG,
|
||
+ base + LS7A_GPE0_STS_REG + 0x3, 0, 0x4));
|
||
+ aml_append(dev, aml_name_decl("_CRS", crs));
|
||
+ aml_append(scope, dev);
|
||
+ aml_append(dsdt, scope);
|
||
+
|
||
+ scope = aml_scope("\\");
|
||
+ pkg = aml_package(4);
|
||
+ aml_append(pkg, aml_int(7)); /* PM1a_CNT.SLP_TYP */
|
||
+ aml_append(pkg, aml_int(7)); /* PM1b_CNT.SLP_TYP not impl. */
|
||
+ aml_append(pkg, aml_int(0)); /* reserved */
|
||
+ aml_append(pkg, aml_int(0)); /* reserved */
|
||
+ aml_append(scope, aml_name_decl("_S5", pkg));
|
||
+ aml_append(dsdt, scope);
|
||
+
|
||
+ /* copy AML table into ACPI tables blob and patch header there */
|
||
+ g_array_append_vals(table_data, dsdt->buf->data, dsdt->buf->len);
|
||
+ acpi_table_end(linker, &table);
|
||
+ free_aml_allocator();
|
||
+}
|
||
+
|
||
+static void acpi_build(AcpiBuildTables *tables, MachineState *machine)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ GArray *table_offsets;
|
||
+ AcpiFadtData fadt_data;
|
||
+ unsigned facs, rsdt, fadt, dsdt;
|
||
+ uint8_t *u;
|
||
+ size_t aml_len = 0;
|
||
+ GArray *tables_blob = tables->table_data;
|
||
+
|
||
+ init_common_fadt_data(&fadt_data);
|
||
+
|
||
+ table_offsets = g_array_new(false, true, sizeof(uint32_t)); /* clear */
|
||
+ ACPI_BUILD_DPRINTF("init ACPI tables\n");
|
||
+
|
||
+ bios_linker_loader_alloc(tables->linker, ACPI_BUILD_TABLE_FILE,
|
||
+ tables_blob, 64, false /* high memory */);
|
||
+
|
||
+ /*
|
||
+ * FACS is pointed to by FADT.
|
||
+ * We place it first since it's the only table that has alignment
|
||
+ * requirements.
|
||
+ */
|
||
+ facs = tables_blob->len;
|
||
+ build_facs(tables_blob);
|
||
+
|
||
+ /* DSDT is pointed to by FADT */
|
||
+ dsdt = tables_blob->len;
|
||
+ build_dsdt(tables_blob, tables->linker, MACHINE(qdev_get_machine()));
|
||
+
|
||
+ /*
|
||
+ * Count the size of the DSDT and SSDT, we will need it for legacy
|
||
+ * sizing of ACPI tables.
|
||
+ */
|
||
+ aml_len += tables_blob->len - dsdt;
|
||
+
|
||
+ /* ACPI tables pointed to by RSDT */
|
||
+ fadt = tables_blob->len;
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ fadt_data.facs_tbl_offset = &facs;
|
||
+ fadt_data.dsdt_tbl_offset = &dsdt;
|
||
+ fadt_data.xdsdt_tbl_offset = &dsdt;
|
||
+ build_fadt(tables_blob, tables->linker, &fadt_data, "LOONGS", "TP-R00");
|
||
+ aml_len += tables_blob->len - fadt;
|
||
+
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ build_madt(tables_blob, tables->linker, lsms);
|
||
+
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ build_srat(tables_blob, tables->linker, machine);
|
||
+ if (machine->numa_state->have_numa_distance) {
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ build_slit(tables_blob, tables->linker, machine, lsms->oem_id,
|
||
+ lsms->oem_table_id);
|
||
+ }
|
||
+
|
||
+ if (tpm_get_version(tpm_find()) == TPM_VERSION_2_0) {
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ build_tpm2(tables_blob, tables->linker, tables->tcpalog, lsms->oem_id,
|
||
+ lsms->oem_table_id);
|
||
+ }
|
||
+
|
||
+ /* Build mcfg */
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ {
|
||
+ AcpiMcfgInfo mcfg = {
|
||
+ .base = LS_PCIECFG_BASE,
|
||
+ .size = LS_PCIECFG_SIZE,
|
||
+ };
|
||
+ build_mcfg(tables_blob, tables->linker, &mcfg, lsms->oem_id,
|
||
+ lsms->oem_table_id);
|
||
+ }
|
||
+
|
||
+ /* Add tables supplied by user (if any) */
|
||
+ for (u = acpi_table_first(); u; u = acpi_table_next(u)) {
|
||
+ unsigned len = acpi_table_len(u);
|
||
+
|
||
+ acpi_add_table(table_offsets, tables_blob);
|
||
+ g_array_append_vals(tables_blob, u, len);
|
||
+ }
|
||
+
|
||
+ /* RSDT is pointed to by RSDP */
|
||
+ rsdt = tables_blob->len;
|
||
+ build_rsdt(tables_blob, tables->linker, table_offsets, "LOONGS", "TP-R00");
|
||
+
|
||
+ /* RSDP is in FSEG memory, so allocate it separately */
|
||
+ {
|
||
+ AcpiRsdpData rsdp_data = {
|
||
+ .revision = 0,
|
||
+ .oem_id = lsms->oem_id,
|
||
+ .xsdt_tbl_offset = NULL,
|
||
+ .rsdt_tbl_offset = &rsdt,
|
||
+ };
|
||
+ build_rsdp(tables->rsdp, tables->linker, &rsdp_data);
|
||
+ }
|
||
+ acpi_align_size(tables->linker->cmd_blob, ACPI_BUILD_ALIGN_SIZE);
|
||
+
|
||
+ /* Cleanup memory that's no longer used. */
|
||
+ g_array_free(table_offsets, true);
|
||
+}
|
||
+
|
||
+static void acpi_ram_update(MemoryRegion *mr, GArray *data)
|
||
+{
|
||
+ uint32_t size = acpi_data_len(data);
|
||
+
|
||
+ /*
|
||
+ * Make sure RAM size is correct -
|
||
+ * in case it got changed e.g. by migration
|
||
+ */
|
||
+ memory_region_ram_resize(mr, size, &error_abort);
|
||
+
|
||
+ memcpy(memory_region_get_ram_ptr(mr), data->data, size);
|
||
+ memory_region_set_dirty(mr, 0, size);
|
||
+}
|
||
+
|
||
+static void acpi_build_update(void *build_opaque)
|
||
+{
|
||
+ AcpiBuildState *build_state = build_opaque;
|
||
+ AcpiBuildTables tables;
|
||
+
|
||
+ /* No state to update or already patched? Nothing to do. */
|
||
+ if (!build_state || build_state->patched) {
|
||
+ return;
|
||
+ }
|
||
+ build_state->patched = 1;
|
||
+
|
||
+ acpi_build_tables_init(&tables);
|
||
+
|
||
+ acpi_build(&tables, MACHINE(qdev_get_machine()));
|
||
+
|
||
+ acpi_ram_update(build_state->table_mr, tables.table_data);
|
||
+
|
||
+ if (build_state->rsdp) {
|
||
+ memcpy(build_state->rsdp, tables.rsdp->data,
|
||
+ acpi_data_len(tables.rsdp));
|
||
+ } else {
|
||
+ acpi_ram_update(build_state->rsdp_mr, tables.rsdp);
|
||
+ }
|
||
+
|
||
+ acpi_ram_update(build_state->linker_mr, tables.linker->cmd_blob);
|
||
+ acpi_build_tables_cleanup(&tables, true);
|
||
+}
|
||
+
|
||
+static void acpi_build_reset(void *build_opaque)
|
||
+{
|
||
+ AcpiBuildState *build_state = build_opaque;
|
||
+ build_state->patched = 0;
|
||
+}
|
||
+
|
||
+static const VMStateDescription vmstate_acpi_build = {
|
||
+ .name = "acpi_build",
|
||
+ .version_id = 1,
|
||
+ .minimum_version_id = 1,
|
||
+ .fields = (VMStateField[]){ VMSTATE_UINT8(patched, AcpiBuildState),
|
||
+ VMSTATE_END_OF_LIST() },
|
||
+};
|
||
+
|
||
+void loongarch_acpi_setup(void)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(qdev_get_machine());
|
||
+ AcpiBuildTables tables;
|
||
+ AcpiBuildState *build_state;
|
||
+
|
||
+ if (!lsms->fw_cfg) {
|
||
+ ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n");
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (!lsms->acpi_build_enabled) {
|
||
+ ACPI_BUILD_DPRINTF("ACPI build disabled. Bailing out.\n");
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (!loongarch_is_acpi_enabled(lsms)) {
|
||
+ ACPI_BUILD_DPRINTF("ACPI disabled. Bailing out.\n");
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ build_state = g_malloc0(sizeof *build_state);
|
||
+
|
||
+ acpi_build_tables_init(&tables);
|
||
+ acpi_build(&tables, MACHINE(lsms));
|
||
+
|
||
+ /* Now expose it all to Guest */
|
||
+ build_state->table_mr =
|
||
+ acpi_add_rom_blob(acpi_build_update, build_state, tables.table_data,
|
||
+ ACPI_BUILD_TABLE_FILE);
|
||
+ assert(build_state->table_mr != NULL);
|
||
+
|
||
+ build_state->linker_mr =
|
||
+ acpi_add_rom_blob(acpi_build_update, build_state,
|
||
+ tables.linker->cmd_blob, "etc/table-loader");
|
||
+
|
||
+ fw_cfg_add_file(lsms->fw_cfg, ACPI_BUILD_TPMLOG_FILE, tables.tcpalog->data,
|
||
+ acpi_data_len(tables.tcpalog));
|
||
+
|
||
+ build_state->rsdp = NULL;
|
||
+ build_state->rsdp_mr = acpi_add_rom_blob(
|
||
+ acpi_build_update, build_state, tables.rsdp, ACPI_BUILD_RSDP_FILE);
|
||
+
|
||
+ qemu_register_reset(acpi_build_reset, build_state);
|
||
+ acpi_build_reset(build_state);
|
||
+ vmstate_register(NULL, 0, &vmstate_acpi_build, build_state);
|
||
+
|
||
+ /*
|
||
+ * Cleanup tables but don't free the memory: we track it
|
||
+ * in build_state.
|
||
+ */
|
||
+ acpi_build_tables_cleanup(&tables, false);
|
||
+}
|
||
diff --git a/hw/loongarch/acpi-build.h b/hw/loongarch/acpi-build.h
|
||
new file mode 100644
|
||
index 0000000000..97d53a9258
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/acpi-build.h
|
||
@@ -0,0 +1,32 @@
|
||
+/*
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#ifndef HW_LARCH_ACPI_BUILD_H
|
||
+#define HW_LARCH_ACPI_BUILD_H
|
||
+
|
||
+#define EFI_ACPI_OEM_ID "LARCH"
|
||
+#define EFI_ACPI_OEM_TABLE_ID "LARCH" /* OEM table id 8 bytes long */
|
||
+#define EFI_ACPI_OEM_REVISION 0x00000002
|
||
+#define EFI_ACPI_CREATOR_ID "LINUX"
|
||
+#define EFI_ACPI_CREATOR_REVISION 0x01000013
|
||
+
|
||
+#define ACPI_COMPATIBLE_1_0 0
|
||
+#define ACPI_COMPATIBLE_2_0 1
|
||
+
|
||
+void loongarch_acpi_setup(void);
|
||
+
|
||
+#endif
|
||
diff --git a/hw/loongarch/apic.c b/hw/loongarch/apic.c
|
||
new file mode 100644
|
||
index 0000000000..9e762cf0fe
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/apic.c
|
||
@@ -0,0 +1,689 @@
|
||
+/*
|
||
+ * Loongarch 3A5000 interrupt controller emulation
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qapi/error.h"
|
||
+#include "hw/boards.h"
|
||
+#include "hw/irq.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "hw/sysbus.h"
|
||
+#include "qemu/host-utils.h"
|
||
+#include "qemu/error-report.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "hw/hw.h"
|
||
+#include "hw/irq.h"
|
||
+#include "target/loongarch64/cpu.h"
|
||
+#include "exec/address-spaces.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+#include "migration/vmstate.h"
|
||
+
|
||
+#define DEBUG_APIC 0
|
||
+
|
||
+#define DPRINTF(fmt, ...) \
|
||
+ do { \
|
||
+ if (DEBUG_APIC) { \
|
||
+ fprintf(stderr, "APIC: " fmt, ##__VA_ARGS__); \
|
||
+ } \
|
||
+ } while (0)
|
||
+
|
||
+#define APIC_OFFSET 0x400
|
||
+#define APIC_BASE (0x1f010000ULL)
|
||
+#define EXTIOI_NODETYPE_START (0x4a0 - APIC_OFFSET)
|
||
+#define EXTIOI_NODETYPE_END (0x4c0 - APIC_OFFSET)
|
||
+#define EXTIOI_IPMAP_START (0x4c0 - APIC_OFFSET)
|
||
+#define EXTIOI_IPMAP_END (0x4c8 - APIC_OFFSET)
|
||
+#define EXTIOI_ENABLE_START (0x600 - APIC_OFFSET)
|
||
+#define EXTIOI_ENABLE_END (0x620 - APIC_OFFSET)
|
||
+#define EXTIOI_BOUNCE_START (0x680 - APIC_OFFSET)
|
||
+#define EXTIOI_BOUNCE_END (0x6a0 - APIC_OFFSET)
|
||
+#define EXTIOI_ISR_START (0x700 - APIC_OFFSET)
|
||
+#define EXTIOI_ISR_END (0x720 - APIC_OFFSET)
|
||
+#define EXTIOI_COREMAP_START (0xC00 - APIC_OFFSET)
|
||
+#define EXTIOI_COREMAP_END (0xD00 - APIC_OFFSET)
|
||
+#define EXTIOI_COREISR_START (0x10000)
|
||
+#define EXTIOI_COREISR_END (EXTIOI_COREISR_START + 0x10000)
|
||
+
|
||
+static int ext_irq_pre_save(void *opaque)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ apicState *apic = opaque;
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ struct kvm_loongarch_ls3a_extirq_state *kstate;
|
||
+ int ret, length, i, vcpuid;
|
||
+#endif
|
||
+ if ((!kvm_enabled()) || (!kvm_irqchip_in_kernel())) {
|
||
+ return 0;
|
||
+ }
|
||
+#ifdef CONFIG_KVM
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct kvm_loongarch_ls3a_extirq_state);
|
||
+ chip = g_malloc0(length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS3A_EXTIRQ;
|
||
+ chip->len = length;
|
||
+
|
||
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_IRQCHIP, chip);
|
||
+ if (ret < 0) {
|
||
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
|
||
+ abort();
|
||
+ }
|
||
+
|
||
+ kstate = (struct kvm_loongarch_ls3a_extirq_state *)chip->data;
|
||
+ for (i = 0; i < EXTIOI_IRQS_BITMAP_SIZE; i++) {
|
||
+ apic->ext_en[i] = kstate->ext_en_r.reg_u8[i];
|
||
+ apic->ext_bounce[i] = kstate->bounce_r.reg_u8[i];
|
||
+ apic->ext_isr[i] = kstate->ext_isr_r.reg_u8[i];
|
||
+ for (vcpuid = 0; vcpuid < MAX_CORES; vcpuid++) {
|
||
+ apic->ext_coreisr[vcpuid][i] =
|
||
+ kstate->ext_core_isr_r.reg_u8[vcpuid][i];
|
||
+ }
|
||
+ }
|
||
+ for (i = 0; i < EXTIOI_IRQS_IPMAP_SIZE; i++) {
|
||
+ apic->ext_ipmap[i] = kstate->ip_map_r.reg_u8[i];
|
||
+ }
|
||
+ for (i = 0; i < EXTIOI_IRQS; i++) {
|
||
+ apic->ext_coremap[i] = kstate->core_map_r.reg_u8[i];
|
||
+ }
|
||
+ for (i = 0; i < 16; i++) {
|
||
+ apic->ext_nodetype[i] = kstate->node_type_r.reg_u16[i];
|
||
+ }
|
||
+ g_free(chip);
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static int ext_irq_post_load(void *opaque, int version)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ apicState *apic = opaque;
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ struct kvm_loongarch_ls3a_extirq_state *kstate;
|
||
+ int ret, length, i, vcpuid;
|
||
+#endif
|
||
+ if ((!kvm_enabled()) || (!kvm_irqchip_in_kernel())) {
|
||
+ return 0;
|
||
+ }
|
||
+#ifdef CONFIG_KVM
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct kvm_loongarch_ls3a_extirq_state);
|
||
+ chip = g_malloc0(length);
|
||
+
|
||
+ chip->chip_id = KVM_IRQCHIP_LS3A_EXTIRQ;
|
||
+ chip->len = length;
|
||
+
|
||
+ kstate = (struct kvm_loongarch_ls3a_extirq_state *)chip->data;
|
||
+ for (i = 0; i < EXTIOI_IRQS_BITMAP_SIZE; i++) {
|
||
+ kstate->ext_en_r.reg_u8[i] = apic->ext_en[i];
|
||
+ kstate->bounce_r.reg_u8[i] = apic->ext_bounce[i];
|
||
+ kstate->ext_isr_r.reg_u8[i] = apic->ext_isr[i];
|
||
+ for (vcpuid = 0; vcpuid < MAX_CORES; vcpuid++) {
|
||
+ kstate->ext_core_isr_r.reg_u8[vcpuid][i] =
|
||
+ apic->ext_coreisr[vcpuid][i];
|
||
+ }
|
||
+ }
|
||
+ for (i = 0; i < EXTIOI_IRQS_IPMAP_SIZE; i++) {
|
||
+ kstate->ip_map_r.reg_u8[i] = apic->ext_ipmap[i];
|
||
+ }
|
||
+ for (i = 0; i < EXTIOI_IRQS; i++) {
|
||
+ kstate->core_map_r.reg_u8[i] = apic->ext_coremap[i];
|
||
+ }
|
||
+ for (i = 0; i < 16; i++) {
|
||
+ kstate->node_type_r.reg_u16[i] = apic->ext_nodetype[i];
|
||
+ }
|
||
+
|
||
+ ret = kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, chip);
|
||
+ if (ret < 0) {
|
||
+ fprintf(stderr, "KVM_SET_IRQCHIP failed: %s\n", strerror(ret));
|
||
+ abort();
|
||
+ }
|
||
+ g_free(chip);
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+typedef struct nodeApicState {
|
||
+ unsigned long addr;
|
||
+ int nodeid;
|
||
+ apicState *apic;
|
||
+} nodeApicState;
|
||
+
|
||
+static void ioapic_update_irq(void *opaque, int irq, int level)
|
||
+{
|
||
+ apicState *s = opaque;
|
||
+ uint8_t ipnum, cpu, cpu_ipnum;
|
||
+ unsigned long found1, found2;
|
||
+ uint8_t reg_count, reg_bit;
|
||
+
|
||
+ reg_count = irq / 32;
|
||
+ reg_bit = irq % 32;
|
||
+
|
||
+ ipnum = s->ext_sw_ipmap[irq];
|
||
+ cpu = s->ext_sw_coremap[irq];
|
||
+ cpu_ipnum = cpu * LS3A_INTC_IP + ipnum;
|
||
+ if (level == 1) {
|
||
+ if (test_bit(reg_bit, ((void *)s->ext_en + 0x4 * reg_count)) ==
|
||
+ false) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (test_bit(reg_bit, ((void *)s->ext_isr + 0x4 * reg_count)) ==
|
||
+ false) {
|
||
+ return;
|
||
+ }
|
||
+ bitmap_set(((void *)s->ext_coreisr[cpu] + 0x4 * reg_count), reg_bit,
|
||
+ 1);
|
||
+ found1 =
|
||
+ find_next_bit(((void *)s->ext_ipisr[cpu_ipnum] + 0x4 * reg_count),
|
||
+ EXTIOI_IRQS, 0);
|
||
+ bitmap_set(((void *)s->ext_ipisr[cpu_ipnum] + 0x4 * reg_count),
|
||
+ reg_bit, 1);
|
||
+ if (found1 >= EXTIOI_IRQS) {
|
||
+ qemu_set_irq(s->parent_irq[cpu][ipnum], level);
|
||
+ }
|
||
+ } else {
|
||
+ bitmap_clear(((void *)s->ext_isr + 0x4 * reg_count), reg_bit, 1);
|
||
+ bitmap_clear(((void *)s->ext_coreisr[cpu] + 0x4 * reg_count), reg_bit,
|
||
+ 1);
|
||
+ found1 =
|
||
+ find_next_bit(((void *)s->ext_ipisr[cpu_ipnum] + 0x4 * reg_count),
|
||
+ EXTIOI_IRQS, 0);
|
||
+ found1 += reg_count * 32;
|
||
+ bitmap_clear(((void *)s->ext_ipisr[cpu_ipnum] + 0x4 * reg_count),
|
||
+ reg_bit, 1);
|
||
+ found2 =
|
||
+ find_next_bit(((void *)s->ext_ipisr[cpu_ipnum] + 0x4 * reg_count),
|
||
+ EXTIOI_IRQS, 0);
|
||
+ if ((found1 < EXTIOI_IRQS) && (found2 >= EXTIOI_IRQS)) {
|
||
+ qemu_set_irq(s->parent_irq[cpu][ipnum], level);
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+static void ioapic_setirq(void *opaque, int irq, int level)
|
||
+{
|
||
+ apicState *s = opaque;
|
||
+ uint8_t reg_count, reg_bit;
|
||
+
|
||
+ reg_count = irq / 32;
|
||
+ reg_bit = irq % 32;
|
||
+
|
||
+ if (level) {
|
||
+ bitmap_set(((void *)s->ext_isr + 0x4 * reg_count), reg_bit, 1);
|
||
+ } else {
|
||
+ bitmap_clear(((void *)s->ext_isr + 0x4 * reg_count), reg_bit, 1);
|
||
+ }
|
||
+
|
||
+ ioapic_update_irq(s, irq, level);
|
||
+}
|
||
+
|
||
+static uint32_t apic_readb(void *opaque, hwaddr addr)
|
||
+{
|
||
+ nodeApicState *node;
|
||
+ apicState *state;
|
||
+ unsigned long off;
|
||
+ uint8_t ret;
|
||
+ int cpu;
|
||
+
|
||
+ node = (nodeApicState *)opaque;
|
||
+ state = node->apic;
|
||
+ off = addr & 0xfffff;
|
||
+ ret = 0;
|
||
+ if ((off >= EXTIOI_ENABLE_START) && (off < EXTIOI_ENABLE_END)) {
|
||
+ off -= EXTIOI_ENABLE_START;
|
||
+ ret = *(uint8_t *)((void *)state->ext_en + off);
|
||
+ } else if ((off >= EXTIOI_BOUNCE_START) && (off < EXTIOI_BOUNCE_END)) {
|
||
+ off -= EXTIOI_BOUNCE_START;
|
||
+ ret = *(uint8_t *)((void *)state->ext_bounce + off);
|
||
+ } else if ((off >= EXTIOI_ISR_START) && (off < EXTIOI_ISR_END)) {
|
||
+ off -= EXTIOI_ISR_START;
|
||
+ ret = *(uint8_t *)((void *)state->ext_isr + off);
|
||
+ } else if ((off >= EXTIOI_COREISR_START) && (off < EXTIOI_COREISR_END)) {
|
||
+ off -= EXTIOI_COREISR_START;
|
||
+ cpu = (off >> 8) & 0xff;
|
||
+ ret = *(uint8_t *)((void *)state->ext_coreisr[cpu] + (off & 0x1f));
|
||
+ } else if ((off >= EXTIOI_IPMAP_START) && (off < EXTIOI_IPMAP_END)) {
|
||
+ off -= EXTIOI_IPMAP_START;
|
||
+ ret = *(uint8_t *)((void *)state->ext_ipmap + off);
|
||
+ } else if ((off >= EXTIOI_COREMAP_START) && (off < EXTIOI_COREMAP_END)) {
|
||
+ off -= EXTIOI_COREMAP_START;
|
||
+ ret = *(uint8_t *)((void *)state->ext_coremap + off);
|
||
+ } else if ((off >= EXTIOI_NODETYPE_START) && (off < EXTIOI_NODETYPE_END)) {
|
||
+ off -= EXTIOI_NODETYPE_START;
|
||
+ ret = *(uint8_t *)((void *)state->ext_nodetype + off);
|
||
+ }
|
||
+
|
||
+ DPRINTF("readb reg 0x" TARGET_FMT_plx " = %x\n", node->addr + addr, ret);
|
||
+ return ret;
|
||
+}
|
||
+
|
||
+static uint32_t apic_readw(void *opaque, hwaddr addr)
|
||
+{
|
||
+ nodeApicState *node;
|
||
+ apicState *state;
|
||
+ unsigned long off;
|
||
+ uint16_t ret;
|
||
+ int cpu;
|
||
+
|
||
+ node = (nodeApicState *)opaque;
|
||
+ state = node->apic;
|
||
+ off = addr & 0xfffff;
|
||
+ ret = 0;
|
||
+ if ((off >= EXTIOI_ENABLE_START) && (off < EXTIOI_ENABLE_END)) {
|
||
+ off -= EXTIOI_ENABLE_START;
|
||
+ ret = *(uint16_t *)((void *)state->ext_en + off);
|
||
+ } else if ((off >= EXTIOI_BOUNCE_START) && (off < EXTIOI_BOUNCE_END)) {
|
||
+ off -= EXTIOI_BOUNCE_START;
|
||
+ ret = *(uint16_t *)((void *)state->ext_bounce + off);
|
||
+ } else if ((off >= EXTIOI_ISR_START) && (off < EXTIOI_ISR_END)) {
|
||
+ off -= EXTIOI_ISR_START;
|
||
+ ret = *(uint16_t *)((void *)state->ext_isr + off);
|
||
+ } else if ((off >= EXTIOI_COREISR_START) && (off < EXTIOI_COREISR_END)) {
|
||
+ off -= EXTIOI_COREISR_START;
|
||
+ cpu = (off >> 8) & 0xff;
|
||
+ ret = *(uint16_t *)((void *)state->ext_coreisr[cpu] + (off & 0x1f));
|
||
+ } else if ((off >= EXTIOI_IPMAP_START) && (off < EXTIOI_IPMAP_END)) {
|
||
+ off -= EXTIOI_IPMAP_START;
|
||
+ ret = *(uint16_t *)((void *)state->ext_ipmap + off);
|
||
+ } else if ((off >= EXTIOI_COREMAP_START) && (off < EXTIOI_COREMAP_END)) {
|
||
+ off -= EXTIOI_COREMAP_START;
|
||
+ ret = *(uint16_t *)((void *)state->ext_coremap + off);
|
||
+ } else if ((off >= EXTIOI_NODETYPE_START) && (off < EXTIOI_NODETYPE_END)) {
|
||
+ off -= EXTIOI_NODETYPE_START;
|
||
+ ret = *(uint16_t *)((void *)state->ext_nodetype + off);
|
||
+ }
|
||
+
|
||
+ DPRINTF("readw reg 0x" TARGET_FMT_plx " = %x\n", node->addr + addr, ret);
|
||
+ return ret;
|
||
+}
|
||
+
|
||
+static uint32_t apic_readl(void *opaque, hwaddr addr)
|
||
+{
|
||
+ nodeApicState *node;
|
||
+ apicState *state;
|
||
+ unsigned long off;
|
||
+ uint32_t ret;
|
||
+ int cpu;
|
||
+
|
||
+ node = (nodeApicState *)opaque;
|
||
+ state = node->apic;
|
||
+ off = addr & 0xfffff;
|
||
+ ret = 0;
|
||
+ if ((off >= EXTIOI_ENABLE_START) && (off < EXTIOI_ENABLE_END)) {
|
||
+ off -= EXTIOI_ENABLE_START;
|
||
+ ret = *(uint32_t *)((void *)state->ext_en + off);
|
||
+ } else if ((off >= EXTIOI_BOUNCE_START) && (off < EXTIOI_BOUNCE_END)) {
|
||
+ off -= EXTIOI_BOUNCE_START;
|
||
+ ret = *(uint32_t *)((void *)state->ext_bounce + off);
|
||
+ } else if ((off >= EXTIOI_ISR_START) && (off < EXTIOI_ISR_END)) {
|
||
+ off -= EXTIOI_ISR_START;
|
||
+ ret = *(uint32_t *)((void *)state->ext_isr + off);
|
||
+ } else if ((off >= EXTIOI_COREISR_START) && (off < EXTIOI_COREISR_END)) {
|
||
+ off -= EXTIOI_COREISR_START;
|
||
+ cpu = (off >> 8) & 0xff;
|
||
+ ret = *(uint32_t *)((void *)state->ext_coreisr[cpu] + (off & 0x1f));
|
||
+ } else if ((off >= EXTIOI_IPMAP_START) && (off < EXTIOI_IPMAP_END)) {
|
||
+ off -= EXTIOI_IPMAP_START;
|
||
+ ret = *(uint32_t *)((void *)state->ext_ipmap + off);
|
||
+ } else if ((off >= EXTIOI_COREMAP_START) && (off < EXTIOI_COREMAP_END)) {
|
||
+ off -= EXTIOI_COREMAP_START;
|
||
+ ret = *(uint32_t *)((void *)state->ext_coremap + off);
|
||
+ } else if ((off >= EXTIOI_NODETYPE_START) && (off < EXTIOI_NODETYPE_END)) {
|
||
+ off -= EXTIOI_NODETYPE_START;
|
||
+ ret = *(uint32_t *)((void *)state->ext_nodetype + off);
|
||
+ }
|
||
+
|
||
+ DPRINTF("readl reg 0x" TARGET_FMT_plx " = %x\n", node->addr + addr, ret);
|
||
+ return ret;
|
||
+}
|
||
+
|
||
+static void apic_writeb(void *opaque, hwaddr addr, uint32_t val)
|
||
+{
|
||
+ nodeApicState *node;
|
||
+ apicState *state;
|
||
+ unsigned long off;
|
||
+ uint8_t old;
|
||
+ int cpu, i, ipnum, level, mask;
|
||
+
|
||
+ node = (nodeApicState *)opaque;
|
||
+ state = node->apic;
|
||
+ off = addr & 0xfffff;
|
||
+ if ((off >= EXTIOI_ENABLE_START) && (off < EXTIOI_ENABLE_END)) {
|
||
+ off -= EXTIOI_ENABLE_START;
|
||
+ old = *(uint8_t *)((void *)state->ext_en + off);
|
||
+ if (old != val) {
|
||
+ *(uint8_t *)((void *)state->ext_en + off) = val;
|
||
+ old = old ^ val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 8; i++) {
|
||
+ if (old & mask) {
|
||
+ level = !!(val & (0x1 << i));
|
||
+ ioapic_update_irq(state, i + off * 8, level);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_BOUNCE_START) && (off < EXTIOI_BOUNCE_END)) {
|
||
+ off -= EXTIOI_BOUNCE_START;
|
||
+ *(uint8_t *)((void *)state->ext_bounce + off) = val;
|
||
+ } else if ((off >= EXTIOI_ISR_START) && (off < EXTIOI_ISR_END)) {
|
||
+ off -= EXTIOI_ISR_START;
|
||
+ old = *(uint8_t *)((void *)state->ext_isr + off);
|
||
+ *(uint8_t *)((void *)state->ext_isr + off) = old & ~val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 8; i++) {
|
||
+ if ((old & mask) && (val & mask)) {
|
||
+ ioapic_update_irq(state, i + off * 8, 0);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_COREISR_START) && (off < EXTIOI_COREISR_END)) {
|
||
+ off -= EXTIOI_COREISR_START;
|
||
+ cpu = (off >> 8) & 0xff;
|
||
+ off = off & 0x1f;
|
||
+ old = *(uint8_t *)((void *)state->ext_coreisr[cpu] + off);
|
||
+ *(uint8_t *)((void *)state->ext_coreisr[cpu] + off) = old & ~val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 8; i++) {
|
||
+ if ((old & mask) && (val & mask)) {
|
||
+ ioapic_update_irq(state, i + off * 8, 0);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_IPMAP_START) && (off < EXTIOI_IPMAP_END)) {
|
||
+ off -= EXTIOI_IPMAP_START;
|
||
+ val = val & 0xf;
|
||
+ *(uint8_t *)((void *)state->ext_ipmap + off) = val;
|
||
+ ipnum = 0;
|
||
+ for (i = 0; i < 4; i++) {
|
||
+ if (val & (0x1 << i)) {
|
||
+ ipnum = i;
|
||
+ break;
|
||
+ }
|
||
+ }
|
||
+ if (val) {
|
||
+ for (i = 0; i < 32; i++) {
|
||
+ cpu = off * 32 + i;
|
||
+ state->ext_sw_ipmap[cpu] = ipnum;
|
||
+ }
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_COREMAP_START) && (off < EXTIOI_COREMAP_END)) {
|
||
+ off -= EXTIOI_COREMAP_START;
|
||
+ val = val & 0xff;
|
||
+ *(uint8_t *)((void *)state->ext_coremap + off) = val;
|
||
+ state->ext_sw_coremap[off] = val;
|
||
+ } else if ((off >= EXTIOI_NODETYPE_START) && (off < EXTIOI_NODETYPE_END)) {
|
||
+ off -= EXTIOI_NODETYPE_START;
|
||
+ *(uint8_t *)((void *)state->ext_nodetype + off) = val;
|
||
+ }
|
||
+
|
||
+ DPRINTF("writeb reg 0x" TARGET_FMT_plx " = %x\n", node->addr + addr, val);
|
||
+}
|
||
+
|
||
+static void apic_writew(void *opaque, hwaddr addr, uint32_t val)
|
||
+{
|
||
+ nodeApicState *node;
|
||
+ apicState *state;
|
||
+ unsigned long off;
|
||
+ uint16_t old;
|
||
+ int cpu, i, level, mask;
|
||
+
|
||
+ node = (nodeApicState *)opaque;
|
||
+ state = node->apic;
|
||
+ off = addr & 0xfffff;
|
||
+ if ((off >= EXTIOI_ENABLE_START) && (off < EXTIOI_ENABLE_END)) {
|
||
+ off -= EXTIOI_ENABLE_START;
|
||
+ old = *(uint16_t *)((void *)state->ext_en + off);
|
||
+ if (old != val) {
|
||
+ *(uint16_t *)((void *)state->ext_en + off) = val;
|
||
+ old = old ^ val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 16; i++) {
|
||
+ if (old & mask) {
|
||
+ level = !!(val & (0x1 << i));
|
||
+ ioapic_update_irq(state, i + off * 8, level);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_BOUNCE_START) && (off < EXTIOI_BOUNCE_END)) {
|
||
+ off -= EXTIOI_BOUNCE_START;
|
||
+ *(uint16_t *)((void *)state->ext_bounce + off) = val;
|
||
+ } else if ((off >= EXTIOI_ISR_START) && (off < EXTIOI_ISR_END)) {
|
||
+ off -= EXTIOI_ISR_START;
|
||
+ old = *(uint16_t *)((void *)state->ext_isr + off);
|
||
+ *(uint16_t *)((void *)state->ext_isr + off) = old & ~val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 16; i++) {
|
||
+ if ((old & mask) && (val & mask)) {
|
||
+ ioapic_update_irq(state, i + off * 8, 0);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_COREISR_START) && (off < EXTIOI_COREISR_END)) {
|
||
+ off -= EXTIOI_COREISR_START;
|
||
+ cpu = (off >> 8) & 0xff;
|
||
+ off = off & 0x1f;
|
||
+ old = *(uint16_t *)((void *)state->ext_coreisr[cpu] + off);
|
||
+ *(uint16_t *)((void *)state->ext_coreisr[cpu] + off) = old & ~val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 16; i++) {
|
||
+ if ((old & mask) && (val & mask)) {
|
||
+ ioapic_update_irq(state, i + off * 8, 0);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_IPMAP_START) && (off < EXTIOI_IPMAP_END)) {
|
||
+ apic_writeb(opaque, addr, val & 0xff);
|
||
+ apic_writeb(opaque, addr + 1, (val >> 8) & 0xff);
|
||
+
|
||
+ } else if ((off >= EXTIOI_COREMAP_START) && (off < EXTIOI_COREMAP_END)) {
|
||
+ apic_writeb(opaque, addr, val & 0xff);
|
||
+ apic_writeb(opaque, addr + 1, (val >> 8) & 0xff);
|
||
+
|
||
+ } else if ((off >= EXTIOI_NODETYPE_START) && (off < EXTIOI_NODETYPE_END)) {
|
||
+ off -= EXTIOI_NODETYPE_START;
|
||
+ *(uint16_t *)((void *)state->ext_nodetype + off) = val;
|
||
+ }
|
||
+
|
||
+ DPRINTF("writew reg 0x" TARGET_FMT_plx " = %x\n", node->addr + addr, val);
|
||
+}
|
||
+
|
||
+static void apic_writel(void *opaque, hwaddr addr, uint32_t val)
|
||
+{
|
||
+ nodeApicState *node;
|
||
+ apicState *state;
|
||
+ unsigned long off;
|
||
+ uint32_t old;
|
||
+ int cpu, i, level, mask;
|
||
+
|
||
+ node = (nodeApicState *)opaque;
|
||
+ state = node->apic;
|
||
+ off = addr & 0xfffff;
|
||
+ if ((off >= EXTIOI_ENABLE_START) && (off < EXTIOI_ENABLE_END)) {
|
||
+ off -= EXTIOI_ENABLE_START;
|
||
+ old = *(uint32_t *)((void *)state->ext_en + off);
|
||
+ if (old != val) {
|
||
+ *(uint32_t *)((void *)state->ext_en + off) = val;
|
||
+ old = old ^ val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 32; i++) {
|
||
+ if (old & mask) {
|
||
+ level = !!(val & (0x1 << i));
|
||
+ ioapic_update_irq(state, i + off * 8, level);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_BOUNCE_START) && (off < EXTIOI_BOUNCE_END)) {
|
||
+ off -= EXTIOI_BOUNCE_START;
|
||
+ *(uint32_t *)((void *)state->ext_bounce + off) = val;
|
||
+ } else if ((off >= EXTIOI_ISR_START) && (off < EXTIOI_ISR_END)) {
|
||
+ off -= EXTIOI_ISR_START;
|
||
+ old = *(uint32_t *)((void *)state->ext_isr + off);
|
||
+ *(uint32_t *)((void *)state->ext_isr + off) = old & ~val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 32; i++) {
|
||
+ if ((old & mask) && (val & mask)) {
|
||
+ ioapic_update_irq(state, i + off * 8, 0);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_COREISR_START) && (off < EXTIOI_COREISR_END)) {
|
||
+ off -= EXTIOI_COREISR_START;
|
||
+ cpu = (off >> 8) & 0xff;
|
||
+ off = off & 0x1f;
|
||
+ old = *(uint32_t *)((void *)state->ext_coreisr[cpu] + off);
|
||
+ *(uint32_t *)((void *)state->ext_coreisr[cpu] + off) = old & ~val;
|
||
+ mask = 0x1;
|
||
+ for (i = 0; i < 32; i++) {
|
||
+ if ((old & mask) && (val & mask)) {
|
||
+ ioapic_update_irq(state, i + off * 8, 0);
|
||
+ }
|
||
+ mask = mask << 1;
|
||
+ }
|
||
+ } else if ((off >= EXTIOI_IPMAP_START) && (off < EXTIOI_IPMAP_END)) {
|
||
+ apic_writeb(opaque, addr, val & 0xff);
|
||
+ apic_writeb(opaque, addr + 1, (val >> 8) & 0xff);
|
||
+ apic_writeb(opaque, addr + 2, (val >> 16) & 0xff);
|
||
+ apic_writeb(opaque, addr + 3, (val >> 24) & 0xff);
|
||
+
|
||
+ } else if ((off >= EXTIOI_COREMAP_START) && (off < EXTIOI_COREMAP_END)) {
|
||
+ apic_writeb(opaque, addr, val & 0xff);
|
||
+ apic_writeb(opaque, addr + 1, (val >> 8) & 0xff);
|
||
+ apic_writeb(opaque, addr + 2, (val >> 16) & 0xff);
|
||
+ apic_writeb(opaque, addr + 3, (val >> 24) & 0xff);
|
||
+
|
||
+ } else if ((off >= EXTIOI_NODETYPE_START) && (off < EXTIOI_NODETYPE_END)) {
|
||
+ off -= EXTIOI_NODETYPE_START;
|
||
+ *(uint32_t *)((void *)state->ext_nodetype + off) = val;
|
||
+ }
|
||
+
|
||
+ DPRINTF("writel reg 0x" TARGET_FMT_plx " = %x\n", node->addr + addr, val);
|
||
+}
|
||
+
|
||
+static uint64_t apic_readfn(void *opaque, hwaddr addr, unsigned size)
|
||
+{
|
||
+ switch (size) {
|
||
+ case 1:
|
||
+ return apic_readb(opaque, addr);
|
||
+ case 2:
|
||
+ return apic_readw(opaque, addr);
|
||
+ case 4:
|
||
+ return apic_readl(opaque, addr);
|
||
+ default:
|
||
+ g_assert_not_reached();
|
||
+ }
|
||
+}
|
||
+
|
||
+static void apic_writefn(void *opaque, hwaddr addr, uint64_t value,
|
||
+ unsigned size)
|
||
+{
|
||
+ switch (size) {
|
||
+ case 1:
|
||
+ apic_writeb(opaque, addr, value);
|
||
+ break;
|
||
+ case 2:
|
||
+ apic_writew(opaque, addr, value);
|
||
+ break;
|
||
+ case 4:
|
||
+ apic_writel(opaque, addr, value);
|
||
+ break;
|
||
+ default:
|
||
+ g_assert_not_reached();
|
||
+ }
|
||
+}
|
||
+
|
||
+static const VMStateDescription vmstate_apic = {
|
||
+ .name = "apic",
|
||
+ .version_id = 1,
|
||
+ .minimum_version_id = 1,
|
||
+ .pre_save = ext_irq_pre_save,
|
||
+ .post_load = ext_irq_post_load,
|
||
+ .fields =
|
||
+ (VMStateField[]){
|
||
+ VMSTATE_UINT8_ARRAY(ext_en, apicState, EXTIOI_IRQS_BITMAP_SIZE),
|
||
+ VMSTATE_UINT8_ARRAY(ext_bounce, apicState,
|
||
+ EXTIOI_IRQS_BITMAP_SIZE),
|
||
+ VMSTATE_UINT8_ARRAY(ext_isr, apicState, EXTIOI_IRQS_BITMAP_SIZE),
|
||
+ VMSTATE_UINT8_2DARRAY(ext_coreisr, apicState, MAX_CORES,
|
||
+ EXTIOI_IRQS_BITMAP_SIZE),
|
||
+ VMSTATE_UINT8_ARRAY(ext_ipmap, apicState, EXTIOI_IRQS_IPMAP_SIZE),
|
||
+ VMSTATE_UINT8_ARRAY(ext_coremap, apicState, EXTIOI_IRQS),
|
||
+ VMSTATE_UINT16_ARRAY(ext_nodetype, apicState, 16),
|
||
+ VMSTATE_UINT64(ext_control, apicState),
|
||
+ VMSTATE_UINT8_ARRAY(ext_sw_ipmap, apicState, EXTIOI_IRQS),
|
||
+ VMSTATE_UINT8_ARRAY(ext_sw_coremap, apicState, EXTIOI_IRQS),
|
||
+ VMSTATE_UINT8_2DARRAY(ext_ipisr, apicState,
|
||
+ MAX_CORES *LS3A_INTC_IP,
|
||
+ EXTIOI_IRQS_BITMAP_SIZE),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static const MemoryRegionOps apic_ops = {
|
||
+ .read = apic_readfn,
|
||
+ .write = apic_writefn,
|
||
+ .impl.min_access_size = 1,
|
||
+ .impl.max_access_size = 4,
|
||
+ .valid.min_access_size = 1,
|
||
+ .valid.max_access_size = 4,
|
||
+ .endianness = DEVICE_NATIVE_ENDIAN,
|
||
+};
|
||
+
|
||
+int cpu_init_apic(LoongarchMachineState *ms, CPULOONGARCHState *env, int cpu)
|
||
+{
|
||
+ apicState *apic;
|
||
+ nodeApicState *node;
|
||
+ MemoryRegion *iomem;
|
||
+ unsigned long base;
|
||
+ int pin;
|
||
+ char str[32];
|
||
+
|
||
+ if (ms->apic == NULL) {
|
||
+ apic = g_malloc0(sizeof(apicState));
|
||
+ vmstate_register(NULL, 0, &vmstate_apic, apic);
|
||
+ apic->irq = qemu_allocate_irqs(ioapic_setirq, apic, EXTIOI_IRQS);
|
||
+
|
||
+ for (pin = 0; pin < LS3A_INTC_IP; pin++) {
|
||
+ /* cpu_pin[9:2] <= intc_pin[7:0] */
|
||
+ apic->parent_irq[cpu][pin] = env->irq[pin + 2];
|
||
+ }
|
||
+ ms->apic = apic;
|
||
+
|
||
+ if (cpu == 0) {
|
||
+ base = APIC_BASE;
|
||
+ node = g_malloc0(sizeof(nodeApicState));
|
||
+ node->apic = ms->apic;
|
||
+ node->addr = base;
|
||
+
|
||
+ iomem = g_new(MemoryRegion, 1);
|
||
+ sprintf(str, "apic%d", cpu);
|
||
+ /* extioi addr 0x1f010000~0x1f02ffff */
|
||
+ memory_region_init_io(iomem, NULL, &apic_ops, node, str, 0x20000);
|
||
+ memory_region_add_subregion(get_system_memory(), base, iomem);
|
||
+ }
|
||
+
|
||
+ } else {
|
||
+ if (cpu != 0) {
|
||
+ for (pin = 0; pin < LS3A_INTC_IP; pin++) {
|
||
+ ms->apic->parent_irq[cpu][pin] = env->irq[pin + 2];
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ return 0;
|
||
+}
|
||
diff --git a/hw/loongarch/ioapic.c b/hw/loongarch/ioapic.c
|
||
new file mode 100644
|
||
index 0000000000..102102781f
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/ioapic.c
|
||
@@ -0,0 +1,419 @@
|
||
+/*
|
||
+ * LS7A1000 Northbridge IOAPIC support
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "hw/sysbus.h"
|
||
+#include "hw/irq.h"
|
||
+#include "qemu/log.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "linux/kvm.h"
|
||
+#include "migration/vmstate.h"
|
||
+
|
||
+#define DEBUG_LS7A_APIC 0
|
||
+
|
||
+#define DPRINTF(fmt, ...) \
|
||
+ do { \
|
||
+ if (DEBUG_LS7A_APIC) { \
|
||
+ fprintf(stderr, "IOAPIC: " fmt, ##__VA_ARGS__); \
|
||
+ } \
|
||
+ } while (0)
|
||
+
|
||
+#define TYPE_LS7A_APIC "ioapic"
|
||
+#define LS7A_APIC(obj) OBJECT_CHECK(LS7AApicState, (obj), TYPE_LS7A_APIC)
|
||
+
|
||
+#define LS7A_IOAPIC_ROUTE_ENTRY_OFFSET 0x100
|
||
+#define LS7A_IOAPIC_INT_ID_OFFSET 0x00
|
||
+#define LS7A_INT_ID_VAL 0x7000000UL
|
||
+#define LS7A_INT_ID_VER 0x1f0001UL
|
||
+#define LS7A_IOAPIC_INT_MASK_OFFSET 0x20
|
||
+#define LS7A_IOAPIC_INT_EDGE_OFFSET 0x60
|
||
+#define LS7A_IOAPIC_INT_CLEAR_OFFSET 0x80
|
||
+#define LS7A_IOAPIC_INT_STATUS_OFFSET 0x3a0
|
||
+#define LS7A_IOAPIC_INT_POL_OFFSET 0x3e0
|
||
+#define LS7A_IOAPIC_HTMSI_EN_OFFSET 0x40
|
||
+#define LS7A_IOAPIC_HTMSI_VEC_OFFSET 0x200
|
||
+#define LS7A_AUTO_CTRL0_OFFSET 0xc0
|
||
+#define LS7A_AUTO_CTRL1_OFFSET 0xe0
|
||
+
|
||
+typedef struct LS7AApicState {
|
||
+ SysBusDevice parent_obj;
|
||
+ qemu_irq parent_irq[257];
|
||
+ uint64_t int_id;
|
||
+ uint64_t int_mask; /*0x020 interrupt mask register*/
|
||
+ uint64_t htmsi_en; /*0x040 1=msi*/
|
||
+ uint64_t intedge; /*0x060 edge=1 level =0*/
|
||
+ uint64_t intclr; /*0x080 for clean edge int,set 1 clean,set 0 is noused*/
|
||
+ uint64_t auto_crtl0; /*0x0c0*/
|
||
+ uint64_t auto_crtl1; /*0x0e0*/
|
||
+ uint8_t route_entry[64]; /*0x100 - 0x140*/
|
||
+ uint8_t htmsi_vector[64]; /*0x200 - 0x240*/
|
||
+ uint64_t intisr_chip0; /*0x300*/
|
||
+ uint64_t intisr_chip1; /*0x320*/
|
||
+ uint64_t last_intirr; /* edge detection */
|
||
+ uint64_t intirr; /* 0x380 interrupt request register */
|
||
+ uint64_t intisr; /* 0x3a0 interrupt service register */
|
||
+ /*
|
||
+ * 0x3e0 interrupt level polarity
|
||
+ * selection register 0 for high level tirgger
|
||
+ */
|
||
+ uint64_t int_polarity;
|
||
+ MemoryRegion iomem;
|
||
+} LS7AApicState;
|
||
+
|
||
+static void update_irq(LS7AApicState *s)
|
||
+{
|
||
+ int i;
|
||
+ if ((s->intirr & (~s->int_mask)) & (~s->htmsi_en)) {
|
||
+ DPRINTF("7a update irqline up\n");
|
||
+ s->intisr = (s->intirr & (~s->int_mask) & (~s->htmsi_en));
|
||
+ qemu_set_irq(s->parent_irq[256], 1);
|
||
+ } else {
|
||
+ DPRINTF("7a update irqline down\n");
|
||
+ s->intisr &= (~s->htmsi_en);
|
||
+ qemu_set_irq(s->parent_irq[256], 0);
|
||
+ }
|
||
+ if (s->htmsi_en) {
|
||
+ for (i = 0; i < 64; i++) {
|
||
+ if ((((~s->intisr) & s->intirr) & s->htmsi_en) & (1ULL << i)) {
|
||
+ s->intisr |= 1ULL << i;
|
||
+ qemu_set_irq(s->parent_irq[s->htmsi_vector[i]], 1);
|
||
+ } else if (((~(s->intisr | s->intirr)) & s->htmsi_en) &
|
||
+ (1ULL << i)) {
|
||
+ qemu_set_irq(s->parent_irq[s->htmsi_vector[i]], 0);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+static void irq_handler(void *opaque, int irq, int level)
|
||
+{
|
||
+ LS7AApicState *s = opaque;
|
||
+
|
||
+ assert(irq < 64);
|
||
+ uint64_t mask = 1ULL << irq;
|
||
+ DPRINTF("------ %s irq %d %d\n", __func__, irq, level);
|
||
+
|
||
+ if (s->intedge & mask) {
|
||
+ /* edge triggered */
|
||
+ /*TODO*/
|
||
+ } else {
|
||
+ /* level triggered */
|
||
+ if (level) {
|
||
+ s->intirr |= mask;
|
||
+ } else {
|
||
+ s->intirr &= ~mask;
|
||
+ }
|
||
+ }
|
||
+ update_irq(s);
|
||
+}
|
||
+
|
||
+static uint64_t ls7a_apic_reg_read(void *opaque, hwaddr addr, unsigned size)
|
||
+{
|
||
+ LS7AApicState *a = opaque;
|
||
+ uint64_t val = 0;
|
||
+ uint64_t offset;
|
||
+ int64_t offset_tmp;
|
||
+ offset = addr & 0xfff;
|
||
+ if (8 == size) {
|
||
+ switch (offset) {
|
||
+ case LS7A_IOAPIC_INT_ID_OFFSET:
|
||
+ val = LS7A_INT_ID_VER;
|
||
+ val = (val << 32) + LS7A_INT_ID_VAL;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_MASK_OFFSET:
|
||
+ val = a->int_mask;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_STATUS_OFFSET:
|
||
+ val = a->intisr & (~a->int_mask);
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_EDGE_OFFSET:
|
||
+ val = a->intedge;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_POL_OFFSET:
|
||
+ val = a->int_polarity;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_HTMSI_EN_OFFSET:
|
||
+ val = a->htmsi_en;
|
||
+ break;
|
||
+ case LS7A_AUTO_CTRL0_OFFSET:
|
||
+ case LS7A_AUTO_CTRL1_OFFSET:
|
||
+ break;
|
||
+ default:
|
||
+ break;
|
||
+ }
|
||
+ } else if (1 == size) {
|
||
+ if (offset >= LS7A_IOAPIC_HTMSI_VEC_OFFSET) {
|
||
+ offset_tmp = offset - LS7A_IOAPIC_HTMSI_VEC_OFFSET;
|
||
+ if (offset_tmp >= 0 && offset_tmp < 64) {
|
||
+ val = a->htmsi_vector[offset_tmp];
|
||
+ }
|
||
+ } else if (offset >= LS7A_IOAPIC_ROUTE_ENTRY_OFFSET) {
|
||
+ offset_tmp = offset - LS7A_IOAPIC_ROUTE_ENTRY_OFFSET;
|
||
+ if (offset_tmp >= 0 && offset_tmp < 64) {
|
||
+ val = a->route_entry[offset_tmp];
|
||
+ DPRINTF("addr %lx val %lx\n", addr, val);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ DPRINTF(TARGET_FMT_plx " val %lx\n", addr, val);
|
||
+ return val;
|
||
+}
|
||
+
|
||
+static void ls7a_apic_reg_write(void *opaque, hwaddr addr, uint64_t data,
|
||
+ unsigned size)
|
||
+{
|
||
+ LS7AApicState *a = opaque;
|
||
+ int64_t offset_tmp;
|
||
+ uint64_t offset;
|
||
+ offset = addr & 0xfff;
|
||
+ DPRINTF(TARGET_FMT_plx " size %d val %lx\n", addr, size, data);
|
||
+ if (8 == size) {
|
||
+ switch (offset) {
|
||
+ case LS7A_IOAPIC_INT_MASK_OFFSET:
|
||
+ a->int_mask = data;
|
||
+ update_irq(a);
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_STATUS_OFFSET:
|
||
+ a->intisr = data;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_EDGE_OFFSET:
|
||
+ a->intedge = data;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_CLEAR_OFFSET:
|
||
+ a->intisr &= (~data);
|
||
+ update_irq(a);
|
||
+ break;
|
||
+ case LS7A_IOAPIC_INT_POL_OFFSET:
|
||
+ a->int_polarity = data;
|
||
+ break;
|
||
+ case LS7A_IOAPIC_HTMSI_EN_OFFSET:
|
||
+ a->htmsi_en = data;
|
||
+ break;
|
||
+ case LS7A_AUTO_CTRL0_OFFSET:
|
||
+ case LS7A_AUTO_CTRL1_OFFSET:
|
||
+ break;
|
||
+ default:
|
||
+ break;
|
||
+ }
|
||
+ } else if (1 == size) {
|
||
+ if (offset >= LS7A_IOAPIC_HTMSI_VEC_OFFSET) {
|
||
+ offset_tmp = offset - LS7A_IOAPIC_HTMSI_VEC_OFFSET;
|
||
+ if (offset_tmp >= 0 && offset_tmp < 64) {
|
||
+ a->htmsi_vector[offset_tmp] = (uint8_t)(data & 0xff);
|
||
+ }
|
||
+ } else if (offset >= LS7A_IOAPIC_ROUTE_ENTRY_OFFSET) {
|
||
+ offset_tmp = offset - LS7A_IOAPIC_ROUTE_ENTRY_OFFSET;
|
||
+ if (offset_tmp >= 0 && offset_tmp < 64) {
|
||
+ a->route_entry[offset_tmp] = (uint8_t)(data & 0xff);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+static const MemoryRegionOps ls7a_apic_ops = {
|
||
+ .read = ls7a_apic_reg_read,
|
||
+ .write = ls7a_apic_reg_write,
|
||
+ .valid = {
|
||
+ .min_access_size = 1,
|
||
+ .max_access_size = 8,
|
||
+ },
|
||
+ .impl = {
|
||
+ .min_access_size = 1,
|
||
+ .max_access_size = 8,
|
||
+ },
|
||
+ .endianness = DEVICE_NATIVE_ENDIAN,
|
||
+};
|
||
+
|
||
+static int kvm_ls7a_pre_save(void *opaque)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ LS7AApicState *s = opaque;
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ struct ls7a_ioapic_state *state;
|
||
+ int ret, i, length;
|
||
+
|
||
+ if ((!kvm_enabled()) || (!kvm_irqchip_in_kernel())) {
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct ls7a_ioapic_state);
|
||
+ chip = g_malloc0(length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS7A_IOAPIC;
|
||
+ chip->len = length;
|
||
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_IRQCHIP, chip);
|
||
+ if (ret < 0) {
|
||
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
|
||
+ abort();
|
||
+ }
|
||
+ state = (struct ls7a_ioapic_state *)chip->data;
|
||
+ s->int_id = state->int_id;
|
||
+ s->int_mask = state->int_mask;
|
||
+ s->htmsi_en = state->htmsi_en;
|
||
+ s->intedge = state->intedge;
|
||
+ s->intclr = state->intclr;
|
||
+ s->auto_crtl0 = state->auto_crtl0;
|
||
+ s->auto_crtl1 = state->auto_crtl1;
|
||
+ for (i = 0; i < 64; i++) {
|
||
+ s->route_entry[i] = state->route_entry[i];
|
||
+ s->htmsi_vector[i] = state->htmsi_vector[i];
|
||
+ }
|
||
+ s->intisr_chip0 = state->intisr_chip0;
|
||
+ s->intisr_chip1 = state->intisr_chip1;
|
||
+ s->intirr = state->intirr;
|
||
+ s->intisr = state->intisr;
|
||
+ s->int_polarity = state->int_polarity;
|
||
+ g_free(chip);
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static int kvm_ls7a_post_load(void *opaque, int version)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ LS7AApicState *s = opaque;
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ struct ls7a_ioapic_state *state;
|
||
+ int ret, i, length;
|
||
+
|
||
+ if ((!kvm_enabled()) || (!kvm_irqchip_in_kernel())) {
|
||
+ return 0;
|
||
+ }
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct ls7a_ioapic_state);
|
||
+ chip = g_malloc0(length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS7A_IOAPIC;
|
||
+ chip->len = length;
|
||
+
|
||
+ state = (struct ls7a_ioapic_state *)chip->data;
|
||
+ state->int_id = s->int_id;
|
||
+ state->int_mask = s->int_mask;
|
||
+ state->htmsi_en = s->htmsi_en;
|
||
+ state->intedge = s->intedge;
|
||
+ state->intclr = s->intclr;
|
||
+ state->auto_crtl0 = s->auto_crtl0;
|
||
+ state->auto_crtl1 = s->auto_crtl1;
|
||
+ for (i = 0; i < 64; i++) {
|
||
+ state->route_entry[i] = s->route_entry[i];
|
||
+ state->htmsi_vector[i] = s->htmsi_vector[i];
|
||
+ }
|
||
+ state->intisr_chip0 = s->intisr_chip0;
|
||
+ state->intisr_chip1 = s->intisr_chip1;
|
||
+ state->last_intirr = 0;
|
||
+ state->intirr = s->intirr;
|
||
+ state->intisr = s->intisr;
|
||
+ state->int_polarity = s->int_polarity;
|
||
+
|
||
+ ret = kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, chip);
|
||
+ if (ret < 0) {
|
||
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
|
||
+ abort();
|
||
+ }
|
||
+ g_free(chip);
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static void ls7a_apic_reset(DeviceState *d)
|
||
+{
|
||
+ LS7AApicState *s = LS7A_APIC(d);
|
||
+ int i;
|
||
+
|
||
+ s->int_id = 0x001f000107000000;
|
||
+ s->int_mask = 0xffffffffffffffff;
|
||
+ s->htmsi_en = 0x0;
|
||
+ s->intedge = 0x0;
|
||
+ s->intclr = 0x0;
|
||
+ s->auto_crtl0 = 0x0;
|
||
+ s->auto_crtl1 = 0x0;
|
||
+ for (i = 0; i < 64; i++) {
|
||
+ s->route_entry[i] = 0x1;
|
||
+ s->htmsi_vector[i] = 0x0;
|
||
+ }
|
||
+ s->intisr_chip0 = 0x0;
|
||
+ s->intisr_chip1 = 0x0;
|
||
+ s->intirr = 0x0;
|
||
+ s->intisr = 0x0;
|
||
+ s->int_polarity = 0x0;
|
||
+ kvm_ls7a_post_load(s, 0);
|
||
+}
|
||
+
|
||
+static void ls7a_apic_init(Object *obj)
|
||
+{
|
||
+ DeviceState *dev = DEVICE(obj);
|
||
+ LS7AApicState *s = LS7A_APIC(obj);
|
||
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||
+ int tmp;
|
||
+ memory_region_init_io(&s->iomem, obj, &ls7a_apic_ops, s, TYPE_LS7A_APIC,
|
||
+ 0x1000);
|
||
+ sysbus_init_mmio(sbd, &s->iomem);
|
||
+ for (tmp = 0; tmp < 257; tmp++) {
|
||
+ sysbus_init_irq(sbd, &s->parent_irq[tmp]);
|
||
+ }
|
||
+ qdev_init_gpio_in(dev, irq_handler, 64);
|
||
+}
|
||
+
|
||
+static const VMStateDescription vmstate_ls7a_apic = {
|
||
+ .name = TYPE_LS7A_APIC,
|
||
+ .version_id = 1,
|
||
+ .minimum_version_id = 1,
|
||
+ .pre_save = kvm_ls7a_pre_save,
|
||
+ .post_load = kvm_ls7a_post_load,
|
||
+ .fields =
|
||
+ (VMStateField[]){ VMSTATE_UINT64(int_mask, LS7AApicState),
|
||
+ VMSTATE_UINT64(htmsi_en, LS7AApicState),
|
||
+ VMSTATE_UINT64(intedge, LS7AApicState),
|
||
+ VMSTATE_UINT64(intclr, LS7AApicState),
|
||
+ VMSTATE_UINT64(auto_crtl0, LS7AApicState),
|
||
+ VMSTATE_UINT64(auto_crtl1, LS7AApicState),
|
||
+ VMSTATE_UINT8_ARRAY(route_entry, LS7AApicState, 64),
|
||
+ VMSTATE_UINT8_ARRAY(htmsi_vector, LS7AApicState, 64),
|
||
+ VMSTATE_UINT64(intisr_chip0, LS7AApicState),
|
||
+ VMSTATE_UINT64(intisr_chip1, LS7AApicState),
|
||
+ VMSTATE_UINT64(last_intirr, LS7AApicState),
|
||
+ VMSTATE_UINT64(intirr, LS7AApicState),
|
||
+ VMSTATE_UINT64(intisr, LS7AApicState),
|
||
+ VMSTATE_UINT64(int_polarity, LS7AApicState),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static void ls7a_apic_class_init(ObjectClass *klass, void *data)
|
||
+{
|
||
+ DeviceClass *dc = DEVICE_CLASS(klass);
|
||
+
|
||
+ dc->reset = ls7a_apic_reset;
|
||
+ dc->vmsd = &vmstate_ls7a_apic;
|
||
+}
|
||
+
|
||
+static const TypeInfo ls7a_apic_info = {
|
||
+ .name = TYPE_LS7A_APIC,
|
||
+ .parent = TYPE_SYS_BUS_DEVICE,
|
||
+ .instance_size = sizeof(LS7AApicState),
|
||
+ .instance_init = ls7a_apic_init,
|
||
+ .class_init = ls7a_apic_class_init,
|
||
+};
|
||
+
|
||
+static void ls7a_apic_register_types(void)
|
||
+{
|
||
+ type_register_static(&ls7a_apic_info);
|
||
+}
|
||
+
|
||
+type_init(ls7a_apic_register_types)
|
||
diff --git a/hw/loongarch/iocsr.c b/hw/loongarch/iocsr.c
|
||
new file mode 100644
|
||
index 0000000000..a1eb54bdd2
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/iocsr.c
|
||
@@ -0,0 +1,227 @@
|
||
+/*
|
||
+ * LOONGARCH IOCSR support
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "hw/sysbus.h"
|
||
+#include "qemu/log.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "linux/kvm.h"
|
||
+#include "migration/vmstate.h"
|
||
+#include "hw/boards.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+
|
||
+#define BIT_ULL(nr) (1ULL << (nr))
|
||
+#define LOONGARCH_IOCSR_FEATURES 0x8
|
||
+#define IOCSRF_TEMP BIT_ULL(0)
|
||
+#define IOCSRF_NODECNT BIT_ULL(1)
|
||
+#define IOCSRF_MSI BIT_ULL(2)
|
||
+#define IOCSRF_EXTIOI BIT_ULL(3)
|
||
+#define IOCSRF_CSRIPI BIT_ULL(4)
|
||
+#define IOCSRF_FREQCSR BIT_ULL(5)
|
||
+#define IOCSRF_FREQSCALE BIT_ULL(6)
|
||
+#define IOCSRF_DVFSV1 BIT_ULL(7)
|
||
+#define IOCSRF_GMOD BIT_ULL(9)
|
||
+#define IOCSRF_VM BIT_ULL(11)
|
||
+#define LOONGARCH_IOCSR_VENDOR 0x10
|
||
+#define LOONGARCH_IOCSR_CPUNAME 0x20
|
||
+#define LOONGARCH_IOCSR_NODECNT 0x408
|
||
+#define LOONGARCH_IOCSR_MISC_FUNC 0x420
|
||
+#define IOCSR_MISC_FUNC_TIMER_RESET BIT_ULL(21)
|
||
+#define IOCSR_MISC_FUNC_EXT_IOI_EN BIT_ULL(48)
|
||
+
|
||
+enum {
|
||
+ IOCSR_FEATURES,
|
||
+ IOCSR_VENDOR,
|
||
+ IOCSR_CPUNAME,
|
||
+ IOCSR_NODECNT,
|
||
+ IOCSR_MISC_FUNC,
|
||
+ IOCSR_MAX
|
||
+};
|
||
+
|
||
+#ifdef CONFIG_KVM
|
||
+static uint32_t iocsr_array[IOCSR_MAX] = {
|
||
+ [IOCSR_FEATURES] = LOONGARCH_IOCSR_FEATURES,
|
||
+ [IOCSR_VENDOR] = LOONGARCH_IOCSR_VENDOR,
|
||
+ [IOCSR_CPUNAME] = LOONGARCH_IOCSR_CPUNAME,
|
||
+ [IOCSR_NODECNT] = LOONGARCH_IOCSR_NODECNT,
|
||
+ [IOCSR_MISC_FUNC] = LOONGARCH_IOCSR_MISC_FUNC,
|
||
+};
|
||
+#endif
|
||
+
|
||
+#define TYPE_IOCSR "iocsr"
|
||
+#define IOCSR(obj) OBJECT_CHECK(IOCSRState, (obj), TYPE_IOCSR)
|
||
+
|
||
+typedef struct IOCSRState {
|
||
+ SysBusDevice parent_obj;
|
||
+ uint64_t iocsr_val[IOCSR_MAX];
|
||
+} IOCSRState;
|
||
+
|
||
+IOCSRState iocsr_init = { .iocsr_val = {
|
||
+ IOCSRF_NODECNT | IOCSRF_MSI | IOCSRF_EXTIOI |
|
||
+ IOCSRF_CSRIPI | IOCSRF_GMOD | IOCSRF_VM,
|
||
+ 0x6e6f73676e6f6f4c, /* Loongson */
|
||
+ 0x303030354133, /*3A5000*/
|
||
+ 0x4,
|
||
+ 0x0,
|
||
+ } };
|
||
+
|
||
+static int kvm_iocsr_pre_save(void *opaque)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ IOCSRState *s = opaque;
|
||
+ struct kvm_iocsr_entry entry;
|
||
+ int i = 0;
|
||
+
|
||
+ if ((!kvm_enabled())) {
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ for (i = 0; i < IOCSR_MAX; i++) {
|
||
+ entry.addr = iocsr_array[i];
|
||
+ kvm_vm_ioctl(kvm_state, KVM_LOONGARCH_GET_IOCSR, &entry);
|
||
+ s->iocsr_val[i] = entry.data;
|
||
+ }
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static int kvm_iocsr_post_load(void *opaque, int version)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ IOCSRState *s = opaque;
|
||
+ struct kvm_iocsr_entry entry;
|
||
+ int i = 0;
|
||
+
|
||
+ if (!kvm_enabled()) {
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ for (i = 0; i < IOCSR_MAX; i++) {
|
||
+ entry.addr = iocsr_array[i];
|
||
+ entry.data = s->iocsr_val[i];
|
||
+ kvm_vm_ioctl(kvm_state, KVM_LOONGARCH_SET_IOCSR, &entry);
|
||
+ }
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static void iocsr_reset(DeviceState *d)
|
||
+{
|
||
+ IOCSRState *s = IOCSR(d);
|
||
+ int i;
|
||
+
|
||
+ for (i = 0; i < IOCSR_MAX; i++) {
|
||
+ s->iocsr_val[i] = iocsr_init.iocsr_val[i];
|
||
+ }
|
||
+ kvm_iocsr_post_load(s, 0);
|
||
+}
|
||
+
|
||
+static void init_vendor_cpuname(uint64_t *vendor, uint64_t *cpu_name,
|
||
+ char *cpuname)
|
||
+{
|
||
+ int i = 0, len = 0;
|
||
+ char *index = NULL, *index_end = NULL;
|
||
+ char *vendor_c = (char *)vendor;
|
||
+ char *cpu_name_c = (char *)cpu_name;
|
||
+
|
||
+ index = strstr(cpuname, "-");
|
||
+ len = strlen(cpuname);
|
||
+ if ((index == NULL) || (len <= 0)) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ *vendor = 0;
|
||
+ *cpu_name = 0;
|
||
+ index_end = cpuname + len;
|
||
+
|
||
+ while (((cpuname + i) < index) && (i < sizeof(uint64_t))) {
|
||
+ vendor_c[i] = cpuname[i];
|
||
+ i++;
|
||
+ }
|
||
+
|
||
+ index += 1;
|
||
+ i = 0;
|
||
+
|
||
+ while (((index + i) < index_end) && (i < sizeof(uint64_t))) {
|
||
+ cpu_name_c[i] = index[i];
|
||
+ i++;
|
||
+ }
|
||
+
|
||
+ return;
|
||
+}
|
||
+
|
||
+static void iocsr_instance_init(Object *obj)
|
||
+{
|
||
+ IOCSRState *s = IOCSR(obj);
|
||
+ int i;
|
||
+ LoongarchMachineState *lsms;
|
||
+ LoongarchMachineClass *lsmc;
|
||
+ Object *machine = qdev_get_machine();
|
||
+ ObjectClass *mc = object_get_class(machine);
|
||
+
|
||
+ /* 'lams' should be initialized */
|
||
+ if (!strcmp(MACHINE_CLASS(mc)->name, "none")) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ lsms = LoongarchMACHINE(machine);
|
||
+ lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+
|
||
+ init_vendor_cpuname((uint64_t *)&iocsr_init.iocsr_val[IOCSR_VENDOR],
|
||
+ (uint64_t *)&iocsr_init.iocsr_val[IOCSR_CPUNAME],
|
||
+ lsmc->cpu_name);
|
||
+
|
||
+ for (i = 0; i < IOCSR_MAX; i++) {
|
||
+ s->iocsr_val[i] = iocsr_init.iocsr_val[i];
|
||
+ }
|
||
+}
|
||
+
|
||
+static const VMStateDescription vmstate_iocsr = {
|
||
+ .name = TYPE_IOCSR,
|
||
+ .version_id = 1,
|
||
+ .minimum_version_id = 1,
|
||
+ .pre_save = kvm_iocsr_pre_save,
|
||
+ .post_load = kvm_iocsr_post_load,
|
||
+ .fields = (VMStateField[]){ VMSTATE_UINT64_ARRAY(iocsr_val, IOCSRState,
|
||
+ IOCSR_MAX),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static void iocsr_class_init(ObjectClass *klass, void *data)
|
||
+{
|
||
+ DeviceClass *dc = DEVICE_CLASS(klass);
|
||
+
|
||
+ dc->reset = iocsr_reset;
|
||
+ dc->vmsd = &vmstate_iocsr;
|
||
+}
|
||
+
|
||
+static const TypeInfo iocsr_info = {
|
||
+ .name = TYPE_IOCSR,
|
||
+ .parent = TYPE_SYS_BUS_DEVICE,
|
||
+ .instance_size = sizeof(IOCSRState),
|
||
+ .instance_init = iocsr_instance_init,
|
||
+ .class_init = iocsr_class_init,
|
||
+};
|
||
+
|
||
+static void iocsr_register_types(void)
|
||
+{
|
||
+ type_register_static(&iocsr_info);
|
||
+}
|
||
+
|
||
+type_init(iocsr_register_types)
|
||
diff --git a/hw/loongarch/ipi.c b/hw/loongarch/ipi.c
|
||
new file mode 100644
|
||
index 0000000000..affa97392e
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/ipi.c
|
||
@@ -0,0 +1,284 @@
|
||
+/*
|
||
+ * LOONGARCH IPI support
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qemu/units.h"
|
||
+#include "qapi/error.h"
|
||
+#include "hw/hw.h"
|
||
+#include "hw/irq.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "sysemu/sysemu.h"
|
||
+#include "sysemu/cpus.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "hw/core/cpu.h"
|
||
+#include "qemu/log.h"
|
||
+#include "hw/loongarch/bios.h"
|
||
+#include "elf.h"
|
||
+#include "linux/kvm.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+#include "hw/loongarch/ls7a.h"
|
||
+#include "migration/vmstate.h"
|
||
+
|
||
+static int gipi_pre_save(void *opaque)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ gipiState *state = opaque;
|
||
+ struct loongarch_gipiState *kstate;
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ int ret, i, j, length;
|
||
+#endif
|
||
+
|
||
+ if ((!kvm_enabled()) || (!kvm_irqchip_in_kernel())) {
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+#ifdef CONFIG_KVM
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct loongarch_gipiState);
|
||
+ chip = g_malloc0(length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS3A_GIPI;
|
||
+ chip->len = length;
|
||
+ ret = kvm_vm_ioctl(kvm_state, KVM_GET_IRQCHIP, chip);
|
||
+ if (ret < 0) {
|
||
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
|
||
+ abort();
|
||
+ }
|
||
+
|
||
+ kstate = (struct loongarch_gipiState *)chip->data;
|
||
+
|
||
+ for (i = 0; i < MAX_GIPI_CORE_NUM; i++) {
|
||
+ state->core[i].status = kstate->core[i].status;
|
||
+ state->core[i].en = kstate->core[i].en;
|
||
+ state->core[i].set = kstate->core[i].set;
|
||
+ state->core[i].clear = kstate->core[i].clear;
|
||
+ for (j = 0; j < MAX_GIPI_MBX_NUM; j++) {
|
||
+ state->core[i].buf[j] = kstate->core[i].buf[j];
|
||
+ }
|
||
+ }
|
||
+ g_free(chip);
|
||
+#endif
|
||
+
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static int gipi_post_load(void *opaque, int version)
|
||
+{
|
||
+#ifdef CONFIG_KVM
|
||
+ gipiState *state = opaque;
|
||
+ struct loongarch_gipiState *kstate;
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ int ret, i, j, length;
|
||
+#endif
|
||
+
|
||
+ if ((!kvm_enabled()) || (!kvm_irqchip_in_kernel())) {
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+#ifdef CONFIG_KVM
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct loongarch_gipiState);
|
||
+ chip = g_malloc0(length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS3A_GIPI;
|
||
+ chip->len = length;
|
||
+ kstate = (struct loongarch_gipiState *)chip->data;
|
||
+
|
||
+ for (i = 0; i < MAX_GIPI_CORE_NUM; i++) {
|
||
+ kstate->core[i].status = state->core[i].status;
|
||
+ kstate->core[i].en = state->core[i].en;
|
||
+ kstate->core[i].set = state->core[i].set;
|
||
+ kstate->core[i].clear = state->core[i].clear;
|
||
+ for (j = 0; j < MAX_GIPI_MBX_NUM; j++) {
|
||
+ kstate->core[i].buf[j] = state->core[i].buf[j];
|
||
+ }
|
||
+ }
|
||
+
|
||
+ ret = kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, chip);
|
||
+ if (ret < 0) {
|
||
+ fprintf(stderr, "KVM_GET_IRQCHIP failed: %s\n", strerror(ret));
|
||
+ abort();
|
||
+ }
|
||
+ g_free(chip);
|
||
+#endif
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static const VMStateDescription vmstate_gipi_core = {
|
||
+ .name = "gipi-single",
|
||
+ .version_id = 0,
|
||
+ .minimum_version_id = 0,
|
||
+ .fields =
|
||
+ (VMStateField[]){
|
||
+ VMSTATE_UINT32(status, gipi_core), VMSTATE_UINT32(en, gipi_core),
|
||
+ VMSTATE_UINT32(set, gipi_core), VMSTATE_UINT32(clear, gipi_core),
|
||
+ VMSTATE_UINT64_ARRAY(buf, gipi_core, MAX_GIPI_MBX_NUM),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static const VMStateDescription vmstate_gipi = {
|
||
+ .name = "gipi",
|
||
+ .pre_save = gipi_pre_save,
|
||
+ .post_load = gipi_post_load,
|
||
+ .version_id = 0,
|
||
+ .minimum_version_id = 0,
|
||
+ .fields = (VMStateField[]){ VMSTATE_STRUCT_ARRAY(
|
||
+ core, gipiState, MAX_GIPI_CORE_NUM, 0,
|
||
+ vmstate_gipi_core, gipi_core),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static void gipi_writel(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
||
+{
|
||
+ gipi_core *s = opaque;
|
||
+ gipi_core *ss;
|
||
+ void *pbuf;
|
||
+ uint32_t cpu, action_data, mailaddr;
|
||
+ LoongarchMachineState *ms = LoongarchMACHINE(qdev_get_machine());
|
||
+
|
||
+ if ((size != 4) && (size != 8)) {
|
||
+ hw_error("size not 4 and not 8");
|
||
+ }
|
||
+ addr &= 0xff;
|
||
+ switch (addr) {
|
||
+ case CORE0_STATUS_OFF:
|
||
+ hw_error("CORE0_STATUS_OFF Can't be write\n");
|
||
+ break;
|
||
+ case CORE0_EN_OFF:
|
||
+ s->en = val;
|
||
+ break;
|
||
+ case CORE0_IPI_SEND:
|
||
+ cpu = (val >> 16) & 0x3ff;
|
||
+ action_data = 1UL << (val & 0x1f);
|
||
+ ss = &ms->gipi->core[cpu];
|
||
+ ss->status |= action_data;
|
||
+ if (ss->status != 0) {
|
||
+ qemu_irq_raise(ss->irq);
|
||
+ }
|
||
+ break;
|
||
+ case CORE0_MAIL_SEND:
|
||
+ cpu = (val >> 16) & 0x3ff;
|
||
+ mailaddr = (val >> 2) & 0x7;
|
||
+ ss = &ms->gipi->core[cpu];
|
||
+ pbuf = (void *)ss->buf + mailaddr * 4;
|
||
+ *(unsigned int *)pbuf = (val >> 32);
|
||
+ break;
|
||
+ case CORE0_SET_OFF:
|
||
+ hw_error("CORE0_SET_OFF Can't be write\n");
|
||
+ break;
|
||
+ case CORE0_CLEAR_OFF:
|
||
+ s->status ^= val;
|
||
+ if (s->status == 0) {
|
||
+ qemu_irq_lower(s->irq);
|
||
+ }
|
||
+ break;
|
||
+ case 0x20 ... 0x3c:
|
||
+ pbuf = (void *)s->buf + (addr - 0x20);
|
||
+ if (size == 1) {
|
||
+ *(unsigned char *)pbuf = (unsigned char)val;
|
||
+ } else if (size == 2) {
|
||
+ *(unsigned short *)pbuf = (unsigned short)val;
|
||
+ } else if (size == 4) {
|
||
+ *(unsigned int *)pbuf = (unsigned int)val;
|
||
+ } else if (size == 8) {
|
||
+ *(unsigned long *)pbuf = (unsigned long)val;
|
||
+ }
|
||
+ break;
|
||
+ default:
|
||
+ break;
|
||
+ }
|
||
+}
|
||
+
|
||
+static uint64_t gipi_readl(void *opaque, hwaddr addr, unsigned size)
|
||
+{
|
||
+ gipi_core *s = opaque;
|
||
+ uint64_t ret = 0;
|
||
+ void *pbuf;
|
||
+
|
||
+ addr &= 0xff;
|
||
+ if ((size != 4) && (size != 8)) {
|
||
+ hw_error("size not 4 and not 8 size:%d\n", size);
|
||
+ }
|
||
+ switch (addr) {
|
||
+ case CORE0_STATUS_OFF:
|
||
+ ret = s->status;
|
||
+ break;
|
||
+ case CORE0_EN_OFF:
|
||
+ ret = s->en;
|
||
+ break;
|
||
+ case CORE0_SET_OFF:
|
||
+ ret = 0;
|
||
+ break;
|
||
+ case CORE0_CLEAR_OFF:
|
||
+ ret = 0;
|
||
+ break;
|
||
+ case 0x20 ... 0x3c:
|
||
+ pbuf = (void *)s->buf + (addr - 0x20);
|
||
+ if (size == 1) {
|
||
+ ret = *(unsigned char *)pbuf;
|
||
+ } else if (size == 2) {
|
||
+ ret = *(unsigned short *)pbuf;
|
||
+ } else if (size == 4) {
|
||
+ ret = *(unsigned int *)pbuf;
|
||
+ } else if (size == 8) {
|
||
+ ret = *(unsigned long *)pbuf;
|
||
+ }
|
||
+ break;
|
||
+ default:
|
||
+ break;
|
||
+ }
|
||
+
|
||
+ return ret;
|
||
+}
|
||
+
|
||
+static const MemoryRegionOps gipi_ops = {
|
||
+ .read = gipi_readl,
|
||
+ .write = gipi_writel,
|
||
+ .valid = {
|
||
+ .min_access_size = 4,
|
||
+ .max_access_size = 8,
|
||
+ },
|
||
+ .impl = {
|
||
+ .min_access_size = 4,
|
||
+ .max_access_size = 8,
|
||
+ },
|
||
+ .endianness = DEVICE_NATIVE_ENDIAN,
|
||
+};
|
||
+
|
||
+int cpu_init_ipi(LoongarchMachineState *ms, qemu_irq parent, int cpu)
|
||
+{
|
||
+ hwaddr addr;
|
||
+ MemoryRegion *region;
|
||
+ char str[32];
|
||
+
|
||
+ if (ms->gipi == NULL) {
|
||
+ ms->gipi = g_malloc0(sizeof(gipiState));
|
||
+ vmstate_register(NULL, 0, &vmstate_gipi, ms->gipi);
|
||
+ }
|
||
+
|
||
+ ms->gipi->core[cpu].irq = parent;
|
||
+
|
||
+ addr = SMP_GIPI_MAILBOX | (cpu << 8);
|
||
+ region = g_new(MemoryRegion, 1);
|
||
+ sprintf(str, "gipi%d", cpu);
|
||
+ memory_region_init_io(region, NULL, &gipi_ops, &ms->gipi->core[cpu], str,
|
||
+ 0x100);
|
||
+ memory_region_add_subregion(get_system_memory(), addr, region);
|
||
+ return 0;
|
||
+}
|
||
diff --git a/hw/loongarch/larch_3a.c b/hw/loongarch/larch_3a.c
|
||
new file mode 100644
|
||
index 0000000000..fe786008ac
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/larch_3a.c
|
||
@@ -0,0 +1,2063 @@
|
||
+/*
|
||
+ * QEMU loongarch 3a develop board emulation
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qemu/units.h"
|
||
+#include "qapi/error.h"
|
||
+#include "qemu/datadir.h"
|
||
+#include "hw/hw.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "hw/i386/pc.h"
|
||
+#include "hw/char/serial.h"
|
||
+#include "hw/isa/isa.h"
|
||
+#include "hw/qdev-core.h"
|
||
+#include "sysemu/sysemu.h"
|
||
+#include "sysemu/runstate.h"
|
||
+#include "sysemu/reset.h"
|
||
+#include "migration/vmstate.h"
|
||
+#include "sysemu/cpus.h"
|
||
+#include "hw/boards.h"
|
||
+#include "qemu/log.h"
|
||
+#include "hw/loongarch/bios.h"
|
||
+#include "hw/loader.h"
|
||
+#include "elf.h"
|
||
+#include "exec/address-spaces.h"
|
||
+#include "hw/ide.h"
|
||
+#include "hw/pci/pci_host.h"
|
||
+#include "hw/pci/msi.h"
|
||
+#include "linux/kvm.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "sysemu/numa.h"
|
||
+#include "hw/rtc/mc146818rtc.h"
|
||
+#include "hw/irq.h"
|
||
+#include "net/net.h"
|
||
+#include "hw/platform-bus.h"
|
||
+#include "hw/timer/i8254.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+#include "hw/loongarch/ls7a.h"
|
||
+#include "hw/nvram/fw_cfg.h"
|
||
+#include "hw/firmware/smbios.h"
|
||
+#include "acpi-build.h"
|
||
+#include <sys/wait.h>
|
||
+#include <sys/resource.h>
|
||
+#include "sysemu/block-backend.h"
|
||
+#include "hw/block/flash.h"
|
||
+#include "sysemu/device_tree.h"
|
||
+#include "qapi/visitor.h"
|
||
+#include "qapi/qapi-visit-common.h"
|
||
+#include "sysemu/tpm.h"
|
||
+#include "hw/loongarch/sysbus-fdt.h"
|
||
+
|
||
+#include <libfdt.h>
|
||
+
|
||
+#define DMA64_SUPPORTED 0x2
|
||
+#define MAX_IDE_BUS 2
|
||
+
|
||
+#define BOOTPARAM_PHYADDR 0x0ff00000ULL
|
||
+#define BOOTPARAM_ADDR (0x9000000000000000ULL + BOOTPARAM_PHYADDR)
|
||
+#define SMBIOS_PHYSICAL_ADDRESS 0x0fe00000
|
||
+#define SMBIOS_SIZE_LIMIT 0x200000
|
||
+#define RESERVED_SIZE_LIMIT 0x1100000
|
||
+#define COMMAND_LINE_SIZE 4096
|
||
+#define FW_CONF_ADDR 0x0fff0000
|
||
+
|
||
+#define PHYS_TO_VIRT(x) ((x) | 0x9000000000000000ULL)
|
||
+
|
||
+#define TARGET_REALPAGE_MASK (TARGET_PAGE_MASK << 2)
|
||
+
|
||
+#ifdef CONFIG_KVM
|
||
+#define LS_ISA_IO_SIZE 0x02000000
|
||
+#else
|
||
+#define LS_ISA_IO_SIZE 0x00010000
|
||
+#endif
|
||
+
|
||
+#ifdef CONFIG_KVM
|
||
+#define align(x) (((x) + 63) & ~63)
|
||
+#else
|
||
+#define align(x) (((x) + 15) & ~15)
|
||
+#endif
|
||
+
|
||
+#define DEBUG_LOONGARCH3A 0
|
||
+#define FLASH_SECTOR_SIZE 4096
|
||
+
|
||
+#define DPRINTF(fmt, ...) \
|
||
+ do { \
|
||
+ if (DEBUG_LOONGARCH3A) { \
|
||
+ fprintf(stderr, fmt, ##__VA_ARGS__); \
|
||
+ } \
|
||
+ } while (0)
|
||
+
|
||
+#define DEFINE_LS3A5K_MACHINE(suffix, name, optionfn) \
|
||
+ static void ls3a5k_init_##suffix(MachineState *machine) \
|
||
+ { \
|
||
+ ls3a5k_init(machine); \
|
||
+ } \
|
||
+ DEFINE_LOONGARCH_MACHINE(suffix, name, ls3a5k_init_##suffix, optionfn)
|
||
+
|
||
+struct efi_memory_map_loongarch {
|
||
+ uint16_t vers; /* version of efi_memory_map */
|
||
+ uint32_t nr_map; /* number of memory_maps */
|
||
+ uint32_t mem_freq; /* memory frequence */
|
||
+ struct mem_map {
|
||
+ uint32_t node_id; /* node_id which memory attached to */
|
||
+ uint32_t mem_type; /* system memory, pci memory, pci io, etc. */
|
||
+ uint64_t mem_start; /* memory map start address */
|
||
+ uint32_t mem_size; /* each memory_map size, not the total size */
|
||
+ } map[128];
|
||
+} __attribute__((packed));
|
||
+
|
||
+enum loongarch_cpu_type { Loongson3 = 0x1, Loongson3_comp = 0x2 };
|
||
+
|
||
+struct GlobalProperty loongarch_compat[] = {
|
||
+ {
|
||
+ .driver = "rtl8139",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "e1000",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "virtio-net-pci",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "qxl-vga",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "VGA",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "cirrus-vga",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "virtio-vga",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+ {
|
||
+ .driver = "vmware-svga",
|
||
+ .property = "romfile",
|
||
+ .value = "",
|
||
+ },
|
||
+};
|
||
+const size_t loongarch_compat_len = G_N_ELEMENTS(loongarch_compat);
|
||
+
|
||
+/*
|
||
+ * Capability and feature descriptor structure for LOONGARCH CPU
|
||
+ */
|
||
+struct efi_cpuinfo_loongarch {
|
||
+ uint16_t vers; /* version of efi_cpuinfo_loongarch */
|
||
+ uint32_t processor_id; /* PRID, e.g. 6305, 6306 */
|
||
+ enum loongarch_cpu_type cputype; /* 3A, 3B, etc. */
|
||
+ uint32_t total_node; /* num of total numa nodes */
|
||
+ uint16_t cpu_startup_core_id; /* Core id */
|
||
+ uint16_t reserved_cores_mask;
|
||
+ uint32_t cpu_clock_freq; /* cpu_clock */
|
||
+ uint32_t nr_cpus;
|
||
+} __attribute__((packed));
|
||
+
|
||
+#define MAX_UARTS 64
|
||
+struct uart_device {
|
||
+ uint32_t iotype; /* see include/linux/serial_core.h */
|
||
+ uint32_t uartclk;
|
||
+ uint32_t int_offset;
|
||
+ uint64_t uart_base;
|
||
+} __attribute__((packed));
|
||
+
|
||
+#define MAX_SENSORS 64
|
||
+#define SENSOR_TEMPER 0x00000001
|
||
+#define SENSOR_VOLTAGE 0x00000002
|
||
+#define SENSOR_FAN 0x00000004
|
||
+struct sensor_device {
|
||
+ char name[32]; /* a formal name */
|
||
+ char label[64]; /* a flexible description */
|
||
+ uint32_t type; /* SENSOR_* */
|
||
+ uint32_t id; /* instance id of a sensor-class */
|
||
+ /*
|
||
+ * see arch/loongarch/include/
|
||
+ * asm/mach-loongarch/loongarch_hwmon.h
|
||
+ */
|
||
+ uint32_t fan_policy;
|
||
+ uint32_t fan_percent; /* only for constant speed policy */
|
||
+ uint64_t base_addr; /* base address of device registers */
|
||
+} __attribute__((packed));
|
||
+
|
||
+struct system_loongarch {
|
||
+ uint16_t vers; /* version of system_loongarch */
|
||
+ uint32_t ccnuma_smp; /* 0: no numa; 1: has numa */
|
||
+ uint32_t sing_double_channel; /* 1:single; 2:double */
|
||
+ uint32_t nr_uarts;
|
||
+ struct uart_device uarts[MAX_UARTS];
|
||
+ uint32_t nr_sensors;
|
||
+ struct sensor_device sensors[MAX_SENSORS];
|
||
+ char has_ec;
|
||
+ char ec_name[32];
|
||
+ uint64_t ec_base_addr;
|
||
+ char has_tcm;
|
||
+ char tcm_name[32];
|
||
+ uint64_t tcm_base_addr;
|
||
+ uint64_t workarounds; /* see workarounds.h */
|
||
+} __attribute__((packed));
|
||
+
|
||
+struct irq_source_routing_table {
|
||
+ uint16_t vers;
|
||
+ uint16_t size;
|
||
+ uint16_t rtr_bus;
|
||
+ uint16_t rtr_devfn;
|
||
+ uint32_t vendor;
|
||
+ uint32_t device;
|
||
+ uint32_t PIC_type; /* conform use HT or PCI to route to CPU-PIC */
|
||
+ uint64_t ht_int_bit; /* 3A: 1<<24; 3B: 1<<16 */
|
||
+ uint64_t ht_enable; /* irqs used in this PIC */
|
||
+ uint32_t node_id; /* node id: 0x0-0; 0x1-1; 0x10-2; 0x11-3 */
|
||
+ uint64_t pci_mem_start_addr;
|
||
+ uint64_t pci_mem_end_addr;
|
||
+ uint64_t pci_io_start_addr;
|
||
+ uint64_t pci_io_end_addr;
|
||
+ uint64_t pci_config_addr;
|
||
+ uint32_t dma_mask_bits;
|
||
+ uint16_t dma_noncoherent;
|
||
+} __attribute__((packed));
|
||
+
|
||
+struct interface_info {
|
||
+ uint16_t vers; /* version of the specificition */
|
||
+ uint16_t size;
|
||
+ uint8_t flag;
|
||
+ char description[64];
|
||
+} __attribute__((packed));
|
||
+
|
||
+#define MAX_RESOURCE_NUMBER 128
|
||
+struct resource_loongarch {
|
||
+ uint64_t start; /* resource start address */
|
||
+ uint64_t end; /* resource end address */
|
||
+ char name[64];
|
||
+ uint32_t flags;
|
||
+};
|
||
+
|
||
+struct archdev_data {
|
||
+}; /* arch specific additions */
|
||
+
|
||
+struct board_devices {
|
||
+ char name[64]; /* hold the device name */
|
||
+ uint32_t num_resources; /* number of device_resource */
|
||
+ /* for each device's resource */
|
||
+ struct resource_loongarch resource[MAX_RESOURCE_NUMBER];
|
||
+ /* arch specific additions */
|
||
+ struct archdev_data archdata;
|
||
+};
|
||
+
|
||
+struct loongarch_special_attribute {
|
||
+ uint16_t vers; /* version of this special */
|
||
+ char special_name[64]; /* special_atribute_name */
|
||
+ uint32_t loongarch_special_type; /* type of special device */
|
||
+ /* for each device's resource */
|
||
+ struct resource_loongarch resource[MAX_RESOURCE_NUMBER];
|
||
+};
|
||
+
|
||
+struct loongarch_params {
|
||
+ uint64_t memory_offset; /* efi_memory_map_loongarch struct offset */
|
||
+ uint64_t cpu_offset; /* efi_cpuinfo_loongarch struct offset */
|
||
+ uint64_t system_offset; /* system_loongarch struct offset */
|
||
+ uint64_t irq_offset; /* irq_source_routing_table struct offset */
|
||
+ uint64_t interface_offset; /* interface_info struct offset */
|
||
+ uint64_t special_offset; /* loongarch_special_attribute struct offset */
|
||
+ uint64_t boarddev_table_offset; /* board_devices offset */
|
||
+};
|
||
+
|
||
+struct smbios_tables {
|
||
+ uint16_t vers; /* version of smbios */
|
||
+ uint64_t vga_bios; /* vga_bios address */
|
||
+ struct loongarch_params lp;
|
||
+};
|
||
+
|
||
+struct efi_reset_system_t {
|
||
+ uint64_t ResetCold;
|
||
+ uint64_t ResetWarm;
|
||
+ uint64_t ResetType;
|
||
+ uint64_t Shutdown;
|
||
+ uint64_t DoSuspend; /* NULL if not support */
|
||
+};
|
||
+
|
||
+struct efi_loongarch {
|
||
+ uint64_t mps; /* MPS table */
|
||
+ uint64_t acpi; /* ACPI table (IA64 ext 0.71) */
|
||
+ uint64_t acpi20; /* ACPI table (ACPI 2.0) */
|
||
+ struct smbios_tables smbios; /* SM BIOS table */
|
||
+ uint64_t sal_systab; /* SAL system table */
|
||
+ uint64_t boot_info; /* boot info table */
|
||
+};
|
||
+
|
||
+struct boot_params {
|
||
+ struct efi_loongarch efi;
|
||
+ struct efi_reset_system_t reset_system;
|
||
+};
|
||
+
|
||
+static struct _loaderparams {
|
||
+ unsigned long ram_size;
|
||
+ const char *kernel_filename;
|
||
+ const char *kernel_cmdline;
|
||
+ const char *initrd_filename;
|
||
+ unsigned long a0, a1, a2;
|
||
+} loaderparams;
|
||
+
|
||
+static struct _firmware_config {
|
||
+ unsigned long ram_size;
|
||
+ unsigned int mem_freq;
|
||
+ unsigned int cpu_nr;
|
||
+ unsigned int cpu_clock_freq;
|
||
+} fw_config;
|
||
+
|
||
+struct la_memmap_entry {
|
||
+ uint64_t address;
|
||
+ uint64_t length;
|
||
+ uint32_t type;
|
||
+ uint32_t reserved;
|
||
+};
|
||
+
|
||
+static void *boot_params_buf;
|
||
+static void *boot_params_p;
|
||
+static struct la_memmap_entry *la_memmap_table;
|
||
+static unsigned la_memmap_entries;
|
||
+
|
||
+CPULOONGARCHState *cpu_states[LOONGARCH_MAX_VCPUS];
|
||
+
|
||
+struct kvm_cpucfg ls3a5k_cpucfgs = {
|
||
+ .cpucfg[LOONGARCH_CPUCFG0] = CPUCFG0_3A5000_PRID,
|
||
+ .cpucfg[LOONGARCH_CPUCFG1] =
|
||
+ CPUCFG1_ISGR64 | CPUCFG1_PAGING | CPUCFG1_IOCSR | CPUCFG1_PABITS |
|
||
+ CPUCFG1_VABITS | CPUCFG1_UAL | CPUCFG1_RI | CPUCFG1_XI | CPUCFG1_RPLV |
|
||
+ CPUCFG1_HUGEPG | CPUCFG1_IOCSRBRD,
|
||
+ .cpucfg[LOONGARCH_CPUCFG2] =
|
||
+ CPUCFG2_FP | CPUCFG2_FPSP | CPUCFG2_FPDP | CPUCFG2_FPVERS |
|
||
+ CPUCFG2_LSX | CPUCFG2_LASX | CPUCFG2_COMPLEX | CPUCFG2_CRYPTO |
|
||
+ CPUCFG2_LLFTP | CPUCFG2_LLFTPREV | CPUCFG2_LSPW | CPUCFG2_LAM,
|
||
+ .cpucfg[LOONGARCH_CPUCFG3] =
|
||
+ CPUCFG3_CCDMA | CPUCFG3_SFB | CPUCFG3_UCACC | CPUCFG3_LLEXC |
|
||
+ CPUCFG3_SCDLY | CPUCFG3_LLDBAR | CPUCFG3_ITLBT | CPUCFG3_ICACHET |
|
||
+ CPUCFG3_SPW_LVL | CPUCFG3_SPW_HG_HF | CPUCFG3_RVA | CPUCFG3_RVAMAX,
|
||
+ .cpucfg[LOONGARCH_CPUCFG4] = CCFREQ_100M,
|
||
+ .cpucfg[LOONGARCH_CPUCFG5] = CPUCFG5_CCMUL | CPUCFG5_CCDIV,
|
||
+ .cpucfg[LOONGARCH_CPUCFG6] = CPUCFG6_PMP | CPUCFG6_PAMVER | CPUCFG6_PMNUM |
|
||
+ CPUCFG6_PMBITS | CPUCFG6_UPM,
|
||
+ .cpucfg[LOONGARCH_CPUCFG16] = CPUCFG16_L1_IUPRE | CPUCFG16_L1_DPRE |
|
||
+ CPUCFG16_L2_IUPRE | CPUCFG16_L2_IUUNIFY |
|
||
+ CPUCFG16_L2_IUPRIV | CPUCFG16_L3_IUPRE |
|
||
+ CPUCFG16_L3_IUUNIFY | CPUCFG16_L3_IUINCL,
|
||
+ .cpucfg[LOONGARCH_CPUCFG17] =
|
||
+ CPUCFG17_L1I_WAYS_M | CPUCFG17_L1I_SETS_M | CPUCFG17_L1I_SIZE_M,
|
||
+ .cpucfg[LOONGARCH_CPUCFG18] =
|
||
+ CPUCFG18_L1D_WAYS_M | CPUCFG18_L1D_SETS_M | CPUCFG18_L1D_SIZE_M,
|
||
+ .cpucfg[LOONGARCH_CPUCFG19] =
|
||
+ CPUCFG19_L2_WAYS_M | CPUCFG19_L2_SETS_M | CPUCFG19_L2_SIZE_M,
|
||
+ .cpucfg[LOONGARCH_CPUCFG20] =
|
||
+ CPUCFG20_L3_WAYS_M | CPUCFG20_L3_SETS_M | CPUCFG20_L3_SIZE_M,
|
||
+};
|
||
+
|
||
+bool loongarch_is_acpi_enabled(LoongarchMachineState *vms)
|
||
+{
|
||
+ if (vms->acpi == ON_OFF_AUTO_OFF) {
|
||
+ return false;
|
||
+ }
|
||
+ return true;
|
||
+}
|
||
+
|
||
+static void loongarch_get_acpi(Object *obj, Visitor *v, const char *name,
|
||
+ void *opaque, Error **errp)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(obj);
|
||
+ OnOffAuto acpi = lsms->acpi;
|
||
+
|
||
+ visit_type_OnOffAuto(v, name, &acpi, errp);
|
||
+}
|
||
+
|
||
+static void loongarch_set_acpi(Object *obj, Visitor *v, const char *name,
|
||
+ void *opaque, Error **errp)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(obj);
|
||
+
|
||
+ visit_type_OnOffAuto(v, name, &lsms->acpi, errp);
|
||
+}
|
||
+
|
||
+int la_memmap_add_entry(uint64_t address, uint64_t length, uint32_t type)
|
||
+{
|
||
+ int i;
|
||
+
|
||
+ for (i = 0; i < la_memmap_entries; i++) {
|
||
+ if (la_memmap_table[i].address == address) {
|
||
+ fprintf(stderr, "%s address:0x%lx length:0x%lx already exists\n",
|
||
+ __func__, address, length);
|
||
+ return 0;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ la_memmap_table = g_renew(struct la_memmap_entry, la_memmap_table,
|
||
+ la_memmap_entries + 1);
|
||
+ la_memmap_table[la_memmap_entries].address = cpu_to_le64(address);
|
||
+ la_memmap_table[la_memmap_entries].length = cpu_to_le64(length);
|
||
+ la_memmap_table[la_memmap_entries].type = cpu_to_le32(type);
|
||
+ la_memmap_entries++;
|
||
+
|
||
+ return la_memmap_entries;
|
||
+}
|
||
+
|
||
+static ram_addr_t get_hotplug_membase(ram_addr_t ram_size)
|
||
+{
|
||
+ ram_addr_t sstart;
|
||
+
|
||
+ if (ram_size <= 0x10000000) {
|
||
+ sstart = 0x90000000;
|
||
+ } else {
|
||
+ sstart = 0x90000000 + ROUND_UP((ram_size - 0x10000000),
|
||
+ LOONGARCH_HOTPLUG_MEM_ALIGN);
|
||
+ }
|
||
+ return sstart;
|
||
+}
|
||
+
|
||
+static struct efi_memory_map_loongarch *init_memory_map(void *g_map)
|
||
+{
|
||
+ struct efi_memory_map_loongarch *emap = g_map;
|
||
+
|
||
+ emap->nr_map = 4;
|
||
+ emap->mem_freq = 266000000;
|
||
+
|
||
+ emap->map[0].node_id = 0;
|
||
+ emap->map[0].mem_type = 1;
|
||
+ emap->map[0].mem_start = 0x0;
|
||
+#ifdef CONFIG_KVM
|
||
+ emap->map[0].mem_size =
|
||
+ (loaderparams.ram_size > 0x10000000 ? 256
|
||
+ : (loaderparams.ram_size >> 20)) -
|
||
+ 18;
|
||
+#else
|
||
+ emap->map[0].mem_size = atoi(getenv("memsize"));
|
||
+#endif
|
||
+
|
||
+ emap->map[1].node_id = 0;
|
||
+ emap->map[1].mem_type = 2;
|
||
+ emap->map[1].mem_start = 0x90000000;
|
||
+#ifdef CONFIG_KVM
|
||
+ emap->map[1].mem_size = (loaderparams.ram_size > 0x10000000
|
||
+ ? (loaderparams.ram_size >> 20) - 256
|
||
+ : 0);
|
||
+#else
|
||
+ emap->map[1].mem_size = atoi(getenv("highmemsize"));
|
||
+#endif
|
||
+
|
||
+ /* support for smbios */
|
||
+ emap->map[2].node_id = 0;
|
||
+ emap->map[2].mem_type = 10;
|
||
+ emap->map[2].mem_start = SMBIOS_PHYSICAL_ADDRESS;
|
||
+ emap->map[2].mem_size = SMBIOS_SIZE_LIMIT >> 20;
|
||
+
|
||
+ emap->map[3].node_id = 0;
|
||
+ emap->map[3].mem_type = 3;
|
||
+ emap->map[3].mem_start = 0xee00000;
|
||
+ emap->map[3].mem_size = RESERVED_SIZE_LIMIT >> 20;
|
||
+
|
||
+ return emap;
|
||
+}
|
||
+
|
||
+static uint64_t get_host_cpu_freq(void)
|
||
+{
|
||
+ int fd = 0;
|
||
+ char buf[1024];
|
||
+ uint64_t freq = 0, size = 0;
|
||
+ char *buf_p;
|
||
+
|
||
+ fd = open("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq",
|
||
+ O_RDONLY);
|
||
+ if (fd == -1) {
|
||
+ fprintf(stderr, "/sys/devices/system/cpu/cpu0/cpufreq/ \
|
||
+ cpuinfo_max_freq not exist!\n");
|
||
+ fprintf(stderr, "Trying /proc/cpuinfo...\n");
|
||
+ } else {
|
||
+ size = read(fd, buf, 16);
|
||
+ if (size == -1) {
|
||
+ fprintf(stderr, "read err...\n");
|
||
+ }
|
||
+ close(fd);
|
||
+ freq = (uint64_t)atoi(buf);
|
||
+ return freq * 1000;
|
||
+ }
|
||
+
|
||
+ fd = open("/proc/cpuinfo", O_RDONLY);
|
||
+ if (fd == -1) {
|
||
+ fprintf(stderr, "Failed to open /proc/cpuinfo!\n");
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ size = read(fd, buf, 1024);
|
||
+ if (size == -1) {
|
||
+ fprintf(stderr, "read err...\n");
|
||
+ }
|
||
+ close(fd);
|
||
+
|
||
+ buf_p = strstr(buf, "MHz");
|
||
+ if (buf_p) {
|
||
+ while (*buf_p != ':') {
|
||
+ buf_p++;
|
||
+ }
|
||
+ buf_p += 2;
|
||
+ } else {
|
||
+ buf_p = strstr(buf, "name");
|
||
+ while (*buf_p != '@') {
|
||
+ buf_p++;
|
||
+ }
|
||
+ buf_p += 2;
|
||
+ }
|
||
+
|
||
+ memcpy(buf, buf_p, 12);
|
||
+ buf_p = buf;
|
||
+ while ((*buf_p >= '0') && (*buf_p <= '9')) {
|
||
+ buf_p++;
|
||
+ }
|
||
+ *buf_p = '\0';
|
||
+
|
||
+ freq = (uint64_t)atoi(buf);
|
||
+ return freq * 1000 * 1000;
|
||
+}
|
||
+
|
||
+static char *get_host_cpu_model_name(void)
|
||
+{
|
||
+ int fd = 0;
|
||
+ int size = 0;
|
||
+ static char buf[1024];
|
||
+ char *buf_p;
|
||
+
|
||
+ fd = open("/proc/cpuinfo", O_RDONLY);
|
||
+ if (fd == -1) {
|
||
+ fprintf(stderr, "Failed to open /proc/cpuinfo!\n");
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ size = read(fd, buf, 1024);
|
||
+ if (size == -1) {
|
||
+ fprintf(stderr, "read err...\n");
|
||
+ }
|
||
+ close(fd);
|
||
+ buf_p = strstr(buf, "Name");
|
||
+ if (!buf_p) {
|
||
+ buf_p = strstr(buf, "name");
|
||
+ }
|
||
+ if (!buf_p) {
|
||
+ fprintf(stderr, "Can't find cpu name\n");
|
||
+ return 0;
|
||
+ }
|
||
+
|
||
+ while (*buf_p != ':') {
|
||
+ buf_p++;
|
||
+ }
|
||
+ buf_p = buf_p + 2;
|
||
+ memcpy(buf, buf_p, 40);
|
||
+ buf_p = buf;
|
||
+ while (*buf_p != '\n') {
|
||
+ buf_p++;
|
||
+ }
|
||
+
|
||
+ *(buf_p) = '\0';
|
||
+
|
||
+ return buf;
|
||
+}
|
||
+
|
||
+static void fw_conf_init(unsigned long ramsize)
|
||
+{
|
||
+ MachineState *ms = MACHINE(qdev_get_machine());
|
||
+ int smp_cpus = ms->smp.cpus;
|
||
+ fw_config.ram_size = ramsize;
|
||
+ fw_config.mem_freq = 266000000;
|
||
+ fw_config.cpu_nr = smp_cpus;
|
||
+ fw_config.cpu_clock_freq = get_host_cpu_freq();
|
||
+}
|
||
+
|
||
+static struct efi_cpuinfo_loongarch *init_cpu_info(void *g_cpuinfo_loongarch)
|
||
+{
|
||
+ struct efi_cpuinfo_loongarch *c = g_cpuinfo_loongarch;
|
||
+ MachineState *ms = MACHINE(qdev_get_machine());
|
||
+ int smp_cpus = ms->smp.cpus;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(qdev_get_machine());
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+
|
||
+ if (strstr(lsmc->cpu_name, "5000")) {
|
||
+ c->processor_id = 0x14c010;
|
||
+ }
|
||
+ c->cputype = Loongson3_comp;
|
||
+ c->cpu_clock_freq = get_host_cpu_freq();
|
||
+ if (!c->cpu_clock_freq) {
|
||
+ c->cpu_clock_freq = 200000000;
|
||
+ }
|
||
+ c->total_node = 1;
|
||
+ c->nr_cpus = smp_cpus;
|
||
+ c->cpu_startup_core_id = 0;
|
||
+ c->reserved_cores_mask = 0xffff & (0xffff << smp_cpus);
|
||
+
|
||
+ return c;
|
||
+}
|
||
+
|
||
+static struct system_loongarch *init_system_loongarch(void *g_sysitem)
|
||
+{
|
||
+ struct system_loongarch *s = g_sysitem;
|
||
+
|
||
+ s->ccnuma_smp = 1;
|
||
+ s->ccnuma_smp = 0;
|
||
+ s->sing_double_channel = 1;
|
||
+
|
||
+ return s;
|
||
+}
|
||
+
|
||
+enum loongarch_irq_source_enum { HT, I8259, UNKNOWN };
|
||
+
|
||
+static struct irq_source_routing_table *init_irq_source(void *g_irq_source)
|
||
+{
|
||
+ struct irq_source_routing_table *irq_info = g_irq_source;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(qdev_get_machine());
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+
|
||
+ irq_info->PIC_type = HT;
|
||
+ irq_info->ht_int_bit = 1 << 24;
|
||
+ irq_info->ht_enable = 0x0000d17b;
|
||
+ irq_info->node_id = 0;
|
||
+
|
||
+ irq_info->pci_mem_start_addr = PCIE_MEMORY_BASE;
|
||
+ irq_info->pci_mem_end_addr =
|
||
+ irq_info->pci_mem_start_addr + PCIE_MEMORY_SIZE - 1;
|
||
+
|
||
+ if (strstr(lsmc->cpu_name, "5000")) {
|
||
+ irq_info->pci_io_start_addr = LS3A5K_ISA_IO_BASE;
|
||
+ }
|
||
+ irq_info->dma_noncoherent = 1;
|
||
+ return irq_info;
|
||
+}
|
||
+
|
||
+static struct interface_info *init_interface_info(void *g_interface)
|
||
+{
|
||
+ struct interface_info *inter = g_interface;
|
||
+ int flashsize = 0x80000;
|
||
+
|
||
+ inter->vers = 0x0001;
|
||
+ inter->size = flashsize / 0x400;
|
||
+ inter->flag = 1;
|
||
+
|
||
+ strcpy(inter->description, "PMON_Version_v2.1");
|
||
+
|
||
+ return inter;
|
||
+}
|
||
+
|
||
+static struct board_devices *board_devices_info(void *g_board)
|
||
+{
|
||
+ struct board_devices *bd = g_board;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(qdev_get_machine());
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+
|
||
+ if (!strcmp(lsmc->bridge_name, "ls7a")) {
|
||
+ strcpy(bd->name, "Loongarch-3A-7A-1w-V1.03-demo");
|
||
+ }
|
||
+ bd->num_resources = 10;
|
||
+
|
||
+ return bd;
|
||
+}
|
||
+
|
||
+static struct loongarch_special_attribute *init_special_info(void *g_special)
|
||
+{
|
||
+ struct loongarch_special_attribute *special = g_special;
|
||
+ char update[11] = "2013-01-01";
|
||
+ int VRAM_SIZE = 0x20000;
|
||
+
|
||
+ strcpy(special->special_name, update);
|
||
+ special->resource[0].flags = 0;
|
||
+ special->resource[0].start = 0;
|
||
+ special->resource[0].end = VRAM_SIZE;
|
||
+ strcpy(special->resource[0].name, "SPMODULE");
|
||
+ special->resource[0].flags |= DMA64_SUPPORTED;
|
||
+
|
||
+ return special;
|
||
+}
|
||
+
|
||
+static void init_loongarch_params(struct loongarch_params *lp)
|
||
+{
|
||
+ void *p = boot_params_p;
|
||
+
|
||
+ lp->memory_offset =
|
||
+ (unsigned long long)init_memory_map(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct efi_memory_map_loongarch));
|
||
+
|
||
+ lp->cpu_offset =
|
||
+ (unsigned long long)init_cpu_info(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct efi_cpuinfo_loongarch));
|
||
+
|
||
+ lp->system_offset =
|
||
+ (unsigned long long)init_system_loongarch(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct system_loongarch));
|
||
+
|
||
+ lp->irq_offset =
|
||
+ (unsigned long long)init_irq_source(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct irq_source_routing_table));
|
||
+
|
||
+ lp->interface_offset =
|
||
+ (unsigned long long)init_interface_info(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct interface_info));
|
||
+
|
||
+ lp->boarddev_table_offset =
|
||
+ (unsigned long long)board_devices_info(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct board_devices));
|
||
+
|
||
+ lp->special_offset =
|
||
+ (unsigned long long)init_special_info(p) - (unsigned long long)lp;
|
||
+ p += align(sizeof(struct loongarch_special_attribute));
|
||
+
|
||
+ boot_params_p = p;
|
||
+}
|
||
+
|
||
+static void init_smbios(struct smbios_tables *smbios)
|
||
+{
|
||
+ smbios->vers = 1;
|
||
+ smbios->vga_bios = 1;
|
||
+ init_loongarch_params(&(smbios->lp));
|
||
+}
|
||
+
|
||
+static void init_efi(struct efi_loongarch *efi)
|
||
+{
|
||
+ init_smbios(&(efi->smbios));
|
||
+}
|
||
+
|
||
+static int init_boot_param(struct boot_params *bp)
|
||
+{
|
||
+ init_efi(&(bp->efi));
|
||
+
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static unsigned int ls3a5k_aui_boot_code[] = {
|
||
+ 0x0380200d, /* ori $r13,$r0,0x8 */
|
||
+ 0x0400002d, /* csrwr $r13,0x0 */
|
||
+ 0x0401000e, /* csrrd $r14,0x40 */
|
||
+ 0x0343fdce, /* andi $r14,$r14,0xff */
|
||
+ 0x143fc02c, /* lu12i.w $r12,261889(0x1fe01) */
|
||
+ 0x1600000c, /* lu32i.d $r12,0 */
|
||
+ 0x0320018c, /* lu52i.d $r12,$r12,-1792(0x800) */
|
||
+ 0x03400dcf, /* andi $r15,$r14,0x3 */
|
||
+ 0x004121ef, /* slli.d $r15,$r15,0x8 */
|
||
+ 0x00153d8c, /* or $r12,$r12,$r15 */
|
||
+ 0x034031d0, /* andi $r16,$r14,0xc */
|
||
+ 0x0041aa10, /* slli.d $r16,$r16,0x2a */
|
||
+ 0x0015418c, /* or $r12,$r12,$r16 */
|
||
+ 0x28808184, /* ld.w $r4,$r12,32(0x20) */
|
||
+ 0x43fffc9f, /* beqz $r4,0 -4 <waitforinit+0x4> */
|
||
+ 0x28c08184, /* ld.d $r4,$r12,32(0x20) */
|
||
+ 0x28c0a183, /* ld.d $r3,$r12,40(0x28) */
|
||
+ 0x28c0c182, /* ld.d $r2,$r12,48(0x30) */
|
||
+ 0x28c0e185, /* ld.d $r5,$r12,56(0x38) */
|
||
+ 0x4c000080, /* jirl $r0,$r4,0 */
|
||
+};
|
||
+
|
||
+static int set_bootparam_uefi(ram_addr_t initrd_offset, long initrd_size)
|
||
+{
|
||
+ long params_size;
|
||
+ char memenv[32];
|
||
+ char highmemenv[32];
|
||
+ void *params_buf;
|
||
+ unsigned long *parg_env;
|
||
+ int ret = 0;
|
||
+
|
||
+ /* Allocate params_buf for command line. */
|
||
+ params_size = 0x100000;
|
||
+ params_buf = g_malloc0(params_size);
|
||
+
|
||
+ /*
|
||
+ * Layout of params_buf looks like this:
|
||
+ * argv[0], argv[1], 0, env[0], env[1], ...env[i], 0,
|
||
+ * argv[0]'s data, argv[1]'s data, env[0]'data, ..., env[i]'s data, 0
|
||
+ */
|
||
+ parg_env = (void *)params_buf;
|
||
+
|
||
+ ret = (3 + 1) * sizeof(target_ulong);
|
||
+ *parg_env++ = (BOOTPARAM_ADDR + ret);
|
||
+ ret += (1 + snprintf(params_buf + ret, COMMAND_LINE_SIZE - ret, "g"));
|
||
+
|
||
+ /* argv1 */
|
||
+ *parg_env++ = BOOTPARAM_ADDR + ret;
|
||
+ if (initrd_size > 0)
|
||
+ ret += (1 + snprintf(params_buf + ret, COMMAND_LINE_SIZE - ret,
|
||
+ "rd_start=0x%llx rd_size=%li %s",
|
||
+ PHYS_TO_VIRT((uint32_t)initrd_offset),
|
||
+ initrd_size, loaderparams.kernel_cmdline));
|
||
+ else
|
||
+ ret += (1 + snprintf(params_buf + ret, COMMAND_LINE_SIZE - ret, "%s",
|
||
+ loaderparams.kernel_cmdline));
|
||
+
|
||
+ /* argv2 */
|
||
+ *parg_env++ = 0;
|
||
+
|
||
+ /* env */
|
||
+ sprintf(memenv, "%lu",
|
||
+ loaderparams.ram_size > 0x10000000
|
||
+ ? 256
|
||
+ : (loaderparams.ram_size >> 20));
|
||
+ sprintf(highmemenv, "%lu",
|
||
+ loaderparams.ram_size > 0x10000000
|
||
+ ? (loaderparams.ram_size >> 20) - 256
|
||
+ : 0);
|
||
+
|
||
+ setenv("memsize", memenv, 1);
|
||
+ setenv("highmemsize", highmemenv, 1);
|
||
+
|
||
+ ret = ((ret + 32) & ~31);
|
||
+
|
||
+ boot_params_buf = (void *)(params_buf + ret);
|
||
+ boot_params_p = boot_params_buf + align(sizeof(struct boot_params));
|
||
+ init_boot_param(boot_params_buf);
|
||
+ rom_add_blob_fixed("params", params_buf, params_size, BOOTPARAM_PHYADDR);
|
||
+ loaderparams.a0 = 2;
|
||
+ loaderparams.a1 = BOOTPARAM_ADDR;
|
||
+ loaderparams.a2 = BOOTPARAM_ADDR + ret;
|
||
+
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static uint64_t cpu_loongarch_virt_to_phys(void *opaque, uint64_t addr)
|
||
+{
|
||
+ return addr & 0x1fffffffll;
|
||
+}
|
||
+
|
||
+static void fw_cfg_add_kernel_info(FWCfgState *fw_cfg, uint64_t highram_size,
|
||
+ uint64_t phyAddr_initrd)
|
||
+{
|
||
+ int64_t entry, kernel_low, kernel_high;
|
||
+ long initrd_size = 0;
|
||
+ uint64_t initrd_offset = 0;
|
||
+ void *cmdline_buf;
|
||
+ int ret = 0;
|
||
+
|
||
+ ret = load_elf(loaderparams.kernel_filename, NULL,
|
||
+ cpu_loongarch_virt_to_phys, NULL, (uint64_t *)&entry,
|
||
+ (uint64_t *)&kernel_low, (uint64_t *)&kernel_high, NULL, 0,
|
||
+ EM_LOONGARCH, 1, 0);
|
||
+
|
||
+ if (0 > ret) {
|
||
+ error_report("kernel image load error");
|
||
+ exit(1);
|
||
+ }
|
||
+
|
||
+ fw_cfg_add_i64(fw_cfg, FW_CFG_KERNEL_ENTRY, entry);
|
||
+
|
||
+ if (loaderparams.initrd_filename) {
|
||
+ initrd_size = get_image_size(loaderparams.initrd_filename);
|
||
+ if (0 < initrd_size) {
|
||
+ if (initrd_size > highram_size) {
|
||
+ error_report("initrd size is too big, should below %ld MB",
|
||
+ highram_size / MiB);
|
||
+ /*prevent write io memory address space*/
|
||
+ exit(1);
|
||
+ }
|
||
+ initrd_offset =
|
||
+ (phyAddr_initrd - initrd_size) & TARGET_REALPAGE_MASK;
|
||
+ initrd_size = load_image_targphys(
|
||
+ loaderparams.initrd_filename, initrd_offset,
|
||
+ loaderparams.ram_size - initrd_offset);
|
||
+ fw_cfg_add_i64(fw_cfg, FW_CFG_INITRD_ADDR, initrd_offset);
|
||
+ fw_cfg_add_i64(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size);
|
||
+ } else {
|
||
+ error_report("initrd image size is error");
|
||
+ }
|
||
+ }
|
||
+
|
||
+ cmdline_buf = g_malloc0(COMMAND_LINE_SIZE);
|
||
+ if (initrd_size > 0)
|
||
+ ret = (1 + snprintf(cmdline_buf, COMMAND_LINE_SIZE,
|
||
+ "rd_start=0x%llx rd_size=%li %s",
|
||
+ PHYS_TO_VIRT(initrd_offset), initrd_size,
|
||
+ loaderparams.kernel_cmdline));
|
||
+ else
|
||
+ ret = (1 + snprintf(cmdline_buf, COMMAND_LINE_SIZE, "%s",
|
||
+ loaderparams.kernel_cmdline));
|
||
+
|
||
+ fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, ret);
|
||
+ fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, (const char *)cmdline_buf);
|
||
+
|
||
+ return;
|
||
+}
|
||
+
|
||
+static int64_t load_kernel(void)
|
||
+{
|
||
+ int64_t entry, kernel_low, kernel_high;
|
||
+ long initrd_size = 0;
|
||
+ ram_addr_t initrd_offset = 0;
|
||
+
|
||
+ load_elf(loaderparams.kernel_filename, NULL, cpu_loongarch_virt_to_phys,
|
||
+ NULL, (uint64_t *)&entry, (uint64_t *)&kernel_low,
|
||
+ (uint64_t *)&kernel_high, NULL, 0, EM_LOONGARCH, 1, 0);
|
||
+
|
||
+ if (loaderparams.initrd_filename) {
|
||
+ initrd_size = get_image_size(loaderparams.initrd_filename);
|
||
+
|
||
+ if (initrd_size > 0) {
|
||
+ initrd_offset = (kernel_high * 4 + ~TARGET_REALPAGE_MASK) &
|
||
+ TARGET_REALPAGE_MASK;
|
||
+ initrd_size = load_image_targphys(
|
||
+ loaderparams.initrd_filename, initrd_offset,
|
||
+ loaderparams.ram_size - initrd_offset);
|
||
+ }
|
||
+ }
|
||
+ set_bootparam_uefi(initrd_offset, initrd_size);
|
||
+
|
||
+ return entry;
|
||
+}
|
||
+
|
||
+static void main_cpu_reset(void *opaque)
|
||
+{
|
||
+ ResetData *s = (ResetData *)opaque;
|
||
+ CPULOONGARCHState *env = &s->cpu->env;
|
||
+
|
||
+ cpu_reset(CPU(s->cpu));
|
||
+ env->active_tc.PC = s->vector;
|
||
+ env->active_tc.gpr[4] = loaderparams.a0;
|
||
+ env->active_tc.gpr[5] = loaderparams.a1;
|
||
+ env->active_tc.gpr[6] = loaderparams.a2;
|
||
+}
|
||
+
|
||
+void slave_cpu_reset(void *opaque)
|
||
+{
|
||
+ ResetData *s = (ResetData *)opaque;
|
||
+
|
||
+ cpu_reset(CPU(s->cpu));
|
||
+}
|
||
+
|
||
+/* KVM_IRQ_LINE irq field index values */
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_SHIFT 24
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_MASK 0xff
|
||
+#define KVM_LOONGARCH_IRQ_VCPU_SHIFT 16
|
||
+#define KVM_LOONGARCH_IRQ_VCPU_MASK 0xff
|
||
+#define KVM_LOONGARCH_IRQ_NUM_SHIFT 0
|
||
+#define KVM_LOONGARCH_IRQ_NUM_MASK 0xffff
|
||
+
|
||
+/* irq_type field */
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_CPU_IP 0
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_CPU_IO 1
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_HT 2
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_MSI 3
|
||
+#define KVM_LOONGARCH_IRQ_TYPE_IOAPIC 4
|
||
+
|
||
+static void legacy_set_irq(void *opaque, int irq, int level)
|
||
+{
|
||
+ qemu_irq *pic = opaque;
|
||
+
|
||
+ qemu_set_irq(pic[irq], level);
|
||
+}
|
||
+
|
||
+typedef struct ls3a_intctlstate {
|
||
+ uint8_t nodecounter_reg[0x100];
|
||
+ uint8_t pm_reg[0x100];
|
||
+ uint8_t msi_reg[0x8];
|
||
+ CPULOONGARCHState **env;
|
||
+ DeviceState *apicdev;
|
||
+ qemu_irq *ioapic_irq;
|
||
+#ifdef CONFIG_KVM
|
||
+ struct loongarch_kvm_irqchip chip;
|
||
+#endif
|
||
+} ls3a_intctlstate;
|
||
+
|
||
+typedef struct ls3a_func_args {
|
||
+ ls3a_intctlstate *state;
|
||
+ uint64_t base;
|
||
+ uint32_t mask;
|
||
+ uint8_t *mem;
|
||
+} ls3a_func_args;
|
||
+
|
||
+static uint64_t ls3a_msi_mem_read(void *opaque, hwaddr addr, unsigned size)
|
||
+{
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static void ls3a_msi_mem_write(void *opaque, hwaddr addr, uint64_t val,
|
||
+ unsigned size)
|
||
+{
|
||
+ struct kvm_msi msi;
|
||
+ apicState *apic;
|
||
+
|
||
+ apic = (apicState *)opaque;
|
||
+ msi.address_lo = 0;
|
||
+ msi.address_hi = 0;
|
||
+ msi.data = val & 0xff;
|
||
+ msi.flags = 0;
|
||
+ memset(msi.pad, 0, sizeof(msi.pad));
|
||
+
|
||
+ if (kvm_irqchip_in_kernel()) {
|
||
+ kvm_vm_ioctl(kvm_state, KVM_SIGNAL_MSI, &msi);
|
||
+ } else {
|
||
+ qemu_set_irq(apic->irq[msi.data], 1);
|
||
+ }
|
||
+}
|
||
+
|
||
+static const MemoryRegionOps ls3a_msi_ops = {
|
||
+ .read = ls3a_msi_mem_read,
|
||
+ .write = ls3a_msi_mem_write,
|
||
+ .endianness = DEVICE_NATIVE_ENDIAN,
|
||
+};
|
||
+
|
||
+static const VMStateDescription vmstate_ls3a_msi = {
|
||
+ .name = "ls3a-msi",
|
||
+ .version_id = 0,
|
||
+ .minimum_version_id = 0,
|
||
+ .fields =
|
||
+ (VMStateField[]){ VMSTATE_UINT8_ARRAY(msi_reg, ls3a_intctlstate, 0x8),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static void ioapic_handler(void *opaque, int irq, int level)
|
||
+{
|
||
+ apicState *apic;
|
||
+ int kvm_irq;
|
||
+
|
||
+ apic = (apicState *)opaque;
|
||
+
|
||
+ if (kvm_irqchip_in_kernel()) {
|
||
+ kvm_irq =
|
||
+ (KVM_LOONGARCH_IRQ_TYPE_IOAPIC << KVM_LOONGARCH_IRQ_TYPE_SHIFT) |
|
||
+ (0 << KVM_LOONGARCH_IRQ_VCPU_SHIFT) | irq;
|
||
+ kvm_set_irq(kvm_state, kvm_irq, !!level);
|
||
+ } else {
|
||
+ qemu_set_irq(apic->irq[irq], level);
|
||
+ }
|
||
+}
|
||
+
|
||
+static void *ls3a_intctl_init(MachineState *machine, CPULOONGARCHState *env[])
|
||
+{
|
||
+ qemu_irq *irqhandler;
|
||
+ ls3a_intctlstate *s;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ LoongarchMachineClass *mc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ DeviceState *dev;
|
||
+ SysBusDevice *busdev;
|
||
+ MemoryRegion *address_space_mem = get_system_memory();
|
||
+ MemoryRegion *iomem = NULL;
|
||
+#ifdef CONFIG_KVM
|
||
+ int i;
|
||
+#endif
|
||
+
|
||
+ s = g_malloc0(sizeof(ls3a_intctlstate));
|
||
+
|
||
+ if (!s) {
|
||
+ return NULL;
|
||
+ }
|
||
+
|
||
+ /*Add MSI mmio memory*/
|
||
+ iomem = g_new(MemoryRegion, 1);
|
||
+ memory_region_init_io(iomem, NULL, &ls3a_msi_ops, lsms->apic, "ls3a_msi",
|
||
+ 0x8);
|
||
+ memory_region_add_subregion(address_space_mem, MSI_ADDR_LOW, iomem);
|
||
+ vmstate_register(NULL, 0, &vmstate_ls3a_msi, s);
|
||
+
|
||
+ s->env = env;
|
||
+
|
||
+ if (!strcmp(mc->bridge_name, "ls7a")) {
|
||
+ if (lsms->apic_xrupt_override) {
|
||
+ DPRINTF("irqchip in kernel %d\n", kvm_irqchip_in_kernel());
|
||
+#ifdef CONFIG_KVM
|
||
+ if (kvm_has_gsi_routing()) {
|
||
+ for (i = 0; i < 32; ++i) {
|
||
+ kvm_irqchip_add_irq_route(kvm_state, i, 0, i);
|
||
+ }
|
||
+ kvm_gsi_routing_allowed = true;
|
||
+ }
|
||
+ kvm_msi_via_irqfd_allowed = kvm_irqfds_enabled();
|
||
+#endif
|
||
+ }
|
||
+
|
||
+ irqhandler = qemu_allocate_irqs(ioapic_handler, lsms->apic, 64);
|
||
+ dev = qdev_new("ioapic");
|
||
+ busdev = SYS_BUS_DEVICE(dev);
|
||
+ sysbus_realize_and_unref(busdev, &error_fatal);
|
||
+ sysbus_mmio_map(busdev, 0, mc->ls7a_ioapic_reg_base);
|
||
+ s->ioapic_irq = irqhandler;
|
||
+ s->apicdev = dev;
|
||
+ return s->ioapic_irq;
|
||
+ }
|
||
+ return NULL;
|
||
+}
|
||
+
|
||
+/* Network support */
|
||
+static void network_init(PCIBus *pci_bus)
|
||
+{
|
||
+ int i;
|
||
+
|
||
+ for (i = 0; i < nb_nics; i++) {
|
||
+ NICInfo *nd = &nd_table[i];
|
||
+
|
||
+ if (!nd->model) {
|
||
+ nd->model = g_strdup("virtio-net-pci");
|
||
+ }
|
||
+
|
||
+ pci_nic_init_nofail(nd, pci_bus, nd->model, NULL);
|
||
+ }
|
||
+}
|
||
+
|
||
+void loongarch_cpu_destroy(MachineState *machine, LOONGARCHCPU *cpu)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ unsigned int id;
|
||
+ int smp_cpus = machine->smp.cpus;
|
||
+ id = cpu->id;
|
||
+ qemu_unregister_reset(slave_cpu_reset, lsms->reset_info[id]);
|
||
+ g_free(lsms->reset_info[id]);
|
||
+ lsms->reset_info[id] = NULL;
|
||
+
|
||
+ smp_cpus -= 1;
|
||
+ if (lsms->fw_cfg) {
|
||
+ fw_cfg_modify_i16(lsms->fw_cfg, FW_CFG_NB_CPUS, (uint16_t)smp_cpus);
|
||
+ }
|
||
+
|
||
+ qemu_del_vm_change_state_handler(cpu->cpuStateEntry);
|
||
+}
|
||
+
|
||
+LOONGARCHCPU *loongarch_cpu_create(MachineState *machine, LOONGARCHCPU *cpu,
|
||
+ Error **errp)
|
||
+{
|
||
+ CPULOONGARCHState *env;
|
||
+ unsigned int id;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ int smp_cpus = machine->smp.cpus;
|
||
+ id = cpu->id;
|
||
+ env = &cpu->env;
|
||
+ cpu_states[id] = env;
|
||
+ env->CSR_TMID |= id;
|
||
+
|
||
+ lsms = LoongarchMACHINE(machine);
|
||
+ lsms->reset_info[id] = g_malloc0(sizeof(ResetData));
|
||
+ lsms->reset_info[id]->cpu = cpu;
|
||
+ lsms->reset_info[id]->vector = env->active_tc.PC;
|
||
+ qemu_register_reset(slave_cpu_reset, lsms->reset_info[id]);
|
||
+
|
||
+ /* Init CPU internal devices */
|
||
+ cpu_init_irq(cpu);
|
||
+ cpu_loongarch_clock_init(cpu);
|
||
+
|
||
+ smp_cpus += 1;
|
||
+ if (lsms->fw_cfg) {
|
||
+ fw_cfg_modify_i16(lsms->fw_cfg, FW_CFG_NB_CPUS, (uint16_t)smp_cpus);
|
||
+ }
|
||
+ cpu_init_ipi(lsms, env->irq[12], id);
|
||
+ cpu_init_apic(lsms, env, id);
|
||
+
|
||
+ return cpu;
|
||
+}
|
||
+
|
||
+static void fw_cfg_boot_set(void *opaque, const char *boot_device,
|
||
+ Error **errp)
|
||
+{
|
||
+ fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]);
|
||
+}
|
||
+
|
||
+static FWCfgState *loongarch_fw_cfg_init(ram_addr_t ram_size,
|
||
+ LoongarchMachineState *lsms)
|
||
+{
|
||
+ FWCfgState *fw_cfg;
|
||
+ uint64_t *numa_fw_cfg;
|
||
+ int i;
|
||
+ const CPUArchIdList *cpus;
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(lsms);
|
||
+ MachineState *ms = MACHINE(OBJECT(lsms));
|
||
+ int max_cpus = ms->smp.max_cpus;
|
||
+ int smp_cpus = ms->smp.cpus;
|
||
+ int nb_numa_nodes = ms->numa_state->num_nodes;
|
||
+ NodeInfo *numa_info = ms->numa_state->nodes;
|
||
+
|
||
+ fw_cfg = fw_cfg_init_mem_wide(FW_CFG_ADDR + 8, FW_CFG_ADDR, 8, 0, NULL);
|
||
+ fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)max_cpus);
|
||
+ fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
|
||
+ fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, (uint16_t)smp_cpus);
|
||
+
|
||
+ /*
|
||
+ * allocate memory for the NUMA channel: one (64bit) word for the number
|
||
+ * of nodes, one word for each VCPU->node and one word for each node to
|
||
+ * hold the amount of memory.
|
||
+ */
|
||
+ numa_fw_cfg = g_new0(uint64_t, 1 + max_cpus + nb_numa_nodes);
|
||
+ numa_fw_cfg[0] = cpu_to_le64(nb_numa_nodes);
|
||
+ cpus = mc->possible_cpu_arch_ids(MACHINE(lsms));
|
||
+ for (i = 0; i < cpus->len; i++) {
|
||
+ unsigned int apic_id = cpus->cpus[i].arch_id;
|
||
+ assert(apic_id < max_cpus);
|
||
+ numa_fw_cfg[apic_id + 1] = cpu_to_le64(cpus->cpus[i].props.node_id);
|
||
+ }
|
||
+ for (i = 0; i < nb_numa_nodes; i++) {
|
||
+ numa_fw_cfg[max_cpus + 1 + i] = cpu_to_le64(numa_info[i].node_mem);
|
||
+ }
|
||
+ fw_cfg_add_bytes(fw_cfg, FW_CFG_NUMA, numa_fw_cfg,
|
||
+ (1 + max_cpus + nb_numa_nodes) * sizeof(*numa_fw_cfg));
|
||
+
|
||
+ qemu_register_boot_set(fw_cfg_boot_set, fw_cfg);
|
||
+ return fw_cfg;
|
||
+}
|
||
+
|
||
+static void loongarch_build_smbios(LoongarchMachineState *lsms)
|
||
+{
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ MachineState *ms = MACHINE(OBJECT(lsms));
|
||
+ uint8_t *smbios_tables, *smbios_anchor;
|
||
+ size_t smbios_tables_len, smbios_anchor_len;
|
||
+ const char *product = "QEMU Virtual Machine";
|
||
+ ms->smp.cores = 4;
|
||
+
|
||
+ if (!lsms->fw_cfg) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (kvm_enabled()) {
|
||
+ if (strstr(lsmc->cpu_name, "5000")) {
|
||
+ product = "KVM";
|
||
+ }
|
||
+ } else {
|
||
+ product = "Loongarch-3A5K-7A1000-TCG";
|
||
+ }
|
||
+
|
||
+ smbios_set_defaults("Loongson", product, lsmc->cpu_name, false, true,
|
||
+ SMBIOS_ENTRY_POINT_30);
|
||
+
|
||
+ smbios_get_tables(ms, NULL, 0, &smbios_tables, &smbios_tables_len,
|
||
+ &smbios_anchor, &smbios_anchor_len, &error_fatal);
|
||
+
|
||
+ if (smbios_anchor) {
|
||
+ fw_cfg_add_file(lsms->fw_cfg, "etc/smbios/smbios-tables",
|
||
+ smbios_tables, smbios_tables_len);
|
||
+ fw_cfg_add_file(lsms->fw_cfg, "etc/smbios/smbios-anchor",
|
||
+ smbios_anchor, smbios_anchor_len);
|
||
+ }
|
||
+}
|
||
+
|
||
+static void loongarch_machine_done(Notifier *notifier, void *data)
|
||
+{
|
||
+ LoongarchMachineState *lsms =
|
||
+ container_of(notifier, LoongarchMachineState, machine_done);
|
||
+
|
||
+ platform_bus_add_all_fdt_nodes(
|
||
+ lsms->fdt, NULL, VIRT_PLATFORM_BUS_BASEADDRESS, VIRT_PLATFORM_BUS_SIZE,
|
||
+ VIRT_PLATFORM_BUS_IRQ);
|
||
+
|
||
+ qemu_fdt_dumpdtb(lsms->fdt, lsms->fdt_size);
|
||
+ /* load fdt */
|
||
+ MemoryRegion *fdt_rom = g_new(MemoryRegion, 1);
|
||
+ memory_region_init_rom(fdt_rom, NULL, "fdt", LS_FDT_SIZE, &error_fatal);
|
||
+ memory_region_add_subregion(get_system_memory(), LS_FDT_BASE, fdt_rom);
|
||
+ rom_add_blob_fixed("fdt", lsms->fdt, lsms->fdt_size, LS_FDT_BASE);
|
||
+
|
||
+ loongarch_acpi_setup();
|
||
+ loongarch_build_smbios(lsms);
|
||
+}
|
||
+
|
||
+#ifdef CONFIG_TCG
|
||
+#define FEATURE_REG 0x1fe00008
|
||
+#define VENDOR_REG 0x1fe00010
|
||
+#define CPUNAME_REG 0x1fe00020
|
||
+#define OTHER_FUNC_REG 0x1fe00420
|
||
+#define _str(x) #x
|
||
+#define str(x) _str(x)
|
||
+#define SIMPLE_OPS(ADDR, SIZE) \
|
||
+ ({ \
|
||
+ MemoryRegion *iomem = g_new(MemoryRegion, 1); \
|
||
+ memory_region_init_io(iomem, NULL, &loongarch_qemu_ops, (void *)ADDR, \
|
||
+ str(ADDR), SIZE); \
|
||
+ memory_region_add_subregion_overlap(address_space_mem, ADDR, iomem, \
|
||
+ 1); \
|
||
+ })
|
||
+
|
||
+static int reg180;
|
||
+
|
||
+static void loongarch_qemu_write(void *opaque, hwaddr addr, uint64_t val,
|
||
+ unsigned size)
|
||
+{
|
||
+ addr = ((hwaddr)(long)opaque) + addr;
|
||
+ addr = addr & 0xffffffff;
|
||
+ switch (addr) {
|
||
+ case 0x1fe00180:
|
||
+ reg180 = val;
|
||
+ break;
|
||
+ }
|
||
+}
|
||
+
|
||
+static uint64_t loongarch_qemu_read(void *opaque, hwaddr addr, unsigned size)
|
||
+{
|
||
+ uint64_t feature = 0UL;
|
||
+ addr = ((hwaddr)(long)opaque) + addr;
|
||
+ addr = addr & 0xffffffff;
|
||
+ switch (addr) {
|
||
+ case 0x1fe00180:
|
||
+ return reg180;
|
||
+ case 0x1001041c:
|
||
+ return 0xa800;
|
||
+ case FEATURE_REG:
|
||
+ feature |= 1UL << 2 | 1UL << 3 | 1UL << 4 | 1UL << 11;
|
||
+ return feature;
|
||
+ case VENDOR_REG:
|
||
+ return *(uint64_t *)"Loongson-3A5000";
|
||
+ case CPUNAME_REG:
|
||
+ return *(uint64_t *)"3A5000";
|
||
+ case 0x10013ffc:
|
||
+ return 0x80;
|
||
+ }
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+static const MemoryRegionOps loongarch_qemu_ops = {
|
||
+ .read = loongarch_qemu_read,
|
||
+ .write = loongarch_qemu_write,
|
||
+ .endianness = DEVICE_NATIVE_ENDIAN,
|
||
+ .valid = {
|
||
+ .min_access_size = 4,
|
||
+ .max_access_size = 8,
|
||
+ },
|
||
+ .impl = {
|
||
+ .min_access_size = 4,
|
||
+ .max_access_size = 8,
|
||
+ },
|
||
+};
|
||
+#endif
|
||
+
|
||
+static void loongarch_system_flash_cleanup_unused(LoongarchMachineState *lsms)
|
||
+{
|
||
+ char *prop_name;
|
||
+ int i;
|
||
+ Object *dev_obj;
|
||
+
|
||
+ for (i = 0; i < ARRAY_SIZE(lsms->flash); i++) {
|
||
+ dev_obj = OBJECT(lsms->flash[i]);
|
||
+ if (!object_property_get_bool(dev_obj, "realized", &error_abort)) {
|
||
+ prop_name = g_strdup_printf("pflash%d", i);
|
||
+ object_property_del(OBJECT(lsms), prop_name);
|
||
+ g_free(prop_name);
|
||
+ object_unparent(dev_obj);
|
||
+ lsms->flash[i] = NULL;
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+static bool loongarch_system_flash_init(LoongarchMachineState *lsms)
|
||
+{
|
||
+ int i = 0;
|
||
+ int64_t size = 0;
|
||
+ PFlashCFI01 *pflash = NULL;
|
||
+ BlockBackend *pflash_blk;
|
||
+
|
||
+ for (i = 0; i < ARRAY_SIZE(lsms->flash); i++) {
|
||
+ pflash_blk = NULL;
|
||
+ pflash = NULL;
|
||
+
|
||
+ pflash = lsms->flash[i];
|
||
+ pflash_cfi01_legacy_drive(pflash, drive_get(IF_PFLASH, 0, i));
|
||
+
|
||
+ pflash_blk = pflash_cfi01_get_blk(pflash);
|
||
+ /*The pflash0 must be exist, or not support boot by pflash*/
|
||
+ if (pflash_blk == NULL) {
|
||
+ if (i == 0) {
|
||
+ return false;
|
||
+ } else {
|
||
+ break;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ size = blk_getlength(pflash_blk);
|
||
+ if (size == 0 || size % FLASH_SECTOR_SIZE != 0) {
|
||
+ error_report("system firmware block device %s has invalid size "
|
||
+ "%" PRId64,
|
||
+ blk_name(pflash_blk), size);
|
||
+ error_report("its size must be a non-zero multiple of 0x%x",
|
||
+ FLASH_SECTOR_SIZE);
|
||
+ exit(1);
|
||
+ }
|
||
+ qdev_prop_set_uint32(DEVICE(pflash), "num-blocks",
|
||
+ size / FLASH_SECTOR_SIZE);
|
||
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(pflash), &error_fatal);
|
||
+ if (i == 0) {
|
||
+ sysbus_mmio_map(SYS_BUS_DEVICE(pflash), 0, LS_BIOS_BASE);
|
||
+ } else {
|
||
+ sysbus_mmio_map_overlap(SYS_BUS_DEVICE(pflash), 0,
|
||
+ LS_BIOS_VAR_BASE, 1);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return true;
|
||
+}
|
||
+
|
||
+static void ls3a5k_bios_init(LoongarchMachineState *lsms, ram_addr_t ram_size,
|
||
+ uint64_t highram_size, uint64_t phyAddr_initrd,
|
||
+ const char *kernel_filename,
|
||
+ const char *kernel_cmdline,
|
||
+ const char *initrd_filename)
|
||
+{
|
||
+ MemoryRegion *bios;
|
||
+ bool fw_cfg_used = false;
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ char *filename;
|
||
+ int bios_size;
|
||
+ const char *bios_name;
|
||
+
|
||
+ bios_name = MACHINE(lsms)->firmware;
|
||
+ if (kernel_filename) {
|
||
+ loaderparams.ram_size = ram_size;
|
||
+ loaderparams.kernel_filename = kernel_filename;
|
||
+ loaderparams.kernel_cmdline = kernel_cmdline;
|
||
+ loaderparams.initrd_filename = initrd_filename;
|
||
+ }
|
||
+
|
||
+ if (loongarch_system_flash_init(lsms)) {
|
||
+ fw_cfg_used = true;
|
||
+ } else {
|
||
+ bios = g_new(MemoryRegion, 1);
|
||
+ memory_region_init_ram(bios, NULL, "loongarch.bios", LS_BIOS_SIZE,
|
||
+ &error_fatal);
|
||
+ memory_region_set_readonly(bios, true);
|
||
+ memory_region_add_subregion(get_system_memory(), LS_BIOS_BASE, bios);
|
||
+
|
||
+ /* BIOS load */
|
||
+ if (bios_name) {
|
||
+ if (access(bios_name, R_OK) == 0) {
|
||
+ load_image_targphys(bios_name, LS_BIOS_BASE, LS_BIOS_SIZE);
|
||
+ } else {
|
||
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
|
||
+ load_image_targphys(filename, LS_BIOS_BASE, LS_BIOS_SIZE);
|
||
+ g_free(filename);
|
||
+ }
|
||
+ fw_cfg_used = true;
|
||
+ } else {
|
||
+ if (strstr(lsmc->cpu_name, "5000")) {
|
||
+ bios_size = sizeof(ls3a5k_aui_boot_code);
|
||
+ rom_add_blob_fixed("bios", ls3a5k_aui_boot_code, bios_size,
|
||
+ LS_BIOS_BASE);
|
||
+ }
|
||
+
|
||
+ if (kernel_filename) {
|
||
+ lsms->reset_info[0]->vector = load_kernel();
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ loongarch_system_flash_cleanup_unused(lsms);
|
||
+
|
||
+ if (fw_cfg_used) {
|
||
+ lsms->fw_cfg = loongarch_fw_cfg_init(ram_size, lsms);
|
||
+ rom_set_fw(lsms->fw_cfg);
|
||
+ fw_conf_init(ram_size);
|
||
+ rom_add_blob_fixed("fw_conf", (void *)&fw_config, sizeof(fw_config),
|
||
+ FW_CONF_ADDR);
|
||
+
|
||
+ if (kernel_filename) {
|
||
+ fw_cfg_add_kernel_info(lsms->fw_cfg, highram_size, phyAddr_initrd);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (lsms->fw_cfg != NULL) {
|
||
+ fw_cfg_add_file(lsms->fw_cfg, "etc/memmap", la_memmap_table,
|
||
+ sizeof(struct la_memmap_entry) * (la_memmap_entries));
|
||
+ }
|
||
+
|
||
+ return;
|
||
+}
|
||
+
|
||
+static void create_fdt(LoongarchMachineState *lsms)
|
||
+{
|
||
+ lsms->fdt = create_device_tree(&lsms->fdt_size);
|
||
+ if (!lsms->fdt) {
|
||
+ error_report("create_device_tree() failed");
|
||
+ exit(1);
|
||
+ }
|
||
+
|
||
+ /* Header */
|
||
+ qemu_fdt_setprop_string(lsms->fdt, "/", "compatible",
|
||
+ "linux,dummy-loongson3");
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, "/", "#address-cells", 0x2);
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, "/", "#size-cells", 0x2);
|
||
+}
|
||
+
|
||
+static void fdt_add_cpu_nodes(const LoongarchMachineState *lsms)
|
||
+{
|
||
+ int num;
|
||
+ const MachineState *ms = MACHINE(lsms);
|
||
+ int smp_cpus = ms->smp.cpus;
|
||
+
|
||
+ qemu_fdt_add_subnode(lsms->fdt, "/cpus");
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, "/cpus", "#address-cells", 0x1);
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, "/cpus", "#size-cells", 0x0);
|
||
+
|
||
+ /* cpu nodes */
|
||
+ for (num = smp_cpus - 1; num >= 0; num--) {
|
||
+ char *nodename = g_strdup_printf("/cpus/cpu@%d", num);
|
||
+ LOONGARCHCPU *cpu = LOONGARCH_CPU(qemu_get_cpu(num));
|
||
+
|
||
+ qemu_fdt_add_subnode(lsms->fdt, nodename);
|
||
+ qemu_fdt_setprop_string(lsms->fdt, nodename, "device_type", "cpu");
|
||
+ qemu_fdt_setprop_string(lsms->fdt, nodename, "compatible",
|
||
+ cpu->dtb_compatible);
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, nodename, "reg", num);
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, nodename, "phandle",
|
||
+ qemu_fdt_alloc_phandle(lsms->fdt));
|
||
+ g_free(nodename);
|
||
+ }
|
||
+
|
||
+ /*cpu map */
|
||
+ qemu_fdt_add_subnode(lsms->fdt, "/cpus/cpu-map");
|
||
+
|
||
+ for (num = smp_cpus - 1; num >= 0; num--) {
|
||
+ char *cpu_path = g_strdup_printf("/cpus/cpu@%d", num);
|
||
+ char *map_path;
|
||
+
|
||
+ if (ms->smp.threads > 1) {
|
||
+ map_path =
|
||
+ g_strdup_printf("/cpus/cpu-map/socket%d/core%d/thread%d",
|
||
+ num / (ms->smp.cores * ms->smp.threads),
|
||
+ (num / ms->smp.threads) % ms->smp.cores,
|
||
+ num % ms->smp.threads);
|
||
+ } else {
|
||
+ map_path =
|
||
+ g_strdup_printf("/cpus/cpu-map/socket%d/core%d",
|
||
+ num / ms->smp.cores, num % ms->smp.cores);
|
||
+ }
|
||
+ qemu_fdt_add_path(lsms->fdt, map_path);
|
||
+ qemu_fdt_setprop_phandle(lsms->fdt, map_path, "cpu", cpu_path);
|
||
+
|
||
+ g_free(map_path);
|
||
+ g_free(cpu_path);
|
||
+ }
|
||
+}
|
||
+
|
||
+static void fdt_add_fw_cfg_node(const LoongarchMachineState *lsms)
|
||
+{
|
||
+ char *nodename;
|
||
+ hwaddr base = FW_CFG_ADDR;
|
||
+
|
||
+ nodename = g_strdup_printf("/fw_cfg@%" PRIx64, base);
|
||
+ qemu_fdt_add_subnode(lsms->fdt, nodename);
|
||
+ qemu_fdt_setprop_string(lsms->fdt, nodename, "compatible",
|
||
+ "qemu,fw-cfg-mmio");
|
||
+ qemu_fdt_setprop_sized_cells(lsms->fdt, nodename, "reg", 2, base, 2, 0x8);
|
||
+ qemu_fdt_setprop(lsms->fdt, nodename, "dma-coherent", NULL, 0);
|
||
+ g_free(nodename);
|
||
+}
|
||
+
|
||
+static void fdt_add_pcie_node(const LoongarchMachineState *lsms)
|
||
+{
|
||
+ char *nodename;
|
||
+ hwaddr base_mmio = PCIE_MEMORY_BASE;
|
||
+ hwaddr size_mmio = PCIE_MEMORY_SIZE;
|
||
+ hwaddr base_pio = LS3A5K_ISA_IO_BASE;
|
||
+ hwaddr size_pio = LS_ISA_IO_SIZE;
|
||
+ hwaddr base_pcie = LS_PCIECFG_BASE;
|
||
+ hwaddr size_pcie = LS_PCIECFG_SIZE;
|
||
+ hwaddr base = base_pcie;
|
||
+
|
||
+ nodename = g_strdup_printf("/pcie@%" PRIx64, base);
|
||
+ qemu_fdt_add_subnode(lsms->fdt, nodename);
|
||
+ qemu_fdt_setprop_string(lsms->fdt, nodename, "compatible",
|
||
+ "pci-host-ecam-generic");
|
||
+ qemu_fdt_setprop_string(lsms->fdt, nodename, "device_type", "pci");
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, nodename, "#address-cells", 3);
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, nodename, "#size-cells", 2);
|
||
+ qemu_fdt_setprop_cell(lsms->fdt, nodename, "linux,pci-domain", 0);
|
||
+ qemu_fdt_setprop_cells(lsms->fdt, nodename, "bus-range", 0,
|
||
+ PCIE_MMCFG_BUS(LS_PCIECFG_SIZE - 1));
|
||
+ qemu_fdt_setprop(lsms->fdt, nodename, "dma-coherent", NULL, 0);
|
||
+ qemu_fdt_setprop_sized_cells(lsms->fdt, nodename, "reg", 2, base_pcie, 2,
|
||
+ size_pcie);
|
||
+ qemu_fdt_setprop_sized_cells(lsms->fdt, nodename, "ranges", 1,
|
||
+ FDT_PCI_RANGE_IOPORT, 2, 0, 2, base_pio, 2,
|
||
+ size_pio, 1, FDT_PCI_RANGE_MMIO, 2, base_mmio,
|
||
+ 2, base_mmio, 2, size_mmio);
|
||
+ g_free(nodename);
|
||
+}
|
||
+
|
||
+static void create_platform_bus(LoongarchMachineState *s, qemu_irq *pic)
|
||
+{
|
||
+ DeviceState *dev;
|
||
+ SysBusDevice *sysbus;
|
||
+ int i;
|
||
+ MemoryRegion *sysmem = get_system_memory();
|
||
+
|
||
+ dev = qdev_new(TYPE_PLATFORM_BUS_DEVICE);
|
||
+ dev->id = g_strdup(TYPE_PLATFORM_BUS_DEVICE);
|
||
+ qdev_prop_set_uint32(dev, "num_irqs", VIRT_PLATFORM_BUS_NUM_IRQS);
|
||
+ qdev_prop_set_uint32(dev, "mmio_size", VIRT_PLATFORM_BUS_SIZE);
|
||
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
||
+ s->platform_bus_dev = dev;
|
||
+
|
||
+ sysbus = SYS_BUS_DEVICE(dev);
|
||
+ for (i = 0; i < VIRT_PLATFORM_BUS_NUM_IRQS; i++) {
|
||
+ int irq = VIRT_PLATFORM_BUS_IRQ + i;
|
||
+ sysbus_connect_irq(sysbus, i, pic[irq - LOONGARCH_PCH_IRQ_BASE]);
|
||
+ }
|
||
+
|
||
+ memory_region_add_subregion(sysmem, VIRT_PLATFORM_BUS_BASEADDRESS,
|
||
+ sysbus_mmio_get_region(sysbus, 0));
|
||
+}
|
||
+
|
||
+static void ls3a5k_init(MachineState *args)
|
||
+{
|
||
+ int i;
|
||
+ const char *cpu_model = args->cpu_type;
|
||
+ const char *kernel_filename = args->kernel_filename;
|
||
+ const char *kernel_cmdline = args->kernel_cmdline;
|
||
+ const char *initrd_filename = args->initrd_filename;
|
||
+
|
||
+ ram_addr_t ram_size = args->ram_size;
|
||
+ MemoryRegion *address_space_mem = get_system_memory();
|
||
+ ram_addr_t offset = 0;
|
||
+ MemoryRegion *isa_io = g_new(MemoryRegion, 1);
|
||
+ MemoryRegion *isa_mem = g_new(MemoryRegion, 1);
|
||
+ MachineState *machine = args;
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ int smp_cpus = machine->smp.cpus;
|
||
+ int nb_numa_nodes = machine->numa_state->num_nodes;
|
||
+ NodeInfo *numa_info = machine->numa_state->nodes;
|
||
+ LOONGARCHCPU *cpu;
|
||
+ CPULOONGARCHState *env;
|
||
+ qemu_irq *ls7a_apic = NULL;
|
||
+ qemu_irq *pirq = NULL;
|
||
+ PCIBus *pci_bus = NULL;
|
||
+ char *ramName = NULL;
|
||
+ uint64_t lowram_size = 0, highram_size = 0, phyAddr = 0, memmap_size = 0,
|
||
+ highram_end_addr = 0;
|
||
+
|
||
+ CPUArchIdList *possible_cpus;
|
||
+ if (strstr(lsmc->cpu_name, "5000")) {
|
||
+ if (strcmp(cpu_model, LOONGARCH_CPU_TYPE_NAME("Loongson-3A5000")) &&
|
||
+ strcmp(cpu_model, LOONGARCH_CPU_TYPE_NAME("host"))) {
|
||
+ error_report("machine type %s does not match cpu type %s",
|
||
+ lsmc->cpu_name, cpu_model);
|
||
+ exit(1);
|
||
+ }
|
||
+ if (kvm_enabled()) {
|
||
+ kvm_vm_ioctl(kvm_state, KVM_LARCH_SET_CPUCFG, ls3a5k_cpucfgs);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ create_fdt(lsms);
|
||
+
|
||
+ DPRINTF("isa 0x%lx\n", lsmc->isa_io_base);
|
||
+ DPRINTF("cpu_name %s bridge_name %s\n", lsmc->cpu_name, lsmc->bridge_name);
|
||
+
|
||
+ /* init CPUs */
|
||
+ mc->possible_cpu_arch_ids(machine);
|
||
+ possible_cpus = machine->possible_cpus;
|
||
+
|
||
+ for (i = 0; i < smp_cpus; i++) {
|
||
+ Object *obj = NULL;
|
||
+ Error *local_err = NULL;
|
||
+
|
||
+ obj = object_new(possible_cpus->cpus[i].type);
|
||
+
|
||
+ object_property_set_uint(obj, "id", possible_cpus->cpus[i].arch_id,
|
||
+ &local_err);
|
||
+ object_property_set_bool(obj, "realized", true, &local_err);
|
||
+
|
||
+ object_unref(obj);
|
||
+ error_propagate(&error_fatal, local_err);
|
||
+
|
||
+ cpu = LOONGARCH_CPU(CPU(obj));
|
||
+ if (cpu == NULL) {
|
||
+ fprintf(stderr, "Unable to find CPU definition\n");
|
||
+ exit(1);
|
||
+ }
|
||
+
|
||
+ env = &cpu->env;
|
||
+ cpu_states[i] = env;
|
||
+ env->CSR_TMID |= i;
|
||
+
|
||
+ lsms->reset_info[i] = g_malloc0(sizeof(ResetData));
|
||
+ lsms->reset_info[i]->cpu = cpu;
|
||
+ lsms->reset_info[i]->vector = env->active_tc.PC;
|
||
+ if (i == 0) {
|
||
+ qemu_register_reset(main_cpu_reset, lsms->reset_info[i]);
|
||
+ } else {
|
||
+ qemu_register_reset(slave_cpu_reset, lsms->reset_info[i]);
|
||
+ }
|
||
+
|
||
+ /* Init CPU internal devices */
|
||
+ cpu_init_irq(cpu);
|
||
+ cpu_loongarch_clock_init(cpu);
|
||
+ cpu_init_ipi(lsms, env->irq[12], i);
|
||
+ cpu_init_apic(lsms, env, i);
|
||
+ }
|
||
+
|
||
+ lsms->hotpluged_cpu_num = 0;
|
||
+ fdt_add_cpu_nodes(lsms);
|
||
+ env = cpu_states[0];
|
||
+
|
||
+ /* node0 mem*/
|
||
+ phyAddr = (uint64_t)0;
|
||
+ MemoryRegion *lowmem = g_new(MemoryRegion, 1);
|
||
+ ramName = g_strdup_printf("loongarch_ls3a.node%d.lowram", 0);
|
||
+
|
||
+ lowram_size = MIN(ram_size, 256 * 0x100000);
|
||
+ memory_region_init_alias(lowmem, NULL, ramName, machine->ram, 0,
|
||
+ lowram_size);
|
||
+ memory_region_add_subregion(address_space_mem, phyAddr, lowmem);
|
||
+
|
||
+ offset += lowram_size;
|
||
+ if (nb_numa_nodes > 0) {
|
||
+ highram_size = numa_info[0].node_mem - 256 * MiB;
|
||
+ if (numa_info[0].node_mem > GiB) {
|
||
+ memmap_size = numa_info[0].node_mem - GiB;
|
||
+ la_memmap_add_entry(0xc0000000ULL, memmap_size, SYSTEM_RAM);
|
||
+ }
|
||
+ } else {
|
||
+ highram_size = ram_size - 256 * MiB;
|
||
+ if (ram_size > GiB) {
|
||
+ memmap_size = ram_size - GiB;
|
||
+ la_memmap_add_entry(0xc0000000ULL, memmap_size, SYSTEM_RAM);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ phyAddr = (uint64_t)0x90000000;
|
||
+ MemoryRegion *highmem = g_new(MemoryRegion, 1);
|
||
+ ramName = g_strdup_printf("loongarch_ls3a.node%d.highram", 0);
|
||
+ memory_region_init_alias(highmem, NULL, ramName, machine->ram, offset,
|
||
+ highram_size);
|
||
+ memory_region_add_subregion(address_space_mem, phyAddr, highmem);
|
||
+ offset += highram_size;
|
||
+ phyAddr += highram_size;
|
||
+
|
||
+ /* initrd address use high mem from high to low */
|
||
+ highram_end_addr = phyAddr;
|
||
+ /* node1~ nodemax */
|
||
+ for (i = 1; i < nb_numa_nodes; i++) {
|
||
+ MemoryRegion *nodemem = g_new(MemoryRegion, 1);
|
||
+ ramName = g_strdup_printf("loongarch_ls3a.node%d.ram", i);
|
||
+ memory_region_init_alias(nodemem, NULL, ramName, machine->ram, offset,
|
||
+ numa_info[i].node_mem);
|
||
+ memory_region_add_subregion(address_space_mem, phyAddr, nodemem);
|
||
+ la_memmap_add_entry(phyAddr, numa_info[i].node_mem, SYSTEM_RAM);
|
||
+ offset += numa_info[i].node_mem;
|
||
+ phyAddr += numa_info[i].node_mem;
|
||
+ }
|
||
+
|
||
+ fdt_add_fw_cfg_node(lsms);
|
||
+ ls3a5k_bios_init(lsms, ram_size, highram_size, highram_end_addr,
|
||
+ kernel_filename, kernel_cmdline, initrd_filename);
|
||
+
|
||
+ lsms->machine_done.notify = loongarch_machine_done;
|
||
+ qemu_add_machine_init_done_notifier(&lsms->machine_done);
|
||
+ /*vmstate_register_ram_global(bios);*/
|
||
+
|
||
+ /* initialize hotplug memory address space */
|
||
+ lsms->hotplug_memory_size = 0;
|
||
+
|
||
+ /* always allocate the device memory information */
|
||
+ machine->device_memory = g_malloc0(sizeof(*machine->device_memory));
|
||
+ if (machine->ram_size < machine->maxram_size) {
|
||
+ int max_memslots;
|
||
+
|
||
+ lsms->hotplug_memory_size = machine->maxram_size - machine->ram_size;
|
||
+ /*
|
||
+ * Limit the number of hotpluggable memory slots to half the number
|
||
+ * slots that KVM supports, leaving the other half for PCI and other
|
||
+ * devices. However ensure that number of slots doesn't drop below 32.
|
||
+ */
|
||
+ max_memslots = LOONGARCH_MAX_RAM_SLOTS;
|
||
+ if (kvm_enabled()) {
|
||
+ max_memslots = kvm_get_max_memslots() / 2;
|
||
+ }
|
||
+
|
||
+ if (machine->ram_slots == 0)
|
||
+ machine->ram_slots =
|
||
+ lsms->hotplug_memory_size / LOONGARCH_HOTPLUG_MEM_ALIGN;
|
||
+
|
||
+ if (machine->ram_slots > max_memslots) {
|
||
+ error_report("Specified number of memory slots %" PRIu64
|
||
+ " exceeds max supported %d",
|
||
+ machine->ram_slots, max_memslots);
|
||
+ exit(1);
|
||
+ }
|
||
+
|
||
+ lsms->ram_slots = machine->ram_slots;
|
||
+
|
||
+ machine->device_memory->base = get_hotplug_membase(machine->ram_size);
|
||
+ memory_region_init(&machine->device_memory->mr, OBJECT(lsms),
|
||
+ "device-memory", lsms->hotplug_memory_size);
|
||
+ memory_region_add_subregion(get_system_memory(),
|
||
+ machine->device_memory->base,
|
||
+ &machine->device_memory->mr);
|
||
+ }
|
||
+
|
||
+ memory_region_init_alias(isa_io, NULL, "isa-io", get_system_io(), 0,
|
||
+ LS_ISA_IO_SIZE);
|
||
+ memory_region_init(isa_mem, NULL, "isa-mem", PCIE_MEMORY_SIZE);
|
||
+ memory_region_add_subregion(get_system_memory(), lsmc->isa_io_base,
|
||
+ isa_io);
|
||
+ memory_region_add_subregion(get_system_memory(), PCIE_MEMORY_BASE,
|
||
+ isa_mem);
|
||
+
|
||
+ if (!strcmp(lsmc->bridge_name, "ls7a")) {
|
||
+ /*Initialize the 7A IO interrupt subsystem*/
|
||
+ DeviceState *ls7a_dev;
|
||
+ lsms->apic_xrupt_override = kvm_irqchip_in_kernel();
|
||
+ ls7a_apic = ls3a_intctl_init(machine, cpu_states);
|
||
+ if (!ls7a_apic) {
|
||
+ perror("Init 7A APIC failed\n");
|
||
+ exit(1);
|
||
+ }
|
||
+ pci_bus = ls7a_init(machine, ls7a_apic, &ls7a_dev);
|
||
+
|
||
+ object_property_add_link(
|
||
+ OBJECT(machine), LOONGARCH_MACHINE_ACPI_DEVICE_PROP,
|
||
+ TYPE_HOTPLUG_HANDLER, (Object **)&lsms->acpi_dev,
|
||
+ object_property_allow_set_link, OBJ_PROP_LINK_STRONG);
|
||
+ object_property_set_link(OBJECT(machine),
|
||
+ LOONGARCH_MACHINE_ACPI_DEVICE_PROP,
|
||
+ OBJECT(ls7a_dev), &error_abort);
|
||
+
|
||
+ create_platform_bus(lsms, ls7a_apic);
|
||
+
|
||
+#ifdef CONFIG_KVM
|
||
+ if (kvm_enabled()) {
|
||
+ kvm_direct_msi_allowed =
|
||
+ (kvm_check_extension(kvm_state, KVM_CAP_SIGNAL_MSI) > 0);
|
||
+ } else {
|
||
+ kvm_direct_msi_allowed = 0;
|
||
+ }
|
||
+ msi_nonbroken = kvm_direct_msi_allowed;
|
||
+#else
|
||
+ msi_nonbroken = true;
|
||
+#endif
|
||
+ sysbus_create_simple("ls7a_rtc", LS7A_RTC_REG_BASE,
|
||
+ ls7a_apic[LS7A_RTC_IRQ - LOONGARCH_PCH_IRQ_BASE]);
|
||
+ }
|
||
+
|
||
+ /*Initialize the CPU serial device*/
|
||
+
|
||
+ if (serial_hd(0)) {
|
||
+ pirq = qemu_allocate_irqs(
|
||
+ legacy_set_irq,
|
||
+ ls7a_apic + (LS7A_UART_IRQ - LOONGARCH_PCH_IRQ_BASE), 1);
|
||
+ serial_mm_init(address_space_mem, LS7A_UART_BASE, 0, pirq[0], 115200,
|
||
+ serial_hd(0), DEVICE_NATIVE_ENDIAN);
|
||
+ }
|
||
+
|
||
+ /*network card*/
|
||
+ network_init(pci_bus);
|
||
+ /* VGA setup. Don't bother loading the bios. */
|
||
+ pci_vga_init(pci_bus);
|
||
+
|
||
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(qdev_new("iocsr")), &error_fatal);
|
||
+
|
||
+#ifdef CONFIG_TCG
|
||
+ int nb_nodes = (smp_cpus - 1) / 4;
|
||
+ for (i = 0; i <= nb_nodes; i++) {
|
||
+ uint64_t off = (uint64_t)i << 44;
|
||
+ SIMPLE_OPS(((hwaddr)0x1fe00180 | off), 0x8);
|
||
+ SIMPLE_OPS(((hwaddr)0x1fe0019c | off), 0x8);
|
||
+ SIMPLE_OPS(((hwaddr)0x1fe001d0 | off), 0x8);
|
||
+ SIMPLE_OPS(((hwaddr)FEATURE_REG | off), 0x8);
|
||
+ SIMPLE_OPS(((hwaddr)VENDOR_REG | off), 0x8);
|
||
+ SIMPLE_OPS(((hwaddr)CPUNAME_REG | off), 0x8);
|
||
+ SIMPLE_OPS(((hwaddr)OTHER_FUNC_REG | off), 0x8);
|
||
+ }
|
||
+
|
||
+ SIMPLE_OPS(0x1001041c, 0x4);
|
||
+ SIMPLE_OPS(0x10002000, 0x14);
|
||
+ SIMPLE_OPS(0x10013ffc, 0x4);
|
||
+#endif
|
||
+
|
||
+ fdt_add_pcie_node(lsms);
|
||
+}
|
||
+
|
||
+static const CPUArchIdList *loongarch_possible_cpu_arch_ids(MachineState *ms)
|
||
+{
|
||
+ int i;
|
||
+ int max_cpus = ms->smp.max_cpus;
|
||
+
|
||
+ if (ms->possible_cpus) {
|
||
+ /*
|
||
+ * make sure that max_cpus hasn't changed since the first use, i.e.
|
||
+ * -smp hasn't been parsed after it
|
||
+ */
|
||
+ assert(ms->possible_cpus->len == max_cpus);
|
||
+ return ms->possible_cpus;
|
||
+ }
|
||
+
|
||
+ ms->possible_cpus =
|
||
+ g_malloc0(sizeof(CPUArchIdList) + sizeof(CPUArchId) * max_cpus);
|
||
+ ms->possible_cpus->len = max_cpus;
|
||
+ for (i = 0; i < ms->possible_cpus->len; i++) {
|
||
+ ms->possible_cpus->cpus[i].type = ms->cpu_type;
|
||
+ ms->possible_cpus->cpus[i].vcpus_count = 1;
|
||
+ ms->possible_cpus->cpus[i].props.has_core_id = true;
|
||
+ ms->possible_cpus->cpus[i].props.core_id = i;
|
||
+ ms->possible_cpus->cpus[i].arch_id = i;
|
||
+ }
|
||
+ return ms->possible_cpus;
|
||
+}
|
||
+
|
||
+static PFlashCFI01 *loongarch_pflash_create(LoongarchMachineState *lsms,
|
||
+ const char *name,
|
||
+ const char *alias_prop_name)
|
||
+{
|
||
+ DeviceState *dev = qdev_new(TYPE_PFLASH_CFI01);
|
||
+
|
||
+ qdev_prop_set_uint64(dev, "sector-length", FLASH_SECTOR_SIZE);
|
||
+ qdev_prop_set_uint8(dev, "width", 1);
|
||
+ qdev_prop_set_string(dev, "name", name);
|
||
+ object_property_add_child(OBJECT(lsms), name, OBJECT(dev));
|
||
+ object_property_add_alias(OBJECT(lsms), alias_prop_name, OBJECT(dev),
|
||
+ "drive");
|
||
+ return PFLASH_CFI01(dev);
|
||
+}
|
||
+
|
||
+static void loongarch_system_flash_create(LoongarchMachineState *lsms)
|
||
+{
|
||
+ lsms->flash[0] = loongarch_pflash_create(lsms, "system.flash0", "pflash0");
|
||
+ lsms->flash[1] = loongarch_pflash_create(lsms, "system.flash1", "pflash1");
|
||
+}
|
||
+
|
||
+static void loongarch_machine_initfn(Object *obj)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(obj);
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ lsms->acpi_build_enabled = lsmc->has_acpi_build;
|
||
+ loongarch_system_flash_create(lsms);
|
||
+ lsms->oem_id = g_strndup(EFI_ACPI_OEM_ID, 6);
|
||
+ lsms->oem_table_id = g_strndup(EFI_ACPI_OEM_TABLE_ID, 6);
|
||
+}
|
||
+
|
||
+static void ls3a5k_ls7a_machine_options(MachineClass *m)
|
||
+{
|
||
+ char *cpu_name = get_host_cpu_model_name();
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_CLASS(m);
|
||
+ m->desc = "Loongarch3a5k LS7A1000 machine";
|
||
+ m->max_cpus = LOONGARCH_MAX_VCPUS;
|
||
+ m->alias = "loongson7a";
|
||
+ m->is_default = 1;
|
||
+ lsmc->isa_io_base = LS3A5K_ISA_IO_BASE;
|
||
+ lsmc->pciecfg_base = LS_PCIECFG_BASE;
|
||
+ lsmc->ls7a_ioapic_reg_base = LS3A5K_LS7A_IOAPIC_REG_BASE;
|
||
+ lsmc->node_shift = 44;
|
||
+ strncpy(lsmc->cpu_name, cpu_name, sizeof(lsmc->cpu_name) - 1);
|
||
+ lsmc->cpu_name[sizeof(lsmc->cpu_name) - 1] = 0;
|
||
+ strncpy(lsmc->bridge_name, "ls7a", sizeof(lsmc->bridge_name) - 1);
|
||
+ lsmc->bridge_name[sizeof(lsmc->bridge_name) - 1] = 0;
|
||
+ compat_props_add(m->compat_props, loongarch_compat, loongarch_compat_len);
|
||
+}
|
||
+
|
||
+static void ls3a_board_reset(MachineState *ms)
|
||
+{
|
||
+ qemu_devices_reset();
|
||
+#ifdef CONFIG_KVM
|
||
+ struct loongarch_kvm_irqchip *chip;
|
||
+ int length;
|
||
+
|
||
+ if (!kvm_enabled()) {
|
||
+ return;
|
||
+ }
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct loongarch_gipiState);
|
||
+ chip = g_malloc0(length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS3A_GIPI;
|
||
+ chip->len = length;
|
||
+ kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, chip);
|
||
+
|
||
+ length = sizeof(struct loongarch_kvm_irqchip) +
|
||
+ sizeof(struct ls7a_ioapic_state);
|
||
+ chip = g_realloc(chip, length);
|
||
+ memset(chip, 0, length);
|
||
+ chip->chip_id = KVM_IRQCHIP_LS7A_IOAPIC;
|
||
+ chip->len = length;
|
||
+ kvm_vm_ioctl(kvm_state, KVM_SET_IRQCHIP, chip);
|
||
+
|
||
+ g_free(chip);
|
||
+#endif
|
||
+}
|
||
+
|
||
+static CpuInstanceProperties ls3a_cpu_index_to_props(MachineState *ms,
|
||
+ unsigned cpu_index)
|
||
+{
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(ms);
|
||
+ const CPUArchIdList *possible_cpus = mc->possible_cpu_arch_ids(ms);
|
||
+
|
||
+ assert(cpu_index < possible_cpus->len);
|
||
+ return possible_cpus->cpus[cpu_index].props;
|
||
+}
|
||
+
|
||
+static int64_t ls3a_get_default_cpu_node_id(const MachineState *ms, int idx)
|
||
+{
|
||
+ int nb_numa_nodes = ms->numa_state->num_nodes;
|
||
+ int smp_cores = ms->smp.cores;
|
||
+ return idx / smp_cores % nb_numa_nodes;
|
||
+}
|
||
+
|
||
+static void loongarch_class_init(ObjectClass *oc, void *data)
|
||
+{
|
||
+ MachineClass *mc = MACHINE_CLASS(oc);
|
||
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(oc);
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_CLASS(oc);
|
||
+
|
||
+ lsmc->get_hotplug_handler = mc->get_hotplug_handler;
|
||
+ lsmc->has_acpi_build = true;
|
||
+ mc->get_hotplug_handler = loongarch_get_hotpug_handler;
|
||
+ mc->has_hotpluggable_cpus = true;
|
||
+ mc->cpu_index_to_instance_props = ls3a_cpu_index_to_props;
|
||
+ mc->possible_cpu_arch_ids = loongarch_possible_cpu_arch_ids;
|
||
+ mc->get_default_cpu_node_id = ls3a_get_default_cpu_node_id;
|
||
+ mc->default_ram_size = 1 * GiB;
|
||
+ mc->default_cpu_type = LOONGARCH_CPU_TYPE_NAME("Loongson-3A5000");
|
||
+ mc->default_ram_id = "loongarch_ls3a.ram";
|
||
+
|
||
+#ifdef CONFIG_TPM
|
||
+ machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);
|
||
+#endif
|
||
+
|
||
+ mc->reset = ls3a_board_reset;
|
||
+ mc->max_cpus = LOONGARCH_MAX_VCPUS;
|
||
+ hc->pre_plug = loongarch_machine_device_pre_plug;
|
||
+ hc->plug = loongarch_machine_device_plug;
|
||
+ hc->unplug = longson_machine_device_unplug;
|
||
+ hc->unplug_request = loongarch_machine_device_unplug_request;
|
||
+
|
||
+ object_class_property_add(oc, "acpi", "OnOffAuto", loongarch_get_acpi,
|
||
+ loongarch_set_acpi, NULL, NULL);
|
||
+ object_class_property_set_description(oc, "acpi", "Enable ACPI");
|
||
+}
|
||
+
|
||
+static const TypeInfo loongarch_info = {
|
||
+ .name = TYPE_LOONGARCH_MACHINE,
|
||
+ .parent = TYPE_MACHINE,
|
||
+ .abstract = true,
|
||
+ .instance_size = sizeof(LoongarchMachineState),
|
||
+ .instance_init = loongarch_machine_initfn,
|
||
+ .class_size = sizeof(LoongarchMachineClass),
|
||
+ .class_init = loongarch_class_init,
|
||
+ .interfaces = (InterfaceInfo[]){ { TYPE_HOTPLUG_HANDLER }, {} },
|
||
+};
|
||
+
|
||
+static void loongarch_machine_register_types(void)
|
||
+{
|
||
+ type_register_static(&loongarch_info);
|
||
+}
|
||
+
|
||
+type_init(loongarch_machine_register_types)
|
||
+
|
||
+ DEFINE_LS3A5K_MACHINE(loongson7a_v1_0, "loongson7a_v1.0",
|
||
+ ls3a5k_ls7a_machine_options);
|
||
diff --git a/hw/loongarch/larch_hotplug.c b/hw/loongarch/larch_hotplug.c
|
||
new file mode 100644
|
||
index 0000000000..52f13af7b3
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/larch_hotplug.c
|
||
@@ -0,0 +1,377 @@
|
||
+/*
|
||
+ * Hotplug emulation on Loongarch system.
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This library is free software; you can redistribute it and/or
|
||
+ * modify it under the terms of the GNU Lesser General Public
|
||
+ * License as published by the Free Software Foundation; either
|
||
+ * version 2 of the License, or (at your option) any later version.
|
||
+ *
|
||
+ * This library is distributed in the hope that it will be useful,
|
||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
+ * Lesser General Public License for more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU Lesser General Public
|
||
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qapi/error.h"
|
||
+#include "qemu-common.h"
|
||
+#include "qemu/queue.h"
|
||
+#include "qemu/units.h"
|
||
+#include "qemu/cutils.h"
|
||
+#include "qemu/bcd.h"
|
||
+#include "hw/hotplug.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "hw/mem/memory-device.h"
|
||
+#include "sysemu/numa.h"
|
||
+#include "sysemu/cpus.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+#include "hw/cpu/core.h"
|
||
+#include "hw/nvram/fw_cfg.h"
|
||
+#include "hw/platform-bus.h"
|
||
+
|
||
+/* find cpu slot in machine->possible_cpus by core_id */
|
||
+static CPUArchId *loongarch_find_cpu_slot(MachineState *ms, uint32_t id,
|
||
+ int *idx)
|
||
+{
|
||
+ int index = id;
|
||
+
|
||
+ if (index >= ms->possible_cpus->len) {
|
||
+ return NULL;
|
||
+ }
|
||
+ if (idx) {
|
||
+ *idx = index;
|
||
+ }
|
||
+ return &ms->possible_cpus->cpus[index];
|
||
+}
|
||
+
|
||
+static void loongarch_memory_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ Error *local_err = NULL;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(hotplug_dev);
|
||
+ HotplugHandlerClass *hhc;
|
||
+ uint64_t size;
|
||
+
|
||
+ size = memory_device_get_region_size(MEMORY_DEVICE(dev), &error_abort);
|
||
+ if (size % LOONGARCH_HOTPLUG_MEM_ALIGN) {
|
||
+ error_setg(&local_err,
|
||
+ "Hotplugged memory size must be a multiple of "
|
||
+ "%lld MB",
|
||
+ LOONGARCH_HOTPLUG_MEM_ALIGN / MiB);
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ pc_dimm_plug(PC_DIMM(dev), MACHINE(lsms));
|
||
+
|
||
+ hhc = HOTPLUG_HANDLER_GET_CLASS(lsms->acpi_dev);
|
||
+ hhc->plug(HOTPLUG_HANDLER(lsms->acpi_dev), dev, &error_abort);
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+static void loongarch_memory_unplug_request(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ Error *local_err = NULL;
|
||
+ HotplugHandlerClass *hhc;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(hotplug_dev);
|
||
+
|
||
+ if (!lsms->acpi_dev || !loongarch_is_acpi_enabled(lsms)) {
|
||
+ error_setg(
|
||
+ &local_err,
|
||
+ "memory hotplug is not enabled: missing acpi device or acpi disabled");
|
||
+ goto out;
|
||
+ }
|
||
+ hhc = HOTPLUG_HANDLER_GET_CLASS(lsms->acpi_dev);
|
||
+ hhc->unplug_request(HOTPLUG_HANDLER(lsms->acpi_dev), dev, &local_err);
|
||
+
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+static void loongarch_cpu_unplug(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||
+ Error **errp)
|
||
+{
|
||
+ CPUArchId *found_cpu;
|
||
+ HotplugHandlerClass *hhc;
|
||
+ Error *local_err = NULL;
|
||
+ LOONGARCHCPU *cpu = LOONGARCH_CPU(dev);
|
||
+ MachineState *machine = MACHINE(OBJECT(hotplug_dev));
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+
|
||
+ hhc = HOTPLUG_HANDLER_GET_CLASS(lsms->acpi_dev);
|
||
+ hhc->unplug(HOTPLUG_HANDLER(lsms->acpi_dev), dev, &local_err);
|
||
+
|
||
+ if (local_err) {
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ loongarch_cpu_destroy(machine, cpu);
|
||
+
|
||
+ found_cpu = loongarch_find_cpu_slot(MACHINE(lsms), cpu->id, NULL);
|
||
+ found_cpu->cpu = NULL;
|
||
+ object_unparent(OBJECT(dev));
|
||
+ lsms->hotpluged_cpu_num -= 1;
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+static void loongarch_memory_unplug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ Error *local_err = NULL;
|
||
+ HotplugHandlerClass *hhc;
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(hotplug_dev);
|
||
+
|
||
+ hhc = HOTPLUG_HANDLER_GET_CLASS(lsms->acpi_dev);
|
||
+ hhc->unplug(HOTPLUG_HANDLER(lsms->acpi_dev), dev, &local_err);
|
||
+
|
||
+ if (local_err) {
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ pc_dimm_unplug(PC_DIMM(dev), MACHINE(hotplug_dev));
|
||
+ object_unparent(OBJECT(dev));
|
||
+
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+static void loongarch_cpu_pre_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ MachineState *ms = MACHINE(OBJECT(hotplug_dev));
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(hotplug_dev);
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(ms);
|
||
+ LOONGARCHCPU *cpu = LOONGARCH_CPU(dev);
|
||
+ CPUArchId *cpu_slot;
|
||
+ Error *local_err = NULL;
|
||
+ int index;
|
||
+ int free_index = lsms->hotpluged_cpu_num + ms->smp.cpus;
|
||
+ int max_cpus = ms->smp.max_cpus;
|
||
+
|
||
+ if (dev->hotplugged && !mc->has_hotpluggable_cpus) {
|
||
+ error_setg(&local_err, "CPU hotplug not supported for this machine");
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ if (!object_dynamic_cast(OBJECT(cpu), ms->cpu_type)) {
|
||
+ error_setg(errp, "Invalid CPU type, expected cpu type: '%s'",
|
||
+ ms->cpu_type);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ /* if ID is not set, set it based on core properties */
|
||
+ if (cpu->id == UNASSIGNED_CPU_ID) {
|
||
+ if ((cpu->core_id) > (max_cpus - 1)) {
|
||
+ error_setg(errp, "Invalid CPU core-id: %u must be in range 0:%u",
|
||
+ cpu->core_id, max_cpus - 1);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (free_index > (max_cpus - 1)) {
|
||
+ error_setg(errp, "The maximum number of CPUs cannot exceed %u.",
|
||
+ max_cpus);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (cpu->core_id != free_index) {
|
||
+ error_setg(errp, "Invalid CPU core-id: %u must be :%u",
|
||
+ cpu->core_id, free_index);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ cpu->id = cpu->core_id;
|
||
+ }
|
||
+
|
||
+ cpu_slot = loongarch_find_cpu_slot(MACHINE(hotplug_dev), cpu->id, &index);
|
||
+ if (!cpu_slot) {
|
||
+ error_setg(&local_err, "core id %d out of range", cpu->id);
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ if (cpu_slot->cpu) {
|
||
+ error_setg(&local_err, "core %d already populated", cpu->id);
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ numa_cpu_pre_plug(cpu_slot, dev, &local_err);
|
||
+
|
||
+ return;
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+static void loongarch_memory_pre_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ MachineState *machine = MACHINE(OBJECT(hotplug_dev));
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ PCDIMMDevice *dimm = PC_DIMM(dev);
|
||
+ Error *local_err = NULL;
|
||
+ uint64_t size;
|
||
+
|
||
+ if (!lsms->acpi_dev || !loongarch_is_acpi_enabled(lsms)) {
|
||
+ error_setg(
|
||
+ errp,
|
||
+ "memory hotplug is not enabled: missing acpi device or acpi disabled");
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ size = memory_device_get_region_size(MEMORY_DEVICE(dimm), &local_err);
|
||
+ if (local_err) {
|
||
+ error_propagate(errp, local_err);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (size % LOONGARCH_HOTPLUG_MEM_ALIGN) {
|
||
+ error_setg(errp,
|
||
+ "Hotplugged memory size must be a multiple of "
|
||
+ "%lld MB",
|
||
+ LOONGARCH_HOTPLUG_MEM_ALIGN / MiB);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ pc_dimm_pre_plug(dimm, MACHINE(hotplug_dev), NULL, errp);
|
||
+}
|
||
+
|
||
+static void loongarch_cpu_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
|
||
+ Error **errp)
|
||
+{
|
||
+ CPUArchId *found_cpu;
|
||
+ HotplugHandlerClass *hhc;
|
||
+ Error *local_err = NULL;
|
||
+ MachineState *machine = MACHINE(OBJECT(hotplug_dev));
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ LOONGARCHCPU *cpu = LOONGARCH_CPU(dev);
|
||
+
|
||
+ if (lsms->acpi_dev) {
|
||
+ loongarch_cpu_create(machine, cpu, errp);
|
||
+ hhc = HOTPLUG_HANDLER_GET_CLASS(lsms->acpi_dev);
|
||
+ hhc->plug(HOTPLUG_HANDLER(lsms->acpi_dev), dev, &local_err);
|
||
+ if (local_err) {
|
||
+ goto out;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ found_cpu = loongarch_find_cpu_slot(MACHINE(lsms), cpu->id, NULL);
|
||
+ found_cpu->cpu = OBJECT(dev);
|
||
+ lsms->hotpluged_cpu_num += 1;
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+static void loongarch_cpu_unplug_request(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ MachineState *machine = MACHINE(OBJECT(hotplug_dev));
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ LOONGARCHCPU *cpu = LOONGARCH_CPU(dev);
|
||
+ Error *local_err = NULL;
|
||
+ HotplugHandlerClass *hhc;
|
||
+ int idx = -1;
|
||
+
|
||
+ if (!lsms->acpi_dev) {
|
||
+ error_setg(&local_err, "CPU hot unplug not supported without ACPI");
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ loongarch_find_cpu_slot(MACHINE(lsms), cpu->id, &idx);
|
||
+ assert(idx != -1);
|
||
+ if (idx == 0) {
|
||
+ error_setg(&local_err, "Boot CPU is unpluggable");
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+ hhc = HOTPLUG_HANDLER_GET_CLASS(lsms->acpi_dev);
|
||
+ hhc->unplug_request(HOTPLUG_HANDLER(lsms->acpi_dev), dev, &local_err);
|
||
+
|
||
+ if (local_err) {
|
||
+ goto out;
|
||
+ }
|
||
+
|
||
+out:
|
||
+ error_propagate(errp, local_err);
|
||
+}
|
||
+
|
||
+void longson_machine_device_unplug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine());
|
||
+
|
||
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
+ loongarch_memory_unplug(hotplug_dev, dev, errp);
|
||
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) {
|
||
+ if (!mc->has_hotpluggable_cpus) {
|
||
+ error_setg(errp, "CPU hot unplug not supported on this machine");
|
||
+ return;
|
||
+ }
|
||
+ loongarch_cpu_unplug(hotplug_dev, dev, errp);
|
||
+ } else {
|
||
+ error_setg(errp,
|
||
+ "acpi: device unplug for not supported device"
|
||
+ " type: %s",
|
||
+ object_get_typename(OBJECT(dev)));
|
||
+ }
|
||
+
|
||
+ return;
|
||
+}
|
||
+
|
||
+void loongarch_machine_device_unplug_request(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
+ loongarch_memory_unplug_request(hotplug_dev, dev, errp);
|
||
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) {
|
||
+ loongarch_cpu_unplug_request(hotplug_dev, dev, errp);
|
||
+ }
|
||
+}
|
||
+
|
||
+HotplugHandler *loongarch_get_hotpug_handler(MachineState *machine,
|
||
+ DeviceState *dev)
|
||
+{
|
||
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM) ||
|
||
+ object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU) ||
|
||
+ object_dynamic_cast(OBJECT(dev), TYPE_SYS_BUS_DEVICE)) {
|
||
+ return HOTPLUG_HANDLER(machine);
|
||
+ }
|
||
+ return NULL;
|
||
+}
|
||
+
|
||
+void loongarch_machine_device_pre_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
+ loongarch_memory_pre_plug(hotplug_dev, dev, errp);
|
||
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) {
|
||
+ loongarch_cpu_pre_plug(hotplug_dev, dev, errp);
|
||
+ }
|
||
+}
|
||
+
|
||
+void loongarch_machine_device_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(hotplug_dev);
|
||
+
|
||
+ if (lsms->platform_bus_dev) {
|
||
+ MachineClass *mc = MACHINE_GET_CLASS(lsms);
|
||
+
|
||
+ if (device_is_dynamic_sysbus(mc, dev)) {
|
||
+ platform_bus_link_device(
|
||
+ PLATFORM_BUS_DEVICE(lsms->platform_bus_dev),
|
||
+ SYS_BUS_DEVICE(dev));
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
|
||
+ loongarch_memory_plug(hotplug_dev, dev, errp);
|
||
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_LOONGARCH_CPU)) {
|
||
+ loongarch_cpu_plug(hotplug_dev, dev, errp);
|
||
+ }
|
||
+}
|
||
diff --git a/hw/loongarch/larch_int.c b/hw/loongarch/larch_int.c
|
||
new file mode 100644
|
||
index 0000000000..ff3750e982
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/larch_int.c
|
||
@@ -0,0 +1,87 @@
|
||
+/*
|
||
+ * QEMU LOONGARCH interrupt support
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qemu/main-loop.h"
|
||
+#include "hw/hw.h"
|
||
+#include "hw/irq.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "cpu.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "kvm_larch.h"
|
||
+#ifdef CONFIG_KVM
|
||
+#include <linux/kvm.h>
|
||
+#endif
|
||
+
|
||
+static void cpu_irq_request(void *opaque, int irq, int level)
|
||
+{
|
||
+ LOONGARCHCPU *cpu = opaque;
|
||
+ CPULOONGARCHState *env = &cpu->env;
|
||
+ CPUState *cs = CPU(cpu);
|
||
+ bool locked = false;
|
||
+
|
||
+ if (irq < 0 || irq > 13) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ /* Make sure locking works even if BQL is already held by the caller */
|
||
+ if (!qemu_mutex_iothread_locked()) {
|
||
+ locked = true;
|
||
+ qemu_mutex_lock_iothread();
|
||
+ }
|
||
+
|
||
+ if (level) {
|
||
+ env->CSR_ESTAT |= 1 << irq;
|
||
+ } else {
|
||
+ env->CSR_ESTAT &= ~(1 << irq);
|
||
+ }
|
||
+
|
||
+ if (kvm_enabled()) {
|
||
+ if (irq == 2) {
|
||
+ kvm_loongarch_set_interrupt(cpu, irq, level);
|
||
+ } else if (irq == 3) {
|
||
+ kvm_loongarch_set_interrupt(cpu, irq, level);
|
||
+ } else if (irq == 12) {
|
||
+ kvm_loongarch_set_ipi_interrupt(cpu, irq, level);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (env->CSR_ESTAT & CSR_ESTAT_IPMASK) {
|
||
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
|
||
+ } else {
|
||
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
|
||
+ }
|
||
+
|
||
+ if (locked) {
|
||
+ qemu_mutex_unlock_iothread();
|
||
+ }
|
||
+}
|
||
+
|
||
+void cpu_init_irq(LOONGARCHCPU *cpu)
|
||
+{
|
||
+ CPULOONGARCHState *env = &cpu->env;
|
||
+ qemu_irq *qi;
|
||
+ int i;
|
||
+
|
||
+ qi = qemu_allocate_irqs(cpu_irq_request, loongarch_env_get_cpu(env),
|
||
+ N_IRQS);
|
||
+ for (i = 0; i < N_IRQS; i++) {
|
||
+ env->irq[i] = qi[i];
|
||
+ }
|
||
+}
|
||
diff --git a/hw/loongarch/ls7a_nb.c b/hw/loongarch/ls7a_nb.c
|
||
new file mode 100644
|
||
index 0000000000..933b3f2869
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/ls7a_nb.c
|
||
@@ -0,0 +1,289 @@
|
||
+/*
|
||
+ * Loongarch 7A1000 north bridge support
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+
|
||
+#include "hw/hw.h"
|
||
+#include "hw/irq.h"
|
||
+#include "hw/sysbus.h"
|
||
+#include "hw/pci/pci.h"
|
||
+#include "hw/i386/pc.h"
|
||
+#include "hw/pci/pci_host.h"
|
||
+#include "hw/pci/pcie_host.h"
|
||
+#include "sysemu/sysemu.h"
|
||
+#include "exec/address-spaces.h"
|
||
+#include "qapi/error.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "hw/acpi/ls7a.h"
|
||
+#include "hw/i386/pc.h"
|
||
+#include "hw/isa/isa.h"
|
||
+#include "hw/boards.h"
|
||
+#include "qemu/log.h"
|
||
+#include "hw/loongarch/bios.h"
|
||
+#include "hw/loader.h"
|
||
+#include "elf.h"
|
||
+#include "exec/address-spaces.h"
|
||
+#include "exec/memory.h"
|
||
+#include "hw/pci/pci_bridge.h"
|
||
+#include "hw/pci/pci_bus.h"
|
||
+#include "linux/kvm.h"
|
||
+#include "sysemu/kvm.h"
|
||
+#include "sysemu/runstate.h"
|
||
+#include "sysemu/reset.h"
|
||
+#include "migration/vmstate.h"
|
||
+#include "hw/loongarch/larch.h"
|
||
+#include "hw/loongarch/ls7a.h"
|
||
+
|
||
+#undef DEBUG_LS7A
|
||
+
|
||
+#ifdef DEBUG_LS7A
|
||
+#define DPRINTF(fmt, ...) fprintf(stderr, "%s: " fmt, __func__, ##__VA_ARGS__)
|
||
+#else
|
||
+#define DPRINTF(fmt, ...)
|
||
+#endif
|
||
+
|
||
+static void ls7a_reset(void *opaque)
|
||
+{
|
||
+ uint64_t wmask;
|
||
+ wmask = ~(-1);
|
||
+
|
||
+ PCIDevice *dev = opaque;
|
||
+ pci_set_word(dev->config + PCI_VENDOR_ID, 0x0014);
|
||
+ pci_set_word(dev->wmask + PCI_VENDOR_ID, wmask & 0xffff);
|
||
+ pci_set_word(dev->cmask + PCI_VENDOR_ID, 0xffff);
|
||
+ pci_set_word(dev->config + PCI_DEVICE_ID, 0x7a00);
|
||
+ pci_set_word(dev->wmask + PCI_DEVICE_ID, wmask & 0xffff);
|
||
+ pci_set_word(dev->cmask + PCI_DEVICE_ID, 0xffff);
|
||
+ pci_set_word(dev->config + 0x4, 0x0000);
|
||
+ pci_set_word(dev->config + PCI_STATUS, 0x0010);
|
||
+ pci_set_word(dev->wmask + PCI_STATUS, wmask & 0xffff);
|
||
+ pci_set_word(dev->cmask + PCI_STATUS, 0xffff);
|
||
+ pci_set_byte(dev->config + PCI_REVISION_ID, 0x0);
|
||
+ pci_set_byte(dev->wmask + PCI_REVISION_ID, wmask & 0xff);
|
||
+ pci_set_byte(dev->cmask + PCI_REVISION_ID, 0xff);
|
||
+ pci_set_byte(dev->config + 0x9, 0x00);
|
||
+ pci_set_byte(dev->wmask + 0x9, wmask & 0xff);
|
||
+ pci_set_byte(dev->cmask + 0x9, 0xff);
|
||
+ pci_set_byte(dev->config + 0xa, 0x00);
|
||
+ pci_set_byte(dev->wmask + 0xa, wmask & 0xff);
|
||
+ pci_set_byte(dev->cmask + 0xa, 0xff);
|
||
+ pci_set_byte(dev->config + 0xb, 0x06);
|
||
+ pci_set_byte(dev->wmask + 0xb, wmask & 0xff);
|
||
+ pci_set_byte(dev->cmask + 0xb, 0xff);
|
||
+ pci_set_byte(dev->config + 0xc, 0x00);
|
||
+ pci_set_byte(dev->wmask + 0xc, wmask & 0xff);
|
||
+ pci_set_byte(dev->cmask + 0xc, 0xff);
|
||
+ pci_set_byte(dev->config + 0xe, 0x80);
|
||
+ pci_set_byte(dev->wmask + 0xe, wmask & 0xff);
|
||
+ pci_set_byte(dev->cmask + 0xe, 0xff);
|
||
+}
|
||
+
|
||
+static const VMStateDescription vmstate_ls7a_pcie = {
|
||
+ .name = "LS7A_PCIE",
|
||
+ .version_id = 1,
|
||
+ .minimum_version_id = 1,
|
||
+ .fields = (VMStateField[]){ VMSTATE_PCI_DEVICE(dev, LS7APCIState),
|
||
+ VMSTATE_STRUCT(pm, LS7APCIState, 0,
|
||
+ vmstate_ls7a_pm, LS7APCIPMRegs),
|
||
+ VMSTATE_END_OF_LIST() }
|
||
+};
|
||
+
|
||
+static PCIINTxRoute ls7a_route_intx_pin_to_irq(void *opaque, int pin)
|
||
+{
|
||
+ PCIINTxRoute route;
|
||
+
|
||
+ route.irq = pin;
|
||
+ route.mode = PCI_INTX_ENABLED;
|
||
+ return route;
|
||
+}
|
||
+
|
||
+static int pci_ls7a_map_irq(PCIDevice *d, int irq_num)
|
||
+{
|
||
+ int irq;
|
||
+
|
||
+ irq = 16 + ((PCI_SLOT(d->devfn) * 4 + irq_num) & 0xf);
|
||
+ return irq;
|
||
+}
|
||
+
|
||
+static void pci_ls7a_set_irq(void *opaque, int irq_num, int level)
|
||
+{
|
||
+ qemu_irq *pic = opaque;
|
||
+ DPRINTF("------ %s irq %d %d\n", __func__, irq_num, level);
|
||
+ qemu_set_irq(pic[irq_num], level);
|
||
+}
|
||
+
|
||
+static void ls7a_pcie_realize(PCIDevice *dev, Error **errp)
|
||
+{
|
||
+ LS7APCIState *s = PCIE_LS7A(dev);
|
||
+ /* Ls7a North Bridge, built on FPGA, VENDOR_ID/DEVICE_ID are "undefined" */
|
||
+ pci_config_set_prog_interface(dev->config, 0x00);
|
||
+
|
||
+ /* set the default value of north bridge pci config */
|
||
+ qemu_register_reset(ls7a_reset, s);
|
||
+}
|
||
+
|
||
+static AddressSpace *ls7a_pci_dma_iommu(PCIBus *bus, void *opaque, int devfn)
|
||
+{
|
||
+ return &address_space_memory;
|
||
+}
|
||
+
|
||
+static PCIBus *pci_ls7a_init(MachineState *machine, DeviceState *dev,
|
||
+ qemu_irq *pic)
|
||
+{
|
||
+ LoongarchMachineState *lsms = LoongarchMACHINE(machine);
|
||
+ LoongarchMachineClass *lsmc = LoongarchMACHINE_GET_CLASS(lsms);
|
||
+ PCIExpressHost *e;
|
||
+ PCIHostState *phb;
|
||
+
|
||
+ e = PCIE_HOST_BRIDGE(dev);
|
||
+ phb = PCI_HOST_BRIDGE(e);
|
||
+ phb->bus = pci_register_root_bus(
|
||
+ dev, "pcie.0", pci_ls7a_set_irq, pci_ls7a_map_irq, pic,
|
||
+ get_system_memory(), get_system_io(), (1 << 3), 128, TYPE_PCIE_BUS);
|
||
+ pcie_host_mmcfg_update(e, true, lsmc->pciecfg_base, LS_PCIECFG_SIZE);
|
||
+ DPRINTF("------ %d\n", __LINE__);
|
||
+
|
||
+ pci_bus_set_route_irq_fn(phb->bus, ls7a_route_intx_pin_to_irq);
|
||
+
|
||
+ return phb->bus;
|
||
+}
|
||
+
|
||
+PCIBus *ls7a_init(MachineState *machine, qemu_irq *pic, DeviceState **ls7a_dev)
|
||
+{
|
||
+ DeviceState *dev;
|
||
+ PCIHostState *phb;
|
||
+ LS7APCIState *pbs;
|
||
+ PCIDevice *pcid;
|
||
+ PCIBus *pci_bus;
|
||
+ PCIExpressHost *e;
|
||
+
|
||
+ /*1. init the HT PCI CFG*/
|
||
+ DPRINTF("------ %d\n", __LINE__);
|
||
+ dev = qdev_new(TYPE_LS7A_PCIE_HOST_BRIDGE);
|
||
+ e = PCIE_HOST_BRIDGE(dev);
|
||
+ phb = PCI_HOST_BRIDGE(e);
|
||
+
|
||
+ DPRINTF("------ %d\n", __LINE__);
|
||
+ pci_bus = pci_ls7a_init(machine, dev, pic);
|
||
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
||
+ phb->bus = pci_bus;
|
||
+ /* set the pcihost pointer after rs780_pcihost_initfn is called */
|
||
+ DPRINTF("------ %d\n", __LINE__);
|
||
+ pcid = pci_new(PCI_DEVFN(0, 0), TYPE_PCIE_LS7A);
|
||
+ pbs = PCIE_LS7A(pcid);
|
||
+ pbs->pciehost = LS7A_PCIE_HOST_BRIDGE(dev);
|
||
+ pbs->pciehost->pci_dev = pbs;
|
||
+
|
||
+ if (ls7a_dev) {
|
||
+ *ls7a_dev = DEVICE(pcid);
|
||
+ }
|
||
+
|
||
+ pci_realize_and_unref(pcid, phb->bus, &error_fatal);
|
||
+
|
||
+ /* IOMMU */
|
||
+ pci_setup_iommu(phb->bus, ls7a_pci_dma_iommu, NULL);
|
||
+
|
||
+ ls7a_pm_init(&pbs->pm, pic);
|
||
+ DPRINTF("------ %d\n", __LINE__);
|
||
+ /*3. init the north bridge VGA,not do now*/
|
||
+ return pci_bus;
|
||
+}
|
||
+
|
||
+LS7APCIState *get_ls7a_type(Object *obj)
|
||
+{
|
||
+ LS7APCIState *pbs;
|
||
+
|
||
+ pbs = PCIE_LS7A(obj);
|
||
+ return pbs;
|
||
+}
|
||
+
|
||
+static void ls7a_pcie_class_init(ObjectClass *klass, void *data)
|
||
+{
|
||
+ DeviceClass *dc = DEVICE_CLASS(klass);
|
||
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
||
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
|
||
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(klass);
|
||
+
|
||
+ k->realize = ls7a_pcie_realize;
|
||
+ k->vendor_id = 0x0014;
|
||
+ k->device_id = 0x7a00;
|
||
+ k->revision = 0x00;
|
||
+ k->class_id = PCI_CLASS_BRIDGE_HOST;
|
||
+ dc->desc = "LS7A1000 PCIE Host bridge";
|
||
+ dc->vmsd = &vmstate_ls7a_pcie;
|
||
+ /*
|
||
+ * PCI-facing part of the host bridge, not usable without the
|
||
+ * host-facing part, which can't be device_add'ed, yet.
|
||
+ */
|
||
+ dc->user_creatable = false;
|
||
+ hc->plug = ls7a_pm_device_plug_cb;
|
||
+ hc->unplug_request = ls7a_pm_device_unplug_request_cb;
|
||
+ hc->unplug = ls7a_pm_device_unplug_cb;
|
||
+ adevc->ospm_status = ls7a_pm_ospm_status;
|
||
+ adevc->send_event = ls7a_send_gpe;
|
||
+ adevc->madt_cpu = ls7a_madt_cpu_entry;
|
||
+}
|
||
+
|
||
+static void ls7a_pci_add_properties(LS7APCIState *ls7a)
|
||
+{
|
||
+ ls7a_pm_add_properties(OBJECT(ls7a), &ls7a->pm, NULL);
|
||
+}
|
||
+
|
||
+static void ls7a_pci_initfn(Object *obj)
|
||
+{
|
||
+ LS7APCIState *ls7a = get_ls7a_type(obj);
|
||
+
|
||
+ ls7a_pci_add_properties(ls7a);
|
||
+}
|
||
+
|
||
+static const TypeInfo ls7a_pcie_device_info = {
|
||
+ .name = TYPE_PCIE_LS7A,
|
||
+ .parent = TYPE_PCI_DEVICE,
|
||
+ .instance_size = sizeof(LS7APCIState),
|
||
+ .class_init = ls7a_pcie_class_init,
|
||
+ .instance_init = ls7a_pci_initfn,
|
||
+ .interfaces =
|
||
+ (InterfaceInfo[]){
|
||
+ { TYPE_HOTPLUG_HANDLER },
|
||
+ { TYPE_ACPI_DEVICE_IF },
|
||
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
||
+ {},
|
||
+ },
|
||
+};
|
||
+
|
||
+static void ls7a_pciehost_class_init(ObjectClass *klass, void *data)
|
||
+{
|
||
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||
+ k->parent_class.fw_name = "pci";
|
||
+}
|
||
+
|
||
+static const TypeInfo ls7a_pciehost_info = {
|
||
+ .name = TYPE_LS7A_PCIE_HOST_BRIDGE,
|
||
+ .parent = TYPE_PCIE_HOST_BRIDGE,
|
||
+ .instance_size = sizeof(LS7APCIEHost),
|
||
+ .class_init = ls7a_pciehost_class_init,
|
||
+};
|
||
+
|
||
+static void ls7a_register_types(void)
|
||
+{
|
||
+ type_register_static(&ls7a_pciehost_info);
|
||
+ type_register_static(&ls7a_pcie_device_info);
|
||
+}
|
||
+
|
||
+type_init(ls7a_register_types)
|
||
diff --git a/hw/loongarch/meson.build b/hw/loongarch/meson.build
|
||
new file mode 100644
|
||
index 0000000000..ca4d5567b5
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/meson.build
|
||
@@ -0,0 +1,15 @@
|
||
+loongarch_ss = ss.source_set()
|
||
+loongarch_ss.add(files('larch_3a.c'), fdt)
|
||
+loongarch_ss.add(files(
|
||
+ 'larch_int.c',
|
||
+ 'larch_hotplug.c',
|
||
+ 'ls7a_nb.c',
|
||
+ 'ioapic.c',
|
||
+ 'acpi-build.c',
|
||
+ 'ipi.c',
|
||
+ 'apic.c',
|
||
+ 'iocsr.c',
|
||
+ 'sysbus-fdt.c',
|
||
+))
|
||
+
|
||
+hw_arch += {'loongarch64': loongarch_ss}
|
||
diff --git a/hw/loongarch/sysbus-fdt.c b/hw/loongarch/sysbus-fdt.c
|
||
new file mode 100644
|
||
index 0000000000..05b4dda33a
|
||
--- /dev/null
|
||
+++ b/hw/loongarch/sysbus-fdt.c
|
||
@@ -0,0 +1,178 @@
|
||
+/*
|
||
+ * Loongarch Platform Bus device tree generation helpers
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/osdep.h"
|
||
+#include "qapi/error.h"
|
||
+#include <libfdt.h>
|
||
+#include "qemu/error-report.h"
|
||
+#include "sysemu/device_tree.h"
|
||
+#include "hw/platform-bus.h"
|
||
+#include "hw/display/ramfb.h"
|
||
+#include "hw/loongarch/sysbus-fdt.h"
|
||
+#include "sysemu/tpm.h"
|
||
+
|
||
+/*
|
||
+ * internal struct that contains the information to create dynamic
|
||
+ * sysbus device node
|
||
+ */
|
||
+typedef struct PlatformBusFDTData {
|
||
+ void *fdt; /* device tree handle */
|
||
+ int irq_start; /* index of the first IRQ usable by platform bus devices */
|
||
+ const char *pbus_node_name; /* name of the platform bus node */
|
||
+ PlatformBusDevice *pbus;
|
||
+} PlatformBusFDTData;
|
||
+
|
||
+/* struct that allows to match a device and create its FDT node */
|
||
+typedef struct BindingEntry {
|
||
+ const char *typename;
|
||
+ const char *compat;
|
||
+ int (*add_fn)(SysBusDevice *sbdev, void *opaque);
|
||
+ bool (*match_fn)(SysBusDevice *sbdev, const struct BindingEntry *combo);
|
||
+} BindingEntry;
|
||
+
|
||
+static int no_fdt_node(SysBusDevice *sbdev, void *opaque)
|
||
+{
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+/* Device type based matching */
|
||
+static bool type_match(SysBusDevice *sbdev, const BindingEntry *entry)
|
||
+{
|
||
+ return !strcmp(object_get_typename(OBJECT(sbdev)), entry->typename);
|
||
+}
|
||
+
|
||
+#define TYPE_BINDING(type, add_fn) \
|
||
+ { \
|
||
+ (type), NULL, (add_fn), NULL \
|
||
+ }
|
||
+
|
||
+#ifdef CONFIG_TPM
|
||
+/*
|
||
+ * add_tpm_tis_fdt_node: Create a DT node for TPM TIS
|
||
+ *
|
||
+ * See kernel documentation:
|
||
+ * Documentation/devicetree/bindings/security/tpm/tpm_tis_mmio.txt
|
||
+ * Optional interrupt for command completion is not exposed
|
||
+ */
|
||
+static int add_tpm_tis_fdt_node(SysBusDevice *sbdev, void *opaque)
|
||
+{
|
||
+ PlatformBusFDTData *data = opaque;
|
||
+ PlatformBusDevice *pbus = data->pbus;
|
||
+ void *fdt = data->fdt;
|
||
+ const char *parent_node = data->pbus_node_name;
|
||
+ char *nodename;
|
||
+ uint32_t reg_attr[2];
|
||
+ uint64_t mmio_base;
|
||
+
|
||
+ mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
|
||
+ nodename = g_strdup_printf("%s/tpm_tis@%" PRIx64, parent_node, mmio_base);
|
||
+ qemu_fdt_add_subnode(fdt, nodename);
|
||
+
|
||
+ qemu_fdt_setprop_string(fdt, nodename, "compatible", "tcg,tpm-tis-mmio");
|
||
+
|
||
+ reg_attr[0] = cpu_to_be32(mmio_base);
|
||
+ reg_attr[1] = cpu_to_be32(0x5000);
|
||
+ qemu_fdt_setprop(fdt, nodename, "reg", reg_attr, 2 * sizeof(uint32_t));
|
||
+
|
||
+ g_free(nodename);
|
||
+
|
||
+ return 0;
|
||
+}
|
||
+#endif
|
||
+
|
||
+/* list of supported dynamic sysbus bindings */
|
||
+static const BindingEntry bindings[] = {
|
||
+#ifdef CONFIG_TPM
|
||
+ TYPE_BINDING(TYPE_TPM_TIS_SYSBUS, add_tpm_tis_fdt_node),
|
||
+#endif
|
||
+ TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node),
|
||
+ TYPE_BINDING("", NULL), /* last element */
|
||
+};
|
||
+
|
||
+/**
|
||
+ * add_fdt_node - add the device tree node of a dynamic sysbus device
|
||
+ *
|
||
+ * @sbdev: handle to the sysbus device
|
||
+ * @opaque: handle to the PlatformBusFDTData
|
||
+ *
|
||
+ * Checks the sysbus type belongs to the list of device types that
|
||
+ * are dynamically instantiable and if so call the node creation
|
||
+ * function.
|
||
+ */
|
||
+static void add_fdt_node(SysBusDevice *sbdev, void *opaque)
|
||
+{
|
||
+ int i, ret;
|
||
+
|
||
+ for (i = 0; i < ARRAY_SIZE(bindings); i++) {
|
||
+ const BindingEntry *iter = &bindings[i];
|
||
+
|
||
+ if (type_match(sbdev, iter)) {
|
||
+ if (!iter->match_fn || iter->match_fn(sbdev, iter)) {
|
||
+ ret = iter->add_fn(sbdev, opaque);
|
||
+ assert(!ret);
|
||
+ return;
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ error_report("Device %s can not be dynamically instantiated",
|
||
+ qdev_fw_name(DEVICE(sbdev)));
|
||
+ exit(1);
|
||
+}
|
||
+
|
||
+void platform_bus_add_all_fdt_nodes(void *fdt, const char *intc, hwaddr addr,
|
||
+ hwaddr bus_size, int irq_start)
|
||
+{
|
||
+ const char platcomp[] = "qemu,platform\0simple-bus";
|
||
+ PlatformBusDevice *pbus;
|
||
+ DeviceState *dev;
|
||
+ gchar *node;
|
||
+
|
||
+ assert(fdt);
|
||
+
|
||
+ node = g_strdup_printf("/platform@%" PRIx64, addr);
|
||
+
|
||
+ /* Create a /platform node that we can put all devices into */
|
||
+ qemu_fdt_add_subnode(fdt, node);
|
||
+ qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp));
|
||
+
|
||
+ /*
|
||
+ * Our platform bus region is less than 32bits, so 1 cell is enough for
|
||
+ * address and size
|
||
+ */
|
||
+ qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1);
|
||
+ qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1);
|
||
+ qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, bus_size);
|
||
+ if (intc != NULL) {
|
||
+ qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", intc);
|
||
+ }
|
||
+ dev = qdev_find_recursive(sysbus_get_default(), TYPE_PLATFORM_BUS_DEVICE);
|
||
+ pbus = PLATFORM_BUS_DEVICE(dev);
|
||
+
|
||
+ PlatformBusFDTData data = {
|
||
+ .fdt = fdt,
|
||
+ .irq_start = irq_start,
|
||
+ .pbus_node_name = node,
|
||
+ .pbus = pbus,
|
||
+ };
|
||
+
|
||
+ /* Loop through all dynamic sysbus devices and create their node */
|
||
+ foreach_dynamic_sysbus_device(add_fdt_node, &data);
|
||
+
|
||
+ g_free(node);
|
||
+}
|
||
diff --git a/include/disas/dis-asm.h b/include/disas/dis-asm.h
|
||
index 4590bcc968..b165453fa1 100644
|
||
--- a/include/disas/dis-asm.h
|
||
+++ b/include/disas/dis-asm.h
|
||
@@ -336,7 +336,7 @@ typedef struct disassemble_info {
|
||
Returns an errno value or 0 for success. */
|
||
int (*read_memory_func)
|
||
(bfd_vma memaddr, bfd_byte *myaddr, int length,
|
||
- struct disassemble_info *info);
|
||
+ struct disassemble_info *info);
|
||
|
||
/* Function which should be called if we get an error that we can't
|
||
recover from. STATUS is the errno value from read_memory_func and
|
||
@@ -465,6 +465,7 @@ int print_insn_riscv32 (bfd_vma, disassemble_info*);
|
||
int print_insn_riscv64 (bfd_vma, disassemble_info*);
|
||
int print_insn_rx(bfd_vma, disassemble_info *);
|
||
int print_insn_hexagon(bfd_vma, disassemble_info *);
|
||
+int print_insn_loongarch(bfd_vma, disassemble_info*);
|
||
|
||
#ifdef CONFIG_CAPSTONE
|
||
bool cap_disas_target(disassemble_info *info, uint64_t pc, size_t size);
|
||
diff --git a/include/elf.h b/include/elf.h
|
||
index 79c188b62f..cd7808f37a 100644
|
||
--- a/include/elf.h
|
||
+++ b/include/elf.h
|
||
@@ -182,6 +182,8 @@ typedef struct mips_elf_abiflags_v0 {
|
||
|
||
#define EM_NANOMIPS 249 /* Wave Computing nanoMIPS */
|
||
|
||
+#define EM_LOONGARCH 258 /* Loongarch */
|
||
+
|
||
/*
|
||
* This is an interim value that we will use until the committee comes
|
||
* up with a final number.
|
||
diff --git a/include/hw/loongarch/bios.h b/include/hw/loongarch/bios.h
|
||
new file mode 100644
|
||
index 0000000000..8e0f6c7d64
|
||
--- /dev/null
|
||
+++ b/include/hw/loongarch/bios.h
|
||
@@ -0,0 +1,24 @@
|
||
+/*
|
||
+ * bios on Loongarch system.
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#include "qemu/units.h"
|
||
+#include "cpu.h"
|
||
+
|
||
+#define BIOS_SIZE (4 * MiB)
|
||
+#define BIOS_FILENAME "loongarch_bios.bin"
|
||
diff --git a/include/hw/loongarch/cpudevs.h b/include/hw/loongarch/cpudevs.h
|
||
new file mode 100644
|
||
index 0000000000..ea4007f8fa
|
||
--- /dev/null
|
||
+++ b/include/hw/loongarch/cpudevs.h
|
||
@@ -0,0 +1,71 @@
|
||
+/*
|
||
+ * cpu device emulation on Loongarch system.
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#ifndef HW_LOONGARCH_CPUDEVS_H
|
||
+#define HW_LOONGARCH_CPUDEVS_H
|
||
+
|
||
+#include "target/loongarch64/cpu-qom.h"
|
||
+
|
||
+/* Definitions for LOONGARCH CPU internal devices. */
|
||
+#define MAX_GIPI_CORE_NUM 256
|
||
+#define MAX_GIPI_MBX_NUM 4
|
||
+
|
||
+#define LS3A_INTC_IP 8
|
||
+#define MAX_CORES 256
|
||
+#define EXTIOI_IRQS (256)
|
||
+#define EXTIOI_IRQS_BITMAP_SIZE (256 / 8)
|
||
+/* map to ipnum per 32 irqs */
|
||
+#define EXTIOI_IRQS_IPMAP_SIZE (256 / 32)
|
||
+
|
||
+typedef struct gipi_core {
|
||
+ uint32_t status;
|
||
+ uint32_t en;
|
||
+ uint32_t set;
|
||
+ uint32_t clear;
|
||
+ uint64_t buf[MAX_GIPI_MBX_NUM];
|
||
+ qemu_irq irq;
|
||
+} gipi_core;
|
||
+
|
||
+typedef struct gipiState {
|
||
+ gipi_core core[MAX_GIPI_CORE_NUM];
|
||
+} gipiState;
|
||
+
|
||
+typedef struct apicState {
|
||
+ /* hardware state */
|
||
+ uint8_t ext_en[EXTIOI_IRQS_BITMAP_SIZE];
|
||
+ uint8_t ext_bounce[EXTIOI_IRQS_BITMAP_SIZE];
|
||
+ uint8_t ext_isr[EXTIOI_IRQS_BITMAP_SIZE];
|
||
+ uint8_t ext_coreisr[MAX_CORES][EXTIOI_IRQS_BITMAP_SIZE];
|
||
+ uint8_t ext_ipmap[EXTIOI_IRQS_IPMAP_SIZE];
|
||
+ uint8_t ext_coremap[EXTIOI_IRQS];
|
||
+ uint16_t ext_nodetype[16];
|
||
+ uint64_t ext_control;
|
||
+
|
||
+ /* software state */
|
||
+ uint8_t ext_sw_ipmap[EXTIOI_IRQS];
|
||
+ uint8_t ext_sw_coremap[EXTIOI_IRQS];
|
||
+ uint8_t ext_ipisr[MAX_CORES * LS3A_INTC_IP][EXTIOI_IRQS_BITMAP_SIZE];
|
||
+
|
||
+ qemu_irq parent_irq[MAX_CORES][LS3A_INTC_IP];
|
||
+ qemu_irq *irq;
|
||
+} apicState;
|
||
+
|
||
+void cpu_init_irq(LOONGARCHCPU *cpu);
|
||
+void cpu_loongarch_clock_init(LOONGARCHCPU *cpu);
|
||
+#endif
|
||
diff --git a/include/hw/loongarch/larch.h b/include/hw/loongarch/larch.h
|
||
new file mode 100644
|
||
index 0000000000..a9f8dea9f5
|
||
--- /dev/null
|
||
+++ b/include/hw/loongarch/larch.h
|
||
@@ -0,0 +1,164 @@
|
||
+/*
|
||
+ * Hotplug emulation on Loongarch system.
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#ifndef HW_LOONGARCH_H
|
||
+#define HW_LOONGARCH_H
|
||
+
|
||
+#include "target/loongarch64/cpu.h"
|
||
+#include "qemu-common.h"
|
||
+#include "exec/memory.h"
|
||
+#include "hw/mem/pc-dimm.h"
|
||
+#include "hw/hotplug.h"
|
||
+#include "hw/boards.h"
|
||
+#include "hw/acpi/acpi.h"
|
||
+#include "qemu/notify.h"
|
||
+#include "qemu/error-report.h"
|
||
+#include "qemu/queue.h"
|
||
+#include "hw/acpi/memory_hotplug.h"
|
||
+#include "hw/loongarch/cpudevs.h"
|
||
+#include "hw/block/flash.h"
|
||
+
|
||
+#define LOONGARCH_MAX_VCPUS 256
|
||
+#define LOONGARCH_MAX_PFLASH 2
|
||
+/* 256MB alignment for hotplug memory region */
|
||
+#define LOONGARCH_HOTPLUG_MEM_ALIGN (1ULL << 28)
|
||
+#define LOONGARCH_MAX_RAM_SLOTS 10
|
||
+
|
||
+/* Memory types: */
|
||
+#define SYSTEM_RAM 1
|
||
+#define SYSTEM_RAM_RESERVED 2
|
||
+#define ACPI_TABLE 3
|
||
+#define ACPI_NVS 4
|
||
+#define SYSTEM_PMEM 5
|
||
+
|
||
+#define MAX_MEM_MAP 128
|
||
+
|
||
+typedef struct LoongarchMachineClass {
|
||
+ /*< private >*/
|
||
+ MachineClass parent_class;
|
||
+
|
||
+ /* Methods: */
|
||
+ HotplugHandler *(*get_hotplug_handler)(MachineState *machine,
|
||
+ DeviceState *dev);
|
||
+
|
||
+ bool has_acpi_build;
|
||
+
|
||
+ /* save different cpu address*/
|
||
+ uint64_t isa_io_base;
|
||
+ uint64_t ht_control_regs_base;
|
||
+ uint64_t hpet_mmio_addr;
|
||
+ uint64_t smbus_cfg_base;
|
||
+ uint64_t pciecfg_base;
|
||
+ uint64_t ls7a_ioapic_reg_base;
|
||
+ uint32_t node_shift;
|
||
+ char cpu_name[40];
|
||
+ char bridge_name[16];
|
||
+
|
||
+} LoongarchMachineClass;
|
||
+
|
||
+typedef struct ResetData {
|
||
+ LOONGARCHCPU *cpu;
|
||
+ uint64_t vector;
|
||
+} ResetData;
|
||
+
|
||
+typedef struct LoongarchMachineState {
|
||
+ /*< private >*/
|
||
+ MachineState parent_obj;
|
||
+
|
||
+ /* <public> */
|
||
+ ram_addr_t hotplug_memory_size;
|
||
+
|
||
+ /* State for other subsystems/APIs: */
|
||
+ Notifier machine_done;
|
||
+ /* Pointers to devices and objects: */
|
||
+ HotplugHandler *acpi_dev;
|
||
+ int ram_slots;
|
||
+ ResetData *reset_info[LOONGARCH_MAX_VCPUS];
|
||
+ DeviceState *rtc;
|
||
+ gipiState *gipi;
|
||
+ apicState *apic;
|
||
+
|
||
+ FWCfgState *fw_cfg;
|
||
+ bool acpi_build_enabled;
|
||
+ bool apic_xrupt_override;
|
||
+ CPUArchIdList *possible_cpus;
|
||
+ PFlashCFI01 *flash[LOONGARCH_MAX_PFLASH];
|
||
+ void *fdt;
|
||
+ int fdt_size;
|
||
+ unsigned int hotpluged_cpu_num;
|
||
+ DeviceState *platform_bus_dev;
|
||
+ OnOffAuto acpi;
|
||
+ char *oem_id;
|
||
+ char *oem_table_id;
|
||
+} LoongarchMachineState;
|
||
+
|
||
+#define LOONGARCH_MACHINE_ACPI_DEVICE_PROP "loongarch-acpi-device"
|
||
+#define TYPE_LOONGARCH_MACHINE "loongarch-machine"
|
||
+
|
||
+#define LoongarchMACHINE(obj) \
|
||
+ OBJECT_CHECK(LoongarchMachineState, (obj), TYPE_LOONGARCH_MACHINE)
|
||
+#define LoongarchMACHINE_GET_CLASS(obj) \
|
||
+ OBJECT_GET_CLASS(LoongarchMachineClass, (obj), TYPE_LOONGARCH_MACHINE)
|
||
+#define LoongarchMACHINE_CLASS(klass) \
|
||
+ OBJECT_CLASS_CHECK(LoongarchMachineClass, (klass), TYPE_LOONGARCH_MACHINE)
|
||
+
|
||
+#define DEFINE_LOONGARCH_MACHINE(suffix, namestr, initfn, optsfn) \
|
||
+ static void loongarch_machine_##suffix##_class_init(ObjectClass *oc, \
|
||
+ void *data) \
|
||
+ { \
|
||
+ MachineClass *mc = MACHINE_CLASS(oc); \
|
||
+ optsfn(mc); \
|
||
+ mc->init = initfn; \
|
||
+ } \
|
||
+ static const TypeInfo loongarch_machine_type_##suffix = { \
|
||
+ .name = namestr TYPE_MACHINE_SUFFIX, \
|
||
+ .parent = TYPE_LOONGARCH_MACHINE, \
|
||
+ .class_init = loongarch_machine_##suffix##_class_init, \
|
||
+ }; \
|
||
+ static void loongarch_machine_init_##suffix(void) \
|
||
+ { \
|
||
+ type_register(&loongarch_machine_type_##suffix); \
|
||
+ } \
|
||
+ type_init(loongarch_machine_init_##suffix)
|
||
+
|
||
+void loongarch_machine_device_unplug_request(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp);
|
||
+void longson_machine_device_unplug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp);
|
||
+HotplugHandler *loongarch_get_hotpug_handler(MachineState *machine,
|
||
+ DeviceState *dev);
|
||
+void loongarch_machine_device_pre_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp);
|
||
+void loongarch_machine_device_plug(HotplugHandler *hotplug_dev,
|
||
+ DeviceState *dev, Error **errp);
|
||
+
|
||
+LOONGARCHCPU *loongarch_cpu_create(MachineState *machine, LOONGARCHCPU *cpu,
|
||
+ Error **errp);
|
||
+void loongarch_cpu_destroy(MachineState *machine, LOONGARCHCPU *cpu);
|
||
+int cpu_init_ipi(LoongarchMachineState *ms, qemu_irq parent, int cpu);
|
||
+int cpu_init_apic(LoongarchMachineState *ms, CPULOONGARCHState *env, int cpu);
|
||
+int la_memmap_add_entry(uint64_t address, uint64_t length, uint32_t type);
|
||
+bool loongarch_is_acpi_enabled(LoongarchMachineState *vms);
|
||
+
|
||
+/* acpi-build.c */
|
||
+void ls7a_madt_cpu_entry(AcpiDeviceIf *adev, int uid,
|
||
+ const CPUArchIdList *apic_ids, GArray *entry,
|
||
+ bool force_enabled);
|
||
+void slave_cpu_reset(void *opaque);
|
||
+#endif
|
||
diff --git a/include/hw/loongarch/ls7a.h b/include/hw/loongarch/ls7a.h
|
||
new file mode 100644
|
||
index 0000000000..e1f3c8d032
|
||
--- /dev/null
|
||
+++ b/include/hw/loongarch/ls7a.h
|
||
@@ -0,0 +1,167 @@
|
||
+/*
|
||
+ * Acpi emulation on Loongarch system.
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#ifndef HW_LS7A_H
|
||
+#define HW_LS7A_H
|
||
+
|
||
+#include "hw/hw.h"
|
||
+#include "hw/isa/isa.h"
|
||
+#include "hw/sysbus.h"
|
||
+#include "hw/isa/apm.h"
|
||
+#include "hw/pci/pci.h"
|
||
+#include "hw/pci/pcie_host.h"
|
||
+#include "hw/pci/pci_bridge.h"
|
||
+#include "hw/acpi/acpi.h"
|
||
+#include "hw/acpi/ls7a.h"
|
||
+#include "hw/pci/pci_bus.h"
|
||
+
|
||
+/* LS7A PCH Registers (Misc, Confreg) */
|
||
+#define LS7A_PCH_REG_BASE 0x10000000UL
|
||
+#define LS3A5K_LS7A_IOAPIC_REG_BASE (LS7A_PCH_REG_BASE)
|
||
+#define LS7A_MISC_REG_BASE (LS7A_PCH_REG_BASE + 0x00080000)
|
||
+#define LS7A_ACPI_REG_BASE (LS7A_MISC_REG_BASE + 0x00050000)
|
||
+
|
||
+#define LOONGARCH_PCH_IRQ_BASE 64
|
||
+#define LS7A_UART_IRQ (LOONGARCH_PCH_IRQ_BASE + 2)
|
||
+#define LS7A_RTC_IRQ (LOONGARCH_PCH_IRQ_BASE + 3)
|
||
+#define LS7A_SCI_IRQ (LOONGARCH_PCH_IRQ_BASE + 4)
|
||
+#define LS7A_ACPI_IO_BASE 0x800
|
||
+#define LS7A_ACPI_IO_SIZE 0x100
|
||
+#define LS7A_PM_EVT_BLK (0x0C) /* 4 bytes */
|
||
+#define LS7A_PM_CNT_BLK (0x14) /* 2 bytes */
|
||
+#define LS7A_GPE0_STS_REG (0x28) /* 4 bytes */
|
||
+#define LS7A_GPE0_ENA_REG (0x2C) /* 4 bytes */
|
||
+#define LS7A_GPE0_RESET_REG (0x30) /* 4 bytes */
|
||
+#define LS7A_PM_TMR_BLK (0x18) /* 4 bytes */
|
||
+#define LS7A_GPE0_LEN (8)
|
||
+#define LS7A_RTC_REG_BASE (LS7A_MISC_REG_BASE + 0x00050100)
|
||
+#define LS7A_RTC_LEN (0x100)
|
||
+
|
||
+#define ACPI_IO_BASE (LS7A_ACPI_REG_BASE)
|
||
+#define ACPI_GPE0_LEN (LS7A_GPE0_LEN)
|
||
+#define ACPI_IO_SIZE (LS7A_ACPI_IO_SIZE)
|
||
+#define ACPI_SCI_IRQ (LS7A_SCI_IRQ)
|
||
+
|
||
+#define VIRT_PLATFORM_BUS_BASEADDRESS 0x16000000
|
||
+#define VIRT_PLATFORM_BUS_SIZE 0x02000000
|
||
+#define VIRT_PLATFORM_BUS_NUM_IRQS 2
|
||
+#define VIRT_PLATFORM_BUS_IRQ (LOONGARCH_PCH_IRQ_BASE + 5)
|
||
+
|
||
+#define LS3A5K_ISA_IO_BASE 0x18000000UL
|
||
+#define LS_BIOS_BASE 0x1c000000
|
||
+#define LS_BIOS_VAR_BASE 0x1c3a0000
|
||
+#define LS_BIOS_SIZE (4 * 1024 * 1024)
|
||
+#define LS_FDT_BASE 0x1c400000
|
||
+#define LS_FDT_SIZE 0x00100000
|
||
+
|
||
+#define FW_CFG_ADDR 0x1e020000
|
||
+#define LS7A_REG_BASE 0x1FE00000
|
||
+#define LS7A_UART_BASE 0x1fe001e0
|
||
+#define LS7A_UART_LEN 0x8
|
||
+#define SMP_GIPI_MAILBOX 0x1f000000ULL
|
||
+#define CORE0_STATUS_OFF 0x000
|
||
+#define CORE0_EN_OFF 0x004
|
||
+#define CORE0_SET_OFF 0x008
|
||
+#define CORE0_CLEAR_OFF 0x00c
|
||
+#define CORE0_BUF_20 0x020
|
||
+#define CORE0_BUF_28 0x028
|
||
+#define CORE0_BUF_30 0x030
|
||
+#define CORE0_BUF_38 0x038
|
||
+#define CORE0_IPI_SEND 0x040
|
||
+#define CORE0_MAIL_SEND 0x048
|
||
+#define INT_ROUTER_REGS_BASE 0x1fe01400UL
|
||
+#define INT_ROUTER_REGS_SIZE 0x100
|
||
+#define INT_ROUTER_REGS_SYS_INT0 0x00
|
||
+#define INT_ROUTER_REGS_SYS_INT1 0x01
|
||
+#define INT_ROUTER_REGS_SYS_INT2 0x02
|
||
+#define INT_ROUTER_REGS_SYS_INT3 0x03
|
||
+#define INT_ROUTER_REGS_PCI_INT0 0x04
|
||
+#define INT_ROUTER_REGS_PCI_INT1 0x05
|
||
+#define INT_ROUTER_REGS_PCI_INT2 0x06
|
||
+#define INT_ROUTER_REGS_PCI_INT3 0x07
|
||
+#define INT_ROUTER_REGS_MATRIX_INT0 0x08
|
||
+#define INT_ROUTER_REGS_MATRIX_INT1 0x09
|
||
+#define INT_ROUTER_REGS_LPC_INT 0x0a
|
||
+#define INT_ROUTER_REGS_MC0 0x0b
|
||
+#define INT_ROUTER_REGS_MC1 0x0c
|
||
+#define INT_ROUTER_REGS_BARRIER 0x0d
|
||
+#define INT_ROUTER_REGS_THSENS_INT 0x0e
|
||
+#define INT_ROUTER_REGS_PCI_PERR 0x0f
|
||
+#define INT_ROUTER_REGS_HT0_INT0 0x10
|
||
+#define INT_ROUTER_REGS_HT0_INT1 0x11
|
||
+#define INT_ROUTER_REGS_HT0_INT2 0x12
|
||
+#define INT_ROUTER_REGS_HT0_INT3 0x13
|
||
+#define INT_ROUTER_REGS_HT0_INT4 0x14
|
||
+#define INT_ROUTER_REGS_HT0_INT5 0x15
|
||
+#define INT_ROUTER_REGS_HT0_INT6 0x16
|
||
+#define INT_ROUTER_REGS_HT0_INT7 0x17
|
||
+#define INT_ROUTER_REGS_HT1_INT0 0x18
|
||
+#define INT_ROUTER_REGS_HT1_INT1 0x19
|
||
+#define INT_ROUTER_REGS_HT1_INT2 0x1a
|
||
+#define INT_ROUTER_REGS_HT1_INT3 0x1b
|
||
+#define INT_ROUTER_REGS_HT1_INT4 0x1c
|
||
+#define INT_ROUTER_REGS_HT1_INT5 0x1d
|
||
+#define INT_ROUTER_REGS_HT1_INT6 0x1e
|
||
+#define INT_ROUTER_REGS_HT1_INT7 0x1f
|
||
+#define INT_ROUTER_REGS_ISR 0x20
|
||
+#define INT_ROUTER_REGS_EN 0x24
|
||
+#define INT_ROUTER_REGS_EN_SET 0x28
|
||
+#define INT_ROUTER_REGS_EN_CLR 0x2c
|
||
+#define INT_ROUTER_REGS_EDGE 0x38
|
||
+#define INT_ROUTER_REGS_CORE0_INTISR 0x40
|
||
+#define INT_ROUTER_REGS_CORE1_INTISR 0x48
|
||
+#define INT_ROUTER_REGS_CORE2_INTISR 0x50
|
||
+#define INT_ROUTER_REGS_CORE3_INTISR 0x58
|
||
+
|
||
+#define LS_PCIECFG_BASE 0x20000000
|
||
+#define LS_PCIECFG_SIZE 0x08000000
|
||
+#define MSI_ADDR_LOW 0x2FF00000
|
||
+#define MSI_ADDR_HI 0x0
|
||
+
|
||
+#define PCIE_MEMORY_BASE 0x40000000
|
||
+#define PCIE_MEMORY_SIZE 0x40000000
|
||
+
|
||
+typedef struct LS7APCIState LS7APCIState;
|
||
+typedef struct LS7APCIEHost {
|
||
+ PCIExpressHost parent_obj;
|
||
+ LS7APCIState *pci_dev;
|
||
+} LS7APCIEHost;
|
||
+
|
||
+struct LS7APCIState {
|
||
+ PCIDevice dev;
|
||
+
|
||
+ LS7APCIEHost *pciehost;
|
||
+
|
||
+ /* LS7A registers */
|
||
+ MemoryRegion iomem;
|
||
+ LS7APCIPMRegs pm;
|
||
+};
|
||
+
|
||
+#define TYPE_LS7A_PCIE_HOST_BRIDGE "ls7a1000-pciehost"
|
||
+#define LS7A_PCIE_HOST_BRIDGE(obj) \
|
||
+ OBJECT_CHECK(LS7APCIEHost, (obj), TYPE_LS7A_PCIE_HOST_BRIDGE)
|
||
+
|
||
+#define TYPE_PCIE_LS7A "ls7a1000_pcie"
|
||
+#define PCIE_LS7A(obj) OBJECT_CHECK(LS7APCIState, (obj), TYPE_PCIE_LS7A)
|
||
+
|
||
+PCIBus *ls7a_init(MachineState *machine, qemu_irq *irq,
|
||
+ DeviceState **ls7a_dev);
|
||
+LS7APCIState *get_ls7a_type(Object *obj);
|
||
+
|
||
+#endif /* HW_LS7A_H */
|
||
diff --git a/include/hw/loongarch/sysbus-fdt.h b/include/hw/loongarch/sysbus-fdt.h
|
||
new file mode 100644
|
||
index 0000000000..6bf53097e1
|
||
--- /dev/null
|
||
+++ b/include/hw/loongarch/sysbus-fdt.h
|
||
@@ -0,0 +1,33 @@
|
||
+/*
|
||
+ * Dynamic sysbus device tree node generation API
|
||
+ *
|
||
+ * Copyright (c) 2023 Loongarch Technology
|
||
+ *
|
||
+ * This program is free software; you can redistribute it and/or modify it
|
||
+ * under the terms and conditions of the GNU General Public License,
|
||
+ * version 2 or later, as published by the Free Software Foundation.
|
||
+ *
|
||
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
||
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
+ * more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along with
|
||
+ * this program. If not, see <http://www.gnu.org/licenses/>.
|
||
+ *
|
||
+ */
|
||
+
|
||
+#ifndef HW_ARM_SYSBUS_FDT_H
|
||
+#define HW_ARM_SYSBUS_FDT_H
|
||
+
|
||
+#include "exec/hwaddr.h"
|
||
+
|
||
+/**
|
||
+ * platform_bus_add_all_fdt_nodes - create all the platform bus nodes
|
||
+ *
|
||
+ * builds the parent platform bus node and all the nodes of dynamic
|
||
+ * sysbus devices attached to it.
|
||
+ */
|
||
+void platform_bus_add_all_fdt_nodes(void *fdt, const char *intc, hwaddr addr,
|
||
+ hwaddr bus_size, int irq_start);
|
||
+#endif
|
||
diff --git a/include/qemu/osdep.h b/include/qemu/osdep.h
|
||
index 60718fc342..fd9e53f623 100644
|
||
--- a/include/qemu/osdep.h
|
||
+++ b/include/qemu/osdep.h
|
||
@@ -533,6 +533,10 @@ static inline void qemu_cleanup_generic_vfree(void *p)
|
||
Valgrind does not support alignments larger than 1 MiB,
|
||
therefore we need special code which handles running on Valgrind. */
|
||
# define QEMU_VMALLOC_ALIGN (512 * 4096)
|
||
+#elif defined(__linux__) && defined(__loongarch__)
|
||
+ /* Use 32 MiB alignment so transparent hugepages can be used by KVM. */
|
||
+#define QEMU_VMALLOC_ALIGN (qemu_real_host_page_size * \
|
||
+ qemu_real_host_page_size / 8)
|
||
#elif defined(__linux__) && defined(__s390x__)
|
||
/* Use 1 MiB (segment size) alignment so gmap can be used by KVM. */
|
||
# define QEMU_VMALLOC_ALIGN (256 * 4096)
|
||
--
|
||
2.27.0
|
||
|