[f2fs-dev] [PATCH V2 1/2] mm/filemap: Add NUMA mempolicy support to filemap_alloc_folio()

2025-06-23 Thread Shivank Garg via Linux-f2fs-devel
From: "Matthew Wilcox (Oracle)" 

Add a mempolicy parameter to filemap_alloc_folio() to enable NUMA-aware
page cache allocations. This will be used by upcoming changes to
support NUMA policies in guest-memfd, where guest_memory needs to be
allocated according to NUMA policy specified by the VMM.

All existing users pass NULL maintaining current behavior.

Reviewed-by: Pankaj Gupta 
Reviewed-by: Vlastimil Babka 
Signed-off-by: Matthew Wilcox (Oracle) 
Signed-off-by: Shivank Garg 
---
Changes in V2:
- fix checkpatch warnings.
- touch up commit description and fix code alignments to make it
  more readable.

V1: https://lore.kernel.org/all/20250620143502.3055777-1-wi...@infradead.org

 fs/bcachefs/fs-io-buffered.c |  2 +-
 fs/btrfs/compression.c   |  4 ++--
 fs/btrfs/verity.c|  2 +-
 fs/erofs/zdata.c |  2 +-
 fs/f2fs/compress.c   |  2 +-
 include/linux/pagemap.h  |  8 +---
 mm/filemap.c | 14 +-
 mm/readahead.c   |  2 +-
 8 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/fs/bcachefs/fs-io-buffered.c b/fs/bcachefs/fs-io-buffered.c
index 66bacdd49f78..392344232b16 100644
--- a/fs/bcachefs/fs-io-buffered.c
+++ b/fs/bcachefs/fs-io-buffered.c
@@ -124,7 +124,7 @@ static int readpage_bio_extend(struct btree_trans *trans,
if (folio && !xa_is_value(folio))
break;
 
-   folio = 
filemap_alloc_folio(readahead_gfp_mask(iter->mapping), order);
+   folio = 
filemap_alloc_folio(readahead_gfp_mask(iter->mapping), order, NULL);
if (!folio)
break;
 
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index 48d07939fee4..a0808c8f897f 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -474,8 +474,8 @@ static noinline int add_ra_bio_pages(struct inode *inode,
continue;
}
 
-   folio = filemap_alloc_folio(mapping_gfp_constraint(mapping,
-  ~__GFP_FS), 
0);
+   folio = filemap_alloc_folio(mapping_gfp_constraint(mapping, 
~__GFP_FS),
+   0, NULL);
if (!folio)
break;
 
diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index b7a96a005487..c43a789ba6d2 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -742,7 +742,7 @@ static struct page *btrfs_read_merkle_tree_page(struct 
inode *inode,
}
 
folio = filemap_alloc_folio(mapping_gfp_constraint(inode->i_mapping, 
~__GFP_FS),
-   0);
+   0, NULL);
if (!folio)
return ERR_PTR(-ENOMEM);
 
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index fe8071844724..00e9160a0d24 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -562,7 +562,7 @@ static void z_erofs_bind_cache(struct z_erofs_frontend *fe)
 * Allocate a managed folio for cached I/O, or it may be
 * then filled with a file-backed folio for in-place I/O
 */
-   newfolio = filemap_alloc_folio(gfp, 0);
+   newfolio = filemap_alloc_folio(gfp, 0, NULL);
if (!newfolio)
continue;
newfolio->private = Z_EROFS_PREALLOCATED_FOLIO;
diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index b3c1df93a163..7ef937dd7624 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -1942,7 +1942,7 @@ void f2fs_cache_compressed_page(struct f2fs_sb_info *sbi, 
struct page *page,
return;
}
 
-   cfolio = filemap_alloc_folio(__GFP_NOWARN | __GFP_IO, 0);
+   cfolio = filemap_alloc_folio(__GFP_NOWARN | __GFP_IO, 0, NULL);
if (!cfolio)
return;
 
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index e63fbfbd5b0f..78ea357d2077 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -646,9 +646,11 @@ static inline void *detach_page_private(struct page *page)
 }
 
 #ifdef CONFIG_NUMA
-struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int order);
+struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int order,
+   struct mempolicy *policy);
 #else
-static inline struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int 
order)
+static inline struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int 
order,
+   struct mempolicy *policy)
 {
return folio_alloc_noprof(gfp, order);
 }
