Module Name:    src
Committed By:   riastradh
Date:           Sun Dec 19 12:28:13 UTC 2021

Modified Files:
        src/sys/external/bsd/drm2/amdgpu: amdgpu_pci.c
        src/sys/external/bsd/drm2/i915drm: i915_pci_autoconf.c
        src/sys/external/bsd/drm2/nouveau: nouveau_pci.c
        src/sys/external/bsd/drm2/radeon: radeon_pci.c

Log Message:
drm: Rework attach/detach and deferred task logic.

- Reduce the number of states the softc can be in.
- Fix races between attach and other threads.


To generate a diff of this commit:
cvs rdiff -u -r1.9 -r1.10 src/sys/external/bsd/drm2/amdgpu/amdgpu_pci.c
cvs rdiff -u -r1.9 -r1.10 \
    src/sys/external/bsd/drm2/i915drm/i915_pci_autoconf.c
cvs rdiff -u -r1.33 -r1.34 src/sys/external/bsd/drm2/nouveau/nouveau_pci.c
cvs rdiff -u -r1.19 -r1.20 src/sys/external/bsd/drm2/radeon/radeon_pci.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/external/bsd/drm2/amdgpu/amdgpu_pci.c
diff -u src/sys/external/bsd/drm2/amdgpu/amdgpu_pci.c:1.9 src/sys/external/bsd/drm2/amdgpu/amdgpu_pci.c:1.10
--- src/sys/external/bsd/drm2/amdgpu/amdgpu_pci.c:1.9	Sun Dec 19 12:21:29 2021
+++ src/sys/external/bsd/drm2/amdgpu/amdgpu_pci.c	Sun Dec 19 12:28:12 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: amdgpu_pci.c,v 1.9 2021/12/19 12:21:29 riastradh Exp $	*/
+/*	$NetBSD: amdgpu_pci.c,v 1.10 2021/12/19 12:28:12 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -30,9 +30,10 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: amdgpu_pci.c,v 1.9 2021/12/19 12:21:29 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: amdgpu_pci.c,v 1.10 2021/12/19 12:28:12 riastradh Exp $");
 
 #include <sys/types.h>
+#include <sys/atomic.h>
 #include <sys/queue.h>
 #include <sys/systm.h>
 #include <sys/workqueue.h>
@@ -57,14 +58,9 @@ SIMPLEQ_HEAD(amdgpu_task_head, amdgpu_ta
 struct amdgpu_softc {
 	device_t			sc_dev;
 	struct pci_attach_args		sc_pa;
-	enum {
-		AMDGPU_TASK_ATTACH,
-		AMDGPU_TASK_WORKQUEUE,
-	}				sc_task_state;
-	union {
-		struct workqueue		*workqueue;
-		struct amdgpu_task_head		attach;
-	}				sc_task_u;
+	struct lwp			*sc_task_thread;
+	struct amdgpu_task_head		sc_tasks;
+	struct workqueue		*sc_task_wq;
 	struct drm_device		*sc_drm_dev;
 	struct pci_dev			sc_pci_dev;
 	bool				sc_pci_attached;
@@ -135,20 +131,30 @@ amdgpu_attach(device_t parent, device_t 
 {
 	struct amdgpu_softc *const sc = device_private(self);
 	const struct pci_attach_args *const pa = aux;
+	int error;
 
 	pci_aprint_devinfo(pa, NULL);
 
-	if (!pmf_device_register(self, &amdgpu_do_suspend, &amdgpu_do_resume))
-		aprint_error_dev(self, "unable to establish power handler\n");
+	/* Initialize the Linux PCI device descriptor.  */
+	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
 
-	/*
-	 * Trivial initialization first; the rest will come after we
-	 * have mounted the root file system and can load firmware
-	 * images.
-	 */
-	sc->sc_dev = NULL;
+	sc->sc_dev = self;
 	sc->sc_pa = *pa;
+	sc->sc_task_thread = NULL;
+	SIMPLEQ_INIT(&sc->sc_tasks);
+	error = workqueue_create(&sc->sc_task_wq, "amdgpufb",
+	    &amdgpu_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
+	if (error) {
+		aprint_error_dev(self, "unable to create workqueue: %d\n",
+		    error);
+		sc->sc_task_wq = NULL;
+		return;
+	}
 
+	/*
+	 * Defer the remainder of initialization until we have mounted
+	 * the root file system and can load firmware images.
+	 */
 	config_mountroot(self, &amdgpu_attach_real);
 }
 
@@ -164,11 +170,11 @@ amdgpu_attach_real(device_t self)
 	ok = amdgpu_pci_lookup(pa, &flags);
 	KASSERT(ok);
 
