Skip to content

tgl-display: the T14's display engine lets memory idle (stage 1), and the display track - #1125

Merged
Japabu merged 6 commits into
mainfrom
display-stage1
Nov 1, 2026
Merged

Japabu merged 6 commits into
mainfrom
display-stage1

Conversation

@Japabu

@Japabu Japabu commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

With the panel lit, the T14's idle package stays in PC2. The display as the firmware's GOP left it holds the package there: about 1.2 W at idle and 1.6 W under a 10 ms sleeper, against Linux's driver (pr-1100 r7).

A read-only scout of the GOP's display engine found:

  • one linear RGB8888 plane;
  • watermark level 0 only, with levels 1-7 disabled;
  • DC states not allowed, and PSR off.

The display therefore never lets memory reach a low-power latency level.

What this PR does

  1. Records the display-engine track (issues/each-gpu-drives-its-displays-from-a-userland-driver.md).
    • Owner rulings, 2026-10-31:
    • The display task's design, not a ruling:
      • one userland driver per GPU, bringing its outputs to the compositor over a display-output port;
      • the stages, with stages 2 and 3 measured together (Vol 12: DC5 with a pipe running needs PSR active);
      • the defaults for display audio, docks and backlight.
  2. Stage 1: tgl-display, a new driver claiming the T14's iGPU (8086:9a49). It keeps the firmware's mode and surface. It reads pcode's memory latencies and SAGV block time and programs PLANE_WM levels 0-7 by Vol 12's "Watermark Algorithm".
    • Where the firmware's state is not what it understands, it refuses by name and writes nothing:
      • more than one plane or pipe;
      • the cursor on;
      • a YUV plane;
      • a stopped pipe;
      • a line time that is not the mode's;
      • a mailbox error;
      • a plane that cannot tolerate SAGV.
    • Pure logic is in toyos-tgl-display, host-tested against a modelled engine.
  3. Kernel, by the owner's ruling of 2026-10-04 ("Display and USB only"):
    • Once the DMAR walk is whole, kernel::rmrr::classify (pure, host-tested) decides every function the RMRRs name, over scopes vtd resolved.
    • A function's memory is its alone only where every RMRR overlapping any of its own names it and nothing else.
      • A scope that is not an endpoint, or a sub-hierarchy, that resolves counts as another device sharing the range. That covers another kind (an ACPI namespace device among them), a path through an unconfigured bridge, and a malformed entry together with the rest of its list.
    • A display or USB controller whose memory is its alone, in whole 4 KiB pages, keeps it.
      • Its identical, overlapping and adjacent RMRRs merge, so each address is placed once. A merged range is held against the union of the regions every domain avoids (kernel::rmrr::covered).
      • The merged ranges are mapped at their own addresses in its own domain before it attaches (VT-d §8.4), in the leaves kernel::rmrr::leaves names: 2 MiB where one fits, 4 KiB pages at the ends.
    • Every other function is refused a claim by name, in VT-d's unclaimable, as AMD-Vi refuses an IVMD's.
    • An RMRR on a segment group other than the enumerated one has unknown scopes, so it shares.
    • The T14's plane reads the firmware's framebuffer through a GGTT entry (9c800001) inside its RMRR, so without this the claim would fault every scan-out read.

Decisions

  • The pixel rate is measured off the frame counter over 30 frames and held against the firmware's own WM_LINETIME (within one unit), not decoded from the PLL. Clocks belong to the modesetting stage.
  • The transition watermark stays disabled. It tunes IPC priority, not power, and disabled it acts as the top of the allocation.
  • No DBUF reallocation: every level fits the GOP's 682 blocks at this mode. PLANE_BUF_CFG's Buffer End is inclusive, as Vol 12 "Display Buffer Allocation" gives blocks 0-159 as 0x009F0000.
  • Refused, not sized: 90/270 rotation and decompressed planes.
  • The process never exits: the claim's release resets the function, which would end the firmware's scan-out.
  • The AMD adapter's boundary for external input, clean-room and licence notes are in the track.
  • Vol 12's worked watermark example omits the "+1" its own algorithm states. The tests follow the algorithm and are hand-derived.

Checks

At the head rebased onto 3add613c1, 5fc1ba8ad: cargo run -- --ci host exit 0, 107 steps green; cargo run -- --build-only exit 0. The rebase changed no content: git range-diff shows every commit identical except one context line in images/system.toml, where main reworded netstack's comment.

