From feafc7b050ff802cdff38252e4364189234711f3 Mon Sep 17 00:00:00 2001
From: Rob Norris <robn@despairlabs.com>
Date: Mon, 1 Dec 2025 16:05:27 +1100
Subject: [PATCH 1/3] Linux 6.19: replace i_state access with
 inode_state_read_once()

Sponsored-by: https://despairlabs.com/sponsor/
Signed-off-by: Rob Norris <robn@despairlabs.com>
---
 config/kernel-inode-state.m4               | 23 ++++++++++++++++++++++
 config/kernel.m4                           |  2 ++
 include/os/linux/kernel/linux/vfs_compat.h |  8 ++++++++
 module/os/linux/zfs/zfs_vnops_os.c         |  3 ++-
 4 files changed, 35 insertions(+), 1 deletion(-)
 create mode 100644 config/kernel-inode-state.m4

diff --git a/config/kernel-inode-state.m4 b/config/kernel-inode-state.m4
new file mode 100644
index 000000000000..02ec1380af53
--- /dev/null
+++ b/config/kernel-inode-state.m4
@@ -0,0 +1,23 @@
+dnl #
+dnl # 6.19 API change. inode->i_state no longer accessible directly; helper
+dnl # functions exist.
+dnl #
+AC_DEFUN([ZFS_AC_KERNEL_SRC_INODE_STATE_READ_ONCE], [
+	ZFS_LINUX_TEST_SRC([inode_state_read_once], [
+		#include <linux/fs.h>
+	], [
+		struct inode i = {};
+		inode_state_read_once(&i);
+	],[])
+])
+
+AC_DEFUN([ZFS_AC_KERNEL_INODE_STATE_READ_ONCE], [
+	AC_MSG_CHECKING([whether inode_state_read_once() exists])
+	ZFS_LINUX_TEST_RESULT([inode_state_read_once], [
+		AC_MSG_RESULT(yes)
+		AC_DEFINE(HAVE_INODE_STATE_READ_ONCE, 1,
+		    [inode_state_read_once() exists])
+	],[
+		AC_MSG_RESULT(no)
+	])
+])
diff --git a/config/kernel.m4 b/config/kernel.m4
index f095c8f20280..eb2e827d5cbf 100644
--- a/config/kernel.m4
+++ b/config/kernel.m4
@@ -59,6 +59,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_SRC], [
 	ZFS_AC_KERNEL_SRC_ACL
 	ZFS_AC_KERNEL_SRC_INODE_SETATTR
 	ZFS_AC_KERNEL_SRC_INODE_GETATTR
+	ZFS_AC_KERNEL_SRC_INODE_STATE_READ_ONCE
 	ZFS_AC_KERNEL_SRC_SHOW_OPTIONS
 	ZFS_AC_KERNEL_SRC_SHRINKER
 	ZFS_AC_KERNEL_SRC_MKDIR
@@ -181,6 +182,7 @@ AC_DEFUN([ZFS_AC_KERNEL_TEST_RESULT], [
 	ZFS_AC_KERNEL_ACL
 	ZFS_AC_KERNEL_INODE_SETATTR
 	ZFS_AC_KERNEL_INODE_GETATTR
+	ZFS_AC_KERNEL_INODE_STATE_READ_ONCE
 	ZFS_AC_KERNEL_SHOW_OPTIONS
 	ZFS_AC_KERNEL_SHRINKER
 	ZFS_AC_KERNEL_MKDIR
diff --git a/include/os/linux/kernel/linux/vfs_compat.h b/include/os/linux/kernel/linux/vfs_compat.h
index d9dc904bc322..24219637d266 100644
--- a/include/os/linux/kernel/linux/vfs_compat.h
+++ b/include/os/linux/kernel/linux/vfs_compat.h
@@ -269,4 +269,12 @@ zpl_is_32bit_api(void)
 #define	generic_drop_inode(ip)		inode_generic_drop(ip)
 #endif
 
+#ifndef HAVE_INODE_STATE_READ_ONCE
+/*
+ * 6.19 API change. We should no longer access i_state directly. If the new
+ * helper function doesn't exist, define our own.
+ */
+#define	inode_state_read_once(ip)	READ_ONCE(ip->i_state)
+#endif
+
 #endif /* _ZFS_VFS_H */
diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c
index a788e3fd4862..da09faba1373 100644
--- a/module/os/linux/zfs/zfs_vnops_os.c
+++ b/module/os/linux/zfs/zfs_vnops_os.c
@@ -3513,7 +3513,8 @@ zfs_link(znode_t *tdzp, znode_t *szp, char *name, cred_t *cr,
 	boolean_t	is_tmpfile = 0;
 	uint64_t	txg;
 
-	is_tmpfile = (sip->i_nlink == 0 && (sip->i_state & I_LINKABLE));
+	is_tmpfile = (sip->i_nlink == 0 &&
+	    (inode_state_read_once(sip) & I_LINKABLE));
 
 	ASSERT(S_ISDIR(ZTOI(tdzp)->i_mode));
 

From 390553f69be13a61cc648657acef70f156d94635 Mon Sep 17 00:00:00 2001
From: Rob Norris <robn@despairlabs.com>
Date: Wed, 17 Dec 2025 21:21:40 +1100
Subject: [PATCH 2/3] kmem: don't pass __GFP_HIGHMEM to __vmalloc

Since Linux 4.12 (torvalds/linux@19809c2da28ae) __GFP_HIGHMEM has been
automatically added to calls to __vmalloc() internally, so we don't need
it anymore. This is good, because since 6.19 the kernel warns if you use
__GFP_HIGHMEM.

Sponsored-by: https://despairlabs.com/sponsor/
Signed-off-by: Rob Norris <robn@despairlabs.com>
---
 module/os/linux/spl/spl-kmem-cache.c | 2 +-
 module/os/linux/spl/spl-kmem.c       | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/module/os/linux/spl/spl-kmem-cache.c b/module/os/linux/spl/spl-kmem-cache.c
index 5594b2f80c02..211180e082bc 100644
--- a/module/os/linux/spl/spl-kmem-cache.c
+++ b/module/os/linux/spl/spl-kmem-cache.c
@@ -144,7 +144,7 @@ kv_alloc(spl_kmem_cache_t *skc, int size, int flags)
 
 	if (skc->skc_flags & KMC_RECLAIMABLE)
 		lflags |= __GFP_RECLAIMABLE;
-	ptr = spl_vmalloc(size, lflags | __GFP_HIGHMEM);
+	ptr = spl_vmalloc(size, lflags);
 
 	/* Resulting allocated memory will be page aligned */
 	ASSERT(IS_P2ALIGNED(ptr, PAGE_SIZE));
diff --git a/module/os/linux/spl/spl-kmem.c b/module/os/linux/spl/spl-kmem.c
index 9fe008cef868..9fe4042b5079 100644
--- a/module/os/linux/spl/spl-kmem.c
+++ b/module/os/linux/spl/spl-kmem.c
@@ -188,7 +188,7 @@ spl_kvmalloc(size_t size, gfp_t lflags)
 		return (ptr);
 	}
 
-	return (spl_vmalloc(size, lflags | __GFP_HIGHMEM));
+	return (spl_vmalloc(size, lflags));
 }
 
 /*
@@ -237,7 +237,7 @@ spl_kmem_alloc_impl(size_t size, int flags, int node)
 		 */
 		if (size > spl_kmem_alloc_max) {
 			if (flags & KM_VMEM) {
-				ptr = spl_vmalloc(size, lflags | __GFP_HIGHMEM);
+				ptr = spl_vmalloc(size, lflags);
 			} else {
 				return (NULL);
 			}

From 3125c8b1d66c4c92b38b2cb0cba73c87c0c0a049 Mon Sep 17 00:00:00 2001
From: Rob Norris <robn@despairlabs.com>
Date: Wed, 17 Dec 2025 22:12:05 +1100
Subject: [PATCH 3/3] kmem: don't add __GFP_COMP for KM_VMEM allocations

It hasn't been necessary since Linux 3.13
(torvalds/linux@a57a49887eb33), and since 6.19 the kernel warns if you
use it.

Sponsored-by: https://despairlabs.com/sponsor/
Signed-off-by: Rob Norris <robn@despairlabs.com>
---
 include/os/linux/spl/sys/kmem.h      | 5 ++++-
 module/os/linux/spl/spl-kmem-cache.c | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/include/os/linux/spl/sys/kmem.h b/include/os/linux/spl/sys/kmem.h
index fe34de9c179e..705f9c4d7169 100644
--- a/include/os/linux/spl/sys/kmem.h
+++ b/include/os/linux/spl/sys/kmem.h
@@ -66,7 +66,10 @@ void *spl_kvmalloc(size_t size, gfp_t flags);
 static inline gfp_t
 kmem_flags_convert(int flags)
 {
-	gfp_t lflags = __GFP_NOWARN | __GFP_COMP;
+	gfp_t lflags = __GFP_NOWARN;
+
+	if (!(flags & KM_VMEM))
+		lflags |= __GFP_COMP;
 
 	if (flags & KM_NOSLEEP) {
 		lflags |= GFP_ATOMIC | __GFP_NORETRY;
diff --git a/module/os/linux/spl/spl-kmem-cache.c b/module/os/linux/spl/spl-kmem-cache.c
index 211180e082bc..3b9518a60d14 100644
--- a/module/os/linux/spl/spl-kmem-cache.c
+++ b/module/os/linux/spl/spl-kmem-cache.c
@@ -139,7 +139,7 @@ static void spl_cache_shrink(spl_kmem_cache_t *skc, void *obj);
 static void *
 kv_alloc(spl_kmem_cache_t *skc, int size, int flags)
 {
-	gfp_t lflags = kmem_flags_convert(flags);
+	gfp_t lflags = kmem_flags_convert(flags | KM_VMEM);
 	void *ptr;
 
 	if (skc->skc_flags & KMC_RECLAIMABLE)

From a1319bf654bea2ee1f2caf66b14b72ed2086ab24 Mon Sep 17 00:00:00 2001
From: Rob Norris <robn@despairlabs.com>
Date: Tue, 6 Jan 2026 08:35:13 +1100
Subject: [PATCH] kmem: don't add __GFP_RECLAIMABLE for KM_VMEM allocations

vmalloc()'d memory is not movable/reclaimable, so __GFP_RECLAIMABLE is
not a valid flag, and since 6.19 the kernel warns if you use it.

Sponsored-by: https://despairlabs.com/sponsor/
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Signed-off-by: Rob Norris <robn@despairlabs.com>
Closes #18107
---
 module/os/linux/spl/spl-kmem-cache.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/module/os/linux/spl/spl-kmem-cache.c b/module/os/linux/spl/spl-kmem-cache.c
index 3b9518a60..6d496e685 100644
--- a/module/os/linux/spl/spl-kmem-cache.c
+++ b/module/os/linux/spl/spl-kmem-cache.c
@@ -142,8 +142,6 @@ kv_alloc(spl_kmem_cache_t *skc, int size, int flags)
 	gfp_t lflags = kmem_flags_convert(flags | KM_VMEM);
 	void *ptr;
 
-	if (skc->skc_flags & KMC_RECLAIMABLE)
-		lflags |= __GFP_RECLAIMABLE;
 	ptr = spl_vmalloc(size, lflags);
 
 	/* Resulting allocated memory will be page aligned */
@@ -424,7 +422,7 @@ spl_emergency_alloc(spl_kmem_cache_t *skc, int flags, void **obj)
 	if (!empty)
 		return (-EEXIST);
 
-	if (skc->skc_flags & KMC_RECLAIMABLE)
+	if (skc->skc_flags & KMC_RECLAIMABLE && !(flags & KM_VMEM))
 		lflags |= __GFP_RECLAIMABLE;
 	ske = kmalloc(sizeof (*ske), lflags);
 	if (ske == NULL)
