Skip to content

drm/apple-g5p: Firmware park and bounded system sleep - #120

Open
brkosebay wants to merge 53 commits into
OrchardLinux:mainfrom
brkosebay:j71t/g5p-suspend
Open

brkosebay wants to merge 53 commits into
OrchardLinux:mainfrom
brkosebay:j71t/g5p-suspend

Conversation

@brkosebay

@brkosebay brkosebay commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Part 10 of 11 of the iPad 5 (J71t) series in this repository; merge after #119.

Problem

The GPU blocked every system sleep, and an idle GPU kept its power on because the firmware was never allowed to park itself.

Change

Let the firmware self-park after bring-up and ring a parked firmware without the GPC settle wait. Implement a bounded retained system sleep: register the reservation as a suspend admission, hold clients across it, answer the readiness query, and refuse sleep with a named reason while work, a kexec power hold or pmgr's gfx hold is active. pmdomain ends the warm boot hold at sync_state, the gfx gate is cycled with auto-PM like pmgr-pwrstate, and the other admission users name their refusals too. System sleep is refused by default on J71t, because GPU resume does not work yet: #117's testing reached a completed suspend cycle, but the GPU did not come back. The last patch adds a read-only suspend parameter to the G5P driver, off by default. While it is off, the GPU's sleep admission refuses before anything is shut, and /sys/power/suspend_ready reads not-ready <gpu>:resume-unsupported -95. To try sleep anyway, boot with a9_gpu_drm.suspend=1; sleep is then admitted exactly as before. Boards without the G5P driver are not affected.

Testing

  • Hardware: iPad 5 Wi-Fi (J71t). Hands-on testing on 2026-11-11 with an integrated build of this series, made before the final failure-path fixes, covered firmware park while idle.
  • Not covered on hardware: GPU resume after a completed system sleep, which did not come back in testing.
  • 2026-11-12: no further hardware test of this change beyond the above. The hardware results are from the release build tested on J71t that day (20+ warm restarts without a freeze); since then main took several PRs of this series in reworked form, so the top of this series no longer applies to exactly that kernel tree and has not been flown as it is now.
  • Host: the Linux test suite passes at this branch's head, and the kernel and m1n1 patch series apply on apple-16k and apple-4k. At the top of the series the applied kernel tree differs from the built release image only where main took earlier PRs of this series in reworked form, by the closed perf: apple: A9 memory port byte counters #123, and by the new G5P sleep refusal (drivers/drm 0045, not yet run on the device).
  • Other boards that share these drivers or compatibles have not been tested.

Dependencies

Depends on #119.

This branch is stacked on the PRs it depends on and carries their commits; the change of this PR is the commit "drm/apple-g5p: Firmware park and bounded system sleep", and the merge commits after it bring the branch onto current main.

This update keeps the branch's history: it merges current main and the updated branch below it into this branch. Nothing is forced.

🤖 Generated with Claude Code

Add the A9 (S8003) G5P render driver used by the merged PowerVR/G5P Mesa
winsys as kernel patches, enable it on J71t in the device tree, set
CONFIG_DRM_APPLE_G5P=y for apple-16k, and add an install-time recipe that
extracts the original META LDR (apple/agx-g5p-c0.ldr) from the device's own
iOS kernelcache into /lib/firmware. No firmware is committed.

The driver provides TYPED_V3 rendering, GEM buffers, VM/arena mappings,
PRIME import/export, syncobj, bounded retirement and recovery. It does not
expose the old compute or fixed-fixture ABI.

Hardware: the exact kernel this derives from (brkosebay/linux a9/gpu-pr1
407b7bf2cbbe) booted on a J71t with the merged Mesa, rendered labwc on the
GPU with KMS scanout at 60 Hz, and passed the G5P device rungs with zero GPU
events. This Orchard adaptation itself has not yet been built by orchard or
run on hardware.

Assisted-by: LLM
kernel/configs/apple-16k/config:
CONFIG_DRM_APPLE_G5P=y
These options provide g5p and its Kconfig dependencies on both page sizes.

Assisted-by: LLM
kernel/configs/apple-16k/config:
CONFIG_APPLE_AOP_GYRO=m
CONFIG_APPLE_AOP_PRESSURE=m
These options provide aop and its Kconfig dependencies on both page sizes.

Assisted-by: LLM
kernel/configs/apple-16k/config:
# CONFIG_NVME_APPLE_ANS_EARLY_USB is not set
These options provide warm-storage and its Kconfig dependencies on both page sizes.

kernel/configs/apple-4k/config:
# CONFIG_NVME_APPLE_ANS_EARLY_USB is not set
These options provide warm-storage and its Kconfig dependencies on both page sizes.

