⬢ DragonFlyBSD Kernel Audit
DF-4890 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/acpica/Osd/OsdSynch.c
+++ b/sys/dev/acpica/Osd/OsdSynch.c
@@ -380,10 +380,17 @@ AcpiOsAcquireLock (ACPI_SPINLOCK Spin)
 {
     globaldata_t gd = mycpu;
 
-    if (gd->gd_curthread == &gd->gd_idlethread) {
-	spin_lock(&Spin->slock);
-    } else {
+    /*
+     * Both paths must serialize on the same spinlock.  The old code used
+     * the spinlock for the idle thread and lockmgr for everyone else,
+     * which provided no mutual exclusion between the two classes at all:
+     * an idle-thread holder on one CPU and a normal-thread holder on
+	 * another CPU could be inside the "same" ACPICA critical section
+     * concurrently (e.g. AcpiGbl_HardwareLock RMW of PM1/PM2 registers
+     * from acpi_cst_idle racing AcpiWriteBitRegister()/ClearAcuiStatus()
+     * on another CPU).  lockmgr remains as the sleepable contention lock
+     * for normal threads; the actual critical section is always guarded
+     * by slock.  This is safe because every in-tree OSL-spinlock holder
+     * performs only short, non-blocking register accesses inside the
+     * section (hwxface.c AcpiWriteBitRegister, hwregs.c
+     * AcpiHwClearAcuiStatus, utmutex.c reference counts), so the idle
+     * thread never spins against a sleeping section holder.
+     */
+    if (gd->gd_curthread != &gd->gd_idlethread) {
 	lockmgr(&Spin->lock, LK_EXCLUSIVE);
 	crit_enter();
     }
+    spin_lock(&Spin->slock);
 
 #ifdef ACPI_DEBUG_LOCKS
     if (Spin->owner) {
@@ -421,13 +428,11 @@ AcpiOsReleaseLock (ACPI_SPINLOCK Spin, ACPI_CPU_FLAGS Flags)
 #endif
     globaldata_t gd = mycpu;
 
-    if (gd->gd_curthread == &gd->gd_idlethread) {
-	spin_unlock(&Spin->slock);
-    } else {
+    spin_unlock(&Spin->slock);
+    if (gd->gd_curthread != &gd->gd_idlethread) {
 	crit_exit();
 	lockmgr(&Spin->lock, LK_RELEASE);
     }
 }
 
 /* Section 5.2.9.1:  global lock acquire/release functions */