* along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/bits.h>
 #include <linux/errno.h>
 #include <linux/err.h>
+#include <linux/nospec.h>
+#include <linux/kernel.h>
 #include <linux/kvm_host.h>
 #include <linux/module.h>
 #include <linux/stddef.h>
 #include <kvm/arm_psci.h>
 #include <asm/cputype.h>
 #include <linux/uaccess.h>
+#include <asm/fpsimd.h>
 #include <asm/kvm.h>
 #include <asm/kvm_emulate.h>
 #include <asm/kvm_coproc.h>
+#include <asm/kvm_host.h>
+#include <asm/sigcontext.h>
 
 #include "trace.h"
 
        return err;
 }
 
+#define SVE_REG_SLICE_SHIFT    0
+#define SVE_REG_SLICE_BITS     5
+#define SVE_REG_ID_SHIFT       (SVE_REG_SLICE_SHIFT + SVE_REG_SLICE_BITS)
+#define SVE_REG_ID_BITS                5
+
+#define SVE_REG_SLICE_MASK                                     \
+       GENMASK(SVE_REG_SLICE_SHIFT + SVE_REG_SLICE_BITS - 1,   \
+               SVE_REG_SLICE_SHIFT)
+#define SVE_REG_ID_MASK                                                        \
+       GENMASK(SVE_REG_ID_SHIFT + SVE_REG_ID_BITS - 1, SVE_REG_ID_SHIFT)
+
+#define SVE_NUM_SLICES (1 << SVE_REG_SLICE_BITS)
+
+#define KVM_SVE_ZREG_SIZE KVM_REG_SIZE(KVM_REG_ARM64_SVE_ZREG(0, 0))
+#define KVM_SVE_PREG_SIZE KVM_REG_SIZE(KVM_REG_ARM64_SVE_PREG(0, 0))
+
+/* Bounds of a single SVE register slice within vcpu->arch.sve_state */
+struct sve_state_reg_region {
+       unsigned int koffset;   /* offset into sve_state in kernel memory */
+       unsigned int klen;      /* length in kernel memory */
+       unsigned int upad;      /* extra trailing padding in user memory */
+};
+
+/* Get sanitised bounds for user/kernel SVE register copy */
+static int sve_reg_to_region(struct sve_state_reg_region *region,
+                            struct kvm_vcpu *vcpu,
+                            const struct kvm_one_reg *reg)
+{
+       /* reg ID ranges for Z- registers */
+       const u64 zreg_id_min = KVM_REG_ARM64_SVE_ZREG(0, 0);
+       const u64 zreg_id_max = KVM_REG_ARM64_SVE_ZREG(SVE_NUM_ZREGS - 1,
+                                                      SVE_NUM_SLICES - 1);
+
+       /* reg ID ranges for P- registers and FFR (which are contiguous) */
+       const u64 preg_id_min = KVM_REG_ARM64_SVE_PREG(0, 0);
+       const u64 preg_id_max = KVM_REG_ARM64_SVE_FFR(SVE_NUM_SLICES - 1);
+
+       unsigned int vq;
+       unsigned int reg_num;
+
+       unsigned int reqoffset, reqlen; /* User-requested offset and length */
+       unsigned int maxlen; /* Maxmimum permitted length */
+
+       size_t sve_state_size;
+
+       /* Only the first slice ever exists, for now: */
+       if ((reg->id & SVE_REG_SLICE_MASK) != 0)
+               return -ENOENT;
+
+       vq = sve_vq_from_vl(vcpu->arch.sve_max_vl);
+
+       reg_num = (reg->id & SVE_REG_ID_MASK) >> SVE_REG_ID_SHIFT;
+
+       if (reg->id >= zreg_id_min && reg->id <= zreg_id_max) {
+               reqoffset = SVE_SIG_ZREG_OFFSET(vq, reg_num) -
+                               SVE_SIG_REGS_OFFSET;
+               reqlen = KVM_SVE_ZREG_SIZE;
+               maxlen = SVE_SIG_ZREG_SIZE(vq);
+       } else if (reg->id >= preg_id_min && reg->id <= preg_id_max) {
+               reqoffset = SVE_SIG_PREG_OFFSET(vq, reg_num) -
+                               SVE_SIG_REGS_OFFSET;
+               reqlen = KVM_SVE_PREG_SIZE;
+               maxlen = SVE_SIG_PREG_SIZE(vq);
+       } else {
+               return -ENOENT;
+       }
+
+       sve_state_size = vcpu_sve_state_size(vcpu);
+       if (!sve_state_size)
+               return -EINVAL;
+
+       region->koffset = array_index_nospec(reqoffset, sve_state_size);
+       region->klen = min(maxlen, reqlen);
+       region->upad = reqlen - region->klen;
+
+       return 0;
+}
+
+static int get_sve_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
+{
+       struct sve_state_reg_region region;
+       char __user *uptr = (char __user *)reg->addr;
+
+       if (!vcpu_has_sve(vcpu) || sve_reg_to_region(®ion, vcpu, reg))
+               return -ENOENT;
+
+       if (copy_to_user(uptr, vcpu->arch.sve_state + region.koffset,
+                        region.klen) ||
+           clear_user(uptr + region.klen, region.upad))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int set_sve_reg(struct kvm_vcpu *vcpu, const struct kvm_one_reg *reg)
+{
+       struct sve_state_reg_region region;
+       const char __user *uptr = (const char __user *)reg->addr;
+
+       if (!vcpu_has_sve(vcpu) || sve_reg_to_region(®ion, vcpu, reg))
+               return -ENOENT;
+
+       if (copy_from_user(vcpu->arch.sve_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;
        if ((reg->id & ~KVM_REG_SIZE_MASK) >> 32 != KVM_REG_ARM64 >> 32)
                return -EINVAL;
 
-       /* Register group 16 means we want a core register. */
-       if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
-               return get_core_reg(vcpu, reg);
-
-       if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_FW)
-               return kvm_arm_get_fw_reg(vcpu, reg);
+       switch (reg->id & KVM_REG_ARM_COPROC_MASK) {
+       case KVM_REG_ARM_CORE:  return get_core_reg(vcpu, reg);
+       case KVM_REG_ARM_FW:    return kvm_arm_get_fw_reg(vcpu, reg);
+       case KVM_REG_ARM64_SVE: return get_sve_reg(vcpu, reg);
+       default: break; /* fall through */
+       }
 
        if (is_timer_reg(reg->id))
                return get_timer_reg(vcpu, reg);
        if ((reg->id & ~KVM_REG_SIZE_MASK) >> 32 != KVM_REG_ARM64 >> 32)
                return -EINVAL;
 
-       /* Register group 16 means we set a core register. */
-       if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_CORE)
-               return set_core_reg(vcpu, reg);
-
-       if ((reg->id & KVM_REG_ARM_COPROC_MASK) == KVM_REG_ARM_FW)
-               return kvm_arm_set_fw_reg(vcpu, reg);
+       switch (reg->id & KVM_REG_ARM_COPROC_MASK) {
+       case KVM_REG_ARM_CORE:  return set_core_reg(vcpu, reg);
+       case KVM_REG_ARM_FW:    return kvm_arm_set_fw_reg(vcpu, reg);
+       case KVM_REG_ARM64_SVE: return set_sve_reg(vcpu, reg);
+       default: break; /* fall through */
+       }
 
        if (is_timer_reg(reg->id))
                return set_timer_reg(vcpu, reg);