git: adbf7727b3a2 - main - virtio_random(8): avoid deadlock at shutdown time

From: Eugene Grosbein <eugen_at_FreeBSD.org>
Date: Wed, 16 Mar 2022 04:53:03 UTC
The branch main has been updated by eugen:

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

commit adbf7727b3a2aad3c2faa6e543ee7fa7a6c9a3d5
Author:     Eugene Grosbein <eugen@FreeBSD.org>
AuthorDate: 2022-03-16 04:41:51 +0000
Commit:     Eugene Grosbein <eugen@FreeBSD.org>
CommitDate: 2022-03-16 04:41:51 +0000

    virtio_random(8): avoid deadlock at shutdown time
    
    FreeBSD 13+ running as virtual guest may load virtio_random(8) driver
    by means of devd(8) unless the driver is blacklisted or disabled
    via device.hints(5). Currently, the driver may prevent
    the system from rebooting or shutting down correctly.
    
    This change deactivates virtio_random at very late stage
    during system shutdown sequence to avoid deadlock
    that results in kernel hang.
    
    PR:             253175
    Tested by:      tom
    MFC after:      3 days
---
 sys/dev/virtio/random/virtio_random.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/sys/dev/virtio/random/virtio_random.c b/sys/dev/virtio/random/virtio_random.c
index a8553ecab287..a95dcadcddcd 100644
--- a/sys/dev/virtio/random/virtio_random.c
+++ b/sys/dev/virtio/random/virtio_random.c
@@ -32,6 +32,8 @@
 __FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
+#include <sys/types.h>
+#include <sys/eventhandler.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
 #include <sys/module.h>
@@ -52,6 +54,8 @@ struct vtrnd_softc {
 	device_t		 vtrnd_dev;
 	uint64_t		 vtrnd_features;
 	struct virtqueue	*vtrnd_vq;
+	eventhandler_tag	 eh;
+	bool			 inactive;
 };
 
 static int	vtrnd_modevent(module_t, int, void *);
@@ -59,6 +63,7 @@ static int	vtrnd_modevent(module_t, int, void *);
 static int	vtrnd_probe(device_t);
 static int	vtrnd_attach(device_t);
 static int	vtrnd_detach(device_t);
+static int	vtrnd_shutdown(device_t);
 
 static int	vtrnd_negotiate_features(struct vtrnd_softc *);
 static int	vtrnd_setup_features(struct vtrnd_softc *);
@@ -86,6 +91,7 @@ static device_method_t vtrnd_methods[] = {
 	DEVMETHOD(device_probe,		vtrnd_probe),
 	DEVMETHOD(device_attach,	vtrnd_attach),
 	DEVMETHOD(device_detach,	vtrnd_detach),
+	DEVMETHOD(device_shutdown,	vtrnd_shutdown),
 
 	DEVMETHOD_END
 };
@@ -160,6 +166,16 @@ vtrnd_attach(device_t dev)
 		error = EEXIST;
 		goto fail;
 	}
+
+	sc->eh = EVENTHANDLER_REGISTER(shutdown_post_sync,
+		vtrnd_shutdown, dev, SHUTDOWN_PRI_LAST + 1); /* ??? */
+	if (sc->eh == NULL) {
+		device_printf(dev, "Shutdown event registration failed\n");
+		error = ENXIO;
+		goto fail;
+	}
+
+	sc->inactive = false;
 	random_source_register(&random_vtrnd);
 
 fail:
@@ -179,11 +195,27 @@ vtrnd_detach(device_t dev)
 	    atomic_load_explicit(&g_vtrnd_softc, memory_order_acquire) == sc,
 	    ("only one global instance at a time"));
 
+	sc->inactive = true;
+	if (sc->eh != NULL) {
+		EVENTHANDLER_DEREGISTER(shutdown_post_sync, sc->eh);
+		sc->eh = NULL;
+	}
 	random_source_deregister(&random_vtrnd);
 	atomic_store_explicit(&g_vtrnd_softc, NULL, memory_order_release);
 	return (0);
 }
 
+static int
+vtrnd_shutdown(device_t dev)
+{
+	struct vtrnd_softc *sc;
+
+	sc = device_get_softc(dev);
+	sc->inactive = true;
+
+	return(0);
+}
+
 static int
 vtrnd_negotiate_features(struct vtrnd_softc *sc)
 {
@@ -235,6 +267,9 @@ vtrnd_harvest(struct vtrnd_softc *sc, void *buf, size_t *sz)
 
 	_Static_assert(sizeof(value) < PAGE_SIZE, "sglist assumption");
 
+	if (sc->inactive)
+		return (EDEADLK);
+
 	sglist_init(&sg, 1, segs);
 	error = sglist_append(&sg, value, *sz);
 	if (error != 0)