# DF-4890 — AcpiOsAcquireLock: idle-thread and normal-thread callers use
# disjoint locking primitives (spinlock vs lockmgr) — no mutual exclusion
# between them

## Files audited in this batch
- `sys/dev/acpica/Osd/OsdSynch.c` (this finding)
- `sys/dev/drm/amd/amdgpu/psp_v10_0.c` (clean, no finding)

## What the bug is (one paragraph)
`AcpiOsAcquireLock()` (sys/dev/acpica/Osd/OsdSynch.c:374-403) picks the
underlying primitive based on WHO the caller is:

- idle thread  (`gd_curthread == &gd_idlethread`): `spin_lock(&Spin->slock)`
  (OsdSynch.c:383-384)
- any normal thread: `lockmgr(&Spin->lock, LK_EXCLUSIVE)` + `crit_enter()`
  (OsdSynch.c:385-387) — **`slock` is never touched**

The two primitives have *nothing to do with each other*: a holder on the
idle-thread path and a holder on the normal-thread path can be inside the
"same" ACPICA critical section simultaneously on different CPUs. ACPICA's
contract for `AcpiOsAcquireLock` is a single exclusive lock per handle
(ACPI OSPM/OVL layer). The result is a data race on whatever the lock
protects.

## What the lock protects
In this ACPICA version the OSL spinlocks created by `AcpiUtMutexInitialize`
(sys/contrib/dev/acpica/source/components/utilities/utmutex.c:206-218) are
`AcpiGbl_GpeLock`, `AcpiGbl_HardwareLock`, `AcpiGbl_ReferenceCountLock`.
Only `AcpiGbl_HardwareLock` is reachable from the idle thread:

    cpu_idle_hook = acpi_cst_idle                 (acpi_cpu_cstate.c:786)
    acpi_cst_idle: C3 with BM_STS flag ->
        AcpiReadBitRegister(BUS_MASTER_STATUS)    (acpi_cpu_cstate.c:933)
        AcpiWriteBitRegister(BUS_MASTER_STATUS,1) (acpi_cpu_cstate.c:935)
    C3 preamble ARB ->
        AcpiWriteBitRegister(ARB_DISABLE, 1|0)    (acpi_cpu_cstate.c:958,977)
        -> AcpiWriteBitRegister takes AcpiGbl_HardwareLock around a
           read-modify-write of PM1_STS / PM2_CNT
           (hwxface.c:411 `AcpiOsAcquireLock(AcpiGbl_HardwareLock)` .. :471)

Normal-thread takers of the same lock: `AcpiWriteBitRegister()` from
non-idle contexts — e.g. `acpi_cpu_cstate.c:668` and `:810`
(BUS_MASTER_RLD writes in CST probe/attach/notify paths), and ACPICA's
`AcpiHwClearAcuiStatus()` (hwregs.c:574) on sleep-entry/exit
(`AcpiEnableSubsystem`, resume path).

Both sides do masked read-modify-write of PM1/PM2 registers under the
"lock"; interleaving loses updates (lost BM_STS clear, lost ARB_DISABLE
set/clear, lost BM_RLD write, mangled PM1 status clears during resume).

## How to reproduce (real hardware)
Preconditions: multi-CPU system, ACPI `_CST` advertising C3 (or P_BLK C3)
with BM/ARB preamble, and a concurrent normal-thread bit-register write
(CST re-evaluation via `hw.acpi.cpu.cx_lowest`/notify, or S3 resume's
`AcpiHwClearAcuiStatus`).

1. `sysctl hw.ncpu` >= 2, `sysctl hw.acpi.cpu0.cx_supported` shows C2/C3.
2. `sysctl hw.acpi.cpu.cx_lowest=C3` so idle threads take the C3 path.
3. Generate normal-thread `AcpiWriteBitRegister` traffic while CPUs idle
   (`race_trigger.sh` hammers CST notify/RLD paths).
4. Observe with an instrumented kernel (kprintf in both branches keyed on
   `Spin == AcpiGbl_HardwareLock`) — on stock kernels the lost update is
   silent; the visible symptoms are missed wake events / wrong sleep
   transitions / C3 entered with arbitration disabled.

## On this QEMU guest
`precond_probe.sh` output is in `env.txt`: 6 CPUs, `cpu_cst0..5` attached,
but `cx_supported: C1/0` only — QEMU's DSDT/FADT exposes no C2/C3, so the
idle-thread side of the race (C3 BM/ARB register writes) never executes in
this guest. The race therefore cannot be hosted here; see VERDICT.md.

## Fix
`fix.diff`: make the normal-thread path take the *same* spinlock for the
critical section (lockmgr stays as the sleepable contention lock; the
HardwareLock sections are short, non-blocking register RMWs — verified at
hwxface.c:411-471 and hwregs.c:574-581).