@@ -659,7 +661,7 @@ static inline struct folio 
*filemap_alloc_folio_noprof(gfp_t gfp, unsigned int o
 
 static inline struct page *__page_cache_alloc(gfp_t gfp)
 {
-   return &filemap_alloc_folio(gfp, 0)->page;
+   return &filemap_alloc_folio(gfp, 0, NULL)->pa

[f2fs-dev] [PATCH V2 2/2] mm/filemap: Extend __filemap_get_folio() to support NUMA memory policies

2025-06-23 Thread Shivank Garg via Linux-f2fs-devel
From: "Matthew Wilcox (Oracle)" 

Extend __filemap_get_folio() to support NUMA memory policies by
renaming the implementation to __filemap_get_folio_mpol() and adding
a mempolicy parameter. The original function becomes a static inline
wrapper that passes NULL for the mempolicy.

This infrastructure will enable future support for NUMA-aware page cache
allocations in guest_memfd memory backend KVM guests.

Reviewed-by: Pankaj Gupta 
Reviewed-by: Vlastimil Babka 
Signed-off-by: Matthew Wilcox (Oracle) 
Signed-off-by: Shivank Garg 
---
Changes in V2:
- fix checkpatch warnings.
- touch up commit description and fix code alignments to make it
  more readable.
V1:
https://lore.kernel.org/all/20250620143502.3055777-2-wi...@infradead.org

 include/linux/pagemap.h | 10 --
 mm/filemap.c| 11 ++-
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 78ea357d2077..981ff97b4445 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -747,11 +747,17 @@ static inline fgf_t fgf_set_order(size_t size)
 }
 
 void *filemap_get_entry(struct address_space *mapping, pgoff_t index);
-struct folio *__filemap_get_folio(struct address_space *mapping, pgoff_t index,
-   fgf_t fgp_flags, gfp_t gfp);
+struct folio *__filemap_get_folio_mpol(struct address_space *mapping,
+   pgoff_t index, fgf_t fgf_flags, gfp_t gfp, struct mempolicy 
*policy);
 struct page *pagecache_get_page(struct address_space *mapping, pgoff_t index,
fgf_t fgp_flags, gfp_t gfp);
 
+static inline struct folio *__filemap_get_folio(struct address_space *mapping,
+   pgoff_t index, fgf_t fgf_flags, gfp_t gfp)
+{
+   return __filemap_get_folio_mpol(mapping, index, fgf_flags, gfp, NULL);
+}
+
 /**
  * filemap_get_folio - Find and get a folio.
  * @mapping: The address_space to search.
diff --git a/mm/filemap.c b/mm/filemap.c
index a30cd4dd085a..ec7de38c17c1 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1896,11 +1896,12 @@ void *filemap_get_entry(struct address_space *mapping, 
pgoff_t index)
 }
 
 /**
- * __filemap_get_folio - Find and get a reference to a folio.
+ * __filemap_get_folio_mpol - Find and get a reference to a folio.
  * @mapping: The address_space to search.
  * @index: The page index.
  * @fgp_flags: %FGP flags modify how the folio is returned.
  * @gfp: Memory allocation flags to use if %FGP_CREAT is specified.
+ * @policy: NUMA memory allocation policy to follow.
  *
  * Looks up the page cache entry at @mapping & @index.
  *
@@ -1911,8 +1912,8 @@ void *filemap_get_entry(struct address_space *mapping, 
pgoff_t index)
  *
  * Return: The found folio or an ERR_PTR() otherwise.
  */
-struct folio *__filemap_get_folio(struct address_space *mapping, pgoff_t index,
-   fgf_t fgp_flags, gfp_t gfp)
+struct folio *__filemap_get_folio_mpol(struct address_space *mapping,
+   pgoff_t index, fgf_t fgp_flags, gfp_t gfp, struct mempolicy 
*policy)
 {
struct folio *folio;
 
@@ -1982,7 +1983,7 @@ struct folio *__filemap_get_folio(struct address_space 
*mapping, pgoff_t index,
err = -ENOMEM;
if (order > min_order)
alloc_gfp |= __GFP_NORETRY | __GFP_NOWARN;
-   folio = filemap_alloc_folio(alloc_gfp, order, NULL);
+   folio = filemap_alloc_folio(alloc_gfp, order, policy);
if (!folio)
continue;
 
@@ -2029,7 +2030,7 @@ struct folio *__filemap_get_folio(struct address_space 
*mapping, pgoff_t index,
folio_clear_dropbehind(folio);
return folio;
 }
-EXPORT_SYMBOL(__filemap_get_folio);
+EXPORT_SYMBOL(__filemap_get_folio_mpol);
 
 static inline struct folio *find_get_entry(struct xa_state *xas, pgoff_t max,
xa_mark_t mark)
-- 
2.43.0



___
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel


[f2fs-dev] [PATCH kvm-next V11 2/7] mm/filemap: Extend __filemap_get_folio() to support NUMA memory policies

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
From: "Matthew Wilcox (Oracle)" 

Extend __filemap_get_folio() to support NUMA memory policies by
renaming the implementation to __filemap_get_folio_mpol() and adding
a mempolicy parameter. The original function becomes a static inline
wrapper that passes NULL for the mempolicy.

This infrastructure will enable future support for NUMA-aware page cache
allocations in guest_memfd memory backend KVM guests.

Reviewed-by: Pankaj Gupta 
Reviewed-by: Vlastimil Babka 
Signed-off-by: Matthew Wilcox (Oracle) 
Reviewed-by: David Hildenbrand 
Signed-off-by: Shivank Garg 
---
 include/linux/pagemap.h | 10 --
 mm/filemap.c| 11 ++-
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index ce617a35dc35..94d65ced0a1d 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -747,11 +747,17 @@ static inline fgf_t fgf_set_order(size_t size)
 }
 
 void *filemap_get_entry(struct address_space *mapping, pgoff_t index);
-struct folio *__filemap_get_folio(struct address_space *mapping, pgoff_t index,
-   fgf_t fgp_flags, gfp_t gfp);
+struct folio *__filemap_get_folio_mpol(struct address_space *mapping,
+   pgoff_t index, fgf_t fgf_flags, gfp_t gfp, struct mempolicy 
*policy);
 struct page *pagecache_get_page(struct address_space *mapping, pgoff_t index,
fgf_t fgp_flags, gfp_t gfp);
 
+static inline struct folio *__filemap_get_folio(struct address_space *mapping,
+   pgoff_t index, fgf_t fgf_flags, gfp_t gfp)
+{
+   return __filemap_get_folio_mpol(mapping, index, fgf_flags, gfp, NULL);
+}
+
 /**
  * write_begin_get_folio - Get folio for write_begin with flags.
  * @iocb: The kiocb passed from write_begin (may be NULL).
diff --git a/mm/filemap.c b/mm/filemap.c
index 495f7f5c3d2e..03f223be575c 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1897,11 +1897,12 @@ void *filemap_get_entry(struct address_space *mapping, 
pgoff_t index)
 }
 
 /**
- * __filemap_get_folio - Find and get a reference to a folio.
+ * __filemap_get_folio_mpol - Find and get a reference to a folio.
  * @mapping: The address_space to search.
  * @index: The page index.
  * @fgp_flags: %FGP flags modify how the folio is returned.
  * @gfp: Memory allocation flags to use if %FGP_CREAT is specified.
+ * @policy: NUMA memory allocation policy to follow.
  *
  * Looks up the page cache entry at @mapping & @index.
  *
@@ -1912,8 +1913,8 @@ void *filemap_get_entry(struct address_space *mapping, 
pgoff_t index)
  *
  * Return: The found folio or an ERR_PTR() otherwise.
  */
-struct folio *__filemap_get_folio(struct address_space *mapping, pgoff_t index,
-   fgf_t fgp_flags, gfp_t gfp)
+struct folio *__filemap_get_folio_mpol(struct address_space *mapping,
+   pgoff_t index, fgf_t fgp_flags, gfp_t gfp, struct mempolicy 
*policy)
 {
struct folio *folio;
 
@@ -1983,7 +1984,7 @@ struct folio *__filemap_get_folio(struct address_space 
*mapping, pgoff_t index,
err = -ENOMEM;
if (order > min_order)
alloc_gfp |= __GFP_NORETRY | __GFP_NOWARN;
-   folio = filemap_alloc_folio(alloc_gfp, order, NULL);
+   folio = filemap_alloc_folio(alloc_gfp, order, policy);
if (!folio)
continue;
 
@@ -2030,7 +2031,7 @@ struct folio *__filemap_get_folio(struct address_space 
*mapping, pgoff_t index,
folio_clear_dropbehind(folio);
return folio;
 }
-EXPORT_SYMBOL(__filemap_get_folio);
+EXPORT_SYMBOL(__filemap_get_folio_mpol);
 
 static inline struct folio *find_get_entry(struct xa_state *xas, pgoff_t max,
xa_mark_t mark)
-- 
2.43.0



___
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel


[f2fs-dev] [PATCH kvm-next V11 4/7] KVM: guest_memfd: Use guest mem inodes instead of anonymous inodes

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
From: Ackerley Tng 

guest_memfd's inode represents memory the guest_memfd is
providing. guest_memfd's file represents a struct kvm's view of that
memory.

Using a custom inode allows customization of the inode teardown
process via callbacks. For example, ->evict_inode() allows
customization of the truncation process on file close, and
->destroy_inode() and ->free_inode() allow customization of the inode
freeing process.

Customizing the truncation process allows flexibility in management of
guest_memfd memory and customization of the inode freeing process
allows proper cleanup of memory metadata stored on the inode.

Memory metadata is more appropriately stored on the inode (as opposed
to the file), since the metadata is for the memory and is not unique
to a specific binding and struct kvm.

Acked-by: David Hildenbrand 
Co-developed-by: Fuad Tabba 
Signed-off-by: Fuad Tabba 
Signed-off-by: Ackerley Tng 
Signed-off-by: Shivank Garg 
---
 include/uapi/linux/magic.h |   1 +
 virt/kvm/guest_memfd.c | 129 ++---
 virt/kvm/kvm_main.c|   7 +-
 virt/kvm/kvm_mm.h  |   9 +--
 4 files changed, 119 insertions(+), 27 deletions(-)

diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index bb575f3ab45e..638ca21b7a90 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -103,5 +103,6 @@
 #define DEVMEM_MAGIC   0x454d444d  /* "DMEM" */
 #define SECRETMEM_MAGIC0x5345434d  /* "SECM" */
 #define PID_FS_MAGIC   0x50494446  /* "PIDF" */
+#define GUEST_MEMFD_MAGIC  0x474d454d  /* "GMEM" */
 
 #endif /* __LINUX_MAGIC_H__ */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 08a6bc7d25b6..6c66a0974055 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -1,12 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0
+#include 
 #include 
 #include 
+#include 
 #include 
+#include 
 #include 
-#include 
 
 #include "kvm_mm.h"
 
+static struct vfsmount *kvm_gmem_mnt;
+
 struct kvm_gmem {
struct kvm *kvm;
struct xarray bindings;
@@ -385,9 +389,45 @@ static struct file_operations kvm_gmem_fops = {
.fallocate  = kvm_gmem_fallocate,
 };
 
-void kvm_gmem_init(struct module *module)
+static int kvm_gmem_init_fs_context(struct fs_context *fc)
+{
+   if (!init_pseudo(fc, GUEST_MEMFD_MAGIC))
+   return -ENOMEM;
+
+   fc->s_iflags |= SB_I_NOEXEC;
+   fc->s_iflags |= SB_I_NODEV;
+
+   return 0;
+}
+
+static struct file_system_type kvm_gmem_fs = {
+   .name= "guest_memfd",
+   .init_fs_context = kvm_gmem_init_fs_context,
+   .kill_sb = kill_anon_super,
+};
+
+static int kvm_gmem_init_mount(void)
+{
+   kvm_gmem_mnt = kern_mount(&kvm_gmem_fs);
+
+   if (IS_ERR(kvm_gmem_mnt))
+   return PTR_ERR(kvm_gmem_mnt);
+
+   kvm_gmem_mnt->mnt_flags |= MNT_NOEXEC;
+   return 0;
+}
+
+int kvm_gmem_init(struct module *module)
 {
kvm_gmem_fops.owner = module;
+
+   return kvm_gmem_init_mount();
+}
+
+void kvm_gmem_exit(void)
+{
+   kern_unmount(kvm_gmem_mnt);
+   kvm_gmem_mnt = NULL;
 }
 
 static int kvm_gmem_migrate_folio(struct address_space *mapping,
@@ -463,11 +503,72 @@ bool __weak kvm_arch_supports_gmem_mmap(struct kvm *kvm)
return true;
 }
 
+static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,
+ loff_t size, u64 flags)
+{
+   struct inode *inode;
+
+   inode = anon_inode_make_secure_inode(kvm_gmem_mnt->mnt_sb, name, NULL);
+   if (IS_ERR(inode))
+   return inode;
+
+   inode->i_private = (void *)(unsigned long)flags;
+   inode->i_op = &kvm_gmem_iops;
+   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);
+   /* Unmovable mappings are supposed to be marked unevictable as well. */
+   WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping));
+
+   return inode;
+}
+
+static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,
+ u64 flags)
+{
+   static const char *name = "[kvm-gmem]";
+   struct inode *inode;
+   struct file *file;
+   int err;
+
+   err = -ENOENT;
+   /* __fput() will take care of fops_put(). */
+   if (!fops_get(&kvm_gmem_fops))
+   goto err;
+
+   inode = kvm_gmem_inode_make_secure_inode(name, size, flags);
+   if (IS_ERR(inode)) {
+   err = PTR_ERR(inode);
+   goto err_fops_put;
+   }
+
+   file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR,
+&kvm_gmem_fops);
+   if (IS_ERR(file)) {
+   err = PTR_ERR(file);
+   goto err_put_inode;
+   }
+
+

