Merge tag 'pull-target-arm-20240111' of https://git.linaro.org/people/pmaydell/qemu-arm into staging
target-arm queue:
* Emulate FEAT_NV, FEAT_NV2
* add cache controller for Freescale i.MX6
* Add minimal support for the B-L475E-IOT01A board
* Allow SoC models to configure M-profile CPUs with correct number
of NVIC priority bits
* Add missing QOM parent for v7-M SoCs
* Set CTR_EL0.{IDC,DIC} for the 'max' CPU
* hw/intc/arm_gicv3_cpuif: handle LPIs in in the list registers
# -----BEGIN PGP SIGNATURE-----
#
# iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAmWfypMZHHBldGVyLm1h
# eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3sleD/4tQOMteba5BNMDns6R96E4
# kj5q0Iy9XyzQ486Q4cIZXI5N3BddCp2ks8WeS2W3w4IT/lms0U6UwXV4E98I4I/b
# KSfOoUd/cp8IvdvzfpWbmQcPMoauHZdCUN33pYYXOjfi1RkpzgNU5Qgh09Nl/xYU
# V3oaEvWhLtepT/fwJLYxoqVHDaEmyW+6zriF0+eGjZvkhgPyhllla9eti7AyHTfH
# T3A4Fyx/wudRE3NP6xsLfxldriJTxQeba+TqLSh3IXn/PMtK13/ARsY/hl72Q4ML
# Fgad8Zho4eXbuOQ9oiqb7gp4K3IKd9/8FbCzECoIAq7AnLAD4KwpLQR8GULRvYW3
# 0eQq2txTXQWNcmWpIyDRRME+qeNVwWSk+QJDs5WuhVqlVQ4hpqtgFf1EX+7ORdS1
# WG0fb8etvr8oCSkzCmP/o6xYGJ0EyTVMU5DmWviy3bxMrUMcmobjvCQr/n2gC713
# 1NDmEaYPbl+pX8EMu8byst7/No2PXRgIO0UVVb4KZybfhNy+BBs+LiMVlSRS5YH4
# 8NWtoYZlG9RcPnY+8Xrxz9VTi2cNAAcdbf5uK3snJxkFV2SmV3oBoMxWen3mee0f
# 2PNVEbt9zvPV8hViBVLsqRhVXd9wMq6motIRlkKge1u1TvwIxO21ibykI3tvYOGv
# BffIjhUdnYtX90JAtXtFDw==
# =yQwf
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 11 Jan 2024 11:01:39 GMT
# gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE
# gpg: issuer "peter.maydell@linaro.org"
# gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate]
# gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate]
# gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate]
# gpg: aka "Peter Maydell <peter@archaic.org.uk>" [ultimate]
# Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE
* tag 'pull-target-arm-20240111' of https://git.linaro.org/people/pmaydell/qemu-arm: (41 commits)
target/arm: Add FEAT_NV2 to max, neoverse-n2, neoverse-v1 CPUs
target/arm: Enhance CPU_LOG_INT to show SPSR on AArch64 exception-entry
target/arm: Report HCR_EL2.{NV,NV1,NV2} in cpu dumps
hw/intc/arm_gicv3_cpuif: Mark up VNCR offsets for GIC CPU registers
target/arm: Mark up VNCR offsets (offsets >= 0x200, except GIC)
target/arm: Mark up VNCR offsets (offsets 0x168..0x1f8)
target/arm: Mark up VNCR offsets (offsets 0x100..0x160)
target/arm: Mark up VNCR offsets (offsets 0x0..0xff)
target/arm: Report VNCR_EL2 based faults correctly
target/arm: Implement FEAT_NV2 redirection of sysregs to RAM
target/arm: Handle FEAT_NV2 redirection of SPSR_EL2, ELR_EL2, ESR_EL2, FAR_EL2
target/arm: Handle FEAT_NV2 changes to when SPSR_EL1.M reports EL2
target/arm: Implement VNCR_EL2 register
target/arm: Handle HCR_EL2 accesses for FEAT_NV2 bits
target/arm: Add FEAT_NV to max, neoverse-n2, neoverse-v1 CPUs
target/arm: Handle FEAT_NV page table attribute changes
target/arm: Treat LDTR* and STTR* as LDR/STR when NV, NV1 is 1, 1
target/arm: Don't honour PSTATE.PAN when HCR_EL2.{NV, NV1} == {1, 1}
target/arm: Always use arm_pan_enabled() when checking if PAN is enabled
target/arm: Trap registers when HCR_EL2.{NV, NV1} == {1, 1}
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/MAINTAINERS b/MAINTAINERS
index 00ec1f7..b406fb2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1122,6 +1122,21 @@
S: Maintained
F: hw/arm/olimex-stm32-h405.c
+STM32L4x5 SoC Family
+M: Arnaud Minier <arnaud.minier@telecom-paris.fr>
+M: Inès Varhol <ines.varhol@telecom-paris.fr>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/stm32l4x5_soc.c
+F: include/hw/arm/stm32l4x5_soc.h
+
+B-L475E-IOT01A IoT Node
+M: Arnaud Minier <arnaud.minier@telecom-paris.fr>
+M: Inès Varhol <ines.varhol@telecom-paris.fr>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/b-l475e-iot01a.c
+
SmartFusion2
M: Subbaraya Sundeep <sundeep.lkml@gmail.com>
M: Peter Maydell <peter.maydell@linaro.org>
diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-softmmu/default.mak
index 980c48a..023faa2 100644
--- a/configs/devices/arm-softmmu/default.mak
+++ b/configs/devices/arm-softmmu/default.mak
@@ -19,6 +19,7 @@
# CONFIG_NSERIES=n
# CONFIG_STELLARIS=n
# CONFIG_STM32VLDISCOVERY=n
+# CONFIG_B_L475E_IOT01A=n
# CONFIG_REALVIEW=n
# CONFIG_VERSATILE=n
# CONFIG_VEXPRESS=n
diff --git a/docs/system/arm/b-l475e-iot01a.rst b/docs/system/arm/b-l475e-iot01a.rst
new file mode 100644
index 0000000..2b128e6
--- /dev/null
+++ b/docs/system/arm/b-l475e-iot01a.rst
@@ -0,0 +1,46 @@
+B-L475E-IOT01A IoT Node (``b-l475e-iot01a``)
+============================================
+
+The B-L475E-IOT01A IoT Node uses the STM32L475VG SoC which is based on
+ARM Cortex-M4F core. It is part of STMicroelectronics
+:doc:`STM32 boards </system/arm/stm32>` and more specifically the STM32L4
+ultra-low power series. The STM32L4x5 chip runs at up to 80 MHz and
+integrates 128 KiB of SRAM and up to 1MiB of Flash. The B-L475E-IOT01A board
+namely features 64 Mibit QSPI Flash, BT, WiFi and RF connectivity,
+USART, I2C, SPI, CAN and USB OTG, as well as a variety of sensors.
+
+Supported devices
+"""""""""""""""""
+
+Currently, B-L475E-IOT01A machine's implementation is minimal,
+it only supports the following device:
+
+- Cortex-M4F based STM32L4x5 SoC
+
+Missing devices
+"""""""""""""""
+
+The B-L475E-IOT01A does *not* support the following devices:
+
+- Extended interrupts and events controller (EXTI)
+- Reset and clock control (RCC)
+- Serial ports (UART)
+- System configuration controller (SYSCFG)
+- General-purpose I/Os (GPIO)
+- Analog to Digital Converter (ADC)
+- SPI controller
+- Timer controller (TIMER)
+
+See the complete list of unimplemented peripheral devices
+in the STM32L4x5 module : ``./hw/arm/stm32l4x5_soc.c``
+
+Boot options
+""""""""""""
+
+The B-L475E-IOT01A machine can be started using the ``-kernel``
+option to load a firmware. Example:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -M b-l475e-iot01a -kernel firmware.bin
+
diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst
index 0b604f9..f67aea2 100644
--- a/docs/system/arm/emulation.rst
+++ b/docs/system/arm/emulation.rst
@@ -63,6 +63,8 @@
- FEAT_MTE (Memory Tagging Extension)
- FEAT_MTE2 (Memory Tagging Extension)
- FEAT_MTE3 (MTE Asymmetric Fault Handling)
+- FEAT_NV (Nested Virtualization)
+- FEAT_NV2 (Enhanced nested virtualization support)
- FEAT_PACIMP (Pointer authentication - IMPLEMENTATION DEFINED algorithm)
- FEAT_PACQARMA3 (Pointer authentication - QARMA3 algorithm)
- FEAT_PACQARMA5 (Pointer authentication - QARMA5 algorithm)
diff --git a/docs/system/arm/stm32.rst b/docs/system/arm/stm32.rst
index d7265b7..3b640f3 100644
--- a/docs/system/arm/stm32.rst
+++ b/docs/system/arm/stm32.rst
@@ -16,11 +16,13 @@
- ``netduino2`` Netduino 2 board with STM32F205RFT6 microcontroller
-The STM32F4 series is based on ARM Cortex-M4F core. This series is pin-to-pin
-compatible with STM32F2 series. The following machines are based on this chip :
+The STM32F4 series is based on ARM Cortex-M4F core, as well as the STM32L4
+ultra-low-power series. The STM32F4 series is pin-to-pin compatible with STM32F2 series.
+The following machines are based on this ARM Cortex-M4F chip :
- ``netduinoplus2`` Netduino Plus 2 board with STM32F405RGT6 microcontroller
- ``olimex-stm32-h405`` Olimex STM32 H405 board with STM32F405RGT6 microcontroller
+- ``b-l475e-iot01a`` :doc:`B-L475E-IOT01A IoT Node </system/arm/b-l475e-iot01a>` board with STM32L475VG microcontroller
There are many other STM32 series that are currently not supported by QEMU.
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index 790ac1b..c9d7c0d 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -84,6 +84,7 @@
arm/vexpress
arm/aspeed
arm/bananapi_m2u.rst
+ arm/b-l475e-iot01a.rst
arm/sabrelite
arm/digic
arm/cubieboard
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 660f49d..39d2554 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -449,6 +449,17 @@
select STM32F4XX_SYSCFG
select STM32F4XX_EXTI
+config B_L475E_IOT01A
+ bool
+ default y
+ depends on TCG && ARM
+ select STM32L4X5_SOC
+
+config STM32L4X5_SOC
+ bool
+ select ARM_V7M
+ select OR_IRQ
+
config XLNX_ZYNQMP_ARM
bool
default y if PIXMAN
@@ -537,6 +548,7 @@
select IMX_I2C
select IMX_USBPHY
select WDT_IMX2
+ select PL310 # cache controller
select SDHCI
config ASPEED_SOC
diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c
index e39b61b..1f21827 100644
--- a/hw/arm/armv7m.c
+++ b/hw/arm/armv7m.c
@@ -256,6 +256,8 @@
object_initialize_child(obj, "nvic", &s->nvic, TYPE_NVIC);
object_property_add_alias(obj, "num-irq",
OBJECT(&s->nvic), "num-irq");
+ object_property_add_alias(obj, "num-prio-bits",
+ OBJECT(&s->nvic), "num-prio-bits");
object_initialize_child(obj, "systick-reg-ns", &s->systick[M_REG_NS],
TYPE_SYSTICK);
diff --git a/hw/arm/b-l475e-iot01a.c b/hw/arm/b-l475e-iot01a.c
new file mode 100644
index 0000000..6ecde2d
--- /dev/null
+++ b/hw/arm/b-l475e-iot01a.c
@@ -0,0 +1,72 @@
+/*
+ * B-L475E-IOT01A Discovery Kit machine
+ * (B-L475E-IOT01A IoT Node)
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This work is heavily inspired by the netduinoplus2 by Alistair Francis.
+ * Original code is licensed under the MIT License:
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ */
+
+/*
+ * The reference used is the STMicroElectronics UM2153 User manual
+ * Discovery kit for IoT node, multi-channel communication with STM32L4.
+ * https://www.st.com/en/evaluation-tools/b-l475e-iot01a.html#documentation
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/boards.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-clock.h"
+#include "qemu/error-report.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/arm/boot.h"
+
+/* Main SYSCLK frequency in Hz (80MHz) */
+#define MAIN_SYSCLK_FREQ_HZ 80000000ULL
+
+static void b_l475e_iot01a_init(MachineState *machine)
+{
+ const Stm32l4x5SocClass *sc;
+ DeviceState *dev;
+ Clock *sysclk;
+
+ /* This clock doesn't need migration because it is fixed-frequency */
+ sysclk = clock_new(OBJECT(machine), "SYSCLK");
+ clock_set_hz(sysclk, MAIN_SYSCLK_FREQ_HZ);
+
+ dev = qdev_new(TYPE_STM32L4X5XG_SOC);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
+ qdev_connect_clock_in(dev, "sysclk", sysclk);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ sc = STM32L4X5_SOC_GET_CLASS(dev);
+ armv7m_load_kernel(ARM_CPU(first_cpu),
+ machine->kernel_filename,
+ 0, sc->flash_size);
+}
+
+static void b_l475e_iot01a_machine_init(MachineClass *mc)
+{
+ static const char *machine_valid_cpu_types[] = {
+ ARM_CPU_TYPE_NAME("cortex-m4"),
+ NULL
+ };
+ mc->desc = "B-L475E-IOT01A Discovery Kit (Cortex-M4)";
+ mc->init = b_l475e_iot01a_init;
+ mc->valid_cpu_types = machine_valid_cpu_types;
+
+ /* SRAM pre-allocated as part of the SoC instantiation */
+ mc->default_ram_size = 0;
+}
+
+DEFINE_MACHINE("b-l475e-iot01a", b_l475e_iot01a_machine_init)
diff --git a/hw/arm/fsl-imx6.c b/hw/arm/fsl-imx6.c
index b215302..af2e982 100644
--- a/hw/arm/fsl-imx6.c
+++ b/hw/arm/fsl-imx6.c
@@ -154,6 +154,9 @@
qdev_get_gpio_in(DEVICE(&s->cpu[i]), ARM_CPU_FIQ));
}
+ /* L2 cache controller */
+ sysbus_create_simple("l2x0", FSL_IMX6_PL310_ADDR, NULL);
+
if (!sysbus_realize(SYS_BUS_DEVICE(&s->ccm), errp)) {
return;
}
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 68245d3..bb92b27 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -42,6 +42,8 @@
arm_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
arm_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
arm_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
+arm_ss.add(when: 'CONFIG_B_L475E_IOT01A', if_true: files('b-l475e-iot01a.c'))
+arm_ss.add(when: 'CONFIG_STM32L4X5_SOC', if_true: files('stm32l4x5_soc.c'))
arm_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp.c', 'xlnx-zcu102.c'))
arm_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal.c', 'xlnx-versal-virt.c'))
arm_ss.add(when: 'CONFIG_FSL_IMX25', if_true: files('fsl-imx25.c', 'imx25_pdk.c'))
diff --git a/hw/arm/msf2-som.c b/hw/arm/msf2-som.c
index eb74b23..a269cf0 100644
--- a/hw/arm/msf2-som.c
+++ b/hw/arm/msf2-som.c
@@ -60,6 +60,7 @@
memory_region_add_subregion(sysmem, DDR_BASE_ADDRESS, ddr);
dev = qdev_new(TYPE_MSF2_SOC);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
qdev_prop_set_string(dev, "part-name", "M2S010");
qdev_prop_set_string(dev, "cpu-type", mc->default_cpu_type);
diff --git a/hw/arm/netduino2.c b/hw/arm/netduino2.c
index 501f63a..8b1a9a2 100644
--- a/hw/arm/netduino2.c
+++ b/hw/arm/netduino2.c
@@ -44,6 +44,7 @@
clock_set_hz(sysclk, SYSCLK_FRQ);
dev = qdev_new(TYPE_STM32F205_SOC);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
qdev_connect_clock_in(dev, "sysclk", sysclk);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
diff --git a/hw/arm/netduinoplus2.c b/hw/arm/netduinoplus2.c
index 2e58984..bccd100 100644
--- a/hw/arm/netduinoplus2.c
+++ b/hw/arm/netduinoplus2.c
@@ -44,6 +44,7 @@
clock_set_hz(sysclk, SYSCLK_FRQ);
dev = qdev_new(TYPE_STM32F405_SOC);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
qdev_connect_clock_in(dev, "sysclk", sysclk);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
diff --git a/hw/arm/olimex-stm32-h405.c b/hw/arm/olimex-stm32-h405.c
index d793de7..4ad7b04 100644
--- a/hw/arm/olimex-stm32-h405.c
+++ b/hw/arm/olimex-stm32-h405.c
@@ -47,6 +47,7 @@
clock_set_hz(sysclk, SYSCLK_FRQ);
dev = qdev_new(TYPE_STM32F405_SOC);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
qdev_connect_clock_in(dev, "sysclk", sysclk);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
diff --git a/hw/arm/stellaris.c b/hw/arm/stellaris.c
index 729a8bf..d18b114 100644
--- a/hw/arm/stellaris.c
+++ b/hw/arm/stellaris.c
@@ -47,6 +47,7 @@
#define BP_GAMEPAD 0x04
#define NUM_IRQ_LINES 64
+#define NUM_PRIO_BITS 3
typedef const struct {
const char *name;
@@ -1067,6 +1068,7 @@
nvic = qdev_new(TYPE_ARMV7M);
qdev_prop_set_uint32(nvic, "num-irq", NUM_IRQ_LINES);
+ qdev_prop_set_uint8(nvic, "num-prio-bits", NUM_PRIO_BITS);
qdev_prop_set_string(nvic, "cpu-type", ms->cpu_type);
qdev_prop_set_bit(nvic, "enable-bitband", true);
qdev_connect_clock_in(nvic, "cpuclk",
diff --git a/hw/arm/stm32f100_soc.c b/hw/arm/stm32f100_soc.c
index b90d440..808b783 100644
--- a/hw/arm/stm32f100_soc.c
+++ b/hw/arm/stm32f100_soc.c
@@ -115,6 +115,7 @@
/* Init ARMv7m */
armv7m = DEVICE(&s->armv7m);
qdev_prop_set_uint32(armv7m, "num-irq", 61);
+ qdev_prop_set_uint8(armv7m, "num-prio-bits", 4);
qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
qdev_prop_set_bit(armv7m, "enable-bitband", true);
qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
diff --git a/hw/arm/stm32f205_soc.c b/hw/arm/stm32f205_soc.c
index 1a54864..a451e21 100644
--- a/hw/arm/stm32f205_soc.c
+++ b/hw/arm/stm32f205_soc.c
@@ -127,6 +127,7 @@
armv7m = DEVICE(&s->armv7m);
qdev_prop_set_uint32(armv7m, "num-irq", 96);
+ qdev_prop_set_uint8(armv7m, "num-prio-bits", 4);
qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m3"));
qdev_prop_set_bit(armv7m, "enable-bitband", true);
qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
diff --git a/hw/arm/stm32f405_soc.c b/hw/arm/stm32f405_soc.c
index a65bbe2..2ad5b79 100644
--- a/hw/arm/stm32f405_soc.c
+++ b/hw/arm/stm32f405_soc.c
@@ -149,6 +149,7 @@
armv7m = DEVICE(&s->armv7m);
qdev_prop_set_uint32(armv7m, "num-irq", 96);
+ qdev_prop_set_uint8(armv7m, "num-prio-bits", 4);
qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m4"));
qdev_prop_set_bit(armv7m, "enable-bitband", true);
qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
diff --git a/hw/arm/stm32l4x5_soc.c b/hw/arm/stm32l4x5_soc.c
new file mode 100644
index 0000000..159d531
--- /dev/null
+++ b/hw/arm/stm32l4x5_soc.c
@@ -0,0 +1,266 @@
+/*
+ * STM32L4x5 SoC family
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This work is heavily inspired by the stm32f405_soc by Alistair Francis.
+ * Original code is licensed under the MIT License:
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ */
+
+/*
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ * https://www.st.com/en/microcontrollers-microprocessors/stm32l4x5/documentation.html
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+#include "hw/arm/stm32l4x5_soc.h"
+#include "hw/qdev-clock.h"
+#include "hw/misc/unimp.h"
+
+#define FLASH_BASE_ADDRESS 0x08000000
+#define SRAM1_BASE_ADDRESS 0x20000000
+#define SRAM1_SIZE (96 * KiB)
+#define SRAM2_BASE_ADDRESS 0x10000000
+#define SRAM2_SIZE (32 * KiB)
+
+static void stm32l4x5_soc_initfn(Object *obj)
+{
+ Stm32l4x5SocState *s = STM32L4X5_SOC(obj);
+
+ s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0);
+ s->refclk = qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0);
+}
+
+static void stm32l4x5_soc_realize(DeviceState *dev_soc, Error **errp)
+{
+ ERRP_GUARD();
+ Stm32l4x5SocState *s = STM32L4X5_SOC(dev_soc);
+ const Stm32l4x5SocClass *sc = STM32L4X5_SOC_GET_CLASS(dev_soc);
+ MemoryRegion *system_memory = get_system_memory();
+ DeviceState *armv7m;
+
+ /*
+ * We use s->refclk internally and only define it with qdev_init_clock_in()
+ * so it is correctly parented and not leaked on an init/deinit; it is not
+ * intended as an externally exposed clock.
+ */
+ if (clock_has_source(s->refclk)) {
+ error_setg(errp, "refclk clock must not be wired up by the board code");
+ return;
+ }
+
+ if (!clock_has_source(s->sysclk)) {
+ error_setg(errp, "sysclk clock must be wired up by the board code");
+ return;
+ }
+
+ /*
+ * TODO: ideally we should model the SoC RCC and its ability to
+ * change the sysclk frequency and define different sysclk sources.
+ */
+
+ /* The refclk always runs at frequency HCLK / 8 */
+ clock_set_mul_div(s->refclk, 8, 1);
+ clock_set_source(s->refclk, s->sysclk);
+
+ if (!memory_region_init_rom(&s->flash, OBJECT(dev_soc), "flash",
+ sc->flash_size, errp)) {
+ return;
+ }
+ memory_region_init_alias(&s->flash_alias, OBJECT(dev_soc),
+ "flash_boot_alias", &s->flash, 0,
+ sc->flash_size);
+
+ memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, &s->flash);
+ memory_region_add_subregion(system_memory, 0, &s->flash_alias);
+
+ if (!memory_region_init_ram(&s->sram1, OBJECT(dev_soc), "SRAM1", SRAM1_SIZE,
+ errp)) {
+ return;
+ }
+ memory_region_add_subregion(system_memory, SRAM1_BASE_ADDRESS, &s->sram1);
+
+ if (!memory_region_init_ram(&s->sram2, OBJECT(dev_soc), "SRAM2", SRAM2_SIZE,
+ errp)) {
+ return;
+ }
+ memory_region_add_subregion(system_memory, SRAM2_BASE_ADDRESS, &s->sram2);
+
+ object_initialize_child(OBJECT(dev_soc), "armv7m", &s->armv7m, TYPE_ARMV7M);
+ armv7m = DEVICE(&s->armv7m);
+ qdev_prop_set_uint32(armv7m, "num-irq", 96);
+ qdev_prop_set_uint32(armv7m, "num-prio-bits", 4);
+ qdev_prop_set_string(armv7m, "cpu-type", ARM_CPU_TYPE_NAME("cortex-m4"));
+ qdev_prop_set_bit(armv7m, "enable-bitband", true);
+ qdev_connect_clock_in(armv7m, "cpuclk", s->sysclk);
+ qdev_connect_clock_in(armv7m, "refclk", s->refclk);
+ object_property_set_link(OBJECT(&s->armv7m), "memory",
+ OBJECT(system_memory), &error_abort);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
+ return;
+ }
+
+ /* APB1 BUS */
+ create_unimplemented_device("TIM2", 0x40000000, 0x400);
+ create_unimplemented_device("TIM3", 0x40000400, 0x400);
+ create_unimplemented_device("TIM4", 0x40000800, 0x400);
+ create_unimplemented_device("TIM5", 0x40000C00, 0x400);
+ create_unimplemented_device("TIM6", 0x40001000, 0x400);
+ create_unimplemented_device("TIM7", 0x40001400, 0x400);
+ /* RESERVED: 0x40001800, 0x1000 */
+ create_unimplemented_device("RTC", 0x40002800, 0x400);
+ create_unimplemented_device("WWDG", 0x40002C00, 0x400);
+ create_unimplemented_device("IWDG", 0x40003000, 0x400);
+ /* RESERVED: 0x40001800, 0x400 */
+ create_unimplemented_device("SPI2", 0x40003800, 0x400);
+ create_unimplemented_device("SPI3", 0x40003C00, 0x400);
+ /* RESERVED: 0x40004000, 0x400 */
+ create_unimplemented_device("USART2", 0x40004400, 0x400);
+ create_unimplemented_device("USART3", 0x40004800, 0x400);
+ create_unimplemented_device("UART4", 0x40004C00, 0x400);
+ create_unimplemented_device("UART5", 0x40005000, 0x400);
+ create_unimplemented_device("I2C1", 0x40005400, 0x400);
+ create_unimplemented_device("I2C2", 0x40005800, 0x400);
+ create_unimplemented_device("I2C3", 0x40005C00, 0x400);
+ /* RESERVED: 0x40006000, 0x400 */
+ create_unimplemented_device("CAN1", 0x40006400, 0x400);
+ /* RESERVED: 0x40006800, 0x400 */
+ create_unimplemented_device("PWR", 0x40007000, 0x400);
+ create_unimplemented_device("DAC1", 0x40007400, 0x400);
+ create_unimplemented_device("OPAMP", 0x40007800, 0x400);
+ create_unimplemented_device("LPTIM1", 0x40007C00, 0x400);
+ create_unimplemented_device("LPUART1", 0x40008000, 0x400);
+ /* RESERVED: 0x40008400, 0x400 */
+ create_unimplemented_device("SWPMI1", 0x40008800, 0x400);
+ /* RESERVED: 0x40008C00, 0x800 */
+ create_unimplemented_device("LPTIM2", 0x40009400, 0x400);
+ /* RESERVED: 0x40009800, 0x6800 */
+
+ /* APB2 BUS */
+ create_unimplemented_device("SYSCFG", 0x40010000, 0x30);
+ create_unimplemented_device("VREFBUF", 0x40010030, 0x1D0);
+ create_unimplemented_device("COMP", 0x40010200, 0x200);
+ create_unimplemented_device("EXTI", 0x40010400, 0x400);
+ /* RESERVED: 0x40010800, 0x1400 */
+ create_unimplemented_device("FIREWALL", 0x40011C00, 0x400);
+ /* RESERVED: 0x40012000, 0x800 */
+ create_unimplemented_device("SDMMC1", 0x40012800, 0x400);
+ create_unimplemented_device("TIM1", 0x40012C00, 0x400);
+ create_unimplemented_device("SPI1", 0x40013000, 0x400);
+ create_unimplemented_device("TIM8", 0x40013400, 0x400);
+ create_unimplemented_device("USART1", 0x40013800, 0x400);
+ /* RESERVED: 0x40013C00, 0x400 */
+ create_unimplemented_device("TIM15", 0x40014000, 0x400);
+ create_unimplemented_device("TIM16", 0x40014400, 0x400);
+ create_unimplemented_device("TIM17", 0x40014800, 0x400);
+ /* RESERVED: 0x40014C00, 0x800 */
+ create_unimplemented_device("SAI1", 0x40015400, 0x400);
+ create_unimplemented_device("SAI2", 0x40015800, 0x400);
+ /* RESERVED: 0x40015C00, 0x400 */
+ create_unimplemented_device("DFSDM1", 0x40016000, 0x400);
+ /* RESERVED: 0x40016400, 0x9C00 */
+
+ /* AHB1 BUS */
+ create_unimplemented_device("DMA1", 0x40020000, 0x400);
+ create_unimplemented_device("DMA2", 0x40020400, 0x400);
+ /* RESERVED: 0x40020800, 0x800 */
+ create_unimplemented_device("RCC", 0x40021000, 0x400);
+ /* RESERVED: 0x40021400, 0xC00 */
+ create_unimplemented_device("FLASH", 0x40022000, 0x400);
+ /* RESERVED: 0x40022400, 0xC00 */
+ create_unimplemented_device("CRC", 0x40023000, 0x400);
+ /* RESERVED: 0x40023400, 0x400 */
+ create_unimplemented_device("TSC", 0x40024000, 0x400);
+
+ /* RESERVED: 0x40024400, 0x7FDBC00 */
+
+ /* AHB2 BUS */
+ create_unimplemented_device("GPIOA", 0x48000000, 0x400);
+ create_unimplemented_device("GPIOB", 0x48000400, 0x400);
+ create_unimplemented_device("GPIOC", 0x48000800, 0x400);
+ create_unimplemented_device("GPIOD", 0x48000C00, 0x400);
+ create_unimplemented_device("GPIOE", 0x48001000, 0x400);
+ create_unimplemented_device("GPIOF", 0x48001400, 0x400);
+ create_unimplemented_device("GPIOG", 0x48001800, 0x400);
+ create_unimplemented_device("GPIOH", 0x48001C00, 0x400);
+ /* RESERVED: 0x48002000, 0x7FDBC00 */
+ create_unimplemented_device("OTG_FS", 0x50000000, 0x40000);
+ create_unimplemented_device("ADC", 0x50040000, 0x400);
+ /* RESERVED: 0x50040400, 0x20400 */
+ create_unimplemented_device("RNG", 0x50060800, 0x400);
+
+ /* AHB3 BUS */
+ create_unimplemented_device("FMC", 0xA0000000, 0x1000);
+ create_unimplemented_device("QUADSPI", 0xA0001000, 0x400);
+}
+
+static void stm32l4x5_soc_class_init(ObjectClass *klass, void *data)
+{
+
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = stm32l4x5_soc_realize;
+ /* Reason: Mapped at fixed location on the system bus */
+ dc->user_creatable = false;
+ /* No vmstate or reset required: device has no internal state */
+}
+
+static void stm32l4x5xc_soc_class_init(ObjectClass *oc, void *data)
+{
+ Stm32l4x5SocClass *ssc = STM32L4X5_SOC_CLASS(oc);
+
+ ssc->flash_size = 256 * KiB;
+}
+
+static void stm32l4x5xe_soc_class_init(ObjectClass *oc, void *data)
+{
+ Stm32l4x5SocClass *ssc = STM32L4X5_SOC_CLASS(oc);
+
+ ssc->flash_size = 512 * KiB;
+}
+
+static void stm32l4x5xg_soc_class_init(ObjectClass *oc, void *data)
+{
+ Stm32l4x5SocClass *ssc = STM32L4X5_SOC_CLASS(oc);
+
+ ssc->flash_size = 1 * MiB;
+}
+
+static const TypeInfo stm32l4x5_soc_types[] = {
+ {
+ .name = TYPE_STM32L4X5XC_SOC,
+ .parent = TYPE_STM32L4X5_SOC,
+ .class_init = stm32l4x5xc_soc_class_init,
+ }, {
+ .name = TYPE_STM32L4X5XE_SOC,
+ .parent = TYPE_STM32L4X5_SOC,
+ .class_init = stm32l4x5xe_soc_class_init,
+ }, {
+ .name = TYPE_STM32L4X5XG_SOC,
+ .parent = TYPE_STM32L4X5_SOC,
+ .class_init = stm32l4x5xg_soc_class_init,
+ }, {
+ .name = TYPE_STM32L4X5_SOC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Stm32l4x5SocState),
+ .instance_init = stm32l4x5_soc_initfn,
+ .class_size = sizeof(Stm32l4x5SocClass),
+ .class_init = stm32l4x5_soc_class_init,
+ .abstract = true,
+ }
+};
+
+DEFINE_TYPES(stm32l4x5_soc_types)
diff --git a/hw/arm/stm32vldiscovery.c b/hw/arm/stm32vldiscovery.c
index 190db61..cc41935 100644
--- a/hw/arm/stm32vldiscovery.c
+++ b/hw/arm/stm32vldiscovery.c
@@ -47,6 +47,7 @@
clock_set_hz(sysclk, SYSCLK_FRQ);
dev = qdev_new(TYPE_STM32F100_SOC);
+ object_property_add_child(OBJECT(machine), "soc", OBJECT(dev));
qdev_connect_clock_in(dev, "sysclk", sysclk);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c
index 77c2a6d..e1a60d8 100644
--- a/hw/intc/arm_gicv3_cpuif.c
+++ b/hw/intc/arm_gicv3_cpuif.c
@@ -1434,16 +1434,25 @@
idx = icv_find_active(cs, irq);
if (idx < 0) {
- /* No valid list register corresponding to EOI ID */
- icv_increment_eoicount(cs);
+ /*
+ * No valid list register corresponding to EOI ID; if this is a vLPI
+ * not in the list regs then do nothing; otherwise increment EOI count
+ */
+ if (irq < GICV3_LPI_INTID_START) {
+ icv_increment_eoicount(cs);
+ }
} else {
uint64_t lr = cs->ich_lr_el2[idx];
int thisgrp = (lr & ICH_LR_EL2_GROUP) ? GICV3_G1NS : GICV3_G0;
int lr_gprio = ich_lr_prio(lr) & icv_gprio_mask(cs, grp);
if (thisgrp == grp && lr_gprio == dropprio) {
- if (!icv_eoi_split(env, cs)) {
- /* Priority drop and deactivate not split: deactivate irq now */
+ if (!icv_eoi_split(env, cs) || irq >= GICV3_LPI_INTID_START) {
+ /*
+ * Priority drop and deactivate not split: deactivate irq now.
+ * LPIs always get their active state cleared immediately
+ * because no separate deactivate is expected.
+ */
icv_deactivate_irq(cs, idx);
}
}
@@ -2675,6 +2684,7 @@
{ .name = "ICH_AP0R0_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 8, .opc2 = 0,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x480,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2682,6 +2692,7 @@
{ .name = "ICH_AP1R0_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 9, .opc2 = 0,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x4a0,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2689,6 +2700,7 @@
{ .name = "ICH_HCR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 11, .opc2 = 0,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x4c0,
.access = PL2_RW,
.readfn = ich_hcr_read,
.writefn = ich_hcr_write,
@@ -2720,6 +2732,7 @@
{ .name = "ICH_VMCR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 11, .opc2 = 7,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x4c8,
.access = PL2_RW,
.readfn = ich_vmcr_read,
.writefn = ich_vmcr_write,
@@ -2730,6 +2743,7 @@
{ .name = "ICH_AP0R1_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 8, .opc2 = 1,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x488,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2737,6 +2751,7 @@
{ .name = "ICH_AP1R1_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 9, .opc2 = 1,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x4a8,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2747,6 +2762,7 @@
{ .name = "ICH_AP0R2_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 8, .opc2 = 2,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x490,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2754,6 +2770,7 @@
{ .name = "ICH_AP0R3_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 8, .opc2 = 3,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x498,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2761,6 +2778,7 @@
{ .name = "ICH_AP1R2_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 9, .opc2 = 2,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x4b0,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2768,6 +2786,7 @@
{ .name = "ICH_AP1R3_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 9, .opc2 = 3,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x4b8,
.access = PL2_RW,
.readfn = ich_ap_read,
.writefn = ich_ap_write,
@@ -2889,6 +2908,7 @@
.opc0 = 3, .opc1 = 4, .crn = 12,
.crm = 12 + (j >> 3), .opc2 = j & 7,
.type = ARM_CP_IO | ARM_CP_NO_RAW,
+ .nv2_redirect_offset = 0x400 + 8 * j,
.access = PL2_RW,
.readfn = ich_lr_read,
.writefn = ich_lr_write,
diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
index 50f9a97..404a445 100644
--- a/hw/intc/armv7m_nvic.c
+++ b/hw/intc/armv7m_nvic.c
@@ -2572,6 +2572,11 @@
static Property props_nvic[] = {
/* Number of external IRQ lines (so excluding the 16 internal exceptions) */
DEFINE_PROP_UINT32("num-irq", NVICState, num_irq, 64),
+ /*
+ * Number of the maximum priority bits that can be used. 0 means
+ * to use a reasonable default.
+ */
+ DEFINE_PROP_UINT8("num-prio-bits", NVICState, num_prio_bits, 0),
DEFINE_PROP_END_OF_LIST()
};
@@ -2685,7 +2690,23 @@
/* include space for internal exception vectors */
s->num_irq += NVIC_FIRST_IRQ;
- s->num_prio_bits = arm_feature(&s->cpu->env, ARM_FEATURE_V7) ? 8 : 2;
+ if (s->num_prio_bits == 0) {
+ /*
+ * If left unspecified, use 2 bits by default on Cortex-M0/M0+/M1
+ * and 8 bits otherwise.
+ */
+ s->num_prio_bits = arm_feature(&s->cpu->env, ARM_FEATURE_V7) ? 8 : 2;
+ } else {
+ uint8_t min_prio_bits =
+ arm_feature(&s->cpu->env, ARM_FEATURE_V7) ? 3 : 2;
+ if (s->num_prio_bits < min_prio_bits || s->num_prio_bits > 8) {
+ error_setg(errp,
+ "num-prio-bits %d is outside "
+ "NVIC acceptable range [%d-8]",
+ s->num_prio_bits, min_prio_bits);
+ return;
+ }
+ }
/*
* This device provides a single memory region which covers the
diff --git a/include/hw/arm/armv7m.h b/include/hw/arm/armv7m.h
index e2cebbd..5c057ab 100644
--- a/include/hw/arm/armv7m.h
+++ b/include/hw/arm/armv7m.h
@@ -43,6 +43,7 @@
* a qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET).
* + Property "cpu-type": CPU type to instantiate
* + Property "num-irq": number of external IRQ lines
+ * + Property "num-prio-bits": number of priority bits in the NVIC
* + Property "memory": MemoryRegion defining the physical address space
* that CPU accesses see. (The NVIC, bitbanding and other CPU-internal
* devices will be automatically layered on top of this view.)
diff --git a/include/hw/arm/stm32l4x5_soc.h b/include/hw/arm/stm32l4x5_soc.h
new file mode 100644
index 0000000..2fd44a3
--- /dev/null
+++ b/include/hw/arm/stm32l4x5_soc.h
@@ -0,0 +1,57 @@
+/*
+ * STM32L4x5 SoC family
+ *
+ * Copyright (c) 2023 Arnaud Minier <arnaud.minier@telecom-paris.fr>
+ * Copyright (c) 2023 Inès Varhol <ines.varhol@telecom-paris.fr>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This work is heavily inspired by the stm32f405_soc by Alistair Francis.
+ * Original code is licensed under the MIT License:
+ *
+ * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me>
+ */
+
+/*
+ * The reference used is the STMicroElectronics RM0351 Reference manual
+ * for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
+ * https://www.st.com/en/microcontrollers-microprocessors/stm32l4x5/documentation.html
+ */
+
+#ifndef HW_ARM_STM32L4x5_SOC_H
+#define HW_ARM_STM32L4x5_SOC_H
+
+#include "exec/memory.h"
+#include "hw/arm/armv7m.h"
+#include "qom/object.h"
+
+#define TYPE_STM32L4X5_SOC "stm32l4x5-soc"
+#define TYPE_STM32L4X5XC_SOC "stm32l4x5xc-soc"
+#define TYPE_STM32L4X5XE_SOC "stm32l4x5xe-soc"
+#define TYPE_STM32L4X5XG_SOC "stm32l4x5xg-soc"
+OBJECT_DECLARE_TYPE(Stm32l4x5SocState, Stm32l4x5SocClass, STM32L4X5_SOC)
+
+struct Stm32l4x5SocState {
+ SysBusDevice parent_obj;
+
+ ARMv7MState armv7m;
+
+ MemoryRegion sram1;
+ MemoryRegion sram2;
+ MemoryRegion flash;
+ MemoryRegion flash_alias;
+
+ Clock *sysclk;
+ Clock *refclk;
+};
+
+struct Stm32l4x5SocClass {
+ SysBusDeviceClass parent_class;
+
+ size_t flash_size;
+};
+
+#endif
diff --git a/target/arm/cpregs.h b/target/arm/cpregs.h
index f1293d1..b6fdd0f 100644
--- a/target/arm/cpregs.h
+++ b/target/arm/cpregs.h
@@ -118,6 +118,11 @@
* ARM pseudocode function CheckSMEAccess().
*/
ARM_CP_SME = 1 << 19,
+ /*
+ * Flag: one of the four EL2 registers which redirect to the
+ * equivalent EL1 register when FEAT_NV2 is enabled.
+ */
+ ARM_CP_NV2_REDIRECT = 1 << 20,
};
/*
@@ -821,6 +826,11 @@
#define CP_ANY 0xff
+/* Flags in the high bits of nv2_redirect_offset */
+#define NV2_REDIR_NV1 0x4000 /* Only redirect when HCR_EL2.NV1 == 1 */
+#define NV2_REDIR_NO_NV1 0x8000 /* Only redirect when HCR_EL2.NV1 == 0 */
+#define NV2_REDIR_FLAG_MASK 0xc000
+
/* Definition of an ARM coprocessor register */
struct ARMCPRegInfo {
/* Name of register (useful mainly for debugging, need not be unique) */
@@ -862,6 +872,13 @@
* value encodes both the trap register and bit within it.
*/
FGTBit fgt;
+
+ /*
+ * Offset from VNCR_EL2 when FEAT_NV2 redirects access to memory;
+ * may include an NV2_REDIR_* flag.
+ */
+ uint32_t nv2_redirect_offset;
+
/*
* The opaque pointer passed to define_arm_cp_regs_with_opaque() when
* this register was defined: can be used to hand data through to the
@@ -937,7 +954,7 @@
CPResetFn *resetfn;
/*
- * "Original" writefn and readfn.
+ * "Original" readfn, writefn, accessfn.
* For ARMv8.1-VHE register aliases, we overwrite the read/write
* accessor functions of various EL1/EL0 to perform the runtime
* check for which sysreg should actually be modified, and then
@@ -948,6 +965,7 @@
*/
CPReadFn *orig_readfn;
CPWriteFn *orig_writefn;
+ CPAccessFn *orig_accessfn;
};
/*
@@ -1079,4 +1097,38 @@
CPAccessResult access_tvm_trvm(CPUARMState *, const ARMCPRegInfo *, bool);
+/**
+ * arm_cpreg_trap_in_nv: Return true if cpreg traps in nested virtualization
+ *
+ * Return true if this cpreg is one which should be trapped to EL2 if
+ * it is executed at EL1 when nested virtualization is enabled via HCR_EL2.NV.
+ */
+static inline bool arm_cpreg_traps_in_nv(const ARMCPRegInfo *ri)
+{
+ /*
+ * The Arm ARM defines the registers to be trapped in terms of
+ * their names (I_TZTZL). However the underlying principle is "if
+ * it would UNDEF at EL1 but work at EL2 then it should trap", and
+ * the way the encoding of sysregs and system instructions is done
+ * means that the right set of registers is exactly those where
+ * the opc1 field is 4 or 5. (You can see this also in the assert
+ * we do that the opc1 field and the permissions mask line up in
+ * define_one_arm_cp_reg_with_opaque().)
+ * Checking the opc1 field is easier for us and avoids the problem
+ * that we do not consistently use the right architectural names
+ * for all sysregs, since we treat the name field as largely for debug.
+ *
+ * However we do this check, it is going to be at least potentially
+ * fragile to future new sysregs, but this seems the least likely
+ * to break.
+ *
+ * In particular, note that the released sysreg XML defines that
+ * the FEAT_MEC sysregs and instructions do not follow this FEAT_NV
+ * trapping rule, so we will need to add an ARM_CP_* flag to indicate
+ * "register does not trap on NV" to handle those if/when we implement
+ * FEAT_MEC.
+ */
+ return ri->opc1 == 4 || ri->opc1 == 5;
+}
+
#endif /* TARGET_ARM_CPREGS_H */
diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
index 954d358..7a590c8 100644
--- a/target/arm/cpu-features.h
+++ b/target/arm/cpu-features.h
@@ -839,6 +839,16 @@
return FIELD_EX64(id->id_aa64mmfr2, ID_AA64MMFR2, E0PD) != 0;
}
+static inline bool isar_feature_aa64_nv(const ARMISARegisters *id)
+{
+ return FIELD_EX64(id->id_aa64mmfr2, ID_AA64MMFR2, NV) != 0;
+}
+
+static inline bool isar_feature_aa64_nv2(const ARMISARegisters *id)
+{
+ return FIELD_EX64(id->id_aa64mmfr2, ID_AA64MMFR2, NV) >= 2;
+}
+
static inline bool isar_feature_aa64_pmuv3p1(const ARMISARegisters *id)
{
return FIELD_EX64(id->id_aa64dfr0, ID_AA64DFR0, PMUVER) >= 4 &&
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 1c8b787..826ce84 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -1059,6 +1059,7 @@
uint32_t psr = pstate_read(env);
int i, j;
int el = arm_current_el(env);
+ uint64_t hcr = arm_hcr_el2_eff(env);
const char *ns_status;
bool sve;
@@ -1096,6 +1097,10 @@
if (cpu_isar_feature(aa64_bti, cpu)) {
qemu_fprintf(f, " BTYPE=%d", (psr & PSTATE_BTYPE) >> 10);
}
+ qemu_fprintf(f, "%s%s%s",
+ (hcr & HCR_NV) ? " NV" : "",
+ (hcr & HCR_NV1) ? " NV1" : "",
+ (hcr & HCR_NV2) ? " NV2" : "");
if (!(flags & CPU_DUMP_FPU)) {
qemu_fprintf(f, "\n");
return;
@@ -2238,9 +2243,6 @@
/* FEAT_MPAM (Memory Partitioning and Monitoring Extension) */
cpu->isar.id_aa64pfr0 =
FIELD_DP64(cpu->isar.id_aa64pfr0, ID_AA64PFR0, MPAM, 0);
- /* FEAT_NV (Nested Virtualization) */
- cpu->isar.id_aa64mmfr2 =
- FIELD_DP64(cpu->isar.id_aa64mmfr2, ID_AA64MMFR2, NV, 0);
}
/* MPU can be configured out of a PMSA CPU either by setting has-mpu
diff --git a/target/arm/cpu.h b/target/arm/cpu.h
index 8c3ca2e..ec276fc 100644
--- a/target/arm/cpu.h
+++ b/target/arm/cpu.h
@@ -120,12 +120,12 @@
#define TARGET_INSN_START_EXTRA_WORDS 2
/* The 2nd extra word holding syndrome info for data aborts does not use
- * the upper 6 bits nor the lower 14 bits. We mask and shift it down to
+ * the upper 6 bits nor the lower 13 bits. We mask and shift it down to
* help the sleb128 encoder do a better job.
* When restoring the CPU state, we shift it back up.
*/
#define ARM_INSN_START_WORD2_MASK ((1 << 26) - 1)
-#define ARM_INSN_START_WORD2_SHIFT 14
+#define ARM_INSN_START_WORD2_SHIFT 13
/* We currently assume float and double are IEEE single and double
precision respectively.
@@ -547,6 +547,9 @@
uint64_t gpccr_el3;
uint64_t gptbr_el3;
uint64_t mfar_el3;
+
+ /* NV2 register */
+ uint64_t vncr_el2;
} cp15;
struct {
@@ -3232,17 +3235,26 @@
FIELD(TBFLAG_A64, SVL, 24, 4)
/* Indicates that SME Streaming mode is active, and SMCR_ELx.FA64 is not. */
FIELD(TBFLAG_A64, SME_TRAP_NONSTREAMING, 28, 1)
-FIELD(TBFLAG_A64, FGT_ERET, 29, 1)
+FIELD(TBFLAG_A64, TRAP_ERET, 29, 1)
FIELD(TBFLAG_A64, NAA, 30, 1)
FIELD(TBFLAG_A64, ATA0, 31, 1)
+FIELD(TBFLAG_A64, NV, 32, 1)
+FIELD(TBFLAG_A64, NV1, 33, 1)
+FIELD(TBFLAG_A64, NV2, 34, 1)
+/* Set if FEAT_NV2 RAM accesses use the EL2&0 translation regime */
+FIELD(TBFLAG_A64, NV2_MEM_E20, 35, 1)
+/* Set if FEAT_NV2 RAM accesses are big-endian */
+FIELD(TBFLAG_A64, NV2_MEM_BE, 36, 1)
/*
- * Helpers for using the above.
+ * Helpers for using the above. Note that only the A64 accessors use
+ * FIELD_DP64() and FIELD_EX64(), because in the other cases the flags
+ * word either is or might be 32 bits only.
*/
#define DP_TBFLAG_ANY(DST, WHICH, VAL) \
(DST.flags = FIELD_DP32(DST.flags, TBFLAG_ANY, WHICH, VAL))
#define DP_TBFLAG_A64(DST, WHICH, VAL) \
- (DST.flags2 = FIELD_DP32(DST.flags2, TBFLAG_A64, WHICH, VAL))
+ (DST.flags2 = FIELD_DP64(DST.flags2, TBFLAG_A64, WHICH, VAL))
#define DP_TBFLAG_A32(DST, WHICH, VAL) \
(DST.flags2 = FIELD_DP32(DST.flags2, TBFLAG_A32, WHICH, VAL))
#define DP_TBFLAG_M32(DST, WHICH, VAL) \
@@ -3251,7 +3263,7 @@
(DST.flags2 = FIELD_DP32(DST.flags2, TBFLAG_AM32, WHICH, VAL))
#define EX_TBFLAG_ANY(IN, WHICH) FIELD_EX32(IN.flags, TBFLAG_ANY, WHICH)
-#define EX_TBFLAG_A64(IN, WHICH) FIELD_EX32(IN.flags2, TBFLAG_A64, WHICH)
+#define EX_TBFLAG_A64(IN, WHICH) FIELD_EX64(IN.flags2, TBFLAG_A64, WHICH)
#define EX_TBFLAG_A32(IN, WHICH) FIELD_EX32(IN.flags2, TBFLAG_A32, WHICH)
#define EX_TBFLAG_M32(IN, WHICH) FIELD_EX32(IN.flags2, TBFLAG_M32, WHICH)
#define EX_TBFLAG_AM32(IN, WHICH) FIELD_EX32(IN.flags2, TBFLAG_AM32, WHICH)
diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c
index 83d2619..7d856ac 100644
--- a/target/arm/debug_helper.c
+++ b/target/arm/debug_helper.c
@@ -844,6 +844,16 @@
return CP_ACCESS_OK;
}
+static CPAccessResult access_dbgvcr32(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ /* MCDR_EL3.TDMA doesn't apply for FEAT_NV traps */
+ if (arm_current_el(env) == 2 && (env->cp15.mdcr_el3 & MDCR_TDA)) {
+ return CP_ACCESS_TRAP_EL3;
+ }
+ return CP_ACCESS_OK;
+}
+
/*
* Check for traps to Debug Comms Channel registers. If FEAT_FGT
* is implemented then these are controlled by MDCR_EL2.TDCC for
@@ -950,6 +960,7 @@
.cp = 14, .opc0 = 2, .opc1 = 0, .crn = 0, .crm = 2, .opc2 = 2,
.access = PL1_RW, .accessfn = access_tda,
.fgt = FGT_MDSCR_EL1,
+ .nv2_redirect_offset = 0x158,
.fieldoffset = offsetof(CPUARMState, cp15.mdscr_el1),
.resetvalue = 0 },
/*
@@ -1062,7 +1073,7 @@
*/
{ .name = "DBGVCR32_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 2, .opc1 = 4, .crn = 0, .crm = 7, .opc2 = 0,
- .access = PL2_RW, .accessfn = access_tda,
+ .access = PL2_RW, .accessfn = access_dbgvcr32,
.type = ARM_CP_NOP | ARM_CP_EL3_NO_EL2_KEEP },
};
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 7889fd4..dc8f14f 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -263,6 +263,18 @@
g_list_free(keys);
}
+static bool arm_pan_enabled(CPUARMState *env)
+{
+ if (is_a64(env)) {
+ if ((arm_hcr_el2_eff(env) & (HCR_NV | HCR_NV1)) == (HCR_NV | HCR_NV1)) {
+ return false;
+ }
+ return env->pstate & PSTATE_PAN;
+ } else {
+ return env->uncached_cpsr & CPSR_PAN;
+ }
+}
+
/*
* Some registers are not accessible from AArch32 EL3 if SCR.NS == 0.
*/
@@ -635,6 +647,7 @@
.opc0 = 3, .opc1 = 0, .crn = 13, .crm = 0, .opc2 = 1,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_CONTEXTIDR_EL1,
+ .nv2_redirect_offset = 0x108 | NV2_REDIR_NV1,
.secure = ARM_CP_SECSTATE_NS,
.fieldoffset = offsetof(CPUARMState, cp15.contextidr_el[1]),
.resetvalue = 0, .writefn = contextidr_write, .raw_writefn = raw_write, },
@@ -871,6 +884,7 @@
{ .name = "CPACR", .state = ARM_CP_STATE_BOTH, .opc0 = 3,
.crn = 1, .crm = 0, .opc1 = 0, .opc2 = 2, .accessfn = cpacr_access,
.fgt = FGT_CPACR_EL1,
+ .nv2_redirect_offset = 0x100 | NV2_REDIR_NV1,
.access = PL1_RW, .fieldoffset = offsetof(CPUARMState, cp15.cpacr_el1),
.resetfn = cpacr_reset, .writefn = cpacr_write, .readfn = cpacr_read },
};
@@ -2238,11 +2252,13 @@
.opc0 = 3, .opc1 = 0, .crn = 5, .crm = 1, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_AFSR0_EL1,
+ .nv2_redirect_offset = 0x128 | NV2_REDIR_NV1,
.type = ARM_CP_CONST, .resetvalue = 0 },
{ .name = "AFSR1_EL1", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 0, .crn = 5, .crm = 1, .opc2 = 1,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_AFSR1_EL1,
+ .nv2_redirect_offset = 0x130 | NV2_REDIR_NV1,
.type = ARM_CP_CONST, .resetvalue = 0 },
/*
* MAIR can just read-as-written because we don't implement caches
@@ -2252,6 +2268,7 @@
.opc0 = 3, .opc1 = 0, .crn = 10, .crm = 2, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_MAIR_EL1,
+ .nv2_redirect_offset = 0x140 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.mair_el[1]),
.resetvalue = 0 },
{ .name = "MAIR_EL3", .state = ARM_CP_STATE_AA64,
@@ -3174,6 +3191,7 @@
.opc0 = 3, .opc1 = 3, .crn = 14, .crm = 2, .opc2 = 1,
.type = ARM_CP_IO, .access = PL0_RW,
.accessfn = gt_ptimer_access,
+ .nv2_redirect_offset = 0x180 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].ctl),
.resetvalue = 0,
.readfn = gt_phys_redir_ctl_read, .raw_readfn = raw_read,
@@ -3191,6 +3209,7 @@
.opc0 = 3, .opc1 = 3, .crn = 14, .crm = 3, .opc2 = 1,
.type = ARM_CP_IO, .access = PL0_RW,
.accessfn = gt_vtimer_access,
+ .nv2_redirect_offset = 0x170 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].ctl),
.resetvalue = 0,
.readfn = gt_virt_redir_ctl_read, .raw_readfn = raw_read,
@@ -3270,6 +3289,7 @@
.opc0 = 3, .opc1 = 3, .crn = 14, .crm = 2, .opc2 = 2,
.access = PL0_RW,
.type = ARM_CP_IO,
+ .nv2_redirect_offset = 0x178 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].cval),
.resetvalue = 0, .accessfn = gt_ptimer_access,
.readfn = gt_phys_redir_cval_read, .raw_readfn = raw_read,
@@ -3287,6 +3307,7 @@
.opc0 = 3, .opc1 = 3, .crn = 14, .crm = 3, .opc2 = 2,
.access = PL0_RW,
.type = ARM_CP_IO,
+ .nv2_redirect_offset = 0x168 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].cval),
.resetvalue = 0, .accessfn = gt_vtimer_access,
.readfn = gt_virt_redir_cval_read, .raw_readfn = raw_read,
@@ -3324,6 +3345,11 @@
static CPAccessResult e2h_access(CPUARMState *env, const ARMCPRegInfo *ri,
bool isread)
{
+ if (arm_current_el(env) == 1) {
+ /* This must be a FEAT_NV access */
+ /* TODO: FEAT_ECV will need to check CNTHCTL_EL2 here */
+ return CP_ACCESS_OK;
+ }
if (!(arm_hcr_el2_eff(env) & HCR_E2H)) {
return CP_ACCESS_TRAP;
}
@@ -3609,7 +3635,7 @@
g_assert(ss != ARMSS_Secure); /* ARMv8.4-SecEL2 is 64-bit only */
/* fall through */
case 1:
- if (ri->crm == 9 && (env->uncached_cpsr & CPSR_PAN)) {
+ if (ri->crm == 9 && arm_pan_enabled(env)) {
mmu_idx = ARMMMUIdx_Stage1_E1_PAN;
} else {
mmu_idx = ARMMMUIdx_Stage1_E1;
@@ -3703,6 +3729,15 @@
return at_e012_access(env, ri, isread);
}
+static CPAccessResult at_s1e01_access(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ if (arm_current_el(env) == 1 && (arm_hcr_el2_eff(env) & HCR_AT)) {
+ return CP_ACCESS_TRAP_EL2;
+ }
+ return at_e012_access(env, ri, isread);
+}
+
static void ats_write64(CPUARMState *env, const ARMCPRegInfo *ri,
uint64_t value)
{
@@ -3716,7 +3751,7 @@
case 0:
switch (ri->opc1) {
case 0: /* AT S1E1R, AT S1E1W, AT S1E1RP, AT S1E1WP */
- if (ri->crm == 9 && (env->pstate & PSTATE_PAN)) {
+ if (ri->crm == 9 && arm_pan_enabled(env)) {
mmu_idx = regime_e20 ?
ARMMMUIdx_E20_2_PAN : ARMMMUIdx_Stage1_E1_PAN;
} else {
@@ -4252,6 +4287,7 @@
.opc0 = 3, .crn = 6, .crm = 0, .opc1 = 0, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_FAR_EL1,
+ .nv2_redirect_offset = 0x220 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.far_el[1]),
.resetvalue = 0, },
};
@@ -4261,11 +4297,13 @@
.opc0 = 3, .crn = 5, .crm = 2, .opc1 = 0, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_ESR_EL1,
+ .nv2_redirect_offset = 0x138 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.esr_el[1]), .resetvalue = 0, },
{ .name = "TTBR0_EL1", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 0, .crn = 2, .crm = 0, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_TTBR0_EL1,
+ .nv2_redirect_offset = 0x200 | NV2_REDIR_NV1,
.writefn = vmsa_ttbr_write, .resetvalue = 0, .raw_writefn = raw_write,
.bank_fieldoffsets = { offsetof(CPUARMState, cp15.ttbr0_s),
offsetof(CPUARMState, cp15.ttbr0_ns) } },
@@ -4273,6 +4311,7 @@
.opc0 = 3, .opc1 = 0, .crn = 2, .crm = 0, .opc2 = 1,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_TTBR1_EL1,
+ .nv2_redirect_offset = 0x210 | NV2_REDIR_NV1,
.writefn = vmsa_ttbr_write, .resetvalue = 0, .raw_writefn = raw_write,
.bank_fieldoffsets = { offsetof(CPUARMState, cp15.ttbr1_s),
offsetof(CPUARMState, cp15.ttbr1_ns) } },
@@ -4280,6 +4319,7 @@
.opc0 = 3, .crn = 2, .crm = 0, .opc1 = 0, .opc2 = 2,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_TCR_EL1,
+ .nv2_redirect_offset = 0x120 | NV2_REDIR_NV1,
.writefn = vmsa_tcr_el12_write,
.raw_writefn = raw_write,
.resetvalue = 0,
@@ -4519,6 +4559,7 @@
.opc0 = 3, .crn = 10, .crm = 3, .opc1 = 0, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_AMAIR_EL1,
+ .nv2_redirect_offset = 0x148 | NV2_REDIR_NV1,
.type = ARM_CP_CONST, .resetvalue = 0 },
/* AMAIR1 is mapped to AMAIR_EL1[63:32] */
{ .name = "AMAIR1", .cp = 15, .crn = 10, .crm = 3, .opc1 = 0, .opc2 = 1,
@@ -5341,6 +5382,19 @@
}
}
+static CPAccessResult access_nv1(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ if (arm_current_el(env) == 1) {
+ uint64_t hcr_nv = arm_hcr_el2_eff(env) & (HCR_NV | HCR_NV1 | HCR_NV2);
+
+ if (hcr_nv == (HCR_NV | HCR_NV1)) {
+ return CP_ACCESS_TRAP_EL2;
+ }
+ }
+ return CP_ACCESS_OK;
+}
+
#ifdef CONFIG_USER_ONLY
/*
* `IC IVAU` is handled to improve compatibility with JITs that dual-map their
@@ -5568,22 +5622,22 @@
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 0,
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.fgt = FGT_ATS1E1R,
- .accessfn = at_e012_access, .writefn = ats_write64 },
+ .accessfn = at_s1e01_access, .writefn = ats_write64 },
{ .name = "AT_S1E1W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 1,
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.fgt = FGT_ATS1E1W,
- .accessfn = at_e012_access, .writefn = ats_write64 },
+ .accessfn = at_s1e01_access, .writefn = ats_write64 },
{ .name = "AT_S1E0R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 2,
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.fgt = FGT_ATS1E0R,
- .accessfn = at_e012_access, .writefn = ats_write64 },
+ .accessfn = at_s1e01_access, .writefn = ats_write64 },
{ .name = "AT_S1E0W", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 8, .opc2 = 3,
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.fgt = FGT_ATS1E0W,
- .accessfn = at_e012_access, .writefn = ats_write64 },
+ .accessfn = at_s1e01_access, .writefn = ats_write64 },
{ .name = "AT_S12E1R", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 4, .crn = 7, .crm = 8, .opc2 = 4,
.access = PL2_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
@@ -5689,12 +5743,14 @@
{ .name = "ELR_EL1", .state = ARM_CP_STATE_AA64,
.type = ARM_CP_ALIAS,
.opc0 = 3, .opc1 = 0, .crn = 4, .crm = 0, .opc2 = 1,
- .access = PL1_RW,
+ .access = PL1_RW, .accessfn = access_nv1,
+ .nv2_redirect_offset = 0x230 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, elr_el[1]) },
{ .name = "SPSR_EL1", .state = ARM_CP_STATE_AA64,
.type = ARM_CP_ALIAS,
.opc0 = 3, .opc1 = 0, .crn = 4, .crm = 0, .opc2 = 0,
- .access = PL1_RW,
+ .access = PL1_RW, .accessfn = access_nv1,
+ .nv2_redirect_offset = 0x160 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, banked_spsr[BANK_SVC]) },
/*
* We rely on the access checks not allowing the guest to write to the
@@ -5708,6 +5764,7 @@
.fieldoffset = offsetof(CPUARMState, sp_el[0]) },
{ .name = "SP_EL1", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 4, .crm = 1, .opc2 = 0,
+ .nv2_redirect_offset = 0x240,
.access = PL2_RW, .type = ARM_CP_ALIAS | ARM_CP_EL3_NO_EL2_KEEP,
.fieldoffset = offsetof(CPUARMState, sp_el[1]) },
{ .name = "SPSel", .state = ARM_CP_STATE_AA64,
@@ -5815,6 +5872,12 @@
if (cpu_isar_feature(aa64_rme, cpu)) {
valid_mask |= HCR_GPF;
}
+ if (cpu_isar_feature(aa64_nv, cpu)) {
+ valid_mask |= HCR_NV | HCR_NV1 | HCR_AT;
+ }
+ if (cpu_isar_feature(aa64_nv2, cpu)) {
+ valid_mask |= HCR_NV2;
+ }
}
if (cpu_isar_feature(any_evt, cpu)) {
@@ -5833,9 +5896,10 @@
* HCR_DC disables stage1 and enables stage2 translation
* HCR_DCT enables tagging on (disabled) stage1 translation
* HCR_FWB changes the interpretation of stage2 descriptor bits
+ * HCR_NV and HCR_NV1 affect interpretation of descriptor bits
*/
if ((env->cp15.hcr_el2 ^ value) &
- (HCR_VM | HCR_PTW | HCR_DC | HCR_DCT | HCR_FWB)) {
+ (HCR_VM | HCR_PTW | HCR_DC | HCR_DCT | HCR_FWB | HCR_NV | HCR_NV1)) {
tlb_flush(CPU(cpu));
}
env->cp15.hcr_el2 = value;
@@ -6001,7 +6065,7 @@
static CPAccessResult access_hxen(CPUARMState *env, const ARMCPRegInfo *ri,
bool isread)
{
- if (arm_current_el(env) < 3
+ if (arm_current_el(env) == 2
&& arm_feature(env, ARM_FEATURE_EL3)
&& !(env->cp15.scr_el3 & SCR_HXEN)) {
return CP_ACCESS_TRAP_EL3;
@@ -6013,6 +6077,7 @@
.name = "HCRX_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 2, .opc2 = 2,
.access = PL2_RW, .writefn = hcrx_write, .accessfn = access_hxen,
+ .nv2_redirect_offset = 0xa0,
.fieldoffset = offsetof(CPUARMState, cp15.hcrx_el2),
};
@@ -6079,6 +6144,7 @@
.type = ARM_CP_IO,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 0,
.access = PL2_RW, .fieldoffset = offsetof(CPUARMState, cp15.hcr_el2),
+ .nv2_redirect_offset = 0x78,
.writefn = hcr_write, .raw_writefn = raw_write },
{ .name = "HCR", .state = ARM_CP_STATE_AA32,
.type = ARM_CP_ALIAS | ARM_CP_IO,
@@ -6089,14 +6155,16 @@
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 7,
.access = PL2_RW, .type = ARM_CP_CONST, .resetvalue = 0 },
{ .name = "ELR_EL2", .state = ARM_CP_STATE_AA64,
- .type = ARM_CP_ALIAS,
+ .type = ARM_CP_ALIAS | ARM_CP_NV2_REDIRECT,
.opc0 = 3, .opc1 = 4, .crn = 4, .crm = 0, .opc2 = 1,
.access = PL2_RW,
.fieldoffset = offsetof(CPUARMState, elr_el[2]) },
{ .name = "ESR_EL2", .state = ARM_CP_STATE_BOTH,
+ .type = ARM_CP_NV2_REDIRECT,
.opc0 = 3, .opc1 = 4, .crn = 5, .crm = 2, .opc2 = 0,
.access = PL2_RW, .fieldoffset = offsetof(CPUARMState, cp15.esr_el[2]) },
{ .name = "FAR_EL2", .state = ARM_CP_STATE_BOTH,
+ .type = ARM_CP_NV2_REDIRECT,
.opc0 = 3, .opc1 = 4, .crn = 6, .crm = 0, .opc2 = 0,
.access = PL2_RW, .fieldoffset = offsetof(CPUARMState, cp15.far_el[2]) },
{ .name = "HIFAR", .state = ARM_CP_STATE_AA32,
@@ -6105,7 +6173,7 @@
.access = PL2_RW,
.fieldoffset = offsetofhigh32(CPUARMState, cp15.far_el[2]) },
{ .name = "SPSR_EL2", .state = ARM_CP_STATE_AA64,
- .type = ARM_CP_ALIAS,
+ .type = ARM_CP_ALIAS | ARM_CP_NV2_REDIRECT,
.opc0 = 3, .opc1 = 4, .crn = 4, .crm = 0, .opc2 = 0,
.access = PL2_RW,
.fieldoffset = offsetof(CPUARMState, banked_spsr[BANK_HYP]) },
@@ -6161,6 +6229,7 @@
{ .name = "VTCR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 2, .crm = 1, .opc2 = 2,
.access = PL2_RW,
+ .nv2_redirect_offset = 0x40,
/* no .writefn needed as this can't cause an ASID change */
.fieldoffset = offsetof(CPUARMState, cp15.vtcr_el2) },
{ .name = "VTTBR", .state = ARM_CP_STATE_AA32,
@@ -6172,6 +6241,7 @@
{ .name = "VTTBR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 2, .crm = 1, .opc2 = 0,
.access = PL2_RW, .writefn = vttbr_write, .raw_writefn = raw_write,
+ .nv2_redirect_offset = 0x20,
.fieldoffset = offsetof(CPUARMState, cp15.vttbr_el2) },
{ .name = "SCTLR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 0, .opc2 = 0,
@@ -6180,6 +6250,7 @@
{ .name = "TPIDR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 13, .crm = 0, .opc2 = 2,
.access = PL2_RW, .resetvalue = 0,
+ .nv2_redirect_offset = 0x90,
.fieldoffset = offsetof(CPUARMState, cp15.tpidr_el[2]) },
{ .name = "TTBR0_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 2, .crm = 0, .opc2 = 0,
@@ -6275,6 +6346,7 @@
.opc0 = 3, .opc1 = 4, .crn = 14, .crm = 0, .opc2 = 3,
.access = PL2_RW, .type = ARM_CP_IO, .resetvalue = 0,
.writefn = gt_cntvoff_write,
+ .nv2_redirect_offset = 0x60,
.fieldoffset = offsetof(CPUARMState, cp15.cntvoff_el2) },
{ .name = "CNTVOFF", .cp = 15, .opc1 = 4, .crm = 14,
.access = PL2_RW, .type = ARM_CP_64BIT | ARM_CP_ALIAS | ARM_CP_IO,
@@ -6313,6 +6385,7 @@
{ .name = "HSTR_EL2", .state = ARM_CP_STATE_BOTH,
.cp = 15, .opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 3,
.access = PL2_RW,
+ .nv2_redirect_offset = 0x80,
.fieldoffset = offsetof(CPUARMState, cp15.hstr_el2) },
};
@@ -6338,10 +6411,12 @@
{ .name = "VSTTBR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 2, .crm = 6, .opc2 = 0,
.access = PL2_RW, .accessfn = sel2_access,
+ .nv2_redirect_offset = 0x30,
.fieldoffset = offsetof(CPUARMState, cp15.vsttbr_el2) },
{ .name = "VSTCR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 2, .crm = 6, .opc2 = 2,
.access = PL2_RW, .accessfn = sel2_access,
+ .nv2_redirect_offset = 0x48,
.fieldoffset = offsetof(CPUARMState, cp15.vstcr_el2) },
};
@@ -6509,6 +6584,42 @@
writefn(env, ri, value);
}
+static uint64_t el2_e2h_e12_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+ /* Pass the EL1 register accessor its ri, not the EL12 alias ri */
+ return ri->orig_readfn(env, ri->opaque);
+}
+
+static void el2_e2h_e12_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ /* Pass the EL1 register accessor its ri, not the EL12 alias ri */
+ return ri->orig_writefn(env, ri->opaque, value);
+}
+
+static CPAccessResult el2_e2h_e12_access(CPUARMState *env,
+ const ARMCPRegInfo *ri,
+ bool isread)
+{
+ if (arm_current_el(env) == 1) {
+ /*
+ * This must be a FEAT_NV access (will either trap or redirect
+ * to memory). None of the registers with _EL12 aliases want to
+ * apply their trap controls for this kind of access, so don't
+ * call the orig_accessfn or do the "UNDEF when E2H is 0" check.
+ */
+ return CP_ACCESS_OK;
+ }
+ /* FOO_EL12 aliases only exist when E2H is 1; otherwise they UNDEF */
+ if (!(arm_hcr_el2_eff(env) & HCR_E2H)) {
+ return CP_ACCESS_TRAP_UNCATEGORIZED;
+ }
+ if (ri->orig_accessfn) {
+ return ri->orig_accessfn(env, ri->opaque, isread);
+ }
+ return CP_ACCESS_OK;
+}
+
static void define_arm_vh_e2h_redirects_aliases(ARMCPU *cpu)
{
struct E2HAlias {
@@ -6608,6 +6719,41 @@
new_reg->type |= ARM_CP_ALIAS;
/* Remove PL1/PL0 access, leaving PL2/PL3 R/W in place. */
new_reg->access &= PL2_RW | PL3_RW;
+ /* The new_reg op fields are as per new_key, not the target reg */
+ new_reg->crn = (a->new_key & CP_REG_ARM64_SYSREG_CRN_MASK)
+ >> CP_REG_ARM64_SYSREG_CRN_SHIFT;
+ new_reg->crm = (a->new_key & CP_REG_ARM64_SYSREG_CRM_MASK)
+ >> CP_REG_ARM64_SYSREG_CRM_SHIFT;
+ new_reg->opc0 = (a->new_key & CP_REG_ARM64_SYSREG_OP0_MASK)
+ >> CP_REG_ARM64_SYSREG_OP0_SHIFT;
+ new_reg->opc1 = (a->new_key & CP_REG_ARM64_SYSREG_OP1_MASK)
+ >> CP_REG_ARM64_SYSREG_OP1_SHIFT;
+ new_reg->opc2 = (a->new_key & CP_REG_ARM64_SYSREG_OP2_MASK)
+ >> CP_REG_ARM64_SYSREG_OP2_SHIFT;
+ new_reg->opaque = src_reg;
+ new_reg->orig_readfn = src_reg->readfn ?: raw_read;
+ new_reg->orig_writefn = src_reg->writefn ?: raw_write;
+ new_reg->orig_accessfn = src_reg->accessfn;
+ if (!new_reg->raw_readfn) {
+ new_reg->raw_readfn = raw_read;
+ }
+ if (!new_reg->raw_writefn) {
+ new_reg->raw_writefn = raw_write;
+ }
+ new_reg->readfn = el2_e2h_e12_read;
+ new_reg->writefn = el2_e2h_e12_write;
+ new_reg->accessfn = el2_e2h_e12_access;
+
+ /*
+ * If the _EL1 register is redirected to memory by FEAT_NV2,
+ * then it shares the offset with the _EL12 register,
+ * and which one is redirected depends on HCR_EL2.NV1.
+ */
+ if (new_reg->nv2_redirect_offset) {
+ assert(new_reg->nv2_redirect_offset & NV2_REDIR_NV1);
+ new_reg->nv2_redirect_offset &= ~NV2_REDIR_NV1;
+ new_reg->nv2_redirect_offset |= NV2_REDIR_NO_NV1;
+ }
ok = g_hash_table_insert(cpu->cp_regs,
(gpointer)(uintptr_t)a->new_key, new_reg);
@@ -6741,9 +6887,11 @@
.type = ARM_CP_CONST, .resetvalue = 0 },
{ .name = "VDISR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 12, .crm = 1, .opc2 = 1,
+ .nv2_redirect_offset = 0x500,
.access = PL2_RW, .fieldoffset = offsetof(CPUARMState, cp15.vdisr_el2) },
{ .name = "VSESR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 5, .crm = 2, .opc2 = 3,
+ .nv2_redirect_offset = 0x508,
.access = PL2_RW, .fieldoffset = offsetof(CPUARMState, cp15.vsesr_el2) },
};
@@ -6915,6 +7063,7 @@
static const ARMCPRegInfo zcr_reginfo[] = {
{ .name = "ZCR_EL1", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 0, .crn = 1, .crm = 2, .opc2 = 0,
+ .nv2_redirect_offset = 0x1e0 | NV2_REDIR_NV1,
.access = PL1_RW, .type = ARM_CP_SVE,
.fieldoffset = offsetof(CPUARMState, vfp.zcr_el[1]),
.writefn = zcr_write, .raw_writefn = raw_write },
@@ -6951,10 +7100,21 @@
return CP_ACCESS_OK;
}
-static CPAccessResult access_esm(CPUARMState *env, const ARMCPRegInfo *ri,
- bool isread)
+static CPAccessResult access_smprimap(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
{
- /* TODO: FEAT_FGT for SMPRI_EL1 but not SMPRIMAP_EL2 */
+ /* If EL1 this is a FEAT_NV access and CPTR_EL3.ESM doesn't apply */
+ if (arm_current_el(env) == 2
+ && arm_feature(env, ARM_FEATURE_EL3)
+ && !FIELD_EX64(env->cp15.cptr_el[3], CPTR_EL3, ESM)) {
+ return CP_ACCESS_TRAP_EL3;
+ }
+ return CP_ACCESS_OK;
+}
+
+static CPAccessResult access_smpri(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
if (arm_current_el(env) < 3
&& arm_feature(env, ARM_FEATURE_EL3)
&& !FIELD_EX64(env->cp15.cptr_el[3], CPTR_EL3, ESM)) {
@@ -7045,6 +7205,7 @@
.writefn = svcr_write, .raw_writefn = raw_write },
{ .name = "SMCR_EL1", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 0, .crn = 1, .crm = 2, .opc2 = 6,
+ .nv2_redirect_offset = 0x1f0 | NV2_REDIR_NV1,
.access = PL1_RW, .type = ARM_CP_SME,
.fieldoffset = offsetof(CPUARMState, vfp.smcr_el[1]),
.writefn = smcr_write, .raw_writefn = raw_write },
@@ -7073,12 +7234,13 @@
*/
{ .name = "SMPRI_EL1", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 0, .crn = 1, .crm = 2, .opc2 = 4,
- .access = PL1_RW, .accessfn = access_esm,
+ .access = PL1_RW, .accessfn = access_smpri,
.fgt = FGT_NSMPRI_EL1,
.type = ARM_CP_CONST, .resetvalue = 0 },
{ .name = "SMPRIMAP_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 2, .opc2 = 5,
- .access = PL2_RW, .accessfn = access_esm,
+ .nv2_redirect_offset = 0x1f8,
+ .access = PL2_RW, .accessfn = access_smprimap,
.type = ARM_CP_CONST, .resetvalue = 0 },
};
@@ -7728,7 +7890,46 @@
bool isread)
{
int el = arm_current_el(env);
+ if (el < 2 && arm_is_el2_enabled(env)) {
+ uint64_t hcr = arm_hcr_el2_eff(env);
+ if (!(hcr & HCR_ATA) && (!(hcr & HCR_E2H) || !(hcr & HCR_TGE))) {
+ return CP_ACCESS_TRAP_EL2;
+ }
+ }
+ if (el < 3 &&
+ arm_feature(env, ARM_FEATURE_EL3) &&
+ !(env->cp15.scr_el3 & SCR_ATA)) {
+ return CP_ACCESS_TRAP_EL3;
+ }
+ return CP_ACCESS_OK;
+}
+static CPAccessResult access_tfsr_el1(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ CPAccessResult nv1 = access_nv1(env, ri, isread);
+
+ if (nv1 != CP_ACCESS_OK) {
+ return nv1;
+ }
+ return access_mte(env, ri, isread);
+}
+
+static CPAccessResult access_tfsr_el2(CPUARMState *env, const ARMCPRegInfo *ri,
+ bool isread)
+{
+ /*
+ * TFSR_EL2: similar to generic access_mte(), but we need to
+ * account for FEAT_NV. At EL1 this must be a FEAT_NV access;
+ * if NV2 is enabled then we will redirect this to TFSR_EL1
+ * after doing the HCR and SCR ATA traps; otherwise this will
+ * be a trap to EL2 and the HCR/SCR traps do not apply.
+ */
+ int el = arm_current_el(env);
+
+ if (el == 1 && (arm_hcr_el2_eff(env) & HCR_NV2)) {
+ return CP_ACCESS_OK;
+ }
if (el < 2 && arm_is_el2_enabled(env)) {
uint64_t hcr = arm_hcr_el2_eff(env);
if (!(hcr & HCR_ATA) && (!(hcr & HCR_E2H) || !(hcr & HCR_TGE))) {
@@ -7760,11 +7961,13 @@
.fieldoffset = offsetof(CPUARMState, cp15.tfsr_el[0]) },
{ .name = "TFSR_EL1", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 0, .crn = 5, .crm = 6, .opc2 = 0,
- .access = PL1_RW, .accessfn = access_mte,
+ .access = PL1_RW, .accessfn = access_tfsr_el1,
+ .nv2_redirect_offset = 0x190 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.tfsr_el[1]) },
{ .name = "TFSR_EL2", .state = ARM_CP_STATE_AA64,
+ .type = ARM_CP_NV2_REDIRECT,
.opc0 = 3, .opc1 = 4, .crn = 5, .crm = 6, .opc2 = 0,
- .access = PL2_RW, .accessfn = access_mte,
+ .access = PL2_RW, .accessfn = access_tfsr_el2,
.fieldoffset = offsetof(CPUARMState, cp15.tfsr_el[2]) },
{ .name = "TFSR_EL3", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 6, .crn = 5, .crm = 6, .opc2 = 0,
@@ -7912,6 +8115,18 @@
return CP_ACCESS_OK;
}
+static CPAccessResult access_scxtnum_el1(CPUARMState *env,
+ const ARMCPRegInfo *ri,
+ bool isread)
+{
+ CPAccessResult nv1 = access_nv1(env, ri, isread);
+
+ if (nv1 != CP_ACCESS_OK) {
+ return nv1;
+ }
+ return access_scxtnum(env, ri, isread);
+}
+
static const ARMCPRegInfo scxtnum_reginfo[] = {
{ .name = "SCXTNUM_EL0", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 3, .crn = 13, .crm = 0, .opc2 = 7,
@@ -7920,8 +8135,9 @@
.fieldoffset = offsetof(CPUARMState, scxtnum_el[0]) },
{ .name = "SCXTNUM_EL1", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 0, .crn = 13, .crm = 0, .opc2 = 7,
- .access = PL1_RW, .accessfn = access_scxtnum,
+ .access = PL1_RW, .accessfn = access_scxtnum_el1,
.fgt = FGT_SCXTNUM_EL1,
+ .nv2_redirect_offset = 0x188 | NV2_REDIR_NV1,
.fieldoffset = offsetof(CPUARMState, scxtnum_el[1]) },
{ .name = "SCXTNUM_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 13, .crm = 0, .opc2 = 7,
@@ -7946,25 +8162,53 @@
static const ARMCPRegInfo fgt_reginfo[] = {
{ .name = "HFGRTR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 4,
+ .nv2_redirect_offset = 0x1b8,
.access = PL2_RW, .accessfn = access_fgt,
.fieldoffset = offsetof(CPUARMState, cp15.fgt_read[FGTREG_HFGRTR]) },
{ .name = "HFGWTR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 5,
+ .nv2_redirect_offset = 0x1c0,
.access = PL2_RW, .accessfn = access_fgt,
.fieldoffset = offsetof(CPUARMState, cp15.fgt_write[FGTREG_HFGWTR]) },
{ .name = "HDFGRTR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 3, .crm = 1, .opc2 = 4,
+ .nv2_redirect_offset = 0x1d0,
.access = PL2_RW, .accessfn = access_fgt,
.fieldoffset = offsetof(CPUARMState, cp15.fgt_read[FGTREG_HDFGRTR]) },
{ .name = "HDFGWTR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 3, .crm = 1, .opc2 = 5,
+ .nv2_redirect_offset = 0x1d8,
.access = PL2_RW, .accessfn = access_fgt,
.fieldoffset = offsetof(CPUARMState, cp15.fgt_write[FGTREG_HDFGWTR]) },
{ .name = "HFGITR_EL2", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 1, .opc2 = 6,
+ .nv2_redirect_offset = 0x1c8,
.access = PL2_RW, .accessfn = access_fgt,
.fieldoffset = offsetof(CPUARMState, cp15.fgt_exec[FGTREG_HFGITR]) },
};
+
+static void vncr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+ uint64_t value)
+{
+ /*
+ * Clear the RES0 bottom 12 bits; this means at runtime we can guarantee
+ * that VNCR_EL2 + offset is 64-bit aligned. We don't need to do anything
+ * about the RESS bits at the top -- we choose the "generate an EL2
+ * translation abort on use" CONSTRAINED UNPREDICTABLE option (i.e. let
+ * the ptw.c code detect the resulting invalid address).
+ */
+ env->cp15.vncr_el2 = value & ~0xfffULL;
+}
+
+static const ARMCPRegInfo nv2_reginfo[] = {
+ { .name = "VNCR_EL2", .state = ARM_CP_STATE_AA64,
+ .opc0 = 3, .opc1 = 4, .crn = 2, .crm = 2, .opc2 = 0,
+ .access = PL2_RW,
+ .writefn = vncr_write,
+ .nv2_redirect_offset = 0xb0,
+ .fieldoffset = offsetof(CPUARMState, cp15.vncr_el2) },
+};
+
#endif /* TARGET_AARCH64 */
static CPAccessResult access_predinv(CPUARMState *env, const ARMCPRegInfo *ri,
@@ -8125,12 +8369,14 @@
.opc0 = 3, .opc1 = 5, .crn = 14, .crm = 2, .opc2 = 1,
.type = ARM_CP_IO | ARM_CP_ALIAS,
.access = PL2_RW, .accessfn = e2h_access,
+ .nv2_redirect_offset = 0x180 | NV2_REDIR_NO_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].ctl),
.writefn = gt_phys_ctl_write, .raw_writefn = raw_write },
{ .name = "CNTV_CTL_EL02", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 5, .crn = 14, .crm = 3, .opc2 = 1,
.type = ARM_CP_IO | ARM_CP_ALIAS,
.access = PL2_RW, .accessfn = e2h_access,
+ .nv2_redirect_offset = 0x170 | NV2_REDIR_NO_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].ctl),
.writefn = gt_virt_ctl_write, .raw_writefn = raw_write },
{ .name = "CNTP_TVAL_EL02", .state = ARM_CP_STATE_AA64,
@@ -8147,11 +8393,13 @@
.opc0 = 3, .opc1 = 5, .crn = 14, .crm = 2, .opc2 = 2,
.type = ARM_CP_IO | ARM_CP_ALIAS,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_PHYS].cval),
+ .nv2_redirect_offset = 0x178 | NV2_REDIR_NO_NV1,
.access = PL2_RW, .accessfn = e2h_access,
.writefn = gt_phys_cval_write, .raw_writefn = raw_write },
{ .name = "CNTV_CVAL_EL02", .state = ARM_CP_STATE_AA64,
.opc0 = 3, .opc1 = 5, .crn = 14, .crm = 3, .opc2 = 2,
.type = ARM_CP_IO | ARM_CP_ALIAS,
+ .nv2_redirect_offset = 0x168 | NV2_REDIR_NO_NV1,
.fieldoffset = offsetof(CPUARMState, cp15.c14_timer[GTIMER_VIRT].cval),
.access = PL2_RW, .accessfn = e2h_access,
.writefn = gt_virt_cval_write, .raw_writefn = raw_write },
@@ -8164,12 +8412,12 @@
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 9, .opc2 = 0,
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.fgt = FGT_ATS1E1RP,
- .accessfn = at_e012_access, .writefn = ats_write64 },
+ .accessfn = at_s1e01_access, .writefn = ats_write64 },
{ .name = "AT_S1E1WP", .state = ARM_CP_STATE_AA64,
.opc0 = 1, .opc1 = 0, .crn = 7, .crm = 9, .opc2 = 1,
.access = PL1_W, .type = ARM_CP_NO_RAW | ARM_CP_RAISES_EXC,
.fgt = FGT_ATS1E1WP,
- .accessfn = at_e012_access, .writefn = ats_write64 },
+ .accessfn = at_s1e01_access, .writefn = ats_write64 },
};
static const ARMCPRegInfo ats1cp_reginfo[] = {
@@ -8793,6 +9041,7 @@
.opc0 = 3, .opc1 = 4, .crn = 0, .crm = 0, .opc2 = 0,
.access = PL2_RW, .resetvalue = cpu->midr,
.type = ARM_CP_EL3_NO_EL2_C_NZ,
+ .nv2_redirect_offset = 0x88,
.fieldoffset = offsetof(CPUARMState, cp15.vpidr_el2) },
{ .name = "VMPIDR", .state = ARM_CP_STATE_AA32,
.cp = 15, .opc1 = 4, .crn = 0, .crm = 0, .opc2 = 5,
@@ -8804,6 +9053,7 @@
.opc0 = 3, .opc1 = 4, .crn = 0, .crm = 0, .opc2 = 5,
.access = PL2_RW, .resetvalue = vmpidr_def,
.type = ARM_CP_EL3_NO_EL2_C_NZ,
+ .nv2_redirect_offset = 0x50,
.fieldoffset = offsetof(CPUARMState, cp15.vmpidr_el2) },
};
/*
@@ -9233,6 +9483,7 @@
{ .name = "ACTLR_EL1", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 0, .crn = 1, .crm = 0, .opc2 = 1,
.access = PL1_RW, .accessfn = access_tacr,
+ .nv2_redirect_offset = 0x118,
.type = ARM_CP_CONST, .resetvalue = cpu->reset_auxcr },
{ .name = "ACTLR_EL2", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .opc1 = 4, .crn = 1, .crm = 0, .opc2 = 1,
@@ -9302,7 +9553,9 @@
{ .name = "VBAR", .state = ARM_CP_STATE_BOTH,
.opc0 = 3, .crn = 12, .crm = 0, .opc1 = 0, .opc2 = 0,
.access = PL1_RW, .writefn = vbar_write,
+ .accessfn = access_nv1,
.fgt = FGT_VBAR_EL1,
+ .nv2_redirect_offset = 0x250 | NV2_REDIR_NV1,
.bank_fieldoffsets = { offsetof(CPUARMState, cp15.vbar_s),
offsetof(CPUARMState, cp15.vbar_ns) },
.resetvalue = 0 },
@@ -9317,6 +9570,7 @@
.opc0 = 3, .opc1 = 0, .crn = 1, .crm = 0, .opc2 = 0,
.access = PL1_RW, .accessfn = access_tvm_trvm,
.fgt = FGT_SCTLR_EL1,
+ .nv2_redirect_offset = 0x110 | NV2_REDIR_NV1,
.bank_fieldoffsets = { offsetof(CPUARMState, cp15.sctlr_s),
offsetof(CPUARMState, cp15.sctlr_ns) },
.writefn = sctlr_write, .resetvalue = cpu->reset_sctlr,
@@ -9447,6 +9701,10 @@
define_arm_cp_regs(cpu, rme_mte_reginfo);
}
}
+
+ if (cpu_isar_feature(aa64_nv2, cpu)) {
+ define_arm_cp_regs(cpu, nv2_reginfo);
+ }
#endif
if (cpu_isar_feature(any_predinv, cpu)) {
@@ -11134,6 +11392,20 @@
old_mode = pstate_read(env);
aarch64_save_sp(env, arm_current_el(env));
env->elr_el[new_el] = env->pc;
+
+ if (cur_el == 1 && new_el == 1) {
+ uint64_t hcr = arm_hcr_el2_eff(env);
+ if ((hcr & (HCR_NV | HCR_NV1 | HCR_NV2)) == HCR_NV ||
+ (hcr & (HCR_NV | HCR_NV2)) == (HCR_NV | HCR_NV2)) {
+ /*
+ * FEAT_NV, FEAT_NV2 may need to report EL2 in the SPSR
+ * by setting M[3:2] to 0b10.
+ * If NV2 is disabled, change SPSR when NV,NV1 == 1,0 (I_ZJRNN)
+ * If NV2 is enabled, change SPSR when NV is 1 (I_DBTLM)
+ */
+ old_mode = deposit32(old_mode, 2, 2, 2);
+ }
+ }
} else {
old_mode = cpsr_read_for_spsr_elx(env);
env->elr_el[new_el] = env->regs[15];
@@ -11144,6 +11416,7 @@
}
env->banked_spsr[aarch64_banked_spsr_index(new_el)] = old_mode;
+ qemu_log_mask(CPU_LOG_INT, "...with SPSR 0x%x\n", old_mode);
qemu_log_mask(CPU_LOG_INT, "...with ELR 0x%" PRIx64 "\n",
env->elr_el[new_el]);
@@ -11987,15 +12260,6 @@
}
#endif
-static bool arm_pan_enabled(CPUARMState *env)
-{
- if (is_a64(env)) {
- return env->pstate & PSTATE_PAN;
- } else {
- return env->uncached_cpsr & CPSR_PAN;
- }
-}
-
ARMMMUIdx arm_mmu_idx_el(CPUARMState *env, int el)
{
ARMMMUIdx idx;
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index 0ecd3a3..2d4fa8d 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -1581,6 +1581,12 @@
}
}
+static bool nv_nv1_enabled(CPUARMState *env, S1Translate *ptw)
+{
+ uint64_t hcr = arm_hcr_el2_eff_secstate(env, ptw->in_space);
+ return (hcr & (HCR_NV | HCR_NV1)) == (HCR_NV | HCR_NV1);
+}
+
/**
* get_phys_addr_lpae: perform one stage of page table walk, LPAE format
*
@@ -1989,6 +1995,21 @@
xn = extract64(attrs, 54, 1);
pxn = extract64(attrs, 53, 1);
+ if (el == 1 && nv_nv1_enabled(env, ptw)) {
+ /*
+ * With FEAT_NV, when HCR_EL2.{NV,NV1} == {1,1}, the block/page
+ * descriptor bit 54 holds PXN, 53 is RES0, and the effective value
+ * of UXN is 0. Similarly for bits 59 and 60 in table descriptors
+ * (which we have already folded into bits 53 and 54 of attrs).
+ * AP[1] (descriptor bit 6, our ap bit 0) is treated as 0.
+ * Similarly, APTable[0] from the table descriptor is treated as 0;
+ * we already folded this into AP[1] and squashing that to 0 does
+ * the right thing.
+ */
+ pxn = xn;
+ xn = 0;
+ ap &= ~1;
+ }
/*
* Note that we modified ptw->in_space earlier for NSTable, but
* result->f.attrs retains a copy of the original security space.
diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h
index 95454b5..1a49767 100644
--- a/target/arm/syndrome.h
+++ b/target/arm/syndrome.h
@@ -86,6 +86,9 @@
#define ARM_EL_IL (1 << ARM_EL_IL_SHIFT)
#define ARM_EL_ISV (1 << ARM_EL_ISV_SHIFT)
+/* In the Data Abort syndrome */
+#define ARM_EL_VNCR (1 << 13)
+
static inline uint32_t syn_get_ec(uint32_t syn)
{
return syn >> ARM_EL_EC_SHIFT;
@@ -256,13 +259,12 @@
(cv << 24) | (cond << 20) | rm;
}
-static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc,
+static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr,
int cm, int s1ptw, int wnr, int fsc)
{
- /* TODO: FEAT_NV2 adds VNCR */
return (EC_GPC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (s2ptw << 21)
- | (ind << 20) | (gpcsc << 14) | (cm << 8) | (s1ptw << 7)
- | (wnr << 6) | fsc;
+ | (ind << 20) | (gpcsc << 14) | (vncr << 13) | (cm << 8)
+ | (s1ptw << 7) | (wnr << 6) | fsc;
}
static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc)
@@ -295,6 +297,16 @@
| (ea << 9) | (cm << 8) | (s1ptw << 7) | (wnr << 6) | fsc;
}
+/*
+ * Faults due to FEAT_NV2 VNCR_EL2-based accesses report as same-EL
+ * Data Aborts with the VNCR bit set.
+ */
+static inline uint32_t syn_data_abort_vncr(int ea, int wnr, int fsc)
+{
+ return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (1 << ARM_EL_EC_SHIFT)
+ | ARM_EL_IL | ARM_EL_VNCR | (wnr << 6) | fsc;
+}
+
static inline uint32_t syn_swstep(int same_el, int isv, int ex)
{
return (EC_SOFTWARESTEP << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT)
diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
index fcda99e..5fba2c0 100644
--- a/target/arm/tcg/cpu64.c
+++ b/target/arm/tcg/cpu64.c
@@ -1105,6 +1105,16 @@
u = FIELD_DP32(u, CLIDR_EL1, LOUU, 0);
cpu->clidr = u;
+ /*
+ * Set CTR_EL0.DIC and IDC to tell the guest it doesnt' need to
+ * do any cache maintenance for data-to-instruction or
+ * instruction-to-guest coherence. (Our cache ops are nops.)
+ */
+ t = cpu->ctr;
+ t = FIELD_DP64(t, CTR_EL0, IDC, 1);
+ t = FIELD_DP64(t, CTR_EL0, DIC, 1);
+ cpu->ctr = t;
+
t = cpu->isar.id_aa64isar0;
t = FIELD_DP64(t, ID_AA64ISAR0, AES, 2); /* FEAT_PMULL */
t = FIELD_DP64(t, ID_AA64ISAR0, SHA1, 1); /* FEAT_SHA1 */
@@ -1194,6 +1204,7 @@
t = FIELD_DP64(t, ID_AA64MMFR2, UAO, 1); /* FEAT_UAO */
t = FIELD_DP64(t, ID_AA64MMFR2, IESB, 1); /* FEAT_IESB */
t = FIELD_DP64(t, ID_AA64MMFR2, VARANGE, 1); /* FEAT_LVA */
+ t = FIELD_DP64(t, ID_AA64MMFR2, NV, 2); /* FEAT_NV2 */
t = FIELD_DP64(t, ID_AA64MMFR2, ST, 1); /* FEAT_TTST */
t = FIELD_DP64(t, ID_AA64MMFR2, AT, 1); /* FEAT_LSE2 */
t = FIELD_DP64(t, ID_AA64MMFR2, IDS, 1); /* FEAT_IDST */
diff --git a/target/arm/tcg/hflags.c b/target/arm/tcg/hflags.c
index a6ebd75..8e5d35d 100644
--- a/target/arm/tcg/hflags.c
+++ b/target/arm/tcg/hflags.c
@@ -169,6 +169,7 @@
CPUARMTBFlags flags = {};
ARMMMUIdx stage1 = stage_1_mmu_idx(mmu_idx);
uint64_t tcr = regime_tcr(env, mmu_idx);
+ uint64_t hcr = arm_hcr_el2_eff(env);
uint64_t sctlr;
int tbii, tbid;
@@ -260,8 +261,10 @@
switch (mmu_idx) {
case ARMMMUIdx_E10_1:
case ARMMMUIdx_E10_1_PAN:
- /* TODO: ARMv8.3-NV */
- DP_TBFLAG_A64(flags, UNPRIV, 1);
+ /* FEAT_NV: NV,NV1 == 1,1 means we don't do UNPRIV accesses */
+ if ((hcr & (HCR_NV | HCR_NV1)) != (HCR_NV | HCR_NV1)) {
+ DP_TBFLAG_A64(flags, UNPRIV, 1);
+ }
break;
case ARMMMUIdx_E20_2:
case ARMMMUIdx_E20_2_PAN:
@@ -285,13 +288,34 @@
if (arm_fgt_active(env, el)) {
DP_TBFLAG_ANY(flags, FGT_ACTIVE, 1);
if (FIELD_EX64(env->cp15.fgt_exec[FGTREG_HFGITR], HFGITR_EL2, ERET)) {
- DP_TBFLAG_A64(flags, FGT_ERET, 1);
+ DP_TBFLAG_A64(flags, TRAP_ERET, 1);
}
if (fgt_svc(env, el)) {
DP_TBFLAG_ANY(flags, FGT_SVC, 1);
}
}
+ /*
+ * ERET can also be trapped for FEAT_NV. arm_hcr_el2_eff() takes care
+ * of "is EL2 enabled" and the NV bit can only be set if FEAT_NV is present.
+ */
+ if (el == 1 && (hcr & HCR_NV)) {
+ DP_TBFLAG_A64(flags, TRAP_ERET, 1);
+ DP_TBFLAG_A64(flags, NV, 1);
+ if (hcr & HCR_NV1) {
+ DP_TBFLAG_A64(flags, NV1, 1);
+ }
+ if (hcr & HCR_NV2) {
+ DP_TBFLAG_A64(flags, NV2, 1);
+ if (hcr & HCR_E2H) {
+ DP_TBFLAG_A64(flags, NV2_MEM_E20, 1);
+ }
+ if (env->cp15.sctlr_el[2] & SCTLR_EE) {
+ DP_TBFLAG_A64(flags, NV2_MEM_BE, 1);
+ }
+ }
+ }
+
if (cpu_isar_feature(aa64_mte, env_archcpu(env))) {
/*
* Set MTE_ACTIVE if any access may be Checked, and leave clear
diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c
index 105ab63..b5ac260 100644
--- a/target/arm/tcg/op_helper.c
+++ b/target/arm/tcg/op_helper.c
@@ -985,7 +985,14 @@
*
* Conduit SMC, valid call Trap to EL2 PSCI Call
* Conduit SMC, inval call Trap to EL2 Undef insn
- * Conduit not SMC Undef insn Undef insn
+ * Conduit not SMC Undef or trap[1] Undef insn
+ *
+ * [1] In this case:
+ * - if HCR_EL2.NV == 1 we must trap to EL2
+ * - if HCR_EL2.NV == 0 then newer architecture revisions permit
+ * AArch64 (but not AArch32) to trap to EL2 as an IMPDEF choice
+ * - otherwise we must UNDEF
+ * We take the IMPDEF choice to always UNDEF if HCR_EL2.NV == 0.
*/
/* On ARMv8 with EL3 AArch64, SMD applies to both S and NS state.
@@ -999,9 +1006,12 @@
: smd_flag && !secure;
if (!arm_feature(env, ARM_FEATURE_EL3) &&
+ !(arm_hcr_el2_eff(env) & HCR_NV) &&
cpu->psci_conduit != QEMU_PSCI_CONDUIT_SMC) {
- /* If we have no EL3 then SMC always UNDEFs and can't be
- * trapped to EL2. PSCI-via-SMC is a sort of ersatz EL3
+ /*
+ * If we have no EL3 then traditionally SMC always UNDEFs and can't be
+ * trapped to EL2. For nested virtualization, SMC can be trapped to
+ * the outer hypervisor. PSCI-via-SMC is a sort of ersatz EL3
* firmware within QEMU, and we want an EL2 guest to be able
* to forbid its EL1 from making PSCI calls into QEMU's
* "firmware" via HCR.TSC, so for these purposes treat
diff --git a/target/arm/tcg/tlb_helper.c b/target/arm/tcg/tlb_helper.c
index 4fdd853..dd5de74 100644
--- a/target/arm/tcg/tlb_helper.c
+++ b/target/arm/tcg/tlb_helper.c
@@ -50,7 +50,15 @@
* ST64BV, or ST64BV0 insns report syndrome info even for stage-1
* faults and regardless of the target EL.
*/
- if (!(template_syn & ARM_EL_ISV) || target_el != 2
+ if (template_syn & ARM_EL_VNCR) {
+ /*
+ * FEAT_NV2 faults on accesses via VNCR_EL2 are a special case:
+ * they are always reported as "same EL", even though we are going
+ * from EL1 to EL2.
+ */
+ assert(!fi->stage2);
+ syn = syn_data_abort_vncr(fi->ea, is_write, fsc);
+ } else if (!(template_syn & ARM_EL_ISV) || target_el != 2
|| fi->s1ptw || !fi->stage2) {
syn = syn_data_abort_no_iss(same_el, 0,
fi->ea, 0, fi->s1ptw, is_write, fsc);
@@ -169,6 +177,20 @@
int current_el = arm_current_el(env);
bool same_el;
uint32_t syn, exc, fsr, fsc;
+ /*
+ * We know this must be a data or insn abort, and that
+ * env->exception.syndrome contains the template syndrome set
+ * up at translate time. So we can check only the VNCR bit
+ * (and indeed syndrome does not have the EC field in it,
+ * because we masked that out in disas_set_insn_syndrome())
+ */
+ bool is_vncr = (mmu_idx != MMU_INST_FETCH) &&
+ (env->exception.syndrome & ARM_EL_VNCR);
+
+ if (is_vncr) {
+ /* FEAT_NV2 faults on accesses via VNCR_EL2 go to EL2 */
+ target_el = 2;
+ }
if (report_as_gpc_exception(cpu, current_el, fi)) {
target_el = 3;
@@ -177,7 +199,8 @@
syn = syn_gpc(fi->stage2 && fi->type == ARMFault_GPCFOnWalk,
access_type == MMU_INST_FETCH,
- encode_gpcsc(fi), 0, fi->s1ptw,
+ encode_gpcsc(fi), is_vncr,
+ 0, fi->s1ptw,
access_type == MMU_DATA_STORE, fsc);
env->cp15.mfar_el3 = fi->paddr;
diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
index f3b5b91..27335e8 100644
--- a/target/arm/tcg/translate-a64.c
+++ b/target/arm/tcg/translate-a64.c
@@ -1606,7 +1606,7 @@
if (s->current_el == 0) {
return false;
}
- if (s->fgt_eret) {
+ if (s->trap_eret) {
gen_exception_insn_el(s, 0, EXCP_UDEF, syn_erettrap(0), 2);
return true;
}
@@ -1633,7 +1633,7 @@
return false;
}
/* The FGT trap takes precedence over an auth trap. */
- if (s->fgt_eret) {
+ if (s->trap_eret) {
gen_exception_insn_el(s, 0, EXCP_UDEF, syn_erettrap(a->m ? 3 : 2), 2);
return true;
}
@@ -2132,16 +2132,19 @@
crn, crm, op0, op1, op2);
const ARMCPRegInfo *ri = get_arm_cp_reginfo(s->cp_regs, key);
bool need_exit_tb = false;
+ bool nv_trap_to_el2 = false;
+ bool nv_redirect_reg = false;
+ bool skip_fp_access_checks = false;
+ bool nv2_mem_redirect = false;
TCGv_ptr tcg_ri = NULL;
TCGv_i64 tcg_rt;
- uint32_t syndrome;
+ uint32_t syndrome = syn_aa64_sysregtrap(op0, op1, op2, crn, crm, rt, isread);
if (crn == 11 || crn == 15) {
/*
* Check for TIDCP trap, which must take precedence over
* the UNDEF for "no such register" etc.
*/
- syndrome = syn_aa64_sysregtrap(op0, op1, op2, crn, crm, rt, isread);
switch (s->current_el) {
case 0:
if (dc_isar_feature(aa64_tidcp1, s)) {
@@ -2165,17 +2168,65 @@
return;
}
+ if (s->nv2 && ri->nv2_redirect_offset) {
+ /*
+ * Some registers always redirect to memory; some only do so if
+ * HCR_EL2.NV1 is 0, and some only if NV1 is 1 (these come in
+ * pairs which share an offset; see the table in R_CSRPQ).
+ */
+ if (ri->nv2_redirect_offset & NV2_REDIR_NV1) {
+ nv2_mem_redirect = s->nv1;
+ } else if (ri->nv2_redirect_offset & NV2_REDIR_NO_NV1) {
+ nv2_mem_redirect = !s->nv1;
+ } else {
+ nv2_mem_redirect = true;
+ }
+ }
+
/* Check access permissions */
if (!cp_access_ok(s->current_el, ri, isread)) {
- gen_sysreg_undef(s, isread, op0, op1, op2, crn, crm, rt);
- return;
+ /*
+ * FEAT_NV/NV2 handling does not do the usual FP access checks
+ * for registers only accessible at EL2 (though it *does* do them
+ * for registers accessible at EL1).
+ */
+ skip_fp_access_checks = true;
+ if (s->nv2 && (ri->type & ARM_CP_NV2_REDIRECT)) {
+ /*
+ * This is one of the few EL2 registers which should redirect
+ * to the equivalent EL1 register. We do that after running
+ * the EL2 register's accessfn.
+ */
+ nv_redirect_reg = true;
+ assert(!nv2_mem_redirect);
+ } else if (nv2_mem_redirect) {
+ /*
+ * NV2 redirect-to-memory takes precedence over trap to EL2 or
+ * UNDEF to EL1.
+ */
+ } else if (s->nv && arm_cpreg_traps_in_nv(ri)) {
+ /*
+ * This register / instruction exists and is an EL2 register, so
+ * we must trap to EL2 if accessed in nested virtualization EL1
+ * instead of UNDEFing. We'll do that after the usual access checks.
+ * (This makes a difference only for a couple of registers like
+ * VSTTBR_EL2 where the "UNDEF if NonSecure" should take priority
+ * over the trap-to-EL2. Most trapped-by-FEAT_NV registers have
+ * an accessfn which does nothing when called from EL1, because
+ * the trap-to-EL3 controls which would apply to that register
+ * at EL2 don't take priority over the FEAT_NV trap-to-EL2.)
+ */
+ nv_trap_to_el2 = true;
+ } else {
+ gen_sysreg_undef(s, isread, op0, op1, op2, crn, crm, rt);
+ return;
+ }
}
if (ri->accessfn || (ri->fgt && s->fgt_active)) {
/* Emit code to perform further access permissions checks at
* runtime; this may result in an exception.
*/
- syndrome = syn_aa64_sysregtrap(op0, op1, op2, crn, crm, rt, isread);
gen_a64_update_pc(s, 0);
tcg_ri = tcg_temp_new_ptr();
gen_helper_access_check_cp_reg(tcg_ri, tcg_env,
@@ -2190,6 +2241,78 @@
gen_a64_update_pc(s, 0);
}
+ if (!skip_fp_access_checks) {
+ if ((ri->type & ARM_CP_FPU) && !fp_access_check_only(s)) {
+ return;
+ } else if ((ri->type & ARM_CP_SVE) && !sve_access_check(s)) {
+ return;
+ } else if ((ri->type & ARM_CP_SME) && !sme_access_check(s)) {
+ return;
+ }
+ }
+
+ if (nv_trap_to_el2) {
+ gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
+ return;
+ }
+
+ if (nv_redirect_reg) {
+ /*
+ * FEAT_NV2 redirection of an EL2 register to an EL1 register.
+ * Conveniently in all cases the encoding of the EL1 register is
+ * identical to the EL2 register except that opc1 is 0.
+ * Get the reginfo for the EL1 register to use for the actual access.
+ * We don't use the EL1 register's access function, and
+ * fine-grained-traps on EL1 also do not apply here.
+ */
+ key = ENCODE_AA64_CP_REG(CP_REG_ARM64_SYSREG_CP,
+ crn, crm, op0, 0, op2);
+ ri = get_arm_cp_reginfo(s->cp_regs, key);
+ assert(ri);
+ assert(cp_access_ok(s->current_el, ri, isread));
+ /*
+ * We might not have done an update_pc earlier, so check we don't
+ * need it. We could support this in future if necessary.
+ */
+ assert(!(ri->type & ARM_CP_RAISES_EXC));
+ }
+
+ if (nv2_mem_redirect) {
+ /*
+ * This system register is being redirected into an EL2 memory access.
+ * This means it is not an IO operation, doesn't change hflags,
+ * and need not end the TB, because it has no side effects.
+ *
+ * The access is 64-bit single copy atomic, guaranteed aligned because
+ * of the definition of VCNR_EL2. Its endianness depends on
+ * SCTLR_EL2.EE, not on the data endianness of EL1.
+ * It is done under either the EL2 translation regime or the EL2&0
+ * translation regime, depending on HCR_EL2.E2H. It behaves as if
+ * PSTATE.PAN is 0.
+ */
+ TCGv_i64 ptr = tcg_temp_new_i64();
+ MemOp mop = MO_64 | MO_ALIGN | MO_ATOM_IFALIGN;
+ ARMMMUIdx armmemidx = s->nv2_mem_e20 ? ARMMMUIdx_E20_2 : ARMMMUIdx_E2;
+ int memidx = arm_to_core_mmu_idx(armmemidx);
+ uint32_t syn;
+
+ mop |= (s->nv2_mem_be ? MO_BE : MO_LE);
+
+ tcg_gen_ld_i64(ptr, tcg_env, offsetof(CPUARMState, cp15.vncr_el2));
+ tcg_gen_addi_i64(ptr, ptr,
+ (ri->nv2_redirect_offset & ~NV2_REDIR_FLAG_MASK));
+ tcg_rt = cpu_reg(s, rt);
+
+ syn = syn_data_abort_vncr(0, !isread, 0);
+ disas_set_insn_syndrome(s, syn);
+ if (isread) {
+ tcg_gen_qemu_ld_i64(tcg_rt, ptr, memidx, mop);
+ } else {
+ tcg_gen_qemu_st_i64(tcg_rt, ptr, memidx, mop);
+ }
+ return;
+ }
+
/* Handle special cases first */
switch (ri->type & ARM_CP_SPECIAL_MASK) {
case 0:
@@ -2205,12 +2328,17 @@
}
return;
case ARM_CP_CURRENTEL:
- /* Reads as current EL value from pstate, which is
+ {
+ /*
+ * Reads as current EL value from pstate, which is
* guaranteed to be constant by the tb flags.
+ * For nested virt we should report EL2.
*/
+ int el = s->nv ? 2 : s->current_el;
tcg_rt = cpu_reg(s, rt);
- tcg_gen_movi_i64(tcg_rt, s->current_el << 2);
+ tcg_gen_movi_i64(tcg_rt, el << 2);
return;
+ }
case ARM_CP_DC_ZVA:
/* Writes clear the aligned block of memory which rt points into. */
if (s->mte_active[0]) {
@@ -2268,13 +2396,6 @@
default:
g_assert_not_reached();
}
- if ((ri->type & ARM_CP_FPU) && !fp_access_check_only(s)) {
- return;
- } else if ((ri->type & ARM_CP_SVE) && !sve_access_check(s)) {
- return;
- } else if ((ri->type & ARM_CP_SME) && !sme_access_check(s)) {
- return;
- }
if (ri->type & ARM_CP_IO) {
/* I/O operations must end the TB here (whether read or write) */
@@ -13980,7 +14101,7 @@
dc->pstate_il = EX_TBFLAG_ANY(tb_flags, PSTATE__IL);
dc->fgt_active = EX_TBFLAG_ANY(tb_flags, FGT_ACTIVE);
dc->fgt_svc = EX_TBFLAG_ANY(tb_flags, FGT_SVC);
- dc->fgt_eret = EX_TBFLAG_A64(tb_flags, FGT_ERET);
+ dc->trap_eret = EX_TBFLAG_A64(tb_flags, TRAP_ERET);
dc->sve_excp_el = EX_TBFLAG_A64(tb_flags, SVEEXC_EL);
dc->sme_excp_el = EX_TBFLAG_A64(tb_flags, SMEEXC_EL);
dc->vl = (EX_TBFLAG_A64(tb_flags, VL) + 1) * 16;
@@ -13997,6 +14118,11 @@
dc->pstate_za = EX_TBFLAG_A64(tb_flags, PSTATE_ZA);
dc->sme_trap_nonstreaming = EX_TBFLAG_A64(tb_flags, SME_TRAP_NONSTREAMING);
dc->naa = EX_TBFLAG_A64(tb_flags, NAA);
+ dc->nv = EX_TBFLAG_A64(tb_flags, NV);
+ dc->nv1 = EX_TBFLAG_A64(tb_flags, NV1);
+ dc->nv2 = EX_TBFLAG_A64(tb_flags, NV2);
+ dc->nv2_mem_e20 = EX_TBFLAG_A64(tb_flags, NV2_MEM_E20);
+ dc->nv2_mem_be = EX_TBFLAG_A64(tb_flags, NV2_MEM_BE);
dc->vec_len = 0;
dc->vec_stride = 0;
dc->cp_regs = arm_cpu->cp_regs;
diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
index 3c3bb34..93be745 100644
--- a/target/arm/tcg/translate.h
+++ b/target/arm/tcg/translate.h
@@ -138,12 +138,22 @@
bool mve_no_pred;
/* True if fine-grained traps are active */
bool fgt_active;
- /* True if fine-grained trap on ERET is enabled */
- bool fgt_eret;
/* True if fine-grained trap on SVC is enabled */
bool fgt_svc;
+ /* True if a trap on ERET is enabled (FGT or NV) */
+ bool trap_eret;
/* True if FEAT_LSE2 SCTLR_ELx.nAA is set */
bool naa;
+ /* True if FEAT_NV HCR_EL2.NV is enabled */
+ bool nv;
+ /* True if NV enabled and HCR_EL2.NV1 is set */
+ bool nv1;
+ /* True if NV enabled and HCR_EL2.NV2 is set */
+ bool nv2;
+ /* True if NV2 enabled and NV2 RAM accesses use EL2&0 translation regime */
+ bool nv2_mem_e20;
+ /* True if NV2 enabled and NV2 RAM accesses are big-endian */
+ bool nv2_mem_be;
/*
* >= 0, a copy of PSTATE.BTYPE, which will be 0 without v8.5-BTI.
* < 0, set by the current instruction.
@@ -159,6 +169,8 @@
int c15_cpar;
/* TCG op of the current insn_start. */
TCGOp *insn_start;
+ /* Offset from VNCR_EL2 when FEAT_NV2 redirects this reg to memory */
+ uint32_t nv2_redirect_offset;
} DisasContext;
typedef struct DisasCompare {