The rest is at review r3's head, 230c6201d. Main's commits since touch none of the kernel files this PR changes, so they still apply. Evidence: https://github.com/ToyOSOrg/evidence/blob/e5a6e65e83e8a27da540398c9f1e2e2296e1b42d/r5/README.md. Review r2's checks are at https://github.com/ToyOSOrg/evidence/blob/4faea3bcd84adefdae5564697eac69a775e1d6bb/r4/README.md.

  • cargo run -- --ci host: exit 0, 107 steps green.
  • cargo test -p toyos-tgl-display: 19 pass.
  • kernel::rmrr: 11 pass, including every kept range covered by its source RMRRs' union. They cover one endpoint, two endpoints, a sub-hierarchy, an unknown scope (unfollowable, kind 5 or malformed), the class rule, partial pages, overlapping RMRRs of two functions, duplicates of one, and the 2 MiB leaf split.
  • Negative controls, each a checked mutation restored in the same run (exit 101 each):
    • covered checking one RMRR at a time (r5);
    • an Unknown scope that does not share (r4);
    • "alone" judged over a function's own RMRRs only (r4);
    • result blocks without "+1", and level 0 without SAGV's block time (r1, at cedde3c90). The watermark arithmetic they mutate is unchanged since.
  • QEMU: the_compositor_drives_the_virtio_gpu, exit 0. It is the one test whose claims cross VT-d (intel-iommu, which publishes no RMRR). amdvi_selftest (exit 0 at r1) runs on amd-iommu and covers only the changed OwnSpace::create caller.
  • The segment rule and table::put_4k are established by inspection only. No planned machine has a second segment. The T14's range is 2 MiB-aligned, so no planned machine runs it. The split that drives it is host-tested.