[f2fs-dev] [PATCH kvm-next V11 5/7] KVM: guest_memfd: Add slab-allocated inode cache

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
Add dedicated inode structure (kvm_gmem_inode_info) and slab-allocated
inode cache for guest memory backing, similar to how shmem handles inodes.

This adds the necessary allocation/destruction functions and prepares
for upcoming guest_memfd NUMA policy support changes.

Signed-off-by: Shivank Garg 
---
 virt/kvm/guest_memfd.c | 70 --
 1 file changed, 68 insertions(+), 2 deletions(-)

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 6c66a0974055..356947d36a47 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -17,6 +17,15 @@ struct kvm_gmem {
struct list_head entry;
 };
 
+struct kvm_gmem_inode_info {
+   struct inode vfs_inode;
+};
+
+static inline struct kvm_gmem_inode_info *KVM_GMEM_I(struct inode *inode)
+{
+   return container_of(inode, struct kvm_gmem_inode_info, vfs_inode);
+}
+
 /**
  * folio_file_pfn - like folio_file_page, but return a pfn.
  * @folio: The folio which contains this index.
@@ -389,13 +398,46 @@ static struct file_operations kvm_gmem_fops = {
.fallocate  = kvm_gmem_fallocate,
 };
 
+static struct kmem_cache *kvm_gmem_inode_cachep;
+
+static struct inode *kvm_gmem_alloc_inode(struct super_block *sb)
+{
+   struct kvm_gmem_inode_info *info;
+
+   info = alloc_inode_sb(sb, kvm_gmem_inode_cachep, GFP_KERNEL);
+   if (!info)
+   return NULL;
+
+   return &info->vfs_inode;
+}
+
+static void kvm_gmem_destroy_inode(struct inode *inode)
+{
+}
+
+static void kvm_gmem_free_inode(struct inode *inode)
+{
+   kmem_cache_free(kvm_gmem_inode_cachep, KVM_GMEM_I(inode));
+}
+
+static const struct super_operations kvm_gmem_super_operations = {
+   .statfs = simple_statfs,
+   .alloc_inode= kvm_gmem_alloc_inode,
+   .destroy_inode  = kvm_gmem_destroy_inode,
+   .free_inode = kvm_gmem_free_inode,
+};
+
 static int kvm_gmem_init_fs_context(struct fs_context *fc)
 {
+   struct pseudo_fs_context *ctx;
+
if (!init_pseudo(fc, GUEST_MEMFD_MAGIC))
return -ENOMEM;
 
fc->s_iflags |= SB_I_NOEXEC;
fc->s_iflags |= SB_I_NODEV;
+   ctx = fc->fs_private;
+   ctx->ops = &kvm_gmem_super_operations;
 
return 0;
 }
@@ -417,17 +459,41 @@ static int kvm_gmem_init_mount(void)
return 0;
 }
 
+static void kvm_gmem_init_inode(void *foo)
+{
+   struct kvm_gmem_inode_info *info = foo;
+
+   inode_init_once(&info->vfs_inode);
+}
+
 int kvm_gmem_init(struct module *module)
 {
-   kvm_gmem_fops.owner = module;
+   int ret;
+   struct kmem_cache_args args = {
+   .align = 0,
+   .ctor = kvm_gmem_init_inode,
+   };
 
-   return kvm_gmem_init_mount();
+   kvm_gmem_fops.owner = module;
+   kvm_gmem_inode_cachep = kmem_cache_create("kvm_gmem_inode_cache",
+ sizeof(struct 
kvm_gmem_inode_info),
+ &args, SLAB_ACCOUNT);
+   if (!kvm_gmem_inode_cachep)
+   return -ENOMEM;
+   ret = kvm_gmem_init_mount();
+   if (ret) {
+   kmem_cache_destroy(kvm_gmem_inode_cachep);
+   return ret;
+   }
+   return 0;
 }
 
 void kvm_gmem_exit(void)
 {
kern_unmount(kvm_gmem_mnt);
kvm_gmem_mnt = NULL;
+   rcu_barrier();
+   kmem_cache_destroy(kvm_gmem_inode_cachep);
 }
 
 static int kvm_gmem_migrate_folio(struct address_space *mapping,
-- 
2.43.0



___
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel


[f2fs-dev] [PATCH kvm-next V11 1/7] mm/filemap: Add NUMA mempolicy support to filemap_alloc_folio()

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
From: "Matthew Wilcox (Oracle)" 

Add a mempolicy parameter to filemap_alloc_folio() to enable NUMA-aware
page cache allocations. This will be used by upcoming changes to
support NUMA policies in guest-memfd, where guest_memory need to be
allocated NUMA policy specified by VMM.

All existing users pass NULL maintaining current behavior.

Reviewed-by: Pankaj Gupta 
Reviewed-by: Vlastimil Babka 
Signed-off-by: Matthew Wilcox (Oracle) 
Reviewed-by: David Hildenbrand 
Signed-off-by: Shivank Garg 
---
 fs/bcachefs/fs-io-buffered.c |  2 +-
 fs/btrfs/compression.c   |  4 ++--
 fs/btrfs/verity.c|  2 +-
 fs/erofs/zdata.c |  2 +-
 fs/f2fs/compress.c   |  2 +-
 include/linux/pagemap.h  |  8 +---
 mm/filemap.c | 14 +-
 mm/readahead.c   |  2 +-
 8 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/fs/bcachefs/fs-io-buffered.c b/fs/bcachefs/fs-io-buffered.c
index 1c54b9b5bd69..3af2eabb7ed3 100644
--- a/fs/bcachefs/fs-io-buffered.c
+++ b/fs/bcachefs/fs-io-buffered.c
@@ -124,7 +124,7 @@ static int readpage_bio_extend(struct btree_trans *trans,
if (folio && !xa_is_value(folio))
break;
 
-   folio = 
filemap_alloc_folio(readahead_gfp_mask(iter->mapping), order);
+   folio = 
filemap_alloc_folio(readahead_gfp_mask(iter->mapping), order, NULL);
if (!folio)
break;
 
diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c
index d09d622016ef..139f9609e737 100644
--- a/fs/btrfs/compression.c
+++ b/fs/btrfs/compression.c
@@ -474,8 +474,8 @@ static noinline int add_ra_bio_pages(struct inode *inode,
continue;
}
 
-   folio = filemap_alloc_folio(mapping_gfp_constraint(mapping,
-  ~__GFP_FS), 
0);
+   folio = filemap_alloc_folio(mapping_gfp_constraint(mapping, 
~__GFP_FS),
+   0, NULL);
if (!folio)
break;
 
diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c
index b7a96a005487..c43a789ba6d2 100644
--- a/fs/btrfs/verity.c
+++ b/fs/btrfs/verity.c
@@ -742,7 +742,7 @@ static struct page *btrfs_read_merkle_tree_page(struct 
inode *inode,
}
 
folio = filemap_alloc_folio(mapping_gfp_constraint(inode->i_mapping, 
~__GFP_FS),
-   0);
+   0, NULL);
if (!folio)
return ERR_PTR(-ENOMEM);
 
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index 2d73297003d2..e9a1bf7568c9 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -562,7 +562,7 @@ static void z_erofs_bind_cache(struct z_erofs_frontend *fe)
 * Allocate a managed folio for cached I/O, or it may be
 * then filled with a file-backed folio for in-place I/O
 */
