[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CA+EHjTwMy1crsZLqfy8_Y56NFPJZ7vGyN-egc433GhxB_n=7aA@mail.gmail.com>
Date: Tue, 13 Jan 2026 14:06:53 +0000
From: Fuad Tabba <tabba@...gle.com>
To: Mark Brown <broonie@...nel.org>
Cc: Marc Zyngier <maz@...nel.org>, Joey Gouly <joey.gouly@....com>,
Catalin Marinas <catalin.marinas@....com>, Suzuki K Poulose <suzuki.poulose@....com>,
Will Deacon <will@...nel.org>, Paolo Bonzini <pbonzini@...hat.com>, Jonathan Corbet <corbet@....net>,
Shuah Khan <shuah@...nel.org>, Oliver Upton <oupton@...nel.org>, Dave Martin <Dave.Martin@....com>,
Mark Rutland <mark.rutland@....com>, Ben Horgan <ben.horgan@....com>,
linux-arm-kernel@...ts.infradead.org, kvmarm@...ts.linux.dev,
linux-kernel@...r.kernel.org, kvm@...r.kernel.org, linux-doc@...r.kernel.org,
linux-kselftest@...r.kernel.org, Peter Maydell <peter.maydell@...aro.org>,
Eric Auger <eric.auger@...hat.com>
Subject: Re: [PATCH v9 22/30] KVM: arm64: Expose SME specific state to userspace
On Tue, 23 Dec 2025 at 01:23, Mark Brown <broonie@...nel.org> wrote:
>
> SME introduces two new registers, the ZA matrix register and the ZT0 LUT
> register. Both of these registers are only accessible when PSTATE.ZA is
> set and ZT0 is only present if SME2 is enabled for the guest. Provide
> support for configuring these from VMMs.
>
> The ZA matrix is a single SVL*SVL register which is available when
> PSTATE.ZA is set. We follow the pattern established by the architecture
> itself and expose this to userspace as a series of horizontal SVE vectors
> with the streaming mode vector length, using the format already established
> for the SVE vectors themselves.
>
> ZT0 is a single register with a refreshingly fixed size 512 bit register
> which is like ZA accessible only when PSTATE.ZA is set. Add support for it
> to the userspace API, as with ZA we allow the register to be read or written
> regardless of the state of PSTATE.ZA in order to simplify userspace usage.
> The value will be reset to 0 whenever PSTATE.ZA changes from 0 to 1,
> userspace can read stale values but these are not observable by the guest
> without manipulation of PSTATE.ZA by userspace.
>
> While there is currently only one ZT register the naming as ZT0 and the
> instruction encoding clearly leave room for future extensions adding more
> ZT registers. This encoding can readily support such an extension if one is
> introduced.
>
> Signed-off-by: Mark Brown <broonie@...nel.org>
> ---
> arch/arm64/include/uapi/asm/kvm.h | 17 ++++++
> arch/arm64/kvm/guest.c | 114 +++++++++++++++++++++++++++++++++++++-
> 2 files changed, 129 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 498a49a61487..9a19cc58d227 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -357,6 +357,23 @@ struct kvm_arm_counter_offset {
> /* SME registers */
> #define KVM_REG_ARM64_SME (0x17 << KVM_REG_ARM_COPROC_SHIFT)
>
> +#define KVM_ARM64_SME_VQ_MIN __SVE_VQ_MIN
> +#define KVM_ARM64_SME_VQ_MAX __SVE_VQ_MAX
> +
> +/* ZA and ZTn occupy blocks at the following offsets within this range: */
> +#define KVM_REG_ARM64_SME_ZA_BASE 0
> +#define KVM_REG_ARM64_SME_ZT_BASE 0x600
> +
> +#define KVM_ARM64_SME_MAX_ZAHREG (__SVE_VQ_BYTES * KVM_ARM64_SME_VQ_MAX)
> +
> +#define KVM_REG_ARM64_SME_ZAHREG(n, i) \
> + (KVM_REG_ARM64 | KVM_REG_ARM64_SME | KVM_REG_ARM64_SME_ZA_BASE | \
> + KVM_REG_SIZE_U2048 | \
> + (((n) & (KVM_ARM64_SME_MAX_ZAHREG - 1)) << 5) | \
> + ((i) & (KVM_ARM64_SVE_MAX_SLICES - 1)))
> +
> +#define KVM_REG_ARM64_SME_ZTREG_SIZE (512 / 8)
> +
> /* Vector lengths pseudo-register: */
> #define KVM_REG_ARM64_SME_VLS (KVM_REG_ARM64 | KVM_REG_ARM64_SME | \
> KVM_REG_SIZE_U512 | 0xfffe)
> diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
> index 90dcacb35f01..d4e30eb57a9c 100644
> --- a/arch/arm64/kvm/guest.c
> +++ b/arch/arm64/kvm/guest.c
> @@ -594,23 +594,133 @@ static int set_sme_vls(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
> return set_vec_vls(ARM64_VEC_SME, vcpu, reg);
> }
>
> +/*
> + * Validate SVE register ID and get sanitised bounds for user/kernel SVE
> + * register copy
> + */
> +static int sme_reg_to_region(struct vec_state_reg_region *region,
> + struct kvm_vcpu *vcpu,
> + const struct kvm_one_reg *reg)
> +{
> + /* reg ID ranges for ZA.H[n] registers */
> + unsigned int vq = vcpu_sme_max_vq(vcpu) - 1;
> + const u64 za_h_max = vq * __SVE_VQ_BYTES;
> + const u64 zah_id_min = KVM_REG_ARM64_SME_ZAHREG(0, 0);
> + const u64 zah_id_max = KVM_REG_ARM64_SME_ZAHREG(za_h_max - 1,
> + SVE_NUM_SLICES - 1);
> + unsigned int reg_num;
> +
> + unsigned int reqoffset, reqlen; /* User-requested offset and length */
> + unsigned int maxlen; /* Maximum permitted length */
> +
> + size_t sme_state_size;
> +
> + reg_num = (reg->id & SVE_REG_ID_MASK) >> SVE_REG_ID_SHIFT;
You use array_index_nospec() below for koffset, but it might be worth
using it for intermediate values, such as this one.
> +
> + if (reg->id >= zah_id_min && reg->id <= zah_id_max) {
> + if (!vcpu_has_sme(vcpu) || (reg->id & SVE_REG_SLICE_MASK) > 0)
> + return -ENOENT;
> +
> + /* ZA is exposed as SVE vectors ZA.H[n] */
> + reqoffset = ZA_SIG_ZAV_OFFSET(vq, reg_num) -
> + ZA_SIG_REGS_OFFSET;
> + reqlen = KVM_SVE_ZREG_SIZE;
> + maxlen = SVE_SIG_ZREG_SIZE(vq);
> + } else if (reg->id == KVM_REG_ARM64_SME_ZT_BASE) {
> + /* ZA is exposed as SVE vectors ZA.H[n] */
> + if (!kvm_has_feat(vcpu->kvm, ID_AA64PFR1_EL1, SME, SME2) ||
> + (reg->id & SVE_REG_SLICE_MASK) > 0 ||
> + reg_num > 0)
> + return -ENOENT;
> +
> + /* ZT0 is stored after ZA */
> + reqlen = KVM_REG_ARM64_SME_ZTREG_SIZE;
> + maxlen = KVM_REG_ARM64_SME_ZTREG_SIZE;
> + } else {
> + return -EINVAL;
> + }
> +
> + sme_state_size = vcpu_sme_state_size(vcpu);
Is it worth caching this value and storing it in arch, since the state
size doesn't change after finalization?
> + if (WARN_ON(!sme_state_size))
> + return -EINVAL;
> +
> + region->koffset = array_index_nospec(reqoffset, sme_state_size);
> + region->klen = min(maxlen, reqlen);
> + region->upad = reqlen - region->klen;
> +
> + return 0;
> +}
> +
> +/*
> + * ZA is exposed as an array of horizontal vectors with the same
> + * format as SVE, mirroring the architecture's LDR ZA[Wv, offs], [Xn]
> + * instruction.
> + */
> +
> static int get_sme_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
> {
> + int ret;
> + struct vec_state_reg_region region;
> + char __user *uptr = (char __user *)reg->addr;
> +
> /* Handle the KVM_REG_ARM64_SME_VLS pseudo-reg as a special case: */
> if (reg->id == KVM_REG_ARM64_SME_VLS)
> return get_sme_vls(vcpu, reg);
>
> - return -EINVAL;
> + /* Try to interpret reg ID as an architectural SME register... */
> + ret = sme_reg_to_region(®ion, vcpu, reg);
> + if (ret)
> + return ret;
> +
> + if (!kvm_arm_vcpu_vec_finalized(vcpu))
> + return -EPERM;
> +
> + /*
> + * None of the SME specific registers are accessible unless
> + * PSTATE.ZA is set.
> + */
> + if (!vcpu_za_enabled(vcpu))
> + return -EINVAL;
I think this should be something other than -EINVAL, since the issue
isn't that the register isn't valid, but that it's inaccessible, as
the comment says. -EACCESS or something?
> +
> + if (copy_from_user(vcpu->arch.sme_state + region.koffset, uptr,
> + region.klen))
> + return -EFAULT;
This should be copy_to_user()
> +
> + return 0;
> }
>
> static int set_sme_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
> {
> + int ret;
> + struct vec_state_reg_region region;
> + char __user *uptr = (char __user *)reg->addr;
> +
> /* Handle the KVM_REG_ARM64_SME_VLS pseudo-reg as a special case: */
> if (reg->id == KVM_REG_ARM64_SME_VLS)
> return set_sme_vls(vcpu, reg);
>
> - return -EINVAL;
> + /* Try to interpret reg ID as an architectural SME register... */
> + ret = sme_reg_to_region(®ion, vcpu, reg);
> + if (ret)
> + return ret;
> +
> + if (!kvm_arm_vcpu_vec_finalized(vcpu))
> + return -EPERM;
> +
> + /*
> + * None of the SME specific registers are accessible unless
> + * PSTATE.ZA is set.
> + */
> + if (!vcpu_za_enabled(vcpu))
> + return -EINVAL;
Same as get_sme_reg().
Cheers,
/fuad
> +
> + if (copy_from_user(vcpu->arch.sme_state + region.koffset, uptr,
> + region.klen))
> + return -EFAULT;
> +
> + return 0;
> }
> +
> int kvm_arch_vcpu_ioctl_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
> {
> return -EINVAL;
>
> --
> 2.47.3
>
Powered by blists - more mailing lists