commit fdfca0e86009c5a7b188fa39e939de800a73391d
Author: Michael Kelly <mike@weatherwax.co.uk>
Date:   Sun Aug 31 23:21:54 2025 +0200

    Add mach_port_set_ktype RPC to set ktype of a user port
    
    For now, we only allow a newly-introduced MACH_PORT_KTYPE_USER_DEVICE type
    that makes ipc_kmsg_copyin_body use page lists, which keep them in a busy
    state that prevents them from being paged out.

diff --git a/include/mach/mach_port.defs b/include/mach/mach_port.defs
index 3823bb14..3f2aed14 100644
--- a/include/mach/mach_port.defs
+++ b/include/mach/mach_port.defs
@@ -358,3 +358,21 @@ routine mach_port_set_protected_payload(
 routine mach_port_clear_protected_payload(
 		task		: ipc_space_t;
 		name		: mach_port_name_t);
+
+/*
+ *	Set the kernel port type for specific kernel behaviour.
+ *	ktype must be one of:
+ *	MACH_PORT_KTYPE_NONE
+ *	MACH_PORT_KTYPE_USER_DEVICE
+ *	The named port must not be currently associated with any
+ *	other kernel port type.
+ */
+
+type mach_port_ktype_t = unsigned;
+
+routine mach_port_set_ktype(
+		host            : host_priv_t;
+		task		: ipc_space_t;
+		name		: mach_port_name_t;
+		right		: mach_port_right_t;
+		ktype		: mach_port_ktype_t);
diff --git a/include/mach/port.h b/include/mach/port.h
index c9bbcf17..488d2c9e 100644
--- a/include/mach/port.h
+++ b/include/mach/port.h
@@ -156,4 +156,10 @@ typedef struct mach_port_status {
 #define MACH_PORT_QLIMIT_DEFAULT	((mach_port_msgcount_t) 5)
 #define MACH_PORT_QLIMIT_MAX		((mach_port_msgcount_t) 16)
 
+typedef unsigned int mach_port_ktype_t;
+
+/* Constants for calls to mach_port_set_ktype() */
+#define MACH_PORT_KTYPE_NONE             0
+#define MACH_PORT_KTYPE_USER_DEVICE      28
+
 #endif	/* _MACH_PORT_H_ */
diff --git a/ipc/ipc_object.c b/ipc/ipc_object.c
index 1074fb2c..d6c332e6 100644
--- a/ipc/ipc_object.c
+++ b/ipc/ipc_object.c
@@ -944,6 +944,7 @@ char *ikot_print_array[IKOT_MAX_TYPE] = {
 	"(CLOCK)            ",
 	"(CLOCK_CTRL)       ",
 	"(PAGER_PROXY)      ",	/* 27 */
+	"(USER_DEVICE)      ",	/* 28 */
 				/* << new entries here	*/
 	"(UNKNOWN)     "	/* magic catchall	*/
 };	/* Please keep in sync with kern/ipc_kobject.h	*/
diff --git a/ipc/mach_port.c b/ipc/mach_port.c
index d8696e23..eed3e725 100644
--- a/ipc/mach_port.c
+++ b/ipc/mach_port.c
@@ -1567,6 +1567,45 @@ mach_port_clear_protected_payload(
 	return KERN_SUCCESS;
 }
 
+kern_return_t
+mach_port_set_ktype(
+        host_t host_priv,
+        ipc_space_t space,
+        mach_port_name_t name,
+        mach_port_right_t right,
+        mach_port_ktype_t ktype)
+{
+	ipc_port_t port;
+	kern_return_t kr;
+
+	if (host_priv == HOST_NULL)
+	  return KERN_INVALID_HOST;
+
+	if (space == IS_NULL)
+	  return KERN_INVALID_TASK;
+
+	if (ktype != MACH_PORT_KTYPE_NONE
+	    && ktype != MACH_PORT_KTYPE_USER_DEVICE)
+	  return KERN_INVALID_ARGUMENT;
+
+	kr = ipc_object_translate(space, name, right, (ipc_object_t *)&port);
+	if (kr != KERN_SUCCESS)
+	  return kr;
+
+	/* port is locked and active */
+	if (ip_kotype(port) == IKOT_NONE || ip_kotype(port) == IKOT_USER_DEVICE)
+	  ipc_kobject_set(port, IKO_NULL,
+			  ktype == MACH_PORT_KTYPE_NONE
+			  ? IKOT_NONE
+			  : IKOT_USER_DEVICE);
+	else
+	  kr = KERN_INVALID_ARGUMENT;
+
+	ip_unlock(port);
+
+	return kr;
+}
+
 #if	MACH_KDB
 
 void
diff --git a/kern/ipc_kobject.h b/kern/ipc_kobject.h
index 606a66a9..63ad87c5 100644
--- a/kern/ipc_kobject.h
+++ b/kern/ipc_kobject.h
@@ -77,9 +77,10 @@ typedef unsigned int ipc_kobject_type_t;
 #define IKOT_CLOCK		25
 #define IKOT_CLOCK_CTRL		26
 #define	IKOT_PAGER_PROXY	27
+#define	IKOT_USER_DEVICE	28
 					/* << new entries here	*/
-#define	IKOT_UNKNOWN		28	/* magic catchall	*/
-#define	IKOT_MAX_TYPE		29	/* # of IKOT_ types	*/
+#define	IKOT_UNKNOWN		29	/* magic catchall	*/
+#define	IKOT_MAX_TYPE		30	/* # of IKOT_ types	*/
  /* Please keep ipc/ipc_object.c:ikot_print_array up to date	*/
 
 #define is_ipc_kobject(ikot)	(ikot != IKOT_NONE)
@@ -90,7 +91,9 @@ typedef unsigned int ipc_kobject_type_t;
  */
 
 #define ipc_kobject_vm_page_list(ikot) 			\
-	((ikot == IKOT_PAGING_REQUEST) || (ikot == IKOT_DEVICE))
+  ((ikot == IKOT_PAGING_REQUEST) || \
+   (ikot == IKOT_DEVICE) || \
+   (ikot == IKOT_USER_DEVICE))
 
 #define ipc_kobject_vm_page_steal(ikot)	(ikot == IKOT_PAGING_REQUEST)
 