-   newfolio = filemap_alloc_folio(gfp, 0);
+   newfolio = filemap_alloc_folio(gfp, 0, NULL);
if (!newfolio)
continue;
newfolio->private = Z_EROFS_PREALLOCATED_FOLIO;
diff --git a/fs/f2fs/compress.c b/fs/f2fs/compress.c
index 5c1f47e45dab..56a51c9ba4f1 100644
--- a/fs/f2fs/compress.c
+++ b/fs/f2fs/compress.c
@@ -1942,7 +1942,7 @@ static void f2fs_cache_compressed_page(struct 
f2fs_sb_info *sbi,
return;
}
 
-   cfolio = filemap_alloc_folio(__GFP_NOWARN | __GFP_IO, 0);
+   cfolio = filemap_alloc_folio(__GFP_NOWARN | __GFP_IO, 0, NULL);
if (!cfolio)
return;
 
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 12a12dae727d..ce617a35dc35 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -646,9 +646,11 @@ static inline void *detach_page_private(struct page *page)
 }
 
 #ifdef CONFIG_NUMA
-struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int order);
+struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int order,
+   struct mempolicy *policy);
 #else
-static inline struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int 
order)
+static inline struct folio *filemap_alloc_folio_noprof(gfp_t gfp, unsigned int 
order,
+   struct mempolicy *policy)
 {
return folio_alloc_noprof(gfp, order);
 }
