aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2022-04-26 13:12:37 -0700
committerRichard Henderson <richard.henderson@linaro.org>2022-04-26 13:12:37 -0700
commit88d5814e6b02515f823086abb91dc7cdbb31c9f1 (patch)
treedcce7c9d3f084a14aca85dcef4feb65ff63a48a3
parenteab18e4021b80a03729424385c506e2454cd635c (diff)
parent7f176c5a0bcb70492f3b158a36311e75f1eb87d7 (diff)
downloadqemu-88d5814e6b02515f823086abb91dc7cdbb31c9f1.zip
qemu-88d5814e6b02515f823086abb91dc7cdbb31c9f1.tar.gz
qemu-88d5814e6b02515f823086abb91dc7cdbb31c9f1.tar.bz2
Merge tag 'pull-nios2-20220426' of https://gitlab.com/rth7680/qemu into staging
Fix nios2-linux-user syscalls. Fix nios2-linux-user sigreturn. Enable tests for nios2-linux-user. Remove special handling of SIGSEGV. Check supervisor for eret, bret. Split special registers out of env->regs[]. Clean up interrupt processing. Raise unaligned data and destination exceptions. Set TLBMISC fields correctly on exceptions. Prevent writes to read-only or reserved control fields. Use tcg_constant_tl(). Implement shadow register sets. Implement external interrupt controller interface. Implement vectored interrupt controller. Enable semihosting tests for nios2-softmmu. # -----BEGIN PGP SIGNATURE----- # # iQFRBAABCgA7FiEEekgeeIaLTbaoWgXAZN846K9+IV8FAmJoNuQdHHJpY2hhcmQu # aGVuZGVyc29uQGxpbmFyby5vcmcACgkQZN846K9+IV+a0ggAhawc3tod4OTHRlRq # rvZrJK740bNMo8rtidDnh71+IGjBiz8pXahqkE78cADtMzNmQoScwWbjht3cuMN2 # TMV0sbNDeA2OB98QzX6JTbCRtEfQAB7pyjpFvg6oXhYYSfwwhWbTR9QsYTHjq157 # ZKOprafoSlmDlgWJhlAikLdvJb07/5jgmvsLbBzu8/G/HiJ4HhHyjZxL1wNz1t/+ # 0KTAbnn3SWGDAhLGS/P6BMZKeU1EAExAwo7CtZeUbs+9QCfeM3cBAurG3WB1Vw14 # ERPoGPPrARtoNPtgQFMHu0am3HH5HtneuzJfWaLT96rrwNyTrYY0EYti1NtFDW8O # CCz42Q== # =MHar # -----END PGP SIGNATURE----- # gpg: Signature made Tue 26 Apr 2022 11:16:04 AM PDT # gpg: using RSA key 7A481E78868B4DB6A85A05C064DF38E8AF7E215F # gpg: issuer "richard.henderson@linaro.org" # gpg: Good signature from "Richard Henderson <richard.henderson@linaro.org>" [ultimate] * tag 'pull-nios2-20220426' of https://gitlab.com/rth7680/qemu: (68 commits) tests/tcg/nios2: Add test-shadow-1 tests/tcg/nios2: Add semihosting multiarch tests hw/nios2: Machine with a Vectored Interrupt Controller hw/nios2: Move memory regions into Nios2Machine hw/nios2: Introduce Nios2MachineState hw/intc: Vectored Interrupt Controller (VIC) linux-user/nios2: Handle various SIGILL exceptions target/nios2: Advance pc when raising exceptions target/nios2: Implement EIC interrupt processing target/nios2: Update helper_eret for shadow registers target/nios2: Implement rdprs, wrprs target/nios2: Introduce shadow register sets target/nios2: Implement Misaligned destination exception target/nios2: Use tcg_gen_lookup_and_goto_ptr target/nios2: Use gen_goto_tb for DISAS_TOO_MANY target/nios2: Hoist set of is_jmp into gen_goto_tb target/nios2: Create gen_jumpr target/nios2: Enable unaligned traps for system mode target/nios2: Drop CR_STATUS_EH from tb->flags target/nios2: Introduce dest_gpr ... Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
-rw-r--r--configs/targets/nios2-softmmu.mak1
-rw-r--r--hw/intc/Kconfig3
-rw-r--r--hw/intc/meson.build1
-rw-r--r--hw/intc/nios2_vic.c313
-rw-r--r--hw/nios2/10m50_devboard.c115
-rw-r--r--hw/nios2/Kconfig1
-rw-r--r--include/hw/intc/nios2_vic.h64
-rw-r--r--linux-user/elfload.c3
-rw-r--r--linux-user/nios2/cpu_loop.c96
-rw-r--r--linux-user/nios2/signal.c25
-rw-r--r--linux-user/nios2/target_cpu.h1
-rw-r--r--target/nios2/cpu.c209
-rw-r--r--target/nios2/cpu.h252
-rw-r--r--target/nios2/helper.c363
-rw-r--r--target/nios2/helper.h5
-rw-r--r--target/nios2/meson.build7
-rw-r--r--target/nios2/mmu.c78
-rw-r--r--target/nios2/op_helper.c88
-rw-r--r--target/nios2/translate.c755
-rw-r--r--tests/tcg/nios2/10m50-ghrd.ld66
-rw-r--r--tests/tcg/nios2/Makefile.softmmu-target33
-rw-r--r--tests/tcg/nios2/Makefile.target11
-rw-r--r--tests/tcg/nios2/boot.S218
-rw-r--r--tests/tcg/nios2/intr.S31
-rw-r--r--tests/tcg/nios2/semicall.h28
-rw-r--r--tests/tcg/nios2/test-shadow-1.S40
26 files changed, 2105 insertions, 702 deletions
diff --git a/configs/targets/nios2-softmmu.mak b/configs/targets/nios2-softmmu.mak
index 9a372f0..1e93b54 100644
--- a/configs/targets/nios2-softmmu.mak
+++ b/configs/targets/nios2-softmmu.mak
@@ -1 +1,2 @@
TARGET_ARCH=nios2
+TARGET_ALIGNED_ONLY=y
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index a7cf301..eded1b5 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -84,3 +84,6 @@ config GOLDFISH_PIC
config M68K_IRQC
bool
+
+config NIOS2_VIC
+ bool
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index d6d012f..8b35139 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -62,3 +62,4 @@ specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'],
if_true: files('spapr_xive_kvm.c'))
specific_ss.add(when: 'CONFIG_GOLDFISH_PIC', if_true: files('goldfish_pic.c'))
specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
+specific_ss.add(when: 'CONFIG_NIOS2_VIC', if_true: files('nios2_vic.c'))
diff --git a/hw/intc/nios2_vic.c b/hw/intc/nios2_vic.c
new file mode 100644
index 0000000..cf63212
--- /dev/null
+++ b/hw/intc/nios2_vic.c
@@ -0,0 +1,313 @@
+/*
+ * Vectored Interrupt Controller for nios2 processor
+ *
+ * Copyright (c) 2022 Neuroblade
+ *
+ * Interface:
+ * QOM property "cpu": link to the Nios2 CPU (must be set)
+ * Unnamed GPIO inputs 0..NIOS2_VIC_MAX_IRQ-1: input IRQ lines
+ * IRQ should be connected to nios2 IRQ0.
+ *
+ * Reference: "Embedded Peripherals IP User Guide
+ * for Intel® Quartus® Prime Design Suite: 21.4"
+ * Chapter 38 "Vectored Interrupt Controller Core"
+ * See: https://www.intel.com/content/www/us/en/docs/programmable/683130/21-4/vectored-interrupt-controller-core.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qom/object.h"
+#include "hw/intc/nios2_vic.h"
+#include "cpu.h"
+
+
+enum {
+ INT_CONFIG0 = 0,
+ INT_CONFIG31 = 31,
+ INT_ENABLE = 32,
+ INT_ENABLE_SET = 33,
+ INT_ENABLE_CLR = 34,
+ INT_PENDING = 35,
+ INT_RAW_STATUS = 36,
+ SW_INTERRUPT = 37,
+ SW_INTERRUPT_SET = 38,
+ SW_INTERRUPT_CLR = 39,
+ VIC_CONFIG = 40,
+ VIC_STATUS = 41,
+ VEC_TBL_BASE = 42,
+ VEC_TBL_ADDR = 43,
+ CSR_COUNT /* Last! */
+};
+
+/* Requested interrupt level (INT_CONFIG[0:5]) */
+static inline uint32_t vic_int_config_ril(const Nios2VIC *vic, int irq_num)
+{
+ return extract32(vic->int_config[irq_num], 0, 6);
+}
+
+/* Requested NMI (INT_CONFIG[6]) */
+static inline uint32_t vic_int_config_rnmi(const Nios2VIC *vic, int irq_num)
+{
+ return extract32(vic->int_config[irq_num], 6, 1);
+}
+
+/* Requested register set (INT_CONFIG[7:12]) */
+static inline uint32_t vic_int_config_rrs(const Nios2VIC *vic, int irq_num)
+{
+ return extract32(vic->int_config[irq_num], 7, 6);
+}
+
+static inline uint32_t vic_config_vec_size(const Nios2VIC *vic)
+{
+ return 1 << (2 + extract32(vic->vic_config, 0, 3));
+}
+
+static inline uint32_t vic_int_pending(const Nios2VIC *vic)
+{
+ return (vic->int_raw_status | vic->sw_int) & vic->int_enable;
+}
+
+static void vic_update_irq(Nios2VIC *vic)
+{
+ Nios2CPU *cpu = NIOS2_CPU(vic->cpu);
+ uint32_t pending = vic_int_pending(vic);
+ int irq = -1;
+ int max_ril = 0;
+ /* Note that if RIL is 0 for an interrupt it is effectively disabled */
+
+ vic->vec_tbl_addr = 0;
+ vic->vic_status = 0;
+
+ if (pending == 0) {
+ qemu_irq_lower(vic->output_int);
+ return;
+ }
+
+ for (int i = 0; i < NIOS2_VIC_MAX_IRQ; i++) {
+ if (pending & BIT(i)) {
+ int ril = vic_int_config_ril(vic, i);
+ if (ril > max_ril) {
+ irq = i;
+ max_ril = ril;
+ }
+ }
+ }
+
+ if (irq < 0) {
+ qemu_irq_lower(vic->output_int);
+ return;
+ }
+
+ vic->vec_tbl_addr = irq * vic_config_vec_size(vic) + vic->vec_tbl_base;
+ vic->vic_status = irq | BIT(31);
+
+ /*
+ * In hardware, the interface between the VIC and the CPU is via the
+ * External Interrupt Controller interface, where the interrupt controller
+ * presents the CPU with a packet of data containing:
+ * - Requested Handler Address (RHA): 32 bits
+ * - Requested Register Set (RRS) : 6 bits
+ * - Requested Interrupt Level (RIL) : 6 bits
+ * - Requested NMI flag (RNMI) : 1 bit
+ * In our emulation, we implement this by writing the data directly to
+ * fields in the CPU object and then raising the IRQ line to tell
+ * the CPU that we've done so.
+ */
+
+ cpu->rha = vic->vec_tbl_addr;
+ cpu->ril = max_ril;
+ cpu->rrs = vic_int_config_rrs(vic, irq);
+ cpu->rnmi = vic_int_config_rnmi(vic, irq);
+
+ qemu_irq_raise(vic->output_int);
+}
+
+static void vic_set_irq(void *opaque, int irq_num, int level)
+{
+ Nios2VIC *vic = opaque;
+
+ vic->int_raw_status = deposit32(vic->int_raw_status, irq_num, 1, !!level);
+ vic_update_irq(vic);
+}
+
+static void nios2_vic_reset(DeviceState *dev)
+{
+ Nios2VIC *vic = NIOS2_VIC(dev);
+
+ memset(&vic->int_config, 0, sizeof(vic->int_config));
+ vic->vic_config = 0;
+ vic->int_raw_status = 0;
+ vic->int_enable = 0;
+ vic->sw_int = 0;
+ vic->vic_status = 0;
+ vic->vec_tbl_base = 0;
+ vic->vec_tbl_addr = 0;
+}
+
+static uint64_t nios2_vic_csr_read(void *opaque, hwaddr offset, unsigned size)
+{
+ Nios2VIC *vic = opaque;
+ int index = offset / 4;
+
+ switch (index) {
+ case INT_CONFIG0 ... INT_CONFIG31:
+ return vic->int_config[index - INT_CONFIG0];
+ case INT_ENABLE:
+ return vic->int_enable;
+ case INT_PENDING:
+ return vic_int_pending(vic);
+ case INT_RAW_STATUS:
+ return vic->int_raw_status;
+ case SW_INTERRUPT:
+ return vic->sw_int;
+ case VIC_CONFIG:
+ return vic->vic_config;
+ case VIC_STATUS:
+ return vic->vic_status;
+ case VEC_TBL_BASE:
+ return vic->vec_tbl_base;
+ case VEC_TBL_ADDR:
+ return vic->vec_tbl_addr;
+ default:
+ return 0;
+ }
+}
+
+static void nios2_vic_csr_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ Nios2VIC *vic = opaque;
+ int index = offset / 4;
+
+ switch (index) {
+ case INT_CONFIG0 ... INT_CONFIG31:
+ vic->int_config[index - INT_CONFIG0] = value;
+ break;
+ case INT_ENABLE:
+ vic->int_enable = value;
+ break;
+ case INT_ENABLE_SET:
+ vic->int_enable |= value;
+ break;
+ case INT_ENABLE_CLR:
+ vic->int_enable &= ~value;
+ break;
+ case SW_INTERRUPT:
+ vic->sw_int = value;
+ break;
+ case SW_INTERRUPT_SET:
+ vic->sw_int |= value;
+ break;
+ case SW_INTERRUPT_CLR:
+ vic->sw_int &= ~value;
+ break;
+ case VIC_CONFIG:
+ vic->vic_config = value;
+ break;
+ case VEC_TBL_BASE:
+ vic->vec_tbl_base = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "nios2-vic: write to invalid CSR address %#"
+ HWADDR_PRIx "\n", offset);
+ }
+
+ vic_update_irq(vic);
+}
+
+static const MemoryRegionOps nios2_vic_csr_ops = {
+ .read = nios2_vic_csr_read,
+ .write = nios2_vic_csr_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = { .min_access_size = 4, .max_access_size = 4 }
+};
+
+static void nios2_vic_realize(DeviceState *dev, Error **errp)
+{
+ Nios2VIC *vic = NIOS2_VIC(dev);
+
+ if (!vic->cpu) {
+ /* This is a programming error in the code using this device */
+ error_setg(errp, "nios2-vic 'cpu' link property was not set");
+ return;
+ }
+
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &vic->output_int);
+ qdev_init_gpio_in(dev, vic_set_irq, NIOS2_VIC_MAX_IRQ);
+
+ memory_region_init_io(&vic->csr, OBJECT(dev), &nios2_vic_csr_ops, vic,
+ "nios2.vic.csr", CSR_COUNT * sizeof(uint32_t));
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &vic->csr);
+}
+
+static Property nios2_vic_properties[] = {
+ DEFINE_PROP_LINK("cpu", Nios2VIC, cpu, TYPE_CPU, CPUState *),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static const VMStateDescription nios2_vic_vmstate = {
+ .name = "nios2-vic",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]){
+ VMSTATE_UINT32_ARRAY(int_config, Nios2VIC, 32),
+ VMSTATE_UINT32(vic_config, Nios2VIC),
+ VMSTATE_UINT32(int_raw_status, Nios2VIC),
+ VMSTATE_UINT32(int_enable, Nios2VIC),
+ VMSTATE_UINT32(sw_int, Nios2VIC),
+ VMSTATE_UINT32(vic_status, Nios2VIC),
+ VMSTATE_UINT32(vec_tbl_base, Nios2VIC),
+ VMSTATE_UINT32(vec_tbl_addr, Nios2VIC),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void nios2_vic_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = nios2_vic_reset;
+ dc->realize = nios2_vic_realize;
+ dc->vmsd = &nios2_vic_vmstate;
+ device_class_set_props(dc, nios2_vic_properties);
+}
+
+static const TypeInfo nios2_vic_info = {
+ .name = TYPE_NIOS2_VIC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Nios2VIC),
+ .class_init = nios2_vic_class_init,
+};
+
+static void nios2_vic_register_types(void)
+{
+ type_register_static(&nios2_vic_info);
+}
+
+type_init(nios2_vic_register_types);
diff --git a/hw/nios2/10m50_devboard.c b/hw/nios2/10m50_devboard.c
index 3d1205b..91383fb 100644
--- a/hw/nios2/10m50_devboard.c
+++ b/hw/nios2/10m50_devboard.c
@@ -27,6 +27,7 @@
#include "hw/sysbus.h"
#include "hw/char/serial.h"
+#include "hw/intc/nios2_vic.h"
#include "hw/qdev-properties.h"
#include "sysemu/sysemu.h"
#include "hw/boards.h"
@@ -36,17 +37,28 @@
#include "boot.h"
+struct Nios2MachineState {
+ MachineState parent_obj;
+
+ MemoryRegion phys_tcm;
+ MemoryRegion phys_tcm_alias;
+ MemoryRegion phys_ram;
+ MemoryRegion phys_ram_alias;
+
+ bool vic;
+};
+
+#define TYPE_NIOS2_MACHINE MACHINE_TYPE_NAME("10m50-ghrd")
+OBJECT_DECLARE_TYPE(Nios2MachineState, MachineClass, NIOS2_MACHINE)
+
#define BINARY_DEVICE_TREE_FILE "10m50-devboard.dtb"
static void nios2_10m50_ghrd_init(MachineState *machine)
{
+ Nios2MachineState *nms = NIOS2_MACHINE(machine);
Nios2CPU *cpu;
DeviceState *dev;
MemoryRegion *address_space_mem = get_system_memory();
- MemoryRegion *phys_tcm = g_new(MemoryRegion, 1);
- MemoryRegion *phys_tcm_alias = g_new(MemoryRegion, 1);
- MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
- MemoryRegion *phys_ram_alias = g_new(MemoryRegion, 1);
ram_addr_t tcm_base = 0x0;
ram_addr_t tcm_size = 0x1000; /* 1 kiB, but QEMU limit is 4 kiB */
ram_addr_t ram_base = 0x08000000;
@@ -55,27 +67,56 @@ static void nios2_10m50_ghrd_init(MachineState *machine)
int i;
/* Physical TCM (tb_ram_1k) with alias at 0xc0000000 */
- memory_region_init_ram(phys_tcm, NULL, "nios2.tcm", tcm_size,
+ memory_region_init_ram(&nms->phys_tcm, NULL, "nios2.tcm", tcm_size,
&error_abort);
- memory_region_init_alias(phys_tcm_alias, NULL, "nios2.tcm.alias",
- phys_tcm, 0, tcm_size);
- memory_region_add_subregion(address_space_mem, tcm_base, phys_tcm);
+ memory_region_init_alias(&nms->phys_tcm_alias, NULL, "nios2.tcm.alias",
+ &nms->phys_tcm, 0, tcm_size);
+ memory_region_add_subregion(address_space_mem, tcm_base, &nms->phys_tcm);
memory_region_add_subregion(address_space_mem, 0xc0000000 + tcm_base,
- phys_tcm_alias);
+ &nms->phys_tcm_alias);
/* Physical DRAM with alias at 0xc0000000 */
- memory_region_init_ram(phys_ram, NULL, "nios2.ram", ram_size,
+ memory_region_init_ram(&nms->phys_ram, NULL, "nios2.ram", ram_size,
&error_abort);
- memory_region_init_alias(phys_ram_alias, NULL, "nios2.ram.alias",
- phys_ram, 0, ram_size);
- memory_region_add_subregion(address_space_mem, ram_base, phys_ram);
+ memory_region_init_alias(&nms->phys_ram_alias, NULL, "nios2.ram.alias",
+ &nms->phys_ram, 0, ram_size);
+ memory_region_add_subregion(address_space_mem, ram_base, &nms->phys_ram);
memory_region_add_subregion(address_space_mem, 0xc0000000 + ram_base,
- phys_ram_alias);
+ &nms->phys_ram_alias);
- /* Create CPU -- FIXME */
- cpu = NIOS2_CPU(cpu_create(TYPE_NIOS2_CPU));
- for (i = 0; i < 32; i++) {
- irq[i] = qdev_get_gpio_in_named(DEVICE(cpu), "IRQ", i);
+ /* Create CPU. We need to set eic_present between init and realize. */
+ cpu = NIOS2_CPU(object_new(TYPE_NIOS2_CPU));
+
+ /* Enable the External Interrupt Controller within the CPU. */
+ cpu->eic_present = nms->vic;
+
+ /* Configure new exception vectors. */
+ cpu->reset_addr = 0xd4000000;
+ cpu->exception_addr = 0xc8000120;
+ cpu->fast_tlb_miss_addr = 0xc0000100;
+
+ qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal);
+
+ if (nms->vic) {
+ DeviceState *dev = qdev_new(TYPE_NIOS2_VIC);
+ MemoryRegion *dev_mr;
+ qemu_irq cpu_irq;
+
+ object_property_set_link(OBJECT(dev), "cpu", OBJECT(cpu), &error_fatal);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ cpu_irq = qdev_get_gpio_in_named(DEVICE(cpu), "EIC", 0);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, cpu_irq);
+ for (int i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ dev_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+ memory_region_add_subregion(address_space_mem, 0x18002000, dev_mr);
+ } else {
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in_named(DEVICE(cpu), "IRQ", i);
+ }
}
/* Register: Altera 16550 UART */
@@ -96,20 +137,44 @@ static void nios2_10m50_ghrd_init(MachineState *machine)
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xe0000880);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[5]);
- /* Configure new exception vectors and reset CPU for it to take effect. */
- cpu->reset_addr = 0xd4000000;
- cpu->exception_addr = 0xc8000120;
- cpu->fast_tlb_miss_addr = 0xc0000100;
-
nios2_load_kernel(cpu, ram_base, ram_size, machine->initrd_filename,
BINARY_DEVICE_TREE_FILE, NULL);
}
-static void nios2_10m50_ghrd_machine_init(struct MachineClass *mc)
+static bool get_vic(Object *obj, Error **errp)
+{
+ Nios2MachineState *nms = NIOS2_MACHINE(obj);
+ return nms->vic;
+}
+
+static void set_vic(Object *obj, bool value, Error **errp)
+{
+ Nios2MachineState *nms = NIOS2_MACHINE(obj);
+ nms->vic = value;
+}
+
+static void nios2_10m50_ghrd_class_init(ObjectClass *oc, void *data)
{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
mc->desc = "Altera 10M50 GHRD Nios II design";
mc->init = nios2_10m50_ghrd_init;
mc->is_default = true;
+
+ object_class_property_add_bool(oc, "vic", get_vic, set_vic);
+ object_class_property_set_description(oc, "vic",
+ "Set on/off to enable/disable the Vectored Interrupt Controller");
}
-DEFINE_MACHINE("10m50-ghrd", nios2_10m50_ghrd_machine_init);
+static const TypeInfo nios2_10m50_ghrd_type_info = {
+ .name = TYPE_NIOS2_MACHINE,
+ .parent = TYPE_MACHINE,
+ .instance_size = sizeof(Nios2MachineState),
+ .class_init = nios2_10m50_ghrd_class_init,
+};
+
+static void nios2_10m50_ghrd_type_init(void)
+{
+ type_register_static(&nios2_10m50_ghrd_type_info);
+}
+type_init(nios2_10m50_ghrd_type_init);
diff --git a/hw/nios2/Kconfig b/hw/nios2/Kconfig
index b10ea64..4748ae2 100644
--- a/hw/nios2/Kconfig
+++ b/hw/nios2/Kconfig
@@ -3,6 +3,7 @@ config NIOS2_10M50
select NIOS2
select SERIAL
select ALTERA_TIMER
+ select NIOS2_VIC
config NIOS2_GENERIC_NOMMU
bool
diff --git a/include/hw/intc/nios2_vic.h b/include/hw/intc/nios2_vic.h
new file mode 100644
index 0000000..af1517a
--- /dev/null
+++ b/include/hw/intc/nios2_vic.h
@@ -0,0 +1,64 @@
+/*
+ * Vectored Interrupt Controller for nios2 processor
+ *
+ * Copyright (c) 2022 Neuroblade
+ *
+ * Interface:
+ * QOM property "cpu": link to the Nios2 CPU (must be set)
+ * Unnamed GPIO inputs 0..NIOS2_VIC_MAX_IRQ-1: input IRQ lines
+ * IRQ should be connected to nios2 IRQ0.
+ *
+ * Reference: "Embedded Peripherals IP User Guide
+ * for Intel® Quartus® Prime Design Suite: 21.4"
+ * Chapter 38 "Vectored Interrupt Controller Core"
+ * See: https://www.intel.com/content/www/us/en/docs/programmable/683130/21-4/vectored-interrupt-controller-core.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef HW_INTC_NIOS2_VIC
+#define HW_INTC_NIOS2_VIC
+
+#define TYPE_NIOS2_VIC "nios2-vic"
+OBJECT_DECLARE_SIMPLE_TYPE(Nios2VIC, NIOS2_VIC)
+
+#define NIOS2_VIC_MAX_IRQ 32
+
+struct Nios2VIC {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ qemu_irq output_int;
+
+ /* properties */
+ CPUState *cpu;
+ MemoryRegion csr;
+
+ uint32_t int_config[NIOS2_VIC_MAX_IRQ];
+ uint32_t vic_config;
+ uint32_t int_raw_status;
+ uint32_t int_enable;
+ uint32_t sw_int;
+ uint32_t vic_status;
+ uint32_t vec_tbl_base;
+ uint32_t vec_tbl_addr;
+};
+
+#endif /* HW_INTC_NIOS2_VIC */
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index d6bb1fc..61063fd 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -1096,7 +1096,6 @@ static void init_thread(struct target_pt_regs *regs, struct image_info *infop)
{
regs->ea = infop->entry;
regs->sp = infop->start_stack;
- regs->estatus = 0x3;
}
#define LO_COMMPAGE TARGET_PAGE_SIZE
@@ -1170,7 +1169,7 @@ static void elf_core_copy_regs(target_elf_gregset_t *regs,
(*regs)[30] = -1; /* R_SSTATUS */
(*regs)[31] = tswapreg(env->regs[R_RA]);
- (*regs)[32] = tswapreg(env->regs[R_PC]);
+ (*regs)[32] = tswapreg(env->pc);
(*regs)[33] = -1; /* R_STATUS */
(*regs)[34] = tswapreg(env->regs[CR_ESTATUS]);
diff --git a/linux-user/nios2/cpu_loop.c b/linux-user/nios2/cpu_loop.c
index 1e93ef3..da77ede7 100644
--- a/linux-user/nios2/cpu_loop.c
+++ b/linux-user/nios2/cpu_loop.c
@@ -26,7 +26,6 @@
void cpu_loop(CPUNios2State *env)
{
CPUState *cs = env_cpu(env);
- target_siginfo_t info;
int trapnr, ret;
for (;;) {
@@ -39,6 +38,30 @@ void cpu_loop(CPUNios2State *env)
/* just indicate that signals should be handled asap */
break;
+ case EXCP_DIV:
+ /* Match kernel's handle_diverror_c(). */
+ env->pc -= 4;
+ force_sig_fault(TARGET_SIGFPE, TARGET_FPE_INTDIV, env->pc);
+ break;
+
+ case EXCP_UNALIGN:
+ case EXCP_UNALIGND:
+ force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN,
+ env->ctrl[CR_BADADDR]);
+ break;
+
+ case EXCP_ILLEGAL:
+ case EXCP_UNIMPL:
+ /* Match kernel's handle_illegal_c(). */
+ env->pc -= 4;
+ force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC, env->pc);
+ break;
+ case EXCP_SUPERI:
+ /* Match kernel's handle_supervisor_instr(). */
+ env->pc -= 4;
+ force_sig_fault(TARGET_SIGILL, TARGET_ILL_PRVOPC, env->pc);
+ break;
+
case EXCP_TRAP:
switch (env->error_code) {
case 0:
@@ -49,32 +72,41 @@ void cpu_loop(CPUNios2State *env)
env->regs[7], env->regs[8], env->regs[9],
0, 0);
- if (env->regs[2] == 0) { /* FIXME: syscall 0 workaround */
- ret = 0;
+ if (ret == -QEMU_ESIGRETURN) {
+ /* rt_sigreturn has set all state. */
+ break;
}
-
+ if (ret == -QEMU_ERESTARTSYS) {
+ env->pc -= 4;
+ break;
+ }
+ /*
+ * See the code after translate_rc_and_ret: all negative
+ * values are errors (aided by userspace restricted to 2G),
+ * errno is returned positive in r2, and error indication
+ * is a boolean in r7.
+ */
env->regs[2] = abs(ret);
- /* Return value is 0..4096 */
- env->regs[7] = ret > 0xfffff000u;
- env->regs[R_PC] += 4;
+ env->regs[7] = ret < 0;
break;
case 1:
qemu_log_mask(CPU_LOG_INT, "\nTrap 1\n");
- force_sig_fault(TARGET_SIGUSR1, 0, env->regs[R_PC]);
+ force_sig_fault(TARGET_SIGUSR1, 0, env->pc);
break;
case 2:
qemu_log_mask(CPU_LOG_INT, "\nTrap 2\n");
- force_sig_fault(TARGET_SIGUSR2, 0, env->regs[R_PC]);
+ force_sig_fault(TARGET_SIGUSR2, 0, env->pc);
break;
case 31:
qemu_log_mask(CPU_LOG_INT, "\nTrap 31\n");
- force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->regs[R_PC]);
+ /* Match kernel's breakpoint_c(). */
+ env->pc -= 4;
+ force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->pc);
break;
default:
qemu_log_mask(CPU_LOG_INT, "\nTrap %d\n", env->error_code);
- force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLTRP,
- env->regs[R_PC]);
+ force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLTRP, env->pc);
break;
case 16: /* QEMU specific, for __kuser_cmpxchg */
@@ -99,27 +131,13 @@ void cpu_loop(CPUNios2State *env)
o = env->regs[5];
n = env->regs[6];
env->regs[2] = qatomic_cmpxchg(h, o, n) - o;
- env->regs[R_PC] += 4;
}
break;
}
break;
case EXCP_DEBUG:
- info.si_signo = TARGET_SIGTRAP;
- info.si_errno = 0;
- info.si_code = TARGET_TRAP_BRKPT;
- queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
- break;
- case 0xaa:
- {
- info.si_signo = TARGET_SIGSEGV;
- info.si_errno = 0;
- /* TODO: check env->error_code */
- info.si_code = TARGET_SEGV_MAPERR;
- info._sifields._sigfault._addr = env->regs[R_PC];
- queue_signal(env, info.si_signo, QEMU_SI_FAULT, &info);
- }
+ force_sig_fault(TARGET_SIGTRAP, TARGET_TRAP_BRKPT, env->pc);
break;
default:
EXCP_DUMP(env, "\nqemu: unhandled CPU exception %#x - aborting\n",
@@ -133,28 +151,6 @@ void cpu_loop(CPUNios2State *env)
void target_cpu_copy_regs(CPUArchState *env, struct target_pt_regs *regs)
{
- env->regs[0] = 0;
- env->regs[1] = regs->r1;
- env->regs[2] = regs->r2;
- env->regs[3] = regs->r3;
- env->regs[4] = regs->r4;
- env->regs[5] = regs->r5;
- env->regs[6] = regs->r6;
- env->regs[7] = regs->r7;
- env->regs[8] = regs->r8;
- env->regs[9] = regs->r9;
- env->regs[10] = regs->r10;
- env->regs[11] = regs->r11;
- env->regs[12] = regs->r12;
- env->regs[13] = regs->r13;
- env->regs[14] = regs->r14;
- env->regs[15] = regs->r15;
- /* TODO: unsigned long orig_r2; */
- env->regs[R_RA] = regs->ra;
- env->regs[R_FP] = regs->fp;
env->regs[R_SP] = regs->sp;
- env->regs[R_GP] = regs->gp;
- env->regs[CR_ESTATUS] = regs->estatus;
- env->regs[R_PC] = regs->ea;
- /* TODO: unsigned long orig_r7; */
+ env->pc = regs->ea;
}
diff --git a/linux-user/nios2/signal.c b/linux-user/nios2/signal.c
index 517cd39..32b3dc9 100644
--- a/linux-user/nios2/signal.c
+++ b/linux-user/nios2/signal.c
@@ -73,12 +73,11 @@ static void rt_setup_ucontext(struct target_ucontext *uc, CPUNios2State *env)
__put_user(env->regs[R_RA], &gregs[23]);
__put_user(env->regs[R_FP], &gregs[24]);
__put_user(env->regs[R_GP], &gregs[25]);
- __put_user(env->regs[R_PC], &gregs[27]);
+ __put_user(env->pc, &gregs[27]);
__put_user(env->regs[R_SP], &gregs[28]);
}
-static int rt_restore_ucontext(CPUNios2State *env, struct target_ucontext *uc,
- int *pr2)
+static int rt_restore_ucontext(CPUNios2State *env, struct target_ucontext *uc)
{
int temp;
unsigned long *gregs = uc->tuc_mcontext.gregs;
@@ -122,14 +121,12 @@ static int rt_restore_ucontext(CPUNios2State *env, struct target_ucontext *uc,
__get_user(env->regs[R_GP], &gregs[25]);
/* Not really necessary no user settable bits */
__get_user(temp, &gregs[26]);
- __get_user(env->regs[R_PC], &gregs[27]);
+ __get_user(env->pc, &gregs[27]);
__get_user(env->regs[R_RA], &gregs[23]);
__get_user(env->regs[R_SP], &gregs[28]);
target_restore_altstack(&uc->tuc_stack, env);
-
- *pr2 = env->regs[2];
return 0;
}
@@ -180,25 +177,17 @@ void setup_rt_frame(int sig, struct target_sigaction *ka,
env->regs[4] = sig;
env->regs[5] = frame_addr + offsetof(struct target_rt_sigframe, info);
env->regs[6] = frame_addr + offsetof(struct target_rt_sigframe, uc);
- env->regs[R_PC] = ka->_sa_handler;
+ env->pc = ka->_sa_handler;
unlock_user_struct(frame, frame_addr, 1);
}
-long do_sigreturn(CPUNios2State *env)
-{
- trace_user_do_sigreturn(env, 0);
- qemu_log_mask(LOG_UNIMP, "do_sigreturn: not implemented\n");
- return -TARGET_ENOSYS;
-}
-
long do_rt_sigreturn(CPUNios2State *env)
{
/* Verify, can we follow the stack back */
abi_ulong frame_addr = env->regs[R_SP];
struct target_rt_sigframe *frame;
sigset_t set;
- int rval;
if (!lock_user_struct(VERIFY_READ, frame, frame_addr, 1)) {
goto badframe;
@@ -207,15 +196,15 @@ long do_rt_sigreturn(CPUNios2State *env)
target_to_host_sigset(&set, &frame->uc.tuc_sigmask);
set_sigmask(&set);
- if (rt_restore_ucontext(env, &frame->uc, &rval)) {
+ if (rt_restore_ucontext(env, &frame->uc)) {
goto badframe;
}
unlock_user_struct(frame, frame_addr, 0);
- return rval;
+ return -QEMU_ESIGRETURN;
badframe:
unlock_user_struct(frame, frame_addr, 0);
force_sig(TARGET_SIGSEGV);
- return 0;
+ return -QEMU_ESIGRETURN;
}
diff --git a/linux-user/nios2/target_cpu.h b/linux-user/nios2/target_cpu.h
index 2d2008f..830b4c0 100644
--- a/linux-user/nios2/target_cpu.h
+++ b/linux-user/nios2/target_cpu.h
@@ -27,6 +27,7 @@ static inline void cpu_clone_regs_child(CPUNios2State *env, target_ulong newsp,
env->regs[R_SP] = newsp;
}
env->regs[R_RET0] = 0;
+ env->regs[7] = 0;
}
static inline void cpu_clone_regs_parent(CPUNios2State *env, unsigned flags)
diff --git a/target/nios2/cpu.c b/target/nios2/cpu.c
index b0877cb..19b2409 100644
--- a/target/nios2/cpu.c
+++ b/target/nios2/cpu.c
@@ -31,12 +31,12 @@ static void nios2_cpu_set_pc(CPUState *cs, vaddr value)
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- env->regs[R_PC] = value;
+ env->pc = value;
}
static bool nios2_cpu_has_work(CPUState *cs)
{
- return cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_NMI);
+ return cs->interrupt_request & CPU_INTERRUPT_HARD;
}
static void nios2_cpu_reset(DeviceState *dev)
@@ -48,27 +48,42 @@ static void nios2_cpu_reset(DeviceState *dev)
ncc->parent_reset(dev);
- memset(env->regs, 0, sizeof(uint32_t) * NUM_CORE_REGS);
- env->regs[R_PC] = cpu->reset_addr;
+ memset(env->ctrl, 0, sizeof(env->ctrl));
+ env->pc = cpu->reset_addr;
#if defined(CONFIG_USER_ONLY)
/* Start in user mode with interrupts enabled. */
- env->regs[CR_STATUS] = CR_STATUS_U | CR_STATUS_PIE;
+ env->ctrl[CR_STATUS] = CR_STATUS_RSIE | CR_STATUS_U | CR_STATUS_PIE;
+ memset(env->regs, 0, sizeof(env->regs));
#else
- env->regs[CR_STATUS] = 0;
+ env->ctrl[CR_STATUS] = CR_STATUS_RSIE;
+ nios2_update_crs(env);
+ memset(env->shadow_regs, 0, sizeof(env->shadow_regs));
#endif
}
#ifndef CONFIG_USER_ONLY
-static void nios2_cpu_set_irq(void *opaque, int irq, int level)
+static void eic_set_irq(void *opaque, int irq, int level)
+{
+ Nios2CPU *cpu = opaque;
+ CPUState *cs = CPU(cpu);
+
+ if (level) {
+ cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+ } else {
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+ }
+}
+
+static void iic_set_irq(void *opaque, int irq, int level)
{
Nios2CPU *cpu = opaque;
CPUNios2State *env = &cpu->env;
CPUState *cs = CPU(cpu);
- env->regs[CR_IPENDING] = deposit32(env->regs[CR_IPENDING], irq, 1, !!level);
+ env->ctrl[CR_IPENDING] = deposit32(env->ctrl[CR_IPENDING], irq, 1, !!level);
- if (env->regs[CR_IPENDING]) {
+ if (env->ctrl[CR_IPENDING]) {
cpu_interrupt(cs, CPU_INTERRUPT_HARD);
} else {
cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
@@ -84,15 +99,6 @@ static void nios2_cpu_initfn(Object *obj)
#if !defined(CONFIG_USER_ONLY)
mmu_init(&cpu->env);
-
- /*
- * These interrupt lines model the IIC (internal interrupt
- * controller). QEMU does not currently support the EIC
- * (external interrupt controller) -- if we did it would be
- * a separate device in hw/intc with a custom interface to
- * the CPU, and boards using it would not wire up these IRQ lines.
- */
- qdev_init_gpio_in_named(DEVICE(cpu), nios2_cpu_set_irq, "IRQ", 32);
#endif
}
@@ -101,37 +107,148 @@ static ObjectClass *nios2_cpu_class_by_name(const char *cpu_model)
return object_class_by_name(TYPE_NIOS2_CPU);
}
+static void realize_cr_status(CPUState *cs)
+{
+ Nios2CPU *cpu = NIOS2_CPU(cs);
+
+ /* Begin with all fields of all registers are reserved. */
+ memset(cpu->cr_state, 0, sizeof(cpu->cr_state));
+
+ /*
+ * The combination of writable and readonly is the set of all
+ * non-reserved fields. We apply writable as a mask to bits,
+ * and merge in existing readonly bits, before storing.
+ */
+#define WR_REG(C) cpu->cr_state[C].writable = -1
+#define RO_REG(C) cpu->cr_state[C].readonly = -1
+#define WR_FIELD(C, F) cpu->cr_state[C].writable |= R_##C##_##F##_MASK
+#define RO_FIELD(C, F) cpu->cr_state[C].readonly |= R_##C##_##F##_MASK
+
+ WR_FIELD(CR_STATUS, PIE);
+ WR_REG(CR_ESTATUS);
+ WR_REG(CR_BSTATUS);
+ RO_REG(CR_CPUID);
+ RO_REG(CR_EXCEPTION);
+ WR_REG(CR_BADADDR);
+
+ if (cpu->eic_present) {
+ WR_FIELD(CR_STATUS, RSIE);
+ RO_FIELD(CR_STATUS, NMI);
+ WR_FIELD(CR_STATUS, PRS);
+ RO_FIELD(CR_STATUS, CRS);
+ WR_FIELD(CR_STATUS, IL);
+ WR_FIELD(CR_STATUS, IH);
+ } else {
+ RO_FIELD(CR_STATUS, RSIE);
+ WR_REG(CR_IENABLE);
+ RO_REG(CR_IPENDING);
+ }
+
+ if (cpu->mmu_present) {
+ WR_FIELD(CR_STATUS, U);
+ WR_FIELD(CR_STATUS, EH);
+
+ WR_FIELD(CR_PTEADDR, VPN);
+ WR_FIELD(CR_PTEADDR, PTBASE);
+
+ RO_FIELD(CR_TLBMISC, D);
+ RO_FIELD(CR_TLBMISC, PERM);
+ RO_FIELD(CR_TLBMISC, BAD);
+ RO_FIELD(CR_TLBMISC, DBL);
+ WR_FIELD(CR_TLBMISC, PID);
+ WR_FIELD(CR_TLBMISC, WE);
+ WR_FIELD(CR_TLBMISC, RD);
+ WR_FIELD(CR_TLBMISC, WAY);
+
+ WR_REG(CR_TLBACC);
+ }
+
+ /*
+ * TODO: ECC (config, eccinj) and MPU (config, mpubase, mpuacc) are
+ * unimplemented, so their corresponding control regs remain reserved.
+ */
+
+#undef WR_REG
+#undef RO_REG
+#undef WR_FIELD
+#undef RO_FIELD
+}
+
static void nios2_cpu_realizefn(DeviceState *dev, Error **errp)
{
CPUState *cs = CPU(dev);
+ Nios2CPU *cpu = NIOS2_CPU(cs);
Nios2CPUClass *ncc = NIOS2_CPU_GET_CLASS(dev);
Error *local_err = NULL;
+#ifndef CONFIG_USER_ONLY
+ if (cpu->eic_present) {
+ qdev_init_gpio_in_named(DEVICE(cpu), eic_set_irq, "EIC", 1);
+ } else {
+ qdev_init_gpio_in_named(DEVICE(cpu), iic_set_irq, "IRQ", 32);
+ }
+#endif
+
cpu_exec_realizefn(cs, &local_err);
if (local_err != NULL) {
error_propagate(errp, local_err);
return;
}
+ realize_cr_status(cs);
qemu_init_vcpu(cs);
cpu_reset(cs);
+ /* We have reserved storage for cpuid; might as well use it. */
+ cpu->env.ctrl[CR_CPUID] = cs->cpu_index;
+
ncc->parent_realize(dev, errp);
}
#ifndef CONFIG_USER_ONLY
-static bool nios2_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
+static bool eic_take_interrupt(Nios2CPU *cpu)
{
- Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
+ const uint32_t status = env->ctrl[CR_STATUS];
- if ((interrupt_request & CPU_INTERRUPT_HARD) &&
- (env->regs[CR_STATUS] & CR_STATUS_PIE) &&
- (env->regs[CR_IPENDING] & env->regs[CR_IENABLE])) {
- cs->exception_index = EXCP_IRQ;
- nios2_cpu_do_interrupt(cs);
+ if (cpu->rnmi) {
+ return !(status & CR_STATUS_NMI);
+ }
+ if (!(status & CR_STATUS_PIE)) {
+ return false;
+ }
+ if (cpu->ril <= FIELD_EX32(status, CR_STATUS, IL)) {
+ return false;
+ }
+ if (cpu->rrs != FIELD_EX32(status, CR_STATUS, CRS)) {
return true;
}
+ return status & CR_STATUS_RSIE;
+}
+
+static bool iic_take_interrupt(Nios2CPU *cpu)
+{
+ CPUNios2State *env = &cpu->env;
+
+ if (!(env->ctrl[CR_STATUS] & CR_STATUS_PIE)) {
+ return false;
+ }
+ return env->ctrl[CR_IPENDING] & env->ctrl[CR_IENABLE];
+}
+
+static bool nios2_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
+{
+ Nios2CPU *cpu = NIOS2_CPU(cs);
+
+ if (interrupt_request & CPU_INTERRUPT_HARD) {
+ if (cpu->eic_present
+ ? eic_take_interrupt(cpu)
+ : iic_take_interrupt(cpu)) {
+ cs->exception_index = EXCP_IRQ;
+ nios2_cpu_do_interrupt(cs);
+ return true;
+ }
+ }
return false;
}
#endif /* !CONFIG_USER_ONLY */
@@ -146,23 +263,26 @@ static void nios2_cpu_disas_set_info(CPUState *cpu, disassemble_info *info)
static int nios2_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n)
{
Nios2CPU *cpu = NIOS2_CPU(cs);
- CPUClass *cc = CPU_GET_CLASS(cs);
CPUNios2State *env = &cpu->env;
-
- if (n > cc->gdb_num_core_regs) {
- return 0;
- }
+ uint32_t val;
if (n < 32) { /* GP regs */
- return gdb_get_reg32(mem_buf, env->regs[n]);
+ val = env->regs[n];
} else if (n == 32) { /* PC */
- return gdb_get_reg32(mem_buf, env->regs[R_PC]);
+ val = env->pc;
} else if (n < 49) { /* Status regs */
- return gdb_get_reg32(mem_buf, env->regs[n - 1]);
+ unsigned cr = n - 33;
+ if (nios2_cr_reserved(&cpu->cr_state[cr])) {
+ val = 0;
+ } else {
+ val = env->ctrl[n - 33];
+ }
+ } else {
+ /* Invalid regs */
+ return 0;
}
- /* Invalid regs */
- return 0;
+ return gdb_get_reg32(mem_buf, val);
}
static int nios2_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n)
@@ -170,23 +290,32 @@ static int nios2_cpu_gdb_write_register(CPUState *cs, uint8_t *mem_buf, int n)
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUClass *cc = CPU_GET_CLASS(cs);
CPUNios2State *env = &cpu->env;
+ uint32_t val;
if (n > cc->gdb_num_core_regs) {
return 0;
}
+ val = ldl_p(mem_buf);
if (n < 32) { /* GP regs */
- env->regs[n] = ldl_p(mem_buf);
+ env->regs[n] = val;
} else if (n == 32) { /* PC */
- env->regs[R_PC] = ldl_p(mem_buf);
+ env->pc = val;
} else if (n < 49) { /* Status regs */
- env->regs[n - 1] = ldl_p(mem_buf);
+ unsigned cr = n - 33;
+ /* ??? Maybe allow the debugger to write to readonly fields. */
+ val &= cpu->cr_state[cr].writable;
+ val |= cpu->cr_state[cr].readonly & env->ctrl[cr];
+ env->ctrl[cr] = val;
+ } else {
+ g_assert_not_reached();
}
return 4;
}
static Property nios2_properties[] = {
+ DEFINE_PROP_BOOL("diverr_present", Nios2CPU, diverr_present, true),
DEFINE_PROP_BOOL("mmu_present", Nios2CPU, mmu_present, true),
/* ALTR,pid-num-bits */
DEFINE_PROP_UINT32("mmu_pid_num_bits", Nios2CPU, pid_num_bits, 8),
@@ -210,9 +339,7 @@ static const struct SysemuCPUOps nios2_sysemu_ops = {
static const struct TCGCPUOps nios2_tcg_ops = {
.initialize = nios2_tcg_init,
-#ifdef CONFIG_USER_ONLY
- .record_sigsegv = nios2_cpu_record_sigsegv,
-#else
+#ifndef CONFIG_USER_ONLY
.tlb_fill = nios2_cpu_tlb_fill,
.cpu_exec_interrupt = nios2_cpu_exec_interrupt,
.do_interrupt = nios2_cpu_do_interrupt,
diff --git a/target/nios2/cpu.h b/target/nios2/cpu.h
index 1bab805..f85581e 100644
--- a/target/nios2/cpu.h
+++ b/target/nios2/cpu.h
@@ -23,6 +23,7 @@
#include "exec/cpu-defs.h"
#include "hw/core/cpu.h"
+#include "hw/registerfields.h"
#include "qom/object.h"
typedef struct CPUArchState CPUNios2State;
@@ -56,83 +57,114 @@ struct Nios2CPUClass {
#define EXCEPTION_ADDRESS 0x00000004
#define FAST_TLB_MISS_ADDRESS 0x00000008
+#define NUM_GP_REGS 32
+#define NUM_CR_REGS 32
-/* GP regs + CR regs + PC */
-#define NUM_CORE_REGS (32 + 32 + 1)
+#ifndef CONFIG_USER_ONLY
+/* 63 shadow register sets; index 0 is the primary register set. */
+#define NUM_REG_SETS 64
+#endif
/* General purpose register aliases */
-#define R_ZERO 0
-#define R_AT 1
-#define R_RET0 2
-#define R_RET1 3
-#define R_ARG0 4
-#define R_ARG1 5
-#define R_ARG2 6
-#define R_ARG3 7
-#define R_ET 24
-#define R_BT 25
-#define R_GP 26
-#define R_SP 27
-#define R_FP 28
-#define R_EA 29
-#define R_BA 30
-#define R_RA 31
+enum {
+ R_ZERO = 0,
+ R_AT = 1,
+ R_RET0 = 2,
+ R_RET1 = 3,
+ R_ARG0 = 4,
+ R_ARG1 = 5,
+ R_ARG2 = 6,
+ R_ARG3 = 7,
+ R_ET = 24,
+ R_BT = 25,
+ R_GP = 26,
+ R_SP = 27,
+ R_FP = 28,
+ R_EA = 29,
+ R_BA = 30,
+ R_SSTATUS = 30,
+ R_RA = 31,
+};
/* Control register aliases */
-#define CR_BASE 32
-#define CR_STATUS (CR_BASE + 0)
-#define CR_STATUS_PIE (1 << 0)
-#define CR_STATUS_U (1 << 1)
-#define CR_STATUS_EH (1 << 2)
-#define CR_STATUS_IH (1 << 3)
-#define CR_STATUS_IL (63 << 4)
-#define CR_STATUS_CRS (63 << 10)
-#define CR_STATUS_PRS (63 << 16)
-#define CR_STATUS_NMI (1 << 22)
-#define CR_STATUS_RSIE (1 << 23)
-#define CR_ESTATUS (CR_BASE + 1)
-#define CR_BSTATUS (CR_BASE + 2)
-#define CR_IENABLE (CR_BASE + 3)
-#define CR_IPENDING (CR_BASE + 4)
-#define CR_CPUID (CR_BASE + 5)
-#define CR_CTL6 (CR_BASE + 6)
-#define CR_EXCEPTION (CR_BASE + 7)
-#define CR_PTEADDR (CR_BASE + 8)
-#define CR_PTEADDR_PTBASE_SHIFT 22
-#define CR_PTEADDR_PTBASE_MASK (0x3FF << CR_PTEADDR_PTBASE_SHIFT)
-#define CR_PTEADDR_VPN_SHIFT 2
-#define CR_PTEADDR_VPN_MASK (0xFFFFF << CR_PTEADDR_VPN_SHIFT)
-#define CR_TLBACC (CR_BASE + 9)
-#define CR_TLBACC_IGN_SHIFT 25
-#define CR_TLBACC_IGN_MASK (0x7F << CR_TLBACC_IGN_SHIFT)
-#define CR_TLBACC_C (1 << 24)
-#define CR_TLBACC_R (1 << 23)
-#define CR_TLBACC_W (1 << 22)
-#define CR_TLBACC_X (1 << 21)
-#define CR_TLBACC_G (1 << 20)
-#define CR_TLBACC_PFN_MASK 0x000FFFFF
-#define CR_TLBMISC (CR_BASE + 10)
-#define CR_TLBMISC_WAY_SHIFT 20
-#define CR_TLBMISC_WAY_MASK (0xF << CR_TLBMISC_WAY_SHIFT)
-#define CR_TLBMISC_RD (1 << 19)
-#define CR_TLBMISC_WR (1 << 18)
-#define CR_TLBMISC_PID_SHIFT 4
-#define CR_TLBMISC_PID_MASK (0x3FFF << CR_TLBMISC_PID_SHIFT)
-#define CR_TLBMISC_DBL (1 << 3)
-#define CR_TLBMISC_BAD (1 << 2)
-#define CR_TLBMISC_PERM (1 << 1)
-#define CR_TLBMISC_D (1 << 0)
-#define CR_ENCINJ (CR_BASE + 11)
-#define CR_BADADDR (CR_BASE + 12)
-#define CR_CONFIG (CR_BASE + 13)
-#define CR_MPUBASE (CR_BASE + 14)
-#define CR_MPUACC (CR_BASE + 15)
-
-/* Other registers */
-#define R_PC 64
+enum {
+ CR_STATUS = 0,
+ CR_ESTATUS = 1,
+ CR_BSTATUS = 2,
+ CR_IENABLE = 3,
+ CR_IPENDING = 4,
+ CR_CPUID = 5,
+ CR_EXCEPTION = 7,
+ CR_PTEADDR = 8,
+ CR_TLBACC = 9,
+ CR_TLBMISC = 10,
+ CR_ENCINJ = 11,
+ CR_BADADDR = 12,
+ CR_CONFIG = 13,
+ CR_MPUBASE = 14,
+ CR_MPUACC = 15,
+};
+
+FIELD(CR_STATUS, PIE, 0, 1)
+FIELD(CR_STATUS, U, 1, 1)
+FIELD(CR_STATUS, EH, 2, 1)
+FIELD(CR_STATUS, IH, 3, 1)
+FIELD(CR_STATUS, IL, 4, 6)
+FIELD(CR_STATUS, CRS, 10, 6)
+FIELD(CR_STATUS, PRS, 16, 6)
+FIELD(CR_STATUS, NMI, 22, 1)
+FIELD(CR_STATUS, RSIE, 23, 1)
+FIELD(CR_STATUS, SRS, 31, 1) /* only in sstatus */
+
+#define CR_STATUS_PIE R_CR_STATUS_PIE_MASK
+#define CR_STATUS_U R_CR_STATUS_U_MASK
+#define CR_STATUS_EH R_CR_STATUS_EH_MASK
+#define CR_STATUS_IH R_CR_STATUS_IH_MASK
+#define CR_STATUS_NMI R_CR_STATUS_NMI_MASK
+#define CR_STATUS_RSIE R_CR_STATUS_RSIE_MASK
+#define CR_STATUS_SRS R_CR_STATUS_SRS_MASK
+
+FIELD(CR_EXCEPTION, CAUSE, 2, 5)
+FIELD(CR_EXCEPTION, ECCFTL, 31, 1)
+
+FIELD(CR_PTEADDR, VPN, 2, 20)
+FIELD(CR_PTEADDR, PTBASE, 22, 10)
+
+FIELD(CR_TLBACC, PFN, 0, 20)
+FIELD(CR_TLBACC, G, 20, 1)
+FIELD(CR_TLBACC, X, 21, 1)
+FIELD(CR_TLBACC, W, 22, 1)
+FIELD(CR_TLBACC, R, 23, 1)
+FIELD(CR_TLBACC, C, 24, 1)
+FIELD(CR_TLBACC, IG, 25, 7)
+
+#define CR_TLBACC_C R_CR_TLBACC_C_MASK
+#define CR_TLBACC_R R_CR_TLBACC_R_MASK
+#define CR_TLBACC_W R_CR_TLBACC_W_MASK
+#define CR_TLBACC_X R_CR_TLBACC_X_MASK
+#define CR_TLBACC_G R_CR_TLBACC_G_MASK
+
+FIELD(CR_TLBMISC, D, 0, 1)
+FIELD(CR_TLBMISC, PERM, 1, 1)
+FIELD(CR_TLBMISC, BAD, 2, 1)
+FIELD(CR_TLBMISC, DBL, 3, 1)
+FIELD(CR_TLBMISC, PID, 4, 14)
+FIELD(CR_TLBMISC, WE, 18, 1)
+FIELD(CR_TLBMISC, RD, 19, 1)
+FIELD(CR_TLBMISC, WAY, 20, 4)
+FIELD(CR_TLBMISC, EE, 24, 1)
+
+#define CR_TLBMISC_EE R_CR_TLBMISC_EE_MASK
+#define CR_TLBMISC_RD R_CR_TLBMISC_RD_MASK
+#define CR_TLBMISC_WE R_CR_TLBMISC_WE_MASK
+#define CR_TLBMISC_DBL R_CR_TLBMISC_DBL_MASK
+#define CR_TLBMISC_BAD R_CR_TLBMISC_BAD_MASK
+#define CR_TLBMISC_PERM R_CR_TLBMISC_PERM_MASK
+#define CR_TLBMISC_D R_CR_TLBMISC_D_MASK
/* Exceptions */
#define EXCP_BREAK 0x1000
+#define EXCP_SEMIHOST 0x1001
#define EXCP_RESET 0
#define EXCP_PRESET 1
#define EXCP_IRQ 2
@@ -142,20 +174,27 @@ struct Nios2CPUClass {
#define EXCP_UNALIGN 6
#define EXCP_UNALIGND 7
#define EXCP_DIV 8
-#define EXCP_SUPERA 9
+#define EXCP_SUPERA_X 9
#define EXCP_SUPERI 10
-#define EXCP_SUPERD 11
-#define EXCP_TLBD 12
-#define EXCP_TLBX 13
-#define EXCP_TLBR 14
-#define EXCP_TLBW 15
+#define EXCP_SUPERA_D 11
+#define EXCP_TLB_X 12
+#define EXCP_TLB_D (0x1000 | EXCP_TLB_X)
+#define EXCP_PERM_X 13
+#define EXCP_PERM_R 14
+#define EXCP_PERM_W 15
#define EXCP_MPUI 16
#define EXCP_MPUD 17
-#define CPU_INTERRUPT_NMI CPU_INTERRUPT_TGT_EXT_3
-
struct CPUArchState {
- uint32_t regs[NUM_CORE_REGS];
+#ifdef CONFIG_USER_ONLY
+ uint32_t regs[NUM_GP_REGS];
+#else
+ uint32_t shadow_regs[NUM_REG_SETS][NUM_GP_REGS];
+ /* Pointer into shadow_regs for the current register set. */
+ uint32_t *regs;
+#endif
+ uint32_t ctrl[NUM_CR_REGS];
+ uint32_t pc;
#if !defined(CONFIG_USER_ONLY)
Nios2MMU mmu;
@@ -163,6 +202,11 @@ struct CPUArchState {
int error_code;
};
+typedef struct {
+ uint32_t writable;
+ uint32_t readonly;
+} ControlRegState;
+
/**
* Nios2CPU:
* @env: #CPUNios2State
@@ -177,7 +221,10 @@ struct ArchCPU {
CPUNegativeOffsetState neg;
CPUNios2State env;
+ bool diverr_present;
bool mmu_present;
+ bool eic_present;
+
uint32_t pid_num_bits;
uint32_t tlb_num_ways;
uint32_t tlb_num_entries;
@@ -186,9 +233,31 @@ struct ArchCPU {
uint32_t reset_addr;
uint32_t exception_addr;
uint32_t fast_tlb_miss_addr;
+
+ /* Bits within each control register which are reserved or readonly. */
+ ControlRegState cr_state[NUM_CR_REGS];
+
+ /* External Interrupt Controller Interface */
+ uint32_t rha; /* Requested handler address */
+ uint32_t ril; /* Requested interrupt level */
+ uint32_t rrs; /* Requested register set */
+ bool rnmi; /* Requested nonmaskable interrupt */
};
+static inline bool nios2_cr_reserved(const ControlRegState *s)
+{
+ return (s->writable | s->readonly) == 0;
+}
+
+static inline void nios2_update_crs(CPUNios2State *env)
+{
+#ifndef CONFIG_USER_ONLY
+ unsigned crs = FIELD_EX32(env->ctrl[CR_STATUS], CR_STATUS, CRS);
+ env->regs = env->shadow_regs[crs];
+#endif
+}
+
void nios2_tcg_init(void);
void nios2_cpu_do_interrupt(CPUState *cs);
void dump_mmu(CPUNios2State *env);
@@ -197,6 +266,8 @@ hwaddr nios2_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr);
G_NORETURN void nios2_cpu_do_unaligned_access(CPUState *cpu, vaddr addr,
MMUAccessType access_type, int mmu_idx,
uintptr_t retaddr);
+G_NORETURN void nios2_cpu_loop_exit_advance(CPUNios2State *env,
+ uintptr_t retaddr);
void do_nios2_semihosting(CPUNios2State *env);
@@ -212,36 +283,35 @@ void do_nios2_semihosting(CPUNios2State *env);
static inline int cpu_mmu_index(CPUNios2State *env, bool ifetch)
{
- return (env->regs[CR_STATUS] & CR_STATUS_U) ? MMU_USER_IDX :
+ return (env->ctrl[CR_STATUS] & CR_STATUS_U) ? MMU_USER_IDX :
MMU_SUPERVISOR_IDX;
}
-#ifdef CONFIG_USER_ONLY
-void nios2_cpu_record_sigsegv(CPUState *cpu, vaddr addr,
- MMUAccessType access_type,
- bool maperr, uintptr_t ra);
-#else
+#ifndef CONFIG_USER_ONLY
bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
MMUAccessType access_type, int mmu_idx,
bool probe, uintptr_t retaddr);
#endif
-static inline int cpu_interrupts_enabled(CPUNios2State *env)
-{
- return env->regs[CR_STATUS] & CR_STATUS_PIE;
-}
-
typedef CPUNios2State CPUArchState;
typedef Nios2CPU ArchCPU;
#include "exec/cpu-all.h"
+FIELD(TBFLAGS, CRS0, 0, 1) /* Set if CRS == 0. */
+FIELD(TBFLAGS, U, 1, 1) /* Overlaps CR_STATUS_U */
+FIELD(TBFLAGS, R0_0, 2, 1) /* Set if R0 == 0. */
+
static inline void cpu_get_tb_cpu_state(CPUNios2State *env, target_ulong *pc,
target_ulong *cs_base, uint32_t *flags)
{
- *pc = env->regs[R_PC];
+ unsigned crs = FIELD_EX32(env->ctrl[CR_STATUS], CR_STATUS, CRS);
+
+ *pc = env->pc;
*cs_base = 0;
- *flags = (env->regs[CR_STATUS] & (CR_STATUS_EH | CR_STATUS_U));
+ *flags = (env->ctrl[CR_STATUS] & CR_STATUS_U)
+ | (crs ? 0 : R_TBFLAGS_CRS0_MASK)
+ | (env->regs[0] ? 0 : R_TBFLAGS_R0_0_MASK);
}
#endif /* NIOS2_CPU_H */
diff --git a/target/nios2/helper.c b/target/nios2/helper.c
index e5c9865..bb3b09e 100644
--- a/target/nios2/helper.c
+++ b/target/nios2/helper.c
@@ -28,176 +28,234 @@
#include "exec/helper-proto.h"
#include "semihosting/semihost.h"
-#if defined(CONFIG_USER_ONLY)
-void nios2_cpu_do_interrupt(CPUState *cs)
+static void do_exception(Nios2CPU *cpu, uint32_t exception_addr,
+ uint32_t tlbmisc_set, bool is_break)
{
- Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- cs->exception_index = -1;
- env->regs[R_EA] = env->regs[R_PC] + 4;
-}
+ CPUState *cs = CPU(cpu);
+ uint32_t old_status = env->ctrl[CR_STATUS];
+ uint32_t new_status = old_status;
-void nios2_cpu_record_sigsegv(CPUState *cs, vaddr addr,
- MMUAccessType access_type,
- bool maperr, uintptr_t retaddr)
-{
- /* FIXME: Disentangle kuser page from linux-user sigsegv handling. */
- cs->exception_index = 0xaa;
- cpu_loop_exit_restore(cs, retaddr);
-}
+ /* With shadow regs, exceptions are always taken into CRS 0. */
+ new_status &= ~R_CR_STATUS_CRS_MASK;
+ env->regs = env->shadow_regs[0];
-#else /* !CONFIG_USER_ONLY */
+ if ((old_status & CR_STATUS_EH) == 0) {
+ int r_ea = R_EA, cr_es = CR_ESTATUS;
-void nios2_cpu_do_interrupt(CPUState *cs)
-{
- Nios2CPU *cpu = NIOS2_CPU(cs);
- CPUNios2State *env = &cpu->env;
+ if (is_break) {
+ r_ea = R_BA;
+ cr_es = CR_BSTATUS;
+ }
+ env->ctrl[cr_es] = old_status;
+ env->regs[r_ea] = env->pc;
+
+ if (cpu->mmu_present) {
+ new_status |= CR_STATUS_EH;
+
+ /*
+ * There are 4 bits that are always written.
+ * Explicitly clear them, to be set via the argument.
+ */
+ env->ctrl[CR_TLBMISC] &= ~(CR_TLBMISC_D |
+ CR_TLBMISC_PERM |
+ CR_TLBMISC_BAD |
+ CR_TLBMISC_DBL);
+ env->ctrl[CR_TLBMISC] |= tlbmisc_set;
+ }
- switch (cs->exception_index) {
- case EXCP_IRQ:
- assert(env->regs[CR_STATUS] & CR_STATUS_PIE);
+ /*
+ * With shadow regs, and EH == 0, PRS is set from CRS.
+ * At least, so says Table 3-9, and some other text,
+ * though Table 3-38 says otherwise.
+ */
+ new_status = FIELD_DP32(new_status, CR_STATUS, PRS,
+ FIELD_EX32(old_status, CR_STATUS, CRS));
+ }
- qemu_log_mask(CPU_LOG_INT, "interrupt at pc=%x\n", env->regs[R_PC]);
+ new_status &= ~(CR_STATUS_PIE | CR_STATUS_U);
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[CR_STATUS] |= CR_STATUS_IH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
+ env->ctrl[CR_STATUS] = new_status;
+ if (!is_break) {
+ env->ctrl[CR_EXCEPTION] = FIELD_DP32(0, CR_EXCEPTION, CAUSE,
+ cs->exception_index);
+ }
+ env->pc = exception_addr;
+}
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
+static void do_iic_irq(Nios2CPU *cpu)
+{
+ do_exception(cpu, cpu->exception_addr, 0, false);
+}
- env->regs[R_EA] = env->regs[R_PC] + 4;
- env->regs[R_PC] = cpu->exception_addr;
- break;
+static void do_eic_irq(Nios2CPU *cpu)
+{
+ CPUNios2State *env = &cpu->env;
+ uint32_t old_status = env->ctrl[CR_STATUS];
+ uint32_t new_status = old_status;
+ uint32_t old_rs = FIELD_EX32(old_status, CR_STATUS, CRS);
+ uint32_t new_rs = cpu->rrs;
+
+ new_status = FIELD_DP32(new_status, CR_STATUS, CRS, new_rs);
+ new_status = FIELD_DP32(new_status, CR_STATUS, IL, cpu->ril);
+ new_status = FIELD_DP32(new_status, CR_STATUS, NMI, cpu->rnmi);
+ new_status &= ~(CR_STATUS_RSIE | CR_STATUS_U);
+ new_status |= CR_STATUS_IH;
+
+ if (!(new_status & CR_STATUS_EH)) {
+ new_status = FIELD_DP32(new_status, CR_STATUS, PRS, old_rs);
+ if (new_rs == 0) {
+ env->ctrl[CR_ESTATUS] = old_status;
+ } else {
+ if (new_rs != old_rs) {
+ old_status |= CR_STATUS_SRS;
+ }
+ env->shadow_regs[new_rs][R_SSTATUS] = old_status;
+ }
+ env->shadow_regs[new_rs][R_EA] = env->pc;
+ }
- case EXCP_TLBD:
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- qemu_log_mask(CPU_LOG_INT, "TLB MISS (fast) at pc=%x\n",
- env->regs[R_PC]);
+ env->ctrl[CR_STATUS] = new_status;
+ nios2_update_crs(env);
- /* Fast TLB miss */
- /* Variation from the spec. Table 3-35 of the cpu reference shows
- * estatus not being changed for TLB miss but this appears to
- * be incorrect. */
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
+ env->pc = cpu->rha;
+}
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
+void nios2_cpu_do_interrupt(CPUState *cs)
+{
+ Nios2CPU *cpu = NIOS2_CPU(cs);
+ CPUNios2State *env = &cpu->env;
+ uint32_t tlbmisc_set = 0;
- env->regs[CR_TLBMISC] &= ~CR_TLBMISC_DBL;
- env->regs[CR_TLBMISC] |= CR_TLBMISC_WR;
+ if (qemu_loglevel_mask(CPU_LOG_INT)) {
+ const char *name = NULL;
- env->regs[R_EA] = env->regs[R_PC] + 4;
- env->regs[R_PC] = cpu->fast_tlb_miss_addr;
+ switch (cs->exception_index) {
+ case EXCP_IRQ:
+ name = "interrupt";
+ break;
+ case EXCP_TLB_X:
+ case EXCP_TLB_D:
+ if (env->ctrl[CR_STATUS] & CR_STATUS_EH) {
+ name = "TLB MISS (double)";
+ } else {
+ name = "TLB MISS (fast)";
+ }
+ break;
+ case EXCP_PERM_R:
+ case EXCP_PERM_W:
+ case EXCP_PERM_X:
+ name = "TLB PERM";
+ break;
+ case EXCP_SUPERA_X:
+ case EXCP_SUPERA_D:
+ name = "SUPERVISOR (address)";
+ break;
+ case EXCP_SUPERI:
+ name = "SUPERVISOR (insn)";
+ break;
+ case EXCP_ILLEGAL:
+ name = "ILLEGAL insn";
+ break;
+ case EXCP_UNALIGN:
+ name = "Misaligned (data)";
+ break;
+ case EXCP_UNALIGND:
+ name = "Misaligned (destination)";
+ break;
+ case EXCP_DIV:
+ name = "DIV error";
+ break;
+ case EXCP_TRAP:
+ name = "TRAP insn";
+ break;
+ case EXCP_BREAK:
+ name = "BREAK insn";
+ break;
+ case EXCP_SEMIHOST:
+ name = "SEMIHOST insn";
+ break;
+ }
+ if (name) {
+ qemu_log("%s at pc=0x%08x\n", name, env->pc);
} else {
- qemu_log_mask(CPU_LOG_INT, "TLB MISS (double) at pc=%x\n",
- env->regs[R_PC]);
-
- /* Double TLB miss */
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- env->regs[CR_TLBMISC] |= CR_TLBMISC_DBL;
+ qemu_log("Unknown exception %d at pc=0x%08x\n",
+ cs->exception_index, env->pc);
+ }
+ }
- env->regs[R_PC] = cpu->exception_addr;
+ switch (cs->exception_index) {
+ case EXCP_IRQ:
+ /* Note that PC is advanced for interrupts as well. */
+ env->pc += 4;
+ if (cpu->eic_present) {
+ do_eic_irq(cpu);
+ } else {
+ do_iic_irq(cpu);
}
break;
- case EXCP_TLBR:
- case EXCP_TLBW:
- case EXCP_TLBX:
- qemu_log_mask(CPU_LOG_INT, "TLB PERM at pc=%x\n", env->regs[R_PC]);
-
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_TLBMISC] |= CR_TLBMISC_WR;
+ case EXCP_TLB_D:
+ tlbmisc_set = CR_TLBMISC_D;
+ /* fall through */
+ case EXCP_TLB_X:
+ if (env->ctrl[CR_STATUS] & CR_STATUS_EH) {
+ tlbmisc_set |= CR_TLBMISC_DBL;
+ /*
+ * Normally, we don't write to tlbmisc unless !EH,
+ * so do it manually for the double-tlb miss exception.
+ */
+ env->ctrl[CR_TLBMISC] &= ~(CR_TLBMISC_D |
+ CR_TLBMISC_PERM |
+ CR_TLBMISC_BAD);
+ env->ctrl[CR_TLBMISC] |= tlbmisc_set;
+ do_exception(cpu, cpu->exception_addr, 0, false);
+ } else {
+ tlbmisc_set |= CR_TLBMISC_WE;
+ do_exception(cpu, cpu->fast_tlb_miss_addr, tlbmisc_set, false);
}
-
- env->regs[R_EA] = env->regs[R_PC] + 4;
- env->regs[R_PC] = cpu->exception_addr;
break;
- case EXCP_SUPERA:
- case EXCP_SUPERI:
- case EXCP_SUPERD:
- qemu_log_mask(CPU_LOG_INT, "SUPERVISOR exception at pc=%x\n",
- env->regs[R_PC]);
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[R_EA] = env->regs[R_PC] + 4;
+ case EXCP_PERM_R:
+ case EXCP_PERM_W:
+ tlbmisc_set = CR_TLBMISC_D;
+ /* fall through */
+ case EXCP_PERM_X:
+ tlbmisc_set |= CR_TLBMISC_PERM;
+ if (!(env->ctrl[CR_STATUS] & CR_STATUS_EH)) {
+ tlbmisc_set |= CR_TLBMISC_WE;
}
+ do_exception(cpu, cpu->exception_addr, tlbmisc_set, false);
+ break;
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- env->regs[R_PC] = cpu->exception_addr;
+ case EXCP_SUPERA_D:
+ case EXCP_UNALIGN:
+ tlbmisc_set = CR_TLBMISC_D;
+ /* fall through */
+ case EXCP_SUPERA_X:
+ case EXCP_UNALIGND:
+ tlbmisc_set |= CR_TLBMISC_BAD;
+ do_exception(cpu, cpu->exception_addr, tlbmisc_set, false);
break;
+ case EXCP_SUPERI:
case EXCP_ILLEGAL:
+ case EXCP_DIV:
case EXCP_TRAP:
- qemu_log_mask(CPU_LOG_INT, "TRAP exception at pc=%x\n",
- env->regs[R_PC]);
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_ESTATUS] = env->regs[CR_STATUS];
- env->regs[R_EA] = env->regs[R_PC] + 4;
- }
-
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
-
- env->regs[R_PC] = cpu->exception_addr;
+ do_exception(cpu, cpu->exception_addr, 0, false);
break;
case EXCP_BREAK:
- qemu_log_mask(CPU_LOG_INT, "BREAK exception at pc=%x\n",
- env->regs[R_PC]);
- /* The semihosting instruction is "break 1". */
- if (semihosting_enabled() &&
- cpu_ldl_code(env, env->regs[R_PC]) == 0x003da07a) {
- qemu_log_mask(CPU_LOG_INT, "Entering semihosting\n");
- env->regs[R_PC] += 4;
- do_nios2_semihosting(env);
- break;
- }
-
- if ((env->regs[CR_STATUS] & CR_STATUS_EH) == 0) {
- env->regs[CR_BSTATUS] = env->regs[CR_STATUS];
- env->regs[R_BA] = env->regs[R_PC] + 4;
- }
-
- env->regs[CR_STATUS] |= CR_STATUS_EH;
- env->regs[CR_STATUS] &= ~(CR_STATUS_PIE | CR_STATUS_U);
-
- env->regs[CR_EXCEPTION] &= ~(0x1F << 2);
- env->regs[CR_EXCEPTION] |= (cs->exception_index & 0x1F) << 2;
+ do_exception(cpu, cpu->exception_addr, 0, true);
+ break;
- env->regs[R_PC] = cpu->exception_addr;
+ case EXCP_SEMIHOST:
+ do_nios2_semihosting(env);
break;
default:
- cpu_abort(cs, "unhandled exception type=%d\n",
- cs->exception_index);
- break;
+ cpu_abort(cs, "unhandled exception type=%d\n", cs->exception_index);
}
}
@@ -232,9 +290,9 @@ void nios2_cpu_do_unaligned_access(CPUState *cs, vaddr addr,
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- env->regs[CR_BADADDR] = addr;
- env->regs[CR_EXCEPTION] = EXCP_UNALIGN << 2;
- helper_raise_exception(env, EXCP_UNALIGN);
+ env->ctrl[CR_BADADDR] = addr;
+ cs->exception_index = EXCP_UNALIGN;
+ nios2_cpu_loop_exit_advance(env, retaddr);
}
bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
@@ -243,7 +301,7 @@ bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
{
Nios2CPU *cpu = NIOS2_CPU(cs);
CPUNios2State *env = &cpu->env;
- unsigned int excp = EXCP_TLBD;
+ unsigned int excp;
target_ulong vaddr, paddr;
Nios2MMULookup lu;
unsigned int hit;
@@ -270,9 +328,10 @@ bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
if (probe) {
return false;
}
- cs->exception_index = EXCP_SUPERA;
- env->regs[CR_BADADDR] = address;
- cpu_loop_exit_restore(cs, retaddr);
+ cs->exception_index = (access_type == MMU_INST_FETCH
+ ? EXCP_SUPERA_X : EXCP_SUPERA_D);
+ env->ctrl[CR_BADADDR] = address;
+ nios2_cpu_loop_exit_advance(env, retaddr);
}
}
@@ -291,25 +350,23 @@ bool nios2_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
}
/* Permission violation */
- excp = (access_type == MMU_DATA_LOAD ? EXCP_TLBR :
- access_type == MMU_DATA_STORE ? EXCP_TLBW : EXCP_TLBX);
+ excp = (access_type == MMU_DATA_LOAD ? EXCP_PERM_R :
+ access_type == MMU_DATA_STORE ? EXCP_PERM_W : EXCP_PERM_X);
+ } else {
+ excp = (access_type == MMU_INST_FETCH ? EXCP_TLB_X: EXCP_TLB_D);
}
if (probe) {
return false;
}
- if (access_type == MMU_INST_FETCH) {
- env->regs[CR_TLBMISC] &= ~CR_TLBMISC_D;
- } else {
- env->regs[CR_TLBMISC] |= CR_TLBMISC_D;
- }
- env->regs[CR_PTEADDR] &= CR_PTEADDR_PTBASE_MASK;
- env->regs[CR_PTEADDR] |= (address >> 10) & CR_PTEADDR_VPN_MASK;
- env->mmu.pteaddr_wr = env->regs[CR_PTEADDR];
+ env->ctrl[CR_TLBMISC] = FIELD_DP32(env->ctrl[CR_TLBMISC], CR_TLBMISC, D,
+ access_type != MMU_INST_FETCH);
+ env->ctrl[CR_PTEADDR] = FIELD_DP32(env->ctrl[CR_PTEADDR], CR_PTEADDR, VPN,
+ address >> TARGET_PAGE_BITS);
+ env->mmu.pteaddr_wr = env->ctrl[CR_PTEADDR];
cs->exception_index = excp;
- env->regs[CR_BADADDR] = address;
- cpu_loop_exit_restore(cs, retaddr);
+ env->ctrl[CR_BADADDR] = address;
+ nios2_cpu_loop_exit_advance(env, retaddr);
}
-#endif /* !CONFIG_USER_ONLY */
diff --git a/target/nios2/helper.h b/target/nios2/helper.h
index a44ecfd..1648d76 100644
--- a/target/nios2/helper.h
+++ b/target/nios2/helper.h
@@ -19,8 +19,13 @@
*/
DEF_HELPER_FLAGS_2(raise_exception, TCG_CALL_NO_WG, noreturn, env, i32)
+DEF_HELPER_FLAGS_3(divs, TCG_CALL_NO_WG, s32, env, s32, s32)
+DEF_HELPER_FLAGS_3(divu, TCG_CALL_NO_WG, i32, env, i32, i32)
#if !defined(CONFIG_USER_ONLY)
+DEF_HELPER_3(eret, noreturn, env, i32, i32)
+DEF_HELPER_FLAGS_2(rdprs, TCG_CALL_NO_WG, i32, env, i32)
+DEF_HELPER_3(wrprs, void, env, i32, i32)
DEF_HELPER_2(mmu_write_tlbacc, void, env, i32)
DEF_HELPER_2(mmu_write_tlbmisc, void, env, i32)
DEF_HELPER_2(mmu_write_pteaddr, void, env, i32)
diff --git a/target/nios2/meson.build b/target/nios2/meson.build
index 62b3847..2bd60ba 100644
--- a/target/nios2/meson.build
+++ b/target/nios2/meson.build
@@ -1,14 +1,17 @@
nios2_ss = ss.source_set()
nios2_ss.add(files(
'cpu.c',
- 'helper.c',
'nios2-semi.c',
'op_helper.c',
'translate.c',
))
nios2_softmmu_ss = ss.source_set()
-nios2_softmmu_ss.add(files('monitor.c', 'mmu.c'))
+nios2_softmmu_ss.add(files(
+ 'helper.c',
+ 'monitor.c',
+ 'mmu.c'
+))
target_arch += {'nios2': nios2_ss}
target_softmmu_arch += {'nios2': nios2_softmmu_ss}
diff --git a/target/nios2/mmu.c b/target/nios2/mmu.c
index 4daab2a..d9b690b 100644
--- a/target/nios2/mmu.c
+++ b/target/nios2/mmu.c
@@ -33,7 +33,7 @@ unsigned int mmu_translate(CPUNios2State *env,
target_ulong vaddr, int rw, int mmu_idx)
{
Nios2CPU *cpu = env_archcpu(env);
- int pid = (env->mmu.tlbmisc_wr & CR_TLBMISC_PID_MASK) >> 4;
+ int pid = FIELD_EX32(env->mmu.tlbmisc_wr, CR_TLBMISC, PID);
int vpn = vaddr >> 12;
int way, n_ways = cpu->tlb_num_ways;
@@ -49,7 +49,7 @@ unsigned int mmu_translate(CPUNios2State *env,
}
lu->vaddr = vaddr & TARGET_PAGE_MASK;
- lu->paddr = (entry->data & CR_TLBACC_PFN_MASK) << TARGET_PAGE_BITS;
+ lu->paddr = FIELD_EX32(entry->data, CR_TLBACC, PFN) << TARGET_PAGE_BITS;
lu->prot = ((entry->data & CR_TLBACC_R) ? PAGE_READ : 0) |
((entry->data & CR_TLBACC_W) ? PAGE_WRITE : 0) |
((entry->data & CR_TLBACC_X) ? PAGE_EXEC : 0);
@@ -86,27 +86,27 @@ void helper_mmu_write_tlbacc(CPUNios2State *env, uint32_t v)
CPUState *cs = env_cpu(env);
Nios2CPU *cpu = env_archcpu(env);
- trace_nios2_mmu_write_tlbacc(v >> CR_TLBACC_IGN_SHIFT,
+ trace_nios2_mmu_write_tlbacc(FIELD_EX32(v, CR_TLBACC, IG),
(v & CR_TLBACC_C) ? 'C' : '.',
(v & CR_TLBACC_R) ? 'R' : '.',
(v & CR_TLBACC_W) ? 'W' : '.',
(v & CR_TLBACC_X) ? 'X' : '.',
(v & CR_TLBACC_G) ? 'G' : '.',
- v & CR_TLBACC_PFN_MASK);
+ FIELD_EX32(v, CR_TLBACC, PFN));
/* if tlbmisc.WE == 1 then trigger a TLB write on writes to TLBACC */
- if (env->regs[CR_TLBMISC] & CR_TLBMISC_WR) {
- int way = (env->regs[CR_TLBMISC] >> CR_TLBMISC_WAY_SHIFT);
- int vpn = (env->mmu.pteaddr_wr & CR_PTEADDR_VPN_MASK) >> 2;
- int pid = (env->mmu.tlbmisc_wr & CR_TLBMISC_PID_MASK) >> 4;
- int g = (v & CR_TLBACC_G) ? 1 : 0;
- int valid = ((vpn & CR_TLBACC_PFN_MASK) < 0xC0000) ? 1 : 0;
+ if (env->ctrl[CR_TLBMISC] & CR_TLBMISC_WE) {
+ int way = FIELD_EX32(env->ctrl[CR_TLBMISC], CR_TLBMISC, WAY);
+ int vpn = FIELD_EX32(env->mmu.pteaddr_wr, CR_PTEADDR, VPN);
+ int pid = FIELD_EX32(env->mmu.tlbmisc_wr, CR_TLBMISC, PID);
+ int g = FIELD_EX32(v, CR_TLBACC, G);
+ int valid = FIELD_EX32(vpn, CR_TLBACC, PFN) < 0xC0000;
Nios2TLBEntry *entry =
&env->mmu.tlb[(way * cpu->tlb_num_ways) +
(vpn & env->mmu.tlb_entry_mask)];
uint32_t newTag = (vpn << 12) | (g << 11) | (valid << 10) | pid;
uint32_t newData = v & (CR_TLBACC_C | CR_TLBACC_R | CR_TLBACC_W |
- CR_TLBACC_X | CR_TLBACC_PFN_MASK);
+ CR_TLBACC_X | R_CR_TLBACC_PFN_MASK);
if ((entry->tag != newTag) || (entry->data != newData)) {
if (entry->tag & (1 << 10)) {
@@ -117,10 +117,9 @@ void helper_mmu_write_tlbacc(CPUNios2State *env, uint32_t v)
entry->data = newData;
}
/* Auto-increment tlbmisc.WAY */
- env->regs[CR_TLBMISC] =
- (env->regs[CR_TLBMISC] & ~CR_TLBMISC_WAY_MASK) |
- (((way + 1) & (cpu->tlb_num_ways - 1)) <<
- CR_TLBMISC_WAY_SHIFT);
+ env->ctrl[CR_TLBMISC] = FIELD_DP32(env->ctrl[CR_TLBMISC],
+ CR_TLBMISC, WAY,
+ (way + 1) & (cpu->tlb_num_ways - 1));
}
/* Writes to TLBACC don't change the read-back value */
@@ -130,40 +129,41 @@ void helper_mmu_write_tlbacc(CPUNios2State *env, uint32_t v)
void helper_mmu_write_tlbmisc(CPUNios2State *env, uint32_t v)
{
Nios2CPU *cpu = env_archcpu(env);
+ uint32_t new_pid = FIELD_EX32(v, CR_TLBMISC, PID);
+ uint32_t old_pid = FIELD_EX32(env->mmu.tlbmisc_wr, CR_TLBMISC, PID);
+ uint32_t way = FIELD_EX32(v, CR_TLBMISC, WAY);
- trace_nios2_mmu_write_tlbmisc(v >> CR_TLBMISC_WAY_SHIFT,
+ trace_nios2_mmu_write_tlbmisc(way,
(v & CR_TLBMISC_RD) ? 'R' : '.',
- (v & CR_TLBMISC_WR) ? 'W' : '.',
+ (v & CR_TLBMISC_WE) ? 'W' : '.',
(v & CR_TLBMISC_DBL) ? '2' : '.',
(v & CR_TLBMISC_BAD) ? 'B' : '.',
(v & CR_TLBMISC_PERM) ? 'P' : '.',
(v & CR_TLBMISC_D) ? 'D' : '.',
- (v & CR_TLBMISC_PID_MASK) >> 4);
+ new_pid);
- if ((v & CR_TLBMISC_PID_MASK) !=
- (env->mmu.tlbmisc_wr & CR_TLBMISC_PID_MASK)) {
- mmu_flush_pid(env, (env->mmu.tlbmisc_wr & CR_TLBMISC_PID_MASK) >>
- CR_TLBMISC_PID_SHIFT);
+ if (new_pid != old_pid) {
+ mmu_flush_pid(env, old_pid);
}
+
/* if tlbmisc.RD == 1 then trigger a TLB read on writes to TLBMISC */
if (v & CR_TLBMISC_RD) {
- int way = (v >> CR_TLBMISC_WAY_SHIFT);
- int vpn = (env->mmu.pteaddr_wr & CR_PTEADDR_VPN_MASK) >> 2;
+ int vpn = FIELD_EX32(env->mmu.pteaddr_wr, CR_PTEADDR, VPN);
Nios2TLBEntry *entry =
&env->mmu.tlb[(way * cpu->tlb_num_ways) +
(vpn & env->mmu.tlb_entry_mask)];
- env->regs[CR_TLBACC] &= CR_TLBACC_IGN_MASK;
- env->regs[CR_TLBACC] |= entry->data;
- env->regs[CR_TLBACC] |= (entry->tag & (1 << 11)) ? CR_TLBACC_G : 0;
- env->regs[CR_TLBMISC] =
- (v & ~CR_TLBMISC_PID_MASK) |
- ((entry->tag & ((1 << cpu->pid_num_bits) - 1)) <<
- CR_TLBMISC_PID_SHIFT);
- env->regs[CR_PTEADDR] &= ~CR_PTEADDR_VPN_MASK;
- env->regs[CR_PTEADDR] |= (entry->tag >> 12) << CR_PTEADDR_VPN_SHIFT;
+ env->ctrl[CR_TLBACC] &= R_CR_TLBACC_IG_MASK;
+ env->ctrl[CR_TLBACC] |= entry->data;
+ env->ctrl[CR_TLBACC] |= (entry->tag & (1 << 11)) ? CR_TLBACC_G : 0;
+ env->ctrl[CR_TLBMISC] = FIELD_DP32(v, CR_TLBMISC, PID,
+ entry->tag &
+ ((1 << cpu->pid_num_bits) - 1));
+ env->ctrl[CR_PTEADDR] = FIELD_DP32(env->ctrl[CR_PTEADDR],
+ CR_PTEADDR, VPN,
+ entry->tag >> TARGET_PAGE_BITS);
} else {
- env->regs[CR_TLBMISC] = v;
+ env->ctrl[CR_TLBMISC] = v;
}
env->mmu.tlbmisc_wr = v;
@@ -171,12 +171,12 @@ void helper_mmu_write_tlbmisc(CPUNios2State *env, uint32_t v)
void helper_mmu_write_pteaddr(CPUNios2State *env, uint32_t v)
{
- trace_nios2_mmu_write_pteaddr(v >> CR_PTEADDR_PTBASE_SHIFT,
- (v & CR_PTEADDR_VPN_MASK) >> CR_PTEADDR_VPN_SHIFT);
+ trace_nios2_mmu_write_pteaddr(FIELD_EX32(v, CR_PTEADDR, PTBASE),
+ FIELD_EX32(v, CR_PTEADDR, VPN));
/* Writes to PTEADDR don't change the read-back VPN value */
- env->regs[CR_PTEADDR] = (v & ~CR_PTEADDR_VPN_MASK) |
- (env->regs[CR_PTEADDR] & CR_PTEADDR_VPN_MASK);
+ env->ctrl[CR_PTEADDR] = ((v & ~R_CR_PTEADDR_VPN_MASK) |
+ (env->ctrl[CR_PTEADDR] & R_CR_PTEADDR_VPN_MASK));
env->mmu.pteaddr_wr = v;
}
@@ -207,7 +207,7 @@ void dump_mmu(CPUNios2State *env)
entry->tag >> 12,
entry->tag & ((1 << cpu->pid_num_bits) - 1),
(entry->tag & (1 << 11)) ? 'G' : '-',
- entry->data & CR_TLBACC_PFN_MASK,
+ FIELD_EX32(entry->data, CR_TLBACC, PFN),
(entry->data & CR_TLBACC_C) ? 'C' : '-',
(entry->data & CR_TLBACC_R) ? 'R' : '-',
(entry->data & CR_TLBACC_W) ? 'W' : '-',
diff --git a/target/nios2/op_helper.c b/target/nios2/op_helper.c
index caa885f..2e30d0a 100644
--- a/target/nios2/op_helper.c
+++ b/target/nios2/op_helper.c
@@ -30,3 +30,91 @@ void helper_raise_exception(CPUNios2State *env, uint32_t index)
cs->exception_index = index;
cpu_loop_exit(cs);
}
+
+void nios2_cpu_loop_exit_advance(CPUNios2State *env, uintptr_t retaddr)
+{
+ CPUState *cs = env_cpu(env);
+
+ /*
+ * Note that PC is advanced for all hardware exceptions.
+ * Do this here, rather than in restore_state_to_opc(),
+ * lest we affect QEMU internal exceptions, like EXCP_DEBUG.
+ */
+ cpu_restore_state(cs, retaddr, true);
+ env->pc += 4;
+ cpu_loop_exit(cs);
+}
+
+static void maybe_raise_div(CPUNios2State *env, uintptr_t ra)
+{
+ Nios2CPU *cpu = env_archcpu(env);
+ CPUState *cs = env_cpu(env);
+
+ if (cpu->diverr_present) {
+ cs->exception_index = EXCP_DIV;
+ nios2_cpu_loop_exit_advance(env, ra);
+ }
+}
+
+int32_t helper_divs(CPUNios2State *env, int32_t num, int32_t den)
+{
+ if (unlikely(den == 0) || unlikely(den == -1 && num == INT32_MIN)) {
+ maybe_raise_div(env, GETPC());
+ return num; /* undefined */
+ }
+ return num / den;
+}
+
+uint32_t helper_divu(CPUNios2State *env, uint32_t num, uint32_t den)
+{
+ if (unlikely(den == 0)) {
+ maybe_raise_div(env, GETPC());
+ return num; /* undefined */
+ }
+ return num / den;
+}
+
+#ifndef CONFIG_USER_ONLY
+void helper_eret(CPUNios2State *env, uint32_t new_status, uint32_t new_pc)
+{
+ Nios2CPU *cpu = env_archcpu(env);
+ CPUState *cs = env_cpu(env);
+
+ if (unlikely(new_pc & 3)) {
+ env->ctrl[CR_BADADDR] = new_pc;
+ cs->exception_index = EXCP_UNALIGND;
+ nios2_cpu_loop_exit_advance(env, GETPC());
+ }
+
+ /*
+ * None of estatus, bstatus, or sstatus have constraints on write;
+ * do not allow reserved fields in status to be set.
+ * When shadow registers are enabled, eret *does* restore CRS.
+ * Rather than testing eic_present to decide, mask CRS out of
+ * the set of readonly fields.
+ */
+ new_status &= cpu->cr_state[CR_STATUS].writable |
+ (cpu->cr_state[CR_STATUS].readonly & R_CR_STATUS_CRS_MASK);
+
+ env->ctrl[CR_STATUS] = new_status;
+ env->pc = new_pc;
+ nios2_update_crs(env);
+ cpu_loop_exit(cs);
+}
+
+/*
+ * RDPRS and WRPRS are implemented out of line so that if PRS == CRS,
+ * all of the tcg global temporaries are synced back to ENV.
+ */
+uint32_t helper_rdprs(CPUNios2State *env, uint32_t regno)
+{
+ unsigned prs = FIELD_EX32(env->ctrl[CR_STATUS], CR_STATUS, PRS);
+ return env->shadow_regs[prs][regno];
+}
+
+void helper_wrprs(CPUNios2State *env, uint32_t regno, uint32_t val)
+{
+ unsigned prs = FIELD_EX32(env->ctrl[CR_STATUS], CR_STATUS, PRS);
+ env->shadow_regs[prs][regno] = val;
+}
+#endif /* !CONFIG_USER_ONLY */
diff --git a/target/nios2/translate.c b/target/nios2/translate.c
index 89b97ef..3a037a6 100644
--- a/target/nios2/translate.c
+++ b/target/nios2/translate.c
@@ -33,9 +33,9 @@
#include "exec/translator.h"
#include "qemu/qemu-print.h"
#include "exec/gen-icount.h"
+#include "semihosting/semihost.h"
/* is_jmp field values */
-#define DISAS_JUMP DISAS_TARGET_0 /* only pc was modified dynamically */
#define DISAS_UPDATE DISAS_TARGET_1 /* cpu state was modified dynamically */
#define INSTRUCTION_FLG(func, flags) { (func), (flags) }
@@ -52,32 +52,53 @@
#define INSN_R_TYPE 0x3A
/* I-Type instruction parsing */
+typedef struct {
+ uint8_t op;
+ union {
+ uint16_t u;
+ int16_t s;
+ } imm16;
+ uint8_t b;
+ uint8_t a;
+} InstrIType;
+
#define I_TYPE(instr, code) \
- struct { \
- uint8_t op; \
- union { \
- uint16_t u; \
- int16_t s; \
- } imm16; \
- uint8_t b; \
- uint8_t a; \
- } (instr) = { \
+ InstrIType (instr) = { \
.op = extract32((code), 0, 6), \
.imm16.u = extract32((code), 6, 16), \
.b = extract32((code), 22, 5), \
.a = extract32((code), 27, 5), \
}
+typedef target_ulong ImmFromIType(const InstrIType *);
+
+static target_ulong imm_unsigned(const InstrIType *i)
+{
+ return i->imm16.u;
+}
+
+static target_ulong imm_signed(const InstrIType *i)
+{
+ return i->imm16.s;
+}
+
+static target_ulong imm_shifted(const InstrIType *i)
+{
+ return i->imm16.u << 16;
+}
+
/* R-Type instruction parsing */
+typedef struct {
+ uint8_t op;
+ uint8_t imm5;
+ uint8_t opx;
+ uint8_t c;
+ uint8_t b;
+ uint8_t a;
+} InstrRType;
+
#define R_TYPE(instr, code) \
- struct { \
- uint8_t op; \
- uint8_t imm5; \
- uint8_t opx; \
- uint8_t c; \
- uint8_t b; \
- uint8_t a; \
- } (instr) = { \
+ InstrRType (instr) = { \
.op = extract32((code), 0, 6), \
.imm5 = extract32((code), 6, 5), \
.opx = extract32((code), 11, 6), \
@@ -87,23 +108,36 @@
}
/* J-Type instruction parsing */
+typedef struct {
+ uint8_t op;
+ uint32_t imm26;
+} InstrJType;
+
#define J_TYPE(instr, code) \
- struct { \
- uint8_t op; \
- uint32_t imm26; \
- } (instr) = { \
+ InstrJType (instr) = { \
.op = extract32((code), 0, 6), \
.imm26 = extract32((code), 6, 26), \
}
+typedef void GenFn2i(TCGv, TCGv, target_long);
+typedef void GenFn3(TCGv, TCGv, TCGv);
+typedef void GenFn4(TCGv, TCGv, TCGv, TCGv);
+
typedef struct DisasContext {
DisasContextBase base;
- TCGv_i32 zero;
target_ulong pc;
int mem_idx;
+ uint32_t tb_flags;
+ TCGv sink;
+ const ControlRegState *cr_state;
+ bool eic_present;
} DisasContext;
-static TCGv cpu_R[NUM_CORE_REGS];
+static TCGv cpu_R[NUM_GP_REGS];
+static TCGv cpu_pc;
+#ifndef CONFIG_USER_ONLY
+static TCGv cpu_crs_R[NUM_GP_REGS];
+#endif
typedef struct Nios2Instruction {
void (*handler)(DisasContext *dc, uint32_t code, uint32_t flags);
@@ -122,31 +156,57 @@ static uint8_t get_opxcode(uint32_t code)
return instr.opx;
}
-static TCGv load_zero(DisasContext *dc)
+static TCGv load_gpr(DisasContext *dc, unsigned reg)
{
- if (!dc->zero) {
- dc->zero = tcg_const_i32(0);
+ assert(reg < NUM_GP_REGS);
+
+ /*
+ * With shadow register sets, register r0 does not necessarily contain 0,
+ * but it is overwhelmingly likely that it does -- software is supposed
+ * to have set r0 to 0 in every shadow register set before use.
+ */
+ if (unlikely(reg == R_ZERO) && FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0)) {
+ return tcg_constant_tl(0);
+ }
+ if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) {
+ return cpu_R[reg];
}
- return dc->zero;
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ return cpu_crs_R[reg];
+#endif
}
-static TCGv load_gpr(DisasContext *dc, uint8_t reg)
+static TCGv dest_gpr(DisasContext *dc, unsigned reg)
{
- if (likely(reg != R_ZERO)) {
+ assert(reg < NUM_GP_REGS);
+
+ /*
+ * The spec for shadow register sets isn't clear, but we assume that
+ * writes to r0 are discarded regardless of CRS.
+ */
+ if (unlikely(reg == R_ZERO)) {
+ if (dc->sink == NULL) {
+ dc->sink = tcg_temp_new();
+ }
+ return dc->sink;
+ }
+ if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) {
return cpu_R[reg];
- } else {
- return load_zero(dc);
}
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ return cpu_crs_R[reg];
+#endif
}
-static void t_gen_helper_raise_exception(DisasContext *dc,
- uint32_t index)
+static void t_gen_helper_raise_exception(DisasContext *dc, uint32_t index)
{
- TCGv_i32 tmp = tcg_const_i32(index);
-
- tcg_gen_movi_tl(cpu_R[R_PC], dc->pc);
- gen_helper_raise_exception(cpu_env, tmp);
- tcg_temp_free_i32(tmp);
+ /* Note that PC is advanced for all hardware exceptions. */
+ tcg_gen_movi_tl(cpu_pc, dc->base.pc_next);
+ gen_helper_raise_exception(cpu_env, tcg_constant_i32(index));
dc->base.is_jmp = DISAS_NORETURN;
}
@@ -156,12 +216,36 @@ static void gen_goto_tb(DisasContext *dc, int n, uint32_t dest)
if (translator_use_goto_tb(&dc->base, dest)) {
tcg_gen_goto_tb(n);
- tcg_gen_movi_tl(cpu_R[R_PC], dest);
+ tcg_gen_movi_tl(cpu_pc, dest);
tcg_gen_exit_tb(tb, n);
} else {
- tcg_gen_movi_tl(cpu_R[R_PC], dest);
- tcg_gen_exit_tb(NULL, 0);
+ tcg_gen_movi_tl(cpu_pc, dest);
+ tcg_gen_lookup_and_goto_ptr();
}
+ dc->base.is_jmp = DISAS_NORETURN;
+}
+
+static void gen_jumpr(DisasContext *dc, int regno, bool is_call)
+{
+ TCGLabel *l = gen_new_label();
+ TCGv test = tcg_temp_new();
+ TCGv dest = load_gpr(dc, regno);
+
+ tcg_gen_andi_tl(test, dest, 3);
+ tcg_gen_brcondi_tl(TCG_COND_NE, test, 0, l);
+ tcg_temp_free(test);
+
+ tcg_gen_mov_tl(cpu_pc, dest);
+ if (is_call) {
+ tcg_gen_movi_tl(dest_gpr(dc, R_RA), dc->base.pc_next);
+ }
+ tcg_gen_lookup_and_goto_ptr();
+
+ gen_set_label(l);
+ tcg_gen_st_tl(dest, cpu_env, offsetof(CPUNios2State, ctrl[CR_BADADDR]));
+ t_gen_helper_raise_exception(dc, EXCP_UNALIGND);
+
+ dc->base.is_jmp = DISAS_NORETURN;
}
static void gen_excp(DisasContext *dc, uint32_t code, uint32_t flags)
@@ -169,12 +253,14 @@ static void gen_excp(DisasContext *dc, uint32_t code, uint32_t flags)
t_gen_helper_raise_exception(dc, flags);
}
-static void gen_check_supervisor(DisasContext *dc)
+static bool gen_check_supervisor(DisasContext *dc)
{
- if (dc->base.tb->flags & CR_STATUS_U) {
+ if (FIELD_EX32(dc->tb_flags, TBFLAGS, U)) {
/* CPU in user mode, privileged instruction called, stop. */
t_gen_helper_raise_exception(dc, EXCP_SUPERI);
+ return false;
}
+ return true;
}
/*
@@ -193,12 +279,11 @@ static void jmpi(DisasContext *dc, uint32_t code, uint32_t flags)
{
J_TYPE(instr, code);
gen_goto_tb(dc, 0, (dc->pc & 0xF0000000) | (instr.imm26 << 2));
- dc->base.is_jmp = DISAS_NORETURN;
}
static void call(DisasContext *dc, uint32_t code, uint32_t flags)
{
- tcg_gen_movi_tl(cpu_R[R_RA], dc->base.pc_next);
+ tcg_gen_movi_tl(dest_gpr(dc, R_RA), dc->base.pc_next);
jmpi(dc, code, flags);
}
@@ -211,27 +296,10 @@ static void gen_ldx(DisasContext *dc, uint32_t code, uint32_t flags)
I_TYPE(instr, code);
TCGv addr = tcg_temp_new();
- TCGv data;
-
- /*
- * WARNING: Loads into R_ZERO are ignored, but we must generate the
- * memory access itself to emulate the CPU precisely. Load
- * from a protected page to R_ZERO will cause SIGSEGV on
- * the Nios2 CPU.
- */
- if (likely(instr.b != R_ZERO)) {
- data = cpu_R[instr.b];
- } else {
- data = tcg_temp_new();
- }
+ TCGv data = dest_gpr(dc, instr.b);
tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s);
tcg_gen_qemu_ld_tl(data, addr, dc->mem_idx, flags);
-
- if (unlikely(instr.b == R_ZERO)) {
- tcg_temp_free(data);
- }
-
tcg_temp_free(addr);
}
@@ -253,7 +321,6 @@ static void br(DisasContext *dc, uint32_t code, uint32_t flags)
I_TYPE(instr, code);
gen_goto_tb(dc, 0, dc->base.pc_next + (instr.imm16.s & -4));
- dc->base.is_jmp = DISAS_NORETURN;
}
static void gen_bxx(DisasContext *dc, uint32_t code, uint32_t flags)
@@ -261,48 +328,86 @@ static void gen_bxx(DisasContext *dc, uint32_t code, uint32_t flags)
I_TYPE(instr, code);
TCGLabel *l1 = gen_new_label();
- tcg_gen_brcond_tl(flags, cpu_R[instr.a], cpu_R[instr.b], l1);
+ tcg_gen_brcond_tl(flags, load_gpr(dc, instr.a), load_gpr(dc, instr.b), l1);
gen_goto_tb(dc, 0, dc->base.pc_next);
gen_set_label(l1);
gen_goto_tb(dc, 1, dc->base.pc_next + (instr.imm16.s & -4));
- dc->base.is_jmp = DISAS_NORETURN;
}
/* Comparison instructions */
-#define gen_i_cmpxx(fname, op3) \
-static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
-{ \
- I_TYPE(instr, (code)); \
- tcg_gen_setcondi_tl(flags, cpu_R[instr.b], cpu_R[instr.a], (op3)); \
+static void do_i_cmpxx(DisasContext *dc, uint32_t insn,
+ TCGCond cond, ImmFromIType *imm)
+{
+ I_TYPE(instr, insn);
+ tcg_gen_setcondi_tl(cond, dest_gpr(dc, instr.b),
+ load_gpr(dc, instr.a), imm(&instr));
}
-gen_i_cmpxx(gen_cmpxxsi, instr.imm16.s)
-gen_i_cmpxx(gen_cmpxxui, instr.imm16.u)
+#define gen_i_cmpxx(fname, imm) \
+ static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
+ { do_i_cmpxx(dc, code, flags, imm); }
+
+gen_i_cmpxx(gen_cmpxxsi, imm_signed)
+gen_i_cmpxx(gen_cmpxxui, imm_unsigned)
/* Math/logic instructions */
-#define gen_i_math_logic(fname, insn, resimm, op3) \
-static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
-{ \
- I_TYPE(instr, (code)); \
- if (unlikely(instr.b == R_ZERO)) { /* Store to R_ZERO is ignored */ \
- return; \
- } else if (instr.a == R_ZERO) { /* MOVxI optimizations */ \
- tcg_gen_movi_tl(cpu_R[instr.b], (resimm) ? (op3) : 0); \
- } else { \
- tcg_gen_##insn##_tl(cpu_R[instr.b], cpu_R[instr.a], (op3)); \
- } \
-}
-
-gen_i_math_logic(addi, addi, 1, instr.imm16.s)
-gen_i_math_logic(muli, muli, 0, instr.imm16.s)
-
-gen_i_math_logic(andi, andi, 0, instr.imm16.u)
-gen_i_math_logic(ori, ori, 1, instr.imm16.u)
-gen_i_math_logic(xori, xori, 1, instr.imm16.u)
-
-gen_i_math_logic(andhi, andi, 0, instr.imm16.u << 16)
-gen_i_math_logic(orhi , ori, 1, instr.imm16.u << 16)
-gen_i_math_logic(xorhi, xori, 1, instr.imm16.u << 16)
+static void do_i_math_logic(DisasContext *dc, uint32_t insn,
+ GenFn2i *fn, ImmFromIType *imm,
+ bool x_op_0_eq_x)
+{
+ I_TYPE(instr, insn);
+ target_ulong val;
+
+ if (unlikely(instr.b == R_ZERO)) {
+ /* Store to R_ZERO is ignored -- this catches the canonical NOP. */
+ return;
+ }
+
+ val = imm(&instr);
+
+ if (instr.a == R_ZERO && FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0)) {
+ /* This catches the canonical expansions of movi and movhi. */
+ tcg_gen_movi_tl(dest_gpr(dc, instr.b), x_op_0_eq_x ? val : 0);
+ } else {
+ fn(dest_gpr(dc, instr.b), load_gpr(dc, instr.a), val);
+ }
+}
+
+#define gen_i_math_logic(fname, insn, x_op_0, imm) \
+ static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
+ { do_i_math_logic(dc, code, tcg_gen_##insn##_tl, imm, x_op_0); }
+
+gen_i_math_logic(addi, addi, 1, imm_signed)
+gen_i_math_logic(muli, muli, 0, imm_signed)
+
+gen_i_math_logic(andi, andi, 0, imm_unsigned)
+gen_i_math_logic(ori, ori, 1, imm_unsigned)
+gen_i_math_logic(xori, xori, 1, imm_unsigned)
+
+gen_i_math_logic(andhi, andi, 0, imm_shifted)
+gen_i_math_logic(orhi , ori, 1, imm_shifted)
+gen_i_math_logic(xorhi, xori, 1, imm_shifted)
+
+/* rB <- prs.rA + sigma(IMM16) */
+static void rdprs(DisasContext *dc, uint32_t code, uint32_t flags)
+{
+ if (!dc->eic_present) {
+ t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
+ return;
+ }
+ if (!gen_check_supervisor(dc)) {
+ return;
+ }
+
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ I_TYPE(instr, code);
+ TCGv dest = dest_gpr(dc, instr.b);
+ gen_helper_rdprs(dest, cpu_env, tcg_constant_i32(instr.a));
+ tcg_gen_addi_tl(dest, dest, instr.imm16.s);
+#endif
+}
/* Prototype only, defined below */
static void handle_r_type_instr(DisasContext *dc, uint32_t code,
@@ -365,7 +470,7 @@ static const Nios2Instruction i_type_instructions[] = {
INSTRUCTION_FLG(gen_stx, MO_SL), /* stwio */
INSTRUCTION_FLG(gen_bxx, TCG_COND_LTU), /* bltu */
INSTRUCTION_FLG(gen_ldx, MO_UL), /* ldwio */
- INSTRUCTION_UNIMPLEMENTED(), /* rdprs */
+ INSTRUCTION(rdprs), /* rdprs */
INSTRUCTION_ILLEGAL(),
INSTRUCTION_FLG(handle_r_type_instr, 0), /* R-Type */
INSTRUCTION_NOP(), /* flushd */
@@ -384,26 +489,51 @@ static const Nios2Instruction i_type_instructions[] = {
*/
static void eret(DisasContext *dc, uint32_t code, uint32_t flags)
{
- tcg_gen_mov_tl(cpu_R[CR_STATUS], cpu_R[CR_ESTATUS]);
- tcg_gen_mov_tl(cpu_R[R_PC], cpu_R[R_EA]);
+ if (!gen_check_supervisor(dc)) {
+ return;
+ }
- dc->base.is_jmp = DISAS_JUMP;
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) {
+ TCGv tmp = tcg_temp_new();
+ tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPUNios2State, ctrl[CR_ESTATUS]));
+ gen_helper_eret(cpu_env, tmp, load_gpr(dc, R_EA));
+ tcg_temp_free(tmp);
+ } else {
+ gen_helper_eret(cpu_env, load_gpr(dc, R_SSTATUS), load_gpr(dc, R_EA));
+ }
+ dc->base.is_jmp = DISAS_NORETURN;
+#endif
}
/* PC <- ra */
static void ret(DisasContext *dc, uint32_t code, uint32_t flags)
{
- tcg_gen_mov_tl(cpu_R[R_PC], cpu_R[R_RA]);
-
- dc->base.is_jmp = DISAS_JUMP;
+ gen_jumpr(dc, R_RA, false);
}
-/* PC <- ba */
+/*
+ * status <- bstatus
+ * PC <- ba
+ */
static void bret(DisasContext *dc, uint32_t code, uint32_t flags)
{
- tcg_gen_mov_tl(cpu_R[R_PC], cpu_R[R_BA]);
+ if (!gen_check_supervisor(dc)) {
+ return;
+ }
- dc->base.is_jmp = DISAS_JUMP;
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ TCGv tmp = tcg_temp_new();
+ tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPUNios2State, ctrl[CR_BSTATUS]));
+ gen_helper_eret(cpu_env, tmp, load_gpr(dc, R_BA));
+ tcg_temp_free(tmp);
+
+ dc->base.is_jmp = DISAS_NORETURN;
+#endif
}
/* PC <- rA */
@@ -411,9 +541,7 @@ static void jmp(DisasContext *dc, uint32_t code, uint32_t flags)
{
R_TYPE(instr, code);
- tcg_gen_mov_tl(cpu_R[R_PC], load_gpr(dc, instr.a));
-
- dc->base.is_jmp = DISAS_JUMP;
+ gen_jumpr(dc, instr.a, false);
}
/* rC <- PC + 4 */
@@ -421,9 +549,7 @@ static void nextpc(DisasContext *dc, uint32_t code, uint32_t flags)
{
R_TYPE(instr, code);
- if (likely(instr.c != R_ZERO)) {
- tcg_gen_movi_tl(cpu_R[instr.c], dc->base.pc_next);
- }
+ tcg_gen_movi_tl(dest_gpr(dc, instr.c), dc->base.pc_next);
}
/*
@@ -434,24 +560,29 @@ static void callr(DisasContext *dc, uint32_t code, uint32_t flags)
{
R_TYPE(instr, code);
- tcg_gen_mov_tl(cpu_R[R_PC], load_gpr(dc, instr.a));
- tcg_gen_movi_tl(cpu_R[R_RA], dc->base.pc_next);
-
- dc->base.is_jmp = DISAS_JUMP;
+ gen_jumpr(dc, instr.a, true);
}
/* rC <- ctlN */
static void rdctl(DisasContext *dc, uint32_t code, uint32_t flags)
{
- R_TYPE(instr, code);
+ if (!gen_check_supervisor(dc)) {
+ return;
+ }
- gen_check_supervisor(dc);
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ R_TYPE(instr, code);
+ TCGv t1, t2, dest = dest_gpr(dc, instr.c);
- if (unlikely(instr.c == R_ZERO)) {
+ /* Reserved registers read as zero. */
+ if (nios2_cr_reserved(&dc->cr_state[instr.imm5])) {
+ tcg_gen_movi_tl(dest, 0);
return;
}
- switch (instr.imm5 + CR_BASE) {
+ switch (instr.imm5) {
case CR_IPENDING:
/*
* The value of the ipending register is synthetic.
@@ -461,24 +592,44 @@ static void rdctl(DisasContext *dc, uint32_t code, uint32_t flags)
* must perform the AND here, and anywhere else we need the
* guest value of ipending.
*/
- tcg_gen_and_tl(cpu_R[instr.c], cpu_R[CR_IPENDING], cpu_R[CR_IENABLE]);
+ t1 = tcg_temp_new();
+ t2 = tcg_temp_new();
+ tcg_gen_ld_tl(t1, cpu_env, offsetof(CPUNios2State, ctrl[CR_IPENDING]));
+ tcg_gen_ld_tl(t2, cpu_env, offsetof(CPUNios2State, ctrl[CR_IENABLE]));
+ tcg_gen_and_tl(dest, t1, t2);
+ tcg_temp_free(t1);
+ tcg_temp_free(t2);
break;
default:
- tcg_gen_mov_tl(cpu_R[instr.c], cpu_R[instr.imm5 + CR_BASE]);
+ tcg_gen_ld_tl(dest, cpu_env,
+ offsetof(CPUNios2State, ctrl[instr.imm5]));
break;
}
+#endif
}
/* ctlN <- rA */
static void wrctl(DisasContext *dc, uint32_t code, uint32_t flags)
{
- gen_check_supervisor(dc);
+ if (!gen_check_supervisor(dc)) {
+ return;
+ }
-#ifndef CONFIG_USER_ONLY
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
R_TYPE(instr, code);
TCGv v = load_gpr(dc, instr.a);
+ uint32_t ofs = offsetof(CPUNios2State, ctrl[instr.imm5]);
+ uint32_t wr = dc->cr_state[instr.imm5].writable;
+ uint32_t ro = dc->cr_state[instr.imm5].readonly;
+
+ /* Skip reserved or readonly registers. */
+ if (wr == 0) {
+ return;
+ }
- switch (instr.imm5 + CR_BASE) {
+ switch (instr.imm5) {
case CR_PTEADDR:
gen_helper_mmu_write_pteaddr(cpu_env, v);
break;
@@ -488,145 +639,163 @@ static void wrctl(DisasContext *dc, uint32_t code, uint32_t flags)
case CR_TLBMISC:
gen_helper_mmu_write_tlbmisc(cpu_env, v);
break;
- case CR_IPENDING:
- /* ipending is read only, writes ignored. */
- break;
case CR_STATUS:
case CR_IENABLE:
/* If interrupts were enabled using WRCTL, trigger them. */
dc->base.is_jmp = DISAS_UPDATE;
/* fall through */
default:
- tcg_gen_mov_tl(cpu_R[instr.imm5 + CR_BASE], v);
+ if (wr == -1) {
+ /* The register is entirely writable. */
+ tcg_gen_st_tl(v, cpu_env, ofs);
+ } else {
+ /*
+ * The register is partially read-only or reserved:
+ * merge the value.
+ */
+ TCGv n = tcg_temp_new();
+
+ tcg_gen_andi_tl(n, v, wr);
+
+ if (ro != 0) {
+ TCGv o = tcg_temp_new();
+ tcg_gen_ld_tl(o, cpu_env, ofs);
+ tcg_gen_andi_tl(o, o, ro);
+ tcg_gen_or_tl(n, n, o);
+ tcg_temp_free(o);
+ }
+
+ tcg_gen_st_tl(n, cpu_env, ofs);
+ tcg_temp_free(n);
+ }
break;
}
#endif
}
+/* prs.rC <- rA */
+static void wrprs(DisasContext *dc, uint32_t code, uint32_t flags)
+{
+ if (!dc->eic_present) {
+ t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
+ return;
+ }
+ if (!gen_check_supervisor(dc)) {
+ return;
+ }
+
+#ifdef CONFIG_USER_ONLY
+ g_assert_not_reached();
+#else
+ R_TYPE(instr, code);
+ gen_helper_wrprs(cpu_env, tcg_constant_i32(instr.c),
+ load_gpr(dc, instr.a));
+ /*
+ * The expected write to PRS[r0] is 0, from CRS[r0].
+ * If not, and CRS == PRS (which we cannot tell from here),
+ * we may now have a non-zero value in our current r0.
+ * By ending the TB, we re-evaluate tb_flags and find out.
+ */
+ if (instr.c == 0
+ && (instr.a != 0 || !FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0))) {
+ dc->base.is_jmp = DISAS_UPDATE;
+ }
+#endif
+}
+
/* Comparison instructions */
static void gen_cmpxx(DisasContext *dc, uint32_t code, uint32_t flags)
{
R_TYPE(instr, code);
- if (likely(instr.c != R_ZERO)) {
- tcg_gen_setcond_tl(flags, cpu_R[instr.c], cpu_R[instr.a],
- cpu_R[instr.b]);
- }
+ tcg_gen_setcond_tl(flags, dest_gpr(dc, instr.c),
+ load_gpr(dc, instr.a), load_gpr(dc, instr.b));
}
/* Math/logic instructions */
-#define gen_r_math_logic(fname, insn, op3) \
-static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
-{ \
- R_TYPE(instr, (code)); \
- if (likely(instr.c != R_ZERO)) { \
- tcg_gen_##insn(cpu_R[instr.c], load_gpr((dc), instr.a), (op3)); \
- } \
-}
-
-gen_r_math_logic(add, add_tl, load_gpr(dc, instr.b))
-gen_r_math_logic(sub, sub_tl, load_gpr(dc, instr.b))
-gen_r_math_logic(mul, mul_tl, load_gpr(dc, instr.b))
-
-gen_r_math_logic(and, and_tl, load_gpr(dc, instr.b))
-gen_r_math_logic(or, or_tl, load_gpr(dc, instr.b))
-gen_r_math_logic(xor, xor_tl, load_gpr(dc, instr.b))
-gen_r_math_logic(nor, nor_tl, load_gpr(dc, instr.b))
-
-gen_r_math_logic(srai, sari_tl, instr.imm5)
-gen_r_math_logic(srli, shri_tl, instr.imm5)
-gen_r_math_logic(slli, shli_tl, instr.imm5)
-gen_r_math_logic(roli, rotli_tl, instr.imm5)
-
-#define gen_r_mul(fname, insn) \
-static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
-{ \
- R_TYPE(instr, (code)); \
- if (likely(instr.c != R_ZERO)) { \
- TCGv t0 = tcg_temp_new(); \
- tcg_gen_##insn(t0, cpu_R[instr.c], \
- load_gpr(dc, instr.a), load_gpr(dc, instr.b)); \
- tcg_temp_free(t0); \
- } \
-}
-
-gen_r_mul(mulxss, muls2_tl)
-gen_r_mul(mulxuu, mulu2_tl)
-gen_r_mul(mulxsu, mulsu2_tl)
-
-#define gen_r_shift_s(fname, insn) \
-static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
-{ \
- R_TYPE(instr, (code)); \
- if (likely(instr.c != R_ZERO)) { \
- TCGv t0 = tcg_temp_new(); \
- tcg_gen_andi_tl(t0, load_gpr((dc), instr.b), 31); \
- tcg_gen_##insn(cpu_R[instr.c], load_gpr((dc), instr.a), t0); \
- tcg_temp_free(t0); \
- } \
-}
-
-gen_r_shift_s(sra, sar_tl)
-gen_r_shift_s(srl, shr_tl)
-gen_r_shift_s(sll, shl_tl)
-gen_r_shift_s(rol, rotl_tl)
-gen_r_shift_s(ror, rotr_tl)
+static void do_ri_math_logic(DisasContext *dc, uint32_t insn, GenFn2i *fn)
+{
+ R_TYPE(instr, insn);
+ fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), instr.imm5);
+}
-static void divs(DisasContext *dc, uint32_t code, uint32_t flags)
+static void do_rr_math_logic(DisasContext *dc, uint32_t insn, GenFn3 *fn)
{
- R_TYPE(instr, (code));
+ R_TYPE(instr, insn);
+ fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), load_gpr(dc, instr.b));
+}
- /* Stores into R_ZERO are ignored */
- if (unlikely(instr.c == R_ZERO)) {
- return;
- }
+#define gen_ri_math_logic(fname, insn) \
+ static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
+ { do_ri_math_logic(dc, code, tcg_gen_##insn##_tl); }
+
+#define gen_rr_math_logic(fname, insn) \
+ static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
+ { do_rr_math_logic(dc, code, tcg_gen_##insn##_tl); }
+
+gen_rr_math_logic(add, add)
+gen_rr_math_logic(sub, sub)
+gen_rr_math_logic(mul, mul)
- TCGv t0 = tcg_temp_new();
- TCGv t1 = tcg_temp_new();
- TCGv t2 = tcg_temp_new();
- TCGv t3 = tcg_temp_new();
+gen_rr_math_logic(and, and)
+gen_rr_math_logic(or, or)
+gen_rr_math_logic(xor, xor)
+gen_rr_math_logic(nor, nor)
- tcg_gen_ext32s_tl(t0, load_gpr(dc, instr.a));
- tcg_gen_ext32s_tl(t1, load_gpr(dc, instr.b));
- tcg_gen_setcondi_tl(TCG_COND_EQ, t2, t0, INT_MIN);
- tcg_gen_setcondi_tl(TCG_COND_EQ, t3, t1, -1);
- tcg_gen_and_tl(t2, t2, t3);
- tcg_gen_setcondi_tl(TCG_COND_EQ, t3, t1, 0);
- tcg_gen_or_tl(t2, t2, t3);
- tcg_gen_movi_tl(t3, 0);
- tcg_gen_movcond_tl(TCG_COND_NE, t1, t2, t3, t2, t1);
- tcg_gen_div_tl(cpu_R[instr.c], t0, t1);
- tcg_gen_ext32s_tl(cpu_R[instr.c], cpu_R[instr.c]);
+gen_ri_math_logic(srai, sari)
+gen_ri_math_logic(srli, shri)
+gen_ri_math_logic(slli, shli)
+gen_ri_math_logic(roli, rotli)
- tcg_temp_free(t3);
- tcg_temp_free(t2);
- tcg_temp_free(t1);
- tcg_temp_free(t0);
+static void do_rr_mul_high(DisasContext *dc, uint32_t insn, GenFn4 *fn)
+{
+ R_TYPE(instr, insn);
+ TCGv discard = tcg_temp_new();
+
+ fn(discard, dest_gpr(dc, instr.c),
+ load_gpr(dc, instr.a), load_gpr(dc, instr.b));
+ tcg_temp_free(discard);
}
-static void divu(DisasContext *dc, uint32_t code, uint32_t flags)
+#define gen_rr_mul_high(fname, insn) \
+ static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
+ { do_rr_mul_high(dc, code, tcg_gen_##insn##_tl); }
+
+gen_rr_mul_high(mulxss, muls2)
+gen_rr_mul_high(mulxuu, mulu2)
+gen_rr_mul_high(mulxsu, mulsu2)
+
+static void do_rr_shift(DisasContext *dc, uint32_t insn, GenFn3 *fn)
{
- R_TYPE(instr, (code));
+ R_TYPE(instr, insn);
+ TCGv sh = tcg_temp_new();
- /* Stores into R_ZERO are ignored */
- if (unlikely(instr.c == R_ZERO)) {
- return;
- }
+ tcg_gen_andi_tl(sh, load_gpr(dc, instr.b), 31);
+ fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), sh);
+ tcg_temp_free(sh);
+}
- TCGv t0 = tcg_temp_new();
- TCGv t1 = tcg_temp_new();
- TCGv t2 = tcg_const_tl(0);
- TCGv t3 = tcg_const_tl(1);
+#define gen_rr_shift(fname, insn) \
+ static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \
+ { do_rr_shift(dc, code, tcg_gen_##insn##_tl); }
- tcg_gen_ext32u_tl(t0, load_gpr(dc, instr.a));
- tcg_gen_ext32u_tl(t1, load_gpr(dc, instr.b));
- tcg_gen_movcond_tl(TCG_COND_EQ, t1, t1, t2, t3, t1);
- tcg_gen_divu_tl(cpu_R[instr.c], t0, t1);
- tcg_gen_ext32s_tl(cpu_R[instr.c], cpu_R[instr.c]);
+gen_rr_shift(sra, sar)
+gen_rr_shift(srl, shr)
+gen_rr_shift(sll, shl)
+gen_rr_shift(rol, rotl)
+gen_rr_shift(ror, rotr)
- tcg_temp_free(t3);
- tcg_temp_free(t2);
- tcg_temp_free(t1);
- tcg_temp_free(t0);
+static void divs(DisasContext *dc, uint32_t code, uint32_t flags)
+{
+ R_TYPE(instr, (code));
+ gen_helper_divs(dest_gpr(dc, instr.c), cpu_env,
+ load_gpr(dc, instr.a), load_gpr(dc, instr.b));
+}
+
+static void divu(DisasContext *dc, uint32_t code, uint32_t flags)
+{
+ R_TYPE(instr, (code));
+ gen_helper_divu(dest_gpr(dc, instr.c), cpu_env,
+ load_gpr(dc, instr.a), load_gpr(dc, instr.b));
}
static void trap(DisasContext *dc, uint32_t code, uint32_t flags)
@@ -644,6 +813,20 @@ static void trap(DisasContext *dc, uint32_t code, uint32_t flags)
t_gen_helper_raise_exception(dc, EXCP_TRAP);
}
+static void gen_break(DisasContext *dc, uint32_t code, uint32_t flags)
+{
+#ifndef CONFIG_USER_ONLY
+ /* The semihosting instruction is "break 1". */
+ R_TYPE(instr, code);
+ if (semihosting_enabled() && instr.imm5 == 1) {
+ t_gen_helper_raise_exception(dc, EXCP_SEMIHOST);
+ return;
+ }
+#endif
+
+ t_gen_helper_raise_exception(dc, EXCP_BREAK);
+}
+
static const Nios2Instruction r_type_instructions[] = {
INSTRUCTION_ILLEGAL(),
INSTRUCTION(eret), /* eret */
@@ -665,7 +848,7 @@ static const Nios2Instruction r_type_instructions[] = {
INSTRUCTION_ILLEGAL(),
INSTRUCTION(slli), /* slli */
INSTRUCTION(sll), /* sll */
- INSTRUCTION_UNIMPLEMENTED(), /* wrprs */
+ INSTRUCTION(wrprs), /* wrprs */
INSTRUCTION_ILLEGAL(),
INSTRUCTION(or), /* or */
INSTRUCTION(mulxsu), /* mulxsu */
@@ -697,7 +880,7 @@ static const Nios2Instruction r_type_instructions[] = {
INSTRUCTION(add), /* add */
INSTRUCTION_ILLEGAL(),
INSTRUCTION_ILLEGAL(),
- INSTRUCTION_FLG(gen_excp, EXCP_BREAK), /* break */
+ INSTRUCTION(gen_break), /* break */
INSTRUCTION_ILLEGAL(),
INSTRUCTION(nop), /* nop */
INSTRUCTION_ILLEGAL(),
@@ -730,7 +913,7 @@ illegal_op:
t_gen_helper_raise_exception(dc, EXCP_ILLEGAL);
}
-static const char * const regnames[] = {
+static const char * const gr_regnames[NUM_GP_REGS] = {
"zero", "at", "r2", "r3",
"r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11",
@@ -739,16 +922,20 @@ static const char * const regnames[] = {
"r20", "r21", "r22", "r23",
"et", "bt", "gp", "sp",
"fp", "ea", "ba", "ra",
+};
+
+#ifndef CONFIG_USER_ONLY
+static const char * const cr_regnames[NUM_CR_REGS] = {
"status", "estatus", "bstatus", "ienable",
- "ipending", "cpuid", "reserved0", "exception",
+ "ipending", "cpuid", "res6", "exception",
"pteaddr", "tlbacc", "tlbmisc", "reserved1",
"badaddr", "config", "mpubase", "mpuacc",
- "reserved2", "reserved3", "reserved4", "reserved5",
- "reserved6", "reserved7", "reserved8", "reserved9",
- "reserved10", "reserved11", "reserved12", "reserved13",
- "reserved14", "reserved15", "reserved16", "reserved17",
- "rpc"
+ "res16", "res17", "res18", "res19",
+ "res20", "res21", "res22", "res23",
+ "res24", "res25", "res26", "res27",
+ "res28", "res29", "res30", "res31",
};
+#endif
#include "exec/gen-icount.h"
@@ -757,9 +944,13 @@ static void nios2_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
{
DisasContext *dc = container_of(dcbase, DisasContext, base);
CPUNios2State *env = cs->env_ptr;
+ Nios2CPU *cpu = env_archcpu(env);
int page_insns;
dc->mem_idx = cpu_mmu_index(env, false);
+ dc->cr_state = cpu->cr_state;
+ dc->tb_flags = dc->base.tb->flags;
+ dc->eic_present = cpu->eic_present;
/* Bound the number of insns to execute to those left on the page. */
page_insns = -(dc->base.pc_first | TARGET_PAGE_MASK) / 4;
@@ -796,13 +987,13 @@ static void nios2_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs)
return;
}
- dc->zero = NULL;
+ dc->sink = NULL;
instr = &i_type_instructions[op];
instr->handler(dc, code, instr->flags);
- if (dc->zero) {
- tcg_temp_free(dc->zero);
+ if (dc->sink) {
+ tcg_temp_free(dc->sink);
}
}
@@ -813,14 +1004,12 @@ static void nios2_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs)
/* Indicate where the next block should start */
switch (dc->base.is_jmp) {
case DISAS_TOO_MANY:
- case DISAS_UPDATE:
- /* Save the current PC back into the CPU register */
- tcg_gen_movi_tl(cpu_R[R_PC], dc->base.pc_next);
- tcg_gen_exit_tb(NULL, 0);
+ gen_goto_tb(dc, 0, dc->base.pc_next);
break;
- case DISAS_JUMP:
- /* The jump will already have updated the PC register */
+ case DISAS_UPDATE:
+ /* Save the current PC, and return to the main loop. */
+ tcg_gen_movi_tl(cpu_pc, dc->base.pc_next);
tcg_gen_exit_tb(NULL, 0);
break;
@@ -861,41 +1050,67 @@ void nios2_cpu_dump_state(CPUState *cs, FILE *f, int flags)
CPUNios2State *env = &cpu->env;
int i;
- if (!env) {
- return;
- }
-
- qemu_fprintf(f, "IN: PC=%x %s\n",
- env->regs[R_PC], lookup_symbol(env->regs[R_PC]));
+ qemu_fprintf(f, "IN: PC=%x %s\n", env->pc, lookup_symbol(env->pc));
- for (i = 0; i < NUM_CORE_REGS; i++) {
- qemu_fprintf(f, "%9s=%8.8x ", regnames[i], env->regs[i]);
+ for (i = 0; i < NUM_GP_REGS; i++) {
+ qemu_fprintf(f, "%9s=%8.8x ", gr_regnames[i], env->regs[i]);
if ((i + 1) % 4 == 0) {
qemu_fprintf(f, "\n");
}
}
+
#if !defined(CONFIG_USER_ONLY)
- qemu_fprintf(f, " mmu write: VPN=%05X PID %02X TLBACC %08X\n",
- env->mmu.pteaddr_wr & CR_PTEADDR_VPN_MASK,
- (env->mmu.tlbmisc_wr & CR_TLBMISC_PID_MASK) >> 4,
- env->mmu.tlbacc_wr);
+ int j;
+
+ for (i = j = 0; i < NUM_CR_REGS; i++) {
+ if (!nios2_cr_reserved(&cpu->cr_state[i])) {
+ qemu_fprintf(f, "%9s=%8.8x ", cr_regnames[i], env->ctrl[i]);
+ if (++j % 4 == 0) {
+ qemu_fprintf(f, "\n");
+ }
+ }
+ }
+ if (j % 4 != 0) {
+ qemu_fprintf(f, "\n");
+ }
+ if (cpu->mmu_present) {
+ qemu_fprintf(f, " mmu write: VPN=%05X PID %02X TLBACC %08X\n",
+ env->mmu.pteaddr_wr & R_CR_PTEADDR_VPN_MASK,
+ FIELD_EX32(env->mmu.tlbmisc_wr, CR_TLBMISC, PID),
+ env->mmu.tlbacc_wr);
+ }
#endif
qemu_fprintf(f, "\n\n");
}
void nios2_tcg_init(void)
{
- int i;
+#ifndef CONFIG_USER_ONLY
+ TCGv_ptr crs = tcg_global_mem_new_ptr(cpu_env,
+ offsetof(CPUNios2State, regs), "crs");
- for (i = 0; i < NUM_CORE_REGS; i++) {
- cpu_R[i] = tcg_global_mem_new(cpu_env,
- offsetof(CPUNios2State, regs[i]),
- regnames[i]);
+ for (int i = 0; i < NUM_GP_REGS; i++) {
+ cpu_crs_R[i] = tcg_global_mem_new(crs, 4 * i, gr_regnames[i]);
}
+
+#define offsetof_regs0(N) offsetof(CPUNios2State, shadow_regs[0][N])
+#else
+#define offsetof_regs0(N) offsetof(CPUNios2State, regs[N])
+#endif
+
+ for (int i = 0; i < NUM_GP_REGS; i++) {
+ cpu_R[i] = tcg_global_mem_new(cpu_env, offsetof_regs0(i),
+ gr_regnames[i]);
+ }
+
+#undef offsetof_regs0
+
+ cpu_pc = tcg_global_mem_new(cpu_env,
+ offsetof(CPUNios2State, pc), "pc");
}
void restore_state_to_opc(CPUNios2State *env, TranslationBlock *tb,
target_ulong *data)
{
- env->regs[R_PC] = data[0];
+ env->pc = data[0];
}
diff --git a/tests/tcg/nios2/10m50-ghrd.ld b/tests/tcg/nios2/10m50-ghrd.ld
new file mode 100644
index 0000000..7db0d59
--- /dev/null
+++ b/tests/tcg/nios2/10m50-ghrd.ld
@@ -0,0 +1,66 @@
+/*
+ * Link script for the Nios2 10m50-ghrd board.
+ *
+ * Copyright Linaro Ltd 2022
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+MEMORY
+{
+ tpf (rx) : ORIGIN = 0xc0000000, LENGTH = 1K
+ ram (rwx) : ORIGIN = 0xc8000000, LENGTH = 128M
+}
+
+PHDRS
+{
+ RAM PT_LOAD;
+}
+
+ENTRY(_start)
+EXTERN(_start)
+EXTERN(_interrupt)
+EXTERN(_fast_tlb_miss)
+
+SECTIONS
+{
+ /* Begin at the (hardcoded) _interrupt entry point. */
+ .text 0xc8000120 : {
+ *(.text.intr)
+ *(.text .text.* .gnu.linkonce.t.*)
+ } >ram :RAM
+
+ .rodata : ALIGN(4) {
+ *(.rodata .rodata.* .gnu.linkonce.r.*)
+ } > ram :RAM
+
+ .eh_frame_hdr : ALIGN (4) {
+ KEEP (*(.eh_frame_hdr))
+ *(.eh_frame_entry .eh_frame_entry.*)
+ } >ram :RAM
+ .eh_frame : ALIGN (4) {
+ KEEP (*(.eh_frame)) *(.eh_frame.*)
+ } >ram :RAM
+
+ .data : ALIGN(4) {
+ *(.shdata)
+ *(.data .data.* .gnu.linkonce.d.*)
+ . = ALIGN(4);
+ _gp = ABSOLUTE(. + 0x8000);
+ *(.got.plt) *(.got)
+ *(.lit8)
+ *(.lit4)
+ *(.sdata .sdata.* .gnu.linkonce.s.*)
+ } >ram :RAM
+
+ .bss : ALIGN(4) {
+ __bss_start = ABSOLUTE(.);
+ *(.sbss .sbss.* .gnu.linkonce.sb.*)
+ *(.scommon)
+ *(.bss .bss.* .gnu.linkonce.b.*)
+ *(COMMON)
+ . = ALIGN(4);
+ __bss_end = ABSOLUTE(.);
+ } >ram :RAM
+
+ __stack = ORIGIN(ram) + LENGTH(ram);
+}
diff --git a/tests/tcg/nios2/Makefile.softmmu-target b/tests/tcg/nios2/Makefile.softmmu-target
new file mode 100644
index 0000000..c3d0594
--- /dev/null
+++ b/tests/tcg/nios2/Makefile.softmmu-target
@@ -0,0 +1,33 @@
+#
+# Nios2 system tests
+#
+# Copyright Linaro Ltd 2022
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+
+NIOS2_SYSTEM_SRC = $(SRC_PATH)/tests/tcg/nios2
+VPATH += $(NIOS2_SYSTEM_SRC)
+
+# These objects provide the basic boot code and helper functions for all tests
+CRT_OBJS = boot.o intr.o $(MINILIB_OBJS)
+LINK_SCRIPT = $(NIOS2_SYSTEM_SRC)/10m50-ghrd.ld
+
+CFLAGS += -nostdlib -g -O0 $(MINILIB_INC)
+LDFLAGS += -Wl,-T$(LINK_SCRIPT) -static -nostdlib $(CRT_OBJS) -lgcc
+
+%.o: %.S
+ $(call quiet-command, $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -x assembler-with-cpp -c $< -o $@, AS, $@)
+
+%.o: %.c
+ $(call quiet-command, $(CC) $(CFLAGS) $(EXTRA_CFLAGS) -c $< -o $@, CC, $@)
+
+# Build and link the tests
+%: %.o $(LINK_SCRIPT) $(CRT_OBJS)
+ $(call quiet-command, $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< -o $@ $(LDFLAGS), LD, $@)
+
+# FIXME: nios2 semihosting writes to stdout, not a chardev
+QEMU_OPTS = -M 10m50-ghrd,vic=on -semihosting >$@.out -kernel
+
+memory: CFLAGS+=-DCHECK_UNALIGNED=0
+TESTS += $(MULTIARCH_TESTS)
+TESTS += test-shadow-1
diff --git a/tests/tcg/nios2/Makefile.target b/tests/tcg/nios2/Makefile.target
deleted file mode 100644
index b38e235..0000000
--- a/tests/tcg/nios2/Makefile.target
+++ /dev/null
@@ -1,11 +0,0 @@
-# nios2 specific test tweaks
-
-# Currently nios2 signal handling is broken
-run-signals: signals
- $(call skip-test, $<, "BROKEN")
-run-plugin-signals-with-%:
- $(call skip-test, $<, "BROKEN")
-run-linux-test: linux-test
- $(call skip-test, $<, "BROKEN")
-run-plugin-linux-test-with-%:
- $(call skip-test, $<, "BROKEN")
diff --git a/tests/tcg/nios2/boot.S b/tests/tcg/nios2/boot.S
new file mode 100644
index 0000000..f6771cb
--- /dev/null
+++ b/tests/tcg/nios2/boot.S
@@ -0,0 +1,218 @@
+/*
+ * Minimal Nios2 system boot code.
+ *
+ * Copyright Linaro Ltd 2022
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "semicall.h"
+
+ .text
+ .set noat
+
+_start:
+ /* Linker script defines stack at end of ram. */
+ movia sp, __stack
+
+ /* Install trampoline to _fast_tlb_miss at hardcoded vector. */
+ movia r4, 0xc0000100
+ movia r5, _ftm_tramp
+ movi r6, .L__ftm_end - _ftm_tramp
+ call memcpy
+
+ /* Zero the bss to satisfy C. */
+ movia r4, __bss_start
+ movia r6, __bss_end
+ sub r6, r6, r4
+ movi r5, 0
+ call memset
+
+ /* Test! */
+ call main
+
+ /* Exit with main's return value. */
+ movi r4, HOSTED_EXIT
+ mov r5, r2
+ semihosting_call
+
+ .globl _start
+ .type _start, @function
+ .size _start, . - _start
+
+_ftm_tramp:
+ movia et, _fast_tlb_miss
+ jmp et
+.L__ftm_end:
+
+ .type _ftm_tramp, @function
+ .size _ftm_tramp, . - _ftm_tramp
+
+#define dst r4
+#define src r5
+#define len r6
+
+memcpy:
+ /* Store return value right away, per API */
+ mov r2, dst
+
+ /* Check for both dst and src aligned. */
+ or at, dst, src
+ andi at, at, 3
+ bne at, zero, .L_mc_test1
+
+ /* Copy blocks of 8. */
+
+ movi at, 8
+ bltu len, at, .L_mc_test4
+
+.L_mc_loop8:
+ ldw r8, 0(src)
+ ldw r9, 4(src)
+ addi src, src, 8
+ addi dst, dst, 8
+ subi len, len, 8
+ stw r8, -8(dst)
+ stw r9, -4(dst)
+ bgeu len, at, .L_mc_loop8
+
+ /* Copy final aligned block of 4. */
+
+.L_mc_test4:
+ movi at, 4
+ bltu len, at, .L_mc_test1
+
+ ldw r8, 0(src)
+ addi src, src, 4
+ addi dst, dst, 4
+ subi len, len, 4
+ stw r8, -4(dst)
+
+ /* Copy single bytes to finish. */
+
+.L_mc_test1:
+ beq len, zero, .L_mc_done
+
+.L_mc_loop1:
+ ldb r8, 0(src)
+ addi src, src, 1
+ addi dst, dst, 1
+ subi len, len, 1
+ stb r8, -1(dst)
+ bne len, zero, .L_mc_loop1
+
+.L_mc_done:
+ ret
+
+#undef dst
+#undef src
+#undef len
+
+ .global memcpy
+ .type memcpy, @function
+ .size memcpy, . - memcpy
+
+#define dst r4
+#define val r5
+#define len r6
+
+memset:
+ /* Store return value right away, per API */
+ mov r2, dst
+
+ /* Check for small blocks; fall back to bytewise. */
+ movi r3, 8
+ bltu len, r3, .L_ms_test1
+
+ /* Replicate the byte across the word. */
+ andi val, val, 0xff
+ slli at, val, 8
+ or val, val, at
+ slli at, val, 16
+ or val, val, at
+
+ /* Check for destination alignment; realign if needed. */
+ andi at, dst, 3
+ bne at, zero, .L_ms_align
+
+ /* Set blocks of 8. */
+
+.L_ms_loop8:
+ stw val, 0(dst)
+ stw val, 4(dst)
+ addi dst, dst, 8
+ subi len, len, 8
+ bgeu len, r3, .L_ms_loop8
+
+ /* Set final aligned block of 4. */
+
+.L_ms_test4:
+ movi at, 4
+ bltu len, at, .L_ms_test1
+
+ stw r8, 0(dst)
+ addi dst, dst, 4
+ subi len, len, 4
+ stw r8, -4(dst)
+
+ /* Set single bytes to finish. */
+
+.L_ms_test1:
+ beq len, zero, .L_ms_done
+
+.L_ms_loop1:
+ stb r8, 0(dst)
+ addi dst, dst, 1
+ subi len, len, 1
+ bne len, zero, .L_ms_loop1
+
+.L_ms_done:
+ ret
+
+ /* Realign for a large block, len >= 8. */
+.L_ms_align:
+ andi at, dst, 1
+ beq at, zero, 2f
+
+ stb val, 0(dst)
+ addi dst, dst, 1
+ subi len, len, 1
+
+2: andi at, dst, 2
+ beq at, zero, 4f
+
+ sth val, 0(dst)
+ addi dst, dst, 2
+ subi len, len, 2
+
+4: bgeu len, r3, .L_ms_loop8
+ br .L_ms_test4
+
+#undef dst
+#undef val
+#undef len
+
+ .global memset
+ .type memset, @function
+ .size memset, . - memset
+
+/*
+ * void __sys_outc(char c);
+ */
+__sys_outc:
+ subi sp, sp, 16
+ stb r4, 0(sp) /* buffer[0] = c */
+ movi at, 1
+ stw at, 4(sp) /* STDOUT_FILENO */
+ stw sp, 8(sp) /* buffer */
+ stw at, 12(sp) /* len */
+
+ movi r4, HOSTED_WRITE
+ addi r5, sp, 4
+ semihosting_call
+
+ addi sp, sp, 16
+ ret
+
+ .global __sys_outc
+ .type __sys_outc, @function
+ .size __sys_outc, . - __sys_outc
diff --git a/tests/tcg/nios2/intr.S b/tests/tcg/nios2/intr.S
new file mode 100644
index 0000000..c173069
--- /dev/null
+++ b/tests/tcg/nios2/intr.S
@@ -0,0 +1,31 @@
+/*
+ * Minimal Nios2 system boot code -- exit on interrupt.
+ *
+ * Copyright Linaro Ltd 2022
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "semicall.h"
+
+ .section .text.intr, "ax"
+ .global _interrupt
+ .type _interrupt, @function
+
+_interrupt:
+ rdctl r5, exception /* extract exception.CAUSE */
+ srli r5, r5, 2
+ movi r4, HOSTED_EXIT
+ semihosting_call
+
+ .size _interrupt, . - _interrupt
+
+ .text
+ .global _fast_tlb_miss
+ .type _fast_tlb_miss, @function
+
+_fast_tlb_miss:
+ movi r5, 32
+ movi r4, HOSTED_EXIT
+ semihosting_call
+
+ .size _fast_tlb_miss, . - _fast_tlb_miss
diff --git a/tests/tcg/nios2/semicall.h b/tests/tcg/nios2/semicall.h
new file mode 100644
index 0000000..6ad4978
--- /dev/null
+++ b/tests/tcg/nios2/semicall.h
@@ -0,0 +1,28 @@
+/*
+ * Nios2 semihosting interface.
+ *
+ * Copyright Linaro Ltd 2022
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SEMICALL_H
+#define SEMICALL_H
+
+#define HOSTED_EXIT 0
+#define HOSTED_INIT_SIM 1
+#define HOSTED_OPEN 2
+#define HOSTED_CLOSE 3
+#define HOSTED_READ 4
+#define HOSTED_WRITE 5
+#define HOSTED_LSEEK 6
+#define HOSTED_RENAME 7
+#define HOSTED_UNLINK 8
+#define HOSTED_STAT 9
+#define HOSTED_FSTAT 10
+#define HOSTED_GETTIMEOFDAY 11
+#define HOSTED_ISATTY 12
+#define HOSTED_SYSTEM 13
+
+#define semihosting_call break 1
+
+#endif /* SEMICALL_H */
diff --git a/tests/tcg/nios2/test-shadow-1.S b/tests/tcg/nios2/test-shadow-1.S
new file mode 100644
index 0000000..79ef69d
--- /dev/null
+++ b/tests/tcg/nios2/test-shadow-1.S
@@ -0,0 +1,40 @@
+/*
+ * Regression test for TCG indirect global lowering.
+ *
+ * Copyright Linaro Ltd 2022
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "semicall.h"
+
+ .text
+ .set noat
+ .align 2
+ .globl main
+ .type main, @function
+
+main:
+ /* Initialize r0 in shadow register set 1. */
+ movhi at, 1 /* PRS=1, CRS=0, RSIE=0, PIE=0 */
+ wrctl status, at
+ wrprs zero, zero
+
+ /* Change current register set to 1. */
+ movi at, 1 << 10 /* PRS=0, CRS=1, RSIE=0, PIE=0 */
+ wrctl estatus, at
+ movia ea, 1f
+ eret
+
+ /* Load address for callr, then end TB. */
+1: movia at, 3f
+ br 2f
+
+ /* Test case! TCG abort on indirect lowering across brcond. */
+2: callr at
+
+ /* exit(0) */
+3: movi r4, HOSTED_EXIT
+ movi r5, 0
+ semihosting_call
+
+ .size main, . - main