-	sc->sc_task_state = AMDGPU_TASK_ATTACH;
-	SIMPLEQ_INIT(&sc->sc_task_u.attach);
-
-	/* Initialize the Linux PCI device descriptor.  */
-	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
+	/*
+	 * Cause any tasks issued synchronously during attach to be
+	 * processed at the end of this function.
+	 */
+	sc->sc_task_thread = curlwp;
 
 	sc->sc_drm_dev = drm_dev_alloc(amdgpu_drm_driver, self);
 	if (IS_ERR(sc->sc_drm_dev)) {
@@ -190,29 +196,29 @@ amdgpu_attach_real(device_t self)
 	error = -drm_dev_register(sc->sc_drm_dev, flags);
 	if (error) {
 		aprint_error_dev(self, "unable to register drm: %d\n", error);
-		return;
+		goto out;
 	}
 	sc->sc_dev_registered = true;
 
-	while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) {
-		struct amdgpu_task *const task =
-		    SIMPLEQ_FIRST(&sc->sc_task_u.attach);
+	if (!pmf_device_register(self, &amdgpu_do_suspend, &amdgpu_do_resume))
+		aprint_error_dev(self, "unable to establish power handler\n");
 
-		SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, rt_u.queue);
-		(*task->rt_fn)(task);
-	}
+	/*
+	 * Process asynchronous tasks queued synchronously during
+	 * attach.  This will be for display detection to attach a
+	 * framebuffer, so we have the opportunity for a console device
+	 * to attach before autoconf has completed, in time for init(8)
+	 * to find that console without panicking.
+	 */
+	while (!SIMPLEQ_EMPTY(&sc->sc_tasks)) {
+		struct amdgpu_task *const task = SIMPLEQ_FIRST(&sc->sc_tasks);
 
-	sc->sc_task_state = AMDGPU_TASK_WORKQUEUE;
-	error = workqueue_create(&sc->sc_task_u.workqueue, "amdgpufb",
-	    &amdgpu_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
-	if (error) {
-		aprint_error_dev(self, "unable to create workqueue: %d\n",
-		    error);
-		sc->sc_task_u.workqueue = NULL;
-		goto out;
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_tasks, rt_u.queue);
+		(*task->rt_fn)(task);
 	}
 
-out:	sc->sc_dev = self;
+out:	/* Cause any subesquent tasks to be processed by the workqueue.  */
+	atomic_store_relaxed(&sc->sc_task_thread, NULL);
 }
 
 static int
@@ -221,35 +227,29 @@ amdgpu_detach(device_t self, int flags)
 	struct amdgpu_softc *const sc = device_private(self);
 	int error;
 
-	if (sc->sc_dev == NULL)
-		/* Not done attaching.  */
-		return EBUSY;
-
 	/* XXX Check for in-use before tearing it all down...  */
 	error = config_detach_children(self, flags);
 	if (error)
 		return error;
 
-	if (sc->sc_task_state == AMDGPU_TASK_ATTACH)
-		goto out0;
-	if (sc->sc_task_u.workqueue != NULL) {
-		workqueue_destroy(sc->sc_task_u.workqueue);
-		sc->sc_task_u.workqueue = NULL;
+	KASSERT(sc->sc_task_thread == NULL);
+	KASSERT(SIMPLEQ_EMPTY(&sc->sc_tasks));
+
+	pmf_device_deregister(self);
+	if (sc->sc_dev_registered)
+		drm_dev_unregister(sc->sc_drm_dev);
+	if (sc->sc_pci_attached)
+		drm_pci_detach(sc->sc_drm_dev);
+	if (sc->sc_drm_dev) {
+		drm_dev_put(sc->sc_drm_dev);
+		sc->sc_drm_dev = NULL;
 	}
+	if (sc->sc_task_wq) {
+		workqueue_destroy(sc->sc_task_wq);
+		sc->sc_task_wq = NULL;
+	}
+	linux_pci_dev_destroy(&sc->sc_pci_dev);
 
-	if (sc->sc_drm_dev == NULL)
-		goto out1;
-	if (!sc->sc_pci_attached)
-		goto out2;
-	if (!sc->sc_dev_registered)
-		goto out3;
-
-	drm_dev_unregister(sc->sc_drm_dev);
-out3:	drm_pci_detach(sc->sc_drm_dev);
-out2:	drm_dev_put(sc->sc_drm_dev);
-	sc->sc_drm_dev = NULL;
-out1:	linux_pci_dev_destroy(&sc->sc_pci_dev);
-out0:	pmf_device_deregister(self);
 	return 0;
 }
 