@@ -659,7 +661,7 @@ static inline struct folio 
*filemap_alloc_folio_noprof(gfp_t gfp, unsigned int o
 
 static inline struct page *__page_cache_alloc(gfp_t gfp)
 {
-   return &filemap_alloc_folio(gfp, 0)->page;
+   return &filemap_alloc_folio(gfp, 0, NULL)->page;
 }
 
 static inline gfp_t readahead_gfp_mask(struct address_space *x)
diff --git a/mm/filemap.c b/mm/filemap.c
index 751838ef05e5..495f7f5c3d2e 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -989,

[f2fs-dev] [PATCH kvm-next V11 6/7] KVM: guest_memfd: Enforce NUMA mempolicy using shared policy

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
Previously, guest-memfd allocations followed local NUMA node id in absence
of process mempolicy, resulting in arbitrary memory allocation.
Moreover, mbind() couldn't be used  by the VMM as guest memory wasn't
mapped into userspace when allocation occurred.

Enable NUMA policy support by implementing vm_ops for guest-memfd mmap
operation. This allows the VMM to map the memory and use mbind() to set the
desired NUMA policy. The policy is stored in the inode structure via
kvm_gmem_inode_info, as memory policy is a property of the memory (struct
inode) itself. The policy is then retrieved via mpol_shared_policy_lookup()
and passed to filemap_grab_folio_mpol() to ensure that allocations follow
the specified memory policy.

This enables the VMM to control guest memory NUMA placement by calling
mbind() on the mapped memory regions, providing fine-grained control over
guest memory allocation across NUMA nodes.

The policy change only affect future allocations and does not migrate
existing memory. This matches mbind(2)'s default behavior which affects
only new allocations unless overridden with MPOL_MF_MOVE/MPOL_MF_MOVE_ALL
flags, which are not supported for guest_memfd as it is unmovable.

Suggested-by: David Hildenbrand 
Acked-by: David Hildenbrand 
Acked-by: Vlastimil Babka 
Signed-off-by: Shivank Garg 
---
 virt/kvm/guest_memfd.c | 67 --
 1 file changed, 65 insertions(+), 2 deletions(-)

diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 356947d36a47..85edc597bb9f 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -4,6 +4,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -18,6 +19,7 @@ struct kvm_gmem {
 };
 
 struct kvm_gmem_inode_info {
+   struct shared_policy policy;
struct inode vfs_inode;
 };
 
@@ -26,6 +28,9 @@ static inline struct kvm_gmem_inode_info *KVM_GMEM_I(struct 
inode *inode)
return container_of(inode, struct kvm_gmem_inode_info, vfs_inode);
 }
 