Assisted-by: LLM
kernel/configs/apple-16k/config:
CONFIG_APPLE_AOP_GYRO=m
CONFIG_APPLE_AOP_PRESSURE=m
These options provide aop and its Kconfig dependencies on both page sizes.

Assisted-by: LLM
kernel/configs/apple-16k/config:
# CONFIG_NVME_APPLE_ANS_EARLY_USB is not set
These options provide warm-storage and its Kconfig dependencies on both page sizes.

kernel/configs/apple-4k/config:
# CONFIG_NVME_APPLE_ANS_EARLY_USB is not set
These options provide warm-storage and its Kconfig dependencies on both page sizes.

Assisted-by: LLM
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
A Mac grants an iPad more than the standard USB current, but the charger
took the gadget's reported budget unchecked and the composite gadget
could draw the extra current while the bus was suspended.

Cap and log the USB host budget in the TriStar charger, draw no extra
Apple current while suspended, and let the initramfs and orchard USB
helpers opt J71t into the Mac current profile. Documented in
docs/bring-up/charging/j71t-mac-current.md, with tests.

Assisted-by: LLM
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
The A9 AOP hosts the accelerometer, gyroscope, pressure and
ambient-light sensors. Its firmware needs boot arguments and per-device
calibration that m1n1 did not pass on S8003, and a reloaded AOP (after
kexec or sleep) lost its sensor firmware state, so the sensors did not
come back.

m1n1 passes the A9 AOP boot arguments and sensor calibration. The kernel
selects a certified AOP reload path through device-tree match data
(bindings, DT, mailbox, pmdomain, IIO and AOP driver patches), keeps the
AOP power domain and firmware state for that reload, and refuses a
suspend that would interrupt the firmware's boot. The J71t firmware
recipe gains the AOP pieces, the gyroscope and pressure drivers are
enabled, and J71t's accelerometer and gyroscope move from missing to
working, and its light sensor from untested to working.

Assisted-by: LLM
The USB serial number changed between installed boots and RAM sessions,
so the host saw a new device every boot and could not keep its SSH
known_hosts entry or serial console name.

Share one BOARD/hashed-ID serial format between installed boots and RAM
sessions (usb-identity), and get the per-boot SSH identity through the
existing ACM console. Documented in docs/device-protocol.md, with tests.

Assisted-by: LLM
Opening the Smart Cover should wake the iPad, but the PMIC GPIO driver
had no notion of wake-only events, and arming the cover events while the
cover was absent aborted every suspend.

Describe wake-only events in the apple,d2365-pmic-gpio binding, let the
driver wake from sleep on the listed GPIO events, and list the Smart
Cover events in the J71t DT.

Assisted-by: LLM
Bring this branch onto current main. The merge keeps the other side's
tree; the next commit carries the reworked version of this branch's
change.
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
Bring this branch onto current main and the series it depends on. The
merge keeps the other side's tree; the next commit carries the reworked
version of this branch's change.
A7-A9 have no EL2 and boot without any PSCI firmware, so Linux on an
iPad 5 could not bring secondary CPUs down and up again, and a kexec had
nowhere to park them. CPU hotplug, kexec and a clean poweroff all need a
PSCI implementation that stays resident after m1n1 hands over.

Carry m1n1 patches that keep a small EL3 PSCI implementation resident:
CPU_ON/CPU_OFF with a firmware rendezvous (EL1 WFI/WFE trap to EL3),
SYSTEM_OFF, and a guarded SYSTEM_RESET that boots the default entry
again. The loader marks retained hardware in the device tree after a
PSCI re-entry. The resident PSCI is on by default on these SoCs; the
shipped boot framing keeps psci_reset=0, so SYSTEM_RESET stays off
unless a loader enables it. Adds boot/m1n1/psci-loader and its tests.

Assisted-by: LLM
The TriStar charger subscribed to the PMU's RTC event 16 for source
identification. An acknowledgement there could take the RTC's wake
event, and the polled cover inputs had no driver enabled.

Give RTC event 16 to the RTC alone and keep the PMU and RTC event split
(DT, RTC and USB patches), so one consumer's acknowledgement cannot take
the other's wake event. Enable KEYBOARD_GPIO_POLLED for the polled cover
inputs.

Assisted-by: LLM
On J71t the NVMe controller (ANS) sits behind the H9P PCIe port, and
resetting that port loses the controller until a cold boot. A kexec
therefore has to hand over a stopped but powered ANS, with its DART
translations retained, or the next kernel loses its root filesystem. The
first version of this handoff panicked when a step failed, which with
panic=0 is an indefinite hang.

