(u32)r->CRn, (u32)r->CRm, (u32)r->Op2);
        u64 val = raz ? 0 : read_sanitised_ftr_reg(id);
 
-       if (id == SYS_ID_AA64PFR0_EL1) {
-               if (val & (0xfUL << ID_AA64PFR0_SVE_SHIFT))
-                       kvm_debug("SVE unsupported for guests, suppressing\n");
-
+       if (id == SYS_ID_AA64PFR0_EL1 && !vcpu_has_sve(vcpu)) {
                val &= ~(0xfUL << ID_AA64PFR0_SVE_SHIFT);
        } else if (id == SYS_ID_AA64ISAR1_EL1) {
                const u64 ptrauth_mask = (0xfUL << ID_AA64ISAR1_APA_SHIFT) |
 static int reg_to_user(void __user *uaddr, const u64 *val, u64 id);
 static u64 sys_reg_to_index(const struct sys_reg_desc *reg);
 
+/* Visibility overrides for SVE-specific control registers */
+static unsigned int sve_visibility(const struct kvm_vcpu *vcpu,
+                                  const struct sys_reg_desc *rd)
+{
+       if (vcpu_has_sve(vcpu))
+               return 0;
+
+       return REG_HIDDEN_USER | REG_HIDDEN_GUEST;
+}
+
+/* Visibility overrides for SVE-specific ID registers */
+static unsigned int sve_id_visibility(const struct kvm_vcpu *vcpu,
+                                     const struct sys_reg_desc *rd)
+{
+       if (vcpu_has_sve(vcpu))
+               return 0;
+
+       return REG_HIDDEN_USER;
+}
+
+/* Generate the emulated ID_AA64ZFR0_EL1 value exposed to the guest */
+static u64 guest_id_aa64zfr0_el1(const struct kvm_vcpu *vcpu)
+{
+       if (!vcpu_has_sve(vcpu))
+               return 0;
+
+       return read_sanitised_ftr_reg(SYS_ID_AA64ZFR0_EL1);
+}
+
+static bool access_id_aa64zfr0_el1(struct kvm_vcpu *vcpu,
+                                  struct sys_reg_params *p,
+                                  const struct sys_reg_desc *rd)
+{
+       if (p->is_write)
+               return write_to_read_only(vcpu, p, rd);
+
+       p->regval = guest_id_aa64zfr0_el1(vcpu);
+       return true;
+}
+
+static int get_id_aa64zfr0_el1(struct kvm_vcpu *vcpu,
+               const struct sys_reg_desc *rd,
+               const struct kvm_one_reg *reg, void __user *uaddr)
+{
+       u64 val;
+
+       if (!vcpu_has_sve(vcpu))
+               return -ENOENT;
+
+       val = guest_id_aa64zfr0_el1(vcpu);
+       return reg_to_user(uaddr, &val, reg->id);
+}
+
+static int set_id_aa64zfr0_el1(struct kvm_vcpu *vcpu,
+               const struct sys_reg_desc *rd,
+               const struct kvm_one_reg *reg, void __user *uaddr)
+{
+       const u64 id = sys_reg_to_index(rd);
+       int err;
+       u64 val;
+
+       if (!vcpu_has_sve(vcpu))
+               return -ENOENT;
+
+       err = reg_from_user(&val, uaddr, id);
+       if (err)
+               return err;
+
+       /* This is what we mean by invariant: you can't change it. */
+       if (val != guest_id_aa64zfr0_el1(vcpu))
+               return -EINVAL;
+
+       return 0;
+}
+
 /*
  * cpufeature ID register user accessors
  *
        ID_SANITISED(ID_AA64PFR1_EL1),
        ID_UNALLOCATED(4,2),
        ID_UNALLOCATED(4,3),
-       ID_UNALLOCATED(4,4),
+       { SYS_DESC(SYS_ID_AA64ZFR0_EL1), access_id_aa64zfr0_el1, .get_user = get_id_aa64zfr0_el1, .set_user = set_id_aa64zfr0_el1, .visibility = sve_id_visibility },
        ID_UNALLOCATED(4,5),
        ID_UNALLOCATED(4,6),
        ID_UNALLOCATED(4,7),
 
        { SYS_DESC(SYS_SCTLR_EL1), access_vm_reg, reset_val, SCTLR_EL1, 0x00C50078 },
        { SYS_DESC(SYS_CPACR_EL1), NULL, reset_val, CPACR_EL1, 0 },
+       { SYS_DESC(SYS_ZCR_EL1), NULL, reset_val, ZCR_EL1, 0, .visibility = sve_visibility },
        { SYS_DESC(SYS_TTBR0_EL1), access_vm_reg, reset_unknown, TTBR0_EL1 },
        { SYS_DESC(SYS_TTBR1_EL1), access_vm_reg, reset_unknown, TTBR1_EL1 },
        { SYS_DESC(SYS_TCR_EL1), access_vm_reg, reset_val, TCR_EL1, 0 },