+static struct mempolicy *kvm_gmem_get_pgoff_policy(struct kvm_gmem_inode_info 
*info,
+  pgoff_t index);
+
 /**
  * folio_file_pfn - like folio_file_page, but return a pfn.
  * @folio: The folio which contains this index.
@@ -112,7 +117,25 @@ static int kvm_gmem_prepare_folio(struct kvm *kvm, struct 
kvm_memory_slot *slot,
 static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index)
 {
/* TODO: Support huge pages. */
-   return filemap_grab_folio(inode->i_mapping, index);
+   struct mempolicy *policy;
+   struct folio *folio;
+
+   /*
+* Fast-path: See if folio is already present in mapping to avoid
+* policy_lookup.
+*/
+   folio = __filemap_get_folio(inode->i_mapping, index,
+   FGP_LOCK | FGP_ACCESSED, 0);
+   if (!IS_ERR(folio))
+   return folio;
+
+   policy = kvm_gmem_get_pgoff_policy(KVM_GMEM_I(inode), index);
+   folio = __filemap_get_folio_mpol(inode->i_mapping, index,
+FGP_LOCK | FGP_ACCESSED | FGP_CREAT,
+mapping_gfp_mask(inode->i_mapping), 
policy);
+   mpol_cond_put(policy);
+
+   return folio;
 }
 
 static void kvm_gmem_invalidate_begin(struct kvm_gmem *gmem, pgoff_t start,
@@ -372,8 +395,45 @@ static vm_fault_t kvm_gmem_fault_user_mapping(struct 
vm_fault *vmf)
return ret;
 }
 
+#ifdef CONFIG_NUMA
+static int kvm_gmem_set_policy(struct vm_area_struct *vma, struct mempolicy 
*mpol)
+{
+   struct inode *inode = file_inode(vma->vm_file);
+
+   return mpol_set_shared_policy(&KVM_GMEM_I(inode)->policy, vma, mpol);
+}
+
+static struct mempolicy *kvm_gmem_get_policy(struct vm_area_struct *vma,
+unsigned long addr, pgoff_t *pgoff)
+{
+   struct inode *inode = file_inode(vma->vm_file);
+
+   *pgoff = vma->vm_pgoff + ((addr - vma->vm_start) >> PAGE_SHIFT);
+   return mpol_shared_policy_lookup(&KVM_GMEM_I(inode)->policy, *pgoff);
+}
+
+static struct mempolicy *kvm_gmem_get_pgoff_policy(struct kvm_gmem_inode_info 
*info,
+  pgoff_t index)
+{
+   struct mempolicy *mpol;
+
+   mpol = mpol_shared_policy_lookup(&info->policy, index);
+   return mpol ? mpol : get_task_policy(current);
+}
+#else
+static struct mempolicy *kvm_gmem_get_pgoff_policy(struct kvm_gmem_inode_info 
*info,
+  pgoff_t index)
+{
+   return NULL;
+}
+#endif /* CONFIG_NUMA */
+
 static const struct vm_operations_struct kvm_gmem_vm_ops = {
-   .fault = kvm_gmem_fault_user_mapping,
+   .fault  = kvm_gmem_fault_user_mapping,
+#ifdef CONFIG_NUMA
+   .get_policy = kvm_gmem_get_policy,
+   .set_policy = kvm_gmem_set_policy,
+#endif
 };
 
 static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vm

[f2fs-dev] [PATCH kvm-next V11 0/7] Add NUMA mempolicy support for KVM guest-memfd

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
This series introduces NUMA-aware memory placement support for KVM guests
with guest_memfd memory backends. It builds upon Fuad Tabba's work (V17)
that enabled host-mapping for guest_memfd memory [1] and can be applied
directly applied on KVM tree [2] (branch kvm-next, base commit: a6ad5413,
Merge branch 'guest-memfd-mmap' into HEAD)

== Background == 
KVM's guest-memfd memory backend currently lacks support for NUMA policy
enforcement, causing guest memory allocations to be distributed across host
nodes  according to kernel's default behavior, irrespective of any policy
specified by the VMM. This limitation arises because conventional userspace
NUMA control mechanisms like mbind(2) don't work since the memory isn't
directly mapped to userspace when allocations occur.
Fuad's work [1] provides the necessary mmap capability, and this series
leverages it to enable mbind(2).

== Implementation ==
This series implements proper NUMA policy support for guest-memfd by:

1. Adding mempolicy-aware allocation APIs to the filemap layer.
2. Introducing custom inodes (via a dedicated slab-allocated inode cache,
   kvm_gmem_inode_info) to store NUMA policy and metadata for guest memory.
3. Implementing get/set_policy vm_ops in guest_memfd to support NUMA
   policy.

With these changes, VMMs can now control guest memory placement by mapping
guest_memfd file descriptor and using mbind(2) to specify:
- Policy modes: default, bind, interleave, or preferred
- Host NUMA nodes: List of target nodes for memory allocation

These Policies affect only future allocations and do not migrate existing
memory. This matches mbind(2)'s default behavior which affects only new
allocations unless overridden with MPOL_MF_MOVE/MPOL_MF_MOVE_ALL flags (Not
supported for guest_memfd as it is unmovable by design).

== Upstream Plan ==
Phased approach as per David's guest_memfd extension overview [3] and
community calls [4]:

Phase 1 (this series):
1. Focuses on shared guest_memfd support (non-CoCo VMs).
2. Builds on Fuad's host-mapping work [1].

Phase2 (future work):
1. NUMA support for private guest_memfd (CoCo VMs).
2. Depends on SNP in-place conversion support [5].

This series provides a clean integration path for NUMA-aware memory
management for guest_memfd and lays the groundwork for future confidential
computing NUMA capabilities.

Thanks,
Shivank

== Changelog ==

