E17 ticket drafts — power management Filed 2026-09-21

Every issue for EPIC E17, written out in full — and now filed. This page is the companion to NextBSD power management. It records the epic, the new area:power label and all 28 child tickets: title, target repo, labels, dependencies, and the exact Markdown body for each. The plan's ticket map is generated from the same source as this page. Filed on 2026-09-21 as epic nextbsd#490 and 28 child issues, attached as GitHub sub-issues and placed on the Roadmap. The bodies below are what was filed; each issue also carries a footer naming its parent and its dependencies by issue number. E16 was already taken by Bluetooth (nextbsd#473), so this is E17.

TL;DR

Done before filing

1. Label — done. area:power was created in all four repos below.
2. Roadmap — done. E17 Power management was added to the Epic dropdown with all 16 existing options passed back by id; a before-and-after snapshot of all 429 board items showed no Epic value changed. The epic was added to the project, its sub-issues followed automatically, and all 29 items carry Epic = E17 and Status = Backlog.
3. Epic number — confirmed. E1–E16 existed; E17 was free when filed.

Label

Namearea:power
DescriptionPower management: pmconfigd, IOPMLib, power sources, ACPI sleep/wake, cpufreq, reboot/shutdown
Colour#fbca04
Create innextbsd, nextbsd-kernel, nextbsd-userland, nextbsd-freebsd-compat

Epic

EPIC: E17 Power management — pmconfigd, IOPMLib/IOPowerSources, ACPI sleep/wake, pmset/caffeinate

Reponextbsd
Labelsepic area:power
Filed asnextbsd#490
## Summary

Power management the Darwin way. Apple's `pmconfigd` (shipped as `powerd`) runs as a launchd MachService and plays the role xnu's in-kernel `IOPMrootDomain` plays toward applications, over a small **in-kernel ACPI bridge** that publishes an `IOPMrootDomain` node and `IOPMPowerSource` nodes into the existing `/dev/ioregistry`.

**Read first:** https://pkgdemon.github.io/nextbsd-power-management-plan.html
**Ticket drafts (full bodies):** https://pkgdemon.github.io/nextbsd-e17-tickets.html

## Why

- Nothing from Apple's PowerManagement, and none of Apple's reboot/shutdown/halt, is vendored today. FreeBSD's `powerd` ships but **no LaunchDaemon runs it**, so CPU frequency is unmanaged on laptops.
- **Sleep skips userland entirely.** FreeBSD's `acpi_ReqSleepState` enters S3 at once when nothing holds `/dev/devctl` open -- "If devd(8) is not running, immediately enter the sleep state" -- and devd is gone. No process is told before a suspend, and no `Resume` event is ever sent.
- Every prerequisite has landed: the in-kernel IORegistry (K1/K2), kernel-originated Mach notifications, notifyd, ASL, configd, launchd MachServices.

## Direction (agreed)

Hybrid: userspace `pmconfigd` now, with the bridge shaped so an in-kernel root domain can replace it later (spike R1). v1: power sources + `pmset -g`; assertions + `caffeinate`; ACPI S3 sleep/wake with apps notified; reboot/shutdown/halt + `pmset schedule`. amd64 ACPI laptops first.

## Tracks

- **K** -- kernel bridge and FreeBSD patches
- **L** -- libIOKit power APIs
- **D** -- pmconfigd
- **C** -- pmset / caffeinate
- **S** -- reboot/shutdown/halt and scheduling
- **V** -- display sleep and idle time
- **M** -- the MIG out-of-line decision
- **T / E / R** -- CI, packaging, kernel-later spike

## Children

Replace IDs with issue numbers once filed.