@@ -260,9 +260,6 @@ amdgpu_do_suspend(device_t self, const p
 	struct drm_device *const dev = sc->sc_drm_dev;
 	int ret;
 
-	if (dev == NULL)
-		return true;
-
 	ret = amdgpu_device_suspend(dev, /*fbcon*/true);
 	if (ret)
 		return false;
@@ -277,9 +274,6 @@ amdgpu_do_resume(device_t self, const pm
 	struct drm_device *const dev = sc->sc_drm_dev;
 	int ret;
 
-	if (dev == NULL)
-		return true;
-
 	ret = amdgpu_device_resume(dev, /*fbcon*/true);
 	if (ret)
 		return false;
@@ -301,20 +295,10 @@ amdgpu_task_schedule(device_t self, stru
 {
 	struct amdgpu_softc *const sc = device_private(self);
 
-	switch (sc->sc_task_state) {
-	case AMDGPU_TASK_ATTACH:
-		SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, rt_u.queue);
-		return 0;
-	case AMDGPU_TASK_WORKQUEUE:
-		if (sc->sc_task_u.workqueue == NULL) {
-			aprint_error_dev(self, "unable to schedule task\n");
-			return EIO;
-		}
-		workqueue_enqueue(sc->sc_task_u.workqueue, &task->rt_u.work,
-		    NULL);
-		return 0;
-	default:
-		panic("amdgpu in invalid task state: %d\n",
-		    (int)sc->sc_task_state);
-	}
+	if (atomic_load_relaxed(&sc->sc_task_thread) == curlwp)
+		SIMPLEQ_INSERT_TAIL(&sc->sc_tasks, task, rt_u.queue);
+	else
+		workqueue_enqueue(sc->sc_task_wq, &task->rt_u.work, NULL);
+
+	return 0;
 }

Index: src/sys/external/bsd/drm2/i915drm/i915_pci_autoconf.c
diff -u src/sys/external/bsd/drm2/i915drm/i915_pci_autoconf.c:1.9 src/sys/external/bsd/drm2/i915drm/i915_pci_autoconf.c:1.10
--- src/sys/external/bsd/drm2/i915drm/i915_pci_autoconf.c:1.9	Sun Dec 19 11:54:10 2021
+++ src/sys/external/bsd/drm2/i915drm/i915_pci_autoconf.c	Sun Dec 19 12:28:12 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: i915_pci_autoconf.c,v 1.9 2021/12/19 11:54:10 riastradh Exp $	*/
+/*	$NetBSD: i915_pci_autoconf.c,v 1.10 2021/12/19 12:28:12 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -30,9 +30,10 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: i915_pci_autoconf.c,v 1.9 2021/12/19 11:54:10 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: i915_pci_autoconf.c,v 1.10 2021/12/19 12:28:12 riastradh Exp $");
 
 #include <sys/types.h>
+#include <sys/atomic.h>
 #include <sys/queue.h>
 #include <sys/systm.h>
 #include <sys/queue.h>
@@ -50,18 +51,11 @@ SIMPLEQ_HEAD(i915drmkms_task_head, i915d
 struct i915drmkms_softc {
 	device_t			sc_dev;
 	struct pci_attach_args		sc_pa;
-	enum {
-		I915DRMKMS_TASK_ATTACH,
-		I915DRMKMS_TASK_WORKQUEUE,
-	}				sc_task_state;
-	union {
-		struct workqueue		*workqueue;
-		struct i915drmkms_task_head	attach;
-	}				sc_task_u;
+	struct lwp			*sc_task_thread;
+	struct i915drmkms_task_head	sc_tasks;
+	struct workqueue		*sc_task_wq;
 	struct drm_device		*sc_drm_dev;
 	struct pci_dev			sc_pci_dev;
-	bool				sc_pci_attached;
-	bool				sc_dev_registered;
 };
 
 static const struct pci_device_id *
@@ -144,21 +138,30 @@ i915drmkms_attach(device_t parent, devic
 {
 	struct i915drmkms_softc *const sc = device_private(self);
 	const struct pci_attach_args *const pa = aux;
+	int error;
 
 	pci_aprint_devinfo(pa, NULL);
 
-	if (!pmf_device_register(self, &i915drmkms_suspend,
-		&i915drmkms_resume))
-		aprint_error_dev(self, "unable to establish power handler\n");
+	/* Initialize the Linux PCI device descriptor.  */
+	linux_pci_dev_init(&sc->sc_pci_dev, self, parent, pa, 0);
 
-	/*
-	 * Trivial initialization first; the rest will come after we
-	 * have mounted the root file system and can load firmware
-	 * images.
-	 */
 	sc->sc_dev = self;
 	sc->sc_pa = *pa;
+	sc->sc_task_thread = NULL;
+	SIMPLEQ_INIT(&sc->sc_tasks);
+	error = workqueue_create(&sc->sc_task_wq, "intelfb",
+	    &i915drmkms_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
+	if (error) {
+		aprint_error_dev(self, "unable to create workqueue: %d\n",
+		    error);
+		sc->sc_task_wq = NULL;
+		return;
+	}
 
+	/*
+	 * Defer the remainder of initialization until we have mounted
+	 * the root file system and can load firmware images.
+	 */
 	config_mountroot(self, &i915drmkms_attach_real);
 }
 
@@ -173,38 +176,46 @@ i915drmkms_attach_real(device_t self)
 
 	KASSERT(info != NULL);
 
-	sc->sc_task_state = I915DRMKMS_TASK_ATTACH;
-	SIMPLEQ_INIT(&sc->sc_task_u.attach);
-
-	/* Initialize the Linux PCI device descriptor.  */
-	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
+	/*
+	 * Cause any tasks issued synchronously during attach to be
+	 * processed at the end of this function.
+	 */
+	sc->sc_task_thread = curlwp;
 
+	/* Attach the drm driver.  */
 	/* XXX errno Linux->NetBSD */
 	error = -i915_driver_probe(&sc->sc_pci_dev, ent);
 	if (error) {
 		aprint_error_dev(self, "unable to register drm: %d\n", error);
 		return;
 	}
-	sc->sc_dev_registered = true;
 	sc->sc_drm_dev = pci_get_drvdata(&sc->sc_pci_dev);
 
-	while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) {
+	/*
+	 * Now that the drm driver is attached, we can safely suspend
+	 * and resume.
+	 */
+	if (!pmf_device_register(self, &i915drmkms_suspend,
+		&i915drmkms_resume))
+		aprint_error_dev(self, "unable to establish power handler\n");
+
+	/*
+	 * Process asynchronous tasks queued synchronously during
+	 * attach.  This will be for display detection to attach a
+	 * framebuffer, so we have the opportunity for a console device
+	 * to attach before autoconf has completed, in time for init(8)
+	 * to find that console without panicking.
+	 */
+	while (!SIMPLEQ_EMPTY(&sc->sc_tasks)) {
 		struct i915drmkms_task *const task =
-		    SIMPLEQ_FIRST(&sc->sc_task_u.attach);
+		    SIMPLEQ_FIRST(&sc->sc_tasks);
 
-		SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, ift_u.queue);
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_tasks, ift_u.queue);
 		(*task->ift_fn)(task);
 	}
 