- v1,v2: Extended the KVM_CREATE_GUEST_MEMFD IOCTL to pass mempolicy.
- v3: Introduced fbind() syscall for VMM memory-placement configuration.
- v4-v6: Current approach using shared_policy support and vm_ops (based on
 suggestions from David [6] and guest_memfd bi-weekly upstream
 call discussion [7]).
- v7: Use inodes to store NUMA policy instead of file [8].
- v8: Rebase on top of Fuad's V12: Host mmaping for guest_memfd memory.
- v9: Rebase on top of Fuad's V13 and incorporate review comments
- V10: Rebase on top of Fuad's V17. Use latest guest_memfd inode patch
   from Ackerley (with David's review comments). Use newer 
kmem_cache_create()
   API variant with arg parameter (Vlastimil)
- V11: Rebase on kvm-next, remove RFC tag, use Ackerley's latest patch
   and fix a rcu race bug during kvm module unload.

[1] https://lore.kernel.org/all/20250729225455.670324-1-sea...@google.com
[2] https://git.kernel.org/pub/scm/virt/kvm/kvm.git/log/?h=next 
[3] https://lore.kernel.org/all/c1c9591d-218a-495c-957b-ba356c8f8...@redhat.com
[4] 
https://docs.google.com/document/d/1M6766BzdY1Lhk7LiR5IqVR8B8mG3cr-cxTxOrAosPOk/edit?tab=t.0#heading=h.svcbod20b5ur
[5] https://lore.kernel.org/all/20250613005400.3694904-1-michael.r...@amd.com
[6] https://lore.kernel.org/all/6fbef654-36e2-4be5-906e-2a648a845...@redhat.com
[7] https://lore.kernel.org/all/2b77e055-98ac-43a1-a7ad-9f9065d7f...@amd.com
[8] https://lore.kernel.org/all/diqzbjumm167@ackerleytng-ctop.c.googlers.com

Ackerley Tng (1):
  KVM: guest_memfd: Use guest mem inodes instead of anonymous inodes

Matthew Wilcox (Oracle) (2):
  mm/filemap: Add NUMA mempolicy support to filemap_alloc_folio()
  mm/filemap: Extend __filemap_get_folio() to support NUMA memory
policies

Shivank Garg (4):
  mm/mempolicy: Export memory policy symbols
  KVM: guest_memfd: Add slab-allocated inode cache
  KVM: guest_memfd: Enforce NUMA mempolicy using shared policy
  KVM: guest_memfd: selftests: Add tests for mmap and NUMA policy
support

 fs/bcachefs/fs-io-buffered.c  |   2 +-
 fs/btrfs/compression.c|   4 +-
 fs/btrfs/verity.c |   2 +-
 fs/erofs/zdata.c  |   2 +-
 fs/f2fs/compress.c|   2 +-
 include/linux/pagemap.h   |  18 +-
 include/uapi/linux/magic.h|   1 +
 mm/filemap.c  |  23 +-
 mm/mempolicy.c|   6 +
 mm/readahead.c|   2 +-
 tools/testing/selftests/kvm/Makefile.kvm

[f2fs-dev] [PATCH kvm-next V11 3/7] mm/mempolicy: Export memory policy symbols

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
KVM guest_memfd wants to implement support for NUMA policies just like
shmem already does using the shared policy infrastructure. As
guest_memfd currently resides in KVM module code, we have to export the
relevant symbols.

In the future, guest_memfd might be moved to core-mm, at which point the
symbols no longer would have to be exported. When/if that happens is
still unclear.

Acked-by: David Hildenbrand 
Acked-by: Vlastimil Babka 
Signed-off-by: Shivank Garg 
---
 mm/mempolicy.c | 6 ++
 1 file changed, 6 insertions(+)

diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index eb83cff7db8c..3d797d47a040 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -354,6 +354,7 @@ struct mempolicy *get_task_policy(struct task_struct *p)
 
return &default_policy;
 }
+EXPORT_SYMBOL_FOR_MODULES(get_task_policy, "kvm");
 
 static const struct mempolicy_operations {
int (*create)(struct mempolicy *pol, const nodemask_t *nodes);
@@ -487,6 +488,7 @@ void __mpol_put(struct mempolicy *pol)
return;
kmem_cache_free(policy_cache, pol);
 }
+EXPORT_SYMBOL_FOR_MODULES(__mpol_put, "kvm");
 
 static void mpol_rebind_default(struct mempolicy *pol, const nodemask_t *nodes)
 {
@@ -2885,6 +2887,7 @@ struct mempolicy *mpol_shared_policy_lookup(struct 
shared_policy *sp,
read_unlock(&sp->lock);
return pol;
 }
+EXPORT_SYMBOL_FOR_MODULES(mpol_shared_policy_lookup, "kvm");
 
 static void sp_free(struct sp_node *n)
 {
@@ -3170,6 +3173,7 @@ void mpol_shared_policy_init(struct shared_policy *sp, 
struct mempolicy *mpol)
mpol_put(mpol); /* drop our incoming ref on sb mpol */
}
 }
+EXPORT_SYMBOL_FOR_MODULES(mpol_shared_policy_init, "kvm");
 
 int mpol_set_shared_policy(struct shared_policy *sp,
struct vm_area_struct *vma, struct mempolicy *pol)
@@ -3188,6 +3192,7 @@ int mpol_set_shared_policy(struct shared_policy *sp,
sp_free(new);
return err;
 }
+EXPORT_SYMBOL_FOR_MODULES(mpol_set_shared_policy, "kvm");
 
 /* Free a backing policy store on inode delete. */
 void mpol_free_shared_policy(struct shared_policy *sp)
@@ -3206,6 +3211,7 @@ void mpol_free_shared_policy(struct shared_policy *sp)
}
write_unlock(&sp->lock);
 }
+EXPORT_SYMBOL_FOR_MODULES(mpol_free_shared_policy, "kvm");
 
 #ifdef CONFIG_NUMA_BALANCING
 static int __initdata numabalancing_override;
