# DF-5623 — Unbounded FID/VID transition spin + no completion verification in the AMD-0F cpufreq shim

**File:** `sys/platform/pc64/x86_64/cpufreq_machdep.c` (pass 2, GLM 5.3)
**Class:** CWE-835 loop with unreachable exit condition + CWE-754 improper
check for unusual/exceptional conditions
**Severity:** Info (hardening; hang preconditioned on misbehaving/non-converging
K8 FID/VID hardware or a consuming-but-ignoring VMM; trigger actor is root or
firmware — no unprivileged path)
**Status:** static analysis certain on the loop structure; not triggerable on the
audit guest (AMD-0F gate live-proven OFF — see DF-5622's `pstprobe` harness).

## What the bug is

Two unbounded loops and a missing success check in `amd0f_set_fidvid()`:

1. **`AMD0F_WAIT_FIDVID_CHG`** (cpufreq_machdep.c:53-56):

   ```c
   #define AMD0F_WAIT_FIDVID_CHG(status) \
   do { \
           (status) = rdmsr(AMD0F_MSR_FIDVID_STATUS); \
   } while (AMD0F_STA_PENDING(status))
   ```

   A busy-wait on the K8 FID/VID_STATUS pending bit (MSR 0xc0010042 bit 31)
   with **no iteration/time bound**. Used at 5 sites (cpufreq_machdep.c:102,
   112, 138, 146, 157). If the voltage regulator / power management unit never
   completes the transition (stuck pending bit — failing hardware, or a VMM
   that consumes `WRMSR(0xc0010041)` but leaves STATUS untouched), the CPU
   spins forever at kernel priority.

2. **Phase-2 VCO ramp loop** (cpufreq_machdep.c:126-142):

   ```c
   while (abs(vco_fid - vco_cfid) > 2) {
           ...
           AMD0F_WRITE_FIDVID(val, cvid, ...);
           AMD0F_WAIT_FIDVID_CHG(status);
           cfid = AMD0F_STA_CFID(status);
           ...
           vco_cfid = AMD0F_FID2VCO(cfid);
   }
   ```

   Termination *assumes* each write moves `cfid` toward `fv->fid` by 2.
   Hardware that rejects/ignores the FID write (max-FID cap, thermal limit,
   VMM without FID emulation) leaves `cfid` unchanged → infinite loop.

3. **No completion verification:** after phases 2-3 the function returns 0
   unconditionally (cpufreq_machdep.c:161) without checking that
   `cfid == fv->fid && cvid == fv->vid`. A silently-ignored final write is
   reported to the caller (`acpi_pst_amd0f_set_pstate`,
   acpi_pstate_machdep.c:387) as success, so the P-state bookkeeping
   (`dom->pd_state`, sysctls, powerd) desynchronizes from hardware.

## Execution context (why it is a hard wedge, not a thread hang)

Transitions are dispatched via `cpuhelper` **onto the target CPU**
(`acpi_pst_set_pstate`, sys/dev/acpica/acpi_cpu_pstate.c:1452-1465) while the
initiating CPU holds `dom->pd_serialize` and blocks on the cpuhelper reply
(acpi_cpu_pstate.c:994-996, cpuhelper_domsg). A cpuhelper handler that never
returns strands the helper CPU at kernel priority *and* the initiator forever
— with `hw.acpi.cpu.pstate.global` (all domains) this wedges every CPU that
owns a domain. powerd (root, default-enabled adaptive mode) triggers
transitions continuously, so the hang fires unattended once the hardware
misbehaves.

Reference: Linux `powernow-k8` bounds its transition wait
(`transition_latency`-scaled `do...while` with explicit error bail-out) and
reports failures instead of looping/spinning indefinitely.

## Reproduce the introspection evidence (this guest)

Same harness as DF-5622 (`pstprobe.c`): proves the amd0f code is compiled
into the running GENERIC kernel and that its CPU gate is OFF on this guest
(Intel vendor, APMI EDX=0, `acpi_pst_md_probe()==NULL`), so the hang path
cannot be exercised here. On real K8 hardware the trigger is any P-state
request while FID/VID transitions do not complete (failing VRM; or under a
VMM that models MSR 0xc0010041 as writable-but-inert).

```
./build.sh && ./run.sh     # introspection (see run.log / build.log)
```

## Fix

`fix.diff` (compile-validated against the guest's kernel headers — see
`fixbuild.log`):
- bound `AMD0F_WAIT_FIDVID_CHG` at 8e7 spins and `return ETIMEDOUT` at all 5
  call sites when the pending bit never clears;
- bound the phase-2 ramp loop at 64 iterations (`return ETIMEDOUT`);
- `kprintf` a diagnostic when the final FID/VID did not converge before
  returning 0.
