diff mbox series

[v7,10/10] KVM: arm64: selftests: Test for setting ID register from usersapce

Message ID 20230801152007.337272-11-jingzhangos@google.com (mailing list archive)
State New, archived
Headers show
Series Enable writable for idregs DFR0,PFR0, MMFR{0,1,2,3} | expand

Commit Message

Jing Zhang Aug. 1, 2023, 3:20 p.m. UTC
Add tests to verify setting ID registers from userapce is handled
correctly by KVM. Also add a test case to use ioctl
KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS to get writable masks.

Signed-off-by: Jing Zhang <jingzhangos@google.com>
---
 tools/arch/arm64/include/uapi/asm/kvm.h       |  25 +++
 tools/include/uapi/linux/kvm.h                |   2 +
 tools/testing/selftests/kvm/Makefile          |   1 +
 .../selftests/kvm/aarch64/set_id_regs.c       | 191 ++++++++++++++++++
 4 files changed, 219 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/aarch64/set_id_regs.c

Comments

Oliver Upton Aug. 2, 2023, 5:27 p.m. UTC | #1
Hi Jing,

On Tue, Aug 01, 2023 at 08:20:06AM -0700, Jing Zhang wrote:
> Add tests to verify setting ID registers from userapce is handled
> correctly by KVM. Also add a test case to use ioctl
> KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS to get writable masks.
> 
> Signed-off-by: Jing Zhang <jingzhangos@google.com>
> ---
>  tools/arch/arm64/include/uapi/asm/kvm.h       |  25 +++
>  tools/include/uapi/linux/kvm.h                |   2 +

Why is this diff needed? I thought we wound up using the latest headers
from the kernel.

>  tools/testing/selftests/kvm/Makefile          |   1 +

Need to add your file to .gitignore too.

>  .../selftests/kvm/aarch64/set_id_regs.c       | 191 ++++++++++++++++++
>  4 files changed, 219 insertions(+)
>  create mode 100644 tools/testing/selftests/kvm/aarch64/set_id_regs.c

[...]

> diff --git a/tools/testing/selftests/kvm/aarch64/set_id_regs.c b/tools/testing/selftests/kvm/aarch64/set_id_regs.c
> new file mode 100644
> index 000000000000..9c8f439ac7b3
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/aarch64/set_id_regs.c
> @@ -0,0 +1,191 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * set_id_regs - Test for setting ID register from usersapce.
> + *
> + * Copyright (c) 2023 Google LLC.
> + *
> + *
> + * Test that KVM supports setting ID registers from userspace and handles the
> + * feature set correctly.
> + */
> +
> +#include <stdint.h>
> +#include "kvm_util.h"
> +#include "processor.h"
> +#include "test_util.h"
> +#include <linux/bitfield.h>
> +
> +#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffsl(_mask) - 1))
> +#define field_prep(_mask, _val) (((_val) << (ffsl(_mask) - 1)) & (_mask))
> +

Shadowing the naming of the kernel's own FIELD_{GET,PREP}() is a bit
awkward. I'm guessing that you're working around @_mask not being a
compile-time constant?

> +struct reg_feature {
> +	uint64_t reg;
> +	uint64_t ftr_mask;
> +};
> +
> +static void guest_code(void)
> +{
> +	for (;;)
> +		GUEST_SYNC(0);
> +}

The test should check that the written values are visible both from the
guest as well as userspace.

> +static struct reg_feature lower_safe_reg_ftrs[] = {
> +	{ KVM_ARM64_SYS_REG(SYS_ID_AA64DFR0_EL1), ARM64_FEATURE_MASK(ID_AA64DFR0_WRPS) },
> +	{ KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1), ARM64_FEATURE_MASK(ID_AA64PFR0_EL3) },
> +	{ KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR0_FGT) },
> +	{ KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR1_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR1_PAN) },
> +	{ KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR2_FWB) },
> +};

My preference would be to organize the field descriptors by register
rather than the policy. This matches what the kernel does in cpufeature.c
quite closely and allows us to easily reason about which fields are/aren't
tested.