- [ ] K1 kernel: acpi_ReqSleepState waits for a registered sleep listener instead of a /dev/devctl reader; ack timeout becomes tunable
- [ ] K2 kernel: fixed-feature power button emits an ACPI Button event, and the wake PWRBTN status is recorded before it is cleared
- [ ] K3 kernel: devctl_notify hook becomes a list, so an in-kernel bridge can consume ACPI events while /dev/devctl stays unopened
- [ ] K4 kernel: iopmroot bridge driver -- IOPMrootDomain0 node and /dev/iopmroot user client for sleep request, acknowledgement and state
- [ ] K5 kernel: IORegistry property-provider hook, so a driver can add keys to its own node
- [ ] K6 kernel: IORegistry interest notifications -- IOREG_EVENT_INTEREST and an 'IOIN' Mach message carrying messageType, messageArgument and serial
- [ ] K7 kernel: IOPMPowerSource nodes for ACPI batteries and AC, with Apple's power-source keys
- [ ] M1 Decide: pmconfigd's out-of-line MIG routines -- fix OOL in mach.ko (nextbsd#66) or route them over XPC
- [ ] D1 pmconfigd: vendor PowerManagement-1846.120.8.0.1 and boot a minimal daemon as the com.apple.powerd MachService
- [ ] D2 pmconfigd: one kernel shim -- root-domain selectors become /dev/iopmroot ioctls, and 'IOIN' interest messages drive the daemon
- [ ] D3 pmconfigd: power sources from IOPMPowerSource nodes -- kIOPS dictionaries, notify state bits, and an AC-only source when there is no battery
- [ ] D4 pmconfigd: assertion engine and idle-sleep policy -- PreventUserIdleSystemSleep/DisplaySleep, PreventSystemSleep, sleep and displaysleep timers
- [ ] D5 pmconfigd: S3 sleep/wake orchestration -- CanSystemSleep, WillSleep, WillNotSleep, WillPowerOn, HasPoweredOn, with a 30 s acknowledgement budget
- [ ] D6 pmconfigd: publish power state for Gershwin in SCDynamicStore -- battery %, AC, lid, sleep pending
- [ ] D7 pmconfigd: CPU power policy -- set Intel HWP EPP by AC/battery profile; replaces FreeBSD powerd, with an interim LaunchDaemon until then
- [ ] L1 libIOKit: IOServiceAddInterestNotification over 'IOIN', and IORegistryEntrySetCFProperty on the root domain
- [ ] L2 libIOKit: IOPowerSources (ps.subproj) -- IOPSCopyPowerSourcesInfo and friends, plus <IOKit/ps/*.h>
- [ ] L3 libIOKit: IOPMAssertion API and the IOPMLib / IOPMLibPrivate headers
- [ ] L4 libIOKit: IORegisterForSystemPower, IOAllowPowerChange, IOCancelPowerChange and IOPMSleepSystem re-aimed at pmconfigd
- [ ] C1 pmset: port pmset.m -- -g, -g batt, -g assertions, -g log, sleepnow, displaysleep/sleep settings, schedule
- [ ] C2 caffeinate: port caffeinate.c
- [ ] S1 reboot/shutdown/halt: vendor Apple's system_cmds versions over the BSD PID-1 signal protocol launchd already honours
- [ ] S2 pmconfigd: pmset schedule sleep/shutdown/restart performed by pmconfigd itself; wake/poweron waits on S3's RTC alarm
- [ ] S3 Spike: an RTC wake alarm for pmset schedule wake -- FreeBSD has no rtcwake
- [ ] V1 Display sleep and HID idle time without IODisplayWrangler -- pmconfigd owns display idle
- [ ] T1 CI: power boot markers -- PM-BOOT-OK, PM-PS-OK, PM-ASSERT-OK, and PM-SLEEP-OK through QEMU S3
- [ ] E1 Packaging: power LaunchDaemons, build blocks, man pages, compat strip entries, and ACPI sysctl defaults
- [ ] R1 Spike: an in-kernel IOPMrootDomain -- move the sleep/wake fan-out, acknowledgements and policy into iopmroot

## Linked, not sub-issues

- nextbsd/nextbsd#225 (E6) -- Retire /dev/devctl for IOKit notifications -- K3 keeps it inert
- nextbsd/nextbsd#66 (E1) -- MIG out-of-line data -- decides M1
- nextbsd/nextbsd#115 (E9) -- system_cmds port -- its IOPMLib-stub item is delivered by S1
- nextbsd/nextbsd#395 (E11) -- Display brightness -- same pref-pane seam as D6 and V1
- nextbsd/nextbsd#179 (E6) -- ko-to-kext -- needed for acpi_video, coretemp, amdtemp
- nextbsd/nextbsd#182 (E6) -- kext_tools / OSKext -- the kextmanager reboot lock S1 must serve or drop
- nextbsd/nextbsd#390 (E5) -- GPU re-init races -- wake from S3 must re-check /dev/dri
- nextbsd/nextbsd#428 (E10) -- Test gates pass at the login prompt -- pairs with T1

Index

IDTicketRepoDepends onStatusIssue
K1kernel: acpi_ReqSleepState waits for a registered sleep listener instead of a /dev/devctl reader; ack timeout becomes tunablenextbsd-kernelreadynextbsd-kernel#238
K2kernel: fixed-feature power button emits an ACPI Button event, and the wake PWRBTN status is recorded before it is clearednextbsd-kernelreadynextbsd-kernel#239
K3kernel: devctl_notify hook becomes a list, so an in-kernel bridge can consume ACPI events while /dev/devctl stays unopenednextbsd-kernelreadynextbsd-kernel#240
K4kernel: iopmroot bridge driver -- IOPMrootDomain0 node and /dev/iopmroot user client for sleep request, acknowledgement and statenextbsd-kernelK1, K3, K5, K6readynextbsd-kernel#243
K5kernel: IORegistry property-provider hook, so a driver can add keys to its own nodenextbsd-kernelreadynextbsd-kernel#241
K6kernel: IORegistry interest notifications -- IOREG_EVENT_INTEREST and an 'IOIN' Mach message carrying messageType, messageArgument and serialnextbsd-kernelreadynextbsd-kernel#242
K7kernel: IOPMPowerSource nodes for ACPI batteries and AC, with Apple's power-source keysnextbsd-kernelK4, K5readynextbsd-kernel#244
M1Decide: pmconfigd's out-of-line MIG routines -- fix OOL in mach.ko (nextbsd#66) or route them over XPCnextbsdneeds answernextbsd#491
D1pmconfigd: vendor PowerManagement-1846.120.8.0.1 and boot a minimal daemon as the com.apple.powerd MachServicenextbsd-userlandreadynextbsd-userland#224
D2pmconfigd: one kernel shim -- root-domain selectors become /dev/iopmroot ioctls, and 'IOIN' interest messages drive the daemonnextbsd-userlandD1, K4, L1readynextbsd-userland#226
D3pmconfigd: power sources from IOPMPowerSource nodes -- kIOPS dictionaries, notify state bits, and an AC-only source when there is no batterynextbsd-userlandD1, K7, M1readynextbsd-userland#227
D4pmconfigd: assertion engine and idle-sleep policy -- PreventUserIdleSystemSleep/DisplaySleep, PreventSystemSleep, sleep and displaysleep timersnextbsd-userlandD1, D2, M1readynextbsd-userland#228
D5pmconfigd: S3 sleep/wake orchestration -- CanSystemSleep, WillSleep, WillNotSleep, WillPowerOn, HasPoweredOn, with a 30 s acknowledgement budgetnextbsd-userlandD2, K1, K4readynextbsd-userland#229
D6pmconfigd: publish power state for Gershwin in SCDynamicStore -- battery %, AC, lid, sleep pendingnextbsd-userlandD3readynextbsd-userland#230
D7pmconfigd: CPU power policy -- set Intel HWP EPP by AC/battery profile; replaces FreeBSD powerd, with an interim LaunchDaemon until thennextbsd-userlandD3readynextbsd-userland#231
L1libIOKit: IOServiceAddInterestNotification over 'IOIN', and IORegistryEntrySetCFProperty on the root domainnextbsd-userlandK6readynextbsd-userland#225
L2libIOKit: IOPowerSources (ps.subproj) -- IOPSCopyPowerSourcesInfo and friends, plus <IOKit/ps/*.h>nextbsd-userlandD3, M1readynextbsd-userland#232
L3libIOKit: IOPMAssertion API and the IOPMLib / IOPMLibPrivate headersnextbsd-userlandD4, M1readynextbsd-userland#233
L4libIOKit: IORegisterForSystemPower, IOAllowPowerChange, IOCancelPowerChange and IOPMSleepSystem re-aimed at pmconfigdnextbsd-userlandD5, L1readynextbsd-userland#234
C1pmset: port pmset.m -- -g, -g batt, -g assertions, -g log, sleepnow, displaysleep/sleep settings, schedulenextbsd-userlandD1, D3, L2, L3, L4readynextbsd-userland#235
C2caffeinate: port caffeinate.cnextbsd-userlandL3readynextbsd-userland#236
S1reboot/shutdown/halt: vendor Apple's system_cmds versions over the BSD PID-1 signal protocol launchd already honoursnextbsd-userlandreadynextbsd-userland#237
S2pmconfigd: pmset schedule sleep/shutdown/restart performed by pmconfigd itself; wake/poweron waits on S3's RTC alarmnextbsd-userlandD1, S1readynextbsd-userland#238
S3Spike: an RTC wake alarm for pmset schedule wake -- FreeBSD has no rtcwakenextbsd-kernelK4spikenextbsd-kernel#245
V1Display sleep and HID idle time without IODisplayWrangler -- pmconfigd owns display idlenextbsd-userlandD4readynextbsd-userland#239
T1CI: power boot markers -- PM-BOOT-OK, PM-PS-OK, PM-ASSERT-OK, and PM-SLEEP-OK through QEMU S3nextbsdD1, D5, K2readynextbsd#492
E1Packaging: power LaunchDaemons, build blocks, man pages, compat strip entries, and ACPI sysctl defaultsnextbsd-userlandD1, S1, D7readynextbsd-userland#240
R1Spike: an in-kernel IOPMrootDomain -- move the sleep/wake fan-out, acknowledgements and policy into iopmrootnextbsd-kernelD5spikenextbsd-kernel#246

Tickets

K1 · kernel: acpi_ReqSleepState waits for a registered sleep listener instead of a /dev/devctl reader; ack timeout becomes tunable ready

Reponextbsd-kernel
Labelsbug area:power
Depends on
Filed asnextbsd-kernel#238
Parentnextbsd#490
## Summary

With devd gone, **every** ACPI sleep request suspends immediately and nobody is told. `acpi_ReqSleepState()` (`sys/dev/acpica/acpi.c`, releng/15.1 ~3176-3248) only opens the userland acknowledgement window when `devctl_process_running()` -- "is `/dev/devctl` open" (`kern_devctl.c:364-366`) -- and otherwise calls `acpi_EnterSleepState()` straight away:

```c
    /* If devd(8) is not running, immediately enter the sleep state. */
    if (!devctl_process_running()) {
```

It also only emits the `Resume` notification when `/dev/devctl` is open (~3563), so no wake event exists either. Today `acpiconf -s 3`, a sleep button, or a lid close with `hw.acpi.lid_switch_state` set all suspend without warning.

## Design

New patch in `nk/patches/series` (~60 LOC in `acpi.c` and `acpivar.h`):

- an `acpi_register_sleep_listener()` / `acpi_deregister_sleep_listener()` API; the gate becomes `acpi_sleep_listener != NULL || devctl_process_running()`
- the 10 s force callout (`callout_reset(&sc->susp_force_to, 10*hz, ...)`) becomes a tunable `hw.acpi.suspend_ack_timeout`, **default 45 s** -- above Apple's 30 s app acknowledgement budget, so pmconfigd can finish its fan-out
- keep `/dev/acpi` and `acpiconf` behaviour unchanged

The listener is the K4 bridge driver; its fd deregisters on close, so a crashed pmconfigd cannot wedge sleep -- the timeout is the backstop, exactly as it is for a hung devd today.

## Acceptance

- [ ] with a registered listener, `sysctl hw.acpi.sleep_state=S3` waits until acknowledged (or the timeout)
- [ ] with no listener and no `/dev/devctl` reader, behaviour is unchanged
- [ ] `hw.acpi.suspend_ack_timeout` is honoured
- [ ] verified in QEMU q35 (S3 enabled) with `acpiconf -s 3`

K2 · kernel: fixed-feature power button emits an ACPI Button event, and the wake PWRBTN status is recorded before it is cleared ready

Reponextbsd-kernel
Labelsbug area:power
Depends on
Filed asnextbsd-kernel#239
Parentnextbsd#490
## Summary

Control-method buttons (`acpi_button.c`) notify userland; the **fixed-feature** power button does not. `acpi_event_power_button_sleep/wake` (acpi.c ~3914-3947) only fire the `acpi_sleep_event` eventhandler, with no `acpi_UserNotify("Button", ...)`. QEMU's `system_powerdown` and many laptops use the fixed button, so pmconfigd would never learn of a press.

Separately, on resume `acpi_EnterSleepState` clears a latched fixed power-button status (~3466-3480) so the wake is not misread as a poweroff -- which also erases the only evidence that the button caused the wake.

## Design

- add `acpi_UserNotify("Button", ACPI_ROOT_OBJECT, 0)` in the fixed-feature handlers (~6 LOC), matching the control-method path's value convention
- record the PWRBTN status into the softc before clearing it, so the bridge (K4) can report `Wake Reason` "Power Button" (~10 LOC)

## Acceptance

- [ ] QMP `system_powerdown` in QEMU produces a `Button` event visible to the bridge
- [ ] a button-initiated wake is distinguishable from an RTC or lid wake

K3 · kernel: devctl_notify hook becomes a list, so an in-kernel bridge can consume ACPI events while /dev/devctl stays unopened ready

Reponextbsd-kernel
Labelsenhancement area:power
Depends on
Filed asnextbsd-kernel#240
Parentnextbsd#490
## Summary

Every ACPI event goes `acpi_UserNotify()` -> `devctl_notify("ACPI", <subsystem>, ...)` (acpi.c ~4307-4324; subsystems `Lid`, `Button`, `ACAD`, `CMBAT`, `Thermal`, `Suspend`, `Resume`). `devctl_notify` feeds `/dev/devctl` and a **single** `devctl_notify_hook` slot (`kern_devctl.c` ~140-141, 601-609).

On NextBSD nothing consumes either: devd was removed, and the multi-reader netlink route (`nlsysevent`) is **module-only** -- `netlink_sysevent.c` is not in `sys/conf/files`, and NextBSD ships no modules.

## Design

- turn the hook into a short list (~30 LOC in `kern_devctl.c`, `sys/bus.h`); the bridge (K4) registers a filter for `system == "ACPI"`
- `/dev/devctl` stays unopened and inert, keeping nextbsd#225's direction intact: IOKit-shaped notifications, not a second event bus

## Acceptance

- [ ] two in-kernel hook consumers both receive the same ACPI event
- [ ] `/dev/devctl` remains closed on a booted system

Links: nextbsd#225 (E6).

K4 · kernel: iopmroot bridge driver -- IOPMrootDomain0 node and /dev/iopmroot user client for sleep request, acknowledgement and state ready

Reponextbsd-kernel
Labelsenhancement area:power
Depends onK1, K3, K5, K6
Filed asnextbsd-kernel#243
Parentnextbsd#490
## Summary

The kernel half of "pmconfigd plays IOPMrootDomain". A new pseudo-driver publishes the node Apple's code looks for, and a character device stands in for `RootDomainUserClient`.

## Design

New `src-overlay/sys/dev/iokit/iokit_pmroot.c` (~800-1000 LOC), `src-overlay/sys/sys/iopmroot.h` (ABI), `src-overlay/conf/files.iopmroot` -- all `standard`, no module.

- **Node:** a newbus pseudo-device `IOPMrootDomain0` (child of `acpi0` on x86, of `nexus0` elsewhere), so `iokit_registry.c` mints it an id with no registry changes and `IOServiceMatching("IOPMrootDomain")` matches by class name
- **`/dev/iopmroot`** (root:operator 0660), one open per pmconfigd; the fd carries the K1 listener registration:
  - `IOPMROOT_SLEEP{reason}` -> records reason/PID, calls `acpi_ReqSleepState(sc, suspend_sx)`
  - `IOPMROOT_ACK{serial, allow|errno}` -> `acpi_AckSleepState(sc->acpi_clone, ...)`; an error cancels (clears `acpi_next_sstate`)
  - `IOPMROOT_GETSTATE` (sleep supported, clamshell, AC, last sleep/wake reason), `IOPMROOT_SETCLAMSHELLSLEEP`, `IOPMROOT_SETPROP` (`System Shutdown` -> `kern.suspend_blocked`; `SleepDisabled`)
- **Subscribes:** the K1 listener (`Suspend`), `power_suspend_early` (stamp sleep time / UUID), `power_resume` (wake), `acpi_acad_event`, `acpi_sleep_event` / `acpi_wakeup_event`, and the K3 hook for `Lid`, `Button`, `CMBAT`, `Thermal`
- **Publishes** on the node (via K5): `Last Sleep Reason`, `Wake Reason`, `Wake Type`, `AppleClamshellState`, `AppleClamshellCausesSleep`, `IOPMSystemSleepType` = 2, `IOSleepSupported` (from `hw.acpi.supported_sleep_state`), `Supported Features`, `SleepWakeUUID`, `System Capabilities`
- **Emits** (via K6): "sleep requested {serial, reason}", "did wake", `kIOPMMessageClamshellStateChange`, `kIOPMMessageSystemPowerEventOccurred` (thermal `0xcc`)
- **Timestamps:** `kern.sleeptime` / `kern.waketime` with xnu semantics, which pmconfigd and `IOPMGetLastWakeTime` read and FreeBSD lacks

The kernel mints the acknowledgement serial even in v1, so it already has its final meaning if R1 moves the fan-out into the kernel.

## Acceptance

- [ ] `ioreg -c IOPMrootDomain` shows the node and its properties
- [ ] a sleep request -> listener message -> `IOPMROOT_ACK` -> S3 -> resume -> "did wake", in QEMU
- [ ] `IOPMROOT_ACK` with an error cancels the sleep

K5 · kernel: IORegistry property-provider hook, so a driver can add keys to its own node ready

Reponextbsd-kernel
Labelsenhancement area:power
Depends on
Filed asnextbsd-kernel#241
Parentnextbsd#490
## Summary

Registry property bags are synthesized from fixed newbus fields only -- id, parent, state, name, class, driver, path, PCI ids, description (`iokit_registry.c` `ioreg_build_bag`, ~260-297). There is no per-driver provider, so nothing can publish `AppleClamshellState` or `CurrentCapacity`.

## Design

`ioreg_register_prop_provider(device_t, int (*)(device_t, nvlist_t *))`, consulted by `ioreg_build_bag` (~40 LOC). A provider change emits a property-change interest notification (K6).

## Acceptance

- [ ] a test provider's keys appear in `ioreg -l` and through `IORegistryEntryCreateCFProperty`

K6 · kernel: IORegistry interest notifications -- IOREG_EVENT_INTEREST and an 'IOIN' Mach message carrying messageType, messageArgument and serial ready

Reponextbsd-kernel
Labelsenhancement area:power
Depends on
Filed asnextbsd-kernel#242
Parentnextbsd#490
## Summary

The kernel already sends Mach notifications for device arrival and departure (`src-overlay/sys/compat/mach/iokit_notify.c`, msgid 'IONT'), but not Apple's per-object **interest** messages -- the channel `IORegisterForSystemPower`, battery changes and clamshell changes all ride on.

## Design

- a new watch kind `IOREG_EVENT_INTEREST` in `IOREGIOCWATCH`, pinned to a node id
- a new msgid 'IOIN' with `{header, NDR, id, messageType (uint32), messageArgument (uint64), serial (uint32)}` -- the existing 'IONT' struct is documented as frozen once shipped
- ~100 LOC in `iokit_registry.c`, ~80 in `iokit_notify.c/.h`

**The same 'IOIN' struct is what pmconfigd sends to apps in v1** (L4), so moving the fan-out into the kernel later (R1) changes the sender, not the wire format.

## Acceptance

- [ ] a watcher pinned to a node receives an interest message with the right type, argument and serial

K7 · kernel: IOPMPowerSource nodes for ACPI batteries and AC, with Apple's power-source keys ready

Reponextbsd-kernel
Labelsenhancement area:power
Depends onK4, K5
Filed asnextbsd-kernel#244
Parentnextbsd#490
## Summary

pmconfigd finds batteries with `IOServiceMatching("IOPMPowerSource")` and re-reads every property on any general-interest message. It **drops any battery lacking `BatteryInstalled`, `CurrentCapacity` or `MaxCapacity`** (`BatteryTimeRemaining.m` `_batteryChanged`, ~790-870).

## Design

Children of `IOPMrootDomain0`: `IOPMPowerSource0..N`, one per `acpi_cmbat` unit, plus one AC-only source when `acpi_battery_get_units() == 0` (VMs, desktops). Properties read on demand through `acpi_battery_get_battinfo` / `ACPI_BATT_GET_INFO` / `ACPI_BATT_GET_STATUS` and `acpi_acad_get_acline`:

| Apple key | FreeBSD source |
|---|---|
| `ExternalConnected` | `acpi_acad_get_acline` |
| `BatteryInstalled` | not `ACPI_BATT_STAT_NOT_PRESENT` |
| `IsCharging` | `ACPI_BATT_STAT_CHARGING` |
| `CurrentCapacity` / `MaxCapacity` / `DesignCapacity` | `bst.cap` / `bix.lfcap` / `bix.dcap` (convert mAh x voltage when units are mA, as `acpi_battery.c` does) |
| `TimeRemaining` | `battinfo.min` |
| `Amperage` / `Voltage` | `bst.rate` / `bst.volt` |
| `CycleCount` | `bix.cycles` |
| `Manufacturer` / `Model` / `Serial` | `bix.oeminfo` / `model` / `serial` |
| `AtWarnLevel` / `AtCriticalLevel` | `cap <= wcap` / `lcap` |

`acpi_cmbat`'s notify (`0x80` status, `0x81` info) is the push signal; it arrives through the K3 hook and becomes `kIOPMMessageBatteryStatusHasChanged` on the node (K6).

## Acceptance

- [ ] on a laptop, `ioreg -c IOPMPowerSource` shows real capacity and charge state
- [ ] in QEMU, a single AC-only source appears

M1 · Decide: pmconfigd's out-of-line MIG routines -- fix OOL in mach.ko (nextbsd#66) or route them over XPC needs answer

Reponextbsd
Labelsquestion area:power area:mach
Depends on
Filed asnextbsd#491
Parentnextbsd#490
## Summary

Apple's `powermanagement.defs` (IOKitUser, subsystem 73000) passes serialized property lists as out-of-line `pointer_t` data -- `io_pm_assertion_create`, `io_pm_assertion_set_properties`, `io_ps_copy_powersources_info` and others. **MIG out-of-line data is broken in this kernel**: `src/WLAN/wlan.defs` says so and uses only fixed-size arguments, and nextbsd#66 ("OOL receive proper VM fix: mmap-based mig_allocate + real base pointers", E1) is open.

## Options

- **Fix #66.** Keeps Apple's MIG interface unchanged, and every other Darwin daemon that uses OOL benefits.
- **Route over XPC.** Apple's client libraries already use XPC (`com.apple.iokit.powerdxpc`) for power sources and assertions in places, so this diverges least from Apple's own code paths -- but it means patching every OOL routine.

## Blocks

The pmconfigd MIG server (D1), IOPowerSources (L2), the assertion API (L3), `pmset -g batt` and `pmset -g assertions` (C1).

D1 · pmconfigd: vendor PowerManagement-1846.120.8.0.1 and boot a minimal daemon as the com.apple.powerd MachService ready

Reponextbsd-userland
Labelsenhancement area:power
Depends on
Filed asnextbsd-userland#224
Parentnextbsd#490
## Summary

Vendor Apple's PowerManagement into `src/PowerManagement/` with a `freebsd-shims/` directory (the Libnotify pattern) and get a minimal `pmconfigd` running.

## Scope

The shipping target is `powerd-binary` (35,713 lines). The **Xcode project is the source of truth -- the drop's `pmconfigd/Makefile` is stale** (it references files that no longer exist). The portable core: `pmconfigd.m`, `PMStore.c`, `PMSettings.m`, `PrivateLib.m` (trimmed), `CommonLib.c`, `SystemLoad.c`, `TTYKeepAwake.c`. Compile out battery health, SmartPowerNap, LowPowerMode, SkyLight display control (`TARGET_CPU_ARM64`), IOReport, MessageTracer/FDR, SMC and nvram. Target "Intel macOS without SMC/ASBM" (`!TARGET_CPU_ARM64`, `!HAVE_SMART_BATTERY`) to select the most portable branches.

## Shims

- `os_log` / `os_signpost` (68 + 39 sites)
- an Objective-C / Foundation subset, or de-ObjC the few hundred lines that need it
- entitlements: `SecTaskCreateWithAuditToken` / `auditTokenHasEntitlement` -> uid-based checks (no Security.framework)
- `libproc` (`proc_pidinfo`, `proc_name`, `proc_pidpath`) -> `kinfo_proc`
- `dispatch_mach_create_f` / `dispatch_mach_mig_demux` (private libdispatch) -> a `DISPATCH_SOURCE_TYPE_MACH_RECV` source and `powermanagement_server()`, if the private API is missing
- `gethostuuid` -> `kern.hostuuid`
- CF-ICU: `strftime`/`strptime` for the timestamp and date-parsing sites

## launchd

`overlay/System/Library/LaunchDaemons/com.apple.powerd.plist`, keeping Apple's label: MachServices **`com.apple.PowerManagement.control`** (MIG) and **`com.apple.iokit.powerdxpc`** (XPC). Copy the check-in shape from `src/WLAN/mach_service.c` and the plist from `org.nextbsd.wland.plist`.

## Acceptance

- [ ] the daemon checks in and `launchctl list` shows `com.apple.powerd`
- [ ] boot marker `PM-BOOT-OK`

D2 · pmconfigd: one kernel shim -- root-domain selectors become /dev/iopmroot ioctls, and 'IOIN' interest messages drive the daemon ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD1, K4, L1
Filed asnextbsd-userland#226
Parentnextbsd#490
## Summary

pmconfigd reaches the kernel through `IOServiceOpen` on `IOPower:/IOPowerConnection/IOPMrootDomain` and `IOConnectCallMethod` with 19 selectors (`IOPMLibDefs.h`). In the userspace model most of those are **pmconfigd's own state**; only a few need the kernel.

## Design

Confine all kernel-facing code to **one shim file** (~400 LOC), a selector -> ioctl table over `/dev/iopmroot`:

| Selector | v1 handling |
|---|---|
| `kPMSleepSystem` (2) | enter pmconfigd's own sleep state machine (D5), then `IOPMROOT_SLEEP` |
| `kPMAllowPowerChange` / `kPMCancelPowerChange` (3/4) | pmconfigd's response tracker, then `IOPMROOT_ACK` |
| `kPMSetAggressiveness` (0), `kPMSetUserAssertionLevels` (9), `kPMActivityTickle` (10), `kPMSetClamshellSleepState` (12), `kPMRequestIdleSleepRevert` (17) | internal policy -- idle timer, assertion gating, clamshell |
| `kPMGetSystemSleepType` (11) | return `kIOPMSleepTypeNormalSleep` |
| watchdog, debug trigger, maintenance wake, display power-on | `kIOReturnUnsupported` for v1 |

Kernel events arrive as 'IOIN' interest messages (K6) on the root-domain and power-source nodes. Keeping the kernel boundary in one file is what makes R1's in-kernel swap cheap.

## Acceptance

- [ ] pmconfigd starts, opens `/dev/iopmroot`, and receives a lid or AC event in its log

D3 · pmconfigd: power sources from IOPMPowerSource nodes -- kIOPS dictionaries, notify state bits, and an AC-only source when there is no battery ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD1, K7, M1
Filed asnextbsd-userland#227
Parentnextbsd#490
## Summary

Port the core of `BatteryTimeRemaining.m`: first-match on `IOPMPowerSource`, general interest per battery, unpack properties, compute time remaining, and publish.

## Design

- publishes the notify state bits on `com.apple.system.powersources.timeremaining` (packed: valid / external / charging / minutes / active source, `IOPowerSourcesPrivate.h`) plus `percent`, `source`, `attach`, `lowbattery`, `criticallevel`
- serves `io_ps_copy_powersources_info` (the blob behind `IOPSCopyPowerSourcesInfo`; OOL -- see M1)
- `State:/IOKit/LowBatteryWarning` in SCDynamicStore
- no ASBM: `IOPSRequestBatteryUpdate` is a no-op, and `startBatteryPoll` must not spin when the ASBM timestamp keys are missing
- **no battery gives an AC-only list, not an error** -- VMs and desktops
- ~60% of the file (health, NVRAM, calibration, authentication) is deferred

## Acceptance

- [ ] `pmset -g batt` on a laptop matches `acpiconf -i 0`
- [ ] in QEMU it reports "AC Power" with no battery

D4 · pmconfigd: assertion engine and idle-sleep policy -- PreventUserIdleSystemSleep/DisplaySleep, PreventSystemSleep, sleep and displaysleep timers ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD1, D2, M1
Filed asnextbsd-userland#228
Parentnextbsd#490
## Summary

Port `PMAssertions.c` (7,650 lines), `PMAssertionLog.c` and the v1 parts of `PMSettings.m`. When the system is idle and no assertion prevents it, pmconfigd requests sleep (`IOPMROOT_SLEEP` with reason "Idle Sleep").

## Design

- MIG routines `io_pm_assertion_create` / `set_properties` / `retain_release` / `copy_details`, `io_pm_declare_user_active`
- strip IOReport/powerlog logging, the iOS "async offload" assertion path (verify `gAssertionConnection` still sets up on macOS), SMC, and entitlement gating (D1 shims)
- idle time from `TTYKeepAwake` (utmpx + tty atime) and the HID idle source (V1) -- there is no `IOHIDEventSystem`
- settings in CFPreferences `com.apple.PowerManagement` (and the `<host-UUID>` domain), changes signalled on `com.apple.system.powermanagement.prefschange`
- the assertion timeout and category plists from `Resources/` are loaded at start (`initSystemAssertionTimeoutList` / `initAssertionCategories`) -- ship them

## Acceptance

- [ ] `caffeinate -i sleep 60` holds off idle sleep, and `pmset -g assertions` shows it
- [ ] with no assertions, idle sleep fires after the configured timer

D5 · pmconfigd: S3 sleep/wake orchestration -- CanSystemSleep, WillSleep, WillNotSleep, WillPowerOn, HasPoweredOn, with a 30 s acknowledgement budget ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD2, K1, K4
Filed asnextbsd-userland#229
Parentnextbsd#490
## Summary

The central deliverable: pmconfigd reproduces the message sequence apps observe from xnu's `IOPMrootDomain`, then lets the kernel sleep.

## Sequence (xnu semantics)

1. a sleep request: software (`IOPMSleepSystem`), idle (D4), lid or power button (policy below), or low battery
2. **`kIOMessageCanSystemSleep` (0x270) for idle sleep only** -- apps may veto; xnu does not send it for demand sleep
3. `kIOMessageSystemWillSleep` (0x280) -- apps must acknowledge; **30 s budget per client** (`kWillSleepMaxTimeReq`), tardy clients are logged and sleep proceeds
4. `IOPMROOT_ACK{serial, 0}` -> S3; a veto sends `IOPMROOT_ACK{serial, EBUSY}`, then `kIOMessageSystemWillNotSleep` (0x290)
5. on wake: `kIOMessageSystemWillPowerOn` (0x320), then `kIOMessageSystemHasPoweredOn` (0x300)

## Design

- port `PMConnection.m`'s connection and acknowledgement state machine (~1,500 of its 4,153 lines); dark wake, sleep services, TCP keep-alive and auto-power-off are deferred
- v1: every wake is a full wake -- report `CPU|Graphics|Audio|Network` and send a single capability `DidChange`, so the state machine is satisfied
- **policy moves into pmconfigd**: set `hw.acpi.lid_switch_state`, `sleep_button_state` and `power_button_state` to `NONE` so the kernel never initiates sleep itself (except its own thermal-critical poweroff), and apply Apple's clamshell rule and the `Sleep On Power Button` setting in pmconfigd
- publish `Last Sleep Reason` strings exactly as Apple does: "Idle Sleep", "Software Sleep", "Clamshell Sleep", "Power Button Sleep", "Low Power Sleep"

## Acceptance

- [ ] a test client registered with `IORegisterForSystemPower` sees WillSleep -> HasPoweredOn across `pmset sleepnow` in QEMU
- [ ] a client vetoing CanSystemSleep blocks idle sleep, and the veto is logged
- [ ] a client that never acknowledges delays sleep by at most 30 s

D6 · pmconfigd: publish power state for Gershwin in SCDynamicStore -- battery %, AC, lid, sleep pending ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD3
Filed asnextbsd-userland#230
Parentnextbsd#490
## Summary

Gershwin cannot speak Mach -- its libdispatch is built with `HAVE_MACH` disabled -- so it cannot use IOPMLib. It consumes power state through SCDynamicStore keys, notify(3) and the `pmset` CLI.

## Design

Publish `State:/IOKit/PowerManagement/CurrentSettings` (Apple's `kIOPMDynamicStoreSettingsKey`), `State:/IOKit/LowBatteryWarning`, and power-source summary keys through `PMStore`. This is the same pref-pane seam as nextbsd#395 / #396 / #397 (E11).

## Acceptance

- [ ] `scutil` shows battery percentage, AC state and lid state, and they update on change

D7 · pmconfigd: CPU power policy -- set Intel HWP EPP by AC/battery profile; replaces FreeBSD powerd, with an interim LaunchDaemon until then ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD3
Filed asnextbsd-userland#231
Parentnextbsd#490
## Summary

FreeBSD's `powerd` ships but **no LaunchDaemon runs it**, so laptop CPU frequency is unmanaged today. The srclist marks `usr.sbin/powerd` REPLACE by PowerManagement.

## Design

- **Interim, now:** an `org.nextbsd.powerd` LaunchDaemon running base `powerd -a hiadaptive -b adaptive`
- **pmconfigd:** don't port powerd's polling loop. On Intel HWP machines `hwpstate_intel` is info-only and the hardware picks the frequency; the knob is `dev.hwpstate_intel.N.epp` (0 = performance ... 100 = efficiency). Set it by profile -- e.g. low on AC, high on battery, 100 in low-power mode
- **AMD:** `hwpstate_amd` has no EPP/CPPC in 15.1, only discrete `dev.cpu.N.freq` levels -- lift powerd's adaptive loop (~300 LOC) or leave it to the kernel's `power_profile`
- retire the interim daemon, and add `/usr/sbin/powerd` to `nextbsd-freebsd-compat`'s superseded list, once pmconfigd owns this

## Acceptance

- [ ] on a HWP laptop, unplugging AC changes `dev.hwpstate_intel.0.epp`

L1 · libIOKit: IOServiceAddInterestNotification over 'IOIN', and IORegistryEntrySetCFProperty on the root domain ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onK6
Filed asnextbsd-userland#225
Parentnextbsd#490
## Summary

libIOKit (`src/libIOKit`) has matching notifications but no interest notifications, no `IOServiceOpen`, and no `IOConnectCallMethod`.

## Design

- `IOServiceAddInterestNotification`: register through `IOREGIOCWATCH` with `IOREG_EVENT_INTEREST`, and decode 'IOIN' in the receive path (`IOKitNotify.c` already demuxes by msgid); deliver `(refcon, service, messageType, messageArgument)` through an `IONotificationPortRef` that supports both dispatch queues and run loops (~200 LOC)
- `IORegistryEntrySetCFProperty(ies)` on the root-domain node -> `IOPMROOT_SETPROP` (~60 LOC)
- keep `IOServiceOpen` / `IOConnectCallMethod` out of scope, as the hardware-registry plan decided; L4 re-aims the power calls instead

## Acceptance

- [ ] a test client receives a clamshell interest message from `IOPMrootDomain0`

L2 · libIOKit: IOPowerSources (ps.subproj) -- IOPSCopyPowerSourcesInfo and friends, plus <IOKit/ps/*.h> ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD3, M1
Filed asnextbsd-userland#232
Parentnextbsd#490
## Summary

Port `IOKitUser/ps.subproj` (`IOPowerSources.c` 460, `IOPowerSourcesPrivate.c` 531) and install `IOPowerSources.h`, `IOPSKeys.h` and the private headers.

## Design

- `IOPSCopyPowerSourcesInfo` -> MIG `io_ps_copy_powersources_info` (OOL -- M1)
- `IOPSGetTimeRemainingEstimate`, `IOPSGetProvidingPowerSourceType` and `IOPSDrawingUnlimitedPower` decode `notify_get_state` bits and need nothing more
- `IOPSNotificationCreateRunLoopSource` uses `notify_register_mach_port` + `CFMachPort`
- `IOPSRequestBatteryUpdate` is a no-op; `IOPSPowerSourceSupported` reads `IODeviceTree:/product` -- patch it, or publish `builtin-battery`

## Acceptance

- [ ] a small test prints the same power sources as `pmset -g batt`

L3 · libIOKit: IOPMAssertion API and the IOPMLib / IOPMLibPrivate headers ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD4, M1
Filed asnextbsd-userland#233
Parentnextbsd#490
## Summary

Port the assertion client side of `IOKitUser/pwr_mgt.subproj` -- `IOPMAssertions.c` (3,816 lines) and `IOPMLibPrivate.c` (2,305) -- and install `IOPMLib.h`, `IOPMLibPrivate.h`, `IOPMKeys.h` and `powermanagement_mig.h` under `<IOKit/pwr_mgt/>`.

## Design

- `IOPMAssertionCreateWithName` / `Description` / `Properties`, `Retain`, `Release`, `SetProperty`, `CopyAssertionsByProcess`, `CopyAssertionsStatus`, `DeclareUserActivity`
- `_pm_connect` uses `bootstrap_look_up2(kIOPMServerBootstrapName, BOOTSTRAP_PRIVILEGED_SERVER)` -- check launchd's support for the privileged flag
- replace the `CFNumberFormatter` site (`IOPMAssertions.c` ~547-560) with `CFStringCreateWithFormat`
- about 1,500 lines of `IOPMAssertions.c` are the iOS async-offload machinery -- compile out where not needed
- `IOPMEnergyPrefs.c` for `IOPMCopyPMPreferences` / `IOPMSetPMPreferences` / `IOPMFeatureIsAvailable`, which `pmset` needs

## Acceptance

- [ ] a C test creates and releases an assertion, and `pmset -g assertions` lists it by PID

L4 · libIOKit: IORegisterForSystemPower, IOAllowPowerChange, IOCancelPowerChange and IOPMSleepSystem re-aimed at pmconfigd ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD5, L1
Filed asnextbsd-userland#234
Parentnextbsd#490
## Summary

Apple's `IOPMLib.c` implements these with `IOServiceOpen` on the root domain, `IOConnectCallScalarMethod` (selectors 2 and 3/4), and `IOServiceAddInterestNotification(kIOAppPowerStateInterest)`. In the userspace design pmconfigd is the notifier, so these calls go to it.

## Design

- `IORegisterForSystemPower` returns an opaque client handle in place of an `io_connect_t` (as `io_object_t` already is) and subscribes to pmconfigd; messages arrive using the **same 'IOIN' wire struct** the kernel sends (K6)
- `IOAllowPowerChange` / `IOCancelPowerChange(refcon)` acknowledge to pmconfigd with the kernel-minted serial
- `IOPMSleepSystem` / `IOPMFindPowerManagement` -> pmconfigd's sleep entry point
- because the wire struct and serials are the kernel's, R1 changes only the destination

## Acceptance

- [ ] an unmodified client written against Apple's `IORegisterForSystemPower` example receives sleep and wake notifications

C1 · pmset: port pmset.m -- -g, -g batt, -g assertions, -g log, sleepnow, displaysleep/sleep settings, schedule ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD1, D3, L2, L3, L4
Filed asnextbsd-userland#235
Parentnextbsd#490
## Summary

Port `pmset/pmset.m` (8,640 lines, Objective-C). The hardware-registry plan's K3 marker is "`pmset -g` prints sensible output".

## Paths

- `pmset -g` reads SCDynamicStore `State:/IOKit/PowerManagement/CurrentSettings` and root-domain `Supported Features`
- `pmset -g batt` -> `IOPSCopyPowerSourcesInfo`; `pmset -g assertions` -> `IOPMCopyAssertionsStatus` / `ByProcess`
- `pmset sleepnow` -> `IOPMSleepSystem`, returning `kIOReturnNotPrivileged` for non-root
- setting values -> `IOPMSetPMPreferences` + the prefs-change notify
- `pmset -g log` queries ASL (43 sites) against powerd's store
- `pmset schedule` -> `IOPMSchedulePowerEvent` (S2)

## Porting

- `CFDateFormatter` date parsing for `pmset schedule` is the one real ICU dependency -> `strptime`
- compile out IOReport (53 sites) and SkyLight (arm64)

## Acceptance

- [ ] `pmset -g`, `pmset -g batt` and `pmset -g assertions` print sensible output on a laptop and in QEMU
- [ ] `pmset sleepnow` suspends a QEMU guest through D5

C2 · caffeinate: port caffeinate.c ready

Reponextbsd-userland
Labelsenhancement area:power good first issue
Depends onL3
Filed asnextbsd-userland#236
Parentnextbsd#490
## Summary

`caffeinate/caffeinate.c` is 297 lines of plain C using libdispatch and CF, and makes exactly two IOPMLib calls: `IOPMAssertionCreateWithDescription` and `IOPMAssertionSetProperty(kIOPMAssertionOnBehalfOfPID)`. It holds the assertion until its child exits (`DISPATCH_SOURCE_TYPE_PROC`).

Flags map to `PreventUserIdleSystemSleep` (`-i`, default), `PreventUserIdleDisplaySleep` (`-d`), `PreventSystemSleep` (`-s`), `UserIsActive` (`-u`) and `PreventDiskIdle` (`-m`).

It hard-codes `/System/Library/CoreServices/powerd.bundle` as its localization bundle path -- ship that layout or patch the string. **The ideal first end-to-end smoke test.**

## Acceptance

- [ ] `caffeinate -d sleep 30` shows a display-sleep assertion in `pmset -g assertions` for 30 s

S1 · reboot/shutdown/halt: vendor Apple's system_cmds versions over the BSD PID-1 signal protocol launchd already honours ready

Reponextbsd-userland
Labelsenhancement area:power area:base
Depends on
Filed asnextbsd-userland#237
Parentnextbsd#490
## Summary

None of Apple's reboot, shutdown or halt is vendored -- FreeBSD's ship live. The compat audit's "Apple reboot vendored but deferred on IOPMLib" is stale.

## Design

From `system_cmds-1042.120.1`:

- on `__APPLE__`, `shutdown`/`reboot` call `reboot3(howto)` ("launchd(8) handles reboot"), which lives in macOS libxpc; **NextBSD has no `reboot3`**. The `!__APPLE__` branch sends PID 1 `SIGINT` (reboot), `SIGUSR1` (halt), `SIGUSR2` (poweroff), `SIGWINCH` (power cycle) -- and **launchd already honours exactly that** (`src/launchd/src/core.c` ~4390-4440, nextbsd#398 closed). Use it.
- IOPMLib is only needed for `shutdown -s` (`IOPMFindPowerManagement` + `IOPMSleepSystem`) -- compile `-s` out until L4
- both call `reserve_reboot()` -> `kextmanager_lock_reboot` on `com.apple.KernelExtensionServer`, which kextd does not currently register. Either have kextd serve a trivial lock, or ifdef it out (E6, nextbsd#182)
- `halt` is `reboot` under another name
- add `/sbin/reboot`, `/sbin/halt` and `/sbin/shutdown` to `nextbsd-freebsd-compat`'s superseded list

Remove the "IOPMLib stubs for halt/shutdown/reboot" item from nextbsd#115 (E9); this ticket delivers it.

## Acceptance

- [ ] Apple's `reboot`, `halt` and `shutdown -h/-r now` work on a booted system

S2 · pmconfigd: pmset schedule sleep/shutdown/restart performed by pmconfigd itself; wake/poweron waits on S3's RTC alarm ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD1, S1
Filed asnextbsd-userland#238
Parentnextbsd#490
## Summary

Port `AutoWakeScheduler.c` and `RepeatingAutoWake.c` (`io_pm_schedule_power_event`, `_schedule_repeat_event`, `_cancel_repeat_events`), persisting to SCPreferences `com.apple.AutoWake.xml`.

## Design

- On macOS, scheduled sleep, shutdown and restart only post a **distributed CF notification** for loginwindow to act on (`_askNicelyThen*`). NextBSD has no loginwindow doing that, and no `distnoted` -- so pmconfigd performs them itself: sleep through its own state machine (D5), shutdown and restart through the S1 signal path
- replace the `CFCalendar` (Gregorian) sites with plain C time arithmetic
- **wake / poweron** needs an RTC alarm programmed at sleep time -- blocked on S3

## Acceptance

- [ ] `pmset schedule sleep "<in 2 minutes>"` sleeps the machine; `pmset -g sched` lists it

S3 · Spike: an RTC wake alarm for pmset schedule wake -- FreeBSD has no rtcwake spike

Reponextbsd-kernel
Labelsarea:power area:research
Depends onK4
Filed asnextbsd-kernel#245
Parentnextbsd#490
## Summary

`pmset schedule wake` reaches the hardware in xnu through root-domain properties (`WakeByCalendarDate`, `wake` seconds), which program an RTC or SMC alarm at sleep. FreeBSD has **no `rtcwake`-style API** to arm an S3 wake alarm.

## Questions

- an `atrtc` alarm ioctl, the ACPI time-and-alarm device (`ACPI000E`, `_TIV` / `_STV`), or something else?
- which laptops expose a usable RTC alarm, and does it wake from S3?
- QEMU: `system_wakeup` over QMP covers CI; is there an emulated RTC-alarm path?

## Deliverable

A design note with a recommendation, then the implementation ticket.

V1 · Display sleep and HID idle time without IODisplayWrangler -- pmconfigd owns display idle ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD4
Filed asnextbsd-userland#239
Parentnextbsd#490
## Summary

On Intel Macs pmconfigd watches `IODisplayWrangler` for display power and reads HID idle time from `IOHIDSystem` / `IOHIDEventSystemClient` (47 IOHID sites). NextBSD has neither.

## Design

- **Idle time:** evdev -- `/dev/input/event*` timestamps -- or the display server's idle notifier, published as the idle source SystemLoad and TTYKeepAwake consume
- **Display sleep actor:** pmconfigd runs the display idle timer from `displaysleep`, and blanks through DPMS (the X server) or DRM
- `pmset displaysleepnow` sets `IORequestIdle` on the wrangler on macOS -- route it to the same actor

Links: nextbsd#395 (brightness, E11), E5 (display and DRM resume, nextbsd#390).

## Acceptance

- [ ] with no input for the `displaysleep` interval, the display blanks; input wakes it

T1 · CI: power boot markers -- PM-BOOT-OK, PM-PS-OK, PM-ASSERT-OK, and PM-SLEEP-OK through QEMU S3 ready

Reponextbsd
Labelsarea:ci area:power
Depends onD1, D5, K2
Filed asnextbsd#492
Parentnextbsd#490
## Summary

Make the power path testable in the CI VM, which can exercise the whole sleep handshake but not batteries, lid or HWP.

## Stages

- **PM-BOOT-OK** -- pmconfigd checks in
- **PM-PS-OK** -- `pmset -g batt` reports an AC-only source (QEMU has no ACPI battery)
- **PM-ASSERT-OK** -- `caffeinate` plus `pmset -g assertions`
- **PM-SLEEP-OK** -- guest `pmset sleepnow` -> S3 -> QMP `system_wakeup` -> HasPoweredOn seen by a test client; needs QEMU q35 with S3 enabled (`ICH9-LPC.disable_s3=0`)
- **power button** -- QMP `system_powerdown` raises the fixed-feature button (K2)

Pairs with nextbsd#428 (the test gates pass at the login prompt).

E1 · Packaging: power LaunchDaemons, build blocks, man pages, compat strip entries, and ACPI sysctl defaults ready

Reponextbsd-userland
Labelsenhancement area:power
Depends onD1, S1, D7
Filed asnextbsd-userland#240
Parentnextbsd#490
## Summary

- LaunchDaemon plists: `com.apple.powerd` (D1), the interim `org.nextbsd.powerd` (D7)
- `build-userland.sh` blocks for PowerManagement and the libIOKit additions, after notifyd
- man pages: `pmset.1`, `caffeinate.8`, `reboot.8`, `shutdown.8`
- `nextbsd-freebsd-compat` superseded entries for `/sbin/reboot`, `/sbin/halt`, `/sbin/shutdown` and, later, `/usr/sbin/powerd`
- defaults: `hw.acpi.lid_switch_state`, `sleep_button_state` and `power_button_state` = `NONE` once pmconfigd owns the policy (D5), falling back to the kernel's S5 on the power button only when pmconfigd is not running

R1 · Spike: an in-kernel IOPMrootDomain -- move the sleep/wake fan-out, acknowledgements and policy into iopmroot spike

Reponextbsd-kernel
Labelsarea:power area:research
Depends onD5
Filed asnextbsd-kernel#246
Parentnextbsd#490
## Summary

The hybrid's kernel-later path. It is cheaper than first assumed: **the IORegistry is already in the kernel** (hwregd was retired, nextbsd#218), and kernel-originated Mach sends already work (`iokit_notify.c`, `ipc_mqueue_send_always`). The original reason to keep this in userspace -- "no in-kernel IOKit" -- no longer holds.

## Questions

- a per-port client registry in `iokit_pmroot.c` for `kIOAppPowerStateInterest` -- a generalisation of the `apm_clone` voter model -- with a 30 s acknowledgement callout (`kern.pmtimeout`) and CanSystemSleep for idle sleep only
- acknowledgement by ioctl (cheap) or an in-kernel MIG server (faithful; in-kernel MIG servers already exist in `files.compat_mach`)
- which policy moves in -- idle timer, assertion-level gating, clamshell and power-button rules, low-battery and thermal emergency sleep -- and what stays in pmconfigd: assertion aggregation, preferences, power-source republishing, scheduled wake
- estimate: +1,000-1,500 kernel LOC; the userland change is deleting pmconfigd's fan-out and pointing L4 at `/dev/iopmroot`

## Deliverable

A design note with a recommendation, as E14's S1 (nextbsd-kernel#227). Cross-links E12 (nextbsd#445), nextbsd#225, `nextbsd-inkernel-iokit-feasibility.html`.

Linked issues

These stay in their current epics; E17 cross-links them.

IssueEpicWhy it relates
nextbsd#225E6Retire /dev/devctl for IOKit notifications -- K3 keeps it inert
nextbsd#66E1MIG out-of-line data -- decides M1
nextbsd#115E9system_cmds port -- its IOPMLib-stub item is delivered by S1
nextbsd#395E11Display brightness -- same pref-pane seam as D6 and V1
nextbsd#179E6ko-to-kext -- needed for acpi_video, coretemp, amdtemp
nextbsd#182E6kext_tools / OSKext -- the kextmanager reboot lock S1 must serve or drop
nextbsd#390E5GPU re-init races -- wake from S3 must re-check /dev/dri
nextbsd#428E10Test gates pass at the login prompt -- pairs with T1

Recreating in GitHub

Everything is filed; these commands are kept in case the issues ever need recreating. File the epic first, so each child can name its parent, then the children in dependency order, attaching each as a sub-issue of the epic.

# 1. the label, in all four repos
gh label create area:power --color fbca04 \
  --description "Power management: pmconfigd, IOPMLib, power sources, ACPI sleep/wake, cpufreq, reboot/shutdown" \
  --repo nextbsd/nextbsd-kernel        # repeat for each repo

# 2. any single ticket: copy its body from this page into a file
gh issue create --repo nextbsd/nextbsd-kernel \
  --title "..." --label bug,area:power --body-file K1.md

# 3. or all of them, from the embedded JSON
curl -s https://pkgdemon.github.io/nextbsd-e17-tickets.html | python3 -c '
import sys, re, json
page = sys.stdin.read()
m = re.search(r"<script type=\"application/json\" id=\"e17-data\">(.*?)</script>", page, re.S)
print(json.dumps(json.loads(m.group(1)), indent=1))' > e17.json