From: Darkhan Mukashov <darkhan@xxxxxxxxxx> In order to read/write arbitrary number of registers, one has to call KVM_(GET|SET)_ONE_REG ioctls multiple times. New KVM APIs KVM_(GET|SET)_MANY_REGS make it possible to bulk read/write vCPU registers at one ioctl call. These ioctls can be very useful when vCPU state serialization/deserialization is required (e.g. live update of kvm, live migration of guests), hence all registers have to be saved/restored. KVM_(GET|SET)_MANY_REGS will help avoid performance overhead associated with syscall (ioctl in our case) handling. Tests conducted on AWS Graviton2 Processors (64-bit ARM Neoverse cores) show that average save/restore time of all vCPU registers can be optimized ~3.5 times per vCPU with new ioctls. Test results can be found in Table 1. +---------+-------------+---------------+ | | kvm_one_reg | kvm_many_regs | +---------+-------------+---------------+ | get all | 123 usec | 33 usec | +---------+-------------+---------------+ | set all | 120 usec | 36 usec | +---------+-------------+---------------+ Table 1. Test results KVM_(GET|SET)_MANY_REGS accept one argument struct kvm_many_regs { /* * number of regs to be got/set */ __u64 n; /* * the same struct used by KVM_(GET|SET)_ONE_REG */ struct kvm_one_reg reg[0]; } New ioctls allow to get/set values of multiple registers implemented in a vcpu. Registers to get/set are indicated by the 'id' field of each struct kvm_one_reg in the passed array. On success, KVM_GET_MANY_REGS writes values of indicated registers to memory locations pointed by the 'addr' field of each kvm_one_reg, KVM_SET_MANY_REGS changes values of registers to values located at memory locations pointed by the 'addr' field of each kvm_one_reg. KVM_(GET|SET)_MANY_REGS rely on KVM_(GET|SET)_ONE_REG, which are not implemented in x86. Therefore, new ioctls support all architectures except x86. Signed-off-by: Darkhan Mukashov <darkhan@xxxxxxxxxx> --- Documentation/virt/kvm/api.rst | 74 ++++++++++++++++++++++++++++++++++ include/uapi/linux/kvm.h | 11 +++++ virt/kvm/kvm_main.c | 42 +++++++++++++++++++ 3 files changed, 127 insertions(+) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index 6d6135c15729..9a229f7e182e 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -4808,6 +4808,80 @@ If a vCPU is in running state while this ioctl is invoked, the vCPU may experience inconsistent filtering behavior on MSR accesses. +4.127 KVM_GET_MANY_REGS +----------------------- + +:Capability: basic +:Architectures: all except x86 +:Type: vcpu ioctl +:Parameters: struct kvm_many_regs (in) +:Returns: 0 on success, negative value on failure + +Errors: + + ====== ============================================================ + E2BIG the passed array of kvm_one_regs is too big, its size should + not exceed KVM_MANY_REGS_MAX_SIZE + ====== ============================================================ + +As this ioctl uses KVM_GET_ONE_REG, errors from KVM_GET_ONE_REG are propagated. + +(These error codes are indicative only: do not rely on a specific error +code being returned in a specific situation.) + +:: + + struct kvm_many_regs { + __u64 n; + struct kvm_one_reg reg[0]; + }; + +This ioctl allows to receive values of multiple registers implemented +in a vcpu. Registers to read are indicated by the 'id' field of each +kvm_one_reg struct in the passed array. On success, values of indicated +registers are written to memory locations pointed by the 'addr' field +of each kvm_one_reg. + +In case of duplicate IDs in the 'reg' array, the register will be read as many +times as it appears in the array. + +The list of registers accessible using this interface is identical to the +list in 4.68. + +4.128 KVM_SET_MANY_REGS +----------------------- + +:Capability: basic +:Architectures: all except x86 +:Type: vcpu ioctl +:Parameters: struct kvm_many_regs (in) +:Returns: 0 on success, negative value on failure + +Errors: + + ====== ============================================================ + E2BIG the passed array of kvm_one_regs is too big, its size should + not exceed KVM_MANY_REGS_MAX_SIZE + ====== ============================================================ + +As this ioctl uses KVM_SET_ONE_REG, errors from KVM_SET_ONE_REG are propagated. + +(These error codes are indicative only: do not rely on a specific error +code being returned in a specific situation.) + +This ioctl allows to set values of multiple registers implemented +in a vcpu. Registers to set are indicated by the 'id' field of each +kvm_one_reg struct in the passed array. On success, values of registers +are changed to values located at memory locations pointed by the 'addr' +field of each kvm_one_reg. + +In case of duplicate IDs in the 'reg' array, the register will be set as many +times as it appears in the array. + +The list of registers accessible using this interface is identical to the +list in 4.68. + + 5. The kvm_run structure ======================== diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index ca41220b40b8..87e799457fc3 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -1223,6 +1223,14 @@ struct kvm_one_reg { __u64 addr; }; +/* For KVM_(GET|SET)_MANY_REGS */ +#define KVM_MANY_REGS_MAX_SIZE 16384 + +struct kvm_many_regs { + __u64 n; + struct kvm_one_reg reg[0]; +}; + #define KVM_MSI_VALID_DEVID (1U << 0) struct kvm_msi { __u32 address_lo; @@ -1557,6 +1565,9 @@ struct kvm_pv_cmd { /* Available with KVM_CAP_X86_MSR_FILTER */ #define KVM_X86_SET_MSR_FILTER _IOW(KVMIO, 0xc6, struct kvm_msr_filter) +#define KVM_GET_MANY_REGS _IOW(KVMIO, 0xc7, struct kvm_many_regs) +#define KVM_SET_MANY_REGS _IOW(KVMIO, 0xc8, struct kvm_many_regs) + /* Secure Encrypted Virtualization command */ enum sev_cmd_id { /* Guest initialization commands */ diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index e14185f4977f..3bb618882d2c 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -3255,6 +3255,48 @@ static long kvm_vcpu_ioctl(struct file *filp, kvm_arch_after_reg_access(vcpu); break; } + case KVM_SET_MANY_REGS: + case KVM_GET_MANY_REGS: { + struct kvm_many_regs __user *user_regs = argp; + struct kvm_many_regs many_regs; + struct kvm_one_reg *reg; + unsigned int i, size; + + r = kvm_arch_before_reg_access(vcpu); + if (r < 0) + break; + + r = -EFAULT; + if (copy_from_user(&many_regs, user_regs, sizeof(many_regs))) + goto out_after_regs_access; + + r = -E2BIG; + size = sizeof(struct kvm_one_reg) * many_regs.n; + if (size > KVM_MANY_REGS_MAX_SIZE) + goto out_after_regs_access; + + r = -ENOMEM; + reg = (struct kvm_one_reg *)memdup_user(user_regs->reg, size); + if (IS_ERR(reg)) + goto out_after_regs_access; + + if (ioctl == KVM_GET_MANY_REGS) + for (i = 0; i < many_regs.n; i++) { + r = kvm_arch_vcpu_ioctl_get_one_reg(vcpu, ®[i]); + if (r < 0) + break; + } + else + for (i = 0; i < many_regs.n; i++) { + r = kvm_arch_vcpu_ioctl_set_one_reg(vcpu, ®[i]); + if (r < 0) + break; + } + kfree(reg); +out_after_regs_access: + kvm_arch_after_reg_access(vcpu); + break; + } case KVM_GET_REGS: { struct kvm_regs *kvm_regs; -- 2.17.1