> +static void test_user_set_lower_safe(struct kvm_vcpu *vcpu)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(lower_safe_reg_ftrs); i++) {
> +		struct reg_feature *reg_ftr = lower_safe_reg_ftrs + i;
> +		uint64_t val, new_val, ftr;
> +
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +		ftr = field_get(reg_ftr->ftr_mask, val);
> +
> +		/* Set a safe value for the feature */
> +		if (ftr > 0)
> +			ftr--;
> +
> +		val &= ~reg_ftr->ftr_mask;
> +		val |= field_prep(reg_ftr->ftr_mask, ftr);
> +
> +		vcpu_set_reg(vcpu, reg_ftr->reg, val);
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &new_val);
> +		ASSERT_EQ(new_val, val);
> +	}
> +}
> +
> +static void test_user_set_fail(struct kvm_vcpu *vcpu)
> +{
> +	int i, r;
> +
> +	for (i = 0; i < ARRAY_SIZE(lower_safe_reg_ftrs); i++) {
> +		struct reg_feature *reg_ftr = lower_safe_reg_ftrs + i;
> +		uint64_t val, old_val, ftr;
> +
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +		ftr = field_get(reg_ftr->ftr_mask, val);
> +
> +		/* Set a invalid value (too big) for the feature */
> +		if (ftr >= GENMASK_ULL(ARM64_FEATURE_FIELD_BITS - 1, 0))
> +			continue;

This assumes that the fields in the register are unsigned, but there are
several which are not.

> +		ftr++;
> +
> +		old_val = val;
> +		val &= ~reg_ftr->ftr_mask;
> +		val |= field_prep(reg_ftr->ftr_mask, ftr);
> +
> +		r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
> +		TEST_ASSERT(r < 0 && errno == EINVAL,
> +			    "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
> +
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +		ASSERT_EQ(val, old_val);
> +	}
> +}
> +
> +static struct reg_feature exact_reg_ftrs[] = {
> +	/* Items will be added when there is appropriate field of type
> +	 * FTR_EXACT enabled writing from userspace later.
> +	 */
> +};
> +
> +static void test_user_set_exact(struct kvm_vcpu *vcpu)
> +{
> +	int i, r;
> +
> +	for (i = 0; i < ARRAY_SIZE(exact_reg_ftrs); i++) {
> +		struct reg_feature *reg_ftr = exact_reg_ftrs + i;
> +		uint64_t val, old_val, ftr;
> +
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +		ftr = field_get(reg_ftr->ftr_mask, val);
> +		old_val = val;
> +
> +		/* Exact match */
> +		vcpu_set_reg(vcpu, reg_ftr->reg, val);
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +		ASSERT_EQ(val, old_val);
> +
> +		/* Smaller value */
> +		if (ftr > 0) {
> +			ftr--;
> +			val &= ~reg_ftr->ftr_mask;
> +			val |= field_prep(reg_ftr->ftr_mask, ftr);
> +			r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
> +			TEST_ASSERT(r < 0 && errno == EINVAL,
> +				    "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
> +			vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +			ASSERT_EQ(val, old_val);
> +			ftr++;
> +		}
> +
> +		/* Bigger value */
> +		ftr++;
> +		val &= ~reg_ftr->ftr_mask;
> +		val |= field_prep(reg_ftr->ftr_mask, ftr);
> +		r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
> +		TEST_ASSERT(r < 0 && errno == EINVAL,
> +			    "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
> +		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> +		ASSERT_EQ(val, old_val);
> +	}
> +}

Don't add dead code, this can be added when we actually test FTR_EXACT
fields. Are there not any in the registers exposed by this series?

> +static uint32_t writable_regs[] = {
> +	SYS_ID_DFR0_EL1,
> +	SYS_ID_AA64DFR0_EL1,
> +	SYS_ID_AA64PFR0_EL1,
> +	SYS_ID_AA64MMFR0_EL1,
> +	SYS_ID_AA64MMFR1_EL1,
> +	SYS_ID_AA64MMFR2_EL1,
> +};
> +
> +void test_user_get_writable_masks(struct kvm_vm *vm)
> +{
> +	struct feature_id_writable_masks masks;
> +
> +	vm_ioctl(vm, KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS, &masks);
> +
> +	for (int i = 0; i < ARRAY_SIZE(writable_regs); i++) {
> +		uint32_t reg = writable_regs[i];
> +		int idx = ARM64_FEATURE_ID_SPACE_IDX(sys_reg_Op0(reg),
> +				sys_reg_Op1(reg), sys_reg_CRn(reg),
> +				sys_reg_CRm(reg), sys_reg_Op2(reg));
> +
> +		ASSERT_EQ(masks.mask[idx], GENMASK_ULL(63, 0));
> +	}
> +}

The more robust test would be to check that every field this test knows
is writable is actually advertised as such in the ioctl. So you could
fetch this array at the start of the entire test and pass it through to
the routines that do granular checks against the fields of every
register.

It'd also be good to see basic sanity tests on the ioctl (i.e. call
fails if ::rsvd is nonzero), since KVM has screwed that up on several
occasions in past.

> +int main(void)
> +{
> +	struct kvm_vcpu *vcpu;
> +	struct kvm_vm *vm;
> +
> +	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
> +
> +	ksft_print_header();
> +	ksft_set_plan(4);
> +
> +	test_user_get_writable_masks(vm);
> +	ksft_test_result_pass("test_user_get_writable_masks\n");
> +
> +	test_user_set_exact(vcpu);
> +	ksft_test_result_pass("test_user_set_exact\n");
> +
> +	test_user_set_fail(vcpu);
> +	ksft_test_result_pass("test_user_set_fail\n");
> +
> +	test_user_set_lower_safe(vcpu);
> +	ksft_test_result_pass("test_user_set_lower_safe\n");
> +
> +	kvm_vm_free(vm);
> +
> +	ksft_finished();
> +}
> -- 
> 2.41.0.585.gd2178a4bd4-goog
>
Jing Zhang Aug. 3, 2023, 11:42 p.m. UTC | #2
Hi Oliver,

On Wed, Aug 2, 2023 at 10:28 AM Oliver Upton <oliver.upton@linux.dev> wrote:
>
> Hi Jing,
>
> On Tue, Aug 01, 2023 at 08:20:06AM -0700, Jing Zhang wrote:
> > Add tests to verify setting ID registers from userapce is handled
> > correctly by KVM. Also add a test case to use ioctl
> > KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS to get writable masks.
> >
> > Signed-off-by: Jing Zhang <jingzhangos@google.com>
> > ---
> >  tools/arch/arm64/include/uapi/asm/kvm.h       |  25 +++
> >  tools/include/uapi/linux/kvm.h                |   2 +
>
> Why is this diff needed? I thought we wound up using the latest headers
> from the kernel.

You are right. These changes are not needed. Will drop them.

>
> >  tools/testing/selftests/kvm/Makefile          |   1 +
>
> Need to add your file to .gitignore too.

Will do.

>
> >  .../selftests/kvm/aarch64/set_id_regs.c       | 191 ++++++++++++++++++
> >  4 files changed, 219 insertions(+)
> >  create mode 100644 tools/testing/selftests/kvm/aarch64/set_id_regs.c
>
> [...]
>
> > diff --git a/tools/testing/selftests/kvm/aarch64/set_id_regs.c b/tools/testing/selftests/kvm/aarch64/set_id_regs.c
> > new file mode 100644
> > index 000000000000..9c8f439ac7b3
> > --- /dev/null
> > +++ b/tools/testing/selftests/kvm/aarch64/set_id_regs.c
> > @@ -0,0 +1,191 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * set_id_regs - Test for setting ID register from usersapce.
> > + *
> > + * Copyright (c) 2023 Google LLC.
> > + *
> > + *
> > + * Test that KVM supports setting ID registers from userspace and handles the
> > + * feature set correctly.
> > + */
> > +
> > +#include <stdint.h>
> > +#include "kvm_util.h"
> > +#include "processor.h"
> > +#include "test_util.h"
> > +#include <linux/bitfield.h>
> > +
> > +#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffsl(_mask) - 1))
> > +#define field_prep(_mask, _val) (((_val) << (ffsl(_mask) - 1)) & (_mask))
> > +
>
> Shadowing the naming of the kernel's own FIELD_{GET,PREP}() is a bit
> awkward. I'm guessing that you're working around @_mask not being a
> compile-time constant?

Yes, they are used to work around @_mask not being a compile-time constant.
As we discussed offline, I'll port automatic system registers
definition generation in selftests and use those definition instead.

>
> > +struct reg_feature {
> > +     uint64_t reg;
> > +     uint64_t ftr_mask;
> > +};
> > +
> > +static void guest_code(void)
> > +{
> > +     for (;;)
> > +             GUEST_SYNC(0);
> > +}
>
> The test should check that the written values are visible both from the
> guest as well as userspace.

Sure. Will add checks in guest too.

>
> > +static struct reg_feature lower_safe_reg_ftrs[] = {
> > +     { KVM_ARM64_SYS_REG(SYS_ID_AA64DFR0_EL1), ARM64_FEATURE_MASK(ID_AA64DFR0_WRPS) },
> > +     { KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1), ARM64_FEATURE_MASK(ID_AA64PFR0_EL3) },
> > +     { KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR0_FGT) },
> > +     { KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR1_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR1_PAN) },
> > +     { KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR2_FWB) },
> > +};
>
> My preference would be to organize the field descriptors by register
> rather than the policy. This matches what the kernel does in cpufeature.c
> quite closely and allows us to easily reason about which fields are/aren't
> tested.

