guest_memfd folios are currently always marked unmmovable, which prevents the MM from invoking ->migrate_folio callback in scenarios like compaction, offlining, etc. This is unavoidable for confidential VMs (SEV-SNP, TDX), since memory is encrypted and copying it needs firmware assistance. However, for non-confidential VMs (like firecracker), we can still migrate the folios.
Add kvm_arch_supports_gmem_migration() so an architecture can control whether migration is supported. Add GUEST_MEMFD_FLAG_MIGRATABLE so userspace can opt-in to folio migration. For opted-in guest_memfds, use GFP_HIGHUSER_MOVABLE so their folios can be allocated from ZONE_MOVABLE/CMA and grouped by mobility, otherwise keep the existing unmmovable behavior. Remove redundant WARN_ON_ONCE for checking unevictable mapping. Signed-off-by: Shivank Garg <[email protected]> --- arch/x86/kvm/x86.c | 9 +++++++++ include/linux/kvm_host.h | 4 ++++ include/uapi/linux/kvm.h | 1 + tools/include/uapi/linux/kvm.h | 1 + virt/kvm/guest_memfd.c | 18 ++++++++++++++---- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index afcac1042947..4a7016013271 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -14124,6 +14124,15 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm) return !kvm_arch_has_private_mem(kvm); } +/* + * Migration of guest_memfd with private memory is not supported yet + * as this may require architecture-specific handling. + */ +bool kvm_arch_supports_gmem_migration(struct kvm *kvm) +{ + return !kvm_arch_has_private_mem(kvm); +} + #ifdef CONFIG_HAVE_KVM_ARCH_GMEM_PREPARE int kvm_arch_gmem_prepare(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn, int max_order) { diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index ab8cfaec82d3..7b2f15b21970 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -731,6 +731,7 @@ 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_migration(struct kvm *kvm); static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm) { @@ -738,6 +739,9 @@ static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm) if (!kvm || kvm_arch_supports_gmem_init_shared(kvm)) flags |= GUEST_MEMFD_FLAG_INIT_SHARED; + if (IS_ENABLED(CONFIG_MIGRATION) && + (!kvm || kvm_arch_supports_gmem_migration(kvm))) + flags |= GUEST_MEMFD_FLAG_MIGRATABLE; return flags; } diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 419011097fa8..e1615a62ed78 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -1654,6 +1654,7 @@ struct kvm_memory_attributes { #define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) #define GUEST_MEMFD_FLAG_MMAP (1ULL << 0) #define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1) +#define GUEST_MEMFD_FLAG_MIGRATABLE (1ULL << 2) struct kvm_create_guest_memfd { __u64 size; diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h index 419011097fa8..e1615a62ed78 100644 --- a/tools/include/uapi/linux/kvm.h +++ b/tools/include/uapi/linux/kvm.h @@ -1654,6 +1654,7 @@ struct kvm_memory_attributes { #define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) #define GUEST_MEMFD_FLAG_MMAP (1ULL << 0) #define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1) +#define GUEST_MEMFD_FLAG_MIGRATABLE (1ULL << 2) struct kvm_create_guest_memfd { __u64 size; diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index ec881cabe8e3..8ae455a6b38d 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -589,6 +589,11 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm) return true; } +bool __weak kvm_arch_supports_gmem_migration(struct kvm *kvm) +{ + return false; +} + static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) { static const char *name = "[kvm-gmem]"; @@ -623,11 +628,16 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) inode->i_mapping->a_ops = &kvm_gmem_aops; inode->i_mode |= S_IFREG; inode->i_size = size; - mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER); mapping_set_inaccessible(inode->i_mapping); - mapping_set_unmovable(inode->i_mapping); - /* Unmovable mappings are supposed to be marked unevictable as well. */ - WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping)); + /* guest_memfd mappings should be marked unevictable. */ + mapping_set_unevictable(inode->i_mapping); + + if (flags & GUEST_MEMFD_FLAG_MIGRATABLE) { + mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER_MOVABLE); + } else { + mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER); + mapping_set_unmovable(inode->i_mapping); + } GMEM_I(inode)->flags = flags; -- 2.43.0

