git: d38ef1aca969 - main - cuse: Implement hot-unload

From: Christos Margiolis <christos_at_FreeBSD.org>
Date: Wed, 30 Sep 2026 09:51:30 UTC
The branch main has been updated by christos:

URL: https://cgit.FreeBSD.org/src/commit/?id=d38ef1aca969f9a79572958c06bc4a4e03f053c2

commit d38ef1aca969f9a79572958c06bc4a4e03f053c2
Author:     Christos Margiolis <christos@FreeBSD.org>
AuthorDate: 2026-09-30 09:50:41 +0000
Commit:     Christos Margiolis <christos@FreeBSD.org>
CommitDate: 2026-09-30 09:50:41 +0000

    cuse: Implement hot-unload
    
    cuse_kern_uninit() can hang on destroy_dev(), because of threads
    sleeping in CUSE_IOCTL_GET_COMMAND, so implement d_purge to wake them up
    before calling destroy_dev(). Also do not allow threads to go back to
    sleep if the is_closing flag has been set.
    
    MFC after:      1 week
    Sponsored by:   The FreeBSD Foundation
    Reviewed by:    kib
    Differential Revision:  https://reviews.freebsd.org/D60022
---
 sys/fs/cuse/cuse.c | 92 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 59 insertions(+), 33 deletions(-)

diff --git a/sys/fs/cuse/cuse.c b/sys/fs/cuse/cuse.c
index 0bd8cc0d5dac..081fe1fe0bad 100644
--- a/sys/fs/cuse/cuse.c
+++ b/sys/fs/cuse/cuse.c
@@ -209,6 +209,7 @@ static d_read_t cuse_server_read;
 static d_write_t cuse_server_write;
 static d_poll_t cuse_server_poll;
 static d_mmap_single_t cuse_server_mmap_single;
+static d_purge_t cuse_server_purge;
 
 static struct cdevsw cuse_server_devsw = {
 	.d_version = D_VERSION,
@@ -221,6 +222,7 @@ static struct cdevsw cuse_server_devsw = {
 	.d_write = cuse_server_write,
 	.d_poll = cuse_server_poll,
 	.d_mmap_single = cuse_server_mmap_single,
+	.d_purge = cuse_server_purge,
 };
 
 static void cuse_client_is_closing(struct cuse_client *);
@@ -268,33 +270,6 @@ cuse_cmd_unlock(struct cuse_client_command *pccmd)
 	sx_xunlock(&pccmd->sx);
 }
 
-static int
-cuse_kern_init(void)
-{
-	TAILQ_INIT(&cuse_server_head);
-
-	mtx_init(&cuse_global_mtx, "cuse-global-mtx", NULL, MTX_DEF);
-
-	cuse_dev = make_dev_credf(MAKEDEV_CHECKNAME,
-	    &cuse_server_devsw, 0, NULL, UID_ROOT, GID_OPERATOR, 0600, "cuse");
-	if (cuse_dev == NULL)
-		return (ENODEV);
-
-	return (0);
-}
-
-static void
-cuse_kern_uninit(void)
-{
-	/* destroy_dev() runs the cdevpriv destructor of every open instance. */
-	if (cuse_dev != NULL)
-		destroy_dev(cuse_dev);
-
-	MPASS(TAILQ_EMPTY(&cuse_server_head));
-
-	mtx_destroy(&cuse_global_mtx);
-}
-
 static int
 cuse_server_get(struct cuse_server **ppcs)
 {
@@ -667,19 +642,24 @@ cuse_server_unref(struct cuse_server *pcs)
 {
 	struct cuse_memory *mem;
 
+	/*
+	 * Take the global lock before the server lock, to avoid a lock order
+	 * reversal in cuse_kern_uninit().
+	 */
+	cuse_global_lock();
 	cuse_server_lock(pcs);
 	if (--(pcs->refs) != 0) {
 		cuse_server_unlock(pcs);
+		cuse_global_unlock();
 		return;
 	}
+	TAILQ_REMOVE(&cuse_server_head, pcs, entry);
+	cuse_global_unlock();
+
 	cuse_server_is_closing(pcs);
 	/* final client wakeup, if any */
 	cuse_server_wakeup_all_client_locked(pcs);
 
-	cuse_global_lock();
-	TAILQ_REMOVE(&cuse_server_head, pcs, entry);
-	cuse_global_unlock();
-
 	/* The cdevpriv destructor destroys the devices before unreffing. */
 	MPASS(TAILQ_EMPTY(&pcs->hdev));
 
@@ -1083,10 +1063,10 @@ cuse_server_ioctl(struct cdev *dev, unsigned long cmd,
 		cuse_server_lock(pcs);
 
 		while ((pccmd = TAILQ_FIRST(&pcs->head)) == NULL) {
-			error = cv_wait_sig(&pcs->cv, &pcs->mtx);
-
 			if (pcs->is_closing)
 				error = ENXIO;
+			else
+				error = cv_wait_sig(&pcs->cv, &pcs->mtx);
 
 			if (error) {
 				cuse_server_unlock(pcs);
@@ -1412,6 +1392,25 @@ cuse_server_mmap_single(struct cdev *dev, vm_ooffset_t *offset,
 	return (cuse_common_mmap_single(pcs, offset, size, object));
 }
 
+static void
+cuse_server_purge(struct cdev *dev __unused)
+{
+	struct cuse_server *pcs;
+
+	/*
+	 * Wake up the servers sleeping in CUSE_IOCTL_GET_COMMAND, so that
+	 * destroy_dev() can return.
+	 */
+	cuse_global_lock();
+	TAILQ_FOREACH(pcs, &cuse_server_head, entry) {
+		cuse_server_lock(pcs);
+		cuse_server_is_closing(pcs);
+		cv_broadcast(&pcs->cv);
+		cuse_server_unlock(pcs);
+	}
+	cuse_global_unlock();
+}
+
 /*------------------------------------------------------------------------*
  *	CUSE CLIENT PART
  *------------------------------------------------------------------------*/
@@ -2026,6 +2025,33 @@ cuse_client_kqfilter(struct cdev *dev, struct knote *kn)
 	return (error);
 }
 
+static int
+cuse_kern_init(void)
+{
+	TAILQ_INIT(&cuse_server_head);
+
+	mtx_init(&cuse_global_mtx, "cuse-global-mtx", NULL, MTX_DEF);
+
+	cuse_dev = make_dev_credf(MAKEDEV_CHECKNAME,
+	    &cuse_server_devsw, 0, NULL, UID_ROOT, GID_OPERATOR, 0600, "cuse");
+	if (cuse_dev == NULL)
+		return (ENODEV);
+
+	return (0);
+}
+
+static void
+cuse_kern_uninit(void)
+{
+	/* destroy_dev() runs the cdevpriv destructor of every open instance. */
+	if (cuse_dev != NULL)
+		destroy_dev(cuse_dev);
+
+	MPASS(TAILQ_EMPTY(&cuse_server_head));
+
+	mtx_destroy(&cuse_global_mtx);
+}
+
 static int
 cuse_modevent(module_t mod, int type, void *data)
 {