Sure. Will do.

>
> > +static void test_user_set_lower_safe(struct kvm_vcpu *vcpu)
> > +{
> > +     int i;
> > +
> > +     for (i = 0; i < ARRAY_SIZE(lower_safe_reg_ftrs); i++) {
> > +             struct reg_feature *reg_ftr = lower_safe_reg_ftrs + i;
> > +             uint64_t val, new_val, ftr;
> > +
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +             ftr = field_get(reg_ftr->ftr_mask, val);
> > +
> > +             /* Set a safe value for the feature */
> > +             if (ftr > 0)
> > +                     ftr--;
> > +
> > +             val &= ~reg_ftr->ftr_mask;
> > +             val |= field_prep(reg_ftr->ftr_mask, ftr);
> > +
> > +             vcpu_set_reg(vcpu, reg_ftr->reg, val);
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &new_val);
> > +             ASSERT_EQ(new_val, val);
> > +     }
> > +}
> > +
> > +static void test_user_set_fail(struct kvm_vcpu *vcpu)
> > +{
> > +     int i, r;
> > +
> > +     for (i = 0; i < ARRAY_SIZE(lower_safe_reg_ftrs); i++) {
> > +             struct reg_feature *reg_ftr = lower_safe_reg_ftrs + i;
> > +             uint64_t val, old_val, ftr;
> > +
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +             ftr = field_get(reg_ftr->ftr_mask, val);
> > +
> > +             /* Set a invalid value (too big) for the feature */
> > +             if (ftr >= GENMASK_ULL(ARM64_FEATURE_FIELD_BITS - 1, 0))
> > +                     continue;
>
> This assumes that the fields in the register are unsigned, but there are
> several which are not.

This is based on the reg/fields which are defined previously. Those
are carefully chosen without unsigned ones.

>
> > +             ftr++;
> > +
> > +             old_val = val;
> > +             val &= ~reg_ftr->ftr_mask;
> > +             val |= field_prep(reg_ftr->ftr_mask, ftr);
> > +
> > +             r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
> > +             TEST_ASSERT(r < 0 && errno == EINVAL,
> > +                         "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
> > +
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +             ASSERT_EQ(val, old_val);
> > +     }
> > +}
> > +
> > +static struct reg_feature exact_reg_ftrs[] = {
> > +     /* Items will be added when there is appropriate field of type
> > +      * FTR_EXACT enabled writing from userspace later.
> > +      */
> > +};
> > +
> > +static void test_user_set_exact(struct kvm_vcpu *vcpu)
> > +{
> > +     int i, r;
> > +
> > +     for (i = 0; i < ARRAY_SIZE(exact_reg_ftrs); i++) {
> > +             struct reg_feature *reg_ftr = exact_reg_ftrs + i;
> > +             uint64_t val, old_val, ftr;
> > +
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +             ftr = field_get(reg_ftr->ftr_mask, val);
> > +             old_val = val;
> > +
> > +             /* Exact match */
> > +             vcpu_set_reg(vcpu, reg_ftr->reg, val);
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +             ASSERT_EQ(val, old_val);
> > +
> > +             /* Smaller value */
> > +             if (ftr > 0) {
> > +                     ftr--;
> > +                     val &= ~reg_ftr->ftr_mask;
> > +                     val |= field_prep(reg_ftr->ftr_mask, ftr);
> > +                     r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
> > +                     TEST_ASSERT(r < 0 && errno == EINVAL,
> > +                                 "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
> > +                     vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +                     ASSERT_EQ(val, old_val);
> > +                     ftr++;
> > +             }
> > +
> > +             /* Bigger value */
> > +             ftr++;
> > +             val &= ~reg_ftr->ftr_mask;
> > +             val |= field_prep(reg_ftr->ftr_mask, ftr);
> > +             r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
> > +             TEST_ASSERT(r < 0 && errno == EINVAL,
> > +                         "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
> > +             vcpu_get_reg(vcpu, reg_ftr->reg, &val);
> > +             ASSERT_EQ(val, old_val);
> > +     }
> > +}
>
> Don't add dead code, this can be added when we actually test FTR_EXACT
> fields. Are there not any in the registers exposed by this series?

Nope, there is no in the registers exposed by this series.
Anyway, I'll drop this code in this series.

>
> > +static uint32_t writable_regs[] = {
> > +     SYS_ID_DFR0_EL1,
> > +     SYS_ID_AA64DFR0_EL1,
> > +     SYS_ID_AA64PFR0_EL1,
> > +     SYS_ID_AA64MMFR0_EL1,
> > +     SYS_ID_AA64MMFR1_EL1,
> > +     SYS_ID_AA64MMFR2_EL1,
> > +};
> > +
> > +void test_user_get_writable_masks(struct kvm_vm *vm)
> > +{
> > +     struct feature_id_writable_masks masks;
> > +
> > +     vm_ioctl(vm, KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS, &masks);
> > +
> > +     for (int i = 0; i < ARRAY_SIZE(writable_regs); i++) {
> > +             uint32_t reg = writable_regs[i];
> > +             int idx = ARM64_FEATURE_ID_SPACE_IDX(sys_reg_Op0(reg),
> > +                             sys_reg_Op1(reg), sys_reg_CRn(reg),
> > +                             sys_reg_CRm(reg), sys_reg_Op2(reg));
> > +
> > +             ASSERT_EQ(masks.mask[idx], GENMASK_ULL(63, 0));
> > +     }
> > +}
>
> The more robust test would be to check that every field this test knows
> is writable is actually advertised as such in the ioctl. So you could
> fetch this array at the start of the entire test and pass it through to
> the routines that do granular checks against the fields of every
> register.

Makes sense. Will do.

>
> It'd also be good to see basic sanity tests on the ioctl (i.e. call
> fails if ::rsvd is nonzero), since KVM has screwed that up on several
> occasions in past.

Sure. Will add some basic sanity tests on the ioctl.

>
> > +int main(void)
> > +{
> > +     struct kvm_vcpu *vcpu;
> > +     struct kvm_vm *vm;
> > +
> > +     vm = vm_create_with_one_vcpu(&vcpu, guest_code);
> > +
> > +     ksft_print_header();
> > +     ksft_set_plan(4);
> > +
> > +     test_user_get_writable_masks(vm);
> > +     ksft_test_result_pass("test_user_get_writable_masks\n");
> > +
> > +     test_user_set_exact(vcpu);
> > +     ksft_test_result_pass("test_user_set_exact\n");
> > +
> > +     test_user_set_fail(vcpu);
> > +     ksft_test_result_pass("test_user_set_fail\n");
> > +
> > +     test_user_set_lower_safe(vcpu);
> > +     ksft_test_result_pass("test_user_set_lower_safe\n");
> > +
> > +     kvm_vm_free(vm);
> > +
> > +     ksft_finished();
> > +}
> > --
> > 2.41.0.585.gd2178a4bd4-goog
> >
>
> --
> Thanks,
> Oliver

Thanks,
Jing
diff mbox series

Patch

diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index f7ddd73a8c0f..2970c0d792ee 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -505,6 +505,31 @@  struct kvm_smccc_filter {
 #define KVM_HYPERCALL_EXIT_SMC		(1U << 0)
 #define KVM_HYPERCALL_EXIT_16BIT	(1U << 1)
 
+/* Get feature ID registers userspace writable mask. */
+/*
+ * From DDI0487J.a, D19.2.66 ("ID_AA64MMFR2_EL1, AArch64 Memory Model
+ * Feature Register 2"):
+ *
+ * "The Feature ID space is defined as the System register space in
+ * AArch64 with op0==3, op1=={0, 1, 3}, CRn==0, CRm=={0-7},
+ * op2=={0-7}."
+ *
+ * This covers all R/O registers that indicate anything useful feature
+ * wise, including the ID registers.
+ */
+#define ARM64_FEATURE_ID_SPACE_IDX(op0, op1, crn, crm, op2)		\
+	({								\
+		__u64 __op1 = (op1) & 3;				\
+		__op1 -= (__op1 == 3);					\
+		(__op1 << 6 | ((crm) & 7) << 3 | (op2));		\
+	})
+
+#define ARM64_FEATURE_ID_SPACE_SIZE	(3 * 8 * 8)
+
+struct feature_id_writable_masks {
+	__u64 mask[ARM64_FEATURE_ID_SPACE_SIZE];
+};
+
 #endif
 
 #endif /* __ARM_KVM_H__ */
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index f089ab290978..9abe69cf4001 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1555,6 +1555,8 @@  struct kvm_s390_ucas_mapping {
 #define KVM_ARM_MTE_COPY_TAGS	  _IOR(KVMIO,  0xb4, struct kvm_arm_copy_mte_tags)
 /* Available with KVM_CAP_COUNTER_OFFSET */
 #define KVM_ARM_SET_COUNTER_OFFSET _IOW(KVMIO,  0xb5, struct kvm_arm_counter_offset)
+#define KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS \
+			_IOR(KVMIO,  0xb6, struct kvm_arm_feature_id_masks)
 
 /* ioctl for vm fd */
 #define KVM_CREATE_DEVICE	  _IOWR(KVMIO,  0xe0, struct kvm_create_device)
diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index c692cc86e7da..87ceadc1292a 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -144,6 +144,7 @@  TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list
 TEST_GEN_PROGS_aarch64 += aarch64/hypercalls
 TEST_GEN_PROGS_aarch64 += aarch64/page_fault_test
 TEST_GEN_PROGS_aarch64 += aarch64/psci_test
+TEST_GEN_PROGS_aarch64 += aarch64/set_id_regs
 TEST_GEN_PROGS_aarch64 += aarch64/smccc_filter
 TEST_GEN_PROGS_aarch64 += aarch64/vcpu_width_config
 TEST_GEN_PROGS_aarch64 += aarch64/vgic_init
diff --git a/tools/testing/selftests/kvm/aarch64/set_id_regs.c b/tools/testing/selftests/kvm/aarch64/set_id_regs.c
new file mode 100644
index 000000000000..9c8f439ac7b3
--- /dev/null
+++ b/tools/testing/selftests/kvm/aarch64/set_id_regs.c
@@ -0,0 +1,191 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * set_id_regs - Test for setting ID register from usersapce.
+ *
+ * Copyright (c) 2023 Google LLC.
+ *
+ *
+ * Test that KVM supports setting ID registers from userspace and handles the
+ * feature set correctly.
+ */
+
+#include <stdint.h>
+#include "kvm_util.h"
+#include "processor.h"
+#include "test_util.h"
+#include <linux/bitfield.h>
+
+#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffsl(_mask) - 1))
+#define field_prep(_mask, _val) (((_val) << (ffsl(_mask) - 1)) & (_mask))
+
+struct reg_feature {
+	uint64_t reg;
+	uint64_t ftr_mask;
+};
+
+static void guest_code(void)
+{
+	for (;;)
+		GUEST_SYNC(0);
+}
+
+static struct reg_feature lower_safe_reg_ftrs[] = {
+	{ KVM_ARM64_SYS_REG(SYS_ID_AA64DFR0_EL1), ARM64_FEATURE_MASK(ID_AA64DFR0_WRPS) },
+	{ KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1), ARM64_FEATURE_MASK(ID_AA64PFR0_EL3) },
+	{ KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR0_FGT) },
+	{ KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR1_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR1_PAN) },
+	{ KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1), ARM64_FEATURE_MASK(ID_AA64MMFR2_FWB) },
+};
+
+static void test_user_set_lower_safe(struct kvm_vcpu *vcpu)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(lower_safe_reg_ftrs); i++) {
+		struct reg_feature *reg_ftr = lower_safe_reg_ftrs + i;
+		uint64_t val, new_val, ftr;
+
+		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+		ftr = field_get(reg_ftr->ftr_mask, val);
+
+		/* Set a safe value for the feature */
+		if (ftr > 0)
+			ftr--;
+
+		val &= ~reg_ftr->ftr_mask;
+		val |= field_prep(reg_ftr->ftr_mask, ftr);
+
+		vcpu_set_reg(vcpu, reg_ftr->reg, val);
+		vcpu_get_reg(vcpu, reg_ftr->reg, &new_val);
+		ASSERT_EQ(new_val, val);
+	}
+}
+
+static void test_user_set_fail(struct kvm_vcpu *vcpu)
+{
+	int i, r;
+
+	for (i = 0; i < ARRAY_SIZE(lower_safe_reg_ftrs); i++) {
+		struct reg_feature *reg_ftr = lower_safe_reg_ftrs + i;
+		uint64_t val, old_val, ftr;
+
+		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+		ftr = field_get(reg_ftr->ftr_mask, val);
+
+		/* Set a invalid value (too big) for the feature */
+		if (ftr >= GENMASK_ULL(ARM64_FEATURE_FIELD_BITS - 1, 0))
+			continue;
+		ftr++;
+
+		old_val = val;
+		val &= ~reg_ftr->ftr_mask;
+		val |= field_prep(reg_ftr->ftr_mask, ftr);
+
+		r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
+		TEST_ASSERT(r < 0 && errno == EINVAL,
+			    "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
+
+		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+		ASSERT_EQ(val, old_val);
+	}
+}
+
+static struct reg_feature exact_reg_ftrs[] = {
+	/* Items will be added when there is appropriate field of type
+	 * FTR_EXACT enabled writing from userspace later.
+	 */
+};
+
+static void test_user_set_exact(struct kvm_vcpu *vcpu)
+{
+	int i, r;
+
+	for (i = 0; i < ARRAY_SIZE(exact_reg_ftrs); i++) {
+		struct reg_feature *reg_ftr = exact_reg_ftrs + i;
+		uint64_t val, old_val, ftr;
+
+		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+		ftr = field_get(reg_ftr->ftr_mask, val);
+		old_val = val;
+
+		/* Exact match */
+		vcpu_set_reg(vcpu, reg_ftr->reg, val);
+		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+		ASSERT_EQ(val, old_val);
+
+		/* Smaller value */
+		if (ftr > 0) {
+			ftr--;
+			val &= ~reg_ftr->ftr_mask;
+			val |= field_prep(reg_ftr->ftr_mask, ftr);
+			r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
+			TEST_ASSERT(r < 0 && errno == EINVAL,
+				    "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
+			vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+			ASSERT_EQ(val, old_val);
+			ftr++;
+		}
+
+		/* Bigger value */
+		ftr++;
+		val &= ~reg_ftr->ftr_mask;
+		val |= field_prep(reg_ftr->ftr_mask, ftr);
+		r = __vcpu_set_reg(vcpu, reg_ftr->reg, val);
+		TEST_ASSERT(r < 0 && errno == EINVAL,
+			    "Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
+		vcpu_get_reg(vcpu, reg_ftr->reg, &val);
+		ASSERT_EQ(val, old_val);
+	}
+}
+
+static uint32_t writable_regs[] = {
+	SYS_ID_DFR0_EL1,
+	SYS_ID_AA64DFR0_EL1,
+	SYS_ID_AA64PFR0_EL1,
+	SYS_ID_AA64MMFR0_EL1,
+	SYS_ID_AA64MMFR1_EL1,
+	SYS_ID_AA64MMFR2_EL1,
+};
+
+void test_user_get_writable_masks(struct kvm_vm *vm)
+{
+	struct feature_id_writable_masks masks;
+
+	vm_ioctl(vm, KVM_ARM_GET_FEATURE_ID_WRITABLE_MASKS, &masks);
+
+	for (int i = 0; i < ARRAY_SIZE(writable_regs); i++) {
+		uint32_t reg = writable_regs[i];
+		int idx = ARM64_FEATURE_ID_SPACE_IDX(sys_reg_Op0(reg),
+				sys_reg_Op1(reg), sys_reg_CRn(reg),
+				sys_reg_CRm(reg), sys_reg_Op2(reg));
+
+		ASSERT_EQ(masks.mask[idx], GENMASK_ULL(63, 0));
+	}
+}
+
+int main(void)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+
+	ksft_print_header();
+	ksft_set_plan(4);
+
+	test_user_get_writable_masks(vm);
+	ksft_test_result_pass("test_user_get_writable_masks\n");
+
+	test_user_set_exact(vcpu);
+	ksft_test_result_pass("test_user_set_exact\n");
+
+	test_user_set_fail(vcpu);
+	ksft_test_result_pass("test_user_set_fail\n");
+
+	test_user_set_lower_safe(vcpu);
+	ksft_test_result_pass("test_user_set_lower_safe\n");
+
+	kvm_vm_free(vm);
+
+	ksft_finished();
+}