Keep the certified ANS kexec handoff: DT and bindings describe it, the
DART, H9P and NVMe drivers stop queues and translations in order and the
next kernel picks them up, and core/printk export what the modules need.
Every failure on the outgoing side now ends in a native cold reset
through apple_cold_reset() instead of a panic, with explicit returns
after it. The port is handed over powered, with PERST# asserted and the
link stopped, and the incoming kernel brings it up with the cold-boot
sequence (port power cycle, NVMe mode, port enable, then link training).
Retraining the powered port by PERST# alone, as iOS never does, left the
root port in Detect.Quiet and cost a timeout on every warm boot.

Assisted-by: LLM
After a kexec the next kernel found the S8000 GPIO controller with
interrupt modes armed by the previous kernel, the PIO SPI controller
with queued transfers and an asserted chip select, and the touch
controller still running. An orphaned GPIO interrupt fires forever and
touch did not come back.

Retire inherited GPIO interrupt modes before the chained parent
interrupts are enabled, drain and idle the S8000 PIO SPI controller on
shutdown and at probe, and stop the Apple Z2 touch controller before its
SPI parent shuts down.

Assisted-by: LLM
A warm restart through kexec needs the next kernel to see the pristine
device tree from boot, the pstore region, the display state and the
power domains that must not be cut while the next kernel starts.

DT, OF, DRM, pmdomain and pstore patches retain the board-selected warm
handoff state, so probe and shutdown keep what the next kernel needs:
the pristine boot device tree, the pstore region, the display state and
the Apple pmgr warm hold. The Orchard service file learns the retained
state.

Assisted-by: LLM
On a tethered J71t boot a restart must be a native cold reset, but the
watchdog's restart handler ran below PSCI's, and its restart guard
dereferenced a parent that probe never set.

Describe J71t's tethered boot in DT. On such a board the watchdog
restart handler registers above PSCI and performs the native cold reset;
in the warm and soft modes it leaves the watchdog alone and returns
success, so PSCI's restart runs. Probe sets the parent before
registering, and the restart callback tolerates a missing one. Other
devices keep the current priority.

Assisted-by: LLM
Suspend on J71t lost the NVMe controller (its port must keep power and
refclk), the DART translations, the USB gadget link and Wi-Fi after
resume, and drivers could only refuse sleep after the filesystems had
been synced.

Add a driver admission step and a precheck notifier before sync to the
PM core, with a suspend_ready report. Pair suspend to idle with retained
ANS power in the H9P driver, stop and rebuild the ANS DART and NVMe
controller around sleep, keep the dwc2 gadget connected, stop the VXD
firmware, and keep Wi-Fi down when its link is lost. The PMIC GPIO
driver refuses sleep when it cannot program the wake mask, the charger
uses J71t's plug event as a wake source, and the J71t DT enables paired
sleep, the s2idle gadget and charger wake. On restart and poweroff, NVMe
stops the Apple ANS as it does before a kexec, and the H9P driver hands
the S8003 ports off in the same powered state; these two patches sit
here because they build on this topic's H9P and NVMe sleep code.

Assisted-by: LLM
The A9 GPU (PowerVR-derived G5P) had no kernel driver, so the desktop
rendered in software.

Add the apple-g5p DRM driver: bindings and J71t DT, the render/compute
UAPI, the firmware transport and recovery core, buffer VMs with cache
visibility, typed render and compute submission, and the GEM exporter's
CPU cache contract. The firmware recipe extracts the G5P firmware from
the device's own IPSW (src/orchard/apple/agx_g5p.py), and DRM_APPLE_G5P
is enabled for apple-16k.

Assisted-by: LLM
Each render job paid a fixed host cost (serial submission, polling after
events, uninterruptible VM binds), which capped frame rates well below
the GPU's capacity.

Account per-job GPU and host time, reduce serial job overhead, make VM
binds killable and tune post-event polling, record joint latency,
pipeline two render jobs deep, honour fragment-only waits, start the
next job early during the 3D wait, gate TA PM records on dirty state,
and default to the measured pipeline configuration.

Assisted-by: LLM
The GPU blocked every system sleep, and an idle GPU kept its power on
because the firmware was never allowed to park itself.

Let the firmware self-park after bring-up and ring a parked firmware
without the GPC settle wait. Implement a bounded retained system sleep:
register the reservation as a suspend admission, hold clients across it,
answer the readiness query, and refuse sleep with a named reason while
work, a kexec power hold or pmgr's gfx hold is active. pmdomain ends the
warm boot hold at sync_state, the gfx gate is cycled with auto-PM like
pmgr-pwrstate, and the other admission users name their refusals too.