T14 A/B result (at bb1f335f7, before review r1's kernel changes)

The A/B runs from measurement branch display-stage1-ab, which is this branch merged with #1100's energy bench. That branch is not for landing.

Evidence: https://github.com/ToyOSOrg/evidence/blob/58471e7d2c0e8a08c8a223d6778be0654e9725fc/r2/README.md

Two boots, both exit 0.

Boot B's lines:

[11.014 cpu0 kernel] iommu: domain3 keeps 00:02.0's reserved 0x9c000000..0xa0800000 at its own address
[11.014 cpu0 kernel] iommu: 00:02.0 moves to domain3
[11.525 tgl-display] tgl-display: plane 1 1920 px wide, totals 2080x1112, at 138745 kHz; latencies [Some(3), Some(54), Some(54), Some(54), Some(54), Some(73), Some(110), Some(115)] us, SAGV block 11 us; WM levels were [80004011, 00004011, 00004007, 00004007, 00004007, 00004007, 00004007, 00004007]
[11.525 tgl-display] tgl-display: WM levels now 80004011 80010041 80010041 80010041 80010041 80014051 80020081 80020081
  • No fault record in either boot.
  • nvme 04:00.0 and i219 00:1f.6 behave as in A: the same hand-over, queues, link-up and interrupt lines, with only timings differing. usbd claims nothing in either boot, because its rows match QEMU's xHCI and the boot xHCI, and the T14 boots off its NVMe.
  • Idle residency with the panel lit: PC2 995 → 346 ‰, PC3 0 → 648 ‰. The track's exit asks for PC2 at most 400 ‰ and PC3 at least 600 ‰, which is r7's "planes off", 338/656 ‰. It is met.
  • 10 ms sleeper: 2860 → 2691 mW, with disjoint ranges.
  • Idle p50: 6583 → 6062 mW. That is within the bimodal row's spread for one boot each, so it is not established.
  • The panel was not observed.
  • B rerun at review r1's claim path (display-stage1-ab d0dc7c34a, image a8f0d1ec…, exit 0, 228 s; evidence r4):
    • It logs iommu: rmrr0 is 00:02.0's alone, kept in its own domain, then keeps → moves.
    • No NotIsolated, no fault.
    • 682 blocks, the same WM words.
    • Idle PC2/PC3 346/648 ‰, within the exit. The 10 ms sleeper drew 2646 mW.
    • On the T14 the RMRR has one endpoint scope, so review r2's whole-DMAR classification cannot differ there.

Risks still open (measured by the first T14 boot)

  • A claim clears Bus Master Enable until the driver starts it, a few ms later. Whether the panel glitched in that window was not observed. No fault was recorded, and the scan-out was reading again afterwards, since the package residency followed the new watermarks.
  • The iGPU moved from the identity domain to its own under live scan-out with no fault in this one boot. The intermittent fault class (issues/the-t14s-igpu-faults-read-permission-after-translation-goes-off-and-on-under-its-scanout.md, about 1 in 27 cycles) means one boot does not rule it out.
  • If the driver ever exits, the function's reset blanks the panel.
  • Until NVMe APST lands, the NVMe caps the package at PC3, so this stage can show at most PC2 to PC3.

Integration tests

T14 only: this is the Tiger Lake display engine, and the AMD bench's display is AMD DCN.

The A/B is two boots, one with the driver and one without. The driver holds its claim for the boot, so A and B cannot share one. Both are osbench_energy on the bench image, compared against pr-1100 r7's Linux rows.

🤖 Generated with Claude Code

Japabu added a commit that referenced this pull request Nov 1, 2026
…one keeps it

The owner's ruling of 2026-10-04 ("Display and USB only") now decides each
function an RMRR names, at the DMAR walk: `kernel::rmrr::verdict`, pure and
host-tested, keeps the range for a display or USB controller named alone in
whole 4 KiB pages, and refuses every other function by name in VT-d's
`unclaimable`, as AMD-Vi refuses an IVMD's. A scope behind a bridge is
followed through the bridges this kernel enumerated, a sub-hierarchy names
every function below it, and a path that cannot be followed is another device
sharing the range. So `keep_reserved` cannot fail and spends no domain id on a
refusal, and a kept range is mapped in 2 MiB leaves where it covers one and
4 KiB pages at its ends, which a usual small xHCI RMRR needs.

tgl-display: the frame counter and its time are read together; a plane
rotated 90/270 or decompressing is refused; PLANE_BUF_CFG's Buffer End is
inclusive (Vol 12's own 0x009F0000 for blocks 0-159), so the T14's plane has
682 blocks; level 0 is a value, not an option; the window covers only the
registers reached; dead registers are gone. New host tests: method 1 under a
short allocation, a mailbox that stays busy, rotation and decompression.

Records: the track's rulings are only the owner's, with the earlier ones
cited; stage 1's transition and SAGV lines match the code; its exit states
the residency. The IOMMU track's exit and the identity-domain defect say
what the claim now keeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 1, 2026
…nknown scope shares

`kernel::rmrr::classify` now decides every function the RMRRs name once the
DMAR walk is whole, over scopes vtd resolved: a function's memory is its
alone only where every region overlapping any of its own names it and
nothing else, so two overlapping RMRRs of two functions refuse both. One
function's identical or overlapping RMRRs merge, so each address is placed
once and a firmware table cannot make keep_reserved map a leaf twice. A
scope that is not an endpoint or a sub-hierarchy that resolves - another
kind, an ACPI namespace device among them, a path through an unconfigured
bridge, or a malformed entry and the rest of its list - is a device sharing
the range. The leaves a kept range is mapped with are `kernel::rmrr::leaves`,
host-tested across a 2 MiB boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 1, 2026
…from, and a region off segment 0 shares

A function's overlapping or adjacent RMRRs merge into one kept range, which
no single avoided region contains: `Domains::reserved_space` now asserts the
range lies inside their union (`kernel::rmrr::covered`), which a host test
holds for every kept range against the RMRRs it came from. Every function is
enumerated on one segment group, so an RMRR on another names devices this
kernel cannot tell from its own: its scopes are unknown, and it shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu and others added 6 commits November 1, 2026 09:47
The display-engine track, with the owner's rulings of 2026-10-31: release-1
scope on both benches; several outputs, extended or mirrored; Intel a Rust
driver from Intel's PRMs; AMD an adapter around AMD's Display Core; a panic
surface the driver registers. It records the ownership between each GPU's
driver and the compositor over a `display-output` port, the kernel's two
generic mechanisms, the defaults for display audio, docks and backlight, the
stages, and what the read-only scout of the T14's GOP found that stage 1
rests on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e 1)

The T14's GOP enables watermark level 0 alone, so its scan-out never lets
memory reach a low-power latency level; the package stays in PC2 with the
panel lit. A new userland driver claims the iGPU (8086:9a49), keeps the
firmware's mode and surface, reads pcode's memory latencies and SAGV block
time (IHD-OS-TGL-Vol 12-12.21 "Memory Values"), and programs PLANE_WM
levels 0-7 by Vol 12's "Watermark Algorithm", with level 0 carrying the SAGV
block time where the plane tolerates it. Anything of the firmware's state it
does not understand - more than one plane or pipe, the cursor on, a YUV plane,
a stopped pipe, a line time not the mode's, a mailbox error, a plane that
cannot tolerate SAGV - is refused by name and nothing is written.

The pixel rate is measured off the frame counter over 30 frames and held
against the firmware's own WM_LINETIME, rather than decoded from the PLL,
which the modesetting stage owns. The transition watermark stays disabled:
it tunes Isochronous Priority Control, not power.

Kernel: a claimed function's own IOMMU domain now maps every RMRR whose
scope names it, at its own address, when the domain is made and before the
function attaches (VT-d Rev. 4.0 §8.4). The T14's plane reads the firmware
framebuffer through a GGTT entry inside its RMRR, so without this the claim
would fault every scan-out read. AMD-Vi already refuses claims of functions
an IVMD names; AArch64 reads no such table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DC is vendored with a patch set that covers only its OS interface and build,
and passes the licence evaluator and NOTICE. ToyOS's side is written clean-room
from amdgpu_dm and the DRM helpers. Every sink byte DC takes from its OS side
reaches a Rust parser first; what DC parses straight off its AUX hardware is a
recorded compromise with an exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one keeps it

The owner's ruling of 2026-10-04 ("Display and USB only") now decides each
function an RMRR names, at the DMAR walk: `kernel::rmrr::verdict`, pure and
host-tested, keeps the range for a display or USB controller named alone in
whole 4 KiB pages, and refuses every other function by name in VT-d's
`unclaimable`, as AMD-Vi refuses an IVMD's. A scope behind a bridge is
followed through the bridges this kernel enumerated, a sub-hierarchy names
every function below it, and a path that cannot be followed is another device
sharing the range. So `keep_reserved` cannot fail and spends no domain id on a
refusal, and a kept range is mapped in 2 MiB leaves where it covers one and
4 KiB pages at its ends, which a usual small xHCI RMRR needs.

tgl-display: the frame counter and its time are read together; a plane
rotated 90/270 or decompressing is refused; PLANE_BUF_CFG's Buffer End is
inclusive (Vol 12's own 0x009F0000 for blocks 0-159), so the T14's plane has
682 blocks; level 0 is a value, not an option; the window covers only the
registers reached; dead registers are gone. New host tests: method 1 under a
short allocation, a mailbox that stays busy, rotation and decompression.

Records: the track's rulings are only the owner's, with the earlier ones
cited; stage 1's transition and SAGV lines match the code; its exit states
the residency. The IOMMU track's exit and the identity-domain defect say
what the claim now keeps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nknown scope shares

`kernel::rmrr::classify` now decides every function the RMRRs name once the
DMAR walk is whole, over scopes vtd resolved: a function's memory is its
alone only where every region overlapping any of its own names it and
nothing else, so two overlapping RMRRs of two functions refuse both. One
function's identical or overlapping RMRRs merge, so each address is placed
once and a firmware table cannot make keep_reserved map a leaf twice. A
scope that is not an endpoint or a sub-hierarchy that resolves - another
kind, an ACPI namespace device among them, a path through an unconfigured
bridge, or a malformed entry and the rest of its list - is a device sharing
the range. The leaves a kept range is mapped with are `kernel::rmrr::leaves`,
host-tested across a 2 MiB boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…from, and a region off segment 0 shares

A function's overlapping or adjacent RMRRs merge into one kept range, which
no single avoided region contains: `Domains::reserved_space` now asserts the
range lies inside their union (`kernel::rmrr::covered`), which a host test
holds for every kept range against the RMRRs it came from. Every function is
enumerated on one segment group, so an RMRR on another names devices this
kernel cannot tell from its own: its scopes are unknown, and it shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu marked this pull request as ready for review November 1, 2026 09:38
@Japabu
Japabu added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit a2a4548 Nov 1, 2026
10 checks passed
@Japabu
Japabu deleted the display-stage1 branch November 1, 2026 09:57
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