Hi Khushit,
On 7/16/26 11:38 PM, Khushit Shah wrote:
> From: Shaju Abraham <[email protected]>
>
> Introduce a small named-CPU-model layer on top of the field-backed
> property infrastructure. Each model is a flat table of (name, value)
> overrides and a parent pointer. At instance-init, the parent chain is
> walked root-first and every level's properties are applied via QOM,
> so a child entry overrides its ancestors for the same name.
>
> For example, the graviton3-v1 named model hierarchy is:
> non-zero-defaults-v1 Known non-zero default ID regs field values
> arm-v8_4-a-v1 ARMv8.4-A architectural mandate
> neoverse-v1-v1 Neoverse V1
> graviton3-v1 AWS Graviton3
>
> non-zero-defaults-v1:
> Field that should be populated with some non zero value by default.
following which specification? This is not clear to me. Is that layer
really meaningful? Shouldn't we start with the next one?
>
> arm-v8_4-a-v1:
> Only features mandated by the ARM ARM.
>
> neoverse-v1-v1:
> Reference manual derived feature values for the reference core.
> Values differ from TRM values based on what was exposed to the guest
> with -cpu host.
>
> graviton3-v1:
> SoC integration choices (crypto pin, cache hints, PAuth alg).
>
> The model values are figured out from ID register dump visible to
> QEMU with -cpu host on Graviton3 machine.
>
> Model realization:
> 1. Zero out ID regs array.
> 2. Add all properties to the CPU Object
> 3. arm_realize_model_chain: Walk the parent chain from root first and
> apply all the properties.
>
> Co-authored-by: Shaju Abraham <[email protected]>
> Signed-off-by: Khushit Shah <[email protected]>
> ---
> hw/arm/virt.c | 5 +
> target/arm/arm-cpu-models.c | 336 ++++++++++++++++++++++++++++++++++++
> target/arm/arm-cpu-models.h | 35 ++++
> target/arm/meson.build | 7 +-
> 4 files changed, 381 insertions(+), 2 deletions(-)
> create mode 100644 target/arm/arm-cpu-models.c
> create mode 100644 target/arm/arm-cpu-models.h
>
> diff --git a/hw/arm/virt.c b/hw/arm/virt.c
> index fe19030886..34e56dc1f1 100644
> --- a/hw/arm/virt.c
> +++ b/hw/arm/virt.c
> @@ -3812,6 +3812,11 @@ static GPtrArray *virt_get_valid_cpu_types(const
> MachineState *ms)
> g_ptr_array_add(vct, g_strdup(ARM_CPU_TYPE_NAME("host")));
> }
> }
> + if (kvm_enabled() && target_aarch64()) {
> + g_ptr_array_add(vct, g_strdup(ARM_CPU_TYPE_NAME("arm-v8_4-a-v1")));
> + g_ptr_array_add(vct, g_strdup(ARM_CPU_TYPE_NAME("neoverse-v1-v1")));
> + g_ptr_array_add(vct, g_strdup(ARM_CPU_TYPE_NAME("graviton3-v1")));
> + }
> g_ptr_array_add(vct, g_strdup(ARM_CPU_TYPE_NAME("max"))); return vct;
> diff --git a/target/arm/arm-cpu-models.c
> b/target/arm/arm-cpu-models.c new file mode 100644 index
> 0000000000..747e0df010 --- /dev/null +++ b/target/arm/arm-cpu-models.c
> @@ -0,0 +1,336 @@ +/* + * ARM named CPU model definitions. + * + *
> Each model is defined as an array of MODEL_PROP macro, listing only +
> * the properties that DIFFER from the parent model. + * + * At
> realisation the parent chain is walked root-first and + * every
> level's props are applied via QOM, so the leaf's values + * naturally
> override its ancestors. + * + * Hierarchy: + * + *
> non-zero-defaults-v1 Known non-zero default ID reg field values + *
> arm-v8_4-a-v1 ARMv8.4-A architectural mandate + * neoverse-v1-v1
> Neoverse V1 (TRM 102649) + * graviton3-v1 AWS Graviton3 + * + *
> SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/error-report.h"
> +#include "system/kvm.h"
> +#include "cpu.h"
> +#include "internals.h"
> +#include "kvm_arm.h"
> +#include "arm-cpu-models.h"
> +#include "cpu-idregs.h"
> +
> +#define MODEL_PROP_FIELD_STR str
> +#define MODEL_PROP_FIELD_BOOL b
> +#define MODEL_PROP_FIELD_NUM num
> +
> +#define MODEL_PROP(_name, _type, _value) \
> + { .name = (_name), .type = ARM_MODEL_PROP_##_type, \
> + .u.MODEL_PROP_FIELD_##_type = (_value) }
> +
> +#define MODEL_PROP_END { .name = NULL }
Regarding settings somehow we end up with something that is quite
similar to TCG CPU
definitions like in
aarch64_neoverse_n1_initfn(). To me this is in general more readable
because we use name fields instead of putting the full reg value.
This does not prevent us from adding comments along settings to make
this more readable and explicit.
> +
> +/*
> + * These fields should be populated with some non-zero default values.
> + */
> +static const ArmModelPropValue non_zero_defaults_v1_props[] = {
> + /* ID_AA64PFR0_EL1 */
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_EL0", NUM, 0b0001),
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_EL1", NUM, 0b0001),
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_FP", NUM, 0b1111),
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_AdvSIMD", NUM, 0b1111),
> +
> + /* ID_AA64PFR1_EL1 */
> + MODEL_PROP("SYSREG_ID_AA64PFR1_EL1_MTE_frac", NUM, 0b1111),
so I still believe we want a wrapper that transparently either sets a
prop if the field happens to be writable or simply performs checks
against the host if the prop is not available.
> +
> + /* ID_AA64MMFR0_EL1 */
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran64", NUM, 0b1111),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran4", NUM, 0b1111),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran16_2", NUM, 0b0001),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran64_2", NUM, 0b0001),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran4_2", NUM, 0b0001),
> +
> + /* ID_AA64DFR0_EL1 */
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_DebugVer", NUM, 0x6),
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_DoubleLock", NUM, 0b1111),
> +
> + /* CTR_EL0 */
> + MODEL_PROP("SYSREG_CTR_EL0_L1Ip", NUM, 0b10),
> + MODEL_PROP("SYSREG_CTR_EL0_RES1_31", NUM, 1),
> +
> + /* ID_AA64MMFR4_EL1 */
> + MODEL_PROP("SYSREG_ID_AA64MMFR4_EL1_E2H0", NUM, 0b1111),
> +
> + MODEL_PROP_END,
> +};
To ease the review I would suggest to put a dump of all id regs of a
leaf model in the commit description. It is less error prone that
understanding the various overrides.
I would suggest to have separate patches for each model too.
So you said a leaf model shall have explicit values for each field,
including non writable ones. The code does not check that. How do we
make sure we are not missing settings?
> +
> +static const ArmModelPropValue armv8_4_a_v1_props[] = {
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_CRC32", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_Atomic", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_HPDS", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_LO", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_DPB", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_RAS", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64PFR1_EL1_RAS_frac", NUM, 0),
> +
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_PAN", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_UAO", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_CnP", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_IESB", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_DIT", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_DebugVer", NUM, 9),
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_PMUVer", NUM, 5),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_TS", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_LRCPC", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_AT", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_IDS", NUM, 1),
> + /* KVM zeroes out these field when MTE is not enabled. */
> + MODEL_PROP("SYSREG_ID_AA64PFR1_EL1_MTE_frac", NUM, 0),
> + MODEL_PROP_END,
> +};
> +
> +static const ArmModelPropValue neoverse_v1_v1_props[] = {
> + MODEL_PROP("SYSREG_MIDR_EL1_Implementer", NUM, 0x41),
> + MODEL_PROP("SYSREG_MIDR_EL1_Variant", NUM, 0x1),
> + MODEL_PROP("SYSREG_MIDR_EL1_Architecture", NUM, 0xF),
> + MODEL_PROP("SYSREG_MIDR_EL1_PartNum", NUM, 0xD40),
> + MODEL_PROP("SYSREG_MIDR_EL1_Revision", NUM, 0x1),
> +
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_BRPs", NUM, 0x5),
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_WRPs", NUM, 0x3),
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_CTX_CMPs", NUM, 0x1),
> + MODEL_PROP("SYSREG_ID_AA64DFR0_EL1_DoubleLock", NUM, 0xF),
> +
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_RDM", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_DP", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_FHM", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_DPB", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_JSCVT", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_FCMA", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_BF16", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_DGH", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_I8MM", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_FP", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_AdvSIMD", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_RAS", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64PFR1_EL1_RAS_frac", NUM, 0),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_CSV2", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64PFR1_EL1_CSV2_frac", NUM, 0),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_CSV3", NUM, 1),
> +
> + MODEL_PROP("SYSREG_ID_AA64PFR1_EL1_SSBS", NUM, 2),
> +
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_PARange", NUM, 5),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_ASIDBits", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_BigEnd", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_SNSMem", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran4", NUM, 0),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran16", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran64", NUM, 0),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran4_2", NUM, 0),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran16_2", NUM, 0),
> + MODEL_PROP("SYSREG_ID_AA64MMFR0_EL1_TGran64_2", NUM, 0),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_HAFDBS", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_VMIDBits", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_VH", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_HPDS", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_XNX", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR1_EL1_SpecSEI", NUM, 0),
> +
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_FWB", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_BBM", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64MMFR2_EL1_EVT", NUM, 2),
> +
> + MODEL_PROP("SYSREG_ID_AA64MMFR4_EL1_E2H0", NUM, 0),
> +
> + MODEL_PROP("SYSREG_CTR_EL0_IminLine", NUM, 4),
> + MODEL_PROP("SYSREG_CTR_EL0_L1Ip", NUM, 3),
> + MODEL_PROP("SYSREG_CTR_EL0_DminLine", NUM, 4),
> + MODEL_PROP("SYSREG_CTR_EL0_ERG", NUM, 4),
> + MODEL_PROP("SYSREG_CTR_EL0_CWG", NUM, 4),
> +
> + MODEL_PROP("SYSREG_DCZID_EL0_BS", NUM, 0x4),
> +
> + /* pauth */
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_APA", NUM, 3),
> + MODEL_PROP("SYSREG_ID_AA64ISAR1_EL1_GPA", NUM, 1),
> +
> + /* sve */
> + MODEL_PROP("SYSREG_ID_AA64PFR0_EL1_SVE", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ZFR0_EL1_BF16", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ZFR0_EL1_I8MM", NUM, 1),
> + MODEL_PROP("sve128", BOOL, true),
> +
> + MODEL_PROP_END,
> +};
> +
> +static const ArmModelPropValue graviton3_v1_props[] = {
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_AES", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_SHA1", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_SHA2", NUM, 2),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_SHA3", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_SM3", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_SM4", NUM, 1),
> + MODEL_PROP("SYSREG_ID_AA64ISAR0_EL1_RNDR", NUM, 1),
> +
> + MODEL_PROP("SYSREG_CTR_EL0_IDC", NUM, 1),
> + MODEL_PROP("SYSREG_CTR_EL0_DIC", NUM, 1),
> +
> + MODEL_PROP("SYSREG_REVIDR_EL1_VAL", NUM, 1),
> +
> + MODEL_PROP_END,
> +};
> +
> +static const ArmNamedCpuModel arm_cpu_models[] = {
> + {
> + .name = "non-zero-defaults-v1",
> + .parent = NULL,
> + .props = non_zero_defaults_v1_props,
> + },
> + {
> + .name = "arm-v8_4-a-v1",
> + .parent = "non-zero-defaults-v1",
> + .props = armv8_4_a_v1_props,
> + },
> + {
> + .name = "neoverse-v1-v1",
> + .parent = "arm-v8_4-a-v1",
> + .props = neoverse_v1_v1_props,
> + },
> + {
> + .name = "graviton3-v1",
> + .parent = "neoverse-v1-v1",
> + .props = graviton3_v1_props,
> + },
> +};
> +
> +static ARMCPUInfo arm_named_cpu_infos[ARRAY_SIZE(arm_cpu_models)];
> +const ArmNamedCpuModel *arm_find_model(const char *name)
> +{
> + size_t i;
> + for (i = 0; i < ARRAY_SIZE(arm_cpu_models); i++) {
> + if (g_str_equal(arm_cpu_models[i].name, name)) {
> + return &arm_cpu_models[i];
> + }
> + }
> + return NULL;
> +}
> +
> +static void arm_apply_model_props(Object *obj, const ArmModelPropValue
> *props,
> + Error **errp)
> +{
> + const ArmModelPropValue *pv;
> + ERRP_GUARD();
> +
> + for (pv = props; pv->name; pv++) {
> + switch (pv->type) {
> + case ARM_MODEL_PROP_BOOL:
> + object_property_set_bool(obj, pv->name, pv->u.b, errp);
> + break;
> + case ARM_MODEL_PROP_NUM:
> + object_property_set_uint(obj, pv->name, pv->u.num, errp);
> + break;
> + default:
> + g_assert_not_reached();
> + }
> + if (*errp) {
> + error_prepend(errp, "property '%s': ", pv->name);
> + return;
> + }
> + }
> +}
> +
> +static void arm_realize_model_chain(Object *obj, const ArmNamedCpuModel
> *model,
> + Error **errp)
> +{
> + const ArmNamedCpuModel *cur, *parent;
> + const ArmNamedCpuModel *chain[ARRAY_SIZE(arm_cpu_models)];
> + size_t depth = 0;
> + for (cur = model; cur; ) {
> + if (depth >= ARRAY_SIZE(chain)) {
> + error_setg(errp, "model '%s': parent chain too deep "
> + "(possible cycle)", model->name);
> + return;
> + }
> + chain[depth++] = cur;
> +
> + if (!cur->parent) {
> + break;
> + }
> + parent = arm_find_model(cur->parent);
> + if (!parent) {
> + error_setg(errp, "model '%s': unknown parent '%s'",
> + cur->name, cur->parent);
> + return;
> + }
> + cur = parent;
> + }
> +
> + while (depth--) {
> + arm_apply_model_props(obj, chain[depth]->props, errp);
> + if (*errp) {
> + return;
> + }
> + }
> +}
> +
> +static void arm_named_cpu_initfn(Object *obj)
> +{
> + ARMCPU *cpu = ARM_CPU(obj);
> + ARMCPUClass *acc = ARM_CPU_GET_CLASS(obj);
> + const ArmNamedCpuModel *model = arm_find_model(acc->info->name);
> + if (!model) {
> + error_report("'%s' CPU model entry not found)",
> + acc->info->name);
> + return;
> + }
> +
> + if (!kvm_enabled()) {
> + error_report("'%s' CPU model requires KVM (-accel kvm)",
> + acc->info->name);
> + return;
> + }
> +
> + kvm_arm_set_cpu_features_from_host(cpu);
> + if (!arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) {
> + return;
> + }
> +
> + /* 1. Reset ID regs to 0 */
> + memset(cpu->isar.idregs, 0, sizeof(cpu->isar.idregs));
> +
> + /* 2. Add sveNNNN and SYSREG_ properties */
> + aarch64_add_sve_properties(obj);
> + aarch64_add_idregs_properties(obj);
> +
> + /* 3. Realize the model chain. */
> + arm_realize_model_chain(obj, model, &error_abort);
> +}
> +
> +void arm_register_named_cpu_models(void)
> +{
> + size_t i;
> + for (i = 0; i < ARRAY_SIZE(arm_cpu_models); i++) {
> + arm_named_cpu_infos[i].name = arm_cpu_models[i].name;
> + arm_named_cpu_infos[i].initfn = arm_named_cpu_initfn;
> + arm_cpu_register(&arm_named_cpu_infos[i]);
> + }
> +}
> +
> +type_init(arm_register_named_cpu_models)
> diff --git a/target/arm/arm-cpu-models.h b/target/arm/arm-cpu-models.h
> new file mode 100644
> index 0000000000..ac4e510499
> --- /dev/null
> +++ b/target/arm/arm-cpu-models.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * ARM named CPU model definitions - public API.
> + */
> +#ifndef ARM_CPU_MODELS_H
> +#define ARM_CPU_MODELS_H
> +
> +#include "qapi/error.h"
> +#include "qom/object.h"
> +
> +typedef enum ArmModelPropType {
> + ARM_MODEL_PROP_BOOL,
> + ARM_MODEL_PROP_NUM,
> +} ArmModelPropType;
> +
> +typedef struct ArmModelPropValue {
> + const char *name;
> + ArmModelPropType type;
> + union {
> + bool b;
> + uint64_t num;
> + } u;
> +} ArmModelPropValue;
> +
> +typedef struct ArmNamedCpuModel {
> + const char *name;
> + const char *parent;
> + const ArmModelPropValue *props;
> +} ArmNamedCpuModel;
> +
> +void arm_register_named_cpu_models(void);
> +
> +const ArmNamedCpuModel *arm_find_model(const char *name);
> +
> +#endif /* ARM_CPU_MODELS_H */
> diff --git a/target/arm/meson.build b/target/arm/meson.build
> index 64d1ec63ab..72eed1bcc6 100644
> --- a/target/arm/meson.build
> +++ b/target/arm/meson.build
> @@ -10,7 +10,7 @@ arm_user_ss.add(files('gdbstub.c'))
>
> arm_ss.add(when: 'TARGET_AARCH64', if_true: files(
> 'cpu64.c',
> - 'gdbstub64.c'
> + 'gdbstub64.c',
> ))
>
> arm_common_ss.add(files(
> @@ -21,7 +21,10 @@ arm_common_system_ss.add(files(
> 'arm-qmp-cmds.c',
> 'cpu-idregs.c',
> ))
> -arm_system_ss.add(when: 'CONFIG_KVM', if_true: files('hyp_gdbstub.c',
> 'kvm.c'))
> +arm_system_ss.add(when: 'TARGET_AARCH64', if_true: files('arm-cpu-models.c'))
> +arm_system_ss.add(when: 'CONFIG_KVM',
> + if_true: files('hyp_gdbstub.c',
> + 'kvm.c'))
> arm_system_ss.add(when: 'CONFIG_HVF', if_true: files('hyp_gdbstub.c'))
>
> arm_user_ss.add(files('cpu.c'))
Thanks
Eric