-	sc->sc_task_state = I915DRMKMS_TASK_WORKQUEUE;
-	error = workqueue_create(&sc->sc_task_u.workqueue, "intelfb",
-	    &i915drmkms_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
-	if (error) {
-		aprint_error_dev(self, "unable to create workqueue: %d\n",
-		    error);
-		sc->sc_task_u.workqueue = NULL;
-		return;
-	}
+	/* Cause any subesquent tasks to be processed by the workqueue.  */
+	atomic_store_relaxed(&sc->sc_task_thread, NULL);
 }
 
 static int
@@ -218,20 +229,20 @@ i915drmkms_detach(device_t self, int fla
 	if (error)
 		return error;
 
-	if (sc->sc_task_state == I915DRMKMS_TASK_ATTACH)
-		goto out0;
-	if (sc->sc_task_u.workqueue != NULL) {
-		workqueue_destroy(sc->sc_task_u.workqueue);
-		sc->sc_task_u.workqueue = NULL;
-	}
-
-	if (sc->sc_drm_dev == NULL)
-		goto out0;
+	KASSERT(sc->sc_task_thread == NULL);
+	KASSERT(SIMPLEQ_EMPTY(&sc->sc_tasks));
 
-	i915_driver_remove(sc->sc_drm_dev->dev_private);
-	sc->sc_drm_dev = NULL;
-out0:	linux_pci_dev_destroy(&sc->sc_pci_dev);
 	pmf_device_deregister(self);
+	if (sc->sc_drm_dev) {
+		i915_driver_remove(sc->sc_drm_dev->dev_private);
+		sc->sc_drm_dev = NULL;
+	}
+	if (sc->sc_task_wq) {
+		workqueue_destroy(sc->sc_task_wq);
+		sc->sc_task_wq = NULL;
+	}
+	linux_pci_dev_destroy(&sc->sc_pci_dev);
+
 	return 0;
 }
 
