diff --git a/AGENTS.md b/AGENTS.md index 54c70fea..0e0187f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,11 @@ when their work informed a change. ## Build and test Build with `python3 tools/build.py`; run CPU tests with `python3 tools/test.py`. +The builder checks each generated Compat plist, including both DEBUG and RELEASE +in CI. Existing output can be checked with `python3 tools/check_compat_version.py` +and its build directory. Compat's release version and oldest compatible ABI are independent; +do not raise the compatibility floor during an ordinary version bump. An ABI +break requires coordinated Graphics client updates and final-pkg validation. Lilu.kext and MacKernelSDK are local ignored dependencies. Optional native replay requires the exact Apple drivers accepted by the tests. Do not commit generated payloads, Apple driver files, credentials or local machine state. diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index 8c31560c..0c1d5afa 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp @@ -52,7 +52,7 @@ static uint32_t read32(void *access, unsigned long offset) { // it does not read plugin boot arguments or depend on framebuffer globals. // The provider pins the immutable binding until reboot, like the donor owner. extern "C" uint32_t xelpProbeCapabilities(uint32_t schema,uint32_t configABI) { - return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP : 0; + return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP|XELP::ProbeDisplay13Panel : 0; } extern "C" bool xelpConfigureDisplay(void *controller,const XELP::ProbeConfig *config) { if(!controller || !config || !config->valid())return false; diff --git a/CHANGELOG.md b/CHANGELOG.md index 192fb6b4..e3e3d37d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.6.1 + +Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics/releases) package. + +### Added + +- Add experimental internal display (eDP) and brightness support for Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U GPUs. Not yet tested on hardware. + +### Fixed + +- Fix DEBUG boot freezes and restart loops caused by excessive kernel stack usage. + ## 0.6.0 Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics/releases) package. diff --git a/README.md b/README.md index bdbe5049..48f9838e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ for OS coverage and remaining problems. - Tiger Lake-H45 (GT1): `9A60`, `9A68`, `9A70`. - Other supported Alder Lake-N configurations and Twin Lake. - Other Alder Lake-H/P/U and Raptor Lake-H/P/U configurations with supported fixed HDMI or fixed TC DisplayPort wiring. +- Experimental internal eDP on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U: a single firmware-described Combo A/B panel with PPS0 and PCH PWM brightness. This path has local CPU and donor replay coverage only; use the corresponding WhateverXe Graphics release with this plugin. See [display limits](docs/device-selection.md#display-limits). - Other supported Alder Lake-S configurations, Alder Lake-HX, Raptor Lake-S and Raptor Lake-HX. These devices use implemented support shared with the tested families. Their @@ -62,8 +63,8 @@ limits are listed in [device selection](docs/device-selection.md). ### Further implementation required - On Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U, - internal eDP panels, Combo-PHY DisplayPort and Type-C-managed output need - further implementation. Accepting the GPU ID does not enable these outputs. + external Combo-PHY DisplayPort and Type-C-managed output need + further implementation. eDP panels using other power/backlight methods also need additional support. Accepting the GPU ID does not enable these outputs. - Rocket Lake-S: its display power and clock handling differ from Tiger Lake. It is not supported by this release, and no hardware validation is scheduled. @@ -266,3 +267,5 @@ WhateverXe retains WhateverGreen's copyright notices and BSD-3-Clause license. 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. diff --git a/README_ja.md b/README_ja.md index 81291d24..2aa82e20 100644 --- a/README_ja.md +++ b/README_ja.md @@ -49,10 +49,13 @@ OS別の検証範囲と残る問題は[動作確認済みの機能と既知の CPUモデル名や機種固有の設定は不要ですが、実機での動作確認は残っています。 受付IDの一覧と対応する資源構成は[デバイス選択](docs/device-selection.md)を参照してください。 +実験的に、Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uの内蔵eDPを実装しています。対象はfirmwareで一意に記述されたCombo A/Bの単一パネルとPPS0・PCH PWMによる輝度制御です。CPU試験とdonor再生試験のみで、実機未検証です。対応するWhateverXe Graphicsリリースと組み合わせて使用してください。条件は[表示の制限](docs/device-selection.md#display-limits)を参照してください。 + ### 対応に追加改修が必要 -- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uの内蔵eDPパネル・ - Combo PHY経由のDisplayPort・Type-C管理下の出力には追加改修が必要です。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uの + Combo PHY経由の外部DisplayPort・Type-C管理下の出力には追加改修が必要です。 + その他の電源・輝度制御方式を使うeDPパネルも追加対応が必要です。 GPUのIDを受け付けるだけで、これらの出力が有効になるわけではありません。 - Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 このリリースでは未対応で、実機検証の予定もありません。 @@ -196,3 +199,5 @@ WhateverXeとAppleIntelTGLCompatのkext、およびソースのmanifestは`build WhateverXeはWhateverGreenの著作権表示とBSD-3-Clauseライセンスを継承しています。[LICENSE.txt](LICENSE.txt)を参照してください。LiluとMacKernelSDKは別の依存コンポーネントです。 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)も、ハードウェアの挙動と実装の比較に参照しています。 diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 5d257d01..11c68039 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -131,9 +131,14 @@ struct DisplayReadout { // Read only. Check Display 13 PW_A/B/C before pipe-local registers; do not // access TGL FIA/TC/Dekel addresses or issue selector/power/MMIO writes. -template DisplayReadout readDisplay(IO &io, bool admitted) { - DisplayReadout r; - if (!admitted) return r; +template void readDisplay(IO &io, bool admitted, DisplayReadout &r) { + // Reused storage must not retain pipe bits or registers from an earlier + // snapshot. Reset fields in place: aggregate assignment can itself reserve + // a full DisplayReadout temporary on the unoptimized kernel stack. + r.version=2;r.status=ReadoutStatus::NotAdmitted; + r.count=r.poweredPipes=r.activePipes=r.hdmiBPipes=0; + for(auto ®:r.registers) {reg.offset=0;reg.value=0;} + if (!admitted) return; const auto add = [&](uint32_t offset) { if (r.count >= sizeof(r.registers) / sizeof(r.registers[0]) || !io.readable(offset)) { r.status = ReadoutStatus::Unavailable; @@ -146,7 +151,7 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { }; r.status = ReadoutStatus::Complete; const uint32_t power = add(0x45404); - if (r.status != ReadoutStatus::Complete) return r; + if (r.status != ReadoutStatus::Complete) return; // Always-on / PW1 display state, including BIOS and DDI I/O ownership. const uint32_t common[] {0x45400, 0x45408, 0x4540C, 0x45450, 0x45454, 0x4545C, 0x45420, 0x45424, 0x45444, 0x42000, 0x42084, @@ -188,6 +193,12 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { const auto after = add(0x45404); if (r.status == ReadoutStatus::Complete && after != power) r.status = ReadoutStatus::PowerChanged; +} +// Value-returning callers retain the existing API. Early bootstrap uses the +// in-place overload with one allocated snapshot instead of stacked copies. +template DisplayReadout readDisplay(IO &io, bool admitted) { + DisplayReadout r; + readDisplay(io,admitted,r); return r; } @@ -200,9 +211,9 @@ inline bool value(const DisplayReadout &r, uint32_t offset, uint32_t &out) { // Source-side certificate for an already-running, uncompressed external SST // GOP. This does not prove sink DPCD state, PLL rate or native AUX ownership. -inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const Display13TC::Boot *tc=nullptr) { +inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const Display13TC::Boot *tc=nullptr,bool panel=false) { const bool typeC=ddi>=3 && ddi<=6; - if(pipe>=3 || (ddi>=2 && !typeC) || boot.status!=ReadoutStatus::Complete || + if((panel && ddi>=2) || pipe>=3 || (ddi>=2 && !typeC) || boot.status!=ReadoutStatus::Complete || !(boot.activePipes&boot.poweredPipes&(1U<ownershipOptional?0x80000080U:0x800000C0U))== (typeC && !tc->ownershipOptional?0x80000040U:0x80000000U) && (clock>>28)==ddi+(typeC?3:1) && (!typeC || clock==tc->transClock) && - (tp&~(1U<<18))==0x80000300U && + (tp&~((1U<<18)|(panel?1U<<6:0)))==0x80000300U && !(dss1&0xF0300000U) && !(dss2&0x80008000U) && !(dm&0x81000000U) && (dm&0xFFFFFF) && dn && !(dn&0xFF000000U) && lm && !(lm&0xFF000000U) && ln && !(ln&0xFF000000U) && (dm&0xFFFFFF)<=dn; } -enum class GOPProtocol : uint32_t {TMDS,DP}; +enum class GOPProtocol : uint32_t {TMDS,DP,EDP}; // Source and power must remain unchanged while only the plane is reinitialized. // This is an early, serialized bootstrap transaction, not a modeset or link // adoption certificate. A changed link stops further writes, including rollback. template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi, - uint32_t addedAUXRequest=0,const Display13TC::Boot *tc=nullptr) { + uint32_t addedAUXRequest=0,const Display13TC::Boot *tc=nullptr,bool panel=false) { // addedAUXRequest is the existing Combo boot-lease transition only. // TC lifetime uses Mode's own AUX lease, not the Combo MainLinkAUX slot. - if(!dpSource(r,pipe,ddi,tc) || (addedAUXRequest && (ddi>=2 || addedAUXRequest!=(2U<<(2*ddi)))))return false; + if(!dpSource(r,pipe,ddi,tc,panel) || (addedAUXRequest && (ddi>=2 || addedAUXRequest!=(2U<<(2*ddi)))))return false; if(ddi>=3 && !tc->unchanged(io))return false; const uint32_t bank=pipe*0x1000; // Recheck power before touching pipe-local registers. A captured snapshot @@ -271,9 +282,9 @@ template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigne // inventing a native transaction reference or claiming the firmware request. // Caller holds the display owner lock throughout both proof and acquisition. template Display13Power::Result holdDPBootAUX(IO &io,const DisplayReadout &boot, - Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,unsigned ddi) { + Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,unsigned ddi,bool panel=false) { using namespace Display13Power;Result r; - if(ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi) || !dpSourceUnchanged(io,boot,0,ddi) || + if(ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi,nullptr,panel) || !dpSourceUnchanged(io,boot,0,ddi,0,nullptr,panel) || bridge.bootPort!=ddi || bridge.bootAdopted || !bridge.bootPGProtected || !bridge.bootDDIProtected || !bridge.outputHeld(io,ddi,0) || link.owner || link.bootPending || link.lease.owner)return r; uint32_t aux=0; @@ -281,7 +292,7 @@ template Display13Power::Result holdDPBootAUX(IO &io,const DisplayRead r=acquire(io,bridge.state,link.lease,auxDomain(ddi)); if(r.status!=Status::Complete)return r; link.port=ddi;link.bootPending=true; - if(!dpSourceUnchanged(io,boot,0,ddi,2U<<(2*ddi)) || !held(io,bridge.state,link.lease.domains)) { + if(!dpSourceUnchanged(io,boot,0,ddi,2U<<(2*ddi),nullptr,panel) || !held(io,bridge.state,link.lease.domains)) { // Do not release a newly acquired request under a still-active GOP. bridge.state.faulted=true;r.status=Status::RequestLost; } @@ -291,9 +302,9 @@ template Display13Power::Result holdDPBootAUX(IO &io,const DisplayRead // The caller retains that object on success and then adopts the boot path. // No hardware request or native AUX transaction counter changes at handoff. template Display13Power::Result adoptDPBootAUX(IO &io,const DisplayReadout &boot, - Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,uintptr_t target,unsigned ddi) { + Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,uintptr_t target,unsigned ddi,bool panel=false) { using namespace Display13Power;Result r; - if(!target || ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi) || !dpSourceUnchanged(io,boot,0,ddi) || + if(!target || ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi,nullptr,panel) || !dpSourceUnchanged(io,boot,0,ddi,0,nullptr,panel) || bridge.bootPort!=ddi || bridge.bootAdopted || !bridge.bootPGProtected || !bridge.bootDDIProtected || !bridge.outputHeld(io,ddi,0) || !link.bootPending || link.owner || link.port!=ddi || link.lease.owner!=&bridge.state || link.lease.domains!=dependencies(auxDomain(ddi)) || @@ -360,7 +371,7 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d p={}; if (ddi>=7 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || (r.activePipes & (r.activePipes-1)) || (r.activePipes&~r.poweredPipes) || - (protocol!=GOPProtocol::TMDS && protocol!=GOPProtocol::DP) || + (protocol!=GOPProtocol::TMDS && protocol!=GOPProtocol::DP && protocol!=GOPProtocol::EDP) || (protocol==GOPProtocol::TMDS && ddi==1 && r.activePipes!=r.hdmiBPipes)) return LinearStatus::NotSingleHDMI; while (!(r.activePipes & (1U<>27)&15)!=ddi+1 || (protocol==GOPProtocol::TMDS && ((trans>>24)&7)>1)) return LinearStatus::NotSingleHDMI; - if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi,tc))return LinearStatus::InvalidDPSource; + if(protocol!=GOPProtocol::TMDS && !dpSource(r,p.pipe,ddi,tc,protocol==GOPProtocol::EDP))return LinearStatus::InvalidDPSource; p.geometry=XELPGeometry::linear(p.control,p.stride,p.size,p.position,p.offset); if(!p.geometry.valid || (p.surface&4095) || p.surface!=surfLive || ((trans>>20)&7)!=0 || (pipeConf&0xC0000000)!=0xC0000000 || @@ -415,7 +426,7 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout bool linkChanged=false; const auto linkStable=[&] { if(!bootGeometryUnchanged(io,r,p.pipe))linkChanged=true; - if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi,0,tc))linkChanged=true; + if(protocol!=GOPProtocol::TMDS && !dpSourceUnchanged(io,r,p.pipe,ddi,0,tc,protocol==GOPProtocol::EDP))linkChanged=true; if(ddi>=3 && (!tc || !tc->unchanged(io)))linkChanged=true; return !linkChanged; }; diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index d048343b..d12027a1 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -23,7 +23,7 @@ template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout & const auto stable=[&] { return AlderLakeN::bootGeometryUnchanged(io,display,plan.pipe) && (ddi<3 || (tc && tc->unchanged(io))) && - (protocol!=AlderLakeN::GOPProtocol::DP || AlderLakeN::dpSourceUnchanged(io,display,plan.pipe,ddi,0,tc)); + (protocol==AlderLakeN::GOPProtocol::TMDS || AlderLakeN::dpSourceUnchanged(io,display,plan.pipe,ddi,0,tc,protocol==AlderLakeN::GOPProtocol::EDP)); }; if(!stable())return false; for(unsigned i=0;i UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::Displ r.status=Status::Complete;return r; } +// Caller-owned storage keeps snapshots and per-port witnesses off the nested +// kernel bootstrap stack. The input remains live until the last peer check. +struct NormalizeScratch { + AlderLakeN::DisplayReadout after; + Display13TC::Boot tc[3]; +}; template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, - const Display13HDMIWiring &w,const XELPMemory::Layout &memory) { + const Display13HDMIWiring &w,const XELPMemory::Layout &memory, + NormalizeScratch &scratch) { Result r;r.before=boot.activePipes; - if(!admitted)return r; + if(!admitted || &boot==&scratch.after)return r; r.status=Status::Topology; const auto active=boot.activePipes; - if(!memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete || + if(w.panel || !memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete || !(active&1) || active==1 || (active&~7U) || (active&~boot.poweredPipes))return r; uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0; - Display13TC::Boot tc[3]; + auto &tc=scratch.tc; + for(auto &port:tc)port={}; uint32_t power=0,ddiPower=0,auxPower=0,gate=0; if(!AlderLakeN::value(boot,0x45404,power) || !AlderLakeN::value(boot,0x45454,ddiPower) || !AlderLakeN::value(boot,0x45444,auxPower) || @@ -306,7 +314,7 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo parked|=1U< Readout inspect(IO &io,const void *controller,size_t controll const void *framebuffer,size_t framebufferBytes, 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) { + const AlderLakeN::DisplayReadout *dp=nullptr,bool panel=false) { Readout r; if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) { r.status=Status::Association;return r; @@ -340,7 +340,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll r.status=Status::DisplayState;return r; } if(bootScanout && dp && (dp->activePipes!=1 || - !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))) { + !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc,panel))) { r.status=Status::DisplayState;return r; } r.pool=pool(controller);const auto &p=r.pool; @@ -401,7 +401,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll io.read(0x7019C)!=r.surface || io.read(0x701AC)!=r.surface || 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))))) { + (dp && !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc,panel))))) { r.status=Status::StateChanged;return r; } r.status=Status::Complete;return r; diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 19157990..0ca75370 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -23,7 +23,7 @@ inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false) { // A TC route uses the same validated handoff regardless of the GT family. // Keep the established Combo-only ADL-N startup ordering unchanged. inline bool deferredBootPrepare(const Display13OutputWiring &w,bool mobile) { - return mobile || (w.selected()&0x78); + return w.panel || mobile || (w.selected()&0x78); } inline bool multiOutputWiring(const Display13OutputWiring &w) { return selectedWiring(w) && (w.selected()&(w.selected()-1)); @@ -47,7 +47,7 @@ inline uint32_t bootDDI(const Display13OutputWiring &w,uint32_t trans,bool dpSou const auto mode=(trans>>24)&7; // Selection is not an adoption certificate. The DP caller must also prove // source state, physical TC ownership/power, memory and native handoff. - if(dpSource && mode==2 && (port<2 || port>=3) && (w.dp&(1U<=3) && (w.links()&(1U<1)return UINT32_MAX; return (w.hdmi&(1U< Combo pins1/2, DDC3..6 => TC pins9..12. @@ -21,9 +22,11 @@ namespace XELP { namespace VBT { } // Display13 VBT AUX F..I maps directly to TC1..4. Capabilities do not // select live DP-alt: only the provider's legacy HPD/mode proof admits it. - inline Display13OutputWiring display13Fixed(const Ports &ports,uint32_t backend) { + inline Display13OutputWiring display13Fixed(const Ports &ports,uint32_t backend,Family family=Family::AlderLakeN) { auto w=display13HDMI(ports,backend); if(!w.valid || !ports.valid())return w; + if(PanelPower::nativePPS0(ports,family) && (backend&(1U<=Ports)return false; if(hdmi&(1U<(platform);if(p[0x12]!=Ports)return false; - for(unsigned d=0;d(p+0x20+24*d); const uint32_t panel[]={0,0,0,0,2,0x18}; if(!d && !memcmp(c,panel,24))continue; @@ -63,7 +68,11 @@ struct Display13OutputWiring { const bool panel=!d && !c[2]; c[1]=ddc[d];c[2]=2;c[4]=0x800;c[5]=panel?0x202:(c[5]&~9U)|2U; } - return TGLComboDP::apply(dp&3,platform,bytes); + if(!TGLComboDP::apply(dp&3,platform,bytes))return false; + for(unsigned d=0;d<2;++d)if(panel&(1U< bool capture(IO &io,const XELP::VBT::Ports::Panel &panel) { + if(captured)return pwm.valid(); + const auto on=io.read(0xC7208),off=io.read(0xC720C),control=io.read(0xC7204); + const auto period=io.read(0xC8254),polarity=io.read(0xC8250); + const auto selected=XELP::PanelPWM::select(period,polarity,period?0:io.read(0xC2014),panel.pwmHz,panel.activeLowPWM); + if(on==~0U || off==~0U || control==~0U || !selected.valid())return false; + bios[0]=on;bios[1]=off;bios[2]=control;pwm=selected;captured=true;return true; + } +}; +// WA 22019252566 (Display13/14): block PCH DPLS clock gating over panel on. +// Keep a pre-existing firmware/peer request and unrelated bits. Call finish +// after every native exit, including timeout. The caller owns PG1 and serializes PPS. +struct OnGate { + static constexpr uint32_t Reg=0xC2020,Bit=1U<<29; + bool changed {}; + template bool begin(IO &io) { + const auto before=io.read(Reg);if(before==~0U)return false; + if(before&Bit)return true; + changed=true;io.write(Reg,before|Bit);return io.read(Reg)==(before|Bit); + } + template bool finish(IO &io) { + if(!changed)return true; + const auto value=io.read(Reg);if(value==~0U)return false; + io.write(Reg,value&~Bit);const bool ok=io.read(Reg)==(value&~Bit); + if(ok)changed=false;return ok; + } +}; +} +#endif diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index a0b5de2c..db79068d 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -35,7 +35,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { // hooks even when the selected profile deliberately has no Render backend. active=profile.displayAdmitted(); const auto display=XELP::displayAdapter(profile.family); - backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS) && + backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS || + display==XELP::DisplayAdapter::ADLP || display==XELP::DisplayAdapter::ADLN) && info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple; backlightGPU=backlightEnabled ? gpu : nullptr; if(published) { diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 60fb0493..daf40539 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -44,6 +44,7 @@ #include "kern_tgl_combo_signal.hpp" #include "kern_tgl_panel_depth.hpp" #include "kern_xelp_panel_power.hpp" +#include "kern_display13_panel.hpp" #include "kern_tgl_pcode_gate.hpp" #include "kern_tgl_pcode_edram.hpp" #include "kern_tgl_tc_cold.hpp" @@ -3848,7 +3849,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { if(!xeProfile.displayAdmitted() || xeProfile.display!=XELP::Display::XeLPD)return {}; // Display13 physical capacity is shared by ADL-N and mobile ADL/RPL. // VBT chooses board routing; a family name must not exclude TC PHYs. - if(!property)return XELP::VBT::display13Fixed(firmwarePorts,Display13OutputWiring::Physical); + if(!property)return XELP::VBT::display13Fixed(firmwarePorts,Display13OutputWiring::Physical,xeProfile.family); // Preserve the older ADL-N Combo property contract. It cannot select TC. if(adlpDisplaySelected)return {}; auto wiring=OSDynamicCast(OSData,property); @@ -3890,6 +3891,19 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { auto map = pci->mapDeviceMemoryWithRegister(0x10, (linearTest || gtTest ? 0 : kIOMapReadOnly) | kIOMapInhibitCache); if (!map) return; + // All snapshots below are consumed synchronously; setProperty copies the + // bytes. Reuse one allocation rather than reserve every branch's snapshot + // (including return temporaries) in the DEBUG bootstrap stack frame. + struct BootstrapScratch { + AlderLakeN::DisplayReadout display; + Display13Boot::NormalizeScratch normalize; + }; + auto snapshot=static_cast(IOMalloc(sizeof(BootstrapScratch))); + if(!snapshot) { + pci->setProperty("WEXDisplay13BootstrapAllocationFailed",true); + map->release();return; + } + auto &result=snapshot->display; struct ReadIO { volatile uint8_t *base; uint64_t length; @@ -3918,7 +3932,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { const bool reservationReady=XELPMemory::reservation(xelpMemory,reserved); uint64_t reservation[] {1,reservationReady,reserved,xelpMemory.usable()}; pci->setProperty("N97StolenReservation",reservation,sizeof(reservation)); - auto result = AlderLakeN::readDisplay(io, barReady); + AlderLakeN::readDisplay(io,barReady,result); pci->setProperty("N97Display13Boot", &result, sizeof(result)); // Bounded pre-native evidence for selected fixed TC ports, including ports // which firmware owns without routing a transcoder to them. @@ -3936,9 +3950,9 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { bool bootstrapReady=true; if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { - auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory); + auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory,snapshot->normalize); pci->setProperty("N97DualGOPNormalize",&parked,sizeof(parked)); - result=AlderLakeN::readDisplay(io,barReady); + AlderLakeN::readDisplay(io,barReady,result); pci->setProperty("N97DualGOPAfter",&result,sizeof(result)); bootstrapReady=parked.status==Display13Boot::Status::Complete; } @@ -3964,8 +3978,8 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI, AlderLakeN::GOPProtocol::TMDS,bootDDI>=3 ? &tc : nullptr); pci->setProperty("N97Display13Linear",&linear,sizeof(linear)); - auto after=AlderLakeN::readDisplay(io,barReady); - pci->setProperty("N97Display13After",&after,sizeof(after)); + AlderLakeN::readDisplay(io,barReady,result); + pci->setProperty("N97Display13After",&result,sizeof(result)); SYSLOG("igfx", "AlderLakeN Display13 linear status=%u pipe=%u writes=%u frames=%u..%u restored=%u", static_cast(linear.status),linear.pipe,linear.writes,linear.framesBefore,linear.framesAfter,linear.restored); if (checkKernelArgument("-wex-adln-scanout")) { @@ -4005,7 +4019,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if (!predecessor) break; bzero(addresses,addressBytes); bzero(predecessor,addressBytes); - auto lease=AlderLakeN::leaseGOPGGTT(io,after,predecessor,barReady && xelpMemoryMatches(pci),xelpMemory); + auto lease=AlderLakeN::leaseGOPGGTT(io,result,predecessor,barReady && xelpMemoryMatches(pci),xelpMemory); pci->setProperty("N97GGTTLease",&lease,sizeof(lease)); if (lease.status!=1) {scan.status=uint64_t(AlderLakeN::ScanoutStatus::BusyGGTT);break;} for (unsigned b=0;b<2;++b) { @@ -4039,8 +4053,8 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { } if (scan.buffers!=2) break; allocation[0]=8; - auto state=AlderLakeN::readDisplay(io,barReady); - scan=AlderLakeN::testScanout(io,state,addresses,true,predecessor); + AlderLakeN::readDisplay(io,barReady,result); + scan=AlderLakeN::testScanout(io,result,addresses,true,predecessor); scan.buffers=2; } while(false); scan.pciCommand=command;scan.descriptorOptions=descriptorOptions; @@ -4078,7 +4092,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { gt.resources.eus,gt.resources.l3Banks); } if(displayRequested) { - auto display=AlderLakeN::readDisplay(io,barReady); + AlderLakeN::readDisplay(io,barReady,result); auto optionProperty=pci->getProperty("IOMemoryDescriptorOptions"); auto opts=OSDynamicCast(OSNumber,optionProperty); const uint64_t mapperOptions=opts ? opts->unsigned64BitValue() : 0; @@ -4087,15 +4101,16 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { ((!optionProperty || opts) && !mapperOptions && !systemMapper); uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); - const bool dp=bootDDI<7 && (board.dp&(1U<=3 && xelpMemoryMatches(pci),bootDDI,0,dp); if(bootDDI>=3 && bootDDI<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); AlderLakeN::LinearPlan bootPlan; uint32_t sourceProof[]={1,bootDDI,uint32_t(protocol),uint32_t(AlderLakeN::linearPlan( - display,bootPlan,bootDDI,protocol,bootDDI>=3?&tc:nullptr)),tc.valid()}; + result,bootPlan,bootDDI,protocol,bootDDI>=3?&tc:nullptr)),tc.valid()}; pci->setProperty("N97NativeBootSource",sourceProof,sizeof(sourceProof)); - const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory, + const bool prefix=Display13Boot::reservedPrefix(io,result,bootstrapReady,bootDDI,xelpMemory, bootDDI>=3 ? &tc : nullptr,protocol); auto aperture=pci->getDeviceMemoryWithRegister(0x18); const bool memoryReady= @@ -4130,8 +4145,8 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if (!aperture || aperture->getLength()!=0x10000000) break; predecessor=static_cast(IOMalloc(bytes)); if (!predecessor) break; - auto state=AlderLakeN::readDisplay(io,true); - auto lease=AlderLakeN::leaseGOPGGTT(io,state,predecessor,true,xelpMemory); + AlderLakeN::readDisplay(io,true,result); + auto lease=AlderLakeN::leaseGOPGGTT(io,result,predecessor,true,xelpMemory); pci->setProperty("N97GPULease",&lease,sizeof(lease)); if (lease.status!=1) {gpu.status=uint64_t(AlderLakeN::GPUStatus::BusyGGTT);break;} buffer=IOBufferMemoryDescriptor::withOptions(kIODirectionInOut|kIOMemoryMapperNone,AlderLakeN::GPUBytes,4096); @@ -4158,6 +4173,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { gpu.status,gpu.stage,gpu.engineWrites,gpu.head,gpu.tail,gpu.result,gpu.token,gpu.retained); } + IOFree(snapshot,sizeof(*snapshot)); map->release(); } @@ -4177,7 +4193,8 @@ bool XE::xelpBindProbe(void *controller) { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || !Display13FBTopology::supportedWiring(adlnOutputWiring,xeProfile.display==XELP::Display::XeLPD) || ((adlnOutputWiring.selected()&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || - (adlnOutputWiring.dp && (!display13TCDPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; + (adlnOutputWiring.dp && (!display13TCDPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)) || + (adlnOutputWiring.panel && (!display13PanelProbeReady || !display13PanelPowerReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3; config.legacy=adlnOutputWiring.selected()&0x78;config.legacyBackend=config.legacy?0x78:0; } @@ -4253,8 +4270,12 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { }; for(auto ®:registers)reg[1]=io.read(reg[0]); physicalGPU->setProperty("ADLPDisplayBoot",registers,sizeof(registers)); - auto display=AlderLakeN::readDisplay(io,true); - physicalGPU->setProperty("ADLPScanoutBoot",&display,sizeof(display)); + auto display=static_cast(IOMalloc(sizeof(AlderLakeN::DisplayReadout))); + if(display) { + AlderLakeN::readDisplay(io,true,*display); + physicalGPU->setProperty("ADLPScanoutBoot",display,sizeof(*display)); + IOFree(display,sizeof(*display)); + } else physicalGPU->setProperty("ADLPReadoutAllocationFailed",true); SYSLOG("igfx","ADL-P readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x; donor blocked", static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], gt.releasedMask,gt.releaseFailedMask); @@ -4391,6 +4412,8 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add patcher.clearError(); display13DPProbeReady=query>=address && query-address(query)); + display13PanelProbeReady=query>=address && query-address(query)); display13TCProbeReady=query>=address && query-address(query)); display13TCDPProbeReady=query>=address && query-addressloadIndex == index) + return processTGLAccelerator(patcher, index, address, size); + return false; } static bool adlnWriterMappingBound(); @@ -4469,6 +4496,10 @@ struct Display13FBPowerContext { OSObject *tcObject[4] {}; uint32_t tcBusy[4] {}; bool tcSignalReady[4] {}; + IOLock *panelLock {}; + Display13Panel::State panel; + bool panelFaulted {}; + bool bootPanel {}; bool bootDP {}; // Board protocol captured with the protected boot reference. Display13TC::DPRetainedLink tcDPLink[4]; Display13FBPipeContext pipe[3]; @@ -4481,6 +4512,9 @@ struct Display13FBPowerContext { bool engineEnabled {}; volatile uintptr_t arbitrationWriterBase {}; // Qualified in sleepable engine context. IOLock *lock {}; + // Borrow only while lock is held, and finish consuming the snapshot + // before releasing it or calling a native operation that can reenter. + AlderLakeN::DisplayReadout displayScratch; void *power {}, *controller {}, *access {}; IOMemoryMap *map {}; // Owned mapping remains valid even on native start failure. uint32_t sequence {}, errors {}, writes {}, firstFaultLine {}; @@ -4784,6 +4818,16 @@ struct Display13FBPowerIO { } void delayUs(unsigned us) { IODelay(us); } }; +// Panel adapter may touch only PPS readback, PWM and the documented on WA. +// PPS control itself remains donor-owned; do not broaden the common power IO. +struct Display13FBPanelIO : Display13FBPowerIO { + explicit Display13FBPanelIO(IOMemoryMap *m):Display13FBPowerIO{m} {} + void write(uint32_t r,uint32_t v) { + if(r==0xC2020 || r==0xC8250 || r==0xC8258) + *reinterpret_cast(map->getVirtualAddress()+r)=v; + else Display13FBPowerIO::write(r,v); + } +}; // The static boot reader selects powered banks and fixed Display13 offsets. // Broader read access does not expand the power-only write allowlist above. struct Display13FBBootIO : Display13FBPowerIO { @@ -5069,10 +5113,11 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo } io {c.map}; const auto tc=Display13TC::Boot::capture(io,bootScanout && c.bridge.bootPort>=3,c.bridge.bootPort,0); const bool dp=bootScanout && c.bootDP; - const auto source=AlderLakeN::readDisplay(io,dp); + auto &source=c.displayScratch;AlderLakeN::readDisplay(io,dp,source); 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.bridge.bootPort>=3 ? &tc : nullptr,dp?&source:nullptr, + c.bootPanel); } } @@ -5278,7 +5323,9 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBLifecycleHooksReady=processDisplay13FramebufferLifecycle(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBLifecycleHooksReady",display13FBLifecycleHooksReady); display13FBOutputHooksReady=processDisplay13FramebufferOutput(patcher,index,address,size); + if(adlnOutputWiring.panel)processDisplay13Panel(patcher,index,address,size); display13FBTCHooksReady=processDisplay13FramebufferTC(patcher,index,address,size); + if(!(adlnOutputWiring.selected()&0x78) && adlnOutputWiring.panel)processDisplay13Training(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCHooksReady",display13FBTCHooksReady); IORegistryEntry::getRegistryRoot()->setProperty("N97FBOutputHooksReady",display13FBOutputHooksReady); display13FBGTTHooksReady=processDisplay13FramebufferGTT(patcher,index,address,size); @@ -5289,7 +5336,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBPowerBackendReady=C{C::Power13,installed}.ready(); display13FBClockReady=C{C::Clock13,installed}.ready(); display13FBMemoryReady=C{C::MemoryGroup13,installed}.ready(); - const bool backend=C::display13(installed).ready(); + const bool backend=C::display13(installed,adlnOutputWiring.dp!=0,adlnOutputWiring.panel!=0).ready(); IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferBackendReady",backend); return true; // Installed routes; actual startup/scanout still require runtime checks. } @@ -6398,6 +6445,8 @@ bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { uint32_t dcPolicy[] {Display13FBMemory::field(controller,0xB48),0}; memcpy(reinterpret_cast(controller)+0xB48,&dcPolicy[1],sizeof(uint32_t)); s->adlnTopologyController=controller; + s->tglObservationController=controller; + display13FBPower.panel={};display13FBPower.panelFaulted=false;resetPanelDepth(); controller->setProperty("N97FBConnectorPolicy",copy+0x20,24*Display13FBTopology::Ports); controller->setProperty("N97FBDMCPolicy",dcPolicy,sizeof(dcPolicy)); return true; @@ -6508,7 +6557,7 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { Display13FBTopology::bootDDI(self->adlnOutputWiring,bootIO.read(0x60400),true)==ddi && (bootIO.read(0x70008)&0xc0000000)==0xc0000000 && boot.unchanged(bootIO); if(inherited && dpBoot) { - const auto source=AlderLakeN::readDisplay(bootIO,true); + auto &source=c.displayScratch;AlderLakeN::readDisplay(bootIO,true,source); inherited=source.activePipes==1 && AlderLakeN::dpSourceUnchanged(bootIO,source,0,ddi,0,&boot); } const auto preparation=framebufferLinkRequirements(port,4).preparation(inherited); @@ -6577,6 +6626,7 @@ void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; if(config && config[0]<3) { + if((self->adlnOutputWiring.links()&(1U<display13ClockOrderReady && !display13StopTranscoder(raw))return; bool keep=false; if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,false,keep)) { adlnFault(c.clockFaulted,__LINE__);return; @@ -7036,9 +7086,10 @@ bool XE::processFramebufferTraining(KernelPatcher &patcher,size_t index,mach_vm_ } bool XE::processDisplay13Training(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - if(!adlnOutputWiring.dp)return true; + if(!adlnOutputWiring.links())return true; using namespace FramebufferTrainingNative; - if(!framebufferTrainingHooksReady || !display13FBDPHooksReady || !display13FBSSTHooksReady || + if(!framebufferTrainingHooksReady || (adlnOutputWiring.dp && !display13FBDPHooksReady) || + (adlnOutputWiring.panel && (!display13PanelPowerReady || !tglSinkFramingReady)) || !display13FBSSTHooksReady || !display13FBPowerReferencesReady || !orgDisplay13GMBusWrite)return false; const auto &e=entries[PortTrain]; const auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); @@ -7046,7 +7097,8 @@ bool XE::processDisplay13Training(KernelPatcher &patcher,size_t index,mach_vm_ad KernelPatcher::RouteRequest route(e.symbol,wrapDisplay13PortTraining,orgDisplay13PortTraining); const bool routed=patcher.routeMultipleShort(index,&route,1,address,size);patcher.clearError(); if(!routed)return false; - trainingLeaseDP13Mask=adlnOutputWiring.dp; + trainingLeaseDP13Mask=adlnOutputWiring.links(); + display13DPImage=address; trainingLeaseValidate=display13LeaseValidate;trainingLeasePower=display13LeasePower;trainingLeaseStop=display13LeaseStop; tglDisablePort=reinterpret_cast(wrapDisplay13DisablePort); tglDisableClocks=reinterpret_cast(wrapDisplay13TCDisableClocks); @@ -7079,7 +7131,10 @@ bool XE::display13LeaseStop(void *controller,void *port) { IOLockLock(c.lock);auto p=display13SSTPort(port);bool ok=false; if(p) { const auto ddi=Display13FBMemory::field(p,0x120);Display13TC::Port phy; - if(Display13TC::Port::resolve(ddi,phy)) { + if(ddi<2) { + Display13FBPowerIO io{c.map};const auto mux=io.read(0x164280); + ok=mux!=UINT32_MAX && (mux&(1U<<(10+ddi))) && !c.bridge.nativeDDIRefs(ddi) && !c.mainLink[ddi].owner; + } else if(Display13TC::Port::resolve(ddi,phy)) { Display13FBTCIO io{static_cast(port),ddi-3}; if(io.powerHeld()) { const auto pll=io.read(phy.pll); @@ -7136,7 +7191,7 @@ bool XE::display13DeferClock(void *access,uint64_t reg,uint32_t value,uintptr_t const auto pipe=unsigned((reg-0x46140)/4);void *port=nullptr; // The native caller owns the path/FB mutex. Look up the real pool entry, // including partial modesets not yet published to the scanout allocator. - for(unsigned ddi=3;ddi<7;++ddi)if(s->adlnOutputWiring.dp&(1U<adlnOutputWiring.links()&(1U<(s->tglPortByDDI)(ddi); auto path=candidate?reinterpret_cast(s->orgTGLPathLookup)(candidate):nullptr; if(path && Display13FBMemory::field(path,0x3640)==pipe && @@ -7168,7 +7223,7 @@ bool XE::display13StopTranscoder(void *port) { auto &deferred=display13DeferredClocks[pipe]; ok=buffer!=UINT32_MAX && conf!=UINT32_MAX && func!=UINT32_MAX && tp!=UINT32_MAX && clock!=UINT32_MAX && !(buffer&0x80000000U) && !(conf&0xc0000000U) && !((func|tp)&0x80000000U) && - (!(clock&0xf0000000U) || (clock>>28)==ddi+3) && (!deferred.pending || deferred.port==port); + (!(clock&0xf0000000U) || (clock>>28)==ddi+(ddi>=3?3:1)) && (!deferred.pending || deferred.port==port); if(ok) { if(!(buffer&0x80U))IODelay(8); // DDI disable: idle or >=8us before clock removal. const auto value=deferred.pending?deferred.value:(clock&0x0fffffffU); @@ -7221,10 +7276,10 @@ uint8_t *XE::display13SSTPort(void *port) { if(!s->display13FramebufferRequested || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBSSTHooksReady || !object || object->getMetaClass()->getClassSize()<0x624 || !c.controller || c.controller!=s->adlnTopologyController || !adlnWriterMappingBound() || - c.clockFaulted || c.memoryFaulted || c.clockStopped || !s->adlnOutputWiring.described())return nullptr; + c.clockFaulted || c.memoryFaulted || c.panelFaulted || c.clockStopped || !s->adlnOutputWiring.described())return nullptr; auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || - !(s->adlnOutputWiring.dp&(1U<adlnOutputWiring.links()&(1U<(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=(config[0]>=3?4U:1U) || Display13FBMemory::field(p,0x578)!=c.access)return nullptr; return p; @@ -7266,7 +7321,7 @@ bool XE::display13MainLinkAUX(void *port,bool acquire) { uint32_t XE::wrapDisplay13EnablePort(void *port) { auto s=callbackXE; - if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + if(!s->adlnOutputWiring.links() || display13HDMIPort(port)) return FunctionCast(wrapDisplay13EnablePort,s->orgDisplay13EnablePort)(port); if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; const auto result=FunctionCast(wrapDisplay13EnablePort,s->orgDisplay13EnablePort)(port); @@ -7276,7 +7331,7 @@ uint32_t XE::wrapDisplay13EnablePort(void *port) { uint32_t XE::wrapDisplay13DisablePort(void *port) { auto s=callbackXE; - if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + if(!s->adlnOutputWiring.links() || display13HDMIPort(port)) return FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; const auto result=FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); @@ -7293,14 +7348,14 @@ uint32_t XE::wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphas Display13FBMemory::field(port,0x120)>=3) return wrapDisplay13TCSignal(port,swing,emphasis,overrideConfig); // No protocol replacement for Combo HDMI; preserve the donor's behavior. - if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + if(!s->adlnOutputWiring.links() || display13HDMIPort(port)) return FunctionCast(wrapDisplay13SignalLevels,s->orgDisplay13SignalLevels)(port,swing,emphasis,overrideConfig); if(!c.lock)return kIOReturnNotReady; IOLockLock(c.lock);auto p=display13SSTPort(port);uint32_t result=kIOReturnNotReady; if(p && !overrideConfig) { const auto ddi=Display13FBMemory::field(p,0x120);TGLComboSignal selected; Display13FBPowerIO io{c.map}; - if(selected.selectADLP(ddi,Display13FBMemory::field(p,0x124),Display13FBMemory::field(p,0x128),swing,emphasis) && + if(selected.selectADLP(ddi,Display13FBMemory::field(p,0x124),Display13FBMemory::field(p,0x128),swing,emphasis,(s->adlnOutputWiring.panel&(1U<firmwarePorts.panel.lowSwing) && Display13FBMemory::field(p,0x620)==selected.base+0x804 && Display13FBMemory::field(p,0x5C8)==selected.base+0x28 && Display13FBMemory::field(c.access,0x60)>=selected.base+0xBA0 && c.bridge.ddiHeld(io,ddi)) { @@ -7314,7 +7369,7 @@ uint32_t XE::wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphas uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { auto s=callbackXE;auto &c=display13FBPower; - if(!s->adlnOutputWiring.dp)return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + if(!s->adlnOutputWiring.links())return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); auto object=static_cast(path); if(!c.lock || !object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; if(display13HDMIPort(Display13FBMemory::field(path,0x3648))) @@ -7347,7 +7402,7 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { (!r.reason || (r.reason==2 && r.clockBefore==r.clockWanted && r.funcBefore==r.funcWanted)); } } else { - const auto r=XELP::SSTProgram::framing(read,write,binding.pipe,p[0x11A],false,false,0,true); + const auto r=XELP::SSTProgram::framing(read,write,binding.pipe,p[0x11A],(s->adlnOutputWiring.panel&(1U<tglSinkFramingReady,p[0x118],true); ok=r.ready && r.after!=UINT32_MAX && !(r.after&0x08000000U) && (!r.active || r.after==r.wanted); } } @@ -7855,6 +7910,7 @@ uint32_t XE::wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,b uint64_t r[8] {1,block,force,0,0,config ? config[0] : 9}; auto root=IORegistryEntry::getRegistryRoot();if(selected)root->setProperty("N97FBPortDDCEntry",r,sizeof(r)); const auto result=FunctionCast(wrapAlderLakeNPortDDC,callbackXE->orgAlderLakeNPortDDC)(port,block,data,force);r[3]=result; + cachePanelDepth(port,block,data,result); if(selected) { if(!result && data && (block==1 || block==2)) { uint32_t sum=0;for(unsigned i=0;i<128;++i)sum+=data[i];r[4]=sum&255; @@ -7883,10 +7939,11 @@ bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { uint32_t trans=UINT32_MAX;AlderLakeN::value(boot,0x60400,trans); const auto ddi=Display13FBTopology::bootstrapDDI(adlnOutputWiring, boot.status==AlderLakeN::ReadoutStatus::Complete,boot.poweredPipes,boot.activePipes,trans,true); - const bool dp=ddi<7 && (adlnOutputWiring.dp&(1U<=3,ddi,0,dp); if(ddi>=3 && ddi<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); auto result=Display13Boot::prepareLinear(io,boot,mapped,ddi,xelpMemory,ddi>=3?&tc:nullptr, + ddi<2 && (adlnOutputWiring.panel&(1U<release(); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13BootHandoff",&result,sizeof(result)); @@ -7905,7 +7962,7 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { if((board.selected()&0x78) && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBTCUnavailable",true);return false; } - const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); + const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0,s->adlnOutputWiring.panel!=0); const bool ready=s->display13FramebufferRequested && s->display13BootReady && s->tglActive && contract.ready() && pci && xelpDisplay13Device(pci); uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; @@ -8072,17 +8129,17 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) if(valid) { if(!c.bridge.bootAdopted) { const auto ddi=c.bridge.bootPort; - const bool dp=ddi<7 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.links()&(1U<=3) { const auto source=Display13TC::Boot::capture(io,true,ddi,0); Display13FBTCIO tc{port,ddi-3}; linked=tc.owned(ddi) && tc.linkHeld() && boot.activePipes==1 && AlderLakeN::dpSourceUnchanged(io,boot,0,ddi,0,&source); } else if(ddi<2) { - result=AlderLakeN::adoptDPBootAUX(io,boot,c.bridge,c.mainLink[ddi],reinterpret_cast(port),ddi); + result=AlderLakeN::adoptDPBootAUX(io,boot,c.bridge,c.mainLink[ddi],reinterpret_cast(port),ddi,(callbackXE->adlnOutputWiring.panel&(1U<retain(); // Released only after real buffer stop/idle. } @@ -9907,7 +9964,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { Display13FBPowerIO io {map}; const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); - const bool dpBoot=bootPort<7 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.links()&(1U<=3,bootPort,0,true); - boot=AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort,0,bootPort>=3?&tc:nullptr); + boot=AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort,0,bootPort>=3?&tc:nullptr,(callbackXE->adlnOutputWiring.panel&(1U<adlnOutputWiring.selected()); - if(result.status==Display13Power::Status::Complete)c.bootDP=dpBoot; + if(result.status==Display13Power::Status::Complete){c.bootDP=dpBoot;c.bootPanel=(callbackXE->adlnOutputWiring.panel&(1U<adlnOutputWiring.panel&(1U<adlnPlatform,config,self->adlnOutputWiring) || mode!=(ddi>=3?4U:1U))return req; req={self->xeProfile.display,ddi>=3?R::PHY::TC:R::PHY::Combo, - (self->adlnOutputWiring.dp&(1U<adlnOutputWiring.links()&(1U<(reinterpret_cast(self->tglPlatformCopy)+0x20+24*ddi); @@ -11746,34 +11803,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLComboPLLFractionReady", known); patcher.clearError(); } - { - // getDPCDInfo stores MAX_LANE_COUNT bit7 at port+0x10, - // but setupPort defaults enhanced framing to !MST. - static const uint8_t capability[] { - 0x41,0xc6,0x47,0x10,0x00,0x41,0x38,0x57,0x0e,0x75,0x0e, - 0x48,0xff,0x05,0xb6,0x89,0x0b,0x00,0x41,0x80,0x7f,0x0d,0x00, - 0x74,0x0e,0x48,0xff,0x05,0xa0,0x89,0x0b,0x00,0xc0,0xe8,0x07,0x41,0x88,0x47,0x10}; - static const uint8_t selection[] { - 0x0f,0xb6,0x43,0x1e,0x48,0x01,0x05,0x66,0xad,0x0b,0x00, - 0x34,0x01,0x88,0x83,0x1a,0x01,0x00,0x00}; - // The same port field is sent to DPCD EDP_CONFIGURATION_SET. - static const uint8_t alternateSR[] { - 0x41,0x0f,0xb6,0x85,0x18,0x01,0x00,0x00, - 0x48,0x01,0x05,0x8e,0xa8,0x0b,0x00,0x48,0x8d,0x55,0xca, - 0x88,0x02,0x4c,0x89,0xef,0xbe,0x0a,0x01,0x00,0x00, - 0xb9,0x01,0x00,0x00,0x00,0xe8,0x16,0x12,0x00,0x00}; - const auto info = patcher.solveSymbol(index, - "__ZN14AppleIntelPort11getDPCDInfoEv", address, size); - const auto setup = patcher.solveSymbol(index, - "__ZN14AppleIntelPort9setupPortEv", address, size); - tglSinkFramingReady = size >= 0x1E34F && info == address + 0x1DE0E && - setup == address + 0x1B588 && - !memcmp(reinterpret_cast(address + 0x1E328), capability, sizeof(capability)) && - !memcmp(reinterpret_cast(address + 0x1B607), selection, sizeof(selection)) && - !memcmp(reinterpret_cast(address + 0x1BC73), alternateSR, sizeof(alternateSR)); - IORegistryEntry::getRegistryRoot()->setProperty("TGLSinkFramingReady", tglSinkFramingReady); - patcher.clearError(); - } { const auto fn = patcher.solveSymbol(index, "__ZN19AppleIntelPowerWell22hwSetPowerWellStateAuxEbj", address, size); @@ -11840,134 +11869,8 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } - if (tglActive) { - // The donor copies a complete timing before this callback, then maps - // its depth and computes lanes/M/N/dither. Clamp that copy, never an - // already-running plane or the framebuffer's pixel format. - const char *symbol="__ZN31AppleIntelFramebufferController21configureModeSetHooksEP21AppleIntelFramebufferP29IODetailedTimingInformationV2"; - const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xD6,0x48,0x89,0xF3,0x48,0xFF,0x05,0xA2,0xF7,0x08,0x00}; - const uint8_t call[]={0x4C,0x89,0xF6,0x48,0x89,0xDA,0xE8,0x24,0x07,0x00,0x00}; - const auto target=patcher.solveSymbol(index,symbol,address,size); - const bool known=size>0x50420 && target==address+0x50142 && - !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && - !memcmp(reinterpret_cast(address+0x4FA13),call,sizeof(call)); - KernelPatcher::RouteRequest route(symbol,wrapTGLModeTiming,orgTGLModeTiming); - tglPanelDepthReady=known && patcher.routeMultiple(index,&route,1,address,size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLPanelDepthReady",tglPanelDepthReady); - patcher.clearError(); - } - - if(tglActive) { - // PANEL_TIMING_INSTALL: exact property writer and its native consumer. - const char *symbol="__ZN31AppleIntelFramebufferController26hwGetPanelTimingPropertiesEv"; - const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x08,0x60,0x09,0x00}; - const uint8_t call[]={0xe8,0xb5,0x4c,0x00,0x00,0x8b,0x93,0x48,0x15,0x00,0x00}; - const auto target=patcher.solveSymbol(index,symbol,address,size); - const auto config=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",address,size); - const bool known=size>=0x48651 && target==address+0x4848c && config==address+0x437a6 && - !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && - !memcmp(reinterpret_cast(address+0x437d2),call,sizeof(call)); - KernelPatcher::RouteRequest routes[] { - {symbol,wrapTGLPanelTiming,orgTGLPanelTiming}, - {"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",wrapTGLPanelConfig,orgTGLPanelConfig} - }; - tglPanelTimingReady=known && patcher.routeMultiple(index,routes,2,address,size); - IORegistryEntry::getRegistryRoot()->setProperty("WEXPanelTimingReady",tglPanelTimingReady); - patcher.clearError(); - // PANEL_TIMING_INSTALL_END - } - - if(adlsDisplaySelected) { - // ADLS_PANEL_PWM_INSTALL: preflight the native lifecycle and PWM enable site. - const char *light="__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; - const char *brightness="__ZN31AppleIntelFramebufferController14hwSetBacklightEj"; - const uint8_t lightEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89,0xcf}; - const uint8_t brightnessEntry[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x90,0x44,0x09,0x00}; - const uint8_t enable[]={0xbe,0x50,0x82,0x0c,0x00,0xba,0x00,0x00,0x00,0x80,0xff,0x90,0x30,0x01,0x00,0x00}; - const auto lightFn=patcher.solveSymbol(index,light,address,size); - const auto brightnessFn=patcher.solveSymbol(index,brightness,address,size); - const bool known=size>=0x508c4 && lightFn==address+0x5063e && brightnessFn==address+0x4a56a && - !memcmp(reinterpret_cast(lightFn),lightEntry,sizeof(lightEntry)) && - !memcmp(reinterpret_cast(brightnessFn),brightnessEntry,sizeof(brightnessEntry)) && - !memcmp(reinterpret_cast(address+0x507e9),enable,sizeof(enable)); - KernelPatcher::RouteRequest routes[] {{light,wrapADLSLightUpEDP,orgADLSLightUpEDP}, - {brightness,wrapADLSPanelBacklight,orgADLSPanelBacklight}}; - if(known && patcher.routeMultiple(index,routes,2,address,size)) { - uint8_t bridge[]={0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,0x90,0x90,0x90,0x90}; - const auto handler=reinterpret_cast(wrapADLSPanelPWMEnable);memcpy(bridge+2,&handler,sizeof(handler)); - patcher.routeBlock(address+0x507e9,bridge,sizeof(bridge)); - adlsPanelPWMReady=patcher.getError()==KernelPatcher::Error::NoError && - !memcmp(reinterpret_cast(address+0x507e9),bridge,sizeof(bridge)); - } - IORegistryEntry::getRegistryRoot()->setProperty("ADLSPanelPWMReady",adlsPanelPWMReady); - patcher.clearError(); - // ADLS_PANEL_PWM_INSTALL_END - } + processNativePanel(patcher,index,address,size); - if (tglActive) { - // TGL/Xe-LP has no dedicated eDP transcoder. Keep the allocated - // pipe (or getTranscoderOffset's pipe argument), including when - // an eDP port is present. External DP/HDMI branches are unchanged. - // Linux v6.12: XE_D_DISPLAY and intel_ddi_compute_config(). - // Preflight every site before modifying this exact donor image. - struct EDPTranscoderSite { - const char *symbol; uint32_t function, offset; uint8_t length, expected[6]; - }; - static const EDPTranscoderSite sites[] { - // SetupParams must also select a clock for eDP, using DDI+1. - {"__ZN31AppleIntelFramebufferController11SetupParamsEP21AppleIntelFramebufferP21AppleIntelDisplayPathP10CRTCParamsPK29IODetailedTimingInformationV2",0x4E758,0x4E7A3,2,{0x74,0x3F}}, - {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48BB6,0x48E38,6,{0x41,0xBC,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController12setColorModeEP21AppleIntelFramebufferP21AppleIntelDisplayPathjjbPK29IODetailedTimingInformationV2",0x491F2,0x493C6,6,{0x41,0xBC,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController19getTranscoderOffsetEP14AppleIntelPortjj",0x49538,0x4956C,5,{0xB8,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController35hwCRTCToIODetailedTimingInformationEP21AppleIntelFramebufferP29IODetailedTimingInformationV2",0x49D46,0x49EAA,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController15hwWaitForVBlankEP21AppleIntelFramebufferj",0x4AE1E,0x4AF0E,5,{0xBE,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController28hwWaitForEndOfVerticalActiveEP21AppleIntelFramebufferj",0x4CBEC,0x4CCDE,5,{0xBE,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController14disableDisplayEP21AppleIntelFramebufferP21AppleIntelDisplayPathh",0x4D34C,0x4D705,5,{0xB9,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4DF95,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4E165,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4E2AF,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4E439,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController33isVSCSDPSetForRequiredColorimetryEP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x4F32E,0x4F3B0,5,{0xBE,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController11hwEnableVRREP21AppleIntelFramebufferPK29IODetailedTimingInformationV2",0x4F586,0x4F77F,6,{0x41,0xBC,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController13disableVSCDIPEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x5248C,0x5252B,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController18disableDRMDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52576,0x52615,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController18disableAVIDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x5265E,0x526FD,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController12enableVSCDIPEP21AppleIntelFramebufferP21AppleIntelDisplayPathhhj",0x5298C,0x52A6D,5,{0xBB,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController25setVSCSDPForPixelEncodingEP21AppleIntelFramebufferP21AppleIntelDisplayPathhhj",0x576C8,0x5776D,5,{0xBE,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController17enableAVIDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x57984,0x57A42,6,{0x41,0xBF,0x0F,0x00,0x00,0x00}}, - {"__ZN31AppleIntelFramebufferController17enableDRMDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPatht",0x57BDA,0x57CD8,6,{0x41,0xBD,0x0F,0x00,0x00,0x00}}, - }; - static const uint8_t noOverride[] {0x90,0x90,0x90,0x90,0x90,0x90}; - static const uint8_t timingExpected[] {0x48,0x8B,0x0D,0x48,0x47,0x09,0x00,0x48,0x8D,0x41,0x01,0x48,0x89,0x05,0x3D,0x47,0x09,0x00}; - static const uint8_t timingReplacement[] {0x85,0xC0,0x0F,0x84,0xD3,0xFE,0xFF,0xFF,0x48,0x8B,0x0D,0x40,0x47,0x09,0x00,0x90,0x90,0x90}; - // N=0 means no usable DP timing. Reuse native zero-output and - // lock-release handling, rather than inventing a clock or divisor. - static const uint8_t zeroExit[] {0xBE,0xA0,0x00,0x00,0x00,0x4C,0x89,0xFF}; - bool ready = size > 0x57CDE && - !memcmp(reinterpret_cast(address+0x49EE9),timingExpected,sizeof(timingExpected)) && - !memcmp(reinterpret_cast(address+0x49DC4),zeroExit,sizeof(zeroExit)); - for (const auto &site : sites) { - if (!ready) break; - ready = patcher.solveSymbol(index,site.symbol,address,size)==address+site.function && - !memcmp(reinterpret_cast(address+site.offset),site.expected,site.length); - } - if (ready) { - for (const auto &site : sites) { - patcher.routeBlock(address+site.offset,noOverride,site.length); - ready = patcher.getError()==KernelPatcher::Error::NoError && - !memcmp(reinterpret_cast(address+site.offset),noOverride,site.length); - if (!ready) break; - } - if (ready) { - patcher.routeBlock(address+0x49EE9,timingReplacement,sizeof(timingReplacement)); - ready = patcher.getError()==KernelPatcher::Error::NoError && - !memcmp(reinterpret_cast(address+0x49EE9),timingReplacement,sizeof(timingReplacement)); - } - } - tglEDPTranscoderReady = ready; - IORegistryEntry::getRegistryRoot()->setProperty("TGLEDPTranscoderReady",ready); - patcher.clearError(); - } if (tglActive) { if (framebufferPatchFlags.bits.FPFStolenMemorySize) { @@ -12690,22 +12593,303 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t tglRecordStartOrder(1,nullptr); return true; } - if (currentGraphics->loadIndex == index) - return processTGLAccelerator(patcher, index, address, size); return false; } return false; } +// Display13 panel operations use its own physical PG bridge. No display owner +// lock spans a donor call: native AUX/clock callbacks reacquire that lock. +void XE::processDisplay13Panel(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(!adlnOutputWiring.panel || !XELP::PanelPower::nativePPS0(firmwarePorts,xeProfile.family))return; + if(!tglAUXLock)tglAUXLock=IOSimpleLockAlloc(); + if(!tglAUXLock)return; + processNativePanel(patcher,index,address,size); + if(size<0x508c4 || !tglEDPTranscoderReady || !tglPanelDepthReady || !tglPanelTimingReady || !display13PanelPWMReady || !tglSinkFramingReady)return; + const char *symbol="__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xec,0x28,0x89,0xf3,0x49,0x89,0xff}; + const uint8_t configCall[]={0x8b,0xb7,0x48,0x0d,0x00,0x00,0xe8,0x30,0xb1,0xff,0xff,0x83,0xfb,0x02}; + const auto target=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(size<=0x48B88 || target!=address+0x48652 || + memcmp(reinterpret_cast(target),entry,sizeof(entry)) || + memcmp(reinterpret_cast(address+0x4866b),configCall,sizeof(configCall)))return; + for(const auto &patch:Display13Panel::lightPatches) + if(memcmp(reinterpret_cast(address+patch.offset),patch.before,patch.size))return; + for(const auto &patch:Display13Panel::lightPatches) { + patcher.routeBlock(address+patch.offset,patch.after,patch.size); + const bool ok=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+patch.offset),patch.after,patch.size); + patcher.clearError();if(!ok)return; + } + if(!display13FBPower.panelLock)display13FBPower.panelLock=IOLockAlloc(); + if(!display13FBPower.panelLock)return; + KernelPatcher::RouteRequest route(symbol,wrapDisplay13PanelPower,orgDisplay13PanelPower); + display13PanelPowerReady=patcher.routeMultipleShort(index,&route,1,address,size); + patcher.clearError(); +} +// The caller holds c.lock. Only the retained controller/platform/map can own PPS0. +bool XE::display13PanelTarget(void *controller) { + auto s=callbackXE;auto &c=display13FBPower; + return controller && controller==c.controller && controller==s->adlnTopologyController && + s->display13FramebufferRequested && s->adlnOutputWiring.panel && s->display13PanelPowerReady && + XELP::PanelPower::nativePPS0(s->firmwarePorts,s->xeProfile.family) && + adlnWriterMappingBound() && !c.clockStopped && !c.clockFaulted && !c.panelFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && + Display13FBMemory::field(controller,0xD40)==s->adlnPlatform && + Display13FBMemory::field(controller,0xC8)==c.power && + Display13FBMemory::field(controller,0xC40)==c.access && + !Display13FBMemory::field(controller,0x918) && !Display13FBMemory::field(controller,0xD0) && + Display13Power::objectAssociations(c.power,0x80,controller,0x1C20); +} +bool XE::display13PanelHealthy() { + auto &c=display13FBPower;if(!c.lock)return false;IOLockLock(c.lock); + const bool ok=!c.panelFaulted && !c.clockFaulted;IOLockUnlock(c.lock);return ok; +} +bool XE::display13PanelPowerReference(bool acquire) { + auto &c=display13FBPower;Display13FBPanelIO io{c.map}; + const auto r=acquire?c.bridge.getPG(io,1):c.bridge.putPG(io,1); + const bool ok=r.status==Display13Power::Status::Complete && + Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0x1C20); + if(!ok)c.panelFaulted=true;return ok; +} +bool XE::display13PanelCapture(void *controller) { + auto &c=display13FBPower;if(!c.lock)return false;IOLockLock(c.lock); + Display13FBPanelIO io{c.map};bool ok=false; + if(display13PanelTarget(controller) && display13PanelPowerReference(true)) { + ok=c.panel.capture(io,callbackXE->firmwarePorts.panel); + if(ok)*reinterpret_cast(static_cast(controller)+0xE60)=c.panel.pwm.period; + ok=display13PanelPowerReference(false) && ok; + } + IOLockUnlock(c.lock);return ok; +} +void XE::display13PanelTiming(void *controller,bool apply) { + if(!display13PanelCapture(controller) || !apply)return; + auto &c=display13FBPower;IOLockLock(c.lock); + if(display13PanelTarget(controller)) { + const auto plan=XELP::PanelPower::select(callbackXE->firmwarePorts.panel, + static_cast(static_cast(static_cast(controller)+0x1548)),c.panel.bios[0],c.panel.bios[1],c.panel.bios[2]); + if(plan.valid)memcpy(static_cast(controller)+0x1548,plan.fields,sizeof(plan.fields)); + else c.panelFaulted=true; + } + IOLockUnlock(c.lock); +} +uint32_t XE::display13PanelBrightness(void *controller,uint32_t brightness) { + if(!display13PanelCapture(controller))return kIOReturnNotReady; + auto &c=display13FBPower;IOLockLock(c.lock);Display13FBPanelIO io{c.map};bool ok=false;uint32_t duty=0; + if(display13PanelTarget(controller) && c.panel.pwm.duty(brightness,duty) && + display13PanelPowerReference(true)) { + io.write(0xC8258,duty);ok=io.read(0xC8258)==duty; + if(ok)*reinterpret_cast(static_cast(controller)+0xE58)=brightness; + ok=display13PanelPowerReference(false) && ok; + } + IOLockUnlock(c.lock);return ok?0:kIOReturnNotReady; +} +void XE::display13PanelPWMEnable(void *access) { + auto &c=display13FBPower;if(!c.lock)return;IOLockLock(c.lock);Display13FBPanelIO io{c.map}; + if(access==c.access && display13PanelTarget(c.controller) && c.panel.pwm.valid() && + display13PanelPowerReference(true)) { + const auto value=0x80000000U|c.panel.pwm.polarity; + io.write(0xC8250,value);const bool ok=io.read(0xC8250)==value; + if(!display13PanelPowerReference(false) || !ok)c.panelFaulted=true; + } + IOLockUnlock(c.lock); +} +uint32_t XE::wrapDisplay13PanelPower(void *controller,uint32_t state) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->adlnOutputWiring.panel)return FunctionCast(wrapDisplay13PanelPower,s->orgDisplay13PanelPower)(controller,state); + if(!c.panelLock || !c.lock || !display13PanelCapture(controller))return kIOReturnNotReady; + IOLockLock(c.panelLock);IOLockLock(c.lock);Display13FBPanelIO io{c.map};Display13Panel::OnGate gate; + const bool owned=display13PanelTarget(controller) && display13PanelPowerReference(true); + const bool ready=owned && (state!=2 || gate.begin(io)); + IOLockUnlock(c.lock); + const auto result=ready?FunctionCast(wrapDisplay13PanelPower,s->orgDisplay13PanelPower)(controller,state):uint32_t(kIOReturnNotReady); + IOLockLock(c.lock); + // Always restore the workaround before releasing our PG, also after failed on. + const bool restored=!owned || gate.finish(io); + bool ok=ready && restored; + if(owned) { + const auto status=io.read(0xC7200); + ok=ok && status!=UINT32_MAX && (state==2 ? (status&0x80000000U)!=0 : !(status&0x80000000U)); + ok=display13PanelPowerReference(false) && ok; + } + if(!ok)c.panelFaulted=true; + IOLockUnlock(c.lock);IOLockUnlock(c.panelLock); + return ok?result:uint32_t(kIOReturnNotReady); +} + +// Shared exact-donor panel corrections; physical adapters own admission. +void XE::processNativePanel(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + { + // getDPCDInfo stores MAX_LANE_COUNT bit7 at port+0x10, + // but setupPort defaults enhanced framing to !MST. + static const uint8_t capability[] { + 0x41,0xc6,0x47,0x10,0x00,0x41,0x38,0x57,0x0e,0x75,0x0e, + 0x48,0xff,0x05,0xb6,0x89,0x0b,0x00,0x41,0x80,0x7f,0x0d,0x00, + 0x74,0x0e,0x48,0xff,0x05,0xa0,0x89,0x0b,0x00,0xc0,0xe8,0x07,0x41,0x88,0x47,0x10}; + static const uint8_t selection[] { + 0x0f,0xb6,0x43,0x1e,0x48,0x01,0x05,0x66,0xad,0x0b,0x00, + 0x34,0x01,0x88,0x83,0x1a,0x01,0x00,0x00}; + // The same port field is sent to DPCD EDP_CONFIGURATION_SET. + static const uint8_t alternateSR[] { + 0x41,0x0f,0xb6,0x85,0x18,0x01,0x00,0x00, + 0x48,0x01,0x05,0x8e,0xa8,0x0b,0x00,0x48,0x8d,0x55,0xca, + 0x88,0x02,0x4c,0x89,0xef,0xbe,0x0a,0x01,0x00,0x00, + 0xb9,0x01,0x00,0x00,0x00,0xe8,0x16,0x12,0x00,0x00}; + const auto info = patcher.solveSymbol(index, + "__ZN14AppleIntelPort11getDPCDInfoEv", address, size); + const auto setup = patcher.solveSymbol(index, + "__ZN14AppleIntelPort9setupPortEv", address, size); + tglSinkFramingReady = size >= 0x1E34F && info == address + 0x1DE0E && + setup == address + 0x1B588 && + !memcmp(reinterpret_cast(address + 0x1E328), capability, sizeof(capability)) && + !memcmp(reinterpret_cast(address + 0x1B607), selection, sizeof(selection)) && + !memcmp(reinterpret_cast(address + 0x1BC73), alternateSR, sizeof(alternateSR)); + IORegistryEntry::getRegistryRoot()->setProperty("TGLSinkFramingReady", tglSinkFramingReady); + patcher.clearError(); + } + + if (tglActive) { + // The donor copies a complete timing before this callback, then maps + // its depth and computes lanes/M/N/dither. Clamp that copy, never an + // already-running plane or the framebuffer's pixel format. + const char *symbol="__ZN31AppleIntelFramebufferController21configureModeSetHooksEP21AppleIntelFramebufferP29IODetailedTimingInformationV2"; + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xD6,0x48,0x89,0xF3,0x48,0xFF,0x05,0xA2,0xF7,0x08,0x00}; + const uint8_t call[]={0x4C,0x89,0xF6,0x48,0x89,0xDA,0xE8,0x24,0x07,0x00,0x00}; + const auto target=patcher.solveSymbol(index,symbol,address,size); + const bool known=size>0x50420 && target==address+0x50142 && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && + !memcmp(reinterpret_cast(address+0x4FA13),call,sizeof(call)); + KernelPatcher::RouteRequest route(symbol,wrapTGLModeTiming,orgTGLModeTiming); + tglPanelDepthReady=known && patcher.routeMultiple(index,&route,1,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("TGLPanelDepthReady",tglPanelDepthReady); + patcher.clearError(); + } + + if(tglActive) { + // PANEL_TIMING_INSTALL: exact property writer and its native consumer. + const char *symbol="__ZN31AppleIntelFramebufferController26hwGetPanelTimingPropertiesEv"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x08,0x60,0x09,0x00}; + const uint8_t call[]={0xe8,0xb5,0x4c,0x00,0x00,0x8b,0x93,0x48,0x15,0x00,0x00}; + const auto target=patcher.solveSymbol(index,symbol,address,size); + const auto config=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",address,size); + const bool known=size>=0x48651 && target==address+0x4848c && config==address+0x437a6 && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && + !memcmp(reinterpret_cast(address+0x437d2),call,sizeof(call)); + KernelPatcher::RouteRequest routes[] { + {symbol,wrapTGLPanelTiming,orgTGLPanelTiming}, + {"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",wrapTGLPanelConfig,orgTGLPanelConfig} + }; + tglPanelTimingReady=known && patcher.routeMultiple(index,routes,2,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXPanelTimingReady",tglPanelTimingReady); + patcher.clearError(); + // PANEL_TIMING_INSTALL_END + } + + if(adlsDisplaySelected || (display13FramebufferRequested && adlnOutputWiring.panel)) { + // ADLS_PANEL_PWM_INSTALL: preflight the native lifecycle and PWM enable site. + const char *light="__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; + const char *brightness="__ZN31AppleIntelFramebufferController14hwSetBacklightEj"; + const uint8_t lightEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89,0xcf}; + const uint8_t brightnessEntry[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x90,0x44,0x09,0x00}; + const uint8_t enable[]={0xbe,0x50,0x82,0x0c,0x00,0xba,0x00,0x00,0x00,0x80,0xff,0x90,0x30,0x01,0x00,0x00}; + const auto lightFn=patcher.solveSymbol(index,light,address,size); + const auto brightnessFn=patcher.solveSymbol(index,brightness,address,size); + const bool known=size>=0x508c4 && lightFn==address+0x5063e && brightnessFn==address+0x4a56a && + !memcmp(reinterpret_cast(lightFn),lightEntry,sizeof(lightEntry)) && + !memcmp(reinterpret_cast(brightnessFn),brightnessEntry,sizeof(brightnessEntry)) && + !memcmp(reinterpret_cast(address+0x507e9),enable,sizeof(enable)); + KernelPatcher::RouteRequest routes[] {{light,wrapADLSLightUpEDP,orgADLSLightUpEDP}, + {brightness,wrapADLSPanelBacklight,orgADLSPanelBacklight}}; + if(known && patcher.routeMultiple(index,routes,2,address,size)) { + uint8_t bridge[]={0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,0x90,0x90,0x90,0x90}; + const auto handler=reinterpret_cast(wrapADLSPanelPWMEnable);memcpy(bridge+2,&handler,sizeof(handler)); + patcher.routeBlock(address+0x507e9,bridge,sizeof(bridge)); + adlsPanelPWMReady=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+0x507e9),bridge,sizeof(bridge)); + } + if(display13FramebufferRequested){display13PanelPWMReady=adlsPanelPWMReady;adlsPanelPWMReady=false;} + IORegistryEntry::getRegistryRoot()->setProperty("ADLSPanelPWMReady",adlsPanelPWMReady); + patcher.clearError(); + // ADLS_PANEL_PWM_INSTALL_END + } + + if (tglActive) { + // TGL/Xe-LP has no dedicated eDP transcoder. Keep the allocated + // pipe (or getTranscoderOffset's pipe argument), including when + // an eDP port is present. External DP/HDMI branches are unchanged. + // Linux v6.12: XE_D_DISPLAY and intel_ddi_compute_config(). + // Preflight every site before modifying this exact donor image. + struct EDPTranscoderSite { + const char *symbol; uint32_t function, offset; uint8_t length, expected[6]; + }; + static const EDPTranscoderSite sites[] { + // SetupParams must also select a clock for eDP, using DDI+1. + {"__ZN31AppleIntelFramebufferController11SetupParamsEP21AppleIntelFramebufferP21AppleIntelDisplayPathP10CRTCParamsPK29IODetailedTimingInformationV2",0x4E758,0x4E7A3,2,{0x74,0x3F}}, + {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48BB6,0x48E38,6,{0x41,0xBC,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController12setColorModeEP21AppleIntelFramebufferP21AppleIntelDisplayPathjjbPK29IODetailedTimingInformationV2",0x491F2,0x493C6,6,{0x41,0xBC,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController19getTranscoderOffsetEP14AppleIntelPortjj",0x49538,0x4956C,5,{0xB8,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController35hwCRTCToIODetailedTimingInformationEP21AppleIntelFramebufferP29IODetailedTimingInformationV2",0x49D46,0x49EAA,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController15hwWaitForVBlankEP21AppleIntelFramebufferj",0x4AE1E,0x4AF0E,5,{0xBE,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController28hwWaitForEndOfVerticalActiveEP21AppleIntelFramebufferj",0x4CBEC,0x4CCDE,5,{0xBE,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController14disableDisplayEP21AppleIntelFramebufferP21AppleIntelDisplayPathh",0x4D34C,0x4D705,5,{0xB9,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4DF95,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4E165,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4E2AF,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController12hwDisableVRREP21AppleIntelFramebufferb",0x4DD68,0x4E439,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController33isVSCSDPSetForRequiredColorimetryEP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x4F32E,0x4F3B0,5,{0xBE,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController11hwEnableVRREP21AppleIntelFramebufferPK29IODetailedTimingInformationV2",0x4F586,0x4F77F,6,{0x41,0xBC,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController13disableVSCDIPEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x5248C,0x5252B,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController18disableDRMDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52576,0x52615,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController18disableAVIDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x5265E,0x526FD,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController12enableVSCDIPEP21AppleIntelFramebufferP21AppleIntelDisplayPathhhj",0x5298C,0x52A6D,5,{0xBB,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController25setVSCSDPForPixelEncodingEP21AppleIntelFramebufferP21AppleIntelDisplayPathhhj",0x576C8,0x5776D,5,{0xBE,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController17enableAVIDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x57984,0x57A42,6,{0x41,0xBF,0x0F,0x00,0x00,0x00}}, + {"__ZN31AppleIntelFramebufferController17enableDRMDIPforDPEP21AppleIntelFramebufferP21AppleIntelDisplayPatht",0x57BDA,0x57CD8,6,{0x41,0xBD,0x0F,0x00,0x00,0x00}}, + }; + static const uint8_t noOverride[] {0x90,0x90,0x90,0x90,0x90,0x90}; + static const uint8_t timingExpected[] {0x48,0x8B,0x0D,0x48,0x47,0x09,0x00,0x48,0x8D,0x41,0x01,0x48,0x89,0x05,0x3D,0x47,0x09,0x00}; + static const uint8_t timingReplacement[] {0x85,0xC0,0x0F,0x84,0xD3,0xFE,0xFF,0xFF,0x48,0x8B,0x0D,0x40,0x47,0x09,0x00,0x90,0x90,0x90}; + // N=0 means no usable DP timing. Reuse native zero-output and + // lock-release handling, rather than inventing a clock or divisor. + static const uint8_t zeroExit[] {0xBE,0xA0,0x00,0x00,0x00,0x4C,0x89,0xFF}; + bool ready = size > 0x57CDE && + !memcmp(reinterpret_cast(address+0x49EE9),timingExpected,sizeof(timingExpected)) && + !memcmp(reinterpret_cast(address+0x49DC4),zeroExit,sizeof(zeroExit)); + for (const auto &site : sites) { + if (!ready) break; + ready = patcher.solveSymbol(index,site.symbol,address,size)==address+site.function && + !memcmp(reinterpret_cast(address+site.offset),site.expected,site.length); + } + if (ready) { + for (const auto &site : sites) { + patcher.routeBlock(address+site.offset,noOverride,site.length); + ready = patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+site.offset),noOverride,site.length); + if (!ready) break; + } + if (ready) { + patcher.routeBlock(address+0x49EE9,timingReplacement,sizeof(timingReplacement)); + ready = patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+0x49EE9),timingReplacement,sizeof(timingReplacement)); + } + } + tglEDPTranscoderReady = ready; + IORegistryEntry::getRegistryRoot()->setProperty("TGLEDPTranscoderReady",ready); + patcher.clearError(); + } +} + // Retain hwSetPanelPowerConfig's caller-serialization contract. The wrapper // completes its timing-property input; it never writes PPS control/status or // calls the panel on/off sequence recursively. Capture before either config // branch can overwrite BIOS delays; reset on each native controller start. void XE::tglApplyPanelTiming(void *controller,bool apply) { auto s=callbackXE; + if(s->display13FramebufferRequested){display13PanelTiming(controller,apply);return;} auto c=static_cast(controller); const auto &v=s->firmwarePorts; if(!c || c!=s->tglObservationController || !s->tglActive || !s->tglPanelTimingReady || + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || !XELP::PanelPower::nativePPS0(v,s->xeProfile.family) || !s->tglTCNativeFunctions.complete() || *s->tglTCNativeFunctions.controllerSlot!=controller || !s->orgTGLRegisterRead || c[0x918] || *reinterpret_cast(c+0xD0) || @@ -12785,14 +12969,18 @@ bool XE::adlsCapturePanelPWM(void *controller) { } uint32_t XE::wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void *timing) { auto s=callbackXE; + if(s->display13FramebufferRequested && s->adlnOutputWiring.panel && !display13PanelCapture(controller))return kIOReturnNotReady; if(s->adlsDisplaySelected && s->adlsWiring.panel) { if(!adlsCapturePanelPWM(controller))return kIOReturnNotReady; *reinterpret_cast(static_cast(controller)+0xE60)=s->adlsPanelPWM.period; } - return FunctionCast(wrapADLSLightUpEDP,s->orgADLSLightUpEDP)(controller,fb,path,timing); + const auto result=FunctionCast(wrapADLSLightUpEDP,s->orgADLSLightUpEDP)(controller,fb,path,timing); + if(s->display13FramebufferRequested && !display13PanelHealthy())return kIOReturnNotReady; + return result; } uint32_t XE::wrapADLSPanelBacklight(void *controller,uint32_t brightness) { auto s=callbackXE; + if(s->display13FramebufferRequested && s->adlnOutputWiring.panel)return display13PanelBrightness(controller,brightness); if(!s->adlsDisplaySelected || !s->adlsWiring.panel) return FunctionCast(wrapADLSPanelBacklight,s->orgADLSPanelBacklight)(controller,brightness); void *access,*power;uint32_t duty; @@ -12809,7 +12997,8 @@ uint32_t XE::wrapADLSPanelBacklight(void *controller,uint32_t brightness) { f.putPG(power,1);return ready?0:kIOReturnNotReady; } void XE::wrapADLSPanelPWMEnable(void *access) { - auto s=callbackXE;uint32_t value=0x80000000; + auto s=callbackXE; + if(s->display13FramebufferRequested){display13PanelPWMEnable(access);return;}uint32_t value=0x80000000; if(s->adlsDisplaySelected && s->adlsWiring.panel) { void *expected,*power; if(!s->adlsPanelPWM.valid() || !adlsPanelPWMTarget(s->tglObservationController,expected,power) || access!=expected)return; @@ -13020,6 +13209,11 @@ XELP::DisplayContract::Mask XE::display13Available() const { if(display13TrainingReady)a|=C::Training12; if(display13FramingReady)a|=C::Framing; if(!display13ClockOrderReady)a&=~C::ClockOrder; + if(display13PanelPowerReady && display13PanelProbeReady && tglPanelPowerPropertyReady)a|=C::PanelPower; + if(tglEDPTranscoderReady)a|=C::PanelTranscoder; + if(tglPanelDepthReady)a|=C::PanelDepth; + if(tglPanelTimingReady)a|=C::PanelTiming; + if(display13PanelPWMReady)a|=C::PanelPWM; return a; } @@ -13893,6 +14087,18 @@ void XE::wrapTGLHostSWInterrupt(void *streamer,void *source,int count) { tglHostIRQEnd(r); } +// Terminal diagnostics can run on interrupt paths: no allocation, sleeping, +// or waiting for another CPU. The first fatal report owns these bytes until +// panic; a concurrent/reentrant report uses only the short fallback message. +struct TGLFatalLogStorage { + volatile uint32_t claimed {}; + struct { + struct {char trail[1800],vm[1800],loads[1000];} csb; + struct {char detail[1400],boundary[1400],preStart[640];} guc; + } bytes {}; + bool claim() {return __sync_bool_compare_and_swap(&claimed,0,1);} +}; +static TGLFatalLogStorage tglFatalLogStorage; static void tglHostCSBFatal(const char *reason,const uint32_t *entry,void *failedStreamer=nullptr); namespace { @@ -14730,12 +14936,13 @@ void XE::wrapTGLHostCSB(void *streamer,uint32_t *entry,uint32_t *index) { // Both routes run only AFTER the native validator has rejected list state. // Preserve failure, enriching the persisted panic rather than suppressing it. static void tglHostCSBFatal(const char *reason,const uint32_t *entry,void *failedStreamer) { + if(!tglFatalLogStorage.claim())PANIC("igfx","concurrent CSB fatal: %s entry=%x:%x",reason,entry?entry[1]:0,entry?entry[0]:0); uint64_t before=0,writes=0,immediate=0,finalBad=0; for (uint32_t i=0;i(failedStreamer); + auto &trail=tglFatalLogStorage.bytes.csb.trail;size_t used=0;uint32_t kept=0;uint64_t streamer=reinterpret_cast(failedStreamer); // Locate this failure's streamer, then retain its history across interrupt // threads. Fixed records are read only here, after the native fatal decision. for (uint32_t i=tglHost.csbs(count)>=sizeof(vm)-vmUsed) break; vmUsed+=count; } - char loads[1000] {};size_t loadUsed=0;unsigned keptLoads=0; + auto &loads=tglFatalLogStorage.bytes.csb.loads;size_t loadUsed=0;unsigned keptLoads=0; const TGLHostRecord *latestLoad=nullptr; for (uint32_t i=tglHost.loadsorgTGLGuCHeaderPanic)(guc); return; } + if(!tglFatalLogStorage.claim())PANIC("igfx","concurrent GuC CSS-header fatal"); auto base = *reinterpret_cast(static_cast(o.accelerator) + 0x1240); - char detail[1400] {}; size_t used = 0; + auto &detail=tglFatalLogStorage.bytes.guc.detail; size_t used = 0; for (uint32_t i=0; i(n) >= sizeof(detail)-used) break; @@ -15348,13 +15556,13 @@ void XE::wrapTGLGuCHeaderPanic(void *guc) { } // Same native fatal CSS-header condition. Do not fake transfer success or // continue into scheduler/firmware execution; persist the missing evidence. - char boundary[1400] {}; used=0; + auto &boundary=tglFatalLogStorage.bytes.guc.boundary; used=0; for (uint32_t i=0;i<64 && used(n)>=sizeof(boundary)-used) break; used+=n; } - char preStart[640] {}; used=0; + auto &preStart=tglFatalLogStorage.bytes.guc.preStart; used=0; for (uint32_t i=0;i(n)>=sizeof(preStart)-used) break; @@ -16456,19 +16664,30 @@ uint32_t XE::wrapTGLGMBus(void *controller, uint32_t bus, uint32_t speed, void * // failed/invalid reads so old EDID cannot constrain a replacement connection. struct TGLPanelDepthCache {void *port {}; uint32_t maximum {};}; static TGLPanelDepthCache tglPanelDepthCache[2]; // protected by tglAUXLock +void XE::resetPanelDepth() { + if(!tglAUXLock)return;IOSimpleLockLock(tglAUXLock); + for(auto &cache:tglPanelDepthCache)cache={};IOSimpleLockUnlock(tglAUXLock); +} uint32_t XE::tglPanelDepthPort(void *port) { auto s=callbackXE;auto p=static_cast(port); auto c=static_cast(s->tglObservationController); if(!p || !c || !s->tglActive || !s->tglPanelDepthReady || !tglAUXLock || - (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected))return 9; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected && !s->display13FramebufferRequested))return 9; auto cfg=*reinterpret_cast(p+0x548); if(!cfg || cfg[0]>1 || cfg[2]!=0 || cfg[3]!=0 || p[0x117] || *reinterpret_cast(p+0x578)!=*reinterpret_cast(c+0xC40) || !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[cfg[0]].internal() || !s->firmwarePorts.port[cfg[0]].dp())return 9; if(s->adlsDisplaySelected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg))return 9; + if(s->display13FramebufferRequested && (!Display13FBTopology::selected(s->adlnPlatform,cfg,s->adlnOutputWiring) || !(s->adlnOutputWiring.panel&(1U<1)return; + const auto maximum=!result?TGLPanelDepth::maximum(data,128):0; + IOSimpleLockLock(tglAUXLock);tglPanelDepthCache[ddi]={port,maximum};IOSimpleLockUnlock(tglAUXLock); +} + void XE::wrapTGLModeTiming(void *controller,void *fb,void *timing) { FunctionCast(wrapTGLModeTiming,callbackXE->orgTGLModeTiming)(controller,fb,timing); auto b=static_cast(fb);auto t=static_cast(timing); @@ -16498,15 +16717,7 @@ uint32_t XE::wrapTGLPortDDC(void *port, uint32_t block, uint8_t *data, bool forc tglTrace(1, id, false, block, force, 0, 0); const auto r = FunctionCast(wrapTGLPortDDC, callbackXE->orgTGLPortDDC)(port, block, data, force); tglTrace(1, id, true, block, force, r, 0); - if(block==1) { - const auto ddi=tglPanelDepthPort(port); - if(ddi<2) { - const auto maximum=r==0 ? TGLPanelDepth::maximum(data,128) : 0; - IOSimpleLockLock(tglAUXLock); - tglPanelDepthCache[ddi]={port,maximum}; - IOSimpleLockUnlock(tglAUXLock); - } - } + cachePanelDepth(port,block,data,r); if (r == 0 && data && (block == 1 || block == 2)) { char key[40]; snprintf(key, sizeof(key), "TGLWEGPort%uDDC%s", id, block == 1 ? "Base" : "Extension"); IORegistryEntry::getRegistryRoot()->setProperty(key, data, 128); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e41739b7..b65a2fea 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -349,6 +349,20 @@ class XE { mach_vm_address_t orgTGLPlaneSetup {}, orgTGLColorMode {}, orgTGLPixelInfo {}; mach_vm_address_t orgTGLModeTiming {}; bool tglPanelDepthReady {false}; + bool display13PanelPWMReady {},display13PanelPowerReady {},display13PanelProbeReady {}; + mach_vm_address_t orgDisplay13PanelPower {}; + void processDisplay13Panel(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static bool display13PanelTarget(void *controller); + static bool display13PanelHealthy(); + static bool display13PanelPowerReference(bool acquire); + static bool display13PanelCapture(void *controller); + static void display13PanelTiming(void *controller,bool apply); + static uint32_t display13PanelBrightness(void *controller,uint32_t brightness); + static void display13PanelPWMEnable(void *access); + static uint32_t wrapDisplay13PanelPower(void *controller,uint32_t state); + static void resetPanelDepth(); + static void cachePanelDepth(void *port,uint32_t block,const uint8_t *data,uint32_t result); + void processNativePanel(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); bool tglPanelTimingReady {}; mach_vm_address_t orgTGLPanelTiming {},orgTGLPanelConfig {}; uint32_t tglPanelTimingBIOS[3] {}; diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 13807bfa..5c1ce647 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -107,7 +107,10 @@ struct DisplayContract { (panel?Combo12|PanelDepth|PanelTiming|PanelPWM|AUXParent|PowerGet|PowerPut:0),installed}; } static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13|Training12|Framing; - static DisplayContract display13(Mask installed,bool dp=false) {return {Backend13|(dp?DP13:0),installed};} + static constexpr Mask Panel13=SST13|AUX13|Training12|Framing|PanelPower|PanelTranscoder|PanelDepth|PanelTiming|PanelPWM; + static DisplayContract display13(Mask installed,bool dp=false,bool panel=false) { + return {Backend13|(dp?DP13:0)|(panel?Panel13:0),installed}; + } static const char *name(Mask bit) { switch(bit) { case DonorRuntime:return "DonorRuntime"; diff --git a/WhateverXe/kern_xelp_panel_power.hpp b/WhateverXe/kern_xelp_panel_power.hpp index dcd3ca57..3c014ca7 100644 --- a/WhateverXe/kern_xelp_panel_power.hpp +++ b/WhateverXe/kern_xelp_panel_power.hpp @@ -13,7 +13,8 @@ struct PanelPower { const auto &p=v.panel; const auto adapter=displayAdapter(family); const bool backend=(adapter==DisplayAdapter::TGL && p.ddi<2) || - (adapter==DisplayAdapter::ADLS && p.ddi==0); + (adapter==DisplayAdapter::ADLS && p.ddi==0) || + ((adapter==DisplayAdapter::ADLP || adapter==DisplayAdapter::ADLN) && p.ddi<2); if(!backend || !v.valid() || v.unmapped || !p.valid || !p.backlightValid || !p.pwmPresent || p.backlightType!=2 || p.controller)return false; const auto &port=v.port[p.ddi];const auto aux=p.ddi?0x10U:0x40U; diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 0b5ef729..9a857b5b 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -83,10 +83,13 @@ struct NativePorts { // legacy permits probing for mode4; it does not force mode4 or classify a // USB-C-capable connector as physically fixed DP. The provider checks generation-specific live state. // Type-C intent is represented but not admitted by either current backend. -constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U,ProbeDisplay13FixedTC=2U,ProbeDisplay13FixedDP=4U; +constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U,ProbeDisplay13FixedTC=2U,ProbeDisplay13FixedDP=4U,ProbeDisplay13Panel=8U; using QueryProbeCapabilities=uint32_t (*)(uint32_t,uint32_t); // Optional export: old ABI3 providers remain valid for their existing routes, // but their lack of a capability reply cannot authorize a new DP route. +inline bool display13PanelProbeCapable(QueryProbeCapabilities query) { + return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13Panel); +} inline bool display13ProbeCapable(QueryProbeCapabilities query) { return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13ComboDP); } diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index 4e6981b4..022c0c15 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -56,7 +56,8 @@ template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man if(!(b.config.combo&b.config.comboBackend&(1U<=1 && c[1]<=3 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; const bool dp=c[0]<2 && c[1]==c[0]+1 && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; - return hdmi || dp ? 1 : 0; + const bool panel=c[0]<2 && !c[1] && !c[2] && !c[3] && c[4]==2 && c[5]==0x18; + return hdmi || dp || panel ? 1 : 0; } } #endif diff --git a/docs/device-selection.md b/docs/device-selection.md index f561c542..d1bf0b7d 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -42,6 +42,18 @@ non-Intel physical devices are rejected. An invalid override does not silently fall back to the physical ID. Remove an obsolete ICL or other unsupported spoof to use automatic selection. A restart is required after changing the selection. +- ADL-P/N internal panels: experimental standard eDP on Combo A/B, one panel + identified by VBT, PPS0 and PCH PWM0. Native modesetting uses the actual lane + count, link rate, EDID depth and negotiated framing; brightness preserves the + firmware PWM period/polarity. The firmware must already provide a single + linear 8-bpc RGB GOP on pipe A. Multiple active firmware displays with an + internal panel are rejected before bootstrap changes. This does not limit + subsequent native pipe allocation to pipe A. Use matching source builds of + WhateverXe and AppleIntelTGLCompat; older providers do not enable this path. + PPS1, EC/WMI or AUX brightness, DSC/FEC, PSR and startup without a GOP are + outside this implementation. No ADL-P/N eDP hardware result is claimed, + including physical brightness, full link retraining or system sleep/wake. + The Display12 small-GT backends (TGL GT1, ADL-S/HX and RPL-S/HX) require their physical ID without an override. Topology comes from the Gen12 slice, DSS and EU-pair fuses; the supported four-bank capacity is checked separately. @@ -78,7 +90,7 @@ must match the implemented path; ambiguous or cross-routed AUX layouts are exclu - ADL-S/HX and RPL-S/HX: Combo PHY A–C are implemented; PHY D/E and Type-C/TBT-managed routes are not. A USB-C socket wired as fixed DP is different from a Type-C-managed path. Firmware-derived DDI A eDP is implemented but not hardware-tested. -- TGL and ADL-S panel handling supports the qualified PPS0/PWM0 path. Other PPS +- TGL, ADL-S and experimental ADL-P/N panel handling supports the qualified PPS0/PWM0 path. Other PPS controllers and AUX-controlled backlights need additional work. Panel timing comes from VBT, firmware state and explicit properties. High-rate link selection has CPU coverage; not every panel or link rate has been tested on hardware. @@ -89,7 +101,7 @@ must match the implemented path; ambiguous or cross-routed AUX layouts are exclu AUX wiring. Boot handoff supports validated linear RGB scanout, including supported firmware scaling, and retires secondary fixed TC firmware outputs before native startup. ADL-H has been tested with three outputs and a - DP-to-HDMI adapter. Direct DP sinks remain unverified. Combo DP, eDP, MST, + DP-to-HDMI adapter. Direct DP sinks remain unverified. External Combo DP, MST, Type-C-managed output and cross-routed AUX still need further implementation. The Display12 small-GT implementation accepts one slice, up to two DSS, diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index e3534208..67562083 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -22,6 +22,7 @@ XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; + bool display13PanelProbeReady=true,display13PanelPowerReady=true; bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; bool display13TCDPProbeReady=true,display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); diff --git a/tests/adlp_load_only.py b/tests/adlp_load_only.py index f9c61bbe..b335aa37 100644 --- a/tests/adlp_load_only.py +++ b/tests/adlp_load_only.py @@ -11,6 +11,7 @@ code=r''' #include #include +#include #include #include #include @@ -50,13 +51,23 @@ }; constexpr unsigned kIOMapInhibitCache=0; void IODelay(unsigned){assert(false);} +bool allocationFailure=false;unsigned allocations=0,frees=0; +void *IOMalloc(size_t bytes){++allocations;return allocationFailure?nullptr:malloc(bytes);} +void IOFree(void *p,size_t){assert(p);++frees;free(p);} struct DiagnosticGPU { - unsigned command=2,maps=0; + unsigned command=2,maps=0,snapshots=0;bool allocationFailed=false; unsigned configRead16(unsigned r){assert(r==4);return command;} - struct Map {uintptr_t getVirtualAddress(){return 0;}unsigned getLength(){return 0;}void release(){}}map; + struct Map { + std::vector bar;unsigned releases=0; + uintptr_t getVirtualAddress(){return bar.empty()?0:reinterpret_cast(bar.data());} + unsigned getLength(){return bar.size()*4;}void release(){++releases;} + }map; Map *mapDeviceMemoryWithRegister(unsigned r,unsigned){assert(r==0x10);++maps;return ↦} - void setProperty(const char*,const void*,unsigned){} + void setProperty(const char *key,const void*,unsigned bytes){ + if(!strcmp(key,"ADLPScanoutBoot")){assert(bytes==sizeof(AlderLakeN::DisplayReadout));++snapshots;} + } void setProperty(const char*,uint32_t,unsigned){} + void setProperty(const char *key,bool value){assert(!strcmp(key,"ADLPReadoutAllocationFailed"));allocationFailed=value;} }; struct XE { bool adlpLoadOnly=false,adlpDisplaySelected=false;XELP::Profile xeProfile;unsigned initialized=0; @@ -83,7 +94,17 @@ const bool blocked=enabled && physical!=0x4c8a; assert(xe.adlpLoadOnly==blocked && xe.initialized==!blocked && gpu.maps==unsigned(blocked&&powered));++cases; } - readout=false; + // A readable diagnostic BAR exercises snapshot allocation success/failure. + // Either outcome remains load-only, and neither leaks its map or buffer. + for(bool fail:{false,true}) { + XE xe;DiagnosticGPU gpu;gpu.map.bar.resize(0x1000000/4);gpu.map.bar[0xA188/4]=1; + xe.physicalGPU=&gpu;readout=true;allocationFailure=fail;allocations=frees=0; + xe.xeProfile=XELP::identify(0x46a68086,12);xe.diagnose(); + assert(xe.adlpLoadOnly && !xe.initialized && gpu.map.releases==1); + assert(allocations==1 && frees==unsigned(!fail) && gpu.snapshots==unsigned(!fail)); + assert(gpu.allocationFailed==fail);++cases; + } + readout=false;allocationFailure=false; for(unsigned index:{1U,2U})for(unsigned bad=0;bad<4;++bad){ std::vector image(0x50000);auto base=reinterpret_cast(image.data()); uint8_t fb[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x83,0xec,0x10,0x48,0x89,0xf3,0x49,0x89,0xfe}; diff --git a/tests/adls_panel_pwm.py b/tests/adls_panel_pwm.py index 64a9bba8..92c3748a 100644 --- a/tests/adls_panel_pwm.py +++ b/tests/adls_panel_pwm.py @@ -38,6 +38,13 @@ }; struct XE { static XE*callbackXE; + bool display13FramebufferRequested=false,display13PanelPWMReady=false; + struct {unsigned panel=0;}adlnOutputWiring; + static bool display13PanelHealthy(){assert(false);return false;} + static bool display13PanelCapture(void*){assert(false);return false;} + static uint32_t display13PanelBrightness(void*,uint32_t){assert(false);return 1;} + static void display13PanelPWMEnable(void*){assert(false);} + bool adlsDisplaySelected=true,adlsPanelPWMReady=true,tglActive=true;XELP::PanelPWM adlsPanelPWM; XELP::VBT::Ports firmwarePorts;AlderLakeS::Wiring adlsWiring; struct {XELP::Family family=XELP::Family::AlderLakeS;}xeProfile; diff --git a/tests/alderlake_n.cpp b/tests/alderlake_n.cpp index 1942776f..eb313e85 100644 --- a/tests/alderlake_n.cpp +++ b/tests/alderlake_n.cpp @@ -249,6 +249,26 @@ int main() { auto rb=AlderLakeN::readDisplay(b,true); assert(rb.status==AlderLakeN::ReadoutStatus::Complete && rb.activePipes==2 && rb.hdmiBPipes==2); for(auto offset:b.reads)assert(offset!=0x60400 && offset!=0x62400); + // Bootstrap reuses one snapshot. A new pipe, rejected admission, and an + // unreadable BAR must each discard the preceding certificate completely. + auto reused=ra; + AlderLakeN::readDisplay(b,true,reused); + assert(reused.version==2 && reused.status==rb.status && reused.count==rb.count); + assert(reused.poweredPipes==2 && reused.activePipes==2 && reused.hdmiBPipes==2); + for(unsigned i=0;i<160;++i) { + assert(reused.registers[i].offset==rb.registers[i].offset); + assert(reused.registers[i].value==rb.registers[i].value); + } + AlderLakeN::readDisplay(absent,false,reused); + assert(absent.reads.empty() && reused.status==AlderLakeN::ReadoutStatus::NotAdmitted); + assert(!reused.count && !reused.poweredPipes && !reused.activePipes && !reused.hdmiBPipes); + for(auto reg:reused.registers)assert(!reg.offset && !reg.value); + AlderLakeN::readDisplay(a,true,reused); + IO badReuse;badReuse.regs[0x45404]=UINT32_MAX; + AlderLakeN::readDisplay(badReuse,true,reused); + assert(reused.status==AlderLakeN::ReadoutStatus::Unavailable && reused.count==1); + assert(!reused.poweredPipes && !reused.activePipes && !reused.hdmiBPipes); + for(unsigned i=1;i<160;++i)assert(!reused.registers[i].offset && !reused.registers[i].value); IO dp;dp.regs[0x45404]=0x1005;dp.regs[0x61400]=0x92000000; assert(AlderLakeN::readDisplay(dp,true).hdmiBPipes==0); IO unavailable;unavailable.regs[0x45404]=UINT32_MAX; diff --git a/tests/boot_stack_limits.py b/tests/boot_stack_limits.py new file mode 100644 index 00000000..168de087 --- /dev/null +++ b/tests/boot_stack_limits.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Reject old frames and new oversized functions in any translation unit.""" +from pathlib import Path +import importlib.util,tempfile,subprocess +root=Path(__file__).resolve().parents[1] +spec=importlib.util.spec_from_file_location('stack_limits',root/'tools/check_boot_stack.py') +module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) +def line(symbol,size,kind='static'): + return f'/fixture.cpp:1:{symbol}\t{size}\t{kind}\n' +def rejects(paths): + try:module.check(paths) + except ValueError:return + raise AssertionError('unsafe stack records accepted') +with tempfile.TemporaryDirectory(prefix='wex-stack-limits-') as tmp: + p=Path(tmp);core=p/'core.su';extra=p/'other.su' + baseline=''.join(line(s,24) for s in module.REQUIRED) + core.write_text(baseline);extra.write_text(line('_new_callback',2048)) + assert module.check([core,extra])['_new_callback']['bytes']==2048 + # Formerly only kern_xe was checked; a new frame in another TU must fail. + extra.write_text(line('_new_callback',2049));rejects([core,extra]) + extra.write_text(line('_new_callback',24,'dynamic'));rejects([core,extra]) + old={ + '_ZN2XE23wrapAlderLakeNPowerInitEPvS0_':4856, + '_ZN2XE27wrapAlderLakeNSetupWorkLoopEP9IOServiceP10IOWorkLoop':2440, + '_ZN12_GLOBAL__N_124display13FBInspectMemoryEP9IOServicebjb':2408, + '_ZN2XE19wrapDisplay13TCModeEP8OSObjectj':1816, + '_ZL15tglHostCSBFatalPKcPKjPv':6504, + '_ZN2XE21wrapTGLGuCHeaderPanicEPv':3992, + '_ZN13Display13Boot9normalizeI_fixture':2328, + } + for symbol,size in old.items(): + extra.write_text(line(symbol,size));rejects([core,extra]) + # Shared inline bodies can recur; every occurrence must satisfy its budget. + core.write_text(baseline+line('_shared',128));extra.write_text(line('_shared',256)) + assert module.check(p)['_shared']['bytes']==256 + extra.write_text(line('_shared',4096));rejects(p) + core.write_text('');extra.write_text('');rejects(p) + # Optimized data-only objects need no frame report; real code always does. + for name,source in [('data','int value=7;'),('code','int f(int a){return a+1;}')]: + cpp=p/(name+'.cpp');obj=p/(name+'.o');cpp.write_text(source) + subprocess.run(['clang++','-target','x86_64-apple-macos10.15','-O2','-fstack-usage','-c',str(cpp),'-o',str(obj)],check=True) + if name=='data': + obj.with_suffix('.su').unlink(missing_ok=True) + assert module.stack_files([obj])==[] + else: + assert module.stack_files([obj])==[obj.with_suffix('.su')] + obj.with_suffix('.su').unlink() + try:module.stack_files([obj]) + except ValueError:pass + else:raise AssertionError('missing report for emitted code accepted') +print('PASS all-TU frame limits: unknown/dynamic frames, 7 old large frames, duplicate bodies, missing records') diff --git a/tests/compat_bundle_version.py b/tests/compat_bundle_version.py new file mode 100644 index 00000000..2152dd65 --- /dev/null +++ b/tests/compat_bundle_version.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import plistlib +import sys +import tempfile +import unittest +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'tools')) +from check_compat_version import check + + +class CompatVersionTests(unittest.TestCase): + def validate(self, current, minimum): + with tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / 'Info.plist' + path.write_bytes(plistlib.dumps({ + 'CFBundleIdentifier': 'org.b00t0x.AppleIntelTGLCompat', + 'CFBundleVersion': current, 'OSBundleCompatibleVersion': minimum})) + check(path) + + def test_release_bump_preserves_old_clients(self): + for current in ['0.5.0', '0.6.0', '0.7.0', '1.0.0']: + with self.subTest(current=current): + self.validate(current, '0.5.0') + + def test_compatibility_floor_bump_rejects_old_clients(self): + with self.assertRaisesRegex(ValueError, 'no longer accepts'): + self.validate('0.6.0', '0.6.0') + + def test_provider_too_old(self): + with self.assertRaisesRegex(ValueError, 'no longer accepts'): + self.validate('0.4.0', '0.4.0') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index b1452155..8580df4f 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -5,6 +5,13 @@ #include #include #include +// Independent scratch per call for the existing hardware-behavior cases. +// Explicit reuse/alias checks below exercise the persistent caller storage. +template Display13Boot::Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, + const Display13HDMIWiring &w,const XELPMemory::Layout &memory) { + Display13Boot::NormalizeScratch scratch; + return Display13Boot::normalize(io,boot,admitted,w,memory,scratch); +} struct IO { std::map regs; std::vector writes; @@ -173,7 +180,7 @@ static void tcComposition() { for(const auto &c:cases)for(bool owner:{false,true}) { IO io;const auto wiring=multiGOP(io,c.ports,c.count,c.dp,owner); const auto boot=AlderLakeN::readDisplay(io,true);const auto before=io.regs; - const auto r=Display13Boot::normalize(io,boot,true,wiring,memory); + const auto r=normalize(io,boot,true,wiring,memory); assert(r.status==Display13Boot::Status::Complete && r.after==1); for(auto reg:{0x45404U,0x45454U,0x45444U,0x60400U,0x60540U,0x46140U,0x70180U,0x7027cU}) assert(io.regs[reg]==before.at(reg)); @@ -205,7 +212,7 @@ static void tcComposition() { if(failure==1)io.stuckPLL=true; if(failure==2)io.changeRegister=0x46048; // Retained TC3 PLL changes. if(failure==3)io.failWrite=Display13TC::Clock::Gate; - const auto r=Display13Boot::normalize(io,boot,true,wiring,memory); + const auto r=normalize(io,boot,true,wiring,memory); assert(r.status!=Display13Boot::Status::Complete && io.regs[0x7127c]); assert(io.regs[0x64300]&64); // Failure never returns the affected PHY. assert(io.regs[0x70180]&0x80000000U); @@ -216,7 +223,7 @@ static void tcComposition() { if(bad==0)io.regs[0x64310]=0x80000000; if(bad==1)io.regs[0x161500]=1; if(bad==2)io.regs[0x46144]=0x40000000; // Combo selector on a TC port. - assert(Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,wiring,memory).status==Display13Boot::Status::Topology); + assert(normalize(io,AlderLakeN::readDisplay(io,true),true,wiring,memory).status==Display13Boot::Status::Topology); assert(io.writes.empty()); } puts("PASS mixed Combo/TC GOP handoff into normal Mode/clock start, peer preservation and failure retention"); @@ -235,7 +242,7 @@ static void scaledBoot() { setup(io,0,scaler);setup(io,2,1-scaler); auto boot=AlderLakeN::readDisplay(io,true); assert(boot.status==AlderLakeN::ReadoutStatus::Complete && boot.count<=160); - auto n=Display13Boot::normalize(io,boot,true,w,memory); + auto n=normalize(io,boot,true,w,memory); assert(n.status==Display13Boot::Status::Complete && n.after==1); assert(io.regs[0x68180+scaler*0x100]==0x80000000); assert(!io.regs[0x69180+(1-scaler)*0x100]); @@ -263,13 +270,13 @@ static void scaledBoot() { if(fault==8)io.regs[0x60000]=0x400004FF; if(fault==9)io.regs[0x60000]=0x0100077F; if(fault==10)io.regs[0x6000C]=0x046401FF; - auto n=Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory); + auto n=normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory); if(n.status!=Display13Boot::Status::Topology || !io.writes.empty())fprintf(stderr,"fault=%u status=%u writes=%zu\n",fault,unsigned(n.status),io.writes.size()); assert(n.status==Display13Boot::Status::Topology && io.writes.empty()); } IO drift;auto w=multiGOP(drift,ports,3,1U<<5);setup(drift,0,0); drift.changeRegister=0x68174; - auto n=Display13Boot::normalize(drift,AlderLakeN::readDisplay(drift,true),true,w,memory); + auto n=normalize(drift,AlderLakeN::readDisplay(drift,true),true,w,memory); assert(n.status==Display13Boot::Status::PeerChanged && drift.writes.size()==2); puts("PASS scaled multi-GOP: binding, full destination, primary preservation, secondary detach and drift rejection"); } @@ -277,7 +284,7 @@ static void unusedTCBoot() { const unsigned ports[] {5,4}; const auto setup=[&](IO &io) { auto w=multiGOP(io,ports,2,1U<<5);w.hdmi|=1U<<3;w.ddc[3]=9; - assert(Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory).status==Display13Boot::Status::Complete); + assert(normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory).status==Display13Boot::Status::Complete); io.writes.clear();io.regs[0x64300]=0xC0;io.regs[0x161500]=0xC; io.regs[0x4610C]=io.regs[0x46038]=0;io.regs[Display13TC::Clock::Gate]|=1U<<12; io.regs[0x45444]|=3U<<6; @@ -320,12 +327,24 @@ static void unusedTCBoot() { puts("PASS unused fixed TC firmware ownership: cold-only release, peers, live-resource refusal and readback"); } int main() { + // Repeated normalizations must reset old witnesses and read a fresh final + // snapshot. Aliasing that storage with the live input must fail before IO. + Display13Boot::NormalizeScratch scratch; + for(unsigned trial=0;trial<3;++trial) { + IO fresh;auto boot=AlderLakeN::readDisplay(fresh,true); + const auto result=Display13Boot::normalize(fresh,boot,true,wiring,memory,scratch); + assert(result.status==Display13Boot::Status::Complete && result.writes==10); + assert(boot.activePipes==3 && scratch.after.activePipes==1); + } + IO alias;AlderLakeN::readDisplay(alias,true,scratch.after); + const auto rejected=Display13Boot::normalize(alias,scratch.after,true,wiring,memory,scratch); + assert(rejected.status==Display13Boot::Status::Unavailable && alias.writes.empty()); unusedTCBoot(); scaledBoot(); prefixCertificates(); tcComposition(); IO io;const auto before=io.regs; - const auto r=Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,wiring,memory); + const auto r=normalize(io,AlderLakeN::readDisplay(io,true),true,wiring,memory); assert(r.status==Display13Boot::Status::Complete && r.after==1 && r.stage==7 && r.writes==10); assert(!io.regs[0x7127C] && !io.regs[0x7137C] && !io.regs[0x7117C]); for(auto w:io.writes)assert(w.offset==0x71180 || w.offset==0x7119C || w.offset==0x71008 || @@ -335,25 +354,25 @@ int main() { assert(io.regs[pair.first]==pair.second); assert(io.regs[0x46140]==0x10000000 && !io.regs[0x46144] && io.regs[0x61200]==0x1000); IO frames;frames.noFrames=true; - assert(Display13Boot::normalize(frames,AlderLakeN::readDisplay(frames,true),true,wiring,memory).status==Display13Boot::Status::Timeout); + assert(normalize(frames,AlderLakeN::readDisplay(frames,true),true,wiring,memory).status==Display13Boot::Status::Timeout); assert(frames.writes.size()==2 && frames.regs[0x7127C]); IO stop;stop.noStop=true; - assert(Display13Boot::normalize(stop,AlderLakeN::readDisplay(stop,true),true,wiring,memory).status==Display13Boot::Status::Timeout); + assert(normalize(stop,AlderLakeN::readDisplay(stop,true),true,wiring,memory).status==Display13Boot::Status::Timeout); assert(stop.writes.size()==3 && stop.regs[0x7127C]); IO idle;idle.noIdle=true; - assert(Display13Boot::normalize(idle,AlderLakeN::readDisplay(idle,true),true,wiring,memory).status==Display13Boot::Status::Timeout); + assert(normalize(idle,AlderLakeN::readDisplay(idle,true),true,wiring,memory).status==Display13Boot::Status::Timeout); assert(idle.writes.size()==6 && idle.regs[0x7127C]); IO peer;peer.changePeer=true; - assert(Display13Boot::normalize(peer,AlderLakeN::readDisplay(peer,true),true,wiring,memory).status==Display13Boot::Status::PeerChanged); + assert(normalize(peer,AlderLakeN::readDisplay(peer,true),true,wiring,memory).status==Display13Boot::Status::PeerChanged); assert(peer.writes.size()==2 && peer.regs[0x7127C]); for(auto reg:{0x71080U,0x71280U,Display13FBIRQ::DSBControl(1,0),0x60404U}) { IO foreign;foreign.regs[reg]=0x80000001; - assert(Display13Boot::normalize(foreign,AlderLakeN::readDisplay(foreign,true),true,wiring,memory).status==Display13Boot::Status::Topology); + assert(normalize(foreign,AlderLakeN::readDisplay(foreign,true),true,wiring,memory).status==Display13Boot::Status::Topology); assert(foreign.writes.empty()); } IO port;port.regs[0x61400]=0x88030500; - assert(Display13Boot::normalize(port,AlderLakeN::readDisplay(port,true),true,wiring,memory).status==Display13Boot::Status::Topology && port.writes.empty()); - IO denied;assert(Display13Boot::normalize(denied,AlderLakeN::readDisplay(denied,true),false,wiring,memory).status==Display13Boot::Status::Unavailable && denied.writes.empty()); + assert(normalize(port,AlderLakeN::readDisplay(port,true),true,wiring,memory).status==Display13Boot::Status::Topology && port.writes.empty()); + IO denied;assert(normalize(denied,AlderLakeN::readDisplay(denied,true),false,wiring,memory).status==Display13Boot::Status::Unavailable && denied.writes.empty()); // Three firmware GOPs, all six physical-port/pipe permutations. for(unsigned a=0;a<3;++a)for(unsigned b=0;b<3;++b)for(unsigned c=0;c<3;++c) { if(a==b || a==c || b==c)continue; @@ -365,7 +384,7 @@ int main() { t.regs[0x46140+4*p]=(ports[p]+1)<<28; t.regs[0x64000+0x100*ports[p]]=0x80000000; } - auto n=Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,wiring,memory); + auto n=normalize(t,AlderLakeN::readDisplay(t,true),true,wiring,memory); assert(n.status==Display13Boot::Status::Complete && n.before==7 && n.after==1 && n.writes==20); assert(t.regs[0x64000+0x100*a]&0x80000000); assert(!t.regs[0x7127C] && !t.regs[0x7227C] && t.regs[0x7027C]); @@ -379,7 +398,7 @@ int main() { t.regs[0x60000+bank]=((width+280-1)<<16)|(width-1); t.regs[0x6000C+bank]=((height+45-1)<<16)|(height-1); } - assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,wiring,memory).status==Display13Boot::Status::Complete); + assert(normalize(t,AlderLakeN::readDisplay(t,true),true,wiring,memory).status==Display13Boot::Status::Complete); } unsigned combinations=0; for(unsigned a=0;a<2;++a)for(unsigned parked:{1U,2U})for(unsigned dpMask=0;dpMask<4;++dpMask) @@ -397,7 +416,7 @@ int main() { } const auto boot=AlderLakeN::readDisplay(t,true);const auto before=t.regs; assert(boot.version==2 && boot.count==157); - const auto result=Display13Boot::normalize(t,boot,true,mixed,memory); + const auto result=normalize(t,boot,true,mixed,memory); const bool parkDP=dpMask&(1U<<(1-a)); assert(result.status==Display13Boot::Status::Complete && result.after==1 && result.writes==(parkDP?11U:10U)); for(auto reg:{0x60400U,0x60540U,0x60030U,0x60034U,0x60040U,0x60044U,0x46140U,0x78000U,0x78004U}) @@ -424,23 +443,23 @@ int main() { auto reg=bad.offset+(bad.offset>=0x78000?p*0x200:p*0x1000); auto val=bad.value;if(bad.offset==0x60400)val+=p<<27; t.regs[reg]=val; - assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology); + assert(normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology); assert(t.writes.empty()); } for(auto reg:{0x64000U,0x64100U,0x46140U,0x46144U}) { IO t;dp(t,0,0,4,true);dp(t,1,1,4,true);t.regs[reg]=0; - assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology && t.writes.empty()); + assert(normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology && t.writes.empty()); } // Snapshot freshness and peer preservation cover DP metadata as well. for(auto reg:{0x60540U,0x60030U,0x60034U,0x60040U,0x60044U,0x46140U,0x78004U}) { IO t;dp(t,0,0,4,true);dp(t,1,1,4,true);const auto boot=AlderLakeN::readDisplay(t,true); t.regs[reg]^=1; - assert(Display13Boot::normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::Readback && t.writes.empty()); + assert(normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::Readback && t.writes.empty()); t.regs[reg]^=1;t.changeRegister=reg; - assert(Display13Boot::normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::PeerChanged && t.writes.size()==2); + assert(normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::PeerChanged && t.writes.size()==2); } IO tp;dp(tp,0,0,4,true);dp(tp,1,1,4,true);tp.failWrite=0x61540; - auto failure=Display13Boot::normalize(tp,AlderLakeN::readDisplay(tp,true),true,mixed,memory); + auto failure=normalize(tp,AlderLakeN::readDisplay(tp,true),true,mixed,memory); assert(failure.status==Display13Boot::Status::Timeout && failure.stage==4 && tp.writes.size()==7 && tp.regs[0x7127C]); assert(Display13FBTopology::bootDDI(mixed,tp.regs[0x60400])==UINT32_MAX); // Runtime stays closed. printf("PASS GOP bootstrap: %u HDMI/DP combinations, STOP/transport/idle ordering, peer/M-N preservation and bounded failures\n",combinations); diff --git a/tests/display13_boot_aux_fixture.cpp b/tests/display13_boot_aux_fixture.cpp index 12b94186..33fca5ea 100644 --- a/tests/display13_boot_aux_fixture.cpp +++ b/tests/display13_boot_aux_fixture.cpp @@ -33,24 +33,25 @@ struct FaultIO:Display13FBBootIO { }; int main() { unsigned cases=0; - for(unsigned ddi=0;ddi<2;++ddi)for(bool requested:{false,true})for(bool peer:{false,true}) { + for(unsigned ddi=0;ddi<2;++ddi)for(bool requested:{false,true})for(bool peer:{false,true})for(bool panel:{false,true}) { IOMemoryMap map;firmware(map,ddi,requested);const auto other=1-ddi; + if(panel)map.at(0x60540)|=64; if(peer)map.at(AUX)|=3U<<(2*other); const auto initial=map.at(AUX);Display13FBBootIO io{&map};NativeBridge bridge;MainLinkAUX link; assert(bridge.begin(io,ddi,3).status==Status::Complete); const auto snapshot=readDisplay(io,true);assert(snapshot.status==ReadoutStatus::Complete && snapshot.count==157); - const auto heldResult=holdDPBootAUX(io,snapshot,bridge,link,ddi); + const auto heldResult=holdDPBootAUX(io,snapshot,bridge,link,ddi,panel); assert(heldResult.status==Status::Complete && heldResult.writes==(requested?0U:1U)); assert(!link.owner && link.bootPending && link.port==ddi && !bridge.nativeAUXRefs(ddi)); assert(link.lease.owner==&bridge.state && bridge.state.refs[ddi?6:8]==1); assert(link.begin(io,bridge,7,ddi).status==Status::NotReady && link.end(io,bridge,7,ddi).status==Status::NotReady); - assert(holdDPBootAUX(io,readDisplay(io,true),bridge,link,ddi).status==Status::NotReady); + assert(holdDPBootAUX(io,readDisplay(io,true),bridge,link,ddi,panel).status==Status::NotReady); assert(bridge.getAUX(io,ddi).status==Status::Complete && bridge.putAUX(io,ddi).status==Status::Complete); assert(bridge.state.refs[ddi?6:8]==1 && !bridge.nativeAUXRefs(ddi)); const auto after=readDisplay(io,true);const auto requestBefore=map.at(AUX); - assert(adoptDPBootAUX(io,after,bridge,link,7,ddi).status==Status::Complete); + assert(adoptDPBootAUX(io,after,bridge,link,7,ddi,panel).status==Status::Complete); assert(link.owner==7 && !link.bootPending && !bridge.nativeAUXRefs(ddi) && map.at(AUX)==requestBefore); - assert(adoptDPBootAUX(io,after,bridge,link,8,ddi).status==Status::NotReady); + assert(adoptDPBootAUX(io,after,bridge,link,8,ddi,panel).status==Status::NotReady); assert(bridge.adoptBootPath(9,0,ddi).status==Status::Complete); assert(link.begin(io,bridge,7,ddi).status==Status::Complete); assert(link.end(io,bridge,7,ddi).status==Status::NotReady); // Live GOP cannot release. @@ -59,7 +60,7 @@ int main() { assert(map.at(AUX)==initial); // Borrowed firmware/peer requests survive; only our request clears. assert(bridge.putDDI(io,ddi).status==Status::Complete && bridge.putPG(io,1).status==Status::Complete); for(auto refs:bridge.state.refs)assert(!refs); - io.write(0x60540,0);assert(map.at(0x60540)==0x80040300); // Boot IO cannot write link registers. + io.write(0x60540,0);assert(map.at(0x60540)==(0x80040300|(panel?64U:0U))); // Boot IO cannot write link registers. assert(!io.readable(0x1000000) && !Display13FBBootIO{nullptr}.readable(0)); ++cases; } diff --git a/tests/display13_linear_dp.cpp b/tests/display13_linear_dp.cpp index d39357af..14a2afc1 100644 --- a/tests/display13_linear_dp.cpp +++ b/tests/display13_linear_dp.cpp @@ -66,6 +66,16 @@ int main() { assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); } } + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned sr=0;sr<2;++sr) { + auto io=firmware(pipe,ddi,2,true);io.regs[0x60540+0x1000*pipe]|=sr<<6; + auto boot=readDisplay(io,true);LinearPlan plan; + assert(dpSource(boot,pipe,ddi)==!sr); + assert(dpSource(boot,pipe,ddi,nullptr,true)); + assert(linearPlan(boot,plan,ddi,GOPProtocol::EDP)==LinearStatus::Ready); + auto r=reinitializeLinear(io,boot,true,ddi,GOPProtocol::EDP); + assert(r.status==LinearStatus::Success && dpSourceUnchanged(io,boot,pipe,ddi,0,nullptr,true)); + io.regs[0x60540+0x1000*pipe]^=64;assert(!dpSourceUnchanged(io,boot,pipe,ddi,0,nullptr,true)); + } auto bad=firmware(0,0,4,true);bad.regs[0x60540]=0x80000000; auto r=reinitializeLinear(bad,readDisplay(bad,true),true,0,GOPProtocol::DP); assert(r.status==LinearStatus::InvalidDPSource && bad.writes.empty()); diff --git a/tests/display13_panel.cpp b/tests/display13_panel.cpp new file mode 100644 index 00000000..6759cf41 --- /dev/null +++ b/tests/display13_panel.cpp @@ -0,0 +1,69 @@ +#include "kern_display13_panel.hpp" +#include "kern_display13_firmware.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_xelp_probe.hpp" +#include +#include +#include +#include +using namespace XELP; +struct IO { + std::map r; + std::vector writes; + bool drop=false; + uint32_t read(uint32_t a){return r.at(a);} + void write(uint32_t a,uint32_t v){writes.push_back(a);if(!drop)r.at(a)=v;} + bool readable(uint32_t){return false;} +}; +int main() { + for(auto family:{Family::AlderLakeP,Family::RaptorLakeP,Family::AlderLakeN})for(unsigned ddi=0;ddi<2;++ddi) { + VBT::Ports v;v.status=VBT::Status::Valid;v.present=1U<(dst+0x20+24*ddi); + assert(cfg[0]==ddi && !cfg[1] && !cfg[2] && cfg[4]==2 && cfg[5]==0x18 && w.matches(cfg)); + assert(Display13FBTopology::supportedWiring(w,true) && Display13FBTopology::deferredBootPrepare(w,false)); + assert(Display13FBTopology::bootDDI(w,0x82000000|((ddi+1)<<27),true)==ddi); + for(unsigned bad=0;bad<10;++bad) { + auto b=v; + switch(bad) {case 0:b.panel.controller=1;break;case 1:b.panel.backlightType=3;break; + case 2:b.panel.pwmPresent=false;break;case 3:b.unmapped=1;break;case 4:b.panel.valid=false;break; + case 5:b.port[ddi].flags=4;break;case 6:b.port[ddi].capabilities=1;break; + case 7:b.port[ddi].aux=0x90;break;case 8:b.present|=1U<<(1-ddi);b.port[1-ddi]=b.port[ddi];break; + case 9:b.panel.ddi=2;break;} + assert(!PanelPower::nativePPS0(b,family));assert(!VBT::display13Fixed(b,0x7b,family).panel); + } + auto overlap=w;overlap.dp=w.panel;assert(!overlap.described());overlap=w;overlap.panel=3;assert(!overlap.described()); + ProbeBinding binding;binding.controller=1;binding.access=2;binding.platform=reinterpret_cast(dst); + binding.config.profile=identify(family==Family::AlderLakeN?0x46D28086:0xA7A88086,4); + binding.config.combo=w.panel;binding.config.comboBackend=3; + IO io;assert(probeMode(io,binding,2,reinterpret_cast(cfg),cfg)==1); + assert(!probeMode(io,binding,3,reinterpret_cast(cfg),cfg)); + // BIOS PWM wins over VBT; a large period must not overflow at full brightness. + io.r={{0xC7208,0x083401f4},{0xC720C,0x138801f4},{0xC7204,0x67},{0xC8254,120000},{0xC8250,0xa0000000},{0xC2014,256}}; + Display13Panel::State state;assert(state.capture(io,p));uint32_t duty=0; + assert(state.pwm.period==120000 && state.pwm.polarity==(1U<<29)); + assert(state.pwm.duty(65535,duty) && duty==120000);assert(state.pwm.duty(0,duty) && !duty); + assert(!state.pwm.duty(65536,duty));io.r[0xC8254]=0;assert(state.capture(io,p) && state.pwm.period==120000); + state={};assert(state.capture(io,p) && state.pwm.period==120000); + for(auto reg:{0xC7208U,0xC720CU,0xC7204U,0xC8254U,0xC8250U,0xC2014U}) { + auto bad=io;bad.r[reg]=~0U;state={};assert(!state.capture(bad,p) && !state.captured); + } + assert(io.writes.empty()); + } + for(bool initial:{false,true})for(bool failure:{false,true}) { + IO io;io.r[0xC2020]=0x1234|(initial?1U<<29:0);io.drop=failure;Display13Panel::OnGate gate; + assert(gate.begin(io)==(initial || !failure)); + io.drop=false;io.r[0xC2020]|=1U<<17; + assert(gate.finish(io));assert(io.r[0xC2020]==(0x21234|(initial?1U<<29:0))); + assert(io.writes.size()==(initial?0U:2U)); + } + IO io;io.r[0xC2020]=~0U;Display13Panel::OnGate gate;assert(!gate.begin(io) && gate.finish(io) && io.writes.empty()); + puts("PASS Display13 panel topology/probe/PPS0 qualification, PWM preservation/fallback, WA cleanup"); +} diff --git a/tests/display13_panel_runtime.py b/tests/display13_panel_runtime.py new file mode 100644 index 00000000..3ee64954 --- /dev/null +++ b/tests/display13_panel_runtime.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +"""Execute production PPS/PWM wrappers with actual PG bridge and MMIO adapter.""" +from pathlib import Path +import re,sys,subprocess,tempfile,hashlib +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['display13PanelTarget','display13PanelPowerReference','display13PanelCapture','display13PanelTiming','display13PanelBrightness','display13PanelPWMEnable','wrapDisplay13PanelPower'] +methods='\n'.join(re.search(r'(?:bool|void|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) +io=s[s.index('struct Display13FBPowerIO {'):s.index('// The static boot reader')] +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + installer=s[s.index('void XE::processDisplay13Panel('):s.index('bool XE::display13PanelTarget(')] + for name,off in [('entry',0x48652),('configCall',0x4866b)]: + expected=bytes(int(x,0) for x in re.search(name+r'\[\]=\{([^}]+)',installer).group(1).split(',')) + assert b[off:off+len(expected)]==expected + # Native panel on/off and success return: the wrapper must surround both + # control writes and the donor's wait, not just a brightness callback. + assert b[0x487df:0x487e2]==bytes.fromhex('83 ca 03') + assert b[0x4875e:0x48761]==bytes.fromhex('83 e2 f6') + assert b[0x48abc:0x48abe]==bytes.fromhex('31 c0') +code=(r/'tests/display13_panel_runtime_fixture.cpp').read_text().replace('// PRODUCTION_IO',io).replace('// METHODS',methods) +with tempfile.TemporaryDirectory(prefix='display13-panel-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-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')]+sys.argv[1:],check=True) diff --git a/tests/display13_panel_runtime_fixture.cpp b/tests/display13_panel_runtime_fixture.cpp new file mode 100644 index 00000000..09137865 --- /dev/null +++ b/tests/display13_panel_runtime_fixture.cpp @@ -0,0 +1,143 @@ +#include "kern_display13_panel.hpp" +#include "kern_display13_power_bridge.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_output_wiring.hpp" +#include +#include +#include +#include +#include +#include +#include +struct IOMemoryMap { + std::vector memory=std::vector(0x400000); + uintptr_t getVirtualAddress(){return reinterpret_cast(memory.data());} + uint64_t getLength(){return 0x1000000;} + uint32_t &at(uint32_t reg){return memory.at(reg/4);} +}; +struct Lock {bool held=false;}; +void IOLockLock(Lock *p){assert(p && !p->held);p->held=true;} +void IOLockUnlock(Lock *p){assert(p && p->held);p->held=false;} +void IODelay(unsigned){} +bool display13TCAuxSupported(){return false;} +bool display13TCPrepareAUX(IOMemoryMap*,unsigned){assert(false);return false;} +bool display13TCHealthyAUX(IOMemoryMap*,unsigned){assert(false);return false;} +// PRODUCTION_IO +struct Context { + IOMemoryMap *map=nullptr;void *controller=nullptr,*power=nullptr,*access=nullptr; + Lock *lock=nullptr,*panelLock=nullptr;Display13Power::NativeBridge bridge;Display13Panel::State panel; + bool clockStopped=false,clockFaulted=false,panelFaulted=false; +}display13FBPower; +bool mapping=true;bool adlnWriterMappingBound(){return mapping;} +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE { + static XE *callbackXE; + bool display13FramebufferRequested=true,display13PanelPowerReady=true; + Display13OutputWiring adlnOutputWiring;XELP::VBT::Ports firmwarePorts; + XELP::Profile xeProfile;void *adlnTopologyController=nullptr,*adlnPlatform=nullptr; + uintptr_t orgDisplay13PanelPower=0; + static bool display13PanelTarget(void*);static bool display13PanelPowerReference(bool); + static bool display13PanelCapture(void*);static void display13PanelTiming(void*,bool); + static uint32_t display13PanelBrightness(void*,uint32_t);static void display13PanelPWMEnable(void*); + static uint32_t wrapDisplay13PanelPower(void*,uint32_t); +};XE *XE::callbackXE; +// METHODS +unsigned calls=0;bool timeout=false;uint32_t nativeResult=0; +uint32_t nativePower(void *controller,uint32_t state) { + auto &c=display13FBPower;assert(controller==c.controller && !c.lock->held && c.panelLock->held);++calls; + assert(c.bridge.nativeRefs(1)==2 && Display13FBMemory::field(c.power,0x20)==2); + if(state==2)assert(c.map->at(0xC2020)&(1U<<29)); + // Native config invokes the property callback, then proceeds with sequencing. + XE::display13PanelTiming(controller,true); + if(!timeout)c.map->at(0xC7200)=state==2?0x80000000:0; + c.map->at(0xC2020)|=0x10000;return nativeResult; +} +templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +struct Fixture { + IOMemoryMap map;Lock owner,pps;XE xe;alignas(8) uint8_t ctl[0x1c20]{},power[0x80]{},access[0x80]{},platform[0x1c0]{}; + Fixture() { + display13FBPower.~Context();new(&display13FBPower)Context;auto &c=display13FBPower;c.map=↦c.controller=ctl;c.power=power;c.access=access;c.lock=&owner;c.panelLock=&pps; + xe.callbackXE=&xe;xe.adlnTopologyController=ctl;xe.adlnPlatform=platform;xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.panel=1; + xe.xeProfile=XELP::identify(0xA7A88086,4);xe.orgDisplay13PanelPower=reinterpret_cast(nativePower); + auto &v=xe.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78C6,10,0,0x40,0,0}; + v.panel.valid=v.panel.pwmPresent=v.panel.backlightValid=true;v.panel.ddi=0;v.panel.backlightType=2;v.panel.pwmHz=200; + put(ctl,0xC8,static_cast(power));put(ctl,0xC40,static_cast(access));put(ctl,0xD40,static_cast(platform)); + map.at(Display13Power::Driver)=0xFC0F;map.at(Display13Power::DDI)=15;map.at(Display13Power::Fuses)=0x8e380000; + map.at(0xC7208)=0x083401f4;map.at(0xC720C)=0x138801f4;map.at(0xC7204)=0x67; + map.at(0xC8250)=0xa0000000;map.at(0xC8254)=120000;map.at(0xC8258)=60000;map.at(0xC2020)=0x1234; + Display13FBPanelIO io{&map};assert(c.bridge.begin(io,0,1).status==Display13Power::Status::Complete); + assert(Display13Power::publishObject(c.bridge,power,sizeof(power),ctl,sizeof(ctl))); + calls=0;timeout=false;nativeResult=0;mapping=true; + } + void balanced(){auto &c=display13FBPower;assert(c.bridge.nativeRefs(1)==1 && Display13FBMemory::field(power,0x20)==1 && !owner.held && !pps.held);} +}; +unsigned powerResult=0,trainingResult=0;std::vector lightStages; +uint32_t lightPower(void*,uint32_t state){assert(state==2);lightStages.push_back(1);return powerResult;} +void lightClocks(void*,void*,void*){lightStages.push_back(2);} +uint32_t lightTrain(void*,void *request){assert(!request);lightStages.push_back(3);return trainingResult;} +void lightPlane(void*,bool enabled){assert(!enabled);lightStages.push_back(4);} +void lightScaler(void*,void*){lightStages.push_back(5);} +void lightPipe(void*,void*,void*,const void*){lightStages.push_back(6);} +uint32_t lightBacklight(void*,uint32_t){lightStages.push_back(7);return 0;} +void lightPWM(void*){lightStages.push_back(8);} +void lightWrite(void *access,uint64_t reg,uint32_t value){assert(access==display13FBPower.access);display13FBPower.map->at(reg)=value;if(reg==0xC8250)lightStages.push_back(8);} +void lightDelay(unsigned){} +void jump(uint8_t *site,uintptr_t target){site[0]=0x48;site[1]=0xb8;memcpy(site+2,&target,8);site[10]=0xff;site[11]=0xe0;} +void redirect(uint8_t *image,unsigned call,unsigned stub,uintptr_t target){assert(image[call]==0xe8);jump(image+stub,target);int32_t delta=stub-call-5;memcpy(image+call+1,&delta,4);} +int main(int argc,char **argv) { + for(bool gate:{false,true})for(bool fail:{false,true}) { + Fixture f;if(gate)f.map.at(0xC2020)|=1U<<29;timeout=fail; + assert(XE::wrapDisplay13PanelPower(f.ctl,2)==(fail?kIOReturnNotReady:0)); + f.balanced();assert(calls==1 && f.map.at(0xC2020)==(0x11234|(gate?1U<<29:0))); + assert(display13FBPower.panelFaulted==fail); + if(!fail){assert(!XE::wrapDisplay13PanelPower(f.ctl,0));f.balanced();assert(!f.map.at(0xC7200));} + } + for(unsigned bad=0;bad<10;++bad) { + Fixture f;auto &c=display13FBPower; + switch(bad){case 0:mapping=false;break;case 1:f.xe.adlnPlatform=nullptr;break;case 2:c.bridge.state.suspended=true;break; + case 3:c.clockStopped=true;break;case 4:f.xe.firmwarePorts.panel.controller=1;break; + case 5:f.xe.firmwarePorts.panel.backlightType=3;break;case 6:f.map.at(0xC8254)=~0U;break; + case 7:put(f.ctl,0xD0,static_cast(f.power));break;case 8:f.map.at(0xC2020)=~0U;break; + case 9:f.xe.adlnTopologyController=nullptr;break;} + assert(XE::wrapDisplay13PanelPower(f.ctl,2)==kIOReturnNotReady && !calls);f.balanced(); + } + Fixture f;assert(XE::display13PanelCapture(f.ctl)); + assert(Display13FBMemory::field(f.ctl,0xE60)==120000);f.balanced(); + for(unsigned brightness:{0U,1U,32768U,65535U}) { + assert(!XE::display13PanelBrightness(f.ctl,brightness)); + assert(f.map.at(0xC8258)==uint64_t(120000)*brightness/65535);f.balanced(); + } + assert(XE::display13PanelBrightness(f.ctl,65536)==kIOReturnNotReady);f.balanced(); + f.map.at(0xC8250)=0;XE::display13PanelPWMEnable(f.access);assert(f.map.at(0xC8250)==0xa0000000);f.balanced(); + // The actual adapter's write allowlist must not grant a second PPS owner. + Display13FBPanelIO io{&f.map};io.write(0xC7204,0);assert(f.map.at(0xC7204)==0x67); + io.write(0x60400,123);assert(!f.map.at(0x60400)); + if(argc>1) { + std::ifstream input(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(input)),{}); + assert(!bytes.empty() && bytes.size()<0x400000); + auto image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0));assert(image!=MAP_FAILED); + memcpy(image,bytes.data(),bytes.size()); + jump(image+0x48652,reinterpret_cast(lightPower));jump(image+0x6ccb4,reinterpret_cast(lightClocks)); + jump(image+0x1ba02,reinterpret_cast(lightTrain));jump(image+0x1108,reinterpret_cast(lightPlane)); + jump(image+0x65fd4,reinterpret_cast(lightScaler));jump(image+0x48bb6,reinterpret_cast(lightPipe)); + jump(image+0x4a56a,reinterpret_cast(lightBacklight));redirect(image,0x507b1,0x300000,reinterpret_cast(lightDelay)); + uintptr_t vtable[64]{};vtable[0x130/8]=reinterpret_cast(lightWrite);put(f.access,0,vtable); + alignas(8) uint8_t path[0x3730]{};put(path,0x3648,static_cast(path)); + auto light=reinterpret_cast(image+0x5063e); + for(unsigned patched=0;patched<2;++patched) { + if(patched)for(const auto &patch:Display13Panel::lightPatches) { + assert(!memcmp(image+patch.offset,patch.before,patch.size));memcpy(image+patch.offset,patch.after,patch.size); + } + for(unsigned power:{0U,0xe00002d8U})for(unsigned train:{0U,0xe00002d8U}) { + lightStages.clear();powerResult=power;trainingResult=train; + const auto result=light(f.ctl,nullptr,path,nullptr); + if(!patched){assert(lightStages.size()==8 && result==train);} + else {assert(result==(power?power:train));assert(lightStages.size()==(power?1U:train?3U:8U));} + } + } + munmap(image,0x400000); + puts("PASS exact native LightUpEDP power/train failure exits; original donor counterexamples"); + } + puts("PASS production Display13 PPS/PWM wrappers: unlocked native calls, balanced/published PG, on/off/failure cleanup, foreign owner rejection"); +} diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index 0d828d80..a9329914 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -6,6 +6,7 @@ #include #include "kern_display13_power_bridge.hpp" #include "kern_tgl_combo_signal.hpp" +#include "kern_vbt.hpp" #include "kern_xelp_sst_program.hpp" // NATIVE_OBJECTS std::map objectRefs; @@ -33,7 +34,7 @@ struct Map { struct Context { Display13Power::NativeBridge bridge;void *lock=reinterpret_cast(1),*controller=nullptr,*access=nullptr; Display13Power::MainLinkAUX mainLink[2];alignas(void *) uint8_t powerBytes[0x80]{};void *power=powerBytes; - Map *map=nullptr;bool clockFaulted=false,memoryFaulted=false,clockStopped=false; + Map *map=nullptr;bool panelFaulted=false,clockFaulted=false,memoryFaulted=false,clockStopped=false; }display13FBPower; struct Display13FBPowerIO { Map *map;bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned tc){return tc<4;}bool tcAuxHealthy(unsigned tc){return tc<4;} @@ -56,6 +57,7 @@ struct Display13FBSSTIO { bool adlnWriterMappingBound(){return bound;} uintptr_t display13FBExpectedOwner(void*){return 0;} struct XE { + XELP::VBT::Ports firmwarePorts;bool tglSinkFramingReady=true; bool display13ClockOrderReady=false; static bool display13StopTranscoder(void*){assert(false);return false;} static bool display13TrainingPath(void*,uint32_t&,uint32_t&){return false;} @@ -115,7 +117,7 @@ unsigned nativePattern(void *path,unsigned pattern){ if(loseAfterNative)display13FBPower.map->regs[0x45454]=0; return nativeResult; } -void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigned rate){ +void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigned rate,bool panel=false){ display13FBPower.~Context();new(&display13FBPower)Context;state={};calls=nativeResult=0;bound=true;loseAfterNative=false; tcOwned=tcLinked=false;assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; auto &c=display13FBPower;c.controller=f.ctl;c.access=f.access;c.map=↦ @@ -124,6 +126,7 @@ void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigne const unsigned peer=ddi>=3?0:1-ddi; f.wiring.hdmi=1U<=3){put(f.donor,0x20+24*ddi+4,ddi+6);put(f.donor,0x20+24*ddi+12,1U);} + if(panel){f.wiring.panel=1U<(nativePattern);state.orgDisplay13SignalLevels=reinterpret_cast(nativeSignal); @@ -163,6 +166,24 @@ int main(int argc,char **argv){ for(unsigned at:{0x5FA33U,0x5FAC4U,0x5FBEBU,0x5FC4EU})redirect(image,at,0x300000,reinterpret_cast(nativeLog)); for(unsigned at:{0x5FBCBU,0x5FC99U})redirect(image,at,0x300020,reinterpret_cast(nativeDelay)); } + // eDP uses the same real path/power graph with a native internal connector. + // Replay native training patterns for all pipe/DDI/lane/polarity combinations. + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned pipe=0;pipe<3;++pipe) + for(unsigned lanes:{1U,2U,4U})for(unsigned rate:{6U,10U,20U,30U}) + for(bool low:{false,true})for(unsigned sr=0;sr<2;++sr) { + Fixture f(0,pipe,ddi);Map map;setup(f,map,ddi,pipe,lanes,rate,true); + state.firmwarePorts.panel.lowSwing=low;f.port[0x118]=sr; + assert(!XE::wrapDisplay13SignalLevels(f.port,0,0,nullptr)); + XELP::ComboPolicy expected;assert(expected.select(XELP::ComboPolicy::Platform::ADLP,rate,true,low,false,0,0)); + assert(((map.regs[map.base+0x89c]>>24)&0x7f)==expected.coefficients.scalar); + for(unsigned enhanced=0;enhanced<2;++enhanced)for(unsigned pattern=0;pattern<6;++pattern) { + const uint32_t patterns[]={0x200,0,0x100,0x400,0x500,0x300}; + f.port[0x11A]=enhanced;assert(!XE::wrapDisplay13TrainingPattern(f.path,pattern)); + assert(map.regs[0x60540+0x1000*pipe]==(0x80000000|patterns[pattern]|(enhanced<<18)|(sr<<6))); + } + assert(!XE::wrapDisplay13EnablePort(f.port));assert(!XE::wrapDisplay13DisablePort(f.port));assert(objectRefs.empty()); + display13FBPower.panelFaulted=true;assert(!XE::display13SSTPort(f.port)); + } unsigned count=0,replays=0; for(unsigned ddi=0;ddi<2;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned lanes:{1U,2U,4U}) for(unsigned rate:{6U,10U,20U,30U})for(unsigned swing=0;swing<4;++swing)for(unsigned emphasis=0;emphasis<4-swing;++emphasis){ diff --git a/tests/display13_tc_preparation_fixture.cpp b/tests/display13_tc_preparation_fixture.cpp index df3abb25..0fb279d8 100644 --- a/tests/display13_tc_preparation_fixture.cpp +++ b/tests/display13_tc_preparation_fixture.cpp @@ -30,6 +30,7 @@ struct MMIO { } }; struct Context { + AlderLakeN::DisplayReadout displayScratch; Display13Power::NativeBridge bridge;Display13TC::Mode tcMode[4]; bool tcSupported=true,tcBankFaulted=false,clockFaulted=false;unsigned tcBusy[4]{};OSObject*tcObject[4]{}; void *lock=(void*)1,*controller=(void*)2,*access=(void*)3;MMIO*map=nullptr; @@ -59,7 +60,15 @@ struct XE { alignas(8) uint8_t portBytes[0x700]; templatevoid put(unsigned o,T v){memcpy(portBytes+o,&v,sizeof(v));} unsigned nativeCalls=0; -unsigned base(OSObject*p,unsigned mode){assert(!locked);++nativeCalls;memcpy(reinterpret_cast(p)+0x550,&mode,4);return 0;} +unsigned base(OSObject*p,unsigned mode){ + assert(!locked);++nativeCalls; + // Model native reentry borrowing the owner's scratch after Mode drops its + // lock. No reference to the old snapshot may be consumed after this call. + IOLockLock(display13FBPower.lock); + memset(&display13FBPower.displayScratch,0xa5,sizeof(display13FBPower.displayScratch)); + IOLockUnlock(display13FBPower.lock); + memcpy(reinterpret_cast(p)+0x550,&mode,4);return 0; +} int main(){ unsigned cases=0; for(unsigned ddi=3;ddi<=6;++ddi)for(bool dp:{false,true})for(unsigned fault=0;fault<6;++fault){ diff --git a/tests/display13_training_frontend.py b/tests/display13_training_frontend.py index 28ebce3e..09ab215c 100644 --- a/tests/display13_training_frontend.py +++ b/tests/display13_training_frontend.py @@ -39,10 +39,10 @@ def fn(sig):return re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() void *testByDDI(unsigned ddi){return Display13FBMemory::field(testPort,0x120)==ddi?testPort:nullptr;} unsigned nativeTrain(void*p,void*){assert(p==testPort&&!locked);return XE::wrapDisplay13TrainingPattern(currentPath,1);} int main(){IORecursiveLock pool; - for(unsigned ddi=3;ddi<7;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned cap=0;cap<2;++cap)for(unsigned wanted=0;wanted<2;++wanted){ - Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,4,6);testPort=f.port; + for(unsigned ddi:{0U,1U,3U,4U,5U,6U})for(unsigned pipe=0;pipe<3;++pipe)for(unsigned cap=0;cap<2;++cap)for(unsigned wanted=0;wanted<2;++wanted){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,4,6,ddi<2);testPort=f.port; state.display13TrainingReady=state.display13FramingReady=state.display13ClockOrderReady=true; - state.trainingLeaseDP13Mask=0x78;state.tglPathPoolLock=&pool;state.display13DPImage=0x100000; + state.trainingLeaseDP13Mask=0x7b;state.tglPathPoolLock=&pool;state.display13DPImage=0x100000; state.orgTGLPathLookup=(uintptr_t)testLookup;state.tglPortByDDI=(uintptr_t)testByDDI;state.orgDisplay13PortTraining=(uintptr_t)nativeTrain; put(f.path,0x32c8,static_cast(nullptr));put(f.path,0x3644,1U);f.port[0x115]=1;f.port[0x10]=cap;f.port[0x11a]=wanted; auto &lease=state.tglTrainingLeases[ddi];lease.path=f.path;lease.port=f.port;lease.fb=f.fb;lease.power=display13FBPower.power; @@ -53,11 +53,11 @@ def fn(sig):return re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() put(f.fb,0x49e8,1U);assert(!XE::display13TrainingPath(f.path,d,p));put(f.fb,0x49e8,0U); assert(!XE::wrapDisplay13PortTraining(f.port,nullptr));assert(f.port[0x11a]==(cap&wanted)); assert((map.regs[0x60540+0x1000*pipe]&0x40000U)==((cap&wanted)<<18)); - const auto clock=0x46140+4*pipe;assert(map.regs[clock]==((ddi+3)<<28)); + const auto clock=0x46140+4*pipe;assert(map.regs[clock]==((ddi+(ddi>=3?3:1))<<28)); assert(!XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d991)); - assert(XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d990));assert(map.regs[clock]==((ddi+3)<<28)); + assert(XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d990));assert(map.regs[clock]==((ddi+(ddi>=3?3:1))<<28)); map.regs[0x70008+0x1000*pipe]=0;map.regs[0x64000+0x100*ddi]=0x80000000; - assert(!XE::display13StopTranscoder(f.port)&&map.regs[clock]==((ddi+3)<<28)); + assert(!XE::display13StopTranscoder(f.port)&&map.regs[clock]==((ddi+(ddi>=3?3:1))<<28)); map.regs[0x64000+0x100*ddi]=0x80;map.regs[0x60540+0x1000*pipe]&=~0x80000000U; assert(XE::display13StopTranscoder(f.port)&&map.regs[clock]==0&&!display13DeferredClocks[pipe].pending); } diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index f4576490..48659cb5 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -36,7 +36,7 @@ def expression(name): bool processTGLAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Render;return true;} bool processAlderLakeNRejectedAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} bool processAlderLakeNLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=LoadOnly;return true;} - bool processKextFull(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display12;return true;} + bool processKextFull(KernelPatcher&,size_t,mach_vm_address_t,size_t){assert(false && "dispatcher entered framebuffer installer");return false;} int firmware=0,pool=0,bootstrap=0,vm=0,mmio=0; unsigned tglDeviceId=0; struct {bool enabled=true;}tglVM; @@ -121,11 +121,23 @@ def expression(name): x.adlnNativeReady=x.tglActive=render>=2;loadOnly=render==3; const bool result=x.dispatch(patcher,index,0x1000,0x2000); if(display && framebuffer && index==1)assert(result && x.call==XE::Display13); - else if(!render)assert(result && x.call==XE::Display12); + else if(!render)assert(!result && x.call==XE::None); else if(index!=2)assert(!result && x.call==XE::None); else assert(result && x.call==(render==1?XE::Rejected:render==2?XE::Render:XE::LoadOnly)); ++cases; } + // TGL Render dispatch stays outside the framebuffer installer, including + // inactive/unknown kexts and absent graphics/framebuffer objects. + for(bool requested:{false,true})for(bool active:{false,true}) + for(bool framebuffer:{false,true})for(bool accelerator:{false,true}) + for(unsigned index=1;index<=3;++index) { + XE x;x.tglRequested=requested;x.tglActive=active; + x.currentFramebuffer=framebuffer?&fb:nullptr;x.currentGraphics=accelerator?&graphics:nullptr; + const auto expected=!requested||!active?XE::None: + index==1&&framebuffer?XE::Display12:index==2&&accelerator?XE::Render:XE::None; + assert(x.dispatch(patcher,index,0x1000,0x2000)==(expected!=XE::None)); + assert(x.call==expected);++cases; + } for(bool render:{false,true})for(bool display:{false,true})for(bool ready:{false,true})for(bool gt:{false,true}) { XE x;x.adlnDetected=render;x.display13FramebufferRequested=display; x.adlnNativeRequested=render;x.adlnNativeReady=ready&>x.display13BootReady=ready;x.kernel(patcher); diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index 7f3674fd..59157b50 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -16,6 +16,7 @@ using C=XELP::DisplayContract; struct State { FIELDS + bool display13PanelPowerReady=false,display13PanelProbeReady=false; bool display13FBDPHooksReady=false,display13TCDPProbeReady=false,display13ClockOrderReady=true; struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; bool tglHDMIB=false,adlsDisplaySelected=false,tglTrainingLeaseRequested=true; @@ -33,7 +34,7 @@ assert(C::display13(s.display13Available()).missing()==C::DonorRuntime); s.framebufferDonorRuntimeReady=true; assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM|C::DonorRuntime)); - assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13|C::Training12|C::Framing)); + assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13|C::Training12|C::Framing|C::PanelTranscoder|C::PanelDepth|C::PanelTiming|C::PanelPWM)); assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); s.display13DPProbeReady=false; assert(C::display13(s.display13Available()).ready()); // Existing provider/HDMI stays valid. @@ -51,6 +52,12 @@ s.display13ClockOrderReady=true;s.display13FramingReady=false; assert(C::display13(s.display13Available(),true).missing()==C::Framing);s.display13FramingReady=true; s.display13FBDPHooksReady=false;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); + // Panel admission requires the new provider capability and complete panel hooks. + assert(C::display13(s.display13Available(),false,true).missing()==C::PanelPower); + s.display13PanelPowerReady=true;assert(C::display13(s.display13Available(),false,true).missing()==C::PanelPower); + s.display13PanelProbeReady=true;assert(C::display13(s.display13Available(),false,true).ready()); + s.display13PanelPWMReady=false;assert(C::display13(s.display13Available(),false,true).missing()==C::PanelPWM); + s.display13PanelPWMReady=true;s.display13PanelPowerReady=s.display13PanelProbeReady=false; // TGL: candidate not yet registered, native donor setup is complete. // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. s.tglTCAutoCandidates=8;assert(s.start().ready()); diff --git a/tests/fatal_log_storage.py b/tests/fatal_log_storage.py new file mode 100644 index 00000000..74bdf398 --- /dev/null +++ b/tests/fatal_log_storage.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +"""Exercise the production fatal-log storage under simultaneous/reentrant claims.""" +from pathlib import Path +import subprocess,tempfile +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +a=source.index('struct TGLFatalLogStorage {') +b=source.index('static TGLFatalLogStorage tglFatalLogStorage;',a) +code='''#include +#include +#include +#include +#include +#include +#include +'''+source[a:b]+''' +int main() { + TGLFatalLogStorage log; + std::atomic winners{0},ready{0};std::atomic start{false}; + std::vector threads; + for(unsigned i=0;i<16;++i)threads.emplace_back([&] { + ++ready;while(!start.load())std::this_thread::yield(); + if(log.claim()) { + ++winners; + for(char c:log.bytes.csb.trail)assert(c==0); + memset(log.bytes.csb.trail,'T',sizeof(log.bytes.csb.trail)); + memset(log.bytes.csb.vm,'V',sizeof(log.bytes.csb.vm)); + memset(log.bytes.csb.loads,'L',sizeof(log.bytes.csb.loads)); + assert(!log.claim()); // Same-thread reentry must never overwrite/wait. + } + }); + while(ready.load()!=16)std::this_thread::yield();start=true; + for(auto &thread:threads)thread.join(); + assert(winners==1 && !log.claim()); + for(char c:log.bytes.csb.trail)assert(c=='T'); + for(char c:log.bytes.csb.vm)assert(c=='V'); + for(char c:log.bytes.csb.loads)assert(c=='L'); + TGLFatalLogStorage guc;assert(guc.claim()); + for(char c:guc.bytes.guc.detail)assert(c==0); + for(char c:guc.bytes.guc.boundary)assert(c==0); + for(char c:guc.bytes.guc.preStart)assert(c==0); + static_assert(sizeof(log.bytes.csb)==4600 && sizeof(log.bytes.guc)==3440,"preserve diagnostic capacity"); + puts("PASS fatal storage: one of 16 concurrent owners, no reentry, preserved capacities and first record"); +} +''' +with tempfile.TemporaryDirectory(prefix='wex-fatal-storage-') as tmp: + p=Path(tmp);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-pthread',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/host_publication.py b/tests/host_publication.py index f3fb4138..a80017bd 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -69,7 +69,7 @@ events.clear();published=false;publish=test!=1;identity=(test==2 || test>=4)?0x12348086:0x9a498086;deviceProperty=test>=4?&fake:nullptr;fake.value=test==4?0x46D2:0xFFFF;graphics.off=false; hasModel=test==3;modelWrites=0; DeviceInfo::value.videoBuiltin=test==0?nullptr:&gpu;XeHost host;host.processKernel(patcher); - assert(host.backlightEnabled==(test==3 && !firmware)); + assert(host.backlightEnabled==((test==3 || test==4) && !firmware)); const bool normalize=(test==3 || test==4) && me!=4; assert(mei.renames==(normalize && me!=1?1U:0U)); if(normalize)assert(mei.name && !strcmp(mei.name,"IMEI")); @@ -96,7 +96,7 @@ DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; publish=true;published=false;deviceProperty=override?&fake:nullptr;fake.value=0x9A49;identity=0x46A68086;revision=rev;events.clear();graphics.off=false; XeHost host;host.processKernel(patcher); - assert(host.active && !graphics.off && !host.backlightEnabled); + assert(host.active && !graphics.off && host.backlightEnabled==(DeviceInfo::value.firmwareVendor!=DeviceInfo::FirmwareVendor::Apple)); assert(events[events.size()-2]==8); const auto profile=XELP::identify(identity,revision,0x9A498086); assert(profile.displayAdmitted() && profile.admitted()); diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 00a00bad..3fd677a7 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -34,7 +34,7 @@ int main(int argc,char **argv) { assert(XELP::display13TCDPProbeCapable(xelpProbeCapabilities)); assert(!XELP::display13TCDPProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 3;})); for(unsigned schema=0;schema<5;++schema)for(unsigned abi=0;abi<6;++abi) - assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP):0)); + assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP|XELP::ProbeDisplay13Panel):0)); assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 0;})); assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 2;})); assert(argc==2);const bool adln=!strncmp(argv[1],"adln",4); diff --git a/tests/tgl_panel_depth_route.py b/tests/tgl_panel_depth_route.py index 47660fac..4c5dcc19 100644 --- a/tests/tgl_panel_depth_route.py +++ b/tests/tgl_panel_depth_route.py @@ -29,6 +29,7 @@ code=r''' #include "kern_tgl_panel_depth.hpp" #include "kern_alderlake_s.hpp" +#include "kern_display13_fb_topology.hpp" #include #include #include @@ -41,6 +42,10 @@ void IOSimpleLockLock(void *){} void IOSimpleLockUnlock(void *){} struct Ports {struct Port {bool internal(){return true;}bool dp(){return true;}} port[2];unsigned present=1;bool valid(){return true;}}; struct XE { + bool display13FramebufferRequested=false; + Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr; + static void cachePanelDepth(void*,uint32_t,const uint8_t*,uint32_t); + bool tglActive=true,tglPanelDepthReady=true,adlsDisplaySelected=false; AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr; diff --git a/tests/tgl_panel_timing.py b/tests/tgl_panel_timing.py index a0759abf..026828d7 100644 --- a/tests/tgl_panel_timing.py +++ b/tests/tgl_panel_timing.py @@ -41,6 +41,8 @@ }; struct XE { static XE *callbackXE; + bool display13FramebufferRequested=false; + static void display13PanelTiming(void *,bool){assert(false);} bool tglActive=true,tglPanelTimingReady=true,adlsDisplaySelected=false; AlderLakeS::Wiring adlsWiring;XELP::PanelPWM adlsPanelPWM; uintptr_t orgTGLPanelTiming=0,orgTGLRegisterRead=0,orgTGLPanelConfig=0; diff --git a/tests/tgl_sink_framing.py b/tests/tgl_sink_framing.py index f254e02b..c8043094 100644 --- a/tests/tgl_sink_framing.py +++ b/tests/tgl_sink_framing.py @@ -5,7 +5,7 @@ root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() start=s.index('uint32_t XE::wrapTGLPortTraining(') guard=s[s.index('\n',start)+1:s.index(' tglAUXRecord(port,10,false',start)] -install=s[s.index('// getDPCDInfo stores MAX_LANE_COUNT'):s.index('if (tglHDMIB)',s.index('// getDPCDInfo stores MAX_LANE_COUNT'))] +install=s[s.index('// getDPCDInfo stores MAX_LANE_COUNT'):s.index('\n\t\t\t}',s.index('// getDPCDInfo stores MAX_LANE_COUNT'))] if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes() for name,offset in [('capability',0x1e328),('selection',0x1b607),('alternateSR',0x1bc73)]: diff --git a/tools/build.py b/tools/build.py index a7b525c1..5c4e919b 100644 --- a/tools/build.py +++ b/tools/build.py @@ -2,6 +2,8 @@ """Build the standalone x86_64 Lilu plugin and donor symbol provider.""" import argparse,hashlib,json,plistlib,subprocess from pathlib import Path +from check_compat_version import check as check_compat_version +from check_boot_stack import check_objects as check_boot_stack ROOT=Path(__file__).resolve().parents[1] def run(cmd): subprocess.run([str(x) for x in cmd],check=True,cwd=ROOT) def main(): @@ -12,12 +14,12 @@ def main(): p.add_argument('--release',action='store_true') a=p.parse_args();out=a.output.resolve();out.mkdir(parents=True,exist_ok=True) sdk=a.sdk.resolve();lilu=a.lilu.resolve()/'Contents/Resources' - base=['xcrun','clang++','-arch','x86_64','-std=c++17',('-O2' if a.release else '-O0'),'-g','-mkernel','-fapple-kext','-fno-exceptions','-fno-rtti','-fno-stack-protector','-fno-builtin','-fno-asynchronous-unwind-tables','-mno-red-zone','-nostdinc','-DKERNEL','-DKERNEL_PRIVATE','-D__APPLE__','-D__MACH__','-I'+str(sdk/'Headers'),'-I'+str(lilu),'-I'+str(ROOT/'WhateverXe'),'-mmacosx-version-min=10.15','-Wall','-Wextra','-Wno-unused-parameter','-Wno-deprecated-declarations','-Wno-vla','-Wno-ossharedptr-misuse'] + base=['xcrun','clang++','-arch','x86_64','-std=c++17',('-O2' if a.release else '-O0'),'-g','-mkernel','-fapple-kext','-fno-exceptions','-fno-rtti','-fno-stack-protector','-fno-builtin','-fno-asynchronous-unwind-tables','-mno-red-zone','-fstack-usage','-nostdinc','-DKERNEL','-DKERNEL_PRIVATE','-D__APPLE__','-D__MACH__','-I'+str(sdk/'Headers'),'-I'+str(lilu),'-I'+str(ROOT/'WhateverXe'),'-mmacosx-version-min=10.15','-Wall','-Wextra','-Wno-unused-parameter','-Wno-deprecated-declarations','-Wno-vla','-Wno-ossharedptr-misuse'] # Keep build-machine paths out of distributed binaries and debug symbols. 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.6.0',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.6.0',[ROOT/'AppleIntelTGLCompat/AppleIntelTGLCompat.cpp'])]: + for name,version,sources in [('WhateverXe','0.6.1',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.6.1',[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: @@ -28,7 +30,10 @@ def main(): cmd=[x for x in cmd if x not in ['-mkernel','-mmacosx-version-min=10.15']] cmd+=['-mmacosx-version-min=10.6','-DAPPLE_KEXT_ASSERTIONS=1','-DDRIVER_PRIVATE','-DAPPLE','-DNeXT','-fno-common','-fasm-blocks','-mmmx','-msse','-msse2','-msse3','-mssse3','-mfpmath=sse','-ftree-vectorize','-fno-non-call-exceptions'] if not a.release:cmd+=['-DDEBUG=1'] + obj.with_suffix('.su').unlink(missing_ok=True) run(cmd+['-c',source,'-o',obj]);objects.append(obj) + if name=='WhateverXe': + (out/'boot-stack.json').write_text(json.dumps(check_boot_stack(objects),indent=2)+'\n') exe=bundle/'MacOS'/name run(['xcrun','ld','-arch','x86_64','-kext','-undefined','dynamic_lookup','-no_fixup_chains','-macos_version_min',('10.6' if name=='WhateverXe' else '10.15'),'-o',exe,*objects,sdk/'Library/x86_64/libkmod.a']) libs={x:'10.0.0' for x in ['com.apple.kpi.bsd','com.apple.kpi.dsep','com.apple.kpi.iokit','com.apple.kpi.libkern','com.apple.kpi.mach','com.apple.kpi.unsupported']};libs['com.apple.iokit.IOPCIFamily']='1.1' @@ -45,6 +50,7 @@ def main(): # Keep the native policy class, scoped to its Intel client and independent of SMBIOS. info['IOKitPersonalities']=plistlib.loads((ROOT/'AppleIntelTGLCompat/GraphicsPolicy.plist').read_bytes()) (bundle/'Info.plist').write_bytes(plistlib.dumps(info)) + if name=='AppleIntelTGLCompat':check_compat_version(bundle/'Info.plist') # Preserve DWARF before removing the linker's object-path records. if name=='WhateverXe':run(['xcrun','dsymutil',exe,'-o',out/(name+'.kext.dSYM')]) run(['xcrun','strip','-S',exe]) diff --git a/tools/check_boot_stack.py b/tools/check_boot_stack.py new file mode 100644 index 00000000..75eadf25 --- /dev/null +++ b/tools/check_boot_stack.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Check every emitted WEX stack frame, with bounded, reviewed exceptions. + +These are per-function budgets, not a whole-kernel call-chain proof. Native, +Lilu/IOKit and indirect callers still need separate review. DEBUG must not rely +on optimization to remove repeated snapshots or diagnostic string buffers. +""" +import json +import struct +import sys +from pathlib import Path + +DEFAULT_LIMIT = 2048 +# These installers no longer nest inside one another. The remaining larger +# frames retain bounded patch tables, register records or diagnostic snapshots. +LIMITS = { + '_ZN2XE13processKernelER13KernelPatcherP10DeviceInfo': 2048, + '_ZN2XE13adlnBootstrapEP11IOPCIDevice': 5120, + '_ZN2XE11processKextER13KernelPatchermym': 256, + '_ZN2XE21processTGLAcceleratorER13KernelPatchermym': 6144, + '_ZN2XE15processKextFullER13KernelPatchermym': 6144, + '_ZL22tglStartOrderSerializePvS_P11OSSerialize': 3328, + '_ZN2XE24wrapDisplay13WMArmUpdateEP9IOService': 2816, + '_ZN12_GLOBAL__N_120tglCaptureHangDetailERKN14TGLHangPending7CaptureEPKhRKN13TGLHangSelect9SelectionE': 2688, + '_ZN2XE17tglObserveScanoutEPvS0_jbj': 2304, + '_ZN12_GLOBAL__N_120tglRecoverySerializeEPvS0_P11OSSerialize': 2304, + '_ZN2XE17wrapTGLCSBProcessEPvj': 2304, + # Snapshot owners consume reusable storage before native reentry/unlock. + '_ZN2XE23wrapAlderLakeNPowerInitEPvS0_': 1280, + '_ZN2XE27wrapAlderLakeNSetupWorkLoopEP9IOServiceP10IOWorkLoop': 1536, + '_ZN12_GLOBAL__N_124display13FBInspectMemoryEP9IOServicebjb': 1536, + '_ZN2XE19wrapDisplay13TCModeEP8OSObjectj': 1024, + '_ZL15tglHostCSBFatalPKcPKjPv': 2048, + '_ZN2XE21wrapTGLGuCHeaderPanicEPv': 1024, +} +TEMPLATE_LIMITS = { + '_ZN10AlderLakeS4Boot7prepareI': 2304, + '_ZN13Display13Boot9normalizeI': 1280, +} +REQUIRED = { + '_ZN2XE13processKernelER13KernelPatcherP10DeviceInfo', + '_ZN2XE13adlnBootstrapEP11IOPCIDevice', + '_ZN2XE11processKextER13KernelPatchermym', + '_ZN2XE21processTGLAcceleratorER13KernelPatchermym', +} + + +def limit_for(symbol): + if symbol in LIMITS: + return LIMITS[symbol] + for prefix, limit in TEMPLATE_LIMITS.items(): + if symbol.startswith(prefix): + return limit + return DEFAULT_LIMIT + + +def stack_files(objects): + paths = [] + for obj in map(Path, objects): + usage = obj.with_suffix('.su') + if usage.is_file(): + paths.append(usage) + continue + # Clang emits no .su for an optimized data-only translation unit. + # Accept that case only after checking the actual Mach-O sections. + data = obj.read_bytes() + if struct.unpack_from(' frame['limit']: + raise ValueError(f'kernel stack frame exceeds budget: {symbol}: {frame}') + # Inline/template bodies may be emitted by several translation units. + if symbol not in frames or frame['bytes'] > frames[symbol]['bytes']: + frames[symbol] = frame + missing = REQUIRED - frames.keys() + if missing: + raise ValueError(f'missing stack records: {sorted(missing)}') + return frames + + +if __name__ == '__main__': + print(json.dumps(check(sys.argv[1:]), indent=2)) diff --git a/tools/check_compat_version.py b/tools/check_compat_version.py new file mode 100644 index 00000000..fb905471 --- /dev/null +++ b/tools/check_compat_version.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Check that built Compat bundles still accept the shipped framebuffer ABI.""" +import argparse +import plistlib +import re +from pathlib import Path + + +def version(value): + if not isinstance(value, str) or not re.fullmatch(r'\d+\.\d+\.\d+', value): + raise ValueError(f'unsupported Compat version: {value!r}') + return tuple(map(int, value.split('.'))) + + +def check(path): + info = plistlib.loads(path.read_bytes()) + if info['CFBundleIdentifier'] != 'org.b00t0x.AppleIntelTGLCompat': + raise ValueError('wrong Compat bundle identifier') + # Existing Graphics framebuffer payloads request this ABI independently of + # the provider's release version. Changing it requires coordinated clients. + required = (0, 5, 0) + if not version(info['OSBundleCompatibleVersion']) <= required <= version(info['CFBundleVersion']): + raise ValueError('Compat no longer accepts the shipped framebuffer dependency 0.5.0') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('builds', type=Path, nargs='+') + args = parser.parse_args() + for build in args.builds: + check(build / 'AppleIntelTGLCompat.kext/Contents/Info.plist') + print('PASS: built Compat bundles accept the shipped framebuffer dependency') diff --git a/tools/test.py b/tools/test.py index 8217c70a..990f95ab 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,12 +7,12 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['framebuffer_training_registration','framebuffer_common','display13_shared_registration','adlp_render','firmware_readout','display13_offline','adlp_fb_candidate','display13_tc_native','display13_ddc','adlp_load_only','small_gt_urb','display_backend_dispatch','tgl_agdc_port_status','display_contract_adapter','tgl_agdc_aux_observer','xe_data_access','adls_aux_parent','adls_lane_power','adls_clock_guard','adls_clock_runtime','adls_boot_runtime','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] -python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', '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 += ['xelp_combo_clock_guard','display13_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] +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 += ['fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('adls_ddi_power_native',[sys.executable,str(R/'tests/adls_ddi_power_native.py'),str(a.donor.resolve())],None)] -if a.donor:jobs += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_tc_preparation','display13_retirement','display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_tc_preparation','display13_retirement','display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'display13_panel_runtime', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.accelerator:jobs += [('xelp_forcewake_irq_native',[sys.executable,str(R/'tests/xelp_forcewake_irq.py'),'--donor',str(a.accelerator.resolve())],None),('tgl_rpcs_native',[sys.executable,str(R/'tests/tgl_rpcs_native.py'),str(a.accelerator.resolve())],None)] + [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.accelerator.resolve())],None) for n in ['small_gt_urb','tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job