From f7550feec6fd3859e3e1f9200b9a23476c71583a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 01:34:05 +0900 Subject: [PATCH 01/46] Support Raptor Lake-U display clocks and DSI panel initialization --- README.md | 9 +-- README_ja.md | 9 +-- WhateverXe/kern_display13_clock.hpp | 77 ++++++++++++--------- WhateverXe/kern_display13_dsi_trial.hpp | 5 +- WhateverXe/kern_dsi_adapter_backing.inc | 2 +- WhateverXe/kern_dsi_adapter_boot.inc | 2 +- WhateverXe/kern_dsi_adapter_diagnostics.inc | 4 +- WhateverXe/kern_dsi_host.hpp | 2 +- WhateverXe/kern_dsi_panel.hpp | 7 +- WhateverXe/kern_dsi_restart.hpp | 3 +- WhateverXe/kern_dsi_sequence.hpp | 3 +- WhateverXe/kern_xe.cpp | 35 +++++----- docs/device-selection.md | 3 +- tests/display13_clock_set.cpp | 15 ++++ tests/dsi_host.cpp | 4 ++ tests/dsi_panel.py | 8 ++- tests/dsi_resources.py | 1 + tests/dsi_restart.py | 8 ++- tests/dsi_sequence.cpp | 2 + tests/dsi_surface_adapter.py | 2 +- tests/provider_binding_fixture.cpp | 1 + 21 files changed, 130 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index bd1de692..64e18efa 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Sequoia and Tahoe accept compatible updates within their OS family. Future Apple - Twin Lake: 32-EU configurations. - Alder Lake-H: 96-EU configurations, including single-channel operation. - Alder Lake-S: UHD Graphics 770. +- Raptor Lake-U: 48-EU Intel Processor U300 on Big Sur. See [tested features and known issues](#tested-features-and-known-issues) for OS coverage and display configurations. PCI IDs are listed in [device selection](docs/device-selection.md). @@ -32,13 +33,12 @@ See [tested features and known issues](#tested-features-and-known-issues) for OS - Other supported Tiger Lake-U/H35 configurations, including 96 EU, and Tiger Lake-H45. - Other supported Alder Lake-N / Twin Lake configurations. -- Other supported Alder Lake-H/P/U and Raptor Lake-H/P configurations. +- Other supported Alder Lake-H/P/U and Raptor Lake-H/P/U configurations. - Other supported Alder Lake-S/HX and Raptor Lake-S/HX configurations. - Internal eDP on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P is implemented but has not been tested on hardware. ### Further implementation required -- Raptor Lake-U: dedicated display-clock handling is not implemented. - Rocket Lake-S: display control requires additional support. ### No support planned @@ -81,6 +81,7 @@ These settings are unnecessary at 96MB and do not increase firmware-allocated me ### Other settings - Add `SSDT-PNLF` for internal-display brightness control. +- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. - GPU selection is automatic; a `device-id` override is normally unnecessary. - Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P select tiling automatically. Do not use `-yTileDisable`; `AAPL,GfxYTile` is unnecessary. @@ -94,10 +95,11 @@ The common regression checks cover Metal, OpenGL, OpenCL, hardware H.264 encode/ | Tiger Lake laptops | Big Sur–Tahoe: 48 EU, internal eDP and brightness, HDMI/USB-C, up to three displays and display off/on | | Alder Lake-N mini PCs | Big Sur–Tahoe: 16/24 EU, two 1080p60 HDMI displays and display off/on | | Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | +| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | | Alder Lake-H | Big Sur–Tahoe: 96 EU, three displays using HDMI and a DP-to-HDMI adapter. Single-channel `4626` graphics acceleration checked on Big Sur | | Alder Lake-S | Big Sur–Tahoe: UHD 770, HDMI/DP/USB-C and up to three displays; Safari video, with Widevine checked on Big Sur/Tahoe | -MIPI DSI support is **experimental and tested only on GPD MicroPC 2 and CHUWI MiniBook X N100**. Do not expect it to work on other devices. +MIPI DSI support is **experimental**. The tested panels are those in the GPD MicroPC 2 and CHUWI MiniBook X N100 and U300. ### Known issues and limits @@ -109,7 +111,6 @@ MIPI DSI support is **experimental and tested only on GPD MicroPC 2 and CHUWI Mi ### Roadmap -- Implement Raptor Lake-U display-clock support. - Complete DisplayPort, USB-C and internal-display support on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P. - Improve display reconnection reliability and investigate full system sleep/wake. diff --git a/README_ja.md b/README_ja.md index dd365876..505c3a69 100644 --- a/README_ja.md +++ b/README_ja.md @@ -25,6 +25,7 @@ SequoiaとTahoeは同じOS系列内の互換性のある更新に対応します - Twin Lake:32 EU構成。 - Alder Lake-H:シングルチャネルを含む96 EU構成。 - Alder Lake-S:UHD Graphics 770。 +- Raptor Lake-U:Intel Processor U300の48 EU構成(Big Sur)。 OS別の範囲と表示構成は[動作確認済みの機能と既知の問題](#動作確認済みの機能と既知の問題)、PCI IDは[デバイス選択](docs/device-selection.md)を参照してください。 @@ -32,13 +33,12 @@ OS別の範囲と表示構成は[動作確認済みの機能と既知の問題]( - 96 EUを含む対応IDの他のTiger Lake-U/H35構成と、Tiger Lake-H45。 - 対応IDの他のAlder Lake-N / Twin Lake構成。 -- 対応IDの他のAlder Lake-H/P/U・Raptor Lake-H/P構成。 +- 対応IDの他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。 - 対応IDの他のAlder Lake-S/HX・Raptor Lake-S/HX構成。 - Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pの内蔵eDPは実装済み・実機未検証です。 ### 対応に追加改修が必要 -- Raptor Lake-U:専用の表示クロック制御が未実装です。 - Rocket Lake-S:表示制御に追加対応が必要です。 ### 対応予定なし @@ -81,6 +81,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします ### その他の設定 - 内蔵画面の輝度調整には`SSDT-PNLF`を追加します。 +- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。 - GPUは自動選択するため、通常は`device-id`上書きは不要です。 - Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pではタイル形式を自動選択します。`-yTileDisable`は使わないでください。`AAPL,GfxYTile`は不要です。 @@ -94,10 +95,11 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします | Tiger Lakeラップトップ | Big Sur〜Tahoe:48 EU、内蔵eDPと輝度調整、HDMI/USB-C、最大3画面・消灯復帰 | | Alder Lake-NミニPC | Big Sur〜Tahoe:16/24 EU、1080p60のHDMI 2画面・消灯復帰 | | Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | +| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・50 Hz、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | | Alder Lake-H | Big Sur〜Tahoe:96 EU、HDMIとDP→HDMI変換器による3画面。シングルチャネル構成の`4626`でもBig Surの加速を確認 | | Alder Lake-S | Big Sur〜Tahoe:UHD 770、HDMI/DP/USB-C、最大3画面、Safari動画。WidevineはBig Sur/Tahoeで確認 | -MIPI DSIは**GPD MicroPC 2とCHUWI MiniBook X N100のみで検証した試験的対応**です。それ以外の機器での動作は期待しないでください。 +MIPI DSI対応は**試験的**です。GPD MicroPC 2、CHUWI MiniBook X N100とU300の内蔵パネルで検証しました。 ### 既知の問題と制限 @@ -109,7 +111,6 @@ MIPI DSIは**GPD MicroPC 2とCHUWI MiniBook X N100のみで検証した試験的 ### ロードマップ -- Raptor Lake-Uの表示クロック対応。 - Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/PでのDisplayPort・USB-C・内蔵画面対応の拡充。 - 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 diff --git a/WhateverXe/kern_display13_clock.hpp b/WhateverXe/kern_display13_clock.hpp index 06449435..47497dd6 100644 --- a/WhateverXe/kern_display13_clock.hpp +++ b/WhateverXe/kern_display13_clock.hpp @@ -1,6 +1,6 @@ -// ADL-N Display D0 clock readout. Linux v6.12 intel_cdclk.c's non-A-step -// adlp_cdclk_table and bxt_get_cdclk are the hardware reference. This does -// keeps the donor Hz ABI separate from the Linux CDCLK frequency. Transitions +// Display13 CDCLK. Linux v6.12 intel_cdclk.c defines adlp_cdclk_table, +// rplu_cdclk_table and the corresponding PCODE voltage levels. +// Keep the donor Hz ABI separate from the Linux CDCLK frequency. Transitions // below require caller-owned clocks, serialized PCODE and qualified consumers. #ifndef kern_display13_clock_hpp #define kern_display13_clock_hpp @@ -8,11 +8,25 @@ namespace Display13Clock { constexpr uint32_t DSSM=0x51004, PLL=0x46070, Control=0x46000, Squash=0x46008; -struct Entry {uint32_t ref,frequency,ratio;}; +enum class Variant {ADLP, RPLU}; +constexpr Variant variantForPCI(uint32_t identity) { + switch(identity) { + case 0xA7218086: case 0xA7A18086: case 0xA7A98086: case 0xA7AC8086: case 0xA7AD8086: + return Variant::RPLU; + default:return Variant::ADLP; + } +} +struct Entry { + uint32_t ref,frequency,ratio; + bool rpluOnly {}; + bool available(uint32_t reference,Variant variant) const { + return ref==reference && (!rpluOnly || variant==Variant::RPLU); + } +}; static constexpr Entry table[] { - {19200,172800,27},{19200,192000,20},{19200,307200,32},{19200,556800,58},{19200,652800,68}, - {24000,176000,22},{24000,192000,16},{24000,312000,26},{24000,552000,46},{24000,648000,54}, - {38400,179200,14},{38400,192000,10},{38400,307200,16},{38400,556800,29},{38400,652800,34} + {19200,172800,27},{19200,192000,20},{19200,307200,32},{19200,480000,50,true},{19200,556800,58},{19200,652800,68}, + {24000,176000,22},{24000,192000,16},{24000,312000,26},{24000,480000,40,true},{24000,552000,46},{24000,648000,54}, + {38400,179200,14},{38400,192000,10},{38400,307200,16},{38400,480000,25,true},{38400,556800,29},{38400,652800,34} }; enum class Status : uint32_t { NotReady, Unavailable, UnsupportedReference, Disabled, Unlocked, @@ -30,7 +44,7 @@ struct Readout { }; static_assert(sizeof(Readout)==12*4,"Display13 CDCLK readout layout"); -inline Readout decode(uint32_t dssm,uint32_t pll,uint32_t control,uint32_t squash) { +inline Readout decode(uint32_t dssm,uint32_t pll,uint32_t control,uint32_t squash,Variant variant=Variant::ADLP) { Readout r; r.dssm=dssm;r.pll=pll;r.control=control;r.squash=squash; if(dssm==UINT32_MAX || pll==UINT32_MAX || control==UINT32_MAX || squash==UINT32_MAX) { r.status=Status::Unavailable;return r; @@ -39,7 +53,7 @@ inline Readout decode(uint32_t dssm,uint32_t pll,uint32_t control,uint32_t squas const uint32_t references[] {24000,19200,38400}; if(r.refIndex>=3) {r.status=Status::UnsupportedReference;return r;} r.refKHz=references[r.refIndex];r.ratio=pll&0xFF; - for(const auto &e:table)if(e.ref==r.refKHz && e.frequency>r.maxKHz)r.maxKHz=e.frequency; + for(const auto &e:table)if(e.available(r.refKHz,variant) && e.frequency>r.maxKHz)r.maxKHz=e.frequency; if(!(pll&(1U<<31))) {r.status=Status::Disabled;return r;} if(!(pll&(1U<<30))) {r.status=Status::Unlocked;return r;} // ADLP's supported table has no squash waveforms. Do not interpret a @@ -50,7 +64,7 @@ inline Readout decode(uint32_t dssm,uint32_t pll,uint32_t control,uint32_t squas const uint32_t vco=r.refKHz*r.ratio; r.cdclkKHz=(vco+r.divider/2)/r.divider; bool found=false; - for(const auto &e:table) if(e.ref==r.refKHz) { + for(const auto &e:table) if(e.available(r.refKHz,variant)) { if(e.frequency>r.maxKHz)r.maxKHz=e.frequency; if(e.ratio==r.ratio && e.frequency==r.cdclkKHz)found=true; } @@ -62,12 +76,12 @@ inline Readout decode(uint32_t dssm,uint32_t pll,uint32_t control,uint32_t squas r.status=Status::Complete;return r; } -template Readout read(IO &io) { +template Readout read(IO &io,Variant variant=Variant::ADLP) { Readout r; const uint32_t registers[] {DSSM,PLL,Control,Squash}; for(auto reg:registers) if(!io.readable(reg))return r; const auto dssm=io.read(DSSM),pll=io.read(PLL),control=io.read(Control),squash=io.read(Squash); - r=decode(dssm,pll,control,squash); + r=decode(dssm,pll,control,squash,variant); // A concurrent clock transition must not publish a mixed native value. if(io.read(DSSM)!=dssm || io.read(PLL)!=pll || io.read(Control)!=control || io.read(Squash)!=squash) r.status=Status::Changed; @@ -94,23 +108,24 @@ struct Conditions { bool owned {}, watermarksReady {}; uint32_t activePipes {}; }; -inline bool plan(uint32_t reference,uint64_t nativeHz,uint32_t &ratio,uint32_t &divisor,uint32_t &control) { - for(const auto &e:table) if(e.ref==reference && uint64_t(e.frequency)*2000==nativeHz) { +inline bool plan(uint32_t reference,uint64_t nativeHz,uint32_t &ratio,uint32_t &divisor,uint32_t &control,Variant variant=Variant::ADLP) { + for(const auto &e:table) if(e.available(reference,variant) && uint64_t(e.frequency)*2000==nativeHz) { ratio=e.ratio;divisor=e.ref*e.ratio/e.frequency; control=(divisor==3 ? 1U<<22:0U)|0x380000|((2*e.frequency+500)/1000-2); return true; } return false; } -inline uint32_t voltage(uint32_t cdclkKHz) { - return cdclkKHz<=312000 ? 0:cdclkKHz<=326400 ? 1:cdclkKHz<=556800 ? 2:3; +inline uint32_t voltage(uint32_t cdclkKHz,Variant variant=Variant::ADLP) { + const uint32_t level1Max=variant==Variant::RPLU ? 480000 : 326400; + return cdclkKHz<=312000 ? 0:cdclkKHz<=level1Max ? 1:cdclkKHz<=556800 ? 2:3; } // calculateCDClockFrequency's HDMI branch requests the maximum table entry. // The other branch takes a pixel-clock lower bound, with a strict comparison // at table thresholds. Return an actual Display D0 entry in the donor's Hz ABI. -inline uint64_t select(uint32_t reference,uint64_t minimumHz,bool external) { +inline uint64_t select(uint32_t reference,uint64_t minimumHz,bool external,Variant variant=Variant::ADLP) { uint64_t selected=0; - for(const auto &e:table)if(e.ref==reference) { + for(const auto &e:table)if(e.available(reference,variant)) { const auto hz=uint64_t(e.frequency)*2000; if(external)selected=hz; else if(hz>minimumHz)return hz; @@ -154,14 +169,14 @@ template bool mailbox(IO &io,Change &r,uint32_t data,unsigned budgetUs if(r.mailboxStatus&0xFF) {r.status=ChangeStatus::MailboxError;return false;} return true; } -template Change set(IO &io,uint64_t nativeHz,Conditions conditions) { - Change r;r.requestedHz=nativeHz;r.before=read(io); +template Change set(IO &io,uint64_t nativeHz,Conditions conditions,Variant variant=Variant::ADLP) { + Change r;r.requestedHz=nativeHz;r.before=read(io,variant); if(r.before.status!=Status::Complete && r.before.status!=Status::Disabled)return r; if(r.before.squash&(1U<<31))return r; uint32_t ratio=0,divisor=0,control=0; - if(!plan(r.before.refKHz,nativeHz,ratio,divisor,control)) {r.status=ChangeStatus::InvalidFrequency;return r;} + if(!plan(r.before.refKHz,nativeHz,ratio,divisor,control,variant)) {r.status=ChangeStatus::InvalidFrequency;return r;} if(r.before.status==Status::Complete && r.before.nativeHz()==nativeHz) { - r.after=read(io); + r.after=read(io,variant); r.status=r.after.status==Status::Complete && r.after.nativeHz()==nativeHz ? ChangeStatus::Complete:ChangeStatus::ReadbackMismatch; return r; // Actual unchanged clock, no invented fixed-frequency success. } @@ -182,7 +197,7 @@ template Change set(IO &io,uint64_t nativeHz,Conditions conditions) { if(r.delayUs-start>=53000) {r.status=ChangeStatus::PrepareTimeout;return r;} delay(io,r,10); } - const auto prepared=read(io); + const auto prepared=read(io,variant); if(prepared.status!=r.before.status || prepared.dssm!=r.before.dssm || prepared.pll!=r.before.pll || prepared.control!=r.before.control || prepared.squash!=r.before.squash) { r.after=prepared;r.status=ChangeStatus::ReadbackMismatch;return r; @@ -211,10 +226,10 @@ template Change set(IO &io,uint64_t nativeHz,Conditions conditions) { write(io,r,Control,control); // Read back before lowering the prepare voltage. Failed PLL/decimal // proof must not lower voltage or attempt an unproven rollback. - r.after=read(io); + r.after=read(io,variant); if(r.after.status!=Status::Complete || r.after.nativeHz()!=nativeHz) {r.status=ChangeStatus::ReadbackMismatch;return r;} - if(!mailbox(io,r,voltage(r.after.cdclkKHz)))return r; - r.after=read(io); + if(!mailbox(io,r,voltage(r.after.cdclkKHz,variant)))return r; + r.after=read(io,variant); r.status=r.after.status==Status::Complete && r.after.nativeHz()==nativeHz ? ChangeStatus::Complete:ChangeStatus::ReadbackMismatch; return r; } @@ -222,8 +237,8 @@ template Change set(IO &io,uint64_t nativeHz,Conditions conditions) { // Caller proves every pipe/plane/cursor/DSB stopped and holds PW1 throughout. // Follow icl_cdclk_pll_disable and the Display >=12 bypass control, including // PCODE prepare/final voltage. Never report a disabled PLL before LOCK clears. -template Change stop(IO &io,Conditions conditions) { - Change r;r.before=read(io); +template Change stop(IO &io,Conditions conditions,Variant variant=Variant::ADLP) { + Change r;r.before=read(io,variant); if(r.before.status!=Status::Complete && r.before.status!=Status::Disabled)return r; if((r.before.squash&(1U<<31)) || (r.before.pll&0xC00000))return r; if(!conditions.owned) {r.status=ChangeStatus::OwnershipRequired;return r;} @@ -241,7 +256,7 @@ template Change stop(IO &io,Conditions conditions) { if(r.delayUs-start>=53000) {r.status=ChangeStatus::PrepareTimeout;return r;} delay(io,r,10); } - const auto prepared=read(io); + const auto prepared=read(io,variant); if(prepared.status!=r.before.status || prepared.dssm!=r.before.dssm || prepared.pll!=r.before.pll || prepared.control!=r.before.control || prepared.squash!=r.before.squash) { r.after=prepared;r.status=ChangeStatus::ReadbackMismatch;return r; @@ -253,13 +268,13 @@ template Change stop(IO &io,Conditions conditions) { } const auto bypass=r.before.refKHz/2; write(io,r,Control,0x380000|((2*bypass+500)/1000-2)); - r.after=read(io); + r.after=read(io,variant); if(r.after.status!=Status::Disabled || (r.after.pll&0xC0000000) || r.after.control!=(0x380000|((2*bypass+500)/1000-2)) || r.after.dssm!=r.before.dssm) { r.status=ChangeStatus::ReadbackMismatch;return r; } if(!mailbox(io,r,voltage(bypass)))return r; - r.after=read(io); + r.after=read(io,variant); r.status=r.after.status==Status::Disabled && !(r.after.pll&0xC0000000) && r.after.control==(0x380000|((2*bypass+500)/1000-2)) && r.after.dssm==r.before.dssm ? ChangeStatus::Complete:ChangeStatus::ReadbackMismatch; diff --git a/WhateverXe/kern_display13_dsi_trial.hpp b/WhateverXe/kern_display13_dsi_trial.hpp index 1d1e00e9..ee2708f8 100644 --- a/WhateverXe/kern_display13_dsi_trial.hpp +++ b/WhateverXe/kern_display13_dsi_trial.hpp @@ -18,7 +18,8 @@ struct Result { uint32_t stage=0,writes=0,reg=0,value=0,pll=UINT32_MAX,sourcePLL=UINT32_MAX,frames=0; }; template Result transfer(IO &io,bool requested,const AlderLakeN::DisplayReadout &boot, - const XELPMemory::Layout &memory,unsigned hdmiMask) { + const XELPMemory::Layout &memory,unsigned hdmiMask, + Display13Clock::Variant variant=Display13Clock::Variant::ADLP) { Result r; if(!requested)return r; r.status=Status::Source; @@ -48,7 +49,7 @@ template Result transfer(IO &io,bool requested,const AlderLakeN::Displ for(unsigned i=0;i=2 || (mux&(1U<<10)))return r; diff --git a/WhateverXe/kern_dsi_adapter_backing.inc b/WhateverXe/kern_dsi_adapter_backing.inc index c0817c53..d5369af3 100644 --- a/WhateverXe/kern_dsi_adapter_backing.inc +++ b/WhateverXe/kern_dsi_adapter_backing.inc @@ -266,7 +266,7 @@ bool XE::switchDSINativeSurface() { decltype(owned) &owns; IO(IOMemoryMap *m,decltype(owned) &f):Display13FBBootIO{m},owns(f){} bool ready(){return owns();} - uint64_t nativeClockHz(){Display13FBClockIO clock{map};return Display13Clock::read(clock).nativeHz();} + uint64_t nativeClockHz(){Display13FBClockIO clock{map};return Display13Clock::read(clock,display13ClockVariant()).nativeHz();} uint64_t nowUs(){uint64_t t=0,ns=0;clock_get_uptime(&t);absolutetime_to_nanoseconds(t,&ns);return ns/1000;} void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} void write(uint32_t reg,uint32_t value) { diff --git a/WhateverXe/kern_dsi_adapter_boot.inc b/WhateverXe/kern_dsi_adapter_boot.inc index 080ff502..faf4ff58 100644 --- a/WhateverXe/kern_dsi_adapter_boot.inc +++ b/WhateverXe/kern_dsi_adapter_boot.inc @@ -293,7 +293,7 @@ bool XE::bindDSIResources(IOService *controller) { // before native display users start; this is not an opt-in host trial. bool prepareExternalClock=false; if(ready && !dsiPanelOnly && Display13FBTopology::multiOutputWiring(adlnOutputWiring)) { - const auto current=Display13Clock::read(io); + const auto current=Display13Clock::read(io,display13ClockVariant()); ready=current.status==Display13Clock::Status::Complete; prepareExternalClock=ready && current.nativeHz()!=current.nativeMaxHz(); } diff --git a/WhateverXe/kern_dsi_adapter_diagnostics.inc b/WhateverXe/kern_dsi_adapter_diagnostics.inc index af8e1ed5..80591289 100644 --- a/WhateverXe/kern_dsi_adapter_diagnostics.inc +++ b/WhateverXe/kern_dsi_adapter_diagnostics.inc @@ -128,12 +128,12 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { if(prepareExternal) { auto &c=display13FBPower; Display13FBClockIO clockIO {c.map}; - const auto current=Display13Clock::read(clockIO); + const auto current=Display13Clock::read(clockIO,display13ClockVariant()); cdclk.before=current;cdclk.requestedHz=current.nativeMaxHz(); const bool stopped=io.ready() && state.stopped && !state.started && !c.engineEnabled && Display13FBIRQ::consumersStopped(io,7,c.transcoders); if(!stopped || current.status!=Display13Clock::Status::Complete)return false; - cdclk=Display13Clock::set(clockIO,current.nativeMaxHz(),{true,true,0}); + cdclk=Display13Clock::set(clockIO,current.nativeMaxHz(),{true,true,0},display13ClockVariant()); if(cdclk.status!=Display13Clock::ChangeStatus::Complete) { adlnFault(c.clockFaulted,__LINE__);return false; } diff --git a/WhateverXe/kern_dsi_host.hpp b/WhateverXe/kern_dsi_host.hpp index 6c2e84ae..bc2d6d51 100644 --- a/WhateverXe/kern_dsi_host.hpp +++ b/WhateverXe/kern_dsi_host.hpp @@ -15,7 +15,7 @@ inline Packet packet(uint8_t type,Span data) { uint32_t parameters=0; if(type==0x05 && data.size==1)parameters=data.data[0]; else if(type==0x15 && data.size==2)parameters=data.data[0]|uint32_t(data.data[1])<<8; - else if(type==0x39){parameters=uint32_t(data.size);p.payload=data;} + else if(longWrite(type)){parameters=uint32_t(data.size);p.payload=data;} else return p; // Fresh header: VC0, LP, no stale fence/flush or short-packet payload bit. p.header=(p.payload.data?1U<<31:0)|(1U<<30)|(parameters<<8)|type; diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 42624d40..5faed7aa 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -135,8 +135,11 @@ template void cycle(IO &io,const Readout &link,const P bool gpio(unsigned role,unsigned value){return this->io.gpio(role,value);} } port{io}; const auto sequence=[&](unsigned id) { - const bool video=r.stage==Stage::BacklightOff || r.stage==Stage::TranscoderOn || r.stage==Stage::BacklightOn; - const auto budget=video?videoCreditBudgetUs(profile.mode):100; + // INIT can queue a full burst of LP payloads before its first delay. + // Wait for that burst to drain before the next panel operation. + const bool frameBudget=r.stage==Stage::Init || r.stage==Stage::BacklightOff || + r.stage==Stage::TranscoderOn || r.stage==Stage::BacklightOn; + const auto budget=frameBudget?videoCreditBudgetUs(profile.mode):100; if(!r.host.run(port,profile.sequences,id,budget)){r.error=Error::Sequence;return false;}return true; }; const auto delay=[&](unsigned index) { diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp index b232ef2f..04b5cbf0 100644 --- a/WhateverXe/kern_dsi_restart.hpp +++ b/WhateverXe/kern_dsi_restart.hpp @@ -247,7 +247,8 @@ template bool start(IO &io,XELPComboClock::ClockAllocation &allocation !a.rmw(0x6B0C0,0xF000,0x4000) || !a.set(0x6B030,p.transport,~(1U<<20)) || !a.set(0x6B400,p.function))return false; s.stage=Stage::Link; - if(!a.wait(0x6B030,1U<<20,1U<<20,2500,10) || !a.wait(0x6B0D8,0x30001,0,2500,10) || + // Some DSI panels need longer than 2.5 ms after the transcoder is configured. + if(!a.wait(0x6B030,1U<<20,1U<<20,10000,10) || !a.wait(0x6B0D8,0x30001,0,2500,10) || !a.wait(0x6B0D0,0x1FFF,0x1040,100,10))return false; s.stage=Stage::Ready;s.started=true;return true; } diff --git a/WhateverXe/kern_dsi_sequence.hpp b/WhateverXe/kern_dsi_sequence.hpp index e450b31f..12c5a9db 100644 --- a/WhateverXe/kern_dsi_sequence.hpp +++ b/WhateverXe/kern_dsi_sequence.hpp @@ -21,6 +21,7 @@ struct Sequences { uint64_t delayUs {}; bool valid() const {return status==SequenceStatus::Valid;} }; +inline bool longWrite(uint8_t type) {return type==0x29 || type==0x39;} inline uint32_t sequenceU32(const uint8_t *p) { return uint32_t(p[0]) | uint32_t(p[1])<<8 | uint32_t(p[2])<<16 | uint32_t(p[3])<<24; } @@ -41,7 +42,7 @@ inline SequenceStatus validateSequence(Span bytes, Sequences &counts) { // flags require a separate host contract, never silent coercion. if(p[0] || length!=n-4)return SequenceStatus::Packet; if(!((p[1]==0x05 && length==1) || (p[1]==0x15 && length==2) || - (p[1]==0x39 && length>=1)))return SequenceStatus::Packet; + (longWrite(p[1]) && length>=1)))return SequenceStatus::Packet; ++counts.packets;break; } case 2: diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 7b8316bf..761c9a8e 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -228,6 +228,9 @@ static bool xelpInstallNVRAMABI(KernelPatcher &patcher,size_t index,mach_vm_addr XE *XE::callbackXE; static IOPCIDevice *xelpSelectedGPU; static XELP::Profile xelpSelectedProfile; +static Display13Clock::Variant display13ClockVariant() { + return Display13Clock::variantForPCI(xelpSelectedProfile.identity); +} static bool xelpSelectedDevice(IOPCIDevice *pci) { return pci && pci==xelpSelectedGPU && xelpSelectedProfile.displayAdmitted() && XELP::same(xelpSelectedProfile,XELP::deviceProfile(pci)); @@ -4029,7 +4032,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { } auditDSIBoot(pci,io.base,io.length,result,barReady && reservationReady); if(checkKernelArgument("-wex-dsi-hdmi-test")) { - auto handoff=Display13DSITrial::transfer(io,barReady && xelpMemoryMatches(pci),result,xelpMemory,board.hdmi); + auto handoff=Display13DSITrial::transfer(io,barReady && xelpMemoryMatches(pci),result,xelpMemory,board.hdmi,display13ClockVariant()); pci->setProperty("WEXDisplay13DSIHandoff",&handoff,sizeof(handoff)); AlderLakeN::readDisplay(io,barReady,result); pci->setProperty("WEXDisplay13DSIAfter",&result,sizeof(result)); @@ -9029,7 +9032,7 @@ Display13Clock::Readout XE::adlnClockReadout(IOService *controller) { } uint32_t read(uint32_t reg) const {return *reinterpret_cast(base+reg);} } io {static_cast(map->getVirtualAddress())}; - r=Display13Clock::read(io); + r=Display13Clock::read(io,display13ClockVariant()); } OSSafeReleaseNULL(map); } @@ -9075,7 +9078,7 @@ void XE::wrapAlderLakeNClockSet(IOService *controller,uint64_t nativeHz) { Display13FBClockGuard guard; const bool idle=!Display13WM::busyPipes(c.pipe) && !c.dbuf.updating; const bool cold=idle && !active && adlnBeginClockGuard(guard); - const auto current=Display13Clock::read(io); + const auto current=Display13Clock::read(io,display13ClockVariant()); const bool changing=current.nativeHz()!=nativeHz; if(changing && idle && active==1 && mutex && !c.pipe[0].wmPending && !c.dbuf.unretired && !c.dbuf.faulted && current.status==Display13Clock::Status::Complete && c.pipe[0].wmLatch.status==Display13WM::Status::Complete && @@ -9087,7 +9090,7 @@ void XE::wrapAlderLakeNClockSet(IOService *controller,uint64_t nativeHz) { // the current Hz plus target bandwidth qualifies it BEFORE crawl; // a second latch at the actual new Hz qualifies the new certificate. if(changing && activeReady)c.pipe[0].watermarkClockHz=0; - result=Display13Clock::set(io,nativeHz,{c.bridge.bootAdopted && (active || cold),wmReady,active}); + result=Display13Clock::set(io,nativeHz,{c.bridge.bootAdopted && (active || cold),wmReady,active},display13ClockVariant()); if(cold && result.status==Display13Clock::ChangeStatus::Complete)adlnInvalidateWM(); if(changing && activeReady && result.status==Display13Clock::ChangeStatus::Complete) { afterWM=adlnVerifyWMLatch(fb,result.after.nativeHz(),nativeHz);c.pipe[0].wmLatch=afterWM; @@ -9126,12 +9129,12 @@ void XE::adlnSetDualClock(IOService *controller,uint64_t nativeHz) { !c.bridge.state.faulted && !c.clockFaulted && !c.watermarkFaulted && Display13Power::objectAssociations(c.power,0x80,controller,0xC60); Display13FBClockIO clockIO {c.map}; - auto current=valid ? Display13Clock::read(clockIO) : Display13Clock::Readout {}; + auto current=valid ? Display13Clock::read(clockIO,display13ClockVariant()) : Display13Clock::Readout {}; evidence[2]=current.nativeHz(); // Core independently re-reads the actual clock for an unchanged request. // No synthetic unchanged success, gate or peer-lock dependency is added. if(valid && current.status==Display13Clock::Status::Complete && current.nativeHz()==nativeHz) - result=Display13Clock::set(clockIO,nativeHz,{false,false,0}); + result=Display13Clock::set(clockIO,nativeHz,{false,false,0},display13ClockVariant()); else { auto measure=[&](uint32_t &mask) { Display13FBPowerIO io {c.map};const auto wells=io.read(Display13Power::Driver);mask=0; @@ -9189,7 +9192,7 @@ void XE::adlnSetDualClock(IOService *controller,uint64_t nativeHz) { TGLFBBinding::publish(paths[p],reinterpret_cast(fb),reinterpret_cast(path),binding,p); } } - const auto now=ready ? Display13Clock::read(clockIO) : Display13Clock::Readout {}; + const auto now=ready ? Display13Clock::read(clockIO,display13ClockVariant()) : Display13Clock::Readout {}; ready=ready && now.status==current.status && now.nativeHz()==current.nativeHz() && !c.bridge.state.faulted && !c.clockFaulted && !c.watermarkFaulted; evidence[10]=ready; @@ -9207,7 +9210,7 @@ void XE::adlnSetDualClock(IOService *controller,uint64_t nativeHz) { } const bool activeReady=active && evidence[12]==active; if(activeReady)for(unsigned p=0;p<3;++p)if(active&(1U<display13FBLifecycleHooksReady && callbackXE->adlnHasExternalDisplay && controller==c.controller && c.bridge.begun && !c.bridge.state.faulted && !c.clockFaulted) { - Display13FBClockIO io {c.map};const auto clock=Display13Clock::read(io); + Display13FBClockIO io {c.map};const auto clock=Display13Clock::read(io,display13ClockVariant()); if(clock.status==Display13Clock::Status::Complete || (c.clockStopped && clock.status==Display13Clock::Status::Disabled)) { const bool external=reinterpret_cast(callbackXE->adlnHasExternalDisplay)(controller); - selected=Display13Clock::select(clock.refKHz,minimumHz,external); + selected=Display13Clock::select(clock.refKHz,minimumHz,external,display13ClockVariant()); record[3]=external;record[4]=clock.refKHz; if(selected)*reinterpret_cast(reinterpret_cast(controller)+0xEA0)=selected; } @@ -9661,7 +9664,7 @@ Display13WM::Latch XE::adlnVerifyWMLatch(IOService *fb,uint64_t nativeHz,uint64_ IOMemoryMap *map; bool readable(uint32_t reg) const {return map && map->getLength()==0x1000000 && map->getVirtualAddress() && !(reg&3) && reg<=0xFFFFFC;} uint32_t read(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} - uint64_t nativeClockHz() {Display13FBClockIO clockIO {map};return Display13Clock::read(clockIO).nativeHz();} + uint64_t nativeClockHz() {Display13FBClockIO clockIO {map};return Display13Clock::read(clockIO,display13ClockVariant()).nativeHz();} uint64_t nowUs() {uint64_t t=0,ns=0;clock_get_uptime(&t);absolutetime_to_nanoseconds(t,&ns);return ns/1000;} void delayUs(unsigned us) {IOSleep(us/1000);} } io {c.map}; @@ -9858,7 +9861,7 @@ void XE::display13WMArmUpdate(IOService *fb) { uint64_t record[16] {};IOLockLock(c.lock); Display13WM::Latch result;Display13DBUF::Status retired=Display13DBUF::Status::Unavailable; const auto pending=c.pipe[pipe].wmPending,old=c.dbuf.unretired; - Display13FBClockIO clockIO {c.map};const auto actualHz=Display13Clock::read(clockIO).nativeHz(); + Display13FBClockIO clockIO {c.map};const auto actualHz=Display13Clock::read(clockIO,display13ClockVariant()).nativeHz(); trigger[9]=path ? Display13FBMemory::field(path,0x35F0) : UINT32_MAX; if(intent.valid && !trigger[9])result=adlnVerifyWMLatch(fb,actualHz,actualHz); else result.status=Display13WM::Status::Conflict; @@ -9957,13 +9960,13 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { else {Display13FBPowerIO io {c.map};acquired=Display13Power::acquire(io,c.bridge.state,c.engine,Display13Power::bit(Display13Power::Domain::PW1));} } c.writes+=acquired.writes; - Display13FBClockIO clockIO {c.map};auto clock=Display13Clock::read(clockIO); + Display13FBClockIO clockIO {c.map};auto clock=Display13Clock::read(clockIO,display13ClockVariant()); if(clock.status==Display13Clock::Status::Disabled && c.clockStopped && c.resumeClockHz && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.clockFaulted && !c.dbuf.updating && !Display13WM::busyPipes(c.pipe)) { Display13FBClockGuard guard; if(adlnBeginClockGuard(guard)) { - const auto changed=Display13Clock::set(clockIO,c.resumeClockHz,{true,true,0}); + const auto changed=Display13Clock::set(clockIO,c.resumeClockHz,{true,true,0},display13ClockVariant()); if(changed.status==Display13Clock::ChangeStatus::Complete) { clock=changed.after;c.clockStopped=false;adlnInvalidateWM(); *reinterpret_cast(reinterpret_cast(c.controller)+0xE98)=clock.nativeHz(); @@ -10058,7 +10061,7 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { if(ensureGate() && adlnBeginClockGuard(guard)) { Display13FBDBUFIO io {c.map};status=Display13DBUF::stop(io,c.dbuf); if(status==Display13DBUF::Status::Complete) { - Display13FBClockIO clockIO {c.map};clock=Display13Clock::stop(clockIO,{true,false,0}); + Display13FBClockIO clockIO {c.map};clock=Display13Clock::stop(clockIO,{true,false,0},display13ClockVariant()); if(clock.status!=Display13Clock::ChangeStatus::Complete) {status=Display13DBUF::Status::Power;adlnFault(c.clockFaulted,__LINE__);} else { if(clock.before.nativeHz())c.resumeClockHz=clock.before.nativeHz(); diff --git a/docs/device-selection.md b/docs/device-selection.md index 163ccebf..50fad646 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -10,11 +10,12 @@ WEX selects the GPU from its physical PCI ID and detects its actual resources au | Alder Lake-N / Twin Lake | `46D0`–`46D4` | | Alder Lake-H / P / U | `4626`, `4628`, `462A`, `46A0`–`46A3`, `46A6`, `46A8`, `46AA`, `46B0`–`46B3`, `46C0`–`46C3` | | Raptor Lake-H / P | `A720`, `A7A0`, `A7A8`, `A7AA`, `A7AB` | +| Raptor Lake-U | `A721`, `A7A1`, `A7A9`, `A7AC`, `A7AD` | | Alder Lake-S / HX | `4680`, `4682`, `4688`, `468A`, `468B`, `4690`, `4692`, `4693` | | Raptor Lake-S / HX | `A780`, `A781`, `A782`, `A783`, `A788`, `A789`, `A78A`, `A78B` | An implemented backend does not imply hardware validation for every ID or connector. See [compatibility and tested configurations](../README.md#compatibility). -Raptor Lake-U IDs `A721`, `A7A1`, `A7A9`, `A7AC` and `A7AD` are recognized but unsupported: their dedicated display-clock handling is not implemented. Rocket Lake is also unsupported. +Raptor Lake-U uses its own CDCLK table, including 480 MHz and its PCODE voltage level. The physical PCI ID selects this clock policy. Internal DSI and acceleration have been verified on `A7A9` with Big Sur. Rocket Lake remains unsupported. Memory configuration can change the PCI ID, such as `46A6` to `4626` in a single-channel Alder Lake-H configuration. No override is needed; use updated WEX and Graphics components together. diff --git a/tests/display13_clock_set.cpp b/tests/display13_clock_set.cpp index e5c8b7a5..693d4308 100644 --- a/tests/display13_clock_set.cpp +++ b/tests/display13_clock_set.cpp @@ -40,6 +40,21 @@ struct IO { }; int main() { const Conditions ready {true,true,1}; + for(unsigned id:{0xa721U,0xa7a1U,0xa7a9U,0xa7acU,0xa7adU}) + assert(variantForPCI((id<<16)|0x8086)==Variant::RPLU); + assert(variantForPCI(0xa7a88086)==Variant::ADLP); + for(unsigned ref:{19200U,24000U,38400U}) { + assert(select(ref,624000000,false,Variant::RPLU)==960000000); + assert(select(ref,624000000,false,Variant::ADLP)>960000000); + } + { + IO io; + assert(set(io,960000000,ready,Variant::ADLP).status==ChangeStatus::InvalidFrequency); + assert(io.writes.empty()); + auto r=set(io,960000000,ready,Variant::RPLU); + assert(r.status==ChangeStatus::Complete && r.after.cdclkKHz==480000 && + r.after.ratio==50 && io.regs[Data]==1); + } assert(select(38400,0,true)==1305600000 && select(24000,0,true)==1296000000); assert(select(38400,345600000,false)==358400000 && select(38400,358400000,false)==384000000); assert(!select(38400,1305600000,false) && !select(0,0,true)); diff --git a/tests/dsi_host.cpp b/tests/dsi_host.cpp index b160a47e..6e32659b 100644 --- a/tests/dsi_host.cpp +++ b/tests/dsi_host.cpp @@ -29,6 +29,10 @@ int main(int argc,char **argv) { assert(packet(0x15,{data,2}).header==0x40122915); assert(packet(0x39,{data,5}).header==0xC0000539); assert(!packet(6,{data,1}).valid&&!packet(5,{nullptr,1}).valid&&!packet(0x39,{data,257}).valid); + const uint8_t generic[]={0xde,0}; + Mock tx;Host genericHost(true); + assert(genericHost.send(tx,0x29,{generic,2})); + assert(tx.h.size()==1 && tx.h[0]==0xc0000229 && tx.p.size()==1 && tx.p[0]==0xde); Mock io;Host off;assert(!off.send(io,5,{data,1})&&off.error==HostError::Disabled&&io.h.empty()); Host ok(true);assert(ok.send(io,0x39,{data,5})&&ok.pending); assert(io.p.size()==2&&io.p[0]==0x56341229&&io.p[1]==0x78); diff --git a/tests/dsi_panel.py b/tests/dsi_panel.py index e9a39b34..a733d45a 100644 --- a/tests/dsi_panel.py +++ b/tests/dsi_panel.py @@ -22,7 +22,7 @@ struct Sim:IO { uint32_t expectedDuty=UINT32_MAX; bool owned=true,freezeFrame=false,stuckStop=false,stuckStart=false,stuckCommand=false; - uint32_t drop=UINT32_MAX,elapsed=0,headers=0,payload=0,videoCommandDelay=0,creditRelease=0;unsigned irqDepth=0; + uint32_t drop=UINT32_MAX,elapsed=0,headers=0,payload=0,videoCommandDelay=0,initCommandDelay=0,creditRelease=0;unsigned irqDepth=0; std::vector packets,gpioTrace; bool ready(){return owned && (regs[0x45404]&0x403)==0x403;} uint32_t read(uint32_t reg){ @@ -38,6 +38,7 @@ assert(!irqDepth); if(reg==0x6B100){++headers;packets.push_back(value); if(regs[0x7B008]&(1U<<31))creditRelease=elapsed+videoCommandDelay; + else if((value&0x3f)==0x39)creditRelease=elapsed+initCommandDelay; return;} if(reg==0x6B104){++payload;return;} if(reg==0x7B008){regs[reg]=(value&~(1U<<30))|(regs[reg]&(1U<<30));return;} @@ -89,6 +90,11 @@ assert(f.io.regs[0x71180]==0x94000000 && f.io.regs[0x64100]==0x80000000); } int main(int argc,char **argv){ + for(bool stalled:{false,true}){ + Fixture f;f.io.initCommandDelay=stalled?200000:1500; + f.run();assert(f.result.complete()==!stalled); + if(stalled)assert(f.result.stage==Panel::Stage::Init && f.result.host.error==HostError::CreditTimeout); + } for(bool stalled:{false,true}) { Fixture f;f.io.videoCommandDelay=stalled?200000:1000; f.seq[3]={1,5,0,5,1,0,0x11,2,4,0xA0,0x86,1,0,1,5,0,5,1,0,0x29,2,4,0xA0,0x86,1,0,0}; diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index 489b45a2..0cf0108d 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -21,6 +21,7 @@ #include #include using namespace XELP::DSI; +Display13Clock::Variant display13ClockVariant(){return Display13Clock::Variant::ADLP;} IO_FIXTURE unsigned lockLevel=0; struct IORecursiveLock{};struct IOLock{};struct IOSimpleLock{}; diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index 98283ca5..a54cc3bf 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -22,7 +22,7 @@ struct Sim:IO { bool owned=true,dropPLLDisable=false,dropPLLPower=false,dropPLLLock=false,dropBuffer=false,dropLink=false; bool ulpsRequest=false; - uint32_t drop=UINT32_MAX,elapsed=0; + uint32_t drop=UINT32_MAX,elapsed=0,linkReadyAt=0; std::vector gpioTrace; bool gpio(unsigned role,unsigned value){const auto r=Panel::gpio(*this,role,value);if(r.complete)gpioTrace.push_back(role*2+value);return r.complete;} bool ready(){return owned;} @@ -50,7 +50,7 @@ if(pll&(1U<<31)){if(!dropPLLLock)pll|=1U<<30;}else if(!dropPLLDisable)pll&=~(1U<<30); if(!dropBuffer){if(regs[0x64000]&(1U<<31))regs[0x64000]&=~0x80U;else regs[0x64000]|=0x80;} if(ulpsRequest)regs[0x6B0D8]|=1U<<16; - if(!dropLink && (regs[0x6B400]&(1U<<31)))regs[0x6B030]|=1U<<20; + if(!dropLink && elapsed>=linkReadyAt && (regs[0x6B400]&(1U<<31)))regs[0x6B030]|=1U<<20; } }; struct Fixture { @@ -164,6 +164,8 @@ assert(link.matches(f.p)); // program/read back, not just calculate a PLL } {Fixture f;successful(f);} + {Fixture f;assert(f.stop());f.io.linkReadyAt=f.io.elapsed+3000; + assert(f.start() && f.io.elapsed>=f.io.linkReadyAt);} for(unsigned fault=0;fault<4;++fault){Fixture f;retainedCycle(f,fault);} {Fixture f;panelCycle(f);} {Fixture f;panelCycle(f,true);} @@ -195,7 +197,7 @@ assert(!f.start() && f.state.error!=Restart::Error::None); const auto count=f.io.writes.size();assert(!f.start() && f.io.writes.size()==count); assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2 && !f.io.regs[0x7B008]); - assert(f.io.elapsed<5000); + assert(f.io.elapsed<(fault==3?12500U:5000U)); } if(argc==2){ {Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in);f.vbt=Bytes{std::istreambuf_iterator(in),{}}; diff --git a/tests/dsi_sequence.cpp b/tests/dsi_sequence.cpp index 3e42d96d..2a44bf26 100644 --- a/tests/dsi_sequence.cpp +++ b/tests/dsi_sequence.cpp @@ -22,6 +22,8 @@ int main(int argc,char **argv) { } for(auto op:Bytes{4,5,6,7,255}){auto bad=ops;bad[0]=op;rejected(block(panel(bad)));} for(auto flags:Bytes{1,2,8,32,255}){auto bad=ops;bad[2]=flags;rejected(block(panel(bad)));} + assert(parse(block(panel(Bytes{1,6,0,0x29,2,0,0xde,0,0}))).valid()); + rejected(block(panel(Bytes{1,4,0,0x29,0,0,0}))); auto bad=ops;bad[3]=6;rejected(block(panel(bad))); // read unsupported bad=ops;bad[4]=2;rejected(block(panel(bad))); // wrong packet length bad=ops;bad.pop_back();rejected(block(panel(bad))); // no operation END diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index 5f30f69b..c8b58333 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -14,7 +14,7 @@ q=(root/'tests/dsi_quiesce_adapter.py').read_text() q=q.split("code=r'''",1)[1].split("'''.replace",1)[0] prelude='#define DSI_SURFACE_REPLAY\n'+q[:q.index('struct XE {')] -prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"') +prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"\nstatic Display13Clock::Variant display13ClockVariant(){return Display13Clock::Variant::ADLP;}') b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') prelude=prelude.replace('#include "kern_dsi_native_backing.hpp"','#include "kern_dsi_native_backing.hpp"\n#include "kern_dsi_native_mode.hpp"') diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 54ff4102..4ba67668 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -4,6 +4,7 @@ #include #include #include "AppleIntelTGLCompatPortProbe.hpp" +#include "kern_display13_clock.hpp" struct Meta {unsigned size=0x1C20;unsigned getClassSize(){return size;}}; struct OSObject { virtual ~OSObject()=default; From f8f7026d60a289f448a6e87cff0003adedb0b556 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 02:14:53 +0900 Subject: [PATCH 02/46] Park the adopted DSI panel on system restart --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 5 +++++ WhateverXe/kern_dsi_attributes.hpp | 2 +- WhateverXe/kern_xe.cpp | 2 ++ tests/dsi_update_dispatch.py | 11 +++++++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index f258b1f4..d74033d8 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -118,6 +118,11 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu if(value>UINT32_MAX)return kIOReturnUnsupported; return wrapDSISetPowerState(fb,static_cast(value)); } + // IOGraphics sends spwr before the platform resets. Park the adopted + // panel through its own power path while display resources are live. + if(attribute==kIOSystemPowerAttribute && value==kIOMessageSystemWillRestart && + callbackXE->dsiPowerTest && dsiModeTarget(fb)) + return wrapDSISetPowerState(fb,0); if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; diff --git a/WhateverXe/kern_dsi_attributes.hpp b/WhateverXe/kern_dsi_attributes.hpp index cee316ef..760aa962 100644 --- a/WhateverXe/kern_dsi_attributes.hpp +++ b/WhateverXe/kern_dsi_attributes.hpp @@ -10,7 +10,7 @@ inline bool transportAttribute(uint32_t attribute) { // AGPM selectors belong to the shared GPU power implementation and // are not DSI transport requests. Do not filter them here. case 0x68646370U: // hdcp: DisplayPath::initHDCP (DDI/AUX) - case 0x73707772U: // spwr: controller hwShutdown, outside DSI stop + case 0x73707772U: // spwr: never run the donor DDI shutdown on the DSI panel return true; default:return false; } diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 761c9a8e..35f240f5 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -1,5 +1,7 @@ #include "kern_display13_color.hpp" #include +#include +#include // // kern_xe.cpp // WhateverGreen diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 3450874a..c8f84590 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -52,6 +52,7 @@ return XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode)?&t:nullptr; } IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} +constexpr uint32_t kIOSystemPowerAttribute=0x73707772U, kIOMessageSystemWillRestart=0xe0000310U; constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) uint32_t nativeMode(IOService*,int32_t m,int32_t d){assert(!fbLock.depth && !owner);assert(m==0x10000 && d==2);++modeCalls;return 7;} @@ -210,6 +211,16 @@ }; int main(){ + {Case c;put(&c.fb,0x4284,2U); + auto restart=[&]{return XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,kIOMessageSystemWillRestart);}; + assert(restart()==kIOReturnUnsupported && !xe.stopCalls); + xe.dsiPowerTest=true; + assert(XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,0xe0000250U)==kIOReturnUnsupported); + assert(restart()==kIOReturnNotReady && Display13FBMemory::field(&c.fb,0x4284)==2); + xe.stopOK=true;assert(restart()==kIOReturnSuccess && xe.dsiRuntimePower.sleeping); + assert(Display13FBMemory::field(&c.fb,0x4284)==0); + auto stops=xe.stopCalls;assert(restart()==kIOReturnSuccess && xe.stopCalls==stops); + } for(unsigned attr:{0x706F7772U,0x70777273U}){Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==1); assert(Display13FBMemory::field(&c.fb,0x4284)==2); From a14b141d9274e01fdb533ed0b666820d65d30561 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:21:30 +0900 Subject: [PATCH 03/46] Park the adopted DSI panel on system power-off --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 6 +++--- tests/dsi_update_dispatch.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index d74033d8..453971be 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -118,10 +118,10 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu if(value>UINT32_MAX)return kIOReturnUnsupported; return wrapDSISetPowerState(fb,static_cast(value)); } - // IOGraphics sends spwr before the platform resets. Park the adopted + // IOGraphics sends spwr before restart or power-off. Park the adopted // panel through its own power path while display resources are live. - if(attribute==kIOSystemPowerAttribute && value==kIOMessageSystemWillRestart && - callbackXE->dsiPowerTest && dsiModeTarget(fb)) + if(attribute==kIOSystemPowerAttribute && callbackXE->dsiPowerTest && dsiModeTarget(fb) && + (value==kIOMessageSystemWillRestart || value==kIOMessageSystemWillPowerOff)) return wrapDSISetPowerState(fb,0); if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index c8f84590..f9036ac4 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -52,7 +52,7 @@ return XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode)?&t:nullptr; } IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} -constexpr uint32_t kIOSystemPowerAttribute=0x73707772U, kIOMessageSystemWillRestart=0xe0000310U; +constexpr uint32_t kIOSystemPowerAttribute=0x73707772U, kIOMessageSystemWillRestart=0xe0000310U, kIOMessageSystemWillPowerOff=0xe0000250U; constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) uint32_t nativeMode(IOService*,int32_t m,int32_t d){assert(!fbLock.depth && !owner);assert(m==0x10000 && d==2);++modeCalls;return 7;} @@ -211,15 +211,15 @@ }; int main(){ - {Case c;put(&c.fb,0x4284,2U); - auto restart=[&]{return XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,kIOMessageSystemWillRestart);}; - assert(restart()==kIOReturnUnsupported && !xe.stopCalls); + for(auto message:{kIOMessageSystemWillRestart,kIOMessageSystemWillPowerOff}){Case c;put(&c.fb,0x4284,2U); + auto notify=[&]{return XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,message);}; + assert(notify()==kIOReturnUnsupported && !xe.stopCalls); xe.dsiPowerTest=true; - assert(XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,0xe0000250U)==kIOReturnUnsupported); - assert(restart()==kIOReturnNotReady && Display13FBMemory::field(&c.fb,0x4284)==2); - xe.stopOK=true;assert(restart()==kIOReturnSuccess && xe.dsiRuntimePower.sleeping); + assert(XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,0xe0000255U)==kIOReturnUnsupported); + assert(notify()==kIOReturnNotReady && Display13FBMemory::field(&c.fb,0x4284)==2); + xe.stopOK=true;assert(notify()==kIOReturnSuccess && xe.dsiRuntimePower.sleeping); assert(Display13FBMemory::field(&c.fb,0x4284)==0); - auto stops=xe.stopCalls;assert(restart()==kIOReturnSuccess && xe.stopCalls==stops); + auto stops=xe.stopCalls;assert(notify()==kIOReturnSuccess && xe.stopCalls==stops); } for(unsigned attr:{0x706F7772U,0x70777273U}){Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==1); From fef54f8c0ee299c105562d553ac8e02245289f11 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:26:03 +0900 Subject: [PATCH 04/46] Adopt mirrored DSI firmware scanout with USB-C displays attached --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_display13_boot.hpp | 84 ++++++++++++++++++++++--- WhateverXe/kern_display13_fb_memory.hpp | 13 +++- WhateverXe/kern_display13_tc.hpp | 9 +-- WhateverXe/kern_dsi_adapter_backing.inc | 5 +- WhateverXe/kern_dsi_adapter_boot.inc | 1 + WhateverXe/kern_dsi_boot.hpp | 9 ++- WhateverXe/kern_dsi_surface.hpp | 10 +-- WhateverXe/kern_xe.cpp | 14 ++++- WhateverXe/kern_xelp_geometry.hpp | 6 ++ tests/display13_boot.cpp | 34 ++++++++++ tests/display13_fb_memory.cpp | 10 ++- tests/dsi_resources.py | 2 +- tests/dsi_surface.py | 6 ++ 15 files changed, 176 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 64e18efa..fb4bb04b 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ The common regression checks cover Metal, OpenGL, OpenCL, hardware H.264 encode/ | Tiger Lake laptops | Big Sur–Tahoe: 48 EU, internal eDP and brightness, HDMI/USB-C, up to three displays and display off/on | | Alder Lake-N mini PCs | Big Sur–Tahoe: 16/24 EU, two 1080p60 HDMI displays and display off/on | | Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | -| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | +| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | | Alder Lake-H | Big Sur–Tahoe: 96 EU, three displays using HDMI and a DP-to-HDMI adapter. Single-channel `4626` graphics acceleration checked on Big Sur | | Alder Lake-S | Big Sur–Tahoe: UHD 770, HDMI/DP/USB-C and up to three displays; Safari video, with Widevine checked on Big Sur/Tahoe | diff --git a/README_ja.md b/README_ja.md index 505c3a69..57648473 100644 --- a/README_ja.md +++ b/README_ja.md @@ -95,7 +95,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします | Tiger Lakeラップトップ | Big Sur〜Tahoe:48 EU、内蔵eDPと輝度調整、HDMI/USB-C、最大3画面・消灯復帰 | | Alder Lake-NミニPC | Big Sur〜Tahoe:16/24 EU、1080p60のHDMI 2画面・消灯復帰 | | Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | -| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・50 Hz、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | +| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・50 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | | Alder Lake-H | Big Sur〜Tahoe:96 EU、HDMIとDP→HDMI変換器による3画面。シングルチャネル構成の`4626`でもBig Surの加速を確認 | | Alder Lake-S | Big Sur〜Tahoe:UHD 770、HDMI/DP/USB-C、最大3画面、Safari動画。WidevineはBig Sur/Tahoeで確認 | diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index 612bba77..cd1175a8 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -125,13 +125,14 @@ struct NormalizeScratch { }; template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, const Display13HDMIWiring &w,const XELPMemory::Layout &memory, - NormalizeScratch &scratch) { + NormalizeScratch &scratch,const XELP::DSI::Readout *dsi=nullptr) { Result r;r.before=boot.activePipes; if(!admitted || &boot==&scratch.after)return r; r.status=Status::Topology; const auto active=boot.activePipes; if(w.panel || !memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete || - !(active&1) || active==1 || (active&~7U) || (active&~boot.poweredPipes))return r; + (dsi ? (w.dsi!=1 || !active || (active&1) || dsi->identity.pipe!=0 || !XELP::DSI::sourceUnchanged(io,*dsi)) + : (w.dsi || !(active&1) || active==1)) || (active&~7U) || (active&~boot.poweredPipes))return r; uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0; auto &tc=scratch.tc; for(auto &port:tc)port={}; @@ -144,7 +145,7 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo uint32_t ctl=0,trans=0,surface=0,live=0; if(!AlderLakeN::value(boot,0x70180+bank,ctl) || !AlderLakeN::value(boot,0x60400+bank,trans))return r; const auto port=Display13FBTopology::bootDDI(w,trans,true); - if(port>=7 || (used&(1U<=7 || (used&(1U<>24)&7)==2; if(port>=3) { - tc[p]=Display13TC::Boot::capture(io,true,port,p,dp); + const bool alt=dsi && (w.typeC&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo const auto peer=[&] { // These are borrowed firmware requests. Keep them unchanged, including // the stopped output's requests; native leases own subsequent changes. - if(io.read(0x45404)!=power || io.read(0x45454)!=ddiPower || io.read(0x45444)!=auxPower)return false; + if(io.read(0x45404)!=power || io.read(0x45454)!=ddiPower || io.read(0x45444)!=auxPower || + (dsi && !XELP::DSI::sourceUnchanged(io,*dsi)))return false; for(unsigned p=0;p<3;++p)if((active&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo } } access{io,tc[p],r,peer,write}; const auto owner=reinterpret_cast(&io),object=reinterpret_cast(&tc[p]); - if(access.ownership.acquire(access,owner,object,ports[p],true).status!=Display13TC::Status::Complete || + if(access.ownership.acquire(access,owner,object,ports[p],true,tc[p].mode).status!=Display13TC::Status::Complete || !Display13TC::Clock::disable(access,tc[p].port) || access.ownership.release(access,owner,object,ports[p]).status!=Display13TC::Status::Complete) { if(r.status!=Status::PeerChanged)r.status=Status::Readback;return r; @@ -328,8 +332,72 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo } current=UINT32_MAX; auto &after=scratch.after;AlderLakeN::readDisplay(io,true,after);r.after=after.activePipes; - if(after.status!=AlderLakeN::ReadoutStatus::Complete || after.activePipes!=1 || !peer()) {r.status=Status::PeerChanged;return r;} + if(after.status!=AlderLakeN::ReadoutStatus::Complete || after.activePipes!=(dsi?0U:1U) || !peer()) {r.status=Status::PeerChanged;return r;} r.stage=7;r.status=Status::Complete;return r; } +// Preserve the DSI link and the firmware allocation. Once the TC peers have +// drained, remove the primary pipe scaler and present that same (possibly +// smaller) linear plane on the native canvas until the driver owns a full +// panel-sized backing. Never enlarge the firmware plane's memory footprint. +template Result normalizeDSIMirror(IO &io,const AlderLakeN::DisplayReadout &boot, + bool admitted,const Display13HDMIWiring &w,const XELPMemory::Layout &memory, + const XELP::DSI::Profile &profile,NormalizeScratch &scratch) { + Result r;r.before=boot.activePipes; + if(!admitted || !profile.valid() || !memory.reservationKnown || + boot.status!=AlderLakeN::ReadoutStatus::Complete || !(boot.poweredPipes&1) || + !boot.activePipes || (boot.activePipes&~6U) || w.dsi!=1)return r; + r.status=Status::Topology;r.stage=10; + const auto get=[&](uint32_t reg){uint32_t v;return AlderLakeN::value(boot,reg,v)?v:UINT32_MAX;}; + if(!io.readable(0x51004) || !io.readable(0x45404) || io.read(0x45404)!=get(0x45404))return r; + const auto ref=io.read(0x51004)>>29; + const uint32_t refs[]={24000,19200,38400};if(ref>=3)return r; + auto link=XELP::DSI::decodeReadout([&](uint32_t reg,uint32_t &v){ + if(!io.readable(reg))return false;v=io.read(reg);return true; + },refs[ref]); + if(link.identity.pipe!=0 || !link.matches(profile) || !XELP::DSI::sourceUnchanged(io,link) || + !io.readable(0x7B808) || (io.read(0x7B808)&0xC0000000U))return r; + r.stage=11; + const auto control=get(0x70180),surface=get(0x7019C); + const auto g=XELPGeometry::linear(control,get(0x70188),get(0x70190),get(0x7018C),get(0x701A4)); + const auto width=(link.htotal&65535)+1,height=(link.vtotal&65535)+1; + if(!AlderLakeN::bootGeometry(boot,0,g,link.htotal,link.vtotal) || + get(0x6001C)!=((g.width-1)<<16|(g.height-1)) || !memory.range(surface,g.bytes,true) || + surface!=get(0x701AC) || (get(0x60400)&(1U<<31)) || (get(0x70008)&0xC0000000U))return r; + uint32_t scaler=0; + for(unsigned i=0;i<2;++i)if(get(0x68180+i*0x100)&(1U<<31)) { + scaler=0x68170+i*0x100; + if(g.width==width && g.height==height)return r; + } + // Reject other consumers on any powered pipe before touching any of them. + for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U<=2 && uint32_t(v-first)<0x10000000U; + },0x70040,r.framesAfter,r.status,Status::PeerChanged,false); + }; + const auto write=[&](uint32_t reg,uint32_t value) { + r.lastReg=reg;r.expected=value;io.write(reg,value);++r.writes; + r.observed=io.read(reg);return r.observed==value; + }; + if(!stable() || !AlderLakeN::bootGeometryUnchanged(io,boot,0) || + io.read(0x70180)!=control || io.read(0x701AC)!=surface || !frames())return r; + if(!write(0x70180,control&~(1U<<31)) || !write(0x7019C,surface) || !frames())return r; + if(!write(scaler+0x10,0) || !write(scaler,0) || !write(scaler+4,0) || + !write(0x6001C,((width-1)<<16)|(height-1)) || !frames() || + !write(0x70180,control) || !write(0x7019C,surface) || !frames() || + io.read(0x701AC)!=surface)return r; + r.stage=13;r.status=Status::Complete;return r; +} + } #endif diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index 52c2a0d5..ee5af80d 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -364,7 +364,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll uint64_t apertureBytes,const XELPMemory::Layout &memory,bool bootScanout=true,uintptr_t expectedOwner=0, unsigned expectedDDI=1,const Display13TC::Boot *tc=nullptr, const AlderLakeN::DisplayReadout *dp=nullptr,bool panel=false, - const XELP::DSI::Readout *dsi=nullptr) { + const XELP::DSI::Readout *dsi=nullptr,uint32_t clippedBootSize=0) { Readout r; if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) { r.status=Status::Association;return r; @@ -410,12 +410,19 @@ template Readout inspect(IO &io,const void *controller,size_t controll if(wells==UINT32_MAX || (wells&(bootScanout ? 0x403U : 3U))!=(bootScanout ? 0x403U : 3U)) {r.status=Status::DisplayState;return r;} uint32_t dpt=0,trans=0,conf=0,position=0,offset=0; const uint32_t transReg=dsi?0x6B400U:0x60400U,confReg=dsi?0x7B008U:0x70008U; + const auto clippedCanvas=[&]() { + return dsi && clippedBootSize && r.size==clippedBootSize && + io.readable(0x6001C) && io.readable(0x68180) && io.readable(0x68280) && + io.read(0x6001C)==(((dsi->htotal&0x3fff)<<16)|(dsi->vtotal&0x3fff)) && + !(io.read(0x68180)&(1U<<31)) && !(io.read(0x68280)&(1U<<31)); + }; if(bootScanout) { dpt=io.read(0x42084);trans=io.read(transReg);conf=io.read(confReg); position=io.read(0x7018C);offset=io.read(0x701A4); r.surface=io.read(0x7019C);r.control=io.read(0x70180);r.stride=io.read(0x70188);r.size=io.read(0x70190); const auto geometry=XELPGeometry::linear(r.control,r.stride,r.size,position,offset); - const bool source=dsi ? !(io.read(0x60400)&(1U<<31)) && XELPGeometry::timing(geometry,dsi->htotal,dsi->vtotal) : + const bool source=dsi ? !(io.read(0x60400)&(1U<<31)) && (XELPGeometry::timing(geometry,dsi->htotal,dsi->vtotal) || + (clippedCanvas() && geometry.width<=(dsi->htotal&0x3fff)+1 && geometry.height<=(dsi->vtotal&0x3fff)+1)) : (dpt&(1U<<30)) && ((trans>>27)&15)==expectedDDI+1 && (dp ? ((trans>>24)&7)==2 : ((trans>>24)&7)<=1) && !((trans>>20)&7); if (dpt==UINT32_MAX || !source || !(trans&(1U<<31)) || (conf&0xC0000000)!=0xC0000000 || @@ -447,7 +454,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll io.read(0x70180)!=r.control || io.read(0x70188)!=r.stride || io.read(0x70190)!=r.size || (expectedDDI>=3 && !tc->unchanged(io)) || (dp && !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc,panel)) || - (dsi && (!XELP::DSI::sourceUnchanged(io,*dsi) || (io.read(0x60400)&(1U<<31))))))) { + (dsi && (!XELP::DSI::sourceUnchanged(io,*dsi) || (io.read(0x60400)&(1U<<31)) || (clippedBootSize && !clippedCanvas())))))) { r.status=Status::StateChanged;return r; } r.status=Status::Complete;return r; diff --git a/WhateverXe/kern_display13_tc.hpp b/WhateverXe/kern_display13_tc.hpp index 238d443c..4509d411 100644 --- a/WhateverXe/kern_display13_tc.hpp +++ b/WhateverXe/kern_display13_tc.hpp @@ -66,19 +66,20 @@ struct Boot { // left unowned by firmware. Only explicit DP source validation may request // this witness; the later Mode acquisition must still take ownership. bool ownershipOptional {}; + unsigned mode {4}; bool valid() const { const auto powered=pipe<3 ? 5U|(1U<<(10+2*pipe)) : 0U; return port.ddi>=3 && port.ddi<=6 && pipe<3 && power!=UINT32_MAX && (power&powered)==powered && ddiPower!=UINT32_MAX && - (ddiPower&(1U<<(2*port.ddi))) && ready!=UINT32_MAX && (ready&7)==4 && + (ddiPower&(1U<<(2*port.ddi))) && (ready&4) && Port::modeMatches(ready,mode) && (mode==4 || !ownershipOptional) && buffer!=UINT32_MAX && (buffer&0x80000080U)==0x80000000U && (ownershipOptional || (buffer&64)) && clock!=UINT32_MAX && (clock>>28)==8 && pll!=UINT32_MAX && (pll&0xCC000000U)==0xCC000000U && transClock!=UINT32_MAX && (transClock>>28)==port.ddi+3; } - template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe,bool ownershipOptional=false) { + template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe,bool ownershipOptional=false,unsigned mode=4) { Boot b; if(!admitted || pipe>=3 || !Port::resolve(ddi,b.port))return b; - b.pipe=pipe;b.ownershipOptional=ownershipOptional; + b.pipe=pipe;b.ownershipOptional=ownershipOptional;b.mode=mode; if(!io.readable(0x45404) || !io.readable(0x45454))return {}; b.power=io.read(0x45404);b.ddiPower=io.read(0x45454); const auto powered=5U|(1U<<(10+2*pipe)); @@ -95,7 +96,7 @@ struct Boot { } template bool unchanged(IO &io) const { if(!valid())return false; - const auto now=capture(io,true,port.ddi,pipe,ownershipOptional); + const auto now=capture(io,true,port.ddi,pipe,ownershipOptional,mode); return now.valid() && now.power==power && now.ddiPower==ddiPower && now.ready==ready && now.buffer==buffer && now.clock==clock && now.pll==pll && now.transClock==transClock; } diff --git a/WhateverXe/kern_dsi_adapter_backing.inc b/WhateverXe/kern_dsi_adapter_backing.inc index d5369af3..fe968a25 100644 --- a/WhateverXe/kern_dsi_adapter_backing.inc +++ b/WhateverXe/kern_dsi_adapter_backing.inc @@ -297,13 +297,14 @@ bool XE::switchDSINativeSurface() { const Display13WM::Snapshot snapshot{&wm,nullptr,ddb,cursorDDB,static_cast(o.proof.gpu),0,ipc,hz,false,false, Display13WM::pipeLineTime(dsiProfile.mode.htotal(),pixel),pixel,0,c.transcoders}; Surface::Plane current{dsiBoot.control,io.read(0x701CC),dsiBoot.stride,dsiBoot.size,dsiBoot.surface,0,0}; - auto next=current;next.control=Display13WM::arbitrationControl((current.control&~(7U<<10))|(4U<<10)); + auto next=current;next.size=((uint32_t(dsiProfile.mode.height)-1)<<16)|(dsiProfile.mode.width-1); + next.control=Display13WM::arbitrationControl((current.control&~(7U<<10))|(4U<<10)); next.color|=1U<<13;next.stride=static_cast(o.proof.pitch/128);next.surface=static_cast(o.proof.gpu); Surface::Backing backing{reinterpret_cast(o.record),o.proof.gpu,o.proof.bytes,o.proof.pitch}; Capabilities caps;caps.installed=Capabilities::Adoption; if(ddb!=UINT32_MAX && cursorDDB!=UINT32_MAX && Display13DBUF::valid(range) && dsiSession.bind(caps,c.bootDSI,dsiProfile,1,reinterpret_cast(path),reinterpret_cast(&dsiBoot))) { - result=Surface::change(io,dsiSession,c.bootDSI,dsiProfile,current,next,backing,snapshot); + result=Surface::change(io,dsiSession,c.bootDSI,dsiProfile,current,next,backing,snapshot,dsiBoot.clipped); o.armed=result.writes!=0 || dsiSession.state==SessionState::Switching || dsiSession.state==SessionState::Faulted || dsiSession.state==SessionState::Streaming; if(!o.armed)dsiSession={}; diff --git a/WhateverXe/kern_dsi_adapter_boot.inc b/WhateverXe/kern_dsi_adapter_boot.inc index faf4ff58..c31806b1 100644 --- a/WhateverXe/kern_dsi_adapter_boot.inc +++ b/WhateverXe/kern_dsi_adapter_boot.inc @@ -280,6 +280,7 @@ bool XE::bindDSIResources(IOService *controller) { // The live DSI GOP is linear, so this shared mode can be prepared // without disabling its plane. Retain PLL/power on any failure. if(XELP::DSI::prepareGGTT(io,dsiBoot,xelpMemory,dsiGGTT)) { + c.clippedDSIBootSize=dsiBoot.clipped ? dsiBoot.size : 0; c.bootDSI=dsiBoot.source;c.transcoders.dsi0Pipe=0; irq.state.transcoders=c.transcoders;irq.base=c.map->getVirtualAddress();irq.lock=irqLock; __atomic_store_n(&irq.admittedPipes,1U,__ATOMIC_RELEASE); diff --git a/WhateverXe/kern_dsi_boot.hpp b/WhateverXe/kern_dsi_boot.hpp index a22c54d9..62a4f8dc 100644 --- a/WhateverXe/kern_dsi_boot.hpp +++ b/WhateverXe/kern_dsi_boot.hpp @@ -13,6 +13,7 @@ struct Boot { uint32_t version {1};BootStatus status {BootStatus::Unavailable}; Readout source;XELPMemory::Layout memory;XELPGeometry::Linear geometry; uint32_t poweredPipes {},control {},stride {},size {},surface {},dpt {},verifiedPages {}; + bool clipped {}; bool usable() const {return status==BootStatus::Candidate || status==BootStatus::Complete;} bool complete() const {return status==BootStatus::Complete;} }; @@ -50,10 +51,10 @@ template bool bootUnchanged(IO &io,const Boot &boot,const XELPMemory:: const uint32_t expected[]={boot.dpt,boot.control,boot.stride,boot.size,boot.surface,boot.surface,0,0}; for(unsigned i=0;i void captureBoot(IO &io,const AlderLakeN::DisplayReadout &normal, - const Profile &profile,const XELPMemory::Layout &memory,bool admitted,Boot &r) { + const Profile &profile,const XELPMemory::Layout &memory,bool admitted,Boot &r,bool mirrorPrepared=false) { r={};if(!admitted || !profile.valid())return; r.status=BootStatus::Memory; if(!memory.valid || !memory.reservationKnown || !memory.bytes || memory.bytes%4096 || @@ -76,7 +77,9 @@ template void captureBoot(IO &io,const AlderLakeN::DisplayReadout &nor r.dpt=words[0];r.control=words[1];r.stride=words[2];r.size=words[3];r.surface=words[4]; r.geometry=XELPGeometry::linear(r.control,r.stride,r.size,words[6],words[7]); // LINEAR uses byte GGTT offsets even with global DPT-disable clear. - if(!XELPGeometry::timing(r.geometry,r.source.htotal,r.source.vtotal) || r.surface!=words[5] || + r.clipped=mirrorPrepared && !XELPGeometry::timing(r.geometry,r.source.htotal,r.source.vtotal) && + io.readable(0x6001C) && XELPGeometry::insideCanvas(r.geometry,r.source.htotal,r.source.vtotal,io.read(0x6001C)); + if((!r.clipped && !XELPGeometry::timing(r.geometry,r.source.htotal,r.source.vtotal)) || r.surface!=words[5] || !memory.range(r.surface,r.geometry.bytes,true))return; r.status=BootStatus::Candidate; if(!bootUnchanged(io,r,memory))r.status=BootStatus::Changed; diff --git a/WhateverXe/kern_dsi_surface.hpp b/WhateverXe/kern_dsi_surface.hpp index d69d8bc0..3890ded5 100644 --- a/WhateverXe/kern_dsi_surface.hpp +++ b/WhateverXe/kern_dsi_surface.hpp @@ -21,14 +21,16 @@ constexpr uint32_t Registers[]={0x70188,0x7018C,0x70190,0x701A4,0x701CC,0x70180, inline void values(const Plane &p,uint32_t (&v)[7]) { v[0]=p.stride;v[1]=p.position;v[2]=p.size;v[3]=p.offset;v[4]=p.color;v[5]=p.control;v[6]=p.surface; } -inline bool shape(const Plane &p,const Profile &profile) { +inline bool shape(const Plane &p,const Profile &profile,bool clippedBoot=false) { // The existing Display13 WM backend admits linear and uncompressed Y. // Keep the P2 fixed RGB8888 geometry; rotation/scaling/color modes are not // implicitly implemented by copying the donor's register cache. return profile.valid() && profile.mode.valid() && profile.mode.width<=8192 && profile.mode.height<=8192 && Display13WM::supportedRGBControl(p.control) && (p.control&(1U<<31)) && ((p.control>>23)&31)==8 && !(p.control&((3U<<21)|(1U<<9)|(1U<<8))) && - !p.position && !p.offset && p.size==((uint32_t(profile.mode.height)-1)<<16 | (uint32_t(profile.mode.width)-1)) && + !p.position && !p.offset && + (p.size==((uint32_t(profile.mode.height)-1)<<16 | (uint32_t(profile.mode.width)-1)) || + (clippedBoot && !(p.control&(7U<<10)) && (p.size&65535)>16) bool source(IO &io,const Readout &link,bool stoppingCursor=fa // the lease in Session. This never calls cleanup or reports a successful stop. template Result change(IO &io,Session &session,const Readout &link,const Profile &profile, const Plane ¤t,const Plane &next,const Backing &backing, - const Display13WM::Snapshot &wm) { + const Display13WM::Snapshot &wm,bool clippedBoot=false) { Result r; if(!io.ready() || !link.matches(profile) || !session.identity.same(link.identity) || - !shape(current,profile) || !target(next,profile,backing) || next.surface==current.surface || + !shape(current,profile,clippedBoot) || !target(next,profile,backing) || next.surface==current.surface || ((next.control^current.control)&~((7U<<28)|(7U<<10))) || (next.color!=current.color && next.color!=(current.color|(1U<<13))) || wm.pipeIndex!=0 || wm.transcoders.dsi0Pipe!=0 || wm.cursorActive || !wm.primary || diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 35f240f5..be09c440 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4053,8 +4053,17 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("WEXDisplay13TCBootPorts",bootTC,sizeof(bootTC)); } + bool mirrorPrepared=false,mirrorFailed=false; + if(displayRequested && linearTest && board.dsi && result.activePipes) { + auto parked=Display13Boot::normalizeDSIMirror(io,result,barReady && reservationReady && xelpMemoryMatches(pci), + board,xelpMemory,dsiProfile,snapshot->normalize); + pci->setProperty("WEXDSIMirrorNormalize",&parked,sizeof(parked)); + mirrorPrepared=parked.status==Display13Boot::Status::Complete; + mirrorFailed=!mirrorPrepared; + AlderLakeN::readDisplay(io,barReady,result); + } if(displayRequested && board.dsi) - XELP::DSI::captureBoot(io,result,dsiProfile,xelpMemory,barReady && reservationReady,dsiBoot); + XELP::DSI::captureBoot(io,result,dsiProfile,xelpMemory,barReady && reservationReady && !mirrorFailed,dsiBoot,mirrorPrepared); bool bootstrapReady=!board.dsi || dsiBoot.usable(); if(!board.dsi && displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory,snapshot->normalize); @@ -4618,6 +4627,7 @@ struct Display13FBPowerContext { // Publish with IRQ state only under a quiesced adoption/retirement boundary. // No DSI binding is installed until the complete adoption backend exists. Display13Transcoder::Bindings transcoders; + uint32_t clippedDSIBootSize {}; XELP::DSI::Readout bootDSI; // Captured before handoff; not an implicit admission flag. OSObject *dsiPort {}; // Retained after source-qualified native mode binding. uint32_t dsiNativeWriters {}; // Owner-lock count; cursor bodies may wait without the FB mutex. @@ -5283,7 +5293,7 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo return Display13FBMemory::inspect(io,controller,controller->getMetaClass()->getClassSize(), fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort, c.bridge.bootPort>=3 ? &tc : nullptr,dp?&source:nullptr, - c.bootPanel,bootScanout && c.transcoders.dsi(0) ? &c.bootDSI : nullptr); + c.bootPanel,bootScanout && c.transcoders.dsi(0) ? &c.bootDSI : nullptr,c.clippedDSIBootSize); } } diff --git a/WhateverXe/kern_xelp_geometry.hpp b/WhateverXe/kern_xelp_geometry.hpp index 35a25c1f..e3cefbc5 100644 --- a/WhateverXe/kern_xelp_geometry.hpp +++ b/WhateverXe/kern_xelp_geometry.hpp @@ -15,6 +15,12 @@ inline Linear linear(uint32_t control,uint32_t stride,uint32_t size,uint32_t pos if(r.pitch @@ -350,7 +351,40 @@ static void unusedTCBoot() { assert(r.status==Display13Boot::Status::Readback && failed.writes.size()==1); puts("PASS unused fixed TC firmware ownership: cold-only release, peers, live-resource refusal and readback"); } +static void dsiMirror() { + const unsigned ports[]={0,3,4}; + XELP::DSI::Profile profile;profile.status=XELP::DSI::ProfileStatus::Valid; + profile.lanes=4;profile.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + for(unsigned count:{2U,3U})for(unsigned fault=0;fault<5;++fault) { + IO io; + auto w=multiGOP(io,ports,count,count==2 ? 8 : 24); + w.hdmi=0;w.ddc[0]=0;w.dsi=1;w.typeC=w.dp; + assert(XELPMemory::reservation(io.memory,io.memory.base+io.memory.bytes-0x100000)); + for(unsigned p=1;psecond;return true; },38400); }; - const auto inspect=[](Fixture &f,const XELP::DSI::Readout &source) { + const auto inspect=[](Fixture &f,const XELP::DSI::Readout &source,uint32_t cropped=0) { return Display13FBMemory::inspect(f,f.controller,sizeof(f.controller),f.framebuffer,sizeof(f.framebuffer), - 0x10000000,memory,true,0,0,nullptr,nullptr,false,&source); + 0x10000000,memory,true,0,0,nullptr,nullptr,false,&source,cropped); }; Fixture good;const auto source=setup(good);assert(source.complete()); auto result=inspect(good,source);assert(result.status==Status::Complete && result.verifiedPages==good.ptes.size()); + for(bool drift:{false,true}) { + Fixture crop;setup(crop);crop.registers[0x70190]=0x04370437; + crop.registers[0x6001C]=0x0437077F;crop.registers[0x68180]=crop.registers[0x68280]=0; + if(drift)crop.changeRegister=0x6001C; + assert(inspect(crop,source,0x04370437).status==(drift?Status::StateChanged:Status::Complete)); + } // The GOP has global DPT enabled, but its linear plane still addresses GGTT. // No acceptance of tiled/rotated geometry or a shortened pool follows. for(auto pair:std::map{{0x70180,0x94000408},{0x70188,67}, diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index 0cf0108d..5750ecc4 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -47,7 +47,7 @@ }; struct {IORecursiveLock *lock;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;} comboClockTransaction; struct {IOLock *lock;IOService *controller;Map *map;Display13Transcoder::Bindings transcoders; - Readout bootDSI;Display13Power::NativeBridge bridge; + uint32_t clippedDSIBootSize {};Readout bootDSI;Display13Power::NativeBridge bridge; bool engineEnabled=false,memoryFaulted=false,clockFaulted=false;struct {bool begun=false;} dbuf;} display13FBPower; struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; unsigned handlers=0,softwareHandlers=0,admittedPipes=0;bool quiescing=false;} display13FBIRQ; diff --git a/tests/dsi_surface.py b/tests/dsi_surface.py index 212cc47a..ffc4b14e 100644 --- a/tests/dsi_surface.py +++ b/tests/dsi_surface.py @@ -72,6 +72,12 @@ assert(!munmap(image,size));puts("PASS exact donor setupPlane cache feeds the DSI Y surface transaction; X rejected before MMIO"); } int main(int argc,char **argv){ + {Fixture f;f.current.size=f.io.regs[0x70190]=0x04370437; + assert(f.run().status==Surface::Status::Input); + const auto result=Surface::change(f.io,f.session,f.link,f.p,f.current,f.next,f.backing,f.snapshot,true); + assert(result.status==Surface::Status::Complete && f.io.regs[0x70190]==0x077F0437); + } + {Fixture f;const auto result=f.run();assert(result.status==Surface::Status::Complete && result.writes==17); assert(result.latch.status==Display13WM::Status::Complete && result.latch.frameBefore==100 && result.latch.frameAfter==101); assert(f.session.state==SessionState::Streaming && f.session.currentBacking==201 && f.session.retiredBacking==200 && f.session.lease==100); From ae4c04748c2f8e8702091996fb7c6fda729815a7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:48:20 +0900 Subject: [PATCH 05/46] Drive the matching MiniBook X U300 DSI panel at an optional 60 Hz --- README.md | 4 +-- README_ja.md | 4 +-- WhateverXe/kern_dsi_adapter_boot.inc | 14 ++++++++- WhateverXe/kern_dsi_adapter_diagnostics.inc | 35 ++++++++++++++++++--- WhateverXe/kern_dsi_adapter_modes.inc | 6 ++-- WhateverXe/kern_dsi_apple_timing.hpp | 7 +++++ WhateverXe/kern_xe.cpp | 1 + WhateverXe/kern_xe.hpp | 1 + tests/dsi_apple_adapter.py | 2 +- tests/dsi_apple_timing.cpp | 8 +++++ tests/dsi_boot_adapter.py | 31 ++++++++++++++---- tests/dsi_resources.py | 2 +- tests/fixtures/boot_argument_names.json | 1 + 13 files changed, 97 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fb4bb04b..325f94b8 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ These settings are unnecessary at 96MB and do not increase firmware-allocated me ### Other settings - Add `SSDT-PNLF` for internal-display brightness control. -- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. +- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. On the matching U300 panel firmware, add `-wex-dsi-60hz` for 60 Hz. Remove it and reboot for the default 50 Hz. The timing comes from the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). - GPU selection is automatic; a `device-id` override is normally unnecessary. - Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P select tiling automatically. Do not use `-yTileDisable`; `AAPL,GfxYTile` is unnecessary. @@ -95,7 +95,7 @@ The common regression checks cover Metal, OpenGL, OpenCL, hardware H.264 encode/ | Tiger Lake laptops | Big Sur–Tahoe: 48 EU, internal eDP and brightness, HDMI/USB-C, up to three displays and display off/on | | Alder Lake-N mini PCs | Big Sur–Tahoe: 16/24 EU, two 1080p60 HDMI displays and display off/on | | Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | -| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | +| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz or optional 60 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | | Alder Lake-H | Big Sur–Tahoe: 96 EU, three displays using HDMI and a DP-to-HDMI adapter. Single-channel `4626` graphics acceleration checked on Big Sur | | Alder Lake-S | Big Sur–Tahoe: UHD 770, HDMI/DP/USB-C and up to three displays; Safari video, with Widevine checked on Big Sur/Tahoe | diff --git a/README_ja.md b/README_ja.md index 57648473..e6f5b744 100644 --- a/README_ja.md +++ b/README_ja.md @@ -81,7 +81,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします ### その他の設定 - 内蔵画面の輝度調整には`SSDT-PNLF`を追加します。 -- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。 +- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。検証済みのU300とパネルのファームウェアテーブルが一致する場合、`-wex-dsi-60hz` を追加すると60 Hzになります。この引数を削除して再起動すると標準の50 Hzに戻ります。タイミングの出典は [minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md) です。 - GPUは自動選択するため、通常は`device-id`上書きは不要です。 - Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pではタイル形式を自動選択します。`-yTileDisable`は使わないでください。`AAPL,GfxYTile`は不要です。 @@ -95,7 +95,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします | Tiger Lakeラップトップ | Big Sur〜Tahoe:48 EU、内蔵eDPと輝度調整、HDMI/USB-C、最大3画面・消灯復帰 | | Alder Lake-NミニPC | Big Sur〜Tahoe:16/24 EU、1080p60のHDMI 2画面・消灯復帰 | | Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | -| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・50 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | +| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・標準50 Hz/任意設定で60 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | | Alder Lake-H | Big Sur〜Tahoe:96 EU、HDMIとDP→HDMI変換器による3画面。シングルチャネル構成の`4626`でもBig Surの加速を確認 | | Alder Lake-S | Big Sur〜Tahoe:UHD 770、HDMI/DP/USB-C、最大3画面、Safari動画。WidevineはBig Sur/Tahoeで確認 | diff --git a/WhateverXe/kern_dsi_adapter_boot.inc b/WhateverXe/kern_dsi_adapter_boot.inc index c31806b1..fdea5c71 100644 --- a/WhateverXe/kern_dsi_adapter_boot.inc +++ b/WhateverXe/kern_dsi_adapter_boot.inc @@ -10,6 +10,18 @@ bool XE::prepareDSIFirmware(IOService *provider) { if(!copy)return false; auto parsed=XELP::DSI::parseProfile({static_cast(copy->getBytesNoCopy()),copy->getLength()},true); if(!parsed.valid()){copy->release();return false;} + // minibookx-ctl qualifies one U300 VBT. TGLVBT contains the declared + // table only; Linux debugfs also includes trailing allocation padding. + if(checkKernelArgument("-wex-dsi-60hz") && copy->getLength()==10269 && + parsed.mode.clockKHz==138267 && parsed.mode.htotal()==1272 && parsed.mode.vtotal()==2174) { + static const uint8_t known[]={ + 0xf3,0x03,0x0f,0xec,0x11,0x17,0x81,0x7b, + 0xf0,0x4d,0xf2,0xfa,0x32,0xa0,0x3b,0xfa, + 0xbd,0x8d,0x8a,0x9b,0x78,0xdf,0x6c,0x44, + 0x30,0x8c,0x79,0x06,0x1c,0x1b,0x06,0xa9}; + auto digest=Crypto::hash(static_cast(copy->getBytesNoCopy()),copy->getLength()); + if(digest){dsi60Hz=!memcmp(digest,known,sizeof(known));Buffer::deleter(digest);} + } dsiProfile=parsed;dsiFirmware=copy;return true; } @@ -301,7 +313,7 @@ bool XE::bindDSIResources(IOService *controller) { IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(ready)dsiPublishIRQLive(); if(ready && dsiCommandTest)ready=probeDSICommands(controller); - if(ready && (dsiPanelTest || prepareExternalClock))ready=probeDSIPanel(controller,prepareExternalClock); + if(ready && (dsiPanelTest || prepareExternalClock || dsi60Hz))ready=probeDSIPanel(controller,prepareExternalClock); if(ready) { IOLockLock(c.lock); using Light=XELP::DSI::Backlight; diff --git a/WhateverXe/kern_dsi_adapter_diagnostics.inc b/WhateverXe/kern_dsi_adapter_diagnostics.inc index 80591289..216932eb 100644 --- a/WhateverXe/kern_dsi_adapter_diagnostics.inc +++ b/WhateverXe/kern_dsi_adapter_diagnostics.inc @@ -69,7 +69,7 @@ bool XE::probeDSICommands(IOService *controller) { // the optional host trial rebuilds them before panel INIT_OTP. Neither path // releases shared resources or enables runtime power transitions. bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { - const bool restart=dsiHostTest || prepareExternalClock; + const bool restart=dsiHostTest || prepareExternalClock || dsi60Hz; auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!controller || !c.lock || !clock.lock || !irq.lock)return false; IORecursiveLockLock(clock.lock);IOLockLock(c.lock); @@ -113,7 +113,13 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { void delayUs(unsigned us){IODelay(us);} } io{c.map,owned,irq.lock,restart,dsiBoot.source.identity.pll}; Display13Clock::Change bootCDClock; + // Adopt and stop the firmware's original link first. Only the restart + // uses the new clock; panel commands and blanking stay firmware-derived. + auto target=dsiProfile; + if(dsi60Hz)target.mode.clockKHz=165920; + const auto targetPlan=XELP::DSI::Restart::plan(target,dsiBoot.source.referenceKHz); struct Lifecycle { + const XELP::DSI::Restart::Plan &targetPlan; bool enabled,prepareExternal;XELPComboClock::ClockAllocation &allocation;uintptr_t owner; XELP::DSI::Restart::State &state;Display13Clock::Change &cdclk; bool stop(IO &io,const XELP::DSI::Readout &source,const XELP::DSI::Profile &profile) { @@ -142,12 +148,13 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { *reinterpret_cast(reinterpret_cast(controller)+0xE98)=cdclk.after.nativeHz(); *reinterpret_cast(reinterpret_cast(controller)+0xEA0)=cdclk.after.nativeHz(); } + state.config=targetPlan; if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; source=XELP::DSI::Restart::expected(source,state);return source.complete(); } - } lifecycle{restart,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; + } lifecycle{targetPlan,restart,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; uint64_t record[32] {1}; - record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && + record[1]=(!restart || targetPlan.valid) && io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && !dsiCommandHost.faulted && !dsiCommandHost.pending && c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); if(record[1]) { @@ -165,7 +172,8 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { auto nextBoot=dsiBoot; if(dsiPanelTrial.complete())nextBoot.source=dsiPanelTrial.source; record[30]=io.ready() && dsiPanelTrial.complete() && XELP::DSI::bootUnchanged(io,nextBoot,xelpMemory); - if(record[30]){dsiBoot=nextBoot;c.bootDSI=nextBoot.source;} + if(record[30] && !nextBoot.source.matches(target))record[30]=0; + if(record[30]){dsiBoot=nextBoot;c.bootDSI=nextBoot.source;dsiProfile.mode.clockKHz=target.mode.clockKHz;} } const auto &r=dsiPanelTrial;const auto &h=r.host; record[2]=static_cast(r.stage);record[3]=static_cast(r.error); @@ -180,7 +188,26 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { host.reg,host.value,host.writes,host.waitUs,host.stopped,host.started,host.pll, p.pll.cfg0(),p.pll.cfg1(),p.link.clockTiming,p.link.dataTiming,p.hsTimeout,p.lpTimeout,p.taTimeout, record[30],complete}; + uint64_t scan[6] {}; + if(dsi60Hz && complete && checkKernelArgument("-wex-dsi-audit")) { + // Count physical pipe-A frame boundaries, even before WindowServer starts. + scan[0]=1;scan[1]=dsiBoot.source.afeKHz;scan[2]=dsiProfile.mode.clockKHz; + uint32_t first=io.read(0x70040),edge=first;uint64_t start=0; + for(unsigned us=0;us<140000 && io.ready();us+=100) { + const auto frame=io.read(0x70040); + if(frame==UINT32_MAX || first==UINT32_MAX)break; + if(!start && frame!=first){start=mach_absolute_time();edge=frame;} + if(start && uint32_t(frame-edge)>=5) { + scan[3]=uint32_t(frame-edge); + absolutetime_to_nanoseconds(mach_absolute_time()-start,&scan[4]); + scan[5]=XELP::DSI::sourceUnchanged(io,dsiBoot.source); + break; + } + io.delayUs(100); + } + } IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(scan[0])IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIScanTiming",scan,sizeof(scan)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); if(restart)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); if(restart && !dsiPanelOnly) diff --git a/WhateverXe/kern_dsi_adapter_modes.inc b/WhateverXe/kern_dsi_adapter_modes.inc index 6d600ab7..6dab5687 100644 --- a/WhateverXe/kern_dsi_adapter_modes.inc +++ b/WhateverXe/kern_dsi_adapter_modes.inc @@ -256,7 +256,8 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { if(array)for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; - const auto &t=*static_cast(bytes->getBytesNoCopy()); + const auto t=XELP::DSI::AppleAdapter::panelOffer( + *static_cast(bytes->getBytesNoCopy()),callbackXE->dsi60Hz); const bool native=XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i); if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { // Keep the last rejected OS request across later successful @@ -285,7 +286,8 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; - const auto &t=*static_cast(bytes->getBytesNoCopy()); + const auto t=XELP::DSI::AppleAdapter::panelOffer( + *static_cast(bytes->getBytesNoCopy()),callbackXE->dsi60Hz); if(XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i) || XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { if(!replacement->appendBytes(&t,sizeof(t))) { diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 55c23b0e..be6db472 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -40,6 +40,13 @@ inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false, else info.flags&=~0x02000004U; // A scaled source is not the native/default mode. return true; } +// At the U300 60 Hz clock CoreDisplay offers both one- and two-link +// descriptors for the same EDID. Its link count is presentation metadata; +// this panel has one physical DSI link. Keep every other field for validation. +inline Timing panelOffer(Timing t,bool u300At60Hz) { + if(u300At60Hz && t.numLinks==2 && t.pixelClock==165920000)t.numLinks=1; + return t; +} // CoreDisplay can replace the reserved client IDs when rebuilding its mode // list. Treat reserved IDs as opaque: the complete // timing, not an assumed generation/index bit layout, qualifies this alias. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index be09c440..c4a50232 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -18,6 +18,7 @@ #include "kern_xelp_call_target.hpp" #include "kern_xelp_native_placement.hpp" #include +#include #include "kern_alderlake_n.hpp" #include "kern_xelp_device.hpp" #include "kern_xelp_memory_layout.hpp" diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index b35a3d45..a81abf5d 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -145,6 +145,7 @@ class XE { bool display13BootReady {false}; OSData *dsiFirmware {}; // Private immutable copy backing profile/sequence spans. XELP::DSI::Profile dsiProfile; + bool dsi60Hz {}; XELP::DSI::Boot dsiBoot; XELP::DSI::GGTTMode dsiGGTT; bool prepareDSIFirmware(IOService *); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 4b7566d1..a442634d 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -88,7 +88,7 @@ struct {IOLock *lock;void *controller;Display13Transcoder::Bindings transcoders;XELP::DSI::Readout bootDSI; bool memoryFaulted=false;struct {struct {bool faulted=false;} state;} bridge;} display13FBPower; struct XE { - bool dsiPanelOnly=false;bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; + bool dsiPanelOnly=false,dsi60Hz=false;bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[9]{}; static bool updateReady; static bool dsiNativeUpdateAllowed(IOService *){return updateReady;} diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 0931d659..16d3c8cd 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -14,6 +14,14 @@ int main() { auto offered=t;offered.reservedA[0]=0x80001000;offered.signalLevels=1; offered.pixelClock=offered.minPixelClock=offered.maxPixelClock=135260000; assert(AppleAdapter::clientTiming(t,offered,0)); + { + auto fixed=t;fixed.pixelClock=fixed.minPixelClock=fixed.maxPixelClock=165920000; + auto os=fixed;os.numLinks=2;os.reservedA[0]=0x8000300E; + assert(!AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,false),0)); + assert(AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,true),0)); + os.horizontalBlanking++; + assert(!AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,true),0)); + } for(unsigned offset=4;offset(&corrupt)[offset]^=0x80; assert(!AppleAdapter::clientTiming(t,corrupt,0)); diff --git a/tests/dsi_boot_adapter.py b/tests/dsi_boot_adapter.py index 1d511168..93a03dab 100644 --- a/tests/dsi_boot_adapter.py +++ b/tests/dsi_boot_adapter.py @@ -2,7 +2,7 @@ """Execute production firmware-copy and DSI pre-native handoff with mock IOKit.""" from pathlib import Path from production_source import read_source -import re, subprocess, tempfile +import re, subprocess, sys, tempfile root=Path(__file__).resolve().parents[1] source=read_source(root/'WhateverXe/kern_xe.cpp') # Observation must precede the destructive external-output trial and must @@ -23,11 +23,17 @@ #include #include #include +#include +#include using namespace XELP::DSI; FIXTURE bool copyOK=true,deviceOK=true,memoryOK=true,auditEnabled=false;unsigned alive=0,releases=0,audits=0,allocations=0; BootAudit lastAudit; -bool checkKernelArgument(const char *key){assert(!strcmp(key,"-wex-dsi-audit"));return auditEnabled;} +bool request60=false; +namespace Crypto {uint8_t *hash(const uint8_t *p,unsigned n){ + auto out=static_cast(malloc(CC_SHA256_DIGEST_LENGTH));return CC_SHA256(p,n,out);}} +namespace Buffer {void deleter(uint8_t *p){free(p);}} +bool checkKernelArgument(const char *key){if(!strcmp(key,"-wex-dsi-60hz"))return request60;assert(!strcmp(key,"-wex-dsi-audit"));return auditEnabled;} void *IOMalloc(size_t bytes){++allocations;return calloc(1,bytes);} void IOFree(void *p,size_t){--allocations;free(p);} struct OSObject {OSObject(){++alive;}virtual ~OSObject(){--alive;}void release(){delete this;}}; @@ -56,7 +62,7 @@ #define SYSLOG(...) #define OSDynamicCast(T,p) dynamic_cast(p) struct XE { - OSData *dsiFirmware=nullptr;Profile dsiProfile;Boot dsiBoot;bool display13BootReady=true; + OSData *dsiFirmware=nullptr;Profile dsiProfile;Boot dsiBoot;bool display13BootReady=true,dsi60Hz=false; XELPMemory::Layout xelpMemory;Display13OutputWiring adlnOutputWiring; bool xelpDisplay13Device(IOPCIDevice *){return deviceOK;} bool xelpMemoryMatches(IOPCIDevice *){return memoryOK;} @@ -72,11 +78,24 @@ uint64_t readPTE(uint32_t r){uint64_t v;memcpy(&v,map.bytes.data()+r,8);return v;} void word(uint32_t r,uint32_t v){memcpy(map.bytes.data()+r,&v,4);} }; -int main(){ +int main(int argc,char **argv){ + if(argc==2){ + std::ifstream in(argv[1],std::ios::binary);Bytes vbt{std::istreambuf_iterator(in),{}}; + assert(vbt.size()>=10269);vbt.resize(10269); + for(unsigned scenario=0;scenario<3;++scenario){ + XE x;IOService provider;request60=scenario!=0; + if(scenario==2){vbt[18]^=1;vbt[26]+=vbt[18]&1?-1:1;} // same panel timing, different table + provider.property=OSData::withBytes(vbt.data(),vbt.size()); + assert(x.prepareDSIFirmware(&provider) && x.dsi60Hz==(scenario==1)); + assert(x.dsiProfile.mode.clockKHz==138267); // firmware handoff remains 50 Hz + x.dsiFirmware->release();provider.property->release(); + } + } + request60=true; XE xe;IOService provider;assert(!xe.prepareDSIFirmware(&provider)); auto bytes=fixture();provider.property=OSData::withBytes(bytes.data(),bytes.size()); copyOK=false;assert(!xe.prepareDSIFirmware(&provider) && !xe.dsiFirmware && alive==1);copyOK=true; - assert(xe.prepareDSIFirmware(&provider) && xe.dsiProfile.valid() && alive==2); + assert(xe.prepareDSIFirmware(&provider) && xe.dsiProfile.valid() && !xe.dsi60Hz && alive==2); assert(xe.dsiFirmware!=provider.property && xe.prepareDSIFirmware(&provider) && alive==2); auto start=static_cast(xe.dsiFirmware->getBytesNoCopy()); for(auto span:xe.dsiProfile.sequences.sequence)if(span.data)assert(span.data>=start && span.data+span.size<=start+bytes.size()); @@ -125,4 +144,4 @@ with tempfile.TemporaryDirectory(prefix='dsi-boot-adapter-') as directory: p=Path(directory);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) - subprocess.run([str(p/'test')],check=True) + subprocess.run([str(p/'test'),*sys.argv[1:]],check=True) diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index 5750ecc4..2f74207b 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -55,7 +55,7 @@ unsigned published=0; void dsiPublishIRQLive(){assert(lockLevel==0 && display13FBIRQ.controller && display13FBIRQ.lock);++published;} struct XE { - bool dsiPanelTest=false,dsiPanelOnly=false,cycleOK=true;unsigned cycles=0;bool externalPreparation=false; + bool dsi60Hz=false,dsiPanelTest=false,dsiPanelOnly=false,cycleOK=true;unsigned cycles=0;bool externalPreparation=false; bool probeDSIPanel(IOService*,bool prepare){assert(lockLevel==0);++cycles;externalPreparation=prepare;return cycleOK;} bool dsiCommandTest=false;bool probeDSICommands(IOService*){assert(lockLevel==0);return true;} bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index 9bef982c..f6a6c31a 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -1,5 +1,6 @@ { "-wex-dsi-audit": [], + "-wex-dsi-60hz": [], "-wex-dsi-adopt": [], "-wex-dsi-panel-only": [], "-wex-dsi-command-test": [], From b49eb89a76678ae0176871856e15fe3084507440 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:51:37 +0900 Subject: [PATCH 06/46] Use the bound DSI link count for CoreDisplay presentation modes --- WhateverXe/kern_dsi_adapter_modes.inc | 4 ++-- WhateverXe/kern_dsi_apple_timing.hpp | 9 ++++----- tests/dsi_apple_adapter.py | 4 +++- tests/dsi_apple_timing.cpp | 5 ++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_modes.inc b/WhateverXe/kern_dsi_adapter_modes.inc index 6dab5687..24e34b31 100644 --- a/WhateverXe/kern_dsi_adapter_modes.inc +++ b/WhateverXe/kern_dsi_adapter_modes.inc @@ -257,7 +257,7 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto t=XELP::DSI::AppleAdapter::panelOffer( - *static_cast(bytes->getBytesNoCopy()),callbackXE->dsi60Hz); + *static_cast(bytes->getBytesNoCopy()),dsiFixedMode.timing); const bool native=XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i); if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { // Keep the last rejected OS request across later successful @@ -287,7 +287,7 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto t=XELP::DSI::AppleAdapter::panelOffer( - *static_cast(bytes->getBytesNoCopy()),callbackXE->dsi60Hz); + *static_cast(bytes->getBytesNoCopy()),dsiFixedMode.timing); if(XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i) || XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { if(!replacement->appendBytes(&t,sizeof(t))) { diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index be6db472..75891fe5 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -40,11 +40,10 @@ inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false, else info.flags&=~0x02000004U; // A scaled source is not the native/default mode. return true; } -// At the U300 60 Hz clock CoreDisplay offers both one- and two-link -// descriptors for the same EDID. Its link count is presentation metadata; -// this panel has one physical DSI link. Keep every other field for validation. -inline Timing panelOffer(Timing t,bool u300At60Hz) { - if(u300At60Hz && t.numLinks==2 && t.pixelClock==165920000)t.numLinks=1; +// A presentation mode reuses the bound DSI transport. CoreDisplay's +// numLinks hint cannot select a different physical link in this path. +inline Timing panelOffer(Timing t,const Timing &fixed) { + t.numLinks=fixed.numLinks; return t; } // CoreDisplay can replace the reserved client IDs when rebuilding its mode diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index a442634d..c8d82a6c 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -88,7 +88,7 @@ struct {IOLock *lock;void *controller;Display13Transcoder::Bindings transcoders;XELP::DSI::Readout bootDSI; bool memoryFaulted=false;struct {struct {bool faulted=false;} state;} bridge;} display13FBPower; struct XE { - bool dsiPanelOnly=false,dsi60Hz=false;bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; + bool dsiPanelOnly=false;bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[9]{}; static bool updateReady; static bool dsiNativeUpdateAllowed(IOService *){return updateReady;} @@ -208,6 +208,7 @@ data->release();replacement->release(); auto offered=dsiFixedMode.timing;offered.reservedA[0]=0x80001001;offered.signalLevels=1; offered.pixelClock=offered.minPixelClock=offered.maxPixelClock=135260000; + offered.numLinks=2; // CoreDisplay hint on a verified single-link panel auto list=OSArray::withCapacity(1);auto wrong=offered;wrong.horizontalActive=640; auto wrongData=OSData::withBytes(&wrong,sizeof(wrong));auto offeredData=OSData::withBytes(&offered,sizeof(offered)); list->setObject(wrongData);list->setObject(offeredData); @@ -237,6 +238,7 @@ assert(XE::wrapDSIGetModeCount(fb)==2);int32_t pair[2];assert(!XE::wrapDSIGetModes(fb,pair)); assert(static_cast(pair[1])==rotated.reservedA[0]); assert(!XE::wrapDSIValidateMode(fb,pair[1],nullptr,&timing)); + assert(static_cast(timing)->numLinks==1 && rotated.numLinks==2); assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1920 && info.height==1080); // A large OS list with many distinct scaled dimensions must not be diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 16d3c8cd..dcc08646 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -17,10 +17,9 @@ int main() { { auto fixed=t;fixed.pixelClock=fixed.minPixelClock=fixed.maxPixelClock=165920000; auto os=fixed;os.numLinks=2;os.reservedA[0]=0x8000300E; - assert(!AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,false),0)); - assert(AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,true),0)); + assert(AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,fixed),0)); os.horizontalBlanking++; - assert(!AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,true),0)); + assert(!AppleAdapter::clientTiming(fixed,AppleAdapter::panelOffer(os,fixed),0)); } for(unsigned offset=4;offset(&corrupt)[offset]^=0x80; From 7ca22f9393d2467801c7354de512e39d1dfc5269 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:59:37 +0900 Subject: [PATCH 07/46] Derive the optional 60 Hz DSI clock for MiniBook X N100 and U300 --- README.md | 4 ++-- README_ja.md | 4 ++-- WhateverXe/kern_dsi_adapter_boot.inc | 25 +++++++++++++++------ WhateverXe/kern_dsi_adapter_diagnostics.inc | 6 ++--- WhateverXe/kern_xe.hpp | 2 +- tests/dsi_boot_adapter.py | 24 ++++++++++++++------ tests/dsi_resources.py | 2 +- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 325f94b8..147b75e9 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ These settings are unnecessary at 96MB and do not increase firmware-allocated me ### Other settings - Add `SSDT-PNLF` for internal-display brightness control. -- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. On the matching U300 panel firmware, add `-wex-dsi-60hz` for 60 Hz. Remove it and reboot for the default 50 Hz. The timing comes from the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). +- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. On matching MiniBook X U300 and N100 panel firmware, `-wex-dsi-60hz` requests a fixed 60 Hz mode at boot. Both panels were tested under Big Sur using their respective firmware blanking intervals. Remove the argument and reboot for the default 50 Hz. The U300 timing comes from the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). - GPU selection is automatic; a `device-id` override is normally unnecessary. - Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P select tiling automatically. Do not use `-yTileDisable`; `AAPL,GfxYTile` is unnecessary. @@ -94,7 +94,7 @@ The common regression checks cover Metal, OpenGL, OpenCL, hardware H.264 encode/ | Tiger Lake mini PCs | Big Sur–Tahoe: 80 EU, two 1080p60 displays, display off/on, Safari video and Widevine | | Tiger Lake laptops | Big Sur–Tahoe: 48 EU, internal eDP and brightness, HDMI/USB-C, up to three displays and display off/on | | Alder Lake-N mini PCs | Big Sur–Tahoe: 16/24 EU, two 1080p60 HDMI displays and display off/on | -| Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | +| Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MiniBook X N100 also supports the optional 60 Hz mode. MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | | Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz or optional 60 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | | Alder Lake-H | Big Sur–Tahoe: 96 EU, three displays using HDMI and a DP-to-HDMI adapter. Single-channel `4626` graphics acceleration checked on Big Sur | | Alder Lake-S | Big Sur–Tahoe: UHD 770, HDMI/DP/USB-C and up to three displays; Safari video, with Widevine checked on Big Sur/Tahoe | diff --git a/README_ja.md b/README_ja.md index e6f5b744..ab079061 100644 --- a/README_ja.md +++ b/README_ja.md @@ -81,7 +81,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします ### その他の設定 - 内蔵画面の輝度調整には`SSDT-PNLF`を追加します。 -- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。検証済みのU300とパネルのファームウェアテーブルが一致する場合、`-wex-dsi-60hz` を追加すると60 Hzになります。この引数を削除して再起動すると標準の50 Hzに戻ります。タイミングの出典は [minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md) です。 +- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。対象のMiniBook X U300またはN100のパネルテーブルと一致すると、`-wex-dsi-60hz` で起動時に固定60 Hzを要求します。各パネルのファームウェアが指定するブランキング期間を使い、両機種ともBig Surで実機確認済みです。この引数を削除して再起動すると標準の50 Hzに戻ります。U300のタイミングの出典は [minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md) です。 - GPUは自動選択するため、通常は`device-id`上書きは不要です。 - Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pではタイル形式を自動選択します。`-yTileDisable`は使わないでください。`AAPL,GfxYTile`は不要です。 @@ -94,7 +94,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします | Tiger LakeミニPC | Big Sur〜Tahoe:80 EU、1080p60の2画面、消灯復帰、Safari動画・Widevine | | Tiger Lakeラップトップ | Big Sur〜Tahoe:48 EU、内蔵eDPと輝度調整、HDMI/USB-C、最大3画面・消灯復帰 | | Alder Lake-NミニPC | Big Sur〜Tahoe:16/24 EU、1080p60のHDMI 2画面・消灯復帰 | -| Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | +| Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MiniBook X N100は任意設定の60 Hz、MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | | Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・標準50 Hz/任意設定で60 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | | Alder Lake-H | Big Sur〜Tahoe:96 EU、HDMIとDP→HDMI変換器による3画面。シングルチャネル構成の`4626`でもBig Surの加速を確認 | | Alder Lake-S | Big Sur〜Tahoe:UHD 770、HDMI/DP/USB-C、最大3画面、Safari動画。WidevineはBig Sur/Tahoeで確認 | diff --git a/WhateverXe/kern_dsi_adapter_boot.inc b/WhateverXe/kern_dsi_adapter_boot.inc index fdea5c71..ab31d0cf 100644 --- a/WhateverXe/kern_dsi_adapter_boot.inc +++ b/WhateverXe/kern_dsi_adapter_boot.inc @@ -10,17 +10,28 @@ bool XE::prepareDSIFirmware(IOService *provider) { if(!copy)return false; auto parsed=XELP::DSI::parseProfile({static_cast(copy->getBytesNoCopy()),copy->getLength()},true); if(!parsed.valid()){copy->release();return false;} - // minibookx-ctl qualifies one U300 VBT. TGLVBT contains the declared - // table only; Linux debugfs also includes trailing allocation padding. - if(checkKernelArgument("-wex-dsi-60hz") && copy->getLength()==10269 && - parsed.mode.clockKHz==138267 && parsed.mode.htotal()==1272 && parsed.mode.vtotal()==2174) { - static const uint8_t known[]={ + // Explicit override for the captured MiniBook X panel tables. Hash the + // declared VBT supplied by the provider, without Linux debugfs padding. + if(checkKernelArgument("-wex-dsi-60hz")) { + static const uint8_t u300[]={ 0xf3,0x03,0x0f,0xec,0x11,0x17,0x81,0x7b, 0xf0,0x4d,0xf2,0xfa,0x32,0xa0,0x3b,0xfa, 0xbd,0x8d,0x8a,0x9b,0x78,0xdf,0x6c,0x44, 0x30,0x8c,0x79,0x06,0x1c,0x1b,0x06,0xa9}; + static const uint8_t n100[]={ + 0xe0,0x3d,0x64,0x90,0x5f,0x8d,0x0a,0xc1, + 0x6c,0x7c,0x80,0x49,0xfc,0x45,0x4a,0x93, + 0x27,0xb2,0xbd,0xe3,0x6d,0xa2,0x51,0x81, + 0x3c,0xbd,0x9d,0x78,0x45,0x1d,0x35,0x40}; auto digest=Crypto::hash(static_cast(copy->getBytesNoCopy()),copy->getLength()); - if(digest){dsi60Hz=!memcmp(digest,known,sizeof(known));Buffer::deleter(digest);} + if(digest) { + if(!memcmp(digest,u300,sizeof(u300)) || !memcmp(digest,n100,sizeof(n100))) { + // Keep the firmware blanking and use the 10 kHz EDID clock unit. + const auto hz=uint64_t(parsed.mode.htotal())*parsed.mode.vtotal()*60; + dsi60HzClockKHz=static_cast((hz+5000)/10000*10); + } + Buffer::deleter(digest); + } } dsiProfile=parsed;dsiFirmware=copy;return true; } @@ -313,7 +324,7 @@ bool XE::bindDSIResources(IOService *controller) { IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(ready)dsiPublishIRQLive(); if(ready && dsiCommandTest)ready=probeDSICommands(controller); - if(ready && (dsiPanelTest || prepareExternalClock || dsi60Hz))ready=probeDSIPanel(controller,prepareExternalClock); + if(ready && (dsiPanelTest || prepareExternalClock || dsi60HzClockKHz))ready=probeDSIPanel(controller,prepareExternalClock); if(ready) { IOLockLock(c.lock); using Light=XELP::DSI::Backlight; diff --git a/WhateverXe/kern_dsi_adapter_diagnostics.inc b/WhateverXe/kern_dsi_adapter_diagnostics.inc index 216932eb..af8910ef 100644 --- a/WhateverXe/kern_dsi_adapter_diagnostics.inc +++ b/WhateverXe/kern_dsi_adapter_diagnostics.inc @@ -69,7 +69,7 @@ bool XE::probeDSICommands(IOService *controller) { // the optional host trial rebuilds them before panel INIT_OTP. Neither path // releases shared resources or enables runtime power transitions. bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { - const bool restart=dsiHostTest || prepareExternalClock || dsi60Hz; + const bool restart=dsiHostTest || prepareExternalClock || dsi60HzClockKHz; auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!controller || !c.lock || !clock.lock || !irq.lock)return false; IORecursiveLockLock(clock.lock);IOLockLock(c.lock); @@ -116,7 +116,7 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { // Adopt and stop the firmware's original link first. Only the restart // uses the new clock; panel commands and blanking stay firmware-derived. auto target=dsiProfile; - if(dsi60Hz)target.mode.clockKHz=165920; + if(dsi60HzClockKHz)target.mode.clockKHz=dsi60HzClockKHz; const auto targetPlan=XELP::DSI::Restart::plan(target,dsiBoot.source.referenceKHz); struct Lifecycle { const XELP::DSI::Restart::Plan &targetPlan; @@ -189,7 +189,7 @@ bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { p.pll.cfg0(),p.pll.cfg1(),p.link.clockTiming,p.link.dataTiming,p.hsTimeout,p.lpTimeout,p.taTimeout, record[30],complete}; uint64_t scan[6] {}; - if(dsi60Hz && complete && checkKernelArgument("-wex-dsi-audit")) { + if(dsi60HzClockKHz && complete && checkKernelArgument("-wex-dsi-audit")) { // Count physical pipe-A frame boundaries, even before WindowServer starts. scan[0]=1;scan[1]=dsiBoot.source.afeKHz;scan[2]=dsiProfile.mode.clockKHz; uint32_t first=io.read(0x70040),edge=first;uint64_t start=0; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index a81abf5d..0761692f 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -145,7 +145,7 @@ class XE { bool display13BootReady {false}; OSData *dsiFirmware {}; // Private immutable copy backing profile/sequence spans. XELP::DSI::Profile dsiProfile; - bool dsi60Hz {}; + uint32_t dsi60HzClockKHz {}; XELP::DSI::Boot dsiBoot; XELP::DSI::GGTTMode dsiGGTT; bool prepareDSIFirmware(IOService *); diff --git a/tests/dsi_boot_adapter.py b/tests/dsi_boot_adapter.py index 93a03dab..6b2b3a24 100644 --- a/tests/dsi_boot_adapter.py +++ b/tests/dsi_boot_adapter.py @@ -25,6 +25,7 @@ #include #include #include +#include "kern_dsi_restart.hpp" using namespace XELP::DSI; FIXTURE bool copyOK=true,deviceOK=true,memoryOK=true,auditEnabled=false;unsigned alive=0,releases=0,audits=0,allocations=0; @@ -62,7 +63,7 @@ #define SYSLOG(...) #define OSDynamicCast(T,p) dynamic_cast(p) struct XE { - OSData *dsiFirmware=nullptr;Profile dsiProfile;Boot dsiBoot;bool display13BootReady=true,dsi60Hz=false; + OSData *dsiFirmware=nullptr;Profile dsiProfile;Boot dsiBoot;bool display13BootReady=true;uint32_t dsi60HzClockKHz=0; XELPMemory::Layout xelpMemory;Display13OutputWiring adlnOutputWiring; bool xelpDisplay13Device(IOPCIDevice *){return deviceOK;} bool xelpMemoryMatches(IOPCIDevice *){return memoryOK;} @@ -79,15 +80,24 @@ void word(uint32_t r,uint32_t v){memcpy(map.bytes.data()+r,&v,4);} }; int main(int argc,char **argv){ - if(argc==2){ - std::ifstream in(argv[1],std::ios::binary);Bytes vbt{std::istreambuf_iterator(in),{}}; - assert(vbt.size()>=10269);vbt.resize(10269); + for(int i=1;i(in),{}}; + assert(vbt.size()>=28); + const unsigned declared=profileU16(vbt.data()+24);assert(declared<=vbt.size());vbt.resize(declared); + const bool n100=declared==11621;const unsigned clock=n100?164260:165920; + assert(n100 || declared==10269); for(unsigned scenario=0;scenario<3;++scenario){ XE x;IOService provider;request60=scenario!=0; if(scenario==2){vbt[18]^=1;vbt[26]+=vbt[18]&1?-1:1;} // same panel timing, different table provider.property=OSData::withBytes(vbt.data(),vbt.size()); - assert(x.prepareDSIFirmware(&provider) && x.dsi60Hz==(scenario==1)); - assert(x.dsiProfile.mode.clockKHz==138267); // firmware handoff remains 50 Hz + assert(x.prepareDSIFirmware(&provider) && x.dsi60HzClockKHz==(scenario==1?clock:0)); + assert(x.dsiProfile.mode.clockKHz==(n100?136887:138267)); // firmware handoff remains 50 Hz + if(x.dsi60HzClockKHz){ + auto target=x.dsiProfile;target.mode.clockKHz=x.dsi60HzClockKHz; + const auto plan=Restart::plan(target,38400); + assert(plan.valid && plan.link.afeKHz==clock*6); + assert(target.mode.htotal()==(n100?1268:1272) && target.mode.vtotal()==(n100?2159:2174)); + } x.dsiFirmware->release();provider.property->release(); } } @@ -95,7 +105,7 @@ XE xe;IOService provider;assert(!xe.prepareDSIFirmware(&provider)); auto bytes=fixture();provider.property=OSData::withBytes(bytes.data(),bytes.size()); copyOK=false;assert(!xe.prepareDSIFirmware(&provider) && !xe.dsiFirmware && alive==1);copyOK=true; - assert(xe.prepareDSIFirmware(&provider) && xe.dsiProfile.valid() && !xe.dsi60Hz && alive==2); + assert(xe.prepareDSIFirmware(&provider) && xe.dsiProfile.valid() && !xe.dsi60HzClockKHz && alive==2); assert(xe.dsiFirmware!=provider.property && xe.prepareDSIFirmware(&provider) && alive==2); auto start=static_cast(xe.dsiFirmware->getBytesNoCopy()); for(auto span:xe.dsiProfile.sequences.sequence)if(span.data)assert(span.data>=start && span.data+span.size<=start+bytes.size()); diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index 2f74207b..68c411d7 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -55,7 +55,7 @@ unsigned published=0; void dsiPublishIRQLive(){assert(lockLevel==0 && display13FBIRQ.controller && display13FBIRQ.lock);++published;} struct XE { - bool dsi60Hz=false,dsiPanelTest=false,dsiPanelOnly=false,cycleOK=true;unsigned cycles=0;bool externalPreparation=false; + uint32_t dsi60HzClockKHz=0;bool dsiPanelTest=false,dsiPanelOnly=false,cycleOK=true;unsigned cycles=0;bool externalPreparation=false; bool probeDSIPanel(IOService*,bool prepare){assert(lockLevel==0);++cycles;externalPreparation=prepare;return cycleOK;} bool dsiCommandTest=false;bool probeDSICommands(IOService*){assert(lockLevel==0);return true;} bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; From c403a9262fb3ed014d458028d9219dc74af72f07 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 14:03:01 +0900 Subject: [PATCH 08/46] Document MiniBook X N100 60 Hz validation on Tahoe --- README.md | 2 +- README_ja.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 147b75e9..f75efe20 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ These settings are unnecessary at 96MB and do not increase firmware-allocated me ### Other settings - Add `SSDT-PNLF` for internal-display brightness control. -- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. On matching MiniBook X U300 and N100 panel firmware, `-wex-dsi-60hz` requests a fixed 60 Hz mode at boot. Both panels were tested under Big Sur using their respective firmware blanking intervals. Remove the argument and reboot for the default 50 Hz. The U300 timing comes from the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). +- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. On matching MiniBook X U300 and N100 panel firmware, `-wex-dsi-60hz` requests a fixed 60 Hz mode at boot. Both panels were tested under Big Sur using their respective firmware blanking intervals. The N100 was also tested under Tahoe. Remove the argument and reboot for the default 50 Hz. The U300 timing comes from the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). - GPU selection is automatic; a `device-id` override is normally unnecessary. - Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P select tiling automatically. Do not use `-yTileDisable`; `AAPL,GfxYTile` is unnecessary. diff --git a/README_ja.md b/README_ja.md index ab079061..2f062ee8 100644 --- a/README_ja.md +++ b/README_ja.md @@ -81,7 +81,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします ### その他の設定 - 内蔵画面の輝度調整には`SSDT-PNLF`を追加します。 -- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。対象のMiniBook X U300またはN100のパネルテーブルと一致すると、`-wex-dsi-60hz` で起動時に固定60 Hzを要求します。各パネルのファームウェアが指定するブランキング期間を使い、両機種ともBig Surで実機確認済みです。この引数を削除して再起動すると標準の50 Hzに戻ります。U300のタイミングの出典は [minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md) です。 +- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。対象のMiniBook X U300またはN100のパネルテーブルと一致すると、`-wex-dsi-60hz` で起動時に固定60 Hzを要求します。各パネルのファームウェアが指定するブランキング期間を使い、両機種ともBig Surで実機確認済みです。N100はTahoeでも確認しました。この引数を削除して再起動すると標準の50 Hzに戻ります。U300のタイミングの出典は [minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md) です。 - GPUは自動選択するため、通常は`device-id`上書きは不要です。 - Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pではタイル形式を自動選択します。`-yTileDisable`は使わないでください。`AAPL,GfxYTile`は不要です。 From d76d88b30860e2b538f4a7d7e13728524f485b84 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 16:30:24 +0900 Subject: [PATCH 09/46] Recognize native DSI modes in rotated CoreDisplay timing lists --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_adapter_modes.inc | 11 +++++++++-- tests/dsi_apple_adapter.py | 10 +++++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f75efe20..cfcc8868 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ The common regression checks cover Metal, OpenGL, OpenCL, hardware H.264 encode/ | Tiger Lake laptops | Big Sur–Tahoe: 48 EU, internal eDP and brightness, HDMI/USB-C, up to three displays and display off/on | | Alder Lake-N mini PCs | Big Sur–Tahoe: 16/24 EU, two 1080p60 HDMI displays and display off/on | | Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MiniBook X N100 also supports the optional 60 Hz mode. MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | -| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz or optional 60 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode | +| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz or optional 60 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode. Tahoe: internal 50 Hz display, native landscape and HiDPI modes | | Alder Lake-H | Big Sur–Tahoe: 96 EU, three displays using HDMI and a DP-to-HDMI adapter. Single-channel `4626` graphics acceleration checked on Big Sur | | Alder Lake-S | Big Sur–Tahoe: UHD 770, HDMI/DP/USB-C and up to three displays; Safari video, with Widevine checked on Big Sur/Tahoe | diff --git a/README_ja.md b/README_ja.md index 2f062ee8..f133d00a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -95,7 +95,7 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします | Tiger Lakeラップトップ | Big Sur〜Tahoe:48 EU、内蔵eDPと輝度調整、HDMI/USB-C、最大3画面・消灯復帰 | | Alder Lake-NミニPC | Big Sur〜Tahoe:16/24 EU、1080p60のHDMI 2画面・消灯復帰 | | Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MiniBook X N100は任意設定の60 Hz、MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | -| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・標準50 Hz/任意設定で60 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode | +| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・標準50 Hz/任意設定で60 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode。Tahoe:内蔵50 Hz・横向きネイティブ表示・HiDPI | | Alder Lake-H | Big Sur〜Tahoe:96 EU、HDMIとDP→HDMI変換器による3画面。シングルチャネル構成の`4626`でもBig Surの加速を確認 | | Alder Lake-S | Big Sur〜Tahoe:UHD 770、HDMI/DP/USB-C、最大3画面、Safari動画。WidevineはBig Sur/Tahoeで確認 | diff --git a/WhateverXe/kern_dsi_adapter_modes.inc b/WhateverXe/kern_dsi_adapter_modes.inc index 24e34b31..fe7a7076 100644 --- a/WhateverXe/kern_dsi_adapter_modes.inc +++ b/WhateverXe/kern_dsi_adapter_modes.inc @@ -265,7 +265,14 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingRejected",bytes); continue; } - ++count;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; + // A rotated native offer has explicit draw dimensions and a + // transform. Keep an exact wire alias first when both are offered. + const bool swap=(t.scalerFlags&0x10)!=0; + const bool fullSize=!t.horizontalInset && !t.verticalInset && + t.horizontalScaled==(swap?t.verticalActive:t.horizontalActive) && + t.verticalScaled==(swap?t.horizontalActive:t.verticalActive); + if(native || (!client && fullSize))client=t.reservedA[0]; + ++count;offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled; @@ -306,7 +313,7 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { dsiFixedMode.offerCount=count; if(replacement)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingList",replacement); else IORegistryEntry::getRegistryRoot()->removeProperty("WEXDSITimingList"); - if(client)dsiFixedMode.clientMode=client; + dsiFixedMode.clientMode=client ? client : XELP::DSI::AppleAdapter::FixedModeID; OSSafeReleaseNULL(previous); } else { OSSafeReleaseNULL(replacement); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index c8d82a6c..95343c34 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -262,7 +262,15 @@ assert(static_cast(timing)->horizontalScaled==1280+2*i); assert(!XE::wrapDSIGetModeInformation(fb,modes[i],&info) && info.width==1280+2*i && info.height==720+i);} many->release();assert(!XE::wrapDSISetDetailedTimings(fb,rotationList)); - rotationData->release();rotationList->release(); + // A new quarter-turn generation can contain no unrotated wire alias. + auto landscape=OSArray::withCapacity(2);auto scaled=rotated; + scaled.reservedA[0]=0x80005003;scaled.horizontalScaled=1638;scaled.verticalScaled=1024; + auto scaledData=OSData::withBytes(&scaled,sizeof(scaled));landscape->setObject(scaledData);landscape->setObject(rotationData); + assert(!XE::wrapDSISetDetailedTimings(fb,landscape) && dsiFixedMode.clientMode==rotated.reservedA[0]); + assert(!XE::wrapDSIGetCurrentMode(fb,¤t,&depth) && static_cast(current)==rotated.reservedA[0]); + assert(XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)==kIOReturnBadArgument); + assert(!XE::wrapDSISetDetailedTimings(fb,rotationList) && dsiFixedMode.clientMode==offered.reservedA[0]); + scaledData->release();landscape->release();rotationData->release();rotationList->release(); // Legacy UI attributes alone must never fabricate a presentation timing. auto orientation=new OSNumber;fb->setProperty("IOFBTransform",orientation); for(auto transform:{3ULL,5ULL,0ULL}) { From b2c594b30fd1da3d198a9bfb0986498c49d0a751 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 3 Nov 2026 22:58:41 +0900 Subject: [PATCH 10/46] Stage internal DSI system-wake restoration and notify parked framebuffer clients --- WhateverXe/kern_display13_power.hpp | 22 ++- WhateverXe/kern_display13_power_bridge.hpp | 15 ++ WhateverXe/kern_dsi_adapter_lifecycle.inc | 153 ++++++++++++++++++++- WhateverXe/kern_dsi_panel.hpp | 9 +- WhateverXe/kern_dsi_sleep_image.hpp | 28 ++++ WhateverXe/kern_xe.hpp | 5 + tests/display13_tc.cpp | 8 ++ tests/dsi_quiesce_adapter.py | 4 + tests/dsi_surface_adapter.py | 3 +- tests/dsi_update_dispatch.py | 12 +- 10 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 WhateverXe/kern_dsi_sleep_image.hpp diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index d036f4d9..efbd9077 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -57,7 +57,7 @@ struct State { uint32_t refs[Count] {}; // Requests set by this object, versus requests found already set. Never // clear firmware/predecessor driver requests without an explicit handoff. - uint32_t owned {}, borrowed {}; + uint32_t owned {}, borrowed {}, deferredTC {}; uint32_t initialDriver {}, initialDDI {}, initialAUX {}, initialFuses {}; }; struct Lease { @@ -188,8 +188,8 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai if (r.status != Status::Complete) break; } if (!valid(io, control(d), value)) { r.status = Status::Unavailable; break; } - if (s.refs[d] && !(value & request(d))) { r.status = Status::RequestLost; break; } - if(tcAux(d) && !s.refs[d]) { + if (s.refs[d] && !(s.deferredTC & mask) && !(value & request(d))) { r.status = Status::RequestLost; break; } + if(tcAux(d) && (!s.refs[d] || (s.deferredTC&mask))) { r.status=enableTCAux(io,d);if(r.status!=Status::Complete)break; } if (!(value & request(d))) { @@ -211,6 +211,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai } if (r.status == Status::Complete) { for (unsigned d = 0; d < Count; ++d) if (domains & (1U << d)) ++s.refs[d]; + s.deferredTC &= ~domains; lease.owner = &s; lease.domains = domains; r.domain = Count; return r; @@ -252,7 +253,7 @@ template Result release(IO &io, State &s, Lease &lease) { r.status = clear(io, s, d, r); if (r.status != Status::Complete) { s.faulted = true; return r; } } - --s.refs[d]; + if(!--s.refs[d])s.deferredTC &= ~(1U< Result suspend(IO &io,State &s) { // Restore the current held scopes, in dependency order, without duplicate // gets. A request lost during power collapse is acquired anew and becomes // ours; a surviving borrowed request is never adopted merely by this call. -template Result restore(IO &io,State &s) { +template Result restore(IO &io,State &s,bool deferTC=false) { Result r; if(!s.ready || s.faulted)return r; uint32_t value; @@ -294,11 +295,20 @@ template Result restore(IO &io,State &s) { for(auto reg : registers)if(!valid(io,reg,value)) { r.status=Status::Unavailable;return r; } - for(unsigned d=0;d Result rearmRetained(IO &io,uint32_t domain) { + Lease temporary; + auto r=acquire(io,state,temporary,domain); + return r.status==Status::Complete ? release(io,state,temporary) : r; + } template Result getDDI(IO &io, unsigned port) { Result r; if (!begun || state.faulted || state.suspended) return r; @@ -167,6 +174,10 @@ struct NativeBridge { r = acquire(io,state,ddiLease[port],ddiDomain(port)); if (r.status != Status::Complete) return r; } + if(state.deferredTC & ddiDomain(port)) { + r=rearmRetained(io,ddiDomain(port)); + if(r.status!=Status::Complete)return r; + } ++ddiRefs[port]; r.status = Status::Complete; return r; } @@ -195,6 +206,10 @@ struct NativeBridge { r = acquire(io,state,auxLease[port],auxDomain(port)); if (r.status != Status::Complete) return r; } + if(state.deferredTC & auxDomain(port)) { + r=rearmRetained(io,auxDomain(port)); + if(r.status!=Status::Complete)return r; + } ++auxRefs[port]; r.status = Status::Complete; return r; } diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 453971be..4bc94ff7 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -116,6 +116,9 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { if(attribute==0x706F7772 || attribute==0x70777273) { // powr / IOGraphics pwrs relay. if(value>UINT32_MAX)return kIOReturnUnsupported; + // The power wrapper takes its own FB lock. Release ours so + // restored framebuffer clients can re-enter after notification. + IORecursiveLockUnlock(scope.lock);scope.lock=nullptr; return wrapDSISetPowerState(fb,static_cast(value)); } // IOGraphics sends spwr before restart or power-off. Park the adopted @@ -233,6 +236,7 @@ uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint } uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { + bool notifyWake=false; recordDSIClient(fb,9,state,UINT32_MAX); const auto result=[&]() -> uint32_t { if(callbackXE->adlnOutputWiring.dsi) { @@ -246,6 +250,9 @@ uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); if(!ready)return kIOReturnNotReady; + if(state==2 && callbackXE->dsiSystemWakeNotify) { + notifyWake=true;callbackXE->dsiSystemWakeNotify=false; + } *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; return kIOReturnSuccess; } @@ -270,10 +277,38 @@ uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { } return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); }(); + if(notifyWake)callbackXE->finishDSISystemWake(fb); recordDSIClient(fb,9,state,result); return result; } +// IOGraphics parks every published framebuffer before system sleep, including +// offline connectors. Each has a distinct accelerator notification subscriber. +void XE::finishDSISystemWake(IOService *internal) { + if(!dsiFramebufferEvent)return; + auto controller=Display13FBMemory::field(internal,0x1D0); + if(!controller || controller!=adlnTopologyController || controller->getMetaClass()->getClassSize()<0x1C20)return; + using Event=uint32_t (*)(IOService *,int,void *); + const auto event=reinterpret_cast(dsiFramebufferEvent); + uint32_t events[12]={1,dsiPagingLayoutKnown}; + for(unsigned index=0;index<3;++index) { + auto fb=Display13FBMemory::field(controller,0xD60+8*index); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90 || + Display13FBMemory::field(fb,0x1D0)!=controller || + Display13FBMemory::field(fb,0x1DC)!=index || (index==0 && fb!=internal))continue; + fb->retain(); + const bool pending=dsiPagingLayoutKnown && !(Display13FBMemory::field(fb,0x199)&0x20); + if(pending || fb==internal) { + events[2]|=1U<(1)); + events[5+2*index]=event(fb,8,nullptr); + } + if(dsiPagingLayoutKnown && (Display13FBMemory::field(fb,0x199)&0x20))events[3]|=1U<release(); + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISystemWakeEvents",events,sizeof(events)); +} + uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path,const void *timing) { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; @@ -300,6 +335,33 @@ bool XE::processDSINativeAttributes(KernelPatcher &patcher,size_t index,mach_vm_ const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; } + // Native prepareToExitSleep / prepareToEnterWake call the same imported + // IOFramebuffer::handleEvent with DidWake(true) and DidPowerOn(nullptr). + // Resolve the linked call from the identified donor without an extra kext dependency. + if(size>0x26F1C) { + const uint8_t wake[]={0xBA,1,0,0,0,0x4C,0x89,0xF7,0xBE,4,0,0,0,0xE8}; + const uint8_t on[]={0x4C,0x89,0xEF,0xBE,8,0,0,0,0x31,0xD2,0xE8}; + if(!memcmp(reinterpret_cast(address+0x26F0A),wake,sizeof(wake)) && + !memcmp(reinterpret_cast(address+0x26809),on,sizeof(on))) { + int32_t wd,od; + memcpy(&wd,reinterpret_cast(address+0x26F18),sizeof(wd)); + memcpy(&od,reinterpret_cast(address+0x26814),sizeof(od)); + if(wd && od && address+0x26F1C+wd==address+0x26818+od) + dsiFramebufferEvent=address+0x26F1C+wd; + if(dsiFramebufferEvent) { + // Accept the pagingState field only when the linked handleEvent + // sets precisely this bit in both DidWake and DidPowerOn. + auto fn=reinterpret_cast(dsiFramebufferEvent); + if(fn[0]==0xFF && fn[1]==0x25) { + const auto slot=XELPCall::relativeTarget(fn+2,reinterpret_cast(fn)+6); + memcpy(&fn,reinterpret_cast(slot),sizeof(fn)); + } + const uint8_t setPaging[]={0x41,0x80,0x8D,0x99,0x01,0x00,0x00,0x20}; + dsiPagingLayoutKnown=fn && fn[0]==0x55 && !memcmp(fn+0x100,setPaging,sizeof(setPaging)) && + !memcmp(fn+0x1A8,setPaging,sizeof(setPaging)); + } + } + } // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. KernelPatcher::RouteRequest routes[]={ {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, @@ -494,6 +556,7 @@ bool XE::disposeDSIPath(IOService *fb,OSObject *path) { // Host/IRQ/PLL retirement; the native DDI reference follows the stopped clock. // Keep the real stopped source separate from Session cleanup: it is not a full // Session StopProof. The one-shot journal retains a partial stop for diagnosis. + bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool retain) { auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || @@ -703,7 +766,19 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool const bool complete=retain ? (parked && restored && power.host.stopped && !power.host.started && power.host.error==XELP::DSI::Restart::Error::None && dsiPanelStop.complete && qualified() && dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); - if(retain && complete)power.sleeping=true; + if(retain && complete) { + power.sleeping=true; + dsiSleepImage.captured=display13ClockVariant()==Display13Clock::Variant::RPLU && + c.dbuf.selected==1 && !irq.state.active; + if(dsiSleepImage.captured) { + for(unsigned i=0;i 4dbc6). Mirror that CPU completion now, @@ -748,7 +823,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); if(!inGate && !acquired){if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - const auto owned=[&]() { + const auto bindingOwned=[&]() { if(!power.sleeping || power.failed || !dsiStopAttempted || !dsiIRQParked || !dsiResourcesReady || !dsiStop.complete() || !dsiPanelStop.complete || !dsiNativeWriterHooksReady || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || @@ -763,7 +838,10 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || !c.bootDSI.sameSource(dsiBoot.source) || !dsiPublishedTarget(fb) || !dsiPendingBacking.modeReady || !dsiArmedBackingOwned(fb,path))return false; - Display13FBBootIO io{c.map};return c.bridge.outputHeld(io,0,0); + return true; + }; + const auto owned=[&]() { + Display13FBBootIO io{c.map};return bindingOwned() && c.bridge.outputHeld(io,0,0); }; struct IO : Display13FBBootIO { decltype(owned) &owns;IOSimpleLock *lock;unsigned pll; @@ -784,7 +862,64 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { uint64_t receipt[24] {1,1,0,power.cycles,power.sleeping},irqReceipt[24] {1}; bool masked=false,restored=false,rearmed=false,cursor=false,complete=false;uint32_t master=0; auto stoppedPlane=power.plane;stoppedPlane.control&=~(1U<<31); - receipt[5]=owned() && power.host.stopped && !power.host.started && !power.panel.attempted && + // A system sleep may clear even PW1. Restore held references before + // reading powered pipe banks. Display-only wake follows the retained path. + uint32_t coldStage=0,coldReg=0,coldValue=0; + const auto restoreLostDisplay=[&]() { + if(!dsiSleepImage.captured || !bindingOwned() || !io.readable(Display13Power::Driver) || + io.read(Display13Power::Driver)!=0)return true; + coldStage=1; + Display13FBPowerIO wells{c.map}; + c.bridge.state.suspended=true; + const auto restoredPower=Display13Power::restore(wells,c.bridge.state,true); + coldValue=static_cast(restoredPower.status);coldReg=restoredPower.domain; + if(restoredPower.status!=Display13Power::Status::Complete || !owned())return false; + coldStage=2; + if(io.read(0x7B008)&0xC0000000U || io.read(0x70008)&0xC0000000U || + io.read(0x71008)&0xC0000000U || io.read(0x72008)&0xC0000000U || + io.read(0x70180)&(1U<<31))return false; + Display13FBClockIO cdclk{c.map}; + const auto hz=c.clock.nativeHz(); + if(!hz)return false; + const auto change=Display13Clock::set(cdclk,hz,{true,true,0},display13ClockVariant()); + coldValue=static_cast(change.status); + if(change.status!=Display13Clock::ChangeStatus::Complete)return false; + coldStage=3; + auto store=[&](uint32_t reg,uint32_t v) { + coldReg=reg; + *reinterpret_cast(c.map->getVirtualAddress()+reg)=v; + coldValue=io.read(reg);return coldValue==v; + }; + if(io.read(0x4438C)!=c.dbuf.mbus && !store(0x4438C,c.dbuf.mbus))return false; + Display13FBDBUFIO dbuf{c.map}; + for(unsigned i=0;i<4;++i)if(!(dbuf.read(Display13DBUF::regs[i])&Display13DBUF::Request)) + c.dbuf.owned&=~(1U<(slices); + if(slices!=Display13DBUF::Status::Complete)return false; + if(Display13DBUF::tracker(dbuf,c.dbuf)!=Display13DBUF::Status::Complete)return false; + coldStage=4; + for(unsigned i=0;i(c.map->getVirtualAddress()+field.reg)=(before&~field.mask)|value; + coldValue=io.read(field.reg);if((coldValue&field.mask)!=value)return false; + } + } + for(unsigned i=0;i(controller,0x90) && irq.base==c.map->getVirtualAddress()) { @@ -797,7 +932,8 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { !(queued(0xE08)&Display13FBIRQ::pipeEvents(0)) && !queued(0xE0C) && !(queued(0xE10)&3); if(quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && irq.state.begun && irq.state.enabled && !(irq.state.active&1) && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && - master!=UINT32_MAX && (master&Display13FBIRQ::Enable)) { + master!=UINT32_MAX && ((master&Display13FBIRQ::Enable) || coldStage==5)) { + if(coldStage==5)master|=Display13FBIRQ::Enable; irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; irqIO.write(Display13FBIRQ::Master,0); const auto after=irqIO.read(Display13FBIRQ::Master); @@ -815,7 +951,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { } } lifecycle{clock.allocation,reinterpret_cast(controller),power.host}; XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop, - dsiBacklight.initialized?&dsiBacklight:nullptr); + dsiBacklight.initialized?&dsiBacklight:nullptr,coldStage==5); if(power.panel.complete() && owned() && XELP::DSI::Surface::matches(io,power.plane,true)) { io.write(0x70080,power.cursorControl);io.write(0x70088,power.cursorPosition);io.write(0x70084,power.cursorBase); const auto before=io.read(0x70040); @@ -836,6 +972,8 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { const auto wells=irqIO.read(Display13FBIRQ::Driver); if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; if(!irq.state.faulted) { + if(coldStage==5 && !(irqIO.read(Display13FBIRQ::GfxMaster)&Display13FBIRQ::Enable)) + irqIO.write(Display13FBIRQ::GfxMaster,Display13FBIRQ::Enable); irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); const auto after=irqIO.read(Display13FBIRQ::Master); restored=after!=UINT32_MAX && (after&Display13FBIRQ::Enable);irq.state.enabled=restored; @@ -881,6 +1019,9 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeCommands",commandReceipt,sizeof(commandReceipt)); + if(complete && coldStage==5)dsiSystemWakeNotify=true; + uint32_t systemWake[]={coldStage,coldReg,coldValue,dsiSleepImage.captured,domainReady,p.frameBefore,p.frameAfter,p.waitUs,dsiFramebufferEvent!=0}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISystemWake",systemWake,sizeof(systemWake)); publishDSIBacklight(); return complete; } diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 5faed7aa..4dc39972 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -86,7 +86,7 @@ struct Shutdown { // IO::gpio serializes HPD writes; no delays may run under that IRQ lock. // A failed transaction is never retried and must retain all resources. template void cycle(IO &io,const Readout &link,const Profile &profile, - uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle,const Shutdown *shutdown=nullptr,Backlight *backlight=nullptr) { + uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle,const Shutdown *shutdown=nullptr,Backlight *backlight=nullptr,bool systemWake=false) { Readout current=link; if(r.attempted)return; r.attempted=true; @@ -122,11 +122,16 @@ template void cycle(IO &io,const Readout &link,const P }; const auto frame=[&]() { if(!read(0x70040,r.frameBefore))return false; + bool rebased=false; for(uint32_t us=0;;us+=1000) { if(!read(0x70040,r.frameAfter))return false; const auto n=uint32_t(r.frameAfter-r.frameBefore); if(n && n<0x80000000U)return true; - if(n || us>=3*period+1000){r.error=Error::Timeout;return false;} + // The first frame after a system power loss may reload the counter. + // Require actual forward progress from the new value as well. + if(systemWake && n>=0x80000000U && !rebased){r.frameBefore=r.frameAfter;rebased=true;} + else if(n){r.error=Error::Timeout;return false;} + if(us>=(systemWake?10:3)*period+1000){r.error=Error::Timeout;return false;} io.delayUs(1000);r.waitUs+=1000; } }; diff --git a/WhateverXe/kern_dsi_sleep_image.hpp b/WhateverXe/kern_dsi_sleep_image.hpp new file mode 100644 index 00000000..a3ee5799 --- /dev/null +++ b/WhateverXe/kern_dsi_sleep_image.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +namespace XELP { namespace DSI { +struct SystemSleepImage { +// Explicit writable configuration of a parked internal-only pipe. DDB and WM +// precede plane arming. No interrupt identity, status or frame counters. +static constexpr uint32_t config[] = { + 0x6001C,0x45270,0x7027C,0x7017C, + 0x70240,0x70244,0x70248,0x7024C,0x70250,0x70254,0x70258,0x7025C,0x70268, + 0x70140,0x70144,0x70148,0x7014C,0x70150,0x70154,0x70158,0x7015C,0x70168, + 0x70188,0x7018C,0x70190,0x701A4,0x701CC,0x70180,0x7019C, + 0xC8254,0xC8250 +}; + // Linux intel_combo_phy.c: PHY-A voltage/process compensation, master + // current reference and lane power-down enable. Retain only RW fields. + struct Field {uint32_t reg, mask;}; + static constexpr Field fields[]={ + {0x162104,0x00FF00FF},{0x162124,UINT32_MAX},{0x162128,UINT32_MAX}, + {0x162120,1U<<24},{0x162100,1U<<31},{0x162014,1U<<4}, + {0x42084,1U<<30},{0xC7204,1U<<1} + }; + static constexpr unsigned fieldCount=sizeof(fields)/sizeof(fields[0]); + uint32_t savedFields[fieldCount] {}; + static constexpr unsigned count=sizeof(config)/sizeof(config[0]); + uint32_t regs[count] {}; + bool captured {}; +}; +}} diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 0761692f..e3ca3f2a 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -8,6 +8,7 @@ #ifndef kern_xe_hpp #define kern_xe_hpp +#include "kern_dsi_sleep_image.hpp" #include "kern_tgl_tc_boot.hpp" #include "kern_dsi_boot.hpp" #include "kern_dsi_apple_timing.hpp" @@ -246,6 +247,9 @@ class XE { bool processDSINativeTransactions(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool processDSINativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgDSIAttributes[4] {}; + mach_vm_address_t dsiFramebufferEvent {}; + bool dsiSystemWakeNotify {},dsiPagingLayoutKnown {}; + void finishDSISystemWake(IOService *); static uint32_t wrapDSISetStream(IOService *,IOService *,OSObject *,const void *); static uint32_t wrapDSISetAttribute(IOService *,uint32_t,uintptr_t); static uint32_t wrapDSISetConnectionAttribute(IOService *,int32_t,uint32_t,uintptr_t); @@ -265,6 +269,7 @@ class XE { bool dsiHostTest {}; bool dsiStopTest {}; bool dsiPowerTest {}; + XELP::DSI::SystemSleepImage dsiSleepImage; struct { bool sleeping {},failed {};uint32_t cycles {}; XELP::DSI::Surface::Plane plane; diff --git a/tests/display13_tc.cpp b/tests/display13_tc.cpp index c3335b68..d876e7e8 100644 --- a/tests/display13_tc.cpp +++ b/tests/display13_tc.cpp @@ -70,6 +70,14 @@ int main(){ assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==2); assert(suspend(io,b.state).status==Status::Complete); assert(restore(io,b.state).status==Status::Complete && io.prepares==2); + assert(suspend(io,b.state).status==Status::Complete); + const auto priorPW2=b.state.refs[1]; + assert(restore(io,b.state,true).status==Status::Complete && io.prepares==2); + assert(b.state.deferredTC==(auxDomain(port)|ddiDomain(port))); + assert(b.getAUX(io,port).status==Status::Complete && io.prepares==3 && b.auxHeld(io,port)); + assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==priorPW2); + assert(b.getDDI(io,port).status==Status::Complete && b.ddiHeld(io,port) && !b.state.deferredTC); + assert(b.putDDI(io,port).status==Status::Complete && b.state.refs[1]==priorPW2); assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==1 && (io.regs[Driver]&0xC)==0xC); assert(b.putDDI(io,port).status==Status::Complete && !b.nativeDDIRefs(port)); assert((io.regs[0x45404]&0xA80A)==0x802 && (io.regs[0x45454]&0x2AAA)==2); diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 6c3fff82..e7989edc 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -9,6 +9,9 @@ b=(root/'tests/dsi_boot.cpp').read_text();fixture=b[b.index('struct IO {'):b.index('Boot capture(')] code=r''' #include "kern_dsi_quiesce.hpp" +#include "kern_dsi_sleep_image.hpp" +#include "kern_display13_clock.hpp" +static constexpr auto display13ClockVariant(){return Display13Clock::Variant::RPLU;} #include "kern_dsi_clock.hpp" #include "kern_dsi_restart.hpp" #include "kern_dsi_panel.hpp" @@ -110,6 +113,7 @@ struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false,dsiMemoryReleased=false; bool dsiStopTest=false,dsiPowerTest=false; + SystemSleepImage dsiSleepImage; Panel::Shutdown dsiPanelStop; struct {bool sleeping=false,failed=false;unsigned cycles=0;Surface::Plane plane; uint32_t cursorControl=0,cursorPosition=0,cursorBase=0;Restart::State host;Panel::Result panel;}dsiRuntimePower; diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index c8b58333..d14111d7 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -14,7 +14,8 @@ q=(root/'tests/dsi_quiesce_adapter.py').read_text() q=q.split("code=r'''",1)[1].split("'''.replace",1)[0] prelude='#define DSI_SURFACE_REPLAY\n'+q[:q.index('struct XE {')] -prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"\nstatic Display13Clock::Variant display13ClockVariant(){return Display13Clock::Variant::ADLP;}') +prelude=prelude.replace('return Display13Clock::Variant::RPLU;', 'return Display13Clock::Variant::ADLP;') +prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_native_backing.hpp"') b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') prelude=prelude.replace('#include "kern_dsi_native_backing.hpp"','#include "kern_dsi_native_backing.hpp"\n#include "kern_dsi_native_mode.hpp"') diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index f9036ac4..c92e581e 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -31,6 +31,7 @@ using IOService=OSObject; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char *name,const void *data,unsigned bytes){ + if(!strcmp(name,"WEXDSISystemWakeEvents")){assert(bytes==8);return;} assert(!strcmp(name,"WEXDSIModeRequest") && bytes==64); const auto words=static_cast(data);assert(words[0]==1); }}; @@ -76,6 +77,8 @@ uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; + uintptr_t dsiFramebufferEvent=0;bool dsiSystemWakeNotify=false; + void finishDSISystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(dsiFramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);} bool dsiPowerTest=false;struct {bool sleeping=false,failed=false;}dsiRuntimePower;unsigned wakeCalls=0;bool wakeOK=false; bool dsiStopTest=false,dsiMemoryReleased=false,dsiDDIReleased=false,stopOK=false;unsigned stopCalls=0; struct {bool done=false;bool complete(){return done;}}dsiClockStop; @@ -209,6 +212,11 @@ } void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; +static unsigned systemEvents; +static uint32_t systemEvent(IOService*,int event,void *arg){ + assert(!fbLock.depth && !owner); + assert((event==4 && arg==reinterpret_cast(1)) || (event==8 && !arg));++systemEvents;return 0; +} int main(){ for(auto message:{kIOMessageSystemWillRestart,kIOMessageSystemWillPowerOff}){Case c;put(&c.fb,0x4284,2U); @@ -233,9 +241,11 @@ c.refuse();c.refuseCursor();xe.wakeOK=false; assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnNotReady); assert(Display13FBMemory::field(&c.fb,0x4284)==0); + xe.dsiSystemWakeNotify=true;xe.dsiFramebufferEvent=reinterpret_cast(&systemEvent); + const auto eventsBefore=systemEvents; xe.wakeOK=true;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess); assert(!xe.dsiRuntimePower.sleeping && !xe.dsiStopAttempted && Display13FBMemory::field(&c.fb,0x4284)==2); - auto wakes=xe.wakeCalls;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess && xe.wakeCalls==wakes); + auto wakes=xe.wakeCalls;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess && xe.wakeCalls==wakes && systemEvents==eventsBefore+2); } xe.dsiRuntimePower.failed=true;auto calls=xe.stopCalls; assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==calls); From b5f917f39e5cca61e2956c8e1b9ca2211b58593c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:11:04 +0900 Subject: [PATCH 11/46] Restore scanout arbitration and PHY clock fields on DSI wake and retain an unconfirmed first flip --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 7 +++++-- WhateverXe/kern_dsi_sleep_image.hpp | 11 ++++++++++- WhateverXe/kern_xe.cpp | 11 +++++++++++ WhateverXe/kern_xe.hpp | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 4bc94ff7..2cb14e40 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -865,6 +865,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { // A system sleep may clear even PW1. Restore held references before // reading powered pipe banks. Display-only wake follows the retained path. uint32_t coldStage=0,coldReg=0,coldValue=0; + uint32_t dbox[3]={}; const auto restoreLostDisplay=[&]() { if(!dsiSleepImage.captured || !bindingOwned() || !io.readable(Display13Power::Driver) || io.read(Display13Power::Driver)!=0)return true; @@ -902,6 +903,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { for(unsigned i=0;i(c.map->getVirtualAddress()+field.reg)=(before&~field.mask)|value; @@ -912,6 +914,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { const auto reg=XELP::DSI::SystemSleepImage::config[i],value=dsiSleepImage.regs[i]; if(io.read(reg)!=value && !store(reg,value))return false; } + dbox[2]=io.read(0x7003C); // Only the internal clock select/gate belongs to this path. auto mux=io.read(0x164280); if(mux==UINT32_MAX || !store(0x164280,(mux&~0x403U)|0x400U|power.host.pll))return false; @@ -998,7 +1001,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { irq.quiescing=false;irq.blockedPipes=0; adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); complete=restored && rearmed && cursor && power.panel.complete() && owned(); - if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;} + if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;dsiFirstWakeLatch=coldStage==5;} else{power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} } } @@ -1020,7 +1023,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeCommands",commandReceipt,sizeof(commandReceipt)); if(complete && coldStage==5)dsiSystemWakeNotify=true; - uint32_t systemWake[]={coldStage,coldReg,coldValue,dsiSleepImage.captured,domainReady,p.frameBefore,p.frameAfter,p.waitUs,dsiFramebufferEvent!=0}; + uint32_t systemWake[]={coldStage,coldReg,coldValue,dsiSleepImage.captured,domainReady,p.frameBefore,p.frameAfter,p.waitUs,dsiFramebufferEvent!=0,dbox[0],dbox[1],dbox[2]}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISystemWake",systemWake,sizeof(systemWake)); publishDSIBacklight(); return complete; diff --git a/WhateverXe/kern_dsi_sleep_image.hpp b/WhateverXe/kern_dsi_sleep_image.hpp index a3ee5799..fd335b79 100644 --- a/WhateverXe/kern_dsi_sleep_image.hpp +++ b/WhateverXe/kern_dsi_sleep_image.hpp @@ -17,7 +17,16 @@ static constexpr uint32_t config[] = { static constexpr Field fields[]={ {0x162104,0x00FF00FF},{0x162124,UINT32_MAX},{0x162128,UINT32_MAX}, {0x162120,1U<<24},{0x162100,1U<<31},{0x162014,1U<<4}, - {0x42084,1U<<30},{0xC7204,1U<<1} + {0x42084,1U<<30},{0xC7204,1U<<1}, + // ADL-P pipe A pixel fetch credits (skl_watermark.c, intel_mbus_dbox_update). + // Restore the working allocation before arming the first plane. + {0x7003C,0x01FFDF0F}, + // Plane and cursor controls encode Display13 arbitration slots. + // Select those slots on the pipe before rearming either consumer. + {0x70028,1U<<13}, + // PHY A TX_DW8 stores the source/divider for duty-cycle correction. + // Preserve each data lane's other trim fields. + {0x1628A0,7U<<29},{0x1629A0,7U<<29},{0x162AA0,7U<<29},{0x162BA0,7U<<29} }; static constexpr unsigned fieldCount=sizeof(fields)/sizeof(fields[0]); uint32_t savedFields[fieldCount] {}; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index c4a50232..20c36158 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9886,6 +9886,17 @@ void XE::display13WMArmUpdate(IOService *fb) { if(retired==Display13DBUF::Status::Complete) {c.pipe[pipe].watermarkClockHz=actualHz;c.pipe[pipe].wmPending=0;} else result.status=Display13WM::Status::Conflict; } + // A cold DSI restart can pause the frame counter during the first + // native flip. Keep both DDB allocations pinned for the next real flush. + // No clock qualification or retirement is granted until a frame advances. + if(pipe==0 && callbackXE->dsiFirstWakeLatch && c.transcoders.dsi0Pipe==0) { + if(result.status==Display13WM::Status::CommitTimeout && + result.check==Display13WM::LatchCheck::Registers && result.frameAfter==result.frameBefore) { + result.status=Display13WM::Status::Unavailable; + result.check=Display13WM::LatchCheck::AwaitingFlip; + callbackXE->dsiFirstWakeLatch=false; + } else if(result.status==Display13WM::Status::Complete)callbackXE->dsiFirstWakeLatch=false; + } c.pipe[pipe].wmLatch=result;c.pipe[pipe].wmCommitting=false; if(result.status!=Display13WM::Status::Complete && result.status!=Display13WM::Status::Unavailable)adlnFault(c.watermarkFaulted,__LINE__); record[0]=1;record[1]=static_cast(result.status);record[2]=pending;record[3]=c.pipe[pipe].wmPending; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e3ca3f2a..ca213137 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -248,7 +248,7 @@ class XE { bool processDSINativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgDSIAttributes[4] {}; mach_vm_address_t dsiFramebufferEvent {}; - bool dsiSystemWakeNotify {},dsiPagingLayoutKnown {}; + bool dsiSystemWakeNotify {},dsiPagingLayoutKnown {},dsiFirstWakeLatch {}; void finishDSISystemWake(IOService *); static uint32_t wrapDSISetStream(IOService *,IOService *,OSObject *,const void *); static uint32_t wrapDSISetAttribute(IOService *,uint32_t,uintptr_t); From 4b6c350f48dc40c0d6f77515c24b3791471554b5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:41:27 +0900 Subject: [PATCH 12/46] Report a stalled Raptor-U DSI wake before waiting for display transactions --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 32 ++++++++++++++++++++++- WhateverXe/kern_host.cpp | 29 ++++++++++++++++++-- WhateverXe/kern_host.hpp | 4 ++- WhateverXe/kern_xe.hpp | 2 ++ tests/host_publication.py | 16 +++++++++++- 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 2cb14e40..c781ebe1 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -282,6 +282,16 @@ uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { return result; } +bool XE::failedDSIWakeFor(IOService *fb) const { + if(!__atomic_load_n(&dsiWakeDegraded,__ATOMIC_ACQUIRE) || !fb || + strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") || + fb->getMetaClass()->getClassSize()<0x4B90 || + Display13FBMemory::field(fb,0x1D0)!=adlnTopologyController)return false; + if(__atomic_add_fetch(&callbackXE->dsiWakeRejections,1,__ATOMIC_RELAXED)==1) + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeRecoveryRejected",true); + return true; +} + // IOGraphics parks every published framebuffer before system sleep, including // offline connectors. Each has a distinct accelerator notification subscriber. void XE::finishDSISystemWake(IOService *internal) { @@ -1005,6 +1015,26 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { else{power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} } } + uint64_t cadence[6]={}; + if(complete && coldStage==5) { + // Sample while this FB still owns the restored modeset. Distinguish + // an actual slow port from a sticky PIPESTATUS bit or sample jitter. + const auto &mode=dsiProfile.mode; + const uint64_t period=(uint64_t(mode.htotal())*mode.vtotal()*1000+mode.clockKHz-1)/mode.clockKHz; + if(period>=1000 && period<=100000) { + const auto first=io.read(0x70040); + const auto t=mach_absolute_time(); + IOSleep(static_cast((period*8+999)/1000)); + const auto last=io.read(0x70040),port=io.read(0x70058); + uint64_t ns=0;absolutetime_to_nanoseconds(mach_absolute_time()-t,&ns); + cadence[0]=period;cadence[1]=ns/1000;cadence[2]=first;cadence[3]=last;cadence[4]=port; + const bool bad=first!=UINT32_MAX && last!=UINT32_MAX && port!=UINT32_MAX && + (port&(1U<<26)) && cadence[1]>=period*6 && + uint64_t(uint32_t(last-first)+1)*period*2setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeCommands",commandReceipt,sizeof(commandReceipt)); - if(complete && coldStage==5)dsiSystemWakeNotify=true; + if(complete && coldStage==5){dsiSystemWakeNotify=true;IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeCadence",cadence,sizeof(cadence));} uint32_t systemWake[]={coldStage,coldReg,coldValue,dsiSleepImage.captured,domainReady,p.frameBefore,p.frameAfter,p.waitUs,dsiFramebufferEvent!=0,dbox[0],dbox[1],dbox[2]}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISystemWake",systemWake,sizeof(systemWake)); publishDSIBacklight(); diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index db79068d..cddf1853 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -20,7 +20,7 @@ void XeHost::init() { if(resetFramebuffer>=FB_TOTAL)resetFramebuffer=FB_DETECT; lilu.onPatcherLoadForce([](void *u,KernelPatcher &p){static_cast(u)->processKernel(p);},this); lilu.onKextLoadForce(nullptr,0,[](void *u,KernelPatcher &p,size_t i,mach_vm_address_t a,size_t s){static_cast(u)->processKext(p,i,a,s);},this); - if(resetFramebuffer!=FB_NONE)lilu.onKextLoadForce(&graphics); + lilu.onKextLoadForce(&graphics); lilu.onKextLoadForce(&backlight); xe.init(); XeDataAccess::init(); @@ -34,6 +34,7 @@ void XeHost::processKernel(KernelPatcher &patcher) { // The host must dispatch framebuffer patches and accelerator rejection // hooks even when the selected profile deliberately has no Render backend. active=profile.displayAdmitted(); + systemWakeRecovery=active && !profile.overridden() && profile.identity==0xA7A98086; const auto display=XELP::displayAdapter(profile.family); backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS || display==XELP::DisplayAdapter::ADLP || display==XELP::DisplayAdapter::ADLN) && @@ -69,7 +70,7 @@ void XeHost::processKernel(KernelPatcher &patcher) { if(v){consoleVinfo=*v;gotConsoleVinfo=true;}else patcher.clearError(); } } - if(!active || resetFramebuffer==FB_DETECT){resetFramebuffer=FB_NONE;graphics.switchOff();} + if(!active || resetFramebuffer==FB_DETECT){resetFramebuffer=FB_NONE;if(!systemWakeRecovery)graphics.switchOff();} DeviceInfo::deleter(info); } void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { @@ -85,12 +86,36 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a } if(XeDataAccess::processKext(patcher,index,address,size))return; if(graphics.loadIndex==index) { + if(systemWakeRecovery) { + const char *sym="__ZN13IOFramebuffer16setWSAAAttributeEjj"; + const auto fn=patcher.solveSymbol(index,sym,address,size); + patcher.clearError(); + const uint8_t prologue[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x68}; + bool ready=false; + if(size>=0x1cd64+sizeof(prologue) && fn==address+0x1cd64 && !memcmp(reinterpret_cast(fn),prologue,sizeof(prologue))) { + KernelPatcher::RouteRequest route(sym,wrapWSAAAttribute,orgWSAAAttribute); + ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeRecoveryReady",ready); + } + if(resetFramebuffer==FB_NONE)return; gIOFBVerboseBootPtr=patcher.solveSymbol(index,"__ZL16gIOFBVerboseBoot",address,size); if(gIOFBVerboseBootPtr){KernelPatcher::RouteRequest route("__ZN13IOFramebuffer6initFBEv",wrapFramebufferInit,orgFramebufferInit);patcher.routeMultiple(index,&route,1,address,size);} patcher.clearError();return; } xe.processKext(patcher,index,address,size); } +IOReturn XeHost::wrapWSAAAttribute(IOFramebuffer *fb,uint32_t attr,uint32_t value) { + // IOGraphics sends WillExitDefer under its global gate before calling the + // vendor attribute setter. Reject a failed scanout before queue-idle can + // sleep in that gate. No flip is acknowledged or GPU allocation released. + const auto state=value&kIOWSAA_StateMask; + if(attr==kIOWindowServerActiveAttribute && + (state==kIOWSAA_Accelerated || state==kIOWSAA_Unaccelerated) && + callbackHost->xe.failedDSIWakeFor(fb))return kIOReturnNotReady; + return FunctionCast(wrapWSAAAttribute,callbackHost->orgWSAAAttribute)(fb,attr,value); +} + bool XeHost::wrapPanelSetDisplay(IOService *panel,IOService *display) { // Add only the missing UID-19 profile for an actual internal 16-bit PWM // display. Keep Apple's/custom profiles and the native selection algorithm. diff --git a/WhateverXe/kern_host.hpp b/WhateverXe/kern_host.hpp index 903b46f8..da29f4ce 100644 --- a/WhateverXe/kern_host.hpp +++ b/WhateverXe/kern_host.hpp @@ -8,7 +8,9 @@ class XeHost { private: static XeHost *callbackHost; XE xe; - bool active {}; + bool active {},systemWakeRecovery {}; + mach_vm_address_t orgWSAAAttribute {}; + static IOReturn wrapWSAAAttribute(IOFramebuffer *,uint32_t,uint32_t); bool backlightEnabled {}; IOPCIDevice *backlightGPU {}; mach_vm_address_t orgPanelSetDisplay {}; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ca213137..0ce5434d 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -73,6 +73,7 @@ class XE { * @return true if patched anything */ bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); + bool failedDSIWakeFor(IOService *fb) const; private: bool adlpLoadOnly {}; @@ -248,6 +249,7 @@ class XE { bool processDSINativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgDSIAttributes[4] {}; mach_vm_address_t dsiFramebufferEvent {}; + uint32_t dsiWakeDegraded {},dsiWakeRejections {}; bool dsiSystemWakeNotify {},dsiPagingLayoutKnown {},dsiFirstWakeLatch {}; void finishDSISystemWake(IOService *); static uint32_t wrapDSISetStream(IOService *,IOService *,OSObject *,const void *); diff --git a/tests/host_publication.py b/tests/host_publication.py index a86433aa..311e2557 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -38,7 +38,7 @@ struct KernelPatcher { static constexpr int KernelID=0; uintptr_t solveSymbol(int,const char *){return 0;} - templateT solveSymbol(size_t,const char*,mach_vm_address_t,size_t){return nullptr;} + templateT solveSymbol(size_t,const char*,mach_vm_address_t,size_t){return T{};} struct RouteRequest {templateRouteRequest(const char*,T,uintptr_t&) {}}; bool routeMultiple(size_t,RouteRequest*,unsigned,mach_vm_address_t,size_t){return true;} void clearError(){} @@ -47,11 +47,19 @@ namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; struct {size_t loadIndex=5;}backlight; +using IOReturn=int;using IOFramebuffer=IOPCIDevice; +constexpr uint32_t kIOWindowServerActiveAttribute=1,kIOWSAA_StateMask=15,kIOWSAA_Accelerated=1,kIOWSAA_Unaccelerated=0; +constexpr IOReturn kIOReturnNotReady=-1; +#define FunctionCast(fn,addr) reinterpret_cast(addr) struct XeHost { + static XeHost *callbackHost;bool systemWakeRecovery=false; + uintptr_t orgWSAAAttribute=0; + static IOReturn wrapWSAAAttribute(IOFramebuffer*,uint32_t,uint32_t); bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { + IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} } xe; @@ -61,7 +69,13 @@ uintptr_t orgPanelSetDisplay=0,orgFramebufferInit=0;uint8_t *gIOFBVerboseBootPtr=nullptr; }; '''.replace('// DEVICE_ADAPTER',adapter)+body+r''' +XeHost *XeHost::callbackHost; +static IOReturn vendor(IOFramebuffer*,uint32_t,uint32_t){return 42;} int main(){KernelPatcher patcher;IOPCIDevice gpu,mei; +XeHost exercised;XeHost::callbackHost=&exercised;exercised.orgWSAAAttribute=reinterpret_cast(&vendor); +assert(XeHost::wrapWSAAAttribute(&gpu,1,1)==42);exercised.xe.degraded=&gpu; +assert(XeHost::wrapWSAAAttribute(&gpu,1,0x101)==kIOReturnNotReady && XeHost::wrapWSAAAttribute(&gpu,1,0)==kIOReturnNotReady); +assert(XeHost::wrapWSAAAttribute(&mei,1,1)==42 && XeHost::wrapWSAAAttribute(&gpu,2,1)==42 && XeHost::wrapWSAAAttribute(&gpu,1,3)==42); const char *names[]={nullptr,"IMEI","HECI","MEI"}; for(unsigned firmware=0;firmware<2;++firmware)for(unsigned me=0;me<5;++me)for(unsigned test=0;test<6;++test){ mei.name=names[me%4];mei.renames=0;gpu.name="GFX0"; From 9529866b397d3719392a044f218d5820e1325aab Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:51:47 +0900 Subject: [PATCH 13/46] Select MIPI mode before restoring Combo PHY A power on DSI wake --- WhateverXe/kern_display13_power.hpp | 6 +++++- WhateverXe/kern_dsi_adapter_lifecycle.inc | 26 ++++++++++++++++++----- WhateverXe/kern_dsi_restart.hpp | 5 +++-- tests/display13_power.cpp | 10 +++++++++ tests/dsi_restart.py | 5 +++++ 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index efbd9077..0f146caa 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -287,7 +287,7 @@ template Result suspend(IO &io,State &s) { // Restore the current held scopes, in dependency order, without duplicate // gets. A request lost during power collapse is acquired anew and becomes // ours; a surviving borrowed request is never adopted merely by this call. -template Result restore(IO &io,State &s,bool deferTC=false) { +template Result restore(IO &io,State &s,bool deferTC,PrepareDDIA prepareDDIA) { Result r; if(!s.ready || s.faulted)return r; uint32_t value; @@ -313,6 +313,7 @@ template Result restore(IO &io,State &s,bool deferTC=false) { if(!d)r.status=wait(io,Fuses,1U<<27,FuseTimeoutUs); if(r.status!=Status::Complete)break; if(!valid(io,control(d),value)) {r.status=Status::Unavailable;break;} + if(d==unsigned(Domain::DDIA) && !prepareDDIA(value)) {r.status=Status::Unavailable;break;} if(tcAux(d)) {r.status=enableTCAux(io,d);if(r.status!=Status::Complete)break;} if(!(value&request(d))) { if(!s.suspended) {r.status=Status::RequestLost;break;} @@ -330,5 +331,8 @@ template Result restore(IO &io,State &s,bool deferTC=false) { else s.faulted=true; // Keep partial restoration and its prerequisites. return r; } +template Result restore(IO &io,State &s,bool deferTC=false) { + return restore(io,s,deferTC,[](uint32_t){return true;}); +} } #endif diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index c781ebe1..3e0f7e39 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -875,14 +875,29 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { // A system sleep may clear even PW1. Restore held references before // reading powered pipe banks. Display-only wake follows the retained path. uint32_t coldStage=0,coldReg=0,coldValue=0; - uint32_t dbox[3]={}; + uint32_t dbox[3]={},phyPower[4]={}; const auto restoreLostDisplay=[&]() { if(!dsiSleepImage.captured || !bindingOwned() || !io.readable(Display13Power::Driver) || io.read(Display13Power::Driver)!=0)return true; coldStage=1; Display13FBPowerIO wells{c.map}; c.bridge.state.suspended=true; - const auto restoredPower=Display13Power::restore(wells,c.bridge.state,true); + // Intel DSI_IO_MODECTL must select MIPI before the Combo PHY + // power request. PW1 and the pipe are already accessible here. + const auto selectDSI=[&](uint32_t ddi) { + constexpr uint32_t mode=0x6B094; + phyPower[0]=1;phyPower[1]=ddi; + if(!io.readable(mode))return false; + phyPower[2]=io.read(mode); + if(phyPower[2]==UINT32_MAX)return false; + if(!(phyPower[2]&1)) { + if(ddi&3)return false; + io.write(mode,phyPower[2]|1); + } + phyPower[3]=io.read(mode); + return phyPower[3]!=UINT32_MAX && (phyPower[3]&1); + }; + const auto restoredPower=Display13Power::restore(wells,c.bridge.state,true,selectDSI); coldValue=static_cast(restoredPower.status);coldReg=restoredPower.domain; if(restoredPower.status!=Display13Power::Status::Complete || !owned())return false; coldStage=2; @@ -932,6 +947,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { return true; }; const bool domainReady=restoreLostDisplay(); + if(coldStage)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakePHYPower",phyPower,sizeof(phyPower)); receipt[5]=domainReady && owned() && power.host.stopped && !power.host.started && !power.panel.attempted && power.host.error==XELP::DSI::Restart::Error::None && XELP::DSI::Surface::matches(io,stoppedPlane,false); if(receipt[5] && adlnIRQOwner(controller) && irq.lock && irq.lock==Display13FBMemory::field(controller,0x90) && @@ -956,13 +972,13 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); if(canStart) { struct Lifecycle { - XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state; + XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state;bool modeSelected; bool stop(IO &,const XELP::DSI::Readout &,const XELP::DSI::Profile &){return false;} bool start(IO &io,XELP::DSI::Readout &source) { - if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; + if(!XELP::DSI::Restart::start(io,allocation,owner,state,modeSelected))return false; source=XELP::DSI::Restart::expected(source,state);return source.complete(); } - } lifecycle{clock.allocation,reinterpret_cast(controller),power.host}; + } lifecycle{clock.allocation,reinterpret_cast(controller),power.host,phyPower[0]!=0}; XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop, dsiBacklight.initialized?&dsiBacklight:nullptr,coldStage==5); if(power.panel.complete() && owned() && XELP::DSI::Surface::matches(io,power.plane,true)) { diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp index 04b5cbf0..a579442a 100644 --- a/WhateverXe/kern_dsi_restart.hpp +++ b/WhateverXe/kern_dsi_restart.hpp @@ -197,14 +197,15 @@ template bool retainedStopped(IO &io,const Readout &source, // transport derive from the profile; no firmware PLL/PHY register dump replay. // Leaves pixel transfer off, ready for command sequence initialization. The // caller must enable video/plane, validate and publish the new source identity. -template bool start(IO &io,XELPComboClock::ClockAllocation &allocation,uintptr_t owner,State &s) { +template bool start(IO &io,XELPComboClock::ClockAllocation &allocation,uintptr_t owner,State &s,bool modeSelected=false) { if(!s.stopped || s.started || s.stage!=Stage::Stopped || s.error!=Error::None || !s.config.valid)return false; Access a{io,allocation,owner,s};XELPComboClock::PLL pll; if(!XELPComboClock::PLL::resolve(s.pll,pll) || !a.held())return false; if(!a.wait(pll.enable,0xCC000000U,0,0) || !a.wait(0x6B400,1U<<31,0,0) || !a.wait(0x64000,0x80000080U,0x80,0) || !a.wait(0x164280,0x403,0x400|s.pll,0))return false; const auto &p=s.config; - s.stage=Stage::ModeOn;if(!a.rmw(0x6B094,0,1))return false; + s.stage=Stage::ModeOn; + if(!(modeSelected ? a.wait(0x6B094,1,1,0) : a.rmw(0x6B094,0,1)))return false; s.stage=Stage::Escape; if(!a.set(0x6B090,p.link.escapeDivider,0x1FF) || !a.set(0x162190,p.link.escapeDivider,0x1FF) || // Linux ADL_MIPIO_DW8 writes zero (unshifted divider & shifted mask), diff --git a/tests/display13_power.cpp b/tests/display13_power.cpp index 0a802c51..20d83cae 100644 --- a/tests/display13_power.cpp +++ b/tests/display13_power.cpp @@ -253,6 +253,16 @@ int main() { assert(ok(acquire(io,s,a,BootHDMIA)) && ok(acquire(io,s,b,bit(Domain::PipeB)|bit(Domain::DDIB)))); assert(ok(suspend(io,s)) && s.suspended);assert(ok(restore(io,s)) && !s.suspended && s.refs[0]==2 && io.regs[DDI]==0xf); assert(ok(release(io,s,b)) && ok(release(io,s,a)));empty(s);} + // The DSI selector runs with PW1 on and before the DDI A request. + for(bool select : {false,true}) { + IO io;State s;Lease a;assert(initialize(io,s,All)); + assert(ok(acquire(io,s,a,BootHDMIA)) && ok(suspend(io,s))); + unsigned calls=0; + auto r=restore(io,s,true,[&](uint32_t ddi) { + ++calls;assert((io.regs[Driver]&3)==3 && !(ddi&3));return select; + }); + assert(calls==1 && ok(r)==select && bool(io.regs[DDI]&2)==select); + } // A state failure rolls back A/PW_A/PW1, never a borrowed B request. {IO io;io.regs[DDI]=0xc;io.failedEnableReg=DDI;io.failedEnable=1; State s;Lease a;assert(initialize(io,s,All));auto r=acquire(io,s,a,BootHDMIA); diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index a54cc3bf..bc65fde3 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -137,6 +137,11 @@ assert(rebuilt.matches(f.p) && rebuilt.afeKHz==811529); } int main(int argc,char **argv){ + for(unsigned selected=0;selected<2;++selected) { + Fixture f;assert(f.stop());f.io.regs[0x6B094]=selected;f.io.writes.clear(); + assert(Restart::start(f.io,f.allocation,1,f.state,true)==bool(selected)); + for(auto reg:f.io.writes)assert(reg!=0x6B094); + } for(unsigned fault=0;fault<7;++fault){Fixture f; assert(!Restart::retainedStopped(f.io,f.source,f.allocation,1,f.state)); assert(f.stop());const auto count=f.io.writes.size();const auto state=f.state; From f289b1dcacc22f69970168242c320b9b9d809936 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:57:31 +0900 Subject: [PATCH 14/46] Program DSI video timings after cold panel initialization before scanout --- WhateverXe/kern_dsi_panel.hpp | 10 ++++++++++ tests/dsi_restart.py | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 4dc39972..4bd49c51 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -184,6 +184,16 @@ template void cycle(IO &io,const Readout &link,const P // Sequences drain before returning; never enable video over queued data. if(port.failed || r.host.pending || !held())return; r.stage=Stage::TranscoderOn; + if(systemWake) { + // After cold link training and INIT_OTP, the video timing bank is + // clocked but scanout is still off (icl_dsi.c, pre_enable step 6c). + // Reissue the timing writes here even when the shadow reads match. + if(!wait(0x7B008,0xC0000000U,0,0) || !wait(0x6B030,1U<<20,1U<<20,0))return; + const uint32_t timings[][2]={{0x6B000,current.htotal},{0x6B008,current.hsync}, + {0x6B00C,current.vtotal},{0x6B014,current.vsync},{0x6B028,0},{0x6B010,current.vtotal}}; + for(const auto &entry:timings) + if(!write(entry[0],entry[1]) || !wait(entry[0],UINT32_MAX,entry[1],0))return; + } if(!write(0x7B008,link.conf) || !wait(0x7B008,0xC0000000U,0xC0000000U,10000))return; if(!sequence(3))return; r.stage=Stage::PlaneOn; diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index bc65fde3..b592f256 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -89,7 +89,7 @@ assert(f.io.regs[0x70180]==0x94000008 && f.io.regs[0x7B008]==0xC0000000); assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); } -void retainedCycle(Fixture &f,unsigned fault=0){ +void retainedCycle(Fixture &f,unsigned fault=0,bool cold=false){ f.io.regs[0x7B008]=0xC0000000;f.io.regs[0x70180]=0x94001000;f.io.regs[0x7019C]=f.io.regs[0x701AC]=0x44E00000; f.io.regs[0x42084]=1U<<30;f.io.regs[0x6001C]=0x0437077F;f.io.regs[0x6B010]=f.io.regs[0x6B00C]; f.io.regs[0xC7204]=15;f.io.regs[0xC4030]=0x84;f.source.mux=f.io.regs[0x164280]; @@ -105,7 +105,16 @@ assert(Restart::stopQuiesced(f.io,f.source,f.p,stopped,f.allocation,1,f.state)); assert(f.state.stopped && !f.io.regs[0x46014] && f.allocation.external[0]==1 && f.allocation.binding[0]==2); Panel::Result panel;Lifecycle lifecycle{f.allocation,f.state}; - Panel::cycle(f.io,f.source,f.p,0x94001000,0x44E00000,panel,lifecycle,&shutdown); + Panel::cycle(f.io,f.source,f.p,0x94001000,0x44E00000,panel,lifecycle,&shutdown,nullptr,cold); + if(cold){ + size_t ready=0,timing=0,video=0; + for(size_t i=count;i=f.io.linkReadyAt);} for(unsigned fault=0;fault<4;++fault){Fixture f;retainedCycle(f,fault);} From ed00b51b9823ccfb61e27bcfd9253422c157e3d6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 02:19:29 +0900 Subject: [PATCH 15/46] Avoid nesting display suspension on a parked DSI dark wake --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 9 ++++ WhateverXe/kern_host.cpp | 50 +++++++++++++++++++++++ WhateverXe/kern_host.hpp | 3 ++ WhateverXe/kern_xe.hpp | 1 + tests/host_publication.py | 20 +++++++-- 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 3e0f7e39..4ab0b011 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -282,6 +282,15 @@ uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { return result; } +bool XE::parkedDSIFramebuffer(IOService *fb) const { + return fb && dsiPowerTest && dsiPagingLayoutKnown && adlnOutputWiring.dsi && + !strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") && + fb->getMetaClass()->getClassSize()>=0x4B90 && adlnTopologyController && + Display13FBMemory::field(fb,0x1D0)==adlnTopologyController && + Display13FBMemory::field(fb,0x1DC)==0 && + !(Display13FBMemory::field(fb,0x199)&0x20); +} + bool XE::failedDSIWakeFor(IOService *fb) const { if(!__atomic_load_n(&dsiWakeDegraded,__ATOMIC_ACQUIRE) || !fb || strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") || diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index cddf1853..dbcd6abe 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -12,6 +12,8 @@ struct FramebufferViewer : public IOFramebuffer { }; static const char *graphicsPath[] { "/System/Library/Extensions/IOGraphicsFamily.kext/IOGraphicsFamily" }; static KernelPatcher::KextInfo graphics { "com.apple.iokit.IOGraphicsFamily",graphicsPath,arrsize(graphicsPath),{true},{},KernelPatcher::KextInfo::Unloaded }; +static const char *accelPath[] { "/System/Library/Extensions/IOAcceleratorFamily2.kext/Contents/MacOS/IOAcceleratorFamily2" }; +static KernelPatcher::KextInfo accelFamily { "com.apple.iokit.IOAcceleratorFamily2",accelPath,arrsize(accelPath),{true},{},KernelPatcher::KextInfo::Unloaded }; static const char *backlightPath[] { "/System/Library/Extensions/AppleBacklight.kext/Contents/MacOS/AppleBacklight" }; static KernelPatcher::KextInfo backlight { "com.apple.driver.AppleBacklight",backlightPath,arrsize(backlightPath),{true},{},KernelPatcher::KextInfo::Unloaded }; void XeHost::init() { @@ -21,6 +23,7 @@ void XeHost::init() { lilu.onPatcherLoadForce([](void *u,KernelPatcher &p){static_cast(u)->processKernel(p);},this); lilu.onKextLoadForce(nullptr,0,[](void *u,KernelPatcher &p,size_t i,mach_vm_address_t a,size_t s){static_cast(u)->processKext(p,i,a,s);},this); lilu.onKextLoadForce(&graphics); + lilu.onKextLoadForce(&accelFamily); lilu.onKextLoadForce(&backlight); xe.init(); XeDataAccess::init(); @@ -75,6 +78,36 @@ void XeHost::processKernel(KernelPatcher &patcher) { } void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(!active)return; + if(accelFamily.loadIndex==index) { + if(!systemWakeRecovery)return; + // Only the first loop of system_will_sleep(false) opens mode changes. + // Leave its subsequent orphan cleanup and the hardware sleep phase native. + const auto sym=[&](const char *name) { + auto v=patcher.solveSymbol(index,name,address,size);patcher.clearError();return v; + }; + const uint8_t before[]={0x48,0x8b,0xbb,0x78,3,0,0,0xe8,0xdc,0xcb,0xfc,0xff,0x41,0xb6,1,0x85,0xc0,0x74,0x37}; + const uint8_t asleep[]={0xf6,0x83,0x78,0x0c,0,0,1}; + const uint8_t nesting[]={0x8b,0x83,0x0c,1,0,0,0x8d,0x48,1,0x89,0x8b,0x0c,1,0,0}; + sleepFBCount=sym("__ZNK21IOAccelDisplayMachine19getFramebufferCountEv"); + sleepFBAt=sym("__ZNK21IOAccelDisplayMachine16getIOFramebufferEj"); + bool ready=false; + if(size>0x412f0 && sleepFBCount==address+0xde8e && sleepFBAt==address+0xdfc0 && + sym("__ZN22IOGraphicsAccelerator217system_will_sleepEib")==address+0x41144 && + !memcmp(reinterpret_cast(address+0x411bc),asleep,sizeof(asleep)) && + !memcmp(reinterpret_cast(address+0xe104),nesting,sizeof(nesting)) && + !memcmp(reinterpret_cast(address+0x412a6),before,sizeof(before))) { + // Pass the owning accelerator instead of just the display machine. + uint8_t code[]={0x48,0x89,0xdf,0xe8,0,0,0,0,0x90,0x90,0x90,0x90}; + const auto distance=static_cast(reinterpret_cast(pendingSleepDisplays)-(address+0x412ae)); + if(distance==static_cast(distance)) { + const auto rel=static_cast(distance);memcpy(code+4,&rel,4); + patcher.routeBlock(address+0x412a6,code,sizeof(code)); + ready=!memcmp(reinterpret_cast(address+0x412a6),code,sizeof(code));patcher.clearError(); + } + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIDarkWakeReady",ready); + return; + } if(backlight.loadIndex==index) { if(backlightEnabled) { KernelPatcher::RouteRequest route("__ZN15AppleIntelPanel10setDisplayEP9IODisplay",wrapPanelSetDisplay,orgPanelSetDisplay); @@ -105,6 +138,23 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a } xe.processKext(patcher,index,address,size); } +uint32_t XeHost::pendingSleepDisplays(void *accelerator) { + auto h=callbackHost; + auto bytes=static_cast(accelerator); + auto machine=*reinterpret_cast(bytes+0x378); + const auto count=reinterpret_cast(h->sleepFBCount)(machine); + if(!count || !(*reinterpret_cast(bytes+0xc78)&1))return count; + const auto depth=*reinterpret_cast(machine+0x10c); + if(depth(h->sleepFBAt)(machine,0); + if(!h->xe.parkedDSIFramebuffer(fb))return count; + // Already parked by a full two-phase notification, with no intervening + // DidWake. The real resume must close the original scope exactly once. + uint32_t record[]={__atomic_add_fetch(&h->repeatedSleep,1,__ATOMIC_RELAXED),count,depth}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIDarkWakeParked",record,sizeof(record)); + return 0; +} + IOReturn XeHost::wrapWSAAAttribute(IOFramebuffer *fb,uint32_t attr,uint32_t value) { // IOGraphics sends WillExitDefer under its global gate before calling the // vendor attribute setter. Reject a failed scanout before queue-idle can diff --git a/WhateverXe/kern_host.hpp b/WhateverXe/kern_host.hpp index da29f4ce..e5e7f046 100644 --- a/WhateverXe/kern_host.hpp +++ b/WhateverXe/kern_host.hpp @@ -9,6 +9,9 @@ class XeHost { static XeHost *callbackHost; XE xe; bool active {},systemWakeRecovery {}; + mach_vm_address_t sleepFBCount {},sleepFBAt {}; + uint32_t repeatedSleep {}; + static uint32_t pendingSleepDisplays(void *); mach_vm_address_t orgWSAAAttribute {}; static IOReturn wrapWSAAAttribute(IOFramebuffer *,uint32_t,uint32_t); bool backlightEnabled {}; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 0ce5434d..37707b57 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -73,6 +73,7 @@ class XE { * @return true if patched anything */ bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); + bool parkedDSIFramebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; private: diff --git a/tests/host_publication.py b/tests/host_publication.py index 311e2557..be8cba00 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -14,7 +14,7 @@ #include #include "kern_xelp_platform.hpp" using mach_vm_address_t=uintptr_t; -struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}void setProperty(const char*,const void*,size_t){}}; std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086;uint8_t revision=0; struct Object {virtual ~Object()=default;}; struct OSData:Object {uint32_t value=0;void *getBytesNoCopy(){return &value;}size_t getLength(){return 4;}}; @@ -41,24 +41,27 @@ templateT solveSymbol(size_t,const char*,mach_vm_address_t,size_t){return T{};} struct RouteRequest {templateRouteRequest(const char*,T,uintptr_t&) {}}; bool routeMultiple(size_t,RouteRequest*,unsigned,mach_vm_address_t,size_t){return true;} + void routeBlock(uintptr_t,const uint8_t*,size_t){} void clearError(){} }; // The optional observer is inactive in this host-publication fixture. namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; -struct {size_t loadIndex=5;}backlight; -using IOReturn=int;using IOFramebuffer=IOPCIDevice; +struct {size_t loadIndex=5;}backlight;struct {size_t loadIndex=7;}accelFamily; +using IOService=IOPCIDevice;using IOReturn=int;using IOFramebuffer=IOPCIDevice; constexpr uint32_t kIOWindowServerActiveAttribute=1,kIOWSAA_StateMask=15,kIOWSAA_Accelerated=1,kIOWSAA_Unaccelerated=0; constexpr IOReturn kIOReturnNotReady=-1; #define FunctionCast(fn,addr) reinterpret_cast(addr) struct XeHost { static XeHost *callbackHost;bool systemWakeRecovery=false; + uintptr_t sleepFBCount=0,sleepFBAt=0;uint32_t repeatedSleep=0;static uint32_t pendingSleepDisplays(void*); uintptr_t orgWSAAAttribute=0; static IOReturn wrapWSAAAttribute(IOFramebuffer*,uint32_t,uint32_t); bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { + IOService *parked=nullptr;bool parkedDSIFramebuffer(IOService *f)const{return parked && f==parked;} IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} @@ -76,6 +79,17 @@ assert(XeHost::wrapWSAAAttribute(&gpu,1,1)==42);exercised.xe.degraded=&gpu; assert(XeHost::wrapWSAAAttribute(&gpu,1,0x101)==kIOReturnNotReady && XeHost::wrapWSAAAttribute(&gpu,1,0)==kIOReturnNotReady); assert(XeHost::wrapWSAAAttribute(&mei,1,1)==42 && XeHost::wrapWSAAAttribute(&gpu,2,1)==42 && XeHost::wrapWSAAAttribute(&gpu,1,3)==42); +alignas(8) uint8_t accel[0xd00]{},machine[0x140]{}; +*reinterpret_cast(accel+0x378)=machine; +exercised.sleepFBCount=reinterpret_cast(+[](void*)->uint32_t{return 3;}); +exercised.sleepFBAt=reinterpret_cast(+[](void *m,unsigned i)->IOService*{assert(i==0);return *static_cast(m);}); +*reinterpret_cast(machine)=&gpu;exercised.xe.parked=&gpu; +for(unsigned asleep:{0U,1U})for(unsigned depth:{0U,2U,3U,9U})for(bool owned:{false,true}){ + *reinterpret_cast(accel+0xc78)=asleep;*reinterpret_cast(machine+0x10c)=depth; + exercised.xe.parked=owned?&gpu:nullptr; + assert(XeHost::pendingSleepDisplays(accel)==(asleep && depth>=3 && owned?0U:3U)); +} +assert(exercised.repeatedSleep==2); const char *names[]={nullptr,"IMEI","HECI","MEI"}; for(unsigned firmware=0;firmware<2;++firmware)for(unsigned me=0;me<5;++me)for(unsigned test=0;test<6;++test){ mei.name=names[me%4];mei.renames=0;gpu.name="GFX0"; From 8a16aae3ca0f76abcffd6c5fd62a24d080fea9ae Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 22:14:23 +0900 Subject: [PATCH 16/46] Restore DSI snapshots and Type-C ownership across shared display power loss --- WhateverXe/kern_display13_fb_irq.hpp | 12 +++++++++ WhateverXe/kern_display13_tc_mode.hpp | 28 ++++++++++++++++--- WhateverXe/kern_dsi_adapter_lifecycle.inc | 33 ++++++++++++++++++++--- WhateverXe/kern_dsi_sleep_image.hpp | 2 +- tests/display13_fb_irq.cpp | 15 +++++++++++ tests/display13_tc_mode.cpp | 22 +++++++++++++++ tests/dsi_quiesce_adapter.py | 17 +++++++++++- 7 files changed, 120 insertions(+), 9 deletions(-) diff --git a/WhateverXe/kern_display13_fb_irq.hpp b/WhateverXe/kern_display13_fb_irq.hpp index 48be1ac3..2dc2cad6 100644 --- a/WhateverXe/kern_display13_fb_irq.hpp +++ b/WhateverXe/kern_display13_fb_irq.hpp @@ -263,6 +263,18 @@ template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t if(!(io.read(Master)&Enable) || !(io.read(GfxMaster)&Enable))return fail(io,s,Status::Readback); s.enabled=true;return Status::Complete; } +// Caller has proved total display power loss and restored its power/clock +// dependencies. Cached armed banks no longer describe hardware. Reinstall +// both pipe interrupts and connector detection, not just the live DSI bank. +template Status restoreAfterPowerLoss(IO &io,State &s,uint32_t allowed) { + if(!s.begun || s.faulted)return Status::Unavailable; + const auto actual=readActivePipes(io,allowed,s.transcoders); + if(actual.status!=Status::Complete)return actual.status; + io.write(Master,0); + if(io.read(Master)&Enable)return fail(io,s,Status::Readback); + s.enabled=false;s.active=0; + return enable(io,s,s.excludedPCH,NativeGT,actual.active,s.ports,s.typeC); +} template Status disable(IO &io,State &s) { if(!s.begun || !io.readable(Master))return Status::Unavailable; io.write(Master,0);s.enabled=false; diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp index 3b7b1fa0..3866d18b 100644 --- a/WhateverXe/kern_display13_tc_mode.hpp +++ b/WhateverXe/kern_display13_tc_mode.hpp @@ -12,7 +12,7 @@ struct Mode { Display13Power::Result power;uint32_t native {}; }; uintptr_t controller {},object {};unsigned ddi {}; - Phase phase {Phase::Idle};bool disconnectPending {}; + Phase phase {Phase::Idle};bool disconnectPending {},restorePending {}; Ownership ownership; unsigned selectedMode {};Lanes lanes; // ADLP: register/lane access -> ownership -> AUX cold block. @@ -22,6 +22,11 @@ struct Mode { Display13Power::Lease accessLease,auxLease; static uint32_t accessDomains() {return Display13Power::bit(Display13Power::Domain::PW2);} Mode()=default;Mode(const Mode&)=delete;Mode& operator=(const Mode&)=delete; + // Only a caller which observed collapsed display power may invalidate + // the physical ownership witness. Logical leases and native mode survive. + void notePowerLoss() { + if(phase==Phase::Active) {restorePending=true;ownership.active=false;} + } static uint32_t domains(unsigned d) { Port p;return Port::resolve(d,p) ? Display13Power::ddiDomain(d)| Display13Power::auxDomain(d)|Display13Power::bit(Display13Power::Domain::PW2) : 0; @@ -34,9 +39,24 @@ struct Mode { if(phase==Phase::Cleanup){r.status=Status::InUse;return r;} if(phase==Phase::Active) { if(wanted!=selectedMode || io.nativeMode()!=selectedMode || - (wanted==3 && !lanes.unchanged(io,ownership.port))){r.status=Status::Readback;return r;} + (wanted==3 && !restorePending && !lanes.unchanged(io,ownership.port))){r.status=Status::Readback;return r;} r.phy=ownership.acquire(io,owner,identity,d,false,wanted); - r.status=r.phy.status;if(r.status==Status::Complete)disconnectPending=false; + r.status=r.phy.status; + if(r.status==Status::Complete && restorePending) { + // Restore the retained AUX leaf only after PHY ownership. + // A temporary reference rearms hardware without replacing or + // double-counting the native mode's original lease. + Display13Power::Lease temporary; + r.power=io.acquireDomains(temporary,Display13Power::auxDomain(d)); + if(r.power.status==Display13Power::Status::Complete)r.power=io.releaseDomains(temporary); + if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} + if(wanted==3) { + lanes=Lanes::capture(io,ownership.port); + if(!lanes.valid()){r.status=Status::NotReady;return r;} + } + restorePending=false; + } + if(r.status==Status::Complete)disconnectPending=false; return r; } if(io.nativeMode()!=0){r.status=Status::NeedsAdoption;return r;} @@ -82,7 +102,7 @@ struct Mode { r.power=io.releaseDomains(accessLease); if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} } - controller=object=0;ddi=selectedMode=0;lanes={};phase=Phase::Idle;disconnectPending=false;r.status=Status::Complete;return r; + controller=object=0;ddi=selectedMode=0;lanes={};phase=Phase::Idle;disconnectPending=restorePending=false;r.status=Status::Complete;return r; } }; } diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 4ab0b011..95f87377 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -307,6 +307,24 @@ void XE::finishDSISystemWake(IOService *internal) { if(!dsiFramebufferEvent)return; auto controller=Display13FBMemory::field(internal,0x1D0); if(!controller || controller!=adlnTopologyController || controller->getMetaClass()->getClassSize()<0x1C20)return; + // The DSI primary restored the common wells. Reclaim existing TC modes + // before releasing parked display clients; new/disconnected ports still + // follow the normal probe and native modeset path. Enter the mode adapter + // without the FB/shared locks; it owns its existing handshake locking. + auto &c=display13FBPower; + uint32_t ports[9]={1}; + for(unsigned tc=0;c.lock && tc<4;++tc) { + IOLockLock(c.lock); + auto port=c.controller==controller && c.tcMode[tc].restorePending ? c.tcObject[tc] : nullptr; + const auto wanted=c.tcMode[tc].selectedMode; + if(port)port->retain(); + IOLockUnlock(c.lock); + if(port) { + ports[1+2*tc]=wanted;ports[2+2*tc]=wrapDisplay13TCMode(port,wanted); + port->release(); + } + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeTC",ports,sizeof(ports)); using Event=uint32_t (*)(IOService *,int,void *); const auto event=reinterpret_cast(dsiFramebufferEvent); uint32_t events[12]={1,dsiPagingLayoutKnown}; @@ -787,8 +805,10 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); if(retain && complete) { power.sleeping=true; + // These are pipe A / DSI-owned registers. A peer may still be on at + // this point: its later power callback must not veto our snapshot. dsiSleepImage.captured=display13ClockVariant()==Display13Clock::Variant::RPLU && - c.dbuf.selected==1 && !irq.state.active; + (c.dbuf.selected&1) && !(irq.state.active&1); if(dsiSleepImage.captured) { for(unsigned i=0;igetMetaClass()->getClassSize()<0x1C20 || @@ -889,6 +910,10 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { if(!dsiSleepImage.captured || !bindingOwned() || !io.readable(Display13Power::Driver) || io.read(Display13Power::Driver)!=0)return true; coldStage=1; + for(unsigned tc=0;tc<4;++tc) { + c.tcMode[tc].notePowerLoss(); + c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; + } Display13FBPowerIO wells{c.map}; c.bridge.state.suspended=true; // Intel DSI_IO_MODECTL must select MIPI before the Combo PHY @@ -1025,7 +1050,9 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { irqReceipt[7+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)+4); irqReceipt[8+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)-4); } - const auto status=Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes); + const auto status=coldStage==5 ? + Display13FBIRQ::restoreAfterPowerLoss(irqIO,irq.state,irq.admittedPipes) : + Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes); irqReceipt[15]=static_cast(status);irqReceipt[16]=irq.state.active; irqReceipt[17]=static_cast(irq.state.firstFailure); irqReceipt[18]=irq.state.failureRegister;irqReceipt[19]=irq.state.failureBits; diff --git a/WhateverXe/kern_dsi_sleep_image.hpp b/WhateverXe/kern_dsi_sleep_image.hpp index fd335b79..b4389ea1 100644 --- a/WhateverXe/kern_dsi_sleep_image.hpp +++ b/WhateverXe/kern_dsi_sleep_image.hpp @@ -2,7 +2,7 @@ #include namespace XELP { namespace DSI { struct SystemSleepImage { -// Explicit writable configuration of a parked internal-only pipe. DDB and WM +// Explicit writable configuration owned by the parked DSI pipe. DDB and WM // precede plane arming. No interrupt identity, status or frame counters. static constexpr uint32_t config[] = { 0x6001C,0x45270,0x7027C,0x7017C, diff --git a/tests/display13_fb_irq.cpp b/tests/display13_fb_irq.cpp index 9cfa073d..2e08af37 100644 --- a/tests/display13_fb_irq.cpp +++ b/tests/display13_fb_irq.cpp @@ -48,6 +48,21 @@ struct IO { } }; int main() { + // Cold wake erased both peer IRQ banks and HPD programming. Restore only + // actually restarted pipes, while re-enabling both selected connectors. + {IO io;State s;s.transcoders.dsi0Pipe=0; + io.r[0x70008]=0;io.r[0x7B008]=0xC0000000;io.r[0x6B400]=0x80000006; + io.r[0x71008]=0xC0000000; + assert(enable(io,s,0,NativeGT,3,0x18,0x18)==Status::Complete); + IO cold;cold.r[0x70008]=0;cold.r[0x7B008]=0xC0000000;cold.r[0x6B400]=0x80000006; + cold.r[DDIControl]=4;cold.r[HPDBank]=1; // unrelated masked TBT event + assert(restoreAfterPowerLoss(cold,s,7)==Status::Complete && s.active==1 && s.enabled); + assert((cold.r[NorthControl]&0x88)==0x88 && (cold.r[HPDBank+4]&0x30000)==0x30000); + assert(!(cold.r[HPDBank-4]&0x30000) && cold.r[HPDBank]==1); + assert((cold.r[DDIControl]&12)==4 && !(cold.r[PipeIIR(1)+4]&PipeOwned)); + cold.r[0x71008]=0xC0000000; + assert(extendActivePipes(cold,s,7)==Status::Complete && s.active==3); + } // DSI0 can feed A/B/C. IRQ/frame/plane banks follow the pipe, while // control/function come from DSI0. Keep external A active for B/C cases. for(unsigned pipe=0;pipe<3;++pipe) { diff --git a/tests/display13_tc_mode.cpp b/tests/display13_tc_mode.cpp index 327f72db..045c3ae6 100644 --- a/tests/display13_tc_mode.cpp +++ b/tests/display13_tc_mode.cpp @@ -43,6 +43,21 @@ struct IO { bool quiescent(uintptr_t,uintptr_t,unsigned d){return quiet && !(regs[0x64000+0x100*d]&0x80000000U);} }; int main() { + // System power loss keeps leases but erases the PHY and AUX request. + // Reclaim PHY before AUX without accumulating logical references. + for(unsigned d=3;d<=6;++d) { + IO io(d);Mode m;assert(m.connect(io,1,2,d).status==Status::Complete); + const auto before=io.power; + io.regs[0x45404]=io.regs[0x45454]=io.regs[0x45444]=0; + io.regs[io.port.buffer]=0;m.notePowerLoss();io.power.suspended=true; + assert(Display13Power::restore(io,io.power,true).status==Display13Power::Status::Complete); + assert(!m.ownership.active && m.restorePending && !(io.regs[0x45444]&0xc0)); + assert(m.connect(io,1,2,d).status==Status::Complete); + assert(m.ownership.active && !m.restorePending); + for(unsigned n=0;n Date: Wed, 4 Nov 2026 22:26:12 +0900 Subject: [PATCH 17/46] Restore retained peer DDB allocations after display power loss --- WhateverXe/kern_display13_dbuf.hpp | 44 ++++++++++++++++++++--- WhateverXe/kern_dsi_adapter_lifecycle.inc | 18 ++++++++++ tests/display13_dbuf.cpp | 32 +++++++++++++++++ tests/display13_dbuf_setup_fixture.cpp | 14 ++++++++ 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_display13_dbuf.hpp b/WhateverXe/kern_display13_dbuf.hpp index 5441d9b9..b42aa044 100644 --- a/WhateverXe/kern_display13_dbuf.hpp +++ b/WhateverXe/kern_display13_dbuf.hpp @@ -39,6 +39,7 @@ struct State { struct Released {bool valid {};Range primary,cursor;} released[3]; uint32_t capacity {}; // Optional board capacity: reserve B/C banks before either path attaches. uint32_t selected {},updating {},unretired {}; // Updating is a per-pipe transaction mask. + uint32_t restorePending {}; // Retained allocations whose hardware was lost in system sleep. }; inline Status fail(State &s,Status status) {s.faulted=true;return status;} inline uint32_t tracked(const State &s) { @@ -296,6 +297,37 @@ template Status power(IO &io,State &s,uint32_t wanted,uint32_t consume } s.consumers=consumers;return Status::Complete; } +// The caller records total display power loss, with all timing engines off. +// Restore only an existing allocation, before its native mode/WM programming. +// An unpowered bank stays pending until its regular native PG acquisition. +template Status restoreAllocation(IO &io,State &s,unsigned index) { + if(s.faulted)return Status::Faulted; + if(!s.begun || index>=3)return Status::Unavailable; + const auto bit=1U< Status bindOffPipe(IO &io,State &s,unsigned index) { if(!s.begun)return Status::Unavailable; if(index>=3 || s.joined)return fail(s,Status::Topology); const auto bit=1U< Status bindOffPipe(IO &io,State &s,unsigned index) { } s.pipe[index]=next;s.released[index]={};s.selected|=bit;syncMasks(s);return Status::Complete; } -// Read-only admission for a retained, powered allocation. Global startup -// preflights every eligible pipe before it makes any shared slice request. +// Admission for a retained, powered allocation. Restore a proven lost bank +// first; ordinary engine restart remains read-only here. Global startup +// checks every eligible pipe before it makes any shared slice request. template Status resumeProof(IO &io,State &s,unsigned index) { if(s.faulted)return Status::Faulted; if(!s.begun || index>=3 || !(s.selected&(1U< Status detach(IO &io,State &s,unsigned index) { const auto &a=s.pipe[index]; if(a.consumers || a.unretired || (s.updating&(1U<(slices); if(slices!=Display13DBUF::Status::Complete)return false; if(Display13DBUF::tracker(dbuf,c.dbuf)!=Display13DBUF::Status::Complete)return false; + // A peer's logical DDB ownership survives sleep, but its shadow + // registers do not. Restore those stopped banks before the native + // default/optimized allocation checks compare them with the owner. + uint32_t allocations[13]={1,c.dbuf.selected,c.dbuf.restorePending}; + bool allocationsRestored=true; + for(unsigned pipe=0;pipe<3;++pipe)if(c.dbuf.restorePending&(1U<(status); + if(status!=Display13DBUF::Status::Complete){allocationsRestored=false;break;} + } + allocations[12]=c.dbuf.restorePending; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeDBUF",allocations,sizeof(allocations)); + if(!allocationsRestored)return false; coldStage=4; for(unsigned i=0;i Date: Wed, 4 Nov 2026 22:35:04 +0900 Subject: [PATCH 18/46] Restore DDB through real MMIO adapter with retained fetch credits --- WhateverXe/kern_display13_dbuf.hpp | 27 ++++++++++++- WhateverXe/kern_dsi_adapter_lifecycle.inc | 2 + WhateverXe/kern_xe.cpp | 5 +++ tests/display13_dbuf_setup.py | 48 +++++++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_display13_dbuf.hpp b/WhateverXe/kern_display13_dbuf.hpp index b42aa044..ea7edf41 100644 --- a/WhateverXe/kern_display13_dbuf.hpp +++ b/WhateverXe/kern_display13_dbuf.hpp @@ -40,6 +40,7 @@ struct State { uint32_t capacity {}; // Optional board capacity: reserve B/C banks before either path attaches. uint32_t selected {},updating {},unretired {}; // Updating is a per-pipe transaction mask. uint32_t restorePending {}; // Retained allocations whose hardware was lost in system sleep. + uint32_t creditSaved {},credit[3] {}; // Writable MBUS fetch-credit fields of selected, powered pipes. }; inline Status fail(State &s,Status status) {s.faulted=true;return status;} inline uint32_t tracked(const State &s) { @@ -297,6 +298,23 @@ template Status power(IO &io,State &s,uint32_t wanted,uint32_t consume } s.consumers=consumers;return Status::Complete; } +constexpr uint32_t CreditMask=0x01FFDF0F; +// Snapshot while the display owner lock still protects the selected pipes. +// Never read an unpowered bank. Native later enable restores arbitration +// slots separately; these MBUS credits also need the pre-sleep allocation. +template Status captureCredits(IO &io,State &s) { + if(s.faulted)return Status::Faulted; + if(!s.begun)return Status::Unavailable; + s.creditSaved=0; + const auto wells=io.read(0x45404); + if(wells==UINT32_MAX)return fail(s,Status::Power); + for(unsigned p=0;p<3;++p)if((s.selected&(1U< Status restoreAllocation(IO &io,State &s,unsigned index) { if(io.read(addresses[n])!=values[n])io.write(addresses[n],values[n]); if(io.read(addresses[n])!=values[n])return fail(s,Status::Readback); } + if(s.creditSaved&bit) { + const auto reg=0x7003C+bank,before=io.read(reg); + if(before==UINT32_MAX)return fail(s,Status::Readback); + const auto value=(before&~CreditMask)|s.credit[index]; + if(before!=value)io.write(reg,value); + if(io.read(reg)!=value)return fail(s,Status::Readback); + } s.restorePending&=~bit;return Status::Complete; } // Add a native bound path only after the caller proves its FB/plane identity. @@ -452,7 +477,7 @@ template Status detach(IO &io,State &s,unsigned index) { if(a.consumers || a.unretired || (s.updating&(1U<(map->getVirtualAddress()+reg);} void write(uint32_t reg,uint32_t value) { for(auto allowed:Display13DBUF::regs)if(reg==allowed) {*reinterpret_cast(map->getVirtualAddress()+reg)=value;return;} + // Cold restore writes retained primary/cursor allocation and fetch + // credits after restoreAllocation proves this bank is stopped/owned. + for(unsigned pipe=0;pipe<3;++pipe)if(reg==0x7027C+0x1000*pipe || reg==0x7017C+0x1000*pipe || reg==0x7003C+0x1000*pipe) { + *reinterpret_cast(map->getVirtualAddress()+reg)=value;return; + } } void delayUs(unsigned us) {IODelay(us);} }; diff --git a/tests/display13_dbuf_setup.py b/tests/display13_dbuf_setup.py index e6b554c6..c25b037c 100644 --- a/tests/display13_dbuf_setup.py +++ b/tests/display13_dbuf_setup.py @@ -15,3 +15,51 @@ subprocess.run(['clang++', '-std=c++17', '-O1', '-g', '-fsanitize=address,undefined', '-I' + str(r / 'WhateverXe'), str(p / 'test.cpp'), '-o', str(p / 'test')], check=True) subprocess.run([str(p / 'test')], check=True) + +# The ownership fixture above emulates hardware power acknowledgements. Also +# exercise the real MMIO writer: a permissive fake must not hide an adapter +# which silently drops a newly required register write. +adapter = re.search(r'struct Display13FBDBUFIO \{.*?\n\};', s, re.S).group() +code = r''' +#include "kern_display13_dbuf.hpp" +#include +#include +struct IOMemoryMap { + std::vector words=std::vector(0x1000000/4); + uintptr_t getVirtualAddress(){return reinterpret_cast(words.data());} + unsigned getLength(){return 0x1000000;} + uint32_t ®(unsigned r){return words[r/4];} +}; +void IODelay(unsigned){} +ADAPTER +int main(){ + using namespace Display13DBUF; + for(unsigned pipe=0;pipe<3;++pipe){ + IOMemoryMap map;Display13FBDBUFIO io{&map};State s; + s.begun=true;s.selected=s.restorePending=1U< Date: Wed, 4 Nov 2026 22:45:36 +0900 Subject: [PATCH 19/46] Allow gated native SW callback to release its stopped pipe well --- WhateverXe/kern_xe.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ac1b7998..463a651d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10586,7 +10586,7 @@ bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { } uint64_t record[32] {},check[21] {2,operation,domain},auxRecord[10] {},context[24] {},gateEvidence[8] {}; Display13Power::Result result; IOWorkLoop *acquiredGate=nullptr; - Display13FBIRQ::PowerReleaseScope releaseScope;uint64_t irqRelease[12] {}; + Display13FBIRQ::PowerReleaseScope releaseScope;uint64_t irqRelease[16] {}; bool previousQuiescing=false;uint32_t previousBlocked=0; bool disableEngine=false; IOLockLock(c.lock); @@ -10667,9 +10667,21 @@ bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { irqRelease[0]=1;irqRelease[1]=released;irqRelease[2]=affected;irqRelease[3]=irq.handlers; irqRelease[4]=irq.scopes.display;irqRelease[5]=irq.softwareHandlers; irqRelease[6]=queued;irqRelease[7]=aux;irqRelease[8]=hpd; + // Native short-pulse/hotplug handlers get PG before AUX work + // and put it before returning to SWInterruptHandler. That same + // gated stack is the releaser, not a competing SW consumer. + // Permit only stopped leaf pipe wells: shared PW1/PW2 teardown + // still requires the entire software handler to have returned. + constexpr uint32_t pipeWells=Display13Power::bit(Display13Power::Domain::PipeA)| + Display13Power::bit(Display13Power::Domain::PipeB)|Display13Power::bit(Display13Power::Domain::PipeC); + const bool ownSoftware=operation==2 && !(released&~pipeWells) && gateEvidence[4] && + irq.softwareHandlers==1 && irq.softwareThread==reinterpret_cast(current_thread()); + const unsigned competingSoftware=ownSoftware ? 0 : irq.softwareHandlers; + irqRelease[12]=ownSoftware;irqRelease[13]=competingSoftware; + irqRelease[14]=irq.softwareThread==reinterpret_cast(current_thread());irqRelease[15]=gateEvidence[4]; const auto status=!irq.quiescing && irq.scopes.total==irq.handlers ? Display13FBIRQ::beginPowerRelease(irqIO,irq.state,releaseScope,released,affected, - irq.scopes.display,irq.softwareHandlers,queued,aux,hpd) : Display13FBIRQ::Status::Unavailable; + irq.scopes.display,competingSoftware,queued,aux,hpd) : Display13FBIRQ::Status::Unavailable; irqRelease[9]=static_cast(status);releaseAllowed=status==Display13FBIRQ::Status::Complete; if(releaseScope.held) {irq.quiescing=true;irq.blockedPipes=affected;} } else { @@ -10720,6 +10732,7 @@ bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { if(display13TraceEnabled)IORegistryEntry::getRegistryRoot()->setProperty("N97FBPowerContext",context,sizeof(context)); if(gateEvidence[0])IORegistryEntry::getRegistryRoot()->setProperty("N97FBPowerReleaseGate",gateEvidence,sizeof(gateEvidence)); if(irqRelease[0])IORegistryEntry::getRegistryRoot()->setProperty("N97FBPowerReleaseIRQ",irqRelease,sizeof(irqRelease)); + if(irqRelease[12])IORegistryEntry::getRegistryRoot()->setProperty("N97FBOwnSoftwarePGRelease",irqRelease,sizeof(irqRelease)); IORegistryEntry::getRegistryRoot()->setProperty("N97FBAUXPower",auxRecord,sizeof(auxRecord)); if(check[14])IORegistryEntry::getRegistryRoot()->setProperty("N97FBPowerReleaseMap",check,sizeof(check)); if(record[4]!=static_cast(Display13Power::Status::Complete) && record[7]==1) From 503e387b23132f3d3b09bb4f851922a6071d6ce0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:01:54 +0900 Subject: [PATCH 20/46] Observe peer framebuffer mode power and bounded flip outcomes --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 6 +++-- WhateverXe/kern_dsi_adapter_modes.inc | 29 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 11e310fb..29814a10 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -74,9 +74,11 @@ int XE::beginDSIFramebufferWriter(IOService *fb) { } uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t options,void *params) { - const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const int token=beginDSIFramebufferWriter(fb);if(token<0) {recordDSIClient(fb,13,options,kIOReturnNotReady);return kIOReturnNotReady;} const auto result=FunctionCast(wrapDSIFlipTransaction,callbackXE->orgDSITransaction[0])(fb,transaction,options,params); - endDSINativeWriter(token);return result; + endDSINativeWriter(token); + const uint64_t flags=params ? Display13FBMemory::field(params,0x34) : 0; + recordDSIClient(fb,13,(options&UINT32_MAX)|(flags<<32),result);return result; } uint32_t XE::wrapDSIGammaTransaction(IOService *fb,void *transaction,void *params) { diff --git a/WhateverXe/kern_dsi_adapter_modes.inc b/WhateverXe/kern_dsi_adapter_modes.inc index fe7a7076..ae7309e2 100644 --- a/WhateverXe/kern_dsi_adapter_modes.inc +++ b/WhateverXe/kern_dsi_adapter_modes.inc @@ -102,6 +102,35 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad // observe calls before the fixed-mode owner is bound; do not treat the trace // as admission or invoke any driver/hardware operation while collecting it. void XE::recordDSIClient(IOService *fb,unsigned event,uint64_t argument,uint32_t result) { + // Observe peer presentation at the actual framebuffer API boundary. + // Bounded flips per mode/power transition; no alternate rendering path. + if(callbackXE->adlnOutputWiring.dsi && fb && fb->getMetaClass()->getClassSize()>=0x4B90 && + Display13FBMemory::field(fb,0x1D0)==callbackXE->adlnTopologyController) { + const auto index=Display13FBMemory::field(fb,0x1DC); + if(index>0 && index<3 && (event==6 || event==9 || event==10 || event==11 || event==13)) { + DSIModeScope scope(fb);if(!scope.lock)return; + static uint64_t counts[3][14] {}; + if(event==6 || event==9)counts[index][13]=0; + const auto count=++counts[index][event]; + if(event==13 && count>8 && (!result || count>32))return; + auto path=Display13FBMemory::singlePath(fb); + const bool pathReady=path && path->getMetaClass()->getClassSize()>=0x3730; + uint64_t words[]={1,event,count,argument,result,index, + Display13FBMemory::field(fb,0x1E0),Display13FBMemory::field(fb,0x199), + Display13FBMemory::field(fb,0x4004),Display13FBMemory::field(fb,0x4284), + Display13FBMemory::field(fb,0x4270),Display13FBMemory::field(fb,0x4A00), + pathReady,pathReady ? Display13FBMemory::field(path,0x3640) : UINT32_MAX, + pathReady ? Display13FBMemory::field(path,0x3644) : UINT32_MAX, + pathReady ? Display13FBMemory::field(path,0x170) : UINT32_MAX, + pathReady ? Display13FBMemory::field(path,0x32A5) : UINT32_MAX, + pathReady && Display13FBMemory::field(path,0x32B0)==fb, + Display13FBMemory::field(fb,0x4250),Display13FBMemory::field(fb,0x4254)}; + const char *name=event==6 ? "Mode" : event==9 ? "Power" : event==10 ? "Current" : event==11 ? "Online" : "Flip"; + char key[64];snprintf(key,sizeof(key),"WEXPeer%u%s",index,name); + IORegistryEntry::getRegistryRoot()->setProperty(key,words,sizeof(words)); + return; + } + } if(!callbackXE->adlnOutputWiring.dsi || event>=13 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1DC))return; DSIModeScope scope(fb);if(!scope.lock)return; From 01d6031dd24f154328e3621f3719c892dee4901e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:11:47 +0900 Subject: [PATCH 21/46] Park idle HPD interrupts before full display power suspend --- WhateverXe/kern_xe.cpp | 53 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 463a651d..165643f1 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10161,27 +10161,66 @@ void XE::wrapAlderLakeNPowerOverride(void *power,bool enable) { // override(true) is only reached by the donor's simulator backend; our // physical object has simulator=0. Do not force unrelated DDI/AUX domains. if(!enable)wrapAlderLakeNEngineDisable(power); - uint64_t record[32] {};Display13Power::Result result;IOLockLock(c.lock); + uint64_t record[32] {},suspendIRQ[24] {};Display13Power::Result result; + IOWorkLoop *acquiredGate=nullptr;IOLockLock(c.lock); if(callbackXE->display13FBLifecycleHooksReady && !enable && c.bridge.begun && !c.bridge.state.faulted && !c.engineEnabled && c.clockStopped && !c.dbuf.owned && !Display13WM::busyPipes(c.pipe)) { if(c.bridge.state.suspended)result.status=Display13Power::Status::Complete; else { auto &irq=display13FBIRQ;Display13FBIRQIO irqIO {irq.base}; - const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const bool quiet=!irq.quiescing && Display13FBIRQ::quiescent(irqIO,irq.state,c.bridge.state.allowed,irq.handlers); - if(quiet) {irq.quiescing=true;irq.blockedPipes=7;} - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - if(quiet) { - Display13FBPowerIO io {c.map};result=c.bridge.suspend(io); + const bool multi=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring); + bool gated=!multi; + suspendIRQ[0]=1;suspendIRQ[1]=multi;suspendIRQ[2]=c.bridge.state.allowed; + if(multi && c.workLoop && Display13FBMemory::field(c.controller,0xE00)==c.workLoop && + callbackXE->adlnWorkLoopInGate && callbackXE->adlnWorkLoopTryClose && callbackXE->adlnWorkLoopOpen) { + suspendIRQ[3]=reinterpret_cast(callbackXE->adlnWorkLoopInGate)(c.workLoop); + if(suspendIRQ[3])gated=true; + else if(reinterpret_cast(callbackXE->adlnWorkLoopTryClose)(c.workLoop)) { + acquiredGate=c.workLoop;suspendIRQ[4]=1;gated=true; + } + } + suspendIRQ[5]=gated; + Display13FBIRQ::PowerReleaseScope scope;bool quiet=false; + if(gated && irq.lock && irq.base && adlnIRQOwner(static_cast(c.controller))) { + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + suspendIRQ[6]=irq.handlers;suspendIRQ[7]=irq.scopes.display;suspendIRQ[8]=irq.softwareHandlers; + suspendIRQ[9]=irq.state.enabled;suspendIRQ[10]=irqIO.read(Display13FBIRQ::Master); + suspendIRQ[11]=irq.state.active;suspendIRQ[12]=irq.quiescing; + for(unsigned i=0;i<3;++i)suspendIRQ[13+i]=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE08+4*i),__ATOMIC_ACQUIRE); + // Engine idle keeps HPD alive. Full suspend must explicitly park + // that IRQ ownership, not reject the master enable we just armed. + // Require all consumers and competing work to retire first. + if(multi) { + const auto status=!irq.quiescing && irq.scopes.total==irq.handlers ? + Display13FBIRQ::beginPowerRelease(irqIO,irq.state,scope,c.bridge.state.allowed,7, + irq.scopes.display,irq.softwareHandlers,suspendIRQ[13],suspendIRQ[14],suspendIRQ[15]) : Display13FBIRQ::Status::Unavailable; + suspendIRQ[16]=static_cast(status);quiet=status==Display13FBIRQ::Status::Complete; + } else quiet=!irq.quiescing && Display13FBIRQ::quiescent(irqIO,irq.state,c.bridge.state.allowed,irq.handlers); + if(quiet || scope.held) {irq.quiescing=true;irq.blockedPipes=7;} + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + suspendIRQ[17]=quiet;suspendIRQ[18]=c.bridge.auxBusy(); + if(quiet) {Display13FBPowerIO io {c.map};result=c.bridge.suspend(io);} + if(quiet || scope.held) { const auto done=IOSimpleLockLockDisableInterrupt(irq.lock); + suspendIRQ[19]=scope.parked; + if(scope.held && result.status!=Display13Power::Status::Complete) + suspendIRQ[20]=static_cast(Display13FBIRQ::endPowerRelease(irqIO,irq.state,scope)); + // Successful suspend consumes the scope with DE masked. Re-arming + // HPD here would undo suspend when firmware still owns PW1. + scope.held=false;suspendIRQ[21]=irq.state.enabled; irq.quiescing=false;irq.blockedPipes=0;IOSimpleLockUnlockEnableInterrupt(irq.lock,done); } + suspendIRQ[22]=static_cast(result.status);suspendIRQ[23]=c.bridge.state.suspended; } } if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); display13FBPowerRecord(record,8,enable,result);IOLockUnlock(c.lock); + if(acquiredGate)reinterpret_cast(callbackXE->adlnWorkLoopOpen)(acquiredGate); + if(suspendIRQ[0])IORegistryEntry::getRegistryRoot()->setProperty("N97FBSuspendIRQ",suspendIRQ,sizeof(suspendIRQ)); adlnPublishPowerRecord("N97FBPowerOverride",record); } + void XE::wrapAlderLakeNPowerRestore(void *power,bool waking) { auto &c=display13FBPower;if(!c.lock || power!=c.power || !c.controller)return; uint64_t record[32] {};Display13Power::Result result;IOLockLock(c.lock); From beab0f722a84f9af95b3e46108ed7e2619f990ed Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Wed, 4 Nov 2026 23:19:25 +0900 Subject: [PATCH 22/46] Restore retained DSI state after native global power wake --- WhateverXe/kern_dsi_adapter_lifecycle.inc | 138 ++++++++++++---------- WhateverXe/kern_xe.cpp | 48 +++++++- WhateverXe/kern_xe.hpp | 1 + 3 files changed, 120 insertions(+), 67 deletions(-) diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 29814a10..6d7fa35f 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -806,7 +806,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool power.host.error==XELP::DSI::Restart::Error::None && dsiPanelStop.complete && qualified() && dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); if(retain && complete) { - power.sleeping=true; + power.sleeping=true;dsiSharedPowerRestored=false; // These are pipe A / DSI-owned registers. A peer may still be on at // this point: its later power callback must not veto our snapshot. dsiSleepImage.captured=display13ClockVariant()==Display13Clock::Variant::RPLU && @@ -912,74 +912,82 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { uint32_t dbox[3]={},phyPower[4]={}; const auto restoreLostDisplay=[&]() { if(!dsiSleepImage.captured || !bindingOwned() || !io.readable(Display13Power::Driver) || - io.read(Display13Power::Driver)!=0)return true; - coldStage=1; - c.dbuf.restorePending|=c.dbuf.selected; - for(unsigned tc=0;tc<4;++tc) { - c.tcMode[tc].notePowerLoss(); - c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; - } - Display13FBPowerIO wells{c.map}; - c.bridge.state.suspended=true; - // Intel DSI_IO_MODECTL must select MIPI before the Combo PHY - // power request. PW1 and the pipe are already accessible here. - const auto selectDSI=[&](uint32_t ddi) { - constexpr uint32_t mode=0x6B094; - phyPower[0]=1;phyPower[1]=ddi; - if(!io.readable(mode))return false; - phyPower[2]=io.read(mode); - if(phyPower[2]==UINT32_MAX)return false; - if(!(phyPower[2]&1)) { - if(ddi&3)return false; - io.write(mode,phyPower[2]|1); - } - phyPower[3]=io.read(mode); - return phyPower[3]!=UINT32_MAX && (phyPower[3]&1); - }; - const auto restoredPower=Display13Power::restore(wells,c.bridge.state,true,selectDSI); - coldValue=static_cast(restoredPower.status);coldReg=restoredPower.domain; - if(restoredPower.status!=Display13Power::Status::Complete || !owned())return false; - coldStage=2; - if(io.read(0x7B008)&0xC0000000U || io.read(0x70008)&0xC0000000U || - io.read(0x71008)&0xC0000000U || io.read(0x72008)&0xC0000000U || - io.read(0x70180)&(1U<<31))return false; - Display13FBClockIO cdclk{c.map}; - const auto hz=c.clock.nativeHz(); - if(!hz)return false; - const auto change=Display13Clock::set(cdclk,hz,{true,true,0},display13ClockVariant()); - coldValue=static_cast(change.status); - if(change.status!=Display13Clock::ChangeStatus::Complete)return false; - coldStage=3; + (io.read(Display13Power::Driver)!=0 && !dsiSharedPowerRestored))return true; auto store=[&](uint32_t reg,uint32_t v) { coldReg=reg; *reinterpret_cast(c.map->getVirtualAddress()+reg)=v; coldValue=io.read(reg);return coldValue==v; }; - if(io.read(0x4438C)!=c.dbuf.mbus && !store(0x4438C,c.dbuf.mbus))return false; - Display13FBDBUFIO dbuf{c.map}; - for(unsigned i=0;i<4;++i)if(!(dbuf.read(Display13DBUF::regs[i])&Display13DBUF::Request)) - c.dbuf.owned&=~(1U<(slices); - if(slices!=Display13DBUF::Status::Complete)return false; - if(Display13DBUF::tracker(dbuf,c.dbuf)!=Display13DBUF::Status::Complete)return false; - // A peer's logical DDB ownership survives sleep, but its shadow - // registers do not. Restore those stopped banks before the native - // default/optimized allocation checks compare them with the owner. - uint32_t allocations[13]={1,c.dbuf.selected,c.dbuf.restorePending}; - bool allocationsRestored=true; - for(unsigned pipe=0;pipe<3;++pipe)if(c.dbuf.restorePending&(1U<(status); - if(status!=Display13DBUF::Status::Complete){allocationsRestored=false;break;} + if(!dsiSharedPowerRestored) { + coldStage=1; + c.dbuf.restorePending|=c.dbuf.selected; + for(unsigned tc=0;tc<4;++tc) { + c.tcMode[tc].notePowerLoss(); + c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; + } + Display13FBPowerIO wells{c.map}; + c.bridge.state.suspended=true; + // Intel DSI_IO_MODECTL must select MIPI before the Combo PHY + // power request. PW1 and the pipe are already accessible here. + const auto selectDSI=[&](uint32_t ddi) { + constexpr uint32_t mode=0x6B094; + phyPower[0]=1;phyPower[1]=ddi; + if(!io.readable(mode))return false; + phyPower[2]=io.read(mode); + if(phyPower[2]==UINT32_MAX)return false; + if(!(phyPower[2]&1)) { + if(ddi&3)return false; + io.write(mode,phyPower[2]|1); + } + phyPower[3]=io.read(mode); + return phyPower[3]!=UINT32_MAX && (phyPower[3]&1); + }; + const auto restoredPower=Display13Power::restore(wells,c.bridge.state,true,selectDSI); + coldValue=static_cast(restoredPower.status);coldReg=restoredPower.domain; + if(restoredPower.status!=Display13Power::Status::Complete || !owned())return false; + coldStage=2; + if(io.read(0x7B008)&0xC0000000U || io.read(0x70008)&0xC0000000U || + io.read(0x71008)&0xC0000000U || io.read(0x72008)&0xC0000000U || + io.read(0x70180)&(1U<<31))return false; + Display13FBClockIO cdclk{c.map}; + const auto hz=c.clock.nativeHz(); + if(!hz)return false; + const auto change=Display13Clock::set(cdclk,hz,{true,true,0},display13ClockVariant()); + coldValue=static_cast(change.status); + if(change.status!=Display13Clock::ChangeStatus::Complete)return false; + coldStage=3; + if(io.read(0x4438C)!=c.dbuf.mbus && !store(0x4438C,c.dbuf.mbus))return false; + Display13FBDBUFIO dbuf{c.map}; + for(unsigned i=0;i<4;++i)if(!(dbuf.read(Display13DBUF::regs[i])&Display13DBUF::Request)) + c.dbuf.owned&=~(1U<(slices); + if(slices!=Display13DBUF::Status::Complete)return false; + if(Display13DBUF::tracker(dbuf,c.dbuf)!=Display13DBUF::Status::Complete)return false; + // A peer's logical DDB ownership survives sleep, but its shadow + // registers do not. Restore those stopped banks before the native + // default/optimized allocation checks compare them with the owner. + uint32_t allocations[13]={1,c.dbuf.selected,c.dbuf.restorePending}; + bool allocationsRestored=true; + for(unsigned pipe=0;pipe<3;++pipe)if(c.dbuf.restorePending&(1U<(status); + if(status!=Display13DBUF::Status::Complete){allocationsRestored=false;break;} + } + allocations[12]=c.dbuf.restorePending; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeDBUF",allocations,sizeof(allocations)); + if(!allocationsRestored)return false; + } else { + // Native global wake already restored clocks, DBUF and power. + // Peers may now be active: restore only the stopped DSI bank. + if(!owned() || !c.engineEnabled || c.clockStopped)return false; + phyPower[0]=1;phyPower[3]=io.read(0x6B094); + if(phyPower[3]==UINT32_MAX || !(phyPower[3]&1))return false; } - allocations[12]=c.dbuf.restorePending; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeDBUF",allocations,sizeof(allocations)); - if(!allocationsRestored)return false; coldStage=4; for(unsigned i=0;idisplay13FBLifecycleHooksReady && c.bridge.begun && !c.bridge.state.faulted && !c.clockFaulted && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { - Display13FBPowerIO io {c.map};result=Display13Power::restore(io,c.bridge.state); + Display13FBBootIO io {c.map}; + const bool lost=waking && callbackXE->dsiSleepImage.captured && callbackXE->dsiRuntimePower.sleeping && + display13ClockVariant()==Display13Clock::Variant::RPLU && c.transcoders.dsi0Pipe==0 && + io.read(Display13Power::Driver)==0; + if(lost) { + cold[0]=1;cold[1]=c.dbuf.selected; + c.dbuf.restorePending|=c.dbuf.selected; + for(unsigned tc=0;tc<4;++tc) { + c.tcMode[tc].notePowerLoss();c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; + } + c.bridge.state.suspended=true; + } + const auto selectDSI=[&](uint32_t ddi) { + if(!lost)return true; + constexpr uint32_t reg=0x6B094; + const auto before=io.read(reg);cold[2]=before; + if(before==UINT32_MAX)return false; + if(!(before&1)) { + if(ddi&3)return false; + *reinterpret_cast(c.map->getVirtualAddress()+reg)=before|1; + } + cold[3]=io.read(reg);return cold[3]!=UINT32_MAX && (cold[3]&1); + }; + // Retained TC leases cannot enable AUX before the port mode handshake. + // The DSI wake completion reclaims those modes before peer notifications. + result=Display13Power::restore(io,c.bridge.state,lost,selectDSI); + if(lost && result.status==Display13Power::Status::Complete) { + cold[4]=io.read(0x4438C); + if(!Display13FBIRQ::consumersStopped(io,7,c.transcoders))result.status=Display13Power::Status::NotReady; + else { + if(cold[4]!=c.dbuf.mbus) + *reinterpret_cast(c.map->getVirtualAddress()+0x4438C)=c.dbuf.mbus; + cold[5]=io.read(0x4438C); + if(cold[5]!=c.dbuf.mbus)result.status=Display13Power::Status::NotReady; + else { + for(unsigned i=0;i<4;++i)if(!(io.read(Display13DBUF::regs[i])&Display13DBUF::Request))c.dbuf.owned&=~(1U<dsiSharedPowerRestored=true; + } + } + } + cold[6]=static_cast(result.status);cold[7]=c.bridge.state.deferredTC; if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,power,0x80,c.controller,0x1C20))result.status=Display13Power::Status::NotReady; } @@ -10234,6 +10274,10 @@ void XE::wrapAlderLakeNPowerRestore(void *power,bool waking) { display13FBPowerRecord(record,9,waking,result);IOLockUnlock(c.lock); adlnPublishPowerRecord("N97FBPowerRestore",record); if(result.status==Display13Power::Status::Complete)wrapAlderLakeNEngineEnable(power); + if(cold[0]) { + IOLockLock(c.lock);cold[8]=c.engineEnabled;cold[9]=c.clockStopped;cold[10]=c.dbuf.restorePending;cold[11]=c.bridge.state.faulted;IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("N97FBNativeColdRestore",cold,sizeof(cold)); + } } void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,bool wait) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 37707b57..e4a4934b 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -252,6 +252,7 @@ class XE { mach_vm_address_t dsiFramebufferEvent {}; uint32_t dsiWakeDegraded {},dsiWakeRejections {}; bool dsiSystemWakeNotify {},dsiPagingLayoutKnown {},dsiFirstWakeLatch {}; + bool dsiSharedPowerRestored {}; // Native global wake preceded the DSI power callback. void finishDSISystemWake(IOService *); static uint32_t wrapDSISetStream(IOService *,IOService *,OSObject *,const void *); static uint32_t wrapDSISetAttribute(IOService *,uint32_t,uintptr_t); From f80842f237dca495f26dc66dd59950bf33025b3b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 5 Nov 2026 00:15:15 +0900 Subject: [PATCH 23/46] Unify Display13 shared sleep and wake ownership --- WhateverXe/kern_display13_dbuf.hpp | 7 +- .../kern_display13_framebuffer_power.inc | 191 +++++++++++++ WhateverXe/kern_dsi_adapter_lifecycle.inc | 250 ++-------------- WhateverXe/kern_dsi_sleep_image.hpp | 8 +- WhateverXe/kern_host.cpp | 11 +- WhateverXe/kern_xe.cpp | 266 +++++++++++++----- WhateverXe/kern_xe.hpp | 21 +- tests/display13_dbuf.cpp | 13 + tests/display13_dbuf_setup.py | 3 +- tests/display13_shutdown.py | 2 +- tests/dsi_update_dispatch.py | 19 +- tests/host_publication.py | 13 +- 12 files changed, 462 insertions(+), 342 deletions(-) create mode 100644 WhateverXe/kern_display13_framebuffer_power.inc diff --git a/WhateverXe/kern_display13_dbuf.hpp b/WhateverXe/kern_display13_dbuf.hpp index ea7edf41..f7121872 100644 --- a/WhateverXe/kern_display13_dbuf.hpp +++ b/WhateverXe/kern_display13_dbuf.hpp @@ -305,10 +305,12 @@ constexpr uint32_t CreditMask=0x01FFDF0F; template Status captureCredits(IO &io,State &s) { if(s.faulted)return Status::Faulted; if(!s.begun)return Status::Unavailable; - s.creditSaved=0; + s.creditSaved&=s.selected; // Keep banks already powered down by earlier callbacks. + // A probe may get/put PG before native allocation restore. Its reset bank + // is not a new snapshot and must not replace the retained credits. const auto wells=io.read(0x45404); if(wells==UINT32_MAX)return fail(s,Status::Power); - for(unsigned p=0;p<3;++p)if((s.selected&(1U< Status stop(IO &io,State &s) { if(ctl==UINT32_MAX || (ctl&(Request|1)))return fail(s,Status::Conflict); } } + const auto captured=captureCredits(io,s);if(captured!=Status::Complete)return captured; PipeAllocation old[3]; for(unsigned p=0;p<3;++p) {old[p]=s.pipe[p];s.pipe[p].consumers=s.pipe[p].unretired=0;s.pipe[p].heldPrimary=s.pipe[p].heldCursor={};} syncMasks(s);const auto result=power(io,s,0,0); diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc new file mode 100644 index 00000000..1c0770a1 --- /dev/null +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -0,0 +1,191 @@ +// Common IOFramebuffer power dispatch and wake notification contract. +// The controller owns shared power/clock/DBUF/IRQ state. Native HDMI/DP/eDP +// retain their transport lifecycle; DSI substitutes only its panel/host path. +// IOGraphics subscribers include offline framebuffers, not just active pipes. + +uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { + bool notifyWake=false; + recordDSIClient(fb,9,state,UINT32_MAX); + auto result=[&]() -> uint32_t { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(callbackXE->dsiPowerTest && state<=2 && dsiModeTarget(fb)) { + auto controller=Display13FBMemory::field(fb,0x1D0); + auto path=Display13FBMemory::singlePath(fb); + auto &power=callbackXE->dsiRuntimePower; + const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : + (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); + if(!ready)return kIOReturnNotReady; + if(state==2 && callbackXE->dsiSystemWakeNotify) { + notifyWake=true; + } + *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; + return kIOReturnSuccess; + } + // One-way runtime stop experiment through the real power callback. + // Recovery is a reboot; state 2 never replays the donor's DDI path. + if(callbackXE->dsiStopTest && state<2 && dsiModeTarget(fb)) { + const bool stopped=callbackXE->dsiMemoryReleased && callbackXE->dsiDDIReleased && + callbackXE->dsiClockStop.complete() && callbackXE->dsiPanelStop.complete; + if(stopped)return kIOReturnSuccess; + if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return kIOReturnNotReady; + auto controller=Display13FBMemory::field(fb,0x1D0); + auto path=Display13FBMemory::singlePath(fb); + if(!callbackXE->quiesceDSIPath(controller,fb,path))return kIOReturnNotReady; + *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; + return kIOReturnSuccess; + } + // Match only the donor's already-Wake no-op. Do not publish a new + // native power state without executing a DSI power transaction. + if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; + return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; + } + } + if(state==2 && !prepareDisplay13Wake(fb))return kIOReturnNotReady; + return FunctionCast(wrapDisplay13SetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); + }(); + // Preserve the notification obligation across a failed TC handshake or + // subscriber event. Later power callbacks may retry it without rerunning + // a completed DSI panel restart. + if(result==kIOReturnSuccess && state==2 && (notifyWake || callbackXE->dsiSystemWakeNotify)) { + if(callbackXE->finishDisplay13SystemWake(fb))callbackXE->dsiSystemWakeNotify=false; + else result=kIOReturnNotReady; + } + recordDSIClient(fb,9,state,result); + return result; +} + +bool XE::parkedDisplay13Framebuffer(IOService *fb) const { + return fb && display13FBLifecycleHooksReady && display13FBABIReady && display13PagingLayoutKnown && + !strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") && + fb->getMetaClass()->getClassSize()>=0x4B90 && adlnTopologyController && + Display13FBMemory::field(fb,0x1D0)==adlnTopologyController && + Display13FBMemory::field(fb,0x1DC)<3 && + Display13FBMemory::field(adlnTopologyController,0xD60+8*Display13FBMemory::field(fb,0x1DC))==fb && + !(Display13FBMemory::field(fb,0x199)&0x20); +} + +bool XE::finishDisplay13SystemWake(IOService *caller) { + if(!display13FramebufferEvent || !caller)return false; + auto controller=Display13FBMemory::field(caller,0x1D0); + if(!controller || controller!=adlnTopologyController || controller->getMetaClass()->getClassSize()<0x1C20)return false; + auto &c=display13FBPower; + if(!c.lock || !display13PagingLayoutKnown)return false; + // Notifications can re-enter native clients. Do not hold a framebuffer + // mutex, Intel workloop gate or owner lock across handleEvent. A second + // completion caller leaves the obligation pending instead of duplicating it. + if(__atomic_exchange_n(&displayWakeEventBusy,1U,__ATOMIC_ACQUIRE))return false; + struct Completion { + uint32_t *busy; + ~Completion(){__atomic_store_n(busy,0U,__ATOMIC_RELEASE);} + } completion{&displayWakeEventBusy}; + IOLockLock(c.lock);const auto epoch=c.restoreEpoch;IOLockUnlock(c.lock); + // Port handshakes run without FB/shared locks. They apply to TC PHYs in + // fixed HDMI/DP mode too; connector shape is not a wake admission rule. + // DidWake releases parked clients, including offline connectors. It is + // not a scanout-success report and must not depend on an unplugged peer + // acquiring a link. Keep unsuccessful TC restoration pending/observable; + // prepareDisplay13Wake requires it for that output if HPD is present. + const bool portsReady=restoreDisplay13Ports(controller); + if(displayWakeEventEpoch!=epoch) { + displayWakeEventEpoch=epoch;displayWakeEventErrors=0; + for(auto &phase:displayWakeEventPhase)phase=0; + } + using Event=uint32_t (*)(IOService *,int,void *); + const auto event=reinterpret_cast(display13FramebufferEvent); + uint32_t events[14]={3,display13PagingLayoutKnown};bool complete=true; + for(unsigned index=0;index<3;++index) { + IOLockLock(c.lock); + auto fb=Display13FBMemory::field(controller,0xD60+8*index); + if(fb)fb->retain(); + IOLockUnlock(c.lock); + if(!fb)continue; + if(fb->getMetaClass()->getClassSize()<0x4B90 || + Display13FBMemory::field(fb,0x1D0)!=controller || + Display13FBMemory::field(fb,0x1DC)!=index){complete=false;fb->release();continue;} + auto &phase=displayWakeEventPhase[index]; + const bool pending=display13PagingLayoutKnown && !(Display13FBMemory::field(fb,0x199)&0x20); + // Native peers already notified themselves if paging is active. The + // DSI adapter bypassed those native calls and owns its own pair. + const bool internal=adlnOutputWiring.dsi && index==0; + if(!phase && !pending && !internal)phase=2; + if(phase<2) { + events[2]|=1U<(1)); + // handleEvent returns Success without delivery on the wrong + // power thread. Conversely a subscriber error can follow a + // delivered DidWake: never replay that scope-decrement event. + if(Display13FBMemory::field(fb,0x199)&0x20)phase=1; + if(events[4+2*index]!=kIOReturnSuccess)displayWakeEventErrors|=1U<release(); + } + IOLockLock(c.lock);const bool sameEpoch=c.restoreEpoch==epoch;IOLockUnlock(c.lock); + complete=complete && sameEpoch && !displayWakeEventErrors; + events[10]=displayWakeEventEpoch;events[11]=displayWakeEventErrors;events[12]=portsReady;events[13]=complete; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13SystemWakeEvents",events,sizeof(events)); + return complete; +} + +bool XE::processDisplay13NativeAttributes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer12setAttributeEjm", + "__ZN21AppleIntelFramebuffer25setAttributeForConnectionEijm", + "__ZN21AppleIntelFramebuffer24setFramebufferPowerStateEj", + "__ZN31AppleIntelFramebufferController11hwSetStreamEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"}; + const uint32_t offsets[]={0x3331E,0x352D4,0x24DE8,0x50DB0}; + const uint8_t entries[][20]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x50,0x41,0x89,0xF6,0x49,0x89,0xFF,0x48,0xFF,0x05,0xF1}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0x68,0x01,0x00,0x00}}; + if(!display13FramebufferRequested || size<0x50DC4)return false; + for(unsigned i=0;i<4;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; + } + // Native prepareToExitSleep / prepareToEnterWake call the same imported + // IOFramebuffer::handleEvent with DidWake(true) and DidPowerOn(nullptr). + // Resolve the linked call from the identified donor without an extra kext dependency. + if(size>0x26F1C) { + const uint8_t wake[]={0xBA,1,0,0,0,0x4C,0x89,0xF7,0xBE,4,0,0,0,0xE8}; + const uint8_t on[]={0x4C,0x89,0xEF,0xBE,8,0,0,0,0x31,0xD2,0xE8}; + if(!memcmp(reinterpret_cast(address+0x26F0A),wake,sizeof(wake)) && + !memcmp(reinterpret_cast(address+0x26809),on,sizeof(on))) { + int32_t wd,od; + memcpy(&wd,reinterpret_cast(address+0x26F18),sizeof(wd)); + memcpy(&od,reinterpret_cast(address+0x26814),sizeof(od)); + if(wd && od && address+0x26F1C+wd==address+0x26818+od) + display13FramebufferEvent=address+0x26F1C+wd; + if(display13FramebufferEvent) { + // Accept the pagingState field only when the linked handleEvent + // sets precisely this bit in both DidWake and DidPowerOn. + auto fn=reinterpret_cast(display13FramebufferEvent); + if(fn[0]==0xFF && fn[1]==0x25) { + const auto slot=XELPCall::relativeTarget(fn+2,reinterpret_cast(fn)+6); + memcpy(&fn,reinterpret_cast(slot),sizeof(fn)); + } + const uint8_t setPaging[]={0x41,0x80,0x8D,0x99,0x01,0x00,0x00,0x20}; + display13PagingLayoutKnown=fn && fn[0]==0x55 && !memcmp(fn+0x100,setPaging,sizeof(setPaging)) && + !memcmp(fn+0x1A8,setPaging,sizeof(setPaging)); + } + } + } + // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, + {symbols[1],wrapDSISetConnectionAttribute,orgDSIAttributes[1]}, + {symbols[2],wrapDisplay13SetPowerState,orgDSIAttributes[2]}, + {symbols[3],wrapDSISetStream,orgDSIAttributes[3]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 6d7fa35f..144e2967 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -121,13 +121,13 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu // The power wrapper takes its own FB lock. Release ours so // restored framebuffer clients can re-enter after notification. IORecursiveLockUnlock(scope.lock);scope.lock=nullptr; - return wrapDSISetPowerState(fb,static_cast(value)); + return wrapDisplay13SetPowerState(fb,static_cast(value)); } // IOGraphics sends spwr before restart or power-off. Park the adopted // panel through its own power path while display resources are live. if(attribute==kIOSystemPowerAttribute && callbackXE->dsiPowerTest && dsiModeTarget(fb) && (value==kIOMessageSystemWillRestart || value==kIOMessageSystemWillPowerOff)) - return wrapDSISetPowerState(fb,0); + return wrapDisplay13SetPowerState(fb,0); if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; @@ -237,61 +237,7 @@ uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint return result; } -uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { - bool notifyWake=false; - recordDSIClient(fb,9,state,UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(callbackXE->dsiPowerTest && state<=2 && dsiModeTarget(fb)) { - auto controller=Display13FBMemory::field(fb,0x1D0); - auto path=Display13FBMemory::singlePath(fb); - auto &power=callbackXE->dsiRuntimePower; - const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : - (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); - if(!ready)return kIOReturnNotReady; - if(state==2 && callbackXE->dsiSystemWakeNotify) { - notifyWake=true;callbackXE->dsiSystemWakeNotify=false; - } - *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; - return kIOReturnSuccess; - } - // One-way runtime stop experiment through the real power callback. - // Recovery is a reboot; state 2 never replays the donor's DDI path. - if(callbackXE->dsiStopTest && state<2 && dsiModeTarget(fb)) { - const bool stopped=callbackXE->dsiMemoryReleased && callbackXE->dsiDDIReleased && - callbackXE->dsiClockStop.complete() && callbackXE->dsiPanelStop.complete; - if(stopped)return kIOReturnSuccess; - if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return kIOReturnNotReady; - auto controller=Display13FBMemory::field(fb,0x1D0); - auto path=Display13FBMemory::singlePath(fb); - if(!callbackXE->quiesceDSIPath(controller,fb,path))return kIOReturnNotReady; - *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; - return kIOReturnSuccess; - } - // Match only the donor's already-Wake no-op. Do not publish a new - // native power state without executing a DSI power transaction. - if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; - return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; - } - } - return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); - }(); - if(notifyWake)callbackXE->finishDSISystemWake(fb); - recordDSIClient(fb,9,state,result); - return result; -} -bool XE::parkedDSIFramebuffer(IOService *fb) const { - return fb && dsiPowerTest && dsiPagingLayoutKnown && adlnOutputWiring.dsi && - !strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") && - fb->getMetaClass()->getClassSize()>=0x4B90 && adlnTopologyController && - Display13FBMemory::field(fb,0x1D0)==adlnTopologyController && - Display13FBMemory::field(fb,0x1DC)==0 && - !(Display13FBMemory::field(fb,0x199)&0x20); -} bool XE::failedDSIWakeFor(IOService *fb) const { if(!__atomic_load_n(&dsiWakeDegraded,__ATOMIC_ACQUIRE) || !fb || @@ -303,50 +249,6 @@ bool XE::failedDSIWakeFor(IOService *fb) const { return true; } -// IOGraphics parks every published framebuffer before system sleep, including -// offline connectors. Each has a distinct accelerator notification subscriber. -void XE::finishDSISystemWake(IOService *internal) { - if(!dsiFramebufferEvent)return; - auto controller=Display13FBMemory::field(internal,0x1D0); - if(!controller || controller!=adlnTopologyController || controller->getMetaClass()->getClassSize()<0x1C20)return; - // The DSI primary restored the common wells. Reclaim existing TC modes - // before releasing parked display clients; new/disconnected ports still - // follow the normal probe and native modeset path. Enter the mode adapter - // without the FB/shared locks; it owns its existing handshake locking. - auto &c=display13FBPower; - uint32_t ports[9]={1}; - for(unsigned tc=0;c.lock && tc<4;++tc) { - IOLockLock(c.lock); - auto port=c.controller==controller && c.tcMode[tc].restorePending ? c.tcObject[tc] : nullptr; - const auto wanted=c.tcMode[tc].selectedMode; - if(port)port->retain(); - IOLockUnlock(c.lock); - if(port) { - ports[1+2*tc]=wanted;ports[2+2*tc]=wrapDisplay13TCMode(port,wanted); - port->release(); - } - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeTC",ports,sizeof(ports)); - using Event=uint32_t (*)(IOService *,int,void *); - const auto event=reinterpret_cast(dsiFramebufferEvent); - uint32_t events[12]={1,dsiPagingLayoutKnown}; - for(unsigned index=0;index<3;++index) { - auto fb=Display13FBMemory::field(controller,0xD60+8*index); - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90 || - Display13FBMemory::field(fb,0x1D0)!=controller || - Display13FBMemory::field(fb,0x1DC)!=index || (index==0 && fb!=internal))continue; - fb->retain(); - const bool pending=dsiPagingLayoutKnown && !(Display13FBMemory::field(fb,0x199)&0x20); - if(pending || fb==internal) { - events[2]|=1U<(1)); - events[5+2*index]=event(fb,8,nullptr); - } - if(dsiPagingLayoutKnown && (Display13FBMemory::field(fb,0x199)&0x20))events[3]|=1U<release(); - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISystemWakeEvents",events,sizeof(events)); -} uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path,const void *timing) { if(callbackXE->adlnOutputWiring.dsi) { @@ -357,58 +259,6 @@ uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path return FunctionCast(wrapDSISetStream,callbackXE->orgDSIAttributes[3])(controller,fb,path,timing); } -bool XE::processDSINativeAttributes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN21AppleIntelFramebuffer12setAttributeEjm", - "__ZN21AppleIntelFramebuffer25setAttributeForConnectionEijm", - "__ZN21AppleIntelFramebuffer24setFramebufferPowerStateEj", - "__ZN31AppleIntelFramebufferController11hwSetStreamEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"}; - const uint32_t offsets[]={0x3331E,0x352D4,0x24DE8,0x50DB0}; - const uint8_t entries[][20]={ - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x50,0x41,0x89,0xF6,0x49,0x89,0xFF,0x48,0xFF,0x05,0xF1}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0x68,0x01,0x00,0x00}}; - if(!display13FramebufferRequested || size<0x50DC4)return false; - for(unsigned i=0;i<4;++i) { - const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; - } - // Native prepareToExitSleep / prepareToEnterWake call the same imported - // IOFramebuffer::handleEvent with DidWake(true) and DidPowerOn(nullptr). - // Resolve the linked call from the identified donor without an extra kext dependency. - if(size>0x26F1C) { - const uint8_t wake[]={0xBA,1,0,0,0,0x4C,0x89,0xF7,0xBE,4,0,0,0,0xE8}; - const uint8_t on[]={0x4C,0x89,0xEF,0xBE,8,0,0,0,0x31,0xD2,0xE8}; - if(!memcmp(reinterpret_cast(address+0x26F0A),wake,sizeof(wake)) && - !memcmp(reinterpret_cast(address+0x26809),on,sizeof(on))) { - int32_t wd,od; - memcpy(&wd,reinterpret_cast(address+0x26F18),sizeof(wd)); - memcpy(&od,reinterpret_cast(address+0x26814),sizeof(od)); - if(wd && od && address+0x26F1C+wd==address+0x26818+od) - dsiFramebufferEvent=address+0x26F1C+wd; - if(dsiFramebufferEvent) { - // Accept the pagingState field only when the linked handleEvent - // sets precisely this bit in both DidWake and DidPowerOn. - auto fn=reinterpret_cast(dsiFramebufferEvent); - if(fn[0]==0xFF && fn[1]==0x25) { - const auto slot=XELPCall::relativeTarget(fn+2,reinterpret_cast(fn)+6); - memcpy(&fn,reinterpret_cast(slot),sizeof(fn)); - } - const uint8_t setPaging[]={0x41,0x80,0x8D,0x99,0x01,0x00,0x00,0x20}; - dsiPagingLayoutKnown=fn && fn[0]==0x55 && !memcmp(fn+0x100,setPaging,sizeof(setPaging)) && - !memcmp(fn+0x1A8,setPaging,sizeof(setPaging)); - } - } - } - // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. - KernelPatcher::RouteRequest routes[]={ - {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, - {symbols[1],wrapDSISetConnectionAttribute,orgDSIAttributes[1]}, - {symbols[2],wrapDSISetPowerState,orgDSIAttributes[2]}, - {symbols[3],wrapDSISetStream,orgDSIAttributes[3]}}; - const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; -} bool XE::processDSINativeTransactions(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { const char *symbols[]={ @@ -806,11 +656,10 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool power.host.error==XELP::DSI::Restart::Error::None && dsiPanelStop.complete && qualified() && dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); if(retain && complete) { - power.sleeping=true;dsiSharedPowerRestored=false; + power.sleeping=true; // These are pipe A / DSI-owned registers. A peer may still be on at // this point: its later power callback must not veto our snapshot. - dsiSleepImage.captured=display13ClockVariant()==Display13Clock::Variant::RPLU && - (c.dbuf.selected&1) && !(irq.state.active&1); + dsiSleepImage.captured=(c.dbuf.selected&1) && !(irq.state.active&1); if(dsiSleepImage.captured) { Display13FBDBUFIO dbuf{c.map}; dsiSleepImage.captured=Display13DBUF::captureCredits(dbuf,c.dbuf)==Display13DBUF::Status::Complete; @@ -874,7 +723,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { Display13FBMemory::field(controller,0xE00)!=loop || adlnOutputWiring.dsi!=1 || c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || c.transcoders.dsi0Pipe!=0 || c.dsiNativeWriters || c.memoryFaulted || c.clockFaulted || c.watermarkFaulted || - c.bridge.state.faulted || c.bridge.state.suspended || c.dbuf.faulted || c.dbuf.updating || + c.bridge.state.faulted || c.dbuf.faulted || c.dbuf.updating || c.pipe[0].destroyingPath || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || Display13FBMemory::field(c.dsiPort,0x550)!=1 || Display13FBMemory::field(path,0x3644)!=2 || @@ -911,88 +760,22 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { uint32_t coldStage=0,coldReg=0,coldValue=0; uint32_t dbox[3]={},phyPower[4]={}; const auto restoreLostDisplay=[&]() { - if(!dsiSleepImage.captured || !bindingOwned() || !io.readable(Display13Power::Driver) || - (io.read(Display13Power::Driver)!=0 && !dsiSharedPowerRestored))return true; + if(!bindingOwned())return false; + if(!restoreDisplay13ResourcesLocked(c.power,true))return false; + if(c.restoreEpoch==dsiRestoredEpoch)return true; + if(!dsiSleepImage.captured)return false; auto store=[&](uint32_t reg,uint32_t v) { coldReg=reg; *reinterpret_cast(c.map->getVirtualAddress()+reg)=v; coldValue=io.read(reg);return coldValue==v; }; - if(!dsiSharedPowerRestored) { - coldStage=1; - c.dbuf.restorePending|=c.dbuf.selected; - for(unsigned tc=0;tc<4;++tc) { - c.tcMode[tc].notePowerLoss(); - c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; - } - Display13FBPowerIO wells{c.map}; - c.bridge.state.suspended=true; - // Intel DSI_IO_MODECTL must select MIPI before the Combo PHY - // power request. PW1 and the pipe are already accessible here. - const auto selectDSI=[&](uint32_t ddi) { - constexpr uint32_t mode=0x6B094; - phyPower[0]=1;phyPower[1]=ddi; - if(!io.readable(mode))return false; - phyPower[2]=io.read(mode); - if(phyPower[2]==UINT32_MAX)return false; - if(!(phyPower[2]&1)) { - if(ddi&3)return false; - io.write(mode,phyPower[2]|1); - } - phyPower[3]=io.read(mode); - return phyPower[3]!=UINT32_MAX && (phyPower[3]&1); - }; - const auto restoredPower=Display13Power::restore(wells,c.bridge.state,true,selectDSI); - coldValue=static_cast(restoredPower.status);coldReg=restoredPower.domain; - if(restoredPower.status!=Display13Power::Status::Complete || !owned())return false; - coldStage=2; - if(io.read(0x7B008)&0xC0000000U || io.read(0x70008)&0xC0000000U || - io.read(0x71008)&0xC0000000U || io.read(0x72008)&0xC0000000U || - io.read(0x70180)&(1U<<31))return false; - Display13FBClockIO cdclk{c.map}; - const auto hz=c.clock.nativeHz(); - if(!hz)return false; - const auto change=Display13Clock::set(cdclk,hz,{true,true,0},display13ClockVariant()); - coldValue=static_cast(change.status); - if(change.status!=Display13Clock::ChangeStatus::Complete)return false; - coldStage=3; - if(io.read(0x4438C)!=c.dbuf.mbus && !store(0x4438C,c.dbuf.mbus))return false; - Display13FBDBUFIO dbuf{c.map}; - for(unsigned i=0;i<4;++i)if(!(dbuf.read(Display13DBUF::regs[i])&Display13DBUF::Request)) - c.dbuf.owned&=~(1U<(slices); - if(slices!=Display13DBUF::Status::Complete)return false; - if(Display13DBUF::tracker(dbuf,c.dbuf)!=Display13DBUF::Status::Complete)return false; - // A peer's logical DDB ownership survives sleep, but its shadow - // registers do not. Restore those stopped banks before the native - // default/optimized allocation checks compare them with the owner. - uint32_t allocations[13]={1,c.dbuf.selected,c.dbuf.restorePending}; - bool allocationsRestored=true; - for(unsigned pipe=0;pipe<3;++pipe)if(c.dbuf.restorePending&(1U<(status); - if(status!=Display13DBUF::Status::Complete){allocationsRestored=false;break;} - } - allocations[12]=c.dbuf.restorePending; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeDBUF",allocations,sizeof(allocations)); - if(!allocationsRestored)return false; - } else { - // Native global wake already restored clocks, DBUF and power. - // Peers may now be active: restore only the stopped DSI bank. - if(!owned() || !c.engineEnabled || c.clockStopped)return false; - phyPower[0]=1;phyPower[3]=io.read(0x6B094); - if(phyPower[3]==UINT32_MAX || !(phyPower[3]&1))return false; - } - coldStage=4; + if(!owned() || !c.engineEnabled || c.clockStopped)return false; + phyPower[0]=1;phyPower[3]=io.read(0x6B094); + if(phyPower[3]==UINT32_MAX || !(phyPower[3]&1))return false; + coldStage=4;dbox[0]=c.dbuf.credit[0];dbox[1]=io.read(0x7003C); for(unsigned i=0;i(c.map->getVirtualAddress()+field.reg)=(before&~field.mask)|value; @@ -1080,9 +863,10 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { irqReceipt[7+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)+4); irqReceipt[8+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)-4); } - const auto status=coldStage==5 ? + const auto status=irq.powerLost ? Display13FBIRQ::restoreAfterPowerLoss(irqIO,irq.state,irq.admittedPipes) : Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes); + if(status==Display13FBIRQ::Status::Complete)irq.powerLost=false; irqReceipt[15]=static_cast(status);irqReceipt[16]=irq.state.active; irqReceipt[17]=static_cast(irq.state.firstFailure); irqReceipt[18]=irq.state.failureRegister;irqReceipt[19]=irq.state.failureBits; @@ -1093,7 +877,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { irq.quiescing=false;irq.blockedPipes=0; adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); complete=restored && rearmed && cursor && power.panel.complete() && owned(); - if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;dsiFirstWakeLatch=coldStage==5;dsiSharedPowerRestored=false;} + if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;dsiFirstWakeLatch=coldStage==5;dsiRestoredEpoch=c.restoreEpoch;} else{power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} } } @@ -1135,7 +919,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeCommands",commandReceipt,sizeof(commandReceipt)); if(complete && coldStage==5){dsiSystemWakeNotify=true;IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeCadence",cadence,sizeof(cadence));} - uint32_t systemWake[]={coldStage,coldReg,coldValue,dsiSleepImage.captured,domainReady,p.frameBefore,p.frameAfter,p.waitUs,dsiFramebufferEvent!=0,dbox[0],dbox[1],dbox[2]}; + uint32_t systemWake[]={coldStage,coldReg,coldValue,dsiSleepImage.captured,domainReady,p.frameBefore,p.frameAfter,p.waitUs,display13FramebufferEvent!=0,dbox[0],dbox[1],dbox[2]}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISystemWake",systemWake,sizeof(systemWake)); publishDSIBacklight(); return complete; diff --git a/WhateverXe/kern_dsi_sleep_image.hpp b/WhateverXe/kern_dsi_sleep_image.hpp index b4389ea1..a51f2ead 100644 --- a/WhateverXe/kern_dsi_sleep_image.hpp +++ b/WhateverXe/kern_dsi_sleep_image.hpp @@ -3,9 +3,10 @@ namespace XELP { namespace DSI { struct SystemSleepImage { // Explicit writable configuration owned by the parked DSI pipe. DDB and WM -// precede plane arming. No interrupt identity, status or frame counters. +// precede plane arming. Shared DBUF owns DDB/MBUS credits independently. +// No interrupt identity, status or frame counters. static constexpr uint32_t config[] = { - 0x6001C,0x45270,0x7027C,0x7017C, + 0x6001C,0x45270, 0x70240,0x70244,0x70248,0x7024C,0x70250,0x70254,0x70258,0x7025C,0x70268, 0x70140,0x70144,0x70148,0x7014C,0x70150,0x70154,0x70158,0x7015C,0x70168, 0x70188,0x7018C,0x70190,0x701A4,0x701CC,0x70180,0x7019C, @@ -18,9 +19,6 @@ static constexpr uint32_t config[] = { {0x162104,0x00FF00FF},{0x162124,UINT32_MAX},{0x162128,UINT32_MAX}, {0x162120,1U<<24},{0x162100,1U<<31},{0x162014,1U<<4}, {0x42084,1U<<30},{0xC7204,1U<<1}, - // ADL-P pipe A pixel fetch credits (skl_watermark.c, intel_mbus_dbox_update). - // Restore the working allocation before arming the first plane. - {0x7003C,0x01FFDF0F}, // Plane and cursor controls encode Display13 arbitration slots. // Select those slots on the pipe before rearming either consumer. {0x70028,1U<<13}, diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index dbcd6abe..2f80035c 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -37,8 +37,9 @@ void XeHost::processKernel(KernelPatcher &patcher) { // The host must dispatch framebuffer patches and accelerator rejection // hooks even when the selected profile deliberately has no Render backend. active=profile.displayAdmitted(); - systemWakeRecovery=active && !profile.overridden() && profile.identity==0xA7A98086; + const auto display=XELP::displayAdapter(profile.family); + systemWakeRecovery=active && (display==XELP::DisplayAdapter::ADLP || display==XELP::DisplayAdapter::ADLN); backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS || display==XELP::DisplayAdapter::ADLP || display==XELP::DisplayAdapter::ADLN) && info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple; @@ -146,8 +147,12 @@ uint32_t XeHost::pendingSleepDisplays(void *accelerator) { if(!count || !(*reinterpret_cast(bytes+0xc78)&1))return count; const auto depth=*reinterpret_cast(machine+0x10c); if(depth(h->sleepFBAt)(machine,0); - if(!h->xe.parkedDSIFramebuffer(fb))return count; + // Every subscriber in this machine must be parked. FB0 alone cannot + // establish whether an external display acquired a new wake scope. + for(unsigned i=0;i(h->sleepFBAt)(machine,i); + if(!h->xe.parkedDisplay13Framebuffer(fb))return count; + } // Already parked by a full two-phase notification, with no intervening // DidWake. The real resume must close the original scope exactly once. uint32_t record[]={__atomic_add_fetch(&h->repeatedSleep,1,__ATOMIC_RELAXED),count,depth}; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 8d4d6ea4..09d31cc5 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4652,6 +4652,9 @@ struct Display13FBPowerContext { Display13WM::Latency latency; bool watermarkFaulted {}; bool engineEnabled {}; + // Shared reset generation; output callbacks consume it independently. + uint32_t restoreEpoch {}; + bool restorePending {}; volatile uintptr_t arbitrationWriterBase {}; // Qualified in sleepable engine context. IOLock *lock {}; // Borrow only while lock is held, and finish consuming the snapshot @@ -4739,7 +4742,7 @@ struct Display13FBIRQContext { IOSimpleLock *lock {}; IOService *controller {}; Display13FBIRQ::State state; - unsigned handlers {},softwareHandlers {};bool quiescing {};uint32_t blockedPipes {}; + unsigned handlers {},softwareHandlers {};bool quiescing {},powerLost {};uint32_t blockedPipes {}; uintptr_t softwareThread {}; Display13FBIRQ::HandlerScopes scopes; volatile uint32_t admittedPipes {}; // Release-published after actual path/backing proof. @@ -5526,8 +5529,9 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); - dsiNativeWriterHooksReady=!adlnOutputWiring.dsi || (processDSINativeWriters(patcher,index,address,size) && - processDSINativeTransactions(patcher,index,address,size) && processDSINativeAttributes(patcher,index,address,size)); + const bool displayPowerHooks=processDisplay13NativeAttributes(patcher,index,address,size); + dsiNativeWriterHooksReady=displayPowerHooks && (!adlnOutputWiring.dsi || (processDSINativeWriters(patcher,index,address,size) && + processDSINativeTransactions(patcher,index,address,size))); dsiBootProbeReady=processDisplay13DSIBoot(patcher,index,address,size); dsiCacheHooksReady=processDisplay13DSICache(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); @@ -6056,8 +6060,11 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { check[17]=io.read(0xC400C);check[18]=io.read(0xC4004); constexpr uint32_t otherBanks[] {0x44448,0x44468,0x44478,0x44488}; for(unsigned i=0;i<4;++i)check[19+i]=io.read(otherBanks[i]+4)&~io.read(otherBanks[i]-4); - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { - const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); + if(c.powerLost && !c.quiescing) { + c.status=Display13FBIRQ::restoreAfterPowerLoss(io,c.state,c.admittedPipes); + if(c.status==Display13FBIRQ::Status::Complete)c.powerLost=false; + } else if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { + const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); const auto actual=Display13FBIRQ::readActivePipes(io,admitted,c.state.transcoders); c.status=c.quiescing ? Display13FBIRQ::Status::Power : actual.status; if(c.status==Display13FBIRQ::Status::Complete)c.status=c.state.begun && c.state.enabled ? @@ -9978,10 +9985,10 @@ void XE::display13WMArmUpdate(IOService *fb) { snprintf(property,sizeof(property),"N97FBPipe%uWMLatch",pipe);root->setProperty(property,record,sizeof(record)); } -void XE::wrapAlderLakeNEngineEnable(void *power) { +bool XE::enableDisplay13EngineLocked(void *power) { auto &c=display13FBPower; - if(!c.lock || power!=c.power || !c.controller)return; - uint64_t record[32] {};IOLockLock(c.lock); + if(!c.lock || power!=c.power || !c.controller)return false; + uint64_t record[32] {}; Display13Power::Result acquired; if(callbackXE->display13FBEngineEnableHookReady && c.bridge.begun && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.clockFaulted && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { @@ -9990,22 +9997,23 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { } c.writes+=acquired.writes; Display13FBClockIO clockIO {c.map};auto clock=Display13Clock::read(clockIO,display13ClockVariant()); - if(clock.status==Display13Clock::Status::Disabled && c.clockStopped && c.resumeClockHz && + bool clockReady=!c.restorePending; + if((c.restorePending || (clock.status==Display13Clock::Status::Disabled && c.clockStopped)) && c.resumeClockHz && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.clockFaulted && !c.dbuf.updating && !Display13WM::busyPipes(c.pipe)) { Display13FBClockGuard guard; if(adlnBeginClockGuard(guard)) { const auto changed=Display13Clock::set(clockIO,c.resumeClockHz,{true,true,0},display13ClockVariant()); if(changed.status==Display13Clock::ChangeStatus::Complete) { - clock=changed.after;c.clockStopped=false;adlnInvalidateWM(); + clock=changed.after;clockReady=true;c.clockStopped=false;adlnInvalidateWM(); *reinterpret_cast(reinterpret_cast(c.controller)+0xE98)=clock.nativeHz(); - } else adlnFault(c.clockFaulted,__LINE__); + } else if(changed.writes)adlnFault(c.clockFaulted,__LINE__); adlnEndClockGuard(guard,changed.status==Display13Clock::ChangeStatus::Complete || !changed.writes); - } else adlnFault(c.clockFaulted,__LINE__); + } } Display13DBUF::Status status=Display13DBUF::Status::Unavailable; if(callbackXE->display13FBEngineEnableHookReady && c.bridge.begun && !c.bridge.state.faulted && - !c.bridge.state.suspended && acquired.status==Display13Power::Status::Complete && !c.clockFaulted && clock.status==Display13Clock::Status::Complete && + !c.bridge.state.suspended && acquired.status==Display13Power::Status::Complete && clockReady && !c.clockFaulted && clock.status==Display13Clock::Status::Complete && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { Display13FBDBUFIO io {c.map}; status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnOutputWiring.selected())>=3 ? 7U : 0U,c.transcoders); @@ -10020,8 +10028,8 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { // binding here, then publish only the native VA for the nonblocking writer. const auto writerBase=c.engineEnabled && adlnWriterMappingBound() ? Display13FBMemory::field(c.access,0x50) : 0; __atomic_store_n(&c.arbitrationWriterBase,writerBase,__ATOMIC_RELEASE); - if(c.engineEnabled) {c.clock=clock;c.resumeClockHz=clock.nativeHz();} - if(!c.engineEnabled)adlnFault(c.bridge.state.faulted,__LINE__); + if(c.engineEnabled) {c.clock=clock;c.resumeClockHz=clock.nativeHz();c.clockStopped=false;} + if(!c.engineEnabled && (c.clockFaulted || c.dbuf.faulted))adlnFault(c.bridge.state.faulted,__LINE__); auto &d=c.dbuf; record[0]=1;record[1]=static_cast(status);record[2]=c.engineEnabled;record[3]=c.engine.domains; record[4]=clock.nativeHz();record[5]=d.begun;record[6]=d.faulted;record[7]=d.mbus;record[8]=d.joined; @@ -10030,13 +10038,22 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { for(unsigned i=0;i<4;++i) {record[17+i]=d.initial[i];record[21+i]=d.raw[i];} record[25]=writerBase!=0; record[26]=d.selected;record[27]=static_cast(acquired.status);record[28]=static_cast(clock.status); - IOLockUnlock(c.lock); auto root=IORegistryEntry::getRegistryRoot();root->setProperty("N97FBEngine",record,sizeof(record)); if(!record[26] && !root->getProperty("N97FBEngineFirstEmptyResume")) root->setProperty("N97FBEngineFirstEmptyResume",record,sizeof(record)); + return c.engineEnabled; +} + +void XE::wrapAlderLakeNEngineEnable(void *power) { + auto &c=display13FBPower;if(!c.lock)return; + IOLockLock(c.lock);enableDisplay13EngineLocked(power);IOLockUnlock(c.lock); } void XE::wrapAlderLakeNEngineDisable(void *power) { + stopDisplay13Engine(power,false); +} + +void XE::stopDisplay13Engine(void *power,bool systemSuspend) { auto &c=display13FBPower;if(!c.lock || power!=c.power || !c.controller)return; uint64_t record[12] {},hotplug[16] {},irqRelease[12] {};Display13Power::Result released;Display13Clock::Change clock; IOWorkLoop *acquiredGate=nullptr;bool gateAttempted=false,gateReady=false;uint64_t gateEvidence[8] {}; @@ -10124,7 +10141,7 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { } } if(status==Display13DBUF::Status::Complete)c.engineEnabled=false; - if(status==Display13DBUF::Status::Complete && c.bridge.bootAdopted && (c.bridge.state.borrowed&1) && + if(!systemSuspend && status==Display13DBUF::Status::Complete && c.bridge.bootAdopted && (c.bridge.state.borrowed&1) && (dual || (c.pipe[0].memoryFramebuffer && !Display13FBMemory::field(c.pipe[0].memoryFramebuffer,0x1E0))))restoreHPD(); adlnEndClockGuard(guard,status!=Display13DBUF::Status::Complete && !clock.writes && !c.clockStopped); @@ -10133,11 +10150,11 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { } // A repeated native IRQ disable can precede an idempotent engine stop. // Revalidate/re-arm HPD even when no shared resource mutation was needed. - if(dual && status==Display13DBUF::Status::Complete && !hotplug[0] && !c.bridge.state.suspended && + if(!systemSuspend && dual && status==Display13DBUF::Status::Complete && !hotplug[0] && !c.bridge.state.suspended && c.bridge.bootAdopted && (c.bridge.state.borrowed&1) && c.clockStopped && !c.engine.domains)restoreHPD(); if(status==Display13DBUF::Status::Complete)c.engineEnabled=false; - else adlnFault(c.bridge.state.faulted,__LINE__); + else if(c.dbuf.faulted || c.clockFaulted || released.writes || clock.writes)adlnFault(c.bridge.state.faulted,__LINE__); c.writes+=released.writes; record[0]=1;record[1]=static_cast(status);record[2]=c.engineEnabled;record[3]=c.engine.domains; record[4]=static_cast(clock.status);record[5]=c.clockStopped;record[6]=c.resumeClockHz; @@ -10160,7 +10177,7 @@ void XE::wrapAlderLakeNPowerOverride(void *power,bool enable) { finishDisplay13TCShutdown(c.controller); // override(true) is only reached by the donor's simulator backend; our // physical object has simulator=0. Do not force unrelated DDI/AUX domains. - if(!enable)wrapAlderLakeNEngineDisable(power); + if(!enable)stopDisplay13Engine(power,true); uint64_t record[32] {},suspendIRQ[24] {};Display13Power::Result result; IOWorkLoop *acquiredGate=nullptr;IOLockLock(c.lock); if(callbackXE->display13FBLifecycleHooksReady && !enable && c.bridge.begun && !c.bridge.state.faulted && @@ -10214,70 +10231,154 @@ void XE::wrapAlderLakeNPowerOverride(void *power,bool enable) { suspendIRQ[22]=static_cast(result.status);suspendIRQ[23]=c.bridge.state.suspended; } } - if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); + if(result.status!=Display13Power::Status::Complete && result.writes)adlnFault(c.bridge.state.faulted,__LINE__); display13FBPowerRecord(record,8,enable,result);IOLockUnlock(c.lock); if(acquiredGate)reinterpret_cast(callbackXE->adlnWorkLoopOpen)(acquiredGate); if(suspendIRQ[0])IORegistryEntry::getRegistryRoot()->setProperty("N97FBSuspendIRQ",suspendIRQ,sizeof(suspendIRQ)); adlnPublishPowerRecord("N97FBPowerOverride",record); } +// Called with the display owner lock. No framebuffer or connector owns this +// transition. Native global wake and a transport that bypasses native wake +// both enter here; a second caller must not reset an already running peer. +bool XE::restoreDisplay13ResourcesLocked(void *power,bool waking) { + auto &c=display13FBPower; + uint64_t record[32] {},cold[12] {};Display13Power::Result result; + if(power!=c.power || !c.controller || !callbackXE->display13FBLifecycleHooksReady || + !c.bridge.begun || c.bridge.state.faulted || c.clockFaulted || + !Display13Power::objectAssociations(power,0x80,c.controller,0x1C20))return false; + // Serialize against hotplug/port work. Try-close while holding the owner + // lock: another native callback may own the gate and be waiting for us. + auto loop=c.workLoop; + if(!loop || Display13FBMemory::field(c.controller,0xE00)!=loop || + !callbackXE->adlnWorkLoopInGate || !callbackXE->adlnWorkLoopTryClose || !callbackXE->adlnWorkLoopOpen)return false; + struct Gate { + IOWorkLoop *loop;mach_vm_address_t open;bool acquired; + ~Gate(){if(acquired)reinterpret_cast(open)(loop);} + } gate{loop,callbackXE->adlnWorkLoopOpen,false}; + if(!reinterpret_cast(callbackXE->adlnWorkLoopInGate)(loop)) { + gate.acquired=reinterpret_cast(callbackXE->adlnWorkLoopTryClose)(loop); + if(!gate.acquired)return false; + } + Display13FBBootIO io{c.map}; + const auto driver=io.read(Display13Power::Driver); + if(driver==UINT32_MAX)return false; + // The donor also restores from its low-power exit with waking=false. + // Hardware state, not that hint, determines whether shared state was lost. + const bool lost=!(driver&3) || c.bridge.state.suspended; + if(!lost && !c.restorePending && !c.bridge.state.suspended && c.engineEnabled && !c.clockStopped)return true; + if(lost && !c.restorePending) { + ++c.restoreEpoch;c.restorePending=true; + c.dbuf.restorePending|=c.dbuf.selected; + for(unsigned tc=0;tc<4;++tc) { + c.tcMode[tc].notePowerLoss();c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; + } + c.bridge.state.suspended=true; + c.engineEnabled=false;c.clockStopped=true; + if(!c.resumeClockHz)c.resumeClockHz=c.clock.nativeHz(); + auto &irq=display13FBIRQ; + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + irq.powerLost=true; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + cold[0]=c.restorePending;cold[1]=c.restoreEpoch; + // This is a PHY/protocol prerequisite, not a condition for shared wake. + // Combo HDMI/DP/eDP and TC legacy/DP-alt outputs use the same shared path. + const auto selectDSI=[&](uint32_t ddi) { + if(!c.restorePending || c.transcoders.dsi0Pipe>=3)return true; + constexpr uint32_t reg=0x6B094; + const auto before=io.read(reg);cold[2]=before; + if(before==UINT32_MAX)return false; + if(!(before&1)) { + if(ddi&3)return false; + *reinterpret_cast(c.map->getVirtualAddress()+reg)=before|1; + } + cold[3]=io.read(reg);return cold[3]!=UINT32_MAX && (cold[3]&1); + }; + result=Display13Power::restore(io,c.bridge.state,c.restorePending,selectDSI); + if(result.status==Display13Power::Status::Complete && c.restorePending) { + // No clock/MBUS writes after any output starts scanning out. + if(!Display13FBIRQ::consumersStopped(io,7,c.transcoders))result.status=Display13Power::Status::NotReady; + else { + cold[4]=io.read(0x4438C); + if(cold[4]!=c.dbuf.mbus) + *reinterpret_cast(c.map->getVirtualAddress()+0x4438C)=c.dbuf.mbus; + cold[5]=io.read(0x4438C); + if(cold[5]!=c.dbuf.mbus)result.status=Display13Power::Status::NotReady; + else for(unsigned i=0;i<4;++i) + if(!(io.read(Display13DBUF::regs[i])&Display13DBUF::Request))c.dbuf.owned&=~(1U<(result.status);cold[7]=c.bridge.state.deferredTC; + const bool ready=result.status==Display13Power::Status::Complete && + ((c.engineEnabled && !c.restorePending && !c.clockStopped) || enableDisplay13EngineLocked(power)); + if(ready)c.restorePending=false; + else if(result.status==Display13Power::Status::Complete)result.status=Display13Power::Status::NotReady; + // A read/preflight rejection retains the pending generation. Partial + // hardware failures are already latched by the resource that owns them. + cold[8]=c.engineEnabled;cold[9]=c.clockStopped;cold[10]=c.dbuf.restorePending;cold[11]=c.bridge.state.faulted; + display13FBPowerRecord(record,9,waking,result); + adlnPublishPowerRecord("N97FBPowerRestore",record); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13SharedResume",cold,sizeof(cold)); + return ready; +} + +bool XE::restoreDisplay13Ports(IOService *controller,unsigned onlyDDI) { + auto &c=display13FBPower;bool ready=true;uint32_t ports[10]={1}; + for(unsigned tc=0;c.lock && tc<4;++tc) { + if(onlyDDI!=UINT32_MAX && tc+3!=onlyDDI)continue; + IOLockLock(c.lock); + const bool pending=c.controller==controller && c.tcMode[tc].restorePending; + auto port=pending ? c.tcObject[tc] : nullptr; + const auto mode=c.tcMode[tc].selectedMode; + if(port)port->retain(); + IOLockUnlock(c.lock); + if(pending && !port)ready=false; + if(port) { + ports[1+2*tc]=mode;ports[2+2*tc]=wrapDisplay13TCMode(port,mode); + if(ports[2+2*tc]!=kIOReturnSuccess)ready=false; + port->release(); + } + } + ports[9]=ready;IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ResumePorts",ports,sizeof(ports)); + return ready; +} + +// prepareToExitSleep invokes a void restorePowerWellsState and otherwise +// continues on failure. Intercept at the enclosing status-returning FB entry +// so a busy/partial shared transition cannot fall through into native writers. +bool XE::prepareDisplay13Wake(IOService *fb) { + auto &c=display13FBPower; + if(!c.lock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return true; + auto controller=Display13FBMemory::field(fb,0x1D0); + IOLockLock(c.lock); + if(controller!=c.controller || !c.bridge.begun){IOLockUnlock(c.lock);return true;} + Display13FBBootIO io{c.map};const auto driver=io.read(Display13Power::Driver); + const bool needed=c.restorePending || c.bridge.state.suspended || !(driver&3); + const bool ready=driver!=UINT32_MAX && (!needed || restoreDisplay13ResourcesLocked(c.power,true)); + unsigned ddi=UINT32_MAX;bool connected=false; + const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); + if(ready && pipe<3 && c.pipe[pipe].powerPath.binding.status==TGLFBBinding::Status::Complete) { + ddi=c.pipe[pipe].powerPath.binding.ddi; + Display13TC::Port port; + if(Display13TC::Port::resolve(ddi,port) && c.tcMode[ddi-3].restorePending) { + const auto sde=io.read(0xC4000),north=io.read(0x44470); + connected=sde==UINT32_MAX || north==UINT32_MAX || port.legacyHPD(sde) || port.altHPD(sde,north); + } + } + IOLockUnlock(c.lock); + // A disconnected peer must reach native probing/teardown, not prevent + // every other FB from waking. Only this output's live TC link is required. + return ready && (!connected || restoreDisplay13Ports(controller,ddi)); +} + void XE::wrapAlderLakeNPowerRestore(void *power,bool waking) { - auto &c=display13FBPower;if(!c.lock || power!=c.power || !c.controller)return; - uint64_t record[32] {},cold[12] {};Display13Power::Result result;IOLockLock(c.lock); - if(callbackXE->display13FBLifecycleHooksReady && c.bridge.begun && !c.bridge.state.faulted && !c.clockFaulted && - Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { - Display13FBBootIO io {c.map}; - const bool lost=waking && callbackXE->dsiSleepImage.captured && callbackXE->dsiRuntimePower.sleeping && - display13ClockVariant()==Display13Clock::Variant::RPLU && c.transcoders.dsi0Pipe==0 && - io.read(Display13Power::Driver)==0; - if(lost) { - cold[0]=1;cold[1]=c.dbuf.selected; - c.dbuf.restorePending|=c.dbuf.selected; - for(unsigned tc=0;tc<4;++tc) { - c.tcMode[tc].notePowerLoss();c.tcSignalReady[tc]=false;c.tcDPLink[tc]={}; - } - c.bridge.state.suspended=true; - } - const auto selectDSI=[&](uint32_t ddi) { - if(!lost)return true; - constexpr uint32_t reg=0x6B094; - const auto before=io.read(reg);cold[2]=before; - if(before==UINT32_MAX)return false; - if(!(before&1)) { - if(ddi&3)return false; - *reinterpret_cast(c.map->getVirtualAddress()+reg)=before|1; - } - cold[3]=io.read(reg);return cold[3]!=UINT32_MAX && (cold[3]&1); - }; - // Retained TC leases cannot enable AUX before the port mode handshake. - // The DSI wake completion reclaims those modes before peer notifications. - result=Display13Power::restore(io,c.bridge.state,lost,selectDSI); - if(lost && result.status==Display13Power::Status::Complete) { - cold[4]=io.read(0x4438C); - if(!Display13FBIRQ::consumersStopped(io,7,c.transcoders))result.status=Display13Power::Status::NotReady; - else { - if(cold[4]!=c.dbuf.mbus) - *reinterpret_cast(c.map->getVirtualAddress()+0x4438C)=c.dbuf.mbus; - cold[5]=io.read(0x4438C); - if(cold[5]!=c.dbuf.mbus)result.status=Display13Power::Status::NotReady; - else { - for(unsigned i=0;i<4;++i)if(!(io.read(Display13DBUF::regs[i])&Display13DBUF::Request))c.dbuf.owned&=~(1U<dsiSharedPowerRestored=true; - } - } - } - cold[6]=static_cast(result.status);cold[7]=c.bridge.state.deferredTC; - if(result.status==Display13Power::Status::Complete && - !Display13Power::publishObject(c.bridge,power,0x80,c.controller,0x1C20))result.status=Display13Power::Status::NotReady; - } - if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); - display13FBPowerRecord(record,9,waking,result);IOLockUnlock(c.lock); - adlnPublishPowerRecord("N97FBPowerRestore",record); - if(result.status==Display13Power::Status::Complete)wrapAlderLakeNEngineEnable(power); - if(cold[0]) { - IOLockLock(c.lock);cold[8]=c.engineEnabled;cold[9]=c.clockStopped;cold[10]=c.dbuf.restorePending;cold[11]=c.bridge.state.faulted;IOLockUnlock(c.lock); - IORegistryEntry::getRegistryRoot()->setProperty("N97FBNativeColdRestore",cold,sizeof(cold)); - } + auto &c=display13FBPower;if(!c.lock || power!=c.power || !c.controller)return; + IOLockLock(c.lock);const bool ready=restoreDisplay13ResourcesLocked(power,waking); + auto controller=static_cast(c.controller);IOLockUnlock(c.lock); + if(ready)restoreDisplay13Ports(controller); } void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,bool wait) { @@ -10697,6 +10798,14 @@ bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { ++h[13+(h[7]&3)+((h[7]&8)?4:0)]; } else {++h[21];h[8]=h[9]=h[10]=h[11]=h[12]=0;} } + // A bank may be off by the time global STOP runs. Capture it before + // its last native PG put; retain snapshots of already parked peers. + if(operation==2 && c.dbuf.begun) { + Display13FBDBUFIO snapshot{c.map}; + if(Display13DBUF::captureCredits(snapshot,c.dbuf)!=Display13DBUF::Status::Complete) { + IOLockUnlock(c.lock);return false; + } + } Display13FBPowerIO io {c.map}; uint32_t released=0; if(operation==2 && domain>=1 && domain<=4 && c.bridge.pgRefs[domain-1]==1) @@ -10798,7 +10907,7 @@ bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { } if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,power,0x80,c.controller,0xC60)) result.status=Display13Power::Status::NotReady; - if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); + if(result.status!=Display13Power::Status::Complete && (releaseAllowed || display13FBIRQ.state.faulted))adlnFault(c.bridge.state.faulted,__LINE__); disableEngine=operation==2 && result.status==Display13Power::Status::Complete && !c.bridge.nativeRefs(1) && c.engineEnabled; } else if(power==c.power && c.controller)adlnFault(c.bridge.state.faulted,__LINE__); @@ -20208,4 +20317,5 @@ void XE::wrapTGLWriteDekel(void *port, uint32_t index, uint32_t value) { #include "kern_dsi_adapter_backing.inc" #include "kern_dsi_adapter_modes.inc" #include "kern_dsi_adapter_lifecycle.inc" +#include "kern_display13_framebuffer_power.inc" #include "kern_dsi_adapter_diagnostics.inc" diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e4a4934b..067021d3 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -73,7 +73,7 @@ class XE { * @return true if patched anything */ bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); - bool parkedDSIFramebuffer(IOService *fb) const; + bool parkedDisplay13Framebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; private: @@ -247,17 +247,19 @@ class XE { static bool dsiSharedWriterStopped(IOService *,unsigned); bool processDSINativeWriters(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool processDSINativeTransactions(KernelPatcher &,size_t,mach_vm_address_t,size_t); - bool processDSINativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool processDisplay13NativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgDSIAttributes[4] {}; - mach_vm_address_t dsiFramebufferEvent {}; + mach_vm_address_t display13FramebufferEvent {}; uint32_t dsiWakeDegraded {},dsiWakeRejections {}; - bool dsiSystemWakeNotify {},dsiPagingLayoutKnown {},dsiFirstWakeLatch {}; - bool dsiSharedPowerRestored {}; // Native global wake preceded the DSI power callback. - void finishDSISystemWake(IOService *); + bool dsiSystemWakeNotify {},display13PagingLayoutKnown {},dsiFirstWakeLatch {}; + uint32_t dsiRestoredEpoch {}; // Last shared reset consumed by this transport. + bool finishDisplay13SystemWake(IOService *); + uint32_t displayWakeEventEpoch {},displayWakeEventBusy {},displayWakeEventErrors {}; + uint8_t displayWakeEventPhase[3] {}; static uint32_t wrapDSISetStream(IOService *,IOService *,OSObject *,const void *); static uint32_t wrapDSISetAttribute(IOService *,uint32_t,uintptr_t); static uint32_t wrapDSISetConnectionAttribute(IOService *,int32_t,uint32_t,uintptr_t); - static uint32_t wrapDSISetPowerState(IOService *,uint32_t); + static uint32_t wrapDisplay13SetPowerState(IOService *,uint32_t); mach_vm_address_t orgDSITransaction[5] {}; static int beginDSIFramebufferWriter(IOService *); static uint32_t wrapDSIFlipTransaction(IOService *,void *,uint64_t,void *); @@ -406,6 +408,11 @@ class XE { static void wrapAlderLakeNPowerInit(void *power, void *controller); static void wrapAlderLakeNEngineEnable(void *power); static void wrapAlderLakeNEngineDisable(void *power); + static void stopDisplay13Engine(void *power,bool systemSuspend); + static bool enableDisplay13EngineLocked(void *power); + static bool restoreDisplay13ResourcesLocked(void *power,bool waking); + static bool restoreDisplay13Ports(IOService *controller,unsigned onlyDDI=UINT32_MAX); + static bool prepareDisplay13Wake(IOService *fb); static void wrapAlderLakeNPowerOverride(void *power,bool enable); static void wrapAlderLakeNPowerRestore(void *power,bool waking); __attribute__((noinline)) bool processDisplay13FramebufferLifecycle(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); diff --git a/tests/display13_dbuf.cpp b/tests/display13_dbuf.cpp index 38351cc4..71315212 100644 --- a/tests/display13_dbuf.cpp +++ b/tests/display13_dbuf.cpp @@ -18,6 +18,7 @@ struct IO { r[0x60400+pipe*0x1000]=0; if(pipe)r[0x7027C+pipe*0x1000]=r[0x7017C+pipe*0x1000]=0; r[0x70080+pipe*0x1000]=0; + r[0x7003C+pipe*0x1000]=0; for(unsigned d=0;d<3;++d)r[0x70B08+pipe*0x1000+d*0x100]=0; for(unsigned plane=pipe ? 0 : 1;plane<5;++plane)r[0x70180+pipe*0x1000+plane*0x100]=0; } @@ -31,6 +32,18 @@ struct IO { void delayUs(unsigned us) {delays+=us;} }; int main() { + // Port probing may briefly power a reset bank before the next modeset. + // A snapshot during that get/put must preserve the pre-sleep credits. + { + IO io;State s;assert(begin(io,s,7)==Status::Complete); + io.r[0x7003C]=0x01A50A05; + assert(captureCredits(io,s)==Status::Complete); + const auto saved=s.credit[0];assert(saved); + s.restorePending=1;io.r[0x7003C]=0; + assert(captureCredits(io,s)==Status::Complete && s.credit[0]==saved); + io.r[0x45404]&=~(3U<<10); + assert(captureCredits(io,s)==Status::Complete && (s.creditSaved&1) && s.credit[0]==saved); + } // Reproduce system sleep with a retained external allocation but zeroed // DDB shadows. Do not invent new ownership or drop pending retirements. for(unsigned p:{1U,2U})for(unsigned variant=0;variant<8;++variant) { diff --git a/tests/display13_dbuf_setup.py b/tests/display13_dbuf_setup.py index c25b037c..0d6ea124 100644 --- a/tests/display13_dbuf_setup.py +++ b/tests/display13_dbuf_setup.py @@ -36,13 +36,14 @@ using namespace Display13DBUF; for(unsigned pipe=0;pipe<3;++pipe){ IOMemoryMap map;Display13FBDBUFIO io{&map};State s; - s.begun=true;s.selected=s.restorePending=1U<1: b=Path(sys.argv[1]).read_bytes() install=re.search(r'bool XE::processDisplay13Shutdown\(.*?\n\}',s,re.S)[0] diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index c92e581e..1e89a7f7 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -5,13 +5,13 @@ import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=read_source(root/'WhateverXe/kern_xe.cpp') functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\([^;{]*\{.*?\n\}',s,re.S)[0] - for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState','beginDSIFramebufferWriter','wrapDSIFlipTransaction','wrapDSIGammaTransaction','wrapDSICscTransaction','wrapDSIPlaneGammaTransaction','wrapDSISetGammaTable','wrapDSISetAttribute','wrapDSISetConnectionAttribute','wrapDSISetPowerState','wrapDSISetStream']) + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState','beginDSIFramebufferWriter','wrapDSIFlipTransaction','wrapDSIGammaTransaction','wrapDSICscTransaction','wrapDSIPlaneGammaTransaction','wrapDSISetGammaTable','wrapDSISetAttribute','wrapDSISetConnectionAttribute','wrapDisplay13SetPowerState','wrapDSISetStream']) functions=functions.replace('void XE::endDSINativeWriter(int token) {', 'void XE::endDSINativeWriter(int token) {\n struct EndScope {EndScope(){endingWriter=true;}~EndScope(){endingWriter=false;}} endScope;') scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] if len(sys.argv)==2: binary=Path(sys.argv[1]).read_bytes() assert hashlib.sha256(binary).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' - for name,width,count in [('processDSINativeWriters',20,2),('processDSINativeTransactions',17,5),('processDSINativeAttributes',20,4)]: + for name,width,count in [('processDSINativeWriters',20,2),('processDSINativeTransactions',17,5),('processDisplay13NativeAttributes',20,4)]: installer=re.search(r'bool XE::'+name+r'\([^;{]*\{.*?\n\}',s,re.S)[0] offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] entries=re.search(r'entries\[\]\['+str(width)+r'\]=\{(.*?)\};',installer,re.S)[1] @@ -76,9 +76,10 @@ unsigned streamCalls=0;OSObject *expectedPath=nullptr; uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { + static bool prepareDisplay13Wake(IOService*){return true;} struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; - uintptr_t dsiFramebufferEvent=0;bool dsiSystemWakeNotify=false; - void finishDSISystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(dsiFramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);} + uintptr_t display13FramebufferEvent=0;bool dsiSystemWakeNotify=false; + bool finishDisplay13SystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(display13FramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);return true;} bool dsiPowerTest=false;struct {bool sleeping=false,failed=false;}dsiRuntimePower;unsigned wakeCalls=0;bool wakeOK=false; bool dsiStopTest=false,dsiMemoryReleased=false,dsiDDIReleased=false,stopOK=false;unsigned stopCalls=0; struct {bool done=false;bool complete(){return done;}}dsiClockStop; @@ -109,7 +110,7 @@ uint32_t saveDSIBrightness(IOService*,uintptr_t value){assert(fbLock.depth && !owner);return value<=UINT32_MAX?0U:kIOReturnUnsupported;} uint32_t setDSIBrightness(IOService*,uintptr_t value){assert(fbLock.depth && !owner);return value<=65535?0U:kIOReturnUnsupported;} static uint32_t wrapDSISetConnectionAttribute(IOService*,int32_t,uint32_t,uintptr_t); - static uint32_t wrapDSISetPowerState(IOService*,uint32_t); + static uint32_t wrapDisplay13SetPowerState(IOService*,uint32_t); uintptr_t orgDSITransaction[5]{}; static int beginDSIFramebufferWriter(IOService*); static uint32_t wrapDSIFlipTransaction(IOService*,void*,uint64_t,void*); @@ -241,7 +242,7 @@ c.refuse();c.refuseCursor();xe.wakeOK=false; assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnNotReady); assert(Display13FBMemory::field(&c.fb,0x4284)==0); - xe.dsiSystemWakeNotify=true;xe.dsiFramebufferEvent=reinterpret_cast(&systemEvent); + xe.dsiSystemWakeNotify=true;xe.display13FramebufferEvent=reinterpret_cast(&systemEvent); const auto eventsBefore=systemEvents; xe.wakeOK=true;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess); assert(!xe.dsiRuntimePower.sleeping && !xe.dsiStopAttempted && Display13FBMemory::field(&c.fb,0x4284)==2); @@ -380,7 +381,7 @@ assert(attrCalls[1]==before && !display13FBPower.dsiNativeWriters);} for(unsigned old=0;old<3;++old)for(unsigned state=0;state<4;++state){Case c;put(&c.fb,0x4284,old);const auto before=attrCalls[2]; const auto expected=old==2 && state==2?kIOReturnSuccess:kIOReturnUnsupported; - assert(XE::wrapDSISetPowerState(&c.fb,state)==expected && attrCalls[2]==before); + assert(XE::wrapDisplay13SetPowerState(&c.fb,state)==expected && attrCalls[2]==before); assert(Display13FBMemory::field(&c.fb,0x4284)==old); assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,state)==expected);} {Case c;put(&c.fb,0x4284,2U);assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,0x100000002ULL)==kIOReturnUnsupported);} @@ -389,7 +390,7 @@ if(bad==2)xe.dsiNativeWriterHooksReady=false;if(bad==3)display13FBPower.memoryFaulted=true; assert(XE::wrapDSISetAttribute(&c.fb,0x77737276,1)==kIOReturnNotReady); assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x72677363,1)==kIOReturnNotReady); - assert(XE::wrapDSISetPowerState(&c.fb,2)==kIOReturnNotReady); + assert(XE::wrapDisplay13SetPowerState(&c.fb,2)==kIOReturnNotReady); assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !display13FBPower.dsiNativeWriters);} for(bool disabled:{false,true}){Case c;expectedWriters=0;expectedValue=txOptions;expectedAttribute=0x706F7772;expectedConnection=4;expectedPower=0; IOService *fb=&c.fb; @@ -397,7 +398,7 @@ else{panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U);xe.dsiStopAttempted=true;} assert(XE::wrapDSISetAttribute(fb,expectedAttribute,expectedValue)==0xe0000121U); assert(XE::wrapDSISetConnectionAttribute(fb,expectedConnection,expectedAttribute,expectedValue)==0xe0000122U); - assert(XE::wrapDSISetPowerState(fb,expectedPower)==0xe0000123U);} + assert(XE::wrapDisplay13SetPowerState(fb,expectedPower)==0xe0000123U);} {Case c;auto before=streamCalls;assert(XE::wrapDSISetStream(&c.ctl,&c.fb,&c.path,¶mAnchor)==kIOReturnNotReady); assert(streamCalls==before && !fbLock.depth && !owner);} {Case c;panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U);expectedPath=&c.path; diff --git a/tests/host_publication.py b/tests/host_publication.py index be8cba00..dd781009 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -61,7 +61,7 @@ IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { - IOService *parked=nullptr;bool parkedDSIFramebuffer(IOService *f)const{return parked && f==parked;} + IOService *parked=nullptr;bool parkedDisplay13Framebuffer(IOService *f)const{return parked && f==parked;} IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} @@ -82,14 +82,21 @@ alignas(8) uint8_t accel[0xd00]{},machine[0x140]{}; *reinterpret_cast(accel+0x378)=machine; exercised.sleepFBCount=reinterpret_cast(+[](void*)->uint32_t{return 3;}); -exercised.sleepFBAt=reinterpret_cast(+[](void *m,unsigned i)->IOService*{assert(i==0);return *static_cast(m);}); -*reinterpret_cast(machine)=&gpu;exercised.xe.parked=&gpu; +exercised.sleepFBAt=reinterpret_cast(+[](void *m,unsigned i)->IOService*{assert(i<3);return static_cast(m)[i];}); +for(unsigned i=0;i<3;++i)reinterpret_cast(machine)[i]=&gpu;exercised.xe.parked=&gpu; for(unsigned asleep:{0U,1U})for(unsigned depth:{0U,2U,3U,9U})for(bool owned:{false,true}){ *reinterpret_cast(accel+0xc78)=asleep;*reinterpret_cast(machine+0x10c)=depth; exercised.xe.parked=owned?&gpu:nullptr; assert(XeHost::pendingSleepDisplays(accel)==(asleep && depth>=3 && owned?0U:3U)); } assert(exercised.repeatedSleep==2); +// FB0 parked cannot hide a newly awake or foreign peer at any index. +for(unsigned i=0;i<3;++i){ + reinterpret_cast(machine)[i]=&mei; + assert(XeHost::pendingSleepDisplays(accel)==3); + reinterpret_cast(machine)[i]=&gpu; +} +assert(exercised.repeatedSleep==2); const char *names[]={nullptr,"IMEI","HECI","MEI"}; for(unsigned firmware=0;firmware<2;++firmware)for(unsigned me=0;me<5;++me)for(unsigned test=0;test<6;++test){ mei.name=names[me%4];mei.renames=0;gpu.name="GFX0"; From 1dac6191e0223116cb2691610a3040014455a793 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 5 Nov 2026 00:59:09 +0900 Subject: [PATCH 24/46] Observe per-framebuffer display transaction queue drains --- .../kern_display13_framebuffer_power.inc | 9 ++- WhateverXe/kern_host.cpp | 55 +++++++++++++++++++ WhateverXe/kern_host.hpp | 4 ++ WhateverXe/kern_xe.hpp | 1 + tests/host_publication.py | 4 ++ 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc index 1c0770a1..f8a86c48 100644 --- a/WhateverXe/kern_display13_framebuffer_power.inc +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -57,14 +57,17 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { return result; } -bool XE::parkedDisplay13Framebuffer(IOService *fb) const { +bool XE::ownedDisplay13Framebuffer(IOService *fb) const { return fb && display13FBLifecycleHooksReady && display13FBABIReady && display13PagingLayoutKnown && !strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") && fb->getMetaClass()->getClassSize()>=0x4B90 && adlnTopologyController && Display13FBMemory::field(fb,0x1D0)==adlnTopologyController && Display13FBMemory::field(fb,0x1DC)<3 && - Display13FBMemory::field(adlnTopologyController,0xD60+8*Display13FBMemory::field(fb,0x1DC))==fb && - !(Display13FBMemory::field(fb,0x199)&0x20); + Display13FBMemory::field(adlnTopologyController,0xD60+8*Display13FBMemory::field(fb,0x1DC))==fb; +} + +bool XE::parkedDisplay13Framebuffer(IOService *fb) const { + return ownedDisplay13Framebuffer(fb) && !(Display13FBMemory::field(fb,0x199)&0x20); } bool XE::finishDisplay13SystemWake(IOService *caller) { diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 2f80035c..05c61408 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -107,6 +107,21 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a } } IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIDarkWakeReady",ready); + // Observe the queue under its existing native gate. No driver locks or + // live object pointers are retained by a registry serializer. + const auto idle=sym("__ZN18IOAccelDisplayPipe28transaction_queue_idle_gatedEv"); + const auto framebuffer=sym("__ZNK18IOAccelDisplayPipe14getFramebufferEv"); + const uint8_t idleFields[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x50,0x48,0x89,0xfb,0x8b,0x87,0x38,2,0,0,0x8b,0x8f,0x3c,2,0,0}; + const uint8_t framebufferField[]={0x55,0x48,0x89,0xe5,0x48,0x8b,0x87,0x98,0,0,0,0x5d,0xc3}; + const auto within=[&](mach_vm_address_t fn,size_t bytes){return fn>=address && fn-address<=size && bytes<=size-(fn-address);}; + bool queueReady=false; + if(ready && within(idle,sizeof(idleFields)) && within(framebuffer,sizeof(framebufferField)) && + !memcmp(reinterpret_cast(idle),idleFields,sizeof(idleFields)) && + !memcmp(reinterpret_cast(framebuffer),framebufferField,sizeof(framebufferField))) { + KernelPatcher::RouteRequest route("__ZN18IOAccelDisplayPipe28transaction_queue_idle_gatedEv",wrapDisplayQueueIdle,orgDisplayQueueIdle); + queueReady=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplayQueueObserverReady",queueReady); return; } if(backlight.loadIndex==index) { @@ -139,6 +154,46 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a } xe.processKext(patcher,index,address,size); } +void XeHost::recordDisplayQueue(void *pipe,uint32_t serial,uint32_t phase) { + auto bytes=static_cast(pipe); + auto fb=*reinterpret_cast(bytes+0x98); + if(!xe.ownedDisplay13Framebuffer(fb))return; + const auto index=*reinterpret_cast(reinterpret_cast(fb)+0x1DC); + uint64_t record[54] {1,serial,phase,index}; + clock_get_uptime(record+4); + record[5]=*reinterpret_cast(bytes+0x238); + record[6]=*reinterpret_cast(bytes+0x23c); + record[7]=*reinterpret_cast(bytes+0x248); + record[8]=*reinterpret_cast(bytes+0x250); + record[9]=*reinterpret_cast(reinterpret_cast(fb)+0x4284); + record[10]=*reinterpret_cast(reinterpret_cast(fb)+0x199); + auto accelerator=*reinterpret_cast(bytes+0x88); + if(accelerator) { + record[11]=*reinterpret_cast(accelerator+0xc78); + auto machine=*reinterpret_cast(accelerator+0x378); + if(machine)record[12]=*reinterpret_cast(machine+0x10c); + } + record[13]=*reinterpret_cast(bytes+0x29c); + record[14]=*reinterpret_cast(bytes+0x2a4); + record[15]=reinterpret_cast(pipe); + // Embedded event slots only: do not dereference transactions whose lifetime + // can end when the original action drops its gate to wait. + memcpy(record+16,bytes+0x118,4*0x48); + record[52]=*reinterpret_cast(bytes+0x280); + record[53]=*reinterpret_cast(bytes+0x299); + const char *enter[]={"WEXDisplayQueueEnter0","WEXDisplayQueueEnter1","WEXDisplayQueueEnter2"}; + const char *exit[]={"WEXDisplayQueueExit0","WEXDisplayQueueExit1","WEXDisplayQueueExit2"}; + IORegistryEntry::getRegistryRoot()->setProperty((phase==1?enter:exit)[index],record,sizeof(record)); +} + +void XeHost::wrapDisplayQueueIdle(void *pipe) { + auto h=callbackHost; + const auto serial=__atomic_add_fetch(&h->queueObservation,1,__ATOMIC_RELAXED); + h->recordDisplayQueue(pipe,serial,1); + FunctionCast(wrapDisplayQueueIdle,h->orgDisplayQueueIdle)(pipe); + h->recordDisplayQueue(pipe,serial,2); +} + uint32_t XeHost::pendingSleepDisplays(void *accelerator) { auto h=callbackHost; auto bytes=static_cast(accelerator); diff --git a/WhateverXe/kern_host.hpp b/WhateverXe/kern_host.hpp index e5e7f046..827c9424 100644 --- a/WhateverXe/kern_host.hpp +++ b/WhateverXe/kern_host.hpp @@ -12,6 +12,10 @@ class XeHost { mach_vm_address_t sleepFBCount {},sleepFBAt {}; uint32_t repeatedSleep {}; static uint32_t pendingSleepDisplays(void *); + mach_vm_address_t orgDisplayQueueIdle {}; + uint32_t queueObservation {}; + static void wrapDisplayQueueIdle(void *); + void recordDisplayQueue(void *,uint32_t,uint32_t); mach_vm_address_t orgWSAAAttribute {}; static IOReturn wrapWSAAAttribute(IOFramebuffer *,uint32_t,uint32_t); bool backlightEnabled {}; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 067021d3..ba2f85d2 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -73,6 +73,7 @@ class XE { * @return true if patched anything */ bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); + bool ownedDisplay13Framebuffer(IOService *fb) const; bool parkedDisplay13Framebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; diff --git a/tests/host_publication.py b/tests/host_publication.py index dd781009..8d6a94ba 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -14,6 +14,7 @@ #include #include "kern_xelp_platform.hpp" using mach_vm_address_t=uintptr_t; +void clock_get_uptime(uint64_t *value){*value=0;} struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}void setProperty(const char*,const void*,size_t){}}; std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086;uint8_t revision=0; struct Object {virtual ~Object()=default;}; @@ -55,6 +56,8 @@ struct XeHost { static XeHost *callbackHost;bool systemWakeRecovery=false; uintptr_t sleepFBCount=0,sleepFBAt=0;uint32_t repeatedSleep=0;static uint32_t pendingSleepDisplays(void*); + uintptr_t orgDisplayQueueIdle=0;uint32_t queueObservation=0; + static void wrapDisplayQueueIdle(void*);void recordDisplayQueue(void*,uint32_t,uint32_t); uintptr_t orgWSAAAttribute=0; static IOReturn wrapWSAAAttribute(IOFramebuffer*,uint32_t,uint32_t); bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; @@ -62,6 +65,7 @@ struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { IOService *parked=nullptr;bool parkedDisplay13Framebuffer(IOService *f)const{return parked && f==parked;} + bool ownedDisplay13Framebuffer(IOService *)const{return false;} IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} From 1c68d52218081b88c9965f1453c4d810c70b071e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 5 Nov 2026 01:08:13 +0900 Subject: [PATCH 25/46] Fix native queue observer entry signature --- WhateverXe/kern_host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 05c61408..90976606 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -111,7 +111,7 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a // live object pointers are retained by a registry serializer. const auto idle=sym("__ZN18IOAccelDisplayPipe28transaction_queue_idle_gatedEv"); const auto framebuffer=sym("__ZNK18IOAccelDisplayPipe14getFramebufferEv"); - const uint8_t idleFields[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x50,0x48,0x89,0xfb,0x8b,0x87,0x38,2,0,0,0x8b,0x8f,0x3c,2,0,0}; + const uint8_t idleFields[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x89,0xfb,0x8b,0x87,0x38,2,0,0,0x8b,0x8f,0x3c,2,0,0}; const uint8_t framebufferField[]={0x55,0x48,0x89,0xe5,0x48,0x8b,0x87,0x98,0,0,0,0x5d,0xc3}; const auto within=[&](mach_vm_address_t fn,size_t bytes){return fn>=address && fn-address<=size && bytes<=size-(fn-address);}; bool queueReady=false; From e71a706fa91f404515824bd6a13958c179c42d66 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 5 Nov 2026 01:22:54 +0900 Subject: [PATCH 26/46] Pair DSI power-off and power-on subscriber notifications --- .../kern_display13_framebuffer_power.inc | 41 +++++++++++++++++++ WhateverXe/kern_dsi_adapter_lifecycle.inc | 4 +- WhateverXe/kern_xe.hpp | 2 + tests/dsi_update_dispatch.py | 25 +++++++++-- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc index f8a86c48..87d30ed2 100644 --- a/WhateverXe/kern_display13_framebuffer_power.inc +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -6,6 +6,15 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { bool notifyWake=false; recordDSIClient(fb,9,state,UINT32_MAX); + // Subscribers can re-enter clients. Serialize this transport's callback, + // without holding its FB mutex across notifications. + auto busy=callbackXE->adlnOutputWiring.dsi && fb && + fb->getMetaClass()->getClassSize()>=0x4B90 && dsiModeTarget(fb) ? &callbackXE->dsiPowerCallbackBusy : nullptr; + if(busy && __atomic_exchange_n(busy,1U,__ATOMIC_ACQUIRE))return kIOReturnNotReady; + struct PowerCallback { + uint32_t *busy; + ~PowerCallback(){if(busy)__atomic_store_n(busy,0U,__ATOMIC_RELEASE);} + } callback{busy}; auto result=[&]() -> uint32_t { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; @@ -15,6 +24,25 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { auto controller=Display13FBMemory::field(fb,0x1D0); auto path=Display13FBMemory::singlePath(fb); auto &power=callbackXE->dsiRuntimePower; + if(state<2 && !power.sleeping && !power.failed && dsiNativeUpdateAllowed(fb) && + !callbackXE->dsiPowerOffNotified) { + if(!callbackXE->display13FramebufferEvent)return kIOReturnNotReady; + // Match prepareToExitWake's WillPowerOff notification + // before disabling the display and its completion IRQ. + IORecursiveLockUnlock(scope.lock);scope.lock=nullptr; + callbackXE->dsiPowerOffNotified=true; + ++callbackXE->dsiPowerOffEvents; + const auto event=reinterpret_cast(callbackXE->display13FramebufferEvent); + const auto status=event(fb,7,nullptr); + uint32_t notice[]={1,7,status,callbackXE->dsiPowerOffEvents,callbackXE->dsiPowerOnEvents}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPowerNotifications",notice,sizeof(notice)); + if(status!=kIOReturnSuccess)return status; + scope.lock=Display13FBMemory::field(fb,0x4220); + if(!scope.lock)return kIOReturnNotReady; + IORecursiveLockLock(scope.lock); + if(!dsiModeTarget(fb) || Display13FBMemory::singlePath(fb)!=path || + Display13FBMemory::field(fb,0x1D0)!=controller)return kIOReturnNotReady; + } const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); if(!ready)return kIOReturnNotReady; @@ -52,6 +80,14 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { if(result==kIOReturnSuccess && state==2 && (notifyWake || callbackXE->dsiSystemWakeNotify)) { if(callbackXE->finishDisplay13SystemWake(fb))callbackXE->dsiSystemWakeNotify=false; else result=kIOReturnNotReady; + } else if(result==kIOReturnSuccess && state==2 && busy && callbackXE->dsiPowerOffNotified) { + // Display-only doze has no system DidWake obligation. Pair its off + // notification only after the DSI path is usable again. + const auto event=reinterpret_cast(callbackXE->display13FramebufferEvent); + result=event ? event(fb,8,nullptr) : kIOReturnNotReady; + if(result==kIOReturnSuccess){callbackXE->dsiPowerOffNotified=false;++callbackXE->dsiPowerOnEvents;} + uint32_t notice[]={1,8,result,callbackXE->dsiPowerOffEvents,callbackXE->dsiPowerOnEvents}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPowerNotifications",notice,sizeof(notice)); } recordDSIClient(fb,9,state,result); return result; @@ -127,6 +163,11 @@ bool XE::finishDisplay13SystemWake(IOService *caller) { if(phase==1) { events[5+2*index]=event(fb,8,nullptr); phase=2; + if(internal && events[5+2*index]==kIOReturnSuccess && dsiPowerOffNotified) { + dsiPowerOffNotified=false;++dsiPowerOnEvents; + uint32_t notice[]={1,8,events[5+2*index],dsiPowerOffEvents,dsiPowerOnEvents}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPowerNotifications",notice,sizeof(notice)); + } if(events[5+2*index]!=kIOReturnSuccess)displayWakeEventErrors|=1U<<(index+3); } } diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 144e2967..8c83d71d 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -126,8 +126,10 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu // IOGraphics sends spwr before restart or power-off. Park the adopted // panel through its own power path while display resources are live. if(attribute==kIOSystemPowerAttribute && callbackXE->dsiPowerTest && dsiModeTarget(fb) && - (value==kIOMessageSystemWillRestart || value==kIOMessageSystemWillPowerOff)) + (value==kIOMessageSystemWillRestart || value==kIOMessageSystemWillPowerOff)) { + IORecursiveLockUnlock(scope.lock);scope.lock=nullptr; return wrapDisplay13SetPowerState(fb,0); + } if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ba2f85d2..3459f65b 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -253,6 +253,8 @@ class XE { mach_vm_address_t display13FramebufferEvent {}; uint32_t dsiWakeDegraded {},dsiWakeRejections {}; bool dsiSystemWakeNotify {},display13PagingLayoutKnown {},dsiFirstWakeLatch {}; + bool dsiPowerOffNotified {}; + uint32_t dsiPowerCallbackBusy {},dsiPowerOffEvents {},dsiPowerOnEvents {}; uint32_t dsiRestoredEpoch {}; // Last shared reset consumed by this transport. bool finishDisplay13SystemWake(IOService *); uint32_t displayWakeEventEpoch {},displayWakeEventBusy {},displayWakeEventErrors {}; diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 1e89a7f7..63e6b150 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -32,6 +32,7 @@ struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char *name,const void *data,unsigned bytes){ if(!strcmp(name,"WEXDSISystemWakeEvents")){assert(bytes==8);return;} + if(!strcmp(name,"WEXDSIPowerNotifications")){assert(bytes==20);return;} assert(!strcmp(name,"WEXDSIModeRequest") && bytes==64); const auto words=static_cast(data);assert(words[0]==1); }}; @@ -78,8 +79,9 @@ struct XE { static bool prepareDisplay13Wake(IOService*){return true;} struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; - uintptr_t display13FramebufferEvent=0;bool dsiSystemWakeNotify=false; - bool finishDisplay13SystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(display13FramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);return true;} + uintptr_t display13FramebufferEvent=0;bool dsiSystemWakeNotify=false,dsiPowerOffNotified=false; + uint32_t dsiPowerCallbackBusy=0,dsiPowerOffEvents=0,dsiPowerOnEvents=0; + bool finishDisplay13SystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(display13FramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);if(dsiPowerOffNotified){dsiPowerOffNotified=false;++dsiPowerOnEvents;}return true;} bool dsiPowerTest=false;struct {bool sleeping=false,failed=false;}dsiRuntimePower;unsigned wakeCalls=0;bool wakeOK=false; bool dsiStopTest=false,dsiMemoryReleased=false,dsiDDIReleased=false,stopOK=false;unsigned stopCalls=0; struct {bool done=false;bool complete(){return done;}}dsiClockStop; @@ -188,9 +190,11 @@ ++streamCalls;return 0xe0000124U; } templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +static uint32_t systemEvent(IOService*,int,void*); struct Case { IOService fb,ctl,peer;OSObject path,port;IOLock lock; Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=nestedTx=nestedAttribute=false;xe={};dsiFixedMode={}; + xe.display13FramebufferEvent=reinterpret_cast(&systemEvent); xe.orgDSIAttributes[0]=reinterpret_cast(nativeAttribute); xe.orgDSIAttributes[1]=reinterpret_cast(nativeConnectionAttribute); xe.orgDSIAttributes[2]=reinterpret_cast(nativePower); @@ -216,9 +220,24 @@ static unsigned systemEvents; static uint32_t systemEvent(IOService*,int event,void *arg){ assert(!fbLock.depth && !owner); - assert((event==4 && arg==reinterpret_cast(1)) || (event==8 && !arg));++systemEvents;return 0; + assert((event==4 && arg==reinterpret_cast(1)) || ((event==7 || event==8) && !arg));++systemEvents;return 0; } int main(){ + // Real power wrapper must notify without a retained outer attribute lock, + // preserve the off obligation across failed stop/wake, and avoid a false + // system DidWake for display-only doze. + {Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); + assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,1)==kIOReturnNotReady); + assert(xe.dsiPowerOffNotified && xe.dsiPowerOffEvents==1 && !xe.dsiPowerCallbackBusy); + xe.stopOK=true;assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,1)==0); + assert(xe.dsiPowerOffEvents==1); + assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,2)==kIOReturnNotReady); + assert(!xe.dsiPowerOnEvents && xe.dsiPowerOffNotified); + xe.wakeOK=true;auto n=systemEvents; + assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,2)==0); + assert(systemEvents==n+1 && xe.dsiPowerOnEvents==1 && !xe.dsiPowerOffNotified); + } + for(auto message:{kIOMessageSystemWillRestart,kIOMessageSystemWillPowerOff}){Case c;put(&c.fb,0x4284,2U); auto notify=[&]{return XE::wrapDSISetAttribute(&c.fb,kIOSystemPowerAttribute,message);}; From 5d06b71420eacb3a118d2569f0f9143be70d84e2 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 5 Nov 2026 01:36:42 +0900 Subject: [PATCH 27/46] Preserve native ownership of system wake notifications --- .../kern_display13_framebuffer_power.inc | 15 ++- tests/display13_wake_notifications.py | 91 +++++++++++++++++++ tools/test.py | 2 +- 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 tests/display13_wake_notifications.py diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc index 87d30ed2..e09ffb63 100644 --- a/WhateverXe/kern_display13_framebuffer_power.inc +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -134,7 +134,7 @@ bool XE::finishDisplay13SystemWake(IOService *caller) { } using Event=uint32_t (*)(IOService *,int,void *); const auto event=reinterpret_cast(display13FramebufferEvent); - uint32_t events[14]={3,display13PagingLayoutKnown};bool complete=true; + uint32_t events[18]={4,display13PagingLayoutKnown};bool complete=true; for(unsigned index=0;index<3;++index) { IOLockLock(c.lock); auto fb=Display13FBMemory::field(controller,0xD60+8*index); @@ -149,6 +149,15 @@ bool XE::finishDisplay13SystemWake(IOService *caller) { // Native peers already notified themselves if paging is active. The // DSI adapter bypassed those native calls and owns its own pair. const bool internal=adlnOutputWiring.dsi && index==0; + const auto nativePower=Display13FBMemory::field(fb,0x4284); + events[14+index]=nativePower; + // Native Sleep(0)->Doze/Wake runs prepareToExitSleep, which owns + // DidWake(true/false). Do not preempt that callback merely because + // its paging bit is still clear while another FB has already resumed. + // Doze(1)->Wake does not run prepareToExitSleep; an otherwise parked + // client in that state still needs the common notification below. + if(!phase && !internal && pending && nativePower==0)phase=3; + if(phase==3)events[17]|=1U<release(); } IOLockLock(c.lock);const bool sameEpoch=c.restoreEpoch==epoch;IOLockUnlock(c.lock); diff --git a/tests/display13_wake_notifications.py b/tests/display13_wake_notifications.py new file mode 100644 index 00000000..5ce0fcb8 --- /dev/null +++ b/tests/display13_wake_notifications.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Execute the production wake notifier with native-before/after interleavings. + +The native Sleep -> Doze/Wake contract is from prepareToExitSleep in the +identified framebuffer donor. This fixture does not emulate native MMIO or +IOGraphics thread scheduling; those remain live validation requirements. +""" +from pathlib import Path +import re, subprocess, tempfile + +root = Path(__file__).resolve().parents[1] +source = (root/'WhateverXe/kern_display13_framebuffer_power.inc').read_text() +function = re.search(r'bool XE::finishDisplay13SystemWake\([^;{]*\{.*?\n\}', source, re.S)[0] +code = r''' +#include "kern_display13_fb_memory.hpp" +#include +#include +struct IOService { + alignas(8) uint8_t bytes[0x4B90]{}; unsigned refs=1; + IOService *getMetaClass(){return this;} + unsigned getClassSize(){return sizeof(bytes);} + void retain(){++refs;} void release(){assert(refs>1);--refs;} +}; +using IOLock=int; unsigned held=0; +void IOLockLock(IOLock*){assert(!held);++held;} +void IOLockUnlock(IOLock*){assert(held==1);--held;} +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char*,const void*,unsigned){} +}; +constexpr uint32_t kIOReturnSuccess=0; +struct XE { + uintptr_t display13FramebufferEvent=0; IOService *adlnTopologyController=nullptr; + bool display13PagingLayoutKnown=true,dsiPowerOffNotified=true; + uint32_t dsiPowerOffEvents=1,dsiPowerOnEvents=0; + struct {IOLock *lock=nullptr;uint32_t restoreEpoch=1;}display13FBPower; + struct {bool dsi=true;}adlnOutputWiring; + uint32_t displayWakeEventBusy=0,displayWakeEventEpoch=0,displayWakeEventErrors=0; + uint32_t displayWakeEventPhase[3]{}; + bool restoreDisplay13Ports(IOService*){assert(!held);return true;} + bool finishDisplay13SystemWake(IOService*); +}; +FUNCTION +template void put(IOService &fb,unsigned offset,T value){memcpy(fb.bytes+offset,&value,sizeof(value));} +unsigned wakes[3]{},ons[3]{}; bool powerThread=true; +uint32_t event(IOService *fb,int what,void *arg){ + assert(!held); + if(!powerThread)return 0; // handleEvent's successful no-delivery path + const auto i=Display13FBMemory::field(fb,0x1DC); + if(what==4){assert(arg==reinterpret_cast(1));++wakes[i];fb->bytes[0x199]|=0x20;} + else {assert(what==8 && !arg);++ons[i];} + return 0; +} +int main(){ + IOService ctl,fb[3]; IOLock lock=0;XE xe; + xe.adlnTopologyController=&ctl;xe.display13FBPower.lock=&lock; + xe.display13FramebufferEvent=reinterpret_cast(&event); + for(unsigned i=0;i<3;++i){put(ctl,0xD60+8*i,&fb[i]);put(fb[i],0x1D0,&ctl);put(fb[i],0x1DC,i);} + auto reset=[&](bool nativeFirst){ + ++xe.display13FBPower.restoreEpoch;xe.dsiPowerOffNotified=true; + for(unsigned i=0;i<3;++i){wakes[i]=ons[i]=0;fb[i].bytes[0x199]=0;} + put(fb[0],0x4284,2U);put(fb[1],0x4284,0U);put(fb[2],0x4284,1U); + if(nativeFirst){event(&fb[1],4,reinterpret_cast(1));event(&fb[1],8,nullptr);put(fb[1],0x4284,2U);} + }; + // Same topology, either callback order: each client receives one wake. + // FB2 is parked in Doze; native Doze -> Wake emits no DidWake. + for(bool nativeFirst:{false,true}){ + reset(nativeFirst);assert(xe.finishDisplay13SystemWake(&fb[0])); + assert(wakes[1]==unsigned(nativeFirst)); + if(!nativeFirst){event(&fb[1],4,reinterpret_cast(1));event(&fb[1],8,nullptr);put(fb[1],0x4284,2U);} + assert(xe.finishDisplay13SystemWake(&fb[1])); + for(unsigned i=0;i<3;++i)assert(wakes[i]==1 && ons[i]==1 && fb[i].refs==1); + } + // An inactive native client already parked in Wake also needs one wake; + // no new native transition will notify it. + reset(false);put(fb[1],0x4284,2U);assert(xe.finishDisplay13SystemWake(&fb[0]));assert(wakes[1]==1); + // No-DSI controller retains native ownership, including its FB0. + reset(false);xe.adlnOutputWiring.dsi=false;put(fb[0],0x4284,0U); + assert(xe.finishDisplay13SystemWake(&fb[1]));assert(!wakes[0] && !wakes[1] && wakes[2]==1); + // A successful return without paging activation must remain retryable. + reset(false);xe.adlnOutputWiring.dsi=true;powerThread=false; + assert(!xe.finishDisplay13SystemWake(&fb[0]));assert(!wakes[0]); + powerThread=true;assert(xe.finishDisplay13SystemWake(&fb[0]));assert(wakes[0]==1); + assert(!xe.displayWakeEventBusy && !held); + puts("production wake notification ownership: OK"); +} +'''.replace('FUNCTION',function).replace('#include ', '#include \n#include ') +with tempfile.TemporaryDirectory() as temp: + path=Path(temp);(path/'fixture.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(path/'fixture.cpp'),'-o',str(path/'fixture')],check=True) + subprocess.run([path/'fixture'],check=True) diff --git a/tools/test.py b/tools/test.py index 95524582..0347e043 100644 --- a/tools/test.py +++ b/tools/test.py @@ -10,7 +10,7 @@ python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'display13_panel_runtime', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_cursor_resize','dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] -python_tests += ['display13_shutdown'] +python_tests += ['display13_shutdown','display13_wake_notifications'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] if a.donor:jobs += [('display13_agdc_native',[sys.executable,str(R/'tests/display13_agdc.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('display13_shutdown_native',[sys.executable,str(R/'tests/display13_shutdown.py'),str(a.donor.resolve())],None)] From 43a5fb09cf96d7b71e40649021f9b877251e0892 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 6 Nov 2026 02:38:32 +0900 Subject: [PATCH 28/46] Resolve host patch sites independently of build and code placement Use Lilu symbol routing, instruction decoding and pattern search for host power and pmap contracts. Select IOSurface adaptation from exported getters, wait for host notifications before donor setup, and retain failed-wake state through WSAA handling. Keep fixed donor and register/field ABI checks while removing host build-date and recovery OS-name gates. --- .../kern_display13_framebuffer_power.inc | 39 ++- WhateverXe/kern_dsi_adapter_lifecycle.inc | 3 +- WhateverXe/kern_host.cpp | 161 ++++----- WhateverXe/kern_host.hpp | 9 +- WhateverXe/kern_host_power_contract.hpp | 148 ++++++++ WhateverXe/kern_tgl_recovery.hpp | 4 +- WhateverXe/kern_xe.cpp | 327 ++++++++++-------- WhateverXe/kern_xe.hpp | 10 +- WhateverXe/kern_xelp_native_placement.hpp | 121 ++++++- WhateverXe/kern_xelp_os.hpp | 23 +- tests/dsi_update_dispatch.py | 2 + tests/host_power_contract.py | 71 ++++ tests/host_publication.py | 31 +- tests/kernel_os_updates.cpp | 6 +- tests/native_surface.cpp | 27 ++ tests/pmap_kernel_contract.py | 98 ++++-- 16 files changed, 767 insertions(+), 313 deletions(-) create mode 100644 WhateverXe/kern_host_power_contract.hpp create mode 100644 tests/host_power_contract.py create mode 100644 tests/native_surface.cpp diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc index e09ffb63..bd9c2a0a 100644 --- a/WhateverXe/kern_display13_framebuffer_power.inc +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -15,6 +15,10 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { uint32_t *busy; ~PowerCallback(){if(busy)__atomic_store_n(busy,0U,__ATOMIC_RELEASE);} } callback{busy}; + // Keep WSAA out of the native queue wait whenever this callback cannot + // finish hardware restoration or its notification obligation. This covers + // early failures too, not only the post-resume cadence diagnostic. + if(busy && state==2)__atomic_store_n(&callbackXE->dsiWakeIncomplete,1U,__ATOMIC_RELEASE); auto result=[&]() -> uint32_t { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; @@ -89,6 +93,7 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { uint32_t notice[]={1,8,result,callbackXE->dsiPowerOffEvents,callbackXE->dsiPowerOnEvents}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPowerNotifications",notice,sizeof(notice)); } + if(busy && state==2 && result==kIOReturnSuccess)__atomic_store_n(&callbackXE->dsiWakeIncomplete,0U,__ATOMIC_RELEASE); recordDSIClient(fb,9,state,result); return result; } @@ -220,22 +225,32 @@ bool XE::processDisplay13NativeAttributes(KernelPatcher &patcher,size_t index,ma int32_t wd,od; memcpy(&wd,reinterpret_cast(address+0x26F18),sizeof(wd)); memcpy(&od,reinterpret_cast(address+0x26814),sizeof(od)); - if(wd && od && address+0x26F1C+wd==address+0x26818+od) - display13FramebufferEvent=address+0x26F1C+wd; - if(display13FramebufferEvent) { - // Accept the pagingState field only when the linked handleEvent - // sets precisely this bit in both DidWake and DidPowerOn. - auto fn=reinterpret_cast(display13FramebufferEvent); - if(fn[0]==0xFF && fn[1]==0x25) { - const auto slot=XELPCall::relativeTarget(fn+2,reinterpret_cast(fn)+6); - memcpy(&fn,reinterpret_cast(slot),sizeof(fn)); + const auto imported=address+0x26F1C+wd; + if(wd && od && imported==address+0x26818+od) { + auto target=imported; + // Only inspect a linkage stub and pointer slot inside this + // already-qualified donor image. A direct host call needs no + // dereference before comparing its resolved symbol identity. + if(target!=hostFramebufferEvent && target>=address && target-address<=size && size-(target-address)>=6) { + const auto fn=reinterpret_cast(target); + if(fn[0]==0xFF && fn[1]==0x25) { + const auto slot=XELPCall::relativeTarget(fn+2,target+6); + if(slot>=address && slot-address<=size && size-(slot-address)>=sizeof(target)) + memcpy(&target,reinterpret_cast(slot),sizeof(target)); + } } - const uint8_t setPaging[]={0x41,0x80,0x8D,0x99,0x01,0x00,0x00,0x20}; - display13PagingLayoutKnown=fn && fn[0]==0x55 && !memcmp(fn+0x100,setPaging,sizeof(setPaging)) && - !memcmp(fn+0x1A8,setPaging,sizeof(setPaging)); + display13PagingLayoutKnown=hostFramebufferEvent && target==hostFramebufferEvent; + if(display13PagingLayoutKnown)display13FramebufferEvent=imported; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13PagingContractReady",display13PagingLayoutKnown); } } } + // A DSI writer replaces the native power callback and therefore owns its + // notifications and failed-wake escape. Refuse incomplete initialization + // here, before any GOP ownership or panel power is changed. + const bool hostReady=display13FramebufferEvent && display13PagingLayoutKnown && hostSleepReady && hostWakeRecoveryReady; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13HostPowerReady",hostReady); + if(adlnOutputWiring.dsi && !hostReady)return false; // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. KernelPatcher::RouteRequest routes[]={ {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 8c83d71d..522fa0f1 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -242,7 +242,8 @@ uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint bool XE::failedDSIWakeFor(IOService *fb) const { - if(!__atomic_load_n(&dsiWakeDegraded,__ATOMIC_ACQUIRE) || !fb || + if((!__atomic_load_n(&dsiWakeDegraded,__ATOMIC_ACQUIRE) && + !__atomic_load_n(&dsiWakeIncomplete,__ATOMIC_ACQUIRE)) || !fb || strcmp(fb->getMetaClass()->getClassName(),"AppleIntelFramebuffer") || fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1D0)!=adlnTopologyController)return false; diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 90976606..54e717f6 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -1,11 +1,25 @@ // Copyright © 2018 vit9696. Derived from WhateverGreen (BSD-3-Clause). #include "kern_host.hpp" +#include "kern_host_power_contract.hpp" +#include "kern_xelp_native_placement.hpp" +#include #include "kern_xelp_device.hpp" #include "kern_xe_data_access.hpp" #include "kern_backlight_profile.hpp" #include #include #include +static size_t hostInstructionSize(const uint8_t *bytes,size_t length) { + uint8_t code[32] {};memcpy(code,bytes,length(code),&ins); + return !(ins.flags&F_ERROR) && n<=length ? n : 0; +} +static size_t hostFunctionExtent(mach_vm_address_t fn,mach_vm_address_t base,size_t size) { + if(fn=size)return 0; + const auto remaining=size-(fn-base); + return HostPowerContract::extent(reinterpret_cast(fn),remaining<2048?remaining:2048,hostInstructionSize); +} XeHost *XeHost::callbackHost; struct FramebufferViewer : public IOFramebuffer { static IOMemoryMap *&getVramMap(IOFramebuffer *fb) { return static_cast(fb)->fVramMap; } @@ -16,6 +30,8 @@ static const char *accelPath[] { "/System/Library/Extensions/IOAcceleratorFamily static KernelPatcher::KextInfo accelFamily { "com.apple.iokit.IOAcceleratorFamily2",accelPath,arrsize(accelPath),{true},{},KernelPatcher::KextInfo::Unloaded }; static const char *backlightPath[] { "/System/Library/Extensions/AppleBacklight.kext/Contents/MacOS/AppleBacklight" }; static KernelPatcher::KextInfo backlight { "com.apple.driver.AppleBacklight",backlightPath,arrsize(backlightPath),{true},{},KernelPatcher::KextInfo::Unloaded }; +static const char *surfacePath[] { "/System/Library/Extensions/IOSurface.kext/Contents/MacOS/IOSurface" }; +static KernelPatcher::KextInfo surface { "com.apple.iokit.IOSurface",surfacePath,arrsize(surfacePath),{true},{},KernelPatcher::KextInfo::Unloaded }; void XeHost::init() { callbackHost=this; PE_parse_boot_argn("gfxrst",&resetFramebuffer,sizeof(resetFramebuffer)); @@ -25,6 +41,7 @@ void XeHost::init() { lilu.onKextLoadForce(&graphics); lilu.onKextLoadForce(&accelFamily); lilu.onKextLoadForce(&backlight); + lilu.onKextLoadForce(&surface); xe.init(); XeDataAccess::init(); } @@ -77,51 +94,70 @@ void XeHost::processKernel(KernelPatcher &patcher) { if(!active || resetFramebuffer==FB_DETECT){resetFramebuffer=FB_NONE;if(!systemWakeRecovery)graphics.switchOff();} DeviceInfo::deleter(info); } +void XeHost::flushDonors(KernelPatcher &patcher) { + if(!dependenciesSeen())return; + // Lilu enumerates already-loaded kmods in list order, not dependency order. + // Flush synchronously within Lilu's load notification once hosts are known. + // Keep the received donor order; never bypass Lilu's image validation by + // resolving an unnotified host image ourselves. + for(unsigned i=0;i(index,names[i],address,size);patcher.clearError(); + if(fn>=address && fn-address(fn); + const auto remaining=size-(fn-address);lengths[i]=remaining<32?remaining:32; + } + } + const auto layout=XELPNative::surfaceABI(functions,lengths); + xe.setHostSurfaceABI(static_cast(layout)); + surfaceSeen=true; + uint32_t state=static_cast(layout); + IORegistryEntry::getRegistryRoot()->setProperty("WEXHostSurfaceABI",&state,sizeof(state)); + flushDonors(patcher); + return; + } if(accelFamily.loadIndex==index) { - if(!systemWakeRecovery)return; + if(!systemWakeRecovery || accelSeen)return; // Only the first loop of system_will_sleep(false) opens mode changes. // Leave its subsequent orphan cleanup and the hardware sleep phase native. const auto sym=[&](const char *name) { auto v=patcher.solveSymbol(index,name,address,size);patcher.clearError();return v; }; - const uint8_t before[]={0x48,0x8b,0xbb,0x78,3,0,0,0xe8,0xdc,0xcb,0xfc,0xff,0x41,0xb6,1,0x85,0xc0,0x74,0x37}; - const uint8_t asleep[]={0xf6,0x83,0x78,0x0c,0,0,1}; - const uint8_t nesting[]={0x8b,0x83,0x0c,1,0,0,0x8d,0x48,1,0x89,0x8b,0x0c,1,0,0}; sleepFBCount=sym("__ZNK21IOAccelDisplayMachine19getFramebufferCountEv"); sleepFBAt=sym("__ZNK21IOAccelDisplayMachine16getIOFramebufferEj"); + const auto sleep=sym("__ZN22IOGraphicsAccelerator217system_will_sleepEib"); + const auto mode=sym("__ZN21IOAccelDisplayMachine24display_mode_will_changeEj"); + const auto sleepLength=hostFunctionExtent(sleep,address,size); + const auto modeLength=hostFunctionExtent(mode,address,size); + const auto site=HostPowerContract::sleepSite(reinterpret_cast(sleep),sleepLength,sleep,sleepFBCount,hostInstructionSize); bool ready=false; - if(size>0x412f0 && sleepFBCount==address+0xde8e && sleepFBAt==address+0xdfc0 && - sym("__ZN22IOGraphicsAccelerator217system_will_sleepEib")==address+0x41144 && - !memcmp(reinterpret_cast(address+0x411bc),asleep,sizeof(asleep)) && - !memcmp(reinterpret_cast(address+0xe104),nesting,sizeof(nesting)) && - !memcmp(reinterpret_cast(address+0x412a6),before,sizeof(before))) { - // Pass the owning accelerator instead of just the display machine. - uint8_t code[]={0x48,0x89,0xdf,0xe8,0,0,0,0,0x90,0x90,0x90,0x90}; - const auto distance=static_cast(reinterpret_cast(pendingSleepDisplays)-(address+0x412ae)); + if(sleepFBCount && sleepFBAt && site.valid && HostPowerContract::sleepFields( + reinterpret_cast(sleep),sleepLength,reinterpret_cast(mode),modeLength,hostInstructionSize,site.owner)) { + // Pass this accelerator in rdi. The host compiler may keep it in rbx or + // r14; the matched count query establishes which register owns it. + uint8_t code[]={0x48,0x89,0xdf,0xe8,0,0,0,0,0x90,0x90,0x90,0x90,0x90}; + code[0]=0x48|(site.owner>=8 ? 4 : 0);code[2]=0xC7|((site.owner&7)<<3); + const auto target=sleep+site.offset; + const auto distance=static_cast(reinterpret_cast(pendingSleepDisplays)-(target+8)); if(distance==static_cast(distance)) { const auto rel=static_cast(distance);memcpy(code+4,&rel,4); - patcher.routeBlock(address+0x412a6,code,sizeof(code)); - ready=!memcmp(reinterpret_cast(address+0x412a6),code,sizeof(code));patcher.clearError(); + patcher.routeBlock(target,code,site.length); + ready=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(reinterpret_cast(target),code,site.length);patcher.clearError(); } } IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIDarkWakeReady",ready); - // Observe the queue under its existing native gate. No driver locks or - // live object pointers are retained by a registry serializer. - const auto idle=sym("__ZN18IOAccelDisplayPipe28transaction_queue_idle_gatedEv"); - const auto framebuffer=sym("__ZNK18IOAccelDisplayPipe14getFramebufferEv"); - const uint8_t idleFields[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x89,0xfb,0x8b,0x87,0x38,2,0,0,0x8b,0x8f,0x3c,2,0,0}; - const uint8_t framebufferField[]={0x55,0x48,0x89,0xe5,0x48,0x8b,0x87,0x98,0,0,0,0x5d,0xc3}; - const auto within=[&](mach_vm_address_t fn,size_t bytes){return fn>=address && fn-address<=size && bytes<=size-(fn-address);}; - bool queueReady=false; - if(ready && within(idle,sizeof(idleFields)) && within(framebuffer,sizeof(framebufferField)) && - !memcmp(reinterpret_cast(idle),idleFields,sizeof(idleFields)) && - !memcmp(reinterpret_cast(framebuffer),framebufferField,sizeof(framebufferField))) { - KernelPatcher::RouteRequest route("__ZN18IOAccelDisplayPipe28transaction_queue_idle_gatedEv",wrapDisplayQueueIdle,orgDisplayQueueIdle); - queueReady=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplayQueueObserverReady",queueReady); + xe.setHostSleepReady(ready);accelSeen=true;flushDonors(patcher); return; } if(backlight.loadIndex==index) { @@ -135,65 +171,36 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a } if(XeDataAccess::processKext(patcher,index,address,size))return; if(graphics.loadIndex==index) { - if(systemWakeRecovery) { + if(systemWakeRecovery && !graphicsSeen) { const char *sym="__ZN13IOFramebuffer16setWSAAAttributeEjj"; - const auto fn=patcher.solveSymbol(index,sym,address,size); + // Lilu resolves the symbol, verifies the image range, and builds the + // entrance trampoline. This wrapper needs no private implementation ABI. + KernelPatcher::RouteRequest route(sym,wrapWSAAAttribute,orgWSAAAttribute); + const bool ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + xe.setHostWakeRecoveryReady(ready); + // Bound the host function by its own image, not by the Intel donor. + const auto event=patcher.solveSymbol(index,"__ZN13IOFramebuffer11handleEventEiPv",address,size); patcher.clearError(); - const uint8_t prologue[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x68}; - bool ready=false; - if(size>=0x1cd64+sizeof(prologue) && fn==address+0x1cd64 && !memcmp(reinterpret_cast(fn),prologue,sizeof(prologue))) { - KernelPatcher::RouteRequest route(sym,wrapWSAAAttribute,orgWSAAAttribute); - ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); - } + const auto eventLength=hostFunctionExtent(event,address,size); + const bool paging=eventLength && HostPowerContract::paging(reinterpret_cast(event),eventLength,hostInstructionSize); + xe.setHostFramebufferEvent(paging?event:0); + IORegistryEntry::getRegistryRoot()->setProperty("WEXHostPagingContractReady",paging); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIWakeRecoveryReady",ready); + graphicsSeen=true;flushDonors(patcher); } if(resetFramebuffer==FB_NONE)return; gIOFBVerboseBootPtr=patcher.solveSymbol(index,"__ZL16gIOFBVerboseBoot",address,size); if(gIOFBVerboseBootPtr){KernelPatcher::RouteRequest route("__ZN13IOFramebuffer6initFBEv",wrapFramebufferInit,orgFramebufferInit);patcher.routeMultiple(index,&route,1,address,size);} patcher.clearError();return; } - xe.processKext(patcher,index,address,size); -} -void XeHost::recordDisplayQueue(void *pipe,uint32_t serial,uint32_t phase) { - auto bytes=static_cast(pipe); - auto fb=*reinterpret_cast(bytes+0x98); - if(!xe.ownedDisplay13Framebuffer(fb))return; - const auto index=*reinterpret_cast(reinterpret_cast(fb)+0x1DC); - uint64_t record[54] {1,serial,phase,index}; - clock_get_uptime(record+4); - record[5]=*reinterpret_cast(bytes+0x238); - record[6]=*reinterpret_cast(bytes+0x23c); - record[7]=*reinterpret_cast(bytes+0x248); - record[8]=*reinterpret_cast(bytes+0x250); - record[9]=*reinterpret_cast(reinterpret_cast(fb)+0x4284); - record[10]=*reinterpret_cast(reinterpret_cast(fb)+0x199); - auto accelerator=*reinterpret_cast(bytes+0x88); - if(accelerator) { - record[11]=*reinterpret_cast(accelerator+0xc78); - auto machine=*reinterpret_cast(accelerator+0x378); - if(machine)record[12]=*reinterpret_cast(machine+0x10c); + if(xe.isIntelDonor(index) && !dependenciesSeen()) { + for(unsigned i=0;i(bytes+0x29c); - record[14]=*reinterpret_cast(bytes+0x2a4); - record[15]=reinterpret_cast(pipe); - // Embedded event slots only: do not dereference transactions whose lifetime - // can end when the original action drops its gate to wait. - memcpy(record+16,bytes+0x118,4*0x48); - record[52]=*reinterpret_cast(bytes+0x280); - record[53]=*reinterpret_cast(bytes+0x299); - const char *enter[]={"WEXDisplayQueueEnter0","WEXDisplayQueueEnter1","WEXDisplayQueueEnter2"}; - const char *exit[]={"WEXDisplayQueueExit0","WEXDisplayQueueExit1","WEXDisplayQueueExit2"}; - IORegistryEntry::getRegistryRoot()->setProperty((phase==1?enter:exit)[index],record,sizeof(record)); -} - -void XeHost::wrapDisplayQueueIdle(void *pipe) { - auto h=callbackHost; - const auto serial=__atomic_add_fetch(&h->queueObservation,1,__ATOMIC_RELAXED); - h->recordDisplayQueue(pipe,serial,1); - FunctionCast(wrapDisplayQueueIdle,h->orgDisplayQueueIdle)(pipe); - h->recordDisplayQueue(pipe,serial,2); + xe.processKext(patcher,index,address,size); } - uint32_t XeHost::pendingSleepDisplays(void *accelerator) { auto h=callbackHost; auto bytes=static_cast(accelerator); diff --git a/WhateverXe/kern_host.hpp b/WhateverXe/kern_host.hpp index 827c9424..c5f13325 100644 --- a/WhateverXe/kern_host.hpp +++ b/WhateverXe/kern_host.hpp @@ -9,13 +9,14 @@ class XeHost { static XeHost *callbackHost; XE xe; bool active {},systemWakeRecovery {}; + bool surfaceSeen {},graphicsSeen {},accelSeen {}; + struct PendingDonor {size_t index;mach_vm_address_t address;size_t size;}; + PendingDonor pendingDonors[2] {};unsigned pendingDonorCount {}; + bool dependenciesSeen() const {return surfaceSeen && (!systemWakeRecovery || (graphicsSeen && accelSeen));} + void flushDonors(KernelPatcher &); mach_vm_address_t sleepFBCount {},sleepFBAt {}; uint32_t repeatedSleep {}; static uint32_t pendingSleepDisplays(void *); - mach_vm_address_t orgDisplayQueueIdle {}; - uint32_t queueObservation {}; - static void wrapDisplayQueueIdle(void *); - void recordDisplayQueue(void *,uint32_t,uint32_t); mach_vm_address_t orgWSAAAttribute {}; static IOReturn wrapWSAAAttribute(IOFramebuffer *,uint32_t,uint32_t); bool backlightEnabled {}; diff --git a/WhateverXe/kern_host_power_contract.hpp b/WhateverXe/kern_host_power_contract.hpp new file mode 100644 index 00000000..391d06d5 --- /dev/null +++ b/WhateverXe/kern_host_power_contract.hpp @@ -0,0 +1,148 @@ +#pragma once +#include +#include + +// Host IOGraphics/IOAccelerator are OS components, not a fixed Intel donor. +// Match instruction operands and call targets; never their image offsets. +namespace HostPowerContract { +using Decode = size_t (*)(const uint8_t *,size_t); +inline bool equal(const uint8_t *a,const uint8_t *b,size_t n) { + for(size_t i=0;i=10 && p[i]==0x41 && p[i+1]==0x83 && p[i+2]==(r12?0xFC:0xFE)) { + // DidWake falls through the != branch; its paging write must be + // in that case. DidPowerOn branches directly to the other write. + if(p[i+3]==4 && p[i+4]==0x0F && p[i+5]==0x85) { + const auto end=int64_t(i+10)+int32_t(word(p+i+6)); + if(end>int64_t(i+10) && end<=int64_t(size)) { + size_t stop=i+10; + while(stop=sizeof(argument) && equal(p+branch,argument,sizeof(argument)))branch+=sizeof(argument); + if(size-branch>=2 && p[branch]==0x74) { + const auto target=int64_t(branch+2)+int8_t(p[branch+1]); + if(target>=0 && target+sizeof(bit)<=size && equal(p+target,bit,sizeof(bit)))++on; + } + } + } + const auto n=decode(p+i,size-i);if(!n)return false; + i+=n; + } + return wake==1 && on==1; +} +struct SleepSite { + size_t offset {}, length {}; + unsigned owner {}, success {}; + bool valid {}; +}; +inline bool savedRegister(unsigned reg) { return reg==3 || (reg>=12 && reg<=15); } +// Decode mov rdi,[callee-saved owner+disp32]. R12 needs an explicit SIB. +inline size_t machineLoad(const uint8_t *p,size_t size,unsigned &owner) { + if(size<7 || (p[0]!=0x48 && p[0]!=0x49) || p[1]!=0x8B || (p[2]&0xF8)!=0xB8)return 0; + owner=(p[2]&7)+((p[0]&1)?8:0); + if(!savedRegister(owner))return 0; + const size_t disp=(owner&7)==4 ? 4 : 3; + if(size=8)),0x89,uint8_t(0xF8|(owner&7))}; + return occurrences(p,size,save,sizeof(save),decode)==1; +} +inline SleepSite sleepSite(const uint8_t *p,size_t size,uintptr_t address,uintptr_t count,Decode decode) { + SleepSite result;unsigned hits=0,calls=0; + for(size_t i=0;isize-i)return {}; + if(n==5 && p[i]==0xE8 && address+i+5+int32_t(word(p+i+1))==count)++calls; + unsigned owner=0; + const auto load=machineLoad(p+i,size-i,owner); + const auto call=i+load; + if(load && size-call>=5 && p[call]==0xE8 && address+call+5+int32_t(word(p+call+1))==count) { + auto next=call+5; + unsigned high=0; + if(next=6 && (p[next]&0xF8)==0xB0 && p[next+1]==1) { + const unsigned success=(p[next]&7)+high; + next+=2; + if(savedRegister(success) && success!=owner && p[next]==0x85 && p[next+1]==0xC0 && p[next+2]==0x74) { + const auto end=int64_t(next+4)+int8_t(p[next+3]); + if(end>int64_t(next+4) && end<=int64_t(size) && ownerFromThis(p,i,owner,decode)) { + unsigned bounds=0; + for(size_t j=next+4;j=len+5 && p[j+len]==0xE8 && + address+j+len+5+int32_t(word(p+j+len+1))==count)++bounds; + const auto k=decode(p+j,size_t(end)-j);if(!k || k>size_t(end)-j)return {}; + j+=k; + } + if(bounds==1){result={i,load+5,owner,success,true};++hits;} + } + } + } + } + i+=n; + } + return hits==1 && calls==2 ? result : SleepSite{}; +} +inline bool sleepFields(const uint8_t *sleep,size_t sleepSize,const uint8_t *mode,size_t modeSize,Decode decode,unsigned owner) { + uint8_t asleep[9] {};size_t length=0; + if(owner>=8)asleep[length++]=0x41; + asleep[length++]=0xF6;asleep[length++]=0x80|(owner&7); + if((owner&7)==4)asleep[length++]=0x24; + asleep[length++]=0x78;asleep[length++]=0x0C; + asleep[length++]=0;asleep[length++]=0;asleep[length++]=1; + const uint8_t depthRBX[]={0x8B,0x83,0x0C,1,0,0,0x8D,0x48,1,0x89,0x8B,0x0C,1,0,0}; + const uint8_t depthR12[]={0x41,0x8B,0x84,0x24,0x0C,1,0,0,0x8D,0x48,1,0x41,0x89,0x8C,0x24,0x0C,1,0,0}; + return ownerFromThis(sleep,sleepSize,owner,decode) && + occurrences(sleep,sleepSize,asleep,length,decode)==1 && + ((ownerFromThis(mode,modeSize,3,decode) && occurrences(mode,modeSize,depthRBX,sizeof(depthRBX),decode)==1) || + (ownerFromThis(mode,modeSize,12,decode) && occurrences(mode,modeSize,depthR12,sizeof(depthR12),decode)==1)); +} +} diff --git a/WhateverXe/kern_tgl_recovery.hpp b/WhateverXe/kern_tgl_recovery.hpp index cf82e536..4da1a6b5 100644 --- a/WhateverXe/kern_tgl_recovery.hpp +++ b/WhateverXe/kern_tgl_recovery.hpp @@ -1,4 +1,4 @@ -// Exact TGL donor layout, with the 20G1443 ICL recovery caller contract. +// Exact TGL donor layout, with the native ICL single-pass recovery contract. // The caller must qualify the binary and select host scheduler 5 first. #ifndef kern_tgl_recovery_hpp #define kern_tgl_recovery_hpp @@ -15,7 +15,7 @@ struct Result { bool resetOK; }; -// ICL 20G1443 resetHardwareAndReplay (4A712): reset once, account a native +// ICL resetHardwareAndReplay: reset once, account a native // failure, resume scheduling, return to IOAccel's error/event processing. // TGL's lower reset still owns hardware reset, context replay and CSB handling. // Do not append a new stamp or wait inside this recovery work-loop callback. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 09d31cc5..22395d38 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -159,9 +159,12 @@ extern "C" __attribute__((naked,used)) void tglTahoeMatrixBridge() { "1: jmpq *_tglSonomaMatrixZero(%rip)\n" "2: jmpq *_tglSonomaMatrixOther(%rip)\n"); } -static bool tglSonomaSurfaceFramebufferReady=true; +static XELPNative::SurfaceABI tglHostSurfaceABI=XELPNative::SurfaceABI::Unknown; +void XE::setHostSurfaceABI(unsigned layout) { tglHostSurfaceABI=static_cast(layout); } +static bool tglSonomaSurfaceFramebufferReady=false; static bool tglInstallSonomaSurfaceABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size,bool framebuffer) { - if((XELP::kernelOS(version)!=XELP::OS::Sonoma) && (XELP::kernelOS(version)!=XELP::OS::Sequoia) && (XELP::kernelOS(version)!=XELP::OS::Tahoe))return true; + if(tglHostSurfaceABI==XELPNative::SurfaceABI::Unknown)return false; + if(tglHostSurfaceABI==XELPNative::SurfaceABI::Original)return true; struct Site {size_t offset,length;uint8_t before[6],after[6];}; const Site fb[] { {0x2B5E8,6,{0x8B,0x80,0xA4,0,0,0},{0x8B,0x80,0x80,0,0,0}}, // pixelFormat @@ -189,7 +192,7 @@ static bool tglInstallSonomaSurfaceABI(KernelPatcher &patcher,size_t index,mach_ if(!framebuffer) { tglSonomaMatrixZero=address+0x8130E;tglSonomaMatrixOne=address+0x81305;tglSonomaMatrixOther=address+0x81361; uint8_t jump[15] {0xFF,0x25,0,0,0,0}; - const auto target=reinterpret_cast((XELP::kernelOS(version)==XELP::OS::Tahoe) ? tglTahoeMatrixBridge : tglSonomaMatrixBridge); + const auto target=reinterpret_cast((tglHostSurfaceABI==XELPNative::SurfaceABI::Attachment6E) ? tglTahoeMatrixBridge : tglSonomaMatrixBridge); memcpy(jump+6,&target,sizeof(target));jump[14]=0x90; patcher.routeBlock(address+0x812F6,jump,sizeof(jump)); ready=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(reinterpret_cast(address+0x812F6),jump,sizeof(jump)); @@ -1924,38 +1927,76 @@ static bool tglPrepareMMIOBAR(IOMemoryDescriptor *bar) { tglPmapBARFirstPage=first;tglPmapBAREndPage=end; return true; } +static size_t tglNativeInstructionSize(const uint8_t *bytes,size_t length) { + // HDE may read ahead by 32 bytes. Decode a padded local copy, then require + // the complete instruction to lie in the nofault-read native span. + uint8_t code[32] {};memcpy(code,bytes,length(code),&ins); + return !(ins.flags&F_ERROR) && size<=length ? size : 0; +} +static bool tglNativeProtectCall(uintptr_t protect,uintptr_t pmap,XELPNative::ProtectABI abi) { + uint8_t code[512] {}; + return tglVM.nofault && protect && pmap && + tglVM.nofault(protect,reinterpret_cast(code),sizeof(code))==sizeof(code) && + XELPNative::protectCall(code,sizeof(code),protect,pmap,abi,tglNativeInstructionSize); +} +// The kernel can reorder basic blocks and symbols. Lilu locates the +// function and searches its code; our only extra constraints are the live +// instruction boundaries and the stack/register ABI used by each bridge. +struct TGLNativePmapCode { + static constexpr size_t capacity=0x4000; + uint8_t *storage {};size_t length {}; + TGLNativePmapCode(KernelPatcher &patcher,uintptr_t fn) { + const auto panic=patcher.solveSymbol(KernelPatcher::KernelID,"_panic");patcher.clearError(); + if(!tglVM.nofault || !fn || !panic)return; + storage=Buffer::create(capacity*4);if(!storage)return; + const auto copied=tglVM.nofault(fn,reinterpret_cast(storage),capacity); + if(copied)length=XELPNative::reachable(storage,copied,storage+capacity, + reinterpret_cast(storage+capacity*2),fn,panic,tglNativeInstructionSize); + } + ~TGLNativePmapCode(){if(storage)Buffer::deleter(storage);} + template bool find(const uint8_t (&pattern)[N],size_t &offset) const { + return length && XELPNative::uniqueInstruction(storage,length,storage+capacity, + pattern,N,offset,KernelPatcher::findPattern); + } +}; static void tglPrepareMontereyMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { tglMMIOUCReady=false; auto bar=pci->getDeviceMemoryWithRegister(0x10); auto fn=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_enter_options"); auto protect=patcher.solveSymbol(KernelPatcher::KernelID,"_IOProtectCacheMode"); auto kernelMap=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_map"); - const uint8_t frame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0x08,0x01,0,0}; + const uint8_t frame[] {0x55,0x48,0x89,0xE5}; // RBP frame; total stack allocation is not bridge ABI const uint8_t args[] {0x44,0x89,0x4D,0x88,0x89,0x95,0x70,0xFF,0xFF,0xFF,0x89,0xD7,0x49,0x8B,0x77,0x40}; const uint8_t nonEPT[] {0x48,0x89,0x75,0x90}; const uint8_t protection[] {0x89,0x4D,0x8C}; const uint8_t create[] {0x8B,0x4D,0x8C,0x89,0xCB,0x83,0xE3,1,0x48,0x09,0xD3,0xF6,0xC1,2}; const uint8_t update[] {0x8B,0x4D,0x8C,0x41,0x89,0xCE,0x41,0x83,0xE6,1,0x49,0x09,0xC6,0xF6,0xC1,2}; - const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const uint8_t protectCall[] {0xE8,0x1A,0x99,0x02,0}; + TGLNativePmapCode native(patcher,fn); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t createAt=0;const bool createReady=native.find(create,createAt); + size_t updateAt=0;const bool updateReady=native.find(update,updateAt); + const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Legacy); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && - pat==0x1040600070406ULL && fn && protect && kernelMap && - !memcmp(reinterpret_cast(fn),frame,sizeof(frame)) && - !memcmp(reinterpret_cast(fn+0x51),args,sizeof(args)) && - !memcmp(reinterpret_cast(fn+0x78),protection,sizeof(protection)) && - !memcmp(reinterpret_cast(fn+0x116),nonEPT,sizeof(nonEPT)) && - !memcmp(reinterpret_cast(fn+0x15F9),create,sizeof(create)) && - !memcmp(reinterpret_cast(fn+0x12CE),update,sizeof(update)) && - !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && - !memcmp(reinterpret_cast(protect+0xC1),protectCall,sizeof(protectCall)) && + pat==0x1040600070406ULL && fn && protect && kernelMap && protectCallReady && + frameReady && + argsReady && + protectionReady && + nonEPTReady && + createReady && + updateReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; if(ok) { tglProtectCacheMode=reinterpret_cast(protect); - tglPmapNewResume=fn+0x1607;tglPmapUpdateResume=fn+0x12DE; + tglPmapNewResume=fn+createAt+sizeof(create);tglPmapUpdateResume=fn+updateAt+sizeof(update); const uintptr_t targets[] {reinterpret_cast(tglMontereyPmapNewBridge),reinterpret_cast(tglMontereyPmapUpdateBridge)}; - const uintptr_t sites[] {fn+0x15F9,fn+0x12CE}; + const uintptr_t sites[] {fn+createAt,fn+updateAt}; const size_t lengths[] {sizeof(create),sizeof(update)}; for(unsigned i=0;i<2 && ok;++i) { uint8_t jump[16] {0xFF,0x25,0,0,0,0};jump[14]=jump[15]=0x90; @@ -1975,34 +2016,39 @@ static void tglPrepareVenturaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { auto fn=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_enter_options"); auto protect=patcher.solveSymbol(KernelPatcher::KernelID,"_IOProtectCacheMode"); auto kernelMap=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_map"); - const uint8_t frame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0xC8,0x00,0x00,0x00}; + const uint8_t frame[] {0x55,0x48,0x89,0xE5}; // RBP frame; total stack allocation is not bridge ABI const uint8_t flags[] {0x44,0x89,0x4D,0xBC}; const uint8_t args[] {0x89,0x55,0x94,0x89,0xD7,0x49,0x8B,0x75,0x40}; const uint8_t nonEPT[] {0x48,0x89,0x75,0xB0}; const uint8_t protection[] {0x89,0x4D,0xAC}; const uint8_t create[] {0x8B,0x75,0xAC,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xAC,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; - const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const uint8_t protectCall[] {0xE8,0xBA,0x51,0x02,0x00}; + TGLNativePmapCode native(patcher,fn); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t createAt=0;const bool createReady=native.find(create,createAt); + size_t updateAt=0;const bool updateReady=native.find(update,updateAt); + const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Legacy); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && - pat==0x1040600070406ULL && fn && protect && kernelMap && - !memcmp(reinterpret_cast(fn),frame,sizeof(frame)) && - !memcmp(reinterpret_cast(fn+0x14),flags,sizeof(flags)) && - !memcmp(reinterpret_cast(fn+0x47),args,sizeof(args)) && - !memcmp(reinterpret_cast(fn+0x64),protection,sizeof(protection)) && - !memcmp(reinterpret_cast(fn+0xC1),nonEPT,sizeof(nonEPT)) && - !memcmp(reinterpret_cast(fn+0x13F4),create,sizeof(create)) && - !memcmp(reinterpret_cast(fn+0x1097),update,sizeof(update)) && - !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && - !memcmp(reinterpret_cast(protect+0xC1),protectCall,sizeof(protectCall)) && + pat==0x1040600070406ULL && fn && protect && kernelMap && protectCallReady && + frameReady && + flagsReady && + argsReady && + protectionReady && + nonEPTReady && + createReady && + updateReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; if(ok) { tglProtectCacheMode=reinterpret_cast(protect); - tglPmapNewResume=fn+0x1403;tglPmapUpdateResume=fn+0x10A5; + tglPmapNewResume=fn+createAt+sizeof(create);tglPmapUpdateResume=fn+updateAt+sizeof(update); const uintptr_t targets[] {reinterpret_cast(tglVenturaPmapNewBridge),reinterpret_cast(tglVenturaPmapUpdateBridge)}; - const uintptr_t sites[] {fn+0x13F4,fn+0x1097}; + const uintptr_t sites[] {fn+createAt,fn+updateAt}; const size_t lengths[] {sizeof(create),sizeof(update)}; for(unsigned i=0;i<2 && ok;++i) { uint8_t jump[16] {0xFF,0x25,0,0,0,0};jump[14]=jump[15]=0x90; @@ -2016,64 +2062,51 @@ static void tglPrepareVenturaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { auto evidence=OSSerializer::forTarget(nullptr,tglMMIOEvidenceSerialize); if(evidence) {root->setProperty("TGLMMIOMappingEvidence",evidence);evidence->release();} } -static size_t tglNativeInstructionSize(const uint8_t *bytes,size_t length) { - // HDE may read ahead by 32 bytes. Decode a padded local copy, then require - // the complete instruction to lie in the nofault-read native span. - uint8_t code[32] {};memcpy(code,bytes,length(code),&ins); - return !(ins.flags&F_ERROR) && size<=length ? size : 0; -} -static bool tglNativeProtectCall(uintptr_t protect,uintptr_t pmap,bool tahoe) { - uint8_t code[512] {}; - return tglVM.nofault && protect && pmap && - tglVM.nofault(protect,reinterpret_cast(code),sizeof(code))==sizeof(code) && - XELPNative::protectCall(code,sizeof(code),protect,pmap,tahoe,tglNativeInstructionSize); -} static void tglPrepareTahoeMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { tglMMIOUCReady=false; auto bar=pci->getDeviceMemoryWithRegister(0x10); auto fn=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_enter_options"); auto protect=patcher.solveSymbol(KernelPatcher::KernelID,"_IOProtectCacheMode"); auto kernelMap=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_map"); - const uint8_t frame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0xC8,0x00,0x00,0x00}; + const uint8_t frame[] {0x55,0x48,0x89,0xE5}; // RBP frame; total stack allocation is not bridge ABI const uint8_t flags[] {0x44,0x89,0x6D,0xB0}; const uint8_t args[] {0x89,0x55,0x9C,0x89,0xD7,0x49,0x8B,0x76,0x40}; const uint8_t nonEPT[] {0x48,0x89,0x75,0xC0}; const uint8_t protection[] {0x89,0x4D,0xB4}; const uint8_t create[] {0x8B,0x75,0xB4,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB4,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; - const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const bool protectCallReady=tglNativeProtectCall(protect,fn,true); + TGLNativePmapCode native(patcher,fn); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t createAt=0;const bool createReady=native.find(create,createAt); + size_t updateAt=0;const bool updateReady=native.find(update,updateAt); + const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Tahoe); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && pat==0x1040600070406ULL && fn && protect && kernelMap && - !memcmp(reinterpret_cast(fn),frame,sizeof(frame)) && - !memcmp(reinterpret_cast(fn+0x15A),flags,sizeof(flags)) && - !memcmp(reinterpret_cast(fn+0x44),args,sizeof(args)) && - !memcmp(reinterpret_cast(fn+0x61),protection,sizeof(protection)) && - !memcmp(reinterpret_cast(fn+0xAA),nonEPT,sizeof(nonEPT)) && - !memcmp(reinterpret_cast(fn+0x10C1),create,sizeof(create)) && - !memcmp(reinterpret_cast(fn+0x1197),update,sizeof(update)) && - !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && + frameReady && + flagsReady && + argsReady && + protectionReady && + nonEPTReady && + createReady && + updateReady && protectCallReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; uint64_t guard[20] {1,tglVM.enabled,tglVM.pciBAR,bar ? bar->getLength() : 0,pat,bool(fn),bool(protect),bool(kernelMap)}; - if(fn && protect) { - const uintptr_t sites[] {fn,fn+0x15A,fn+0x44,fn+0x61,fn+0xAA,fn+0x10C1,fn+0x1197,protect}; - const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame}; - const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame)}; - for(unsigned i=0;i<8;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); - guard[16]=protectCallReady; - } + const bool checks[] {frameReady,flagsReady,argsReady,protectionReady,nonEPTReady,createReady,updateReady,protectCallReady}; + for(unsigned i=0;isetProperty("TGLTahoeMMIOGuard",guard,sizeof(guard)); if(ok) { tglProtectCacheMode=reinterpret_cast(protect); - tglPmapNewResume=fn+0x10D0;tglPmapUpdateResume=fn+0x11A5; + tglPmapNewResume=fn+createAt+sizeof(create);tglPmapUpdateResume=fn+updateAt+sizeof(update); const uintptr_t targets[] {reinterpret_cast(tglTahoePmapNewBridge),reinterpret_cast(tglTahoePmapUpdateBridge)}; - const uintptr_t sites[] {fn+0x10C1,fn+0x1197}; + const uintptr_t sites[] {fn+createAt,fn+updateAt}; const size_t lengths[] {sizeof(create),sizeof(update)}; for(unsigned i=0;i<2 && ok;++i) { uint8_t jump[16] {0xFF,0x25,0,0,0,0};jump[14]=jump[15]=0x90; @@ -2093,44 +2126,45 @@ static void tglPrepareSequoiaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { auto fn=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_enter_options"); auto protect=patcher.solveSymbol(KernelPatcher::KernelID,"_IOProtectCacheMode"); auto kernelMap=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_map"); - const uint8_t frame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0xC8,0x00,0x00,0x00}; + const uint8_t frame[] {0x55,0x48,0x89,0xE5}; // RBP frame; total stack allocation is not bridge ABI const uint8_t flags[] {0x44,0x89,0x65,0xB0}; const uint8_t args[] {0x89,0x55,0x94,0x89,0xD7,0x49,0x8B,0x76,0x40}; const uint8_t nonEPT[] {0x48,0x89,0x75,0xB8}; const uint8_t protection[] {0x89,0x4D,0xB4}; const uint8_t create[] {0x8B,0x75,0xB4,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB4,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; - const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const bool protectCallReady=tglNativeProtectCall(protect,fn,false); + TGLNativePmapCode native(patcher,fn); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t createAt=0;const bool createReady=native.find(create,createAt); + size_t updateAt=0;const bool updateReady=native.find(update,updateAt); + const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Extended); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && pat==0x1040600070406ULL && fn && protect && kernelMap && - !memcmp(reinterpret_cast(fn),frame,sizeof(frame)) && - !memcmp(reinterpret_cast(fn+0x159),flags,sizeof(flags)) && - !memcmp(reinterpret_cast(fn+0x35),args,sizeof(args)) && - !memcmp(reinterpret_cast(fn+0x52),protection,sizeof(protection)) && - !memcmp(reinterpret_cast(fn+0xB8),nonEPT,sizeof(nonEPT)) && - !memcmp(reinterpret_cast(fn+0x1390),create,sizeof(create)) && - !memcmp(reinterpret_cast(fn+0x1030),update,sizeof(update)) && - !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && + frameReady && + flagsReady && + argsReady && + protectionReady && + nonEPTReady && + createReady && + updateReady && protectCallReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; uint64_t guard[20] {1,tglVM.enabled,tglVM.pciBAR,bar ? bar->getLength() : 0,pat,bool(fn),bool(protect),bool(kernelMap)}; - if(fn && protect) { - const uintptr_t sites[] {fn,fn+0x159,fn+0x35,fn+0x52,fn+0xB8,fn+0x1390,fn+0x1030,protect}; - const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame}; - const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame)}; - for(unsigned i=0;i<8;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); - guard[16]=protectCallReady; - } + const bool checks[] {frameReady,flagsReady,argsReady,protectionReady,nonEPTReady,createReady,updateReady,protectCallReady}; + for(unsigned i=0;isetProperty("TGLSequoiaMMIOGuard",guard,sizeof(guard)); if(ok) { tglProtectCacheMode=reinterpret_cast(protect); - tglPmapNewResume=fn+0x139F;tglPmapUpdateResume=fn+0x103E; + tglPmapNewResume=fn+createAt+sizeof(create);tglPmapUpdateResume=fn+updateAt+sizeof(update); const uintptr_t targets[] {reinterpret_cast(tglSequoiaPmapNewBridge),reinterpret_cast(tglSequoiaPmapUpdateBridge)}; - const uintptr_t sites[] {fn+0x1390,fn+0x1030}; + const uintptr_t sites[] {fn+createAt,fn+updateAt}; const size_t lengths[] {sizeof(create),sizeof(update)}; for(unsigned i=0;i<2 && ok;++i) { uint8_t jump[16] {0xFF,0x25,0,0,0,0};jump[14]=jump[15]=0x90; @@ -2150,43 +2184,44 @@ static void tglPrepareSonomaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { auto fn=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_enter_options"); auto protect=patcher.solveSymbol(KernelPatcher::KernelID,"_IOProtectCacheMode"); auto kernelMap=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_map"); - const uint8_t frame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0xC8,0x00,0x00,0x00}; + const uint8_t frame[] {0x55,0x48,0x89,0xE5}; // RBP frame; total stack allocation is not bridge ABI const uint8_t flags[] {0x44,0x89,0x4D,0xB4}; const uint8_t args[] {0x89,0x55,0x9C,0x89,0xD6,0x4C,0x8B,0x6F,0x40}; const uint8_t nonEPT[] {0x4C,0x89,0x6D,0xB8}; const uint8_t protection[] {0x89,0x4D,0xB0}; const uint8_t create[] {0x8B,0x75,0xB0,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB0,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; - const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const uint8_t protectCall[] {0xE8,0xDC,0x8F,0x02,0x00}; + TGLNativePmapCode native(patcher,fn); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t createAt=0;const bool createReady=native.find(create,createAt); + size_t updateAt=0;const bool updateReady=native.find(update,updateAt); + const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Extended); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && - pat==0x1040600070406ULL && fn && protect && kernelMap && - !memcmp(reinterpret_cast(fn),frame,sizeof(frame)) && - !memcmp(reinterpret_cast(fn+0x14),flags,sizeof(flags)) && - !memcmp(reinterpret_cast(fn+0x48),args,sizeof(args)) && - !memcmp(reinterpret_cast(fn+0x65),protection,sizeof(protection)) && - !memcmp(reinterpret_cast(fn+0x103),nonEPT,sizeof(nonEPT)) && - !memcmp(reinterpret_cast(fn+0x12F2),create,sizeof(create)) && - !memcmp(reinterpret_cast(fn+0xFE1),update,sizeof(update)) && - !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && - !memcmp(reinterpret_cast(protect+0xBF),protectCall,sizeof(protectCall)) && + pat==0x1040600070406ULL && fn && protect && kernelMap && protectCallReady && + frameReady && + flagsReady && + argsReady && + protectionReady && + nonEPTReady && + createReady && + updateReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; uint64_t guard[20] {1,tglVM.enabled,tglVM.pciBAR,bar ? bar->getLength() : 0,pat,bool(fn),bool(protect),bool(kernelMap)}; - if(fn && protect) { - const uintptr_t sites[] {fn,fn+0x14,fn+0x48,fn+0x65,fn+0x103,fn+0x12F2,fn+0xFE1,protect,protect+0xBF}; - const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame,protectCall}; - const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame),sizeof(protectCall)}; - for(unsigned i=0;i<9;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); - } + const bool checks[] {frameReady,flagsReady,argsReady,protectionReady,nonEPTReady,createReady,updateReady,protectCallReady}; + for(unsigned i=0;isetProperty("TGLSonomaMMIOGuard",guard,sizeof(guard)); if(ok) { tglProtectCacheMode=reinterpret_cast(protect); - tglPmapNewResume=fn+0x1301;tglPmapUpdateResume=fn+0xFEF; + tglPmapNewResume=fn+createAt+sizeof(create);tglPmapUpdateResume=fn+updateAt+sizeof(update); const uintptr_t targets[] {reinterpret_cast(tglSonomaPmapNewBridge),reinterpret_cast(tglSonomaPmapUpdateBridge)}; - const uintptr_t sites[] {fn+0x12F2,fn+0xFE1}; + const uintptr_t sites[] {fn+createAt,fn+updateAt}; const size_t lengths[] {sizeof(create),sizeof(update)}; for(unsigned i=0;i<2 && ok;++i) { uint8_t jump[16] {0xFF,0x25,0,0,0,0};jump[14]=jump[15]=0x90; @@ -2223,28 +2258,36 @@ static void tglPrepareMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { auto fn=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_enter_options"); auto protect=patcher.solveSymbol(KernelPatcher::KernelID,"_IOProtectCacheMode"); auto kernelMap=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_map"); - const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const uint8_t protectCall[] {0xE8,0xC9,0x66,0x02,0x00}; const uint8_t frame[] {0x89,0x55,0x98,0x89,0xD0,0x48,0x89,0x85,0xF0,0xFE,0xFF,0xFF,0x48,0xC1,0xE0,0x0C}; + const uint8_t flags[] {0x44,0x89,0x4D,0xB4}; + const uint8_t protection[] {0x89,0x4D,0x9C}; + const uint8_t rootSource[] {0x48,0x8B,0x4F,0x40}; const uint8_t nonEPT[] {0x48,0x89,0x4D,0xA8,0x4C,0x89,0x7D,0xC0}; const uint8_t create[] {0x8B,0x75,0x9C,0x89,0xF1,0x83,0xE1,1,0x48,0x09,0xD1,0x40,0xF6,0xC6,2}; const uint8_t update[] {0x8B,0x75,0x9C,0x89,0xF0,0x83,0xE0,1,0x48,0x09,0xD0,0x40,0xF6,0xC6,2}; + TGLNativePmapCode native(patcher,fn); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); + size_t rootSourceAt=0;const bool rootSourceReady=native.find(rootSource,rootSourceAt); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t createAt=0;const bool createReady=native.find(create,createAt); + size_t updateAt=0;const bool updateReady=native.find(update,updateAt); + const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Legacy); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=native20G && tglPrepareMMIOBAR(bar) && - pat==0x1040600070406ULL && fn && protect && kernelMap && - !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && - !memcmp(reinterpret_cast(protect+0xD2),protectCall,sizeof(protectCall)) && + pat==0x1040600070406ULL && fn && protect && kernelMap && protectCallReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF && - !memcmp(reinterpret_cast(fn+0x48),frame,sizeof(frame)) && - !memcmp(reinterpret_cast(fn+0x157),nonEPT,sizeof(nonEPT)) && - !memcmp(reinterpret_cast(fn+0x15FA),create,sizeof(create)) && - !memcmp(reinterpret_cast(fn+0x1850),update,sizeof(update)); + frameReady && flagsReady && protectionReady && rootSourceReady && + nonEPTReady && + createReady && + updateReady; if (ok) { tglProtectCacheMode=reinterpret_cast(protect); - tglPmapEntryResume=fn+0x58; tglPmapNewResume=fn+0x1609; tglPmapUpdateResume=fn+0x185F; + tglPmapEntryResume=fn+frameAt+sizeof(frame); tglPmapNewResume=fn+createAt+sizeof(create); tglPmapUpdateResume=fn+updateAt+sizeof(update); const uintptr_t targets[] {reinterpret_cast(tglPmapEntryBridge),reinterpret_cast(tglPmapNewBridge),reinterpret_cast(tglPmapUpdateBridge)}; - const uintptr_t sites[] {fn+0x48,fn+0x15FA,fn+0x1850}; + const uintptr_t sites[] {fn+frameAt,fn+createAt,fn+updateAt}; for (unsigned i=0;i<3 && ok;i++) { uint8_t jump[16] {0xFF,0x25,0,0,0,0}; jump[14]=jump[15]=0x90; const size_t length=i ? 15 : 16; @@ -2270,30 +2313,14 @@ void tglVMPrepare(KernelPatcher &patcher,IOPCIDevice *pci) { auto nf=patcher.solveSymbol(KernelPatcher::KernelID,"_ml_nofault_copy"); patcher.clearError(); if (!verify || !kp || !pb || !pe || !nf) return; - const bool dp1=(XELP::kernelOS(version)==XELP::OS::Donor); - const bool monterey=(XELP::kernelOS(version)==XELP::OS::Monterey); - const bool ventura=(XELP::kernelOS(version)==XELP::OS::Ventura); - const bool sonoma=(XELP::kernelOS(version)==XELP::OS::Sonoma); - const bool sequoia=(XELP::kernelOS(version)==XELP::OS::Sequoia); - const bool tahoe=(XELP::kernelOS(version)==XELP::OS::Tahoe); - if(sequoia || tahoe) { - const auto store=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_pmap_store"); - patcher.clearError(); - uint8_t caller[96] {},accessor[64] {}; - const auto copy=reinterpret_cast(nf); - if(!store || copy(verify,reinterpret_cast(caller),sizeof(caller))!=sizeof(caller))return; - const auto target=XELPNative::rootCall(caller,sizeof(caller),verify,store); - if((target>>47)!=0x1FFFF || copy(target,reinterpret_cast(accessor),sizeof(accessor))!=sizeof(accessor) || - !XELPNative::rootAccessor(accessor,sizeof(accessor)))return; - } else { - const uint8_t callPrefix[] {0x48,0x89,0xDE,0xE8}; - if (memcmp(reinterpret_cast(verify+0x10),callPrefix,sizeof(callPrefix))) return; - int32_t displacement;memcpy(&displacement,reinterpret_cast(verify+0x14),4); - const auto target=verify+0x18+displacement; - if (target!=verify-(dp1 ? 0x3930 : monterey ? 0x4190 : ventura ? 0x3D70 : sonoma ? 0x3EF0 : 0x3AB0)) return; - const uint8_t rootLoad[] {0x48,0x8B,0x47,0x50}; - if (memcmp(reinterpret_cast(target+0x20),rootLoad,sizeof(rootLoad))) return; - } + const auto store=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_pmap_store"); + patcher.clearError(); + uint8_t caller[96] {},accessor[64] {}; + const auto copy=reinterpret_cast(nf); + if(!store || copy(verify,reinterpret_cast(caller),sizeof(caller))!=sizeof(caller))return; + const auto target=XELPNative::rootCall(caller,sizeof(caller),verify,store,kp); + if((target>>47)!=0x1FFFF || copy(target,reinterpret_cast(accessor),sizeof(accessor))!=sizeof(accessor) || + !XELPNative::rootAccessor(accessor,sizeof(accessor)))return; tglVM.nofault=reinterpret_cast(nf); if (!tglVMRead(kp,tglVM.kernelPmap) || !tglVMRead(pb,tglVM.directBase) || !tglVMRead(pe,tglVM.directEnd)) return; @@ -4523,6 +4550,11 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } } +bool XE::isIntelDonor(size_t index) const { + return (currentFramebuffer && currentFramebuffer->loadIndex==index) || + (currentGraphics && currentGraphics->loadIndex==index); +} + bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { // No full donor patch set or native display/GT dispatcher in this stage. if(adlpLoadOnly) { @@ -12131,12 +12163,11 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add // target returns after native reset/resume; its lower-level reset still // performs context replay. Port that CALLER contract only. No changed // completion values, queue drops, hardware reset or panic entry points. - // The same donor caller also stalls the accelerator resource lock on - // this Tahoe kernel. Keep the OS-family and exact donor guards: only the - // recovery caller changes; native reset/replay still owns completion. - const bool recoveryRequested=checkKernelArgument("-disablegfxfirmware") && - ((XELP::kernelOS(version)==XELP::OS::BigSur) || - (XELP::kernelOS(version)==XELP::OS::Tahoe)); + // IOAccel on Darwin 20-25 releases its recovery locks after this void + // callback returns. Native ICL uses the same single-pass caller; no host + // requires the TGL donor's extra stamp wait. Select by the firmware mode, + // exact donor and scheduler ABI, not by the OS used to observe a hang. + const bool recoveryRequested=checkKernelArgument("-disablegfxfirmware"); bool recoveryOK=!recoveryRequested; if (recoveryRequested) { const char *recovery="__ZN18IGAccelFIFOChannel22resetHardwareAndReplayEv"; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 3459f65b..e0c64d5e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -76,6 +76,11 @@ class XE { bool ownedDisplay13Framebuffer(IOService *fb) const; bool parkedDisplay13Framebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; + bool isIntelDonor(size_t index) const; + void setHostSurfaceABI(unsigned layout); + void setHostSleepReady(bool ready) { hostSleepReady=ready; } + void setHostWakeRecoveryReady(bool ready) { hostWakeRecoveryReady=ready; } + void setHostFramebufferEvent(mach_vm_address_t event) { hostFramebufferEvent=event; } private: bool adlpLoadOnly {}; @@ -250,8 +255,9 @@ class XE { bool processDSINativeTransactions(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool processDisplay13NativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgDSIAttributes[4] {}; - mach_vm_address_t display13FramebufferEvent {}; - uint32_t dsiWakeDegraded {},dsiWakeRejections {}; + mach_vm_address_t display13FramebufferEvent {},hostFramebufferEvent {}; + uint32_t dsiWakeDegraded {},dsiWakeIncomplete {},dsiWakeRejections {}; + bool hostSleepReady {},hostWakeRecoveryReady {}; bool dsiSystemWakeNotify {},display13PagingLayoutKnown {},dsiFirstWakeLatch {}; bool dsiPowerOffNotified {}; uint32_t dsiPowerCallbackBusy {},dsiPowerOffEvents {},dsiPowerOnEvents {}; diff --git a/WhateverXe/kern_xelp_native_placement.hpp b/WhateverXe/kern_xelp_native_placement.hpp index 152af629..669f762c 100644 --- a/WhateverXe/kern_xelp_native_placement.hpp +++ b/WhateverXe/kern_xelp_native_placement.hpp @@ -3,6 +3,73 @@ #define KERN_XELP_NATIVE_PLACEMENT_HPP #include "kern_xelp_call_target.hpp" namespace XELPNative { +// Lilu supplies instruction decoding and masked search. This small walk adds +// the property a byte search cannot establish: the site belongs to reachable +// code in this function, including blocks placed after an early return. +// Scratch storage is caller-owned (never a large kernel-stack allocation). +template size_t reachable(const uint8_t *bytes,size_t length,uint8_t *marks, + uint16_t *pending,uintptr_t address,uintptr_t panic,Decode decode) { + if(!bytes || !length || length>0x4000)return 0; + memset(marks,0,length);size_t queued=1,end=0;pending[0]=0; + while(queued) { + size_t pos=pending[--queued]; + while(pos=2 && bytes[trap]==0x0F && (bytes[trap+1]==0xB9 || bytes[trap+1]==0x0B)) { + if(trap+2>end)end=trap+2; + break; + } + const auto n=decode(bytes+pos,length-pos); + if(!n || n>15 || n>length-pos)return 0; + for(size_t k=1;kend)end=pos+n; + const auto op=bytes[pos]; + if(n==5 && op==0xE8 && XELPCall::targets(bytes+pos,n,address+pos,panic))break; + if(op==0xC3 || (op==0xC2 && n==3) || (n==2 && op==0x0F && bytes[pos+1]==0x0B))break; + bool branch=false,conditional=false;int32_t delta=0; + if(n==2 && (op==0xEB || (op>=0x70 && op<=0x7F))) { + branch=true;conditional=op!=0xEB;delta=int8_t(bytes[pos+1]); + } else if(n==5 && op==0xE9) { + branch=true;memcpy(&delta,bytes+pos+1,4); + } else if(n==6 && op==0x0F && (bytes[pos+1]&0xF0)==0x80) { + branch=conditional=true;memcpy(&delta,bytes+pos+2,4); + } + if(branch) { + const auto target=int64_t(pos+n)+delta; + if(target<0 || target>=int64_t(length)) { + if(conditional)return 0; + break; // external tail call; do not scan the following function + } + if(queued>=length)return 0; + pending[queued++]=uint16_t(target); + if(!conditional)break; + } + // Do not infer jump-table targets or mark the following bytes as code. + // A patch site must be reachable through resolved direct edges. + // Indirect CALLs do have a fallthrough. + size_t opcode=pos; + if((op&0xF0)==0x40)++opcode; + if(opcode+1 bool uniqueInstruction(const uint8_t *bytes,size_t length,const uint8_t *marks, + const uint8_t *pattern,size_t count,size_t &offset,Find find) { + size_t cursor=0,hits=0; + while(find(pattern,nullptr,count,bytes,length,&cursor)) { + if(marks[cursor]==1 && (cursor+count==length || marks[cursor+count]==1)) {offset=cursor;++hits;} + ++cursor; + } + return hits==1; +} // Only canonical, side-effect-free alignment NOPs may separate the expected // instructions. Never scan past an unrecognised instruction to find a suffix. struct Cursor { @@ -18,15 +85,44 @@ struct Cursor { } bool skip(size_t count) {if(count>length-pos)return false;pos+=count;return true;} }; +enum class SurfaceABI { Unknown, Original, Attachment6A, Attachment6E }; +// These exported getters are the meanings of the fields inlined by the fixed +// donor. OS family names do not establish the host object layout. +inline bool surfaceGetter(const uint8_t *bytes,size_t length,const char *load,size_t size) { + if(!bytes)return false; + Cursor c{bytes,length,0}; + // Frame omission and alignment do not change this leaf accessor's ABI. + auto framed=c;const bool frame=framed.take("\x55",1) && framed.take("\x48\x89\xe5",3); + if(frame)c=framed; + return c.take(load,size) && (!frame || c.take("\x5d",1)) && c.take("\xc3",1); +} +inline SurfaceABI surfaceABI(const uint8_t *const functions[4],const size_t lengths[4]) { + const auto getter=[&](unsigned i,const char *load,size_t n){return surfaceGetter(functions[i],lengths[i],load,n);}; + if(getter(0,"\x8b\x87\xa4\0\0\0",6) && getter(1,"\x8b\x87\xb0\0\0\0",6) && + getter(2,"\x8b\x47\x0c",3) && getter(3,"\x8b\x87\xb4\0\0\0",6))return SurfaceABI::Original; + if(!getter(0,"\x8b\x87\x80\0\0\0",6) || !getter(1,"\x8b\x87\x98\0\0\0",6) || + !getter(2,"\x8b\x47\x10",3))return SurfaceABI::Unknown; + if(getter(3,"\x48\x8b\x87\x78\x01\0\0\x0f\xb6\x40\x6a",11))return SurfaceABI::Attachment6A; + if(getter(3,"\x48\x8b\x87\x78\x01\0\0\x0f\xb6\x40\x6e",11))return SurfaceABI::Attachment6E; + return SurfaceABI::Unknown; +} // Decode the verifier's first call only after proving both argument sources: // original VA in RSI and symbol-resolved kernel_pmap_store in RDI. -inline uintptr_t rootCall(const uint8_t *bytes,size_t length,uintptr_t base,uintptr_t store) { +inline uintptr_t rootCall(const uint8_t *bytes,size_t length,uintptr_t base,uintptr_t store,uintptr_t global=0) { if(!bytes || !base || !store || length>UINTPTR_MAX-base)return 0; Cursor c {bytes,length,0}; if(!c.take("\x55",1) || !c.take("\x48\x89\xe5",3) || !c.take("\x53",1) || - !c.take("\x50",1) || !c.take("\x48\x89\xfb",3) || !c.take("\x48\x8d\x3d",3) || - !c.skip(4) || XELPCall::relativeTarget(bytes+c.pos-4,base+c.pos)!=store || - !c.take("\x48\x89\xde",3) || !c.take("\xe8",1) || !c.skip(4))return 0; + !c.take("\x50",1) || !c.take("\x48\x89\xfb",3))return 0; + auto argument=c; + if(argument.take("\x48\x8d\x3d",3) && argument.skip(4) && + XELPCall::relativeTarget(bytes+argument.pos-4,base+argument.pos)==store)c=argument; + else { + argument=c; + if(!global || !argument.take("\x48\x8b\x3d",3) || !argument.skip(4) || + XELPCall::relativeTarget(bytes+argument.pos-4,base+argument.pos)!=global)return 0; + c=argument; + } + if(!c.take("\x48\x89\xde",3) || !c.take("\xe8",1) || !c.skip(4))return 0; const auto target=XELPCall::relativeTarget(bytes+c.pos-4,base+c.pos); // The caller consumes the returned PTE pointer; an unrelated call is rejected. return c.take("\x48\x85\xc0",3) ? target : 0; @@ -42,19 +138,21 @@ inline bool rootAccessor(const uint8_t *bytes,size_t length) { // Scan a bounded native function on decoded instruction boundaries. Require // exactly one call to pmap, its complete register/stack argument setup, stack // cleanup, and the function's terminating RET. Embedded bytes are not calls. +enum class ProtectABI { Legacy, Extended, Tahoe }; template bool protectCall(const uint8_t *bytes,size_t length,uintptr_t base, - uintptr_t pmap,bool tahoe,Decode decode) { + uintptr_t pmap,ProtectABI abi,Decode decode) { if(!bytes || !base || !pmap || length>UINTPTR_MAX-base)return false; + const bool legacy=abi==ProtectABI::Legacy, extra=abi==ProtectABI::Extended; unsigned found=0; for(size_t pos=0;pos15 || size>length-pos)return false; if(size==1 && bytes[pos]==0xc3)return found==1; Cursor c {bytes,length,pos}; - bool args=(!tahoe || c.take("\x48\x83\xec\x08",4)) && - c.take("\x4c\x89\xff",3) && c.take("\x4c\x89\xee",3) && c.take("\x89\xc2",2) && - c.take("\x8b\x4d\xd4",3) && c.take("\x44\x8b\x4d\xd0",4) && - (tahoe || c.take("\x6a\0",2)) && c.take("\x48\x8d\x45\xb8",4) && + bool args=(extra || c.take("\x48\x83\xec\x08",4)) && + c.take(legacy?"\x48\x89\xdf":"\x4c\x89\xff",3) && c.take(legacy?"\x4c\x89\xe6":"\x4c\x89\xee",3) && c.take("\x89\xc2",2) && + c.take(legacy?"\x8b\x4d\xd0":"\x8b\x4d\xd4",3) && c.take(legacy?"\x44\x8b\x4d\xd4":"\x44\x8b\x4d\xd0",4) && + (!extra || c.take("\x6a\0",2)) && c.take("\x48\x8d\x45\xb8",4) && c.take("\x50",1) && c.take("\x6a\x20",2) && c.take("\x6a\x01",2) && c.take("\xe8",1); if(args && c.skip(4) && XELPCall::relativeTarget(bytes+c.pos-4,base+c.pos)==pmap && c.take("\x48\x83\xc4\x20",4)) { @@ -69,5 +167,10 @@ template bool protectCall(const uint8_t *bytes,size_t length,uintp } return false; } +template bool protectCall(const uint8_t *bytes,size_t length,uintptr_t base, + uintptr_t pmap,bool tahoe,Decode decode) { + return protectCall(bytes,length,base,pmap,tahoe?ProtectABI::Tahoe:ProtectABI::Extended,decode); +} + } #endif diff --git a/WhateverXe/kern_xelp_os.hpp b/WhateverXe/kern_xelp_os.hpp index ad85a66e..9ee4ca59 100644 --- a/WhateverXe/kern_xelp_os.hpp +++ b/WhateverXe/kern_xelp_os.hpp @@ -8,19 +8,13 @@ inline bool hostOS(OS os) { return os>=OS::BigSur && os<=OS::Tahoe; } inline OS kernelOS(const char *version) { - static const char *const versions[] = { - "Darwin Kernel Version 20.0.0: Sat Jun 13 17:58:16 PDT 2020; root:xnu-7090.0.0.111.5~1/RELEASE_X86_64", - "Darwin Kernel Version 20.6.0: Thu Jul 6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64", - "Darwin Kernel Version 21.6.0: Mon Jun 24 00:56:10 PDT 2024; root:xnu-8020.240.18.709.2~1/RELEASE_X86_64", - "Darwin Kernel Version 22.6.0: Tue Jul 15 08:22:28 PDT 2025; root:xnu-8796.141.3.713.2~2/RELEASE_X86_64", - "Darwin Kernel Version 23.6.0: Tue Jul 21 21:56:37 PDT 2026; root:xnu-10063.141.1.713.39~1/RELEASE_X86_64", - }; + // The original DP1 execution environment is a separate exact identity. + // Supported host families below never depend on XNU revision/date text. + static const char donor[]="Darwin Kernel Version 20.0.0: Sat Jun 13 17:58:16 PDT 2020; root:xnu-7090.0.0.111.5~1/RELEASE_X86_64"; if (!version) return OS::Unknown; - for (unsigned i=0;i(i+1); - } + const char *a=version,*b=donor; + while (*a && *a==*b) {++a;++b;} + if (*a==*b) return OS::Donor; // Servicing updates must not silently bypass ABI setup because the build // date/XNU revision changed. Darwin 24.6 cannot distinguish macOS 15.7 // from 15.8: the package enforces 15.8.x. Darwin 25.6+ covers 26.7+. @@ -43,7 +37,10 @@ inline OS kernelOS(const char *version) { const char *tail=end-(sizeof(suffix)-1); for (unsigned i=0;i=20 && numbers[0]<=24 && numbers[1]==6) + return static_cast(unsigned(OS::BigSur)+numbers[0]-20); if (numbers[0]==25 && numbers[1]>=6) return OS::Tahoe; return OS::Unknown; } diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 63e6b150..17bd6d86 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -80,6 +80,7 @@ static bool prepareDisplay13Wake(IOService*){return true;} struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; uintptr_t display13FramebufferEvent=0;bool dsiSystemWakeNotify=false,dsiPowerOffNotified=false; + uint32_t dsiWakeIncomplete=0; uint32_t dsiPowerCallbackBusy=0,dsiPowerOffEvents=0,dsiPowerOnEvents=0; bool finishDisplay13SystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(display13FramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);if(dsiPowerOffNotified){dsiPowerOffNotified=false;++dsiPowerOnEvents;}return true;} bool dsiPowerTest=false;struct {bool sleeping=false,failed=false;}dsiRuntimePower;unsigned wakeCalls=0;bool wakeOK=false; @@ -401,6 +402,7 @@ for(unsigned old=0;old<3;++old)for(unsigned state=0;state<4;++state){Case c;put(&c.fb,0x4284,old);const auto before=attrCalls[2]; const auto expected=old==2 && state==2?kIOReturnSuccess:kIOReturnUnsupported; assert(XE::wrapDisplay13SetPowerState(&c.fb,state)==expected && attrCalls[2]==before); + if(state==2)assert(xe.dsiWakeIncomplete==unsigned(expected!=kIOReturnSuccess)); assert(Display13FBMemory::field(&c.fb,0x4284)==old); assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,state)==expected);} {Case c;put(&c.fb,0x4284,2U);assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,0x100000002ULL)==kIOReturnUnsupported);} diff --git a/tests/host_power_contract.py b/tests/host_power_contract.py new file mode 100644 index 00000000..d8f8ee74 --- /dev/null +++ b/tests/host_power_contract.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Replay production host matchers against supplied extracted host functions. + +Inputs: directories containing -.bin and +-symbols.json (symbol -> virtual address), plus Lilu's hde64.c. +No Apple binaries or machine paths are embedded in this test. +""" +import argparse, json, re, subprocess, tempfile +from pathlib import Path + +p=argparse.ArgumentParser(description=__doc__) +p.add_argument('--host',type=Path,action='append',required=True) +p.add_argument('--hde-source',type=Path,required=True) +a=p.parse_args() +r=Path(__file__).resolve().parents[1] +source=(r/'WhateverXe/kern_host.cpp').read_text() +decoder=re.search(r'static size_t hostInstructionSize\(.*?\n\}',source,re.S)[0] +decoder=decoder.replace('Disassembler::hde_t','hde64s').replace('Disassembler::hdeDisasm(reinterpret_cast(code),','hde64_disasm(code,') +code=r''' +#include +#include +#include +#include +#include +#include "hde64.h" +#include "kern_host_power_contract.hpp" +DECODER +using namespace HostPowerContract; +std::vector read(const char *path){std::ifstream f(path,std::ios::binary);assert(f);return {std::istreambuf_iterator(f),{}};} +int main(int argc,char **argv){ + assert(argc==7);auto event=read(argv[1]),sleep=read(argv[2]),mode=read(argv[3]),wsaa=read(argv[4]); + const auto address=strtoull(argv[5],nullptr,0),count=strtoull(argv[6],nullptr,0); + auto decode=hostInstructionSize; + auto ne=extent(event.data(),event.size(),decode),ns=extent(sleep.data(),sleep.size(),decode),nm=extent(mode.data(),mode.size(),decode); + assert(ne && ns && nm && extent(wsaa.data(),wsaa.size(),decode)); + assert(paging(event.data(),ne,decode)); + const auto site=sleepSite(sleep.data(),ns,address,count,decode);assert(site.valid); + assert(sleepFields(sleep.data(),ns,mode.data(),nm,decode,site.owner)); + assert(!sleepSite(sleep.data(),ns,address,count+1,decode).valid); + auto shifted=sleep;shifted.insert(shifted.begin(),17,0x90); + const auto moved=sleepSite(shifted.data(),ns+17,address-17,count,decode); + assert(moved.valid && moved.offset==site.offset+17 && moved.owner==site.owner); + const auto relocated=sleepSite(sleep.data(),ns,address+0x123400,count+0x123400,decode); + assert(relocated.valid && relocated.offset==site.offset); + // Register allocation of the loop flag is independent of accelerator owner. + for(unsigned flag=12;flag<=15;++flag)if(flag!=site.owner){ + auto changed=sleep;assert(changed[site.offset+site.length]==0x41); + changed[site.offset+site.length+1]=0xB0+(flag-8); + auto alternative=sleepSite(changed.data(),ns,address,count,decode); + assert(alternative.valid && alternative.success==flag && alternative.owner==site.owner); + } + auto wrong=sleep;wrong[site.offset+3]^=8; + assert(!sleepSite(wrong.data(),ns,address,count,decode).valid); + for(size_t n=0;n #include #include +#include +#define arrsize(x) (sizeof(x)/sizeof((x)[0])) +#define SYSLOG(...) ((void)0) #include "kern_xelp_platform.hpp" +#include "kern_host_power_contract.hpp" +#include "kern_xelp_native_placement.hpp" +size_t hostInstructionSize(const uint8_t*,size_t){return 0;} +size_t hostFunctionExtent(uintptr_t,uintptr_t,size_t){return 0;} using mach_vm_address_t=uintptr_t; void clock_get_uptime(uint64_t *value){*value=0;} struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}void setProperty(const char*,const void*,size_t){}}; @@ -37,6 +44,7 @@ namespace WIOKit {bool awaitPublishing(IOPCIDevice *){events.push_back(1);published=publish;return publish;}void renameDevice(IOPCIDevice *device,const char *name){device->name=name;++device->renames;events.push_back(5);}} // DEVICE_ADAPTER struct KernelPatcher { + enum class Error {NoError}; Error getError(){return Error::NoError;} static constexpr int KernelID=0; uintptr_t solveSymbol(int,const char *){return 0;} templateT solveSymbol(size_t,const char*,mach_vm_address_t,size_t){return T{};} @@ -45,25 +53,31 @@ void routeBlock(uintptr_t,const uint8_t*,size_t){} void clearError(){} }; -// The optional observer is inactive in this host-publication fixture. namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; -struct {size_t loadIndex=5;}backlight;struct {size_t loadIndex=7;}accelFamily; +struct {size_t loadIndex=6;}surface;struct {size_t loadIndex=5;}backlight;struct {size_t loadIndex=7;}accelFamily; using IOService=IOPCIDevice;using IOReturn=int;using IOFramebuffer=IOPCIDevice; constexpr uint32_t kIOWindowServerActiveAttribute=1,kIOWSAA_StateMask=15,kIOWSAA_Accelerated=1,kIOWSAA_Unaccelerated=0; constexpr IOReturn kIOReturnNotReady=-1; #define FunctionCast(fn,addr) reinterpret_cast(addr) struct XeHost { static XeHost *callbackHost;bool systemWakeRecovery=false; + bool surfaceSeen=false,graphicsSeen=false,accelSeen=false; + struct PendingDonor {size_t index;uintptr_t address;size_t size;};PendingDonor pendingDonors[2]{};unsigned pendingDonorCount=0; + bool dependenciesSeen() const {return surfaceSeen && (!systemWakeRecovery || (graphicsSeen && accelSeen));} + void flushDonors(KernelPatcher&); uintptr_t sleepFBCount=0,sleepFBAt=0;uint32_t repeatedSleep=0;static uint32_t pendingSleepDisplays(void*); - uintptr_t orgDisplayQueueIdle=0;uint32_t queueObservation=0; - static void wrapDisplayQueueIdle(void*);void recordDisplayQueue(void*,uint32_t,uint32_t); uintptr_t orgWSAAAttribute=0; static IOReturn wrapWSAAAttribute(IOFramebuffer*,uint32_t,uint32_t); bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { + bool defer=false;bool isIntelDonor(size_t i)const{return defer && (i==1 || i==2);} + void setHostSurfaceABI(unsigned){} + void setHostFramebufferEvent(uintptr_t){} + void setHostSleepReady(bool){} + void setHostWakeRecoveryReady(bool){} IOService *parked=nullptr;bool parkedDisplay13Framebuffer(IOService *f)const{return parked && f==parked;} bool ownedDisplay13Framebuffer(IOService *)const{return false;} IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} @@ -101,6 +115,15 @@ reinterpret_cast(machine)[i]=&gpu; } assert(exercised.repeatedSleep==2); +// Already-loaded notifications may arrive in any host dependency order. +// Neither missing contracts nor a late callback may silently skip donor setup. +unsigned order[]={4,6,7};do { + XeHost host;host.active=host.systemWakeRecovery=host.xe.defer=true;events.clear(); + host.processKext(patcher,1,0,0);host.processKext(patcher,2,0,0); + assert(events.empty() && host.pendingDonorCount==2); + for(unsigned i=0;i<3;++i){host.processKext(patcher,order[i],0,0);if(i<2)assert(events.empty());} + assert((events==std::vector{101,102}) && host.pendingDonorCount==0); +}while(std::next_permutation(order,order+3)); const char *names[]={nullptr,"IMEI","HECI","MEI"}; for(unsigned firmware=0;firmware<2;++firmware)for(unsigned me=0;me<5;++me)for(unsigned test=0;test<6;++test){ mei.name=names[me%4];mei.renames=0;gpu.name="GFX0"; diff --git a/tests/kernel_os_updates.cpp b/tests/kernel_os_updates.cpp index de642ecf..3865224c 100644 --- a/tests/kernel_os_updates.cpp +++ b/tests/kernel_os_updates.cpp @@ -15,14 +15,14 @@ int main() { assert(kernelOS("Darwin Kernel Version 25.7.0: update/RELEASE_X86_64")==OS::Tahoe); // Metamorphic family acceptance: vary servicing minor/patch and arbitrary // build metadata, without adding known Apple builds to an allowlist. - for(unsigned major=24;major<=25;++major) for(unsigned minor=6;minor<(major==24 ? 7U : 9U);++minor) + for(unsigned major=20;major<=25;++major) for(unsigned minor=6;minor<(major<25 ? 7U : 9U);++minor) for(unsigned patch=0;patch<100;patch+=7) { char version[160]; snprintf(version,sizeof(version),"Darwin Kernel Version %u.%u.%u: synthetic-%u/RELEASE_X86_64",major,minor,patch,minor*109+patch); - assert(kernelOS(version)==(major==24 ? OS::Sequoia : OS::Tahoe)); + assert(kernelOS(version)==static_cast(unsigned(OS::BigSur)+major-20)); } const char *rejected[] { - "Darwin Kernel Version 23.6.0: update/RELEASE_X86_64", + "Darwin Kernel Version 23.5.0: update/RELEASE_X86_64", "Darwin Kernel Version 24.5.0: update/RELEASE_X86_64", "Darwin Kernel Version 24.7.0: update/RELEASE_X86_64", "Darwin Kernel Version 25.5.0: update/RELEASE_X86_64", diff --git a/tests/native_surface.cpp b/tests/native_surface.cpp new file mode 100644 index 00000000..40859cc3 --- /dev/null +++ b/tests/native_surface.cpp @@ -0,0 +1,27 @@ +#include "kern_xelp_native_placement.hpp" +#include +#include +using Bytes=std::vector; +int main() { + using A=XELPNative::SurfaceABI; + const Bytes old[]={{0x8b,0x87,0xa4,0,0,0},{0x8b,0x87,0xb0,0,0,0},{0x8b,0x47,0x0c},{0x8b,0x87,0xb4,0,0,0}}; + const Bytes recent[]={{0x8b,0x87,0x80,0,0,0},{0x8b,0x87,0x98,0,0,0},{0x8b,0x47,0x10},{0x48,0x8b,0x87,0x78,1,0,0,0x0f,0xb6,0x40,0x6a}}; + for(auto expected:{A::Original,A::Attachment6A,A::Attachment6E})for(bool frame:{false,true})for(unsigned padding:{0U,17U}) { + Bytes functions[4];const uint8_t *ptr[4];size_t length[4]; + for(unsigned i=0;i<4;++i) { + auto &f=functions[i];f.assign(padding,0x90); + if(frame)f.insert(f.end(),{0x55,0x48,0x89,0xe5}); + auto load=expected==A::Original?old[i]:recent[i]; + if(i==3 && expected==A::Attachment6E)load.back()=0x6e; + f.insert(f.end(),load.begin(),load.end());if(frame)f.push_back(0x5d);f.push_back(0xc3); + ptr[i]=f.data();length[i]=f.size(); + } + assert(XELPNative::surfaceABI(ptr,length)==expected); + for(unsigned i=0;i<4;++i) { + auto &f=functions[i];const auto at=padding+(frame?4:0)+2; + f[at]^=1;assert(XELPNative::surfaceABI(ptr,length)==A::Unknown);f[at]^=1; + --length[i];assert(XELPNative::surfaceABI(ptr,length)==A::Unknown);++length[i]; + ptr[i]=nullptr;assert(XELPNative::surfaceABI(ptr,length)==A::Unknown);ptr[i]=f.data(); + } + } +} diff --git a/tests/pmap_kernel_contract.py b/tests/pmap_kernel_contract.py index fa5f763e..e413c20f 100644 --- a/tests/pmap_kernel_contract.py +++ b/tests/pmap_kernel_contract.py @@ -1,4 +1,4 @@ -"""Check runtime guards in any supplied x86_64 kernel/KC, without a build list.""" +"""Replay production pmap location/ABI guards in a supplied x86_64 kernel.""" import re import struct import subprocess @@ -31,45 +31,67 @@ def read(address, length): return data[offset + address - base:offset + address - base + length] raise ValueError(hex(address)) - fn, protect = (symbols[x] for x in ('_pmap_enter_options', '_IOProtectCacheMode')) - block = source.split('static void tglPrepare' + family + 'MMIOUC')[1].split('static void ')[0] - checks = re.findall(r'!memcmp\(reinterpret_cast\((fn|protect)(\+0x[0-9A-Fa-f]+)?\),([A-Za-z]+),sizeof', block) - assert len(checks) == 8, checks - for base, offset, name in checks: - address = (fn if base == 'fn' else protect) + (int(offset[1:], 16) if offset else 0) - values = re.search(r'const uint8_t ' + name + r'\[\] \{([^}]+)\}', block)[1] - expected = bytes(int(v.strip(), 0) for v in values.split(',')) - assert read(address, len(expected)) == expected, name - assert 'tglNativeProtectCall(protect,fn,' + ('true' if family == 'Tahoe' else 'false') + ')' in block - protect_code = read(protect, 512) - verify = symbols['_pmap_verify_noncacheable'] - caller = read(verify, 96) - store = symbols['_kernel_pmap_store'] - # Resolve the call with the production matcher below before fetching its - # callee. No verifier/call/field offsets or particular relative distance. + fn, protect, verify, store, panic = (symbols[x] for x in ( + '_pmap_enter_options', '_IOProtectCacheMode', '_pmap_verify_noncacheable', '_kernel_pmap_store', '_panic')) + name = '' if family == 'BigSur' else family + block = source.split('static void tglPrepare' + name + 'MMIOUC(')[1].split('\n}')[0] + patterns = re.findall(r'const bool (\w+)Ready=native.find\(\1,\1At\);', block) + assert len(patterns) >= 4 + abi = re.search(r'tglNativeProtectCall\(protect,fn,XELPNative::ProtectABI::(\w+)\)', block)[1] + assert not re.search(r'fn\+0x|protect\+0x', block), 'fixed native instruction placement' include = Path(__file__).resolve().parents[1] / 'WhateverXe' + decoder = re.search(r'static size_t tglNativeInstructionSize\(.*?\n\}',source,re.S)[0] + decoder = decoder.replace('Disassembler::hde_t','hde64s').replace('Disassembler::hdeDisasm(reinterpret_cast(code),','hde64_disasm(code,') + locator = re.search(r'struct TGLNativePmapCode \{.*?\n\};',source,re.S)[0] + lilu = (hde_source.parent.parent/'Lilu/Sources/kern_patcher.cpp').read_text() + find = re.search(r'bool KernelPatcher::findPattern\(.*?\n\}',lilu,re.S)[0] + caller = read(verify,96) + # Fetch the accessor from the production resolver; no caller/callee offset. + def literal(value): + return ','.join(str(x) for x in value) with tempfile.TemporaryDirectory(prefix='xe-root-target-') as directory: - path = Path(directory) - (path / 'target.cpp').write_text('#include "kern_xelp_native_placement.hpp"\n#include \nint main(){unsigned char b[]={' + ','.join(map(str,caller)) + '};printf("%llx",(unsigned long long)XELPNative::rootCall(b,sizeof(b),' + str(verify) + 'ULL,' + str(store) + 'ULL));}') + path=Path(directory) + (path/'target.cpp').write_text('#include "kern_xelp_native_placement.hpp"\n#include \nint main(){unsigned char b[]={' + literal(caller) + '};printf("%llx",(unsigned long long)XELPNative::rootCall(b,sizeof(b),' + str(verify) + 'ULL,' + str(store) + 'ULL,'+str(symbols['_kernel_pmap'])+'ULL));}') subprocess.run(['clang++','-std=c++17','-I'+str(include),str(path/'target.cpp'),'-o',str(path/'target')],check=True) - target = int(subprocess.check_output([str(path/'target')],text=True),16) + target=int(subprocess.check_output([str(path/'target')],text=True),16) assert target - accessor = read(target, 64) - # Execute the production decoder/field contract on the real native bytes. - def literal(value): - return ','.join(str(x) for x in value) - # Compile the exact production decoder wrapper against Lilu's HDE source. - decoder = re.search(r'static size_t tglNativeInstructionSize\(.*?\n\}',source,re.S)[0] - decoder = decoder.replace('Disassembler::hde_t','hde64s').replace('Disassembler::hdeDisasm(reinterpret_cast(code),','hde64_disasm(code,') - cpp = '#include "kern_xelp_native_placement.hpp"\n#include "hde64.h"\n#include \n' + decoder + '\nint main(){\n' - cpp += 'unsigned char protect[]={' + literal(protect_code) + '}, accessor[]={' + literal(accessor) + '};\n' - cpp += f'assert(XELPNative::protectCall(protect,sizeof(protect),{protect}ULL,{fn}ULL,' + ('true' if family=='Tahoe' else 'false') + ',tglNativeInstructionSize));\n' - cpp += 'assert(XELPNative::rootAccessor(accessor,sizeof(accessor)));}\n' + cpp=r''' +#include "kern_xelp_native_placement.hpp" +#include "hde64.h" +#include +#include +#include +using Bytes=std::vector; +namespace Buffer { templateT*create(size_t n){return static_cast(malloc(n*sizeof(T)));} templatevoid deleter(T*p){free(p);} } +struct KernelPatcher { + static constexpr int KernelID=0; uintptr_t panic; + uintptr_t solveSymbol(int,const char*){return panic;} void clearError(){} + static bool findPattern(const void*,const void*,size_t,const void*,size_t,size_t*); +}; +struct {uint64_t(*nofault)(uint64_t,uint64_t,uint64_t);}tglVM; +uintptr_t mapped=0;Bytes input; +uint64_t copy(uint64_t from,uint64_t to,uint64_t n){if(from!=mapped)return 0;auto len=n(to),input.data(),len);return len;} +'''+find+'\n'+decoder+'\n'+locator+'\nint main(){\n' + cpp+=f'KernelPatcher patcher{{{panic}ULL}};tglVM.nofault=copy;mapped={fn}ULL;\n' + cpp+='const Bytes original={'+literal(read(fn,0x4000))+'};input=original;\n' + cpp+='for(unsigned shift:{0U,17U}){input=original;input.insert(input.begin(),shift,0x90);input.resize(original.size());mapped='+str(fn)+'ULL-shift;TGLNativePmapCode code(patcher,mapped);assert(code.length);\n' + for key in patterns: + values=re.search(r'const uint8_t '+key+r'\[\]\s*\{([^}]+)\}',block)[1] + cpp+='{const uint8_t pattern[]={'+values+'};size_t at=0;assert(code.find(pattern,at));\n' + cpp+='auto saved=input;input[at+sizeof(pattern)-1]^=1;{TGLNativePmapCode wrong(patcher,mapped);size_t ignored=0;assert(!wrong.find(pattern,ignored));}input=saved;}\n' + cpp+='}\n' + cpp+='uint8_t protection[]={'+literal(read(protect,512))+'},accessor[]={'+literal(read(target,64))+'};\n' + cpp+=f'assert(XELPNative::protectCall(protection,sizeof(protection),{protect}ULL,{fn}ULL,XELPNative::ProtectABI::{abi},tglNativeInstructionSize));\n' + cpp+=f'assert(!XELPNative::protectCall(protection,sizeof(protection),{protect}ULL,{fn+1}ULL,XELPNative::ProtectABI::{abi},tglNativeInstructionSize));\n' + cpp+='assert(XELPNative::rootAccessor(accessor,sizeof(accessor)));}\n' with tempfile.TemporaryDirectory(prefix='xe-kernel-contract-') as directory: - path = Path(directory) - (path / 'test.cpp').write_text(cpp) - headers = include.parent / 'Lilu.kext/Contents/Resources/Headers' - subprocess.run(['clang','-I'+str(headers),'-c',str(hde_source),'-o',str(path/'hde.o')],check=True) - subprocess.run(['clang++', '-std=c++17', '-I' + str(include), '-I'+str(headers), str(path / 'test.cpp'), str(path/'hde.o'), '-o', str(path / 'test')], check=True) - subprocess.run([str(path / 'test')], check=True) - print('PASS: 8 instruction/stack guards, decoded native protect/verifier calls and private pmap accessor; no build/hash/call-offset admission') + path=Path(directory);(path/'test.cpp').write_text(cpp) + subprocess.run(['clang','-I'+str(hde_source.parent),'-c',str(hde_source),'-o',str(path/'hde.o')],check=True) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(include),'-I'+str(hde_source.parent),str(path/'test.cpp'),str(path/'hde.o'),'-o',str(path/'test')],check=True) + subprocess.run([str(path/'test')],check=True) + print('PASS: '+family+' production locator with Lilu search/HDE, placement shift, ABI mutation, resolved protect/verifier calls') + +if __name__=='__main__': + import argparse + p=argparse.ArgumentParser();p.add_argument('--kernel',type=Path,required=True);p.add_argument('--family',choices=['BigSur','Monterey','Ventura','Sonoma','Sequoia','Tahoe'],required=True);p.add_argument('--hde-source',type=Path,required=True);a=p.parse_args() + check(a.kernel,(Path(__file__).resolve().parents[1]/'WhateverXe/kern_xe.cpp').read_text(),a.family,a.hde_source) From dec6d5c150882faf92c7f675d7bef47ee693c427 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 6 Nov 2026 20:56:04 +0900 Subject: [PATCH 29/46] Accept shared KC imports and scope host checks to their consumers --- .../kern_display13_framebuffer_power.inc | 27 +++---- WhateverXe/kern_host.cpp | 9 ++- WhateverXe/kern_xe.cpp | 76 ++++++++++--------- WhateverXe/kern_xe.hpp | 2 +- WhateverXe/kern_xelp_call_target.hpp | 12 +++ WhateverXe/kern_xelp_native_placement.hpp | 22 ++++-- tests/dsi_update_dispatch.py | 18 ++++- tests/host_publication.py | 18 +++-- tests/native_surface.cpp | 17 +++-- tests/pmap_kernel_contract.py | 7 +- tests/xelp_call_target.cpp | 30 ++++++++ 11 files changed, 154 insertions(+), 84 deletions(-) diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc index bd9c2a0a..65b99e9d 100644 --- a/WhateverXe/kern_display13_framebuffer_power.inc +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -17,8 +17,12 @@ uint32_t XE::wrapDisplay13SetPowerState(IOService *fb,uint32_t state) { } callback{busy}; // Keep WSAA out of the native queue wait whenever this callback cannot // finish hardware restoration or its notification obligation. This covers - // early failures too, not only the post-resume cadence diagnostic. - if(busy && state==2)__atomic_store_n(&callbackXE->dsiWakeIncomplete,1U,__ATOMIC_RELEASE); + // early failures too, not only the post-resume cadence diagnostic. A + // startup readiness retry has no sleep/wake obligation and must not + // prevent the initial WindowServer acceleration handshake. + const bool restoring=callbackXE->dsiRuntimePower.sleeping || callbackXE->dsiRuntimePower.failed || + callbackXE->dsiSystemWakeNotify || callbackXE->dsiPowerOffNotified; + if(busy && state==2 && restoring)__atomic_store_n(&callbackXE->dsiWakeIncomplete,1U,__ATOMIC_RELEASE); auto result=[&]() -> uint32_t { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; @@ -216,7 +220,7 @@ bool XE::processDisplay13NativeAttributes(KernelPatcher &patcher,size_t index,ma } // Native prepareToExitSleep / prepareToEnterWake call the same imported // IOFramebuffer::handleEvent with DidWake(true) and DidPowerOn(nullptr). - // Resolve the linked call from the identified donor without an extra kext dependency. + // Compare the donor import with the host symbol validated in its Lilu callback. if(size>0x26F1C) { const uint8_t wake[]={0xBA,1,0,0,0,0x4C,0x89,0xF7,0xBE,4,0,0,0,0xE8}; const uint8_t on[]={0x4C,0x89,0xEF,0xBE,8,0,0,0,0x31,0xD2,0xE8}; @@ -227,18 +231,11 @@ bool XE::processDisplay13NativeAttributes(KernelPatcher &patcher,size_t index,ma memcpy(&od,reinterpret_cast(address+0x26814),sizeof(od)); const auto imported=address+0x26F1C+wd; if(wd && od && imported==address+0x26818+od) { - auto target=imported; - // Only inspect a linkage stub and pointer slot inside this - // already-qualified donor image. A direct host call needs no - // dereference before comparing its resolved symbol identity. - if(target!=hostFramebufferEvent && target>=address && target-address<=size && size-(target-address)>=6) { - const auto fn=reinterpret_cast(target); - if(fn[0]==0xFF && fn[1]==0x25) { - const auto slot=XELPCall::relativeTarget(fn+2,target+6); - if(slot>=address && slot-address<=size && size-(slot-address)>=sizeof(target)) - memcpy(&target,reinterpret_cast(slot),sizeof(target)); - } - } + const auto target=XELPCall::importedTarget(imported,hostFramebufferEvent, + [](uintptr_t from,void *to,size_t length) { + return (from>>47)==0x1FFFF && tglVM.nofault && + tglVM.nofault(from,reinterpret_cast(to),length)==length; + }); display13PagingLayoutKnown=hostFramebufferEvent && target==hostFramebufferEvent; if(display13PagingLayoutKnown)display13FramebufferEvent=imported; IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13PagingContractReady",display13PagingLayoutKnown); diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 54e717f6..934d80d7 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -116,13 +116,14 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a const auto fn=patcher.solveSymbol(index,names[i],address,size);patcher.clearError(); if(fn>=address && fn-address(fn); - const auto remaining=size-(fn-address);lengths[i]=remaining<32?remaining:32; + const auto remaining=size-(fn-address);lengths[i]=remaining<64?remaining:64; } } - const auto layout=XELPNative::surfaceABI(functions,lengths); - xe.setHostSurfaceABI(static_cast(layout)); + const auto fb=XELPNative::surfaceABI(functions,lengths,true); + const auto accel=XELPNative::surfaceABI(functions,lengths,false); + xe.setHostSurfaceABI(static_cast(fb),static_cast(accel)); surfaceSeen=true; - uint32_t state=static_cast(layout); + uint32_t state[]={static_cast(fb),static_cast(accel)}; IORegistryEntry::getRegistryRoot()->setProperty("WEXHostSurfaceABI",&state,sizeof(state)); flushDonors(patcher); return; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 22395d38..4f098f16 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -159,12 +159,16 @@ extern "C" __attribute__((naked,used)) void tglTahoeMatrixBridge() { "1: jmpq *_tglSonomaMatrixZero(%rip)\n" "2: jmpq *_tglSonomaMatrixOther(%rip)\n"); } -static XELPNative::SurfaceABI tglHostSurfaceABI=XELPNative::SurfaceABI::Unknown; -void XE::setHostSurfaceABI(unsigned layout) { tglHostSurfaceABI=static_cast(layout); } +static XELPNative::SurfaceABI tglHostSurfaceABI[2] {}; +void XE::setHostSurfaceABI(unsigned framebuffer,unsigned accelerator) { + tglHostSurfaceABI[0]=static_cast(framebuffer); + tglHostSurfaceABI[1]=static_cast(accelerator); +} static bool tglSonomaSurfaceFramebufferReady=false; static bool tglInstallSonomaSurfaceABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size,bool framebuffer) { - if(tglHostSurfaceABI==XELPNative::SurfaceABI::Unknown)return false; - if(tglHostSurfaceABI==XELPNative::SurfaceABI::Original)return true; + const auto layout=tglHostSurfaceABI[framebuffer?0:1]; + if(layout==XELPNative::SurfaceABI::Unknown)return false; + if(layout==XELPNative::SurfaceABI::Original)return true; struct Site {size_t offset,length;uint8_t before[6],after[6];}; const Site fb[] { {0x2B5E8,6,{0x8B,0x80,0xA4,0,0,0},{0x8B,0x80,0x80,0,0,0}}, // pixelFormat @@ -192,7 +196,7 @@ static bool tglInstallSonomaSurfaceABI(KernelPatcher &patcher,size_t index,mach_ if(!framebuffer) { tglSonomaMatrixZero=address+0x8130E;tglSonomaMatrixOne=address+0x81305;tglSonomaMatrixOther=address+0x81361; uint8_t jump[15] {0xFF,0x25,0,0,0,0}; - const auto target=reinterpret_cast((tglHostSurfaceABI==XELPNative::SurfaceABI::Attachment6E) ? tglTahoeMatrixBridge : tglSonomaMatrixBridge); + const auto target=reinterpret_cast((layout==XELPNative::SurfaceABI::Attachment6E) ? tglTahoeMatrixBridge : tglSonomaMatrixBridge); memcpy(jump+6,&target,sizeof(target));jump[14]=0x90; patcher.routeBlock(address+0x812F6,jump,sizeof(jump)); ready=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(reinterpret_cast(address+0x812F6),jump,sizeof(jump)); @@ -1956,9 +1960,9 @@ struct TGLNativePmapCode { reinterpret_cast(storage+capacity*2),fn,panic,tglNativeInstructionSize); } ~TGLNativePmapCode(){if(storage)Buffer::deleter(storage);} - template bool find(const uint8_t (&pattern)[N],size_t &offset) const { - return length && XELPNative::uniqueInstruction(storage,length,storage+capacity, - pattern,N,offset,KernelPatcher::findPattern); + template bool find(const uint8_t (&pattern)[N],size_t &offset,bool requireUnique=true) const { + return length && XELPNative::findInstruction(storage,length,storage+capacity, + pattern,N,offset,KernelPatcher::findPattern,requireUnique); } }; static void tglPrepareMontereyMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { @@ -1974,10 +1978,10 @@ static void tglPrepareMontereyMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x4D,0x8C,0x89,0xCB,0x83,0xE3,1,0x48,0x09,0xD3,0xF6,0xC1,2}; const uint8_t update[] {0x8B,0x4D,0x8C,0x41,0x89,0xCE,0x41,0x83,0xE6,1,0x49,0x09,0xC6,0xF6,0xC1,2}; TGLNativePmapCode native(patcher,fn); - size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); - size_t argsAt=0;const bool argsReady=native.find(args,argsAt); - size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); - size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt,false); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt,false); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt,false); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt,false); size_t createAt=0;const bool createReady=native.find(create,createAt); size_t updateAt=0;const bool updateReady=native.find(update,updateAt); const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Legacy); @@ -2024,11 +2028,11 @@ static void tglPrepareVenturaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x75,0xAC,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xAC,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; TGLNativePmapCode native(patcher,fn); - size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); - size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); - size_t argsAt=0;const bool argsReady=native.find(args,argsAt); - size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); - size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt,false); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt,false); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt,false); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt,false); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt,false); size_t createAt=0;const bool createReady=native.find(create,createAt); size_t updateAt=0;const bool updateReady=native.find(update,updateAt); const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Legacy); @@ -2076,11 +2080,11 @@ static void tglPrepareTahoeMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x75,0xB4,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB4,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; TGLNativePmapCode native(patcher,fn); - size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); - size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); - size_t argsAt=0;const bool argsReady=native.find(args,argsAt); - size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); - size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt,false); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt,false); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt,false); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt,false); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt,false); size_t createAt=0;const bool createReady=native.find(create,createAt); size_t updateAt=0;const bool updateReady=native.find(update,updateAt); const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Tahoe); @@ -2134,11 +2138,11 @@ static void tglPrepareSequoiaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x75,0xB4,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB4,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; TGLNativePmapCode native(patcher,fn); - size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); - size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); - size_t argsAt=0;const bool argsReady=native.find(args,argsAt); - size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); - size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt,false); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt,false); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt,false); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt,false); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt,false); size_t createAt=0;const bool createReady=native.find(create,createAt); size_t updateAt=0;const bool updateReady=native.find(update,updateAt); const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Extended); @@ -2192,11 +2196,11 @@ static void tglPrepareSonomaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x75,0xB0,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB0,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; TGLNativePmapCode native(patcher,fn); - size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); - size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); - size_t argsAt=0;const bool argsReady=native.find(args,argsAt); - size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); - size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t frameAt=0;const bool frameReady=native.find(frame,frameAt,false); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt,false); + size_t argsAt=0;const bool argsReady=native.find(args,argsAt,false); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt,false); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt,false); size_t createAt=0;const bool createReady=native.find(create,createAt); size_t updateAt=0;const bool updateReady=native.find(update,updateAt); const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Extended); @@ -2267,10 +2271,10 @@ static void tglPrepareMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t update[] {0x8B,0x75,0x9C,0x89,0xF0,0x83,0xE0,1,0x48,0x09,0xD0,0x40,0xF6,0xC6,2}; TGLNativePmapCode native(patcher,fn); size_t frameAt=0;const bool frameReady=native.find(frame,frameAt); - size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt); - size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt); - size_t rootSourceAt=0;const bool rootSourceReady=native.find(rootSource,rootSourceAt); - size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt); + size_t flagsAt=0;const bool flagsReady=native.find(flags,flagsAt,false); + size_t protectionAt=0;const bool protectionReady=native.find(protection,protectionAt,false); + size_t rootSourceAt=0;const bool rootSourceReady=native.find(rootSource,rootSourceAt,false); + size_t nonEPTAt=0;const bool nonEPTReady=native.find(nonEPT,nonEPTAt,false); size_t createAt=0;const bool createReady=native.find(create,createAt); size_t updateAt=0;const bool updateReady=native.find(update,updateAt); const bool protectCallReady=tglNativeProtectCall(protect,fn,XELPNative::ProtectABI::Legacy); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e0c64d5e..756bbb24 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -77,7 +77,7 @@ class XE { bool parkedDisplay13Framebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; bool isIntelDonor(size_t index) const; - void setHostSurfaceABI(unsigned layout); + void setHostSurfaceABI(unsigned framebuffer,unsigned accelerator); void setHostSleepReady(bool ready) { hostSleepReady=ready; } void setHostWakeRecoveryReady(bool ready) { hostWakeRecoveryReady=ready; } void setHostFramebufferEvent(mach_vm_address_t event) { hostFramebufferEvent=event; } diff --git a/WhateverXe/kern_xelp_call_target.hpp b/WhateverXe/kern_xelp_call_target.hpp index ca168842..96029042 100644 --- a/WhateverXe/kern_xelp_call_target.hpp +++ b/WhateverXe/kern_xelp_call_target.hpp @@ -19,5 +19,17 @@ inline bool targets(const uint8_t *bytes,size_t length,uintptr_t site,uintptr_t if(!bytes || length<5 || !site || !target || bytes[0]!=0xE8 || site>UINTPTR_MAX-5)return false; return relativeTarget(bytes+1,site+5)==target; } +// A KC may place branch stubs and GOT slots outside the calling kext. +// Read through the caller's nofault accessor; validate the destination identity. +template uintptr_t importedTarget(uintptr_t entry,uintptr_t expected,Read read) { + if(!entry || !expected)return 0; + if(entry==expected)return entry; + uint8_t stub[6] {};uintptr_t target=0; + if(entry>UINTPTR_MAX-sizeof(stub) || !read(entry,stub,sizeof(stub)) || + stub[0]!=0xFF || stub[1]!=0x25)return 0; + const auto slot=relativeTarget(stub+2,entry+sizeof(stub)); + if(!slot || slot>UINTPTR_MAX-sizeof(target) || !read(slot,&target,sizeof(target)))return 0; + return target==expected ? target : 0; +} } #endif diff --git a/WhateverXe/kern_xelp_native_placement.hpp b/WhateverXe/kern_xelp_native_placement.hpp index 669f762c..32752ef2 100644 --- a/WhateverXe/kern_xelp_native_placement.hpp +++ b/WhateverXe/kern_xelp_native_placement.hpp @@ -61,14 +61,14 @@ template size_t reachable(const uint8_t *bytes,size_t length,uint8 } return end; } -template bool uniqueInstruction(const uint8_t *bytes,size_t length,const uint8_t *marks, - const uint8_t *pattern,size_t count,size_t &offset,Find find) { +template bool findInstruction(const uint8_t *bytes,size_t length,const uint8_t *marks, + const uint8_t *pattern,size_t count,size_t &offset,Find find,bool requireUnique=true) { size_t cursor=0,hits=0; while(find(pattern,nullptr,count,bytes,length,&cursor)) { if(marks[cursor]==1 && (cursor+count==length || marks[cursor+count]==1)) {offset=cursor;++hits;} ++cursor; } - return hits==1; + return requireUnique ? hits==1 : hits>0; } // Only canonical, side-effect-free alignment NOPs may separate the expected // instructions. Never scan past an unrecognised instruction to find a suffix. @@ -85,7 +85,7 @@ struct Cursor { } bool skip(size_t count) {if(count>length-pos)return false;pos+=count;return true;} }; -enum class SurfaceABI { Unknown, Original, Attachment6A, Attachment6E }; +enum class SurfaceABI { Unknown, Original, Attachment6A, Attachment6E, RelocatedFields }; // These exported getters are the meanings of the fields inlined by the fixed // donor. OS family names do not establish the host object layout. inline bool surfaceGetter(const uint8_t *bytes,size_t length,const char *load,size_t size) { @@ -96,12 +96,18 @@ inline bool surfaceGetter(const uint8_t *bytes,size_t length,const char *load,si if(frame)c=framed; return c.take(load,size) && (!frame || c.take("\x5d",1)) && c.take("\xc3",1); } -inline SurfaceABI surfaceABI(const uint8_t *const functions[4],const size_t lengths[4]) { +inline SurfaceABI surfaceABI(const uint8_t *const functions[4],const size_t lengths[4],bool framebuffer) { const auto getter=[&](unsigned i,const char *load,size_t n){return surfaceGetter(functions[i],lengths[i],load,n);}; + // Qualify only the fields this consumer reads. A matrix/plane change must + // not disable the framebuffer, nor an ID change disable the accelerator. + if(framebuffer) { + if(getter(0,"\x8b\x87\xa4\0\0\0",6) && getter(2,"\x8b\x47\x0c",3))return SurfaceABI::Original; + if(getter(0,"\x8b\x87\x80\0\0\0",6) && getter(2,"\x8b\x47\x10",3))return SurfaceABI::RelocatedFields; + return SurfaceABI::Unknown; + } if(getter(0,"\x8b\x87\xa4\0\0\0",6) && getter(1,"\x8b\x87\xb0\0\0\0",6) && - getter(2,"\x8b\x47\x0c",3) && getter(3,"\x8b\x87\xb4\0\0\0",6))return SurfaceABI::Original; - if(!getter(0,"\x8b\x87\x80\0\0\0",6) || !getter(1,"\x8b\x87\x98\0\0\0",6) || - !getter(2,"\x8b\x47\x10",3))return SurfaceABI::Unknown; + getter(3,"\x8b\x87\xb4\0\0\0",6))return SurfaceABI::Original; + if(!getter(0,"\x8b\x87\x80\0\0\0",6) || !getter(1,"\x8b\x87\x98\0\0\0",6))return SurfaceABI::Unknown; if(getter(3,"\x48\x8b\x87\x78\x01\0\0\x0f\xb6\x40\x6a",11))return SurfaceABI::Attachment6A; if(getter(3,"\x48\x8b\x87\x78\x01\0\0\x0f\xb6\x40\x6e",11))return SurfaceABI::Attachment6E; return SurfaceABI::Unknown; diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 17bd6d86..afae756a 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -82,7 +82,8 @@ uintptr_t display13FramebufferEvent=0;bool dsiSystemWakeNotify=false,dsiPowerOffNotified=false; uint32_t dsiWakeIncomplete=0; uint32_t dsiPowerCallbackBusy=0,dsiPowerOffEvents=0,dsiPowerOnEvents=0; - bool finishDisplay13SystemWake(IOService *fb){using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(display13FramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);if(dsiPowerOffNotified){dsiPowerOffNotified=false;++dsiPowerOnEvents;}return true;} + bool notifyOK=true; + bool finishDisplay13SystemWake(IOService *fb){if(!notifyOK)return false;using Event=uint32_t(*)(IOService*,int,void*);auto event=reinterpret_cast(display13FramebufferEvent);event(fb,4,reinterpret_cast(1));event(fb,8,nullptr);if(dsiPowerOffNotified){dsiPowerOffNotified=false;++dsiPowerOnEvents;}return true;} bool dsiPowerTest=false;struct {bool sleeping=false,failed=false;}dsiRuntimePower;unsigned wakeCalls=0;bool wakeOK=false; bool dsiStopTest=false,dsiMemoryReleased=false,dsiDDIReleased=false,stopOK=false;unsigned stopCalls=0; struct {bool done=false;bool complete(){return done;}}dsiClockStop; @@ -261,10 +262,15 @@ assert(Display13FBMemory::field(&c.fb,0x4284)==0 && !xe.dsiMemoryReleased && !xe.dsiDDIReleased); c.refuse();c.refuseCursor();xe.wakeOK=false; assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnNotReady); - assert(Display13FBMemory::field(&c.fb,0x4284)==0); + assert(Display13FBMemory::field(&c.fb,0x4284)==0 && xe.dsiWakeIncomplete); xe.dsiSystemWakeNotify=true;xe.display13FramebufferEvent=reinterpret_cast(&systemEvent); const auto eventsBefore=systemEvents; - xe.wakeOK=true;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess); + xe.wakeOK=true;xe.notifyOK=false; + assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnNotReady); + assert(xe.dsiWakeIncomplete && xe.dsiSystemWakeNotify && !xe.dsiRuntimePower.sleeping); + const auto completedWakeCalls=xe.wakeCalls;xe.notifyOK=true; + assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess); + assert(!xe.dsiWakeIncomplete && xe.wakeCalls==completedWakeCalls); assert(!xe.dsiRuntimePower.sleeping && !xe.dsiStopAttempted && Display13FBMemory::field(&c.fb,0x4284)==2); auto wakes=xe.wakeCalls;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess && xe.wakeCalls==wakes && systemEvents==eventsBefore+2); } @@ -402,7 +408,7 @@ for(unsigned old=0;old<3;++old)for(unsigned state=0;state<4;++state){Case c;put(&c.fb,0x4284,old);const auto before=attrCalls[2]; const auto expected=old==2 && state==2?kIOReturnSuccess:kIOReturnUnsupported; assert(XE::wrapDisplay13SetPowerState(&c.fb,state)==expected && attrCalls[2]==before); - if(state==2)assert(xe.dsiWakeIncomplete==unsigned(expected!=kIOReturnSuccess)); + assert(xe.dsiWakeIncomplete==0); // no prior sleep or pending notification assert(Display13FBMemory::field(&c.fb,0x4284)==old); assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,state)==expected);} {Case c;put(&c.fb,0x4284,2U);assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,0x100000002ULL)==kIOReturnUnsupported);} @@ -412,6 +418,10 @@ assert(XE::wrapDSISetAttribute(&c.fb,0x77737276,1)==kIOReturnNotReady); assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x72677363,1)==kIOReturnNotReady); assert(XE::wrapDisplay13SetPowerState(&c.fb,2)==kIOReturnNotReady); + assert(xe.dsiWakeIncomplete==0); // startup NotReady is not failed wake + xe.dsiRuntimePower.sleeping=true; + assert(XE::wrapDisplay13SetPowerState(&c.fb,2)==kIOReturnNotReady); + assert(xe.dsiWakeIncomplete==1); // same early failure during real restoration assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !display13FBPower.dsiNativeWriters);} for(bool disabled:{false,true}){Case c;expectedWriters=0;expectedValue=txOptions;expectedAttribute=0x706F7772;expectedConnection=4;expectedPower=0; IOService *fb=&c.fb; diff --git a/tests/host_publication.py b/tests/host_publication.py index ea9d38a4..94aec876 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -74,7 +74,7 @@ struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { bool defer=false;bool isIntelDonor(size_t i)const{return defer && (i==1 || i==2);} - void setHostSurfaceABI(unsigned){} + void setHostSurfaceABI(unsigned,unsigned){} void setHostFramebufferEvent(uintptr_t){} void setHostSleepReady(bool){} void setHostWakeRecoveryReady(bool){} @@ -117,13 +117,17 @@ assert(exercised.repeatedSleep==2); // Already-loaded notifications may arrive in any host dependency order. // Neither missing contracts nor a late callback may silently skip donor setup. -unsigned order[]={4,6,7};do { +unsigned order[]={1,2,4,6,7};do { XeHost host;host.active=host.systemWakeRecovery=host.xe.defer=true;events.clear(); - host.processKext(patcher,1,0,0);host.processKext(patcher,2,0,0); - assert(events.empty() && host.pendingDonorCount==2); - for(unsigned i=0;i<3;++i){host.processKext(patcher,order[i],0,0);if(i<2)assert(events.empty());} - assert((events==std::vector{101,102}) && host.pendingDonorCount==0); -}while(std::next_permutation(order,order+3)); + std::vector received; + for(auto id:order){ + if(id==1 || id==2)received.push_back(100+id); + host.processKext(patcher,id,0,0); + if(!host.dependenciesSeen())assert(events.empty()); + else assert(events==received); + } + assert(events==received && events.size()==2 && host.pendingDonorCount==0); +}while(std::next_permutation(order,order+5)); const char *names[]={nullptr,"IMEI","HECI","MEI"}; for(unsigned firmware=0;firmware<2;++firmware)for(unsigned me=0;me<5;++me)for(unsigned test=0;test<6;++test){ mei.name=names[me%4];mei.renames=0;gpu.name="GFX0"; diff --git a/tests/native_surface.cpp b/tests/native_surface.cpp index 40859cc3..9d2474df 100644 --- a/tests/native_surface.cpp +++ b/tests/native_surface.cpp @@ -16,12 +16,17 @@ int main() { f.insert(f.end(),load.begin(),load.end());if(frame)f.push_back(0x5d);f.push_back(0xc3); ptr[i]=f.data();length[i]=f.size(); } - assert(XELPNative::surfaceABI(ptr,length)==expected); - for(unsigned i=0;i<4;++i) { - auto &f=functions[i];const auto at=padding+(frame?4:0)+2; - f[at]^=1;assert(XELPNative::surfaceABI(ptr,length)==A::Unknown);f[at]^=1; - --length[i];assert(XELPNative::surfaceABI(ptr,length)==A::Unknown);++length[i]; - ptr[i]=nullptr;assert(XELPNative::surfaceABI(ptr,length)==A::Unknown);ptr[i]=f.data(); + for(bool framebuffer:{false,true}) { + const auto result=framebuffer && expected!=A::Original ? A::RelocatedFields : expected; + assert(XELPNative::surfaceABI(ptr,length,framebuffer)==result); + for(unsigned i=0;i<4;++i) { + const bool used=framebuffer ? (i==0 || i==2) : i!=2; + const auto changed=used?A::Unknown:result; + auto &f=functions[i];const auto at=padding+(frame?4:0)+2; + f[at]^=1;assert(XELPNative::surfaceABI(ptr,length,framebuffer)==changed);f[at]^=1; + --length[i];assert(XELPNative::surfaceABI(ptr,length,framebuffer)==changed);++length[i]; + ptr[i]=nullptr;assert(XELPNative::surfaceABI(ptr,length,framebuffer)==changed);ptr[i]=f.data(); + } } } } diff --git a/tests/pmap_kernel_contract.py b/tests/pmap_kernel_contract.py index e413c20f..4809ebe5 100644 --- a/tests/pmap_kernel_contract.py +++ b/tests/pmap_kernel_contract.py @@ -35,7 +35,7 @@ def read(address, length): '_pmap_enter_options', '_IOProtectCacheMode', '_pmap_verify_noncacheable', '_kernel_pmap_store', '_panic')) name = '' if family == 'BigSur' else family block = source.split('static void tglPrepare' + name + 'MMIOUC(')[1].split('\n}')[0] - patterns = re.findall(r'const bool (\w+)Ready=native.find\(\1,\1At\);', block) + patterns = re.findall(r'const bool (\w+)Ready=native.find\(\1,\1At(?:,false)?\);', block) assert len(patterns) >= 4 abi = re.search(r'tglNativeProtectCall\(protect,fn,XELPNative::ProtectABI::(\w+)\)', block)[1] assert not re.search(r'fn\+0x|protect\+0x', block), 'fixed native instruction placement' @@ -77,8 +77,9 @@ def literal(value): cpp+='for(unsigned shift:{0U,17U}){input=original;input.insert(input.begin(),shift,0x90);input.resize(original.size());mapped='+str(fn)+'ULL-shift;TGLNativePmapCode code(patcher,mapped);assert(code.length);\n' for key in patterns: values=re.search(r'const uint8_t '+key+r'\[\]\s*\{([^}]+)\}',block)[1] - cpp+='{const uint8_t pattern[]={'+values+'};size_t at=0;assert(code.find(pattern,at));\n' - cpp+='auto saved=input;input[at+sizeof(pattern)-1]^=1;{TGLNativePmapCode wrong(patcher,mapped);size_t ignored=0;assert(!wrong.find(pattern,ignored));}input=saved;}\n' + unique='false' if 'native.find('+key+','+key+'At,false)' in block else 'true' + cpp+='{const uint8_t pattern[]={'+values+'};size_t at=0;assert(code.find(pattern,at,'+unique+'));\n' + cpp+='auto saved=input;input[at+sizeof(pattern)-1]^=1;{TGLNativePmapCode wrong(patcher,mapped);size_t ignored=0;assert(!wrong.find(pattern,ignored,'+unique+'));}input=saved;}\n' cpp+='}\n' cpp+='uint8_t protection[]={'+literal(read(protect,512))+'},accessor[]={'+literal(read(target,64))+'};\n' cpp+=f'assert(XELPNative::protectCall(protection,sizeof(protection),{protect}ULL,{fn}ULL,XELPNative::ProtectABI::{abi},tglNativeInstructionSize));\n' diff --git a/tests/xelp_call_target.cpp b/tests/xelp_call_target.cpp index ac7ab450..45f56abc 100644 --- a/tests/xelp_call_target.cpp +++ b/tests/xelp_call_target.cpp @@ -4,6 +4,36 @@ #include int main() { + // KC common stub/GOT precede the donor, as well as ordinary in-image stubs. + for(uintptr_t base:{0x100000U,0x400000U}) { + const uintptr_t donor=base+0x174000,entry=base+0x1462a,slot=base+0x18838; + const uintptr_t expected=0xffffff8001000000ULL;uintptr_t pointer=expected; + uint8_t stub[]={0xff,0x25,0x08,0x42,0,0};bool readable=true; + auto read=[&](uintptr_t from,void *to,size_t n){ + if(!readable)return false; + if(from==entry && n==sizeof(stub)){memcpy(to,stub,n);return true;} + if(from==slot && n==sizeof(pointer)){memcpy(to,&pointer,n);return true;} + return false; + }; + assert(entry(data)+*at,p,n))return true; + return false; + }; + assert(XELPNative::findInstruction(stores,sizeof(stores),boundaries,stores,4,offset,search,false)); + assert(!XELPNative::findInstruction(stores,sizeof(stores),boundaries,stores,4,offset,search,true)); uint8_t accessor[]={0x55,0x48,0x89,0xe5,0x48,0xb8,0,0,0,0,0,0x80,0xff,0xff,0x48,0x01,0xf0, 0x48,0xc1,0xe8,0x30,0x3d,0xff,0xff,0,0,0x0f,0x82,0xc1,0,0,0,0x48,0x8b,0x47,0x50}; assert(XELPNative::rootAccessor(accessor,sizeof(accessor))); From 322b0cc5e7015cfc9fd49d534550bba637263f8c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:10:13 +0900 Subject: [PATCH 30/46] Balance per-framebuffer sleep preparation across partial DarkWake --- .../kern_display13_framebuffer_power.inc | 4 - WhateverXe/kern_host.cpp | 72 +++++++++++++--- WhateverXe/kern_host.hpp | 14 +++- WhateverXe/kern_host_power_contract.hpp | 7 -- WhateverXe/kern_xe.hpp | 1 - tests/host_publication.py | 83 +++++++++++++++---- 6 files changed, 136 insertions(+), 45 deletions(-) diff --git a/WhateverXe/kern_display13_framebuffer_power.inc b/WhateverXe/kern_display13_framebuffer_power.inc index 65b99e9d..7fe87f29 100644 --- a/WhateverXe/kern_display13_framebuffer_power.inc +++ b/WhateverXe/kern_display13_framebuffer_power.inc @@ -111,10 +111,6 @@ bool XE::ownedDisplay13Framebuffer(IOService *fb) const { Display13FBMemory::field(adlnTopologyController,0xD60+8*Display13FBMemory::field(fb,0x1DC))==fb; } -bool XE::parkedDisplay13Framebuffer(IOService *fb) const { - return ownedDisplay13Framebuffer(fb) && !(Display13FBMemory::field(fb,0x199)&0x20); -} - bool XE::finishDisplay13SystemWake(IOService *caller) { if(!display13FramebufferEvent || !caller)return false; auto controller=Display13FBMemory::field(caller,0x1D0); diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 934d80d7..df844417 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -136,14 +136,18 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a auto v=patcher.solveSymbol(index,name,address,size);patcher.clearError();return v; }; sleepFBCount=sym("__ZNK21IOAccelDisplayMachine19getFramebufferCountEv"); - sleepFBAt=sym("__ZNK21IOAccelDisplayMachine16getIOFramebufferEj"); const auto sleep=sym("__ZN22IOGraphicsAccelerator217system_will_sleepEib"); const auto mode=sym("__ZN21IOAccelDisplayMachine24display_mode_will_changeEj"); const auto sleepLength=hostFunctionExtent(sleep,address,size); const auto modeLength=hostFunctionExtent(mode,address,size); const auto site=HostPowerContract::sleepSite(reinterpret_cast(sleep),sleepLength,sleep,sleepFBCount,hostInstructionSize); bool ready=false; - if(sleepFBCount && sleepFBAt && site.valid && HostPowerContract::sleepFields( + if(!sleepScopeLock)sleepScopeLock=IOSimpleLockAlloc(); + KernelPatcher::RouteRequest notification("__ZN18IOAccelDisplayPipe22display_change_handlerEPvP13IOFramebufferiS0_", + wrapDisplayChange,orgDisplayChange); + const bool notifications=sleepScopeLock && patcher.routeMultiple(index,¬ification,1,address,size); + patcher.clearError(); + if(notifications && sleepFBCount && site.valid && HostPowerContract::sleepFields( reinterpret_cast(sleep),sleepLength,reinterpret_cast(mode),modeLength,hostInstructionSize,site.owner)) { // Pass this accelerator in rdi. The host compiler may keep it in rbx or // r14; the matched count query establishes which register owns it. @@ -202,23 +206,65 @@ void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t a } xe.processKext(patcher,index,address,size); } +// Observe the subscriber that owns a sleep scope. Native system_will_sleep +// ignores its event-number argument and loops over every display pipe; a +// controller-wide asleep flag cannot identify which subscriber already woke. +IOReturn XeHost::wrapDisplayChange(void *pipe,void *reference,IOFramebuffer *fb,int event,void *argument) { + auto h=callbackHost; + const bool tracked=(event==3 || event==4) && !argument && h->xe.ownedDisplay13Framebuffer(fb); + if((event!=3 && event!=4) || argument) + return FunctionCast(wrapDisplayChange,h->orgDisplayChange)(pipe,reference,fb,event,argument); + SleepScope scope;scope.thread=reinterpret_cast(current_thread());scope.fb=tracked && event==3 ? fb : nullptr; + auto saved=IOSimpleLockLockDisableInterrupt(h->sleepScopeLock); + scope.next=h->sleepScopes;h->sleepScopes=&scope; + IOSimpleLockUnlockEnableInterrupt(h->sleepScopeLock,saved); + const auto result=FunctionCast(wrapDisplayChange,h->orgDisplayChange)(pipe,reference,fb,event,argument); + saved=IOSimpleLockLockDisableInterrupt(h->sleepScopeLock); + // The list holds stack records only during a synchronous native callback. + // Another thread or a nested subscriber must never inherit this decision. + auto link=&h->sleepScopes; + while(*link && *link!=&scope)link=&(*link)->next; + if(*link)*link=scope.next; + SleepEntry *entry=nullptr; + if(tracked)for(auto &item:h->sleepEntries)if(item.fb==fb){entry=&item;break;} + if(tracked && !entry)for(auto &item:h->sleepEntries)if(!item.pending){entry=&item;item.fb=fb;break;} + if(entry && result==kIOReturnSuccess) { + if(event==3 && scope.reached){entry->pending=true;++h->sleepNotifications;} + if(event==4){entry->pending=false;++h->wakeNotifications;} + } + uint32_t pending=0; + for(auto &item:h->sleepEntries)if(item.pending)++pending; + uint32_t receipt[]={1,h->sleepNotifications,h->wakeNotifications,h->repeatedSleep,pending, + static_cast(event),scope.reached,scope.skipped,static_cast(result)}; + IOSimpleLockUnlockEnableInterrupt(h->sleepScopeLock,saved); + IORegistryEntry::getRegistryRoot()->setProperty("WEXHostSleepScopes",receipt,sizeof(receipt)); + return result; +} + uint32_t XeHost::pendingSleepDisplays(void *accelerator) { auto h=callbackHost; auto bytes=static_cast(accelerator); auto machine=*reinterpret_cast(bytes+0x378); const auto count=reinterpret_cast(h->sleepFBCount)(machine); - if(!count || !(*reinterpret_cast(bytes+0xc78)&1))return count; - const auto depth=*reinterpret_cast(machine+0x10c); - if(depth(h->sleepFBAt)(machine,i); - if(!h->xe.parkedDisplay13Framebuffer(fb))return count; + if(!count || !h->sleepScopeLock)return count; + const auto thread=reinterpret_cast(current_thread()); + auto saved=IOSimpleLockLockDisableInterrupt(h->sleepScopeLock); + SleepScope *scope=h->sleepScopes; + while(scope && scope->thread!=thread)scope=scope->next; + bool duplicate=false; + if(scope && scope->fb) { + scope->reached=true; + for(auto &entry:h->sleepEntries)if(entry.fb==scope->fb && entry.pending){duplicate=true;break;} + scope->skipped=duplicate; + if(duplicate)++h->repeatedSleep; } - // Already parked by a full two-phase notification, with no intervening - // DidWake. The real resume must close the original scope exactly once. - uint32_t record[]={__atomic_add_fetch(&h->repeatedSleep,1,__ATOMIC_RELAXED),count,depth}; + const auto repeated=h->repeatedSleep; + IOSimpleLockUnlockEnableInterrupt(h->sleepScopeLock,saved); + if(!duplicate)return count; + // Skip only the repeated preparation loop. Orphan cleanup, the hardware + // sleep phase and every native wake notification still execute normally. + const auto depth=*reinterpret_cast(machine+0x10c); + uint32_t record[]={repeated,count,depth}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIDarkWakeParked",record,sizeof(record)); return 0; } diff --git a/WhateverXe/kern_host.hpp b/WhateverXe/kern_host.hpp index c5f13325..d84a89de 100644 --- a/WhateverXe/kern_host.hpp +++ b/WhateverXe/kern_host.hpp @@ -14,8 +14,18 @@ class XeHost { PendingDonor pendingDonors[2] {};unsigned pendingDonorCount {}; bool dependenciesSeen() const {return surfaceSeen && (!systemWakeRecovery || (graphicsSeen && accelSeen));} void flushDonors(KernelPatcher &); - mach_vm_address_t sleepFBCount {},sleepFBAt {}; - uint32_t repeatedSleep {}; + mach_vm_address_t sleepFBCount {}; + uint32_t repeatedSleep {},sleepNotifications {},wakeNotifications {}; + IOSimpleLock *sleepScopeLock {}; + struct SleepEntry {IOService *fb {};bool pending {};}; + SleepEntry sleepEntries[3] {}; + struct SleepScope { + SleepScope *next {};uintptr_t thread {};IOService *fb {}; + bool reached {},skipped {}; + }; + SleepScope *sleepScopes {}; + mach_vm_address_t orgDisplayChange {}; + static IOReturn wrapDisplayChange(void *,void *,IOFramebuffer *,int,void *); static uint32_t pendingSleepDisplays(void *); mach_vm_address_t orgWSAAAttribute {}; static IOReturn wrapWSAAAttribute(IOFramebuffer *,uint32_t,uint32_t); diff --git a/WhateverXe/kern_host_power_contract.hpp b/WhateverXe/kern_host_power_contract.hpp index 391d06d5..9cf408b4 100644 --- a/WhateverXe/kern_host_power_contract.hpp +++ b/WhateverXe/kern_host_power_contract.hpp @@ -132,16 +132,9 @@ inline SleepSite sleepSite(const uint8_t *p,size_t size,uintptr_t address,uintpt return hits==1 && calls==2 ? result : SleepSite{}; } inline bool sleepFields(const uint8_t *sleep,size_t sleepSize,const uint8_t *mode,size_t modeSize,Decode decode,unsigned owner) { - uint8_t asleep[9] {};size_t length=0; - if(owner>=8)asleep[length++]=0x41; - asleep[length++]=0xF6;asleep[length++]=0x80|(owner&7); - if((owner&7)==4)asleep[length++]=0x24; - asleep[length++]=0x78;asleep[length++]=0x0C; - asleep[length++]=0;asleep[length++]=0;asleep[length++]=1; const uint8_t depthRBX[]={0x8B,0x83,0x0C,1,0,0,0x8D,0x48,1,0x89,0x8B,0x0C,1,0,0}; const uint8_t depthR12[]={0x41,0x8B,0x84,0x24,0x0C,1,0,0,0x8D,0x48,1,0x41,0x89,0x8C,0x24,0x0C,1,0,0}; return ownerFromThis(sleep,sleepSize,owner,decode) && - occurrences(sleep,sleepSize,asleep,length,decode)==1 && ((ownerFromThis(mode,modeSize,3,decode) && occurrences(mode,modeSize,depthRBX,sizeof(depthRBX),decode)==1) || (ownerFromThis(mode,modeSize,12,decode) && occurrences(mode,modeSize,depthR12,sizeof(depthR12),decode)==1)); } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 756bbb24..6e44ccee 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -74,7 +74,6 @@ class XE { */ bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); bool ownedDisplay13Framebuffer(IOService *fb) const; - bool parkedDisplay13Framebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; bool isIntelDonor(size_t index) const; void setHostSurfaceABI(unsigned framebuffer,unsigned accelerator); diff --git a/tests/host_publication.py b/tests/host_publication.py index 94aec876..a93466e2 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -56,9 +56,14 @@ namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; struct {size_t loadIndex=6;}surface;struct {size_t loadIndex=5;}backlight;struct {size_t loadIndex=7;}accelFamily; +using IOSimpleLock=int;int scopeLock=0;uintptr_t fakeThread=1; +IOSimpleLock *IOSimpleLockAlloc(){return &scopeLock;} +int IOSimpleLockLockDisableInterrupt(IOSimpleLock*){return 0;} +void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,int){} +void *current_thread(){return reinterpret_cast(fakeThread);} using IOService=IOPCIDevice;using IOReturn=int;using IOFramebuffer=IOPCIDevice; constexpr uint32_t kIOWindowServerActiveAttribute=1,kIOWSAA_StateMask=15,kIOWSAA_Accelerated=1,kIOWSAA_Unaccelerated=0; -constexpr IOReturn kIOReturnNotReady=-1; +constexpr IOReturn kIOReturnSuccess=0,kIOReturnNotReady=-1; #define FunctionCast(fn,addr) reinterpret_cast(addr) struct XeHost { static XeHost *callbackHost;bool systemWakeRecovery=false; @@ -66,7 +71,13 @@ struct PendingDonor {size_t index;uintptr_t address;size_t size;};PendingDonor pendingDonors[2]{};unsigned pendingDonorCount=0; bool dependenciesSeen() const {return surfaceSeen && (!systemWakeRecovery || (graphicsSeen && accelSeen));} void flushDonors(KernelPatcher&); - uintptr_t sleepFBCount=0,sleepFBAt=0;uint32_t repeatedSleep=0;static uint32_t pendingSleepDisplays(void*); + uintptr_t sleepFBCount=0,sleepFBAt=0;uint32_t repeatedSleep=0,sleepNotifications=0,wakeNotifications=0; + IOSimpleLock *sleepScopeLock=nullptr; + struct SleepEntry {IOService *fb=nullptr;bool pending=false;};SleepEntry sleepEntries[3]{}; + struct SleepScope {SleepScope *next=nullptr;uintptr_t thread=0;IOService *fb=nullptr;bool reached=false,skipped=false;};SleepScope *sleepScopes=nullptr; + uintptr_t orgDisplayChange=0; + static IOReturn wrapDisplayChange(void*,void*,IOFramebuffer*,int,void*); + static uint32_t pendingSleepDisplays(void*); uintptr_t orgWSAAAttribute=0; static IOReturn wrapWSAAAttribute(IOFramebuffer*,uint32_t,uint32_t); bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; @@ -78,8 +89,7 @@ void setHostFramebufferEvent(uintptr_t){} void setHostSleepReady(bool){} void setHostWakeRecoveryReady(bool){} - IOService *parked=nullptr;bool parkedDisplay13Framebuffer(IOService *f)const{return parked && f==parked;} - bool ownedDisplay13Framebuffer(IOService *)const{return false;} + std::vector owned;bool ownedDisplay13Framebuffer(IOService *f)const{return std::find(owned.begin(),owned.end(),f)!=owned.end();} IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} @@ -97,24 +107,61 @@ assert(XeHost::wrapWSAAAttribute(&gpu,1,1)==42);exercised.xe.degraded=&gpu; assert(XeHost::wrapWSAAAttribute(&gpu,1,0x101)==kIOReturnNotReady && XeHost::wrapWSAAAttribute(&gpu,1,0)==kIOReturnNotReady); assert(XeHost::wrapWSAAAttribute(&mei,1,1)==42 && XeHost::wrapWSAAAttribute(&gpu,2,1)==42 && XeHost::wrapWSAAAttribute(&gpu,1,3)==42); +// Replay mixed DarkWake with every subscriber ordering and awake subset. alignas(8) uint8_t accel[0xd00]{},machine[0x140]{}; *reinterpret_cast(accel+0x378)=machine; exercised.sleepFBCount=reinterpret_cast(+[](void*)->uint32_t{return 3;}); +exercised.sleepScopeLock=&scopeLock; +IOPCIDevice fb[3],foreign;exercised.xe.owned={fb,fb+1,fb+2}; +for(unsigned i=0;i<3;++i)reinterpret_cast(machine)[i]=fb+i; exercised.sleepFBAt=reinterpret_cast(+[](void *m,unsigned i)->IOService*{assert(i<3);return static_cast(m)[i];}); -for(unsigned i=0;i<3;++i)reinterpret_cast(machine)[i]=&gpu;exercised.xe.parked=&gpu; -for(unsigned asleep:{0U,1U})for(unsigned depth:{0U,2U,3U,9U})for(bool owned:{false,true}){ - *reinterpret_cast(accel+0xc78)=asleep;*reinterpret_cast(machine+0x10c)=depth; - exercised.xe.parked=owned?&gpu:nullptr; - assert(XeHost::pendingSleepDisplays(accel)==(asleep && depth>=3 && owned?0U:3U)); -} -assert(exercised.repeatedSleep==2); -// FB0 parked cannot hide a newly awake or foreign peer at any index. -for(unsigned i=0;i<3;++i){ - reinterpret_cast(machine)[i]=&mei; - assert(XeHost::pendingSleepDisplays(accel)==3); - reinterpret_cast(machine)[i]=&gpu; -} -assert(exercised.repeatedSleep==2); +static uint8_t *testAccel;testAccel=accel;static unsigned cleanups,hardware;static bool reject; +exercised.orgDisplayChange=reinterpret_cast(+[](void*,void*,IOFramebuffer*,int event,void *argument)->IOReturn{ + auto machine=*reinterpret_cast(testAccel+0x378);auto &depth=*reinterpret_cast(machine+0x10c); + if(reject)return -1; + if(event==3 && !argument){depth+=XeHost::pendingSleepDisplays(testAccel);++cleanups;} + if(event==4 && !argument){assert(depth>=3);depth-=3;} + if(argument)++hardware; + return 0; +}); +unsigned ordering[]={0,1,2};do{for(unsigned mask=0;mask<8;++mask){ + for(auto &entry:exercised.sleepEntries)entry={};exercised.repeatedSleep=0; + auto &depth=*reinterpret_cast(machine+0x10c);depth=0;cleanups=hardware=0; + auto notify=[&](unsigned i,int event){assert(XeHost::wrapDisplayChange(nullptr,nullptr,fb+i,event,nullptr)==0);assert(XeHost::wrapDisplayChange(nullptr,nullptr,fb+i,event,reinterpret_cast(1))==0);}; + for(auto i:ordering)notify(i,3);assert(depth==9); + for(unsigned cycle=0;cycle<4;++cycle){ + for(auto i:ordering)if(mask&(1U<(accel+0xc78)=0; + for(auto i:ordering)notify(i,3); + assert(depth==9); + } + for(auto i:ordering)notify(i,4); + assert(depth==0 && exercised.repeatedSleep==4*(3-__builtin_popcount(mask))); + assert(cleanups==15 && hardware==18+4*__builtin_popcount(mask)); + assert(!exercised.sleepScopes); +}} +while(std::next_permutation(ordering,ordering+3)); +// Failed notifications do not establish a completed preparation scope. +for(auto &entry:exercised.sleepEntries)entry={};reject=true; +assert(XeHost::wrapDisplayChange(nullptr,nullptr,fb,3,nullptr)==-1);reject=false; +assert(XeHost::wrapDisplayChange(nullptr,nullptr,fb,3,nullptr)==0); +assert(*reinterpret_cast(machine+0x10c)==3); +// Other threads and direct calls cannot inherit an enclosing skip decision. +XeHost::SleepScope outer;outer.thread=2;outer.fb=fb;exercised.sleepScopes=&outer; +assert(XeHost::pendingSleepDisplays(accel)==3);exercised.sleepScopes=nullptr; +assert(XeHost::pendingSleepDisplays(accel)==3); +// A foreign nested subscriber on this thread is a barrier, not the owner +// of the outer subscriber's pending preparation. +outer.thread=1;outer.fb=fb;exercised.sleepScopes=&outer; +const auto before=*reinterpret_cast(machine+0x10c); +assert(XeHost::wrapDisplayChange(nullptr,nullptr,&foreign,3,nullptr)==0); +assert(*reinterpret_cast(machine+0x10c)==before+3); +assert(exercised.sleepScopes==&outer && !outer.reached); +assert(XeHost::wrapDisplayChange(nullptr,nullptr,&foreign,4,nullptr)==0); +assert(*reinterpret_cast(machine+0x10c)==before); +exercised.sleepScopes=nullptr; // Already-loaded notifications may arrive in any host dependency order. // Neither missing contracts nor a late callback may silently skip donor setup. unsigned order[]={1,2,4,6,7};do { From 20b8ae5c705e12a54d418df03d3665efc874dac6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:01:32 +0900 Subject: [PATCH 31/46] Prepare WEX 0.8.0 with Raptor Lake-U and experimental sleep support --- CHANGELOG.md | 16 ++++++++++++++++ README.md | 23 +++++++++++++---------- README_ja.md | 23 +++++++++++++---------- docs/device-selection.md | 2 +- tools/build.py | 2 +- 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10544d89..a71f4b22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.8.0 + +Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics/releases) package with this update. + +### Added + +- Add Raptor Lake-U graphics support, tested on CHUWI MiniBook X U300 with Big Sur and Tahoe. +- Add experimental system sleep/wake on MiniBook X N100 and U300 with suitable model-specific ACPI configuration. Tested on Big Sur and Tahoe with the internal display alone or up to two external displays; other configurations remain unverified. +- Add an optional 60 Hz internal-display mode for matching MiniBook X N100 and U300 panels. + +### Fixed + +- Fix restart and shutdown with the supported MIPI DSI panels, and accelerated startup with USB-C displays already connected. +- Fix rotated MIPI DSI display-mode selection and improve display restoration after sleep. +- Improve compatibility with supported macOS updates when kernel or system-driver code placement changes. + ## 0.7.0 Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics/releases) package with this update. diff --git a/README.md b/README.md index cfcc8868..d503902a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Sequoia and Tahoe accept compatible updates within their OS family. Future Apple - Twin Lake: 32-EU configurations. - Alder Lake-H: 96-EU configurations, including single-channel operation. - Alder Lake-S: UHD Graphics 770. -- Raptor Lake-U: 48-EU Intel Processor U300 on Big Sur. +- Raptor Lake-U: 48-EU configurations on Big Sur and Tahoe (MiniBook X U300). See [tested features and known issues](#tested-features-and-known-issues) for OS coverage and display configurations. PCI IDs are listed in [device selection](docs/device-selection.md). @@ -35,7 +35,7 @@ See [tested features and known issues](#tested-features-and-known-issues) for OS - Other supported Alder Lake-N / Twin Lake configurations. - Other supported Alder Lake-H/P/U and Raptor Lake-H/P/U configurations. - Other supported Alder Lake-S/HX and Raptor Lake-S/HX configurations. -- Internal eDP on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P is implemented but has not been tested on hardware. +- Internal eDP on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U is implemented but has not been tested on hardware. ### Further implementation required @@ -81,9 +81,8 @@ These settings are unnecessary at 96MB and do not increase firmware-allocated me ### Other settings - Add `SSDT-PNLF` for internal-display brightness control. -- The tested MiniBook X U300 setup uses `-wex-dsi-adopt` to adopt the firmware’s internal DSI scanout. On matching MiniBook X U300 and N100 panel firmware, `-wex-dsi-60hz` requests a fixed 60 Hz mode at boot. Both panels were tested under Big Sur using their respective firmware blanking intervals. The N100 was also tested under Tahoe. Remove the argument and reboot for the default 50 Hz. The U300 timing comes from the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). - GPU selection is automatic; a `device-id` override is normally unnecessary. -- Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P select tiling automatically. Do not use `-yTileDisable`; `AAPL,GfxYTile` is unnecessary. +- Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U select tiling automatically. Do not use `-yTileDisable`; `AAPL,GfxYTile` is unnecessary. ## Tested features and known issues @@ -94,25 +93,27 @@ The common regression checks cover Metal, OpenGL, OpenCL, hardware H.264 encode/ | Tiger Lake mini PCs | Big Sur–Tahoe: 80 EU, two 1080p60 displays, display off/on, Safari video and Widevine | | Tiger Lake laptops | Big Sur–Tahoe: 48 EU, internal eDP and brightness, HDMI/USB-C, up to three displays and display off/on | | Alder Lake-N mini PCs | Big Sur–Tahoe: 16/24 EU, two 1080p60 HDMI displays and display off/on | -| Alder Lake-N / Twin Lake laptops | MIPI DSI on MicroPC 2 (N250) and MiniBook X N100. Big Sur: brightness, rotation, display off/on and external HDMI/USB-C coexistence; MiniBook X N100 also supports the optional 60 Hz mode. MicroPC 2 also has HiDPI and up to three displays. Tahoe: MicroPC 2 internal/external displays, brightness, rotation and HiDPI | -| Raptor Lake-U laptop | Big Sur: MiniBook X U300, internal MIPI DSI at 1200×1920 @ 50 Hz or optional 60 Hz, accelerated startup with up to two USB-C displays connected, Metal compute/render, OpenGL, hardware H.264 encode/decode and HEVC decode. Tahoe: internal 50 Hz display, native landscape and HiDPI modes | +| Alder Lake-N / Twin Lake laptops | MicroPC 2 and MiniBook X N100 on Big Sur/Tahoe: MIPI DSI, brightness, rotation, display off/on and HDMI/USB-C coexistence. MicroPC 2: HiDPI and up to three displays; MiniBook X N100: optional 60 Hz | +| Raptor Lake-U laptop | MiniBook X U300 on Big Sur/Tahoe: internal MIPI DSI, brightness, rotation, HiDPI and up to two USB-C displays. Internal panel: 50 Hz; optional 60 Hz also tested on Big Sur | | Alder Lake-H | Big Sur–Tahoe: 96 EU, three displays using HDMI and a DP-to-HDMI adapter. Single-channel `4626` graphics acceleration checked on Big Sur | | Alder Lake-S | Big Sur–Tahoe: UHD 770, HDMI/DP/USB-C and up to three displays; Safari video, with Widevine checked on Big Sur/Tahoe | -MIPI DSI support is **experimental**. The tested panels are those in the GPD MicroPC 2 and CHUWI MiniBook X N100 and U300. +MIPI DSI support is **experimental**, limited to the tested GPD MicroPC 2 and CHUWI MiniBook X N100/U300 panels. Do not expect it to work on other devices. + +System sleep/wake is **experimental** on MiniBook X N100/U300 with suitable model-specific ACPI configuration. Tested on Big Sur and Tahoe with the internal display alone or up to two external displays. Other models, OS versions and prolonged DarkWake cycles remain unverified. ### Known issues and limits - DisplayPort reconnection can cause black screens or freezes. Tiger Lake USB-C reconnection is also unreliable; Alder Lake-S DP output/reconnection can be slow. - Some image-conversion operations can crash applications; App Store video can freeze the GPU on Sequoia. - Safari video may occasionally drop frames. FairPlay-protected playback is unsupported. -- Up to three displays are supported. Resolutions beyond the tested configurations and full system sleep/wake remain unverified. +- Up to three displays are supported. Resolutions beyond the tested configurations remain unverified. - Some connector layouts and backlight methods, MST and Thunderbolt-managed outputs are unsupported. ### Roadmap -- Complete DisplayPort, USB-C and internal-display support on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P. -- Improve display reconnection reliability and investigate full system sleep/wake. +- Complete DisplayPort, USB-C and internal-display support on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U. +- Improve display reconnection reliability and extend system sleep/wake coverage. ## Build and test @@ -133,3 +134,5 @@ See [LICENSE.txt](LICENSE.txt). Lilu and MacKernelSDK are separate dependencies. Thanks to [JulioNectar/XPS13Plus](https://github.com/JulioNectar/Hackintosh-XPS13Plus-IrisXe-Tahoe) for sharing its Xe graphics work. Its compiler adapter and LLVM bridge informed the OS-native AIR conversion used in WhateverXe Graphics. Internal-panel work also draws on [WhateverXe-RaptorLakeP](https://github.com/samranahmad09/WhateverXe-RaptorLakeP), [X1Carbon-G11](https://github.com/dssoftx/Hackintosh-X1Carbon-G11-IrisXe-Tahoe), XPS13Plus and the [Linux i915 display sources](https://github.com/torvalds/linux/tree/v6.12/drivers/gpu/drm/i915/display) for hardware behavior and implementation comparisons. + +The optional MiniBook X 60 Hz panel timing also draws on the [minibookx-ctl display guide](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md). diff --git a/README_ja.md b/README_ja.md index f133d00a..7dca0327 100644 --- a/README_ja.md +++ b/README_ja.md @@ -25,7 +25,7 @@ SequoiaとTahoeは同じOS系列内の互換性のある更新に対応します - Twin Lake:32 EU構成。 - Alder Lake-H:シングルチャネルを含む96 EU構成。 - Alder Lake-S:UHD Graphics 770。 -- Raptor Lake-U:Intel Processor U300の48 EU構成(Big Sur)。 +- Raptor Lake-U:48 EU構成(MiniBook X U300、Big Sur・Tahoe)。 OS別の範囲と表示構成は[動作確認済みの機能と既知の問題](#動作確認済みの機能と既知の問題)、PCI IDは[デバイス選択](docs/device-selection.md)を参照してください。 @@ -35,7 +35,7 @@ OS別の範囲と表示構成は[動作確認済みの機能と既知の問題]( - 対応IDの他のAlder Lake-N / Twin Lake構成。 - 対応IDの他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。 - 対応IDの他のAlder Lake-S/HX・Raptor Lake-S/HX構成。 -- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pの内蔵eDPは実装済み・実機未検証です。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uの内蔵eDPは実装済み・実機未検証です。 ### 対応に追加改修が必要 @@ -81,9 +81,8 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします ### その他の設定 - 内蔵画面の輝度調整には`SSDT-PNLF`を追加します。 -- MiniBook X U300の検証構成では、内蔵DSIのファームウェア表示を引き継ぐために `-wex-dsi-adopt` を使用します。対象のMiniBook X U300またはN100のパネルテーブルと一致すると、`-wex-dsi-60hz` で起動時に固定60 Hzを要求します。各パネルのファームウェアが指定するブランキング期間を使い、両機種ともBig Surで実機確認済みです。N100はTahoeでも確認しました。この引数を削除して再起動すると標準の50 Hzに戻ります。U300のタイミングの出典は [minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md) です。 - GPUは自動選択するため、通常は`device-id`上書きは不要です。 -- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/Pではタイル形式を自動選択します。`-yTileDisable`は使わないでください。`AAPL,GfxYTile`は不要です。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uではタイル形式を自動選択します。`-yTileDisable`は使わないでください。`AAPL,GfxYTile`は不要です。 ## 動作確認済みの機能と既知の問題 @@ -94,25 +93,27 @@ BIOS/UEFIで設定できる場合は、**DVMT Pre-Allocatedを96MB**にします | Tiger LakeミニPC | Big Sur〜Tahoe:80 EU、1080p60の2画面、消灯復帰、Safari動画・Widevine | | Tiger Lakeラップトップ | Big Sur〜Tahoe:48 EU、内蔵eDPと輝度調整、HDMI/USB-C、最大3画面・消灯復帰 | | Alder Lake-NミニPC | Big Sur〜Tahoe:16/24 EU、1080p60のHDMI 2画面・消灯復帰 | -| Alder Lake-N / Twin Lakeラップトップ | MicroPC 2(N250)とMiniBook X N100のMIPI DSI。Big Sur:輝度・回転・消灯復帰・外部HDMI/USB-C共存。MiniBook X N100は任意設定の60 Hz、MicroPC 2はHiDPI・最大3画面も確認。Tahoe:MicroPC 2の内蔵/外部表示・輝度・回転・HiDPI | -| Raptor Lake-Uラップトップ | Big Sur:MiniBook X U300の内蔵MIPI DSI 1200×1920・標準50 Hz/任意設定で60 Hz、USB-C外部画面を最大2台接続した起動での加速、Metal計算・描画、OpenGL、H.264ハードウェアencode/decode・HEVC decode。Tahoe:内蔵50 Hz・横向きネイティブ表示・HiDPI | +| Alder Lake-N / Twin Lakeラップトップ | Big Sur・Tahoe:MicroPC 2・MiniBook X N100のMIPI DSI・輝度・回転・消灯復帰・HDMI/USB-C共存。MicroPC 2はHiDPI・最大3画面、MiniBook X N100は任意設定の60 Hzも確認 | +| Raptor Lake-Uラップトップ | Big Sur・Tahoe:MiniBook X U300の内蔵MIPI DSI・輝度・回転・HiDPI・USB-C外部2画面。内蔵は50 Hz、Big Surでは任意設定の60 Hzも確認 | | Alder Lake-H | Big Sur〜Tahoe:96 EU、HDMIとDP→HDMI変換器による3画面。シングルチャネル構成の`4626`でもBig Surの加速を確認 | | Alder Lake-S | Big Sur〜Tahoe:UHD 770、HDMI/DP/USB-C、最大3画面、Safari動画。WidevineはBig Sur/Tahoeで確認 | -MIPI DSI対応は**試験的**です。GPD MicroPC 2、CHUWI MiniBook X N100とU300の内蔵パネルで検証しました。 +MIPI DSI対応は**試験的**で、検証したGPD MicroPC 2・CHUWI MiniBook X N100/U300の内蔵パネルに限ります。他の機器での動作を期待しないでください。 + +システム全体のスリープ/復帰は、機種に合ったACPI設定を使用するMiniBook X N100/U300で**試験的**に対応します。Big Sur・Tahoeで内蔵単独と外部2画面までの構成を確認しました。他機種・他OSと長時間のDarkWake反復は未検証です。 ### 既知の問題と制限 - DisplayPortの再接続で黒画面・停止が起きる場合があります。Tiger LakeのUSB-C再接続にも未解決の問題があり、Alder Lake-SのDP表示開始・再接続は時間がかかる場合があります。 - 一部の画像変換でアプリがクラッシュし、SequoiaのApp Store動画ではGPUが停止することがあります。 - Safari動画で時折コマ落ちします。FairPlay保護コンテンツは未対応です。 -- 同時表示は最大3画面です。検証構成を超える解像度とシステム全体のスリープ/復帰は未検証です。 +- 同時表示は最大3画面です。検証構成を超える解像度は未検証です。 - 一部の端子配線・輝度制御方式、MST、Thunderbolt管理の出力は未対応です。 ### ロードマップ -- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/PでのDisplayPort・USB-C・内蔵画面対応の拡充。 -- 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UでのDisplayPort・USB-C・内蔵画面対応の拡充。 +- 画面再接続の安定性改善とシステム全体のスリープ/復帰の対応範囲拡充。 ## ビルドとテスト @@ -132,3 +133,5 @@ WhateverXeはWhateverGreenの著作権表示とBSD-3-Clauseライセンスを継 Xeグラフィックスの取り組みを公開している[JulioNectar/XPS13Plus](https://github.com/JulioNectar/Hackintosh-XPS13Plus-IrisXe-Tahoe)に感謝します。同リポジトリのcompiler adapterとLLVM bridgeを、WhateverXe GraphicsのOS標準AIR変換の参考にしました。 内蔵パネルの対応では、[WhateverXe-RaptorLakeP](https://github.com/samranahmad09/WhateverXe-RaptorLakeP)、[X1Carbon-G11](https://github.com/dssoftx/Hackintosh-X1Carbon-G11-IrisXe-Tahoe)、XPS13Plus、[Linux i915の表示ソース](https://github.com/torvalds/linux/tree/v6.12/drivers/gpu/drm/i915/display)も、ハードウェアの挙動と実装の比較に参照しています。 + +MiniBook Xの任意設定60 Hzのパネルタイミングには、[minibookx-ctlの表示ガイド](https://github.com/ishad0w/minibookx-ctl/blob/main/docs/guide/display.md)も参照しています。 diff --git a/docs/device-selection.md b/docs/device-selection.md index 50fad646..ae349bd2 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -15,7 +15,7 @@ WEX selects the GPU from its physical PCI ID and detects its actual resources au | Raptor Lake-S / HX | `A780`, `A781`, `A782`, `A783`, `A788`, `A789`, `A78A`, `A78B` | An implemented backend does not imply hardware validation for every ID or connector. See [compatibility and tested configurations](../README.md#compatibility). -Raptor Lake-U uses its own CDCLK table, including 480 MHz and its PCODE voltage level. The physical PCI ID selects this clock policy. Internal DSI and acceleration have been verified on `A7A9` with Big Sur. Rocket Lake remains unsupported. +Raptor Lake-U internal DSI and acceleration have been verified on MiniBook X U300 (`A7A9`) with Big Sur and Tahoe. Rocket Lake remains unsupported. Memory configuration can change the PCI ID, such as `46A6` to `4626` in a single-channel Alder Lake-H configuration. No override is needed; use updated WEX and Graphics components together. diff --git a/tools/build.py b/tools/build.py index 4110fceb..8f7ad232 100644 --- a/tools/build.py +++ b/tools/build.py @@ -19,7 +19,7 @@ def main(): base+=['-fdebug-compilation-dir=.', '-ffile-prefix-map='+str(ROOT)+'=/WhateverXe', '-ffile-prefix-map='+str(sdk)+'=/MacKernelSDK', '-ffile-prefix-map='+str(lilu)+'=/Lilu'] manifest={} - for name,version,sources in [('WhateverXe','0.7.0',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.7.0',[ROOT/'AppleIntelTGLCompat/AppleIntelTGLCompat.cpp'])]: + for name,version,sources in [('WhateverXe','0.8.0',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.8.0',[ROOT/'AppleIntelTGLCompat/AppleIntelTGLCompat.cpp'])]: bundle=out/(name+'.kext')/'Contents';(bundle/'MacOS').mkdir(parents=True,exist_ok=True);objdir=out/name;objdir.mkdir(exist_ok=True) objects=[] for source in sources: From d98bf016868af7fee11c3521ef7aec061ac49f66 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:05:01 +0900 Subject: [PATCH 32/46] Keep tested GPU family list consistent --- README.md | 2 +- README_ja.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d503902a..876ae2dc 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Sequoia and Tahoe accept compatible updates within their OS family. Future Apple - Twin Lake: 32-EU configurations. - Alder Lake-H: 96-EU configurations, including single-channel operation. - Alder Lake-S: UHD Graphics 770. -- Raptor Lake-U: 48-EU configurations on Big Sur and Tahoe (MiniBook X U300). +- Raptor Lake-U: 48-EU configurations. See [tested features and known issues](#tested-features-and-known-issues) for OS coverage and display configurations. PCI IDs are listed in [device selection](docs/device-selection.md). diff --git a/README_ja.md b/README_ja.md index 7dca0327..106e385a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -25,7 +25,7 @@ SequoiaとTahoeは同じOS系列内の互換性のある更新に対応します - Twin Lake:32 EU構成。 - Alder Lake-H:シングルチャネルを含む96 EU構成。 - Alder Lake-S:UHD Graphics 770。 -- Raptor Lake-U:48 EU構成(MiniBook X U300、Big Sur・Tahoe)。 +- Raptor Lake-U:48 EU構成。 OS別の範囲と表示構成は[動作確認済みの機能と既知の問題](#動作確認済みの機能と既知の問題)、PCI IDは[デバイス選択](docs/device-selection.md)を参照してください。 From fe9240022dc5ca693dcb8d46b5d47132607b2272 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:40:36 +0900 Subject: [PATCH 33/46] Qualify Tiger Lake EU and L3 capacities independently --- WhateverXe/kern_xelp_gt_resources.hpp | 13 +++---------- tests/tgl_gt2_urb.py | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/WhateverXe/kern_xelp_gt_resources.hpp b/WhateverXe/kern_xelp_gt_resources.hpp index 545028a3..10d4076a 100644 --- a/WhateverXe/kern_xelp_gt_resources.hpp +++ b/WhateverXe/kern_xelp_gt_resources.hpp @@ -54,17 +54,10 @@ inline bool decodeADLPGT(const uint32_t *fuses, GTResources &out) { return true; } -// TGL U/Y shares Gen12 SSEU and L3-disable fuses with the existing reader. -// Reuse only the established 48-EU/6-bank and 80/96-EU/8-bank URB shapes. -// A PCI ID (including Linux's GT2 classification) does not determine capacity. +// TGL U/Y uses the same fuse fields and donor resource backend as ADL-P. +// Qualify each capacity independently: EU count does not determine L3 banks. inline bool decodeTGLGT2(const uint32_t *fuses, GTResources &out) { - GTResources decoded; - out={}; - if(!decodeADLPGT(fuses,decoded))return false; - if(!((decoded.dss==3 && decoded.eus==48 && decoded.l3Banks==6) || - ((decoded.dss==5 || decoded.dss==6) && decoded.eus==16*decoded.dss && decoded.l3Banks==8)))return false; - out=decoded; - return true; + return decodeADLPGT(fuses,out); } } diff --git a/tests/tgl_gt2_urb.py b/tests/tgl_gt2_urb.py index cc164829..5b83d297 100644 --- a/tests/tgl_gt2_urb.py +++ b/tests/tgl_gt2_urb.py @@ -22,11 +22,11 @@ #include #include #include -void check(unsigned id,unsigned rev,unsigned dss,unsigned banks,unsigned media) { +void check(unsigned id,unsigned rev,unsigned dss,unsigned banks,unsigned media,unsigned eu=0) { auto xeProfile=XELP::identify((id<<16)|0x8086,rev); bool display12SmallGTDetected=false; XELP::SmallGTResources display12SmallGT; - uint32_t fuses[]={1,dss,0,banks,media}; + uint32_t fuses[]={1,dss,eu,banks,media}; assert(xeProfile.admitted() && XELP::tigerLakeGT2Device(xeProfile.identity)); bool tglGT2Detected=true;XELP::GTResources tglGT2Resources; assert(XELP::decodeTGLGT2(fuses,tglGT2Resources)); @@ -64,15 +64,23 @@ check(id,rev,7,0xc0,video); // 48 EU, three DSS, six banks check(id,rev,0x3e,0,video); // 80 EU, five DSS, eight banks check(id,rev,0x3f,0,video); // 96 EU, six DSS, eight banks + check(id,rev,0x1f,0x88,video); // 80 EU, six banks; independent fused capacities + check(id,rev,0x3f,0xf0,video,0xc0); // 72 EU, four banks } XELP::GTResources resource; assert(!XELP::decodeTGLGT2(nullptr,resource)); for(unsigned slice=0;slice<4;++slice)for(unsigned dss=0;dss<64;++dss) - for(unsigned eu:{0U,0xc0U,0xf0U,0xffU})for(unsigned banks:{0U,0xc0U,0xf0U,0xffU}) { + for(unsigned eu:{0U,0xc0U,0xf0U,0xffU})for(unsigned banks=0;banks<256;++banks) { uint32_t f[]={slice,dss,eu,banks,0xe00fa};unsigned count=__builtin_popcount(dss); - bool expected=slice==1 && eu==0 && ((count==3 && banks==0xc0) || ((count==5 || count==6) && banks==0)); + unsigned perDSS=2*(8-__builtin_popcount(eu)),enabledBanks=8-__builtin_popcount(banks); + bool expected=slice==1 && count && (perDSS==8 || perDSS==12 || perDSS==16) && + (enabledBanks==4 || enabledBanks==6 || enabledBanks==8); assert(XELP::decodeTGLGT2(f,resource)==expected); + if(expected)assert(resource.nativeSubslices==2*count && resource.eus==perDSS*count && + resource.l3Banks==enabledBanks && resource.urbKiB==64*enabledBanks); } + uint32_t captured[]={1,0x1f,0,0x88,0xe00fa}; + assert(XELP::decodeTGLGT2(captured,resource) && resource.eus==80 && resource.l3Banks==6); for(unsigned i=0;i<5;++i){uint32_t f[]={1,63,0,0,0xe00fa};f[i]=~0U;assert(!XELP::decodeTGLGT2(f,resource));} puts("PASS all eight TGL GT2 IDs, fuse-derived URB native stores, unsupported topology rejection"); }'''.replace('BODY',body).replace('QUALIFICATION',qualification) From 9ba5f8865eb67159c0219536a44228fdbc7aaa0b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 7 Nov 2026 01:29:57 +0900 Subject: [PATCH 34/46] Preserve released admission and resolve Compat before donor setup --- WhateverXe/kern_host.cpp | 9 + WhateverXe/kern_host.hpp | 5 +- WhateverXe/kern_xe.cpp | 17 +- WhateverXe/kern_xe.hpp | 2 + tests/accelerator_resources.py | 104 ++++++ tests/adlp_load_only.py | 2 +- tests/bigsur_pmap.py | 9 +- .../fixtures/released_physical_admission.json | 327 ++++++++++++++++++ tests/framebuffer_common.py | 6 +- tests/host_publication.py | 47 ++- tests/monterey_pmap.py | 25 +- tests/released_physical_admission.py | 44 +++ tests/sonoma_pmap.py | 29 +- tests/ventura_pmap.py | 25 +- tools/export_release_admission.py | 47 +++ tools/test.py | 18 +- tools/test_release_history.py | 69 ++++ 17 files changed, 698 insertions(+), 87 deletions(-) create mode 100644 tests/accelerator_resources.py create mode 100644 tests/fixtures/released_physical_admission.json create mode 100644 tests/released_physical_admission.py create mode 100644 tools/export_release_admission.py create mode 100644 tools/test_release_history.py diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index df844417..dc282578 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -107,6 +107,15 @@ void XeHost::flushDonors(KernelPatcher &patcher) { } void XeHost::processKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(!active)return; + if(xe.isIntelProvider(index)) { + // Donor imports exist before their native start, but Lilu's already-loaded + // kmod walk need not notify their provider first. Resolve its exports before + // the one-shot framebuffer installer consumes them. Seen is not ABI-ready: + // an incompatible provider still reaches the existing rejection guards. + xe.processKext(patcher,index,address,size); + providerSeen=true;flushDonors(patcher); + return; + } if(surface.loadIndex==index) { if(surfaceSeen)return; const char *names[]={"__ZNK9IOSurface14getPixelFormatEv","__ZNK9IOSurface13getPlaneCountEv", diff --git a/WhateverXe/kern_host.hpp b/WhateverXe/kern_host.hpp index d84a89de..44bbe9cf 100644 --- a/WhateverXe/kern_host.hpp +++ b/WhateverXe/kern_host.hpp @@ -9,10 +9,11 @@ class XeHost { static XeHost *callbackHost; XE xe; bool active {},systemWakeRecovery {}; - bool surfaceSeen {},graphicsSeen {},accelSeen {}; + bool surfaceSeen {},graphicsSeen {},accelSeen {},providerSeen {}; struct PendingDonor {size_t index;mach_vm_address_t address;size_t size;}; PendingDonor pendingDonors[2] {};unsigned pendingDonorCount {}; - bool dependenciesSeen() const {return surfaceSeen && (!systemWakeRecovery || (graphicsSeen && accelSeen));} + bool dependenciesSeen() const {return surfaceSeen && (!xe.requiresIntelProvider() || providerSeen) && + (!systemWakeRecovery || (graphicsSeen && accelSeen));} void flushDonors(KernelPatcher &); mach_vm_address_t sleepFBCount {}; uint32_t repeatedSleep {},sleepNotifications {},wakeNotifications {}; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4f098f16..d05e3ba6 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4559,6 +4559,14 @@ bool XE::isIntelDonor(size_t index) const { (currentGraphics && currentGraphics->loadIndex==index); } +bool XE::requiresIntelProvider() const { + return !adlpLoadOnly && (tglRequested || display13FramebufferRequested); +} + +bool XE::isIntelProvider(size_t index) const { + return requiresIntelProvider() && kextAppleIntelTGLCompat.loadIndex==index; +} + bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { // No full donor patch set or native display/GT dispatcher in this stage. if(adlpLoadOnly) { @@ -4566,7 +4574,7 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add return processADLPLoadOnly(patcher,index,address,size); return false; } - if((tglRequested || display13FramebufferRequested) && kextAppleIntelTGLCompat.loadIndex==index) { + if(isIntelProvider(index)) { const auto probe=patcher.solveSymbol(index,"__ZN17AppleIntelPortHAL13probePortModeEv",address,size); patcher.clearError(); const auto configure=patcher.solveSymbol(index,"_xelpConfigureDisplay",address,size); @@ -12197,7 +12205,12 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add } IORegistryEntry::getRegistryRoot()->setProperty("TGLRecoverySinglePassRequested",recoveryRequested); IORegistryEntry::getRegistryRoot()->setProperty("TGLRecoverySinglePassReady",recoveryRequested && recoveryOK); - tglAcceleratorReady = surfaceABIReady && adlnObserved && skuReady && urbReady && ccsReady && hizReady && ccsPSReady && ggttOK && observed && ok && gucOK && dmaWriteOK && hostOK && scratchOK && blitInitOK && tglMMIOUCReady && copyOK && auxAlignOK && iosCCSOK && ffModeOK && cpsOK && flushOK && tlbPreparserOK && semaphoreTLBOK && hangOK && recoveryOK && completionCacheOK && contextCacheOK; + // These resource checks also precede probe/start. Include them in + // admission reporting so an installed patch set cannot hide an + // accelerator rejected before either native lifecycle callback. + const bool resourcesReady=(!tglGT2Detected || tglGT2Ready) && + (!display12SmallGTDetected || display12SmallGTReady); + tglAcceleratorReady = resourcesReady && surfaceABIReady && adlnObserved && skuReady && urbReady && ccsReady && hizReady && ccsPSReady && ggttOK && observed && ok && gucOK && dmaWriteOK && hostOK && scratchOK && blitInitOK && tglMMIOUCReady && copyOK && auxAlignOK && iosCCSOK && ffModeOK && cpsOK && flushOK && tlbPreparserOK && semaphoreTLBOK && hangOK && recoveryOK && completionCacheOK && contextCacheOK; // r201: resource/copy diagnostics are absent, not admission requirements. IORegistryEntry::getRegistryRoot()->setProperty("TGLResourceDiagnosticsRemoved",true); IORegistryEntry::getRegistryRoot()->setProperty("TGLAcceleratorAdmissionReady", tglAcceleratorReady); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 6e44ccee..ddf2740f 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -76,6 +76,8 @@ class XE { bool ownedDisplay13Framebuffer(IOService *fb) const; bool failedDSIWakeFor(IOService *fb) const; bool isIntelDonor(size_t index) const; + bool requiresIntelProvider() const; + bool isIntelProvider(size_t index) const; void setHostSurfaceABI(unsigned framebuffer,unsigned accelerator); void setHostSleepReady(bool ready) { hostSleepReady=ready; } void setHostWakeRecoveryReady(bool ready) { hostWakeRecoveryReady=ready; } diff --git a/tests/accelerator_resources.py b/tests/accelerator_resources.py new file mode 100644 index 00000000..b4914120 --- /dev/null +++ b/tests/accelerator_resources.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""Replay fuse qualification through admission reporting and native probe. + +Resource expectations are explicit Gen12 capacity examples, including a retained +physical 80-EU/six-bank readout. EU and cache populations are independent. A +failed ownership/readout prerequisite must still prevent native entry. +""" +import re, subprocess, tempfile +from pathlib import Path + +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +qualification=source.split(' const bool readoutReady=')[1].split(' if(tglGT2Detected)')[0] +qualification='const bool readoutReady='+qualification +probe=re.search(r'IOService \*XE::wrapTGLAcceleratorProbe\(.*?\n\}',source,re.S)[0] +start=re.search(r'bool XE::wrapTGLAcceleratorStart\(.*?\n\}',source,re.S)[0] +start_guard=start.split('{',1)[1].split(' if (callbackXE->adlnDetected')[0] +aggregate=re.search(r'tglAcceleratorReady = ([^;]+);',source)[0] +resources=re.search(r'const bool resourcesReady=.*?;',source,re.S) +operands=set(re.findall(r'\b\w+\b',aggregate.split('=',1)[1]))-{'resourcesReady'} +code=r''' +#include "kern_xelp_gt_resources.hpp" +#include "kern_alderlake_n.hpp" +#include +#include +#include +using SInt32=int;using mach_vm_address_t=uintptr_t; +struct IOService {virtual ~IOService()=default;}; +struct IOPCIDevice:IOService {uint32_t configRead32(unsigned){return 0x9a498086;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + templatevoid setProperty(T...){};}; +#define OSDynamicCast(T,p) dynamic_cast(p) +#define FunctionCast(fn,p) reinterpret_cast(p) +bool selected=true; +bool xelpSelectedDevice(IOPCIDevice *p){return p && selected;} +bool xelpDisplay13RenderDevice(IOPCIDevice *p){return p && selected;} +bool checkKernelArgument(const char*){return false;} +templatevoid tglObserveAccelerator(T...){} +unsigned nativeCalls=0; +IOService *nativeProbe(IOService *that,IOService*,SInt32*){++nativeCalls;return that;} +struct XE { + static XE *callbackXE; + bool tglGT2Detected=false,tglGT2Ready=false,display12SmallGTDetected=false,display12SmallGTReady=false; + bool adlsDisplaySelected=false,adlsDisplayHooksReady=true,adlsBootConsumersFailed=false; + bool adlnDetected=false,tglAcceleratorRequested=true,tglAcceleratorReady=false; + XELP::GTResources tglGT2Resources,display12SmallGT; + OPERANDS + uintptr_t orgTGLAcceleratorProbe=reinterpret_cast(&nativeProbe); + void qualify(AlderLakeN::GTReadout gt,uint32_t edram){QUALIFICATION} + void report(){RESOURCES AGGREGATE} + static IOService *wrapTGLAcceleratorProbe(IOService*,IOService*,SInt32*); + static bool startResourceGate(){START_GUARD return true;} +}; +XE *XE::callbackXE; +PROBE +struct Capacity {bool small;uint32_t fuses[5];unsigned dss,eus,banks;}; +// The cache-disable mask is not derived from the EU count. Keep both TGL +// 80-EU layouts, and independent donor-supported capacities as separate input. +const Capacity inputs[]={ + {false,{1,0x1f,0,0x88,0xe00fa},5,80,6}, + {false,{1,0x1f,0,0,0xe00fa},5,80,8}, + {false,{1,0x07,0,0xc0,0xe00fe},3,48,6}, + {false,{1,0x3f,0,0,0xe00fa},6,96,8}, + {false,{1,0x3f,0xc0,0xf0,0xe00fa},6,72,4}, + {true,{1,0x03,0,0xf0,0xe00fa},2,32,4}, + {true,{1,0x03,0xc0,0xf0,0xe00fe},2,24,4}, + {true,{1,0x01,0,0xf0,0xe00fa},1,16,4} +}; +int main(){IOPCIDevice pci;IOService accelerator;SInt32 score=0; + for(const auto &input:inputs)for(unsigned failure=0;failure<8;++failure){ + XE xe;XE::callbackXE=&xe;xe.tglGT2Detected=!input.small;xe.display12SmallGTDetected=input.small; + AlderLakeN::GTReadout gt;gt.status=AlderLakeN::GTStatus::ProfileMismatch; + gt.requestedMask=gt.releasedMask=3;memcpy(gt.fuses,input.fuses,sizeof(gt.fuses)); + if(failure==1)gt.status=AlderLakeN::GTStatus::AckTimeout; + if(failure==2)gt.releasedMask=1; + if(failure==3)gt.releaseFailedMask=2; + if(failure==4)gt.fuses[0]=UINT32_MAX; + xe.qualify(gt,failure==5?1U:0U); + const bool capacityReady=failure==0 || failure>=6; + const auto &capacity=input.small?xe.display12SmallGT:xe.tglGT2Resources; + assert((input.small?xe.display12SmallGTReady:xe.tglGT2Ready)==capacityReady); + if(capacityReady)assert(capacity.dss==input.dss && capacity.eus==input.eus && + capacity.nativeSubslices==2*input.dss && capacity.l3Banks==input.banks && capacity.urbKiB==64*input.banks); + if(failure==6)xe.surfaceABIReady=false; + selected=failure!=7;nativeCalls=0;xe.report(); + // Admission reporting must include pre-probe resource rejection. This + // caught the misleading Ready=true report independently of decoder tests. + assert(xe.tglAcceleratorReady==(capacityReady && failure!=6)); + const auto result=XE::wrapTGLAcceleratorProbe(&accelerator,&pci,&score); + assert((result==&accelerator)==(failure==0)); + assert(nativeCalls==(failure==0?1U:0U)); + assert(XE::startResourceGate()==capacityReady); + } + puts("PASS independent capacities reach native probe; failed ownership/readout is rejected and reported"); +} +''' +for key,value in {'OPERANDS':'\n'.join('bool '+x+'=true;' for x in sorted(operands)), + 'QUALIFICATION':qualification,'RESOURCES':resources[0] if resources else '', + 'AGGREGATE':aggregate,'START_GUARD':start_guard,'PROBE':probe}.items(): + code=code.replace(key,value) +with tempfile.TemporaryDirectory(prefix='wex-accelerator-resources-') as t: + p=Path(t);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/adlp_load_only.py b/tests/adlp_load_only.py index 17262154..709fa182 100644 --- a/tests/adlp_load_only.py +++ b/tests/adlp_load_only.py @@ -6,7 +6,7 @@ a=s.index('// ADLP_LOAD_ONLY_HOOKS:');b=s.index('// ADLP_LOAD_ONLY_HOOKS_END',a);body=s[a:b] a=s.index(' // ADLP_LOAD_ONLY_KERNEL:');b=s.index(' // ADLP_LOAD_ONLY_KERNEL_END',a);selection=s[a:b] a=s.index(' if(xeProfile.mobileDisplay13() && physicalGPU && checkKernelArgument("-wex-adlp-readout")');b=s.index(' if(((display12SmallGTDetected',a);diagnostic=s[a:b] -dispatch=s[s.index('bool XE::processKext('):s.index(' if((tglRequested',s.index('bool XE::processKext('))] +dispatch=s[s.index('bool XE::processKext('):s.index(' if(isIntelProvider(index))',s.index('bool XE::processKext('))] assert dispatch.index('if(adlpLoadOnly)') diff --git a/tests/bigsur_pmap.py b/tests/bigsur_pmap.py index 569fd5f9..4902ad50 100644 --- a/tests/bigsur_pmap.py +++ b/tests/bigsur_pmap.py @@ -5,11 +5,14 @@ or GPU coherency. """ from pathlib import Path -import argparse,hashlib,re,struct,subprocess,tempfile +import argparse,re,subprocess,tempfile R=Path(__file__).resolve().parents[1] s=(R/'WhateverXe/kern_xe.cpp').read_text() -a=argparse.ArgumentParser();a.add_argument('--kernel',type=Path);a=a.parse_args() +a=argparse.ArgumentParser();a.add_argument('--kernel',type=Path);a.add_argument('--hde-source',type=Path);a=a.parse_args() bridges='\n'.join(re.search(r'extern "C" __attribute__\(\(naked,used\)\) void tglPmap'+kind+r'Bridge\(\) \{.*?\n\}',s,re.S)[0] for kind in ['New','Update']) +if a.kernel: + from pmap_kernel_contract import check + check(a.kernel,s,"BigSur",a.hde_source) cpp=r'''#include #include #include @@ -80,7 +83,7 @@ capture='\n'.join('"movq %'+r+', _out+'+str(8*i)+'(%rip)\\n"' for i,r in enumerate(regs)) load='\n'.join('"movq _in+'+str(8*i)+'(%rip), %'+r+'\\n"' for i,r in enumerate(regs) if r!='rsp') cpp=cpp.replace('BRIDGES',bridges).replace('CAPTURE',capture).replace('LOAD',load) -with tempfile.TemporaryDirectory(prefix='ventura-pmap-') as temp: +with tempfile.TemporaryDirectory(prefix='bigsur-pmap-') as temp: p=Path(temp);(p/'test.cpp').write_text(cpp) subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O2',str(p/'test.cpp'),'-o',str(p/'test')],check=True) subprocess.run([str(p/'test')],check=True) diff --git a/tests/fixtures/released_physical_admission.json b/tests/fixtures/released_physical_admission.json new file mode 100644 index 00000000..2892d001 --- /dev/null +++ b/tests/fixtures/released_physical_admission.json @@ -0,0 +1,327 @@ +{ + "schema": 1, + "scope": "Physical PCI/backend admission only; not a hardware validation claim.", + "releases": [ + { + "tag": "0.2.2", + "commit": "c3fec934e7ac716d7ae52a70914403d63c802c01", + "physical_ids": [ + { + "devices": [ + "46d0", + "46d1", + "46d2", + "46d3", + "46d4" + ], + "revision_min": 0, + "revision_max": 0 + }, + { + "devices": [ + "9a40", + "9a49", + "9a78" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.2.3", + "commit": "61a449255d385aeb468f64e5d5042e126f387b90", + "physical_ids": [ + { + "devices": [ + "46d0", + "46d1", + "46d2", + "46d3", + "46d4" + ], + "revision_min": 0, + "revision_max": 0 + }, + { + "devices": [ + "9a40", + "9a49", + "9a78" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.3.0", + "commit": "c4881e36f28789a8103eeadcc7c3462e48acb37e", + "physical_ids": [ + { + "devices": [ + "46d0", + "46d1", + "46d2", + "46d3", + "46d4" + ], + "revision_min": 0, + "revision_max": 0 + }, + { + "devices": [ + "9a40", + "9a49", + "9a78" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.4.0", + "commit": "59bbe3cf436272d3a9aa575bdb178afc8fb5cbe0", + "physical_ids": [ + { + "devices": [ + "46d0", + "46d1", + "46d2", + "46d3", + "46d4" + ], + "revision_min": 0, + "revision_max": 0 + }, + { + "devices": [ + "9a40", + "9a49", + "9a78" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.5.0", + "commit": "c6cd8ec5ec5bd64ad3c8a852a30d1f537d819fb1", + "physical_ids": [ + { + "devices": [ + "4680", + "4682", + "4688", + "468a", + "468b", + "4690", + "4692", + "4693", + "46d0", + "46d1", + "46d2", + "46d3", + "46d4", + "9a40", + "9a49", + "9a60", + "9a68", + "9a70", + "9a78", + "a780", + "a781", + "a782", + "a783", + "a788", + "a789", + "a78a", + "a78b" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.6.0", + "commit": "7b6b246dd425ff4847ba31dd66c89740e9c712c0", + "physical_ids": [ + { + "devices": [ + "4680", + "4682", + "4688", + "468a", + "468b", + "4690", + "4692", + "4693", + "46a3", + "46a6", + "46a8", + "46aa", + "46b3", + "46c3", + "46d0", + "46d1", + "46d2", + "46d3", + "46d4", + "9a40", + "9a49", + "9a60", + "9a68", + "9a70", + "9a78", + "a780", + "a781", + "a782", + "a783", + "a788", + "a789", + "a78a", + "a78b", + "a7a0", + "a7a1", + "a7a8", + "a7a9", + "a7aa", + "a7ab", + "a7ac", + "a7ad" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.6.1", + "commit": "68a24273ea9ba668e7bb029ed60fd775c45b16d1", + "physical_ids": [ + { + "devices": [ + "4680", + "4682", + "4688", + "468a", + "468b", + "4690", + "4692", + "4693", + "46a3", + "46a6", + "46a8", + "46aa", + "46b3", + "46c3", + "46d0", + "46d1", + "46d2", + "46d3", + "46d4", + "9a40", + "9a49", + "9a60", + "9a68", + "9a70", + "9a78", + "a780", + "a781", + "a782", + "a783", + "a788", + "a789", + "a78a", + "a78b", + "a7a0", + "a7a1", + "a7a8", + "a7a9", + "a7aa", + "a7ab", + "a7ac", + "a7ad" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + }, + { + "tag": "0.7.0", + "commit": "d0b7fb6275ca51fde088fa7e91595462bd457ec9", + "physical_ids": [ + { + "devices": [ + "4626", + "4628", + "462a", + "4680", + "4682", + "4688", + "468a", + "468b", + "4690", + "4692", + "4693", + "46a0", + "46a1", + "46a2", + "46a3", + "46a6", + "46a8", + "46aa", + "46b0", + "46b1", + "46b2", + "46b3", + "46c0", + "46c1", + "46c2", + "46c3", + "46d0", + "46d1", + "46d2", + "46d3", + "46d4", + "9a40", + "9a49", + "9a59", + "9a60", + "9a68", + "9a70", + "9a78", + "9ac0", + "9ac9", + "9ad9", + "9af8", + "a720", + "a721", + "a780", + "a781", + "a782", + "a783", + "a788", + "a789", + "a78a", + "a78b", + "a7a0", + "a7a1", + "a7a8", + "a7a9", + "a7aa", + "a7ab", + "a7ac", + "a7ad" + ], + "revision_min": 0, + "revision_max": 255 + } + ] + } + ] +} diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index c30a1bed..c486d3f0 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -99,10 +99,14 @@ if(argc>1)assert(!memcmp(image.data()+span.first,span.second.data(),span.second.size())); else memcpy(image.data()+span.first,span.second.data(),span.second.size()); } - for(bool display13:{false,true})for(unsigned failure=0;failure<14;++failure){ + for(bool display13:{false,true})for(unsigned failure=0;failure<16;++failure){ shutdownCalls=0; auto b=image;uintptr_t pure=0x1234;memcpy(b.data()+0xccad0+0x188,&pure,sizeof(pure)); XE x;callbackXE=&x;x.display13FramebufferRequested=display13;KernelPatcher p; + // The provider must be resolved before this one-shot installation. These + // are the real consumer guards behind the host notification-order fixture. + if(failure==14)x.xelpProviderProbe=0; + if(failure==15)x.xelpProviderConfigureProbe=0; surfaceOK=failure!=5;nvramOK=failure!=6;x.runtimeOK=failure!=7;x.agdcOK=failure!=8;x.backendOK=failure!=9;x.trainingOK=failure!=10;x.stopDuringStart=failure==11;x.nativeOK=failure!=12; if(failure>=1&&failure<=4)p.failAt=failure; bool handled=x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); diff --git a/tests/host_publication.py b/tests/host_publication.py index a93466e2..9fdadda0 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -1,10 +1,15 @@ #!/usr/bin/env python3 """Replay actual host admission: no PCI reads before successful publication.""" from pathlib import Path -import subprocess,tempfile +import re,subprocess,tempfile r=Path(__file__).resolve().parents[1] adapter=(r/'WhateverXe/kern_xelp_device.hpp').read_text();adapter=adapter[adapter.index('namespace XELP {'):adapter.rindex('#endif')] s=(r/'WhateverXe/kern_host.cpp').read_text();body=s[s.index('void XeHost::processKernel'):s.index('bool XeHost::wrapPanelSetDisplay')] +header=(r/'WhateverXe/kern_host.hpp').read_text() +host_state=header[header.index(' bool active'):header.index(' void flushDonors')] +xe_source=(r/'WhateverXe/kern_xe.cpp').read_text() +provider_classifier='\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',xe_source,re.S)[0] + for name in ['requiresIntelProvider','isIntelProvider']) code=r''' #include #include @@ -56,6 +61,7 @@ namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; struct {size_t loadIndex=6;}surface;struct {size_t loadIndex=5;}backlight;struct {size_t loadIndex=7;}accelFamily; +struct {size_t loadIndex=3;}kextAppleIntelTGLCompat; using IOSimpleLock=int;int scopeLock=0;uintptr_t fakeThread=1; IOSimpleLock *IOSimpleLockAlloc(){return &scopeLock;} int IOSimpleLockLockDisableInterrupt(IOSimpleLock*){return 0;} @@ -66,10 +72,8 @@ constexpr IOReturn kIOReturnSuccess=0,kIOReturnNotReady=-1; #define FunctionCast(fn,addr) reinterpret_cast(addr) struct XeHost { - static XeHost *callbackHost;bool systemWakeRecovery=false; - bool surfaceSeen=false,graphicsSeen=false,accelSeen=false; - struct PendingDonor {size_t index;uintptr_t address;size_t size;};PendingDonor pendingDonors[2]{};unsigned pendingDonorCount=0; - bool dependenciesSeen() const {return surfaceSeen && (!systemWakeRecovery || (graphicsSeen && accelSeen));} + static XeHost *callbackHost; + HOST_STATE void flushDonors(KernelPatcher&); uintptr_t sleepFBCount=0,sleepFBAt=0;uint32_t repeatedSleep=0,sleepNotifications=0,wakeNotifications=0; IOSimpleLock *sleepScopeLock=nullptr; @@ -80,11 +84,15 @@ static uint32_t pendingSleepDisplays(void*); uintptr_t orgWSAAAttribute=0; static IOReturn wrapWSAAAttribute(IOFramebuffer*,uint32_t,uint32_t); - bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; + bool backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; struct XE { bool defer=false;bool isIntelDonor(size_t i)const{return defer && (i==1 || i==2);} + bool isIntelProvider(size_t index)const; + bool requiresIntelProvider()const; + bool requireProvider=false,providerReady=false,providerOK=true;unsigned rejectedDonors=0; + bool tglRequested=false,display13FramebufferRequested=false,adlpLoadOnly=false; void setHostSurfaceABI(unsigned,unsigned){} void setHostFramebufferEvent(uintptr_t){} void setHostSleepReady(bool){} @@ -92,14 +100,18 @@ std::vector owned;bool ownedDisplay13Framebuffer(IOService *f)const{return std::find(owned.begin(),owned.end(),f)!=owned.end();} IOFramebuffer *degraded=nullptr; bool failedDSIWakeFor(IOFramebuffer *f) const {return f && f==degraded;} void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} - void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} + void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){ + if(index==3){providerReady=providerOK;return;} + if(requireProvider && isIntelDonor(index) && !providerReady)++rejectedDonors; + events.push_back(100+index); + } } xe; void processKernel(KernelPatcher &); void processKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void wrapPanelSetDisplay(){}static void wrapFramebufferInit(){} uintptr_t orgPanelSetDisplay=0,orgFramebufferInit=0;uint8_t *gIOFBVerboseBootPtr=nullptr; }; -'''.replace('// DEVICE_ADAPTER',adapter)+body+r''' +'''.replace('// DEVICE_ADAPTER',adapter).replace('HOST_STATE',host_state)+provider_classifier.replace('XE::','XeHost::XE::')+body+r''' XeHost *XeHost::callbackHost; static IOReturn vendor(IOFramebuffer*,uint32_t,uint32_t){return 42;} int main(){KernelPatcher patcher;IOPCIDevice gpu,mei; @@ -164,8 +176,12 @@ exercised.sleepScopes=nullptr; // Already-loaded notifications may arrive in any host dependency order. // Neither missing contracts nor a late callback may silently skip donor setup. -unsigned order[]={1,2,4,6,7};do { +unsigned order[]={1,2,3,4,6,7};do {for(bool display13:{false,true})for(bool validProvider:{false,true}) { XeHost host;host.active=host.systemWakeRecovery=host.xe.defer=true;events.clear(); + host.systemWakeRecovery=display13; + host.xe.tglRequested=!display13;host.xe.display13FramebufferRequested=display13; + host.xe.providerOK=validProvider; + host.xe.requireProvider=true; std::vector received; for(auto id:order){ if(id==1 || id==2)received.push_back(100+id); @@ -174,7 +190,18 @@ else assert(events==received); } assert(events==received && events.size()==2 && host.pendingDonorCount==0); -}while(std::next_permutation(order,order+5)); + assert(host.xe.rejectedDonors==(validProvider?0U:2U)); + // A failed provider is observed and rejected by consumers. It must not + // leave both donors waiting forever or fabricate valid ABI exports. + assert(host.providerSeen && host.xe.providerReady==validProvider); + } +}while(std::next_permutation(order,order+6)); +for(bool loadOnly:{false,true})for(bool requested:{false,true}) { + XeHost host;host.xe.adlpLoadOnly=loadOnly;host.xe.display13FramebufferRequested=requested; + assert(host.xe.requiresIntelProvider()==(requested && !loadOnly)); + assert(host.xe.isIntelProvider(3)==(requested && !loadOnly)); + assert(!host.xe.isIntelProvider(99)); +} const char *names[]={nullptr,"IMEI","HECI","MEI"}; for(unsigned firmware=0;firmware<2;++firmware)for(unsigned me=0;me<5;++me)for(unsigned test=0;test<6;++test){ mei.name=names[me%4];mei.renames=0;gpu.name="GFX0"; diff --git a/tests/monterey_pmap.py b/tests/monterey_pmap.py index 313b110d..db89586f 100644 --- a/tests/monterey_pmap.py +++ b/tests/monterey_pmap.py @@ -5,31 +5,14 @@ or GPU coherency. Optional --kernel checks all source guards against exact bytes. """ from pathlib import Path -import argparse,hashlib,re,struct,subprocess,tempfile +import argparse,re,subprocess,tempfile R=Path(__file__).resolve().parents[1] s=(R/'WhateverXe/kern_xe.cpp').read_text() -a=argparse.ArgumentParser();a.add_argument('--kernel',type=Path);a=a.parse_args() +a=argparse.ArgumentParser();a.add_argument('--kernel',type=Path);a.add_argument('--hde-source',type=Path);a=a.parse_args() bridges='\n'.join(re.search(r'extern "C" __attribute__\(\(naked,used\)\) void tglMontereyPmap'+kind+r'Bridge\(\) \{.*?\n\}',s,re.S)[0] for kind in ['New','Update']) if a.kernel: - d=a.kernel.read_bytes() - assert hashlib.sha256(d).hexdigest()=='587b17cd2f01663b3492dc11c59dc91787fdcbb1bbe542e7e7684a0d37e0a773' - segments=[];pos=32 - for _ in range(struct.unpack_from(' #include #include diff --git a/tests/released_physical_admission.py b/tests/released_physical_admission.py new file mode 100644 index 00000000..dd88fbb5 --- /dev/null +++ b/tests/released_physical_admission.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Preserve physical selections accepted by each released implementation. + +The frozen corpus comes from the referenced releases' own headers, not the +current DeviceTable. This checks backend selection, not live functionality. +New support may expand the set without rewriting older positive expectations. +""" +import json, subprocess, tempfile +from pathlib import Path + +root=Path(__file__).resolve().parents[1] +corpus=json.loads((root/'tests/fixtures/released_physical_admission.json').read_text()) +assert corpus['schema']==1 and corpus['releases'] +rows=[] +for release in corpus['releases']: + assert len(release['commit'])==40 and release['physical_ids'] + for entry in release['physical_ids']: + for device in entry['devices']: + rows.append('{0x%s,%d,%d,"%s"}' % (device,entry['revision_min'],entry['revision_max'],release['tag'])) +code=r''' +#include "kern_xelp_platform.hpp" +#include +#include +struct Released {unsigned device,first,last;const char *tag;}; +const Released inputs[]={INPUTS}; +int main(){ + for(const auto &input:inputs)for(unsigned rev=input.first;rev<=input.last;++rev){ + const uint32_t physical=(input.device<<16)|0x8086; + auto p=XELP::identify(physical,rev); + if(!p.admitted() || !p.displayAdmitted()){ + fprintf(stderr,"lost released physical selection: %s %04x rev %u\n",input.tag,input.device,rev); + return 1; + } + assert(p.identity==physical && p.effectiveIdentity==physical); + uint32_t property=input.device; + assert(XELP::same(p,XELP::select(physical,rev,true,&property,sizeof(property)))); + } + puts("PASS frozen released physical selections, including explicit unchanged device-id properties"); +} +'''.replace('INPUTS',',\n'.join(rows)) +with tempfile.TemporaryDirectory(prefix='wex-released-selection-') as t: + p=Path(t);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/sonoma_pmap.py b/tests/sonoma_pmap.py index 37a0f367..86a347f6 100644 --- a/tests/sonoma_pmap.py +++ b/tests/sonoma_pmap.py @@ -5,35 +5,14 @@ or GPU coherency. Optional --kernel checks all source guards against exact bytes. """ from pathlib import Path -import argparse,hashlib,re,struct,subprocess,tempfile +import argparse,re,subprocess,tempfile R=Path(__file__).resolve().parents[1] s=(R/'WhateverXe/kern_xe.cpp').read_text() -a=argparse.ArgumentParser();a.add_argument('--kernel',type=Path);a=a.parse_args() +a=argparse.ArgumentParser();a.add_argument('--kernel',type=Path);a.add_argument('--hde-source',type=Path);a=a.parse_args() bridges='\n'.join(re.search(r'extern "C" __attribute__\(\(naked,used\)\) void tglSonomaPmap'+kind+r'Bridge\(\) \{.*?\n\}',s,re.S)[0] for kind in ['New','Update']) if a.kernel: - d=a.kernel.read_bytes() - assert hashlib.sha256(d).hexdigest()=='095754b19dd533699fa9370d68eb8f31a2485060af57ce60644eafa006b455ab' - segments=[];pos=32 - for _ in range(struct.unpack_from('