@@ -242,9 +253,6 @@ i915drmkms_suspend(device_t self, const 
 	struct drm_device *const dev = sc->sc_drm_dev;
 	int ret;
 
-	if (dev == NULL)
-		return true;
-
 	ret = i915_drm_suspend(dev);
 	if (ret)
 		return false;
@@ -262,9 +270,6 @@ i915drmkms_resume(device_t self, const p
 	struct drm_device *const dev = sc->sc_drm_dev;
 	int ret;
 
-	if (dev == NULL)
-		return true;
-
 	ret = i915_drm_resume_early(dev);
 	if (ret)
 		return false;
@@ -289,20 +294,10 @@ i915drmkms_task_schedule(device_t self, 
 {
 	struct i915drmkms_softc *const sc = device_private(self);
 
-	switch (sc->sc_task_state) {
-	case I915DRMKMS_TASK_ATTACH:
-		SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, ift_u.queue);
-		return 0;
-	case I915DRMKMS_TASK_WORKQUEUE:
-		if (sc->sc_task_u.workqueue == NULL) {
-			aprint_error_dev(self, "unable to schedule task\n");
-			return EIO;
-		}
-		workqueue_enqueue(sc->sc_task_u.workqueue, &task->ift_u.work,
-		    NULL);
-		return 0;
-	default:
-		panic("i915drmkms in invalid task state: %d\n",
-		    (int)sc->sc_task_state);
-	}
+	if (atomic_load_relaxed(&sc->sc_task_thread) == curlwp)
+		SIMPLEQ_INSERT_TAIL(&sc->sc_tasks, task, ift_u.queue);
+	else
+		workqueue_enqueue(sc->sc_task_wq, &task->ift_u.work, NULL);
+
+	return 0;
 }

Index: src/sys/external/bsd/drm2/nouveau/nouveau_pci.c
diff -u src/sys/external/bsd/drm2/nouveau/nouveau_pci.c:1.33 src/sys/external/bsd/drm2/nouveau/nouveau_pci.c:1.34
--- src/sys/external/bsd/drm2/nouveau/nouveau_pci.c:1.33	Sun Dec 19 11:53:51 2021
+++ src/sys/external/bsd/drm2/nouveau/nouveau_pci.c	Sun Dec 19 12:28:12 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: nouveau_pci.c,v 1.33 2021/12/19 11:53:51 riastradh Exp $	*/
+/*	$NetBSD: nouveau_pci.c,v 1.34 2021/12/19 12:28:12 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2015 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nouveau_pci.c,v 1.33 2021/12/19 11:53:51 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nouveau_pci.c,v 1.34 2021/12/19 12:28:12 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #if defined(__arm__) || defined(__aarch64__)
@@ -39,6 +39,7 @@ __KERNEL_RCSID(0, "$NetBSD: nouveau_pci.
 #endif
 
 #include <sys/types.h>
+#include <sys/atomic.h>
 #include <sys/device.h>
 #include <sys/queue.h>
 #include <sys/workqueue.h>
@@ -65,18 +66,14 @@ SIMPLEQ_HEAD(nouveau_pci_task_head, nouv
 struct nouveau_pci_softc {
 	device_t		sc_dev;
 	struct pci_attach_args	sc_pa;
-	enum {
-		NOUVEAU_TASK_ATTACH,
-		NOUVEAU_TASK_WORKQUEUE,
-	}			sc_task_state;
-	union {
-		struct workqueue		*workqueue;
-		struct nouveau_pci_task_head	attach;
-	}			sc_task_u;
+	struct lwp		*sc_task_thread;
+	struct nouveau_pci_task_head sc_tasks;
+	struct workqueue	*sc_task_wq;
 	struct drm_device	*sc_drm_dev;
 	struct pci_dev		sc_pci_dev;
 	struct nvkm_device	*sc_nv_dev;
 	bool			sc_pci_attached;
+	bool			sc_nvdev_inited;
 	bool			sc_dev_registered;
 };
 
@@ -154,19 +151,25 @@ nouveau_pci_attach(device_t parent, devi
 {
 	struct nouveau_pci_softc *const sc = device_private(self);
 	const struct pci_attach_args *const pa = aux;
+	int error;
 
 	pci_aprint_devinfo(pa, NULL);
 
-	if (!pmf_device_register(self, &nouveau_pci_suspend, &nouveau_pci_resume))
-		aprint_error_dev(self, "unable to establish power handler\n");
+	/* Initialize the Linux PCI device descriptor.  */
+	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
 
-	/*
-	 * Trivial initialization first; the rest will come after we
-	 * have mounted the root file system and can load firmware
-	 * images.
-	 */
-	sc->sc_dev = NULL;
+	sc->sc_dev = self;
 	sc->sc_pa = *pa;
+	sc->sc_task_thread = NULL;
+	SIMPLEQ_INIT(&sc->sc_tasks);
+	error = workqueue_create(&sc->sc_task_wq, "nouveau_pci",
+	    &nouveau_pci_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
+	if (error) {
+		aprint_error_dev(self, "unable to create workqueue: %d\n",
+		    error);
+		sc->sc_task_wq = NULL;
+		return;
+	}
 
 #ifdef FDT
 	/*
@@ -177,6 +180,10 @@ nouveau_pci_attach(device_t parent, devi
 	fdt_remove_bycompat(fb_compatible);
 #endif
 
+	/*
+	 * Defer the remainder of initialization until we have mounted
+	 * the root file system and can load firmware images.
+	 */
 	config_mountroot(self, &nouveau_pci_attach_real);
 }
 
@@ -184,15 +191,13 @@ static void
 nouveau_pci_attach_real(device_t self)
 {
 	struct nouveau_pci_softc *const sc = device_private(self);
-	const struct pci_attach_args *const pa = &sc->sc_pa;
 	int error;
 
-	sc->sc_dev = self;
-	sc->sc_task_state = NOUVEAU_TASK_ATTACH;
-	SIMPLEQ_INIT(&sc->sc_task_u.attach);
-
-	/* Initialize the Linux PCI device descriptor.  */
-	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
+	/*
+	 * Cause any tasks issued synchronously during attach to be
+	 * processed at the end of this function.
+	 */
+	sc->sc_task_thread = curlwp;
 
 	/* XXX errno Linux->NetBSD */
 	error = -nvkm_device_pci_new(&sc->sc_pci_dev,
@@ -203,7 +208,7 @@ nouveau_pci_attach_real(device_t self)
 		aprint_error_dev(self, "unable to create nouveau device: %d\n",
 		    error);
 		sc->sc_nv_dev = NULL;
-		return;
+		goto out;
 	}
 
 	sc->sc_drm_dev = drm_dev_alloc(nouveau_drm_driver_pci, self);
@@ -211,14 +216,14 @@ nouveau_pci_attach_real(device_t self)
 		aprint_error_dev(self, "unable to create drm device: %ld\n",
 		    PTR_ERR(sc->sc_drm_dev));
 		sc->sc_drm_dev = NULL;
-		return;
+		goto out;
 	}
 
 	/* XXX errno Linux->NetBSD */
 	error = -drm_pci_attach(sc->sc_drm_dev, &sc->sc_pci_dev);
 	if (error) {
 		aprint_error_dev(self, "unable to attach drm: %d\n", error);
-		return;
+		goto out;
 	}
 	sc->sc_pci_attached = true;
 
@@ -226,34 +231,32 @@ nouveau_pci_attach_real(device_t self)
 	error = -nouveau_drm_device_init(sc->sc_drm_dev);
 	if (error) {
 		aprint_error_dev(self, "unable to init nouveau: %d\n", error);
-		return;
+		goto out;
 	}
+	sc->sc_nvdev_inited = true;
 
 	/* XXX errno Linux->NetBSD */
 	error = -drm_dev_register(sc->sc_drm_dev, 0);
 	if (error) {
 		aprint_error_dev(self, "unable to register drm: %d\n", error);
-		return;
+		goto out;
 	}
 	sc->sc_dev_registered = true;
 
-	while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) {
+	if (!pmf_device_register(self, &nouveau_pci_suspend,
+		&nouveau_pci_resume))
+		aprint_error_dev(self, "unable to establish power handler\n");
+
+	while (!SIMPLEQ_EMPTY(&sc->sc_tasks)) {
 		struct nouveau_pci_task *const task =
-		    SIMPLEQ_FIRST(&sc->sc_task_u.attach);
+		    SIMPLEQ_FIRST(&sc->sc_tasks);
 
-		SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, nt_u.queue);
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_tasks, nt_u.queue);
 		(*task->nt_fn)(task);
 	}
 
