The entire memory represented by guest_memfd-only memslot is shared and accessible by userspace. Enable dirty page logging for such memslots, and allow architectures to advertise support for it with the KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES capability. No architecture supports it yet. Signed-off-by: Alexandru Elisei --- Documentation/virt/kvm/api.rst | 9 +++++ include/linux/kvm_host.h | 15 ++++++++ include/uapi/linux/kvm.h | 1 + virt/kvm/guest_memfd.c | 70 ++++++++++++++++++++++++++++++++++ virt/kvm/kvm_main.c | 44 ++++++++++----------- virt/kvm/kvm_mm.h | 11 ++++++ 6 files changed, 128 insertions(+), 22 deletions(-) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index a5f9ee92f43e..5012afe6a9b5 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -9493,6 +9493,15 @@ take care to differentiate between these cases. The presence of this capability indicates that the nested KVM guest can start in ESA mode. +8.48 KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES +--------------------------------------------- + +:Architectures: all + +The presence of this capability indicates that memslots backed by a guest_memfd +file descriptor created with the GUEST_MEMFD_FLAG_MMAP flag can have dirty +page logging enabled. + 9. Known KVM API problems ========================= diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index ab8cfaec82d3..c44e9253eb40 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -56,6 +56,7 @@ */ #define KVM_MEMSLOT_INVALID (1UL << 16) #define KVM_MEMSLOT_GMEM_ONLY (1UL << 17) +#define MEMSLOT_USER_FLAGS_MASK 0xffff /* * Bit 63 of the memslot generation number is an "update in-progress flag", @@ -731,6 +732,9 @@ static inline bool kvm_arch_has_private_mem(struct kvm *kvm) #ifdef CONFIG_KVM_GUEST_MEMFD bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm); +bool kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm); +int kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *slot, + unsigned int fd, loff_t offset); static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm) { @@ -741,6 +745,17 @@ static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm) return flags; } +#else +static inline bool kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm) +{ + return false; +} +static inline int kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *slot, + unsigned int fd, loff_t offset) +{ + WARN_ON_ONCE(1); + return -EIO; +} #endif #ifndef kvm_arch_has_readonly_mem diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 419011097fa8..5a53e2e19b2f 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -997,6 +997,7 @@ struct kvm_enable_cap { #define KVM_CAP_S390_KEYOP 247 #define KVM_CAP_S390_VSIE_ESAMODE 248 #define KVM_CAP_S390_HPAGE_2G 249 +#define KVM_CAP_GUEST_MEMFD_MMAP_LOG_DIRTY_PAGES 250 struct kvm_irq_routing_irqchip { __u32 irqchip; diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 43ef8e908aaf..210bdd76f0aa 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -622,6 +622,11 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm) return true; } +bool __weak kvm_arch_supports_gmem_mmap_dirty_logging(struct kvm *kvm) +{ + return false; +} + static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) { static const char *name = "[kvm-gmem]"; @@ -705,6 +710,66 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args) return __kvm_gmem_create(kvm, size, flags); } +static int __kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *old, + struct file *old_file, unsigned int fd, + loff_t offset) +{ + struct file *new_file; + + new_file = fget(fd); + if (!new_file) + return -EBADF; + if (new_file != old_file) { + fput(new_file); + return -EBADF; + } + fput(new_file); + + if (old->gmem.pgoff != offset >> PAGE_SHIFT) + return -EINVAL; + + return 0; +} + +int kvm_gmem_check_no_change(struct kvm *kvm, struct kvm_memory_slot *old, + unsigned int fd, loff_t offset) +{ + CLASS(gmem_get_file, old_file)(old); + + return __kvm_gmem_check_no_change(kvm, old, old_file, fd, offset); +} + +int kvm_gmem_change_flags(struct kvm *kvm, struct kvm_memory_slot *old, + struct kvm_memory_slot *new, unsigned int fd, + loff_t offset) +{ + struct gmem_file *old_f; + int ret; + + lockdep_assert_held(&kvm->slots_lock); + + if (!kvm_memslot_is_gmem_only(old)) + return -EINVAL; + + CLASS(gmem_get_file, old_file)(old); + + ret = __kvm_gmem_check_no_change(kvm, old, old_file, fd, offset); + if (ret) + return ret; + + old_f = old_file->private_data; + if (xa_load(&old_f->bindings, old->gmem.pgoff) != memslot_to_xa_value(new)) { + WARN_ON_ONCE(xa_to_value(xa_load(&old_f->bindings, old->gmem.pgoff))); + return -EIO; + } + + new->gmem.file = old->gmem.file; + new->gmem.pgoff = old->gmem.pgoff; + new->flags |= KVM_MEMSLOT_GMEM_ONLY; + + return 0; +} + int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot, unsigned int fd, uoff_t offset) { @@ -734,6 +799,11 @@ int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot, if (!PAGE_ALIGNED(offset) || offset + size > i_size_read(inode)) goto err; + if (slot->flags & KVM_MEM_LOG_DIRTY_PAGES && + (!kvm_gmem_supports_mmap(inode) || + !kvm_arch_supports_gmem_mmap_dirty_logging(kvm))) + goto err; + filemap_invalidate_lock(inode->i_mapping); start = offset >> PAGE_SHIFT; diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index e44c20c04961..af380e8f2b68 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1573,14 +1573,14 @@ static void kvm_replace_memslot(struct kvm *kvm, static int check_memory_region_flags(struct kvm *kvm, const struct kvm_userspace_memory_region2 *mem) { - u32 valid_flags = KVM_MEM_LOG_DIRTY_PAGES; + u32 valid_flags = 0; if (IS_ENABLED(CONFIG_KVM_GUEST_MEMFD)) valid_flags |= KVM_MEM_GUEST_MEMFD; - /* Dirty logging private memory is not currently supported. */ - if (mem->flags & KVM_MEM_GUEST_MEMFD) - valid_flags &= ~KVM_MEM_LOG_DIRTY_PAGES; + if (!(mem->flags & KVM_MEM_GUEST_MEMFD) || + kvm_arch_supports_gmem_mmap_dirty_logging(kvm)) + valid_flags |= KVM_MEM_LOG_DIRTY_PAGES; /* * GUEST_MEMFD is incompatible with read-only memslots, as writes to @@ -1739,16 +1739,6 @@ static void kvm_commit_memory_region(struct kvm *kvm, */ if (old->dirty_bitmap && !new->dirty_bitmap) kvm_destroy_dirty_bitmap(old); - - /* - * Unbind the guest_memfd instance as needed; the @new slot has - * already created its own binding. TODO: Drop the WARN when - * dirty logging guest_memfd memslots is supported. Until then, - * flags-only changes on guest_memfd slots should be impossible. - */ - if (WARN_ON_ONCE(old->flags & KVM_MEM_GUEST_MEMFD)) - kvm_gmem_unbind(old); - /* * The final quirk. Free the detached, old slot, but only its * memory, not any metadata. Metadata, including arch specific @@ -2073,22 +2063,27 @@ static int kvm_set_memory_region(struct kvm *kvm, if ((kvm->nr_memslot_pages + npages) < kvm->nr_memslot_pages) return -EINVAL; } else { /* Modify an existing slot. */ - /* Private memslots are immutable, they can only be deleted. */ - if (mem->flags & KVM_MEM_GUEST_MEMFD) - return -EINVAL; if ((mem->userspace_addr != old->userspace_addr) || (npages != old->npages) || ((mem->flags ^ old->flags) & (KVM_MEM_READONLY | KVM_MEM_GUEST_MEMFD))) return -EINVAL; - if (base_gfn != old->base_gfn) + if (base_gfn != old->base_gfn) { change = KVM_MR_MOVE; - else if (mem->flags != old->flags) + } else if (mem->flags != (old->flags & MEMSLOT_USER_FLAGS_MASK)) { change = KVM_MR_FLAGS_ONLY; - else /* Nothing to change. */ + } else if (mem->flags & KVM_MEM_GUEST_MEMFD) { + return kvm_gmem_check_no_change(kvm, old, mem->guest_memfd, + mem->guest_memfd_offset); + } else { return 0; + } } + if (mem->flags & KVM_MEM_GUEST_MEMFD && + change != KVM_MR_CREATE && change != KVM_MR_FLAGS_ONLY) + return -EINVAL; + if ((change == KVM_MR_CREATE || change == KVM_MR_MOVE) && kvm_check_memslot_overlap(slots, id, base_gfn, base_gfn + npages)) return -EEXIST; @@ -2105,7 +2100,12 @@ static int kvm_set_memory_region(struct kvm *kvm, new->flags = mem->flags; new->userspace_addr = mem->userspace_addr; if (mem->flags & KVM_MEM_GUEST_MEMFD) { - r = kvm_gmem_bind(kvm, new, mem->guest_memfd, mem->guest_memfd_offset); + if (change == KVM_MR_CREATE) { + r = kvm_gmem_bind(kvm, new, mem->guest_memfd, mem->guest_memfd_offset); + } else if (change == KVM_MR_FLAGS_ONLY) { + r = kvm_gmem_change_flags(kvm, old, new, mem->guest_memfd, + mem->guest_memfd_offset); + } if (r) goto out; } @@ -2117,7 +2117,7 @@ static int kvm_set_memory_region(struct kvm *kvm, return 0; out_unbind: - if (mem->flags & KVM_MEM_GUEST_MEMFD) + if ((mem->flags & KVM_MEM_GUEST_MEMFD) && change == KVM_MR_CREATE) kvm_gmem_unbind(new); out: kfree(new); diff --git a/virt/kvm/kvm_mm.h b/virt/kvm/kvm_mm.h index 7510ca915dd1..c58dfeb0f3df 100644 --- a/virt/kvm/kvm_mm.h +++ b/virt/kvm/kvm_mm.h @@ -77,6 +77,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args); int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot, unsigned int fd, uoff_t offset); void kvm_gmem_unbind(struct kvm_memory_slot *slot); +int kvm_gmem_change_flags(struct kvm *kvm, struct kvm_memory_slot *old, + struct kvm_memory_slot *new, unsigned int fd, + loff_t offset); #else static inline int kvm_gmem_init(struct module *module) { @@ -95,6 +98,14 @@ static inline void kvm_gmem_unbind(struct kvm_memory_slot *slot) { WARN_ON_ONCE(1); } +static inline int kvm_gmem_change_flags(struct kvm *kvm, + struct kvm_memory_slot *old, + struct kvm_memory_slot *new, + unsigned int fd, loff_t offset) +{ + WARN_ON_ONCE(1); + return -EIO; +} #endif /* CONFIG_KVM_GUEST_MEMFD */ #endif /* __KVM_MM_H__ */ -- 2.43.0