DF-4890 / fix.diff
--- 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 */ |