Assisted-by: LLM
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in drivers/usb are renumbered to follow
main's.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, drivers/base and drivers/usb
are renumbered to follow main's.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, drivers/base and drivers/usb
are renumbered to follow main's.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, drivers/base,
drivers/pmdomain and drivers/usb are renumbered to follow main's.
The A9 AOP driver on main reads apple,aop-target, apple,chip-id and
apple,chip-revision, and the motion sensor drivers read their factory
calibration, from properties the bootloader fills in. Nothing on main
fills them in; going by the driver's probe, the A9 AOP cannot start
without them (read from the code, not run). This is the m1n1 patch that
fills them in, from OrchardLinux#101, unchanged.

Assisted-by: LLM
From OrchardLinux#101, the part that main did not take with the sensors: the
retained AOP reload across kexec (checksummed shutdown log, power-state
observer, pr_flush export, warm_state attribute). The sensor parts that
main carries are left out of these patches; what remains applies on
top of main's AOP driver and its fresh-start order.

Assisted-by: LLM
From OrchardLinux#110, the part main did not take with the stable USB serial: the
platform layer embeds each supported board's pristine DTB and a
compatible-to-file map in the initrd. Before switching root, the initrd
copies the running board's tree to /run/orchard-kexec/pristine.dtb, or,
after a warm restart, the retained tree the previous kernel named with
orchard_warm.dtb=, and records the boot ID next to it. Warm restart
boots the next kernel with that tree, never with one rebuilt from an
updated /boot.

No feature table is added: whether warm restart is offered is decided
from the mechanisms it uses, not from a feature state.

Assisted-by: LLM
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, drivers/base,
drivers/pmdomain and drivers/usb are renumbered to follow main's.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, devices/t8010, drivers/base,
drivers/media, drivers/pmdomain, drivers/power and drivers/usb are
renumbered to follow main's.

Main's versions apply first now, so 0011 in drivers/media keep their
change with a diff regenerated for the new context.
From OrchardLinux#101, the suspend precheck that main did not take with the
sensors. It belongs with suspend: it makes the AOP refuse a pre-sync
system sleep while its firmware is starting or stopping.

Assisted-by: LLM
From OrchardLinux#121, the patch main did not take with HEVC. It uses the VXD
sleep stop and sleep gate this branch adds, so it sits here rather
than with the earlier warm restart branch: a failed firmware stop keeps
the firmware, command buffers and power, and kexec takes the cold-reset
path before those pages become the next kernel's RAM.

Assisted-by: LLM
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, devices/s8003,
devices/t8010, drivers/base, drivers/media, drivers/pmdomain,
drivers/power and drivers/usb are renumbered to follow main's.

Main's versions apply first now, so 0011 in drivers/media keep their
change with a diff regenerated for the new context.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, devices/s8003,
devices/t8010, drivers/base, drivers/media, drivers/pmdomain,
drivers/power and drivers/usb are renumbered to follow main's.

Main's versions apply first now, so 0011 in drivers/media keep their
change with a diff regenerated for the new context.
Bring this branch onto current main. Main has since merged OrchardLinux#124, OrchardLinux#122,
OrchardLinux#118, OrchardLinux#121, OrchardLinux#110, OrchardLinux#127, OrchardLinux#101, OrchardLinux#126, OrchardLinux#108 and OrchardLinux#111, several in part or
reworked, and OrchardLinux#123 and OrchardLinux#128 were closed. Where main carries a topic,
main's version is kept; the left-out parts that warm restart and
suspend need come back in OrchardLinux#107 and OrchardLinux#117.

This branch's own patches in devices/s8000, devices/s8003,
devices/t8010, drivers/base, drivers/media, drivers/pmdomain,
drivers/power, drivers/soc-apple and drivers/usb are renumbered to
follow main's.

Main's versions apply first now, so 0011 in drivers/media keep their
change with a diff regenerated for the new context.
On J71t a completed suspend/resume cycle does not bring the GPU back,
yet the G5P sleep admission admits an idle GPU, so
/sys/power/suspend_ready reads "ready" and userspace that follows it
suspends into a GPU that does not come back.

drivers/drm 0045 adds a read-only "suspend" parameter to the G5P
driver, off by default. While it is off, the GPU's sleep admission
refuses before anything is shut, and suspend_ready reads
"not-ready <gpu>:resume-unsupported -95". Booting with
a9_gpu_drm.suspend=1 admits sleep exactly as before. Boards without
the G5P driver are not affected.

The patch takes the next free number in drivers/drm, after the
gpu-handover branch's 0042-0044, so every existing patch keeps its
number.

Assisted-by: LLM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant