:p
atchew
Login
Hi; here's another arm pullreq. This is big but it's almost entirely the GICv5 emulation. I did throw in a couple of other tiny bugfix patches. thanks -- PMM The following changes since commit ee7eb612be8f8886d48c1d0c1f1c65e495138f83: Merge tag 'single-binary-20260506' of https://github.com/philmd/qemu into staging (2026-05-06 10:45:02 -0400) are available in the Git repository at: https://gitlab.com/pm215/qemu.git tags/pull-target-arm-20260507 for you to fetch changes up to 18b664c90085b0d2be9c2ad8c747e00a7a733402: hw/misc/bcm2835_rng: Specify valid memory access sizes (2026-05-07 15:14:58 +0100) ---------------------------------------------------------------- target-arm queue: * Initial experimental GICv5 interrupt controller emulation * target/arm: Report IL=0 for Thumb 16-bit BKPT insn * hw/misc/bcm2835_rng: Specify valid memory access sizes ---------------------------------------------------------------- Peter Maydell (67): qom/object: Add object_resolve_and_typecheck() hw/core: Permit devices to define an array of link properties hw/intc: Skeleton of GICv5 IRS classes hw/arm/Kconfig: select ARM_GICV5 for ARM_VIRT board hw/intc/arm_gicv5: Implement skeleton code for IRS register frames hw/intc/arm_gicv5: Add migration blocker hw/intc/arm_gicv5: Create and validate QOM properties hw/intc/arm_gicv5: Create inbound GPIO lines for SPIs hw/intc/arm_gicv5: Define macros for config frame registers hw/intc/arm_gicv5: Implement IRS ID regs hw/intc/arm_gicv5: Add link property for MemoryRegion for DMA hw/intc/arm_gicv5: Implement gicv5_class_name() hw/intc/arm_gicv5: Add defines for GICv5 architected PPIs target/arm: GICv5 cpuif: Initial skeleton and GSB barrier insns target/arm: Set up pointer to GICv5 in each CPU hw/intc/arm_gicv5: Implement IRS_IST_{BASER, STATUSR, CFGR} hw/intc/arm_gicv5: Cache LPI IST config in a struct hw/intc/arm_gicv5: Implement gicv5_set_priority() target/arm: GICv5 cpuif: Implement the GIC CDPRI instruction hw/intc/arm_gicv5: Implement IRS_MAP_L2_ISTR hw/intc/arm_gicv5: Implement remaining set-config functions target/arm: GICv5 cpuif: Implement GIC CD* insns for setting config hw/intc/arm_gicv5: Create backing state for SPIs hw/intc/arm_gicv5: Make gicv5_set_* update SPI state hw/intc/arm_gicv5: Implement gicv5_request_config() target/arm: GICv5 cpuif: Implement GIC CDRCFG and ICC_ICSR_EL1 hw/intc/arm_gicv5: Implement IRS_SPI_{SELR, STATUSR, CFGR, DOMAINR} hw/intc/arm_gicv5: Update SPI state for CLEAR/SET events hw/intc/arm_gicv5: Implement IRS_CR0 and IRS_CR1 hw/intc/arm_gicv5: Implement IRS_SYNCR and IRS_SYNC_STATUSR hw/intc/arm_gicv5: Implement IRS_PE_{CR0, SELR, STATUSR} hw/intc/arm_gicv5: Implement CoreSight ID registers hw/intc/arm_gicv5: Cache pending LPIs in a hash table target/arm: GICv5 cpuif: Implement ICC_IAFFIDR_EL1 target/arm: GICv5 cpuif: Implement ICC_IDR0_EL1 target/arm: GICv5 cpuif: Implement GICv5 PPI active set/clear registers target/arm: GICv5 cpuif: Implement PPI handling mode register target/arm: GICv5 cpuif: Implement PPI pending status registers target/arm: GICv5 cpuif: Implement PPI enable register target/arm: GICv5 cpuif: Implement PPI priority registers target/arm: GICv5 cpuif: Implement ICC_APR_EL1 and ICC_HAPR_EL1 target/arm: GICv5 cpuif: Calculate the highest priority PPI hw/intc/arm_gicv5: Calculate HPPI in the IRS target/arm: GICv5 cpuif: Implement ICC_CR0_EL1 target/arm: GICv5 cpuif: Implement ICC_PCR_EL1 target/arm: GICv5 cpuif: Implement ICC_HPPIR_EL1 hw/intc/arm_gicv5: Implement Activate command target/arm: GICv5 cpuif: Implement GICR CDIA command target/arm: GICv5 cpuif: Implement GIC CDEOI hw/intc/arm_gicv5: Implement Deactivate command target/arm: GICv5 cpuif: Implement GIC CDDI target/arm: GICv5 cpuif: Signal IRQ or FIQ target/arm: Connect internal interrupt sources up as GICv5 PPIs target/arm: Add has_gcie property to enable FEAT_GCIE hw/intc/arm_gicv3_cpuif: Don't allow GICv3 if CPU has GICv5 cpuif hw/arm/virt: Remember CPU phandles rather than looking them up by name hw/arm/virt: Move MSI controller creation out of create_gic() hw/arm/virt: Pull "wire CPU interrupts" out of create_gic() hw/arm/virt: Split GICv2 and GICv3/4 creation hw/arm/virt: Create and connect GICv5 hw/arm/virt: Advertise GICv5 in the DTB hw/arm/virt: Handle GICv5 in interrupt bindings for PPIs hw/arm/virt: Use correct interrupt type for GICv5 SPIs in the DTB hw/arm/virt: Enable GICv5 CPU interface when using GICv5 hw/arm/virt: Allow user to select GICv5 target/arm: Report IL=0 for Thumb 16-bit BKPT insn hw/misc/bcm2835_rng: Specify valid memory access sizes docs/system/arm/virt.rst | 19 + hw/arm/Kconfig | 1 + hw/arm/virt.c | 527 +++++++--- hw/core/qdev-properties.c | 78 ++ hw/intc/Kconfig | 5 + hw/intc/arm_gicv3.c | 2 +- hw/intc/arm_gicv3_cpuif.c | 14 +- hw/intc/arm_gicv5.c | 1962 ++++++++++++++++++++++++++++++++++++ hw/intc/arm_gicv5_common.c | 215 ++++ hw/intc/gicv3_internal.h | 2 +- hw/intc/meson.build | 4 + hw/intc/trace-events | 23 + hw/misc/bcm2835_rng.c | 4 + include/hw/arm/fdt.h | 10 + include/hw/arm/virt.h | 15 + include/hw/core/qdev-properties.h | 41 + include/hw/intc/arm_gicv5.h | 51 + include/hw/intc/arm_gicv5_common.h | 247 +++++ include/hw/intc/arm_gicv5_stream.h | 228 +++++ include/hw/intc/arm_gicv5_types.h | 110 ++ include/qom/object.h | 17 + meson.build | 1 + qom/object.c | 41 +- target/arm/cpregs-pmu.c | 9 +- target/arm/cpu-features.h | 6 + target/arm/cpu.c | 62 ++ target/arm/cpu.h | 28 + target/arm/helper.c | 21 + target/arm/internals.h | 9 + target/arm/tcg-stubs.c | 4 + target/arm/tcg/gicv5-cpuif.c | 952 +++++++++++++++++ target/arm/tcg/meson.build | 1 + target/arm/tcg/trace-events | 11 + target/arm/tcg/trace.h | 1 + target/arm/tcg/translate.c | 2 +- 35 files changed, 4564 insertions(+), 159 deletions(-) create mode 100644 hw/intc/arm_gicv5.c create mode 100644 hw/intc/arm_gicv5_common.c create mode 100644 include/hw/intc/arm_gicv5.h create mode 100644 include/hw/intc/arm_gicv5_common.h create mode 100644 include/hw/intc/arm_gicv5_stream.h create mode 100644 include/hw/intc/arm_gicv5_types.h create mode 100644 target/arm/tcg/gicv5-cpuif.c create mode 100644 target/arm/tcg/trace-events create mode 100644 target/arm/tcg/trace.h
Add a new function object_resolve_and_typecheck(), whose purpose is to look up the object at a given QOM path, confirm that it is the expected type, and return it. This is similar to the existing object_resolve_path_type(), but it insists on a non-ambiguous path. We were already using this functionality internally to object.c as part of the object_resolve_link() function, so this patch implements the new function by pulling the link-property specific parts out of the more generic resolve-and-typecheck part. The motivation for this function is that we want to allow devices to provide an array of link properties; for that we will need to be able to provide the expected type of the linked object in a different way to the single-item link properties. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260327111700.795099-2-peter.maydell@linaro.org --- include/qom/object.h | 17 +++++++++++++++++ qom/object.c | 41 +++++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/include/qom/object.h b/include/qom/object.h index XXXXXXX..XXXXXXX 100644 --- a/include/qom/object.h +++ b/include/qom/object.h @@ -XXX,XX +XXX,XX @@ ObjectProperty *object_class_property_add_link(ObjectClass *oc, Object *val, Error **errp), ObjectPropertyLinkFlags flags); +/** + * object_resolve_and_typecheck: + * @path: path to look up + * @name: name of property we are resolving for (used only in error messages) + * @target_type: QOM type we expect @path to resolve to + * @errp: error + * + * Look up the object at @path and return it. If it does not have the + * correct type @target_type, return NULL and set @errp. + * + * This is similar to object_resolve_path_type(), but it insists on a + * non-ambiguous path and it produces error messages that are + * specialised to the use case of setting a link property on an object. + */ +Object *object_resolve_and_typecheck(const char *path, const char *name, + const char *target_type, Error **errp); + /** * object_property_add_str: * @obj: the object to add a property to diff --git a/qom/object.c b/qom/object.c index XXXXXXX..XXXXXXX 100644 --- a/qom/object.c +++ b/qom/object.c @@ -XXX,XX +XXX,XX @@ static void object_get_link_property(Object *obj, Visitor *v, } } -/* - * object_resolve_link: - * - * Lookup an object and ensure its type matches the link property type. This - * is similar to object_resolve_path() except type verification against the - * link property is performed. - * - * Returns: The matched object or NULL on path lookup failures. - */ -static Object *object_resolve_link(Object *obj, const char *name, - const char *path, Error **errp) +Object *object_resolve_and_typecheck(const char *path, const char *name, + const char *target_type, Error **errp) { - const char *type; - char *target_type; bool ambiguous = false; Object *target; - /* Go from link<FOO> to FOO. */ - type = object_property_get_type(obj, name, NULL); - target_type = g_strndup(&type[5], strlen(type) - 6); target = object_resolve_path_type(path, target_type, &ambiguous); if (ambiguous) { @@ -XXX,XX +XXX,XX @@ static Object *object_resolve_link(Object *obj, const char *name, } target = NULL; } - g_free(target_type); - return target; } +/* + * object_resolve_link: + * + * Lookup an object and ensure its type matches the link property type. This + * is similar to object_resolve_path() except type verification against the + * link property is performed. + * + * Returns: The matched object or NULL on path lookup failures. + */ +static Object *object_resolve_link(Object *obj, const char *name, + const char *path, Error **errp) +{ + const char *type; + g_autofree char *target_type = NULL; + + /* Go from link<FOO> to FOO. */ + type = object_property_get_type(obj, name, NULL); + target_type = g_strndup(&type[5], strlen(type) - 6); + return object_resolve_and_typecheck(path, name, target_type, errp); +} + static void object_set_link_property(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) -- 2.43.0
Currently we allow devices to define "link properties" with DEFINE_PROP_LINK(): these are a way to give a device a pointer to another QOM object. (Under the hood this is done by handing it the canonical QOM path for the object.) We also allow devices to define "array properties" with DEFINE_PROP_ARRAY(): these are a way to give a device a variable-length array of properties. However, there is no way to define an array of link properties. If you try to do it by passing qdev_prop_link as the arrayprop argument to DEFINE_PROP_ARRAY() you will get a crash because qdev_prop_link does not provide the .set and .get methods in its PropertyInfo struct. This patch implements a new DEFINE_PROP_LINK_ARRAY(). In a device you can use it like this: struct MyDevice { ... uint32_t num_cpus; ARMCPU **cpus; } and in your Property array: DEFINE_PROP_LINK_ARRAY("cpus", MyDevice, num_cpus, cpus, TYPE_ARM_CPU, ARMCPU *), The array property code will fill in s->num_cpus, allocate memory in s->cpus, and populate it with pointers. On the device-creation side you set the property in the same way as the existing array properties, using the new qlist_append_link() function to append to the QList: QList *cpulist = qlist_new(); for (int i = 0; i < cpus; i++) { qlist_append_link(cpulist, OBJECT(cpu[i])); } qdev_prop_set_array(mydev, "cpus", cpulist); The implementation is mostly in the provision of the .set and .get methods to the qdev_prop_link PropertyInfo struct. The code of these methods parallels the code in object_set_link_property() and object_get_link_property(). We can't completely share the code with those functions because of differences in where we get the information like the target QOM type, but I have pulled out a new function object_resolve_and_typecheck() for the shared "given a QOM path and a type, give me the object or an error" code. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-3-peter.maydell@linaro.org --- hw/core/qdev-properties.c | 78 +++++++++++++++++++++++++++++++ include/hw/core/qdev-properties.h | 41 ++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index XXXXXXX..XXXXXXX 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -XXX,XX +XXX,XX @@ static Property array_elem_prop(Object *obj, const Property *parent_prop, * being inside the device struct. */ .offset = (uintptr_t)elem - (uintptr_t)obj, + .link_type = parent_prop->link_type, }; } @@ -XXX,XX +XXX,XX @@ void qdev_prop_set_array(DeviceState *dev, const char *name, QList *values) qobject_unref(values); } +void qlist_append_link(QList *qlist, Object *obj) +{ + g_autofree char *path = object_get_canonical_path(obj); + qlist_append_str(qlist, path); +} + static GPtrArray *global_props(void) { static GPtrArray *gp; @@ -XXX,XX +XXX,XX @@ static ObjectProperty *create_link_property(ObjectClass *oc, const char *name, OBJ_PROP_LINK_STRONG); } +/* + * The logic in these get_link() and set_link() functions is similar + * to that used for single-element link properties in the + * object_get_link_property() and object_set_link_property() functions. + * The difference is largely in how we get the expected type of the + * link: for us it is in the Property struct, and for a single link + * property it is part of the property name on the object. + */ +static void get_link(Object *obj, Visitor *v, const char *name, void *opaque, + Error **errp) +{ + const Property *prop = opaque; + Object **targetp = object_field_prop_ptr(obj, prop); + g_autofree char *path = NULL; + + if (*targetp) { + path = object_get_canonical_path(*targetp); + visit_type_str(v, name, &path, errp); + } else { + path = g_strdup(""); + visit_type_str(v, name, &path, errp); + } +} + +static void set_link(Object *obj, Visitor *v, const char *name, void *opaque, + Error **errp) +{ + const Property *prop = opaque; + Object **targetp = object_field_prop_ptr(obj, prop); + g_autofree char *path = NULL; + Object *new_target, *old_target = *targetp; + + ERRP_GUARD(); + + /* Get the path to the object we want to set the link to */ + if (!visit_type_str(v, name, &path, errp)) { + return; + } + + /* Now get the pointer to the actual object */ + if (*path) { + new_target = object_resolve_and_typecheck(path, prop->name, + prop->link_type, errp); + if (!new_target) { + return; + } + } else { + new_target = NULL; + } + + /* + * Our link properties are always OBJ_PROP_LINK_STRONG and + * have the allow_set_link_before_realize check. + */ + qdev_prop_allow_set_link_before_realize(obj, prop->name, new_target, errp); + if (*errp) { + return; + } + + *targetp = new_target; + object_ref(new_target); + object_unref(old_target); +} + const PropertyInfo qdev_prop_link = { .type = "link", .create = create_link_property, + /* + * Since we have a create method, the get and set are used + * only in get_prop_array() and set_prop_array() for the case + * where we have an array of link properties. + */ + .get = get_link, + .set = set_link, }; void qdev_property_add_static(DeviceState *dev, const Property *prop) diff --git a/include/hw/core/qdev-properties.h b/include/hw/core/qdev-properties.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/core/qdev-properties.h +++ b/include/hw/core/qdev-properties.h @@ -XXX,XX +XXX,XX @@ extern const PropertyInfo qdev_prop_link; DEFINE_PROP(_name, _state, _field, qdev_prop_link, _ptr_type, \ .link_type = _type) +/** + * DEFINE_PROP_LINK_ARRAY: + * @_name: name of the array + * @_state: name of the device state structure type + * @_field: uint32_t field in @_state to hold the array length + * @_arrayfield: field in @_state (of type '@_arraytype *') which + * will point to the array + * @_linktype: QOM type name of the link type + * @_arraytype: C type of the array elements + * + * Define device properties for a variable-length array _name of links + * (i.e. this is the array version of DEFINE_PROP_LINK). + * + * The array is represented as a list of QStrings in the visitor interface, + * where each string is the QOM path of the object to be linked. + */ +#define DEFINE_PROP_LINK_ARRAY(_name, _state, _field, _arrayfield, \ + _linktype, _arraytype) \ + DEFINE_PROP(_name, _state, _field, qdev_prop_array, uint32_t, \ + .set_default = true, \ + .defval.u = 0, \ + .arrayinfo = &qdev_prop_link, \ + .arrayfieldsize = sizeof(_arraytype), \ + .arrayoffset = offsetof(_state, _arrayfield), \ + .link_type = _linktype) + #define DEFINE_PROP_UINT8(_n, _s, _f, _d) \ DEFINE_PROP_UNSIGNED(_n, _s, _f, _d, qdev_prop_uint8, uint8_t) #define DEFINE_PROP_UINT16(_n, _s, _f, _d) \ @@ -XXX,XX +XXX,XX @@ void qdev_prop_set_enum(DeviceState *dev, const char *name, int value); /* Takes ownership of @values */ void qdev_prop_set_array(DeviceState *dev, const char *name, QList *values); +/** + * qlist_append_link: Add a QOM object to a QList of link properties + * @qlist: list to append to + * @obj: object to append + * + * This is a helper function for constructing a QList to pass to + * qdev_prop_set_array() when the qdev property array is an array of + * link properties (i.e. one defined with DEFINE_PROP_LINK_ARRAY). + * + * The object is encoded into the list as a QString which is the + * canonical path of the object; this is the same encoding that + * object_set_link_property() and object_get_link_property() use. + */ +void qlist_append_link(QList *qlist, Object *obj); + void *object_field_prop_ptr(Object *obj, const Property *prop); void qdev_prop_register_global(GlobalProperty *prop); -- 2.43.0
This commit adds the skeleton of the classes for the GICv5 IRS (Interrupt Routing Service). Since the IRS is the main (and only non-optional) part of the GICv5 outside the CPU, we call it simply "GICv5", in line with how we've handled the GICv3. Since we're definitely going to need to have support for KVM VMs where we present the guest with a GICv5, we use the same split between an abstract "common" and a concrete specific-to-TCG child class that we have for the various GICv3 components. This avoids having to refactor out the base class later. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-4-peter.maydell@linaro.org --- hw/intc/Kconfig | 5 ++++ hw/intc/arm_gicv5.c | 39 ++++++++++++++++++++++++++++++ hw/intc/arm_gicv5_common.c | 31 ++++++++++++++++++++++++ hw/intc/meson.build | 4 +++ include/hw/intc/arm_gicv5.h | 32 ++++++++++++++++++++++++ include/hw/intc/arm_gicv5_common.h | 31 ++++++++++++++++++++++++ 6 files changed, 142 insertions(+) create mode 100644 hw/intc/arm_gicv5.c create mode 100644 hw/intc/arm_gicv5_common.c create mode 100644 include/hw/intc/arm_gicv5.h create mode 100644 include/hw/intc/arm_gicv5_common.h diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/Kconfig +++ b/hw/intc/Kconfig @@ -XXX,XX +XXX,XX @@ config ARM_GIC_KVM bool depends on ARM_GIC && KVM +config ARM_GICV5 + bool + depends on TCG + select MSI_NONBROKEN + config XICS bool diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ +/* + * ARM GICv5 emulation: Interrupt Routing Service (IRS) + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/intc/arm_gicv5.h" + +OBJECT_DEFINE_TYPE(GICv5, gicv5, ARM_GICV5, ARM_GICV5_COMMON) + +static void gicv5_reset_hold(Object *obj, ResetType type) +{ + GICv5 *s = ARM_GICV5(obj); + GICv5Class *c = ARM_GICV5_GET_CLASS(s); + + if (c->parent_phases.hold) { + c->parent_phases.hold(obj, type); + } +} + +static void gicv5_init(Object *obj) +{ +} + +static void gicv5_finalize(Object *obj) +{ +} + +static void gicv5_class_init(ObjectClass *oc, const void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(oc); + GICv5Class *gc = ARM_GICV5_CLASS(oc); + + resettable_class_set_parent_phases(rc, NULL, gicv5_reset_hold, NULL, + &gc->parent_phases); +} diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ +/* + * Common base class for GICv5 IRS + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/intc/arm_gicv5_common.h" + +OBJECT_DEFINE_ABSTRACT_TYPE(GICv5Common, gicv5_common, ARM_GICV5_COMMON, SYS_BUS_DEVICE) + +static void gicv5_common_reset_hold(Object *obj, ResetType type) +{ +} + +static void gicv5_common_init(Object *obj) +{ +} + +static void gicv5_common_finalize(Object *obj) +{ +} + +static void gicv5_common_class_init(ObjectClass *oc, const void *data) +{ + ResettableClass *rc = RESETTABLE_CLASS(oc); + + rc->phases.hold = gicv5_common_reset_hold; +} diff --git a/hw/intc/meson.build b/hw/intc/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: 'CONFIG_ARM_GICV3', if_true: files( 'arm_gicv3_its.c', 'arm_gicv3_redist.c', )) +system_ss.add(when: 'CONFIG_ARM_GICV5', if_true: files( + 'arm_gicv5_common.c', + 'arm_gicv5.c', +)) system_ss.add(when: 'CONFIG_ALLWINNER_A10_PIC', if_true: files('allwinner-a10-pic.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_vic.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_intc.c')) diff --git a/include/hw/intc/arm_gicv5.h b/include/hw/intc/arm_gicv5.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/intc/arm_gicv5.h @@ -XXX,XX +XXX,XX @@ +/* + * ARM GICv5 emulation: Interrupt Routing Service (IRS) + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INTC_ARM_GICV5_H +#define HW_INTC_ARM_GICV5_H + +#include "qom/object.h" +#include "hw/core/sysbus.h" +#include "hw/intc/arm_gicv5_common.h" + +#define TYPE_ARM_GICV5 "arm-gicv5" + +OBJECT_DECLARE_TYPE(GICv5, GICv5Class, ARM_GICV5) + +/* + * This class is for TCG-specific state for the GICv5. + */ +struct GICv5 { + GICv5Common parent_obj; +}; + +struct GICv5Class { + GICv5CommonClass parent_class; + ResettablePhases parent_phases; +}; + +#endif diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ +/* + * Common base class for GICv5 IRS + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INTC_ARM_GICV5_COMMON_H +#define HW_INTC_ARM_GICV5_COMMON_H + +#include "qom/object.h" +#include "hw/core/sysbus.h" + +#define TYPE_ARM_GICV5_COMMON "arm-gicv5-common" + +OBJECT_DECLARE_TYPE(GICv5Common, GICv5CommonClass, ARM_GICV5_COMMON) + +/* + * This class is for common state that will eventually be shared + * between TCG and KVM implementations of the GICv5. + */ +struct GICv5Common { + SysBusDevice parent_obj; +}; + +struct GICv5CommonClass { + SysBusDeviceClass parent_class; +}; + +#endif -- 2.43.0
When building the Arm "virt" board, pull in the GICv5. We haven't added support for creating or wiring up the GICv5 in that board yet, but adding it to the Kconfig early means that the GICv5 code will be compiled and so we can have more confidence that the individual commits building it up are correct (or at least compile). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-5-peter.maydell@linaro.org --- hw/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config ARM_VIRT imply NVDIMM imply IOMMUFD select ARM_GIC + select ARM_GICV5 if TCG select ACPI select ARM_SMMUV3 select GPIO_KEY -- 2.43.0
The GICv5 IRS has one mandatory register frame (the config frame) for each of up to four supported physical interrupt domains. Implement the skeleton of the code needed to create these as sysbus MMIO regions. The config frame has a mix of 32-bit and 64-bit registers, and it is valid to access the 64-bit registers with 32-bit accesses. In a similar way to the various GICv3 devices, we turn the MemoryRegionOps read_with_attrs and write_with_attrs calls into calls on functions specifically to read 32 or 64 bit values. (We can't trivially implement one in terms of the other because various registers have side effects on write which must only trigger when the "correct" half of the 64-bit register is written to.) Unlike the GICv3, we choose to expose a sysbus MMIO region for each interrupt domain even if the config of the GICv5 means that it doesn't implement that domain. This avoids having the config frame for a domain ending up at a different MMIO region index depending on the config of the GICv5. (This matters more for GICv5 because it supports Realm, and so there are more possible valid configurations.) gicv5_common_init_irqs_and_mmio() does not yet create any IRQs, but we name it this way to parallel the equivalent GICv3 function and to avoid having to rename it when we add the IRQ line creation in a subsequent commit. The arm_gicv5_types.h header is a little undermotivated at this point, but the aim is to have somewhere to put definitions that we want in both the GIC proper and the CPU interface. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-6-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 172 +++++++++++++++++++++++++++++ hw/intc/arm_gicv5_common.c | 35 ++++++ hw/intc/trace-events | 6 + include/hw/intc/arm_gicv5.h | 1 + include/hw/intc/arm_gicv5_common.h | 50 +++++++++ include/hw/intc/arm_gicv5_types.h | 28 +++++ 6 files changed, 292 insertions(+) create mode 100644 include/hw/intc/arm_gicv5_types.h diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "hw/intc/arm_gicv5.h" +#include "qapi/error.h" +#include "qemu/log.h" +#include "trace.h" OBJECT_DEFINE_TYPE(GICv5, gicv5, ARM_GICV5, ARM_GICV5_COMMON) +static const char *domain_name[] = { + [GICV5_ID_S] = "Secure", + [GICV5_ID_NS] = "NonSecure", + [GICV5_ID_EL3] = "EL3", + [GICV5_ID_REALM] = "Realm", +}; + +static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, + uint64_t *data, MemTxAttrs attrs) +{ + return false; +} + +static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, + uint64_t data, MemTxAttrs attrs) +{ + return false; +} + +static bool config_readll(GICv5 *s, GICv5Domain domain, hwaddr offset, + uint64_t *data, MemTxAttrs attrs) +{ + return false; +} + +static bool config_writell(GICv5 *s, GICv5Domain domain, hwaddr offset, + uint64_t data, MemTxAttrs attrs) +{ + return false; +} + +static MemTxResult config_read(void *opaque, GICv5Domain domain, hwaddr offset, + uint64_t *data, unsigned size, + MemTxAttrs attrs) +{ + GICv5 *s = ARM_GICV5(opaque); + bool result; + + switch (size) { + case 4: + result = config_readl(s, domain, offset, data, attrs); + break; + case 8: + result = config_readll(s, domain, offset, data, attrs); + break; + default: + result = false; + break; + } + + if (!result) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid guest read for IRS %s config frame " + "at offset " HWADDR_FMT_plx + " size %u\n", __func__, domain_name[domain], + offset, size); + trace_gicv5_badread(domain_name[domain], offset, size); + /* + * The spec requires that reserved registers are RAZ/WI; so we + * log the error but return MEMTX_OK so we don't cause a + * spurious data abort. + */ + *data = 0; + } else { + trace_gicv5_read(domain_name[domain], offset, *data, size); + } + + return MEMTX_OK; +} + +static MemTxResult config_write(void *opaque, GICv5Domain domain, + hwaddr offset, uint64_t data, unsigned size, + MemTxAttrs attrs) +{ + GICv5 *s = ARM_GICV5(opaque); + bool result; + + switch (size) { + case 4: + result = config_writel(s, domain, offset, data, attrs); + break; + case 8: + result = config_writell(s, domain, offset, data, attrs); + break; + default: + result = false; + break; + } + + if (!result) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid guest write for IRS %s config frame " + "at offset " HWADDR_FMT_plx + " size %u\n", __func__, domain_name[domain], + offset, size); + trace_gicv5_badwrite(domain_name[domain], offset, data, size); + /* + * The spec requires that reserved registers are RAZ/WI; so we + * log the error but return MEMTX_OK so we don't cause a + * spurious data abort. + */ + } else { + trace_gicv5_write(domain_name[domain], offset, data, size); + } + + return MEMTX_OK; +} + +#define DEFINE_READ_WRITE_WRAPPERS(NAME, DOMAIN) \ + static MemTxResult config_##NAME##_read(void *opaque, hwaddr offset, \ + uint64_t *data, unsigned size, \ + MemTxAttrs attrs) \ + { \ + return config_read(opaque, DOMAIN, offset, data, size, attrs); \ + } \ + static MemTxResult config_##NAME##_write(void *opaque, hwaddr offset, \ + uint64_t data, unsigned size, \ + MemTxAttrs attrs) \ + { \ + return config_write(opaque, DOMAIN, offset, data, size, attrs); \ + } + +DEFINE_READ_WRITE_WRAPPERS(ns, GICV5_ID_NS) +DEFINE_READ_WRITE_WRAPPERS(realm, GICV5_ID_REALM) +DEFINE_READ_WRITE_WRAPPERS(secure, GICV5_ID_S) +DEFINE_READ_WRITE_WRAPPERS(el3, GICV5_ID_EL3) + +#define FRAME_OP_ENTRY(NAME, DOMAIN) \ + [DOMAIN] = { \ + .read_with_attrs = config_##NAME##_read, \ + .write_with_attrs = config_##NAME##_write, \ + .endianness = DEVICE_LITTLE_ENDIAN, \ + .valid.min_access_size = 4, \ + .valid.max_access_size = 8, \ + .impl.min_access_size = 4, \ + .impl.max_access_size = 8, \ + } + +static const MemoryRegionOps config_frame_ops[NUM_GICV5_DOMAINS] = { + FRAME_OP_ENTRY(ns, GICV5_ID_NS), + FRAME_OP_ENTRY(realm, GICV5_ID_REALM), + FRAME_OP_ENTRY(secure, GICV5_ID_S), + FRAME_OP_ENTRY(el3, GICV5_ID_EL3), +}; + static void gicv5_reset_hold(Object *obj, ResetType type) { GICv5 *s = ARM_GICV5(obj); @@ -XXX,XX +XXX,XX @@ static void gicv5_reset_hold(Object *obj, ResetType type) } } +static void gicv5_realize(DeviceState *dev, Error **errp) +{ + GICv5Common *cs = ARM_GICV5_COMMON(dev); + GICv5Class *gc = ARM_GICV5_GET_CLASS(dev); + + ERRP_GUARD(); + + gc->parent_realize(dev, errp); + if (*errp) { + return; + } + + /* + * When we implement support for more than one interrupt domain, + * we will provide some QOM properties so the board can configure + * which domains are implemented. For now, we only implement the + * NS domain. + */ + cs->implemented_domains = (1 << GICV5_ID_NS); + gicv5_common_init_irqs_and_mmio(cs, config_frame_ops); +} + static void gicv5_init(Object *obj) { } @@ -XXX,XX +XXX,XX @@ static void gicv5_finalize(Object *obj) static void gicv5_class_init(ObjectClass *oc, const void *data) { ResettableClass *rc = RESETTABLE_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); GICv5Class *gc = ARM_GICV5_CLASS(oc); + device_class_set_parent_realize(dc, gicv5_realize, &gc->parent_realize); resettable_class_set_parent_phases(rc, NULL, gicv5_reset_hold, NULL, &gc->parent_phases); } diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ OBJECT_DEFINE_ABSTRACT_TYPE(GICv5Common, gicv5_common, ARM_GICV5_COMMON, SYS_BUS_DEVICE) +static bool bad_frame_accepts(void *opaque, hwaddr addr, unsigned size, + bool is_write, MemTxAttrs attrs) +{ + return false; +} + +/* + * Used for the sysbus MMIO regions corresponding to IRS frames where + * this IRS does not implement the interrupt domain. It's probably a + * board/SoC error to create an IRS and try to wire up this MMIO + * region, but if it does then the region will behave as unassigned + * memory (generating a decode error). These frames are just here so + * that changing which domains are implemented doesn't reorder which + * sysbus MMIO region is which. + */ +static const MemoryRegionOps bad_frame_ops = { + .valid.accepts = bad_frame_accepts, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +void gicv5_common_init_irqs_and_mmio(GICv5Common *cs, + const MemoryRegionOps config_ops[NUM_GICV5_DOMAINS]) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(cs); + + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) { + g_autofree char *memname = g_strdup_printf("gicv5-irs-%d", i); + const MemoryRegionOps *ops = gicv5_domain_implemented(cs, i) ? + &config_ops[i] : &bad_frame_ops; + memory_region_init_io(&cs->iomem[i], OBJECT(cs), ops, cs, + memname, IRS_CONFIG_FRAME_SIZE); + sysbus_init_mmio(sbd, &cs->iomem[i]); + } +} + static void gicv5_common_reset_hold(Object *obj, ResetType type) { } diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv3_its_vte_read(uint32_t vpeid, int valid, uint32_t vptsize, uint64_t vptaddr gicv3_its_vte_read_fault(uint32_t vpeid) "GICv3 ITS: vPE Table read for vPEID 0x%x: faulted" gicv3_its_vte_write(uint32_t vpeid, int valid, uint32_t vptsize, uint64_t vptaddr, uint32_t rdbase) "GICv3 ITS: vPE Table write for vPEID 0x%x: valid %d VPTsize 0x%x VPTaddr 0x%" PRIx64 " RDbase 0x%x" +# arm_gicv5.c +gicv5_read(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +gicv5_badread(const char *domain, uint64_t offset, unsigned size) "GICv5 IRS %s config frame read: offset 0x%" PRIx64 " size %u: error" +gicv5_write(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" +gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u: error" + # armv7m_nvic.c nvic_recompute_state(int vectpending, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d vectpending_prio %d exception_prio %d" nvic_recompute_state_secure(int vectpending, bool vectpending_is_s_banked, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d is_s_banked %d vectpending_prio %d exception_prio %d" diff --git a/include/hw/intc/arm_gicv5.h b/include/hw/intc/arm_gicv5.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5.h +++ b/include/hw/intc/arm_gicv5.h @@ -XXX,XX +XXX,XX @@ struct GICv5 { struct GICv5Class { GICv5CommonClass parent_class; + DeviceRealize parent_realize; ResettablePhases parent_phases; }; diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ #include "qom/object.h" #include "hw/core/sysbus.h" +#include "hw/intc/arm_gicv5_types.h" + +/* + * QEMU interface: + * + * sysbus MMIO regions (in order matching IRS_IDR0.INT_DOM encoding): + * - IRS config frame for the Secure Interrupt Domain + * - IRS config frame for the Non-secure Interrupt Domain + * - IRS config frame for the EL3 Interrupt Domain + * - IRS config frame for the Realm Interrupt Domain + * + * Note that even if this particular IRS does not implement all four + * interrupt domains it will still expose four sysbus MMIO regions. + * The regions corresponding to unimplemented domains will always fail + * accesses with a decode error. Generally the SoC/board should + * probably not map a region for a domain that it configured the IRS + * to not implement; the regions are only exposed so that changing + * which domains are implemented doesn't reorder which sysbus MMIO + * region is which (e.g. NS will always be 1 and EL3 will always be 2). + */ #define TYPE_ARM_GICV5_COMMON "arm-gicv5-common" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_TYPE(GICv5Common, GICv5CommonClass, ARM_GICV5_COMMON) */ struct GICv5Common { SysBusDevice parent_obj; + + MemoryRegion iomem[NUM_GICV5_DOMAINS]; + + /* Bits here are set for each physical interrupt domain implemented */ + uint8_t implemented_domains; }; struct GICv5CommonClass { SysBusDeviceClass parent_class; }; + +#define IRS_CONFIG_FRAME_SIZE 0x10000 + +/** + * gicv5_common_init_irqs_and_mmio: Create IRQs and MMIO regions for the GICv5 + * @s: GIC object + * @ops: array of MemoryRegionOps that implement the config frames behaviour + * + * Subclasses of ARM_GICV5_COMMON should call this to create the sysbus + * MemoryRegions for the IRS config frames, passing in a four element array + * of MemoryRegionOps structs. + */ +void gicv5_common_init_irqs_and_mmio(GICv5Common *cs, + const MemoryRegionOps ops[NUM_GICV5_DOMAINS]); + +/** + * gicv5_domain_implemented: Return true if this IRS implements this domain + * @s: GIC object + * @domain: domain to check + */ +static inline bool gicv5_domain_implemented(GICv5Common *cs, GICv5Domain domain) +{ + return cs->implemented_domains & (1 << domain); +} + #endif diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ +/* + * Type definitions for GICv5 + * + * This file is for type definitions that we want to share between + * the GIC proper and the CPU interface. + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INTC_ARM_GICv5_TYPES_H +#define HW_INTC_ARM_GICv5_TYPES_H + +/* + * The GICv5 has four physical Interrupt Domains. This numbering must + * match the encoding used in IRS_IDR0.INT_DOM. + */ +typedef enum GICv5Domain { + GICV5_ID_S = 0, + GICV5_ID_NS = 1, + GICV5_ID_EL3 = 2, + GICV5_ID_REALM = 3, +} GICv5Domain; + +#define NUM_GICV5_DOMAINS 4 + +#endif -- 2.43.0
This initial version of the GICv5 will not support migration: * the spec is still only at EAC level, so the data to be migrated might in theory change before it is finalised * when we add support for missing features like EL2/EL3/Realm we might find we want to refactor the data structures we use * I still need to check against the proposed KVM GICv5 handling to see if there are any awkward mismatches that might affect how we want to store the data * it's experimental, so for pragmatic reasons I'm skipping it to get the initial version done faster Install a migration blocker to enforce this. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-7-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ #include "qapi/error.h" #include "qemu/log.h" #include "trace.h" +#include "migration/blocker.h" OBJECT_DEFINE_TYPE(GICv5, gicv5, ARM_GICV5, ARM_GICV5_COMMON) @@ -XXX,XX +XXX,XX @@ static void gicv5_realize(DeviceState *dev, Error **errp) { GICv5Common *cs = ARM_GICV5_COMMON(dev); GICv5Class *gc = ARM_GICV5_GET_CLASS(dev); + Error *migration_blocker = NULL; ERRP_GUARD(); @@ -XXX,XX +XXX,XX @@ static void gicv5_realize(DeviceState *dev, Error **errp) return; } + error_setg(&migration_blocker, + "Live migration disabled: not yet supported by GICv5"); + if (migrate_add_blocker(&migration_blocker, errp)) { + return; + } + /* * When we implement support for more than one interrupt domain, * we will provide some QOM properties so the board can configure -- 2.43.0
Add code to the GICv5 skeleton which creates the QOM properties which the board or SoC can use to configure the GIC, and the validation code to check they are in range. Generally these correspond to fields in the IRS ID registers, and the properties are named correspondingly. Notable here is that unlike the GICv3 (which assumes its connected CPUs are the system's CPUs starting from 0), we define a QOM array property which is an array of pointers to the CPUs, and a QOM array property which is an array of integers telling the GIC what the IAFFID (interrupt affinity ID) for each CPU is; so a board or SoC which wants to connect multiple CPUs to this GICv5 would do something like: QList *cpulist = qlist_new(), *iaffidlist = qlist_new(); for (int i = 0; i < ms->smp.cpus; i++) { qlist_append_link(cpulist, OBJECT(qemu_get_cpu(i))); qlist_append_int(iaffidlist, i); } qdev_prop_set_array(vms->gic, "cpus", cpulist); qdev_prop_set_array(vms->gic, "cpu-iaffids", iaffidlist); Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-8-peter.maydell@linaro.org --- hw/intc/arm_gicv5_common.c | 80 ++++++++++++++++++++++++++++++ hw/intc/trace-events | 3 ++ include/hw/intc/arm_gicv5_common.h | 25 ++++++++++ 3 files changed, 108 insertions(+) diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "hw/intc/arm_gicv5_common.h" +#include "hw/core/qdev-properties.h" +#include "qapi/error.h" +#include "trace.h" OBJECT_DEFINE_ABSTRACT_TYPE(GICv5Common, gicv5_common, ARM_GICV5_COMMON, SYS_BUS_DEVICE) +/* Any value > 2^24 is out of the valid range for this property */ +#define GICV5_SPI_IRS_RANGE_NOT_SET 0xffffffff + static bool bad_frame_accepts(void *opaque, hwaddr addr, unsigned size, bool is_write, MemTxAttrs attrs) { @@ -XXX,XX +XXX,XX @@ static void gicv5_common_finalize(Object *obj) { } +static void gicv5_common_realize(DeviceState *dev, Error **errp) +{ + GICv5Common *cs = ARM_GICV5_COMMON(dev); + + if (cs->num_cpus == 0) { + error_setg(errp, "The cpus array property must have at least one CPU"); + return; + } + if (cs->num_cpus >= (1 << 16)) { + /* We'll hit other QEMU limits long before this one :-) */ + error_setg(errp, "Number of CPUs exceeds GICv5 architectural maximum"); + return; + } + if (cs->num_cpus != cs->num_cpu_iaffids) { + error_setg(errp, "The cpu-iaffids array property must be the same size " + "as the cpus array property"); + return; + } + if (cs->irsid >= (1 << 16)) { + error_setg(errp, "irsid (%u) is more than 2^16-1", cs->irsid); + return; + } + if (cs->spi_range > (1 << 24)) { + /* + * Note that IRS_IDR5.SPI_RANGE is a 25 bit field but the largest + * architecturally permitted value is 2^24 (not 2^25-1), hence + * use of > in the range check. + */ + error_setg(errp, "spi-range (%u) is more than 2^24", cs->spi_range); + return; + } + if (cs->spi_irs_range == GICV5_SPI_IRS_RANGE_NOT_SET) { + /* spi-irs-range defaults to same as spi-range */ + cs->spi_irs_range = cs->spi_range; + } + if (cs->spi_irs_range > (1 << 24)) { + /* Similarly IRS_IDR6.SPI_IRS_RANGE */ + error_setg(errp, "spi-irs-range (%u) is more than 2^24", + cs->spi_irs_range); + return; + } + if (cs->spi_base >= (1 << 24)) { + /* IRS_IDR7.SPI_BASE is a 24-bit field, so range check is >= */ + error_setg(errp, "spi-base (%u) is more than 2^24-1", cs->spi_base); + return; + } + /* range checks above mean we know this addition won't overflow */ + if (cs->spi_base + cs->spi_irs_range > cs->spi_range) { + error_setg(errp, "spi-base (%u) + spi-irs-range (%u) is " + "more than spi-range (%u)", + cs->spi_base, cs->spi_irs_range, cs->spi_range); + return; + } + + trace_gicv5_common_realize(cs->irsid, cs->num_cpus, + cs->spi_base, cs->spi_irs_range, cs->spi_range); +} + +static const Property arm_gicv5_common_properties[] = { + DEFINE_PROP_LINK_ARRAY("cpus", GICv5Common, num_cpus, + cpus, TYPE_ARM_CPU, ARMCPU *), + DEFINE_PROP_ARRAY("cpu-iaffids", GICv5Common, num_cpu_iaffids, + cpu_iaffids, qdev_prop_uint32, uint32_t), + DEFINE_PROP_UINT32("irsid", GICv5Common, irsid, 0), + DEFINE_PROP_UINT32("spi-range", GICv5Common, spi_range, 0), + DEFINE_PROP_UINT32("spi-base", GICv5Common, spi_base, 0), + DEFINE_PROP_UINT32("spi-irs-range", GICv5Common, spi_irs_range, + GICV5_SPI_IRS_RANGE_NOT_SET), +}; + static void gicv5_common_class_init(ObjectClass *oc, const void *data) { ResettableClass *rc = RESETTABLE_CLASS(oc); + DeviceClass *dc = DEVICE_CLASS(oc); rc->phases.hold = gicv5_common_reset_hold; + + dc->realize = gicv5_common_realize; + device_class_set_props(dc, arm_gicv5_common_properties); } diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_badread(const char *domain, uint64_t offset, unsigned size) "GICv5 IRS %s gicv5_write(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u: error" +# arm_gicv5_common.c +gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" + # armv7m_nvic.c nvic_recompute_state(int vectpending, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d vectpending_prio %d exception_prio %d" nvic_recompute_state_secure(int vectpending, bool vectpending_is_s_banked, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d is_s_banked %d vectpending_prio %d exception_prio %d" diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ #include "qom/object.h" #include "hw/core/sysbus.h" #include "hw/intc/arm_gicv5_types.h" +#include "target/arm/cpu-qom.h" /* * QEMU interface: * + * + QOM array property "cpus": CPUState pointers to each CPU + * connected to this IRS. + * + QOM array property "cpu-iaffids": array of uint32_t giving the + * IAFFID for each CPU in the "cpus" property array + * + QOM property "irsid": unique identifier for this IRS in the system + * (this is IRS_IDR0.IRSID); default is 0 + * + QOM property "spi-range": total number of SPIs in the system + * IRS (this is IRS_IDR5.SPI_RANGE); must be set + * + QOM property "spi-base": minimum SPI INTID.ID implemented on this + * IRS (this is IRS_IDR7.SPI_BASE); default is 0 + * + QOM property "spi-irs-range": number of SPI INTID.ID managed on this + * IRS (this is IRS_IDR6.SPI_IRS_RANGE); defaults to value of spi-range + * * sysbus MMIO regions (in order matching IRS_IDR0.INT_DOM encoding): * - IRS config frame for the Secure Interrupt Domain * - IRS config frame for the Non-secure Interrupt Domain @@ -XXX,XX +XXX,XX @@ struct GICv5Common { /* Bits here are set for each physical interrupt domain implemented */ uint8_t implemented_domains; + + /* Properties */ + uint32_t num_cpus; + ARMCPU **cpus; + uint32_t num_cpu_iaffids; + uint32_t *cpu_iaffids; + + uint32_t irsid; + uint32_t spi_base; + uint32_t spi_irs_range; + uint32_t spi_range; }; struct GICv5CommonClass { -- 2.43.0
The GICv5 IRS may have inbound GPIO lines corresponding to SPIs (shared peripheral interrupts). Unlike the GICv3, it does not deal with PPIs (private peripheral interrupts, i.e. per-CPU interrupts): in a GICv5 system those are handled entirely within the CPU interface. The inbound GPIO array is therefore a simple sequence of one GPIO per SPI that this IRS handles. Create the GPIO input array in gicv5_common_init_irqs_and_mmio(). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-9-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 11 ++++++++++- hw/intc/arm_gicv5_common.c | 5 +++++ hw/intc/trace-events | 1 + include/hw/intc/arm_gicv5_common.h | 4 ++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static const MemoryRegionOps config_frame_ops[NUM_GICV5_DOMAINS] = { FRAME_OP_ENTRY(el3, GICV5_ID_EL3), }; +static void gicv5_set_spi(void *opaque, int irq, int level) +{ + /* These irqs are all SPIs; the INTID is irq + s->spi_base */ + GICv5Common *cs = ARM_GICV5_COMMON(opaque); + uint32_t spi_id = irq + cs->spi_base; + + trace_gicv5_spi(spi_id, level); +} + static void gicv5_reset_hold(Object *obj, ResetType type) { GICv5 *s = ARM_GICV5(obj); @@ -XXX,XX +XXX,XX @@ static void gicv5_realize(DeviceState *dev, Error **errp) * NS domain. */ cs->implemented_domains = (1 << GICV5_ID_NS); - gicv5_common_init_irqs_and_mmio(cs, config_frame_ops); + gicv5_common_init_irqs_and_mmio(cs, gicv5_set_spi, config_frame_ops); } static void gicv5_init(Object *obj) diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static const MemoryRegionOps bad_frame_ops = { }; void gicv5_common_init_irqs_and_mmio(GICv5Common *cs, + qemu_irq_handler handler, const MemoryRegionOps config_ops[NUM_GICV5_DOMAINS]) { SysBusDevice *sbd = SYS_BUS_DEVICE(cs); + if (cs->spi_irs_range) { + qdev_init_gpio_in(DEVICE(cs), handler, cs->spi_irs_range); + } + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) { g_autofree char *memname = g_strdup_printf("gicv5-irs-%d", i); const MemoryRegionOps *ops = gicv5_domain_implemented(cs, i) ? diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_read(const char *domain, uint64_t offset, uint64_t data, unsigned size) "G gicv5_badread(const char *domain, uint64_t offset, unsigned size) "GICv5 IRS %s config frame read: offset 0x%" PRIx64 " size %u: error" gicv5_write(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u: error" +gicv5_spi(uint32_t id, int level) "GICv5 SPI ID %u asserted at level %d" # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ * IRS (this is IRS_IDR7.SPI_BASE); default is 0 * + QOM property "spi-irs-range": number of SPI INTID.ID managed on this * IRS (this is IRS_IDR6.SPI_IRS_RANGE); defaults to value of spi-range + * + unnamed GPIO inputs: the SPIs handled by this IRS + * (so GPIO input 0 is the SPI with INTID SPI_BASE, input 1 is + * SPI_BASE + 1, and so on up to SPI_BASE + SPI_IRS_RANGE - 1) * * sysbus MMIO regions (in order matching IRS_IDR0.INT_DOM encoding): * - IRS config frame for the Secure Interrupt Domain @@ -XXX,XX +XXX,XX @@ struct GICv5CommonClass { * of MemoryRegionOps structs. */ void gicv5_common_init_irqs_and_mmio(GICv5Common *cs, + qemu_irq_handler handler, const MemoryRegionOps ops[NUM_GICV5_DOMAINS]); /** -- 2.43.0
Define constants for the various registers in the IRS config frame using the REG and FIELD macros. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-10-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 243 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ */ #include "qemu/osdep.h" +#include "hw/core/registerfields.h" #include "hw/intc/arm_gicv5.h" #include "qapi/error.h" #include "qemu/log.h" @@ -XXX,XX +XXX,XX @@ static const char *domain_name[] = { [GICV5_ID_REALM] = "Realm", }; +REG32(IRS_IDR0, 0x0) + FIELD(IRS_IDR0, INT_DOM, 0, 2) + FIELD(IRS_IDR0, PA_RANGE, 2, 4) + FIELD(IRS_IDR0, VIRT, 6, 1) + FIELD(IRS_IDR0, ONE_N, 7, 1) + FIELD(IRS_IDR0, VIRT_ONE_N, 8, 1) + FIELD(IRS_IDR0, SETLPI, 9, 1) + FIELD(IRS_IDR0, MEC, 10, 1) + FIELD(IRS_IDR0, MPAM, 11, 1) + FIELD(IRS_IDR0, SWE, 12, 1) + FIELD(IRS_IDR0, IRSID, 16, 16) + +REG32(IRS_IDR1, 0x4) + FIELD(IRS_IDR1, PE_CNT, 0, 16) + FIELD(IRS_IDR1, IAFFID_BITS, 16, 4) + FIELD(IRS_IDR1, PRI_BITS, 20, 3) + +REG32(IRS_IDR2, 0x8) + FIELD(IRS_IDR2, ID_BITS, 0, 5) + FIELD(IRS_IDR2, LPI, 5, 1) + FIELD(IRS_IDR2, MIN_LPI_ID_BITS, 6, 4) + FIELD(IRS_IDR2, IST_LEVELS, 10, 1) + FIELD(IRS_IDR2, IST_L2SZ, 11, 3) + FIELD(IRS_IDR2, IST_MD, 14, 1) + FIELD(IRS_IDR2, ISTMD_SZ, 15, 5) + +REG32(IRS_IDR3, 0xc) + FIELD(IRS_IDR3, VMD, 0, 1) + FIELD(IRS_IDR3, VMD_SZ, 1, 4) + FIELD(IRS_IDR3, VM_ID_BITS, 5, 5) + FIELD(IRS_IDR3, VMT_LEVELS, 10, 1) + +REG32(IRS_IDR4, 0x10) + FIELD(IRS_IDR4, VPED_SZ, 0, 6) + FIELD(IRS_IDR4, VPE_ID_BITS, 6, 4) + +REG32(IRS_IDR5, 0x14) + FIELD(IRS_IDR5, SPI_RANGE, 0, 25) + +REG32(IRS_IDR6, 0x18) + FIELD(IRS_IDR6, SPI_IRS_RANGE, 0, 25) + +REG32(IRS_IDR7, 0x1c) + FIELD(IRS_IDR7, SPI_BASE, 0, 24) + +REG32(IRS_IIDR, 0x40) + FIELD(IRS_IIDR, IMPLEMENTER, 0, 12) + FIELD(IRS_IIDR, REVISION, 12, 4) + FIELD(IRS_IIDR, VARIANT, 16, 4) + FIELD(IRS_IIDR, PRODUCTID, 20, 12) + +REG32(IRS_AIDR, 0x44) + FIELD(IRS_AIDR, ARCHMINORREV, 0, 4) + FIELD(IRS_AIDR, ARCHMAJORREV, 4, 4) + FIELD(IRS_AIDR, COMPONENT, 8, 4) + +REG32(IRS_CR0, 0x80) + FIELD(IRS_CR0, IRSEN, 0, 1) + FIELD(IRS_CR0, IDLE, 1, 1) + +REG32(IRS_CR1, 0x84) + FIELD(IRS_CR1, SH, 0, 2) + FIELD(IRS_CR1, OC, 2, 2) + FIELD(IRS_CR1, IC, 4, 2) + FIELD(IRS_CR1, IST_RA, 6, 1) + FIELD(IRS_CR1, IST_WA, 7, 1) + FIELD(IRS_CR1, VMT_RA, 8, 1) + FIELD(IRS_CR1, VMT_WA, 9, 1) + FIELD(IRS_CR1, VPET_RA, 10, 1) + FIELD(IRS_CR1, VPET_WA, 11, 1) + FIELD(IRS_CR1, VMD_RA, 12, 1) + FIELD(IRS_CR1, VMD_WA, 13, 1) + FIELD(IRS_CR1, VPED_RA, 14, 1) + FIELD(IRS_CR1, VPED_WA, 15, 1) + +REG32(IRS_SYNCR, 0xc0) + FIELD(IRS_SYNCR, SYNC, 31, 1) + +REG32(IRS_SYNC_STATUSR, 0xc4) + FIELD(IRS_SYNC_STATUSR, IDLE, 0, 1) + +REG64(IRS_SPI_VMR, 0x100) + FIELD(IRS_SPI_VMR, VM_ID, 0, 16) + FIELD(IRS_SPI_VMR, VIRT, 63, 1) + +REG32(IRS_SPI_SELR, 0x108) + FIELD(IRS_SPI_SELR, ID, 0, 24) + +REG32(IRS_SPI_DOMAINR, 0x10c) + FIELD(IRS_SPI_DOMAINR, DOMAIN, 0, 2) + +REG32(IRS_SPI_RESAMPLER, 0x110) + FIELD(IRS_SPI_RESAMPLER, SPI_ID, 0, 24) + +REG32(IRS_SPI_CFGR, 0x114) + FIELD(IRS_SPI_CFGR, TM, 0, 1) + +REG32(IRS_SPI_STATUSR, 0x118) + FIELD(IRS_SPI_STATUSR, IDLE, 0, 1) + FIELD(IRS_SPI_STATUSR, V, 1, 1) + +REG32(IRS_PE_SELR, 0x140) + FIELD(IRS_PE_SELR, IAFFID, 0, 16) + +REG32(IRS_PE_STATUSR, 0x144) + FIELD(IRS_PE_STATUSR, IDLE, 0, 1) + FIELD(IRS_PE_STATUSR, V, 1, 1) + FIELD(IRS_PE_STATUSR, ONLINE, 2, 1) + +REG32(IRS_PE_CR0, 0x148) + FIELD(IRS_PE_CR0, DPS, 0, 1) + +REG64(IRS_IST_BASER, 0x180) + FIELD(IRS_IST_BASER, VALID, 0, 1) + FIELD(IRS_IST_BASER, ADDR, 6, 50) + +REG32(IRS_IST_CFGR, 0x190) + FIELD(IRS_IST_CFGR, LPI_ID_BITS, 0, 5) + FIELD(IRS_IST_CFGR, L2SZ, 5, 2) + FIELD(IRS_IST_CFGR, ISTSZ, 7, 2) + FIELD(IRS_IST_CFGR, STRUCTURE, 16, 1) + +REG32(IRS_IST_STATUSR, 0x194) + FIELD(IRS_IST_STATUSR, IDLE, 0, 1) + +REG32(IRS_MAP_L2_ISTR, 0x1c0) + FIELD(IRS_MAP_L2_ISTR, ID, 0, 24) + +REG64(IRS_VMT_BASER, 0x200) + FIELD(IRS_VMT_BASER, VALID, 0, 1) + FIELD(IRS_VMT_BASER, ADDR, 3, 53) + +REG32(IRS_VMT_CFGR, 0x210) + FIELD(IRS_VMT_CFGR, VM_ID_BITS, 0, 5) + FIELD(IRS_VMT_CFGR, STRUCTURE, 16, 1) + +REG32(IRS_VMT_STATUSR, 0x124) + FIELD(IRS_VMT_STATUSR, IDLE, 0, 1) + +REG64(IRS_VPE_SELR, 0x240) + FIELD(IRS_VPE_SELR, VM_ID, 0, 16) + FIELD(IRS_VPE_SELR, VPE_ID, 32, 16) + FIELD(IRS_VPE_SELR, S, 63, 1) + +REG64(IRS_VPE_DBR, 0x248) + FIELD(IRS_VPE_DBR, INTID, 0, 24) + FIELD(IRS_VPE_DBR, DBPM, 32, 5) + FIELD(IRS_VPE_DBR, REQ_DB, 62, 1) + FIELD(IRS_VPE_DBR, DBV, 63, 1) + +REG32(IRS_VPE_HPPIR, 0x250) + FIELD(IRS_VPE_HPPIR, ID, 0, 24) + FIELD(IRS_VPE_HPPIR, TYPE, 29, 3) + FIELD(IRS_VPE_HPPIR, HPPIV, 32, 1) + +REG32(IRS_VPE_CR0, 0x258) + FIELD(IRS_VPE_CR0, DPS, 0, 1) + +REG32(IRS_VPE_STATUSR, 0x25c) + FIELD(IRS_VPE_STATUSR, IDLE, 0, 1) + FIELD(IRS_VPE_STATUSR, V, 1, 1) + +REG64(IRS_VM_DBR, 0x280) + FIELD(IRS_VM_DBR, VPE_ID, 0, 16) + FIELD(IRS_VM_DBR, EN, 63, 1) + +REG32(IRS_VM_SELR, 0x288) + FIELD(IRS_VM_SELR, VM_ID, 0, 16) + +REG32(IRS_VM_STATUSR, 0x28c) + FIELD(IRS_VM_STATUSR, IDLE, 0, 1) + FIELD(IRS_VM_STATUSR, V, 1, 1) + +REG64(IRS_VMAP_L2_VMTR, 0x2c0) + FIELD(IRS_VMAP_L2_VMTR, VM_ID, 0, 16) + FIELD(IRS_VMAP_L2_VMTR, M, 63, 1) + +REG64(IRS_VMAP_VMR, 0x2c8) + FIELD(IRS_VMAP_VMR, VM_ID, 0, 16) + FIELD(IRS_VMAP_VMR, U, 62, 1) + FIELD(IRS_VMAP_VMR, M, 63, 1) + +REG64(IRS_VMAP_VISTR, 0x2d0) + FIELD(IRS_VMAP_VISTR, TYPE, 29, 3) + FIELD(IRS_VMAP_VISTR, VM_ID, 32, 16) + FIELD(IRS_VMAP_VISTR, U, 62, 1) + FIELD(IRS_VMAP_VISTR, M, 63, 1) + +REG64(IRS_VMAP_L2_VISTR, 0x2d8) + FIELD(IRS_VMAP_L2_VISTR, ID, 0, 24) + FIELD(IRS_VMAP_L2_VISTR, TYPE, 29, 3) + FIELD(IRS_VMAP_L2_VISTR, VM_ID, 32, 16) + FIELD(IRS_VMAP_L2_VISTR, M, 63, 1) + +REG64(IRS_VMAP_VPER, 0x2e0) + FIELD(IRS_VMAP_VPER, VPE_ID, 0, 16) + FIELD(IRS_VMAP_VPER, VM_ID, 32, 16) + FIELD(IRS_VMAP_VPER, M, 63, 1) + +REG64(IRS_SAVE_VMR, 0x300) + FIELD(IRS_SAVE_VMR, VM_ID, 0, 16) + FIELD(IRS_SAVE_VMR, Q, 62, 1) + FIELD(IRS_SAVE_VMR, S, 63, 1) + +REG32(IRS_SAVE_VM_STATUSR, 0x308) + FIELD(IRS_SAVE_VM_STATUSR, IDLE, 0, 1) + FIELD(IRS_SAVE_VM_STATUSR, Q, 1, 1) + +REG32(IRS_MEC_IDR, 0x340) + FIELD(IRS_MEC_IDR, MECIDSIZE, 0, 4) + +REG32(IRS_MEC_MECID_R, 0x344) + FIELD(IRS_MEC_MICID_R, MECID, 0, 16) + +REG32(IRS_MPAM_IDR, 0x380) + FIELD(IRS_MPAM_IDR, PARTID_MAX, 0, 16) + FIELD(IRS_MPAM_IDR, PMG_MAX, 16, 8) + FIELD(IRS_MPAM_IDR, HAS_MPAM_SP, 24, 1) + +REG32(IRS_MPAM_PARTID_R, 0x384) + FIELD(IRS_MPAM_IDR, PARTID, 0, 16) + FIELD(IRS_MPAM_IDR, PMG, 16, 8) + FIELD(IRS_MPAM_IDR, MPAM_SP, 24, 2) + FIELD(IRS_MPAM_IDR, IDLE, 31, 1) + +REG64(IRS_SWERR_STATUSR, 0x3c0) + FIELD(IRS_SWERR_STATUSR, V, 0, 1) + FIELD(IRS_SWERR_STATUSR, S0V, 1, 1) + FIELD(IRS_SWERR_STATUSR, S1V, 2, 1) + FIELD(IRS_SWERR_STATUSR, OF, 3, 1) + FIELD(IRS_SWERR_STATUSR, EC, 16, 8) + FIELD(IRS_SWERR_STATUSR, IMP_EC, 24, 8) + +REG64(IRS_SWERR_SYNDROMER0, 0x3c8) + FIELD(IRS_SWERR_SYNDROMER0, VM_ID, 0, 16) + FIELD(IRS_SWERR_SYNDROMER0, ID, 32, 24) + FIELD(IRS_SWERR_SYNDROMER0, TYPE, 60, 3) + FIELD(IRS_SWERR_SYNDROMER0, VIRTUAL, 63, 1) + +REG64(IRS_SWERR_SYNDROMER1, 0x3d0) + FIELD(IRS_SWERR_SYNDROMER2, ADDR, 3, 53) + static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t *data, MemTxAttrs attrs) { -- 2.43.0
Implement the IRS frame ID registers IRS_IDR[0-7], IRS_IIDR and IRS_AIDR. These are all 32-bit registers. We make these fields in the GIC state struct rather than just hardcoding them in the register read function so that we can later code "do this only if X is implemented" as a test on the ID register value. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-11-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 115 +++++++++++++++++++++++++++++ include/hw/intc/arm_gicv5_common.h | 39 ++++++++++ 2 files changed, 154 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ REG64(IRS_SWERR_SYNDROMER1, 0x3d0) static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t *data, MemTxAttrs attrs) { + GICv5Common *cs = ARM_GICV5_COMMON(s); + uint32_t v = 0; + + switch (offset) { + case A_IRS_IDR0: + v = cs->irs_idr0; + /* INT_DOM reports the domain this register is for */ + v = FIELD_DP32(v, IRS_IDR0, INT_DOM, domain); + if (domain != GICV5_ID_REALM) { + /* MEC field RES0 except for the Realm domain */ + v &= ~R_IRS_IDR0_MEC_MASK; + } + if (domain == GICV5_ID_EL3) { + /* VIRT is RES0 for EL3 domain */ + v &= ~R_IRS_IDR0_VIRT_MASK; + /* ...which means VIRT_ONE_N is also RES0 */ + v &= ~R_IRS_IDR0_VIRT_ONE_N_MASK; + } + return true; + + case A_IRS_IDR1: + *data = cs->irs_idr1; + return true; + + case A_IRS_IDR2: + *data = cs->irs_idr2; + return true; + + case A_IRS_IDR3: + /* In EL3 IDR0.VIRT is 0 so this is RES0 */ + *data = domain == GICV5_ID_EL3 ? 0 : cs->irs_idr3; + return true; + + case A_IRS_IDR4: + /* In EL3 IDR0.VIRT is 0 so this is RES0 */ + *data = domain == GICV5_ID_EL3 ? 0 : cs->irs_idr4; + return true; + + case A_IRS_IDR5: + *data = cs->irs_idr5; + return true; + + case A_IRS_IDR6: + *data = cs->irs_idr6; + return true; + + case A_IRS_IDR7: + *data = cs->irs_idr7; + return true; + + case A_IRS_IIDR: + *data = cs->irs_iidr; + return true; + + case A_IRS_AIDR: + *data = cs->irs_aidr; + return true; + } + return false; } @@ -XXX,XX +XXX,XX @@ static void gicv5_reset_hold(Object *obj, ResetType type) } } +static void gicv5_set_idregs(GICv5Common *cs) +{ + /* Set the ID register value fields */ + uint32_t v; + + /* + * Fields in IDR0 for optional parts of the spec that we don't + * implement are 0. + */ + v = 0; + /* + * We can handle physical addresses of any size, so report support + * for 56 bits of physical address space. + */ + v = FIELD_DP32(v, IRS_IDR0, PA_RANGE, 7); + v = FIELD_DP32(v, IRS_IDR0, IRSID, cs->irsid); + cs->irs_idr0 = v; + + v = 0; + v = FIELD_DP32(v, IRS_IDR1, PE_CNT, cs->num_cpus); + v = FIELD_DP32(v, IRS_IDR1, IAFFID_BITS, QEMU_GICV5_IAFFID_BITS - 1); + v = FIELD_DP32(v, IRS_IDR1, PRI_BITS, QEMU_GICV5_PRI_BITS - 1); + cs->irs_idr1 = v; + + v = 0; + /* We always support physical LPIs with 2-level ISTs of all sizes */ + v = FIELD_DP32(v, IRS_IDR2, ID_BITS, QEMU_GICV5_ID_BITS); + v = FIELD_DP32(v, IRS_IDR2, LPI, 1); + v = FIELD_DP32(v, IRS_IDR2, MIN_LPI_ID_BITS, QEMU_GICV5_MIN_LPI_ID_BITS); + v = FIELD_DP32(v, IRS_IDR2, IST_LEVELS, 1); + v = FIELD_DP32(v, IRS_IDR2, IST_L2SZ, 7); + /* Our impl does not need IST metadata, so ISTMD and ISTMD_SZ are 0 */ + cs->irs_idr2 = v; + + /* We don't implement virtualization yet, so these are zero */ + cs->irs_idr3 = 0; + cs->irs_idr4 = 0; + + /* These three have just one field each */ + cs->irs_idr5 = FIELD_DP32(0, IRS_IDR5, SPI_RANGE, cs->spi_range); + cs->irs_idr6 = FIELD_DP32(0, IRS_IDR6, SPI_IRS_RANGE, cs->spi_irs_range); + cs->irs_idr7 = FIELD_DP32(0, IRS_IDR7, SPI_BASE, cs->spi_base); + + v = 0; + v = FIELD_DP32(v, IRS_IIDR, IMPLEMENTER, QEMU_GICV5_IMPLEMENTER); + v = FIELD_DP32(v, IRS_IIDR, REVISION, QEMU_GICV5_REVISION); + v = FIELD_DP32(v, IRS_IIDR, VARIANT, QEMU_GICV5_VARIANT); + v = FIELD_DP32(v, IRS_IIDR, PRODUCTID, QEMU_GICV5_PRODUCTID); + cs->irs_iidr = v; + + /* This is a GICv5.0 IRS, so all fields are zero */ + cs->irs_aidr = 0; +} + static void gicv5_realize(DeviceState *dev, Error **errp) { GICv5Common *cs = ARM_GICV5_COMMON(dev); @@ -XXX,XX +XXX,XX @@ static void gicv5_realize(DeviceState *dev, Error **errp) * NS domain. */ cs->implemented_domains = (1 << GICV5_ID_NS); + + gicv5_set_idregs(cs); gicv5_common_init_irqs_and_mmio(cs, gicv5_set_spi, config_frame_ops); } diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ struct GICv5Common { /* Bits here are set for each physical interrupt domain implemented */ uint8_t implemented_domains; + /* ID register values: set at realize, constant thereafter */ + uint32_t irs_idr0; + uint32_t irs_idr1; + uint32_t irs_idr2; + uint32_t irs_idr3; + uint32_t irs_idr4; + uint32_t irs_idr5; + uint32_t irs_idr6; + uint32_t irs_idr7; + uint32_t irs_iidr; + uint32_t irs_aidr; + /* Properties */ uint32_t num_cpus; ARMCPU **cpus; @@ -XXX,XX +XXX,XX @@ struct GICv5CommonClass { #define IRS_CONFIG_FRAME_SIZE 0x10000 +/* + * The architecture allows a GICv5 to implement less than the full + * width for various ID fields. QEMU's implementation always supports + * the full width of these fields. These constants define our + * implementation's limits. + */ + +/* Number of INTID.ID bits we support */ +#define QEMU_GICV5_ID_BITS 24 +/* Min LPI_ID_BITS supported */ +#define QEMU_GICV5_MIN_LPI_ID_BITS 14 +/* IAFFID bits supported */ +#define QEMU_GICV5_IAFFID_BITS 16 +/* Number of priority bits supported in the IRS */ +#define QEMU_GICV5_PRI_BITS 5 + +/* + * There are no TRMs currently published for hardware implementations + * of GICv5 that we might identify ourselves as. Instead, we borrow + * the Arm Implementer code and pick a fake product ID that is + * unlikely to be used by any real Arm hardware. + */ +#define QEMU_GICV5_IMPLEMENTER 0x43b +#define QEMU_GICV5_PRODUCTID 0xfff +#define QEMU_GICV5_REVISION 0 +#define QEMU_GICV5_VARIANT 0 + /** * gicv5_common_init_irqs_and_mmio: Create IRQs and MMIO regions for the GICv5 * @s: GIC object -- 2.43.0
The GICv5 IRS keeps data structures in system memory. (Notably, it stores per-interrupt configuration information like the interrupt priority and its active and pending state in an in-memory data structure.) Add a link property so that the board or SoC can wire up a MemoryRegion that we will do DMA to. We name this property "sysmem" to match the GICv3's equivalent property. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-12-peter.maydell@linaro.org --- hw/intc/arm_gicv5_common.c | 8 ++++++++ include/hw/intc/arm_gicv5_common.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static void gicv5_common_realize(DeviceState *dev, Error **errp) cs->spi_base, cs->spi_irs_range, cs->spi_range); return; } + if (!cs->dma) { + error_setg(errp, "sysmem link property not set"); + return; + } + + address_space_init(&cs->dma_as, cs->dma, "gicv5-sysmem"); trace_gicv5_common_realize(cs->irsid, cs->num_cpus, cs->spi_base, cs->spi_irs_range, cs->spi_range); @@ -XXX,XX +XXX,XX @@ static const Property arm_gicv5_common_properties[] = { DEFINE_PROP_UINT32("spi-base", GICv5Common, spi_base, 0), DEFINE_PROP_UINT32("spi-irs-range", GICv5Common, spi_irs_range, GICV5_SPI_IRS_RANGE_NOT_SET), + DEFINE_PROP_LINK("sysmem", GICv5Common, dma, TYPE_MEMORY_REGION, + MemoryRegion *), }; static void gicv5_common_class_init(ObjectClass *oc, const void *data) diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ struct GICv5Common { uint32_t num_cpu_iaffids; uint32_t *cpu_iaffids; + /* MemoryRegion and AS to DMA to/from for in-memory data structures */ + MemoryRegion *dma; + AddressSpace dma_as; + uint32_t irsid; uint32_t spi_base; uint32_t spi_irs_range; -- 2.43.0
Implement a gicv5_class_name() function that does the same job as gicv3_class_name(): allows board code to get the correct QOM type for the GIC at runtime depending on whether KVM is enabled or not. For the GICv5, we don't yet implement KVM support, so the KVM-enabled codepath is always an error. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-13-peter.maydell@linaro.org --- include/hw/intc/arm_gicv5_common.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ #include "hw/core/sysbus.h" #include "hw/intc/arm_gicv5_types.h" #include "target/arm/cpu-qom.h" +#include "qemu/error-report.h" +#include "system/kvm.h" /* * QEMU interface: @@ -XXX,XX +XXX,XX @@ static inline bool gicv5_domain_implemented(GICv5Common *cs, GICv5Domain domain) return cs->implemented_domains & (1 << domain); } +/** + * gicv5_class_name + * + * Return name of GICv5 class to use depending on whether KVM acceleration is + * in use. May throw an error if the chosen implementation is not available. + * + * Returns: class name to use + */ +static inline const char *gicv5_class_name(void) +{ + /* When we implement KVM GICv5 we might return "kvm-arm-gicv5" here. */ + if (kvm_enabled()) { + error_report("Userspace GICv5 is not supported with KVM"); + exit(1); + } + return "arm-gicv5"; +} + #endif -- 2.43.0
The GICv5 defines architected numbers for the PPI sources like the generic timer and the PMU; these are different from the ones traditionally used by GICv2 and GICv3. Add defines for them. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-14-peter.maydell@linaro.org --- include/hw/intc/arm_gicv5_types.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ typedef enum GICv5Domain { #define NUM_GICV5_DOMAINS 4 +/* Architected GICv5 PPIs (as listed in R_XDVCM) */ +#define GICV5_PPI_S_DB_PPI 0 +#define GICV5_PPI_RL_DB_PPI 1 +#define GICV5_PPI_NS_DB_PPI 2 +#define GICV5_PPI_SW_PPI 3 +#define GICV5_PPI_HACDBSIRQ 15 +#define GICV5_PPI_CNTHVS 19 +#define GICV5_PPI_CNTHPS 20 +#define GICV5_PPI_PMBIRQ 21 +#define GICV5_PPI_COMMIRQ 22 +#define GICV5_PPI_PMUIRQ 23 +#define GICV5_PPI_CTIIRQ 24 +#define GICV5_PPI_GICMNT 25 +#define GICV5_PPI_CNTHP 26 +#define GICV5_PPI_CNTV 27 +#define GICV5_PPI_CNTHV 28 +#define GICV5_PPI_CNTPS 29 +#define GICV5_PPI_CNTP 30 +#define GICV5_PPI_TRBIRQ 31 + #endif -- 2.43.0
In the GICv5 architecture, part of the GIC is implemented inside the CPU: this is the CPU interface, which presents software with system instructions and system registers, and communicates with the external part of the GIC (the Interrupt Routing Service, IRS) via an architected stream interface where both sides can send commands and receive responses. Add the initial source files for the GICv5 CPU interface, with initial content implementing just the two GSB GIC barrier instructions, which are no-ops for QEMU. Since we will not initially implement virtualization or the "legacy GICv3" interface that can be provided to a VM guest, we don't have the ICH_VCTLR_EL2 register and do not need to implement an accessfn for the "trap if at EL1 and EL2 enabled and legacy GICv3 is enabled" handling. We will come back and add this later as part of the legacy-GICv3 code. (The GICv3 has a similar architecture with part of the GIC being in the CPU and part external; for QEMU we implemented the CPU interface in hw/intc/, but in retrospect I think this was something of a design mistake, and for GICv5 I am going to stick a bit closer to how the hardware architecture splits things up; hence this code is in target/arm.) Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-15-peter.maydell@linaro.org --- target/arm/cpu-features.h | 6 +++++ target/arm/helper.c | 1 + target/arm/internals.h | 3 +++ target/arm/tcg/gicv5-cpuif.c | 43 ++++++++++++++++++++++++++++++++++++ target/arm/tcg/meson.build | 1 + 5 files changed, 54 insertions(+) create mode 100644 target/arm/tcg/gicv5-cpuif.c diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu-features.h +++ b/target/arm/cpu-features.h @@ -XXX,XX +XXX,XX @@ FIELD(ID_AA64PFR1, PFAR, 60, 4) FIELD(ID_AA64PFR2, MTEPERM, 0, 4) FIELD(ID_AA64PFR2, MTESTOREONLY, 4, 4) FIELD(ID_AA64PFR2, MTEFAR, 8, 4) +FIELD(ID_AA64PFR2, GCIE, 12, 4) FIELD(ID_AA64PFR2, FPMR, 32, 4) FIELD(ID_AA64MMFR0, PARANGE, 0, 4) @@ -XXX,XX +XXX,XX @@ static inline bool isar_feature_aa64_gcs(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64PFR1, GCS) != 0; } +static inline bool isar_feature_aa64_gcie(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64PFR2, GCIE) != 0; +} + static inline bool isar_feature_aa64_tgran4_lpa2(const ARMISARegisters *id) { return FIELD_SEX64_IDREG(id, ID_AA64MMFR0, TGRAN4) >= 1; diff --git a/target/arm/helper.c b/target/arm/helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -XXX,XX +XXX,XX @@ void register_cp_regs_for_features(ARMCPU *cpu) if (tcg_enabled()) { define_tlb_insn_regs(cpu); define_at_insn_regs(cpu); + define_gicv5_cpuif_regs(cpu); } #endif diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ void define_gcs_cpregs(ARMCPU *cpu); /* Add the cpreg definitions for OMAP CP15 regs */ void define_omap_cp_regs(ARMCPU *cpu); +/* Add the cpreg definitions for the GICv5 CPU interface */ +void define_gicv5_cpuif_regs(ARMCPU *cpu); + /* Effective value of MDCR_EL2 */ static inline uint64_t arm_mdcr_el2_eff(CPUARMState *env) { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ +/* + * GICv5 CPU interface + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "internals.h" +#include "cpregs.h" + +static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { + /* + * Barrier: wait until the effects of a cpuif system register + * write have definitely made it to the IRS (and will thus show up + * in cpuif reads from the IRS by this or other CPUs and in the + * status of IRQ, FIQ etc). For QEMU we do all interaction with + * the IRS synchronously, so we can make this a nop. + */ + { .name = "GSB_SYS", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 0, .opc2 = 0, + .access = PL1_W, .type = ARM_CP_NOP, + }, + /* + * Barrier: wait until the effects of acknowledging an interrupt + * (via GICR CDIA or GICR CDNMIA) are visible, including the + * effect on the {IRQ,FIQ,vIRQ,vFIQ} pending state. This is a + * weaker version of GSB SYS. Again, for QEMU this is a nop. + */ + { .name = "GSB_ACK", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 0, .opc2 = 1, + .access = PL1_W, .type = ARM_CP_NOP, + }, +}; + +void define_gicv5_cpuif_regs(ARMCPU *cpu) +{ + if (cpu_isar_feature(aa64_gcie, cpu)) { + define_arm_cp_regs(cpu, gicv5_cpuif_reginfo); + } +} diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -XXX,XX +XXX,XX @@ arm_common_system_ss.add( files( 'cpregs-at.c', 'debug.c', + 'gicv5-cpuif.c', 'hflags.c', 'gengvec.c', 'm_helper.c', -- 2.43.0
The qdev link property array gives the IRS a pointer to each CPU that is connected to it, but the CPU also needs a pointer to the IRS so that it can issue commands. Set this up in a similar way to how we do it for the GICv3: have the GIC's realize function call gicv5_set_gicv5state() to set a pointer in the CPUARMState. The CPU will only allow this link to be made if it actually implements the GICv5 CPU interface; it will be the responsibility of the board code to configure the CPU to have a GICv5 cpuif if it wants to connect a GICv5 to it. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-16-peter.maydell@linaro.org --- hw/intc/arm_gicv5_common.c | 9 +++++++++ include/hw/intc/arm_gicv5_stream.h | 32 ++++++++++++++++++++++++++++++ target/arm/cpu.c | 16 +++++++++++++++ target/arm/cpu.h | 2 ++ 4 files changed, 59 insertions(+) create mode 100644 include/hw/intc/arm_gicv5_stream.h diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "hw/intc/arm_gicv5_common.h" +#include "hw/intc/arm_gicv5_stream.h" #include "hw/core/qdev-properties.h" #include "qapi/error.h" #include "trace.h" @@ -XXX,XX +XXX,XX @@ static void gicv5_common_realize(DeviceState *dev, Error **errp) return; } + for (int i = 0; i < cs->num_cpus; i++) { + if (!gicv5_set_gicv5state(cs->cpus[i], cs)) { + error_setg(errp, + "CPU %d does not implement GICv5 CPU interface", i); + return; + } + } + address_space_init(&cs->dma_as, cs->dma, "gicv5-sysmem"); trace_gicv5_common_realize(cs->irsid, cs->num_cpus, diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ +/* + * Interface between GICv5 CPU interface and GICv5 IRS + * Loosely modelled on the GICv5 Stream Protocol interface documented + * in the GICv5 specification. + * + * Copyright (c) 2025 Linaro Limited + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_INTC_ARM_GICV5_STREAM_H +#define HW_INTC_ARM_GICV5_STREAM_H + +#include "target/arm/cpu-qom.h" + +typedef struct GICv5Common GICv5Common; + +/** + * gicv5_set_gicv5state + * @cpu: CPU object to tell about its IRS + * @cs: the GIC IRS it is connected to + * + * Set the CPU object's GICv5 pointer to point to this GIC IRS. The + * IRS must call this when it is realized, for each CPU it is + * connected to. + * + * Returns true on success, false if the CPU doesn't implement the + * GICv5 CPU interface. + */ +bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs); + +#endif diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ #if !defined(CONFIG_USER_ONLY) #include "hw/core/loader.h" #include "hw/core/boards.h" +#include "hw/intc/arm_gicv5_stream.h" #ifdef CONFIG_TCG #include "hw/intc/armv7m_nvic.h" #endif /* CONFIG_TCG */ @@ -XXX,XX +XXX,XX @@ static void arm_cpu_dump_state(CPUState *cs, FILE *f, int flags) } } +#ifndef CONFIG_USER_ONLY +bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs) +{ + /* + * Set this CPU's gicv5state pointer to point to the GIC that we are + * connected to. + */ + if (!cpu_isar_feature(aa64_gcie, cpu)) { + return false; + } + cpu->env.gicv5state = cs; + return true; +} +#endif + uint64_t arm_build_mp_affinity(int idx, uint8_t clustersz) { uint32_t Aff1 = idx / clustersz; diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { const struct arm_boot_info *boot_info; /* Store GICv3CPUState to access from this struct */ void *gicv3state; + /* Similarly, for a GICv5Common */ + void *gicv5state; #else /* CONFIG_USER_ONLY */ /* For usermode syscall translation. */ bool eabi; -- 2.43.0
Implement the three registers that handle configuration of the interrupt status table for physical LPIs: * IRS_IST_BASER holds the base address of the table, and has the VALID bit that tells the IRS to start using the config * IRS_IST_CFGR has all the other config data for the table * IRS_IST_STATUSR has the IDLE bit that tells software when updates to IRS_IST_BASER have taken effect Implement these registers. Note that neither BASER nor CFGR can be written when VALID == 1, except to clear the VALID bit. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-17-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 74 ++++++++++++++++++++++++++++++ hw/intc/arm_gicv5_common.c | 4 ++ include/hw/intc/arm_gicv5_common.h | 3 ++ 3 files changed, 81 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ REG64(IRS_SWERR_SYNDROMER0, 0x3c8) REG64(IRS_SWERR_SYNDROMER1, 0x3d0) FIELD(IRS_SWERR_SYNDROMER2, ADDR, 3, 53) +static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) +{ + GICv5Common *cs = ARM_GICV5_COMMON(s); + + if (FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID)) { + /* If VALID is set, ADDR is RO and we can only update VALID */ + bool valid = FIELD_EX64(value, IRS_IST_BASER, VALID); + if (valid) { + /* Ignore 1->1 transition */ + return; + } + cs->irs_ist_baser[domain] = FIELD_DP64(cs->irs_ist_baser[domain], + IRS_IST_BASER, VALID, valid); + return; + } + cs->irs_ist_baser[domain] = value; +} + static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t *data, MemTxAttrs attrs) { @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_AIDR: *data = cs->irs_aidr; return true; + + case A_IRS_IST_BASER: + *data = extract64(cs->irs_ist_baser[domain], 0, 32); + return true; + + case A_IRS_IST_BASER + 4: + *data = extract64(cs->irs_ist_baser[domain], 32, 32); + return true; + + case A_IRS_IST_STATUSR: + /* + * For QEMU writes to IRS_IST_BASER and IRS_MAP_L2_ISTR take effect + * instantaneously, and the guest can never see the IDLE bit as 0. + */ + *data = R_IRS_IST_STATUSR_IDLE_MASK; + return true; + + case A_IRS_IST_CFGR: + *data = cs->irs_ist_cfgr[domain]; + return true; } return false; @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t data, MemTxAttrs attrs) { + GICv5Common *cs = ARM_GICV5_COMMON(s); + + switch (offset) { + case A_IRS_IST_BASER: + irs_ist_baser_write(s, domain, + deposit64(cs->irs_ist_baser[domain], 0, 32, data)); + return true; + case A_IRS_IST_BASER + 4: + irs_ist_baser_write(s, domain, + deposit64(cs->irs_ist_baser[domain], 32, 32, data)); + return true; + case A_IRS_IST_CFGR: + if (FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID)) { + qemu_log_mask(LOG_GUEST_ERROR, + "guest tried to write IRS_IST_CFGR for %s config frame " + "while IST_BASER.VALID set\n", domain_name[domain]); + } else { + cs->irs_ist_cfgr[domain] = data; + } + return true; + } + return false; } static bool config_readll(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t *data, MemTxAttrs attrs) { + GICv5Common *cs = ARM_GICV5_COMMON(s); + + switch (offset) { + case A_IRS_IST_BASER: + *data = cs->irs_ist_baser[domain]; + return true; + } + return false; } static bool config_writell(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t data, MemTxAttrs attrs) { + switch (offset) { + case A_IRS_IST_BASER: + irs_ist_baser_write(s, domain, data); + return true; + } + return false; } diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ void gicv5_common_init_irqs_and_mmio(GICv5Common *cs, static void gicv5_common_reset_hold(Object *obj, ResetType type) { + GICv5Common *cs = ARM_GICV5_COMMON(obj); + + memset(cs->irs_ist_baser, 0, sizeof(cs->irs_ist_baser)); + memset(cs->irs_ist_cfgr, 0, sizeof(cs->irs_ist_cfgr)); } static void gicv5_common_init(Object *obj) diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ struct GICv5Common { MemoryRegion iomem[NUM_GICV5_DOMAINS]; + uint64_t irs_ist_baser[NUM_GICV5_DOMAINS]; + uint32_t irs_ist_cfgr[NUM_GICV5_DOMAINS]; + /* Bits here are set for each physical interrupt domain implemented */ uint8_t implemented_domains; -- 2.43.0
The IRS has multiple ISTs, for different contexts: * physical LPIs (separately for each interrupt domain) * virtual LPIs * virtual SPIs The config information for physical LPIs is in the IRS_IST_BASER and IRS_IST_CFGR registers; for virtual LPIs and virtual SPIs it will be in the L2_VMTE VM table entry. We would like to be able to write generic code that can manipulate any of these ISTs. Define a struct which captures the config information for an IST, and cache the IRS_IST_CFGR/IRS_IST_BASER data into this format when the guest sets the VALID bit. This also allows us to enforce the correct handling of reserved and out-of-range values, and expand the encodings of sizes into a more convenient format for later use. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-18-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 64 +++++++++++++++++++++++++++++++++++++ hw/intc/trace-events | 2 ++ include/hw/intc/arm_gicv5.h | 12 +++++++ 3 files changed, 78 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) } cs->irs_ist_baser[domain] = FIELD_DP64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID, valid); + s->phys_lpi_config[domain].valid = false; + trace_gicv5_ist_invalid(domain_name[domain]); return; } cs->irs_ist_baser[domain] = value; + + if (FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID)) { + /* + * If the guest just set VALID then capture data into config struct, + * sanitize the reserved values, and expand fields out into byte counts. + */ + GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + uint8_t istbits, l2bits, l2_idx_bits; + uint8_t id_bits = FIELD_EX64(cs->irs_ist_cfgr[domain], + IRS_IST_CFGR, LPI_ID_BITS); + id_bits = MIN(MAX(id_bits, QEMU_GICV5_MIN_LPI_ID_BITS), QEMU_GICV5_ID_BITS); + + switch (FIELD_EX64(cs->irs_ist_cfgr[domain], IRS_IST_CFGR, ISTSZ)) { + case 0: + case 3: /* reserved: acts like the minimum required size */ + istbits = 2; + break; + case 1: + istbits = 3; + break; + case 2: + istbits = 4; + break; + default: + g_assert_not_reached(); + } + switch (FIELD_EX64(cs->irs_ist_cfgr[domain], IRS_IST_CFGR, L2SZ)) { + case 0: + case 3: /* reserved; CONSTRAINED UNPREDICTABLE */ + l2bits = 12; /* 4K: 12 bits */ + break; + case 1: + l2bits = 14; /* 16K: 14 bits */ + break; + case 2: + l2bits = 16; /* 64K: 16 bits */ + break; + default: + g_assert_not_reached(); + } + /* + * Calculate how many bits of an ID index the L2 table + * (e.g. if we need 14 bits to index each byte in a 16K L2 table, + * but each entry is 4 bytes wide then we need 14 - 2 = 12 bits + * to index an entry in the table). + */ + l2_idx_bits = l2bits - istbits; + cfg->base = cs->irs_ist_baser[domain] & R_IRS_IST_BASER_ADDR_MASK; + cfg->id_bits = id_bits; + cfg->istsz = 1 << istbits; + cfg->l2_idx_bits = l2_idx_bits; + cfg->structure = FIELD_EX64(cs->irs_ist_cfgr[domain], + IRS_IST_CFGR, STRUCTURE); + cfg->valid = true; + trace_gicv5_ist_valid(domain_name[domain], cfg->base, cfg->id_bits, + cfg->l2_idx_bits, cfg->istsz, cfg->structure); + } } static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, @@ -XXX,XX +XXX,XX @@ static void gicv5_reset_hold(Object *obj, ResetType type) if (c->parent_phases.hold) { c->parent_phases.hold(obj, type); } + + /* IRS_IST_BASER and IRS_IST_CFGR reset to 0, clear cached info */ + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) { + s->phys_lpi_config[i].valid = false; + } } static void gicv5_set_idregs(GICv5Common *cs) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_badread(const char *domain, uint64_t offset, unsigned size) "GICv5 IRS %s gicv5_write(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u: error" gicv5_spi(uint32_t id, int level) "GICv5 SPI ID %u asserted at level %d" +gicv5_ist_valid(const char *domain, uint64_t base, uint8_t id_bits, uint8_t l2_idx_bits, uint8_t istsz, bool structure) "GICv5 IRS %s IST now valid: base 0x%" PRIx64 " id_bits %u l2_idx_bits %u IST entry size %u 2-level %d" +gicv5_ist_invalid(const char *domain) "GICv5 IRS %s IST no longer valid" # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" diff --git a/include/hw/intc/arm_gicv5.h b/include/hw/intc/arm_gicv5.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5.h +++ b/include/hw/intc/arm_gicv5.h @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_TYPE(GICv5, GICv5Class, ARM_GICV5) +typedef struct GICv5ISTConfig { + hwaddr base; /* Base address */ + uint8_t id_bits; /* number of bits in an ID for this table */ + uint8_t l2_idx_bits; /* number of ID bits that index into L2 table */ + uint8_t istsz; /* L2 ISTE size in bytes */ + bool structure; /* true if using 2-level table */ + bool valid; /* true if this table is valid and usable */ +} GICv5ISTConfig; + /* * This class is for TCG-specific state for the GICv5. */ struct GICv5 { GICv5Common parent_obj; + + /* This is the info from IRS_IST_BASER and IRS_IST_CFGR */ + GICv5ISTConfig phys_lpi_config[NUM_GICV5_DOMAINS]; }; struct GICv5Class { -- 2.43.0
Implement the gicv5_set_priority() function, which is our equivalent of the Stream Protocol SetPriority command. This acts by looking the interrupt ID up in the Interrupt State Table and storing the new priority value into the table entry. The memory transaction has to have the right transaction attributes for the domain it is for; we precalculate these and keep them in the GICv5ISTConfig. The GIC has an optional software-error reporting mechanism via the IRS_SWERR_* registers; this does not report all failure cases, only those that would be annoying to detect and debug in some other way. We choose not to implement this, but include some comments for reportable error cases for future reference. Our LOG_GUEST_ERROR logging is a superset of this. At this point we implement only handling of SetPriority for LPIs; we will add SPI handling in a later commit. Virtual interrupts aren't supported by this initial EL1-only GICv5 implementation. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-19-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 233 +++++++++++++++++++++++++++++ hw/intc/trace-events | 1 + include/hw/intc/arm_gicv5.h | 1 + include/hw/intc/arm_gicv5_stream.h | 29 ++++ include/hw/intc/arm_gicv5_types.h | 10 ++ 5 files changed, 274 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "hw/core/registerfields.h" #include "hw/intc/arm_gicv5.h" +#include "hw/intc/arm_gicv5_stream.h" #include "qapi/error.h" #include "qemu/log.h" #include "trace.h" @@ -XXX,XX +XXX,XX @@ static const char *domain_name[] = { [GICV5_ID_REALM] = "Realm", }; +static const char *inttype_name(GICv5IntType t) +{ + /* + * We have to be more cautious with getting human readable names + * for a GICv5IntType for trace strings than we do with the domain + * enum, because here the value can come from a guest register + * field. + */ + static const char *names[] = { + [GICV5_PPI] = "PPI", + [GICV5_LPI] = "LPI", + [GICV5_SPI] = "SPI", + }; + if (t >= ARRAY_SIZE(names) || !names[t]) { + return "RESERVED"; + } + return names[t]; +} + REG32(IRS_IDR0, 0x0) FIELD(IRS_IDR0, INT_DOM, 0, 2) FIELD(IRS_IDR0, PA_RANGE, 2, 4) @@ -XXX,XX +XXX,XX @@ REG64(IRS_SWERR_SYNDROMER0, 0x3c8) REG64(IRS_SWERR_SYNDROMER1, 0x3d0) FIELD(IRS_SWERR_SYNDROMER2, ADDR, 3, 53) +FIELD(L1_ISTE, VALID, 0, 1) +FIELD(L1_ISTE, L2_ADDR, 12, 44) + +FIELD(L2_ISTE, PENDING, 0, 1) +FIELD(L2_ISTE, ACTIVE, 1, 1) +FIELD(L2_ISTE, HM, 2, 1) +FIELD(L2_ISTE, ENABLE, 3, 1) +FIELD(L2_ISTE, IRM, 4, 1) +FIELD(L2_ISTE, HWU, 9, 2) +FIELD(L2_ISTE, PRIORITY, 11, 5) +FIELD(L2_ISTE, IAFFID, 16, 16) + +static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain) +{ + /* + * Return a MemTxAttrs to use for IRS memory accesses. IRS_CR1 + * has the usual Arm cacheability/shareability attributes, but + * QEMU doesn't care about those. All we need to specify here is + * the correct security attributes, which depend on the interrupt + * domain. Conveniently, our GICv5Domain encoding matches the + * ARMSecuritySpace one (because both follow an architecturally + * specified field). The exception is that the EL3 domain must be + * Secure instead of Root if we don't implement Realm. + */ + if (domain == GICV5_ID_EL3 && + !gicv5_domain_implemented(cs, GICV5_ID_REALM)) { + domain = GICV5_ID_S; + } + return (MemTxAttrs) { + .space = domain, + .secure = domain == GICV5_ID_S || domain == GICV5_ID_EL3, + }; +} + +static hwaddr l1_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg, + uint32_t id) +{ + /* + * In a 2-level IST configuration, return the address of the L1 + * IST entry for this interrupt ID. The bottom l2_idx_bits of the + * ID value are the index into the L2 table, and the higher bits + * of the ID index the L1 table. + */ + uint32_t l1_index = id >> cfg->l2_idx_bits; + return cfg->base + (l1_index * 8); +} + +static bool get_l2_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg, + uint32_t id, hwaddr *l2_iste_addr) +{ + /* + * Get the address of the L2 interrupt state table entry for this + * interrupt. On success, fill in l2_iste_addr and return true. + * On failure, return false. + */ + hwaddr l2_base; + + if (!cfg->valid) { + return false; + } + + if (id >= (1 << cfg->id_bits)) { + return false; + } + + if (cfg->structure) { + /* + * 2-level table: read the L1 IST. The bottom l2_idx_bits of + * the ID value are the index into the L2 table, and the + * higher bits of the ID index the L1 table. There is always + * at least one L1 table entry. + */ + hwaddr l1_addr = l1_iste_addr(cs, cfg, id); + uint64_t l1_iste; + MemTxResult res; + + l1_iste = address_space_ldq_le(&cs->dma_as, l1_addr, + cfg->txattrs, &res); + if (res != MEMTX_OK) { + /* Reportable with EC=0x01 if sw error reporting implemented */ + qemu_log_mask(LOG_GUEST_ERROR, "L1 ISTE lookup failed for ID 0x%x" + " at physical address 0x" HWADDR_FMT_plx "\n", + id, l1_addr); + return false; + } + if (!FIELD_EX64(l1_iste, L1_ISTE, VALID)) { + return false; + } + l2_base = l1_iste & R_L1_ISTE_L2_ADDR_MASK; + id = extract32(id, 0, cfg->l2_idx_bits); + } else { + /* 1-level table */ + l2_base = cfg->base; + } + + *l2_iste_addr = l2_base + (id * cfg->istsz); + return true; +} + +static bool read_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg, + hwaddr addr, uint32_t *l2_iste) +{ + MemTxResult res; + + *l2_iste = address_space_ldl_le(&cs->dma_as, addr, cfg->txattrs, &res); + if (res != MEMTX_OK) { + /* Reportable with EC=0x02 if sw error reporting implemented */ + qemu_log_mask(LOG_GUEST_ERROR, "L2 ISTE read failed at physical " + "address 0x" HWADDR_FMT_plx "\n", addr); + } + return res == MEMTX_OK; +} + +static bool write_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg, + hwaddr addr, uint32_t l2_iste) +{ + MemTxResult res; + + address_space_stl_le(&cs->dma_as, addr, l2_iste, cfg->txattrs, &res); + if (res != MEMTX_OK) { + /* Reportable with EC=0x02 if sw error reporting implemented */ + qemu_log_mask(LOG_GUEST_ERROR, "L2 ISTE write failed at physical " + "address 0x" HWADDR_FMT_plx "\n", addr); + } + return res == MEMTX_OK; +} + +/* + * This is returned by get_l2_iste() and has everything we need to do + * the writeback of the L2 ISTE word in put_l2_iste(). Currently the + * get/put functions always directly do guest memory reads and writes + * to update the L2 ISTE. In a future commit we will add support for a + * cache of some of the ISTE data in a local hashtable; the APIs are + * designed with that in mind. + */ +typedef struct L2_ISTE_Handle { + hwaddr l2_iste_addr; + uint32_t l2_iste; +} L2_ISTE_Handle; + +static uint32_t *get_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg, + uint32_t id, L2_ISTE_Handle *h) +{ + /* + * Find the L2 ISTE for the interrupt @id. + * + * We return a pointer to the ISTE: the caller can freely read and + * modify the uint64_t pointed to to update the ISTE. If the + * caller modifies the L2 ISTE word, it must call put_l2_iste(), + * passing it @h, to write back the ISTE. If the caller is only + * reading the L2 ISTE, it does not need to call put_l2_iste(). + * + * We fill in @h with information needed for put_l2_iste(). + * + * If the ISTE could not be read (typically because of a memory + * error), return NULL. + */ + if (!get_l2_iste_addr(cs, cfg, id, &h->l2_iste_addr) || + !read_l2_iste_mem(cs, cfg, h->l2_iste_addr, &h->l2_iste)) { + return NULL; + } + return &h->l2_iste; +} + +static void put_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg, + L2_ISTE_Handle *h) +{ + /* + * Write back the modified L2_ISTE word found with get_l2_iste(). + * Once this has been called the L2_ISTE_Handle @h and the pointer + * to the L2 ISTE word are no longer valid. + */ + write_l2_iste_mem(cs, cfg, h->l2_iste_addr, h->l2_iste); +} + +void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, + GICv5Domain domain, GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + + trace_gicv5_set_priority(domain_name[domain], inttype_name(type), virtual, + id, priority); + /* We must ignore unimplemented low-order priority bits */ + priority &= MAKE_64BIT_MASK(5 - QEMU_GICV5_PRI_BITS, QEMU_GICV5_PRI_BITS); + + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set " + "priority of a virtual interrupt\n"); + return; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PRIORITY, priority); + put_l2_iste(cs, cfg, &h); + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set " + "priority of bad interrupt type %d\n", type); + return; + } +} + static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) */ l2_idx_bits = l2bits - istbits; cfg->base = cs->irs_ist_baser[domain] & R_IRS_IST_BASER_ADDR_MASK; + cfg->txattrs = irs_txattrs(cs, domain), cfg->id_bits = id_bits; cfg->istsz = 1 << istbits; cfg->l2_idx_bits = l2_idx_bits; diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size gicv5_spi(uint32_t id, int level) "GICv5 SPI ID %u asserted at level %d" gicv5_ist_valid(const char *domain, uint64_t base, uint8_t id_bits, uint8_t l2_idx_bits, uint8_t istsz, bool structure) "GICv5 IRS %s IST now valid: base 0x%" PRIx64 " id_bits %u l2_idx_bits %u IST entry size %u 2-level %d" gicv5_ist_invalid(const char *domain) "GICv5 IRS %s IST no longer valid" +gicv5_set_priority(const char *domain, const char *type, bool virtual, uint32_t id, uint8_t priority) "GICv5 IRS SetPriority %s %s virtual:%d ID %u prio %u" # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" diff --git a/include/hw/intc/arm_gicv5.h b/include/hw/intc/arm_gicv5.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5.h +++ b/include/hw/intc/arm_gicv5.h @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_TYPE(GICv5, GICv5Class, ARM_GICV5) typedef struct GICv5ISTConfig { hwaddr base; /* Base address */ + MemTxAttrs txattrs; /* TX attrs to use for this table */ uint8_t id_bits; /* number of bits in an ID for this table */ uint8_t l2_idx_bits; /* number of ID bits that index into L2 table */ uint8_t istsz; /* L2 ISTE size in bytes */ diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ #define HW_INTC_ARM_GICV5_STREAM_H #include "target/arm/cpu-qom.h" +#include "hw/intc/arm_gicv5_types.h" typedef struct GICv5Common GICv5Common; @@ -XXX,XX +XXX,XX @@ typedef struct GICv5Common GICv5Common; */ bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs); +/* + * The architected Stream Protocol is asynchronous; commands can be + * initiated both from the IRS and from the CPU interface, and some + * require acknowledgement. For QEMU, we simplify this because we know + * that in the CPU interface code we hold the BQL and so our IRS model + * is not going to be busy; when we send commands from the CPUIF + * ("upstream commands") we can model this as a synchronous function + * call whose return corresponds to the acknowledgement of a completed + * command. + */ + +/** + * gicv5_set_priority + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @priority: priority to set + * @domain: interrupt Domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Set priority of an interrupt; matches stream interface SetPriority + * command from CPUIF to IRS. There is no report back of + * success/failure to the CPUIF in the protocol. + */ +void gicv5_set_priority(GICv5Common *cs, uint32_t id, + uint8_t priority, GICv5Domain domain, + GICv5IntType type, bool virtual); + #endif diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ typedef enum GICv5Domain { #define GICV5_PPI_CNTP 30 #define GICV5_PPI_TRBIRQ 31 +/* + * Type of the interrupt; these values match the 3-bit format + * specified in the GICv5 spec R_GYVWB. + */ +typedef enum GICv5IntType { + GICV5_PPI = 1, + GICV5_LPI = 2, + GICV5_SPI = 3, +} GICv5IntType; + #endif -- 2.43.0
Implement the CPU interface GIC CDPRI instruction, which is a wrapper around the SetPriority operation. As with the barrier insns, we omit for the moment details which are needed when the GICv5 supports virtualization: * traps when legacy GICv3 emulation is enabled * fine-grained-trap handling (which is done via registers that are new in GICv5) * sending the command for the virtual interrupt domain when inside a guest The CD instructions operate on the Current Physical Interrupt Domain, which is the one associated with the current security state and exception level. The spec also has the concept of a Logical Interrupt Domain, which is the one associated with the security state defined by SCR_EL3.{NS,NSE}. Mostly the logical interrupt domain is used by the LD instructions, which are EL3-only; but we will also want the concept later for handling some banked registers, so we define functions for both. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-20-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "internals.h" #include "cpregs.h" +#include "hw/intc/arm_gicv5_stream.h" + +FIELD(GIC_CDPRI, ID, 0, 24) +FIELD(GIC_CDPRI, TYPE, 29, 3) +FIELD(GIC_CDPRI, PRIORITY, 35, 5) + +static GICv5Common *gicv5_get_gic(CPUARMState *env) +{ + return env->gicv5state; +} + +static GICv5Domain gicv5_logical_domain(CPUARMState *env) +{ + /* + * Return the Logical Interrupt Domain, which is the one associated + * with the security state selected by the SCR_EL3.{NS,NSE} bits + */ + switch (arm_security_space_below_el3(env)) { + case ARMSS_Secure: + return GICV5_ID_S; + case ARMSS_NonSecure: + return GICV5_ID_NS; + case ARMSS_Realm: + return GICV5_ID_REALM; + default: + g_assert_not_reached(); + } +} + +static GICv5Domain gicv5_current_phys_domain(CPUARMState *env) +{ + /* + * Return the Current Physical Interrupt Domain as + * defined by R_ZFCXM. + */ + if (arm_current_el(env) == 3) { + return GICV5_ID_EL3; + } + return gicv5_logical_domain(env); +} + +static void gic_cdpri_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + uint8_t priority = FIELD_EX64(value, GIC_CDPRI, PRIORITY); + GICv5IntType type = FIELD_EX64(value, GIC_CDPRI, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDPRI, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + gicv5_set_priority(gic, id, priority, domain, type, virtual); +} static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 0, .opc2 = 1, .access = PL1_W, .type = ARM_CP_NOP, }, + { .name = "GIC_CDPRI", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 2, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cdpri_write, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
The IRS register IRS_MAP_L2_ISTR is used by software to tell the IRS that it has updated the address in an L1 IST entry to point to an L2 IST. The sequence of events here is: * software writes to L1_ISTE.L2_ADDR for some L1 ISTE which is not valid (i.e. where L1_ISTE.VALID is 0); it leaves VALID at 0 * software writes to IRS_MAP_L2_ISTR with some INTID that is inside the range for this L1 ISTE * the IRS sets IRS_IST_STATUSR.IDLE to 0 * the IRS takes note of this information * the IRS writes to the L1_ISTE to set VALID=1 * the IRS sets IRS_IST_STATUSR.IDLE to 1 to indicate that the update is complete For QEMU, we're strictly synchronous, so (as with IRS_IST_BASER updates) we don't need to model the IDLE transitions and can have IRS_IST_STATUSR always return IDLE=1. We also don't currently cache anything for ISTE lookups, so we don't need to invalidate or update anything when software makes the L2 valid. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-21-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, } } +static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value) +{ + GICv5Common *cs = ARM_GICV5_COMMON(s); + GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + uint32_t intid = FIELD_EX32(value, IRS_MAP_L2_ISTR, ID); + hwaddr l1_addr; + uint64_t l1_iste; + MemTxResult res; + + if (!FIELD_EX64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID) || + !cfg->structure) { + /* WI if no IST set up or it is not 2-level */ + return; + } + + /* Find the relevant L1 ISTE and set its VALID bit */ + l1_addr = l1_iste_addr(cs, cfg, intid); + + l1_iste = address_space_ldq_le(&cs->dma_as, l1_addr, cfg->txattrs, &res); + if (res != MEMTX_OK) { + goto txfail; + } + + l1_iste = FIELD_DP64(l1_iste, L1_ISTE, VALID, 1); + + address_space_stq_le(&cs->dma_as, l1_addr, l1_iste, cfg->txattrs, &res); + if (res != MEMTX_OK) { + goto txfail; + } + return; + +txfail: + /* Reportable with EC=0x0 if sw error reporting implemented */ + qemu_log_mask(LOG_GUEST_ERROR, "L1 ISTE update failed for ID 0x%x at " + "physical address 0x" HWADDR_FMT_plx "\n", intid, l1_addr); +} + static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, cs->irs_ist_cfgr[domain] = data; } return true; + case A_IRS_MAP_L2_ISTR: + irs_map_l2_istr_write(s, domain, data); + return true; } return false; -- 2.43.0
Implement the GICv5 functions corresponding to the stream protocol SetEnabled, SetPending, SetHandling, and SetTarget commands. These work exactly like SetPriority: the IRS looks up the L2TE and updates the corresponding field in it with the new value. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-22-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 152 +++++++++++++++++++++++++++++ hw/intc/trace-events | 4 + include/hw/intc/arm_gicv5_stream.h | 68 +++++++++++++ include/hw/intc/arm_gicv5_types.h | 15 +++ 4 files changed, 239 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, } } +void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled, + GICv5Domain domain, GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + + trace_gicv5_set_enabled(domain_name[domain], inttype_name(type), virtual, + id, enabled); + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set " + "enable state of a virtual interrupt\n"); + return; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ENABLE, enabled); + put_l2_iste(cs, cfg, &h); + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set " + "enable state of bad interrupt type %d\n", type); + return; + } +} + +void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending, + GICv5Domain domain, GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + + trace_gicv5_set_pending(domain_name[domain], inttype_name(type), virtual, + id, pending); + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set " + "pending state of a virtual interrupt\n"); + return; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PENDING, pending); + put_l2_iste(cs, cfg, &h); + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set " + "pending state of bad interrupt type %d\n", type); + return; + } +} + +void gicv5_set_handling(GICv5Common *cs, uint32_t id, + GICv5HandlingMode handling, GICv5Domain domain, + GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + + trace_gicv5_set_handling(domain_name[domain], inttype_name(type), virtual, + id, handling); + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set " + "handling mode of a virtual interrupt\n"); + return; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, HM, handling); + put_l2_iste(cs, cfg, &h); + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set " + "handling mode of bad interrupt type %d\n", type); + return; + } +} + +void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, + GICv5RoutingMode irm, GICv5Domain domain, + GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + + trace_gicv5_set_target(domain_name[domain], inttype_name(type), virtual, + id, iaffid, irm); + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set " + "target of a virtual interrupt\n"); + return; + } + if (irm != GICV5_TARGETED) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set " + "1-of-N routing\n"); + /* + * In the cpuif insn "GIC CDAFF", IRM is RES0 for a GIC which + * does not support 1-of-N routing. So warn, and fall through + * to treat IRM=1 the same as IRM=0. + */ + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + /* + * For QEMU we do not implement 1-of-N routing, and so + * L2_ISTE.IRM is RES0. We never read it, and we can skip + * explicitly writing it to zero here. + */ + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, IAFFID, iaffid); + put_l2_iste(cs, cfg, &h); + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set " + "target of bad interrupt type %d\n", type); + return; + } +} + static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_spi(uint32_t id, int level) "GICv5 SPI ID %u asserted at level %d" gicv5_ist_valid(const char *domain, uint64_t base, uint8_t id_bits, uint8_t l2_idx_bits, uint8_t istsz, bool structure) "GICv5 IRS %s IST now valid: base 0x%" PRIx64 " id_bits %u l2_idx_bits %u IST entry size %u 2-level %d" gicv5_ist_invalid(const char *domain) "GICv5 IRS %s IST no longer valid" gicv5_set_priority(const char *domain, const char *type, bool virtual, uint32_t id, uint8_t priority) "GICv5 IRS SetPriority %s %s virtual:%d ID %u prio %u" +gicv5_set_enabled(const char *domain, const char *type, bool virtual, uint32_t id, bool enabled) "GICv5 IRS SetEnabled %s %s virtual:%d ID %u enabled %d" +gicv5_set_pending(const char *domain, const char *type, bool virtual, uint32_t id, bool pending) "GICv5 IRS SetPending %s %s virtual:%d ID %u pending %d" +gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t id, int handling) "GICv5 IRS SetHandling %s %s virtual:%d ID %u handling %d" +gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d" # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, GICv5Domain domain, GICv5IntType type, bool virtual); +/** + * gicv5_set_enabled + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @enabled: new enabled state + * @domain: interrupt Domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Set enabled state of an interrupt; matches stream interface + * SetEnabled command from CPUIF to IRS. There is no report back of + * success/failure to the CPUIF in the protocol. + */ +void gicv5_set_enabled(GICv5Common *cs, uint32_t id, + bool enabled, GICv5Domain domain, + GICv5IntType type, bool virtual); + +/** + * gicv5_set_pending + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @pending: new pending state + * @domain: interrupt Domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Set pending state of an interrupt; matches stream interface + * SetPending command from CPUIF to IRS. There is no report back of + * success/failure to the CPUIF in the protocol. + */ +void gicv5_set_pending(GICv5Common *cs, uint32_t id, + bool pending, GICv5Domain domain, + GICv5IntType type, bool virtual); + +/** + * gicv5_set_handling + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @handling: new handling mode + * @domain: interrupt Domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Set handling mode of an interrupt (edge/level); matches stream + * interface SetHandling command from CPUIF to IRS. There is no report + * back of success/failure to the CPUIF in the protocol. + */ +void gicv5_set_handling(GICv5Common *cs, uint32_t id, + GICv5HandlingMode handling, GICv5Domain domain, + GICv5IntType type, bool virtual); + +/** + * gicv5_set_target + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @iaffid: new target PE's interrupt affinity + * @irm: interrupt routing mode (targeted vs 1-of-N) + * @domain: interrupt Domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Set handling mode of an interrupt (edge/level); matches stream + * interface SetHandling command from CPUIF to IRS. There is no report + * back of success/failure to the CPUIF in the protocol. + */ +void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, + GICv5RoutingMode irm, GICv5Domain domain, + GICv5IntType type, bool virtual); #endif diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ typedef enum GICv5IntType { GICV5_SPI = 3, } GICv5IntType; +/* Interrupt handling mode (same encoding as L2_ISTE.HM) */ +typedef enum GICv5HandlingMode { + GICV5_EDGE = 0, + GICV5_LEVEL = 1, +} GICv5HandlingMode; + +/* + * Interrupt routing mode (same encoding as L2_ISTE.IRM). + * Note that 1-of-N support is option and QEMU does not implement it. + */ +typedef enum GICv5RoutingMode { + GICV5_TARGETED = 0, + GICV5_1OFN = 1, +} GICv5RoutingMode; + #endif -- 2.43.0
Implement the GIC CDDIS, GIC CDEN, GIC CDAFF, GIC CDPEND and GIC CDHM system instructions. These are all simple wrappers around the equivalent gicv5_set_* functions, like GIC CDPRI. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-23-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 108 +++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(GIC_CDPRI, ID, 0, 24) FIELD(GIC_CDPRI, TYPE, 29, 3) FIELD(GIC_CDPRI, PRIORITY, 35, 5) +FIELD(GIC_CDDIS, ID, 0, 24) +FIELD(GIC_CDDIS, TYPE, 29, 3) + +FIELD(GIC_CDEN, ID, 0, 24) +FIELD(GIC_CDEN, TYPE, 29, 3) + +FIELD(GIC_CDAFF, ID, 0, 24) +FIELD(GIC_CDAFF, IRM, 28, 1) +FIELD(GIC_CDAFF, TYPE, 29, 3) +FIELD(GIC_CDAFF, IAFFID, 32, 16) + +FIELD(GIC_CDPEND, ID, 0, 24) +FIELD(GIC_CDPEND, TYPE, 29, 3) +FIELD(GIC_CDPEND, PENDING, 32, 1) + +FIELD(GIC_CDHM, ID, 0, 24) +FIELD(GIC_CDHM, TYPE, 29, 3) +FIELD(GIC_CDHM, HM, 32, 1) + static GICv5Common *gicv5_get_gic(CPUARMState *env) { return env->gicv5state; @@ -XXX,XX +XXX,XX @@ static GICv5Domain gicv5_current_phys_domain(CPUARMState *env) return gicv5_logical_domain(env); } +static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + GICv5IntType type = FIELD_EX64(value, GIC_CDDIS, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDDIS, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + gicv5_set_enabled(gic, id, false, domain, type, virtual); +} + +static void gic_cden_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + GICv5IntType type = FIELD_EX64(value, GIC_CDEN, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDEN, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + gicv5_set_enabled(gic, id, true, domain, type, virtual); +} + static void gic_cdpri_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { @@ -XXX,XX +XXX,XX @@ static void gic_cdpri_write(CPUARMState *env, const ARMCPRegInfo *ri, gicv5_set_priority(gic, id, priority, domain, type, virtual); } +static void gic_cdaff_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + uint32_t iaffid = FIELD_EX64(value, GIC_CDAFF, IAFFID); + GICv5RoutingMode irm = FIELD_EX64(value, GIC_CDAFF, IRM); + GICv5IntType type = FIELD_EX64(value, GIC_CDAFF, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDAFF, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + gicv5_set_target(gic, id, iaffid, irm, domain, type, virtual); +} + +static void gic_cdpend_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + bool pending = FIELD_EX64(value, GIC_CDPEND, PENDING); + GICv5IntType type = FIELD_EX64(value, GIC_CDPEND, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDPEND, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + gicv5_set_pending(gic, id, pending, domain, type, virtual); +} + +static void gic_cdhm_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + GICv5HandlingMode hm = FIELD_EX64(value, GIC_CDHM, HM); + GICv5IntType type = FIELD_EX64(value, GIC_CDAFF, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDAFF, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + gicv5_set_handling(gic, id, hm, domain, type, virtual); +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 0, .opc2 = 1, .access = PL1_W, .type = ARM_CP_NOP, }, + { .name = "GIC_CDDIS", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 0, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cddis_write, + }, + { .name = "GIC_CDEN", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 1, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cden_write, + }, { .name = "GIC_CDPRI", .state = ARM_CP_STATE_AA64, .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 2, .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdpri_write, }, + { .name = "GIC_CDAFF", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 3, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cdaff_write, + }, + { .name = "GIC_CDPEND", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 4, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cdpend_write, + }, + { .name = "GIC_CDHM", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 2, .opc2 = 1, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cdhm_write, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
The GICv5 allows an IRS to implement SPIs, which are fixed-wire interrupts connected directly to the IRS. For QEMU we want to use these for all our traditional fixed-wire interrupt devices. (The other option the architecture permits is an Interrupt Wire Bridge (IWB), which converts from a fixed-wire interrupt to an interrupt event that is then translated through an ITS to send an LPI to the ITS -- this is much more complexity than we need or want.) SPI configuration is set via the same CPUIF instructions as LPI configuration. Create an array of structs which track the SPI state information listed in I_JVVTZ and I_BWPPP (ignoring for the moment the VM assignment state, which we will add when we add virtualization support). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-24-peter.maydell@linaro.org --- hw/intc/arm_gicv5_common.c | 30 ++++++++++++++++++++++++++++++ include/hw/intc/arm_gicv5_common.h | 27 +++++++++++++++++++++++++++ include/hw/intc/arm_gicv5_types.h | 14 ++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static void gicv5_common_reset_hold(Object *obj, ResetType type) memset(cs->irs_ist_baser, 0, sizeof(cs->irs_ist_baser)); memset(cs->irs_ist_cfgr, 0, sizeof(cs->irs_ist_cfgr)); + + if (cs->spi) { + GICv5Domain mp_domain; + + /* + * D_YGLYC, D_TVVRZ: SPIs reset to edge-triggered, inactive, + * idle, disabled, targeted routing mode, not assigned to a + * VM, and assigned to the most-privileged interrupt domain. + * Other state is UNKNOWN: we choose to zero it. + */ + memset(cs->spi, 0, cs->spi_irs_range * sizeof(*cs->spi)); + + /* + * The most-privileged interrupt domain is effectively the + * first in the list (EL3, S, NS) that we implement. + */ + if (gicv5_domain_implemented(cs, GICV5_ID_EL3)) { + mp_domain = GICV5_ID_EL3; + } else if (gicv5_domain_implemented(cs, GICV5_ID_S)) { + mp_domain = GICV5_ID_S; + } else { + mp_domain = GICV5_ID_NS; + } + + for (int i = 0; i < cs->spi_irs_range; i++) { + cs->spi[i].domain = mp_domain; + } + } } static void gicv5_common_init(Object *obj) @@ -XXX,XX +XXX,XX @@ static void gicv5_common_realize(DeviceState *dev, Error **errp) address_space_init(&cs->dma_as, cs->dma, "gicv5-sysmem"); + cs->spi = g_new0(GICv5SPIState, cs->spi_irs_range); + trace_gicv5_common_realize(cs->irsid, cs->num_cpus, cs->spi_base, cs->spi_irs_range, cs->spi_range); } diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_TYPE(GICv5Common, GICv5CommonClass, ARM_GICV5_COMMON) +/* + * This is where we store the state the IRS handles for an SPI. + * Generally this corresponds to the spec's list of state in I_JVVTZ + * and J_BWPPP. level is a QEMU implementation detail and is where we + * store the actual current state of the incoming qemu_irq line. + */ +typedef struct GICv5SPIState { + uint32_t iaffid; + uint8_t priority; + bool level; + bool pending; + bool active; + bool enabled; + GICv5HandlingMode hm; + GICv5RoutingMode irm; + GICv5TriggerMode tm; + GICv5Domain domain; +} GICv5SPIState; + /* * This class is for common state that will eventually be shared * between TCG and KVM implementations of the GICv5. @@ -XXX,XX +XXX,XX @@ struct GICv5Common { uint64_t irs_ist_baser[NUM_GICV5_DOMAINS]; uint32_t irs_ist_cfgr[NUM_GICV5_DOMAINS]; + /* + * Pointer to an array of state information for the SPIs. Array + * element 0 is SPI ID s->spi_base, and there are s->spi_irs_range + * elements in total. SPI state is not per-domain: SPI is + * configurable to a particular domain via IRS_SPI_DOMAINR. + */ + GICv5SPIState *spi; + /* Bits here are set for each physical interrupt domain implemented */ uint8_t implemented_domains; diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ typedef enum GICv5RoutingMode { GICV5_1OFN = 1, } GICv5RoutingMode; +/* + * Interrupt trigger mode (same encoding as IRS_SPI_CFGR.TM) Note that + * this is not the same thing as handling mode, even though the two + * possible states have the same names. Trigger mode applies only for + * SPIs and tells the IRS what kinds of changes to the input signal + * wire should make it generate SET and CLEAR events. Handling mode + * affects whether the pending state of an interrupt is cleared when + * the interrupt is acknowledged, and applies to both SPIs and LPIs. + */ +typedef enum GICv5TriggerMode { + GICV5_TRIGGER_EDGE = 0, + GICV5_TRIGGER_LEVEL = 1, +} GICv5TriggerMode; + #endif -- 2.43.0
The GIC CD* insns that update interrupt state also work for SPIs. Instead of ignoring the GICV5_SPI type in gicv5_set_priority() and friends, update the state in our SPI state array. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-25-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 64 ++++++++++++++++++++++++++++++ include/hw/intc/arm_gicv5_common.h | 40 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, put_l2_iste(cs, cfg, &h); break; } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set " + "priority of unreachable SPI %d\n", id); + return; + } + + spi->priority = priority; + break; + } default: qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set " "priority of bad interrupt type %d\n", type); @@ -XXX,XX +XXX,XX @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled, put_l2_iste(cs, cfg, &h); break; } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set " + "enable state of unreachable SPI %d\n", id); + return; + } + + spi->enabled = true; + break; + } default: qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_enabled: tried to set " "enable state of bad interrupt type %d\n", type); @@ -XXX,XX +XXX,XX @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending, put_l2_iste(cs, cfg, &h); break; } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set " + "pending state of unreachable SPI %d\n", id); + return; + } + + spi->pending = true; + break; + } default: qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_pending: tried to set " "pending state of bad interrupt type %d\n", type); @@ -XXX,XX +XXX,XX @@ void gicv5_set_handling(GICv5Common *cs, uint32_t id, put_l2_iste(cs, cfg, &h); break; } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set " + "priority of unreachable SPI %d\n", id); + } + + spi->hm = handling; + break; + } default: qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_handling: tried to set " "handling mode of bad interrupt type %d\n", type); @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, put_l2_iste(cs, cfg, &h); break; } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set " + "target of unreachable SPI %d\n", id); + return; + } + + spi->iaffid = iaffid; + break; + } default: qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_target: tried to set " "target of bad interrupt type %d\n", type); diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ static inline const char *gicv5_class_name(void) return "arm-gicv5"; } +/** + * gicv5_raw_spi_state + * @cs: GIC object + * @id: INTID of SPI to look up + * + * Return pointer to the GICv5SPIState for this SPI, or NULL if the + * interrupt ID is out of range. This does not do a check that the SPI + * is assigned to the right domain: generally you should call it via + * some other wrapper that performs an appropriate further check. + */ +static inline GICv5SPIState *gicv5_raw_spi_state(GICv5Common *cs, uint32_t id) +{ + if (id < cs->spi_base || id >= cs->spi_base + cs->spi_irs_range) { + return NULL; + } + + return cs->spi + (id - cs->spi_base); +} + +/** + * gicv5_spi_state: + * @cs: GIC object + * @id: INTID of SPI to look up + * @domain: domain to check + * + * Return pointer to the GICv5SPIState for this SPI, or NULL if the + * interrupt is unreachable (which can be because the INTID is out of + * range, or because the SPI is configured for a different domain). + */ +static inline GICv5SPIState *gicv5_spi_state(GICv5Common *cs, uint32_t id, + GICv5Domain domain) +{ + GICv5SPIState *spi = gicv5_raw_spi_state(cs, id); + + if (!spi || spi->domain != domain) { + return NULL; + } + return spi; +} + #endif -- 2.43.0
Implement the gicv5_request_config() function, which corresponds to the RequestConfig command and its RequestConfigAck reply. We provide read_l2_iste() as a separate function to keep the "access the in-guest-memory data structure" layer separate from the "operate on the L2_ISTE values" layer. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-26-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 102 +++++++++++++++++++++++++++++ hw/intc/trace-events | 1 + include/hw/intc/arm_gicv5_stream.h | 24 +++++++ 3 files changed, 127 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ FIELD(L2_ISTE, HWU, 9, 2) FIELD(L2_ISTE, PRIORITY, 11, 5) FIELD(L2_ISTE, IAFFID, 16, 16) +/* + * Format used for gicv5_request_config() return value, which matches + * the ICC_ICSR_EL1 bit layout. + */ +FIELD(ICSR, F, 0, 1) +FIELD(ICSR, ENABLED, 1, 1) +FIELD(ICSR, PENDING, 2, 1) +FIELD(ICSR, IRM, 3, 1) +FIELD(ICSR, ACTIVE, 4, 1) +FIELD(ICSR, HM, 5, 1) +FIELD(ICSR, PRIORITY, 11, 5) +FIELD(ICSR, IAFFID, 32, 16) + static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain) { /* @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, } } +static uint64_t l2_iste_to_icsr(GICv5Common *cs, const GICv5ISTConfig *cfg, + uint32_t id) +{ + uint64_t icsr = 0; + const uint32_t *l2_iste_p; + L2_ISTE_Handle h; + + l2_iste_p = get_l2_iste(cs, cfg, id, &h); + if (!l2_iste_p) { + return R_ICSR_F_MASK; + } + + /* + * The field locations in the L2 ISTE do not line up with the + * corresponding fields in the ICC_ICSR_EL1 register, so we need + * to extract and deposit them individually. + */ + icsr = FIELD_DP64(icsr, ICSR, F, 0); + icsr = FIELD_DP64(icsr, ICSR, ENABLED, FIELD_EX32(*l2_iste_p, L2_ISTE, ENABLE)); + icsr = FIELD_DP64(icsr, ICSR, PENDING, FIELD_EX32(*l2_iste_p, L2_ISTE, PENDING)); + icsr = FIELD_DP64(icsr, ICSR, IRM, FIELD_EX32(*l2_iste_p, L2_ISTE, IRM)); + icsr = FIELD_DP64(icsr, ICSR, ACTIVE, FIELD_EX32(*l2_iste_p, L2_ISTE, ACTIVE)); + icsr = FIELD_DP64(icsr, ICSR, HM, FIELD_EX32(*l2_iste_p, L2_ISTE, HM)); + icsr = FIELD_DP64(icsr, ICSR, PRIORITY, FIELD_EX32(*l2_iste_p, L2_ISTE, PRIORITY)); + icsr = FIELD_DP64(icsr, ICSR, IAFFID, FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID)); + + return icsr; +} + +static uint64_t spi_state_to_icsr(GICv5SPIState *spi) +{ + uint64_t icsr = 0; + + icsr = FIELD_DP64(icsr, ICSR, F, 0); + icsr = FIELD_DP64(icsr, ICSR, ENABLED, spi->enabled); + icsr = FIELD_DP64(icsr, ICSR, PENDING, spi->pending); + icsr = FIELD_DP64(icsr, ICSR, IRM, spi->irm); + icsr = FIELD_DP64(icsr, ICSR, ACTIVE, spi->active); + icsr = FIELD_DP64(icsr, ICSR, HM, spi->hm); + icsr = FIELD_DP64(icsr, ICSR, PRIORITY, spi->priority); + icsr = FIELD_DP64(icsr, ICSR, IAFFID, spi->iaffid); + + return icsr; +} + +uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain, + GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + uint64_t icsr; + + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_request_config: tried to " + "read config of a virtual interrupt\n"); + return R_ICSR_F_MASK; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + + icsr = l2_iste_to_icsr(cs, cfg, id); + trace_gicv5_request_config(domain_name[domain], inttype_name(type), + virtual, id, icsr); + return icsr; + } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_request_config: tried to " + "read config of unreachable SPI %d\n", id); + return R_ICSR_F_MASK; + } + + icsr = spi_state_to_icsr(spi); + trace_gicv5_request_config(domain_name[domain], inttype_name(type), + virtual, id, icsr); + return icsr; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_request_config: tried to " + "read config of bad interrupt type %d\n", type); + return R_ICSR_F_MASK; + } +} + static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_set_enabled(const char *domain, const char *type, bool virtual, uint32_t i gicv5_set_pending(const char *domain, const char *type, bool virtual, uint32_t id, bool pending) "GICv5 IRS SetPending %s %s virtual:%d ID %u pending %d" gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t id, int handling) "GICv5 IRS SetHandling %s %s virtual:%d ID %u handling %d" gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d" +gicv5_request_config(const char *domain, const char *type, bool virtual, uint32_t id, uint64_t icsr) "GICv5 IRS RequestConfig %s %s virtual:%d ID %u ICSR 0x%" PRIx64 # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ void gicv5_set_handling(GICv5Common *cs, uint32_t id, void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, GICv5RoutingMode irm, GICv5Domain domain, GICv5IntType type, bool virtual); + +/** + * gicv5_request_config + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @domain: interrupt domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Query the current configuration of an interrupt; matches stream + * interface RequestConfig command from CPUIF to IRS and the + * RequestConfigAck reply to it. + * + * In the real stream protocol, the RequestConfigAck packet has the + * same information as the register but in a different order; we use + * the register order, not the packet order, so we don't need to + * unpack and repack in the cpuif. + * + * Returns: the config of the interrupt, in the format used by + * ICC_ICSR_EL1. + */ +uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain, + GICv5IntType type, bool virtual); + #endif -- 2.43.0
Implement the GIC CDRCFG system instruction, which asks the IRS for the configuration of an interrupt, and the system register ICC_ICSR_EL1 which is where the answer is placed for the guest to read it. We mark ICC_ICSR_EL1 as ARM_CP_NO_RAW, because we do not want to have this migrated as part of the generic "system register" migration arrays. Instead we will do migration via a GICv5 cpuif vmstate section. This is necessary because some of the cpuif registers are banked by interrupt domain and so need special handling to migrate the data in all the banks; it's also how we handle the gicv3 cpuif registers. (We expect that KVM also will expose the cpuif registers via GIC-specific ioctls rather than as generic sysregs.) We'll mark all the GICv5 sysregs as NO_RAW. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-27-peter.maydell@linaro.org --- target/arm/cpu.h | 5 +++++ target/arm/tcg/gicv5-cpuif.c | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { uint64_t vmecid_a_el2; } cp15; + struct { + /* GICv5 CPU interface data */ + uint64_t icc_icsr_el1; + } gicv5_cpuif; + struct { /* M profile has up to 4 stack pointers: * a Main Stack Pointer and a Process Stack Pointer for each diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(GIC_CDHM, ID, 0, 24) FIELD(GIC_CDHM, TYPE, 29, 3) FIELD(GIC_CDHM, HM, 32, 1) +FIELD(GIC_CDRCFG, ID, 0, 24) +FIELD(GIC_CDRCFG, TYPE, 29, 3) + static GICv5Common *gicv5_get_gic(CPUARMState *env) { return env->gicv5state; @@ -XXX,XX +XXX,XX @@ static void gic_cdpend_write(CPUARMState *env, const ARMCPRegInfo *ri, gicv5_set_pending(gic, id, pending, domain, type, virtual); } +static void gic_cdrcfg_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Common *gic = gicv5_get_gic(env); + GICv5IntType type = FIELD_EX64(value, GIC_CDRCFG, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDRCFG, ID); + bool virtual = false; + GICv5Domain domain = gicv5_current_phys_domain(env); + + env->gicv5_cpuif.icc_icsr_el1 = + gicv5_request_config(gic, id, domain, type, virtual); +} + static void gic_cdhm_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdpend_write, }, + { .name = "GIC_CDRCFG", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 5, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cdrcfg_write, + }, { .name = "GIC_CDHM", .state = ARM_CP_STATE_AA64, .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 2, .opc2 = 1, .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdhm_write, }, + { .name = "ICC_ICSR_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 4, + .access = PL1_RW, .type = ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.icc_icsr_el1), + .resetvalue = 0, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
Implement the IRS registers IRS_SPI_{SELR,STATUSR,CFGR,DOMAINR} which form the config access for setting the trigger mode and domain of an SPI. The way these work is that the guest writes the ID of the interrupt it wants to configure to IRS_SPI_SELR, and then it can read and write the trigger mode of that SPI via IRS_SPI_CFGR and the domain via IRS_SPI_DOMAINR. IRS_SPI_STATUSR has a bit to indicate whether the SPI is valid, and the usual IDLE bit to allow for non-instantaneous updates (which QEMU doesn't do). Since the only domain which can configure the domain of an SPI is EL3 and our initial implementation is NS-only, technically the DOMAINR handling is unused code. However it is straightforward, being almost the same as the CFGR handling, and we'll need it later on. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-28-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 68 ++++++++++++++++++++++++++++++ hw/intc/arm_gicv5_common.c | 9 ++++ include/hw/intc/arm_gicv5_common.h | 1 + 3 files changed, 78 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ FIELD(ICSR, HM, 5, 1) FIELD(ICSR, PRIORITY, 11, 5) FIELD(ICSR, IAFFID, 32, 16) +static GICv5SPIState *spi_for_selr(GICv5Common *cs, GICv5Domain domain) +{ + /* + * If the IRS_SPI_SELR value specifies an SPI that can be managed in + * this domain, return a pointer to its GICv5SPIState; otherwise + * return NULL. + */ + uint32_t id = FIELD_EX32(cs->irs_spi_selr[domain], IRS_SPI_SELR, ID); + GICv5SPIState *spi = gicv5_raw_spi_state(cs, id); + + if (spi && (domain == GICV5_ID_EL3 || domain == spi->domain)) { + return spi; + } + return NULL; +} + static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain) { /* @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_IST_CFGR: *data = cs->irs_ist_cfgr[domain]; return true; + + case A_IRS_SPI_STATUSR: + /* + * QEMU writes to IRS_SPI_{CFGR,DOMAINR,SELR,VMR} take effect + * instantaneously, so the guest can never see the IDLE bit as 0. + */ + v = FIELD_DP32(v, IRS_SPI_STATUSR, V, + spi_for_selr(cs, domain) != NULL); + v = FIELD_DP32(v, IRS_SPI_STATUSR, IDLE, 1); + *data = v; + return true; + + case A_IRS_SPI_CFGR: + { + GICv5SPIState *spi = spi_for_selr(cs, domain); + + if (spi) { + v = FIELD_DP32(v, IRS_SPI_CFGR, TM, spi->tm); + } + *data = v; + return true; + } + case A_IRS_SPI_DOMAINR: + if (domain == GICV5_ID_EL3) { + /* This is RAZ/WI except for the EL3 domain */ + GICv5SPIState *spi = spi_for_selr(cs, domain); + if (spi) { + v = FIELD_DP32(v, IRS_SPI_DOMAINR, DOMAIN, spi->domain); + } + } + *data = v; + return true; } return false; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_MAP_L2_ISTR: irs_map_l2_istr_write(s, domain, data); return true; + case A_IRS_SPI_SELR: + cs->irs_spi_selr[domain] = data; + return true; + case A_IRS_SPI_CFGR: + { + GICv5SPIState *spi = spi_for_selr(cs, domain); + if (spi) { + spi->tm = FIELD_EX32(data, IRS_SPI_CFGR, TM); + } + return true; + } + case A_IRS_SPI_DOMAINR: + if (domain == GICV5_ID_EL3) { + /* this is RAZ/WI except for the EL3 domain */ + GICv5SPIState *spi = spi_for_selr(cs, domain); + if (spi) { + spi->domain = FIELD_EX32(data, IRS_SPI_DOMAINR, DOMAIN); + } + } + return true; } return false; diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static void gicv5_common_reset_hold(Object *obj, ResetType type) cs->spi[i].domain = mp_domain; } } + + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) { + /* + * We reset irs_spi_selr to an invalid value so that our reset + * value for IRS_SPI_STATUSR.V is correctly 0. The guest can + * never read IRS_SPI_SELR directly. + */ + cs->irs_spi_selr[i] = cs->spi_base + cs->spi_irs_range; + } } static void gicv5_common_init(Object *obj) diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ struct GICv5Common { uint64_t irs_ist_baser[NUM_GICV5_DOMAINS]; uint32_t irs_ist_cfgr[NUM_GICV5_DOMAINS]; + uint32_t irs_spi_selr[NUM_GICV5_DOMAINS]; /* * Pointer to an array of state information for the SPIs. Array -- 2.43.0
When an SPI irq line changes level, this causes what the spec describes as SET_LEVEL, SET_EDGE or CLEAR events. These also happen when the trigger mode is reconfigured, or when software requests a manual resample via the IRS_SPI_RESAMPLER register. SET_LEVEL and SET_EDGE events make the interrupt pending, and update its handler mode to match its trigger mode. CLEAR events make the interrupt no longer pending. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-29-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 59 ++++++++++++++++++++++++++++++++++++++++++++ hw/intc/trace-events | 1 + 2 files changed, 60 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) } } +static void spi_sample(GICv5SPIState *spi) +{ + /* + * Sample the state of the SPI input line; this generates + * SET_EDGE, SET_LEVEL or CLEAR events which update the SPI's + * pending state and handling mode per R_HHKMN. The logic is the + * same for "the input line changed" (R_QBXXV) and "software asked + * us to resample" (R_DMTFM). + */ + if (spi->level) { + /* + * SET_LEVEL or SET_EDGE: interrupt becomes pending, and the + * handling mode is updated to match the trigger mode. + */ + spi->pending = true; + spi->hm = spi->tm == GICV5_TRIGGER_EDGE ? GICV5_EDGE : GICV5_LEVEL; + } else if (spi->tm == GICV5_TRIGGER_LEVEL) { + /* falling edges only trigger a CLEAR event for level-triggered */ + spi->pending = false; + } +} + static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t *data, MemTxAttrs attrs) { @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, { GICv5SPIState *spi = spi_for_selr(cs, domain); if (spi) { + GICv5TriggerMode old_tm = spi->tm; spi->tm = FIELD_EX32(data, IRS_SPI_CFGR, TM); + if (spi->tm != old_tm) { + /* + * R_KBPXL: updates to SPI trigger mode can generate CLEAR or + * SET_LEVEL events. This is not the same logic as spi_sample(). + */ + if (spi->tm == GICV5_TRIGGER_LEVEL) { + if (spi->level) { + spi->pending = true; + spi->hm = GICV5_LEVEL; + } else { + spi->pending = false; + } + } else if (spi->level) { + spi->pending = false; + } + } } return true; } @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, } } return true; + case A_IRS_SPI_RESAMPLER: + { + uint32_t id = FIELD_EX32(data, IRS_SPI_RESAMPLER, SPI_ID); + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (spi) { + spi_sample(spi); + } + trace_gicv5_spi_state(id, spi->level, spi->pending, spi->active); + return true; + } } return false; @@ -XXX,XX +XXX,XX @@ static void gicv5_set_spi(void *opaque, int irq, int level) /* These irqs are all SPIs; the INTID is irq + s->spi_base */ GICv5Common *cs = ARM_GICV5_COMMON(opaque); uint32_t spi_id = irq + cs->spi_base; + GICv5SPIState *spi = gicv5_raw_spi_state(cs, spi_id); + + if (!spi || spi->level == level) { + return; + } trace_gicv5_spi(spi_id, level); + + spi->level = level; + spi_sample(spi); + trace_gicv5_spi_state(spi_id, spi->level, spi->pending, spi->active); } static void gicv5_reset_hold(Object *obj, ResetType type) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_set_pending(const char *domain, const char *type, bool virtual, uint32_t i gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t id, int handling) "GICv5 IRS SetHandling %s %s virtual:%d ID %u handling %d" gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d" gicv5_request_config(const char *domain, const char *type, bool virtual, uint32_t id, uint64_t icsr) "GICv5 IRS RequestConfig %s %s virtual:%d ID %u ICSR 0x%" PRIx64 +gicv5_spi_state(uint32_t spi_id, bool level, bool pending, bool active) "GICv5 IRS SPI ID %u now level %d pending %d active %d" # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" -- 2.43.0
The IRS_CR0 register has the main enable bit for the IRS, and an IDLE bit to tell the guest when an enable/disable transition has completed. The IRS_CR1 register has cacheability, shareability and cache hint information to use for IRS memory accesses; since QEMU doesn't care about this we can make it simply reads-as-written. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-30-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 13 +++++++++++++ hw/intc/arm_gicv5_common.c | 2 ++ include/hw/intc/arm_gicv5_common.h | 2 ++ 3 files changed, 17 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, } *data = v; return true; + case A_IRS_CR0: + /* Enabling is instantaneous for us so IDLE is always 1 */ + *data = cs->irs_cr0[domain] | R_IRS_CR0_IDLE_MASK; + return true; + case A_IRS_CR1: + *data = cs->irs_cr1[domain]; + return true; } return false; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, trace_gicv5_spi_state(id, spi->level, spi->pending, spi->active); return true; } + case A_IRS_CR0: + cs->irs_cr0[domain] = data & R_IRS_CR0_IRSEN_MASK; + return true; + case A_IRS_CR1: + cs->irs_cr1[domain] = data; + return true; } return false; diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static void gicv5_common_reset_hold(Object *obj, ResetType type) memset(cs->irs_ist_baser, 0, sizeof(cs->irs_ist_baser)); memset(cs->irs_ist_cfgr, 0, sizeof(cs->irs_ist_cfgr)); + memset(cs->irs_cr0, 0, sizeof(cs->irs_cr0)); + memset(cs->irs_cr1, 0, sizeof(cs->irs_cr1)); if (cs->spi) { GICv5Domain mp_domain; diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ struct GICv5Common { uint64_t irs_ist_baser[NUM_GICV5_DOMAINS]; uint32_t irs_ist_cfgr[NUM_GICV5_DOMAINS]; uint32_t irs_spi_selr[NUM_GICV5_DOMAINS]; + uint32_t irs_cr0[NUM_GICV5_DOMAINS]; + uint32_t irs_cr1[NUM_GICV5_DOMAINS]; /* * Pointer to an array of state information for the SPIs. Array -- 2.43.0
The IRS_SYNCR register is used by software to request synchronization of interrupt events. This means that in-flight interrupt events are guaranteed to have been delivered. Since QEMU's implementation is entirely synchronous, syncs are a no-op for us. This means we can ignore writes to IRS_SYNCR and always report "sync complete" via the IDLE bit in IRS_SYNC_STATUSR. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-31-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_CR1: *data = cs->irs_cr1[domain]; return true; + case A_IRS_SYNC_STATUSR: + /* Sync is a no-op for QEMU: we are always IDLE */ + *data = R_IRS_SYNC_STATUSR_IDLE_MASK; + return true; } return false; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_CR1: cs->irs_cr1[domain] = data; return true; + case A_IRS_SYNCR: + /* Sync is a no-op for QEMU: ignore write */ + return true; } return false; -- 2.43.0
The IRS_PE_CR0, IRS_PE_SELR, IRS_PE_STATUSR registers allow software to set and query per-CPU config. Software writes the AFFID of a CPU to IRS_PE_SELR, and can then read and write the 1ofN config for that CPU to IRS_PE_CR0, and read the CPU's online status from IRS_PE_STATUSR. For QEMU, we do not implement 1-of-N interrupt routing, so IRS_PE_CR0 can be RAZ/WI. Our CPUs are always online and selecting a new one via SELR is instantaneous, so IRS_PE_STATUSR will return either ONLINE | V | IDLE if a valid AFFID was written to SELR, or just IDLE if an invalid AFFID was written. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-32-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 39 ++++++++++++++++++++++++++++++ hw/intc/arm_gicv5_common.c | 1 + include/hw/intc/arm_gicv5_common.h | 1 + 3 files changed, 41 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static void spi_sample(GICv5SPIState *spi) } } +static bool irs_pe_selr_valid(GICv5Common *cs, GICv5Domain domain) +{ + /* + * Return true if IRS_PE_SELR has a valid AFFID in it. We don't + * expect the guest to do this except perhaps once at startup, so + * do a simple linear scan through the cpu_iaffids array. + */ + for (int i = 0; i < cs->num_cpu_iaffids; i++) { + if (cs->irs_pe_selr[domain] == cs->cpu_iaffids[i]) { + return true; + } + } + return false; +} + static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, uint64_t *data, MemTxAttrs attrs) { @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, /* Sync is a no-op for QEMU: we are always IDLE */ *data = R_IRS_SYNC_STATUSR_IDLE_MASK; return true; + case A_IRS_PE_SELR: + *data = cs->irs_pe_selr[domain]; + return true; + case A_IRS_PE_CR0: + /* We don't implement 1ofN, so this is RAZ/WI for us */ + *data = 0; + return true; + case A_IRS_PE_STATUSR: + /* + * Our CPUs are always online, so we're really just reporting + * whether the guest wrote a valid AFFID to IRS_PE_SELR + */ + v = R_IRS_PE_STATUSR_IDLE_MASK; + if (irs_pe_selr_valid(cs, domain)) { + v |= R_IRS_PE_STATUSR_V_MASK | R_IRS_PE_STATUSR_ONLINE_MASK; + } + *data = v; + return true; } return false; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_SYNCR: /* Sync is a no-op for QEMU: ignore write */ return true; + case A_IRS_PE_SELR: + cs->irs_pe_selr[domain] = data; + return true; + case A_IRS_PE_CR0: + /* We don't implement 1ofN, so this is RAZ/WI for us */ + return true; } return false; diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static void gicv5_common_reset_hold(Object *obj, ResetType type) memset(cs->irs_ist_cfgr, 0, sizeof(cs->irs_ist_cfgr)); memset(cs->irs_cr0, 0, sizeof(cs->irs_cr0)); memset(cs->irs_cr1, 0, sizeof(cs->irs_cr1)); + memset(cs->irs_pe_selr, 0, sizeof(cs->irs_pe_selr)); if (cs->spi) { GICv5Domain mp_domain; diff --git a/include/hw/intc/arm_gicv5_common.h b/include/hw/intc/arm_gicv5_common.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_common.h +++ b/include/hw/intc/arm_gicv5_common.h @@ -XXX,XX +XXX,XX @@ struct GICv5Common { uint32_t irs_spi_selr[NUM_GICV5_DOMAINS]; uint32_t irs_cr0[NUM_GICV5_DOMAINS]; uint32_t irs_cr1[NUM_GICV5_DOMAINS]; + uint32_t irs_pe_selr[NUM_GICV5_DOMAINS]; /* * Pointer to an array of state information for the SPIs. Array -- 2.43.0
The GICv5 register blocks all implement the usual Arm CoreSight ID registers; implement these for the IRS. Although we only have one callsite at the moment, the ITS config frame uses the same ID register values, so we abstract this out into a function we can reuse later. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-33-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ REG64(IRS_SWERR_SYNDROMER0, 0x3c8) REG64(IRS_SWERR_SYNDROMER1, 0x3d0) FIELD(IRS_SWERR_SYNDROMER2, ADDR, 3, 53) +REG32(IRS_IDREGS, 0xffd0) +REG32(IRS_DEVARCH, 0xffbc) + FIELD(L1_ISTE, VALID, 0, 1) FIELD(L1_ISTE, L2_ADDR, 12, 44) @@ -XXX,XX +XXX,XX @@ FIELD(ICSR, HM, 5, 1) FIELD(ICSR, PRIORITY, 11, 5) FIELD(ICSR, IAFFID, 32, 16) +#define IRS_DEVARCH_VALUE ((0x23b << 31) | (0x1 << 20) | 0x5a19) + +static uint32_t gicv5_idreg(int regoffset) +{ + /* + * As with the main IRS_IIDR, we don't identify as a specific + * hardware GICv5 implementation. Arm suggests that the + * Implementer, Product, etc in IRS_IIDR should also be reported + * here, so we do that. + */ + static const uint8_t gic_ids[] = { + QEMU_GICV5_IMPLEMENTER >> 8, 0x00, 0x00, 0x00, /* PIDR4..PIDR7 */ + QEMU_GICV5_PRODUCTID & 0xff, /* PIDR0 */ + ((QEMU_GICV5_PRODUCTID >> 8) | + ((QEMU_GICV5_IMPLEMENTER & 0xf) << 4)), /* PIDR1 */ + ((QEMU_GICV5_REVISION << 4) | (1 << 3) | + ((QEMU_GICV5_IMPLEMENTER & 0x70) >> 4)), /* PIDR2 */ + QEMU_GICV5_VARIANT << 4, /* PIDR3 */ + 0x0D, 0xF0, 0x05, 0xB1, /* CIDR0..CIDR3 */ + }; + + regoffset /= 4; + return gic_ids[regoffset]; +} + static GICv5SPIState *spi_for_selr(GICv5Common *cs, GICv5Domain domain) { /* @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, } *data = v; return true; + case A_IRS_DEVARCH: + *data = IRS_DEVARCH_VALUE; + return true; + case A_IRS_IDREGS ... A_IRS_IDREGS + 0x2f: + /* CoreSight ID registers */ + *data = gicv5_idreg(offset - A_IRS_IDREGS); + return true; } return false; -- 2.43.0
The GICv5 stores information about LPIs in a guest-memory data structure. Iterating through this to identify the highest priority pending interrupt would be expensive; to avoid this we will use a hash table which contains an entry for each pending LPI and which caches the L2 ISTE. Typically only a few LPIs will be pending at any one time, so iterating through the hash table should be fast. We can access an L2 ISTE whenever it is valid, and can freely cache the data for as long as the IST is valid. We only need to ensure that we have written back the data at the point where IRS_IST_BASER.VALID is written to 0. We add an LPI to the cache when the pending bit is written to 1, and remove it when it is written to 0. Handling of checking the cache, and of adding and removing entries, is handled within get_l2_iste() and put_l2_iste(), which all the operations that read and write ISTE words use. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-34-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 116 ++++++++++++++++++++++++++++++++++-- include/hw/intc/arm_gicv5.h | 2 + 2 files changed, 112 insertions(+), 6 deletions(-) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static bool write_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg, /* * This is returned by get_l2_iste() and has everything we need to do - * the writeback of the L2 ISTE word in put_l2_iste(). Currently the - * get/put functions always directly do guest memory reads and writes - * to update the L2 ISTE. In a future commit we will add support for a - * cache of some of the ISTE data in a local hashtable; the APIs are - * designed with that in mind. + * the writeback of the L2 ISTE word in put_l2_iste(). Not all these + * fields are always valid; they are private to the implementation of + * get_l2_iste() and put_l2_iste(). */ typedef struct L2_ISTE_Handle { + /* Guest memory address of the L2 ISTE; valid only if !hashed */ hwaddr l2_iste_addr; - uint32_t l2_iste; + union { + /* Actual L2_ISTE word; valid only if !hashed */ + uint32_t l2_iste; + /* Pointer to L2 ISTE word; valid only if hashed */ + uint32_t *l2_iste_p; + }; + uint32_t id; + /* True if this ISTE is currently in the cache */ + bool hashed; } L2_ISTE_Handle; static uint32_t *get_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg, @@ -XXX,XX +XXX,XX @@ static uint32_t *get_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg, * If the ISTE could not be read (typically because of a memory * error), return NULL. */ + uint32_t *hashvalue; + + if (!cfg->valid) { + /* Catch invalid config early, it has no lpi_cache */ + return NULL; + } + + hashvalue = g_hash_table_lookup(cfg->lpi_cache, + GINT_TO_POINTER(id)); + + h->id = id; + + if (hashvalue) { + h->hashed = true; + h->l2_iste_p = hashvalue; + return hashvalue; + } + + h->hashed = false; if (!get_l2_iste_addr(cs, cfg, id, &h->l2_iste_addr) || !read_l2_iste_mem(cs, cfg, h->l2_iste_addr, &h->l2_iste)) { return NULL; @@ -XXX,XX +XXX,XX @@ static void put_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg, * Once this has been called the L2_ISTE_Handle @h and the pointer * to the L2 ISTE word are no longer valid. */ + if (h->hashed) { + uint32_t l2_iste = *h->l2_iste_p; + if (!FIELD_EX32(l2_iste, L2_ISTE, PENDING)) { + /* + * We just made this not pending: remove from hash table + * and write back to memory. + */ + hwaddr l2_iste_addr; + + g_hash_table_remove(cfg->lpi_cache, GINT_TO_POINTER(h->id)); + if (get_l2_iste_addr(cs, cfg, h->id, &l2_iste_addr)) { + write_l2_iste_mem(cs, cfg, l2_iste_addr, l2_iste); + /* Writeback errors are ignored. */ + } + } + return; + } + + if (FIELD_EX32(h->l2_iste, L2_ISTE, PENDING)) { + /* + * We just made this pending: add it to the hash table, and + * don't bother writing it back to memory. + */ + uint32_t *hashvalue = g_new(uint32_t, 1); + *hashvalue = h->l2_iste; + g_hash_table_insert(cfg->lpi_cache, GINT_TO_POINTER(h->id), hashvalue); + return; + } write_l2_iste_mem(cs, cfg, h->l2_iste_addr, h->l2_iste); } @@ -XXX,XX +XXX,XX @@ txfail: "physical address 0x" HWADDR_FMT_plx "\n", intid, l1_addr); } +/* Data we need to pass through to irs_clean_lpi_cache_entry() */ +typedef struct CleanLPICacheUserData { + GICv5Common *cs; + GICv5ISTConfig *cfg; +} CleanLPICacheUserData; + +static gboolean irs_clean_lpi_cache_entry(gpointer key, gpointer value, + gpointer user_data) +{ + /* Drop this entry from the LPI cache, writing it back to guest memory. */ + CleanLPICacheUserData *ud = user_data; + hwaddr l2_iste_addr; + uint64_t id = GPOINTER_TO_INT(key); + uint32_t l2_iste = *(uint32_t *)value; + + if (!get_l2_iste_addr(ud->cs, ud->cfg, id, &l2_iste_addr) || + !write_l2_iste_mem(ud->cs, ud->cfg, l2_iste_addr, l2_iste)) { + /* We drop the cached entry regardless of writeback errors */ + return true; + } + return true; +} + +static void irs_clean_lpi_cache(GICv5Common *cs, GICv5ISTConfig *cfg) +{ + /* Write everything in the LPI cache out to guest memory */ + CleanLPICacheUserData ud; + ud.cs = cs; + ud.cfg = cfg; + + g_hash_table_foreach_remove(cfg->lpi_cache, irs_clean_lpi_cache_entry, &ud); +} + static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) /* Ignore 1->1 transition */ return; } + irs_clean_lpi_cache(cs, &s->phys_lpi_config[domain]); cs->irs_ist_baser[domain] = FIELD_DP64(cs->irs_ist_baser[domain], IRS_IST_BASER, VALID, valid); s->phys_lpi_config[domain].valid = false; @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) cfg->l2_idx_bits = l2_idx_bits; cfg->structure = FIELD_EX64(cs->irs_ist_cfgr[domain], IRS_IST_CFGR, STRUCTURE); + if (!cfg->lpi_cache) { + /* + * Keys are GINT_TO_POINTER(intid), so we want the g_direct_hash + * and g_direct_equal hash and equality functions. We don't + * want to free the keys, but we do want to free the values + * (which are pointer-to-uint32_t). + */ + cfg->lpi_cache = g_hash_table_new_full(NULL, NULL, NULL, g_free); + } cfg->valid = true; trace_gicv5_ist_valid(domain_name[domain], cfg->base, cfg->id_bits, cfg->l2_idx_bits, cfg->istsz, cfg->structure); @@ -XXX,XX +XXX,XX @@ static void gicv5_reset_hold(Object *obj, ResetType type) /* IRS_IST_BASER and IRS_IST_CFGR reset to 0, clear cached info */ for (int i = 0; i < NUM_GICV5_DOMAINS; i++) { s->phys_lpi_config[i].valid = false; + /* + * If we got reset (power-cycled) with data in the cache, don't + * write it out to guest memory; just return to "empty cache". + */ + if (s->phys_lpi_config[i].lpi_cache) { + g_hash_table_remove_all(s->phys_lpi_config[i].lpi_cache); + } } } diff --git a/include/hw/intc/arm_gicv5.h b/include/hw/intc/arm_gicv5.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5.h +++ b/include/hw/intc/arm_gicv5.h @@ -XXX,XX +XXX,XX @@ typedef struct GICv5ISTConfig { uint8_t istsz; /* L2 ISTE size in bytes */ bool structure; /* true if using 2-level table */ bool valid; /* true if this table is valid and usable */ + /* This caches IST information about pending LPIs */ + GHashTable *lpi_cache; } GICv5ISTConfig; /* -- 2.43.0
The CPU system register ICC_IAFFIDR_EL1 is a read-only register that tells the guest what the affinity ID of that CPU is. Implement this register. In real hardware using the stream protocol, the IRS tells the CPU its IAFFID using a DownstreamControl command as part of the handshake process when the IRS-CPU link is brought online. Our analogue of this is to pass the IAFFID as an extra argument to gicv5_set_gicv5state(). (We could have the CPU call into the GIC every time to ask for the value, but this would mean we had to search the cpus[] array for the right CPU to return its IAFFID.) Note that we don't put the IAFFID into the gicv5_cpuif sub-struct, because that part of the CPU struct is zeroed on reset, and we must keep the IAFFID across reset (we only set it up when the GIC device is created). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-35-peter.maydell@linaro.org --- hw/intc/arm_gicv5_common.c | 5 +++-- include/hw/intc/arm_gicv5_stream.h | 3 ++- target/arm/cpu.c | 5 +++-- target/arm/cpu.h | 2 ++ target/arm/tcg/gicv5-cpuif.c | 11 +++++++++++ 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/hw/intc/arm_gicv5_common.c b/hw/intc/arm_gicv5_common.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5_common.c +++ b/hw/intc/arm_gicv5_common.c @@ -XXX,XX +XXX,XX @@ static void gicv5_common_realize(DeviceState *dev, Error **errp) } for (int i = 0; i < cs->num_cpus; i++) { - if (!gicv5_set_gicv5state(cs->cpus[i], cs)) { + if (!gicv5_set_gicv5state(cs->cpus[i], cs, cs->cpu_iaffids[i])) { error_setg(errp, - "CPU %d does not implement GICv5 CPU interface", i); + "CPU %d (IAFFID 0x%x) does not implement GICv5 CPU interface", + i, cs->cpu_iaffids[i]); return; } } diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ typedef struct GICv5Common GICv5Common; * gicv5_set_gicv5state * @cpu: CPU object to tell about its IRS * @cs: the GIC IRS it is connected to + * @iaffid: the IAFFID of this CPU * * Set the CPU object's GICv5 pointer to point to this GIC IRS. The * IRS must call this when it is realized, for each CPU it is @@ -XXX,XX +XXX,XX @@ typedef struct GICv5Common GICv5Common; * Returns true on success, false if the CPU doesn't implement the * GICv5 CPU interface. */ -bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs); +bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs, uint32_t iaffid); /* * The architected Stream Protocol is asynchronous; commands can be diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ static void arm_cpu_dump_state(CPUState *cs, FILE *f, int flags) } #ifndef CONFIG_USER_ONLY -bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs) +bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs, uint32_t iaffid) { /* * Set this CPU's gicv5state pointer to point to the GIC that we are - * connected to. + * connected to, and record our IAFFID. */ if (!cpu_isar_feature(aa64_gcie, cpu)) { return false; } cpu->env.gicv5state = cs; + cpu->env.gicv5_iaffid = iaffid; return true; } #endif diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { void *gicv3state; /* Similarly, for a GICv5Common */ void *gicv5state; + /* For GICv5, this CPU's IAFFID */ + uint64_t gicv5_iaffid; #else /* CONFIG_USER_ONLY */ /* For usermode syscall translation. */ bool eabi; diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.icc_icsr_el1), .resetvalue = 0, }, + { .name = "ICC_IAFFIDR_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 5, + .access = PL1_R, .type = ARM_CP_NO_RAW, + /* ICC_IAFFIDR_EL1 holds the IAFFID only, in its low bits */ + .fieldoffset = offsetof(CPUARMState, gicv5_iaffid), + /* + * The field is a constant value set in gicv5_set_gicv5state(), + * so don't allow it to be overwritten by reset. + */ + .resetfn = arm_cp_reset_ignore, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
ICC_IDR0_EL1 is an identification register; we can implement this as a simple constant value. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-36-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(GIC_CDHM, HM, 32, 1) FIELD(GIC_CDRCFG, ID, 0, 24) FIELD(GIC_CDRCFG, TYPE, 29, 3) +FIELD(ICC_IDR0_EL1, ID_BITS, 0, 4) +FIELD(ICC_IDR0_EL1, PRI_BITS, 4, 4) +FIELD(ICC_IDR0_EL1, GCIE_LEGACY, 8, 4) + +/* + * We implement 24 bits of interrupt ID, the mandated 5 bits of priority, + * and no legacy GICv3.3 vcpu interface (yet) + */ +#define QEMU_ICC_IDR0 \ + ((4 << R_ICC_IDR0_EL1_PRI_BITS_SHIFT) | \ + (1 << R_ICC_IDR0_EL1_ID_BITS_SHIFT)) + static GICv5Common *gicv5_get_gic(CPUARMState *env) { return env->gicv5state; @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdhm_write, }, + { .name = "ICC_IDR0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 2, + .access = PL1_R, .type = ARM_CP_CONST | ARM_CP_NO_RAW, + .resetvalue = QEMU_ICC_IDR0, + }, { .name = "ICC_ICSR_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 4, .access = PL1_RW, .type = ARM_CP_NO_RAW, -- 2.43.0
In the GICv5 PPI state and control lives in the CPU interface; this is different from the GICv3 where this was all in the redistributor. Implement the access system registers for the PPI active state; this is a pair of registers, one of which has "write 1 to clear" behaviour and the other of which has "write 1 to set". In both cases, reads return the current state. We start here by implementing the accessors for the underlying state; we don't yet attempt to do anything (e.g. recalculating the highest priority pending PPI) when the state changes. That will come in subsequent commits. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-37-peter.maydell@linaro.org --- target/arm/cpu.h | 5 +++++ target/arm/tcg/gicv5-cpuif.c | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef enum ARMFPStatusFlavour { } ARMFPStatusFlavour; #define FPST_COUNT 10 +/* Architecturally there are 128 PPIs in a GICv5 */ +#define GICV5_NUM_PPIS 128 + typedef struct CPUArchState { /* Regs for current mode. */ uint32_t regs[16]; @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { struct { /* GICv5 CPU interface data */ uint64_t icc_icsr_el1; + /* Most PPI registers have 1 bit per PPI, so 64 PPIs to a register */ + uint64_t ppi_active[GICV5_NUM_PPIS / 64]; } gicv5_cpuif; struct { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static void gic_cdhm_write(CPUARMState *env, const ARMCPRegInfo *ri, gicv5_set_handling(gic, id, hm, domain, type, virtual); } +static void gic_ppi_cactive_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + uint64_t old = raw_read(env, ri); + raw_write(env, ri, old & ~value); +} + +static void gic_ppi_sactive_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + uint64_t old = raw_read(env, ri); + raw_write(env, ri, old | value); +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { */ .resetfn = arm_cp_reset_ignore, }, + { .name = "ICC_PPI_CACTIVER0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_ALIAS | ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_active[0]), + .writefn = gic_ppi_cactive_write, + }, + { .name = "ICC_PPI_CACTIVER1_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 1, + .access = PL1_RW, .type = ARM_CP_ALIAS | ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_active[1]), + .writefn = gic_ppi_cactive_write, + }, + { .name = "ICC_PPI_SACTIVER0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 2, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_active[0]), + .writefn = gic_ppi_sactive_write, + }, + { .name = "ICC_PPI_SACTIVER1_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 3, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_active[1]), + .writefn = gic_ppi_sactive_write, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
In the GICv5 the handling mode of a PPI is not software configurable; it is reported via read-only CPU interface registers ICC_PPI_HMR0_EL1 and ICC_PPI_HMR1_EL1. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-38-peter.maydell@linaro.org --- target/arm/cpu.h | 1 + target/arm/tcg/gicv5-cpuif.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { uint64_t icc_icsr_el1; /* Most PPI registers have 1 bit per PPI, so 64 PPIs to a register */ uint64_t ppi_active[GICV5_NUM_PPIS / 64]; + uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; } gicv5_cpuif; struct { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(ICC_IDR0_EL1, GCIE_LEGACY, 8, 4) ((4 << R_ICC_IDR0_EL1_PRI_BITS_SHIFT) | \ (1 << R_ICC_IDR0_EL1_ID_BITS_SHIFT)) +/* + * PPI handling modes are fixed and not software configurable. + * R_CFSKX defines them for the architected PPIs: they are all Level, + * except that PPI 24 (CTIIRQ) is IMPDEF and PPI 3 (SW_PPI) is Edge. + * For unimplemented PPIs the field is RES0. The PPI register bits + * are 1 for Level and 0 for Edge. + */ +#define PPI_HMR0_RESET (~(1ULL << GICV5_PPI_SW_PPI)) +#define PPI_HMR1_RESET (~0ULL) + static GICv5Common *gicv5_get_gic(CPUARMState *env) { return env->gicv5state; @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_active[1]), .writefn = gic_ppi_sactive_write, }, + { .name = "ICC_PPI_HMR0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 0, + .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_hm[0]), + .resetvalue = PPI_HMR0_RESET, + }, + { .name = "ICC_PPI_HMR1_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 1, + .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_hm[1]), + .resetvalue = PPI_HMR1_RESET, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
The GICv5 PPI pending status is handled by two registers, one of which is write-1-to-set and one of which is write-1-to-clear. The pending state is read-only for PPIs where the handling mode is Edge. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-39-peter.maydell@linaro.org --- target/arm/cpu.h | 1 + target/arm/tcg/gicv5-cpuif.c | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { /* Most PPI registers have 1 bit per PPI, so 64 PPIs to a register */ uint64_t ppi_active[GICV5_NUM_PPIS / 64]; uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; + uint64_t ppi_pend[GICV5_NUM_PPIS / 64]; } gicv5_cpuif; struct { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static void gic_ppi_sactive_write(CPUARMState *env, const ARMCPRegInfo *ri, raw_write(env, ri, old | value); } +static void gic_ppi_cpend_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + uint64_t old = raw_read(env, ri); + /* If ICC_PPI_HMR_EL1[n].HM is 1, PEND bits are RO */ + uint64_t hm = env->gicv5_cpuif.ppi_hm[ri->opc2 & 1]; + value &= ~hm; + raw_write(env, ri, old & ~value); +} + +static void gic_ppi_spend_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + uint64_t old = raw_read(env, ri); + /* If ICC_PPI_HMR_EL1[n].HM is 1, PEND bits are RO */ + uint64_t hm = env->gicv5_cpuif.ppi_hm[ri->opc2 & 1]; + value &= ~hm; + raw_write(env, ri, old | value); +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_hm[1]), .resetvalue = PPI_HMR1_RESET, }, + { .name = "ICC_PPI_CPENDR0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 4, + .access = PL1_RW, .type = ARM_CP_ALIAS | ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_pend[0]), + .writefn = gic_ppi_cpend_write, + }, + { .name = "ICC_PPI_CPENDR1_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 5, + .access = PL1_RW, .type = ARM_CP_ALIAS | ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_pend[1]), + .writefn = gic_ppi_cpend_write, + }, + { .name = "ICC_PPI_SPENDR0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 6, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_pend[0]), + .writefn = gic_ppi_spend_write, + }, + { .name = "ICC_PPI_SPENDR0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 7, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_pend[1]), + .writefn = gic_ppi_spend_write, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
Implement the GICv5 register which holds the enable state of PPIs: ICC_PPI_ENABLER<n>_EL1. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-40-peter.maydell@linaro.org --- target/arm/cpu.h | 1 + target/arm/tcg/gicv5-cpuif.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { uint64_t ppi_active[GICV5_NUM_PPIS / 64]; uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; uint64_t ppi_pend[GICV5_NUM_PPIS / 64]; + uint64_t ppi_enable[GICV5_NUM_PPIS / 64]; } gicv5_cpuif; struct { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static void gic_ppi_spend_write(CPUARMState *env, const ARMCPRegInfo *ri, raw_write(env, ri, old | value); } +static void gic_ppi_enable_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + raw_write(env, ri, value); +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_hm[1]), .resetvalue = PPI_HMR1_RESET, }, + { .name = "ICC_PPI_ENABLER0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 6, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_enable[0]), + .writefn = gic_ppi_enable_write, + }, + { .name = "ICC_PPI_ENABLER1_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 7, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_enable[1]), + .writefn = gic_ppi_enable_write, + }, { .name = "ICC_PPI_CPENDR0_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 13, .opc2 = 4, .access = PL1_RW, .type = ARM_CP_ALIAS | ARM_CP_IO | ARM_CP_NO_RAW, -- 2.43.0
Implement the GICv5 registers which hold the priority of the PPIs. Each 64-bit register has the priority fields for 8 PPIs, so there are 16 registers in total. This would be a lot of duplication if we wrote it out statically in the array, so instead create each register via a loop in define_gicv5_cpuif_regs(). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-41-peter.maydell@linaro.org --- target/arm/cpu.h | 2 ++ target/arm/tcg/gicv5-cpuif.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; uint64_t ppi_pend[GICV5_NUM_PPIS / 64]; uint64_t ppi_enable[GICV5_NUM_PPIS / 64]; + /* The PRIO regs have 1 byte per PPI, so 8 PPIs to a register */ + uint64_t ppi_priority[GICV5_NUM_PPIS / 8]; } gicv5_cpuif; struct { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static void gic_ppi_enable_write(CPUARMState *env, const ARMCPRegInfo *ri, raw_write(env, ri, value); } +static void gic_ppi_priority_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + raw_write(env, ri, value); +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ void define_gicv5_cpuif_regs(ARMCPU *cpu) { if (cpu_isar_feature(aa64_gcie, cpu)) { define_arm_cp_regs(cpu, gicv5_cpuif_reginfo); + + /* + * There are 16 ICC_PPI_PRIORITYR<n>_EL1 regs, so define them + * programmatically rather than listing them all statically. + */ + for (int i = 0; i < 16; i++) { + g_autofree char *name = g_strdup_printf("ICC_PPI_PRIORITYR%d_EL1", i); + ARMCPRegInfo ppi_prio = { + .name = name, .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, + .crm = 14 + (i >> 3), .opc2 = i & 7, + .access = PL1_RW, .type = ARM_CP_IO, + .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_priority[i]), + .writefn = gic_ppi_priority_write, .raw_writefn = raw_write, + }; + define_one_arm_cp_reg(cpu, &ppi_prio); + } } } -- 2.43.0
The ICC_APR_EL1 GICv5 cpuif register records the physical active priorities. Since the GICv5 always uses 5 bits of priority, this register always has 32 non-RES0 bits, and we don't need the complicated GICv3 setup where there might be 1, 2 or 4 APR registers. ICC_HAPR_EL1 is a read-only register which reports the current running priority. This is defined to be the lowest set bit (i.e. the highest priority) in the APR, or the Idle priority 0xff if there are no active interrupts, so it is effectively a convenience re-presentation of the APR register data. The APR register is banked per interrupt domain; ICC_APR_EL1 accesses the version of the register corresponding to the current logical interrupt domain. The APR data for the final domain (EL3) is accessed via ICC_APR_EL3. Although we are starting with an EL1-only implementation, we define the CPU state as banked here so we don't have to change our representation of it later when we add EL3 and RME support. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-42-peter.maydell@linaro.org --- include/hw/intc/arm_gicv5_types.h | 2 ++ target/arm/cpu.h | 2 ++ target/arm/tcg/gicv5-cpuif.c | 60 +++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ typedef enum GICv5TriggerMode { GICV5_TRIGGER_LEVEL = 1, } GICv5TriggerMode; +#define PRIO_IDLE 0xff + #endif diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ #include "target/arm/gtimer.h" #include "target/arm/cpu-sysregs.h" #include "target/arm/mmuidx.h" +#include "hw/intc/arm_gicv5_types.h" #define EXCP_UDEF 1 /* undefined instruction */ #define EXCP_SWI 2 /* software interrupt */ @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { struct { /* GICv5 CPU interface data */ uint64_t icc_icsr_el1; + uint64_t icc_apr[NUM_GICV5_DOMAINS]; /* Most PPI registers have 1 bit per PPI, so 64 PPIs to a register */ uint64_t ppi_active[GICV5_NUM_PPIS / 64]; uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static GICv5Domain gicv5_current_phys_domain(CPUARMState *env) return gicv5_logical_domain(env); } +static uint64_t gic_running_prio(CPUARMState *env, GICv5Domain domain) +{ + /* + * Return the current running priority; this is the lowest set bit in + * the Active Priority Register, or the idle priority if none (D_XMBQZ) + */ + uint64_t hap = ctz64(env->gicv5_cpuif.icc_apr[domain]); + return hap < 32 ? hap : PRIO_IDLE; +} + static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { @@ -XXX,XX +XXX,XX @@ static void gic_ppi_priority_write(CPUARMState *env, const ARMCPRegInfo *ri, raw_write(env, ri, value); } +/* + * ICC_APR_EL1 is banked and reads/writes as the version for the + * current logical interrupt domain. + */ +static void gic_icc_apr_el1_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + /* + * With an architectural 5 bits of priority, this register has 32 + * non-RES0 bits + */ + GICv5Domain domain = gicv5_logical_domain(env); + value &= 0xffffffff; + env->gicv5_cpuif.icc_apr[domain] = value; +} + +static uint64_t gic_icc_apr_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + GICv5Domain domain = gicv5_logical_domain(env); + return env->gicv5_cpuif.icc_apr[domain]; +} + +static void gic_icc_apr_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.icc_apr); i++) { + env->gicv5_cpuif.icc_apr[i] = 0; + } +} + +static uint64_t gic_icc_hapr_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + /* + * ICC_HAPR_EL1 reports the current running priority, which can be + * calculated from the APR register. + */ + return gic_running_prio(env, gicv5_current_phys_domain(env)); +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_pend[1]), .writefn = gic_ppi_spend_write, }, + { .name = "ICC_APR_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gic_icc_apr_el1_read, + .writefn = gic_icc_apr_el1_write, + .resetfn = gic_icc_apr_el1_reset, + }, + { .name = "ICC_HAPR_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 3, + .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gic_icc_hapr_el1_read, .raw_writefn = arm_cp_write_ignore, + }, }; void define_gicv5_cpuif_regs(ARMCPU *cpu) -- 2.43.0
When the state of PPIs changes, recalculate the highest priority PPI. In subsequent commits we will use this cached value to provide the HPPI info to the guest, decide whether to signal IRQ or FIQ, handle interrupt acknowldge from the guest, and so on. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-43-peter.maydell@linaro.org --- include/hw/intc/arm_gicv5_types.h | 21 +++++++++++ meson.build | 1 + target/arm/cpu.h | 3 ++ target/arm/tcg/gicv5-cpuif.c | 58 +++++++++++++++++++++++++++++++ target/arm/tcg/trace-events | 5 +++ target/arm/tcg/trace.h | 1 + 6 files changed, 89 insertions(+) create mode 100644 target/arm/tcg/trace-events create mode 100644 target/arm/tcg/trace.h diff --git a/include/hw/intc/arm_gicv5_types.h b/include/hw/intc/arm_gicv5_types.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_types.h +++ b/include/hw/intc/arm_gicv5_types.h @@ -XXX,XX +XXX,XX @@ #ifndef HW_INTC_ARM_GICv5_TYPES_H #define HW_INTC_ARM_GICv5_TYPES_H +#include "hw/core/registerfields.h" + /* * The GICv5 has four physical Interrupt Domains. This numbering must * match the encoding used in IRS_IDR0.INT_DOM. @@ -XXX,XX +XXX,XX @@ typedef enum GICv5TriggerMode { #define PRIO_IDLE 0xff +/* + * We keep track of candidate highest possible pending interrupts + * using this struct. + * + * Unlike GICv3, we don't need a separate NMI bool, because for GICv5 + * superpriority is signaled by @prio == 0. + * + * In this struct the intid includes the interrupt type in bits + * [31:29] (i.e. it is in the form defined by R_TJPHS). + */ +typedef struct GICv5PendingIrq { + uint32_t intid; + uint8_t prio; +} GICv5PendingIrq; + +/* Fields in a generic 32-bit INTID, per R_TJPHS */ +FIELD(INTID, ID, 0, 24) +FIELD(INTID, TYPE, 29, 3) + #endif diff --git a/meson.build b/meson.build index XXXXXXX..XXXXXXX 100644 --- a/meson.build +++ b/meson.build @@ -XXX,XX +XXX,XX @@ if have_system or have_user 'hw/core', 'target/arm', 'target/arm/hvf', + 'target/arm/tcg', 'target/hppa', 'target/i386', 'target/i386/kvm', diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { uint64_t ppi_enable[GICV5_NUM_PPIS / 64]; /* The PRIO regs have 1 byte per PPI, so 8 PPIs to a register */ uint64_t ppi_priority[GICV5_NUM_PPIS / 8]; + + /* Cached highest-priority pending PPI for each domain */ + GICv5PendingIrq ppi_hppi[NUM_GICV5_DOMAINS]; } gicv5_cpuif; struct { diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ #include "internals.h" #include "cpregs.h" #include "hw/intc/arm_gicv5_stream.h" +#include "trace.h" FIELD(GIC_CDPRI, ID, 0, 24) FIELD(GIC_CDPRI, TYPE, 29, 3) @@ -XXX,XX +XXX,XX @@ static uint64_t gic_running_prio(CPUARMState *env, GICv5Domain domain) return hap < 32 ? hap : PRIO_IDLE; } +static void gic_recalc_ppi_hppi(CPUARMState *env) +{ + /* + * Recalculate the HPPI PPI: this is the best PPI which is + * enabled, pending and not active. + */ + for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.ppi_hppi); i++) { + env->gicv5_cpuif.ppi_hppi[i].intid = 0; + env->gicv5_cpuif.ppi_hppi[i].prio = PRIO_IDLE; + }; + + for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.ppi_active); i++) { + uint64_t en_pend_nact = env->gicv5_cpuif.ppi_enable[i] & + env->gicv5_cpuif.ppi_pend[i] & + ~env->gicv5_cpuif.ppi_active[i]; + + while (en_pend_nact) { + /* + * When EL3 is supported ICC_PPI_DOMAINR<n>_EL3 tells us + * the domain of each PPI. While we only support EL1, the + * domain is always NS. + */ + GICv5Domain ppi_domain = GICV5_ID_NS; + uint8_t prio; + int ppi; + int bit = ctz64(en_pend_nact); + + en_pend_nact &= ~(1 << bit); + + ppi = i * 64 + bit; + prio = extract64(env->gicv5_cpuif.ppi_priority[ppi / 8], + (ppi & 7) * 8, 5); + + if (prio < env->gicv5_cpuif.ppi_hppi[ppi_domain].prio) { + uint32_t intid = 0; + + intid = FIELD_DP32(intid, INTID, ID, ppi); + intid = FIELD_DP32(intid, INTID, TYPE, GICV5_PPI); + env->gicv5_cpuif.ppi_hppi[ppi_domain].intid = intid; + env->gicv5_cpuif.ppi_hppi[ppi_domain].prio = prio; + } + } + } + + for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.ppi_hppi); i++) { + trace_gicv5_recalc_ppi_hppi(i, + env->gicv5_cpuif.ppi_hppi[i].intid, + env->gicv5_cpuif.ppi_hppi[i].prio); + } +} + static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { @@ -XXX,XX +XXX,XX @@ static void gic_ppi_cactive_write(CPUARMState *env, const ARMCPRegInfo *ri, { uint64_t old = raw_read(env, ri); raw_write(env, ri, old & ~value); + gic_recalc_ppi_hppi(env); } static void gic_ppi_sactive_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -XXX,XX +XXX,XX @@ static void gic_ppi_sactive_write(CPUARMState *env, const ARMCPRegInfo *ri, { uint64_t old = raw_read(env, ri); raw_write(env, ri, old | value); + gic_recalc_ppi_hppi(env); } static void gic_ppi_cpend_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -XXX,XX +XXX,XX @@ static void gic_ppi_cpend_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t hm = env->gicv5_cpuif.ppi_hm[ri->opc2 & 1]; value &= ~hm; raw_write(env, ri, old & ~value); + gic_recalc_ppi_hppi(env); } static void gic_ppi_spend_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -XXX,XX +XXX,XX @@ static void gic_ppi_spend_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t hm = env->gicv5_cpuif.ppi_hm[ri->opc2 & 1]; value &= ~hm; raw_write(env, ri, old | value); + gic_recalc_ppi_hppi(env); } static void gic_ppi_enable_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { raw_write(env, ri, value); + gic_recalc_ppi_hppi(env); } static void gic_ppi_priority_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { raw_write(env, ri, value); + gic_recalc_ppi_hppi(env); } /* diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/arm/tcg/trace-events @@ -XXX,XX +XXX,XX @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# See docs/devel/tracing.rst for syntax documentation. + +# gicv5-cpuif.c +gicv5_recalc_ppi_hppi(int domain, uint32_t id, uint8_t prio) "domain %d new PPI HPPI id 0x%x prio %u" diff --git a/target/arm/tcg/trace.h b/target/arm/tcg/trace.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/target/arm/tcg/trace.h @@ -0,0 +1 @@ +#include "trace/trace-target_arm_tcg.h" -- 2.43.0
The IRS is required to present the highest priority pending interrupt that it has for each domain for each cpu interface. We implement this in the irs_recalc_hppi() function, which we call at every point where some relevant IRS state changes. This function calls gicv5_forward_interrupt() to do the equivalent of the GICv5 stream protocol Forward and Recall commands. For the moment we simply record the HPPI on the CPU interface side without trying to process it; the handling of the HPPI in the cpuif will be added in subsequent commits. There are some cases where we could skip doing the full HPPI recalculation, e.g. when the guest changes the config of an interrupt that is disabled; we expect that the guest will only do interrupt config at startup, so we don't attempt to optimise this. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-44-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 202 +++++++++++++++++++++++++++++ hw/intc/trace-events | 2 + include/hw/intc/arm_gicv5.h | 3 + include/hw/intc/arm_gicv5_stream.h | 24 ++++ target/arm/tcg/gicv5-cpuif.c | 9 ++ 5 files changed, 240 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain) }; } +/* Data we need to pass through to lpi_cache_get_hppi() */ +typedef struct GetHPPIUserData { + GICv5PendingIrq *best; + uint32_t iaffid; +} GetHPPIUserData; + +static void lpi_cache_get_hppi(gpointer key, gpointer value, gpointer user_data) +{ + uint64_t id = GPOINTER_TO_INT(key); + uint64_t l2_iste = *(uint64_t *)value; + uint32_t prio, iaffid; + GetHPPIUserData *ud = user_data; + + if ((l2_iste & (R_L2_ISTE_PENDING_MASK | R_L2_ISTE_ACTIVE_MASK | R_L2_ISTE_ENABLE_MASK)) + != (R_L2_ISTE_PENDING_MASK | R_L2_ISTE_ENABLE_MASK)) { + return; + } + prio = FIELD_EX32(l2_iste, L2_ISTE, PRIORITY); + iaffid = FIELD_EX32(l2_iste, L2_ISTE, IAFFID); + if (iaffid == ud->iaffid && prio < ud->best->prio) { + id = FIELD_DP32(id, INTID, TYPE, GICV5_LPI); + ud->best->intid = id; + ud->best->prio = prio; + } +} + +static int irs_cpuidx_from_iaffid(GICv5Common *cs, uint32_t iaffid) +{ + for (int i = 0; i < cs->num_cpus; i++) { + if (cs->cpu_iaffids[i] == iaffid) { + return i; + } + } + return -1; +} + +static void irs_recalc_hppi(GICv5 *s, GICv5Domain domain, uint32_t iaffid) +{ + /* + * Recalculate the highest priority pending interrupt for the + * specified domain and cpuif. HPPI candidates must be pending, + * inactive and enabled. + */ + GICv5Common *cs = ARM_GICV5_COMMON(s); + int cpuidx = irs_cpuidx_from_iaffid(cs, iaffid); + ARMCPU *cpu = cpuidx >= 0 ? cs->cpus[cpuidx] : NULL; + GICv5PendingIrq best; + + best.intid = 0; + best.prio = PRIO_IDLE; + + if (!cpu) { + /* Nothing happens for iaffids targeting nonexistent CPUs */ + trace_gicv5_irs_recalc_hppi_fail(domain_name[domain], iaffid, + "IAFFID doesn't match any CPU"); + return; + } + + if (!FIELD_EX32(cs->irs_cr0[domain], IRS_CR0, IRSEN)) { + /* When the IRS is disabled we don't forward HPPIs */ + trace_gicv5_irs_recalc_hppi_fail(domain_name[domain], iaffid, + "IRS_CR0.IRSEN is zero"); + return; + } + + if (s->phys_lpi_config[domain].valid) { + GetHPPIUserData ud; + + ud.best = &best; + ud.iaffid = iaffid; + g_hash_table_foreach(s->phys_lpi_config[domain].lpi_cache, + lpi_cache_get_hppi, &ud); + } + + /* + * OPT: consider also caching the SPI interrupt information, + * similarly to how we handle LPIs, if iterating through the whole + * SPI array every time is too expensive. + */ + for (int i = 0; i < cs->spi_irs_range; i++) { + GICv5SPIState *spi = &cs->spi[i]; + + if (spi->active || !spi->pending || !spi->enabled) { + continue; + } + if (spi->domain != domain || spi->iaffid != iaffid) { + continue; + } + if (spi->priority < best.prio) { + uint32_t intid = 0; + intid = FIELD_DP32(intid, INTID, ID, i); + intid = FIELD_DP32(intid, INTID, TYPE, GICV5_SPI); + best.intid = intid; + best.prio = spi->priority; + } + } + + trace_gicv5_irs_recalc_hppi(domain_name[domain], iaffid, + best.intid, best.prio); + + s->hppi[domain][cpuidx] = best; + /* + * Now present the HPPI to the cpuif. In the real hardware stream + * protocol, the connection between IRS and cpuif is asynchronous, + * and so both ends track their idea of the current HPPI, with a + * back-and-forth sequence so they stay in sync and more + * interaction when the cpuif resets. For QEMU, we are strictly + * synchronous and the cpuif asking the IRS for data is a cheap + * function call, so we simplify this: + * - the IRS knows what the current HPPI is + * - s->hppi[][] is a cache we can recalculate + * - the IRS merely tells the cpuif "something changed", and + * the cpuif asks for the current HPPI when it needs it + * - the cpuif does not cache the HPPI on its end + */ + gicv5_forward_interrupt(cpu, domain); +} + +static void irs_recalc_hppi_all_cpus(GICv5 *s, GICv5Domain domain) +{ + /* + * Recalculate the HPPI for every CPU for this domain. This is + * not as efficient as it could be because we will scan through + * the LPI cached hash table and the SPI array for each CPU rather + * than doing a single combined scan, but we only need to do this + * very rarely, when the guest enables or disables the IST, so we + * implement this the simple way. + */ + GICv5Common *cs = ARM_GICV5_COMMON(s); + for (int i = 0; i < cs->num_cpus; i++) { + irs_recalc_hppi(s, domain, cs->cpu_iaffids[i]); + } +} + +static void irs_recall_hppis(GICv5 *s, GICv5Domain domain) +{ + /* + * The IRS was just disabled -- we must recall any pending HPPIs + * we have sent to the CPU interfaces. For us this means that we + * clear our cached HPPI data and tell the cpuif that it has + * changed. + */ + GICv5Common *cs = ARM_GICV5_COMMON(s); + + for (int i = 0; i < cs->num_cpus; i++) { + s->hppi[domain][i].intid = 0; + s->hppi[domain][i].prio = PRIO_IDLE; + gicv5_forward_interrupt(cs->cpus[i], domain); + } +} + static hwaddr l1_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg, uint32_t id) { @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, GICv5Domain domain, GICv5IntType type, bool virtual) { GICv5 *s = ARM_GICV5(cs); + uint32_t iaffid; trace_gicv5_set_priority(domain_name[domain], inttype_name(type), virtual, id, priority); @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, return; } *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PRIORITY, priority); + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID); put_l2_iste(cs, cfg, &h); break; } @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, } spi->priority = priority; + iaffid = spi->iaffid; break; } default: @@ -XXX,XX +XXX,XX @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority, "priority of bad interrupt type %d\n", type); return; } + + irs_recalc_hppi(s, domain, iaffid); } void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled, GICv5Domain domain, GICv5IntType type, bool virtual) { GICv5 *s = ARM_GICV5(cs); + uint32_t iaffid; trace_gicv5_set_enabled(domain_name[domain], inttype_name(type), virtual, id, enabled); @@ -XXX,XX +XXX,XX @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled, return; } *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ENABLE, enabled); + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID); put_l2_iste(cs, cfg, &h); break; } @@ -XXX,XX +XXX,XX @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled, } spi->enabled = true; + iaffid = spi->iaffid; break; } default: @@ -XXX,XX +XXX,XX @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled, "enable state of bad interrupt type %d\n", type); return; } + + irs_recalc_hppi(s, domain, iaffid); } void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending, GICv5Domain domain, GICv5IntType type, bool virtual) { GICv5 *s = ARM_GICV5(cs); + uint32_t iaffid; trace_gicv5_set_pending(domain_name[domain], inttype_name(type), virtual, id, pending); @@ -XXX,XX +XXX,XX @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending, return; } *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PENDING, pending); + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID); put_l2_iste(cs, cfg, &h); break; } @@ -XXX,XX +XXX,XX @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending, } spi->pending = true; + iaffid = spi->iaffid; break; } default: @@ -XXX,XX +XXX,XX @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending, "pending state of bad interrupt type %d\n", type); return; } + + irs_recalc_hppi(s, domain, iaffid); } void gicv5_set_handling(GICv5Common *cs, uint32_t id, @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, GICv5IntType type, bool virtual) { GICv5 *s = ARM_GICV5(cs); + uint32_t old_iaffid; trace_gicv5_set_target(domain_name[domain], inttype_name(type), virtual, id, iaffid, irm); @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, * L2_ISTE.IRM is RES0. We never read it, and we can skip * explicitly writing it to zero here. */ + old_iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID); *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, IAFFID, iaffid); put_l2_iste(cs, cfg, &h); break; @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, return; } + old_iaffid = spi->iaffid; spi->iaffid = iaffid; break; } @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, "target of bad interrupt type %d\n", type); return; } + + irs_recalc_hppi(s, domain, old_iaffid); + irs_recalc_hppi(s, domain, iaffid); } static uint64_t l2_iste_to_icsr(GICv5Common *cs, const GICv5ISTConfig *cfg, @@ -XXX,XX +XXX,XX @@ static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value) if (res != MEMTX_OK) { goto txfail; } + /* + * It's CONSTRAINED UNPREDICTABLE to make an L2 IST valid when + * some of its entries have Pending already set, so we don't need + * to go through looking for Pending bits and pulling them into + * the cache, and we don't need to recalc our HPPI. + */ return; txfail: @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) IRS_IST_BASER, VALID, valid); s->phys_lpi_config[domain].valid = false; trace_gicv5_ist_invalid(domain_name[domain]); + irs_recalc_hppi_all_cpus(s, domain); return; } cs->irs_ist_baser[domain] = value; @@ -XXX,XX +XXX,XX @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value) cfg->valid = true; trace_gicv5_ist_valid(domain_name[domain], cfg->base, cfg->id_bits, cfg->l2_idx_bits, cfg->istsz, cfg->structure); + irs_recalc_hppi_all_cpus(s, domain); } } @@ -XXX,XX +XXX,XX @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset, case A_IRS_CR0: /* Enabling is instantaneous for us so IDLE is always 1 */ *data = cs->irs_cr0[domain] | R_IRS_CR0_IDLE_MASK; + if (FIELD_EX32(cs->irs_cr0[domain], IRS_CR0, IRSEN)) { + irs_recalc_hppi_all_cpus(s, domain); + } else { + irs_recall_hppis(s, domain); + } return true; case A_IRS_CR1: *data = cs->irs_cr1[domain]; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, } else if (spi->level) { spi->pending = false; } + irs_recalc_hppi(s, spi->domain, spi->iaffid); } } return true; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, /* this is RAZ/WI except for the EL3 domain */ GICv5SPIState *spi = spi_for_selr(cs, domain); if (spi) { + GICv5Domain old_domain = spi->domain; spi->domain = FIELD_EX32(data, IRS_SPI_DOMAINR, DOMAIN); + if (spi->domain != old_domain) { + irs_recalc_hppi(s, old_domain, spi->iaffid); + irs_recalc_hppi(s, spi->domain, spi->iaffid); + } } } return true; @@ -XXX,XX +XXX,XX @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset, if (spi) { spi_sample(spi); + irs_recalc_hppi(s, spi->domain, spi->iaffid); } trace_gicv5_spi_state(id, spi->level, spi->pending, spi->active); return true; @@ -XXX,XX +XXX,XX @@ static void gicv5_set_spi(void *opaque, int irq, int level) { /* These irqs are all SPIs; the INTID is irq + s->spi_base */ GICv5Common *cs = ARM_GICV5_COMMON(opaque); + GICv5 *s = ARM_GICV5(cs); uint32_t spi_id = irq + cs->spi_base; GICv5SPIState *spi = gicv5_raw_spi_state(cs, spi_id); @@ -XXX,XX +XXX,XX @@ static void gicv5_set_spi(void *opaque, int irq, int level) spi->level = level; spi_sample(spi); trace_gicv5_spi_state(spi_id, spi->level, spi->pending, spi->active); + + irs_recalc_hppi(s, spi->domain, spi->iaffid); } static void gicv5_reset_hold(Object *obj, ResetType type) @@ -XXX,XX +XXX,XX @@ static void gicv5_set_idregs(GICv5Common *cs) static void gicv5_realize(DeviceState *dev, Error **errp) { + GICv5 *s = ARM_GICV5(dev); GICv5Common *cs = ARM_GICV5_COMMON(dev); GICv5Class *gc = ARM_GICV5_GET_CLASS(dev); Error *migration_blocker = NULL; @@ -XXX,XX +XXX,XX @@ static void gicv5_realize(DeviceState *dev, Error **errp) gicv5_set_idregs(cs); gicv5_common_init_irqs_and_mmio(cs, gicv5_set_spi, config_frame_ops); + + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) { + if (gicv5_domain_implemented(cs, i)) { + s->hppi[i] = g_new0(GICv5PendingIrq, cs->num_cpus); + } + } } static void gicv5_init(Object *obj) diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d" gicv5_request_config(const char *domain, const char *type, bool virtual, uint32_t id, uint64_t icsr) "GICv5 IRS RequestConfig %s %s virtual:%d ID %u ICSR 0x%" PRIx64 gicv5_spi_state(uint32_t spi_id, bool level, bool pending, bool active) "GICv5 IRS SPI ID %u now level %d pending %d active %d" +gicv5_irs_recalc_hppi_fail(const char *domain, uint32_t iaffid, const char *reason) "GICv5 IRS %s IAFFID %u: no HPPI: %s" +gicv5_irs_recalc_hppi(const char *domain, uint32_t iaffid, uint32_t id, uint8_t prio) "GICv5 IRS %s IAFFID %u: new HPPI ID 0x%x prio %u" # arm_gicv5_common.c gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u" diff --git a/include/hw/intc/arm_gicv5.h b/include/hw/intc/arm_gicv5.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5.h +++ b/include/hw/intc/arm_gicv5.h @@ -XXX,XX +XXX,XX @@ struct GICv5 { /* This is the info from IRS_IST_BASER and IRS_IST_CFGR */ GICv5ISTConfig phys_lpi_config[NUM_GICV5_DOMAINS]; + + /* We cache the HPPI for each CPU for each domain here */ + GICv5PendingIrq *hppi[NUM_GICV5_DOMAINS]; }; struct GICv5Class { diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain, GICv5IntType type, bool virtual); +/** + * gicv5_forward_interrupt + * @cpu: CPU interface to forward interrupt to + * @domain: domain this interrupt is for + * + * Tell the CPU interface that the highest priority pending interrupt + * that the IRS has available for it has changed. This is the + * equivalent of the stream protocol's Forward packet, and also of its + * Recall packet. + * + * The stream protocol makes this asynchronous, allowing two Forward + * packets to be in flight and requiring an acknowledge, because the + * cpuif might be about to activate the previous forwarded interrupt + * while we are trying to tell it about a new one. But for QEMU we + * hold the BQL, so we know the vcpu might be executing guest code but + * it cannot be in the middle of changing cpuif state. So we can just + * synchronously tell it that a new HPPI exists (which might cause it + * to assert IRQ or FIQ to itself); this works as if the cpuif gave us + * a Release for the old HPPI. The cpuif will ask the IRS for the + * HPPI info via a function call, so we do not need to pass it across + * here. + */ +void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain); + #endif diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static void gic_recalc_ppi_hppi(CPUARMState *env) } } +void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain) +{ + /* + * For now, we do nothing. Later we will recalculate the overall + * HPPI by combining the IRS HPPI with the PPI HPPI, and possibly + * signal IRQ/FIQ. + */ +} + static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { -- 2.43.0
Implement ICC_CR0_EL1, which is the main control register. This is banked between interrupt domains in the same way as ICC_APR_*. The GICv5 spec assumes that typically there will need to be a hardware handshake between the CPU and the IRS, which is kicked off by guest software setting a LINK bit in this register to bring the link between the two online. However it is permitted to have an implementation where the link is permanently up. We take advantage of this, so our LINK and LINK_IDLE bits are read-only and always 1. This means the only interesting bit in this register for us is the main enable bit: when disabled for a domain, the cpuif considers that there is never an available highest priority interrupt. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-45-peter.maydell@linaro.org --- target/arm/cpu.h | 1 + target/arm/tcg/gicv5-cpuif.c | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { /* GICv5 CPU interface data */ uint64_t icc_icsr_el1; uint64_t icc_apr[NUM_GICV5_DOMAINS]; + uint64_t icc_cr0[NUM_GICV5_DOMAINS]; /* Most PPI registers have 1 bit per PPI, so 64 PPIs to a register */ uint64_t ppi_active[GICV5_NUM_PPIS / 64]; uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(ICC_IDR0_EL1, ID_BITS, 0, 4) FIELD(ICC_IDR0_EL1, PRI_BITS, 4, 4) FIELD(ICC_IDR0_EL1, GCIE_LEGACY, 8, 4) +FIELD(ICC_CR0, EN, 0, 1) +FIELD(ICC_CR0, LINK, 1, 1) +FIELD(ICC_CR0, LINK_IDLE, 2, 1) +FIELD(ICC_CR0, IPPT, 32, 6) +FIELD(ICC_CR0, PID, 38, 1) + /* * We implement 24 bits of interrupt ID, the mandated 5 bits of priority, * and no legacy GICv3.3 vcpu interface (yet) @@ -XXX,XX +XXX,XX @@ static uint64_t gic_icc_hapr_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) return gic_running_prio(env, gicv5_current_phys_domain(env)); } +/* ICC_CR0_EL1 is also banked */ +static uint64_t gic_icc_cr0_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + GICv5Domain domain = gicv5_logical_domain(env); + return env->gicv5_cpuif.icc_cr0[domain]; +} + +static void gic_icc_cr0_el1_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + /* + * For our implementation the link to the IRI is always connected, + * so LINK and LINK_IDLE are always 1. Without EL3, PID and IPPT + * are RAZ/WI, so the only writeable bit is the main enable bit EN. + */ + GICv5Domain domain = gicv5_logical_domain(env); + value &= R_ICC_CR0_EN_MASK; + value |= R_ICC_CR0_LINK_MASK | R_ICC_CR0_LINK_IDLE_MASK; + + env->gicv5_cpuif.icc_cr0[domain] = value; +} + +static void gic_icc_cr0_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + /* The link is always connected so we reset with LINK and LINK_IDLE set */ + for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.icc_cr0); i++) { + env->gicv5_cpuif.icc_cr0[i] = + R_ICC_CR0_LINK_MASK | R_ICC_CR0_LINK_IDLE_MASK; + } +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .writefn = gic_icc_apr_el1_write, .resetfn = gic_icc_apr_el1_reset, }, + { .name = "ICC_CR0_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 1, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gic_icc_cr0_el1_read, + .writefn = gic_icc_cr0_el1_write, + .resetfn = gic_icc_cr0_el1_reset, + }, { .name = "ICC_HAPR_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 3, .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, -- 2.43.0
Implement the ICC_PCR_* registers. These hold the physical priority mask for each interrupt domain -- an HPPI is only sufficiently high priority to preempt if it is higher priority than this mask value. Here we just implement the access to this data. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-46-peter.maydell@linaro.org --- target/arm/cpu.h | 1 + target/arm/tcg/gicv5-cpuif.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ typedef struct CPUArchState { uint64_t icc_icsr_el1; uint64_t icc_apr[NUM_GICV5_DOMAINS]; uint64_t icc_cr0[NUM_GICV5_DOMAINS]; + uint64_t icc_pcr[NUM_GICV5_DOMAINS]; /* Most PPI registers have 1 bit per PPI, so 64 PPIs to a register */ uint64_t ppi_active[GICV5_NUM_PPIS / 64]; uint64_t ppi_hm[GICV5_NUM_PPIS / 64]; diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(ICC_CR0, LINK_IDLE, 2, 1) FIELD(ICC_CR0, IPPT, 32, 6) FIELD(ICC_CR0, PID, 38, 1) +FIELD(ICC_PCR, PRIORITY, 0, 5) + /* * We implement 24 bits of interrupt ID, the mandated 5 bits of priority, * and no legacy GICv3.3 vcpu interface (yet) @@ -XXX,XX +XXX,XX @@ static void gic_icc_cr0_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) } } +static uint64_t gic_icc_pcr_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + GICv5Domain domain = gicv5_logical_domain(env); + return env->gicv5_cpuif.icc_pcr[domain]; +} + +static void gic_icc_pcr_el1_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + GICv5Domain domain = gicv5_logical_domain(env); + + value &= R_ICC_PCR_PRIORITY_MASK; + env->gicv5_cpuif.icc_pcr[domain] = value; +} + +static void gic_icc_pcr_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) +{ + for (int i = 0; i < ARRAY_SIZE(env->gicv5_cpuif.icc_pcr); i++) { + env->gicv5_cpuif.icc_pcr[i] = 0; + } +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .writefn = gic_icc_cr0_el1_write, .resetfn = gic_icc_cr0_el1_reset, }, + { .name = "ICC_PCR_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 2, + .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gic_icc_pcr_el1_read, + .writefn = gic_icc_pcr_el1_write, + .resetfn = gic_icc_pcr_el1_reset, + }, { .name = "ICC_HAPR_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 1, .crn = 12, .crm = 0, .opc2 = 3, .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, -- 2.43.0
Implement ICC_HPPIR_EL1, which the guest can use to read the current highest priority pending interrupt. Like APR, PCR and CR0, this is banked, with the _EL1 register reading the answer for the current logical interrupt domain, and the _EL3 register reading the answer for the EL3 interrupt domain. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-47-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 10 +++++ include/hw/intc/arm_gicv5_stream.h | 13 +++++++ target/arm/tcg/gicv5-cpuif.c | 61 ++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ static void irs_recall_hppis(GICv5 *s, GICv5Domain domain) } } +GICv5PendingIrq gicv5_get_hppi(GICv5Common *cs, GICv5Domain domain, + uint32_t iaffid) +{ + GICv5 *s = ARM_GICV5(cs); + int cpuidx = irs_cpuidx_from_iaffid(cs, iaffid); + + assert(cpuidx >= 0); + return s->hppi[domain][cpuidx]; +} + static hwaddr l1_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg, uint32_t id) { diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain, */ void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain); +/** + * gicv5_get_hppi + * @cs: GIC IRS to query + * @domain: interrupt domain to act on + * @iaffid: IAFFID of this CPU interface + * + * Ask the IRS for the highest priority pending interrupt that it has + * for this CPU. This returns the equivalent of what in the stream + * protocol is the outstanding interrupt sent with a Forward packet. + */ +GICv5PendingIrq gicv5_get_hppi(GICv5Common *cs, GICv5Domain domain, + uint32_t iaffid); + #endif diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(ICC_CR0, PID, 38, 1) FIELD(ICC_PCR, PRIORITY, 0, 5) +FIELD(ICC_HPPIR_EL1, ID, 0, 24) +FIELD(ICC_HPPIR_EL1, TYPE, 29, 3) +FIELD(ICC_HPPIR_EL1, HPPIV, 32, 1) + /* * We implement 24 bits of interrupt ID, the mandated 5 bits of priority, * and no legacy GICv3.3 vcpu interface (yet) @@ -XXX,XX +XXX,XX @@ static uint64_t gic_running_prio(CPUARMState *env, GICv5Domain domain) return hap < 32 ? hap : PRIO_IDLE; } +static GICv5PendingIrq gic_hppi(CPUARMState *env, GICv5Domain domain) +{ + /* + * Return the current highest priority pending interrupt for the + * specified domain, if it has sufficient priority to preempt. The + * intid field of the return value will be in the format of the + * ICC_HPPIR register (and will be zero if and only if there is no + * interrupt that can preempt). + */ + + GICv5Common *gic = gicv5_get_gic(env); + GICv5PendingIrq best, irs_hppi; + + if (!(env->gicv5_cpuif.icc_cr0[domain] & R_ICC_CR0_EN_MASK)) { + /* If cpuif is disabled there is no HPPI */ + return (GICv5PendingIrq) { .intid = 0, .prio = PRIO_IDLE }; + } + + irs_hppi = gicv5_get_hppi(gic, domain, env->gicv5_iaffid); + + /* + * If the best PPI and the best interrupt from the IRS have the + * same priority, it's IMPDEF which we pick (R_VVBPS). We choose + * the PPI. + */ + if (env->gicv5_cpuif.ppi_hppi[domain].prio <= irs_hppi.prio) { + best = env->gicv5_cpuif.ppi_hppi[domain]; + } else { + best = irs_hppi; + } + + /* + * D_MSQKF: an interrupt has sufficient priority if its priority + * is higher than the current running priority and equal to or + * higher than the priority mask. + */ + if (best.prio == PRIO_IDLE || + best.prio > env->gicv5_cpuif.icc_pcr[domain] || + best.prio >= gic_running_prio(env, domain)) { + return (GICv5PendingIrq) { .intid = 0, .prio = PRIO_IDLE }; + } + best.intid |= R_ICC_HPPIR_EL1_HPPIV_MASK; + return best; +} + static void gic_recalc_ppi_hppi(CPUARMState *env) { /* @@ -XXX,XX +XXX,XX @@ static void gic_icc_pcr_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) } } +static uint64_t gic_icc_hppir_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + GICv5Domain domain = gicv5_logical_domain(env); + GICv5PendingIrq hppi = gic_hppi(env, domain); + return hppi.intid; +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .fieldoffset = offsetof(CPUARMState, gicv5_cpuif.ppi_hm[1]), .resetvalue = PPI_HMR1_RESET, }, + { .name = "ICC_HPPIR_EL1", .state = ARM_CP_STATE_AA64, + .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 3, + .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gic_icc_hppir_el1_read, + }, { .name = "ICC_PPI_ENABLER0_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 6, .access = PL1_RW, .type = ARM_CP_IO | ARM_CP_NO_RAW, -- 2.43.0
Implement the equivalent of the GICv5 stream protocol's Activate command, which lets the cpuif tell the IRS to move its current highest priority pending interrupt into the Active state, and to clear the Pending state for an Edge handling mode interrupt. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-48-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 58 ++++++++++++++++++++++++++++++ hw/intc/trace-events | 1 + include/hw/intc/arm_gicv5_stream.h | 23 ++++++++++++ 3 files changed, 82 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain, } } +void gicv5_activate(GICv5Common *cs, uint32_t id, GICv5Domain domain, + GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + uint32_t iaffid; + + trace_gicv5_activate(domain_name[domain], inttype_name(type), virtual, id); + + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_activate: tried to " + "activate a virtual interrupt\n"); + return; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ACTIVE, true); + if (FIELD_EX32(*l2_iste_p, L2_ISTE, HM) == GICV5_EDGE) { + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PENDING, false); + } + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID); + put_l2_iste(cs, cfg, &h); + break; + } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_activate: tried to " + "activate unreachable SPI %d\n", id); + return; + } + + spi->active = true; + if (spi->hm == GICV5_EDGE) { + spi->pending = false; + } + iaffid = spi->iaffid; + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_activate: tried to " + "activate bad interrupt type %d\n", type); + return; + } + + irs_recalc_hppi(s, domain, iaffid); +} + static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_set_pending(const char *domain, const char *type, bool virtual, uint32_t i gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t id, int handling) "GICv5 IRS SetHandling %s %s virtual:%d ID %u handling %d" gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d" gicv5_request_config(const char *domain, const char *type, bool virtual, uint32_t id, uint64_t icsr) "GICv5 IRS RequestConfig %s %s virtual:%d ID %u ICSR 0x%" PRIx64 +gicv5_activate(const char *domain, const char *type, bool virtual, uint32_t id) "GICv5 IRS Activate %s %s virtual:%d ID %u" gicv5_spi_state(uint32_t spi_id, bool level, bool pending, bool active) "GICv5 IRS SPI ID %u now level %d pending %d active %d" gicv5_irs_recalc_hppi_fail(const char *domain, uint32_t iaffid, const char *reason) "GICv5 IRS %s IAFFID %u: no HPPI: %s" gicv5_irs_recalc_hppi(const char *domain, uint32_t iaffid, uint32_t id, uint8_t prio) "GICv5 IRS %s IAFFID %u: new HPPI ID 0x%x prio %u" diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid, uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain, GICv5IntType type, bool virtual); +/** + * gicv5_activate + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @domain: interrupt domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Activate the IRS's highest priority pending interrupt; matches the + * stream interface's Activate command. + * + * In the stream interface, the command has only the domain and + * virtual fields, because both the IRS and the CPUIF keep track of + * the IRS's current HPPI. In QEMU, we also have arguments here for + * @id and @type which are telling the IRS something that in hardware + * it already knows. This is because we have them to hand in the cpuif + * code, and it means we don't need to pass in an iaffid argument to + * tell the IRS which CPU we are so it can find the right element in + * its hppi[][] array. + */ +void gicv5_activate(GICv5Common *cs, uint32_t id, GICv5Domain domain, + GICv5IntType type, bool virtual); + /** * gicv5_forward_interrupt * @cpu: CPU interface to forward interrupt to -- 2.43.0
The GICR CDIA system instruction is what the guest uses to acknowledge the highest priority pending interrupt. It returns a value corresponding to the HPPI for the current physical interrupt domain, if any, and moves that interrupt to being Active. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-49-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 101 +++++++++++++++++++++++++++++++++++ target/arm/tcg/trace-events | 2 + 2 files changed, 103 insertions(+) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(GIC_CDHM, HM, 32, 1) FIELD(GIC_CDRCFG, ID, 0, 24) FIELD(GIC_CDRCFG, TYPE, 29, 3) +FIELD(GICR_CDIA, ID, 0, 24) +FIELD(GICR_CDIA, TYPE, 29, 3) +FIELD(GICR_CDIA, VALID, 32, 1) + FIELD(ICC_IDR0_EL1, ID_BITS, 0, 4) FIELD(ICC_IDR0_EL1, PRI_BITS, 4, 4) FIELD(ICC_IDR0_EL1, GCIE_LEGACY, 8, 4) @@ -XXX,XX +XXX,XX @@ static uint64_t gic_icc_hppir_el1_read(CPUARMState *env, const ARMCPRegInfo *ri) return hppi.intid; } +static bool gic_hppi_is_nmi(CPUARMState *env, GICv5PendingIrq hppi, + GICv5Domain domain) +{ + /* + * For GICv5 an interrupt is an NMI if it is signaled with + * Superpriority and SCTLR_ELx.NMI for the current EL is 1. GICR + * CDIA/CDNMIA always work on the current interrupt domain, so we + * do not need to consider preemptive interrupts. This means that + * the interrupt has Superpriority if and only if it has priority 0. + */ + return hppi.prio == 0 && arm_sctlr(env, arm_current_el(env)) & SCTLR_NMI; +} + +static uint64_t gicr_cdia_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + /* Acknowledge HPPI in the current interrupt domain */ + GICv5Common *gic = gicv5_get_gic(env); + GICv5Domain domain = gicv5_current_phys_domain(env); + GICv5PendingIrq hppi = gic_hppi(env, domain); + GICv5IntType type = FIELD_EX64(hppi.intid, INTID, TYPE); + uint32_t id = FIELD_EX64(hppi.intid, INTID, ID); + + bool cdnmia = ri->opc2 == 1; + + if (!hppi.intid) { + /* No interrupt available to acknowledge */ + trace_gicv5_gicr_cdia_fail(domain, + "no available interrupt to acknowledge"); + return 0; + } + assert(hppi.prio != PRIO_IDLE); + + if (gic_hppi_is_nmi(env, hppi, domain) != cdnmia) { + /* GICR CDIA only acknowledges non-NMI; GICR CDNMIA only NMI */ + trace_gicv5_gicr_cdia_fail(domain, + cdnmia ? "CDNMIA but HPPI is not NMI" : + "CDIA but HPPI is NMI"); + return 0; + } + + trace_gicv5_gicr_cdia(domain, hppi.intid); + + /* + * The interrupt becomes Active. If the handling mode of the + * interrupt is Edge then we also clear the pending state. + */ + + /* + * Set the appropriate bit in the APR to track active priorities. + * We do this now so that when gic_recalc_ppi_hppi() or + * gicv5_activate() cause a re-evaluation of HPPIs they use the + * right (new) running priority. + */ + env->gicv5_cpuif.icc_apr[domain] |= (1 << hppi.prio); + switch (type) { + case GICV5_PPI: + { + uint32_t ppireg, ppibit; + + assert(id < GICV5_NUM_PPIS); + ppireg = id / 64; + ppibit = 1 << (id % 64); + + env->gicv5_cpuif.ppi_active[ppireg] |= ppibit; + if (!(env->gicv5_cpuif.ppi_hm[ppireg] & ppibit)) { + /* handling mode is Edge: clear pending */ + env->gicv5_cpuif.ppi_pend[ppireg] &= ~ppibit; + } + gic_recalc_ppi_hppi(env); + break; + } + case GICV5_LPI: + case GICV5_SPI: + /* + * Send an Activate command to the IRS, which, despite the + * name of the stream command, does both "set Active" and + * "maybe set not Pending" as a single atomic action. + */ + gicv5_activate(gic, id, domain, type, false); + break; + default: + g_assert_not_reached(); + } + + return hppi.intid | R_GICR_CDIA_VALID_MASK; +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdhm_write, }, + { .name = "GICR_CDIA", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 3, .opc2 = 0, + .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gicr_cdia_read, + }, + { .name = "GICR_CDNMIA", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 3, .opc2 = 1, + .access = PL1_R, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .readfn = gicr_cdia_read, + }, { .name = "ICC_IDR0_EL1", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 0, .crn = 12, .crm = 10, .opc2 = 2, .access = PL1_R, .type = ARM_CP_CONST | ARM_CP_NO_RAW, diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/trace-events +++ b/target/arm/tcg/trace-events @@ -XXX,XX +XXX,XX @@ # gicv5-cpuif.c gicv5_recalc_ppi_hppi(int domain, uint32_t id, uint8_t prio) "domain %d new PPI HPPI id 0x%x prio %u" +gicv5_gicr_cdia_fail(int domain, const char *reason) "domain %d CDIA attempt failed: %s" +gicv5_gicr_cdia(int domain, uint32_t id) "domain %d CDIA acknowledge of interrupt 0x%x" -- 2.43.0
Implement the GIC CDEOI instruction, which performs a "priority drop", clearing the highest set bit in the APR register. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-50-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 21 +++++++++++++++++++++ target/arm/tcg/trace-events | 1 + 2 files changed, 22 insertions(+) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static uint64_t gicr_cdia_read(CPUARMState *env, const ARMCPRegInfo *ri) return hppi.intid | R_GICR_CDIA_VALID_MASK; } +static void gic_cdeoi_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + /* + * Perform Priority Drop in the current interrupt domain. + * This is just clearing the lowest set bit in the APR. + */ + GICv5Domain domain = gicv5_current_phys_domain(env); + uint64_t *apr = &env->gicv5_cpuif.icc_apr[domain]; + + trace_gicv5_cdeoi(domain); + + /* clear lowest bit, doing nothing if already zero */ + *apr &= *apr - 1; +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdrcfg_write, }, + { .name = "GIC_CDEOI", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 1, .opc2 = 7, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cdeoi_write, + }, { .name = "GIC_CDHM", .state = ARM_CP_STATE_AA64, .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 2, .opc2 = 1, .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/trace-events +++ b/target/arm/tcg/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_recalc_ppi_hppi(int domain, uint32_t id, uint8_t prio) "domain %d new PPI HPPI id 0x%x prio %u" gicv5_gicr_cdia_fail(int domain, const char *reason) "domain %d CDIA attempt failed: %s" gicv5_gicr_cdia(int domain, uint32_t id) "domain %d CDIA acknowledge of interrupt 0x%x" +gicv5_cdeoi(int domain) "domain %d CDEOI performing priority drop" -- 2.43.0
Implement the equivalent of the GICv5 stream protocol's Deactivate command, which lets the cpuif tell the IRS to deactivate the specified interrupt. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-51-peter.maydell@linaro.org --- hw/intc/arm_gicv5.c | 52 ++++++++++++++++++++++++++++++ hw/intc/trace-events | 1 + include/hw/intc/arm_gicv5_stream.h | 14 ++++++++ 3 files changed, 67 insertions(+) diff --git a/hw/intc/arm_gicv5.c b/hw/intc/arm_gicv5.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv5.c +++ b/hw/intc/arm_gicv5.c @@ -XXX,XX +XXX,XX @@ void gicv5_activate(GICv5Common *cs, uint32_t id, GICv5Domain domain, irs_recalc_hppi(s, domain, iaffid); } +void gicv5_deactivate(GICv5Common *cs, uint32_t id, GICv5Domain domain, + GICv5IntType type, bool virtual) +{ + GICv5 *s = ARM_GICV5(cs); + uint32_t iaffid; + + trace_gicv5_deactivate(domain_name[domain], inttype_name(type), virtual, id); + + if (virtual) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_deactivate: tried to " + "deactivate a virtual interrupt\n"); + return; + } + + switch (type) { + case GICV5_LPI: + { + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain]; + L2_ISTE_Handle h; + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h); + + if (!l2_iste_p) { + return; + } + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ACTIVE, false); + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID); + put_l2_iste(cs, cfg, &h); + break; + } + case GICV5_SPI: + { + GICv5SPIState *spi = gicv5_spi_state(cs, id, domain); + + if (!spi) { + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_deactivate: tried to " + "deactivate unreachable SPI %d\n", id); + return; + } + + spi->active = false; + iaffid = spi->iaffid; + break; + } + default: + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_deactivate: tried to " + "deactivate bad interrupt type %d\n", type); + return; + } + + irs_recalc_hppi(s, domain, iaffid); +} + static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value) { GICv5Common *cs = ARM_GICV5_COMMON(s); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d" gicv5_request_config(const char *domain, const char *type, bool virtual, uint32_t id, uint64_t icsr) "GICv5 IRS RequestConfig %s %s virtual:%d ID %u ICSR 0x%" PRIx64 gicv5_activate(const char *domain, const char *type, bool virtual, uint32_t id) "GICv5 IRS Activate %s %s virtual:%d ID %u" +gicv5_deactivate(const char *domain, const char *type, bool virtual, uint32_t id) "GICv5 IRS Deactivate %s %s virtual:%d ID %u" gicv5_spi_state(uint32_t spi_id, bool level, bool pending, bool active) "GICv5 IRS SPI ID %u now level %d pending %d active %d" gicv5_irs_recalc_hppi_fail(const char *domain, uint32_t iaffid, const char *reason) "GICv5 IRS %s IAFFID %u: no HPPI: %s" gicv5_irs_recalc_hppi(const char *domain, uint32_t iaffid, uint32_t id, uint8_t prio) "GICv5 IRS %s IAFFID %u: new HPPI ID 0x%x prio %u" diff --git a/include/hw/intc/arm_gicv5_stream.h b/include/hw/intc/arm_gicv5_stream.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/intc/arm_gicv5_stream.h +++ b/include/hw/intc/arm_gicv5_stream.h @@ -XXX,XX +XXX,XX @@ void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain); GICv5PendingIrq gicv5_get_hppi(GICv5Common *cs, GICv5Domain domain, uint32_t iaffid); +/** + * gicv5_deactivate + * @cs: GIC IRS to send command to + * @id: interrupt ID + * @domain: interrupt Domain to act on + * @type: interrupt type (LPI or SPI) + * @virtual: true if this is a virtual interrupt + * + * Deactivate the specified interrupt. There is no report back of + * success/failure to the CPUIF in the protocol. + */ +void gicv5_deactivate(GICv5Common *cs, uint32_t id, GICv5Domain domain, + GICv5IntType type, bool virtual); + #endif -- 2.43.0
Implement the GIC CDDI system instruction, which deactivates the specified interrupt. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-52-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 49 ++++++++++++++++++++++++++++++++++++ target/arm/tcg/trace-events | 1 + 2 files changed, 50 insertions(+) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ FIELD(GIC_CDPRI, ID, 0, 24) FIELD(GIC_CDPRI, TYPE, 29, 3) FIELD(GIC_CDPRI, PRIORITY, 35, 5) +FIELD(GIC_CDDI, ID, 0, 24) +FIELD(GIC_CDDI, TYPE, 29, 3) + FIELD(GIC_CDDIS, ID, 0, 24) FIELD(GIC_CDDIS, TYPE, 29, 3) @@ -XXX,XX +XXX,XX @@ static void gic_cdeoi_write(CPUARMState *env, const ARMCPRegInfo *ri, *apr &= *apr - 1; } +static void gic_cddi_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + /* + * Clear the Active state of the specified interrupt in the + * current interrupt domain. + */ + GICv5Common *gic = gicv5_get_gic(env); + GICv5Domain domain = gicv5_current_phys_domain(env); + GICv5IntType type = FIELD_EX64(value, GIC_CDDI, TYPE); + uint32_t id = FIELD_EX64(value, GIC_CDDI, ID); + bool virtual = false; + + trace_gicv5_cddi(domain, value); + + switch (type) { + case GICV5_PPI: + { + uint32_t ppireg, ppibit; + + if (id >= GICV5_NUM_PPIS) { + break; + } + + ppireg = id / 64; + ppibit = 1 << (id % 64); + + env->gicv5_cpuif.ppi_active[ppireg] &= ~ppibit; + gic_recalc_ppi_hppi(env); + break; + } + case GICV5_LPI: + case GICV5_SPI: + /* Tell the IRS to deactivate this interrupt */ + gicv5_deactivate(gic, id, domain, type, virtual); + break; + default: + break; + } +} + static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { /* * Barrier: wait until the effects of a cpuif system register @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo gicv5_cpuif_reginfo[] = { .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, .writefn = gic_cdeoi_write, }, + { .name = "GIC_CDDI", .state = ARM_CP_STATE_AA64, + .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 2, .opc2 = 0, + .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, + .writefn = gic_cddi_write, + }, { .name = "GIC_CDHM", .state = ARM_CP_STATE_AA64, .opc0 = 1, .opc1 = 0, .crn = 12, .crm = 2, .opc2 = 1, .access = PL1_W, .type = ARM_CP_IO | ARM_CP_NO_RAW, diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/trace-events +++ b/target/arm/tcg/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_recalc_ppi_hppi(int domain, uint32_t id, uint8_t prio) "domain %d new PPI gicv5_gicr_cdia_fail(int domain, const char *reason) "domain %d CDIA attempt failed: %s" gicv5_gicr_cdia(int domain, uint32_t id) "domain %d CDIA acknowledge of interrupt 0x%x" gicv5_cdeoi(int domain) "domain %d CDEOI performing priority drop" +gicv5_cddi(int domain, uint32_t id) "domain %d CDDI deactivating interrupt ID 0x%x" -- 2.43.0
The CPU interface must signal IRQ or FIQ (possibly with superpriority) when there is a pending interrupt of sufficient priority available. Implement this logic. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-53-peter.maydell@linaro.org --- target/arm/tcg/gicv5-cpuif.c | 87 ++++++++++++++++++++++++++++++++++-- target/arm/tcg/trace-events | 1 + 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ static GICv5PendingIrq gic_hppi(CPUARMState *env, GICv5Domain domain) return best; } +static void cpu_interrupt_update(CPUARMState *env, int irqtype, bool new_state) +{ + CPUState *cs = env_cpu(env); + + /* + * OPT: calling cpu_interrupt() and cpu_reset_interrupt() has the + * correct behaviour, but is not optimal for the case where we're + * setting the interrupt line to the same level it already has. + * + * Clearing an already clear interrupt is free (it's just doing an + * atomic AND operation). Signalling an already set interrupt is a + * bit less ideal (it might unnecessarily kick the CPU). + * + * We could potentially use cpu_test_interrupt(), like + * arm_cpu_update_{virq,vfiq,vinmi,vserr}, since we always hold + * the BQL here; or perhaps there is an abstraction we could + * provide in the core code that all these places could call. + * + * For now, this is simple and definitely correct. + */ + if (new_state) { + cpu_interrupt(cs, irqtype); + } else { + cpu_reset_interrupt(cs, irqtype); + } +} + +static void gicv5_update_irq_fiq(CPUARMState *env) +{ + /* + * Update whether we are signalling IRQ or FIQ based on the + * current state of the CPU interface (and in particular on the + * HPPI information from the IRS and for the PPIs for each + * interrupt domain); + * + * The logic here for IRQ and FIQ is defined by rules R_QLGBG and + * R_ZGHMN; whether to signal with superpriority is defined by + * rule R_CSBDX. + * + * For the moment, we do not consider preemptive interrupts, + * because these only occur when there is a HPPI of sufficient + * priority for another interrupt domain, and we only support EL1 + * and the NonSecure interrupt domain currently. + * + * NB: when we handle more than just EL1 we will need to arrange + * to call this function to re-evaluate the IRQ and FIQ state when + * we change EL. + */ + GICv5PendingIrq current_hppi; + bool irq, fiq, superpriority; + + /* + * We will never signal FIQ because FIQ is for preemptive + * interrupts or for EL3 HPPIs. + */ + fiq = false; + + /* + * We signal IRQ when we are not signalling FIQ and there is a + * HPPI of sufficient priority for the current domain. It has + * Superpriority if its priority is 0 (in which case it is + * CPU_INTERRUPT_NMI rather than CPU_INTERRUPT_HARD). + */ + current_hppi = gic_hppi(env, gicv5_current_phys_domain(env)); + superpriority = current_hppi.prio == 0; + irq = current_hppi.prio != PRIO_IDLE && !superpriority; + + /* + * Unlike a GICv3 or GICv2, there is no external IRQ or FIQ line + * to the CPU. Instead we directly signal the interrupt via + * cpu_interrupt()/cpu_reset_interrupt(). + */ + trace_gicv5_update_irq_fiq(irq, fiq, superpriority); + cpu_interrupt_update(env, CPU_INTERRUPT_HARD, irq); + cpu_interrupt_update(env, CPU_INTERRUPT_FIQ, fiq); + cpu_interrupt_update(env, CPU_INTERRUPT_NMI, superpriority); +} + static void gic_recalc_ppi_hppi(CPUARMState *env) { /* @@ -XXX,XX +XXX,XX @@ static void gic_recalc_ppi_hppi(CPUARMState *env) env->gicv5_cpuif.ppi_hppi[i].intid, env->gicv5_cpuif.ppi_hppi[i].prio); } + gicv5_update_irq_fiq(env); } void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain) { /* - * For now, we do nothing. Later we will recalculate the overall - * HPPI by combining the IRS HPPI with the PPI HPPI, and possibly - * signal IRQ/FIQ. + * IRS HPPI has changed: recalculate the IRQ/FIQ levels by + * combining the IRS HPPI with the PPI HPPI. */ + gicv5_update_irq_fiq(&cpu->env); } static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri, @@ -XXX,XX +XXX,XX @@ static void gic_icc_cr0_el1_write(CPUARMState *env, const ARMCPRegInfo *ri, value |= R_ICC_CR0_LINK_MASK | R_ICC_CR0_LINK_IDLE_MASK; env->gicv5_cpuif.icc_cr0[domain] = value; + gicv5_update_irq_fiq(env); } static void gic_icc_cr0_el1_reset(CPUARMState *env, const ARMCPRegInfo *ri) @@ -XXX,XX +XXX,XX @@ static void gic_cdeoi_write(CPUARMState *env, const ARMCPRegInfo *ri, /* clear lowest bit, doing nothing if already zero */ *apr &= *apr - 1; + gicv5_update_irq_fiq(env); } static void gic_cddi_write(CPUARMState *env, const ARMCPRegInfo *ri, diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/trace-events +++ b/target/arm/tcg/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_gicr_cdia_fail(int domain, const char *reason) "domain %d CDIA attempt fai gicv5_gicr_cdia(int domain, uint32_t id) "domain %d CDIA acknowledge of interrupt 0x%x" gicv5_cdeoi(int domain) "domain %d CDEOI performing priority drop" gicv5_cddi(int domain, uint32_t id) "domain %d CDDI deactivating interrupt ID 0x%x" +gicv5_update_irq_fiq(bool irq, bool fiq, bool nmi) "now IRQ %d FIQ %d NMI %d" -- 2.43.0
The CPU has several interrupt sources which are exposed as GICv5 PPIs. For QEMU, this means the generic timers and the PMU. In GICv3, we implemented these as qemu_irq lines which connect up to the external interrupt controller device. In a GICv5, the PPIs are handled entirely inside the CPU interface, so there are no external signals. Instead we provide a gicv5_update_ppi_state() function which the emulated timer and PMU code uses to tell the CPU interface about the new state of the PPI source. We make the GICv5 function a no-op if there is no GICv5 present, so that calling code can do both "update the old irq lines" and "update the GICv5 PPI" without having to add conditionals. (In a GICv5 system the old irq lines won't be connected to anything, so the qemu_set_irq() will be a no-op.) Updating PPIs via either mechanism is unnecessary in user-only mode; we got away with not ifdeffing this away before because qemu_set_irq() is built for user-only mode, but since the GICv5 cpuif code is system-emulation only, we do need an ifdef now. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-54-peter.maydell@linaro.org --- target/arm/cpregs-pmu.c | 9 +++++++-- target/arm/helper.c | 20 ++++++++++++++++++++ target/arm/internals.h | 6 ++++++ target/arm/tcg-stubs.c | 4 ++++ target/arm/tcg/gicv5-cpuif.c | 28 ++++++++++++++++++++++++++++ target/arm/tcg/trace-events | 1 + 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/target/arm/cpregs-pmu.c b/target/arm/cpregs-pmu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpregs-pmu.c +++ b/target/arm/cpregs-pmu.c @@ -XXX,XX +XXX,XX @@ static bool pmu_counter_enabled(CPUARMState *env, uint8_t counter) static void pmu_update_irq(CPUARMState *env) { +#ifndef CONFIG_USER_ONLY ARMCPU *cpu = env_archcpu(env); - qemu_set_irq(cpu->pmu_interrupt, (env->cp15.c9_pmcr & PMCRE) && - (env->cp15.c9_pminten & env->cp15.c9_pmovsr)); + bool level = (env->cp15.c9_pmcr & PMCRE) && + (env->cp15.c9_pminten & env->cp15.c9_pmovsr); + + gicv5_update_ppi_state(env, GICV5_PPI_PMUIRQ, level); + qemu_set_irq(cpu->pmu_interrupt, level); +#endif } static bool pmccntr_clockdiv_enabled(CPUARMState *env) diff --git a/target/arm/helper.c b/target/arm/helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -XXX,XX +XXX,XX @@ uint64_t gt_get_countervalue(CPUARMState *env) return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / gt_cntfrq_period_ns(cpu); } +static void gt_update_gicv5_ppi(CPUARMState *env, int timeridx, bool level) +{ + static int timeridx_to_ppi[] = { + [GTIMER_PHYS] = GICV5_PPI_CNTP, + [GTIMER_VIRT] = GICV5_PPI_CNTV, + [GTIMER_HYP] = GICV5_PPI_CNTHP, + [GTIMER_SEC] = GICV5_PPI_CNTPS, + [GTIMER_HYPVIRT] = GICV5_PPI_CNTHV, + [GTIMER_S_EL2_PHYS] = GICV5_PPI_CNTHPS, + [GTIMER_S_EL2_VIRT] = GICV5_PPI_CNTHVS, + }; + + gicv5_update_ppi_state(env, timeridx_to_ppi[timeridx], level); +} + static void gt_update_irq(ARMCPU *cpu, int timeridx) { CPUARMState *env = &cpu->env; @@ -XXX,XX +XXX,XX @@ static void gt_update_irq(ARMCPU *cpu, int timeridx) irqstate = 0; } + /* + * We update both the GICv5 PPI and the external-GIC irq line + * (whichever of the two mechanisms is unused will do nothing) + */ + gt_update_gicv5_ppi(env, timeridx, irqstate); qemu_set_irq(cpu->gt_timer_outputs[timeridx], irqstate); trace_arm_gt_update_irq(timeridx, irqstate); } diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ void define_omap_cp_regs(ARMCPU *cpu); /* Add the cpreg definitions for the GICv5 CPU interface */ void define_gicv5_cpuif_regs(ARMCPU *cpu); +/* + * Update the state of the given GICv5 PPI for this CPU. Does nothing + * if the GICv5 is not present. + */ +void gicv5_update_ppi_state(CPUARMState *env, int ppi, bool level); + /* Effective value of MDCR_EL2 */ static inline uint64_t arm_mdcr_el2_eff(CPUARMState *env) { diff --git a/target/arm/tcg-stubs.c b/target/arm/tcg-stubs.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg-stubs.c +++ b/target/arm/tcg-stubs.c @@ -XXX,XX +XXX,XX @@ void vfp_clear_float_status_exc_flags(CPUARMState *env) void vfp_set_fpcr_to_host(CPUARMState *env, uint32_t val, uint32_t mask) { } + +void gicv5_update_ppi_state(CPUARMState *env, int ppi, bool level) +{ +} diff --git a/target/arm/tcg/gicv5-cpuif.c b/target/arm/tcg/gicv5-cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/gicv5-cpuif.c +++ b/target/arm/tcg/gicv5-cpuif.c @@ -XXX,XX +XXX,XX @@ void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain) gicv5_update_irq_fiq(&cpu->env); } +void gicv5_update_ppi_state(CPUARMState *env, int ppi, bool level) +{ + /* + * Update the state of the given PPI (which is connected to some + * CPU-internal source of interrupts, like the timers). We can + * assume that the PPI is fixed as level-triggered, which means + * that its pending state exactly tracks the input (and the guest + * cannot separately change the pending state, because the pending + * bits are RO). + */ + int oldlevel; + + if (!cpu_isar_feature(aa64_gcie, env_archcpu(env))) { + return; + } + + /* The architected PPIs are 0..63, so in the first PPI register. */ + assert(ppi >= 0 && ppi < 64); + oldlevel = extract64(env->gicv5_cpuif.ppi_pend[0], ppi, 1); + if (oldlevel != level) { + trace_gicv5_update_ppi_state(ppi, level); + + env->gicv5_cpuif.ppi_pend[0] = + deposit64(env->gicv5_cpuif.ppi_pend[0], ppi, 1, level); + gic_recalc_ppi_hppi(env); + } +} + static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t value) { diff --git a/target/arm/tcg/trace-events b/target/arm/tcg/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/trace-events +++ b/target/arm/tcg/trace-events @@ -XXX,XX +XXX,XX @@ gicv5_gicr_cdia(int domain, uint32_t id) "domain %d CDIA acknowledge of interrup gicv5_cdeoi(int domain) "domain %d CDEOI performing priority drop" gicv5_cddi(int domain, uint32_t id) "domain %d CDDI deactivating interrupt ID 0x%x" gicv5_update_irq_fiq(bool irq, bool fiq, bool nmi) "now IRQ %d FIQ %d NMI %d" +gicv5_update_ppi_state(int ppi, bool level) "PPI %d source level now %d" -- 2.43.0
Add a has_gcie QOM property to the CPU which allows the board code to enable FEAT_GCIE, the GICv5 CPU interface. Enabling the GICv5 CPU interface comes with a significant restriction: because the GICv5 architecture is Armv9, it assumes the Armv9 requirement that only EL0 (userspace) may be in AArch32. So there are no GIC control system registers defined for AArch32. We force AArch32 at ELs 1, 2 and 3 to disabled, to avoid a guest being able to get into an EL where interrupts are completely broken. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-55-peter.maydell@linaro.org --- target/arm/cpu.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ target/arm/cpu.h | 2 ++ 2 files changed, 47 insertions(+) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ static const Property arm_cpu_has_el2_property = static const Property arm_cpu_has_el3_property = DEFINE_PROP_BOOL("has_el3", ARMCPU, has_el3, true); + +static const Property arm_cpu_has_gcie_property = + DEFINE_PROP_BOOL("has_gcie", ARMCPU, has_gcie, false); #endif static const Property arm_cpu_cfgend_property = @@ -XXX,XX +XXX,XX @@ static void arm_cpu_post_init(Object *obj) object_property_add_uint64_ptr(obj, "rvbar", &cpu->rvbar_prop, OBJ_PROP_FLAG_READWRITE); + + /* We only allow GICv5 on a 64-bit v8 CPU */ + if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { + qdev_property_add_static(DEVICE(obj), &arm_cpu_has_gcie_property); + } } if (arm_feature(&cpu->env, ARM_FEATURE_EL3)) { @@ -XXX,XX +XXX,XX @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) current_accel_name()); return; } + if (cpu->has_gcie) { + error_setg(errp, + "Cannot enable %s when guest CPU has GICv5 enabled", + current_accel_name()); + return; + } } #endif @@ -XXX,XX +XXX,XX @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) FIELD_DP32_IDREG(isar, ID_PFR1, VIRTUALIZATION, 0); } + /* Report FEAT_GCIE in our ID registers if property was set */ + FIELD_DP64_IDREG(isar, ID_AA64PFR2, GCIE, cpu->has_gcie); + if (cpu_isar_feature(aa64_gcie, cpu)) { + if (!arm_feature(env, ARM_FEATURE_AARCH64)) { + /* + * We only create the have_gcie property for AArch64 CPUs, + * but the user might have tried aarch64=off with has_gcie=on. + */ + error_setg(errp, "Cannot both enable has_gcie and disable aarch64"); + return; + } + + /* + * FEAT_GCIE implies Armv9, which implies no AArch32 above EL0. + * Usually we don't strictly insist on this kind of feature + * dependency, but in this case we enforce it, because the + * GICv5 CPU interface has no AArch32 versions of its system + * registers, so interrupts wouldn't work if we allowed AArch32 + * in EL1 or above. Downgrade "AArch32 and AArch64" to "AArch64". + */ + if (cpu_isar_feature(aa64_aa32_el3, cpu)) { + FIELD_DP64_IDREG(isar, ID_AA64PFR0, EL3, 1); + } + if (cpu_isar_feature(aa64_aa32_el2, cpu)) { + FIELD_DP64_IDREG(isar, ID_AA64PFR0, EL2, 1); + } + if (cpu_isar_feature(aa64_aa32_el1, cpu)) { + FIELD_DP64_IDREG(isar, ID_AA64PFR0, EL1, 1); + } + } + if (cpu_isar_feature(aa64_mte, cpu)) { /* * The architectural range of GM blocksize is 2-6, however qemu diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ struct ArchCPU { bool has_neon; /* CPU has M-profile DSP extension */ bool has_dsp; + /* CPU has FEAT_GCIE GICv5 CPU interface */ + bool has_gcie; /* CPU has memory protection unit */ bool has_mpu; -- 2.43.0
The GICv3 and GICv5 CPU interfaces are not compatible, and a CPU will only implement either one or the other. If we find that we're trying to connect a GICv3 to a CPU that implements FEAT_GCIE, fail. This will only happen if the board code has a bug and doesn't configure its CPUs and its GIC consistently. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-56-peter.maydell@linaro.org --- hw/intc/arm_gicv3.c | 2 +- hw/intc/arm_gicv3_cpuif.c | 14 +++++++++++++- hw/intc/gicv3_internal.h | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/hw/intc/arm_gicv3.c b/hw/intc/arm_gicv3.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv3.c +++ b/hw/intc/arm_gicv3.c @@ -XXX,XX +XXX,XX @@ static void arm_gic_realize(DeviceState *dev, Error **errp) gicv3_init_irqs_and_mmio(s, gicv3_set_irq, gic_ops); - gicv3_init_cpuif(s); + gicv3_init_cpuif(s, errp); } static void arm_gicv3_class_init(ObjectClass *klass, const void *data) diff --git a/hw/intc/arm_gicv3_cpuif.c b/hw/intc/arm_gicv3_cpuif.c index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/arm_gicv3_cpuif.c +++ b/hw/intc/arm_gicv3_cpuif.c @@ -XXX,XX +XXX,XX @@ #include "qemu/bitops.h" #include "qemu/log.h" #include "qemu/main-loop.h" +#include "qapi/error.h" #include "trace.h" #include "gicv3_internal.h" #include "hw/core/irq.h" @@ -XXX,XX +XXX,XX @@ static void gicv3_cpuif_el_change_hook(ARMCPU *cpu, void *opaque) gicv3_cpuif_virt_irq_fiq_update(cs); } -void gicv3_init_cpuif(GICv3State *s) +void gicv3_init_cpuif(GICv3State *s, Error **errp) { /* Called from the GICv3 realize function; register our system * registers with the CPU @@ -XXX,XX +XXX,XX @@ void gicv3_init_cpuif(GICv3State *s) ARMCPU *cpu = ARM_CPU(qemu_get_cpu(s->first_cpu_idx + i)); GICv3CPUState *cs = &s->cpu[i]; + if (cpu_isar_feature(aa64_gcie, cpu)) { + /* + * Attempt to connect GICv3 to a CPU with GICv5 cpuif + * (almost certainly a bug in the board code) + */ + error_setg(errp, + "Cannot connect GICv3 to CPU %d which has GICv5 cpuif", + i); + return; + } + /* * If the CPU doesn't define a GICv3 configuration, probably because * in real hardware it doesn't have one, then we use default values diff --git a/hw/intc/gicv3_internal.h b/hw/intc/gicv3_internal.h index XXXXXXX..XXXXXXX 100644 --- a/hw/intc/gicv3_internal.h +++ b/hw/intc/gicv3_internal.h @@ -XXX,XX +XXX,XX @@ void gicv3_redist_mov_vlpi(GICv3CPUState *src, uint64_t src_vptaddr, void gicv3_redist_vinvall(GICv3CPUState *cs, uint64_t vptaddr); void gicv3_redist_send_sgi(GICv3CPUState *cs, int grp, int irq, bool ns); -void gicv3_init_cpuif(GICv3State *s); +void gicv3_init_cpuif(GICv3State *s, Error **errp); /** * gicv3_cpuif_update: -- 2.43.0
In fdt_add_cpu_nodes(), we currently add phandles for each CPU node if we are going to add a topology description, and when we do, we re-look-up the phandle by node name when creating the topology description. For GICv5 we will also want to refer to the CPU phandles; so always add a phandle, and keep track of those phandles in the VirtMachineState so we don't have to look them up by name in the dtb every time. The phandle property is extra data in the final DTB, but only a tiny amount, so it's not worth trying to carefully track the conditions when we're going to need them so we only emit them when required. (We need to change the smp_cpus variable to unsigned because otherwise gcc thinks that we might be passing a negative number to g_new0() and produces an error: /usr/include/glib-2.0/glib/gmem.h:270:19: error: argument 1 range [18446744071562067968, 18446744073709551615] exceeds maximum object size 9223372036854775807 [-Werror=alloc-size-larger-than=] 270 | __p = g_##func##_n (__n, __s); \ | ^~~~~~~~~~~~~~~~~~~~~~~ /usr/include/glib-2.0/glib/gmem.h:332:57: note: in expansion of macro ‘_G_NEW’ 332 | #define g_new0(struct_type, n_structs) _G_NEW (struct_type, n_structs, malloc0) | ^~~~~~ ../../hw/arm/virt.c:469:25: note: in expansion of macro ‘g_new0’ 469 | vms->cpu_phandles = g_new0(uint32_t, smp_cpus); | ^~~~~~ ) Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-57-peter.maydell@linaro.org --- hw/arm/virt.c | 19 ++++++++++--------- include/hw/arm/virt.h | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static bool partial_cache_description(const MachineState *ms, int num_caches) return false; } -static void fdt_add_cpu_nodes(const VirtMachineState *vms) +static void fdt_add_cpu_nodes(VirtMachineState *vms) { int cpu; int addr_cells = 1; const MachineState *ms = MACHINE(vms); const MachineClass *mc = MACHINE_GET_CLASS(ms); const VirtMachineClass *vmc = VIRT_MACHINE_GET_CLASS(vms); - int smp_cpus = ms->smp.cpus; + unsigned int smp_cpus = ms->smp.cpus; int socket_id, cluster_id, core_id; uint32_t next_level = 0; uint32_t socket_offset = 0; @@ -XXX,XX +XXX,XX @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#address-cells", addr_cells); qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#size-cells", 0x0); + vms->cpu_phandles = g_new0(uint32_t, smp_cpus); + for (cpu = smp_cpus - 1; cpu >= 0; cpu--) { socket_id = cpu / (ms->smp.clusters * ms->smp.cores * ms->smp.threads); cluster_id = cpu / (ms->smp.cores * ms->smp.threads) % ms->smp.clusters; @@ -XXX,XX +XXX,XX @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(cpu)); CPUState *cs = CPU(armcpu); const char *prefix = NULL; + uint32_t phandle; qemu_fdt_add_subnode(ms->fdt, nodename); qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "cpu"); @@ -XXX,XX +XXX,XX @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) ms->possible_cpus->cpus[cs->cpu_index].props.node_id); } - if (!vmc->no_cpu_topology) { - qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", - qemu_fdt_alloc_phandle(ms->fdt)); - } + phandle = qemu_fdt_alloc_phandle(ms->fdt); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", phandle); + vms->cpu_phandles[cpu] = phandle; if (!vmc->no_cpu_topology && num_cache) { for (uint8_t i = 0; i < num_cache; i++) { @@ -XXX,XX +XXX,XX @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) qemu_fdt_add_subnode(ms->fdt, "/cpus/cpu-map"); for (cpu = smp_cpus - 1; cpu >= 0; cpu--) { - char *cpu_path = g_strdup_printf("/cpus/cpu@%d", cpu); char *map_path; if (ms->smp.threads > 1) { @@ -XXX,XX +XXX,XX @@ static void fdt_add_cpu_nodes(const VirtMachineState *vms) cpu % ms->smp.cores); } qemu_fdt_add_path(ms->fdt, map_path); - qemu_fdt_setprop_phandle(ms->fdt, map_path, "cpu", cpu_path); + qemu_fdt_setprop_cell(ms->fdt, map_path, "cpu", + vms->cpu_phandles[cpu]); g_free(map_path); - g_free(cpu_path); } } } diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -XXX,XX +XXX,XX @@ struct VirtMachineState { uint32_t gic_phandle; uint32_t msi_phandle; uint32_t iommu_phandle; + uint32_t *cpu_phandles; int psci_conduit; uint8_t virtio_transports; hwaddr highest_gpa; -- 2.43.0
The create_gic() function also creates the MSI controller; however there isn't really a strong linkage here, and for the GICv5 it will be more convenient to separate it out. Move it to a new create_msi_controller() function. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-58-peter.maydell@linaro.org --- hw/arm/virt.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) } fdt_add_gic_node(vms); +} - if (vms->msi_controller == VIRT_MSI_CTRL_ITS) { +static void create_msi_controller(VirtMachineState *vms) +{ + switch (vms->msi_controller) { + case VIRT_MSI_CTRL_ITS: create_its(vms); - } else if (vms->msi_controller == VIRT_MSI_CTRL_GICV2M) { + break; + case VIRT_MSI_CTRL_GICV2M: create_v2m(vms); + break; + case VIRT_MSI_CTRL_NONE: + break; + default: + g_assert_not_reached(); } } @@ -XXX,XX +XXX,XX @@ static void machvirt_init(MachineState *machine) virt_flash_fdt(vms, sysmem, secure_sysmem ?: sysmem); create_gic(vms, sysmem); + create_msi_controller(vms); virt_post_cpus_gic_realized(vms, sysmem); -- 2.43.0
create_gic() is quite long and mixes GICv2 and GICv3 even though they're mostly different in their creation. As a preliminary to splitting it up, pull out the "wire the CPU interrupts to the GIC PPI inputs" code out into its own function. This is a long and self-contained piece of code that is the main thing that we need to do basically the same way for GICv2 and GICv3. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-59-peter.maydell@linaro.org --- hw/arm/virt.c | 126 +++++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static bool gicv3_nmi_present(VirtMachineState *vms) (vms->gic_version != VIRT_GIC_VERSION_2); } +static void gic_connect_ppis(VirtMachineState *vms) +{ + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, + * and the GIC's IRQ/FIQ/VIRQ/VFIQ/NMI/VINMI interrupt outputs to the + * CPU's inputs. + */ + MachineState *ms = MACHINE(vms); + unsigned int smp_cpus = ms->smp.cpus; + SysBusDevice *gicbusdev = SYS_BUS_DEVICE(vms->gic); + + for (int i = 0; i < smp_cpus; i++) { + DeviceState *cpudev = DEVICE(qemu_get_cpu(i)); + int intidbase = NUM_IRQS + i * GIC_INTERNAL; + /* + * Mapping from the output timer irq lines from the CPU to the + * GIC PPI inputs we use for the virt board. + */ + const int timer_irq[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + [GTIMER_HYPVIRT] = ARCH_TIMER_NS_EL2_VIRT_IRQ, + [GTIMER_S_EL2_PHYS] = ARCH_TIMER_S_EL2_IRQ, + [GTIMER_S_EL2_VIRT] = ARCH_TIMER_S_EL2_VIRT_IRQ, + }; + + for (unsigned irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) { + qdev_connect_gpio_out(cpudev, irq, + qdev_get_gpio_in(vms->gic, + intidbase + timer_irq[irq])); + } + + if (vms->gic_version != VIRT_GIC_VERSION_2) { + qemu_irq irq = qdev_get_gpio_in(vms->gic, + intidbase + ARCH_GIC_MAINT_IRQ); + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", + 0, irq); + } else if (vms->virt) { + qemu_irq irq = qdev_get_gpio_in(vms->gic, + intidbase + ARCH_GIC_MAINT_IRQ); + sysbus_connect_irq(gicbusdev, i + 4 * smp_cpus, irq); + } + + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, + qdev_get_gpio_in(vms->gic, intidbase + + VIRTUAL_PMU_IRQ)); + + sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbusdev, i + smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(gicbusdev, i + 2 * smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicbusdev, i + 3 * smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + + if (vms->gic_version != VIRT_GIC_VERSION_2) { + sysbus_connect_irq(gicbusdev, i + 4 * smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_NMI)); + sysbus_connect_irq(gicbusdev, i + 5 * smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VINMI)); + } + } +} + static void create_gic(VirtMachineState *vms, MemoryRegion *mem) { MachineState *ms = MACHINE(vms); /* We create a standalone GIC */ SysBusDevice *gicbusdev; const char *gictype; - int i; unsigned int smp_cpus = ms->smp.cpus; uint32_t nb_redist_regions = 0; int revision; @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) } } - /* Wire the outputs from each CPU's generic timer and the GICv3 - * maintenance interrupt signal to the appropriate GIC PPI inputs, - * and the GIC's IRQ/FIQ/VIRQ/VFIQ/NMI/VINMI interrupt outputs to the - * CPU's inputs. - */ - for (i = 0; i < smp_cpus; i++) { - DeviceState *cpudev = DEVICE(qemu_get_cpu(i)); - int intidbase = NUM_IRQS + i * GIC_INTERNAL; - /* Mapping from the output timer irq lines from the CPU to the - * GIC PPI inputs we use for the virt board. - */ - const int timer_irq[] = { - [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, - [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, - [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, - [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, - [GTIMER_HYPVIRT] = ARCH_TIMER_NS_EL2_VIRT_IRQ, - [GTIMER_S_EL2_PHYS] = ARCH_TIMER_S_EL2_IRQ, - [GTIMER_S_EL2_VIRT] = ARCH_TIMER_S_EL2_VIRT_IRQ, - }; - - for (unsigned irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) { - qdev_connect_gpio_out(cpudev, irq, - qdev_get_gpio_in(vms->gic, - intidbase + timer_irq[irq])); - } - - if (vms->gic_version != VIRT_GIC_VERSION_2) { - qemu_irq irq = qdev_get_gpio_in(vms->gic, - intidbase + ARCH_GIC_MAINT_IRQ); - qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", - 0, irq); - } else if (vms->virt) { - qemu_irq irq = qdev_get_gpio_in(vms->gic, - intidbase + ARCH_GIC_MAINT_IRQ); - sysbus_connect_irq(gicbusdev, i + 4 * smp_cpus, irq); - } - - qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, - qdev_get_gpio_in(vms->gic, intidbase - + VIRTUAL_PMU_IRQ)); - - sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); - sysbus_connect_irq(gicbusdev, i + smp_cpus, - qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); - sysbus_connect_irq(gicbusdev, i + 2 * smp_cpus, - qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); - sysbus_connect_irq(gicbusdev, i + 3 * smp_cpus, - qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); - - if (vms->gic_version != VIRT_GIC_VERSION_2) { - sysbus_connect_irq(gicbusdev, i + 4 * smp_cpus, - qdev_get_gpio_in(cpudev, ARM_CPU_NMI)); - sysbus_connect_irq(gicbusdev, i + 5 * smp_cpus, - qdev_get_gpio_in(cpudev, ARM_CPU_VINMI)); - } - } + gic_connect_ppis(vms); fdt_add_gic_node(vms); } -- 2.43.0
Currently create_gic() handles GICv2 and GICv3/4 in a single function, with large sections that are conditional on the vms->gic_version. GICv5 will be different to both. Refactor into create_gicv2() and create_gicv3(). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-60-peter.maydell@linaro.org --- hw/arm/virt.c | 139 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 56 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static void gic_connect_ppis(VirtMachineState *vms) } } -static void create_gic(VirtMachineState *vms, MemoryRegion *mem) +static void create_gicv2(VirtMachineState *vms, MemoryRegion *mem) { MachineState *ms = MACHINE(vms); /* We create a standalone GIC */ SysBusDevice *gicbusdev; - const char *gictype; unsigned int smp_cpus = ms->smp.cpus; - uint32_t nb_redist_regions = 0; - int revision; - if (vms->gic_version == VIRT_GIC_VERSION_2) { - gictype = gic_class_name(); - } else { - gictype = gicv3_class_name(); + if (kvm_enabled() && vms->virt) { + error_report("KVM EL2 is only supported with in-kernel GICv3"); + exit(1); } + vms->gic = qdev_new(gic_class_name()); + qdev_prop_set_uint32(vms->gic, "revision", 2); + qdev_prop_set_uint32(vms->gic, "num-cpu", smp_cpus); + /* + * Note that the num-irq property counts both internal and external + * interrupts; there are always 32 of the former (mandated by GIC spec). + */ + qdev_prop_set_uint32(vms->gic, "num-irq", NUM_IRQS + 32); + if (!kvm_irqchip_in_kernel()) { + qdev_prop_set_bit(vms->gic, "has-security-extensions", vms->secure); + qdev_prop_set_bit(vms->gic, "has-virtualization-extensions", vms->virt); + } + + gicbusdev = SYS_BUS_DEVICE(vms->gic); + sysbus_realize_and_unref(gicbusdev, &error_fatal); + sysbus_mmio_map(gicbusdev, 0, vms->memmap[VIRT_GIC_DIST].base); + sysbus_mmio_map(gicbusdev, 1, vms->memmap[VIRT_GIC_CPU].base); + if (vms->virt) { + sysbus_mmio_map(gicbusdev, 2, vms->memmap[VIRT_GIC_HYP].base); + sysbus_mmio_map(gicbusdev, 3, vms->memmap[VIRT_GIC_VCPU].base); + } + + gic_connect_ppis(vms); + + fdt_add_gic_node(vms); +} + +static void create_gicv3(VirtMachineState *vms, MemoryRegion *mem) +{ + MachineState *ms = MACHINE(vms); + /* We create a standalone GIC */ + SysBusDevice *gicbusdev; + unsigned int smp_cpus = ms->smp.cpus; + uint32_t nb_redist_regions; + int revision; + QList *redist_region_count; + uint32_t redist0_capacity = virt_redist_capacity(vms, VIRT_GIC_REDIST); + uint32_t redist0_count = MIN(smp_cpus, redist0_capacity); + switch (vms->gic_version) { - case VIRT_GIC_VERSION_2: - revision = 2; - break; case VIRT_GIC_VERSION_3: revision = 3; break; @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) exit(1); } - vms->gic = qdev_new(gictype); + vms->gic = qdev_new(gicv3_class_name()); qdev_prop_set_uint32(vms->gic, "revision", revision); qdev_prop_set_uint32(vms->gic, "num-cpu", smp_cpus); - /* Note that the num-irq property counts both internal and external + /* + * Note that the num-irq property counts both internal and external * interrupts; there are always 32 of the former (mandated by GIC spec). */ qdev_prop_set_uint32(vms->gic, "num-irq", NUM_IRQS + 32); @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) qdev_prop_set_bit(vms->gic, "has-security-extensions", vms->secure); } - if (vms->gic_version != VIRT_GIC_VERSION_2) { - QList *redist_region_count; - uint32_t redist0_capacity = virt_redist_capacity(vms, VIRT_GIC_REDIST); - uint32_t redist0_count = MIN(smp_cpus, redist0_capacity); + nb_redist_regions = virt_gicv3_redist_region_count(vms); - nb_redist_regions = virt_gicv3_redist_region_count(vms); + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, redist0_count); + if (nb_redist_regions == 2) { + uint32_t redist1_capacity = + virt_redist_capacity(vms, VIRT_HIGH_GIC_REDIST2); - redist_region_count = qlist_new(); - qlist_append_int(redist_region_count, redist0_count); - if (nb_redist_regions == 2) { - uint32_t redist1_capacity = - virt_redist_capacity(vms, VIRT_HIGH_GIC_REDIST2); + qlist_append_int(redist_region_count, + MIN(smp_cpus - redist0_count, redist1_capacity)); + } + qdev_prop_set_array(vms->gic, "redist-region-count", redist_region_count); - qlist_append_int(redist_region_count, - MIN(smp_cpus - redist0_count, redist1_capacity)); - } - qdev_prop_set_array(vms->gic, "redist-region-count", - redist_region_count); - - if (!kvm_irqchip_in_kernel() && - !(hvf_enabled() && hvf_irqchip_in_kernel())) { - if (vms->tcg_its) { - object_property_set_link(OBJECT(vms->gic), "sysmem", - OBJECT(mem), &error_fatal); - qdev_prop_set_bit(vms->gic, "has-lpi", true); - } - } else if (vms->virt) { - qdev_prop_set_uint32(vms->gic, "maintenance-interrupt-id", - ARCH_GIC_MAINT_IRQ); - } - } else { - if (!kvm_irqchip_in_kernel() && !hvf_irqchip_in_kernel()) { - qdev_prop_set_bit(vms->gic, "has-virtualization-extensions", - vms->virt); + if (!kvm_irqchip_in_kernel() && + !(hvf_enabled() && hvf_irqchip_in_kernel())) { + if (vms->tcg_its) { + object_property_set_link(OBJECT(vms->gic), "sysmem", OBJECT(mem), + &error_fatal); + qdev_prop_set_bit(vms->gic, "has-lpi", true); } + } else if (vms->virt) { + qdev_prop_set_uint32(vms->gic, "maintenance-interrupt-id", + ARCH_GIC_MAINT_IRQ); } if (gicv3_nmi_present(vms)) { @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) gicbusdev = SYS_BUS_DEVICE(vms->gic); sysbus_realize_and_unref(gicbusdev, &error_fatal); sysbus_mmio_map(gicbusdev, 0, vms->memmap[VIRT_GIC_DIST].base); - if (vms->gic_version != VIRT_GIC_VERSION_2) { - sysbus_mmio_map(gicbusdev, 1, vms->memmap[VIRT_GIC_REDIST].base); - if (nb_redist_regions == 2) { - sysbus_mmio_map(gicbusdev, 2, - vms->memmap[VIRT_HIGH_GIC_REDIST2].base); - } - } else { - sysbus_mmio_map(gicbusdev, 1, vms->memmap[VIRT_GIC_CPU].base); - if (vms->virt) { - sysbus_mmio_map(gicbusdev, 2, vms->memmap[VIRT_GIC_HYP].base); - sysbus_mmio_map(gicbusdev, 3, vms->memmap[VIRT_GIC_VCPU].base); - } + sysbus_mmio_map(gicbusdev, 1, vms->memmap[VIRT_GIC_REDIST].base); + if (nb_redist_regions == 2) { + sysbus_mmio_map(gicbusdev, 2, vms->memmap[VIRT_HIGH_GIC_REDIST2].base); } gic_connect_ppis(vms); @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) fdt_add_gic_node(vms); } +static void create_gic(VirtMachineState *vms, MemoryRegion *mem) +{ + switch (vms->gic_version) { + case VIRT_GIC_VERSION_2: + create_gicv2(vms, mem); + break; + case VIRT_GIC_VERSION_3: + case VIRT_GIC_VERSION_4: + create_gicv3(vms, mem); + break; + default: + g_assert_not_reached(); + } +} + static void create_msi_controller(VirtMachineState *vms) { switch (vms->msi_controller) { -- 2.43.0
In this commit we create and connect up the GICv5. We do not advertise it in the ACPI tables or DTB; that will be done in a following commit. The user-facing gic-version property still only documents and permits in its setter function the existing set of possible values; we won't permit the user to select a GICv5 until all the code to handle it is in place. Although we currently implement only the IRS, and only for EL1, we reserve space in the virt board's memory map now for all the register frames that the GICv5 may use. Each interrupt domain has: * one IRS config register frame * one ITS config register frame * one ITS translate register frame and each of these frames is 64K in size and 64K aligned and must be at a unique address (that is, it is not permitted to have all the IRS config register frames at the same physical address in the different S/NS/etc physical address spaces). The addresses and layout of these frames are entirely up to the implementation: software will be passed their addresses via firmware data structures (ACPI or DTB). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-61-peter.maydell@linaro.org --- hw/arm/virt.c | 101 ++++++++++++++++++++++++++++++++++++++++++ include/hw/arm/virt.h | 14 ++++++ 2 files changed, 115 insertions(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ #include "hw/intc/arm_gic.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/intc/arm_gicv3_its_common.h" +#include "hw/intc/arm_gicv5_common.h" #include "hw/core/irq.h" #include "kvm_arm.h" #include "whpx_arm.h" @@ -XXX,XX +XXX,XX @@ static const MemMapEntry base_memmap[] = { [VIRT_GIC_ITS] = { 0x08080000, 0x00020000 }, /* This redistributor space allows up to 2*64kB*123 CPUs */ [VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, + /* The GICv5 uses this address range differently from GICv2/v3/v4 */ + [VIRT_GICV5_IRS_S] = { 0x08000000, 0x00010000 }, + [VIRT_GICV5_IRS_NS] = { 0x08010000, 0x00010000 }, + [VIRT_GICV5_IRS_EL3] = { 0x08020000, 0x00010000 }, + [VIRT_GICV5_IRS_REALM] = { 0x08030000, 0x00010000 }, + [VIRT_GICV5_ITS_S] = { 0x08040000, 0x00010000 }, + [VIRT_GICV5_ITS_NS] = { 0x08050000, 0x00010000 }, + [VIRT_GICV5_ITS_EL3] = { 0x08060000, 0x00010000 }, + [VIRT_GICV5_ITS_REALM] = { 0x08070000, 0x00010000 }, + [VIRT_GICV5_ITS_TR_S] = { 0x08080000, 0x00010000 }, + [VIRT_GICV5_ITS_TR_NS] = { 0x08090000, 0x00010000 }, + [VIRT_GICV5_ITS_TR_EL3] = { 0x080A0000, 0x00010000 }, + [VIRT_GICV5_ITS_TR_REALM] = { 0x080B0000, 0x00010000 }, [VIRT_UART0] = { 0x09000000, 0x00001000 }, [VIRT_RTC] = { 0x09010000, 0x00001000 }, [VIRT_FW_CFG] = { 0x09020000, 0x00000018 }, @@ -XXX,XX +XXX,XX @@ static void create_v2m(VirtMachineState *vms) vms->msi_controller = VIRT_MSI_CTRL_GICV2M; } +static void create_gicv5(VirtMachineState *vms, MemoryRegion *mem) +{ + MachineState *ms = MACHINE(vms); + SysBusDevice *gicbusdev; + const char *gictype = gicv5_class_name(); + QList *cpulist = qlist_new(), *iaffidlist = qlist_new(); + + vms->gic = qdev_new(gictype); + qdev_prop_set_uint32(vms->gic, "spi-range", NUM_IRQS); + + object_property_set_link(OBJECT(vms->gic), "sysmem", OBJECT(mem), + &error_fatal); + + for (int i = 0; i < ms->smp.cpus; i++) { + qlist_append_link(cpulist, OBJECT(qemu_get_cpu(i))); + /* + * GICv5 IAFFIDs must be system-wide unique across all GICs. + * For virt we make them the same as the CPU index. + */ + qlist_append_int(iaffidlist, i); + } + qdev_prop_set_array(vms->gic, "cpus", cpulist); + qdev_prop_set_array(vms->gic, "cpu-iaffids", iaffidlist); + + gicbusdev = SYS_BUS_DEVICE(vms->gic); + sysbus_realize_and_unref(gicbusdev, &error_fatal); + + /* + * Map the IRS config frames for the interrupt domains. + * At the moment we implement only the NS domain, so this is simple. + */ + sysbus_mmio_map(gicbusdev, GICV5_ID_NS, + vms->memmap[VIRT_GICV5_IRS_NS].base); + + /* + * The GICv5 does not need to wire up CPU timer IRQ outputs to the GIC + * because for the GICv5 those PPIs are entirely internal to the CPU. + * Nor do we need to wire up GIC IRQ/FIQ signals to the CPUs, because + * that information is communicated directly between a GICv5 IRS and + * the GICv5 CPU interface via our equivalent of the stream protocol. + */ +} + /* * If the CPU has FEAT_NMI, then turn on the NMI support in the GICv3 too. * It's permitted to have a configuration with NMI in the CPU (and thus the @@ -XXX,XX +XXX,XX @@ static void create_gic(VirtMachineState *vms, MemoryRegion *mem) case VIRT_GIC_VERSION_4: create_gicv3(vms, mem); break; + case VIRT_GIC_VERSION_5: + create_gicv5(vms, mem); + break; default: g_assert_not_reached(); } @@ -XXX,XX +XXX,XX @@ static uint64_t virt_cpu_mp_affinity(VirtMachineState *vms, int idx) /* * Adjust MPIDR to make TCG consistent (with 64-bit KVM hosts) * and to improve SGI efficiency. + * - GICv2 only supports 8 CPUs anyway + * - GICv3 wants 16 CPUs per Aff0 because of an ICC_SGIxR + * register limitation + * - GICv5 has no restrictions, so we retain the GICv3 16-per-Aff0 + * layout because that's what KVM does */ if (vms->gic_version == VIRT_GIC_VERSION_2) { clustersz = GIC_TARGETLIST_BITS; @@ -XXX,XX +XXX,XX @@ static VirtGICType finalize_gic_version_do(const char *accel_name, return finalize_gic_version_do(accel_name, VIRT_GIC_VERSION_MAX, gics_supported, max_cpus); case VIRT_GIC_VERSION_MAX: + /* + * We don't (currently) make 'max' select GICv5 as it is not + * backwards compatible for system software with GICv3/v4 and + * at time of writing not widely supported in guest kernels. + */ if (gics_supported & VIRT_GIC_VERSION_4_MASK) { gic_version = VIRT_GIC_VERSION_4; } else if (gics_supported & VIRT_GIC_VERSION_3_MASK) { @@ -XXX,XX +XXX,XX @@ static VirtGICType finalize_gic_version_do(const char *accel_name, case VIRT_GIC_VERSION_2: case VIRT_GIC_VERSION_3: case VIRT_GIC_VERSION_4: + case VIRT_GIC_VERSION_5: break; } @@ -XXX,XX +XXX,XX @@ static VirtGICType finalize_gic_version_do(const char *accel_name, exit(1); } break; + case VIRT_GIC_VERSION_5: + if (!(gics_supported & VIRT_GIC_VERSION_5_MASK)) { + error_report("%s does not support GICv5 emulation", accel_name); + exit(1); + } + break; default: error_report("logic error in finalize_gic_version"); exit(1); @@ -XXX,XX +XXX,XX @@ static void finalize_gic_version(VirtMachineState *vms) gics_supported |= VIRT_GIC_VERSION_4_MASK; } } + if (!hvf_enabled() && module_object_class_by_name("arm-gicv5")) { + /* HVF doesn't have GICv5 support */ + gics_supported |= VIRT_GIC_VERSION_5_MASK; + } } else { error_report("Unsupported accelerator, can not determine GIC support"); exit(1); @@ -XXX,XX +XXX,XX @@ static void finalize_msi_controller(VirtMachineState *vms) vms->msi_controller = VIRT_MSI_CTRL_GICV2M; } else if (hvf_enabled() && hvf_irqchip_in_kernel()) { vms->msi_controller = VIRT_MSI_CTRL_GICV2M; + } else if (vms->gic_version == VIRT_GIC_VERSION_5) { + /* GICv5 ITS is not yet implemented */ + vms->msi_controller = VIRT_MSI_CTRL_NONE; } else { vms->msi_controller = VIRT_MSI_CTRL_ITS; } @@ -XXX,XX +XXX,XX @@ static void finalize_msi_controller(VirtMachineState *vms) error_report("GICv2 + ITS is an invalid configuration."); exit(1); } + if (vms->gic_version == VIRT_GIC_VERSION_5) { + error_report("GICv5 + ITS is not yet implemented."); + exit(1); + } if (whpx_enabled()) { error_report("ITS not supported on WHPX."); exit(1); @@ -XXX,XX +XXX,XX @@ static void machvirt_init(MachineState *machine) */ if (vms->gic_version == VIRT_GIC_VERSION_2) { virt_max_cpus = GIC_NCPU; + } else if (vms->gic_version == VIRT_GIC_VERSION_5) { + /* + * GICv5 imposes no CPU limit beyond the 16-bit IAFFID field. + * The maximum number of CPUs will be limited not by this, but + * by the MachineClass::max_cpus value we set earlier. + */ + virt_max_cpus = 1 << QEMU_GICV5_IAFFID_BITS; } else { virt_max_cpus = virt_redist_capacity(vms, VIRT_GIC_REDIST); if (vms->highmem_redists) { @@ -XXX,XX +XXX,XX @@ static void machvirt_init(MachineState *machine) exit(1); } + if ((vms->virt || vms->secure) && + vms->gic_version == VIRT_GIC_VERSION_5) { + error_report("mach-virt: GICv5 currently supports EL1 only"); + exit(1); + } + create_fdt(vms); assert(possible_cpus->len == max_cpus); diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -XXX,XX +XXX,XX @@ enum { VIRT_GIC_VCPU, VIRT_GIC_ITS, VIRT_GIC_REDIST, + VIRT_GICV5_IRS_S, + VIRT_GICV5_IRS_NS, + VIRT_GICV5_IRS_EL3, + VIRT_GICV5_IRS_REALM, + VIRT_GICV5_ITS_S, + VIRT_GICV5_ITS_NS, + VIRT_GICV5_ITS_EL3, + VIRT_GICV5_ITS_REALM, + VIRT_GICV5_ITS_TR_S, + VIRT_GICV5_ITS_TR_NS, + VIRT_GICV5_ITS_TR_EL3, + VIRT_GICV5_ITS_TR_REALM, VIRT_SMMU, VIRT_UART0, VIRT_MMIO, @@ -XXX,XX +XXX,XX @@ typedef enum VirtGICType { VIRT_GIC_VERSION_2 = 2, VIRT_GIC_VERSION_3 = 3, VIRT_GIC_VERSION_4 = 4, + VIRT_GIC_VERSION_5 = 5, VIRT_GIC_VERSION_NOSEL, } VirtGICType; #define VIRT_GIC_VERSION_2_MASK BIT(VIRT_GIC_VERSION_2) #define VIRT_GIC_VERSION_3_MASK BIT(VIRT_GIC_VERSION_3) #define VIRT_GIC_VERSION_4_MASK BIT(VIRT_GIC_VERSION_4) +#define VIRT_GIC_VERSION_5_MASK BIT(VIRT_GIC_VERSION_5) struct VirtMachineClass { MachineClass parent; -- 2.43.0
Advertise the GICv5 in the DTB. This binding is final as it is in the upstream Linux kernel as: Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5.yaml Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-62-peter.maydell@linaro.org --- hw/arm/virt.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static void create_v2m(VirtMachineState *vms) vms->msi_controller = VIRT_MSI_CTRL_GICV2M; } +static void fdt_add_gicv5_node(VirtMachineState *vms) +{ + MachineState *ms = MACHINE(vms); + const char *nodename = "/intc"; + g_autofree char *irsnodename = NULL; + g_autofree uint32_t *cpu_phandles = g_new(uint32_t, ms->smp.cpus); + g_autofree uint16_t *iaffids = g_new(uint16_t, ms->smp.cpus); + + vms->gic_phandle = qemu_fdt_alloc_phandle(ms->fdt); + qemu_fdt_setprop_cell(ms->fdt, "/", "interrupt-parent", vms->gic_phandle); + + qemu_fdt_add_subnode(ms->fdt, nodename); + qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", vms->gic_phandle); + qemu_fdt_setprop_string(ms->fdt, nodename, "compatible", "arm,gic-v5"); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 3); + qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, nodename, "#size-cells", 0x2); + qemu_fdt_setprop(ms->fdt, nodename, "ranges", NULL, 0); + + /* The IRS node is a child of the top level /intc node */ + irsnodename = g_strdup_printf("%s/irs@%" PRIx64, + nodename, + vms->memmap[VIRT_GICV5_IRS_NS].base); + qemu_fdt_add_subnode(ms->fdt, irsnodename); + qemu_fdt_setprop_string(ms->fdt, irsnodename, "compatible", + "arm,gic-v5-irs"); + /* + * "reg-names" describes the frames whose address/size is in "reg"; + * at the moment we have only the NS config register frame. + */ + qemu_fdt_setprop_string(ms->fdt, irsnodename, "reg-names", "ns-config"); + qemu_fdt_setprop_sized_cells(ms->fdt, irsnodename, "reg", + 2, vms->memmap[VIRT_GICV5_IRS_NS].base, + 2, vms->memmap[VIRT_GICV5_IRS_NS].size); + qemu_fdt_setprop_cell(ms->fdt, irsnodename, "#address-cells", 0x2); + qemu_fdt_setprop_cell(ms->fdt, irsnodename, "#size-cells", 0x2); + qemu_fdt_setprop(ms->fdt, irsnodename, "ranges", NULL, 0); + + /* + * The "cpus" property is an array of phandles to the CPUs, and + * "iaffids" is an array of uint16 IAFFIDs. For virt, our IAFFIDs + * are the CPU indexes. This function is called after + * fdt_add_cpu_nodes(), which allocates the cpu_phandles array. + */ + assert(vms->cpu_phandles); + for (int i = 0; i < ms->smp.cpus; i++) { + /* + * We have to byteswap each element here because we're setting the + * whole property value at once as a lump of raw data, not via a + * helper like qemu_fdt_setprop_cell() that does the swapping for us. + */ + cpu_phandles[i] = cpu_to_be32(vms->cpu_phandles[i]); + iaffids[i] = cpu_to_be16(i); + } + qemu_fdt_setprop(ms->fdt, irsnodename, "cpus", cpu_phandles, + ms->smp.cpus * sizeof(*cpu_phandles)); + qemu_fdt_setprop(ms->fdt, irsnodename, "arm,iaffids", iaffids, + ms->smp.cpus * sizeof(*iaffids)); + + /* + * When we implement the GICv5 IRS, it gets a DTB node which is a + * child of the IRS node. + */ +} + static void create_gicv5(VirtMachineState *vms, MemoryRegion *mem) { MachineState *ms = MACHINE(vms); @@ -XXX,XX +XXX,XX @@ static void create_gicv5(VirtMachineState *vms, MemoryRegion *mem) * that information is communicated directly between a GICv5 IRS and * the GICv5 CPU interface via our equivalent of the stream protocol. */ + + fdt_add_gicv5_node(vms); } /* -- 2.43.0
The GICv5 devicetree binding specifies the "interrupts" property differently to GICv2 and GICv3 for PPIs: the first field is the architectural INTID.TYPE, and the second is the architectural INTID.ID. (The third field defining the level/edge trigger mode has the same values for GICv5 as it did for the older GICs.) In the places in the virt board where we wire up PPIs (the timer and the PMU), handle the GICv5: * use the architectural constant GICV5_PPI for the type * use the architected GICv5 PPI numbers for the interrupt sources (which differ from the old ones and don't need to be adjusted via INTID_TO_PPI()) * leave the irqflags as-is Add some commentary in our include/hw/arm/fdt.h file about what the the constants defined there are valid for. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-63-peter.maydell@linaro.org --- hw/arm/virt.c | 23 +++++++++++++++++++---- include/hw/arm/fdt.h | 10 ++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static void fdt_add_timer_nodes(const VirtMachineState *vms) "arm,armv7-timer"); } qemu_fdt_setprop(ms->fdt, "/timer", "always-on", NULL, 0); - if (vms->ns_el2_virt_timer_irq) { + if (vms->gic_version == VIRT_GIC_VERSION_5) { + /* The GICv5 architects the PPI numbers differently */ + qemu_fdt_setprop_cells(ms->fdt, "/timer", "interrupts", + GICV5_PPI, GICV5_PPI_CNTPS, irqflags, + GICV5_PPI, GICV5_PPI_CNTP, irqflags, + GICV5_PPI, GICV5_PPI_CNTV, irqflags, + GICV5_PPI, GICV5_PPI_CNTHP, irqflags, + GICV5_PPI, GICV5_PPI_CNTHV, irqflags); + } else if (vms->ns_el2_virt_timer_irq) { qemu_fdt_setprop_cells(ms->fdt, "/timer", "interrupts", GIC_FDT_IRQ_TYPE_PPI, INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ), irqflags, @@ -XXX,XX +XXX,XX @@ static void fdt_add_pmu_nodes(const VirtMachineState *vms) qemu_fdt_add_subnode(ms->fdt, "/pmu"); if (arm_feature(&armcpu->env, ARM_FEATURE_V8)) { const char compat[] = "arm,armv8-pmuv3"; + qemu_fdt_setprop(ms->fdt, "/pmu", "compatible", compat, sizeof(compat)); - qemu_fdt_setprop_cells(ms->fdt, "/pmu", "interrupts", - GIC_FDT_IRQ_TYPE_PPI, - INTID_TO_PPI(VIRTUAL_PMU_IRQ), irqflags); + if (vms->gic_version == VIRT_GIC_VERSION_5) { + qemu_fdt_setprop_cells(ms->fdt, "/pmu", "interrupts", + GICV5_PPI, GICV5_PPI_PMUIRQ, irqflags); + } else { + qemu_fdt_setprop_cells(ms->fdt, "/pmu", "interrupts", + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(VIRTUAL_PMU_IRQ), + irqflags); + } } } diff --git a/include/hw/arm/fdt.h b/include/hw/arm/fdt.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fdt.h +++ b/include/hw/arm/fdt.h @@ -XXX,XX +XXX,XX @@ #ifndef QEMU_ARM_FDT_H #define QEMU_ARM_FDT_H +/* + * These are for GICv2/v3/v4 only; GICv5 encodes the interrupt type in + * the DTB "interrupts" properties differently, using constants that + * match the architectural INTID.Type. In QEMU those are available as + * the GICV5_PPI and GICV5_SPI enum values in arm_gicv5_types.h. + */ #define GIC_FDT_IRQ_TYPE_SPI 0 #define GIC_FDT_IRQ_TYPE_PPI 1 +/* + * The trigger type/level field in the DTB "interrupts" property has + * the same encoding for GICv2/v3/v4 and v5. + */ #define GIC_FDT_IRQ_FLAGS_EDGE_LO_HI 1 #define GIC_FDT_IRQ_FLAGS_EDGE_HI_LO 2 #define GIC_FDT_IRQ_FLAGS_LEVEL_HI 4 -- 2.43.0
The GICv5 devicetree binding specifies that the "interrupts" property for devices connected to it should use the architectural INTID.TYPE values to specify whether the interrupt is an SPI, LPI or PPI. This is different to the GICv2 and GICv3, so instead of hardcoding the GIC_FDT_IRQ_TYPE_SPI constant when we create "interrupts" bindings, create a new function gic_fdt_irq_type_spi() that returns the right value for the interrupt controller in use. For SPIs, the INTID.ID and the trigger-mode fields of the "interrupts" property remain the same for GICv5 and the older GIC versions. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-64-peter.maydell@linaro.org --- hw/arm/virt.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ unsigned int virt_get_caches(const VirtMachineState *vms, CPUCoreCaches *caches) return num_cache; } +/* + * The correct value to use in a DTB "interrupts" property for an SPI + * depends on the GIC version. + */ +static int gic_fdt_irq_type_spi(const VirtMachineState *vms) +{ + return vms->gic_version == VIRT_GIC_VERSION_5 ? + GICV5_SPI : GIC_FDT_IRQ_TYPE_SPI; +} + static void create_fdt(VirtMachineState *vms) { MachineState *ms = MACHINE(vms); @@ -XXX,XX +XXX,XX @@ static void create_uart(const VirtMachineState *vms, int uart, qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", - GIC_FDT_IRQ_TYPE_SPI, irq, + gic_fdt_irq_type_spi(vms), irq, GIC_FDT_IRQ_FLAGS_LEVEL_HI); qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", vms->clock_phandle, vms->clock_phandle); @@ -XXX,XX +XXX,XX @@ static void create_rtc(const VirtMachineState *vms) qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", - GIC_FDT_IRQ_TYPE_SPI, irq, + gic_fdt_irq_type_spi(vms), irq, GIC_FDT_IRQ_FLAGS_LEVEL_HI); qemu_fdt_setprop_cell(ms->fdt, nodename, "clocks", vms->clock_phandle); qemu_fdt_setprop_string(ms->fdt, nodename, "clock-names", "apb_pclk"); @@ -XXX,XX +XXX,XX @@ static void create_gpio_devices(const VirtMachineState *vms, int gpio, qemu_fdt_setprop_cell(ms->fdt, nodename, "#gpio-cells", 2); qemu_fdt_setprop(ms->fdt, nodename, "gpio-controller", NULL, 0); qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", - GIC_FDT_IRQ_TYPE_SPI, irq, + gic_fdt_irq_type_spi(vms), irq, GIC_FDT_IRQ_FLAGS_LEVEL_HI); qemu_fdt_setprop_cell(ms->fdt, nodename, "clocks", vms->clock_phandle); qemu_fdt_setprop_string(ms->fdt, nodename, "clock-names", "apb_pclk"); @@ -XXX,XX +XXX,XX @@ static void create_virtio_devices(const VirtMachineState *vms) qemu_fdt_setprop_sized_cells(ms->fdt, nodename, "reg", 2, base, 2, size); qemu_fdt_setprop_cells(ms->fdt, nodename, "interrupts", - GIC_FDT_IRQ_TYPE_SPI, irq, + gic_fdt_irq_type_spi(vms), irq, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); qemu_fdt_setprop(ms->fdt, nodename, "dma-coherent", NULL, 0); g_free(nodename); @@ -XXX,XX +XXX,XX @@ static void create_pcie_irq_map(const MachineState *ms, int devfn, pin; uint32_t full_irq_map[4 * 4 * 10] = { 0 }; uint32_t *irq_map = full_irq_map; + const VirtMachineState *vms = VIRT_MACHINE(ms); for (devfn = 0; devfn <= 0x18; devfn += 0x8) { for (pin = 0; pin < 4; pin++) { - int irq_type = GIC_FDT_IRQ_TYPE_SPI; + int irq_type = gic_fdt_irq_type_spi(vms); int irq_nr = first_irq + ((pin + PCI_SLOT(devfn)) % PCI_NUM_PINS); int irq_level = GIC_FDT_IRQ_FLAGS_LEVEL_HI; int i; @@ -XXX,XX +XXX,XX @@ static void create_smmuv3_dt_bindings(const VirtMachineState *vms, hwaddr base, qemu_fdt_setprop_sized_cells(ms->fdt, node, "reg", 2, base, 2, size); qemu_fdt_setprop_cells(ms->fdt, node, "interrupts", - GIC_FDT_IRQ_TYPE_SPI, irq , GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, - GIC_FDT_IRQ_TYPE_SPI, irq + 1, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, - GIC_FDT_IRQ_TYPE_SPI, irq + 2, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, - GIC_FDT_IRQ_TYPE_SPI, irq + 3, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); + gic_fdt_irq_type_spi(vms), irq , GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, + gic_fdt_irq_type_spi(vms), irq + 1, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, + gic_fdt_irq_type_spi(vms), irq + 2, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI, + gic_fdt_irq_type_spi(vms), irq + 3, GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); qemu_fdt_setprop(ms->fdt, node, "interrupt-names", irq_names, sizeof(irq_names)); -- 2.43.0
If we are using the GICv5 in the virt board, we need to set the has_gcie property on the CPU objects to tell them to implement the cpu interface part of GICv5. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-65-peter.maydell@linaro.org --- hw/arm/virt.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static void machvirt_init(MachineState *machine) object_property_set_bool(cpuobj, "lpa2", false, NULL); } + if (vms->gic_version == VIRT_GIC_VERSION_5) { + if (!object_property_find(cpuobj, "has_gcie")) { + error_report("Using GICv5 but guest CPU does not support it"); + exit(1); + } + object_property_set_bool(cpuobj, "has_gcie", true, NULL); + } + if (object_property_find(cpuobj, "reset-cbar")) { object_property_set_int(cpuobj, "reset-cbar", vms->memmap[VIRT_CPUPERIPHS].base, -- 2.43.0
Allow the user to select a GICv5 via '-machine gic-version=x-5', and document this. The 'x-' prefix indicates that the emulation is still in an "experimental" state as far as QEMU is concerned; the documentation describes what "experimental" means for the user and what parts are not yet implemented. We do not make 'gic-version=max' enable GICv5 here because: * the GICv5 architectural spec is still at the EAC level and could have minor changes between now and its final version; only users who specifically want to start working with the GICv5 should select it * QEMU's implementation here is still not fully featured, and selecting it instead of GICv3 will mean losing functionality such as MSIs * the GICv5 is not backwards compatible with the GICv3/GICv4 for system software, so silently "upgrading" an existing command line to GICv5 is just going to break existing guest kernels The last one in particular suggests that even when the emulation moves out of "experimental" status we will probably not want to change "max". Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260327111700.795099-66-peter.maydell@linaro.org --- docs/system/arm/virt.rst | 19 +++++++++++++++++++ hw/arm/virt.c | 11 ++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/system/arm/virt.rst b/docs/system/arm/virt.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/virt.rst +++ b/docs/system/arm/virt.rst @@ -XXX,XX +XXX,XX @@ gic-version GICv3. This allows up to 512 CPUs. ``4`` GICv4. Requires ``virtualization`` to be ``on``; allows up to 317 CPUs. + ``x-5`` + GICv5 (experimental). This is an experimental emulation of the GICv5, + based on the EAC release of the GICv5 architecture specification. + Experimental means: + + - guest-visible behaviour may change when the final version of + the specification is released and QEMU implements it + - migration support is not yet implemented + - the GICv5 is not exposed to the guest via ACPI tables, only via DTB + - the way the interrupt controller is exposed to the guest and the + command line syntax for enabling it may change + + The current implementation supports only an EL1 guest (no EL2 or + EL3 and no Realm support), and does not implement the ITS (no + MSI support). + + Note that as the GICv5 is an Armv9 feature, enabling it will + automatically disable support for AArch32 at all exception levels + except for EL0 (userspace). ``host`` Use the same GIC version the host provides, when using KVM ``max`` diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ static char *virt_get_gic_version(Object *obj, Error **errp) const char *val; switch (vms->gic_version) { + case VIRT_GIC_VERSION_5: + val = "x-5"; + break; case VIRT_GIC_VERSION_4: val = "4"; break; @@ -XXX,XX +XXX,XX @@ static void virt_set_gic_version(Object *obj, const char *value, Error **errp) { VirtMachineState *vms = VIRT_MACHINE(obj); - if (!strcmp(value, "4")) { + if (!strcmp(value, "x-5")) { + vms->gic_version = VIRT_GIC_VERSION_5; + } else if (!strcmp(value, "4")) { vms->gic_version = VIRT_GIC_VERSION_4; } else if (!strcmp(value, "3")) { vms->gic_version = VIRT_GIC_VERSION_3; @@ -XXX,XX +XXX,XX @@ static void virt_set_gic_version(Object *obj, const char *value, Error **errp) vms->gic_version = VIRT_GIC_VERSION_MAX; /* Will probe later */ } else { error_setg(errp, "Invalid gic-version value"); - error_append_hint(errp, "Valid values are 2, 3, 4, host, and max.\n"); + error_append_hint(errp, "Valid values are 2, 3, 4, x-5, host, and max.\n"); } } @@ -XXX,XX +XXX,XX @@ static void virt_machine_class_init(ObjectClass *oc, const void *data) virt_set_gic_version); object_class_property_set_description(oc, "gic-version", "Set GIC version. " - "Valid values are 2, 3, 4, host and max"); + "Valid values are 2, 3, 4, x-5, host and max"); object_class_property_add_str(oc, "iommu", virt_get_iommu, virt_set_iommu); object_class_property_set_description(oc, "iommu", -- 2.43.0
The Thumb BKPT insn is 16-bit, and the ESR_ELx syndrome register definition requires that we set the IL bit to 0 for this, and 1 for the 32-bit A32 and A64 BKPT/BRK. We used to do this correctly, but accidentally lost it in the conversion to decodetree, because we converted the A32 BKPT first, and then when we converted the T16 BKPT we forgot that trans_BKPT() was unconditionally setting IL=1. Pass the right value for syn_aa32_bkpt()'s is_16bit argument. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3474 Fixes: 43f7e42c7d515f ("target/arm: Convert T16, Miscellaneous 16-bit instructions") Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260505103726.419195-1-peter.maydell@linaro.org --- target/arm/tcg/translate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/translate.c +++ b/target/arm/tcg/translate.c @@ -XXX,XX +XXX,XX @@ static bool trans_BKPT(DisasContext *s, arg_BKPT *a) (a->imm == 0xab)) { gen_exception_internal_insn(s, EXCP_SEMIHOST); } else { - gen_exception_bkpt_insn(s, syn_aa32_bkpt(a->imm, false)); + gen_exception_bkpt_insn(s, syn_aa32_bkpt(a->imm, curr_insn_len(s) == 2)); } return true; } -- 2.43.0
The BCM2835 RNG has 32-bit registers only; specify this in the MemoryRegionOps so wrong-sized accesses are rejected rather than getting to the assertions in the read and write functions, and for clarity add the matching .impl constraints. Cc: qemu-stable@nongnu.org Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3394 Fixes: 54a5ba13a9f ("target-arm: Implement BCM2835 hardware RNG") Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260501162700.4092512-1-peter.maydell@linaro.org --- hw/misc/bcm2835_rng.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/misc/bcm2835_rng.c b/hw/misc/bcm2835_rng.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/bcm2835_rng.c +++ b/hw/misc/bcm2835_rng.c @@ -XXX,XX +XXX,XX @@ static const MemoryRegionOps bcm2835_rng_ops = { .read = bcm2835_rng_read, .write = bcm2835_rng_write, .endianness = DEVICE_NATIVE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .valid.min_access_size = 4, + .valid.max_access_size = 4, }; static const VMStateDescription vmstate_bcm2835_rng = { -- 2.43.0
Hi; here's another arm pullreq; main parts here are the new i.MX 8MM EVK board, and a lot of refactoring. thanks -- PMM The following changes since commit aa15257174da180c6a8a9d58f87319cfe61c5520: Merge tag 'pbouvier/pr/plugins-20260424' of https://gitlab.com/p-b-o/qemu into staging (2026-04-25 10:22:04 -0400) are available in the Git repository at: https://gitlab.com/pm215/qemu.git tags/pull-target-arm-20260427 for you to fetch changes up to 4575da5ecb7a27544aa6da1f78e700e8100ceaea: target/arm: report register in WFIT syndromes (2026-04-27 11:46:34 +0100) ---------------------------------------------------------------- target-arm queue: docs/system: add FEAT_AA32 and FEAT_AA64 to emulation list hw/arm: Add the i.MX 8MM EVK(Evaluation Kit) board target/arm: Build M-profile helper code once only hw/arm: Remove hw_error() for the unimplemented CM_LMBUSCNT register hw: Move ARM_SYSCTL_GPIO definitions to arm sysctl specific header target/arm: Allow 'aarch64=off' to be set for TCG CPUs target/arm: Allow some sysregs to not have to be an exact match for migration hw/arm/raspi4b: NOP all DTB nodes when removing unimplemented devices hw/arm/fsl-imx6ul: Implement LCDIF display device target/arm: Refactor syndrome value code to use registerfields target/arm: Report the register in WFxT syndromes ---------------------------------------------------------------- Alex Bennée (24): docs/system: add FEAT_AA32 and FEAT_AA64 to emulation list target/arm: migrate basic syndrome helpers to registerfields target/arm: migrate system/cp trap syndromes to registerfields target/arm: migrate FP/SIMD trap syndromes to registerfields target/arm: migrate eret trap syndromes to registerfields target/arm: migrate SME trap syndromes to registerfields target/arm: migrate PAC trap syndromes to registerfields target/arm: migrate BTI trap syndromes to registerfields target/arm: migrate BXJ trap syndromes to registerfields target/arm: migrate Granule Protection traps to registerfields target/arm: migrate fault syndromes to registerfields target/arm: migrate debug syndromes to registerfields target/arm: migrate wfx syndromes to registerfields target/arm: migrate gcs syndromes to registerfields target/arm: migrate memory op syndromes to registerfields target/arm: migrate check_hcr_el2_trap to use syndrome helper target/arm: use syndrome helpers in arm_cpu_do_interrupt_aarch32_hyp target/arm: use syndrome helpers to set SAME_EL EC bit target/arm: make whpx use syndrome helpers for decode target/arm: make hvf use syndrome helpers for decode target/arm: use syndrome helpers in merge_syn_data_abort target/arm: use syndrome helpers to query VNCR bit target/arm: remove old syndrome defines target/arm: report register in WFIT syndromes Eric Auger (7): target/arm/cpu: Introduce the infrastructure for cpreg migration tolerances target/arm/machine: Handle ToleranceNotOnBothEnds migration tolerances target/arm/machine: Handle ToleranceOnlySrcTestValue migration tolerance target/arm/cpu64: Mitigate migration failures due to spurious TCR_EL1, PIRE0_EL1 and PIR_EL1 target/arm/cpu64: Define cpreg migration tolerance for KVM_REG_ARM_VENDOR_HYP_BMAP_2 target/arm/helper: Define cpreg migration tolerance for DGBDTR_EL0 Revert "target/arm: Reinstate bogus AArch32 DBGDTRTX register for migration compat" Gaurav Sharma (15): hw/arm: Add the i.MX 8MM EVK(Evaluation Kit) board hw/misc/imx8mp_analog: Add property to analog device hw/arm/fsl-imx8mm: Add Analog device IP to iMX8MM SOC hw/arm/fsl-imx8mm: Add Clock Control Module IP to iMX8MM hw/arm/fsl-imx8mm: Implemented support for SNVS hw/arm/fsl-imx8mm: Adding support for USDHC storage controllers hw/arm/fsl-imx8mm: Add PCIe support hw/arm/fsl-imx8mm: Add GPIO controllers hw/arm/fsl-imx8mm: Adding support for I2C emulation hw/arm/fsl-imx8mm: Adding support for SPI controller hw/arm/fsl-imx8mm: Adding support for Watchdog Timers hw/arm/fsl-imx8mm: Adding support for General Purpose Timers hw/arm/fsl-imx8mm: Adding support for ENET ethernet controller hw/arm/fsl-imx8mm: Adding support for USB controller hw/arm/fsl-imx8mm: Adding functional testing of iMX8MM emulation Osama Abdelkader (1): hw/arm/raspi4b: NOP all DTB nodes when removing unimplemented devices Peter Maydell (4): hw: Move ARM_SYSCTL_GPIO definitions to arm sysctl specific header target/arm: Clear AArch64 ID regs from ARMISARegisters if AArch64 disabled target/arm: Allow 'aarch64=off' to be set for TCG CPUs tests/functional/aarch64: Add basic test of TCG aarch64=off Philippe Mathieu-Daudé (9): target/arm: Ignore endianness when setting MTE tags target/arm: Explode MO_TExx -> MO_TE | MO_xx target/arm: Hoist MO_TE into mve_advance_vpt() target/arm: Hoist MO_TE into MVE DO_VSTR() macro target/arm: Introduce mo_endian() helper target/arm: Replace MO_TE -> mo_endian() for MVE helpers target/arm: Compile mve_helper.c once target/arm: Replace MO_TE -> mo_endian() for Cortex-M helpers target/arm: Compile m_helper.c once Thomas Huth (1): hw/arm: Remove hw_error() for the unimplemented CM_LMBUSCNT register Yucai Liu (2): hw/display: Add i.MX6UL LCDIF device model hw/arm/fsl-imx6ul: Wire in the LCDIF device model MAINTAINERS | 16 +- docs/system/arm/cpu-features.rst | 10 +- docs/system/arm/emulation.rst | 2 + docs/system/arm/{imx8mp-evk.rst => imx8m.rst} | 49 +- docs/system/target-arm.rst | 2 +- hw/arm/Kconfig | 25 + hw/arm/fsl-imx6ul.c | 12 +- hw/arm/fsl-imx8mm.c | 704 ++++++++++++++++++++++ hw/arm/imx8mm-evk.c | 130 ++++ hw/arm/integratorcp.c | 3 - hw/arm/meson.build | 2 + hw/arm/raspi4b.c | 10 +- hw/arm/realview.c | 2 +- hw/arm/vexpress.c | 2 +- hw/arm/virt.c | 1 - hw/display/Kconfig | 4 + hw/display/imx6ul_lcdif.c | 453 ++++++++++++++ hw/display/meson.build | 1 + hw/misc/arm_sysctl.c | 2 +- hw/misc/imx8mp_analog.c | 12 +- hw/timer/imx_gpt.c | 26 + hw/vmapple/vmapple.c | 1 - include/hw/arm/fsl-imx6ul.h | 4 +- include/hw/arm/fsl-imx8mm.h | 242 ++++++++ include/hw/arm/primecell.h | 12 - include/hw/display/imx6ul_lcdif.h | 37 ++ include/hw/misc/arm_sysctl.h | 16 + include/hw/misc/imx8mp_analog.h | 3 + include/hw/timer/imx_gpt.h | 2 + target/arm/cpu-features.h | 5 + target/arm/cpu.c | 153 ++++- target/arm/cpu.h | 4 +- target/arm/cpu64.c | 39 ++ target/arm/debug_helper.c | 29 - target/arm/helper.c | 27 +- target/arm/hvf/hvf.c | 14 +- target/arm/internals.h | 60 ++ target/arm/machine.c | 33 +- target/arm/syndrome.h | 595 ++++++++++++++---- target/arm/tcg/debug.c | 2 +- target/arm/tcg/helper-a64.c | 2 +- target/arm/tcg/helper-defs.h | 2 +- target/arm/tcg/m_helper.c | 8 +- target/arm/tcg/meson.build | 6 +- target/arm/tcg/mve_helper.c | 102 ++-- target/arm/tcg/op_helper.c | 7 +- target/arm/tcg/tlb_helper.c | 6 +- target/arm/tcg/translate-a64.c | 2 +- target/arm/tcg/vfp_helper.c | 5 +- target/arm/trace-events | 2 + target/arm/whpx/whpx-all.c | 13 +- tests/functional/aarch64/meson.build | 3 + tests/functional/aarch64/test_imx8mm_evk.py | 69 +++ tests/functional/aarch64/test_virt_aarch64_off.py | 37 ++ tests/qtest/arm-cpu-features.c | 8 +- 55 files changed, 2743 insertions(+), 275 deletions(-) rename docs/system/arm/{imx8mp-evk.rst => imx8m.rst} (58%) create mode 100644 hw/arm/fsl-imx8mm.c create mode 100644 hw/arm/imx8mm-evk.c create mode 100644 hw/display/imx6ul_lcdif.c create mode 100644 include/hw/arm/fsl-imx8mm.h delete mode 100644 include/hw/arm/primecell.h create mode 100644 include/hw/display/imx6ul_lcdif.h create mode 100644 include/hw/misc/arm_sysctl.h create mode 100755 tests/functional/aarch64/test_imx8mm_evk.py create mode 100755 tests/functional/aarch64/test_virt_aarch64_off.py
From: Alex Bennée <alex.bennee@linaro.org> This is just a documentation tweak as we already support both. FEAT_AA32 implies FEAT_AA32EL0. FEAT_AA64 implies FEAT_AA64EL[0123]. This is however useful if you are using emulation.rst as a source of truth of what QEMU emulates and when cross checking with Features.json from Arm. Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260421093506.616307-1-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- docs/system/arm/emulation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/emulation.rst +++ b/docs/system/arm/emulation.rst @@ -XXX,XX +XXX,XX @@ QEMU's TCG emulation includes support for the Armv5, Armv6, Armv7, Armv8 and Armv9 versions of the A-profile architecture. It also has support for the following architecture extensions: +- FEAT_AA32 (PE Support for AArch32) - FEAT_AA32BF16 (AArch32 BFloat16 instructions) - FEAT_AA32EL0 (Support for AArch32 at EL0) - FEAT_AA32EL1 (Support for AArch32 at EL1) @@ -XXX,XX +XXX,XX @@ the following architecture extensions: - FEAT_AA32EL3 (Support for AArch32 at EL3) - FEAT_AA32HPD (AArch32 hierarchical permission disables) - FEAT_AA32I8MM (AArch32 Int8 matrix multiplication instructions) +- FEAT_AA64 (PE uses AArch64 after last reboot) - FEAT_AA64EL0 (Support for AArch64 at EL0) - FEAT_AA64EL1 (Support for AArch64 at EL1) - FEAT_AA64EL2 (Support for AArch64 at EL2) -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Implemented CPUs, RAM, UARTs and Interrupt Controller Other peripherals are represented as TYPE_UNIMPLEMENTED_DEVICE Complete memory map of the SoC is provided. Set default RAM size to 2GB and default CPU count to 4 to match the real i.MX8MM EVK hardware configuration. Documentation is shared with imx8mp-evk to avoid duplication. Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> [PMM: fixed over-long lines in doc] Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 11 +- docs/system/arm/{imx8mp-evk.rst => imx8m.rst} | 49 ++- docs/system/target-arm.rst | 2 +- hw/arm/Kconfig | 12 + hw/arm/fsl-imx8mm.c | 377 ++++++++++++++++++ hw/arm/imx8mm-evk.c | 112 ++++++ hw/arm/meson.build | 2 + include/hw/arm/fsl-imx8mm.h | 158 ++++++++ 8 files changed, 709 insertions(+), 14 deletions(-) rename docs/system/arm/{imx8mp-evk.rst => imx8m.rst} (58%) create mode 100644 hw/arm/fsl-imx8mm.c create mode 100644 hw/arm/imx8mm-evk.c create mode 100644 include/hw/arm/fsl-imx8mm.h diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/pci-host/designware.c F: include/hw/pci-host/designware.h F: docs/system/arm/mcimx7d-sabre.rst +MCIMX8MM-EVK / iMX8MM +M: Gaurav Sharma <gaurav.sharma_7@nxp.com> +L: qemu-arm@nongnu.org +S: Maintained +F: hw/arm/fsl-imx8mm.c +F: hw/arm/imx8mm-evk.c +F: include/hw/arm/fsl-imx8mm.h +F: docs/system/arm/imx8m.rst + MCIMX8MP-EVK / i.MX8MP M: Bernhard Beschow <shentey@gmail.com> L: qemu-arm@nongnu.org @@ -XXX,XX +XXX,XX @@ F: hw/rtc/rs5c372.c F: include/hw/arm/fsl-imx8mp.h F: include/hw/misc/imx8mp_*.h F: include/hw/pci-host/fsl_imx8m_phy.h -F: docs/system/arm/imx8mp-evk.rst +F: docs/system/arm/imx8m.rst F: tests/functional/aarch64/test_imx8mp_evk.py F: tests/qtest/rs5c372-test.c diff --git a/docs/system/arm/imx8mp-evk.rst b/docs/system/arm/imx8m.rst similarity index 58% rename from docs/system/arm/imx8mp-evk.rst rename to docs/system/arm/imx8m.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/imx8mp-evk.rst +++ b/docs/system/arm/imx8m.rst @@ -XXX,XX +XXX,XX @@ -NXP i.MX 8M Plus Evaluation Kit (``imx8mp-evk``) -================================================ +NXP i.MX 8M Plus and i.MX 8M Mini Evaluation Kits (``imx8mp-evk``, ``imx8mm-evk``) +================================================================================== -The ``imx8mp-evk`` machine models the i.MX 8M Plus Evaluation Kit, based on an -i.MX 8M Plus SoC. +The ``imx8mp-evk`` and ``imx8mm-evk`` machine models the i.MX 8M Plus +and i.MX 8M Mini Evaluation Kits, based on i.MX 8M Plus and i.MX8M +Mini SoCs. Supported devices ----------------- -The ``imx8mp-evk`` machine implements the following devices: +The ``imx8mp-evk`` and ``imx8mm-evk`` machines implement the +following devices: * Up to 4 Cortex-A53 cores * Generic Interrupt Controller (GICv3) @@ -XXX,XX +XXX,XX @@ The ``imx8mp-evk`` machine implements the following devices: Boot options ------------ -The ``imx8mp-evk`` machine can start a Linux kernel directly using the standard -``-kernel`` functionality. +The ``imx8mp-evk`` and ``imx8mm-evk`` machines can start a Linux +kernel directly using the standard ``-kernel`` functionality. Direct Linux Kernel Boot '''''''''''''''''''''''' @@ -XXX,XX +XXX,XX @@ is to generate an image with Buildroot. Version 2024.11.1 is tested at the time of writing and involves two steps. First run the following commands in the toplevel directory of the Buildroot source tree: +For i.MX 8M Plus EVK: + .. code-block:: bash $ make freescale_imx8mpevk_defconfig $ make +For i.MX 8M Mini EVK: + +.. code-block:: bash + + $ make freescale_imx8mmevk_defconfig + $ make + Once finished successfully there is an ``output/image`` subfolder. Navigate into it and resize the SD card image to a power of two: @@ -XXX,XX +XXX,XX @@ it and resize the SD card image to a power of two: Now that everything is prepared the machine can be started as follows: +For i.MX 8M Plus EVK: + .. code-block:: bash $ qemu-system-aarch64 -M imx8mp-evk \ @@ -XXX,XX +XXX,XX @@ Now that everything is prepared the machine can be started as follows: -append "root=/dev/mmcblk2p2" \ -drive file=sdcard.img,if=sd,bus=2,format=raw,id=mmcblk2 +For i.MX 8M Mini EVK: + +.. code-block:: bash + + $ qemu-system-aarch64 -M imx8mm-evk -smp 4 -m 2G \ + -display none -serial null -serial stdio \ + -kernel Image \ + -dtb imx8mm-evk.dtb \ + -append "root=/dev/mmcblk2p2" \ + -drive file=sdcard.img,if=sd,bus=2,format=raw,id=mmcblk2 KVM Acceleration ---------------- @@ -XXX,XX +XXX,XX @@ To enable hardware-assisted acceleration via KVM, append ``-accel kvm`` to the command line. While this speeds up performance significantly, be aware of the following limitations: -* The ``imx8mp-evk`` machine is not included under the "virtualization use case" - of :doc:`QEMU's security policy </system/security>`. This means that you - should not trust that it can contain malicious guests, whether it is run - using TCG or KVM. If you don't trust your guests and you're relying on QEMU to - be the security boundary, you want to choose another machine such as ``virt``. +* The ``imx8mp-evk`` and ``imx8mm-evk`` machines are not included + under the "virtualization use case" of :doc:`QEMU's security + policy </system/security>`. This means that you should not trust that + it can contain malicious guests, whether it is run using TCG or KVM. + If you don't trust your guests and you're relying on QEMU to be the + security boundary, you want to choose another machine such as + ``virt``. * Rather than Cortex-A53 CPUs, the same CPU type as the host's will be used. This is a limitation of KVM and may not work with guests with a tight dependency on Cortex-A53. diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -XXX,XX +XXX,XX @@ Board-specific documentation arm/imx25-pdk arm/mcimx6ul-evk arm/mcimx7d-sabre - arm/imx8mp-evk + arm/imx8m arm/orangepi arm/raspi arm/collie diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK depends on TCG select FSL_IMX8MP +config FSL_IMX8MM + bool + select ARM_GIC + select IMX + +config FSL_IMX8MM_EVK + bool + default y + depends on AARCH64 + depends on TCG + select FSL_IMX8MM + config ARM_SMMUV3_ACCEL bool depends on ARM_SMMUV3 diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ +/* + * i.MX 8MM SoC Implementation + * + * Based on hw/arm/fsl-imx6.c + * + * Copyright (c) 2025, NXP Semiconductors + * Author: Gaurav Sharma <gaurav.sharma_7@nxp.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * iMX8MM Reference Manual - https://www.nxp.com/products/i.MX8MMINI -> Documentation + */ + +#include "qemu/osdep.h" +#include "system/address-spaces.h" +#include "hw/arm/bsa.h" +#include "hw/arm/fsl-imx8mm.h" +#include "hw/misc/unimp.h" +#include "hw/core/boards.h" +#include "system/kvm.h" +#include "system/system.h" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#include "target/arm/kvm_arm.h" +#include "qapi/error.h" +#include "qobject/qlist.h" + +static const struct { + hwaddr addr; + size_t size; + const char *name; +} fsl_imx8mm_memmap[] = { + [FSL_IMX8MM_RAM] = { FSL_IMX8MM_RAM_START, FSL_IMX8MM_RAM_SIZE_MAX, "ram" }, + [FSL_IMX8MM_DDR_PHY_BROADCAST] = { 0x3dc00000, 4 * MiB, "ddr_phy_broadcast" }, + [FSL_IMX8MM_DDR_PERF_MON] = { 0x3d800000, 4 * MiB, "ddr_perf_mon" }, + [FSL_IMX8MM_DDR_CTL] = { 0x3d400000, 4 * MiB, "ddr_ctl" }, + [FSL_IMX8MM_DDR_PHY] = { 0x3c000000, 16 * MiB, "ddr_phy" }, + [FSL_IMX8MM_GIC_DIST] = { 0x38800000, 512 * KiB, "gic_dist" }, + [FSL_IMX8MM_GIC_REDIST] = { 0x38880000, 512 * KiB, "gic_redist" }, + [FSL_IMX8MM_VPU] = { 0x38340000, 2 * MiB, "vpu" }, + [FSL_IMX8MM_VPU_BLK_CTRL] = { 0x38330000, 2 * MiB, "vpu_blk_ctrl" }, + [FSL_IMX8MM_VPU_G2_DECODER] = { 0x38310000, 1 * MiB, "vpu_g2_decoder" }, + [FSL_IMX8MM_VPU_G1_DECODER] = { 0x38300000, 1 * MiB, "vpu_g1_decoder" }, + [FSL_IMX8MM_USB2_OTG] = { 0x32e50200, 0x200, "usb2_otg" }, + [FSL_IMX8MM_USB2] = { 0x32e50000, 0x200, "usb2" }, + [FSL_IMX8MM_USB1_OTG] = { 0x32e40200, 0x200, "usb1_otg" }, + [FSL_IMX8MM_USB1] = { 0x32e40000, 0x200, "usb1" }, + [FSL_IMX8MM_GPU2D] = { 0x38000000, 64 * KiB, "gpu2d" }, + [FSL_IMX8MM_QSPI1_RX_BUFFER] = { 0x34000000, 32 * MiB, "qspi1_rx_buffer" }, + [FSL_IMX8MM_PCIE1] = { 0x33800000, 4 * MiB, "pcie1" }, + [FSL_IMX8MM_QSPI1_TX_BUFFER] = { 0x33008000, 32 * KiB, "qspi1_tx_buffer" }, + [FSL_IMX8MM_APBH_DMA] = { 0x33000000, 32 * KiB, "apbh_dma" }, + + /* AIPS-4 Begin */ + [FSL_IMX8MM_TZASC] = { 0x32f80000, 64 * KiB, "tzasc" }, + [FSL_IMX8MM_PCIE_PHY1] = { 0x32f00000, 64 * KiB, "pcie_phy1" }, + [FSL_IMX8MM_MEDIA_BLK_CTL] = { 0x32e28000, 256, "media_blk_ctl" }, + [FSL_IMX8MM_LCDIF] = { 0x32e00000, 64 * KiB, "lcdif" }, + [FSL_IMX8MM_MIPI_DSI] = { 0x32e10000, 64 * KiB, "mipi_dsi" }, + [FSL_IMX8MM_MIPI_CSI] = { 0x32e30000, 64 * KiB, "mipi_csi" }, + [FSL_IMX8MM_AIPS4_CONFIGURATION] = { 0x32df0000, 64 * KiB, "aips4_configuration" }, + /* AIPS-4 End */ + + [FSL_IMX8MM_INTERCONNECT] = { 0x32700000, 1 * MiB, "interconnect" }, + + /* AIPS-3 Begin */ + [FSL_IMX8MM_ENET1] = { 0x30be0000, 64 * KiB, "enet1" }, + [FSL_IMX8MM_SDMA1] = { 0x30bd0000, 64 * KiB, "sdma1" }, + [FSL_IMX8MM_QSPI] = { 0x30bb0000, 64 * KiB, "qspi" }, + [FSL_IMX8MM_USDHC3] = { 0x30b60000, 64 * KiB, "usdhc3" }, + [FSL_IMX8MM_USDHC2] = { 0x30b50000, 64 * KiB, "usdhc2" }, + [FSL_IMX8MM_USDHC1] = { 0x30b40000, 64 * KiB, "usdhc1" }, + [FSL_IMX8MM_SEMAPHORE_HS] = { 0x30ac0000, 64 * KiB, "semaphore_hs" }, + [FSL_IMX8MM_MU_B] = { 0x30ab0000, 64 * KiB, "mu_b" }, + [FSL_IMX8MM_MU_A] = { 0x30aa0000, 64 * KiB, "mu_a" }, + [FSL_IMX8MM_UART4] = { 0x30a60000, 64 * KiB, "uart4" }, + [FSL_IMX8MM_I2C4] = { 0x30a50000, 64 * KiB, "i2c4" }, + [FSL_IMX8MM_I2C3] = { 0x30a40000, 64 * KiB, "i2c3" }, + [FSL_IMX8MM_I2C2] = { 0x30a30000, 64 * KiB, "i2c2" }, + [FSL_IMX8MM_I2C1] = { 0x30a20000, 64 * KiB, "i2c1" }, + [FSL_IMX8MM_AIPS3_CONFIGURATION] = { 0x309f0000, 64 * KiB, "aips3_configuration" }, + [FSL_IMX8MM_CAAM] = { 0x30900000, 256 * KiB, "caam" }, + [FSL_IMX8MM_SPBA1] = { 0x308f0000, 64 * KiB, "spba1" }, + [FSL_IMX8MM_UART2] = { 0x30890000, 64 * KiB, "uart2" }, + [FSL_IMX8MM_UART3] = { 0x30880000, 64 * KiB, "uart3" }, + [FSL_IMX8MM_UART1] = { 0x30860000, 64 * KiB, "uart1" }, + [FSL_IMX8MM_ECSPI3] = { 0x30840000, 64 * KiB, "ecspi3" }, + [FSL_IMX8MM_ECSPI2] = { 0x30830000, 64 * KiB, "ecspi2" }, + [FSL_IMX8MM_ECSPI1] = { 0x30820000, 64 * KiB, "ecspi1" }, + /* AIPS-3 End */ + + /* AIPS-2 Begin */ + [FSL_IMX8MM_QOSC] = { 0x307f0000, 64 * KiB, "qosc" }, + [FSL_IMX8MM_PERFMON2] = { 0x307d0000, 64 * KiB, "perfmon2" }, + [FSL_IMX8MM_PERFMON1] = { 0x307c0000, 64 * KiB, "perfmon1" }, + [FSL_IMX8MM_GPT4] = { 0x30700000, 64 * KiB, "gpt4" }, + [FSL_IMX8MM_GPT5] = { 0x306f0000, 64 * KiB, "gpt5" }, + [FSL_IMX8MM_GPT6] = { 0x306e0000, 64 * KiB, "gpt6" }, + [FSL_IMX8MM_SYSCNT_CTRL] = { 0x306c0000, 64 * KiB, "syscnt_ctrl" }, + [FSL_IMX8MM_SYSCNT_CMP] = { 0x306b0000, 64 * KiB, "syscnt_cmp" }, + [FSL_IMX8MM_SYSCNT_RD] = { 0x306a0000, 64 * KiB, "syscnt_rd" }, + [FSL_IMX8MM_PWM4] = { 0x30690000, 64 * KiB, "pwm4" }, + [FSL_IMX8MM_PWM3] = { 0x30680000, 64 * KiB, "pwm3" }, + [FSL_IMX8MM_PWM2] = { 0x30670000, 64 * KiB, "pwm2" }, + [FSL_IMX8MM_PWM1] = { 0x30660000, 64 * KiB, "pwm1" }, + [FSL_IMX8MM_AIPS2_CONFIGURATION] = { 0x305f0000, 64 * KiB, "aips2_configuration" }, + /* AIPS-2 End */ + + /* AIPS-1 Begin */ + [FSL_IMX8MM_CSU] = { 0x303e0000, 64 * KiB, "csu" }, + [FSL_IMX8MM_RDC] = { 0x303d0000, 64 * KiB, "rdc" }, + [FSL_IMX8MM_SEMAPHORE2] = { 0x303c0000, 64 * KiB, "semaphore2" }, + [FSL_IMX8MM_SEMAPHORE1] = { 0x303b0000, 64 * KiB, "semaphore1" }, + [FSL_IMX8MM_GPC] = { 0x303a0000, 64 * KiB, "gpc" }, + [FSL_IMX8MM_SRC] = { 0x30390000, 64 * KiB, "src" }, + [FSL_IMX8MM_CCM] = { 0x30380000, 64 * KiB, "ccm" }, + [FSL_IMX8MM_SNVS_HP] = { 0x30370000, 64 * KiB, "snvs_hp" }, + [FSL_IMX8MM_ANA_PLL] = { 0x30360000, 64 * KiB, "ana_pll" }, + [FSL_IMX8MM_OCOTP_CTRL] = { 0x30350000, 64 * KiB, "ocotp_ctrl" }, + [FSL_IMX8MM_IOMUXC_GPR] = { 0x30340000, 64 * KiB, "iomuxc_gpr" }, + [FSL_IMX8MM_IOMUXC] = { 0x30330000, 64 * KiB, "iomuxc" }, + [FSL_IMX8MM_GPT3] = { 0x302f0000, 64 * KiB, "gpt3" }, + [FSL_IMX8MM_GPT2] = { 0x302e0000, 64 * KiB, "gpt2" }, + [FSL_IMX8MM_GPT1] = { 0x302d0000, 64 * KiB, "gpt1" }, + [FSL_IMX8MM_SDMA2] = { 0x302c0000, 64 * KiB, "sdma2" }, + [FSL_IMX8MM_SDMA3] = { 0x302b0000, 64 * KiB, "sdma3" }, + [FSL_IMX8MM_WDOG3] = { 0x302a0000, 64 * KiB, "wdog3" }, + [FSL_IMX8MM_WDOG2] = { 0x30290000, 64 * KiB, "wdog2" }, + [FSL_IMX8MM_WDOG1] = { 0x30280000, 64 * KiB, "wdog1" }, + [FSL_IMX8MM_ANA_OSC] = { 0x30270000, 64 * KiB, "ana_osc" }, + [FSL_IMX8MM_ANA_TSENSOR] = { 0x30260000, 64 * KiB, "ana_tsensor" }, + [FSL_IMX8MM_GPIO5] = { 0x30240000, 64 * KiB, "gpio5" }, + [FSL_IMX8MM_GPIO4] = { 0x30230000, 64 * KiB, "gpio4" }, + [FSL_IMX8MM_GPIO3] = { 0x30220000, 64 * KiB, "gpio3" }, + [FSL_IMX8MM_GPIO2] = { 0x30210000, 64 * KiB, "gpio2" }, + [FSL_IMX8MM_GPIO1] = { 0x30200000, 64 * KiB, "gpio1" }, + [FSL_IMX8MM_AIPS1_CONFIGURATION] = { 0x301f0000, 64 * KiB, "aips1_configuration" }, + [FSL_IMX8MM_SAI6] = { 0x30060000, 64 * KiB, "sai6" }, + [FSL_IMX8MM_SAI5] = { 0x30050000, 64 * KiB, "sai5" }, + [FSL_IMX8MM_SAI3] = { 0x30030000, 64 * KiB, "sai3" }, + [FSL_IMX8MM_SAI2] = { 0x30020000, 64 * KiB, "sai2" }, + [FSL_IMX8MM_SAI1] = { 0x30010000, 64 * KiB, "sai1" }, + + /* AIPS-1 End */ + + [FSL_IMX8MM_A53_DAP] = { 0x28000000, 16 * MiB, "a53_dap" }, + [FSL_IMX8MM_PCIE1_MEM] = { 0x18000000, 128 * MiB, "pcie1_mem" }, + [FSL_IMX8MM_QSPI_MEM] = { 0x08000000, 256 * MiB, "qspi_mem" }, + [FSL_IMX8MM_OCRAM] = { 0x00900000, 256 * KiB, "ocram" }, + [FSL_IMX8MM_TCM_DTCM] = { 0x00800000, 128 * KiB, "tcm_dtcm" }, + [FSL_IMX8MM_TCM_ITCM] = { 0x007e0000, 128 * KiB, "tcm_itcm" }, + [FSL_IMX8MM_OCRAM_S] = { 0x00180000, 32 * KiB, "ocram_s" }, + [FSL_IMX8MM_CAAM_MEM] = { 0x00100000, 32 * KiB, "caam_mem" }, + [FSL_IMX8MM_BOOT_ROM_PROTECTED] = { 0x0003f000, 4 * KiB, "boot_rom_protected" }, + [FSL_IMX8MM_BOOT_ROM] = { 0x00000000, 252 * KiB, "boot_rom" }, +}; + +static void fsl_imx8mm_init(Object *obj) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + FslImx8mmState *s = FSL_IMX8MM(obj); + const char *cpu_type = ms->cpu_type ?: ARM_CPU_TYPE_NAME("cortex-a53"); + int i; + + for (i = 0; i < MIN(ms->smp.cpus, FSL_IMX8MM_NUM_CPUS); i++) { + g_autofree char *name = g_strdup_printf("cpu%d", i); + object_initialize_child(obj, name, &s->cpu[i], cpu_type); + } + + object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { + g_autofree char *name = g_strdup_printf("uart%d", i + 1); + object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); + } + +} + +static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) +{ + MachineState *ms = MACHINE(qdev_get_machine()); + FslImx8mmState *s = FSL_IMX8MM(dev); + DeviceState *gicdev = DEVICE(&s->gic); + int i; + + if (ms->smp.cpus > FSL_IMX8MM_NUM_CPUS) { + error_setg(errp, "%s: Only %d CPUs are supported (%d requested)", + TYPE_FSL_IMX8MM, FSL_IMX8MM_NUM_CPUS, ms->smp.cpus); + return; + } + + /* CPUs */ + for (i = 0; i < ms->smp.cpus; i++) { + /* On uniprocessor, the CBAR is set to 0 */ + if (ms->smp.cpus > 1 && + object_property_find(OBJECT(&s->cpu[i]), "reset-cbar")) { + object_property_set_int(OBJECT(&s->cpu[i]), "reset-cbar", + fsl_imx8mm_memmap[FSL_IMX8MM_GIC_DIST].addr, + &error_abort); + } + + /* + * CNTFID0 base frequency in Hz of system counter + */ + object_property_set_int(OBJECT(&s->cpu[i]), "cntfrq", 8000000, + &error_abort); + + if (object_property_find(OBJECT(&s->cpu[i]), "has_el2")) { + object_property_set_bool(OBJECT(&s->cpu[i]), "has_el2", + !kvm_enabled(), &error_abort); + } + + if (object_property_find(OBJECT(&s->cpu[i]), "has_el3")) { + object_property_set_bool(OBJECT(&s->cpu[i]), "has_el3", + !kvm_enabled(), &error_abort); + } + + if (i) { + /* + * Secondary CPUs start in powered-down state (and can be + * powered up via the SRC system reset controller) + */ + object_property_set_bool(OBJECT(&s->cpu[i]), "start-powered-off", + true, &error_abort); + } + + if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) { + return; + } + } + + /* GIC */ + { + SysBusDevice *gicsbd = SYS_BUS_DEVICE(&s->gic); + QList *redist_region_count; + bool pmu = object_property_get_bool(OBJECT(first_cpu), "pmu", NULL); + + qdev_prop_set_uint32(gicdev, "num-cpu", ms->smp.cpus); + qdev_prop_set_uint32(gicdev, "num-irq", + FSL_IMX8MM_NUM_IRQS + GIC_INTERNAL); + redist_region_count = qlist_new(); + qlist_append_int(redist_region_count, ms->smp.cpus); + qdev_prop_set_array(gicdev, "redist-region-count", redist_region_count); + object_property_set_link(OBJECT(&s->gic), "sysmem", + OBJECT(get_system_memory()), &error_fatal); + if (!sysbus_realize(gicsbd, errp)) { + return; + } + sysbus_mmio_map(gicsbd, 0, fsl_imx8mm_memmap[FSL_IMX8MM_GIC_DIST].addr); + sysbus_mmio_map(gicsbd, 1, fsl_imx8mm_memmap[FSL_IMX8MM_GIC_REDIST].addr); + + /* + * Wire the outputs from each CPU's generic timer and the GICv3 + * maintenance interrupt signal to the appropriate GIC PPI inputs, and + * the GIC's IRQ/FIQ interrupt outputs to the CPU's inputs. + */ + for (i = 0; i < ms->smp.cpus; i++) { + DeviceState *cpudev = DEVICE(&s->cpu[i]); + int intidbase = FSL_IMX8MM_NUM_IRQS + i * GIC_INTERNAL; + qemu_irq irq; + + /* + * Mapping from the output timer irq lines from the CPU to the + * GIC PPI inputs. + */ + static const int timer_irqs[] = { + [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ, + [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ, + [GTIMER_HYP] = ARCH_TIMER_NS_EL2_IRQ, + [GTIMER_SEC] = ARCH_TIMER_S_EL1_IRQ, + }; + + for (int j = 0; j < ARRAY_SIZE(timer_irqs); j++) { + irq = qdev_get_gpio_in(gicdev, intidbase + timer_irqs[j]); + qdev_connect_gpio_out(cpudev, j, irq); + } + + irq = qdev_get_gpio_in(gicdev, intidbase + ARCH_GIC_MAINT_IRQ); + qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", + 0, irq); + + irq = qdev_get_gpio_in(gicdev, intidbase + VIRTUAL_PMU_IRQ); + qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0, irq); + + sysbus_connect_irq(gicsbd, i, + qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicsbd, i + ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(gicsbd, i + 2 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicsbd, i + 3 * ms->smp.cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + + if (kvm_enabled()) { + if (pmu) { + assert(arm_feature(&s->cpu[i].env, ARM_FEATURE_PMU)); + if (kvm_irqchip_in_kernel()) { + kvm_arm_pmu_set_irq(&s->cpu[i], VIRTUAL_PMU_IRQ); + } + kvm_arm_pmu_init(&s->cpu[i]); + } + } + } + } + + /* UARTs */ + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } serial_table[FSL_IMX8MM_NUM_UARTS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_UART1].addr, FSL_IMX8MM_UART1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_UART2].addr, FSL_IMX8MM_UART2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_UART3].addr, FSL_IMX8MM_UART3_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_UART4].addr, FSL_IMX8MM_UART4_IRQ }, + }; + + qdev_prop_set_chr(DEVICE(&s->uart[i]), "chardev", serial_hd(i)); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, serial_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, + qdev_get_gpio_in(gicdev, serial_table[i].irq)); + } + + /* On-Chip RAM */ + if (!memory_region_init_ram(&s->ocram, OBJECT(dev), "imx8mm.ocram", + fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].size, + errp)) { + return; + } + memory_region_add_subregion(get_system_memory(), + fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, + &s->ocram); + + /* Unimplemented devices */ + for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { + switch (i) { + case FSL_IMX8MM_GIC_DIST: + case FSL_IMX8MM_GIC_REDIST: + case FSL_IMX8MM_RAM: + case FSL_IMX8MM_OCRAM: + case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: + /* device implemented and treated above */ + break; + + default: + create_unimplemented_device(fsl_imx8mm_memmap[i].name, + fsl_imx8mm_memmap[i].addr, + fsl_imx8mm_memmap[i].size); + break; + } + } +} + +static void fsl_imx8mm_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = fsl_imx8mm_realize; + + dc->desc = "i.MX 8MM SoC"; +} + +static const TypeInfo fsl_imx8mm_types[] = { + { + .name = TYPE_FSL_IMX8MM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(FslImx8mmState), + .instance_init = fsl_imx8mm_init, + .class_init = fsl_imx8mm_class_init, + }, +}; + +DEFINE_TYPES(fsl_imx8mm_types) diff --git a/hw/arm/imx8mm-evk.c b/hw/arm/imx8mm-evk.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/arm/imx8mm-evk.c @@ -XXX,XX +XXX,XX @@ +/* + * NXP i.MX 8MM Evaluation Kit System Emulation + * + * Copyright (c) 2025, NXP Semiconductors + * Author: Gaurav Sharma <gaurav.sharma_7@nxp.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "system/address-spaces.h" +#include "hw/arm/boot.h" +#include "hw/arm/fsl-imx8mm.h" +#include "hw/arm/machines-qom.h" +#include "hw/core/boards.h" +#include "hw/core/qdev-properties.h" +#include "system/kvm.h" +#include "system/qtest.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include <libfdt.h> + +static void imx8mm_evk_modify_dtb(const struct arm_boot_info *info, void *fdt) +{ + int i, offset; + + /* Temporarily disable following nodes until they are implemented */ + const char *nodes_to_remove[] = { + "nxp,imx8mm-fspi", + "fsl,imx8mm-mipi-csi", + "fsl,imx8mm-mipi-dsim" + }; + + for (i = 0; i < ARRAY_SIZE(nodes_to_remove); i++) { + const char *dev_str = nodes_to_remove[i]; + + offset = fdt_node_offset_by_compatible(fdt, -1, dev_str); + while (offset >= 0) { + fdt_nop_node(fdt, offset); + offset = fdt_node_offset_by_compatible(fdt, offset, dev_str); + } + } + + /* Remove cpu-idle-states property from CPU nodes */ + offset = fdt_node_offset_by_compatible(fdt, -1, "arm,cortex-a53"); + while (offset >= 0) { + fdt_nop_property(fdt, offset, "cpu-idle-states"); + offset = fdt_node_offset_by_compatible(fdt, offset, "arm,cortex-a53"); + } + + if (kvm_enabled()) { + /* Use system counter frequency from host CPU to fix time in guest */ + offset = fdt_node_offset_by_compatible(fdt, -1, "arm,armv8-timer"); + while (offset >= 0) { + fdt_nop_property(fdt, offset, "clock-frequency"); + offset = fdt_node_offset_by_compatible(fdt, offset, "arm,armv8-timer"); + } + } +} + +static void imx8mm_evk_init(MachineState *machine) +{ + static struct arm_boot_info boot_info; + FslImx8mmState *s; + + if (machine->ram_size > FSL_IMX8MM_RAM_SIZE_MAX) { + error_report("RAM size " RAM_ADDR_FMT " above max supported (%08" PRIx64 ")", + machine->ram_size, FSL_IMX8MM_RAM_SIZE_MAX); + exit(1); + } + + boot_info = (struct arm_boot_info) { + .loader_start = FSL_IMX8MM_RAM_START, + .board_id = -1, + .ram_size = machine->ram_size, + .psci_conduit = QEMU_PSCI_CONDUIT_SMC, + .modify_dtb = imx8mm_evk_modify_dtb, + }; + + s = FSL_IMX8MM(object_new_with_props(TYPE_FSL_IMX8MM, OBJECT(machine), + "soc", &error_fatal, NULL)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal); + + memory_region_add_subregion(get_system_memory(), FSL_IMX8MM_RAM_START, + machine->ram); + + if (!qtest_enabled()) { + arm_load_kernel(&s->cpu[0], machine, &boot_info); + } +} + +static const char *imx8mm_evk_get_default_cpu_type(const MachineState *ms) +{ + if (kvm_enabled()) { + return ARM_CPU_TYPE_NAME("host"); + } + + return ARM_CPU_TYPE_NAME("cortex-a53"); +} + +static void imx8mm_evk_machine_init(MachineClass *mc) +{ + mc->desc = "NXP i.MX 8MM EVK Board"; + mc->init = imx8mm_evk_init; + mc->max_cpus = FSL_IMX8MM_NUM_CPUS; + mc->default_cpus = FSL_IMX8MM_NUM_CPUS; + mc->default_ram_size = 2 * GiB; + mc->default_ram_id = "imx8mm-evk.ram"; + mc->get_default_cpu_type = imx8mm_evk_get_default_cpu_type; +} + +DEFINE_MACHINE_AARCH64("imx8mm-evk", imx8mm_evk_machine_init) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -XXX,XX +XXX,XX @@ arm_common_ss.add(when: 'CONFIG_ARMSSE', if_true: files('armsse.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX7', if_true: files('fsl-imx7.c', 'mcimx7d-sabre.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX8MP', if_true: files('fsl-imx8mp.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX8MP_EVK', if_true: files('imx8mp-evk.c')) +arm_common_ss.add(when: 'CONFIG_FSL_IMX8MM', if_true: files('fsl-imx8mm.c')) +arm_common_ss.add(when: 'CONFIG_FSL_IMX8MM_EVK', if_true: files('imx8mm-evk.c')) arm_common_ss.add(when: 'CONFIG_ARM_SMMUV3', if_true: files('smmuv3.c')) arm_common_ss.add(when: 'CONFIG_ARM_SMMUV3_ACCEL', if_true: files('smmuv3-accel.c')) stub_ss.add(files('smmuv3-accel-stubs.c')) diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ +/* + * i.MX 8MM SoC Definitions + * + * Copyright (c) 2025, NXP Semiconductors + * Author: Gaurav Sharma <gaurav.sharma_7@nxp.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef FSL_IMX8MM_H +#define FSL_IMX8MM_H + +#include "cpu.h" +#include "hw/char/imx_serial.h" +#include "hw/intc/arm_gicv3_common.h" +#include "qom/object.h" +#include "qemu/units.h" + +#define TYPE_FSL_IMX8MM "fsl-imx8mm" +OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) + +#define FSL_IMX8MM_RAM_START 0x40000000 +#define FSL_IMX8MM_RAM_SIZE_MAX (4 * GiB) + +enum FslImx8mmConfiguration { + FSL_IMX8MM_NUM_CPUS = 4, + FSL_IMX8MM_NUM_IRQS = 128, + FSL_IMX8MM_NUM_UARTS = 4, +}; + +struct FslImx8mmState { + SysBusDevice parent_obj; + + ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; + GICv3State gic; + IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; + MemoryRegion ocram; +}; + +enum FslImx8mmMemoryRegions { + FSL_IMX8MM_A53_DAP, + FSL_IMX8MM_AIPS1_CONFIGURATION, + FSL_IMX8MM_AIPS2_CONFIGURATION, + FSL_IMX8MM_AIPS3_CONFIGURATION, + FSL_IMX8MM_AIPS4_CONFIGURATION, + FSL_IMX8MM_ANA_OSC, + FSL_IMX8MM_ANA_PLL, + FSL_IMX8MM_ANA_TSENSOR, + FSL_IMX8MM_APBH_DMA, + FSL_IMX8MM_BOOT_ROM, + FSL_IMX8MM_BOOT_ROM_PROTECTED, + FSL_IMX8MM_CAAM, + FSL_IMX8MM_CAAM_MEM, + FSL_IMX8MM_CCM, + FSL_IMX8MM_CSU, + FSL_IMX8MM_DDR_CTL, + FSL_IMX8MM_DDR_PERF_MON, + FSL_IMX8MM_DDR_PHY, + FSL_IMX8MM_DDR_PHY_BROADCAST, + FSL_IMX8MM_ECSPI1, + FSL_IMX8MM_ECSPI2, + FSL_IMX8MM_ECSPI3, + FSL_IMX8MM_ENET1, + FSL_IMX8MM_GIC_DIST, + FSL_IMX8MM_GIC_REDIST, + FSL_IMX8MM_GPC, + FSL_IMX8MM_GPIO1, + FSL_IMX8MM_GPIO2, + FSL_IMX8MM_GPIO3, + FSL_IMX8MM_GPIO4, + FSL_IMX8MM_GPIO5, + FSL_IMX8MM_GPT1, + FSL_IMX8MM_GPT2, + FSL_IMX8MM_GPT3, + FSL_IMX8MM_GPT4, + FSL_IMX8MM_GPT5, + FSL_IMX8MM_GPT6, + FSL_IMX8MM_GPU2D, + FSL_IMX8MM_I2C1, + FSL_IMX8MM_I2C2, + FSL_IMX8MM_I2C3, + FSL_IMX8MM_I2C4, + FSL_IMX8MM_INTERCONNECT, + FSL_IMX8MM_IOMUXC, + FSL_IMX8MM_IOMUXC_GPR, + FSL_IMX8MM_MEDIA_BLK_CTL, + FSL_IMX8MM_LCDIF, + FSL_IMX8MM_MIPI_CSI, + FSL_IMX8MM_MIPI_DSI, + FSL_IMX8MM_MU_A, + FSL_IMX8MM_MU_B, + FSL_IMX8MM_OCOTP_CTRL, + FSL_IMX8MM_OCRAM, + FSL_IMX8MM_OCRAM_S, + FSL_IMX8MM_PCIE1, + FSL_IMX8MM_PCIE1_MEM, + FSL_IMX8MM_PCIE_PHY1, + FSL_IMX8MM_PERFMON1, + FSL_IMX8MM_PERFMON2, + FSL_IMX8MM_PWM1, + FSL_IMX8MM_PWM2, + FSL_IMX8MM_PWM3, + FSL_IMX8MM_PWM4, + FSL_IMX8MM_QOSC, + FSL_IMX8MM_QSPI, + FSL_IMX8MM_QSPI1_RX_BUFFER, + FSL_IMX8MM_QSPI1_TX_BUFFER, + FSL_IMX8MM_QSPI_MEM, + FSL_IMX8MM_RAM, + FSL_IMX8MM_RDC, + FSL_IMX8MM_SAI1, + FSL_IMX8MM_SAI2, + FSL_IMX8MM_SAI3, + FSL_IMX8MM_SAI5, + FSL_IMX8MM_SAI6, + FSL_IMX8MM_SDMA1, + FSL_IMX8MM_SDMA2, + FSL_IMX8MM_SDMA3, + FSL_IMX8MM_SEMAPHORE1, + FSL_IMX8MM_SEMAPHORE2, + FSL_IMX8MM_SEMAPHORE_HS, + FSL_IMX8MM_SNVS_HP, + FSL_IMX8MM_SPBA1, + FSL_IMX8MM_SRC, + FSL_IMX8MM_SYSCNT_CMP, + FSL_IMX8MM_SYSCNT_CTRL, + FSL_IMX8MM_SYSCNT_RD, + FSL_IMX8MM_TCM_DTCM, + FSL_IMX8MM_TCM_ITCM, + FSL_IMX8MM_TZASC, + FSL_IMX8MM_UART1, + FSL_IMX8MM_UART2, + FSL_IMX8MM_UART3, + FSL_IMX8MM_UART4, + FSL_IMX8MM_USB1, + FSL_IMX8MM_USB2, + FSL_IMX8MM_USB1_OTG, + FSL_IMX8MM_USB2_OTG, + FSL_IMX8MM_USDHC1, + FSL_IMX8MM_USDHC2, + FSL_IMX8MM_USDHC3, + FSL_IMX8MM_VPU, + FSL_IMX8MM_VPU_BLK_CTRL, + FSL_IMX8MM_VPU_G1_DECODER, + FSL_IMX8MM_VPU_G2_DECODER, + FSL_IMX8MM_WDOG1, + FSL_IMX8MM_WDOG2, + FSL_IMX8MM_WDOG3, +}; + +enum FslImx8mmIrqs { + FSL_IMX8MM_UART1_IRQ = 26, + FSL_IMX8MM_UART2_IRQ = 27, + FSL_IMX8MM_UART3_IRQ = 28, + FSL_IMX8MM_UART4_IRQ = 29, +}; + +#endif /* FSL_IMX8MM_H */ -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Add configurable properties for register reset values that differ between i.MX 8M variants (Plus, Mini, etc.). This allows the same device implementation to be shared across multiple SoCs. Properties added: - arm-pll-fdiv-ctl0-reset: ARM PLL divider control reset value Default value is set to match i.MX 8MP reset value (0x000FA031). This can be overridden in the variant like iMX8MM with its own reset value. Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/misc/imx8mp_analog.c | 12 +++++++++++- include/hw/misc/imx8mp_analog.h | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/hw/misc/imx8mp_analog.c b/hw/misc/imx8mp_analog.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/imx8mp_analog.c +++ b/hw/misc/imx8mp_analog.c @@ -XXX,XX +XXX,XX @@ #include "qemu/log.h" #include "hw/misc/imx8mp_analog.h" +#include "hw/core/qdev-properties.h" #include "migration/vmstate.h" #define ANALOG_PLL_LOCK BIT(31) @@ -XXX,XX +XXX,XX @@ static void imx8mp_analog_reset(DeviceState *dev) s->analog[ANALOG_VPU_PLL_LOCKD_CTRL] = 0x0010003f; s->analog[ANALOG_VPU_PLL_MNIT_CTRL] = 0x00280081; s->analog[ANALOG_ARM_PLL_GEN_CTRL] = 0x00000810; - s->analog[ANALOG_ARM_PLL_FDIV_CTL0] = 0x000fa031; + + /* Use property value instead of hardcoded */ + s->analog[ANALOG_ARM_PLL_FDIV_CTL0] = s->arm_pll_fdiv_ctl0_reset; + s->analog[ANALOG_ARM_PLL_LOCKD_CTRL] = 0x0010003f; s->analog[ANALOG_ARM_PLL_MNIT_CTRL] = 0x00280081; s->analog[ANALOG_SYS_PLL1_GEN_CTRL] = 0x0aaaa810; @@ -XXX,XX +XXX,XX @@ static const VMStateDescription imx8mp_analog_vmstate = { }, }; +static const Property imx8mp_analog_properties[] = { + DEFINE_PROP_UINT32("arm-pll-fdiv-ctl0-reset", IMX8MPAnalogState, + arm_pll_fdiv_ctl0_reset, 0x000fa031), /* imx8mp default */ +}; + static void imx8mp_analog_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, imx8mp_analog_reset); + device_class_set_props(dc, imx8mp_analog_properties); dc->vmsd = &imx8mp_analog_vmstate; dc->desc = "i.MX 8M Plus Analog Module"; } diff --git a/include/hw/misc/imx8mp_analog.h b/include/hw/misc/imx8mp_analog.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/imx8mp_analog.h +++ b/include/hw/misc/imx8mp_analog.h @@ -XXX,XX +XXX,XX @@ struct IMX8MPAnalogState { } mmio; uint32_t analog[ANALOG_MAX]; + + /* Property for variant-specific reset values */ + uint32_t arm_pll_fdiv_ctl0_reset; }; #endif /* IMX8MP_ANALOG_H */ -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Add the Analog IP to i.MX8MM SoC. iMX8MM and i.MX8MP uses the same Analog IP so the analog ip source will be shared. The ARM PLL divider control register (arm-pll-fdiv-ctl0) has a different reset value on i.MX8MM (0x000fa030) compared to i.MX8MP (0x000fa031). So iMX8MM will be overriding this property with its own reset-value. Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 12 ++++++++++++ include/hw/arm/fsl-imx8mm.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK config FSL_IMX8MM bool select ARM_GIC + select FSL_IMX8MP_ANALOG select IMX config FSL_IMX8MM_EVK diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) } } + /* Analog */ + object_property_set_uint(OBJECT(&s->analog), "arm-pll-fdiv-ctl0-reset", + 0x000fa030, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->analog), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->analog), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_ANA_PLL].addr); + /* UARTs */ for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { + case FSL_IMX8MM_ANA_PLL: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/imx8mp_analog.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMX8MPAnalogState analog; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; }; -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Add the Clock Control Module (CCM) device to i.MX8MM SoC. The CCM implementation is shared with i.MX8MP as the register layout is identical between the two variants.Hence iMX8MM will be using the source of iMX8MP CCM. Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 10 ++++++++++ include/hw/arm/fsl-imx8mm.h | 2 ++ 3 files changed, 13 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM bool select ARM_GIC select FSL_IMX8MP_ANALOG + select FSL_IMX8MP_CCM select IMX config FSL_IMX8MM_EVK diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, "gic", &s->gic, gicv3_class_name()); + object_initialize_child(obj, "ccm", &s->ccm, TYPE_IMX8MP_CCM); + object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) } } + /* CCM */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->ccm), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccm), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_CCM].addr); + /* Analog */ object_property_set_uint(OBJECT(&s->analog), "arm-pll-fdiv-ctl0-reset", 0x000fa030, &error_abort); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { case FSL_IMX8MM_ANA_PLL: + case FSL_IMX8MM_CCM: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx8mp_analog.h" +#include "hw/misc/imx8mp_ccm.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> SNVS contains an RTC which allows Linux to deal correctly with time Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/fsl-imx8mm.c | 10 ++++++++++ include/hw/arm/fsl-imx8mm.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, "analog", &s->analog, TYPE_IMX8MP_ANALOG); + object_initialize_child(obj, "snvs", &s->snvs, TYPE_IMX7_SNVS); + for (i = 0; i < FSL_IMX8MM_NUM_UARTS; i++) { g_autofree char *name = g_strdup_printf("uart%d", i + 1); object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* SNVS */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_SNVS_HP].addr); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_RAM: case FSL_IMX8MM_OCRAM: + case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: /* device implemented and treated above */ break; diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/intc/arm_gicv3_common.h" +#include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { GICv3State gic; IMX8MPCCMState ccm; IMX8MPAnalogState analog; + IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; }; -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of SD/MMC cards through a virtual SDHCI interface The emulated SDHCI controller allows guest OS to use emulated storage as a standard block device. This will allow running the images such as those generated by Buildroot. Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 25 +++++++++++++++++++++++++ hw/arm/imx8mm-evk.c | 17 +++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 7 +++++++ 4 files changed, 50 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select SDHCI config FSL_IMX8MM_EVK bool diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { + g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); + object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); + } } static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* USDHCs */ + for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } usdhc_table[FSL_IMX8MM_NUM_USDHCS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_USDHC1].addr, FSL_IMX8MM_USDHC1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_USDHC2].addr, FSL_IMX8MM_USDHC2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_USDHC3].addr, FSL_IMX8MM_USDHC3_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->usdhc[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->usdhc[i]), 0, usdhc_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->usdhc[i]), 0, + qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); + } + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_OCRAM: case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: + case FSL_IMX8MM_USDHC1 ... FSL_IMX8MM_USDHC3: /* device implemented and treated above */ break; diff --git a/hw/arm/imx8mm-evk.c b/hw/arm/imx8mm-evk.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/imx8mm-evk.c +++ b/hw/arm/imx8mm-evk.c @@ -XXX,XX +XXX,XX @@ static void imx8mm_evk_init(MachineState *machine) memory_region_add_subregion(get_system_memory(), FSL_IMX8MM_RAM_START, machine->ram); + for (int i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { + BusState *bus; + DeviceState *carddev; + BlockBackend *blk; + DriveInfo *di = drive_get(IF_SD, i, 0); + + if (!di) { + continue; + } + + blk = blk_by_legacy_dinfo(di); + bus = qdev_get_child_bus(DEVICE(&s->usdhc[i]), "sd-bus"); + carddev = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(carddev, "drive", blk, &error_fatal); + qdev_realize_and_unref(carddev, bus, &error_fatal); + } + if (!qtest_enabled()) { arm_load_kernel(&s->cpu[0], machine, &boot_info); } diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/sd/sdhci.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, + FSL_IMX8MM_NUM_USDHCS = 3, }; struct FslImx8mmState { @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX7SNVSState snvs; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; + SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmMemoryRegions { }; enum FslImx8mmIrqs { + FSL_IMX8MM_USDHC1_IRQ = 22, + FSL_IMX8MM_USDHC2_IRQ = 23, + FSL_IMX8MM_USDHC3_IRQ = 24, + FSL_IMX8MM_UART1_IRQ = 26, FSL_IMX8MM_UART2_IRQ = 27, FSL_IMX8MM_UART3_IRQ = 28, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> This enables support for Designware PCI Express Controller emulation It provides a controlled environment to debug the linux pci subsystem Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 3 +++ hw/arm/fsl-imx8mm.c | 30 ++++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 10 ++++++++++ 3 files changed, 43 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK config FSL_IMX8MM bool + imply PCI_DEVICES select ARM_GIC select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX select SDHCI + select PCI_EXPRESS_DESIGNWARE + select PCI_EXPRESS_FSL_IMX8M_PHY config FSL_IMX8MM_EVK bool diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); + object_initialize_child(obj, "pcie_phy", &s->pcie_phy, + TYPE_FSL_IMX8M_PCIE_PHY); } static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, fsl_imx8mm_memmap[FSL_IMX8MM_SNVS_HP].addr); + /* PCIe */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_PCIE1].addr); + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTA_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 1, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTB_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 2, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTC_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 3, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_INTD_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->pcie), 4, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_PCI_MSI_IRQ)); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie_phy), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->pcie_phy), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_PCIE_PHY1].addr); + /* Unimplemented devices */ for (i = 0; i < ARRAY_SIZE(fsl_imx8mm_memmap); i++) { switch (i) { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_CCM: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: + case FSL_IMX8MM_PCIE1: + case FSL_IMX8MM_PCIE_PHY1: case FSL_IMX8MM_RAM: case FSL_IMX8MM_OCRAM: case FSL_IMX8MM_SNVS_HP: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/pci-host/designware.h" +#include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; + DesignwarePCIEHost pcie; + FslImx8mPciePhyState pcie_phy; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART2_IRQ = 27, FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + + FSL_IMX8MM_PCI_INTA_IRQ = 122, + FSL_IMX8MM_PCI_INTB_IRQ = 123, + FSL_IMX8MM_PCI_INTC_IRQ = 124, + FSL_IMX8MM_PCI_INTD_IRQ = 125, + FSL_IMX8MM_PCI_MSI_IRQ = 127, }; #endif /* FSL_IMX8MM_H */ -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Enabled GPIO controller emulation Also updated the GPIO IRQ lines of iMX8MM Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/fsl-imx8mm.c | 54 +++++++++++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 14 ++++++++++ 2 files changed, 68 insertions(+) diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { + g_autofree char *name = g_strdup_printf("gpio%d", i + 1); + object_initialize_child(obj, name, &s->gpio[i], TYPE_IMX_GPIO); + } + for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { g_autofree char *name = g_strdup_printf("usdhc%d", i + 1); object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* GPIOs */ + for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { + static const struct { + hwaddr addr; + unsigned int irq_low; + unsigned int irq_high; + } gpio_table[FSL_IMX8MM_NUM_GPIOS] = { + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO1].addr, + FSL_IMX8MM_GPIO1_LOW_IRQ, + FSL_IMX8MM_GPIO1_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO2].addr, + FSL_IMX8MM_GPIO2_LOW_IRQ, + FSL_IMX8MM_GPIO2_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO3].addr, + FSL_IMX8MM_GPIO3_LOW_IRQ, + FSL_IMX8MM_GPIO3_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO4].addr, + FSL_IMX8MM_GPIO4_LOW_IRQ, + FSL_IMX8MM_GPIO4_HIGH_IRQ + }, + { + fsl_imx8mm_memmap[FSL_IMX8MM_GPIO5].addr, + FSL_IMX8MM_GPIO5_LOW_IRQ, + FSL_IMX8MM_GPIO5_HIGH_IRQ + }, + }; + object_property_set_bool(OBJECT(&s->gpio[i]), "has-edge-sel", true, + &error_abort); + object_property_set_bool(OBJECT(&s->gpio[i]), "has-upper-pin-irq", + true, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0, + qdev_get_gpio_in(gicdev, gpio_table[i].irq_low)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 1, + qdev_get_gpio_in(gicdev, gpio_table[i].irq_high)); + } + /* USDHCs */ for (i = 0; i < FSL_IMX8MM_NUM_USDHCS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_CCM: case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: + case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" +#include "hw/gpio/imx_gpio.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, + FSL_IMX8MM_NUM_GPIOS = 5, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, FSL_IMX8MM_NUM_USDHCS = 3, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMXGPIOState gpio[FSL_IMX8MM_NUM_GPIOS]; IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + FSL_IMX8MM_GPIO1_LOW_IRQ = 64, + FSL_IMX8MM_GPIO1_HIGH_IRQ = 65, + FSL_IMX8MM_GPIO2_LOW_IRQ = 66, + FSL_IMX8MM_GPIO2_HIGH_IRQ = 67, + FSL_IMX8MM_GPIO3_LOW_IRQ = 68, + FSL_IMX8MM_GPIO3_HIGH_IRQ = 69, + FSL_IMX8MM_GPIO4_LOW_IRQ = 70, + FSL_IMX8MM_GPIO4_HIGH_IRQ = 71, + FSL_IMX8MM_GPIO5_LOW_IRQ = 72, + FSL_IMX8MM_GPIO5_HIGH_IRQ = 73, + FSL_IMX8MM_PCI_INTA_IRQ = 122, FSL_IMX8MM_PCI_INTB_IRQ = 123, FSL_IMX8MM_PCI_INTC_IRQ = 124, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> This can be used to test and debug I2C device drivers. Added I2C interrupts Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 2 ++ hw/arm/fsl-imx8mm.c | 27 +++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 8 ++++++++ 3 files changed, 37 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MP_EVK config FSL_IMX8MM bool imply PCI_DEVICES + imply I2C_DEVICES select ARM_GIC select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select IMX_I2C select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { + g_autofree char *name = g_strdup_printf("i2c%d", i + 1); + object_initialize_child(obj, name, &s->i2c[i], TYPE_IMX_I2C); + } + for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { g_autofree char *name = g_strdup_printf("gpio%d", i + 1); object_initialize_child(obj, name, &s->gpio[i], TYPE_IMX_GPIO); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* I2Cs */ + for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } i2c_table[FSL_IMX8MM_NUM_I2CS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C1].addr, FSL_IMX8MM_I2C1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C2].addr, FSL_IMX8MM_I2C2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C3].addr, FSL_IMX8MM_I2C3_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_I2C4].addr, FSL_IMX8MM_I2C4_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->i2c[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c[i]), 0, i2c_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0, + qdev_get_gpio_in(gicdev, i2c_table[i].irq)); + } + /* GPIOs */ for (i = 0; i < FSL_IMX8MM_NUM_GPIOS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: + case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: case FSL_IMX8MM_RAM: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "cpu.h" #include "hw/char/imx_serial.h" #include "hw/gpio/imx_gpio.h" +#include "hw/i2c/imx_i2c.h" #include "hw/intc/arm_gicv3_common.h" #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, FSL_IMX8MM_NUM_GPIOS = 5, + FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, FSL_IMX8MM_NUM_USDHCS = 3, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; + IMXI2CState i2c[FSL_IMX8MM_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + FSL_IMX8MM_I2C1_IRQ = 35, + FSL_IMX8MM_I2C2_IRQ = 36, + FSL_IMX8MM_I2C3_IRQ = 37, + FSL_IMX8MM_I2C4_IRQ = 38, + FSL_IMX8MM_GPIO1_LOW_IRQ = 64, FSL_IMX8MM_GPIO1_HIGH_IRQ = 65, FSL_IMX8MM_GPIO2_LOW_IRQ = 66, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of ECSPI in iMX8MM Added SPI IRQ lines Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/fsl-imx8mm.c | 26 ++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { + g_autofree char *name = g_strdup_printf("spi%d", i + 1); + object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); + } + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); } + /* ECSPIs */ + for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } spi_table[FSL_IMX8MM_NUM_ECSPIS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_ECSPI1].addr, FSL_IMX8MM_ECSPI1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_ECSPI2].addr, FSL_IMX8MM_ECSPI2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_ECSPI3].addr, FSL_IMX8MM_ECSPI3_IRQ }, + }; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi[i]), 0, spi_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi[i]), 0, + qdev_get_gpio_in(gicdev, spi_table[i].irq)); + } + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: + case FSL_IMX8MM_ECSPI1 ... FSL_IMX8MM_ECSPI3: case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" +#include "hw/ssi/imx_spi.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_SIMPLE_TYPE(FslImx8mmState, FSL_IMX8MM) enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, + FSL_IMX8MM_NUM_ECSPIS = 3, FSL_IMX8MM_NUM_GPIOS = 5, FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX8MPCCMState ccm; IMX8MPAnalogState analog; IMX7SNVSState snvs; + IMXSPIState spi[FSL_IMX8MM_NUM_ECSPIS]; IMXI2CState i2c[FSL_IMX8MM_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_UART3_IRQ = 28, FSL_IMX8MM_UART4_IRQ = 29, + FSL_IMX8MM_ECSPI1_IRQ = 31, + FSL_IMX8MM_ECSPI2_IRQ = 32, + FSL_IMX8MM_ECSPI3_IRQ = 33, + FSL_IMX8MM_I2C1_IRQ = 35, FSL_IMX8MM_I2C2_IRQ = 36, FSL_IMX8MM_I2C3_IRQ = 37, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of WDT in iMX8MM Added WDT IRQ lines Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 28 ++++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 7 +++++++ 3 files changed, 36 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY + select WDT_IMX2 config FSL_IMX8MM_EVK bool diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); } + for (i = 0; i < FSL_IMX8MM_NUM_WDTS; i++) { + g_autofree char *name = g_strdup_printf("wdt%d", i); + object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); + } + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) sysbus_mmio_map(SYS_BUS_DEVICE(&s->snvs), 0, fsl_imx8mm_memmap[FSL_IMX8MM_SNVS_HP].addr); + /* Watchdogs */ + for (i = 0; i < FSL_IMX8MM_NUM_WDTS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } wdog_table[FSL_IMX8MM_NUM_WDTS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_WDOG1].addr, FSL_IMX8MM_WDOG1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_WDOG2].addr, FSL_IMX8MM_WDOG2_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_WDOG3].addr, FSL_IMX8MM_WDOG3_IRQ }, + }; + + object_property_set_bool(OBJECT(&s->wdt[i]), "pretimeout-support", + true, &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt[i]), 0, wdog_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[i]), 0, + qdev_get_gpio_in(gicdev, wdog_table[i].irq)); + } + /* PCIe */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pcie), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: case FSL_IMX8MM_USDHC1 ... FSL_IMX8MM_USDHC3: + case FSL_IMX8MM_WDOG1 ... FSL_IMX8MM_WDOG3: /* device implemented and treated above */ break; diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" +#include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, FSL_IMX8MM_NUM_USDHCS = 3, + FSL_IMX8MM_NUM_WDTS = 3, }; struct FslImx8mmState { @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; + IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; }; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_GPIO5_LOW_IRQ = 72, FSL_IMX8MM_GPIO5_HIGH_IRQ = 73, + FSL_IMX8MM_WDOG1_IRQ = 78, + FSL_IMX8MM_WDOG2_IRQ = 79, + FSL_IMX8MM_WDOG3_IRQ = 10, + FSL_IMX8MM_PCI_INTA_IRQ = 122, FSL_IMX8MM_PCI_INTB_IRQ = 123, FSL_IMX8MM_PCI_INTC_IRQ = 124, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of GPT in iMX8MM Added GPT IRQ lines Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 54 +++++++++++++++++++++++++++++++++++++ hw/timer/imx_gpt.c | 26 ++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 11 ++++++++ include/hw/timer/imx_gpt.h | 2 ++ 5 files changed, 94 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select FSL_IMX8MP_CCM select IMX select IMX_I2C + select OR_IRQ select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->uart[i], TYPE_IMX_SERIAL); } + for (i = 0; i < FSL_IMX8MM_NUM_GPTS; i++) { + g_autofree char *name = g_strdup_printf("gpt%d", i + 1); + object_initialize_child(obj, name, &s->gpt[i], TYPE_IMX8MM_GPT); + } + object_initialize_child(obj, "gpt5-gpt6-irq", &s->gpt5_gpt6_irq, + TYPE_OR_IRQ); + for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { g_autofree char *name = g_strdup_printf("i2c%d", i + 1); object_initialize_child(obj, name, &s->i2c[i], TYPE_IMX_I2C); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) fsl_imx8mm_memmap[FSL_IMX8MM_OCRAM].addr, &s->ocram); + /* GPTs */ + object_property_set_int(OBJECT(&s->gpt5_gpt6_irq), "num-lines", 2, + &error_abort); + if (!qdev_realize(DEVICE(&s->gpt5_gpt6_irq), NULL, errp)) { + return; + } + + qdev_connect_gpio_out(DEVICE(&s->gpt5_gpt6_irq), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_GPT5_GPT6_IRQ)); + + for (i = 0; i < FSL_IMX8MM_NUM_GPTS; i++) { + hwaddr gpt_addrs[FSL_IMX8MM_NUM_GPTS] = { + fsl_imx8mm_memmap[FSL_IMX8MM_GPT1].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT2].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT3].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT4].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT5].addr, + fsl_imx8mm_memmap[FSL_IMX8MM_GPT6].addr, + }; + + s->gpt[i].ccm = IMX_CCM(&s->ccm); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpt[i]), errp)) { + return; + } + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpt[i]), 0, gpt_addrs[i]); + + if (i < FSL_IMX8MM_NUM_GPTS - 2) { + static const unsigned int gpt_irqs[FSL_IMX8MM_NUM_GPTS - 2] = { + FSL_IMX8MM_GPT1_IRQ, + FSL_IMX8MM_GPT2_IRQ, + FSL_IMX8MM_GPT3_IRQ, + FSL_IMX8MM_GPT4_IRQ, + }; + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, + qdev_get_gpio_in(gicdev, gpt_irqs[i])); + } else { + int irq = i - FSL_IMX8MM_NUM_GPTS + 2; + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpt[i]), 0, + qdev_get_gpio_in(DEVICE(&s->gpt5_gpt6_irq), irq)); + } + } + /* I2Cs */ for (i = 0; i < FSL_IMX8MM_NUM_I2CS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GIC_DIST: case FSL_IMX8MM_GIC_REDIST: case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: + case FSL_IMX8MM_GPT1 ... FSL_IMX8MM_GPT6: case FSL_IMX8MM_ECSPI1 ... FSL_IMX8MM_ECSPI3: case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: diff --git a/hw/timer/imx_gpt.c b/hw/timer/imx_gpt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/timer/imx_gpt.c +++ b/hw/timer/imx_gpt.c @@ -XXX,XX +XXX,XX @@ * Originally written by Hans Jiang * Updated by Peter Chubb * Updated by Jean-Christophe Dubois <jcd@tribudubois.net> + * Updated by Gaurav Sharma <gaurav.sharma_7@nxp.com> * * This code is licensed under GPL version 2 or later. See * the COPYING file in the top-level directory. @@ -XXX,XX +XXX,XX @@ static const IMXClk imx8mp_gpt_clocks[] = { CLK_NONE, /* 111 not defined */ }; +static const IMXClk imx8mm_gpt_clocks[] = { + CLK_NONE, /* 000 No clock source */ + CLK_IPG, /* 001 ipg_clk, 532MHz */ + CLK_IPG_HIGH, /* 010 ipg_clk_highfreq */ + CLK_EXT, /* 011 External clock */ + CLK_32k, /* 100 ipg_clk_32k */ + CLK_HIGH, /* 101 ipg_clk_16M */ + CLK_NONE, /* 110 not defined */ + CLK_NONE, /* 111 not defined */ +}; + /* Must be called from within ptimer_transaction_begin/commit block */ static void imx_gpt_set_freq(IMXGPTState *s) { @@ -XXX,XX +XXX,XX @@ static void imx8mp_gpt_init(Object *obj) s->clocks = imx8mp_gpt_clocks; } +static void imx8mm_gpt_init(Object *obj) +{ + IMXGPTState *s = IMX_GPT(obj); + + s->clocks = imx8mm_gpt_clocks; +} + static const TypeInfo imx25_gpt_info = { .name = TYPE_IMX25_GPT, .parent = TYPE_SYS_BUS_DEVICE, @@ -XXX,XX +XXX,XX @@ static const TypeInfo imx8mp_gpt_info = { .instance_init = imx8mp_gpt_init, }; +static const TypeInfo imx8mm_gpt_info = { + .name = TYPE_IMX8MM_GPT, + .parent = TYPE_IMX25_GPT, + .instance_init = imx8mm_gpt_init, +}; + static void imx_gpt_register_types(void) { type_register_static(&imx25_gpt_info); @@ -XXX,XX +XXX,XX @@ static void imx_gpt_register_types(void) type_register_static(&imx6ul_gpt_info); type_register_static(&imx7_gpt_info); type_register_static(&imx8mp_gpt_info); + type_register_static(&imx8mm_gpt_info); } type_init(imx_gpt_register_types) diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" +#include "hw/or-irq.h" #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" +#include "hw/timer/imx_gpt.h" #include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_CPUS = 4, FSL_IMX8MM_NUM_ECSPIS = 3, FSL_IMX8MM_NUM_GPIOS = 5, + FSL_IMX8MM_NUM_GPTS = 6, FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { ARMCPU cpu[FSL_IMX8MM_NUM_CPUS]; GICv3State gic; + IMXGPTState gpt[FSL_IMX8MM_NUM_GPTS]; IMXGPIOState gpio[FSL_IMX8MM_NUM_GPIOS]; IMX8MPCCMState ccm; IMX8MPAnalogState analog; @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; + OrIRQState gpt5_gpt6_irq; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_I2C3_IRQ = 37, FSL_IMX8MM_I2C4_IRQ = 38, + FSL_IMX8MM_GPT1_IRQ = 55, + FSL_IMX8MM_GPT2_IRQ = 54, + FSL_IMX8MM_GPT3_IRQ = 53, + FSL_IMX8MM_GPT4_IRQ = 52, + FSL_IMX8MM_GPT5_GPT6_IRQ = 51, + FSL_IMX8MM_GPIO1_LOW_IRQ = 64, FSL_IMX8MM_GPIO1_HIGH_IRQ = 65, FSL_IMX8MM_GPIO2_LOW_IRQ = 66, diff --git a/include/hw/timer/imx_gpt.h b/include/hw/timer/imx_gpt.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/timer/imx_gpt.h +++ b/include/hw/timer/imx_gpt.h @@ -XXX,XX +XXX,XX @@ * Originally written by Hans Jiang * Updated by Peter Chubb * Updated by Jean-Christophe Dubois <jcd@tribudubois.net> + * Updated by Gaurav Sharma <gaurav.sharma_7@nxp.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -XXX,XX +XXX,XX @@ #define TYPE_IMX6UL_GPT "imx6ul.gpt" #define TYPE_IMX7_GPT "imx7.gpt" #define TYPE_IMX8MP_GPT "imx8mp.gpt" +#define TYPE_IMX8MM_GPT "imx8mm.gpt" #define TYPE_IMX_GPT TYPE_IMX25_GPT -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of ENET ethernet controller in iMX8MM Enables testing and debugging of network dependent drivers Added ENET MAC IRQ lines Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 24 ++++++++++++++++++++++++ hw/arm/imx8mm-evk.c | 1 + include/hw/arm/fsl-imx8mm.h | 10 +++++++++- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select FSL_IMX8MP_ANALOG select FSL_IMX8MP_CCM select IMX + select IMX_FEC select IMX_I2C select OR_IRQ select SDHCI diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->wdt[i], TYPE_IMX2_WDT); } + object_initialize_child(obj, "eth0", &s->enet, TYPE_IMX_ENET); + object_initialize_child(obj, "pcie", &s->pcie, TYPE_DESIGNWARE_PCIE_HOST); object_initialize_child(obj, "pcie_phy", &s->pcie_phy, TYPE_FSL_IMX8M_PCIE_PHY); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, spi_table[i].irq)); } + /* ENET1 */ + object_property_set_uint(OBJECT(&s->enet), "phy-num", s->phy_num, + &error_abort); + object_property_set_uint(OBJECT(&s->enet), "tx-ring-num", 3, &error_abort); + qemu_configure_nic_device(DEVICE(&s->enet), true, NULL); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->enet), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->enet), 0, + fsl_imx8mm_memmap[FSL_IMX8MM_ENET1].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->enet), 0, + qdev_get_gpio_in(gicdev, FSL_IMX8MM_ENET1_MAC_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->enet), 1, + qdev_get_gpio_in(gicdev, FSL_IMX6_ENET1_MAC_1588_IRQ)); + /* SNVS */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->snvs), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_GPIO1 ... FSL_IMX8MM_GPIO5: case FSL_IMX8MM_GPT1 ... FSL_IMX8MM_GPT6: case FSL_IMX8MM_ECSPI1 ... FSL_IMX8MM_ECSPI3: + case FSL_IMX8MM_ENET1: case FSL_IMX8MM_I2C1 ... FSL_IMX8MM_I2C4: case FSL_IMX8MM_PCIE1: case FSL_IMX8MM_PCIE_PHY1: @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) } } +static const Property fsl_imx8mm_properties[] = { + DEFINE_PROP_UINT32("fec1-phy-num", FslImx8mmState, phy_num, 0), + DEFINE_PROP_BOOL("fec1-phy-connected", FslImx8mmState, phy_connected, true), +}; + static void fsl_imx8mm_class_init(ObjectClass *oc, const void *data) { DeviceClass *dc = DEVICE_CLASS(oc); + device_class_set_props(dc, fsl_imx8mm_properties); dc->realize = fsl_imx8mm_realize; dc->desc = "i.MX 8MM SoC"; diff --git a/hw/arm/imx8mm-evk.c b/hw/arm/imx8mm-evk.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/imx8mm-evk.c +++ b/hw/arm/imx8mm-evk.c @@ -XXX,XX +XXX,XX @@ static void imx8mm_evk_init(MachineState *machine) s = FSL_IMX8MM(object_new_with_props(TYPE_FSL_IMX8MM, OBJECT(machine), "soc", &error_fatal, NULL)); + object_property_set_uint(OBJECT(s), "fec1-phy-num", 1, &error_fatal); sysbus_realize_and_unref(SYS_BUS_DEVICE(s), &error_fatal); memory_region_add_subregion(get_system_memory(), FSL_IMX8MM_RAM_START, diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/misc/imx7_snvs.h" #include "hw/misc/imx8mp_analog.h" #include "hw/misc/imx8mp_ccm.h" -#include "hw/or-irq.h" +#include "hw/net/imx_fec.h" +#include "hw/core/or-irq.h" #include "hw/pci-host/designware.h" #include "hw/pci-host/fsl_imx8m_phy.h" #include "hw/sd/sdhci.h" @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXI2CState i2c[FSL_IMX8MM_NUM_I2CS]; IMXSerialState uart[FSL_IMX8MM_NUM_UARTS]; MemoryRegion ocram; + IMXFECState enet; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; + + uint32_t phy_num; + bool phy_connected; }; enum FslImx8mmMemoryRegions { @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_WDOG2_IRQ = 79, FSL_IMX8MM_WDOG3_IRQ = 10, + FSL_IMX8MM_ENET1_MAC_IRQ = 118, + FSL_IMX6_ENET1_MAC_1588_IRQ = 121, + FSL_IMX8MM_PCI_INTA_IRQ = 122, FSL_IMX8MM_PCI_INTB_IRQ = 123, FSL_IMX8MM_PCI_INTC_IRQ = 124, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> It enables emulation of USB on iMX8MM Enables testing and debugging of USB drivers Reviewed-by: Philippe Mathieu-Daude <philmd@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Bernhard Beschow <shentey@gmail.com> Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx8mm.c | 27 +++++++++++++++++++++++++++ include/hw/arm/fsl-imx8mm.h | 6 ++++++ 3 files changed, 34 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX8MM select SDHCI select PCI_EXPRESS_DESIGNWARE select PCI_EXPRESS_FSL_IMX8M_PHY + select USB_DWC3 select WDT_IMX2 config FSL_IMX8MM_EVK diff --git a/hw/arm/fsl-imx8mm.c b/hw/arm/fsl-imx8mm.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx8mm.c +++ b/hw/arm/fsl-imx8mm.c @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_init(Object *obj) object_initialize_child(obj, name, &s->usdhc[i], TYPE_IMX_USDHC); } + for (i = 0; i < FSL_IMX8MM_NUM_USBS; i++) { + g_autofree char *name = g_strdup_printf("usb%d", i); + object_initialize_child(obj, name, &s->usb[i], TYPE_USB_DWC3); + } + for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { g_autofree char *name = g_strdup_printf("spi%d", i + 1); object_initialize_child(obj, name, &s->spi[i], TYPE_IMX_SPI); @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(gicdev, usdhc_table[i].irq)); } + /* USBs */ + for (i = 0; i < FSL_IMX8MM_NUM_USBS; i++) { + static const struct { + hwaddr addr; + unsigned int irq; + } usb_table[FSL_IMX8MM_NUM_USBS] = { + { fsl_imx8mm_memmap[FSL_IMX8MM_USB1].addr, FSL_IMX8MM_USB1_IRQ }, + { fsl_imx8mm_memmap[FSL_IMX8MM_USB2].addr, FSL_IMX8MM_USB2_IRQ }, + }; + + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "p2", 1); + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "p3", 1); + qdev_prop_set_uint32(DEVICE(&s->usb[i].sysbus_xhci), "slots", 2); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->usb[i]), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, usb_table[i].addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i].sysbus_xhci), 0, + qdev_get_gpio_in(gicdev, usb_table[i].irq)); + } + /* ECSPIs */ for (i = 0; i < FSL_IMX8MM_NUM_ECSPIS; i++) { static const struct { @@ -XXX,XX +XXX,XX @@ static void fsl_imx8mm_realize(DeviceState *dev, Error **errp) case FSL_IMX8MM_OCRAM: case FSL_IMX8MM_SNVS_HP: case FSL_IMX8MM_UART1 ... FSL_IMX8MM_UART4: + case FSL_IMX8MM_USB1 ... FSL_IMX8MM_USB2: case FSL_IMX8MM_USDHC1 ... FSL_IMX8MM_USDHC3: case FSL_IMX8MM_WDOG1 ... FSL_IMX8MM_WDOG3: /* device implemented and treated above */ diff --git a/include/hw/arm/fsl-imx8mm.h b/include/hw/arm/fsl-imx8mm.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx8mm.h +++ b/include/hw/arm/fsl-imx8mm.h @@ -XXX,XX +XXX,XX @@ #include "hw/sd/sdhci.h" #include "hw/ssi/imx_spi.h" #include "hw/timer/imx_gpt.h" +#include "hw/usb/hcd-dwc3.h" #include "hw/watchdog/wdt_imx2.h" #include "qom/object.h" #include "qemu/units.h" @@ -XXX,XX +XXX,XX @@ enum FslImx8mmConfiguration { FSL_IMX8MM_NUM_I2CS = 4, FSL_IMX8MM_NUM_IRQS = 128, FSL_IMX8MM_NUM_UARTS = 4, + FSL_IMX8MM_NUM_USBS = 2, FSL_IMX8MM_NUM_USDHCS = 3, FSL_IMX8MM_NUM_WDTS = 3, }; @@ -XXX,XX +XXX,XX @@ struct FslImx8mmState { IMXFECState enet; SDHCIState usdhc[FSL_IMX8MM_NUM_USDHCS]; IMX2WdtState wdt[FSL_IMX8MM_NUM_WDTS]; + USBDWC3 usb[FSL_IMX8MM_NUM_USBS]; DesignwarePCIEHost pcie; FslImx8mPciePhyState pcie_phy; OrIRQState gpt5_gpt6_irq; @@ -XXX,XX +XXX,XX @@ enum FslImx8mmIrqs { FSL_IMX8MM_I2C3_IRQ = 37, FSL_IMX8MM_I2C4_IRQ = 38, + FSL_IMX8MM_USB1_IRQ = 40, + FSL_IMX8MM_USB2_IRQ = 41, + FSL_IMX8MM_GPT1_IRQ = 55, FSL_IMX8MM_GPT2_IRQ = 54, FSL_IMX8MM_GPT3_IRQ = 53, -- 2.43.0
From: Gaurav Sharma <gaurav.sharma_7@nxp.com> Added script that would validate the iMX8MM emulation by checking the linux console log. If it succeeds, it will return:- ok 1 test_imx8mm_evk.Imx8mmEvkMachine.test_aarch64_imx8mm_evk_usdhc Signed-off-by: Gaurav Sharma <gaurav.sharma_7@nxp.com> Reviewed-by: Thomas Huth <thuth@redhat.com> Reviewed-by: Bernhard Beschow <shentey@gmail.com> [PMM: Add missing blank lines that flake8 wants] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 1 + tests/functional/aarch64/meson.build | 2 + tests/functional/aarch64/test_imx8mm_evk.py | 69 +++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100755 tests/functional/aarch64/test_imx8mm_evk.py diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/arm/fsl-imx8mm.c F: hw/arm/imx8mm-evk.c F: include/hw/arm/fsl-imx8mm.h F: docs/system/arm/imx8m.rst +F: tests/functional/aarch64/test_imx8mm_evk.py MCIMX8MP-EVK / i.MX8MP M: Bernhard Beschow <shentey@gmail.com> diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -XXX,XX +XXX,XX @@ test_aarch64_timeouts = { 'aspeed_ast2700a2' : 600, 'aspeed_ast2700fc' : 600, 'device_passthrough' : 720, + 'imx8mm_evk' : 240, 'imx8mp_evk' : 240, 'raspi4' : 480, 'reverse_debug' : 180, @@ -XXX,XX +XXX,XX @@ tests_aarch64_system_thorough = [ 'aspeed_ast2700fc', 'device_passthrough', 'hotplug_pci', + 'imx8mm_evk', 'imx8mp_evk', 'kvm', 'multiprocess', diff --git a/tests/functional/aarch64/test_imx8mm_evk.py b/tests/functional/aarch64/test_imx8mm_evk.py new file mode 100755 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/functional/aarch64/test_imx8mm_evk.py @@ -XXX,XX +XXX,XX @@ +#!/usr/bin/env python3 +# +# Functional test that boots a Linux kernel and checks the console +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset + + +class Imx8mmEvkMachine(LinuxKernelTest): + + ASSET_IMAGE = Asset( + ('https://cloud.debian.org/images/cloud/bookworm/20231210-1590/' + 'debian-12-generic-arm64-20231210-1590.tar.xz'), + '7ebf1577b32d5af6204df74b54ca2e4675de9b5a9fa14f3ff70b88eeb7b3b359') + + KERNEL_OFFSET = 0x51000000 + KERNEL_SIZE = 32622528 + INITRD_OFFSET = 0x76000000 + INITRD_SIZE = 30987766 + DTB_OFFSET = 0x64DB5000 + DTB_SIZE = 36812 + + def extract(self, in_path, out_path, offset, size): + try: + with open(in_path, "rb") as source: + source.seek(offset) + data = source.read(size) + with open(out_path, "wb") as target: + target.write(data) + except (IOError, ValueError) as e: + self.log.error(f"Failed to extract {out_path}: {e}") + raise + + def setUp(self): + super().setUp() + + self.image_path = self.scratch_file("disk.raw") + self.kernel_path = self.scratch_file("linux") + self.initrd_path = self.scratch_file("initrd.zstd") + self.dtb_path = self.scratch_file("imx8mm-evk.dtb") + + self.archive_extract(self.ASSET_IMAGE) + self.extract(self.image_path, self.kernel_path, + self.KERNEL_OFFSET, self.KERNEL_SIZE) + self.extract(self.image_path, self.initrd_path, + self.INITRD_OFFSET, self.INITRD_SIZE) + self.extract(self.image_path, self.dtb_path, + self.DTB_OFFSET, self.DTB_SIZE) + + def test_aarch64_imx8mm_evk_usdhc(self): + self.require_accelerator("tcg") + self.set_machine('imx8mm-evk') + self.vm.set_console(console_index=1) + self.vm.add_args('-m', '2G', + '-smp', '4', + '-kernel', self.kernel_path, + '-initrd', self.initrd_path, + '-dtb', self.dtb_path, + '-append', 'root=/dev/mmcblk2p1', + '-drive', f'file={self.image_path},if=sd,bus=2,' + 'format=raw,id=mmcblk2,snapshot=on') + + self.vm.launch() + self.wait_for_console_pattern('Welcome to ') + + +if __name__ == '__main__': + LinuxKernelTest.main() -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> As mentioned by Richard in [*]: We don't actually need any specific endianness here, because every byte has the same value. So we could simply drop MO_TE. That would produce a store in host-endianness, which will be fractionally more efficient on some hosts. [*] https://lore.kernel.org/qemu-devel/d64d3576-9188-4e74-afdc-3492c9feb8e0@linaro.org/ Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260414005348.4767-2-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/helper-a64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/helper-a64.c b/target/arm/tcg/helper-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/helper-a64.c +++ b/target/arm/tcg/helper-a64.c @@ -XXX,XX +XXX,XX @@ static uint64_t set_step_tags(CPUARMState *env, uint64_t toaddr, * the page dirty and will use the fast path. */ uint64_t repldata = data * 0x0101010101010101ULL; - MemOpIdx oi16 = make_memop_idx(MO_TE | MO_128, memidx); + MemOpIdx oi16 = make_memop_idx(MO_128, memidx); cpu_st16_mmu(env, toaddr, int128_make128(repldata, repldata), oi16, ra); mte_mops_set_tags(env, toaddr, 16, *mtedesc); return 16; -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Extract the implicit MO_TE definition in order to replace it in the next commit. Mechanical change using: $ for n in UW UL UQ UO SW SL SQ; do \ sed -i -e "s/MO_TE$n/MO_TE | MO_$n/" \ $(git grep -l MO_TE$n target/arm); \ done Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-3-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/m_helper.c | 6 +-- target/arm/tcg/mve_helper.c | 79 ++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest) /* Note that these stores can throw exceptions on MPU faults */ ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); cpu_stl_mmu(env, sp, nextinst, oi, GETPC()); cpu_stl_mmu(env, sp + 4, saved_psr, oi, GETPC()); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr) bool lspact = env->v7m.fpccr[s] & R_V7M_FPCCR_LSPACT_MASK; uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); assert(env->v7m.secure); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr) ARMCPU *cpu = env_archcpu(env); uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); /* fptr is the value of Rn, the frame pointer we load the FP regs from */ diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) } DO_VLDR(vldrb, MO_UB, 1, uint8_t, ldb, 1, uint8_t) -DO_VLDR(vldrh, MO_TEUW, 2, uint16_t, ldw, 2, uint16_t) -DO_VLDR(vldrw, MO_TEUL, 4, uint32_t, ldl, 4, uint32_t) +DO_VLDR(vldrh, MO_TE | MO_UW, 2, uint16_t, ldw, 2, uint16_t) +DO_VLDR(vldrw, MO_TE | MO_UL, 4, uint32_t, ldl, 4, uint32_t) DO_VSTR(vstrb, MO_UB, 1, stb, 1, uint8_t) -DO_VSTR(vstrh, MO_TEUW, 2, stw, 2, uint16_t) -DO_VSTR(vstrw, MO_TEUL, 4, stl, 4, uint32_t) +DO_VSTR(vstrh, MO_TE | MO_UW, 2, stw, 2, uint16_t) +DO_VSTR(vstrw, MO_TE | MO_UL, 4, stl, 4, uint32_t) DO_VLDR(vldrb_sh, MO_SB, 1, int8_t, ldb, 2, int16_t) DO_VLDR(vldrb_sw, MO_SB, 1, int8_t, ldb, 4, int32_t) DO_VLDR(vldrb_uh, MO_UB, 1, uint8_t, ldb, 2, uint16_t) DO_VLDR(vldrb_uw, MO_UB, 1, uint8_t, ldb, 4, uint32_t) -DO_VLDR(vldrh_sw, MO_TESW, 2, int16_t, ldw, 4, int32_t) -DO_VLDR(vldrh_uw, MO_TEUW, 2, uint16_t, ldw, 4, uint32_t) +DO_VLDR(vldrh_sw, MO_TE | MO_SW, 2, int16_t, ldw, 4, int32_t) +DO_VLDR(vldrh_uw, MO_TE | MO_UW, 2, uint16_t, ldw, 4, uint32_t) DO_VSTR(vstrb_h, MO_UB, 1, stb, 2, int16_t) DO_VSTR(vstrb_w, MO_UB, 1, stb, 4, int32_t) -DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) +DO_VSTR(vstrh_w, MO_TE | MO_UW, 2, stw, 4, int32_t) #undef DO_VLDR #undef DO_VSTR @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_TEUW, 2, stw, 4, int32_t) DO_VLDR_SG(vldrb_sg_sh, MO_SB, int8_t, ldb, 2, int16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_sw, MO_SB, int8_t, ldb, 4, int32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_sw, MO_TESW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_sw, MO_TE | MO_SW, int16_t, ldw, 4, + int32_t, uint32_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_ub, MO_UB, uint8_t, ldb, 1, uint8_t, uint8_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uh, MO_UB, uint8_t, ldb, 2, uint16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uw, MO_UB, uint8_t, ldb, 4, uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uh, MO_TEUW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uw, MO_TEUW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrw_sg_uw, MO_TEUL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uh, MO_TE | MO_UW, uint16_t, ldw, 2, + uint16_t, uint16_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uw, MO_TE | MO_UW, uint16_t, ldw, 4, + uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrw_sg_uw, MO_TE | MO_UL, uint32_t, ldl, 4, + uint32_t, uint32_t, ADDR_ADD, false) DO_VLDR64_SG(vldrd_sg_ud, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_os_sw, MO_TESW, int16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_sw, MO_TE | MO_SW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uh, MO_TEUW, uint16_t, ldw, 2, +DO_VLDR_SG(vldrh_sg_os_uh, MO_TE | MO_UW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uw, MO_TEUW, uint16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_uw, MO_TE | MO_UW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrw_sg_os_uw, MO_TEUL, uint32_t, ldl, 4, +DO_VLDR_SG(vldrw_sg_os_uw, MO_TE | MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD_OSW, false) DO_VLDR64_SG(vldrd_sg_os_ud, ADDR_ADD_OSD, false) DO_VSTR_SG(vstrb_sg_ub, MO_UB, stb, 1, uint8_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uh, MO_UB, stb, 2, uint16_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uw, MO_UB, stb, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uh, MO_TEUW, stw, 2, uint16_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uw, MO_TEUW, stw, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrw_sg_uw, MO_TEUL, stl, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrw_sg_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, false) DO_VSTR64_SG(vstrd_sg_ud, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_os_uh, MO_TEUW, stw, 2, uint16_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrh_sg_os_uw, MO_TEUW, stw, 4, uint32_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrw_sg_os_uw, MO_TEUL, stl, 4, uint32_t, ADDR_ADD_OSW, false) +DO_VSTR_SG(vstrh_sg_os_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrh_sg_os_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrw_sg_os_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD_OSW, false) DO_VSTR64_SG(vstrd_sg_os_ud, ADDR_ADD_OSD, false) -DO_VLDR_SG(vldrw_sg_wb_uw, MO_TEUL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, true) +DO_VLDR_SG(vldrw_sg_wb_uw, MO_TE | MO_UL, uint32_t, ldl, 4, + uint32_t, uint32_t, ADDR_ADD, true) DO_VLDR64_SG(vldrd_sg_wb_ud, ADDR_ADD, true) -DO_VSTR_SG(vstrw_sg_wb_uw, MO_TEUL, stl, 4, uint32_t, ADDR_ADD, true) +DO_VSTR_SG(vstrw_sg_wb_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, true) DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) /* @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-4-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/mve_helper.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t eci_mask = mve_eci_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ /* \ * R_SXTM allows the dest reg to become UNKNOWN for abandoned \ * beats so we don't care if we update part of the dest and \ @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t mask = mve_element_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ for (b = 0, e = 0; b < 16; b += ESIZE, e++) { \ if (mask & (1 << b)) { \ cpu_##STTYPE##_mmu(env, addr, d[H##ESIZE(e)], oi, GETPC()); \ @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) } DO_VLDR(vldrb, MO_UB, 1, uint8_t, ldb, 1, uint8_t) -DO_VLDR(vldrh, MO_TE | MO_UW, 2, uint16_t, ldw, 2, uint16_t) -DO_VLDR(vldrw, MO_TE | MO_UL, 4, uint32_t, ldl, 4, uint32_t) +DO_VLDR(vldrh, MO_UW, 2, uint16_t, ldw, 2, uint16_t) +DO_VLDR(vldrw, MO_UL, 4, uint32_t, ldl, 4, uint32_t) DO_VSTR(vstrb, MO_UB, 1, stb, 1, uint8_t) -DO_VSTR(vstrh, MO_TE | MO_UW, 2, stw, 2, uint16_t) -DO_VSTR(vstrw, MO_TE | MO_UL, 4, stl, 4, uint32_t) +DO_VSTR(vstrh, MO_UW, 2, stw, 2, uint16_t) +DO_VSTR(vstrw, MO_UL, 4, stl, 4, uint32_t) DO_VLDR(vldrb_sh, MO_SB, 1, int8_t, ldb, 2, int16_t) DO_VLDR(vldrb_sw, MO_SB, 1, int8_t, ldb, 4, int32_t) DO_VLDR(vldrb_uh, MO_UB, 1, uint8_t, ldb, 2, uint16_t) DO_VLDR(vldrb_uw, MO_UB, 1, uint8_t, ldb, 4, uint32_t) -DO_VLDR(vldrh_sw, MO_TE | MO_SW, 2, int16_t, ldw, 4, int32_t) -DO_VLDR(vldrh_uw, MO_TE | MO_UW, 2, uint16_t, ldw, 4, uint32_t) +DO_VLDR(vldrh_sw, MO_SW, 2, int16_t, ldw, 4, int32_t) +DO_VLDR(vldrh_uw, MO_UW, 2, uint16_t, ldw, 4, uint32_t) DO_VSTR(vstrb_h, MO_UB, 1, stb, 2, int16_t) DO_VSTR(vstrb_w, MO_UB, 1, stb, 4, int32_t) -DO_VSTR(vstrh_w, MO_TE | MO_UW, 2, stw, 4, int32_t) +DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) #undef DO_VLDR #undef DO_VSTR -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-5-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/mve_helper.c | 43 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MFLAG | MO_ALIGN, mmu_idx); \ + MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) DO_VLDR_SG(vldrb_sg_sh, MO_SB, int8_t, ldb, 2, int16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_sw, MO_SB, int8_t, ldb, 4, int32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_sw, MO_TE | MO_SW, int16_t, ldw, 4, - int32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_sw, MO_SW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_ub, MO_UB, uint8_t, ldb, 1, uint8_t, uint8_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uh, MO_UB, uint8_t, ldb, 2, uint16_t, uint16_t, ADDR_ADD, false) DO_VLDR_SG(vldrb_sg_uw, MO_UB, uint8_t, ldb, 4, uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uh, MO_TE | MO_UW, uint16_t, ldw, 2, - uint16_t, uint16_t, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_uw, MO_TE | MO_UW, uint16_t, ldw, 4, - uint32_t, uint32_t, ADDR_ADD, false) -DO_VLDR_SG(vldrw_sg_uw, MO_TE | MO_UL, uint32_t, ldl, 4, - uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uh, MO_UW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD, false) +DO_VLDR_SG(vldrh_sg_uw, MO_UW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD, false) +DO_VLDR_SG(vldrw_sg_uw, MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, false) DO_VLDR64_SG(vldrd_sg_ud, ADDR_ADD, false) -DO_VLDR_SG(vldrh_sg_os_sw, MO_TE | MO_SW, int16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_sw, MO_SW, int16_t, ldw, 4, int32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uh, MO_TE | MO_UW, uint16_t, ldw, 2, +DO_VLDR_SG(vldrh_sg_os_uh, MO_UW, uint16_t, ldw, 2, uint16_t, uint16_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrh_sg_os_uw, MO_TE | MO_UW, uint16_t, ldw, 4, +DO_VLDR_SG(vldrh_sg_os_uw, MO_UW, uint16_t, ldw, 4, uint32_t, uint32_t, ADDR_ADD_OSH, false) -DO_VLDR_SG(vldrw_sg_os_uw, MO_TE | MO_UL, uint32_t, ldl, 4, +DO_VLDR_SG(vldrw_sg_os_uw, MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD_OSW, false) DO_VLDR64_SG(vldrd_sg_os_ud, ADDR_ADD_OSD, false) DO_VSTR_SG(vstrb_sg_ub, MO_UB, stb, 1, uint8_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uh, MO_UB, stb, 2, uint16_t, ADDR_ADD, false) DO_VSTR_SG(vstrb_sg_uw, MO_UB, stb, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD, false) -DO_VSTR_SG(vstrw_sg_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uh, MO_UW, stw, 2, uint16_t, ADDR_ADD, false) +DO_VSTR_SG(vstrh_sg_uw, MO_UW, stw, 4, uint32_t, ADDR_ADD, false) +DO_VSTR_SG(vstrw_sg_uw, MO_UL, stl, 4, uint32_t, ADDR_ADD, false) DO_VSTR64_SG(vstrd_sg_ud, ADDR_ADD, false) -DO_VSTR_SG(vstrh_sg_os_uh, MO_TE | MO_UW, stw, 2, uint16_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrh_sg_os_uw, MO_TE | MO_UW, stw, 4, uint32_t, ADDR_ADD_OSH, false) -DO_VSTR_SG(vstrw_sg_os_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD_OSW, false) +DO_VSTR_SG(vstrh_sg_os_uh, MO_UW, stw, 2, uint16_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrh_sg_os_uw, MO_UW, stw, 4, uint32_t, ADDR_ADD_OSH, false) +DO_VSTR_SG(vstrw_sg_os_uw, MO_UL, stl, 4, uint32_t, ADDR_ADD_OSW, false) DO_VSTR64_SG(vstrd_sg_os_ud, ADDR_ADD_OSD, false) -DO_VLDR_SG(vldrw_sg_wb_uw, MO_TE | MO_UL, uint32_t, ldl, 4, - uint32_t, uint32_t, ADDR_ADD, true) +DO_VLDR_SG(vldrw_sg_wb_uw, MO_UL, uint32_t, ldl, 4, uint32_t, uint32_t, ADDR_ADD, true) DO_VLDR64_SG(vldrd_sg_wb_ud, ADDR_ADD, true) -DO_VSTR_SG(vstrw_sg_wb_uw, MO_TE | MO_UL, stl, 4, uint32_t, ADDR_ADD, true) +DO_VSTR_SG(vstrw_sg_wb_uw, MO_UL, stl, 4, uint32_t, ADDR_ADD, true) DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) /* -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Suggested-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-6-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/internals.h | 6 ++++++ target/arm/tcg/mve_helper.c | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ #include "exec/hwaddr.h" #include "exec/vaddr.h" #include "exec/breakpoint.h" +#include "exec/memop.h" #include "accel/tcg/tb-cpu-state.h" #include "hw/core/registerfields.h" #include "tcg/tcg-gvec-desc.h" @@ -XXX,XX +XXX,XX @@ #define BANK_HYP 6 #define BANK_MON 7 +static inline MemOp mo_endian(CPUARMState *env) +{ + return EX_TBFLAG_ANY(env->hflags, BE_DATA) ? MO_BE : MO_LE; +} + static inline int arm_env_mmu_index(CPUARMState *env) { return EX_TBFLAG_ANY(env->hflags, MMUIDX); diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-7-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/mve_helper.c | 47 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t eci_mask = mve_eci_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ /* \ * R_SXTM allows the dest reg to become UNKNOWN for abandoned \ @@ -XXX,XX +XXX,XX @@ static void mve_advance_vpt(CPUARMState *env) uint16_t mask = mve_element_mask(env); \ unsigned b, e; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ for (b = 0, e = 0; b < 16; b += ESIZE, e++) { \ if (mask & (1 << b)) { \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MFLAG | MO_ALIGN, \ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MFLAG | MO_ALIGN, \ mmu_idx); \ for (e = 0; e < 16 / ESIZE; e++, mask >>= ESIZE, eci_mask >>= ESIZE) { \ if (!(eci_mask & 1)) { \ @@ -XXX,XX +XXX,XX @@ DO_VSTR(vstrh_w, MO_UW, 2, stw, 4, int32_t) unsigned e; \ uint32_t addr; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (e = 0; e < 16 / 4; e++, mask >>= 4, eci_mask >>= 4) { \ if (!(eci_mask & 1)) { \ continue; \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VSTR64_SG(vstrd_sg_wb_ud, ADDR_ADD, true) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD4W(vld43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) static const uint8_t off[4] = { O1, O2, O3, O4 }; \ uint32_t addr, data; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) int y; /* y counts 0 2 0 2 */ \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0, y = 0; beat < 4; beat++, mask >>= 4, y ^= 2) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VLD2W(vld21w, 8, 12, 16, 20) uint32_t *qd; \ int y; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint8_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) int e; \ uint16_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ @@ -XXX,XX +XXX,XX @@ DO_VST4W(vst43w, 6, 7, 8, 9) uint32_t addr, data; \ uint32_t *qd; \ int mmu_idx = arm_to_core_mmu_idx(arm_mmu_idx(env)); \ - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, mmu_idx);\ + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, \ + mmu_idx); \ for (beat = 0; beat < 4; beat++, mask >>= 4) { \ if ((mask & 1) == 0) { \ /* ECI says skip this beat */ \ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-8-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/meson.build | 3 ++- target/arm/tcg/mve_helper.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -XXX,XX +XXX,XX @@ arm_ss.add(when: 'TARGET_AARCH64', if_false: files('stubs32.c')) arm_ss.add(files( 'cpu32.c', 'm_helper.c', - 'mve_helper.c', )) arm_ss.add(when: 'TARGET_AARCH64', if_true: files( @@ -XXX,XX +XXX,XX @@ arm_common_system_ss.add( 'debug.c', 'hflags.c', 'gengvec.c', + 'mve_helper.c', 'neon_helper.c', 'op_helper.c', 'psci.c', @@ -XXX,XX +XXX,XX @@ arm_user_ss.add( 'debug.c', 'gengvec.c', 'hflags.c', + 'mve_helper.c', 'neon_helper.c', 'op_helper.c', 'tlb_helper.c', diff --git a/target/arm/tcg/mve_helper.c b/target/arm/tcg/mve_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/mve_helper.c +++ b/target/arm/tcg/mve_helper.c @@ -XXX,XX +XXX,XX @@ #include "helper-mve.h" #include "internals.h" #include "vec_internal.h" -#include "accel/tcg/cpu-ldst.h" +#include "accel/tcg/cpu-ldst-common.h" #include "tcg/tcg.h" #include "fpu/softfloat.h" #include "crypto/clmul.h" -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-9-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/m_helper.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest) /* Note that these stores can throw exceptions on MPU faults */ ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); cpu_stl_mmu(env, sp, nextinst, oi, GETPC()); cpu_stl_mmu(env, sp + 4, saved_psr, oi, GETPC()); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr) bool lspact = env->v7m.fpccr[s] & R_V7M_FPCCR_LSPACT_MASK; uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); assert(env->v7m.secure); @@ -XXX,XX +XXX,XX @@ void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr) ARMCPU *cpu = env_archcpu(env); uintptr_t ra = GETPC(); ARMMMUIdx mmu_idx = arm_mmu_idx(env); - MemOpIdx oi = make_memop_idx(MO_TE | MO_UL | MO_ALIGN, + MemOpIdx oi = make_memop_idx(mo_endian(env) | MO_UL | MO_ALIGN, arm_to_core_mmu_idx(mmu_idx)); /* fptr is the value of Rn, the frame pointer we load the FP regs from */ -- 2.43.0
From: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260414005348.4767-10-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/m_helper.c | 2 +- target/arm/tcg/meson.build | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/m_helper.c +++ b/target/arm/tcg/m_helper.c @@ -XXX,XX +XXX,XX @@ #include "qemu/log.h" #include "exec/page-protection.h" #ifdef CONFIG_TCG -#include "accel/tcg/cpu-ldst.h" +#include "accel/tcg/cpu-ldst-common.h" #include "semihosting/common-semi.h" #endif #if !defined(CONFIG_USER_ONLY) diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -XXX,XX +XXX,XX @@ arm_ss.add(when: 'TARGET_AARCH64', if_false: files('stubs32.c')) arm_ss.add(files( 'cpu32.c', - 'm_helper.c', )) arm_ss.add(when: 'TARGET_AARCH64', if_true: files( @@ -XXX,XX +XXX,XX @@ arm_common_system_ss.add( 'debug.c', 'hflags.c', 'gengvec.c', + 'm_helper.c', 'mve_helper.c', 'neon_helper.c', 'op_helper.c', @@ -XXX,XX +XXX,XX @@ arm_user_ss.add( 'debug.c', 'gengvec.c', 'hflags.c', + 'm_helper.c', 'mve_helper.c', 'neon_helper.c', 'op_helper.c', -- 2.43.0
From: Thomas Huth <thuth@redhat.com> When writing to this register, QEMU currently aborts: $ echo "readl 0x10000018" | ./qemu-system-arm -audiodev none,id=snd0 \ -M integratorcp,accel=qtest,audiodev=snd0 -display none -qtest stdio [I 0.000000] OPENED [R +0.001907] readl 0x10000018 qemu: hardware error: integratorcm_read: CM_LMBUSCNT [...] Aborted (core dumped) This is bad, a guest should ideally never be able to kill QEMU like this. Now, according to the "Intergrator/CP User Guide" from: https://developer.arm.com/documentation/dui0159/b/porting-integrator-ap-and-im-pd1/registers "The Integrator/AP CM_LMBUSCNT has been removed." That means this register does not seem to be implemented on real CP boards at all, only for older AP boards. Thus it should be fine if we simply ignore this register in QEMU and handle it like all other unimplemented registers in the "default" handler of the case statement. Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3407 Signed-off-by: Thomas Huth <thuth@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260420064933.64765-1-thuth@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/integratorcp.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/integratorcp.c +++ b/hw/arm/integratorcp.c @@ -XXX,XX +XXX,XX @@ static uint64_t integratorcm_read(void *opaque, hwaddr offset, } else { return s->cm_lock; } - case 6: /* CM_LMBUSCNT */ - /* ??? High frequency timer. */ - hw_error("integratorcm_read: CM_LMBUSCNT"); case 7: /* CM_AUXOSC */ return s->cm_auxosc; case 8: /* CM_SDRAM */ -- 2.43.0
include/hw/arm/primecell.h used to be more expansive, but now the only thing it defines is the ARM_SYSCTL_GPIO_* constants for the GPIO lines for the arm-sysctl system-control device used on the Realview, Versatile and Versatile Express boards. Replace it with a header file specific to that device. virt.c and vmapple.c included primecell.h despite not using the constants it defined; there we can simply drop the include entirely. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260416172627.690396-1-peter.maydell@linaro.org --- MAINTAINERS | 2 +- hw/arm/realview.c | 2 +- hw/arm/vexpress.c | 2 +- hw/arm/virt.c | 1 - hw/misc/arm_sysctl.c | 2 +- hw/vmapple/vmapple.c | 1 - include/hw/arm/primecell.h | 12 ------------ include/hw/misc/arm_sysctl.h | 16 ++++++++++++++++ 8 files changed, 20 insertions(+), 18 deletions(-) delete mode 100644 include/hw/arm/primecell.h create mode 100644 include/hw/misc/arm_sysctl.h diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ F: hw/ssi/pl022.c F: include/hw/ssi/pl022.h F: hw/rtc/pl031.c F: include/hw/rtc/pl031.h -F: include/hw/arm/primecell.h F: hw/timer/cmsdk-apb-timer.c F: include/hw/timer/cmsdk-apb-timer.h F: tests/qtest/cmsdk-apb-timer-test.c @@ -XXX,XX +XXX,XX @@ F: hw/*/versatile* F: hw/i2c/arm_sbcon_i2c.c F: include/hw/i2c/arm_sbcon_i2c.h F: hw/misc/arm_sysctl.c +F: include/hw/misc/arm_sysctl.h F: docs/system/arm/versatile.rst Virt diff --git a/hw/arm/realview.c b/hw/arm/realview.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/realview.c +++ b/hw/arm/realview.c @@ -XXX,XX +XXX,XX @@ #include "target/arm/cpu.h" #include "hw/core/sysbus.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/arm/machines-qom.h" #include "hw/core/split-irq.h" +#include "hw/misc/arm_sysctl.h" #include "hw/net/lan9118.h" #include "hw/net/smc91c111.h" #include "hw/pci/pci.h" diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -XXX,XX +XXX,XX @@ #include "qemu/datadir.h" #include "hw/core/sysbus.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/arm/machines-qom.h" +#include "hw/misc/arm_sysctl.h" #include "hw/net/lan9118.h" #include "hw/i2c/i2c.h" #include "net/net.h" diff --git a/hw/arm/virt.c b/hw/arm/virt.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -XXX,XX +XXX,XX @@ #include "monitor/qdev.h" #include "hw/core/sysbus.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/arm/virt.h" #include "hw/arm/machines-qom.h" #include "hw/block/flash.h" diff --git a/hw/misc/arm_sysctl.c b/hw/misc/arm_sysctl.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/arm_sysctl.c +++ b/hw/misc/arm_sysctl.c @@ -XXX,XX +XXX,XX @@ #include "system/runstate.h" #include "qemu/bitops.h" #include "hw/core/sysbus.h" +#include "hw/misc/arm_sysctl.h" #include "migration/vmstate.h" -#include "hw/arm/primecell.h" #include "qemu/log.h" #include "qemu/module.h" #include "qom/object.h" diff --git a/hw/vmapple/vmapple.c b/hw/vmapple/vmapple.c index XXXXXXX..XXXXXXX 100644 --- a/hw/vmapple/vmapple.c +++ b/hw/vmapple/vmapple.c @@ -XXX,XX +XXX,XX @@ #include "hw/core/sysbus.h" #include "hw/usb/usb.h" #include "hw/arm/boot.h" -#include "hw/arm/primecell.h" #include "hw/char/pl011.h" #include "hw/intc/arm_gic.h" #include "hw/intc/arm_gicv3_common.h" diff --git a/include/hw/arm/primecell.h b/include/hw/arm/primecell.h deleted file mode 100644 index XXXXXXX..XXXXXXX --- a/include/hw/arm/primecell.h +++ /dev/null @@ -XXX,XX +XXX,XX @@ -#ifndef PRIMECELL_H -#define PRIMECELL_H - -/* Declarations for ARM PrimeCell based periperals. */ -/* Also includes some devices that are currently only used by the - ARM boards. */ - -/* arm_sysctl GPIO lines */ -#define ARM_SYSCTL_GPIO_MMC_WPROT 0 -#define ARM_SYSCTL_GPIO_MMC_CARDIN 1 - -#endif diff --git a/include/hw/misc/arm_sysctl.h b/include/hw/misc/arm_sysctl.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/misc/arm_sysctl.h @@ -XXX,XX +XXX,XX @@ +/* + * Status and system control registers for ARM RealView/Versatile boards. + * + * Copyright (c) Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_ARM_SYSCTL_H +#define HW_MISC_ARM_SYSCTL_H + +/* arm_sysctl inbound GPIO lines */ +#define ARM_SYSCTL_GPIO_MMC_WPROT 0 +#define ARM_SYSCTL_GPIO_MMC_CARDIN 1 + +#endif -- 2.43.0
If we create a normally-AArch64 CPU and configure it with aarch64=off, this will by default leave all the AArch64 ID register values in its ARMISARegisters struct untouched. That in turn means that tests of cpu_isar_feature(aa64_something, cpu) will return true. Until now we have had a design policy that you shouldn't check an aa64_ feature unless you know that the CPU has AArch64; but this is quite fragile as it's easy to forget and only causes a problem in the corner case where AArch64 was turned off. In particular, when we extend the ability to disable AArch64 from only KVM to also TCG there are many more aa64 feature check points which we would otherwise have to audit for whether they needed to be guarded with a check on ARM_FEATURE_AARCH64. Instead, make the CPU realize function zero out all the 64-bit ID registers if a TCG CPU doesn't have AArch64; this will make aa64_ feature tests generally return false. We only do this for TCG because only TCG really needs it, and for KVM it might be confusing to have QEMU's idea of the ID registers be different from KVM's. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20260416165353.589569-2-peter.maydell@linaro.org --- target/arm/cpu.c | 35 +++++++++++++++++++++++++++++++++++ target/arm/cpu.h | 3 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ void arm_cpu_finalize_features(ARMCPU *cpu, Error **errp) } } +static void arm_clear_aarch64_idregs(ARMCPU *cpu) +{ + /* Zero out all the AArch64 ID registers in ARMISARegisters */ + SET_IDREG(&cpu->isar, ID_AA64ISAR0, 0); + SET_IDREG(&cpu->isar, ID_AA64ISAR1, 0); + SET_IDREG(&cpu->isar, ID_AA64ISAR2, 0); + SET_IDREG(&cpu->isar, ID_AA64PFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64PFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64PFR2, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR2, 0); + SET_IDREG(&cpu->isar, ID_AA64MMFR3, 0); + SET_IDREG(&cpu->isar, ID_AA64DFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64DFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64AFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64AFR1, 0); + SET_IDREG(&cpu->isar, ID_AA64ZFR0, 0); + SET_IDREG(&cpu->isar, ID_AA64SMFR0, 0); +} + static void arm_cpu_realizefn(DeviceState *dev, Error **errp) { CPUState *cs = CPU(dev); @@ -XXX,XX +XXX,XX @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp) } #endif + /* + * A TCG aarch64=off CPU has no AArch64 at all, so we clear out the + * ID registers to avoid cpu_isar_feature(aa64_something, cpu) tests + * incorrectly returning true. We don't do this for other accelerators + * (which in practice means "for KVM", since no others have AArch32 + * guest support) because from KVM's point of view the AArch64 ID + * registers still exist and must have their correct values. So we + * avoid clearing them out so that we don't have QEMU and KVM with + * different ideas of the ID registers. + */ + if (tcg_enabled() && !arm_feature(env, ARM_FEATURE_AARCH64)) { + arm_clear_aarch64_idregs(cpu); + } + #ifdef CONFIG_USER_ONLY /* * User mode relies on IC IVAU instructions to catch modification of diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ struct ArchCPU { * Note that if you add an ID register to the ARMISARegisters struct * you need to also update the 32-bit and 64-bit versions of the * kvm_arm_get_host_cpu_features() function to correctly populate the - * field by reading the value from the KVM vCPU. + * field by reading the value from the KVM vCPU. If it is an AArch64 + * ID register then you also must update arm_clear_aarch64_idregs(). */ struct ARMISARegisters { uint32_t mvfr0; -- 2.43.0
Allow the 'aarch64=off' property, which is currently KVM-only, to be set for TCG CPUs also. Note that we don't permit it on the qemu-aarch64 user-mode binary: this makes no sense as that executable can only handle AArch64 syscalls (and it would also assert at startup since it doesn't compile in the A32-specific GDB xml files like arm-neon.xml). Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Tested-by: Clément Chigot <chigot@adacore.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260416165353.589569-3-peter.maydell@linaro.org --- docs/system/arm/cpu-features.rst | 10 +++++---- target/arm/cpu-features.h | 5 +++++ target/arm/cpu.c | 36 ++++++++++++++++++++++++++++---- tests/qtest/arm-cpu-features.c | 8 ++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/docs/system/arm/cpu-features.rst b/docs/system/arm/cpu-features.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/cpu-features.rst +++ b/docs/system/arm/cpu-features.rst @@ -XXX,XX +XXX,XX @@ not implement ARMv8-A, will not have the ``aarch64`` CPU property. QEMU's support may be limited for some CPU features, only partially supporting the feature or only supporting the feature under certain configurations. For example, the ``aarch64`` CPU feature, which, when -disabled, enables the optional AArch32 CPU feature, is only supported -when using the KVM accelerator and when running on a host CPU type that -supports the feature. While ``aarch64`` currently only works with KVM, -it could work with TCG. CPU features that are specific to KVM are +disabled, enables the optional AArch32 CPU feature, can only be set to +``off`` on the TCG and KVM accelerators, and it cannot be set to +``off`` under KVM unless running on a host CPU type that supports +running guests in AArch32. + +CPU features that are inherently specific to KVM are prefixed with "kvm-" and are described in "KVM VCPU Features". CPU Feature Probing diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu-features.h +++ b/target/arm/cpu-features.h @@ -XXX,XX +XXX,XX @@ static inline bool isar_feature_aa64_aa32_el2(const ARMISARegisters *id) return FIELD_EX64_IDREG(id, ID_AA64PFR0, EL2) >= 2; } +static inline bool isar_feature_aa64_aa32_el3(const ARMISARegisters *id) +{ + return FIELD_EX64_IDREG(id, ID_AA64PFR0, EL3) >= 2; +} + static inline bool isar_feature_aa64_ras(const ARMISARegisters *id) { return FIELD_EX64_IDREG(id, ID_AA64PFR0, RAS) != 0; diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ static void aarch64_cpu_set_aarch64(Object *obj, bool value, Error **errp) * uniform execution state like do_interrupt. */ if (value == false) { - if (!kvm_enabled() || !kvm_arm_aarch32_supported()) { - error_setg(errp, "'aarch64' feature cannot be disabled " - "unless KVM is enabled and 32-bit EL1 " - "is supported"); + if (kvm_enabled()) { + if (!kvm_arm_aarch32_supported()) { + error_setg(errp, "'aarch64' feature cannot be disabled for KVM " + "because this host does not support 32-bit EL1"); + return; + } + } else if (tcg_enabled()) { +#ifdef CONFIG_USER_ONLY + error_setg(errp, "'aarch64' feature cannot be disabled for " + "usermode emulator qemu-aarch64; use qemu-arm instead"); + return; +#else + bool aa32_at_highest_el; + if (arm_feature(&cpu->env, ARM_FEATURE_EL3)) { + aa32_at_highest_el = cpu_isar_feature(aa64_aa32_el3, cpu); + } else if (arm_feature(&cpu->env, ARM_FEATURE_EL2)) { + aa32_at_highest_el = cpu_isar_feature(aa64_aa32_el2, cpu); + } else { + aa32_at_highest_el = cpu_isar_feature(aa64_aa32_el1, cpu); + } + + if (!aa32_at_highest_el) { + error_setg(errp, "'aarch64' feature cannot be disabled for " + "this TCG CPU because it does not support 32-bit " + "execution at its highest implemented exception " + "level"); + return; + } +#endif + } else { + error_setg(errp, "'aarch64' feature cannot be disabled for " + "this accelerator"); return; } unset_feature(&cpu->env, ARM_FEATURE_AARCH64); diff --git a/tests/qtest/arm-cpu-features.c b/tests/qtest/arm-cpu-features.c index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/arm-cpu-features.c +++ b/tests/qtest/arm-cpu-features.c @@ -XXX,XX +XXX,XX @@ static void test_query_cpu_model_expansion(const void *data) sve_tests_default(qts, "max"); pauth_tests_default(qts, "max"); - /* Test that features that depend on KVM generate errors without. */ - assert_error(qts, "max", - "'aarch64' feature cannot be disabled " - "unless KVM is enabled and 32-bit EL1 " - "is supported", - "{ 'aarch64': false }"); + /* TCG allows us to turn off AArch64 on the 'max' CPU type */ + assert_set_feature(qts, "max", "aarch64", false); } qtest_quit(qts); -- 2.43.0
Add a basic test of the TCG 'aarch64=off' functionality; this is the same as our existing arm/test_virt test, but it runs the AArch32 guest kernel on qemu-system-aarch64 with -cpu max,aarch64=off. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260416165353.589569-4-peter.maydell@linaro.org --- tests/functional/aarch64/meson.build | 1 + .../aarch64/test_virt_aarch64_off.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 tests/functional/aarch64/test_virt_aarch64_off.py diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -XXX,XX +XXX,XX @@ tests_aarch64_system_thorough = [ 'tcg_plugins', 'tuxrun', 'virt', + 'virt_aarch64_off', 'virt_gpu', 'virt_vbsa', 'xen', diff --git a/tests/functional/aarch64/test_virt_aarch64_off.py b/tests/functional/aarch64/test_virt_aarch64_off.py new file mode 100755 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/functional/aarch64/test_virt_aarch64_off.py @@ -XXX,XX +XXX,XX @@ +#!/usr/bin/env python3 +# +# Functional test that boots an AArch32 Linux kernel and checks the console +# on a TCG aarch64 CPU with aarch64=off. This is the same image etc +# as we use in tests/functional/arm/test_virt.py. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import LinuxKernelTest, Asset + +class ArmVirtMachine(LinuxKernelTest): + + ASSET_KERNEL = Asset( + ('https://archives.fedoraproject.org/pub/archive/fedora/linux/' + 'releases/29/Everything/armhfp/os/images/pxeboot/vmlinuz'), + '18dd5f1a9a28bd539f9d047f7c0677211bae528e8712b40ca5a229a4ad8e2591') + + def test_arm_virt(self): + self.set_machine('virt') + # KVM aarch64=off requires a host CPU that supports it, so + # restrict the test to TCG only + self.require_accelerator('tcg') + self.vm.add_args('-cpu', 'max,aarch64=off') + + kernel_path = self.ASSET_KERNEL.fetch() + + self.vm.set_console() + kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE + + 'console=ttyAMA0') + self.vm.add_args('-kernel', kernel_path, + '-append', kernel_command_line) + self.vm.launch() + console_pattern = 'Kernel command line: %s' % kernel_command_line + self.wait_for_console_pattern(console_pattern) + +if __name__ == '__main__': + LinuxKernelTest.main() -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> We introduce a datatype for a tolerance with respect to a given cpreg migration issue. The tolerance applies to a given cpreg kvm index, and can be of different types: a) mismatch in cpreg indexes - ToleranceNotOnBothEnds (cpreg index is allowed to be only present on one end) - ToleranceOnlySrcTestValue (cpreg index is allowed to be only present in source if its value @mask field matches @value) b) mismatch in cpreg values - ToleranceDiffInMask (value differences are allowed only within a mask) - ToleranceFieldLT (incoming field value must be less than a given value) - ToleranceFieldGT (incoming field value must be greater than a given value) A QLIST of such tolerances can be populated using a new helper: arm_register_cpreg_mig_tolerance() and arm_cpu_match_cpreg_mig_tolerance() allows to check whether a tolerance exists for a given kvm index and its criterion is matched. callers for those helpers will be introduced in subsequent patches. Only registration of migration tolerances related to cpreg index mismatch is currently allowed. Signed-off-by: Eric Auger <eric.auger@redhat.com> Message-id: 20260420140552.104369-2-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu.c | 82 ++++++++++++++++++++++++++++++++++++++++++ target/arm/cpu.h | 1 + target/arm/internals.h | 54 ++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+) diff --git a/target/arm/cpu.c b/target/arm/cpu.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.c +++ b/target/arm/cpu.c @@ -XXX,XX +XXX,XX @@ void arm_register_el_change_hook(ARMCPU *cpu, ARMELChangeHookFn *hook, QLIST_INSERT_HEAD(&cpu->el_change_hooks, entry, node); } +static ARMCPRegMigTolerance *find_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx) +{ + ARMCPRegMigTolerance *t; + QLIST_FOREACH(t, &cpu->cpreg_mig_tolerances, node) { + if (t->kvmidx == kvmidx) { + return t; + } + } + return NULL; +} + +void arm_register_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t mask, uint64_t value, + ARMCPRegMigToleranceType type) +{ + ARMCPRegMigTolerance *entry; + + /* make sure the kvmidx has not tolerance already registered */ + assert(!find_mig_tolerance(cpu, kvmidx)); + + assert(type == ToleranceNotOnBothEnds || + type == ToleranceOnlySrcTestValue); + + entry = g_new0(ARMCPRegMigTolerance, 1); + + entry->kvmidx = kvmidx; + entry->mask = mask; + entry->value = value; + entry->type = type; + + QLIST_INSERT_HEAD(&cpu->cpreg_mig_tolerances, entry, node); +} + +bool arm_cpu_match_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t vmstate_value, uint64_t local_value, + ARMCPRegMigToleranceType type) +{ + ARMCPRegMigTolerance *t = find_mig_tolerance(cpu, kvmidx); + uint64_t diff, diff_outside_mask, field; + + if (!t || t->type != type) { + return false; + } + + if (type == ToleranceNotOnBothEnds) { + return true; + } + + if (type == ToleranceOnlySrcTestValue && + ((vmstate_value & t->mask) == t->value)) { + return true; + } + + /* Need to check the mask */ + diff = vmstate_value ^ local_value; + diff_outside_mask = diff & ~t->mask; + + if (diff_outside_mask) { + /* there are differences outside of the mask */ + return false; + } + if (type == ToleranceDiffInMask) { + /* differences only in the field, tolerance matched */ + return true; + } + /* need to compare field value against authorized ones */ + field = vmstate_value & t->mask; + if (type == ToleranceFieldLT && (field < t->value)) { + return true; + } + if (type == ToleranceFieldGT && (field > t->value)) { + return true; + } + return false; +} + static void cp_reg_reset(gpointer key, gpointer value, gpointer opaque) { /* Reset a single ARMCPRegInfo register */ @@ -XXX,XX +XXX,XX @@ static void arm_cpu_initfn(Object *obj) QLIST_INIT(&cpu->pre_el_change_hooks); QLIST_INIT(&cpu->el_change_hooks); + QLIST_INIT(&cpu->cpreg_mig_tolerances); #ifdef CONFIG_USER_ONLY # ifdef TARGET_AARCH64 @@ -XXX,XX +XXX,XX @@ static void arm_cpu_finalizefn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); ARMELChangeHook *hook, *next; + ARMCPRegMigTolerance *t, *n; g_hash_table_destroy(cpu->cp_regs); @@ -XXX,XX +XXX,XX @@ static void arm_cpu_finalizefn(Object *obj) QLIST_REMOVE(hook, node); g_free(hook); } + QLIST_FOREACH_SAFE(t, &cpu->cpreg_mig_tolerances, node, n) { + QLIST_REMOVE(t, node); + g_free(t); + } #ifndef CONFIG_USER_ONLY if (cpu->pmu_timer) { timer_free(cpu->pmu_timer); diff --git a/target/arm/cpu.h b/target/arm/cpu.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -XXX,XX +XXX,XX @@ struct ArchCPU { QLIST_HEAD(, ARMELChangeHook) pre_el_change_hooks; QLIST_HEAD(, ARMELChangeHook) el_change_hooks; + QLIST_HEAD(, ARMCPRegMigTolerance) cpreg_mig_tolerances; int32_t node_id; /* NUMA node this CPU belongs to */ diff --git a/target/arm/internals.h b/target/arm/internals.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -XXX,XX +XXX,XX @@ int compare_u64(const void *a, const void *b); /* Used in FEAT_MEC to set the MECIDWidthm1 field in the MECIDR_EL2 register. */ #define MECID_WIDTH 16 +typedef enum { + ToleranceNotOnBothEnds, + ToleranceOnlySrcTestValue, + ToleranceDiffInMask, + ToleranceFieldLT, + ToleranceFieldGT, +} ARMCPRegMigToleranceType; + +typedef struct ARMCPRegMigTolerance { + uint64_t kvmidx; + uint64_t mask; + uint64_t value; + ARMCPRegMigToleranceType type; + QLIST_ENTRY(ARMCPRegMigTolerance) node; +} ARMCPRegMigTolerance; + +/** + * arm_register_cpreg_mig_tolerance: + * Register a migration tolerance wrt one given cpreg identified by its + * @kvmidx. Calling this function twice for the same @kvmidx is a + * programming error and will cause an assertion failure. + * + * @cpu: vcpu to apply the migration tolerance on + * @kvmidx: kvm index of the cpreg the tolerance applies to + * @mask: bitmask where a difference is tolerated + * (relevant with ToleranceDiffInMask) + * @value: value the bitmask field is compared with + * (relevant with ToleranceFieldLT and ToleranceFieldGT) + * @type: type of the migration tolerance: + * - ToleranceNotOnBothEnds (cpreg index is allowed to be only present + * on one end) + * - ToleranceOnlySrcTestValue (cpreg index is allowed to be only + * present in source if its value @mask field matches @value) + * - ToleranceDiffInMask (mismatch in cpreg values are only tolerated + * if differences are within @mask) + * - ToleranceFieldLT (mismatch in cpreg values are only tolerated + * if incoming @bitmask field value is less than @value) + * - ToleranceFieldGT (mismatch in cpreg values are only tolerated + * if incoming @bitmask field value is greater than @value) + */ +void arm_register_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t mask, uint64_t value, + ARMCPRegMigToleranceType type); + +/** + * arm_cpu_match_cpreg_mig_tolerance: + * Check whether a tolerance of type @type exists for a given @kvmidx + * and the tolerance criterion is satisfied + */ +bool arm_cpu_match_cpreg_mig_tolerance(ARMCPU *cpu, uint64_t kvmidx, + uint64_t vmstate_value, uint64_t local_value, + ARMCPRegMigToleranceType type); + + #endif -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> If there is a mismatch between the cpreg indexes found on both ends, check whether a tolerance was registered for the given kvmidx. If any, silence warning/errors. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-3-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/machine.c | 21 +++++++++++++++------ target/arm/trace-events | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/target/arm/machine.c b/target/arm/machine.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/machine.c +++ b/target/arm/machine.c @@ -XXX,XX +XXX,XX @@ static void handle_cpreg_missing_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx { g_autofree gchar *name = print_register_name(kvmidx); + if (arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, + 0, 0, ToleranceNotOnBothEnds)) { + trace_tolerate_cpreg_missing_in_incoming_stream(name); + return; + } warn_report("%s: %s " "expected by the destination but not in the incoming stream: " "skip it", __func__, name); } /* - * Handle the situation where @kvmidx is in the incoming stream - * but not on destination. This currently fails the migration but - * we plan to accomodate some exceptions, hence the boolean returned value. + * Handle the situation where @kvmidx is in the incoming + * stream but not on destination. This fails the migration if + * no cpreg mig tolerance is matched for this @kvmidx + * Return true if the migration should eventually fail */ static bool handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx) { g_autofree gchar *name = print_register_name(kvmidx); - bool fail = true; + if (arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, + 0, 0, ToleranceNotOnBothEnds)) { + trace_tolerate_cpreg_only_in_incoming_stream(name); + return false; + } error_report("%s: %s in the incoming stream but unknown on the " "destination: fail migration", __func__, name); - - return fail; + return true; } static int cpu_post_load(void *opaque, int version_id) diff --git a/target/arm/trace-events b/target/arm/trace-events index XXXXXXX..XXXXXXX 100644 --- a/target/arm/trace-events +++ b/target/arm/trace-events @@ -XXX,XX +XXX,XX @@ arm_psci_call(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint32_t cpuid # machine.c cpu_post_load(uint32_t cpreg_vmstate_array_len, uint32_t cpreg_array_len) "cpreg_vmstate_array_len=%d cpreg_array_len=%d" +tolerate_cpreg_missing_in_incoming_stream(char *name) "%s is missing in incoming stream but this is explicitly tolerated" +tolerate_cpreg_only_in_incoming_stream(char *name) "%s is in incoming stream but not on destination but this is explicitly tolerated" -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> Pass the value of the incoming register to handle_cpreg_only_in_incoming_stream and check whether there is a matching ToleranceOnlySrcTestValue tolerance. Signed-off-by: Eric Auger <eric.auger@redhat.com> Message-id: 20260420140552.104369-4-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/machine.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/target/arm/machine.c b/target/arm/machine.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/machine.c +++ b/target/arm/machine.c @@ -XXX,XX +XXX,XX @@ static void handle_cpreg_missing_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx * no cpreg mig tolerance is matched for this @kvmidx * Return true if the migration should eventually fail */ -static bool handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx) +static bool +handle_cpreg_only_in_incoming_stream(ARMCPU *cpu, uint64_t kvmidx, uint64_t value) { g_autofree gchar *name = print_register_name(kvmidx); if (arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, - 0, 0, ToleranceNotOnBothEnds)) { + 0, 0, ToleranceNotOnBothEnds) || + arm_cpu_match_cpreg_mig_tolerance(cpu, kvmidx, + value, 0, ToleranceOnlySrcTestValue)) { trace_tolerate_cpreg_only_in_incoming_stream(name); return false; } @@ -XXX,XX +XXX,XX @@ static int cpu_post_load(void *opaque, int version_id) } if (cpu->cpreg_vmstate_indexes[v] < cpu->cpreg_indexes[i]) { fail = handle_cpreg_only_in_incoming_stream(cpu, - cpu->cpreg_vmstate_indexes[v++]); + cpu->cpreg_vmstate_indexes[v], + cpu->cpreg_vmstate_values[v]); + v++; continue; } /* matching register, copy the value over */ @@ -XXX,XX +XXX,XX @@ static int cpu_post_load(void *opaque, int version_id) */ for ( ; v < cpu->cpreg_vmstate_array_len; v++) { fail = handle_cpreg_only_in_incoming_stream(cpu, - cpu->cpreg_vmstate_indexes[v]); + cpu->cpreg_vmstate_indexes[v], + cpu->cpreg_vmstate_values[v]); } if (fail) { return -1; -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> Before linux v6.13 those registers were erroneously unconditionally exposed and this was fixed by commits: - 0fcb4eea5345 ("KVM: arm64: Hide TCR2_EL1 from userspace when disabled for guests") - a68cddbe47ef ("KVM: arm64: Hide S1PIE registers from userspace when disabled for guests") in v6.13. This means if we migrate from an old kernel host to a >= 6.13 kernel host, migration currently fails. Declare cpreg migration tolerance for those registers. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Sebastian Ott <sebott@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-5-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu64.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -XXX,XX +XXX,XX @@ static void aarch64_a53_initfn(Object *obj) define_cortex_a72_a57_a53_cp_reginfo(cpu); } +#if defined(CONFIG_KVM) +static void kvm_arm_set_cpreg_mig_tolerances(ARMCPU *cpu) +{ + /* + * Registers that may be in the incoming stream and not exposed + * on the destination + */ + + /* + * TCR_EL1 was erroneously unconditionnally exposed before linux v6.13. + * See commit 0fcb4eea5345 ("KVM: arm64: Hide TCR2_EL1 from userspace + * when disabled for guests") + */ + arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 2, 0, 3), + 0, 0, ToleranceNotOnBothEnds); + /* + * PIRE0_EL1 and PIR_EL1 were erroneously unconditionnally exposed + * before linux v6.13. See commit a68cddbe47ef ("KVM: arm64: Hide + * S1PIE registers from userspace when disabled for guests") + */ + arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 10, 2, 2), + 0, 0, ToleranceNotOnBothEnds); + arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 10, 2, 3), + 0, 0, ToleranceNotOnBothEnds); +} +#endif + static void aarch64_host_initfn(Object *obj) { ARMCPU *cpu = ARM_CPU(obj); @@ -XXX,XX +XXX,XX @@ static void aarch64_host_initfn(Object *obj) #endif #if defined(CONFIG_KVM) + kvm_arm_set_cpreg_mig_tolerances(cpu); kvm_arm_set_cpu_features_from_host(cpu); aarch64_add_sve_properties(obj); #elif defined(CONFIG_HVF) -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> KVM_REG_ARM_VENDOR_HYP_BMAP_2 pseudo FW register is exposed from v6.15 onwards. Backward migration from a >= v6.15 to an older kernel would fail without cpreg migration tolerance definition for this register. If the register is present on source but not on destination, its value must be checked to make sure it matches the reset value, ie. 0, meaning no service is exposed to the guest, hence the choice of a ToleranceOnlySrcTestValue migration tolerance. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-6-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/cpu64.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/cpu64.c +++ b/target/arm/cpu64.c @@ -XXX,XX +XXX,XX @@ static void kvm_arm_set_cpreg_mig_tolerances(ARMCPU *cpu) 0, 0, ToleranceNotOnBothEnds); arm_register_cpreg_mig_tolerance(cpu, ARM64_SYS_REG(3, 0, 10, 2, 3), 0, 0, ToleranceNotOnBothEnds); + + /* + * KVM_REG_ARM_VENDOR_HYP_BMAP_2 pseudo FW register is exposed + * from v6.15 onwards. Backward migration from a >= v6.15 to an older + * kernel would fail without cpreg migration tolerance definition. + * If the register is present on source but not on destination, make + * sure it has its reset value, ie. 0, meaning no service is exposed + * to the guest. + */ + arm_register_cpreg_mig_tolerance(cpu, KVM_REG_ARM_FW_FEAT_BMAP_REG(3), + UINT64_MAX, 0, ToleranceOnlySrcTestValue); } #endif -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> We want to remove AArch32 DBGDTRTX which was erroneously exposed. This was attempted by 655659a74a36b ("target/arm: Correct encoding of Debug Communications Channel registers") but it was discovered that the removal of this debug register broke forward migration on TCG. Now we have the cpreg migration tolerance infrastructure, we can declare one for the DBGDTRTX. This allow to revert the reinstate patch. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Sebastian Ott <sebott@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-7-eric.auger@redhat.com [PMM: revised comment, included note about when we can drop the workaround] Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/helper.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/target/arm/helper.c b/target/arm/helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -XXX,XX +XXX,XX @@ void register_cp_regs_for_features(ARMCPU *cpu) .fgt = FGT_CLIDR_EL1, .resetvalue = GET_IDREG(isar, CLIDR) }; + uint64_t dbgtr_el0_kvmidx = + cpreg_to_kvm_id(ENCODE_CP_REG(14, 0, 1, 0, 5, 3, 0)); + define_one_arm_cp_reg(cpu, &clidr); define_arm_cp_regs(cpu, v7_cp_reginfo); define_debug_regs(cpu); + /* + * We used to incorrectly expose a non-existent AArch32 "DBGDTRTX" + * register with this encoding. This has been fixed by commit + * 655659a74a36 ("target/arm: Correct encoding of Debug + * Communications Channel registers") by the introduction of correct + * separate cpreg definitions for AA64 and AA32 versions. However, + * the old cpreg definition couldn't be removed without breaking + * migration, so commit 4f2b82f604 reinstated the bogus encoding + * for migration data only. + * + * Now that we have migration tolerance infrastructure, we can use + * this to allow forward migration from the buggy QEMU versions, + * accepting and ignoring the bogus register if it is in the + * source data. QEMU 11.0 was the last version that sent the + * bogus encoding, so this workaround can be removed at the point + * where we no longer care about migration from that version + * (i.e. when we remove the "virt-11.0" machine type). + */ + arm_register_cpreg_mig_tolerance(cpu, dbgtr_el0_kvmidx, + 0, 0, ToleranceNotOnBothEnds); } else { define_arm_cp_regs(cpu, not_v7_cp_reginfo); } -- 2.43.0
From: Eric Auger <eric.auger@redhat.com> This reverts commit 4f2b82f60431 ("target/arm: Reinstate bogus AArch32 DBGDTRTX register for migration compat). We don't need that commit anymore as the AArch32 DBGDTRTX register is declared to be safe to ignore in the incoming migration stream. Signed-off-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Sebastian Ott <sebott@redhat.com> Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 20260420140552.104369-8-eric.auger@redhat.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/debug_helper.c | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/target/arm/debug_helper.c b/target/arm/debug_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/debug_helper.c +++ b/target/arm/debug_helper.c @@ -XXX,XX +XXX,XX @@ static void dbgclaimclr_write(CPUARMState *env, const ARMCPRegInfo *ri, env->cp15.dbgclaim &= ~(value & 0xFF); } -static CPAccessResult access_bogus(CPUARMState *env, const ARMCPRegInfo *ri, - bool isread) -{ - /* Always UNDEF, as if this cpreg didn't exist */ - return CP_ACCESS_UNDEFINED; -} - static const ARMCPRegInfo debug_cp_reginfo[] = { /* * DBGDRAR, DBGDSAR: always RAZ since we don't implement memory mapped @@ -XXX,XX +XXX,XX @@ static const ARMCPRegInfo debug_cp_reginfo[] = { .opc0 = 2, .opc1 = 3, .crn = 0, .crm = 4, .opc2 = 0, .access = PL0_RW, .accessfn = access_tdcc, .type = ARM_CP_CONST, .resetvalue = 0 }, - /* - * This is not a real AArch32 register. We used to incorrectly expose - * this due to a QEMU bug; to avoid breaking migration compatibility we - * need to continue to provide it so that we don't fail the inbound - * migration when it tells us about a sysreg that we don't have. - * We set an always-fails .accessfn, which means that the guest doesn't - * actually see this register (it will always UNDEF, identically to if - * there were no cpreg definition for it other than that we won't print - * a LOG_UNIMP message about it), and we set the ARM_CP_NO_GDB flag so the - * gdbstub won't see it either. - * (We can't just set .access = 0, because add_cpreg_to_hashtable() - * helpfully ignores cpregs which aren't accessible to the highest - * implemented EL.) - * - * TODO: implement a system for being able to describe "this register - * can be ignored if it appears in the inbound stream"; then we can - * remove this temporary hack. - */ - { .name = "BOGUS_DBGDTR_EL0", .state = ARM_CP_STATE_AA32, - .cp = 14, .opc1 = 3, .crn = 0, .crm = 5, .opc2 = 0, - .access = PL0_RW, .accessfn = access_bogus, - .type = ARM_CP_CONST | ARM_CP_NO_GDB, .resetvalue = 0 }, /* * OSECCR_EL1 provides a mechanism for an operating system * to access the contents of EDECCR. EDECCR is not implemented though, -- 2.43.0
From: Osama Abdelkader <osama.abdelkader@gmail.com> fdt_node_offset_by_compatible(fdt, -1, compat) only finds the first match. If the blob has more than one node with the same compatible string, extra nodes will remain active. Remove all the matching nodes, using the same loop as imx8mp-evk.c does for this purpose. Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com> Message-id: 20260420162114.308519-1-osama.abdelkader@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/raspi4b.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/raspi4b.c +++ b/hw/arm/raspi4b.c @@ -XXX,XX +XXX,XX @@ static void raspi4_modify_dtb(const struct arm_boot_info *info, void *fdt) for (int i = 0; i < ARRAY_SIZE(nodes_to_remove); i++) { const char *dev_str = nodes_to_remove[i]; + int offset; - int offset = fdt_node_offset_by_compatible(fdt, -1, dev_str); - if (offset >= 0) { - if (!fdt_nop_node(fdt, offset)) { - warn_report("bcm2711 dtc: %s has been disabled!", dev_str); + offset = fdt_node_offset_by_compatible(fdt, -1, dev_str); + while (offset >= 0) { + if (fdt_nop_node(fdt, offset) == 0) { + warn_report("bcm2711 dtb: %s has been disabled!", dev_str); } + offset = fdt_node_offset_by_compatible(fdt, offset, dev_str); } } -- 2.43.0
From: Yucai Liu <1486344514@qq.com> Implement a basic i.MX6UL LCDIF controller model with MMIO registers, frame-done interrupt behavior, and framebuffer-backed display updates for RGB565 and XRGB8888 input formats. Place the LCDIF device under hw/display and build it via a dedicated CONFIG_IMX6UL_LCDIF symbol. Model register fields with registerfields.h helpers and provide migration support via vmstate. Signed-off-by: Yucai Liu <1486344514@qq.com> Message-id: 20260412110240.93116-2-yangyanglan718@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- MAINTAINERS | 2 + hw/display/Kconfig | 4 + hw/display/imx6ul_lcdif.c | 453 ++++++++++++++++++++++++++++++ hw/display/meson.build | 1 + include/hw/display/imx6ul_lcdif.h | 37 +++ 5 files changed, 497 insertions(+) create mode 100644 hw/display/imx6ul_lcdif.c create mode 100644 include/hw/display/imx6ul_lcdif.h diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ L: qemu-arm@nongnu.org S: Odd Fixes F: hw/arm/mcimx6ul-evk.c F: hw/arm/fsl-imx6ul.c +F: hw/display/imx6ul_lcdif.c F: hw/misc/imx6ul_ccm.c F: include/hw/arm/fsl-imx6ul.h +F: include/hw/display/imx6ul_lcdif.h F: include/hw/misc/imx6ul_ccm.h F: docs/system/arm/mcimx6ul-evk.rst diff --git a/hw/display/Kconfig b/hw/display/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -XXX,XX +XXX,XX @@ config PL110 bool select FRAMEBUFFER +config IMX6UL_LCDIF + bool + select FRAMEBUFFER + config SII9022 bool depends on I2C diff --git a/hw/display/imx6ul_lcdif.c b/hw/display/imx6ul_lcdif.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/display/imx6ul_lcdif.c @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * i.MX6UL LCDIF controller + * + * Copyright (c) 2026 Yucai Liu <1486344514@qq.com> + */ + +#include "qemu/osdep.h" +#include "hw/display/imx6ul_lcdif.h" +#include "hw/core/irq.h" +#include "hw/core/registerfields.h" +#include "hw/display/framebuffer.h" +#include "migration/vmstate.h" +#include "system/address-spaces.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "ui/pixel_ops.h" + +#define LCDIF_MMIO_SIZE (16 * KiB) +#define LCDIF_RESET_CTRL1 0x000f0000 + +REG32(CTRL, 0x00) + FIELD(CTRL, RUN, 0, 1) + FIELD(CTRL, WORD_LENGTH, 8, 2) +REG32(CTRL1, 0x10) + FIELD(CTRL1, CUR_FRAME_DONE_IRQ, 9, 1) + FIELD(CTRL1, CUR_FRAME_DONE_IRQ_EN, 13, 1) + FIELD(CTRL1, BYTE_PACKING_FORMAT, 16, 4) +REG32(V4_TRANSFER_COUNT, 0x30) + FIELD(V4_TRANSFER_COUNT, H_COUNT, 0, 16) + FIELD(V4_TRANSFER_COUNT, V_COUNT, 16, 16) +REG32(V4_CUR_BUF, 0x40) +REG32(V4_NEXT_BUF, 0x50) +REG32(AS_NEXT_BUF, 0x230) + +#define REG_SET 0x4 +#define REG_CLR 0x8 +#define REG_TOG 0xc + +#define CTRL_WORD_LENGTH_16 0 +#define CTRL_WORD_LENGTH_24 3 + +#define FRAME_PERIOD_NS (16 * 1000 * 1000ULL) + +enum IMX6ULLCDIFReg { + IMX6UL_LCDIF_REG_CTRL = A_CTRL >> 4, + IMX6UL_LCDIF_REG_CTRL1 = A_CTRL1 >> 4, + IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT = A_V4_TRANSFER_COUNT >> 4, + IMX6UL_LCDIF_REG_V4_CUR_BUF = A_V4_CUR_BUF >> 4, + IMX6UL_LCDIF_REG_V4_NEXT_BUF = A_V4_NEXT_BUF >> 4, + IMX6UL_LCDIF_REG_AS_NEXT_BUF = A_AS_NEXT_BUF >> 4, +}; + +static inline bool imx6ul_lcdif_reg_exists(hwaddr reg) +{ + return (reg >> 4) < IMX6UL_LCDIF_REGS_NUM; +} + +static inline bool imx6ul_lcdif_reg_has_setclr(hwaddr reg) +{ + switch (reg) { + case A_CTRL: + case A_CTRL1: + return true; + default: + return false; + } +} + +static inline bool imx6ul_lcdif_is_running(IMX6ULLCDIFState *s) +{ + uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL]; + + return FIELD_EX32(ctrl, CTRL, RUN); +} + +static inline bool imx6ul_lcdif_frame_done_pending(IMX6ULLCDIFState *s) +{ + uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1]; + + return FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ); +} + +static void imx6ul_lcdif_schedule_frame(IMX6ULLCDIFState *s) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + timer_mod(s->frame_timer, now + FRAME_PERIOD_NS); +} + +static void imx6ul_lcdif_maybe_schedule_frame(IMX6ULLCDIFState *s) +{ + if (imx6ul_lcdif_is_running(s) && !imx6ul_lcdif_frame_done_pending(s)) { + imx6ul_lcdif_schedule_frame(s); + } else { + timer_del(s->frame_timer); + } +} + +static void imx6ul_lcdif_update_irq(IMX6ULLCDIFState *s) +{ + uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1]; + bool level = FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ_EN) && + FIELD_EX32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ); + + qemu_set_irq(s->irq, level); +} + +static void imx6ul_lcdif_frame_done(IMX6ULLCDIFState *s) +{ + uint32_t ctrl1 = s->regs[IMX6UL_LCDIF_REG_CTRL1]; + + ctrl1 = FIELD_DP32(ctrl1, CTRL1, CUR_FRAME_DONE_IRQ, 1); + s->regs[IMX6UL_LCDIF_REG_CTRL1] = ctrl1; + imx6ul_lcdif_update_irq(s); +} + +static void imx6ul_lcdif_draw_line_rgb565(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + int i; + + for (i = 0; i < width; i++) { + uint16_t pixel = lduw_le_p(src); + uint8_t r = ((pixel >> 11) & 0x1f) << 3; + uint8_t g = ((pixel >> 5) & 0x3f) << 2; + uint8_t b = (pixel & 0x1f) << 3; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 2; + } +} + +static void imx6ul_lcdif_draw_line_xrgb8888(void *opaque, uint8_t *dst, + const uint8_t *src, int width, + int dststep) +{ + uint32_t *dst32 = (uint32_t *)dst; + int i; + + for (i = 0; i < width; i++) { + uint32_t pixel = ldl_le_p(src); + uint8_t r = (pixel >> 16) & 0xff; + uint8_t g = (pixel >> 8) & 0xff; + uint8_t b = pixel & 0xff; + + *dst32++ = rgb_to_pixel32(r, g, b); + src += 4; + } +} + +static void imx6ul_lcdif_update_display(void *opaque) +{ + IMX6ULLCDIFState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + uint32_t transfer_count = s->regs[IMX6UL_LCDIF_REG_V4_TRANSFER_COUNT]; + uint32_t width = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, H_COUNT); + uint32_t height = FIELD_EX32(transfer_count, V4_TRANSFER_COUNT, V_COUNT); + uint32_t ctrl = s->regs[IMX6UL_LCDIF_REG_CTRL]; + uint32_t frame_base = s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF]; + drawfn fn; + int first = 0; + int last = 0; + int src_width; + + if (!imx6ul_lcdif_is_running(s) || width == 0 || height == 0) { + return; + } + + switch (FIELD_EX32(ctrl, CTRL, WORD_LENGTH)) { + case CTRL_WORD_LENGTH_16: + s->src_bpp = 2; + fn = imx6ul_lcdif_draw_line_rgb565; + break; + case CTRL_WORD_LENGTH_24: + s->src_bpp = 4; + fn = imx6ul_lcdif_draw_line_xrgb8888; + break; + default: + return; + } + + if (surface_width(surface) != width || surface_height(surface) != height) { + qemu_console_resize(s->con, width, height); + surface = qemu_console_surface(s->con); + s->invalidate = true; + } + + src_width = width * s->src_bpp; + if (s->invalidate || s->fb_base != frame_base || + s->src_width != src_width || s->rows != height) { + framebuffer_update_memory_section(&s->fbsection, get_system_memory(), + frame_base, height, src_width); + s->fb_base = frame_base; + s->src_width = src_width; + s->rows = height; + } + + framebuffer_update_display(surface, &s->fbsection, width, height, + src_width, surface_stride(surface), 0, + s->invalidate, fn, s, &first, &last); + if (first >= 0) { + dpy_gfx_update(s->con, 0, first, width, last - first + 1); + } + + s->invalidate = false; +} + +static void imx6ul_lcdif_invalidate_display(void *opaque) +{ + IMX6ULLCDIFState *s = opaque; + + s->invalidate = true; +} + +static const GraphicHwOps imx6ul_lcdif_graphic_ops = { + .invalidate = imx6ul_lcdif_invalidate_display, + .gfx_update = imx6ul_lcdif_update_display, +}; + +static void imx6ul_lcdif_frame_timer_cb(void *opaque) +{ + IMX6ULLCDIFState *s = opaque; + + if (!imx6ul_lcdif_is_running(s) || imx6ul_lcdif_frame_done_pending(s)) { + return; + } + + imx6ul_lcdif_frame_done(s); +} + +static uint64_t imx6ul_lcdif_read(void *opaque, hwaddr offset, unsigned size) +{ + IMX6ULLCDIFState *s = opaque; + hwaddr reg = offset & ~0xf; + uint32_t idx; + + assert(size == 4); + assert(!(offset & 0x3)); + assert(offset < LCDIF_MMIO_SIZE); + + idx = reg >> 4; + if (idx >= ARRAY_SIZE(s->regs)) { + return 0; + } + + return s->regs[idx]; +} + +static void imx6ul_lcdif_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + IMX6ULLCDIFState *s = opaque; + hwaddr reg = offset & ~0xf; + uint32_t idx; + uint32_t oldv; + + assert(size == 4); + assert(!(offset & 0x3)); + assert(offset < LCDIF_MMIO_SIZE); + + if (!imx6ul_lcdif_reg_exists(reg)) { + return; + } + + idx = reg >> 4; + oldv = s->regs[idx]; + + switch (offset & 0xf) { + case 0: + s->regs[idx] = (uint32_t)value; + break; + case REG_SET: + if (!imx6ul_lcdif_reg_has_setclr(reg)) { + return; + } + s->regs[idx] = oldv | (uint32_t)value; + break; + case REG_CLR: + if (!imx6ul_lcdif_reg_has_setclr(reg)) { + return; + } + s->regs[idx] = oldv & ~(uint32_t)value; + break; + case REG_TOG: + if (!imx6ul_lcdif_reg_has_setclr(reg)) { + return; + } + s->regs[idx] = oldv ^ (uint32_t)value; + break; + default: + g_assert_not_reached(); + } + + switch (reg) { + case A_CTRL: + if (!FIELD_EX32(oldv, CTRL, RUN) && + FIELD_EX32(s->regs[idx], CTRL, RUN)) { + s->invalidate = true; + graphic_hw_invalidate(s->con); + imx6ul_lcdif_maybe_schedule_frame(s); + break; + } + if (FIELD_EX32(oldv, CTRL, RUN) && + !FIELD_EX32(s->regs[idx], CTRL, RUN)) { + timer_del(s->frame_timer); + } + break; + case A_CTRL1: + if (FIELD_EX32(oldv, CTRL1, CUR_FRAME_DONE_IRQ) && + !FIELD_EX32(s->regs[idx], CTRL1, CUR_FRAME_DONE_IRQ)) { + imx6ul_lcdif_maybe_schedule_frame(s); + } + break; + case A_V4_TRANSFER_COUNT: + s->invalidate = true; + graphic_hw_invalidate(s->con); + break; + case A_V4_CUR_BUF: + s->invalidate = true; + graphic_hw_invalidate(s->con); + break; + case A_V4_NEXT_BUF: + s->regs[IMX6UL_LCDIF_REG_V4_CUR_BUF] = s->regs[idx]; + imx6ul_lcdif_frame_done(s); + s->invalidate = true; + graphic_hw_invalidate(s->con); + imx6ul_lcdif_maybe_schedule_frame(s); + return; + case A_AS_NEXT_BUF: + imx6ul_lcdif_frame_done(s); + imx6ul_lcdif_maybe_schedule_frame(s); + return; + default: + break; + } + + imx6ul_lcdif_update_irq(s); +} + +static const MemoryRegionOps imx6ul_lcdif_ops = { + .read = imx6ul_lcdif_read, + .write = imx6ul_lcdif_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx6ul_lcdif_reset(DeviceState *dev) +{ + IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev); + + memset(s->regs, 0, sizeof(s->regs)); + s->regs[IMX6UL_LCDIF_REG_CTRL1] = LCDIF_RESET_CTRL1; + s->fb_base = 0; + s->src_width = 0; + s->rows = 0; + s->src_bpp = 0; + s->invalidate = true; + timer_del(s->frame_timer); + imx6ul_lcdif_update_irq(s); +} + +static int imx6ul_lcdif_post_load(void *opaque, int version_id) +{ + IMX6ULLCDIFState *s = opaque; + + s->fb_base = 0; + s->src_width = 0; + s->rows = 0; + s->src_bpp = 0; + s->invalidate = true; + + imx6ul_lcdif_update_irq(s); + if (imx6ul_lcdif_is_running(s) && + !imx6ul_lcdif_frame_done_pending(s) && + !timer_pending(s->frame_timer)) { + imx6ul_lcdif_schedule_frame(s); + } + + return 0; +} + +static const VMStateDescription vmstate_imx6ul_lcdif = { + .name = TYPE_IMX6UL_LCDIF, + .version_id = 1, + .minimum_version_id = 1, + .post_load = imx6ul_lcdif_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, IMX6ULLCDIFState, IMX6UL_LCDIF_REGS_NUM), + VMSTATE_TIMER_PTR(frame_timer, IMX6ULLCDIFState), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx6ul_lcdif_realize(DeviceState *dev, Error **errp) +{ + IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev); + + s->frame_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + imx6ul_lcdif_frame_timer_cb, s); + s->invalidate = true; + memory_region_init_io(&s->iomem, OBJECT(dev), &imx6ul_lcdif_ops, s, + TYPE_IMX6UL_LCDIF, LCDIF_MMIO_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq); + s->con = graphic_console_init(dev, 0, &imx6ul_lcdif_graphic_ops, s); +} + +static void imx6ul_lcdif_unrealize(DeviceState *dev) +{ + IMX6ULLCDIFState *s = IMX6UL_LCDIF(dev); + + timer_del(s->frame_timer); + timer_free(s->frame_timer); + s->frame_timer = NULL; + + if (s->con) { + graphic_console_close(s->con); + s->con = NULL; + } +} + +static void imx6ul_lcdif_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = imx6ul_lcdif_realize; + dc->unrealize = imx6ul_lcdif_unrealize; + dc->vmsd = &vmstate_imx6ul_lcdif; + device_class_set_legacy_reset(dc, imx6ul_lcdif_reset); + dc->desc = "i.MX6UL LCDIF"; +} + +static const TypeInfo imx6ul_lcdif_info = { + .name = TYPE_IMX6UL_LCDIF, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX6ULLCDIFState), + .class_init = imx6ul_lcdif_class_init, +}; + +static void imx6ul_lcdif_register_types(void) +{ + type_register_static(&imx6ul_lcdif_info); +} + +type_init(imx6ul_lcdif_register_types) diff --git a/hw/display/meson.build b/hw/display/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/display/meson.build +++ b/hw/display/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: ['CONFIG_VGA_CIRRUS', 'CONFIG_VGA_ISA'], if_true: files('cir system_ss.add(when: 'CONFIG_G364FB', if_true: files('g364fb.c')) system_ss.add(when: 'CONFIG_JAZZ_LED', if_true: files('jazz_led.c')) system_ss.add(when: 'CONFIG_PL110', if_true: files('pl110.c')) +system_ss.add(when: 'CONFIG_IMX6UL_LCDIF', if_true: files('imx6ul_lcdif.c')) system_ss.add(when: 'CONFIG_SII9022', if_true: files('sii9022.c')) system_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c')) system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c')) diff --git a/include/hw/display/imx6ul_lcdif.h b/include/hw/display/imx6ul_lcdif.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/display/imx6ul_lcdif.h @@ -XXX,XX +XXX,XX @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * i.MX6UL LCDIF controller + * + * Copyright (c) 2026 Yucai Liu <1486344514@qq.com> + */ + +#ifndef IMX6UL_LCDIF_H +#define IMX6UL_LCDIF_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" +#include "qemu/timer.h" +#include "ui/console.h" + +#define TYPE_IMX6UL_LCDIF "imx6ul-lcdif" +#define IMX6UL_LCDIF_REGS_NUM ((0x230 >> 4) + 1) +OBJECT_DECLARE_SIMPLE_TYPE(IMX6ULLCDIFState, IMX6UL_LCDIF) + +struct IMX6ULLCDIFState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + MemoryRegionSection fbsection; + qemu_irq irq; + QemuConsole *con; + QEMUTimer *frame_timer; + uint32_t fb_base; + uint32_t src_width; + uint32_t rows; + uint8_t src_bpp; + bool invalidate; + uint32_t regs[IMX6UL_LCDIF_REGS_NUM]; +}; + +#endif /* IMX6UL_LCDIF_H */ -- 2.43.0
From: Yucai Liu <1486344514@qq.com> Instantiate LCDIF as a child object of the i.MX6UL SoC in init and realize it in the SoC realize path before MMIO/IRQ hookup. Also make FSL_IMX6UL select CONFIG_IMX6UL_LCDIF and map the LCDIF region with a 16 KiB size to match the SoC memory map. Signed-off-by: Yucai Liu <1486344514@qq.com> Message-id: 20260412110240.93116-3-yangyanglan718@gmail.com Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- hw/arm/Kconfig | 1 + hw/arm/fsl-imx6ul.c | 12 ++++++++++-- include/hw/arm/fsl-imx6ul.h | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -XXX,XX +XXX,XX @@ config FSL_IMX6UL imply I2C_DEVICES select A15MPCORE select IMX + select IMX6UL_LCDIF select IMX_FEC select IMX_I2C select IMX_USBPHY diff --git a/hw/arm/fsl-imx6ul.c b/hw/arm/fsl-imx6ul.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/fsl-imx6ul.c +++ b/hw/arm/fsl-imx6ul.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "hw/arm/fsl-imx6ul.h" +#include "hw/display/imx6ul_lcdif.h" #include "hw/misc/unimp.h" #include "hw/usb/imx-usb-phy.h" #include "hw/core/boards.h" @@ -XXX,XX +XXX,XX @@ static void fsl_imx6ul_init(Object *obj) object_initialize_child(obj, name, &s->usb[i], TYPE_CHIPIDEA); } + /* + * LCDIF + */ + object_initialize_child(obj, "lcdif", &s->lcdif, TYPE_IMX6UL_LCDIF); + /* * SDHCIs */ @@ -XXX,XX +XXX,XX @@ static void fsl_imx6ul_realize(DeviceState *dev, Error **errp) /* * LCD */ - create_unimplemented_device("lcdif", FSL_IMX6UL_LCDIF_ADDR, - FSL_IMX6UL_LCDIF_SIZE); + sysbus_realize(SYS_BUS_DEVICE(&s->lcdif), &error_abort); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->lcdif), 0, FSL_IMX6UL_LCDIF_ADDR); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->lcdif), 0, + qdev_get_gpio_in(gic, FSL_IMX6UL_LCDIF_IRQ)); /* * CSU diff --git a/include/hw/arm/fsl-imx6ul.h b/include/hw/arm/fsl-imx6ul.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/fsl-imx6ul.h +++ b/include/hw/arm/fsl-imx6ul.h @@ -XXX,XX +XXX,XX @@ #include "hw/net/imx_fec.h" #include "hw/usb/chipidea.h" #include "hw/usb/imx-usb-phy.h" +#include "hw/display/imx6ul_lcdif.h" #include "system/memory.h" #include "target/arm/cpu.h" #include "qom/object.h" @@ -XXX,XX +XXX,XX @@ struct FslIMX6ULState { IMX2WdtState wdt[FSL_IMX6UL_NUM_WDTS]; IMXUSBPHYState usbphy[FSL_IMX6UL_NUM_USB_PHYS]; ChipideaState usb[FSL_IMX6UL_NUM_USBS]; + IMX6ULLCDIFState lcdif; MemoryRegion rom; MemoryRegion caam; MemoryRegion ocram; @@ -XXX,XX +XXX,XX @@ enum FslIMX6ULMemoryMap { FSL_IMX6UL_PXP_SIZE = (16 * KiB), FSL_IMX6UL_LCDIF_ADDR = 0x021C8000, - FSL_IMX6UL_LCDIF_SIZE = 0x100, + FSL_IMX6UL_LCDIF_SIZE = (16 * KiB), FSL_IMX6UL_CSI_ADDR = 0x021C4000, FSL_IMX6UL_CSI_SIZE = 0x100, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> We have a registerfields interface which we can use for defining fields alongside helpers to access them. Define the basic syndrome layout and convert the helpers that take the imm16 data directly. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-2-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 75 ++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ #ifndef TARGET_ARM_SYNDROME_H #define TARGET_ARM_SYNDROME_H -#include "qemu/bitops.h" +#include "hw/core/registerfields.h" /* Valid Syndrome Register EC field values */ enum arm_exception_class { @@ -XXX,XX +XXX,XX @@ enum arm_exception_class { EC_AA64_BKPT = 0x3c, }; +/* Generic syndrome encoding layout for HSR and lower 32 bits of ESR_EL2 */ +FIELD(SYNDROME, EC, 26, 6) +FIELD(SYNDROME, IL, 25, 1) +FIELD(SYNDROME, ISS, 0, 25) + typedef enum { SME_ET_AccessTrap, SME_ET_Streaming, @@ -XXX,XX +XXX,XX @@ typedef enum { static inline uint32_t syn_get_ec(uint32_t syn) { - return syn >> ARM_EL_EC_SHIFT; + return FIELD_EX32(syn, SYNDROME, EC); } static inline uint32_t syn_set_ec(uint32_t syn, uint32_t ec) { - return deposit32(syn, ARM_EL_EC_SHIFT, ARM_EL_EC_LENGTH, ec); + return FIELD_DP32(syn, SYNDROME, EC, ec); } /* @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_set_ec(uint32_t syn, uint32_t ec) */ static inline uint32_t syn_uncategorized(void) { - return (EC_UNCATEGORIZED << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_UNCATEGORIZED); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } +FIELD(ISS_IMM16, IMM16, 0, 16) + static inline uint32_t syn_aa64_svc(uint32_t imm16) { - return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_SVC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa64_hvc(uint32_t imm16) { - return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_HVC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa64_smc(uint32_t imm16) { - return (EC_AA64_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_SMC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_16bit) { - return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) - | (is_16bit ? 0 : ARM_EL_IL); + uint32_t res = syn_set_ec(0, EC_AA32_SVC); + res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_hvc(uint32_t imm16) { - return (EC_AA32_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA32_HVC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_smc(void) { - return (EC_AA32_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_AA32_SMC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_aa64_bkpt(uint32_t imm16) { - return (EC_AA64_BKPT << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff); + uint32_t res = syn_set_ec(0, EC_AA64_BKPT); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa32_bkpt(uint32_t imm16, bool is_16bit) { - return (EC_AA32_BKPT << ARM_EL_EC_SHIFT) | (imm16 & 0xffff) - | (is_16bit ? 0 : ARM_EL_IL); + uint32_t res = syn_set_ec(0, EC_AA32_BKPT); + res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); + return res; } static inline uint32_t syn_aa64_sysregtrap(int op0, int op1, int op2, @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_simd_access_trap(int cv, int cond, bool is_16bit) static inline uint32_t syn_sve_access_trap(void) { - return (EC_SVEACCESSTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_SVEACCESSTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } /* @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) static inline uint32_t syn_illegalstate(void) { - return (EC_ILLEGALSTATE << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_ILLEGALSTATE); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_pcalignment(void) { - return (EC_PCALIGNMENT << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_PCALIGNMENT); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_gcs_data_check(GCSInstructionType it, int rn) @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_gcs_gcsstr(int ra, int rn) static inline uint32_t syn_serror(uint32_t extra) { - return (EC_SERROR << ARM_EL_EC_SHIFT) | ARM_EL_IL | extra; + uint32_t res = syn_set_ec(0, EC_SERROR); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, SYNDROME, ISS, extra); + return res; } static inline uint32_t syn_mop(bool is_set, bool is_setg, int options, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Migrate syn_aa64_sysregtrap and co-processor register trap syndromes to the registerfields API. The co-processor syndromes are split between single and duel register moves. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-3-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 124 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ enum arm_exception_class { /* Generic syndrome encoding layout for HSR and lower 32 bits of ESR_EL2 */ FIELD(SYNDROME, EC, 26, 6) -FIELD(SYNDROME, IL, 25, 1) +FIELD(SYNDROME, IL, 25, 1) /* IL=1 for 32 bit instructions */ FIELD(SYNDROME, ISS, 0, 25) typedef enum { @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_aa64_smc(uint32_t imm16) static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_16bit) { uint32_t res = syn_set_ec(0, EC_AA32_SVC); - res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); return res; } @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_aa64_bkpt(uint32_t imm16) static inline uint32_t syn_aa32_bkpt(uint32_t imm16, bool is_16bit) { uint32_t res = syn_set_ec(0, EC_AA32_BKPT); - res = FIELD_DP32(res, SYNDROME, IL, is_16bit ? 0 : 1); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); res = FIELD_DP32(res, ISS_IMM16, IMM16, imm16); return res; } +/* + * ISS encoding for an exception from MSR, MRS, or System instruction + * in AArch64 state. + */ +FIELD(SYSREG_ISS, ISREAD, 0, 1) /* Direction, 1 is read */ +FIELD(SYSREG_ISS, CRM, 1, 4) +FIELD(SYSREG_ISS, RT, 5, 5) +FIELD(SYSREG_ISS, CRN, 10, 4) +FIELD(SYSREG_ISS, OP1, 14, 3) +FIELD(SYSREG_ISS, OP2, 17, 3) +FIELD(SYSREG_ISS, OP0, 20, 2) + static inline uint32_t syn_aa64_sysregtrap(int op0, int op1, int op2, int crn, int crm, int rt, int isread) { - return (EC_SYSTEMREGISTERTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL - | (op0 << 20) | (op2 << 17) | (op1 << 14) | (crn << 10) | (rt << 5) - | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_SYSTEMREGISTERTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, SYSREG_ISS, OP0, op0); + res = FIELD_DP32(res, SYSREG_ISS, OP2, op2); + res = FIELD_DP32(res, SYSREG_ISS, OP1, op1); + res = FIELD_DP32(res, SYSREG_ISS, CRN, crn); + res = FIELD_DP32(res, SYSREG_ISS, RT, rt); + res = FIELD_DP32(res, SYSREG_ISS, CRM, crm); + res = FIELD_DP32(res, SYSREG_ISS, ISREAD, isread); + + return res; } +/* + * ISS encoding for an exception from an MCR or MRC access + * (move to/from co-processor) + */ +FIELD(COPROC_ISS, ISREAD, 0, 1) +FIELD(COPROC_ISS, CRM, 1, 4) +FIELD(COPROC_ISS, RT, 5, 5) +FIELD(COPROC_ISS, CRN, 10, 4) +FIELD(COPROC_ISS, OP1, 14, 3) +FIELD(COPROC_ISS, OP2, 17, 3) +FIELD(COPROC_ISS, COND, 20, 4) +FIELD(COPROC_ISS, CV, 24, 1) + static inline uint32_t syn_cp14_rt_trap(int cv, int cond, int opc1, int opc2, int crn, int crm, int rt, int isread, bool is_16bit) { - return (EC_CP14RTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) - | (crn << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP14RTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_ISS, OP2, opc2); + res = FIELD_DP32(res, COPROC_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_ISS, CRN, crn); + res = FIELD_DP32(res, COPROC_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_ISS, ISREAD, isread); + + return res; } static inline uint32_t syn_cp15_rt_trap(int cv, int cond, int opc1, int opc2, int crn, int crm, int rt, int isread, bool is_16bit) { - return (EC_CP15RTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc2 << 17) | (opc1 << 14) - | (crn << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP15RTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_ISS, OP2, opc2); + res = FIELD_DP32(res, COPROC_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_ISS, CRN, crn); + res = FIELD_DP32(res, COPROC_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_ISS, ISREAD, isread); + + return res; } +/* + * ISS encoding for an exception from an MCRR or MRRC access + * (move to/from co-processor with 2 regs) + */ +FIELD(COPROC_R2_ISS, ISREAD, 0, 1) +FIELD(COPROC_R2_ISS, CRM, 1, 4) +FIELD(COPROC_R2_ISS, RT, 5, 5) +FIELD(COPROC_R2_ISS, RT2, 10, 5) +FIELD(COPROC_R2_ISS, OP1, 16, 4) +FIELD(COPROC_R2_ISS, COND, 20, 4) +FIELD(COPROC_R2_ISS, CV, 24, 1) + static inline uint32_t syn_cp14_rrt_trap(int cv, int cond, int opc1, int crm, int rt, int rt2, int isread, bool is_16bit) { - return (EC_CP14RRTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc1 << 16) - | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP14RRTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_R2_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_R2_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_R2_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_R2_ISS, RT2, rt2); + res = FIELD_DP32(res, COPROC_R2_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_R2_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_R2_ISS, ISREAD, isread); + + return res; } static inline uint32_t syn_cp15_rrt_trap(int cv, int cond, int opc1, int crm, int rt, int rt2, int isread, bool is_16bit) { - return (EC_CP15RRTTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (opc1 << 16) - | (rt2 << 10) | (rt << 5) | (crm << 1) | isread; + uint32_t res = syn_set_ec(0, EC_CP15RRTTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, COPROC_R2_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_R2_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_R2_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_R2_ISS, RT2, rt2); + res = FIELD_DP32(res, COPROC_R2_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_R2_ISS, CRM, crm); + res = FIELD_DP32(res, COPROC_R2_ISS, ISREAD, isread); + + return res; } static inline uint32_t syn_fp_access_trap(int cv, int cond, bool is_16bit, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> The syn_simd_access trap was never used so remove it. We should only see the COPROC encoding on v7 architectures. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-4-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_cp15_rrt_trap(int cv, int cond, int opc1, int crm, return res; } +/* + * ISS encoding for an exception from an access to a register of + * instruction resulting from the FPEN or TFP traps. + */ +FIELD(FP_ISS, COPROC, 0, 4) /* ARMv7 only */ +FIELD(FP_ISS, COND, 20, 4) +FIELD(FP_ISS, CV, 24, 1) + static inline uint32_t syn_fp_access_trap(int cv, int cond, bool is_16bit, int coproc) { /* AArch32 FP trap or any AArch64 FP/SIMD trap: TA == 0 */ - return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | coproc; -} + uint32_t res = syn_set_ec(0, EC_ADVSIMDFPACCESSTRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); -static inline uint32_t syn_simd_access_trap(int cv, int cond, bool is_16bit) -{ - /* AArch32 SIMD trap: TA == 1 coproc == 0 */ - return (EC_ADVSIMDFPACCESSTRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | (cv << 24) | (cond << 20) | (1 << 5); + res = FIELD_DP32(res, FP_ISS, CV, cv); + res = FIELD_DP32(res, FP_ISS, COND, cond); + res = FIELD_DP32(res, FP_ISS, COPROC, coproc); + + return res; } static inline uint32_t syn_sve_access_trap(void) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> For simplicity keep the OP as a two bit field rather than the two interlinked fields in the docs (ERET/ERETA). Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-5-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_sve_access_trap(void) } /* + * ISS encoding for an exception from an ERET, ERETAA or ERETAB + * instructions. + * * eret_op is bits [1:0] of the ERET instruction, so: * 0 for ERET, 2 for ERETAA, 3 for ERETAB. */ +FIELD(ERET_ISS, OP, 0, 2) + static inline uint32_t syn_erettrap(int eret_op) { - return (EC_ERETTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL | eret_op; + uint32_t res = syn_set_ec(0, EC_ERETTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, ERET_ISS, OP, eret_op); + + return res; } static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-6-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_erettrap(int eret_op) return res; } +/* + * ISS encoding for an exception due to SME functionality + */ +FIELD(SME_ISS, SMTC, 0, 2) + static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit) { - return (EC_SMETRAP << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) | etype; + uint32_t res = syn_set_ec(0, EC_SMETRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + res = FIELD_DP32(res, SME_ISS, SMTC, etype); + + return res; } static inline uint32_t syn_pacfail(bool data, int keynumber) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> syn_pactrap is fairly simple as the ISS is all RES0. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-7-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_smetrap(SMEExceptionType etype, bool is_16bit) return res; } +/* + * ISS encoding for a PAC Fail exceptions + */ +FIELD(PACFAIL_ISS, BnA, 0, 1) /* B key or A key */ +FIELD(PACFAIL_ISS, DnI, 1, 1) /* Data or Instruction */ + static inline uint32_t syn_pacfail(bool data, int keynumber) { - int error_code = (data << 1) | keynumber; - return (EC_PACFAIL << ARM_EL_EC_SHIFT) | ARM_EL_IL | error_code; + uint32_t res = syn_set_ec(0, EC_PACFAIL); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, PACFAIL_ISS, DnI, data); + res = FIELD_DP32(res, PACFAIL_ISS, BnA, keynumber); + + return res; } +/* + * ISS encoding for an exception from a trapped Pointer + * Authentication instruction is RES0 + */ static inline uint32_t syn_pactrap(void) { - return (EC_PACTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL; + uint32_t res = syn_set_ec(0, EC_PACTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + return res; } static inline uint32_t syn_btitrap(int btype) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-8-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_pactrap(void) return res; } +/* + * ISS encoding for an exception from a Branch Target Identification + * instruction. + */ +FIELD(BTI_ISS, BTYPE, 0, 2) + static inline uint32_t syn_btitrap(int btype) { - return (EC_BTITRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL | btype; + uint32_t res = syn_set_ec(0, EC_BTITRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, BTI_ISS, BTYPE, btype); + return res; } static inline uint32_t syn_bxjtrap(int cv, int cond, int rm) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> This is an Armv7 specific syndrome for chips with Jazelle functionality. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-9-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_btitrap(int btype) return res; } +/* + * ISS encoding for trapped BXJ execution + * + * This is an Armv7 encoding. + */ +FIELD(BXJ_ISS, RM, 0, 4) +/* bits 4:19 are Reserved, UNK/SBZP */ +FIELD(BXJ_ISS, COND, 20, 4) +FIELD(BXJ_ISS, CV, 24, 1) + static inline uint32_t syn_bxjtrap(int cv, int cond, int rm) { - return (EC_BXJTRAP << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (cv << 24) | (cond << 20) | rm; + uint32_t res = syn_set_ec(0, EC_BXJTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, BXJ_ISS, CV, cv); + res = FIELD_DP32(res, BXJ_ISS, COND, cond); + res = FIELD_DP32(res, BXJ_ISS, RM, rm); + + return res; } static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr, -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-10-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_bxjtrap(int cv, int cond, int rm) return res; } +/* + * ISS encoding for a Granule Protection Check exception + * + * These are only reported to EL3 + */ +FIELD(GPC_ISS, xFSC, 0, 6) +FIELD(GPC_ISS, WnR, 6, 1) /* Write not Read */ +FIELD(GPC_ISS, S1PTW, 7, 1) +FIELD(GPC_ISS, CM, 8, 1) +FIELD(GPC_ISS, VNCR, 13, 1) +FIELD(GPC_ISS, GPCSC, 14, 6) +FIELD(GPC_ISS, InD, 20, 1) /* Instruction not Data access */ +FIELD(GPC_ISS, S2PTW, 21, 1) + static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr, int cm, int s1ptw, int wnr, int fsc) { - return (EC_GPC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (s2ptw << 21) - | (ind << 20) | (gpcsc << 14) | (vncr << 13) | (cm << 8) - | (s1ptw << 7) | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_GPC); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GPC_ISS, S2PTW, s2ptw); + res = FIELD_DP32(res, GPC_ISS, InD, ind); + res = FIELD_DP32(res, GPC_ISS, GPCSC, gpcsc); + res = FIELD_DP32(res, GPC_ISS, VNCR, vncr); + res = FIELD_DP32(res, GPC_ISS, CM, cm); + res = FIELD_DP32(res, GPC_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, GPC_ISS, WnR, wnr); + res = FIELD_DP32(res, GPC_ISS, xFSC, fsc); + + return res; } static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Migrate syn_insn_abort and syn_data_abort_* to the registerfields API. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260422125250.1303100-11-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 87 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_gpc(int s2ptw, int ind, int gpcsc, int vncr, return res; } +/* + * ISS encoding for an exception from an Instruction Abort + * + * (aka instruction abort) + */ +FIELD(IABORT_ISS, IFSC, 0, 6) +FIELD(IABORT_ISS, S1PTW, 7, 1) +FIELD(IABORT_ISS, EA, 9, 1) +FIELD(IABORT_ISS, FnV, 10, 1) /* FAR not Valid */ +FIELD(IABORT_ISS, SET, 11, 2) +FIELD(IABORT_ISS, PFV, 14, 1) +FIELD(IABORT_ISS, TopLevel, 21, 1) /* FEAT_THE */ + static inline uint32_t syn_insn_abort(int same_el, int ea, int s1ptw, int fsc) { - return (EC_INSNABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (ea << 9) | (s1ptw << 7) | fsc; + uint32_t res = syn_set_ec(0, EC_INSNABORT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, IABORT_ISS, EA, ea); + res = FIELD_DP32(res, IABORT_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, IABORT_ISS, IFSC, fsc); + + return res; } +/* + * ISS encoding for an exception from a Data Abort + */ +FIELD(DABORT_ISS, DFSC, 0, 6) +FIELD(DABORT_ISS, WNR, 6, 1) +FIELD(DABORT_ISS, S1PTW, 7, 1) +FIELD(DABORT_ISS, CM, 8, 1) +FIELD(DABORT_ISS, EA, 9, 1) +FIELD(DABORT_ISS, FnV, 10, 1) +FIELD(DABORT_ISS, LST, 11, 2) +FIELD(DABORT_ISS, VNCR, 13, 1) +FIELD(DABORT_ISS, AR, 14, 1) +FIELD(DABORT_ISS, SF, 15, 1) +FIELD(DABORT_ISS, SRT, 16, 5) +FIELD(DABORT_ISS, SSE, 21, 1) +FIELD(DABORT_ISS, SAS, 22, 2) +FIELD(DABORT_ISS, ISV, 24, 1) + static inline uint32_t syn_data_abort_no_iss(int same_el, int fnv, int ea, int cm, int s1ptw, int wnr, int fsc) { - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL - | (fnv << 10) | (ea << 9) | (cm << 8) | (s1ptw << 7) - | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_DATAABORT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, DABORT_ISS, FnV, fnv); + res = FIELD_DP32(res, DABORT_ISS, EA, ea); + res = FIELD_DP32(res, DABORT_ISS, CM, cm); + res = FIELD_DP32(res, DABORT_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, DABORT_ISS, WNR, wnr); + res = FIELD_DP32(res, DABORT_ISS, DFSC, fsc); + + return res; } static inline uint32_t syn_data_abort_with_iss(int same_el, @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_data_abort_with_iss(int same_el, int wnr, int fsc, bool is_16bit) { - return (EC_DATAABORT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | (is_16bit ? 0 : ARM_EL_IL) - | ARM_EL_ISV | (sas << 22) | (sse << 21) | (srt << 16) - | (sf << 15) | (ar << 14) - | (ea << 9) | (cm << 8) | (s1ptw << 7) | (wnr << 6) | fsc; + uint32_t res = syn_set_ec(0, EC_DATAABORT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, DABORT_ISS, ISV, 1); + res = FIELD_DP32(res, DABORT_ISS, SAS, sas); + res = FIELD_DP32(res, DABORT_ISS, SSE, sse); + res = FIELD_DP32(res, DABORT_ISS, SRT, srt); + res = FIELD_DP32(res, DABORT_ISS, SF, sf); + res = FIELD_DP32(res, DABORT_ISS, AR, ar); + res = FIELD_DP32(res, DABORT_ISS, EA, ea); + res = FIELD_DP32(res, DABORT_ISS, CM, cm); + res = FIELD_DP32(res, DABORT_ISS, S1PTW, s1ptw); + res = FIELD_DP32(res, DABORT_ISS, WNR, wnr); + res = FIELD_DP32(res, DABORT_ISS, DFSC, fsc); + + return res; } /* @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_data_abort_with_iss(int same_el, */ 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; + uint32_t res = syn_set_ec(0, EC_DATAABORT_SAME_EL); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, DABORT_ISS, VNCR, 1); + res = FIELD_DP32(res, DABORT_ISS, WNR, wnr); + res = FIELD_DP32(res, DABORT_ISS, DFSC, fsc); + + return res; } static inline uint32_t syn_swstep(int same_el, int isv, int ex) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Migrate syn_swstep, syn_watchpoint and syn_breakpoint to the registerfields API. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-12-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 54 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_data_abort_vncr(int ea, int wnr, int fsc) return res; } +/* + * ISS encoding for an exception from a Software Step exception. + */ +FIELD(SOFTSTEP_ISS, IFSC, 0, 6) +FIELD(SOFTSTEP_ISS, EX, 6, 1) +FIELD(SOFTSTEP_ISS, ISV, 24, 1) + 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) - | ARM_EL_IL | (isv << 24) | (ex << 6) | 0x22; + uint32_t res = syn_set_ec(0, EC_SOFTWARESTEP + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, SOFTSTEP_ISS, ISV, isv); + res = FIELD_DP32(res, SOFTSTEP_ISS, EX, ex); + res = FIELD_DP32(res, SOFTSTEP_ISS, IFSC, 0x22); + + return res; } +/* + * ISS encoding for an exception from a Watchpoint exception + */ +FIELD(WATCHPOINT_ISS, DFSC, 0, 6) +FIELD(WATCHPOINT_ISS, WNR, 6, 1) +FIELD(WATCHPOINT_ISS, CM, 8, 1) +FIELD(WATCHPOINT_ISS, FnV, 10, 1) +FIELD(WATCHPOINT_ISS, VNCR, 13, 1) /* FEAT_NV2 */ +FIELD(WATCHPOINT_ISS, FnP, 15, 1) +FIELD(WATCHPOINT_ISS, WPF, 16, 1) +/* bellow mandatory from FEAT_Debugv8p9 */ +FIELD(WATCHPOINT_ISS, WPTV, 17, 1) /* FEAT_Debugv8p2 - WPT valid */ +FIELD(WATCHPOINT_ISS, WPT, 18, 6) /* FEAT_Debugv8p2 - missing WP number */ + static inline uint32_t syn_watchpoint(int same_el, int cm, int wnr) { - return (EC_WATCHPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | (cm << 8) | (wnr << 6) | 0x22; + uint32_t res = syn_set_ec(0, EC_WATCHPOINT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, WATCHPOINT_ISS, CM, cm); + res = FIELD_DP32(res, WATCHPOINT_ISS, WNR, wnr); + res = FIELD_DP32(res, WATCHPOINT_ISS, DFSC, 0x22); + + return res; } +/* + * ISS encoding for an exception from a Breakpoint or a Vector Catch + * debug exception. + */ +FIELD(BREAKPOINT_ISS, IFSC, 0, 6) + static inline uint32_t syn_breakpoint(int same_el) { - return (EC_BREAKPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) - | ARM_EL_IL | 0x22; + uint32_t res = syn_set_ec(0, EC_BREAKPOINT + same_el); + res = FIELD_DP32(res, SYNDROME, IL, 1); + res = FIELD_DP32(res, BREAKPOINT_ISS, IFSC, 0x22); + + return res; } static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> This will help later when we expand the fields we report. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-13-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_breakpoint(int same_el) return res; } +/* + * ISS encoding for an exception from a WF* instruction + */ +FIELD(WFX_ISS, TI, 0, 2) +FIELD(WFX_ISS, RV, 2, 1) +FIELD(WFX_ISS, RN, 5, 5) +FIELD(WFX_ISS, COND, 20, 4) +FIELD(WFX_ISS, CV, 24, 1) + static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) { - return (EC_WFX_TRAP << ARM_EL_EC_SHIFT) | - (is_16bit ? 0 : (1 << ARM_EL_IL_SHIFT)) | - (cv << 24) | (cond << 20) | ti; + uint32_t res = syn_set_ec(0, EC_WFX_TRAP); + res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); + + res = FIELD_DP32(res, WFX_ISS, CV, cv); + res = FIELD_DP32(res, WFX_ISS, COND, cond); + res = FIELD_DP32(res, WFX_ISS, TI, ti); + + return res; } static inline uint32_t syn_illegalstate(void) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Tweak arg names to make it clear raddr is the data address register number. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-14-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_pcalignment(void) return res; } +/* + * ISS encoding for a GCS exception + * + * Field validity depends on EXTYPE + */ +FIELD(GCS_ISS, IT, 0, 5) +FIELD(GCS_ISS, RN, 5, 5) /* only for non EXLOCK exceptions */ +FIELD(GCS_ISS, RADDR, 10, 5) /* only for GCSSTR/GCSSTTR traps */ +FIELD(GCS_ISS, EXTYPE, 20, 4) + static inline uint32_t syn_gcs_data_check(GCSInstructionType it, int rn) { - return ((EC_GCS << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (GCS_ET_DataCheck << 20) | (rn << 5) | it); + uint32_t res = syn_set_ec(0, EC_GCS); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GCS_ISS, EXTYPE, GCS_ET_DataCheck); + res = FIELD_DP32(res, GCS_ISS, RN, rn); + res = FIELD_DP32(res, GCS_ISS, IT, it); + + return res; } static inline uint32_t syn_gcs_exlock(void) { - return (EC_GCS << ARM_EL_EC_SHIFT) | ARM_EL_IL | (GCS_ET_EXLOCK << 20); + uint32_t res = syn_set_ec(0, EC_GCS); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GCS_ISS, EXTYPE, GCS_ET_EXLOCK); + + return res; } -static inline uint32_t syn_gcs_gcsstr(int ra, int rn) +static inline uint32_t syn_gcs_gcsstr(int raddr, int rn) { - return ((EC_GCS << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (GCS_ET_GCSSTR_GCSSTTR << 20) | (ra << 10) | (rn << 5)); + uint32_t res = syn_set_ec(0, EC_GCS); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, GCS_ISS, EXTYPE, GCS_ET_GCSSTR_GCSSTTR); + res = FIELD_DP32(res, GCS_ISS, RADDR, raddr); + res = FIELD_DP32(res, GCS_ISS, RN, rn); + + return res; } static inline uint32_t syn_serror(uint32_t extra) -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-15-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_serror(uint32_t extra) return res; } +/* + * ISS encoding for an exception from the Memory Copy and Memory Set + * instructions. + */ +FIELD(MOP_ISS, SIZEREG, 0, 5) +FIELD(MOP_ISS, SRCREG, 5, 5) +FIELD(MOP_ISS, DESTREG, 10, 5) +FIELD(MOP_ISS, FORMATOPT, 16, 2) +FIELD(MOP_ISS, OPT_A, 16, 1) +FIELD(MOP_ISS, WRONG_OPT, 17, 1) +FIELD(MOP_ISS, EPILOGUE, 18, 1) +FIELD(MOP_ISS, OPTIONS, 19, 4) +FIELD(MOP_ISS, IS_SETG, 23, 1) +FIELD(MOP_ISS, MEMINST, 24, 1) + static inline uint32_t syn_mop(bool is_set, bool is_setg, int options, bool epilogue, bool wrong_option, bool option_a, int destreg, int srcreg, int sizereg) { - return (EC_MOP << ARM_EL_EC_SHIFT) | ARM_EL_IL | - (is_set << 24) | (is_setg << 23) | (options << 19) | - (epilogue << 18) | (wrong_option << 17) | (option_a << 16) | - (destreg << 10) | (srcreg << 5) | sizereg; + uint32_t res = syn_set_ec(0, EC_MOP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, MOP_ISS, MEMINST, is_set); + res = FIELD_DP32(res, MOP_ISS, IS_SETG, is_setg); + res = FIELD_DP32(res, MOP_ISS, OPTIONS, options); + res = FIELD_DP32(res, MOP_ISS, EPILOGUE, epilogue); + res = FIELD_DP32(res, MOP_ISS, WRONG_OPT, wrong_option); + res = FIELD_DP32(res, MOP_ISS, OPT_A, option_a); + res = FIELD_DP32(res, MOP_ISS, DESTREG, destreg); + res = FIELD_DP32(res, MOP_ISS, SRCREG, srcreg); + res = FIELD_DP32(res, MOP_ISS, SIZEREG, sizereg); + + return res; } -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> It shares the same COPROC_ISS encoding as the other CP traps although not all the fields are used. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-16-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 16 ++++++++++++++++ target/arm/tcg/vfp_helper.c | 5 +---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ FIELD(COPROC_ISS, OP2, 17, 3) FIELD(COPROC_ISS, COND, 20, 4) FIELD(COPROC_ISS, CV, 24, 1) +static inline uint32_t syn_cp10_rt_trap(int cv, int cond, int opc1, + int crn, int rt, int isread) +{ + uint32_t res = syn_set_ec(0, EC_FPIDTRAP); + res = FIELD_DP32(res, SYNDROME, IL, 1); + + res = FIELD_DP32(res, COPROC_ISS, CV, cv); + res = FIELD_DP32(res, COPROC_ISS, COND, cond); + res = FIELD_DP32(res, COPROC_ISS, OP1, opc1); + res = FIELD_DP32(res, COPROC_ISS, CRN, crn); + res = FIELD_DP32(res, COPROC_ISS, RT, rt); + res = FIELD_DP32(res, COPROC_ISS, ISREAD, isread); + + return res; +} + static inline uint32_t syn_cp14_rt_trap(int cv, int cond, int opc1, int opc2, int crn, int crm, int rt, int isread, bool is_16bit) diff --git a/target/arm/tcg/vfp_helper.c b/target/arm/tcg/vfp_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/vfp_helper.c +++ b/target/arm/tcg/vfp_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(check_hcr_el2_trap)(CPUARMState *env, uint32_t rt, uint32_t reg) g_assert_not_reached(); } - syndrome = ((EC_FPIDTRAP << ARM_EL_EC_SHIFT) - | ARM_EL_IL - | (1 << 24) | (0xe << 20) | (7 << 14) - | (reg << 10) | (rt << 5) | 1); + syndrome = syn_cp10_rt_trap(1, 0xe, 7, reg, rt, 1); raise_exception(env, EXCP_HYP_TRAP, syndrome, 2); } -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> One more step towards dropping the old #defines. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-17-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/helper.c b/target/arm/helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -XXX,XX +XXX,XX @@ static void arm_cpu_do_interrupt_aarch32_hyp(CPUState *cs) */ if (cs->exception_index == EXCP_PREFETCH_ABORT || (cs->exception_index == EXCP_DATA_ABORT && - !(env->exception.syndrome & ARM_EL_ISV)) || + !FIELD_EX32(env->exception.syndrome, SYNDROME, IL)) || syn_get_ec(env->exception.syndrome) == EC_UNCATEGORIZED) { - env->exception.syndrome &= ~ARM_EL_IL; + env->exception.syndrome = FIELD_DP32(env->exception.syndrome, SYNDROME, IL, 0); } } env->cp15.esr_el[2] = env->exception.syndrome; -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> This removes the last use of ARM_EL_EC_SHIFT. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-18-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 1 - target/arm/tcg/debug.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ typedef enum { } GCSInstructionType; #define ARM_EL_EC_LENGTH 6 -#define ARM_EL_EC_SHIFT 26 #define ARM_EL_IL_SHIFT 25 #define ARM_EL_ISV_SHIFT 24 #define ARM_EL_IL (1 << ARM_EL_IL_SHIFT) diff --git a/target/arm/tcg/debug.c b/target/arm/tcg/debug.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/debug.c +++ b/target/arm/tcg/debug.c @@ -XXX,XX +XXX,XX @@ raise_exception_debug(CPUARMState *env, uint32_t excp, uint32_t syndrome) * Similarly for watchpoint and breakpoint matches. */ assert(debug_el >= cur_el); - syndrome |= (debug_el == cur_el) << ARM_EL_EC_SHIFT; + syndrome |= (debug_el == cur_el) << R_SYNDROME_EC_SHIFT; raise_exception(env, excp, syndrome, debug_el); } -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Rather than open coding a bunch of shifts and masks we can use the syndrome definitions. While we are at it assert it really is a EC_DATAABORT. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-19-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/whpx/whpx-all.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/target/arm/whpx/whpx-all.c b/target/arm/whpx/whpx-all.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/whpx/whpx-all.c +++ b/target/arm/whpx/whpx-all.c @@ -XXX,XX +XXX,XX @@ static int whpx_handle_mmio(CPUState *cpu, WHV_MEMORY_ACCESS_CONTEXT *ctx) { uint64_t syndrome = ctx->Syndrome; - bool isv = syndrome & ARM_EL_ISV; - bool iswrite = (syndrome >> 6) & 1; - bool sse = (syndrome >> 21) & 1; - uint32_t sas = (syndrome >> 22) & 3; + bool isv = FIELD_EX32(syndrome, DABORT_ISS, ISV); + bool iswrite = FIELD_EX32(syndrome, DABORT_ISS, WNR); + bool sse = FIELD_EX32(syndrome, DABORT_ISS, SSE); + uint32_t sas = FIELD_EX32(syndrome, DABORT_ISS, SAS); uint32_t len = 1 << sas; - uint32_t srt = (syndrome >> 16) & 0x1f; - uint32_t cm = (syndrome >> 8) & 0x1; + uint32_t srt = FIELD_EX32(syndrome, DABORT_ISS, SRT); + uint32_t cm = FIELD_EX32(syndrome, DABORT_ISS, CM); uint64_t val = 0; + assert(syn_get_ec(syndrome) == EC_DATAABORT); assert(!cm); assert(isv); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Rather than open coding a bunch of shifts and masks we can use the syndrome definitions. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Tested-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-id: 20260422125250.1303100-20-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/hvf/hvf.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/hvf/hvf.c +++ b/target/arm/hvf/hvf.c @@ -XXX,XX +XXX,XX @@ static int hvf_handle_exception(CPUState *cpu, hv_vcpu_exit_exception_t *excp) break; } case EC_DATAABORT: { - bool isv = syndrome & ARM_EL_ISV; - bool iswrite = (syndrome >> 6) & 1; - bool s1ptw = (syndrome >> 7) & 1; - bool sse = (syndrome >> 21) & 1; - uint32_t sas = (syndrome >> 22) & 3; + bool isv = FIELD_EX32(syndrome, DABORT_ISS, ISV); + bool iswrite = FIELD_EX32(syndrome, DABORT_ISS, WNR); + bool s1ptw = FIELD_EX32(syndrome, DABORT_ISS, S1PTW); + bool sse = FIELD_EX32(syndrome, DABORT_ISS, SSE); + uint32_t sas = FIELD_EX32(syndrome, DABORT_ISS, SAS); uint32_t len = 1 << sas; - uint32_t srt = (syndrome >> 16) & 0x1f; - uint32_t cm = (syndrome >> 8) & 0x1; + uint32_t srt = FIELD_EX32(syndrome, DABORT_ISS, SRT); + uint32_t cm = FIELD_EX32(syndrome, DABORT_ISS, CM); uint64_t val = 0; uint64_t ipa = excp->physical_address; AddressSpace *as = cpu_get_address_space(cpu, ARMASIdx_NS); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> One more step to removing the old defines. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-21-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/tlb_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/arm/tcg/tlb_helper.c b/target/arm/tcg/tlb_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/tlb_helper.c +++ b/target/arm/tcg/tlb_helper.c @@ -XXX,XX +XXX,XX @@ static inline uint64_t merge_syn_data_abort(uint32_t template_syn, */ assert(!fi->stage2); syn = syn_data_abort_vncr(fi->ea, is_write, fsc); - } else if (!(template_syn & ARM_EL_ISV) || target_el != 2 + } else if (!FIELD_EX32(template_syn, DABORT_ISS, 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); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> These are only valid for data abort syndromes. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-22-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/tcg/tlb_helper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/arm/tcg/tlb_helper.c b/target/arm/tcg/tlb_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/tlb_helper.c +++ b/target/arm/tcg/tlb_helper.c @@ -XXX,XX +XXX,XX @@ static inline uint64_t merge_syn_data_abort(uint32_t template_syn, * ST64BV, or ST64BV0 insns report syndrome info even for stage-1 * faults and regardless of the target EL. */ - if (template_syn & ARM_EL_VNCR) { + if (FIELD_EX32(template_syn, DABORT_ISS, 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 @@ -XXX,XX +XXX,XX @@ void arm_deliver_fault(ARMCPU *cpu, vaddr addr, * because we masked that out in disas_set_insn_syndrome()) */ bool is_vncr = (access_type != MMU_INST_FETCH) && - (env->exception.syndrome & ARM_EL_VNCR); + FIELD_EX32(env->exception.syndrome, DABORT_ISS, VNCR); if (is_vncr) { /* FEAT_NV2 faults on accesses via VNCR_EL2 go to EL2 */ -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Now everything is defined with registerfields we can drop the old defines. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-23-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ typedef enum { GCS_IT_GCSPOPX = 9, } GCSInstructionType; -#define ARM_EL_EC_LENGTH 6 -#define ARM_EL_IL_SHIFT 25 -#define ARM_EL_ISV_SHIFT 24 -#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 FIELD_EX32(syn, SYNDROME, EC); -- 2.43.0
From: Alex Bennée <alex.bennee@linaro.org> Pass the register number (rd) to the wfit helper and report it in the syndrome ISS. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260422125250.1303100-24-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org> --- target/arm/syndrome.h | 11 ++++++++++- target/arm/tcg/helper-defs.h | 2 +- target/arm/tcg/op_helper.c | 7 ++++--- target/arm/tcg/translate-a64.c | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/syndrome.h +++ b/target/arm/syndrome.h @@ -XXX,XX +XXX,XX @@ FIELD(WFX_ISS, RN, 5, 5) FIELD(WFX_ISS, COND, 20, 4) FIELD(WFX_ISS, CV, 24, 1) -static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) +typedef enum { + WFI = 0b00, + WFE = 0b01, + WFIT = 0b10, + WFET = 0xb11 +} wfx_ti; + +static inline uint32_t syn_wfx(int cv, int cond, int rn, bool rv, wfx_ti ti, bool is_16bit) { uint32_t res = syn_set_ec(0, EC_WFX_TRAP); res = FIELD_DP32(res, SYNDROME, IL, !is_16bit); @@ -XXX,XX +XXX,XX @@ static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit) res = FIELD_DP32(res, WFX_ISS, CV, cv); res = FIELD_DP32(res, WFX_ISS, COND, cond); res = FIELD_DP32(res, WFX_ISS, TI, ti); + res = FIELD_DP32(res, WFX_ISS, RN, rn); + res = FIELD_DP32(res, WFX_ISS, RV, rv); return res; } diff --git a/target/arm/tcg/helper-defs.h b/target/arm/tcg/helper-defs.h index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/helper-defs.h +++ b/target/arm/tcg/helper-defs.h @@ -XXX,XX +XXX,XX @@ DEF_HELPER_2(exception_pc_alignment, noreturn, env, vaddr) DEF_HELPER_1(setend, void, env) DEF_HELPER_2(wfi, void, env, i32) DEF_HELPER_1(wfe, void, env) -DEF_HELPER_2(wfit, void, env, i64) +DEF_HELPER_2(wfit, void, env, i32) DEF_HELPER_1(yield, void, env) DEF_HELPER_1(pre_hvc, void, env) DEF_HELPER_2(pre_smc, void, env, i32) diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/op_helper.c +++ b/target/arm/tcg/op_helper.c @@ -XXX,XX +XXX,XX @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) env->regs[15] -= insn_len; } - raise_exception(env, excp, syn_wfx(1, 0xe, 0, insn_len == 2), + raise_exception(env, excp, syn_wfx(1, 0xe, 0, false, WFI, insn_len == 2), target_el); } @@ -XXX,XX +XXX,XX @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len) #endif } -void HELPER(wfit)(CPUARMState *env, uint64_t timeout) +void HELPER(wfit)(CPUARMState *env, uint32_t rd) { #ifdef CONFIG_USER_ONLY /* @@ -XXX,XX +XXX,XX @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) int target_el = check_wfx_trap(env, false, &excp); /* The WFIT should time out when CNTVCT_EL0 >= the specified value. */ uint64_t cntval = gt_get_countervalue(env); + uint64_t timeout = env->xregs[rd]; /* * We want the value that we would get if we read CNTVCT_EL0 from * the current exception level, so the direct_access offset, not @@ -XXX,XX +XXX,XX @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout) if (target_el) { env->pc -= 4; - raise_exception(env, excp, syn_wfx(1, 0xe, 2, false), target_el); + raise_exception(env, excp, syn_wfx(1, 0xe, rd, true, WFIT, false), target_el); } if (uadd64_overflow(timeout, offset, &nexttick)) { diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c index XXXXXXX..XXXXXXX 100644 --- a/target/arm/tcg/translate-a64.c +++ b/target/arm/tcg/translate-a64.c @@ -XXX,XX +XXX,XX @@ static bool trans_WFIT(DisasContext *s, arg_WFIT *a) } gen_a64_update_pc(s, 4); - gen_helper_wfit(tcg_env, cpu_reg(s, a->rd)); + gen_helper_wfit(tcg_env, tcg_constant_i32(a->rd)); /* Go back to the main loop to check for interrupts */ s->base.is_jmp = DISAS_EXIT; return true; -- 2.43.0