-- 
2.43.0



___
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel


[f2fs-dev] [PATCH kvm-next V11 7/7] KVM: guest_memfd: selftests: Add tests for mmap and NUMA policy support

2025-08-27 Thread Shivank Garg via Linux-f2fs-devel
Add tests for NUMA memory policy binding and NUMA aware allocation in
guest_memfd. This extends the existing selftests by adding proper
validation for:
- KVM GMEM set_policy and get_policy() vm_ops functionality using
  mbind() and get_mempolicy()
- NUMA policy application before and after memory allocation

These tests help ensure NUMA support for guest_memfd works correctly.

Signed-off-by: Shivank Garg 
---
 tools/testing/selftests/kvm/Makefile.kvm  |   1 +
 .../testing/selftests/kvm/guest_memfd_test.c  | 121 ++
 2 files changed, 122 insertions(+)

diff --git a/tools/testing/selftests/kvm/Makefile.kvm 
b/tools/testing/selftests/kvm/Makefile.kvm
index 90f03f00cb04..c46cef2a7cd7 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -275,6 +275,7 @@ pgste-option = $(call try-run, echo 'int main(void) { 
return 0; }' | \
$(CC) -Werror -Wl$(comma)--s390-pgste -x c - -o 
"$$TMP",-Wl$(comma)--s390-pgste)
 
 LDLIBS += -ldl
+LDLIBS += -lnuma
 LDFLAGS += -pthread $(no-pie-option) $(pgste-option)
 
 LIBKVM_C := $(filter %.c,$(LIBKVM))
diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c 
b/tools/testing/selftests/kvm/guest_memfd_test.c
index b3ca6737f304..9640d04ec293 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -7,6 +7,8 @@
 #include 
 #include 
 #include 
+#include 
+#include 
 #include 
 #include 
 #include 
@@ -19,6 +21,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "kvm_util.h"
 #include "test_util.h"
@@ -72,6 +75,122 @@ static void test_mmap_supported(int fd, size_t page_size, 
size_t total_size)
TEST_ASSERT(!ret, "munmap() should succeed.");
 }
 
+#define TEST_REQUIRE_NUMA_MULTIPLE_NODES() \
+   TEST_REQUIRE(numa_available() != -1 && numa_max_node() >= 1)
+
+static void test_mbind(int fd, size_t page_size, size_t total_size)
+{
+   unsigned long nodemask = 1; /* nid: 0 */
+   unsigned long maxnode = 8;
+   unsigned long get_nodemask;
+   int get_policy;
+   char *mem;
+   int ret;
+
+   TEST_REQUIRE_NUMA_MULTIPLE_NODES();
+
+   mem = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+   TEST_ASSERT(mem != MAP_FAILED, "mmap for mbind test should succeed");
+
+   /* Test MPOL_INTERLEAVE policy */
+   ret = syscall(__NR_mbind, mem, page_size * 2, MPOL_INTERLEAVE,
+ &nodemask, maxnode, 0);
+   TEST_ASSERT(!ret, "mbind with INTERLEAVE to node 0 should succeed");
+   ret = syscall(__NR_get_mempolicy, &get_policy, &get_nodemask,
+ maxnode, mem, MPOL_F_ADDR);
+   TEST_ASSERT(!ret && get_policy == MPOL_INTERLEAVE && get_nodemask == 
nodemask,
+   "Policy should be MPOL_INTERLEAVE and nodes match");
+
+   /* Test basic MPOL_BIND policy */
+   ret = syscall(__NR_mbind, mem + page_size * 2, page_size * 2, MPOL_BIND,
+ &nodemask, maxnode, 0);
+   TEST_ASSERT(!ret, "mbind with MPOL_BIND to node 0 should succeed");
+   ret = syscall(__NR_get_mempolicy, &get_policy, &get_nodemask,
+ maxnode, mem + page_size * 2, MPOL_F_ADDR);
+   TEST_ASSERT(!ret && get_policy == MPOL_BIND && get_nodemask == nodemask,
+   "Policy should be MPOL_BIND and nodes match");
+
+   /* Test MPOL_DEFAULT policy */
+   ret = syscall(__NR_mbind, mem, total_size, MPOL_DEFAULT, NULL, 0, 0);
+   TEST_ASSERT(!ret, "mbind with MPOL_DEFAULT should succeed");
+   ret = syscall(__NR_get_mempolicy, &get_policy, &get_nodemask,
+ maxnode, mem, MPOL_F_ADDR);
+   TEST_ASSERT(!ret && get_policy == MPOL_DEFAULT && get_nodemask == 0,
+   "Policy should be MPOL_DEFAULT and nodes zero");
+
+   /* Test with invalid policy */
+   ret = syscall(__NR_mbind, mem, page_size, 999, &nodemask, maxnode, 0);
+   TEST_ASSERT(ret == -1 && errno == EINVAL,
+   "mbind with invalid policy should fail with EINVAL");
+
+   TEST_ASSERT(munmap(mem, total_size) == 0, "munmap should succeed");
+}
+
+static void test_numa_allocation(int fd, size_t page_size, size_t total_size)
+{
+   unsigned long node0_mask = 1;  /* Node 0 */
+   unsigned long node1_mask = 2;  /* Node 1 */
+   unsigned long maxnode = 8;
+   void *pages[4];
+   int status[4];
+   char *mem;
+   int ret, i;
+
+   TEST_REQUIRE_NUMA_MULTIPLE_NODES();
+
+   /* Clean slate: deallocate all file space, if any */
+   ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, 
total_size);
+   TEST_ASSERT(!ret, "fallocate(PUNCH_HOLE) should succeed");
+
+   mem = mmap(NULL, total_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+   TEST_ASSERT(mem != MAP_FAILED, "mmap should succeed");
+
+   for (i = 0; i < 4; i++)
+   pages[i] = (char *)mem + page_size * i;
+
+   /* Set NUMA policy a