-	sc->sc_task_state = NOUVEAU_TASK_WORKQUEUE;
-	error = workqueue_create(&sc->sc_task_u.workqueue, "nouveau_pci",
-	    &nouveau_pci_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
-	if (error) {
-		aprint_error_dev(self, "unable to create workqueue: %d\n",
-		    error);
-		sc->sc_task_u.workqueue = NULL;
-		return;
-	}
+out:	/* Cause any subesquent tasks to be processed by the workqueue.  */
+	atomic_store_relaxed(&sc->sc_task_thread, NULL);
 }
 
 static int
@@ -262,38 +265,30 @@ nouveau_pci_detach(device_t self, int fl
 	struct nouveau_pci_softc *const sc = device_private(self);
 	int error;
 
-	if (sc->sc_dev == NULL)
-		/* Not done attaching.  */
-		return EBUSY;
-
 	/* XXX Check for in-use before tearing it all down...  */
 	error = config_detach_children(self, flags);
 	if (error)
 		return error;
 
-	if (sc->sc_task_state == NOUVEAU_TASK_ATTACH)
-		goto out0;
-	if (sc->sc_task_u.workqueue != NULL) {
-		workqueue_destroy(sc->sc_task_u.workqueue);
-		sc->sc_task_u.workqueue = NULL;
+	pmf_device_deregister(self);
+	if (sc->sc_dev_registered)
+		drm_dev_unregister(sc->sc_drm_dev);
+	if (sc->sc_nvdev_inited)
+		nouveau_drm_device_fini(sc->sc_drm_dev);
+	if (sc->sc_pci_attached)
+		drm_pci_detach(sc->sc_drm_dev);
+	if (sc->sc_drm_dev) {
+		drm_dev_put(sc->sc_drm_dev);
+		sc->sc_drm_dev = NULL;
 	}
+	if (sc->sc_nv_dev)
+		nvkm_device_del(&sc->sc_nv_dev);
+	if (sc->sc_task_wq) {
+		workqueue_destroy(sc->sc_task_wq);
+		sc->sc_task_wq = NULL;
+	}
+	linux_pci_dev_destroy(&sc->sc_pci_dev);
 
-	if (sc->sc_nv_dev == NULL)
-		goto out0;
-	if (sc->sc_drm_dev == NULL)
-		goto out1;
-	if (!sc->sc_pci_attached)
-		goto out2;
-	if (!sc->sc_dev_registered)
-		goto out3;
-
-	drm_dev_unregister(sc->sc_drm_dev);
-out3:	drm_pci_detach(sc->sc_drm_dev);
-out2:	drm_dev_put(sc->sc_drm_dev);
-	sc->sc_drm_dev = NULL;
-out1:	nvkm_device_del(&sc->sc_nv_dev);
-out0:	linux_pci_dev_destroy(&sc->sc_pci_dev);
-	pmf_device_deregister(self);
 	return 0;
 }
 
@@ -330,22 +325,12 @@ nouveau_pci_task_schedule(device_t self,
 {
 	struct nouveau_pci_softc *const sc = device_private(self);
 
-	switch (sc->sc_task_state) {
-	case NOUVEAU_TASK_ATTACH:
-		SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, nt_u.queue);
-		return 0;
-	case NOUVEAU_TASK_WORKQUEUE:
-		if (sc->sc_task_u.workqueue == NULL) {
-			aprint_error_dev(self, "unable to schedule task\n");
-			return EIO;
-		}
-		workqueue_enqueue(sc->sc_task_u.workqueue, &task->nt_u.work,
-		    NULL);
-		return 0;
-	default:
-		panic("nouveau in invalid task state: %d\n",
-		    (int)sc->sc_task_state);
-	}
+	if (atomic_load_relaxed(&sc->sc_task_thread) == curlwp)
+		SIMPLEQ_INSERT_TAIL(&sc->sc_tasks, task, nt_u.queue);
+	else
+		workqueue_enqueue(sc->sc_task_wq, &task->nt_u.work, NULL);
+
+	return 0;
 }
 
 extern struct drm_driver *const nouveau_drm_driver_stub; /* XXX */

Index: src/sys/external/bsd/drm2/radeon/radeon_pci.c
diff -u src/sys/external/bsd/drm2/radeon/radeon_pci.c:1.19 src/sys/external/bsd/drm2/radeon/radeon_pci.c:1.20
--- src/sys/external/bsd/drm2/radeon/radeon_pci.c:1.19	Sun Dec 19 11:53:51 2021
+++ src/sys/external/bsd/drm2/radeon/radeon_pci.c	Sun Dec 19 12:28:12 2021
@@ -1,4 +1,4 @@
-/*	$NetBSD: radeon_pci.c,v 1.19 2021/12/19 11:53:51 riastradh Exp $	*/
+/*	$NetBSD: radeon_pci.c,v 1.20 2021/12/19 12:28:12 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: radeon_pci.c,v 1.19 2021/12/19 11:53:51 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: radeon_pci.c,v 1.20 2021/12/19 12:28:12 riastradh Exp $");
 
 #ifdef _KERNEL_OPT
 #include "vga.h"
@@ -40,6 +40,7 @@ __KERNEL_RCSID(0, "$NetBSD: radeon_pci.c
 #endif
 
 #include <sys/types.h>
+#include <sys/atomic.h>
 #include <sys/queue.h>
 #include <sys/systm.h>
 #include <sys/workqueue.h>
@@ -81,14 +82,9 @@ SIMPLEQ_HEAD(radeon_task_head, radeon_ta
 struct radeon_softc {
 	device_t			sc_dev;
 	struct pci_attach_args		sc_pa;
-	enum {
-		RADEON_TASK_ATTACH,
-		RADEON_TASK_WORKQUEUE,
-	}				sc_task_state;
-	union {
-		struct workqueue		*workqueue;
-		struct radeon_task_head		attach;
-	}				sc_task_u;
+	struct lwp			*sc_task_thread;
+	struct radeon_task_head		sc_tasks;
+	struct workqueue		*sc_task_wq;
 	struct drm_device		*sc_drm_dev;
 	struct pci_dev			sc_pci_dev;
 	bool				sc_pci_attached;
@@ -182,19 +178,25 @@ radeon_attach(device_t parent, device_t 
 {
 	struct radeon_softc *const sc = device_private(self);
 	const struct pci_attach_args *const pa = aux;
+	int error;
 
 	pci_aprint_devinfo(pa, NULL);
 
-	if (!pmf_device_register(self, &radeon_do_suspend, &radeon_do_resume))
-		aprint_error_dev(self, "unable to establish power handler\n");
+	/* Initialize the Linux PCI device descriptor.  */
+	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
 
-	/*
-	 * Trivial initialization first; the rest will come after we
-	 * have mounted the root file system and can load firmware
-	 * images.
-	 */
-	sc->sc_dev = NULL;
+	sc->sc_dev = self;
 	sc->sc_pa = *pa;
+	sc->sc_task_thread = NULL;
+	SIMPLEQ_INIT(&sc->sc_tasks);
+	error = workqueue_create(&sc->sc_task_wq, "radeonfb",
+	    &radeon_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
+	if (error) {
+		aprint_error_dev(self, "unable to create workqueue: %d\n",
+		    error);
+		sc->sc_task_wq = NULL;
+		return;
+	}
 
 #ifdef RADEON_PCI_UGLY_MAP_HACK
 	/*
@@ -221,6 +223,10 @@ radeon_attach(device_t parent, device_t 
 	fdt_remove_bycompat(fb_compatible);
 #endif
 
+	/*
+	 * Defer the remainder of initialization until we have mounted
+	 * the root file system and can load firmware images.
+	 */
 	config_mountroot(self, &radeon_attach_real);
 }
 
@@ -245,11 +251,11 @@ radeon_attach_real(device_t self)
 		bus_space_unmap(pa->pa_memt, sc->sc_temp_memh, 0x10000);
 #endif
 
-	sc->sc_task_state = RADEON_TASK_ATTACH;
-	SIMPLEQ_INIT(&sc->sc_task_u.attach);
-
-	/* Initialize the Linux PCI device descriptor.  */
-	linux_pci_dev_init(&sc->sc_pci_dev, self, device_parent(self), pa, 0);
+	/*
+	 * Cause any tasks issued synchronously during attach to be
+	 * processed at the end of this function.
+	 */
+	sc->sc_task_thread = curlwp;
 
 	sc->sc_drm_dev = drm_dev_alloc(radeon_drm_driver, self);
 	if (IS_ERR(sc->sc_drm_dev)) {
@@ -271,29 +277,29 @@ radeon_attach_real(device_t self)
 	error = -drm_dev_register(sc->sc_drm_dev, flags);
 	if (error) {
 		aprint_error_dev(self, "unable to register drm: %d\n", error);
-		return;
+		goto out;
 	}
 	sc->sc_dev_registered = true;
 
-	while (!SIMPLEQ_EMPTY(&sc->sc_task_u.attach)) {
-		struct radeon_task *const task =
-		    SIMPLEQ_FIRST(&sc->sc_task_u.attach);
+	if (!pmf_device_register(self, &radeon_do_suspend, &radeon_do_resume))
+		aprint_error_dev(self, "unable to establish power handler\n");
 
-		SIMPLEQ_REMOVE_HEAD(&sc->sc_task_u.attach, rt_u.queue);
-		(*task->rt_fn)(task);
-	}
+	/*
+	 * Process asynchronous tasks queued synchronously during
+	 * attach.  This will be for display detection to attach a
+	 * framebuffer, so we have the opportunity for a console device
+	 * to attach before autoconf has completed, in time for init(8)
+	 * to find that console without panicking.
+	 */
+	while (!SIMPLEQ_EMPTY(&sc->sc_tasks)) {
+		struct radeon_task *const task = SIMPLEQ_FIRST(&sc->sc_tasks);
 
-	sc->sc_task_state = RADEON_TASK_WORKQUEUE;
-	error = workqueue_create(&sc->sc_task_u.workqueue, "radeonfb",
-	    &radeon_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE);
-	if (error) {
-		aprint_error_dev(self, "unable to create workqueue: %d\n",
-		    error);
-		sc->sc_task_u.workqueue = NULL;
-		goto out;
+		SIMPLEQ_REMOVE_HEAD(&sc->sc_tasks, rt_u.queue);
+		(*task->rt_fn)(task);
 	}
 
-out:	sc->sc_dev = self;
+out:	/* Cause any subesquent tasks to be processed by the workqueue.  */
+	atomic_store_relaxed(&sc->sc_task_thread, NULL);
 }
 
 static int
@@ -302,35 +308,29 @@ radeon_detach(device_t self, int flags)
 	struct radeon_softc *const sc = device_private(self);
 	int error;
 
-	if (sc->sc_dev == NULL)
-		/* Not done attaching.  */
-		return EBUSY;
-
 	/* XXX Check for in-use before tearing it all down...  */
 	error = config_detach_children(self, flags);
 	if (error)
 		return error;
 
-	if (sc->sc_task_state == RADEON_TASK_ATTACH)
-		goto out0;
-	if (sc->sc_task_u.workqueue != NULL) {
-		workqueue_destroy(sc->sc_task_u.workqueue);
-		sc->sc_task_u.workqueue = NULL;
-	}
+	KASSERT(sc->sc_task_thread == NULL);
+	KASSERT(SIMPLEQ_EMPTY(&sc->sc_tasks));
 
-	if (sc->sc_drm_dev == NULL)
-		goto out0;
-	if (!sc->sc_pci_attached)
-		goto out1;
-	if (!sc->sc_dev_registered)
-		goto out2;
-
-	drm_dev_unregister(sc->sc_drm_dev);
-out2:	drm_pci_detach(sc->sc_drm_dev);
-out1:	drm_dev_put(sc->sc_drm_dev);
-	sc->sc_drm_dev = NULL;
-out0:	linux_pci_dev_destroy(&sc->sc_pci_dev);
 	pmf_device_deregister(self);
+	if (sc->sc_dev_registered)
+		drm_dev_unregister(sc->sc_drm_dev);
+	if (sc->sc_pci_attached)
+		drm_pci_detach(sc->sc_drm_dev);
+	if (sc->sc_drm_dev) {
+		drm_dev_put(sc->sc_drm_dev);
+		sc->sc_drm_dev = NULL;
+	}
+	if (sc->sc_task_wq) {
+		workqueue_destroy(sc->sc_task_wq);
+		sc->sc_task_wq = NULL;
+	}
+	linux_pci_dev_destroy(&sc->sc_pci_dev);
+
 	return 0;
 }
 
@@ -342,9 +342,6 @@ radeon_do_suspend(device_t self, const p
 	int ret;
 	bool is_console = true; /* XXX */
 
-	if (dev == NULL)
-		return true;
-
 	ret = radeon_suspend_kms(dev, true, is_console, false);
 	if (ret)
 		return false;
@@ -360,9 +357,6 @@ radeon_do_resume(device_t self, const pm
 	int ret;
 	bool is_console = true; /* XXX */
 
-	if (dev == NULL)
-		return true;
-
 	ret = radeon_resume_kms(dev, true, is_console);
 	if (ret)
 		return false;
@@ -384,20 +378,10 @@ radeon_task_schedule(device_t self, stru
 {
 	struct radeon_softc *const sc = device_private(self);
 
-	switch (sc->sc_task_state) {
-	case RADEON_TASK_ATTACH:
-		SIMPLEQ_INSERT_TAIL(&sc->sc_task_u.attach, task, rt_u.queue);
-		return 0;
-	case RADEON_TASK_WORKQUEUE:
-		if (sc->sc_task_u.workqueue == NULL) {
-			aprint_error_dev(self, "unable to schedule task\n");
-			return EIO;
-		}
-		workqueue_enqueue(sc->sc_task_u.workqueue, &task->rt_u.work,
-		    NULL);
-		return 0;
-	default:
-		panic("radeon in invalid task state: %d\n",
-		    (int)sc->sc_task_state);
-	}
+	if (atomic_load_relaxed(&sc->sc_task_thread) == curlwp)
+		SIMPLEQ_INSERT_TAIL(&sc->sc_tasks, task, rt_u.queue);
+	else
+		workqueue_enqueue(sc->sc_task_wq, &task->rt_u.work, NULL);
+
+	return 0;
 }

Reply via email to