From 359a8ead372fe2738c1d6aa0ae2bb79c4f7e37a4 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 02:43:22 +0900 Subject: [PATCH 001/139] Add opt-in Display13 DSI to HDMI bring-up experiment --- README.md | 2 + README_ja.md | 2 + WhateverXe/kern_display13_dsi_trial.hpp | 157 +++++++++++++++ WhateverXe/kern_xe.cpp | 24 +++ tests/display13_dsi_trial.cpp | 257 ++++++++++++++++++++++++ tests/fixtures/boot_argument_names.json | 3 +- 6 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 WhateverXe/kern_display13_dsi_trial.hpp create mode 100644 tests/display13_dsi_trial.cpp diff --git a/README.md b/README.md index 48f9838e..11de255f 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,8 @@ ADL-P/RPL-P uses its physical ID and detected GPU resources automatically. No device-ID override or machine-specific AGDP kext is required. `-wex-noaccel` retains framebuffer-only operation. +The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 DSI-A GOP to Combo B at 1080p60 for development. It stops the internal display. It is disabled by default and does not provide DSI support or general cold startup. + ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. diff --git a/README_ja.md b/README_ja.md index 2aa82e20..6b0e57d9 100644 --- a/README_ja.md +++ b/README_ja.md @@ -102,6 +102,8 @@ ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起 ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。device-ID上書きや機種別AGDP kextは不要です。`-wex-noaccel`でFramebufferのみの動作を維持します。 +試験用の `-wex-dsi-hdmi-test` は、限定したDisplay13のDSI-A GOPからCombo-Bへ1080p60で移す開発用経路です。内蔵表示を停止します。既定では無効で、DSI対応や汎用のcold起動を提供するものではありません。 + ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 diff --git a/WhateverXe/kern_display13_dsi_trial.hpp b/WhateverXe/kern_display13_dsi_trial.hpp new file mode 100644 index 00000000..892e1ebd --- /dev/null +++ b/WhateverXe/kern_display13_dsi_trial.hpp @@ -0,0 +1,157 @@ +// Opt-in bring-up experiment, not a supported DSI backend or release policy. +// Transfer a qualified DSI-A firmware scanout to Combo-B TMDS, then hand the +// real hardware state to the existing Display13 donor admission. The fixed +// CEA-16 mode is a test fixture; remove this path when native DSI/cold startup +// owns the lifecycle. Never turn a failed source certificate into success. +// Register sequencing: Intel Linux v6.12 icl_dsi.c, intel_ddi.c, +// intel_dpll_mgr.c and intel_ddi_buf_trans.c (MIT). +#ifndef KERN_DISPLAY13_DSI_TRIAL_HPP +#define KERN_DISPLAY13_DSI_TRIAL_HPP +#include "kern_alderlake_n.hpp" +#include "kern_display13_clock.hpp" +#include "kern_display13_wm.hpp" +#include "kern_xelp_combo_clock.hpp" +namespace Display13DSITrial { +enum class Status:uint32_t {NotRequested,Source,Memory,Clock,Latency,PHY,Timeout,Readback,Complete}; +struct Result { + uint32_t version=1;Status status=Status::NotRequested; + uint32_t stage=0,writes=0,reg=0,value=0,pll=UINT32_MAX,sourcePLL=UINT32_MAX,frames=0; +}; +template Result transfer(IO &io,bool requested,const AlderLakeN::DisplayReadout &boot, + const XELPMemory::Layout &memory,unsigned hdmiMask) { + Result r; + if(!requested)return r; + r.status=Status::Source; + if(hdmiMask!=2 || boot.status!=AlderLakeN::ReadoutStatus::Complete || boot.activePipes || + !(boot.poweredPipes&1) || !memory.valid)return r; + const auto read=[&](uint32_t reg){return io.readable(reg)?io.read(reg):UINT32_MAX;}; + const auto get=[&](uint32_t reg){uint32_t v=UINT32_MAX;AlderLakeN::value(boot,reg,v);return v;}; + const auto conf=read(0x7B008),func=read(0x6B400),plane=get(0x70180),surface=get(0x7019C); + const auto geometry=XELPGeometry::linear(plane,get(0x70188),get(0x70190),get(0x7018C),get(0x701A4)); + const auto power=get(0x45404),ddiPower=get(0x45454); + if(conf!=0xC0000000 || !(func&0x80000000U) || (func&0x7000) || read(0x6B404) || + read(0x7B808)&0xC0000000U || read(0x6B094)!=1 || read(0x6B894) || + (read(0x6B030)&0x303F0000U)!=0x20130000U || read(0x6B034) || + get(0x64000)!=0x80000000U || (get(0x64100)&0x80000080U)!=0x80 || + (power&0x405)!=0x405 || (ddiPower&3)!=3 || + !geometry.valid || geometry.width!=1080 || geometry.height!=1920 || + surface!=get(0x701AC) || (surface&4095) || !memory.range(surface,geometry.bytes,true))return r; + for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U<=2 || (mux&(1U<<10)) || !(mux&(1U<<11)))return r; + r.pll=r.sourcePLL^1; + XELPComboClock::PLL target,source; + if(!XELPComboClock::PLL::resolve(r.pll,target) || !XELPComboClock::PLL::resolve(r.sourcePLL,source) || + read(target.enable)&0xCC000000U || (read(source.enable)&0xCC000000U)!=0xCC000000U)return r; + r.status=Status::PHY; + if(!(read(0x6C100)&0x80000000U))return r; // firmware initialized Combo-B compensation + const uint32_t phyRegs[]={0x6C804,0x6C014,0x6C894,0x6C028}; + for(auto reg:phyRegs)if(read(reg)==UINT32_MAX)return r; + const uint32_t laneWords[]={0x888,0x890,0x89C}; + for(unsigned n=0;n<4;++n)for(auto off:laneWords)if(read(0x6C000+0x100*n+off)==UINT32_MAX)return r; + // Early bootstrap is before WEX's/native PCODE clients exist. Refuse an + // already-busy mailbox; use the shared actual-latency calculation. + r.status=Status::Latency; + const auto latency=Display13WM::queryLatency(io); + const auto wm=Display13WM::plan(1920,2200,148500000,2048,latency,false,false,1080); + if(wm.status!=Display13WM::Status::Complete || !wm.sagv.enable)return r; + if(read(0x7B008)!=conf || read(0x6B400)!=func || read(0x70180)!=plane || + read(0x7019C)!=surface || read(0x45404)!=power || read(0x164280)!=mux)return r; + const auto write=[&](uint32_t reg,uint32_t value){io.write(reg,value);++r.writes;r.reg=reg;r.value=read(reg);}; + const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t expected,unsigned us) { + for(unsigned elapsed=0;elapsed<=us;elapsed+=10) { + r.reg=reg;r.value=read(reg); + if(r.value!=UINT32_MAX && (r.value&mask)==expected)return true; + io.delayUs(10); + } + r.status=Status::Timeout;return false; + }; + const auto frames=[&]() { + const auto first=read(0x70040); + for(unsigned elapsed=0;elapsed<100000;elapsed+=100) { + r.frames=read(0x70040)-first; + if(r.frames>=2 && r.frames<0x10000000)return true; + io.delayUs(100); + } + r.status=Status::Timeout;r.reg=0x70040;return false; + }; + // 148.5 MHz * 5 * 12 = 8.91 GHz. P=2,Q=3,K=2. 38.4MHz + // reference uses 19.2MHz arithmetic and WA_22010492432 halves fraction. + r.stage=1;write(0x45454,ddiPower|8); + if(!wait(0x45454,12,12,1000))return r; + write(target.enable,0x08000000); + if(!wait(target.enable,0x0C000000,0x0C000000,1000))return r; + write(target.cfg0,0x001001D0);write(target.cfg1,0x00000E84); + write(target.enable,read(target.enable)|0x80000000U); + if(!wait(target.enable,0xCC000000,0xCC000000,1000))return r; + r.stage=2; + write(0x164280,(mux&~(3U<<2))|(r.pll<<2)); // B stays gated while changing source + write(0x164280,read(0x164280)&~(1U<<11)); + write(0x6C604,read(0x6C804)&~(1U<<26)); // HDMI common keeper off + for(unsigned n=0;n<4;++n)write(0x6C890+0x100*n,(read(0x6C890+0x100*n)&~0x80000000U)|(n?0x80000000U:0)); + write(0x6C014,read(0x6C014)|3); + auto tx=read(0x6C894)&~0x80000000U;write(0x6C694,tx); + tx=(tx&~0x601C0038U)|0x20080030U;write(0x6C694,tx); + // Linux default HDMI table entry: swing6, scalar127, cursor53, post1=0, post2=10. + for(unsigned n=0;n<4;++n) { + const auto b=0x6C880+0x100*n; + write(b+8,(read(b+8)&~0xB8FFU)|0x3098); + write(b+16,(read(b+16)&~0x3FFFFU)|(10U<<6)|53U); + write(b+28,(read(b+28)&~0x7F000000U)|0x7F000000U); + } + write(0x6C694,tx|0x80000000U);write(0x6C028,read(0x6C028)&~0xF0U); + // Last non-destructive check before retiring the firmware surface. + if(read(0x7B008)!=conf || read(0x6B400)!=func || !frames())return r; + r.stage=3; + write(0xC8250,read(0xC8250)&~0x80000000U); + write(0x70180,plane&~0x80000000U);write(0x7019C,surface); + if(!frames())return r; + write(0x7B008,conf&~0x80000000U); + if(!wait(0x7B008,0xC0000000,0,50000))return r; + write(0x6B0D8,(read(0x6B0D8)&~(1U<<8))|1); + if(!wait(0x6B0D8,1U<<16,1U<<16,1000))return r; + write(0x6B400,func&~0x80000000U); + write(0x64000,0); + if(!wait(0x64000,0x80000080,0x80,1000))return r; + write(0x164280,read(0x164280)|(1U<<10)); + write(source.enable,read(source.enable)&~0x80000000U); + if(!wait(source.enable,1U<<30,0,1000))return r; + write(source.enable,read(source.enable)&~(1U<<27)); + if(!wait(source.enable,1U<<26,0,1000))return r; + r.stage=4; + write(0x46140,0x20000000); // pipe A clock from actual Combo B + const uint32_t timing[][2]={{0x60000,0x0897077F},{0x60004,0x0897077F},{0x60008,0x080307D7}, + {0x6000C,0x04640437},{0x60010,0x04640437},{0x60014,0x0440043B},{0x60028,0}, + {0x6001C,0x077F0437}}; + for(auto &v:timing)write(v[0],v[1]); + write(0x42084,read(0x42084)|(1U<<30));write(0x70028,read(0x70028)|(1U<<13)); + write(0x70188,120);write(0x7018C,0);write(0x70190,0x0437077F);write(0x701A4,0); + write(0x45270,Display13WM::pipeLineTime(2200,148500000)); + const auto programmed=Display13WM::program(io,wm,false,false,0); + r.writes+=programmed.writes; + if(programmed.status!=Display13WM::Status::Complete){r.status=Status::Readback;return r;} + r.stage=5; + write(0x60404,0);write(0x60400,0x90030000); // B HDMI, 8bpc, +H/+V + write(0x70008,0x80000000); + if(!wait(0x70008,0xC0000000,0xC0000000,50000))return r; + write(0x64100,0x80000000); + if(!wait(0x64100,0x80000080,0x80000000,3000))return r; + write(0x70180,(plane&~(7U<<28))|(1U<<28));write(0x7019C,surface); + if(!frames())return r; + r.stage=6;r.status=Status::Complete;return r; +} +} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index daf40539..ef356bf0 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -15,6 +15,7 @@ #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_forcewake_irq.hpp" #include "kern_display13_boot.hpp" +#include "kern_display13_dsi_trial.hpp" #include "kern_display13_firmware.hpp" #include "kern_display13_tc_aux.hpp" #include "kern_display13_tc_mode.hpp" @@ -3934,6 +3935,29 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97StolenReservation",reservation,sizeof(reservation)); AlderLakeN::readDisplay(io,barReady,result); pci->setProperty("N97Display13Boot", &result, sizeof(result)); + // Observe the separate DSI transcoder without treating it as a normal + // DDI GOP. Cached words only; no command FIFO or clear-on-read registers. + if(barReady && !result.activePipes && (result.poweredPipes&1)) { + static const uint32_t regs[] {0x7B008,0x7B808,0x6B000,0x6B004,0x6B008,0x6B00C,0x6B010,0x6B014, + 0x6B030,0x6B034,0x6B090,0x6B094,0x6B0D8,0x6B400,0x6B404,0x6B894,0x6BC00, + 0x46010,0x46014,0x164280,0x164284,0x164288,0x16428C,0x164290, + 0xC7200,0xC7204,0xC8250,0xC8254,0xC8258,0x48400,0x51004,0x46070,0x46008, + 0x44300,0x45008,0x4500C,0x45500,0x45504,0x7017C,0x7027C,0x7037C, + 0x162028,0x6C028,0x6C100,0x6C104,0x6C10C,0x6C120,0x6C124,0x6C128,0x64C04, + 0x6C804,0x6C014,0x6C894,0x6C888,0x6C890,0x6C89C,0x6C898}; + auto words=static_cast(IOMalloc(sizeof(regs)*2)); + if(words) { + for(unsigned i=0;isetProperty("WEXDisplay13DSIBoot",words,sizeof(regs)*2); + IOFree(words,sizeof(regs)*2); + } + } + if(checkKernelArgument("-wex-dsi-hdmi-test")) { + auto handoff=Display13DSITrial::transfer(io,barReady && xelpMemoryMatches(pci),result,xelpMemory,board.hdmi); + pci->setProperty("WEXDisplay13DSIHandoff",&handoff,sizeof(handoff)); + AlderLakeN::readDisplay(io,barReady,result); + pci->setProperty("WEXDisplay13DSIAfter",&result,sizeof(result)); + } // Bounded pre-native evidence for selected fixed TC ports, including ports // which firmware owns without routing a transcoder to them. uint32_t bootTC[1+4*8] {1},bootPower=0; diff --git a/tests/display13_dsi_trial.cpp b/tests/display13_dsi_trial.cpp new file mode 100644 index 00000000..b0c6424b --- /dev/null +++ b/tests/display13_dsi_trial.cpp @@ -0,0 +1,257 @@ +#include "kern_display13_boot.hpp" +#include "kern_display13_dsi_trial.hpp" +#include +#include +#include +#include +struct IO { + std::map regs; + std::vector writes; + XELPMemory::Layout memory=XELPMemory::decode(0x3c1,0x7a800000,0); + unsigned time=0,fail=0;bool badPTE=false; + IO():regs{ + {0x45404,0xfc0f}, + {0x45400,0x5405}, + {0x45408,0x40000000}, + {0x4540c,0x5405}, + {0x45450,0x1}, + {0x45454,0x3}, + {0x4545c,0x1}, + {0x45420,0xa5294a1}, + {0x45424,0xe3}, + {0x45444,0x0}, + {0x42000,0x8e380000}, + {0x42084,0x0}, + {0x44300,0xc040c000}, + {0x4438c,0x1c000700}, + {0x45008,0xc040c000}, + {0x4500c,0xffff}, + {0x45010,0x0}, + {0x45014,0x0}, + {0x46000,0x780164}, + {0x46004,0x0}, + {0x164280,0x1e07801}, + {0x164284,0x18001d4}, + {0x164288,0x2644}, + {0x16428c,0x3001fb}, + {0x64000,0x80000000}, + {0x64100,0x80}, + {0x64200,0x80}, + {0x60400,0x30000}, + {0x60000,0x0}, + {0x60004,0x0}, + {0x60008,0x0}, + {0x6000c,0x0}, + {0x60010,0x0}, + {0x60014,0x0}, + {0x6001c,0x437077f}, + {0x70008,0x0}, + {0x70028,0x2a0f}, + {0x70040,0x321}, + {0x70080,0x0}, + {0x70084,0x0}, + {0x70180,0x94000008}, + {0x70188,0x44}, + {0x7018c,0x0}, + {0x70190,0x77f0437}, + {0x7019c,0x0}, + {0x701a4,0x0}, + {0x701ac,0x0}, + {0x7027c,0x7ff0000}, + {0x7037c,0x0}, + {0x70280,0x8}, + {0x70380,0x8}, + {0x70480,0x8}, + {0x70580,0x8}, + {0x70680,0x0}, + {0x70780,0x0}, + {0x60404,0x0}, + {0x60540,0x0}, + {0x60030,0x0}, + {0x60034,0x0}, + {0x60040,0x0}, + {0x60044,0x0}, + {0x46140,0x0}, + {0x68170,0x0}, + {0x68174,0x0}, + {0x68180,0x0}, + {0x68270,0x0}, + {0x68274,0x0}, + {0x68280,0x200}, + {0x78000,0x0}, + {0x78004,0x0}, + {0x61400,0x30000}, + {0x61000,0x0}, + {0x61004,0x0}, + {0x61008,0x0}, + {0x6100c,0x0}, + {0x61010,0x0}, + {0x61014,0x0}, + {0x6101c,0x0}, + {0x71008,0x0}, + {0x71028,0xa0f}, + {0x71040,0x0}, + {0x71080,0x0}, + {0x71084,0x0}, + {0x71180,0x8}, + {0x71188,0x0}, + {0x7118c,0x0}, + {0x71190,0x0}, + {0x7119c,0x0}, + {0x711a4,0x0}, + {0x711ac,0x0}, + {0x7127c,0x0}, + {0x7137c,0x0}, + {0x71280,0x8}, + {0x71380,0x8}, + {0x71480,0x8}, + {0x71580,0x8}, + {0x71680,0x0}, + {0x71780,0x0}, + {0x61404,0x0}, + {0x61540,0x0}, + {0x61030,0x0}, + {0x61034,0x0}, + {0x61040,0x0}, + {0x61044,0x0}, + {0x46144,0x0}, + {0x68970,0x0}, + {0x68974,0x0}, + {0x68980,0x200}, + {0x68a70,0x0}, + {0x68a74,0x0}, + {0x68a80,0x200}, + {0x78200,0x0}, + {0x78204,0x0}, + {0x62400,0x30000}, + {0x62000,0x0}, + {0x62004,0x0}, + {0x62008,0x0}, + {0x6200c,0x0}, + {0x62010,0x0}, + {0x62014,0x0}, + {0x6201c,0x0}, + {0x72008,0x0}, + {0x72028,0xa0f}, + {0x72040,0x0}, + {0x72080,0x0}, + {0x72084,0x0}, + {0x72180,0x8}, + {0x72188,0x0}, + {0x7218c,0x0}, + {0x72190,0x0}, + {0x7219c,0x0}, + {0x721a4,0x0}, + {0x721ac,0x0}, + {0x7227c,0x0}, + {0x7237c,0x0}, + {0x72280,0x8}, + {0x72380,0x8}, + {0x72480,0x8}, + {0x72580,0x8}, + {0x72680,0x0}, + {0x72780,0x0}, + {0x62404,0x0}, + {0x62540,0x0}, + {0x62030,0x0}, + {0x62034,0x0}, + {0x62040,0x0}, + {0x62044,0x0}, + {0x46148,0x0}, + {0x69170,0x0}, + {0x69174,0x0}, + {0x69180,0x200}, + {0x69270,0x0}, + {0x69274,0x0}, + {0x69280,0x200}, + {0x78400,0x0}, + {0x78404,0x0}, + {0x7b008,0xc0000000}, + {0x7b808,0x0}, + {0x6b000,0x4650437}, + {0x6b004,0x0}, + {0x6b008,0x44f044d}, + {0x6b00c,0x7d1077f}, + {0x6b010,0x7d1077f}, + {0x6b014,0x7a907a7}, + {0x6b030,0x20130300}, + {0x6b034,0x0}, + {0x6b090,0x60038}, + {0x6b094,0x1}, + {0x6b0d8,0x0}, + {0x6b400,0x80030006}, + {0x6b404,0x0}, + {0x6b894,0x0}, + {0x6bc00,0x30000}, + {0x46010,0x0}, + {0x46014,0xcc000000}, + {0x164290,0xe84}, + {0xc7200,0x80000008}, + {0xc7204,0xf}, + {0xc8250,0x80000000}, + {0xc8254,0x1e0}, + {0xc8258,0xf0}, + {0x48400,0x0}, + {0x51004,0x40000000}, + {0x46070,0xc000000e}, + {0x46008,0xf00ffff}, + {0x45500,0x0}, + {0x45504,0x0}, + {0x7017c,0x0}, + {0x162028,0x3000a}, + {0x6c028,0x3000a}, + {0x6c100,0x80005f25}, + {0x6c104,0x81000400}, + {0x6c10c,0xc0606f21}, + {0x6c120,0xd0280}, + {0x6c124,0x62ab67bb}, + {0x6c128,0x51914f96}, + {0x64c04,0x23000000}, + {0x6c804,0x1c000004}, + {0x6c014,0x12040458}, + {0x6c894,0x290000}, + {0x6c888,0x1098}, + {0x6c890,0x280018}, + {0x6c89c,0x7f000000}, + {0x6c898,0x0} + } {} + bool readable(unsigned) {return true;} + unsigned read(unsigned r) {if(r==0x70040)return time/1000;return regs[r];} + void write(unsigned r,unsigned v) { + writes.push_back(r); + if(r==0x7b008)assert((regs[0x46010]&0xcc000000)==0xcc000000); + if(r==0x60400 && (v&0x80000000))assert(!(regs[0x7b008]&0xc0000000)); + if(r==0x138124) {regs[r]=0;regs[0x138128]=(v&255)==0x23?35:0x09090909;return;} + regs[r]=v;if(r==fail)return; + if(r==0x45454)regs[r]=v|((v>>1)&0x15); + if(r==0x46010 || r==0x46014)regs[r]=(v&~0x44000000U)|((v&0x80000000U)?0x40000000U:0)|((v&0x08000000U)?0x04000000U:0); + if(r==0x70008 || r==0x7b008)regs[r]=(v&0x80000000U)?0xc0000000U:0; + if(r==0x64000 || r==0x64100)regs[r]=(v&0x80000000U)?v&~0x80U:0x80; + if(r==0x6b0d8)regs[r]=v|0x10000; + if(r==0x7019c)regs[0x701ac]=v; + } + void delayUs(unsigned us) {time+=us;} + unsigned long long readPTE(unsigned r) {return memory.pte((r-0x800000ULL)/8*4096)^(badPTE?4096:0);} +}; +int main() { + using namespace Display13DSITrial; + IO io;auto boot=AlderLakeN::readDisplay(io,true); + auto result=transfer(io,true,boot,io.memory,2); + printf("status=%u stage=%u reg=%x value=%x writes=%u\n",unsigned(result.status),result.stage,result.reg,result.value,result.writes); + assert(result.status==Status::Complete); + auto after=AlderLakeN::readDisplay(io,true);AlderLakeN::LinearPlan plan; + assert(AlderLakeN::linearPlan(after,plan,1)==AlderLakeN::LinearStatus::Ready); + assert(plan.geometry.width==1920 && plan.geometry.height==1080 && plan.geometry.pitch==7680); + for(unsigned mode=0;mode<5;++mode) { + IO x;if(mode==1)x.regs[0x46010]=0xcc000000; + if(mode==2)x.badPTE=true; + if(mode==3)x.regs[0x70180]|=1; + if(mode==4)x.regs[0x7b808]=0xc0000000; + auto b=AlderLakeN::readDisplay(x,true);auto r=transfer(x,mode!=0,b,x.memory,2); + assert(r.status!=Status::Complete && x.writes.empty()); + } + IO stuck;stuck.fail=0x46010; + auto b=AlderLakeN::readDisplay(stuck,true);auto r=transfer(stuck,true,b,stuck.memory,2); + assert(r.status==Status::Timeout && r.stage==1 && stuck.regs[0x7b008]==0xc0000000); + puts("DSI transfer source/PLL refusal, bounded timeout, ordering and real GOP postcondition passed"); +} diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index afbfff46..b7bbd7c3 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -138,5 +138,6 @@ "-wex-display-trace": [], "-wex-adlp-readout": [], "-wex-adlp-load-only": [], - "-wex-display13-offline": [] + "-wex-display13-offline": [], + "-wex-dsi-hdmi-test": [] } From 44e076ebb389ab213ea39bd169eeb812bfcdf6c5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:12:47 +0900 Subject: [PATCH 002/139] Gate idle Combo B before the opt-in DSI handoff trial --- WhateverXe/kern_display13_dsi_trial.hpp | 9 +++++++-- tests/display13_dsi_trial.cpp | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_display13_dsi_trial.hpp b/WhateverXe/kern_display13_dsi_trial.hpp index 892e1ebd..1d1e00e9 100644 --- a/WhateverXe/kern_display13_dsi_trial.hpp +++ b/WhateverXe/kern_display13_dsi_trial.hpp @@ -51,7 +51,7 @@ template Result transfer(IO &io,bool requested,const AlderLakeN::Displ const auto cdclk=Display13Clock::read(io); if(cdclk.status!=Display13Clock::Status::Complete || cdclk.refKHz!=38400 || cdclk.cdclkKHz<148500)return r; const auto mux=read(0x164280);r.sourcePLL=mux&3; - if(r.sourcePLL>=2 || (mux&(1U<<10)) || !(mux&(1U<<11)))return r; + if(r.sourcePLL>=2 || (mux&(1U<<10)))return r; r.pll=r.sourcePLL^1; XELPComboClock::PLL target,source; if(!XELPComboClock::PLL::resolve(r.pll,target) || !XELPComboClock::PLL::resolve(r.sourcePLL,source) || @@ -90,7 +90,12 @@ template Result transfer(IO &io,bool requested,const AlderLakeN::Displ }; // 148.5 MHz * 5 * 12 = 8.91 GHz. P=2,Q=3,K=2. 38.4MHz // reference uses 19.2MHz arithmetic and WA_22010492432 halves fraction. - r.stage=1;write(0x45454,ddiPower|8); + // Firmware may leave B's mux ungated even though its buffer is idle and + // no ordinary transcoder is active. Gate that qualified unused consumer + // before powering/programming the free PLL; A's DSI clock stays intact. + r.stage=1;write(0x164280,mux|(1U<<11)); + if(read(0x164280)!=(mux|(1U<<11))){r.status=Status::Readback;return r;} + write(0x45454,ddiPower|8); if(!wait(0x45454,12,12,1000))return r; write(target.enable,0x08000000); if(!wait(target.enable,0x0C000000,0x0C000000,1000))return r; diff --git a/tests/display13_dsi_trial.cpp b/tests/display13_dsi_trial.cpp index b0c6424b..1e858b55 100644 --- a/tests/display13_dsi_trial.cpp +++ b/tests/display13_dsi_trial.cpp @@ -6,7 +6,7 @@ #include struct IO { std::map regs; - std::vector writes; + std::vector> writes; XELPMemory::Layout memory=XELPMemory::decode(0x3c1,0x7a800000,0); unsigned time=0,fail=0;bool badPTE=false; IO():regs{ @@ -218,7 +218,7 @@ struct IO { bool readable(unsigned) {return true;} unsigned read(unsigned r) {if(r==0x70040)return time/1000;return regs[r];} void write(unsigned r,unsigned v) { - writes.push_back(r); + writes.push_back({r,v}); if(r==0x7b008)assert((regs[0x46010]&0xcc000000)==0xcc000000); if(r==0x60400 && (v&0x80000000))assert(!(regs[0x7b008]&0xc0000000)); if(r==0x138124) {regs[r]=0;regs[0x138128]=(v&255)==0x23?35:0x09090909;return;} @@ -242,6 +242,16 @@ int main() { auto after=AlderLakeN::readDisplay(io,true);AlderLakeN::LinearPlan plan; assert(AlderLakeN::linearPlan(after,plan,1)==AlderLakeN::LinearStatus::Ready); assert(plan.geometry.width==1920 && plan.geometry.height==1080 && plan.geometry.pitch==7680); + // Warm firmware can leave the idle B mux ungated. Its first mutation must + // close only B before the free PLL is powered; preserve the active DSI mux. + IO ungated;ungated.regs[0x164280]&=~(1U<<11); + auto ub=AlderLakeN::readDisplay(ungated,true); + auto ur=transfer(ungated,true,ub,ungated.memory,2); + assert(ur.status==Status::Complete); + bool gated=false;for(const auto &w:ungated.writes) { + if(w.first==0x164280){assert(w.second==0x01e07801);gated=true;break;} + assert(w.first!=0x46010); + }assert(gated); for(unsigned mode=0;mode<5;++mode) { IO x;if(mode==1)x.regs[0x46010]=0xcc000000; if(mode==2)x.badPTE=true; From 331504193ee6b383ea07e91b908b9ee9b42020b6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:39:19 +0900 Subject: [PATCH 003/139] Keep watermark retirement pending when cursor precedes primary scanout --- WhateverXe/kern_display13_wm.hpp | 5 +++-- tests/display13_wm.cpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index abbe3772..13161823 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -348,9 +348,10 @@ template Latch latch(IO &io,const Snapshot &s) { // Native enablePlane may return before any scanout is attached, while // programPipeScaler still flushes a zero SURF trigger via armUpdate. // A disabled primary with no intended surface is not a live latch to - // prove yet. Keep WM/DDB pending; never retire storage or certify the + // prove yet, even if the native cursor becomes active first. Keep + // WM/DDB pending; never retire storage or certify the // shared clock until a later real plane/surface update passes below. - const bool preparing=plane!=UINT32_MAX && !(plane&Enabled) && !s.primarySurface && !cursor && !s.cursorActive; + const bool preparing=plane!=UINT32_MAX && !(plane&Enabled) && !s.primarySurface; if(pipe==UINT32_MAX || plane==UINT32_MAX || cursor==UINT32_MAX || (pipe&0xC0000000)!=0xC0000000 || (!preparing && (!(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled)) || bool(cursor&0x27)!=s.cursorActive || (s.cursorActive && !cursorMatches(cursor,s.cursor->width))) {r.status=Status::Conflict;return r;} diff --git a/tests/display13_wm.cpp b/tests/display13_wm.cpp index 85130b0d..17ad4af0 100644 --- a/tests/display13_wm.cpp +++ b/tests/display13_wm.cpp @@ -385,6 +385,22 @@ int main() { LatchIO blankWithCursor(p);blankWithCursor.moveToPipe(pipe);blankWithCursor.surface=0; blankWithCursor.regs[0x70180+bank]=0;blankWithCursor.regs[0x70080+bank]=7; assert(latch(blankWithCursor,blankWithCursor.snapshot(p)).status==Status::Conflict); + // A qualified cursor may arrive before the first primary surface. + // It cannot certify retirement; the later primary still needs VBlank + // and live-surface evidence. An inconsistent cursor remains a fault. + LatchIO cursorFirst(p,&cursor);cursorFirst.moveToPipe(pipe); + cursorFirst.surface=0;cursorFirst.regs[0x70180+bank]=0;cursorFirst.regs[0x7019C+bank]=0; + const auto cursorPending=latch(cursorFirst,cursorFirst.snapshot(p,&cursor)); + assert(cursorPending.status==Status::Unavailable && cursorPending.check==LatchCheck::AwaitingScanout && + cursorFirst.writes.empty() && !cursorFirst.elapsed); + cursorFirst.regs[0x70080+bank]=0; + assert(latch(cursorFirst,cursorFirst.snapshot(p,&cursor)).status==Status::Conflict); + cursorFirst.regs[0x70080+bank]=0x22; + assert(latch(cursorFirst,cursorFirst.snapshot(p,&cursor)).status==Status::Conflict); + cursorFirst.regs[0x70080+bank]=7; + cursorFirst.regs[0x70180+bank]=0x84000000; + cursorFirst.regs[0x7019C+bank]=cursorFirst.surface=0x100000; + assert(latch(cursorFirst,cursorFirst.snapshot(p,&cursor)).status==Status::Complete && cursorFirst.elapsed==2000); LatchIO peerPlane(p);peerPlane.moveToPipe(pipe);peerPlane.regs[0x70280+0x1000*pipe]=Enabled; const auto peer=latch(peerPlane,peerPlane.snapshot(p)); assert(peer.status==Status::Conflict && peer.check==LatchCheck::OtherPlane && From b5ebea3610a40d87cdd1f7802eb710aa7281e455 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:51:46 +0900 Subject: [PATCH 004/139] Retain pending watermark resources until the native flip is live --- WhateverXe/kern_display13_wm.hpp | 14 +++++++++++--- tests/display13_wm.cpp | 15 ++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index 13161823..ecca24a5 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -289,7 +289,7 @@ inline bool clockFits(const Snapshot &s,uint64_t nativeHz) { (!s.cursorActive || (s.cursor && s.cursor->status==Status::Complete && s.cursor->pixelHz && s.cursor->pixelHz<=nativeHz/2)); } -enum class LatchCheck : uint32_t {None,Controls,OtherPlane,DSB,Clock,Registers,Live,Frame,AwaitingScanout}; +enum class LatchCheck : uint32_t {None,Controls,OtherPlane,DSB,Clock,Registers,Live,Frame,AwaitingScanout,AwaitingFlip}; struct Latch { Status status {Status::Unavailable}; uint32_t frameBefore {},frameAfter {},waitUs {},budgetUs {},lastReg {},expected {},observed {}; @@ -390,12 +390,20 @@ template Latch latch(IO &io,const Snapshot &s) { // readback catches up (observed for a native direct cursor swap). // Keep every programmed/control/clock check on each poll; qualify // retirement only after both actual live surfaces match. A stable - // mismatch at the existing deadline remains a Readback failure. + // mismatch at the existing deadline leaves retirement pending. + // A later native flush may prove it; do not fault the owner or + // infer success from the programmed surface. WM/DDB resources + // and clock qualification remain held until a complete proof. r.check=LatchCheck::Live; const bool live=compare(io,r,0x701AC+bank,s.primarySurface) && (!s.cursorActive || compare(io,r,0x700AC+bank,s.cursorSurface)); if(live) {r.status=Status::Complete;return r;} - if(r.waitUs>=r.budgetUs)return r; + if(r.waitUs>=r.budgetUs) { + if(r.status==Status::Readback && r.observed!=UINT32_MAX) { + r.status=Status::Unavailable;r.check=LatchCheck::AwaitingFlip; + } + return r; + } } if(r.waitUs>=r.budgetUs) {r.status=Status::CommitTimeout;return r;} io.delayUs(1000); diff --git a/tests/display13_wm.cpp b/tests/display13_wm.cpp index 17ad4af0..d342b560 100644 --- a/tests/display13_wm.cpp +++ b/tests/display13_wm.cpp @@ -283,10 +283,19 @@ int main() { assert(delayedProof.status==Status::Complete && delayedProof.frameAfter!=delayedProof.frameBefore && delayedProof.waitUs==3000); DelayedCursor stuckCursor(p,cursor);stuckCursor.stuck=true; const auto stuckProof=latch(stuckCursor,stuckCursor.snapshot(p,&cursor)); - assert(stuckProof.status==Status::Readback && stuckProof.waitUs==52000 && stuckProof.lastReg==0x700AC); + assert(stuckProof.status==Status::Unavailable && stuckProof.check==LatchCheck::AwaitingFlip && stuckProof.waitUs==52000 && stuckProof.lastReg==0x700AC); LatchIO oldLive(p);oldLive.surface=finalFlip.primary;oldLive.regs[0x7019C]=oldLive.surface; oldLive.oldSurface=true; - assert(latch(oldLive,oldLive.snapshot(p)).status==Status::Readback); + const auto pendingFlip=latch(oldLive,oldLive.snapshot(p)); + assert(pendingFlip.status==Status::Unavailable && pendingFlip.check==LatchCheck::AwaitingFlip && oldLive.writes.empty()); + // Repeated attempts cannot manufacture completion or retire anything. + oldLive.elapsed=0; + assert(latch(oldLive,oldLive.snapshot(p)).status==Status::Unavailable); + oldLive.elapsed=0;oldLive.oldSurface=false; + assert(latch(oldLive,oldLive.snapshot(p)).status==Status::Complete); + // An invalid read remains a fault, even at the live-register boundary. + LatchIO invalidLive(p);invalidLive.oldSurface=true;invalidLive.regs[0x701AC]=UINT32_MAX; + assert(latch(invalidLive,invalidLive.snapshot(p)).status==Status::Readback); auto clockPlan=committed.snapshot(p); assert(clockFits(clockPlan,1305600000) && clockFits(clockPlan,297000000)); assert(!clockFits(clockPlan,296999999)); @@ -301,7 +310,7 @@ int main() { const auto timeoutProof=latch(sameFrame,sameFrame.snapshot(p)); assert(timeoutProof.status==Status::CommitTimeout && timeoutProof.waitUs==52000); LatchIO staleSurface(p);staleSurface.oldSurface=true; - assert(latch(staleSurface,staleSurface.snapshot(p)).status==Status::Readback); + assert(latch(staleSurface,staleSurface.snapshot(p)).status==Status::Unavailable); LatchIO poweredOff(p);poweredOff.losePower=true; assert(latch(poweredOff,poweredOff.snapshot(p)).status==Status::Power); LatchIO invalidWM(p);invalidWM.regs[0x70258]^=1; From afd01c2bff19786796a305096756865295a5e67a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:51:53 +0900 Subject: [PATCH 005/139] Enable Display13 USB-C DP Alt Mode with detected lane ownership Reuse SST training and Display12 Dekel pin selection with Display13 TC power, physical lane, clock and HPD handling. Bind the new probe capability without changing the provider ABI. Keep temporary offline boot-resource retirement scoped to the opt-in DSI handoff trial. --- AppleIntelTGLCompat/AppleIntelTGLCompat.cpp | 2 +- README.md | 9 +- README_ja.md | 4 +- WhateverXe/kern_display13_boot.hpp | 20 +++- WhateverXe/kern_display13_fb_irq.hpp | 61 +++++++++--- WhateverXe/kern_display13_firmware.hpp | 9 +- WhateverXe/kern_display13_output_wiring.hpp | 4 +- WhateverXe/kern_display13_tc.hpp | 38 ++++++- WhateverXe/kern_display13_tc_clock.hpp | 4 +- WhateverXe/kern_display13_tc_dp.hpp | 24 +++-- WhateverXe/kern_display13_tc_mode.hpp | 26 +++-- WhateverXe/kern_xe.cpp | 104 ++++++++++++++++---- WhateverXe/kern_xe.hpp | 5 +- WhateverXe/kern_xelp_platform.hpp | 14 ++- WhateverXe/kern_xelp_probe.hpp | 4 + docs/device-selection.md | 5 +- tests/adlp_fb_candidate.py | 2 +- tests/display13_agdc.py | 1 + tests/display13_boot.cpp | 25 ++++- tests/display13_dp_alt_probe.cpp | 36 +++++++ tests/display13_fb_irq.cpp | 20 +++- tests/display13_tc_clock.cpp | 13 ++- tests/display13_tc_mode.cpp | 16 +++ tests/display13_tc_preparation_fixture.cpp | 9 +- tests/display13_trial_retirement.py | 70 +++++++++++++ tests/provider_binding_fixture.cpp | 2 +- tools/test.py | 2 +- 27 files changed, 441 insertions(+), 88 deletions(-) create mode 100644 tests/display13_dp_alt_probe.cpp create mode 100644 tests/display13_trial_retirement.py diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index 0c1d5afa..56a2e258 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|XELP::ProbeDisplay13Panel : 0; + return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP|XELP::ProbeDisplay13Panel|XELP::ProbeDisplay13DPAlt : 0; } extern "C" bool xelpConfigureDisplay(void *controller,const XELP::ProbeConfig *config) { if(!controller || !config || !config->valid())return false; diff --git a/README.md b/README.md index 11de255f..843d855f 100644 --- a/README.md +++ b/README.md @@ -134,14 +134,17 @@ Alder Lake-N HDMI property applies only to Combo routes. ADL-H has been tested with fixed HDMI outputs and a DP-to-HDMI adapter, including three connected displays at startup, firmware-scaled boot displays and DP reconnection. Direct DP sinks and other GPUs in these families have -not been verified with the fixed TC DP path. MST, USB-C DP Alt Mode, -Thunderbolt and cross-routed AUX remain unsupported on these GPUs. +not been verified with the fixed TC DP path. Experimental USB-C DP Alt Mode +supports VBT-described external TC1–TC4 DP wiring with matching AUX. It reuses +SST link training with Type-C ownership and the detected physical lane assignment. +MST, Thunderbolt, cross-routed AUX and USB-C boot-display adoption remain unsupported. +Alder Lake-N USB-C output has been tested at 1080p60 through an HDMI adapter on Big Sur, including Metal and OpenGL acceleration. USB-C hotplug, sleep/wake and other adapter types have not been verified. ADL-P/RPL-P uses its physical ID and detected GPU resources automatically. No device-ID override or machine-specific AGDP kext is required. `-wex-noaccel` retains framebuffer-only operation. -The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 DSI-A GOP to Combo B at 1080p60 for development. It stops the internal display. It is disabled by default and does not provide DSI support or general cold startup. +The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 DSI-A GOP to Combo B at 1080p60 for development. It stops the internal display and retires the temporary HDMI resources when policy switches to another connected output. It is disabled by default and does not provide DSI support or general cold startup. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 6b0e57d9..14d05353 100644 --- a/README_ja.md +++ b/README_ja.md @@ -98,11 +98,11 @@ Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPort Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uでは、Combo A/B・固定TC1〜TC4 HDMI配線をVBTから選択します。試験的な固定TC1〜TC4 SST DisplayPortには、VBTのAUX配線の一致が必要です。Alder Lake-NのHDMI明示propertyはCombo配線にのみ適用します。 -ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と、これらのファミリーの他のGPUは未検証です。これらのGPUではMST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と、これらのファミリーの他のGPUは未検証です。試験的なUSB-C DP Alt Modeは、AUXが一致するVBTの外部TC1〜TC4 DP配線に対応します。既存SSTリンク訓練にType-C所有権と検出した物理レーン割当を組み合わせます。MST、Thunderbolt、交差AUX配線、USB-Cを起動用画面とする引継ぎは未対応です。Alder Lake-NではBig SurでHDMI変換器経由のUSB-C 1080p60表示とMetal/OpenGL加速を確認しています。USB-Cの抜き差し・スリープ復帰・他の変換器は未検証です。 ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。device-ID上書きや機種別AGDP kextは不要です。`-wex-noaccel`でFramebufferのみの動作を維持します。 -試験用の `-wex-dsi-hdmi-test` は、限定したDisplay13のDSI-A GOPからCombo-Bへ1080p60で移す開発用経路です。内蔵表示を停止します。既定では無効で、DSI対応や汎用のcold起動を提供するものではありません。 +試験用の `-wex-dsi-hdmi-test` は、限定したDisplay13のDSI-A GOPからCombo-Bへ1080p60で移す開発用経路です。内蔵表示を停止し、policyが別の接続済み出力へ切り替える際は暫定HDMI資源を解放します。既定では無効で、DSI対応や汎用のcold起動を提供するものではありません。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index d12027a1..a59679a2 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -47,9 +47,9 @@ struct Result { uint32_t version {1};Status status {Status::Unavailable}; uint32_t stage {},writes {},before {},after {},lastReg {},expected {},observed {},framesBefore {},framesAfter {}; }; -// Firmware can leave ownership on a selected fixed TC port that has no GOP. +// Firmware can leave ownership on a selected direct TC port that has no GOP. // Linux intel_tc_port_sanitize_mode disconnects unused PHYs. Here admit only -// the already-cold case: disabled/idle buffer, stopped/gated PLL and clocks, +// the already-cold case: disabled/idle buffer, stopped PLL and clock source, // no transcoder route and no AUX command. Do not stop an unclassified link. struct UnusedTCResult { uint32_t version {1};Status status {Status::Unavailable}; @@ -65,11 +65,12 @@ template UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::Displ uint32_t power=0,auxPower=0; if(!AlderLakeN::value(boot,0x45404,power) || (power&5)!=5 || !AlderLakeN::value(boot,0x45444,auxPower))return r; + uint32_t gated=0; const auto stable=[&] { for(unsigned i=0;i UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::Displ r.ready=io.read(port.ready);r.clock=io.read(port.clock);r.pll=io.read(port.pll); r.gate=io.read(Display13TC::Clock::Gate); const auto aux=0x64310+0x100*(d-3); - if((r.buffer&0x800000C0U)!=0xC0 || r.ready==UINT32_MAX || (r.ready&7)!=4 || + if((r.buffer&0x800000C0U)!=0xC0 || !(r.ready&4) || + !Display13TC::Port::modeMatches(r.ready,(w.typeC&(1U<>3)<<24);} +inline uint32_t pchOwned(const State &s) {return ((s.ports&7)<<16)|(((s.ports&~s.typeC)>>3)<<24);} +inline uint32_t northOwned(const State &s) {return (s.typeC>>3)<<16;} +constexpr uint32_t NorthControl=0x44038; +inline uint32_t northControl(const State &s,uint32_t value) { + uint32_t r=0;for(unsigned tc=0;tc<4;++tc)if(s.typeC&(8U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t sharedGT=0,uint32_t expectedPipes=1,uint32_t ports=3) { +template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t sharedGT=0,uint32_t expectedPipes=1,uint32_t ports=3,uint32_t typeC=0) { if(s.faulted)return Status::Faulted; - if(!ports || (ports&~0x7FU) || (s.begun && ports!=s.ports))return fail(io,s,Status::Topology); - s.ports=ports; + if(!ports || (ports&~0x7FU) || (typeC&~(ports&0x78)) || (s.begun && (ports!=s.ports || typeC!=s.typeC)))return fail(io,s,Status::Topology); + s.ports=ports;s.typeC=typeC;excludedPCH|=(typeC>>3)<<24; if((excludedPCH&pchOwned(s)) || (excludedPCH&~(PCHTypeC|PCHCombo)) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology); - if(!available(io) || ((ports&0x78) && !io.readable(TCControl)))return Status::Unavailable; + if(!available(io) || (typeC && !io.readable(NorthControl)) || ((ports&0x78) && !io.readable(TCControl)))return Status::Unavailable; // Native repeated enable is idempotent. Do not drop a pending flip/DSB // completion or undo hwSetInterruptForTransaction's VBL mask. if(s.enabled && excludedPCH!=s.excludedPCH)return fail(io,s,Status::Topology); @@ -149,15 +162,13 @@ template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t configured|=1U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t if((io.read(HPDBank+4)&excludedHPD) || (~io.read(HPDBank-4)&excludedHPD)) return fail(io,s,Status::Readback); } + if(typeC) { + const auto ctl=io.read(NorthControl); + if(ctl==UINT32_MAX)return fail(io,s,Status::Readback); + io.write(NorthControl,(ctl&~0x333333U)|northControl(s,8)|(ctl&northControl(s,3))); + io.write(HPDBank-4,io.read(HPDBank-4)&~northOwned(s)); + io.write(HPDBank+4,io.read(HPDBank+4)|northOwned(s)); + const auto stale=io.read(HPDBank)&northOwned(s);if(stale)io.write(HPDBank,stale); + if((io.read(NorthControl)&northControl(s,8))!=northControl(s,8) || + (io.read(HPDBank+4)&northOwned(s))!=northOwned(s) || (io.read(HPDBank-4)&northOwned(s))) + return fail(io,s,Status::Readback); + } s.excludedPCH=excludedPCH; for(unsigned p=0;p<3;++p)if(configured&(1U< Events collect(IO &io,State &s) { if(status)io.write(DDIControl,(ddi&~0x3333U)|status); const auto owned=sde&pchOwned(s);if(owned)io.write(0xC4008,owned); } - constexpr uint32_t supported=(7U<<16)|(1U<<23)|Enable; + if(master&(1U<<21)) { + const auto status=io.read(HPDBank); + const auto pending=status&io.read(HPDBank+4)&~io.read(HPDBank-4); + if(status==UINT32_MAX || (pending&~northOwned(s))) {e.status=fail(io,s,Status::ForeignIRQ,HPDBank,pending);return e;} + const auto ctl=io.read(NorthControl); + if(ctl==UINT32_MAX){e.status=fail(io,s,Status::Readback);return e;} + e.aux=nativeDPAlt(pending,ctl); + uint32_t ack=0;for(unsigned tc=0;tc<4;++tc)if(pending&(1U<<(16+tc)))ack|=ctl&(3U<<(4*tc)); + if(ack)io.write(NorthControl,(ctl&~0x333333U)|ack); + const auto owned=status&northOwned(s);if(owned)io.write(HPDBank,owned); + } + const uint32_t supported=(7U<<16)|(1U<<23)|(s.typeC?(1U<<21):0U)|Enable; if(master&~supported) {s.foreign|=master&~supported;e.status=fail(io,s,Status::ForeignIRQ);return e;} io.write(Master,Enable);e.status=Status::Complete;return e; } diff --git a/WhateverXe/kern_display13_firmware.hpp b/WhateverXe/kern_display13_firmware.hpp index 260dfd45..db6f5b1a 100644 --- a/WhateverXe/kern_display13_firmware.hpp +++ b/WhateverXe/kern_display13_firmware.hpp @@ -20,8 +20,8 @@ namespace XELP { namespace VBT { } return w; } - // 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. + // Display13 VBT AUX F..I maps directly to TC1..4. DP-only USB-C records + // describe a possible DP-alt route, not a live connection or ownership. 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; @@ -29,12 +29,13 @@ namespace XELP { namespace VBT { w.panel=1U<>12)&15U)<>b)&1;return n;} + bool valid() const { + return (mask==1 || mask==2 || mask==4 || mask==8 || mask==3 || mask==12 || mask==15) && pin>=1 && pin<=6; + } + template static Lanes capture(IO &io,const Port &p) { + if(!io.readable(p.laneAssignment()) || !io.readable(p.pinAssignment()) || + !io.readable(0xc4000) || !io.readable(0x44470) || !io.readable(p.ready))return {}; + const auto lanes=io.read(p.laneAssignment()),pins=io.read(p.pinAssignment()); + if(lanes==UINT32_MAX || pins==UINT32_MAX || !Port::modeMatches(io.read(p.ready),3) || + !p.altHPD(io.read(0xc4000),io.read(0x44470)))return {}; + Lanes r{(lanes>>(8*(p.tc&1)))&15,(pins>>(4*(p.tc&1)))&15}; + return r.valid()?r:Lanes{}; + } + template bool unchanged(IO &io,const Port &p) const { + const auto now=capture(io,p);return valid() && now.mask==mask && now.pin==pin; + } +}; // Read-only physical certificate for an inherited fixed link. This never takes // ownership or programs the PLL. Callers must select the physical Display13 // backend before reading TCSS; TGL uses a different ownership mechanism. @@ -77,14 +107,14 @@ struct Boot { struct Ownership { uintptr_t owner {},identity {};Port port;bool active {}; Ownership()=default;Ownership(const Ownership&)=delete;Ownership& operator=(const Ownership&)=delete; - template Result acquire(IO &io,uintptr_t who,uintptr_t object,unsigned ddi,bool adopt=false) { + template Result acquire(IO &io,uintptr_t who,uintptr_t object,unsigned ddi,bool adopt=false,unsigned mode=4) { Result r;Port p;if(!who || !object || !Port::resolve(ddi,p))return r; if(owner && (owner!=who || identity!=object || port.ddi!=ddi)){r.status=Status::InUse;return r;} if(!io.powerHeld(who,object,ddi)){r.status=Status::NoPower;return r;} if(!io.readable(p.ready) || !io.readable(p.buffer)){r.status=Status::Unavailable;return r;} const auto initialReady=io.read(p.ready); if(initialReady==UINT32_MAX){r.status=Status::Unavailable;return r;} - if(initialReady&3){r.status=Status::UnsupportedMode;return r;} + if(!Port::modeMatches(initialReady,mode)){r.status=Status::UnsupportedMode;return r;} r.before=io.read(p.buffer);if(r.before==UINT32_MAX){r.status=Status::Unavailable;return r;} if(owner && active && !(r.before&64)) {r.status=Status::Readback;return r;} if(!owner && ((r.before&0x80000040U)!=0) && (!adopt || !io.inheritedVerified(who,object,ddi))) { @@ -102,7 +132,7 @@ struct Ownership { for(;;) { const auto ready=io.read(p.ready); if(ready==UINT32_MAX){r.status=Status::Unavailable;return r;} - if(ready&3){r.status=Status::UnsupportedMode;return r;} + if(!Port::modeMatches(ready,mode)){r.status=Status::UnsupportedMode;return r;} if(ready&4)break; if(r.elapsedUs==500000){r.status=Status::NotReady;return r;} io.delayUs(100);r.elapsedUs+=100; diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp index d97c8e04..ac8f32ac 100644 --- a/WhateverXe/kern_display13_tc_clock.hpp +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -121,7 +121,9 @@ struct Clock { !io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false; const uint32_t registers[]={p.pll,p.clock,p.ready,p.buffer,Gate}; for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false; - return (io.read(p.ready)&7)==4 && (io.read(p.buffer)&0x80000040)==64; + // The identity-bound owner has already selected legacy or DP-alt. + // Both use Dekel; TBT keeps a separate PLL/ownership contract. + return (io.read(p.ready)&6)==4 && (io.read(p.buffer)&0x80000040)==64; } template static bool rmw(IO &io,uint32_t reg,uint32_t mask,uint32_t value) { const auto irq=io.lock();const auto old=io.read(reg); diff --git a/WhateverXe/kern_display13_tc_dp.hpp b/WhateverXe/kern_display13_tc_dp.hpp index 228fecad..ec126145 100644 --- a/WhateverXe/kern_display13_tc_dp.hpp +++ b/WhateverXe/kern_display13_tc_dp.hpp @@ -3,6 +3,7 @@ #ifndef KERN_DISPLAY13_TC_DP_HPP #define KERN_DISPLAY13_TC_DP_HPP #include "kern_display13_tc_clock.hpp" +#include "kern_tgl_port_map.hpp" namespace Display13TC { struct DPClock : TMDSClock { static DPClock calculate(uint32_t linkRate,uint32_t ref) { @@ -57,41 +58,48 @@ struct DPSignal { const auto index=starts[swing]+emphasis; value=voltage[index]|(uint32_t(rate<=10?low[index]:high[index])<<8);return true; } - template static bool mode(IO &io,const Port &p,unsigned lanes) { + template static bool mode(IO &io,const Port &p,unsigned lanes,unsigned pin=0) { if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi) || (lanes!=1 && lanes!=2 && lanes!=4))return false; + // The pin-to-X1/X2 calculation is shared with Display12. Only the + // powered register access and ownership adapter differ on Display13. + uint32_t modes[2];if(!TGLDklDPMode::select(pin,lanes,modes))return false; for(unsigned group=0;group<2;++group) { - uint32_t value=lanes==1?(group?0x40U:0U):0x80U; + uint32_t value=modes[group]; if(!PHY::access(io,p,0x1000*group+0xa0,0xc0,value,true))return false; } return true; } // The donor negotiates a common swing/emphasis for the active lanes. // Training can change the electrical level while the DDI buffer is on. - template static bool program(IO &io,const Port &p,unsigned rate,unsigned swing,unsigned emphasis) { + template static bool program(IO &io,const Port &p,unsigned rate,unsigned swing,unsigned emphasis,unsigned physical=15) { uint32_t selected; - if(!io.powerHeld() || !io.owned(p.ddi) || !coefficients(rate,swing,emphasis,selected))return false; + if(!physical || physical>15 || !io.powerHeld() || !io.owned(p.ddi) || !coefficients(rate,swing,emphasis,selected))return false; for(unsigned group=0;group<2;++group) { + if(!(physical&(3U<<(2*group))))continue; const auto bank=0x1000*group;uint32_t zero=0; if(!PHY::access(io,p,bank+0xd00,UINT32_MAX,zero,true))return false; for(unsigned tx=0;tx<2;++tx) { + if(!(physical&(1U<<(2*group+tx))))continue; auto value=selected; if(!PHY::access(io,p,bank+0x2c0+4*tx,0x3ff07,value,true))return false; } - zero=0;if(!PHY::access(io,p,bank+0x2c8,0x7c,zero,true))return false; + const auto loadMask=4U|((physical&(1U<<(2*group)))?0x18U:0U)|((physical&(2U<<(2*group)))?0x60U:0U); + zero=0;if(!PHY::access(io,p,bank+0x2c8,loadMask,zero,true))return false; } return true; } - template static bool startLink(IO &io,const Port &p,const DPClock &plan,unsigned lanes,Clock::LinkStage *stage=nullptr) { + template static bool startLink(IO &io,const Port &p,const DPClock &plan,unsigned lanes,Clock::LinkStage *stage=nullptr,unsigned pin=0,unsigned physical=15) { auto step=[&](Clock::LinkStage s){if(stage)*stage=s;}; step(Clock::LinkStage::Preflight); - if(!plan.valid() || (lanes!=1 && lanes!=2 && lanes!=4) || !Clock::preflight(io,p))return false; + unsigned available=0;for(unsigned bit=0;bit<4;++bit)available+=(physical>>bit)&1; + if(!physical || physical>15 || available6 || !plan.valid() || (lanes!=1 && lanes!=2 && lanes!=4) || !Clock::preflight(io,p))return false; step(Clock::LinkStage::ReleaseDDI);if(!io.linkPower(false))return false; step(Clock::LinkStage::StopPLL);if(!Clock::disable(io,p))return false; step(Clock::LinkStage::Lanes);if(!Clock::lanes(io,p,lanes))return false; step(Clock::LinkStage::PLL);if(!Clock::enable(io,p,plan))return false; step(Clock::LinkStage::DDI);if(!io.linkPower(true))return false; step(Clock::LinkStage::Signal); - if(!mode(io,p,lanes) || !program(io,p,plan.rate/27000,0,0))return false; + if(!mode(io,p,lanes,pin) || !program(io,p,plan.rate/27000,0,0,physical))return false; step(Clock::LinkStage::Complete);return true; } }; diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp index 1b181f88..3b7b1fa0 100644 --- a/WhateverXe/kern_display13_tc_mode.hpp +++ b/WhateverXe/kern_display13_tc_mode.hpp @@ -1,4 +1,4 @@ -// Fixed legacy TC lifetime. The host serializes native clients and proves +// Legacy/DP-alt TC lifetime. The host serializes native clients and proves // firmware adoption/quiescence; this core never executes the TGL FIA setter. #ifndef KERN_DISPLAY13_TC_MODE_HPP #define KERN_DISPLAY13_TC_MODE_HPP @@ -14,6 +14,7 @@ struct Mode { uintptr_t controller {},object {};unsigned ddi {}; Phase phase {Phase::Idle};bool disconnectPending {}; Ownership ownership; + unsigned selectedMode {};Lanes lanes; // ADLP: register/lane access -> ownership -> AUX cold block. // DDI IO belongs to the clock lifecycle, after the PLL is running. // An already-powered firmware link is not evidence that these scopes can @@ -25,31 +26,36 @@ struct Mode { Port p;return Port::resolve(d,p) ? Display13Power::ddiDomain(d)| Display13Power::auxDomain(d)|Display13Power::bit(Display13Power::Domain::PW2) : 0; } - template Change connect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) { + template Change connect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d,unsigned wanted=4) { Change r; - if(!owner || !identity || !domains(d))return r; + if(!owner || !identity || !domains(d) || (wanted!=3 && wanted!=4))return r; if(controller && (owner!=controller || identity!=object || d!=ddi)){r.status=Status::InUse;return r;} if(!io.admissionReady()){r.status=Status::NotReady;return r;} if(phase==Phase::Cleanup){r.status=Status::InUse;return r;} if(phase==Phase::Active) { - if(io.nativeMode()!=4){r.status=Status::Readback;return r;} - r.phy=ownership.acquire(io,owner,identity,d); + if(wanted!=selectedMode || io.nativeMode()!=selectedMode || + (wanted==3 && !lanes.unchanged(io,ownership.port))){r.status=Status::Readback;return r;} + r.phy=ownership.acquire(io,owner,identity,d,false,wanted); r.status=r.phy.status;if(r.status==Status::Complete)disconnectPending=false; return r; } if(io.nativeMode()!=0){r.status=Status::NeedsAdoption;return r;} // Keep failed acquisitions bound, including their exact power/PHY // cleanup obligations. Never replace them with another native object. - controller=owner;object=identity;ddi=d;phase=Phase::Cleanup; + controller=owner;object=identity;ddi=d;selectedMode=wanted;phase=Phase::Cleanup; r.power=io.acquireDomains(accessLease,accessDomains()); if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} const bool firmware=io.inheritedVerified(owner,identity,d); - r.phy=ownership.acquire(io,owner,identity,d,firmware); + r.phy=ownership.acquire(io,owner,identity,d,firmware,wanted); if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} r.power=io.acquireDomains(auxLease,Display13Power::auxDomain(d)); if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} - r.native=io.nativeSetMode(4); - if(r.native || io.nativeMode()!=4){r.status=Status::Readback;return r;} + if(wanted==3) { + lanes=Lanes::capture(io,ownership.port); + if(!lanes.valid()){r.status=Status::NotReady;return r;} + } + r.native=io.nativeSetMode(wanted); + if(r.native || io.nativeMode()!=wanted){r.status=Status::Readback;return r;} phase=Phase::Active;disconnectPending=false;r.status=Status::Complete;return r; } template Change disconnect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) { @@ -76,7 +82,7 @@ struct Mode { r.power=io.releaseDomains(accessLease); if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} } - controller=object=0;ddi=0;phase=Phase::Idle;disconnectPending=false;r.status=Status::Complete;return r; + controller=object=0;ddi=selectedMode=0;lanes={};phase=Phase::Idle;disconnectPending=false;r.status=Status::Complete;return r; } }; } diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ef356bf0..ffd047ad 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4218,9 +4218,11 @@ bool XE::xelpBindProbe(void *controller) { !Display13FBTopology::supportedWiring(adlnOutputWiring,xeProfile.display==XELP::Display::XeLPD) || ((adlnOutputWiring.selected()&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || (adlnOutputWiring.dp && (!display13TCDPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)) || + (adlnOutputWiring.typeC && !display13DPAltProbeReady) || (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; + config.typeC=adlnOutputWiring.typeC; + config.legacy=(adlnOutputWiring.selected()&0x78)&~config.typeC;config.legacyBackend=(config.legacy|config.typeC)?0x78:0; } const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound); @@ -4440,6 +4442,8 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add XELP::display13PanelProbeCapable(reinterpret_cast(query)); display13TCProbeReady=query>=address && query-address(query)); + display13DPAltProbeReady=query>=address && query-address(query)); display13TCDPProbeReady=query>=address && query-address(query)); } @@ -4803,7 +4807,8 @@ struct Display13FBTCAuxIO { if(!admitted(tc))return false; const auto pw=read(Display13Power::Driver),buffer=read(0x64300+0x100*tc),mode=read(0x161500+4*tc); return pw!=UINT32_MAX && buffer!=UINT32_MAX && mode!=UINT32_MAX && (pw&5)==5 && - (buffer&64) && (mode&7)==4; + (buffer&64) && (mode&4) && Display13TC::Port::modeMatches(mode, + display13FBPower.tcMode[tc].selectedMode==3?3:4); } bool auxPowered(unsigned candidate) const { if(!admitted(candidate))return false;const auto value=read(Display13Power::AUX); @@ -4908,7 +4913,9 @@ struct Display13FBTCIO : Display13FBTCAuxIO { const auto &m=display13FBPower.tcMode[tc]; return ddi==tc+3 && powerHeld() && m.phase==Display13TC::Mode::Phase::Active && m.ownership.active && m.ownership.identity==reinterpret_cast(object) && - Display13FBMemory::field(object,0x550)==4; + Display13FBMemory::field(object,0x550)==m.selectedMode && + Display13TC::Port::modeMatches(read(m.ownership.port.ready),m.selectedMode) && + (m.selectedMode!=3 || m.lanes.unchanged(*this,m.ownership.port)); } bool quiescent(unsigned ddi) { if(ddi!=tc+3 || !accessHeld())return false; @@ -5885,8 +5892,8 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { const auto actual=Display13FBIRQ::readActivePipes(io,admitted); c.status=c.quiescing ? Display13FBIRQ::Status::Power : actual.status; if(c.status==Display13FBIRQ::Status::Complete)c.status=c.state.begun && c.state.enabled ? - Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnOutputWiring.selected()); - } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); + Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnOutputWiring.selected(),callbackXE->adlnOutputWiring.typeC); + } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected(),callbackXE->adlnOutputWiring.typeC); adlnIRQCache(controller,io);adlnIRQRecord(record,1); check[23]=static_cast(c.status);check[24]=c.state.begun; check[25]=c.state.enabled;check[26]=c.state.faulted;check[27]=c.state.active;check[28]=c.state.configured; @@ -5938,7 +5945,7 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec Display13FBIRQ::powered(io.read(Display13FBIRQ::Driver),pipe) && (io.read(0x70008+0x1000*pipe)&Display13FBIRQ::Enable)) { const auto excluded=controller==callbackXE->adlnTopologyController ? Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; - c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); + c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected(),callbackXE->adlnOutputWiring.typeC); } if(!c.state.faulted)c.status=Display13FBIRQ::vblank(io,c.state,pipe,enabled); } @@ -6531,7 +6538,8 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { if(config && config[0]<3)return FunctionCast(wrapDisplay13TCMode,self->orgDisplay13TCMode)(port,wanted); if(!c.lock)return kIOReturnNotReady; IOLockLock(c.lock);const auto ddi=display13TCTarget(port,wanted==0); - if(ddi>=7 || (wanted!=0 && wanted!=4) || c.tcBusy[ddi-3] || + const auto selected=ddi<7 && (self->adlnOutputWiring.typeC&(1U<=7 || (wanted!=0 && wanted!=selected) || c.tcBusy[ddi-3] || (c.tcObject[ddi-3] && c.tcObject[ddi-3]!=port)) {IOLockUnlock(c.lock);return kIOReturnNotReady;} const auto tc=ddi-3;auto &mode=c.tcMode[tc]; if(!c.tcObject[tc]){c.tcObject[tc]=port;port->retain();} @@ -6584,15 +6592,16 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { 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); + const auto preparation=framebufferLinkRequirements(port,selected).preparation(inherited); IO io{port,tc,reinterpret_cast(self->display13TCBaseMode),preparation}; const auto owner=reinterpret_cast(c.controller),object=reinterpret_cast(port); - const auto result=wanted?mode.connect(io,owner,object,ddi):mode.disconnect(io,owner,object,ddi); + const auto result=wanted?mode.connect(io,owner,object,ddi,wanted):mode.disconnect(io,owner,object,ddi); const bool done=result.status==Display13TC::Status::Complete; if(!wanted && done){c.tcSignalReady[tc]=false;c.tcDPLink[tc]={};OSSafeReleaseNULL(c.tcObject[tc]);} - uint32_t record[]={1,ddi,wanted,uint32_t(result.status),uint32_t(result.phy.status),uint32_t(result.power.status),uint32_t(mode.phase),mode.disconnectPending}; + uint32_t record[]={2,ddi,wanted,uint32_t(result.status),uint32_t(result.phy.status),uint32_t(result.power.status),uint32_t(mode.phase),mode.disconnectPending,mode.lanes.mask,mode.lanes.pin}; IOLockUnlock(c.lock);auto root=IORegistryEntry::getRegistryRoot(); root->setProperty("WEXDisplay13TCMode",record,sizeof(record)); + char modeKey[40];snprintf(modeKey,sizeof(modeKey),"WEXDisplay13TCMode%u",ddi);root->setProperty(modeKey,record,sizeof(record)); // Keep the initiating power-domain result; subsequent native getPG errors // can only observe the shared fault and cannot reconstruct this boundary. if(result.status==Display13TC::Status::NoPower) { @@ -6631,7 +6640,9 @@ void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint ok=c.tcSignalReady[tc] && c.tcDPLink[tc].matches(io,p,plan,lanes); if(ok)stage=Display13TC::Clock::LinkStage::Complete; else { - ok=Display13TC::DPSignal::startLink(io,p,plan,lanes,&stage); + ok=Display13TC::DPSignal::startLink(io,p,plan,lanes,&stage, + c.tcMode[tc].selectedMode==3?c.tcMode[tc].lanes.pin:0, + c.tcMode[tc].selectedMode==3?c.tcMode[tc].lanes.mask:15); if(ok)c.tcDPLink[tc]={plan.rate,plan.reference,lanes}; } } else { @@ -6685,7 +6696,8 @@ uint32_t XE::wrapDisplay13TCSignal(void *raw,uint32_t swing,uint32_t emphasis,vo Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && io.linkHeld() && c.tcSignalReady[ddi-3]; if(ok && (self->adlnOutputWiring.dp&(1U<(port,0x128),swing,emphasis); + ok=!drive && Display13TC::DPSignal::program(io,p,Display13FBMemory::field(port,0x128),swing,emphasis, + c.tcMode[ddi-3].selectedMode==3?c.tcMode[ddi-3].lanes.mask:15); } } if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); @@ -6707,8 +6719,10 @@ uint32_t XE::wrapDisplay13DPLaneMask(void *port) { return FunctionCast(wrapDisplay13DPLaneMask,s->orgDisplay13DPLaneMask)(port); auto &c=display13FBPower;if(!c.lock)return 0;IOLockLock(c.lock); const auto ddi=display13TCTarget(object);bool ok=false; - if(ddi>=3 && ddi<=6){Display13FBTCIO io{object,ddi-3};ok=io.owned(ddi);} - IOLockUnlock(c.lock);return ok?15U:0; + uint32_t mask=0; + if(ddi>=3 && ddi<=6){Display13FBTCIO io{object,ddi-3};ok=io.owned(ddi); + mask=c.tcMode[ddi-3].selectedMode==3?c.tcMode[ddi-3].lanes.mask:15U;} + IOLockUnlock(c.lock);return ok?mask:0; } void XE::wrapDisplay13DPPhyGating(void *port,bool gate) { auto s=callbackXE;auto object=static_cast(port); @@ -6724,6 +6738,8 @@ void XE::wrapDisplay13DPPhyGating(void *port,bool gate) { // Same fixed-DP contract as TGL: Linux leaves DP_MODE bits5:1 // intact during training. Do not run the donor's clear operation. if(ok && gate)for(unsigned group=0;group<2;++group) { + const auto &mode=c.tcMode[ddi-3]; + if(mode.selectedMode==3 && !(mode.lanes.mask&(3U<<(2*group))))continue; uint32_t value=0x3e;ok=Display13TC::PHY::access(io,p,0x1000*group+0xa0,0x3e,value,true) && ok; } } @@ -7304,7 +7320,7 @@ uint8_t *XE::display13SSTPort(void *port) { auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || !(s->adlnOutputWiring.links()&(1U<(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=(config[0]>=3?4U:1U) || + Display13FBMemory::field(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=(config[0]>=3?((s->adlnOutputWiring.typeC&(1U<(p,0x578)!=c.access)return nullptr; return p; } @@ -7858,11 +7874,65 @@ bool XE::processFramebufferAGDC(KernelPatcher &patcher,size_t index,mach_vm_addr {client,wrapFramebufferUserClient,orgFramebufferUserClient}, {callback,wrapFramebufferAGDCCallback,orgFramebufferAGDCCallback} }; - const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); + bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); + if(ready)ready=processDisplay13TrialAGDC(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13AGDCGuardReady",ready); return ready; } +// This belongs to the opt-in DSI bootstrap experiment, not the USB-C backend. +// A disconnected temporary boot port needs an explicit native retirement when +// policy first replaces it. Real DSI support can remove this experiment whole. +bool XE::processDisplay13TrialAGDC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(!display13FramebufferRequested || !checkKernelArgument("-wex-dsi-hdmi-test"))return true; + const char *symbol="__ZN31AppleIntelFramebufferController30commitResourceConfigurationSetEP30AGDCResourceConfigurationSet_tS1_"; + const auto fn=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x53,0x50}; + if(size<=0x670B9 || fn!=address+0x66F6E || memcmp(reinterpret_cast(fn),entry,sizeof(entry)))return false; + KernelPatcher::RouteRequest route(symbol,wrapDisplay13TrialCommit,orgDisplay13TrialCommit); + const bool ready=patcher.routeMultipleShort(index,&route,1,address,size);patcher.clearError();return ready; +} + +uint32_t XE::wrapDisplay13TrialCommit(void *controller,void *request,void *output) { + auto req=static_cast(request); + const auto count=req ? req[0]:0; + const auto index=req && output && count && count<=16 ? req[4]:UINT32_MAX; + auto fb=tglResourceFB(controller,index); + // Opt-in DSI handoff trial only: an unplugged temporary boot connector + // never becomes online, so policy does not retire its boot allocation. + // Use the normal stop/free path before committing a different live port. + if(callbackXE->display13FramebufferRequested && checkKernelArgument("-wex-dsi-hdmi-test") && + fb && count==1 && display13RetirementHealthy() && + !Display13FBMemory::field(fb,0x1E0) && + Display13FBMemory::field(fb,0x44DE) && + Display13FBMemory::field(fb,0x1E7) && + Display13FBMemory::field(fb,0x49E8)==1 && + Display13FBMemory::field(fb,0x4A00)==1) { + auto path=Display13FBMemory::singlePath(fb); + const auto binding=adlnInspectPathBinding(static_cast(fb),path, + callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); + const auto target=req[0x48/4]; + auto port=path ? Display13FBMemory::field(path,0x3648) : nullptr; + auto next=target && target<=9 && callbackXE->tglPortByDDI ? + reinterpret_cast(callbackXE->tglPortByDDI)(target-1) : nullptr; + if(binding.status==TGLFBBinding::Status::Complete && port && next && + next!=port && next->getMetaClass()->getClassSize()>=0x580 && + Display13FBTopology::selected(callbackXE->adlnPlatform, + Display13FBMemory::field(next,0x548),callbackXE->adlnOutputWiring) && + Display13FBMemory::field(next,0x120)==target-1 && + Display13FBMemory::field(path,0x3644)>=1 && + Display13FBMemory::field(path,0x3644)<=2 && + Display13FBMemory::field(port,0x554)<2 && + Display13FBMemory::field(next,0x554)>1) { + const auto retired=wrapDisplay13FreeResources(fb); + uint32_t record[]={1,binding.ddi,target-1,retired}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TrialBootRetire",record,sizeof(record)); + if(retired)return retired; + } + } + return FunctionCast(wrapDisplay13TrialCommit,callbackXE->orgDisplay13TrialCommit)(controller,request,output); +} + uint32_t XE::wrapFramebufferUserClient(IOService *fb,void *task,void *security,uint32_t type,void **client) { FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); if(!admission){if(client)*client=nullptr;return kIOReturnNotReady;} @@ -11588,7 +11658,7 @@ XELP::LinkRequirements XE::framebufferLinkRequirements(void *port,uint32_t mode) using R=XELP::LinkRequirements; if(self->display13FramebufferRequested) { if(!Display13FBTopology::selected(self->adlnPlatform,config,self->adlnOutputWiring) || - mode!=(ddi>=3?4U:1U))return req; + mode!=(ddi>=3?((self->adlnOutputWiring.typeC&(1U<xeProfile.display,ddi>=3?R::PHY::TC:R::PHY::Combo, (self->adlnOutputWiring.links()&(1U< uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man Display13TC::Port p; const bool hdmi=c[1]>=9 && c[1]<=12 && c[2]==2 && c[3]==1 && c[4]==0x800 && (c[5]&0xB)==2; const bool dp=c[1]==c[0]+6 && c[2]==1 && c[3]==1 && c[4]==0x400 && c[5]==0x201; + if(dp && (b.config.typeC&b.config.legacyBackend&(1U<(configure); void *adlnTopologyController=(void*)1;Display13OutputWiring adlnOutputWiring,tglHDMIWiring; struct {bool valid=false;unsigned excluded=0;unsigned selected(){return 0;}}adlsWiring; diff --git a/tests/display13_agdc.py b/tests/display13_agdc.py index f6d2c71a..347bb3cc 100644 --- a/tests/display13_agdc.py +++ b/tests/display13_agdc.py @@ -47,6 +47,7 @@ uint32_t framebufferProtocolCalls=0;void *framebufferStartedController=nullptr; uintptr_t orgFramebufferUserClient=0,orgFramebufferAGDCCallback=0; bool processFramebufferAGDC(KernelPatcher &,size_t,uintptr_t,size_t); + bool processDisplay13TrialAGDC(KernelPatcher &,size_t,uintptr_t,size_t){return true;} static uint32_t wrapFramebufferUserClient(IOService *,void *,void *,uint32_t,void **); static uint32_t wrapFramebufferAGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); static void observeFramebufferUserClient(IOService*,uint32_t,void**,uint32_t){}; diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index 8580df4f..321f6087 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -297,6 +297,29 @@ static void unusedTCBoot() { assert(io.writes.size()==1 && io.writes[0].offset==0x64300 && io.writes[0].value==0x80); auto again=Display13Boot::releaseUnusedTC(io,AlderLakeN::readDisplay(io,true),true,w); assert(again.status==Display13Boot::Status::Complete && again.writes==0); + // DSI-only GOP firmware can leave DP-alt ownership with no TC output. + {IO t;auto wiring=setup(t);wiring.hdmi&=~8U;wiring.ddc[3]=0;wiring.dp=wiring.typeC=8; + t.regs[0x161500]=0x1d;const auto before=t.regs; + const auto released=Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring); + assert(released.status==Display13Boot::Status::Complete && released.cleared==8 && released.writes==1); + for(auto x:before)if(x.first!=0x64300)assert(t.regs[x.first]==x.second); + t.regs[0x64300]=0xc0;t.regs[0x161500]=6;t.writes.clear(); + assert(Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring).status!=Display13Boot::Status::Complete && t.writes.empty());} + // Firmware may leave the idle port mux ungated after the last OS used it. + for(unsigned alt=0;alt<2;++alt) { + IO t;auto wiring=setup(t); + if(alt){wiring.hdmi&=~8U;wiring.ddc[3]=0;wiring.dp=wiring.typeC=8;t.regs[0x161500]=0x1d;} + t.regs[Display13TC::Clock::Gate]&=~(1U<<12);const auto before=t.regs; + const auto released=Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring); + assert(released.status==Display13Boot::Status::Complete && released.cleared==8 && released.writes==2); + assert(t.writes[0].offset==Display13TC::Clock::Gate && t.writes[0].value==(before.at(Display13TC::Clock::Gate)|(1U<<12))); + assert(t.writes[1].offset==0x64300 && t.writes[1].value==0x80); + for(auto x:before)if(x.first!=0x64300 && x.first!=Display13TC::Clock::Gate)assert(t.regs[x.first]==x.second); + IO failed;auto fw=setup(failed);failed.regs[Display13TC::Clock::Gate]&=~(1U<<12); + failed.failWrite=Display13TC::Clock::Gate; + const auto rejected=Display13Boot::releaseUnusedTC(failed,AlderLakeN::readDisplay(failed,true),true,fw); + assert(rejected.status==Display13Boot::Status::Readback && failed.regs[0x64300]==0xc0 && failed.writes.size()==1); + } // Any live/ambiguous resource prevents release of this unclassified port. for(unsigned fault=0;fault<9;++fault) { IO t;auto wiring=setup(t); @@ -305,7 +328,7 @@ static void unusedTCBoot() { if(fault==2)t.regs[0x161500]|=1; if(fault==3)t.regs[0x4610C]=0x80000000; if(fault==4)t.regs[0x46038]=0xCC000000; - if(fault==5)t.regs[Display13TC::Clock::Gate]&=~(1U<<12); + if(fault==5)t.regs[Display13TC::Clock::Gate]=UINT32_MAX; if(fault==6)t.regs[0x64310]=0x80000000; if(fault==7)t.regs[0x64300]=UINT32_MAX; if(fault==8)t.regs[0x161500]=UINT32_MAX; diff --git a/tests/display13_dp_alt_probe.cpp b/tests/display13_dp_alt_probe.cpp new file mode 100644 index 00000000..9398d210 --- /dev/null +++ b/tests/display13_dp_alt_probe.cpp @@ -0,0 +1,36 @@ +#include "kern_display13_firmware.hpp" +#include "kern_xelp_probe.hpp" +#include +#include +#include +struct IO { + std::map regs; + bool readable(uint32_t r){return regs.count(r);} + uint32_t read(uint32_t r){assert(readable(r));return regs[r];} +}; +int main() { + for(unsigned d=3;d<=6;++d) { + XELP::VBT::Ports ports;ports.status=XELP::VBT::Status::Valid;ports.present=2|(1U<(reinterpret_cast(instance.adlnPlatform)+0x20+24*ddi); unsigned values[]={ddi,ddi+6,dp?1u:2u,1u,dp?0x400u:0x800u,dp?0x201u:0x202u};memcpy(cfg,values,24); memset(portBytes,0,sizeof(portBytes));auto p=new(portBytes)OSObject; @@ -96,7 +101,7 @@ int main(){ if(fault==3)instance.display13FBTCHooksReady=false; if(fault==4)map.failOwner=true; if(fault==5)cfg[4]=1; - const auto before=map.regs;auto result=XE::wrapDisplay13TCMode(p,4); + const auto before=map.regs;auto result=XE::wrapDisplay13TCMode(p,alt?3:4); if((result==0)!=(fault<=1))fprintf(stderr,"case ddi=%u dp=%u fault=%u phase=%u buffer=%x power=%x aux=%x\n",ddi,dp,fault,unsigned(c.tcMode[ddi-3].phase),map.regs[0x64000+256*ddi],map.regs[0x45404],map.regs[0x45444]); assert((result==0)==(fault<=1));assert(nativeCalls==(fault<=1?1u:0u)); if(result==0){assert(c.tcMode[ddi-3].ownership.active && c.tcMode[ddi-3].auxLease.owner);if(fault==1)assert(map.regs[0x64000+256*ddi]==before.at(0x64000+256*ddi));} diff --git a/tests/display13_trial_retirement.py b/tests/display13_trial_retirement.py new file mode 100644 index 00000000..c776b87d --- /dev/null +++ b/tests/display13_trial_retirement.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Exercise the opt-in boot-retirement wrapper's native ordering and refusals.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:uint32_t|void \*) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['wrapDisplay13TrialCommit']) +code=r''' +#include "kern_display13_fb_memory.hpp" +#include +#include +#include +struct OSObject {alignas(8) unsigned char bytes[0x4b90]{};unsigned size=0x4b90; + OSObject *getMetaClass(){return this;} unsigned getClassSize(){return size;}}; +using IOService=OSObject; +namespace TGLFBBinding {enum class Status {Unavailable,Complete};struct Readout {Status status=Status::Complete;unsigned ddi=1;};} +struct Wiring{}; +bool trial=true,healthy=true,selected=true,bound=true; +TGLFBBinding::Readout adlnInspectPathBinding(IOService*,OSObject*,void*,Wiring){return {bound?TGLFBBinding::Status::Complete:TGLFBBinding::Status::Unavailable,1};} +namespace Display13FBTopology {bool selected(void*,uint32_t*,Wiring){return ::selected;}} +bool checkKernelArgument(const char*){return trial;} +struct IORegistryEntry {static IORegistryEntry*getRegistryRoot(){static IORegistryEntry r;return &r;}templatevoid setProperty(T...){} }; +#define FunctionCast(f,p) reinterpret_cast(p) +OSObject ctl,fb,path,oldPort,newPort;unsigned nativeCalls=0,freeCalls=0,freeError=0;bool fbPresent=true; +unsigned native(void *c,void*,void*){assert(c==&ctl);++nativeCalls;return 123;} +OSObject *lookup(unsigned index){return index==3?&newPort:nullptr;} +struct XE { + bool display13FramebufferRequested=true;void*adlnPlatform=nullptr;Wiring adlnOutputWiring; + uintptr_t tglPortByDDI=(uintptr_t)lookup,orgDisplay13TrialCommit=(uintptr_t)native; + static XE *callbackXE; + static void *tglResourceFB(void *c,unsigned i){return c==&ctl&&i==0&&fbPresent?&fb:nullptr;} + static bool display13RetirementHealthy(){return healthy;} + static unsigned wrapDisplay13FreeResources(void *f){assert(f==&fb&&!nativeCalls);++freeCalls;return freeError;} + static uint32_t wrapDisplay13TrialCommit(void*,void*,void*); +} state; +XE *XE::callbackXE=&state; +FUNCTIONS +templatevoid put(OSObject &o,unsigned at,T v){memcpy(o.bytes+at,&v,sizeof(v));} +int main(){ + for(unsigned bad=0;bad<24;++bad){ + state={};ctl={};fb={};path={};oldPort={};newPort={};trial=healthy=selected=bound=fbPresent=true; + nativeCalls=freeCalls=freeError=0;uint32_t req[32]={1},out[32]{};req[0x48/4]=4; + put(fb,0x44de,uint8_t(1));put(fb,0x1e7,uint8_t(1));put(fb,0x49e8,1u);put(fb,0x4a00,1u); + put(fb,0x4a08,&path);put(path,0x3648,&oldPort);put(path,0x3644,1u);put(newPort,0x554,2u);put(newPort,0x120,3u); + switch(bad){ + case 1:trial=false;break;case 2:state.display13FramebufferRequested=false;break; + case 3:fbPresent=false;break;case 4:req[0]=2;break;case 5:healthy=false;break; + case 6:put(fb,0x1e0,uint8_t(1));break;case 7:put(fb,0x44de,uint8_t(0));break; + case 8:put(fb,0x1e7,uint8_t(0));break;case 9:put(fb,0x49e8,2u);break; + case 10:put(fb,0x4a00,0u);break;case 11:bound=false;break;case 12:req[0x48/4]=0;break; + case 13:req[0x48/4]=10;break;case 14:state.tglPortByDDI=0;break; + case 15:put(path,0x3644,0u);break;case 16:put(path,0x3644,3u);break; + case 17:put(oldPort,0x554,2u);break;case 18:put(newPort,0x554,1u);break; + case 19:selected=false;break;case 20:put(newPort,0x120,4u);break; + case 21:freeError=77;break;case 22:put(path,0x3644,2u);break; + case 23:put(path,0x3648,&newPort);break; + } + const auto result=XE::wrapDisplay13TrialCommit(&ctl,req,out); + const bool retires=bad==0||bad==21||bad==22; + assert(freeCalls==unsigned(retires));assert(nativeCalls==unsigned(bad!=21)); + assert(result==(bad==21?77u:123u)); + } + assert(XE::wrapDisplay13TrialCommit(&ctl,nullptr,nullptr)==123); + puts("PASS trial retirement: native stop before replacement, failure propagation, opt-in and live-resource refusals"); +} +'''.replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='wex-trial-retire-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 3fd677a7..3031bdb3 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|XELP::ProbeDisplay13Panel):0)); + assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP|XELP::ProbeDisplay13Panel|XELP::ProbeDisplay13DPAlt):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/tools/test.py b/tools/test.py index 990f95ab..f88ff01f 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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'] +python_tests += ['fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_retirement','display13_trial_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)] From c09a9d72c260e4ee83ad479814974b4c55c4aad8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:31:56 +0900 Subject: [PATCH 006/139] Drain Display13 USB-C resources after connector state disappears Separate retained ownership from connected-link validation for native port disable, PHY power-down gating and PLL stop. Preserve identity, power, hardware ownership and scanout-idle checks. Exercise HPD and assignment loss through the production IO adapter, followed by complete mode release and reacquisition with changed lanes. --- WhateverXe/kern_display13_tc_clock.hpp | 31 +++++-- WhateverXe/kern_xe.cpp | 29 ++++-- WhateverXe/kern_xe.hpp | 2 +- tests/display13_boot.cpp | 1 + tests/display13_sst_fixture.cpp | 16 ++-- tests/display13_tc_clock.cpp | 25 ++++- tests/display13_tc_disconnect.py | 11 +++ tests/display13_tc_disconnect_fixture.cpp | 108 ++++++++++++++++++++++ tools/test.py | 2 +- 9 files changed, 200 insertions(+), 25 deletions(-) create mode 100644 tests/display13_tc_disconnect.py create mode 100644 tests/display13_tc_disconnect_fixture.cpp diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp index ac8f32ac..278decc3 100644 --- a/WhateverXe/kern_display13_tc_clock.hpp +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -114,16 +114,24 @@ struct Clock { static uint32_t gateBit(unsigned tc) {return tc<3?1U<<(12+tc):tc==3?1U<<21:0;} // Runtime IO validates the immutable owner/config/path bindings. A ready // register alone never authorizes touching an active/foreign clock. - template static bool preflight(IO &io,const Port &p) { + template static bool accessible(IO &io,const Port &p) { Port checked; if(!Port::resolve(p.ddi,checked) || p.pll!=checked.pll || p.clock!=checked.clock || p.ready!=checked.ready || p.buffer!=checked.buffer || p.tc!=checked.tc || p.phy!=checked.phy || p.hip!=checked.hip || p.shift!=checked.shift || - !io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false; - const uint32_t registers[]={p.pll,p.clock,p.ready,p.buffer,Gate}; + !io.powerHeld() || !io.quiescent(p.ddi))return false; + const uint32_t registers[]={p.pll,p.clock,p.buffer,Gate}; for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false; - // The identity-bound owner has already selected legacy or DP-alt. - // Both use Dekel; TBT keeps a separate PLL/ownership contract. - return (io.read(p.ready)&6)==4 && (io.read(p.buffer)&0x80000040)==64; + return true; + } + template static bool preflight(IO &io,const Port &p) { + return accessible(io,p) && io.owned(p.ddi) && io.readable(p.ready) && io.read(p.ready)!=UINT32_MAX && + (io.read(p.ready)&6)==4 && (io.read(p.buffer)&0x80000040)==64; + } + // HPD, pin assignment and TCSS mode may disappear before native teardown. + // Only stop our retained, powered hardware after buffer/scanout quiescence. + // Never use this certificate to start a PLL or program the PHY. + template static bool stopPreflight(IO &io,const Port &p) { + return accessible(io,p) && io.retainedOwned(p.ddi) && (io.read(p.buffer)&0x800000c0U)==0xc0; } template static bool rmw(IO &io,uint32_t reg,uint32_t mask,uint32_t value) { const auto irq=io.lock();const auto old=io.read(reg); @@ -138,12 +146,16 @@ struct Clock { if(elapsed==1000)return false;io.delayUs(10); } } - template static bool disable(IO &io,const Port &p) { - if(!preflight(io,p))return false; +private: + template static bool disableRegisters(IO &io,const Port &p) { return rmw(io,Gate,gateBit(p.tc),gateBit(p.tc)) && rmw(io,p.clock,0xf0000000,0) && rmw(io,p.pll,Enable,0) && wait(io,p.pll,Locked,0) && rmw(io,p.pll,Power,0) && wait(io,p.pll,Powered,0); } +public: + template static bool disable(IO &io,const Port &p) { + return preflight(io,p) && disableRegisters(io,p); + } template static bool enable(IO &io,const Port &p,const TMDSClock &plan) { if(!plan.valid() || !preflight(io,p))return false; // No reprogramming of running PLLs. Preserve an enabled GOP until @@ -180,7 +192,8 @@ struct Clock { step(LinkStage::Complete);return true; } template static bool stopLink(IO &io,const Port &p) { - return io.unstarted() || (preflight(io,p) && io.linkPower(false) && disable(io,p)); + return io.unstarted() || (stopPreflight(io,p) && io.linkPower(false) && + stopPreflight(io,p) && disableRegisters(io,p)); } }; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ffd047ad..c0f26af4 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4909,10 +4909,24 @@ struct Display13FBTCIO : Display13FBTCAuxIO { const auto pll=read(p.pll); return pll!=UINT32_MAX && !(pll&0xcc000000U) && !c.tcSignalReady[tc]; } + bool retainedOwned(unsigned ddi) { + if(ddi!=tc+3 || !powerHeld())return false; + const auto &m=display13FBPower.tcMode[tc]; + const auto native=Display13FBMemory::field(object,0x550); + return (m.phase==Display13TC::Mode::Phase::Active || m.phase==Display13TC::Mode::Phase::Cleanup) && + (m.selectedMode==3 || m.selectedMode==4) && + (native==m.selectedMode || (m.phase==Display13TC::Mode::Phase::Cleanup && !native)) && + m.ownership.active && m.ownership.owner==m.controller && + m.ownership.identity==reinterpret_cast(object) && m.ownership.port.ddi==ddi && + retainedHardwareOwner(m.ownership.port.buffer); + } + bool retainedHardwareOwner(uint32_t reg) { + const auto buffer=read(reg);return buffer!=UINT32_MAX && (buffer&64); + } bool owned(unsigned ddi) { + if(!retainedOwned(ddi))return false; const auto &m=display13FBPower.tcMode[tc]; - return ddi==tc+3 && powerHeld() && m.phase==Display13TC::Mode::Phase::Active && - m.ownership.active && m.ownership.identity==reinterpret_cast(object) && + return m.phase==Display13TC::Mode::Phase::Active && Display13FBMemory::field(object,0x550)==m.selectedMode && Display13TC::Port::modeMatches(read(m.ownership.port.ready),m.selectedMode) && (m.selectedMode!=3 || m.lanes.unchanged(*this,m.ownership.port)); @@ -6734,7 +6748,7 @@ void XE::wrapDisplay13DPPhyGating(void *port,bool gate) { const auto ddi=display13TCTarget(object);bool ok=false; if(ddi>=3 && ddi<=6) { Display13FBTCIO io{object,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); - ok=io.owned(ddi) && io.linkHeld(); + ok=(io.owned(ddi) || (gate && io.retainedOwned(ddi) && io.quiescent(ddi))) && io.linkHeld(); // Same fixed-DP contract as TGL: Linux leaves DP_MODE bits5:1 // intact during training. Do not run the donor's clear operation. if(ok && gate)for(unsigned group=0;group<2;++group) { @@ -7332,7 +7346,7 @@ bool XE::display13HDMIPort(void *port) { return Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) && s->adlnOutputWiring.matchesHDMI(config); } -bool XE::display13MainLinkAUX(void *port,bool acquire) { +bool XE::display13MainLinkAUX(void *port,bool acquire,bool stopping) { auto &c=display13FBPower;if(!c.lock)return false; OSObject *retired=nullptr;bool ok=false;IOLockLock(c.lock); auto p=display13SSTPort(port); @@ -7343,7 +7357,8 @@ bool XE::display13MainLinkAUX(void *port,bool acquire) { if(ddi>=3 && ddi<=6) { // Mode owns AUX from before first DPCD through buffer/PLL stop. Do not // add the Combo main-link lease or release mode power at buffer-disable. - Display13FBTCIO tc{static_cast(port),ddi-3};ok=tc.owned(ddi) && tc.linkHeld(); + Display13FBTCIO tc{static_cast(port),ddi-3}; + ok=(acquire && !stopping ? tc.owned(ddi) : tc.retainedOwned(ddi)) && tc.linkHeld(); } if(ddi<2 && Display13FBMemory::field(p,0x590)==0x64000+0x100*ddi) { auto &link=c.mainLink[ddi];const auto previous=link.owner;Display13FBPowerIO io{c.map}; @@ -7373,10 +7388,10 @@ uint32_t XE::wrapDisplay13DisablePort(void *port) { auto s=callbackXE; if(!s->adlnOutputWiring.links() || display13HDMIPort(port)) return FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); - if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; + if(!display13MainLinkAUX(port,true,true))return kIOReturnNotReady; const auto result=FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); if(!result && s->display13ClockOrderReady && !display13StopTranscoder(port))return kIOReturnNotReady; - const bool released=display13MainLinkAUX(port,false); + const bool released=display13MainLinkAUX(port,false,true); return result ? result : released ? 0 : kIOReturnNotReady; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 13d562ab..635b8833 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -154,7 +154,7 @@ class XE { static uint32_t wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,bool flag); mach_vm_address_t orgDisplay13TrainingPattern {},orgDisplay13SignalLevels {}; mach_vm_address_t orgDisplay13EnablePort {},orgDisplay13DisablePort {}; - static bool display13MainLinkAUX(void *port,bool acquire); + static bool display13MainLinkAUX(void *port,bool acquire,bool stopping=false); static uint32_t wrapDisplay13EnablePort(void *port); static uint32_t wrapDisplay13DisablePort(void *port); bool processDisplay13FramebufferSST(KernelPatcher &,size_t,mach_vm_address_t,size_t); diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index 321f6087..3a70ffdb 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -160,6 +160,7 @@ struct Runtime : IO { } bool inheritedVerified(uintptr_t,uintptr_t,unsigned){return false;} bool powerHeld(){return mode.accessLease.owner && mode.auxLease.owner && Display13Power::held(*this,power,mode.auxLease.domains);} + bool retainedOwned(unsigned d){return owned(d);} bool owned(unsigned d){return d==port.ddi && mode.phase==Display13TC::Mode::Phase::Active && mode.ownership.active && native==4;} bool quiescent(unsigned d){return d==port.ddi && !(read(port.buffer)&0x80000000U);} bool quiescent(uintptr_t,uintptr_t,unsigned d){return quiescent(d) && !(read(port.pll)&0xcc000000U) && !ddi.owner;} diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index a9329914..c014527b 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -44,11 +44,12 @@ struct Display13FBPowerIO { // TC lifecycle itself is exercised by display13_tc_mode. This fixture // supplies the ownership witness and verifies the frontend does not borrow // Combo leases; false is the default for all existing Combo regressions. -bool tcOwned=false,tcLinked=false; +bool tcOwned=false,tcLinked=false,tcConnected=true; struct Display13FBTCIO { OSObject *object;unsigned tc; - bool owned(unsigned d){return tcOwned && tc<4 && d==tc+3 && Display13FBMemory::field(object,0x550)==4;} - bool linkHeld(){return tcLinked && owned(tc+3);} + bool retainedOwned(unsigned d){return tcOwned && tc<4 && d==tc+3 && (Display13FBMemory::field(object,0x550)==4 || Display13FBMemory::field(object,0x550)==3);} + bool owned(unsigned d){return tcConnected && retainedOwned(d);} + bool linkHeld(){return tcLinked && retainedOwned(tc+3);} }; struct Display13FBSSTIO { Map *map;uint32_t read(uint32_t r){assert(locked);return map->read(r);} @@ -65,7 +66,7 @@ struct XE { Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; uintptr_t orgDisplay13EnablePort=0,orgDisplay13DisablePort=0; - static bool display13MainLinkAUX(void*,bool); + static bool display13MainLinkAUX(void*,bool,bool=false); static uint32_t wrapDisplay13EnablePort(void*);static uint32_t wrapDisplay13DisablePort(void*); static uint8_t *display13SSTPort(void*);static bool display13HDMIPort(void*); static uint32_t wrapDisplay13TCSignal(void*,uint32_t,uint32_t,void*){assert(false);return 1;} @@ -119,7 +120,7 @@ unsigned nativePattern(void *path,unsigned pattern){ } 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; + tcOwned=tcLinked=false;tcConnected=true;assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; auto &c=display13FBPower;c.controller=f.ctl;c.access=f.access;c.map=↦ currentPath=f.path; put(f.ctl,0xC8,c.power);put(c.power,0x10,static_cast(f.ctl));put(c.power,0x78,static_cast(f.access)); @@ -233,8 +234,9 @@ int main(int argc,char **argv){ // TC SST frontend: real graph/bridge and native pattern replay. The // separate lifecycle suite proves the ownership witness supplied here. for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe) - for(unsigned lanes:{1U,2U,4U})for(unsigned rate:{6U,10U,20U,30U}){ + for(unsigned lanes:{1U,2U,4U})for(unsigned rate:{6U,10U,20U,30U})for(bool alt:{false,true}){ Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,lanes,rate); + if(alt){state.adlnOutputWiring.typeC=1U< regs,bank; std::vector> writes; + bool connected {true}; bool held {},power {true},owner {true},idle {true},stuckPower {},stuckLock {},fault {}; unsigned time {},locks {},unlocks {};uint32_t reject {},absent {}; unsigned expectedLanes {4}; @@ -25,7 +26,8 @@ struct IO { } bool linkHeld(){return power&&link;} bool powerHeld(){return power;} - bool owned(unsigned d){return owner && d==p.ddi;} + bool retainedOwned(unsigned d){return owner && d==p.ddi;} + bool owned(unsigned d){return retainedOwned(d) && connected;} bool quiescent(unsigned d){return idle && d==p.ddi;} bool readable(uint32_t r){return r!=absent && (regs.count(r) || (r>=p.phy && r>p.shift)&15)<<12)|(r-p.phy);} @@ -56,6 +58,27 @@ int main() { const auto count=io.writes.size(); assert(!DPSignal::startLink(io,io.p,DPClock::calculate(10,38400),4,&stage,4,physical) && io.writes.size()==count); } + // Stop retains ownership and power even when the connector state is gone. + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned gone:{0U,4U,0x1dU,UINT32_MAX}) { + IO io(ddi);io.expectedLanes=2;io.regs[io.p.ready]=0x1d; + for(unsigned group=0;group<2;++group)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[group*0x1000+reg]=0; + assert(DPSignal::startLink(io,io.p,DPClock::calculate(10,38400),2,nullptr,4,12)); + io.connected=false;io.regs[io.p.ready]=gone; + const auto bank=io.bank;const auto peer=io.regs[Clock::Gate]&~Clock::gateBit(io.p.tc); + const auto before=io.writes.size(); + assert(!Clock::enable(io,io.p,TMDSClock::calculate(148500,38400)) && io.writes.size()==before); + assert(Clock::stopLink(io,io.p) && !io.link && io.linkPuts==1); + assert(io.bank==bank && !(io.regs[io.p.pll]&0xcc000000U)); + assert((io.regs[Clock::Gate]&~Clock::gateBit(io.p.tc))==peer); + assert(Clock::stopLink(io,io.p) && io.linkPuts==1); // no double put + } + for(unsigned fault=0;fault<7;++fault) { + IO io(4);io.connected=false;io.regs[io.p.ready]=0; + if(fault==0)io.owner=false;if(fault==1)io.power=false;if(fault==2)io.idle=false; + if(fault==3)io.regs[io.p.buffer]|=1U<<31;if(fault==4)io.regs[io.p.buffer]&=~64U; + if(fault==5)io.regs[io.p.buffer]&=~128U;if(fault==6)io.regs[io.p.pll]=UINT32_MAX; + assert(!Clock::stopLink(io,io.p) && io.writes.empty() && !io.linkPuts); + } // Independent DP divisor oracle: 8.1 GHz / (5 * link symbol clock). const unsigned rates[]={6,10,20,30},high[]={5,3,3,2},div[]={2,2,1,1}; // Existing independent Linux capture of TC3 at RBR x4. diff --git a/tests/display13_tc_disconnect.py b/tests/display13_tc_disconnect.py new file mode 100644 index 00000000..e3c4006b --- /dev/null +++ b/tests/display13_tc_disconnect.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +"""Compose the actual TC IO owner checks with clock stop and mode release.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('struct Display13FBTCIO :');b=s.index('// Only used after native object/BAR binding',a) +code=(r/'tests/display13_tc_disconnect_fixture.cpp').read_text().replace('// PRODUCTION_IO',s[a:b]).replace('// PRODUCTION_GATE',re.search(r'void XE::wrapDisplay13DPPhyGating\(.*?\n\}',s,re.S).group()) +with tempfile.TemporaryDirectory(prefix='tc-disconnect-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_tc_disconnect_fixture.cpp b/tests/display13_tc_disconnect_fixture.cpp new file mode 100644 index 00000000..3ad21dbd --- /dev/null +++ b/tests/display13_tc_disconnect_fixture.cpp @@ -0,0 +1,108 @@ +#include "kern_display13_tc_dp.hpp" +#include "kern_display13_tc_mode.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_output_wiring.hpp" +#include +#include +#include +#include +#include +#include +using namespace Display13TC; +struct OSObject {alignas(8) uint8_t bytes[0x700]{};const OSObject*getMetaClass(){return this;}unsigned getClassSize()const{return sizeof(bytes);}}; +struct MMIO { + std::map regs;std::vector writes,reads;bool noConnectorReads=false; + bool readable(unsigned r){return regs.count(r);} + unsigned read(unsigned r){assert(readable(r));if(noConnectorReads)assert(!(r>=0x161500&&r<=0x16150c)&&r!=0xc4000&&r!=0x44470&&r!=0x1638a0&&r!=0x163880&&r!=0x16e8a0&&r!=0x16e880);reads.push_back(r);return regs[r];} + void write(unsigned r,unsigned v){assert(readable(r));writes.push_back(r); + if(r==0x45404||r==0x45444||r==0x45454){unsigned mask=r==0x45404?0xa80aU:0x2aaaU;v=(v&~(mask>>1))|((v&mask)>>1);} + if(r>=0x46038&&r<=0x46050){v=(v&~(Clock::Locked|Clock::Powered))|((v&Clock::Enable)?Clock::Locked:0)|((v&Clock::Power)?Clock::Powered:0);} + regs[r]=v; + } +}; +struct Context { + Display13Power::NativeBridge bridge;Mode tcMode[4];OSObject *tcObject[4]{};MMIO *map=nullptr; + alignas(8) uint8_t controllerBytes[0xc68]{},powerBytes[0x80]{}; + void *controller=controllerBytes,*power=powerBytes,*lock=(void*)1;bool tcSignalReady[4]{},clockFaulted=false; +} display13FBPower; +struct Display13FBPowerIO { + MMIO *map; + bool readable(unsigned r){return map->readable(r);}unsigned read(unsigned r){return map->read(r);}void write(unsigned r,unsigned v){map->write(r,v);}void delayUs(unsigned){} + bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned t){return (read(0x64300+256*t)&64)!=0;}bool tcAuxHealthy(unsigned){return true;} +}; +struct Display13FBTCAuxIO : Display13FBPowerIO { + unsigned tc;Display13FBTCAuxIO(MMIO*m,unsigned t):Display13FBPowerIO{m},tc(t){} + bool admitted(unsigned t){return t==tc&&tc<4&&map==display13FBPower.map;} + unsigned lock(){return 0;}void unlock(unsigned){}void bankFault(){assert(false);} +}; +void IODelay(unsigned){} +// PRODUCTION_IO +bool locked=false;void IOLockLock(void*){assert(!locked);locked=true;}void IOLockUnlock(void*){assert(locked);locked=false;} +void adlnFault(bool&fault,unsigned){fault=true;} +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE { + Display13OutputWiring adlnOutputWiring;uintptr_t orgDisplay13DPPhyGating=0; + static unsigned display13TCTarget(OSObject*p){return Display13FBMemory::field(p,0x120);} + static void wrapDisplay13DPPhyGating(void*,bool); +} instance,*callbackXE=&instance; +// PRODUCTION_GATE +struct Transport : Display13FBTCIO { + using Display13FBTCIO::Display13FBTCIO; + using Display13FBTCIO::powerHeld;using Display13FBTCIO::quiescent; + bool admissionReady(){return true;}bool inheritedVerified(uintptr_t,uintptr_t,unsigned){return false;} + unsigned nativeMode(){return Display13FBMemory::field(object,0x550);} + unsigned nativeSetMode(unsigned mode){memcpy(object->bytes+0x550,&mode,4);return 0;} + Display13Power::Result acquireDomains(Display13Power::Lease&l,unsigned d){Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,l,d);} + Display13Power::Result releaseDomains(Display13Power::Lease&l){Display13FBPowerIO io{map};return Display13Power::release(io,display13FBPower.bridge.state,l);} + bool powerHeld(uintptr_t c,uintptr_t o,unsigned d){return c==reinterpret_cast(display13FBPower.controller)&&o==reinterpret_cast(object)&&d==tc+3&&accessHeld();} + bool quiescent(uintptr_t c,uintptr_t o,unsigned d){return powerHeld(c,o,d)&&quiescent(d)&&!display13FBPower.bridge.nativeDDIRefs(d)&&!(read(0x46038+8*tc)&0xcc000000U);} +}; +templatevoid put(void*p,unsigned at,T value){memcpy(static_cast(p)+at,&value,sizeof(value));} +int main(){ + for(unsigned d=3;d<=6;++d)for(unsigned gone:{0U,4U,0x2dU,UINT32_MAX})for(bool cleanup:{false,true}){ + display13FBPower.~Context();new(&display13FBPower)Context;auto &c=display13FBPower;MMIO map;c.map=↦ + Port p;assert(Port::resolve(d,p));OSObject port;c.tcObject[p.tc]=&port; + unsigned cfg[6]={d};put(port.bytes,0x548,cfg);put(port.bytes,0x120,d); + instance.adlnOutputWiring.dp=instance.adlnOutputWiring.typeC=1U<(&map)); + map.regs={{0x45404,0},{0x45444,0},{0x45454,0},{0x42000,0x8e380000},{p.ready,0x1d},{p.buffer,0x80},{p.pll,0},{p.clock,0},{Clock::Gate,0xa5000000|Clock::gateBit(p.tc)}, + {p.laneAssignment(),12U<<(8*(p.tc&1))},{p.pinAssignment(),4U<<(4*(p.tc&1))},{0xc4000,0},{0x44470,1U<<(16+p.tc)}, + {0x60400,0},{0x70008,0},{0x46140,0},{p.hip,0x01010101},{p.phyRegister(0xa0),0}}; + Display13FBPowerIO power{&map};assert(Display13Power::initialize(power,c.bridge.state,Display13Power::nativeDomains(0x7b)));c.bridge.begun=true;c.bridge.selectedPorts=0x78; + Transport io{&port,p.tc};auto &mode=c.tcMode[p.tc];const auto who=reinterpret_cast(c.controller),obj=reinterpret_cast(&port); + assert(mode.connect(io,who,obj,d,3).status==Status::Complete&&io.owned(d)); + assert(c.bridge.getDDI(power,d).status==Display13Power::Status::Complete); + map.regs[p.pll]=0xcc000000;map.regs[p.clock]=0x80001234;map.regs[Clock::Gate]&=~Clock::gateBit(p.tc);c.tcSignalReady[p.tc]=true; + map.regs[0x44470]=0;map.regs[p.ready]=gone;map.regs[p.laneAssignment()]=map.regs[p.pinAssignment()]=0; + assert(!io.owned(d)&&io.retainedOwned(d)); + // The power-down PHY gate keeps the captured assignment after HPD falls. + // Pin D mask C selects only the second physical pair. + map.writes.clear();XE::wrapDisplay13DPPhyGating(&port,true); + assert(!c.clockFaulted&&map.regs[p.phyRegister(0xa0)]==0x3e&&map.writes.size()==1); + if(cleanup){mode.phase=Mode::Phase::Cleanup;io.nativeSetMode(0);assert(io.retainedOwned(d));} + const auto buffer=map.regs[p.buffer]; + map.regs[p.buffer]&=~64U;assert(!io.retainedOwned(d)); + map.regs[p.buffer]=UINT32_MAX;assert(!io.retainedOwned(d));map.regs[p.buffer]=buffer; + OSObject stranger;c.tcObject[p.tc]=&stranger;assert(!io.retainedOwned(d));c.tcObject[p.tc]=&port; + auto leaseOwner=mode.auxLease.owner;mode.auxLease.owner=nullptr;assert(!io.retainedOwned(d));mode.auxLease.owner=leaseOwner; + const auto owner=mode.ownership.owner;mode.ownership.owner=owner+1; + assert(!io.retainedOwned(d));mode.ownership.owner=owner; + const auto peer=map.regs[Clock::Gate]&~Clock::gateBit(p.tc);map.writes.clear();map.noConnectorReads=true; + map.regs[p.buffer]|=1U<<31;assert(!Clock::stopLink(io,p)&&map.writes.empty());map.regs[p.buffer]&=~(1U<<31); + const auto wells=map.regs[0x45404];map.regs[0x45404]|=1U<<10; + map.regs[0x60400]=0x80000000U|((d+1)<<27);map.regs[0x70008]=0xc0000000; + assert(!Clock::stopLink(io,p)&&map.writes.empty()); + map.regs[0x60400]=map.regs[0x70008]=0;map.regs[0x45404]=wells; + assert(Clock::stopLink(io,p)&&!c.bridge.nativeDDIRefs(d)&&!(map.regs[p.pll]&0xcc000000U)); + assert((map.regs[Clock::Gate]&~Clock::gateBit(p.tc))==peer); + c.tcSignalReady[p.tc]=false; + assert(mode.disconnect(io,who,obj,d).status==Status::Complete&&!io.nativeMode()); + assert(!(map.regs[p.buffer]&64)&&mode.phase==Mode::Phase::Idle&&!mode.lanes.mask); + for(auto refs:c.bridge.state.refs)assert(!refs); + // Reconnect must capture the new assignment, not the previous orientation. + map.noConnectorReads=false;map.regs[p.ready]=0x1d;map.regs[0x44470]=1U<<(16+p.tc); + map.regs[p.laneAssignment()]=3U<<(8*(p.tc&1));map.regs[p.pinAssignment()]=4U<<(4*(p.tc&1)); + assert(mode.connect(io,who,obj,d,3).status==Status::Complete&&mode.lanes.mask==3&&io.owned(d)); + } + puts("PASS real TC owner IO: lost HPD/mode/assignment, retained identity and power, clock stop then mode release, new orientation on reconnect"); +} diff --git a/tools/test.py b/tools/test.py index f88ff01f..4bc05c1c 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_retirement','display13_trial_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)] From 46a63bf14b704096ee4afa935cae6990ab145dc0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:46:20 +0900 Subject: [PATCH 007/139] Complete deferred Type-C mode release after native path removal --- WhateverXe/kern_xe.cpp | 41 +++++++++++++++++++---- WhateverXe/kern_xe.hpp | 1 + tests/display13_tc_disconnect.py | 3 ++ tests/display13_tc_disconnect_fixture.cpp | 37 ++++++++++++++++---- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index c0f26af4..6d6dd3cc 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4943,6 +4943,17 @@ struct Display13FBTCIO : Display13FBTCAuxIO { } return true; } + bool modeQuiescent(uintptr_t controller,uintptr_t port,unsigned ddi) { + auto &c=display13FBPower; + if(controller!=reinterpret_cast(c.controller) || port!=reinterpret_cast(object) || + ddi!=tc+3 || c.tcBusy[tc] || c.bridge.nativeDDIRefs(ddi) || c.bridge.nativeAUXRefs(ddi) || + !quiescent(ddi))return false; + // 0x570 belongs to cached AGDCDPLinkConfig, cleared by the native + // resetSoftwareState callback. It is not an outstanding-use count. + Display13TC::Port p;Display13TC::Port::resolve(ddi,p); + const auto pll=read(p.pll),aux=read(0x64310+0x100*tc); + return pll!=UINT32_MAX && aux!=UINT32_MAX && !(pll&0xcc000000) && !(aux&0x80000000); + } void delayUs(unsigned us){IODelay(us);} }; // Only used after native object/BAR binding and the power lease proof. The @@ -6585,13 +6596,7 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { port==reinterpret_cast(object) && d==tc+3; } bool quiescent(uintptr_t controller,uintptr_t port,unsigned d) { - auto &c=display13FBPower; - if(controller!=reinterpret_cast(c.controller) || port!=reinterpret_cast(object) || - d!=tc+3 || c.tcBusy[tc] || c.bridge.nativeDDIRefs(d) || c.bridge.nativeAUXRefs(d) || - Display13FBMemory::field(object,0x570) || !Display13FBTCIO::quiescent(d))return false; - Display13TC::Port p;Display13TC::Port::resolve(d,p); - const auto pll=read(p.pll),aux=read(0x64310+0x100*tc); - return pll!=UINT32_MAX && aux!=UINT32_MAX && !(pll&0xc0000000) && !(aux&0x80000000); + return modeQuiescent(controller,port,d); } }; // Adopt only the validated, protected firmware boot route. Merely finding @@ -6631,6 +6636,27 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { return done ? kIOReturnSuccess : kIOReturnNotReady; } +void XE::drainDisplay13TCMode(unsigned ddi) { + auto &c=display13FBPower;auto self=callbackXE; + if(!c.lock || ddi<3 || ddi>6)return; + // This continuation runs after native path removal, under its workloop + // gate. Never race a new connect while dropping the owner lock for reset. + IOLockLock(c.lock);auto port=c.tcObject[ddi-3]; + const bool gated=c.workLoop && self->adlnWorkLoopInGate && + Display13FBMemory::field(c.controller,0xE00)==c.workLoop && + reinterpret_cast(self->adlnWorkLoopInGate)(c.workLoop); + const bool pending=port && c.tcMode[ddi-3].disconnectPending && !c.tcBusy[ddi-3]; + uint32_t record[]={1,ddi,pending,gated,c.bridge.nativeDDIRefs(ddi),c.bridge.nativeAUXRefs(ddi), + port?Display13FBMemory::field(port,0x570):0,0,0}; + if(pending && gated)port->retain(); + IOLockUnlock(c.lock); + if(pending && gated) { + record[7]=wrapDisplay13TCMode(port,0); + record[8]=Display13FBMemory::field(port,0x550);port->release(); + } + if(pending)IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCDrain",record,sizeof(record)); +} + void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint32_t *params) { auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; @@ -8689,6 +8715,7 @@ uint32_t XE::releaseDisplay13Path(IOService *fb,OSObject *path) { record[7]=released;record[8]=detached;record[9]=removed;record[10]=withdrawn!=nullptr; record[11]=c.memoryFaulted;record[12]=c.dbuf.selected;record[13]=c.dbuf.consumers;record[14]=c.dbuf.unretired;record[15]=countOffset; IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex); + if(removed)drainDisplay13TCMode(binding.ddi); OSSafeReleaseNULL(withdrawn);path->release();fb->release(); adlnPublishMemory("N97FBPathFree",pipe,record,sizeof(record)); return selected && !removed ? kIOReturnNotReady : 0; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 635b8833..e61d37aa 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -232,6 +232,7 @@ class XE { bool processDisplay13FramebufferTC(KernelPatcher &,size_t,mach_vm_address_t,size_t); static unsigned display13TCTarget(OSObject *,bool cleanup=false); static uint32_t wrapDisplay13TCMode(OSObject *,uint32_t); + static void drainDisplay13TCMode(unsigned); static void wrapDisplay13TCEnableClocks(void *,void *,void *,uint32_t *); static void wrapDisplay13TCDisableClocks(void *,void *,void *); static uint32_t wrapDisplay13TCSignal(void *,uint32_t,uint32_t,void *); diff --git a/tests/display13_tc_disconnect.py b/tests/display13_tc_disconnect.py index e3c4006b..7abcc20f 100644 --- a/tests/display13_tc_disconnect.py +++ b/tests/display13_tc_disconnect.py @@ -5,6 +5,9 @@ r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() a=s.index('struct Display13FBTCIO :');b=s.index('// Only used after native object/BAR binding',a) code=(r/'tests/display13_tc_disconnect_fixture.cpp').read_text().replace('// PRODUCTION_IO',s[a:b]).replace('// PRODUCTION_GATE',re.search(r'void XE::wrapDisplay13DPPhyGating\(.*?\n\}',s,re.S).group()) +code=code.replace('// PRODUCTION_DRAIN',re.search(r'void XE::drainDisplay13TCMode\(.*?\n\}',s,re.S).group()) +release=s[s.index('uint32_t XE::releaseDisplay13Path('):s.index('void XE::wrapAlderLakeNFreePath(')] +assert release.index('orgAlderLakeNFreePath)(fb,path)') < release.index('if(removed)drainDisplay13TCMode(binding.ddi);') with tempfile.TemporaryDirectory(prefix='tc-disconnect-') as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) diff --git a/tests/display13_tc_disconnect_fixture.cpp b/tests/display13_tc_disconnect_fixture.cpp index 3ad21dbd..391e4e84 100644 --- a/tests/display13_tc_disconnect_fixture.cpp +++ b/tests/display13_tc_disconnect_fixture.cpp @@ -9,7 +9,10 @@ #include #include using namespace Display13TC; -struct OSObject {alignas(8) uint8_t bytes[0x700]{};const OSObject*getMetaClass(){return this;}unsigned getClassSize()const{return sizeof(bytes);}}; +struct OSObject {alignas(8) uint8_t bytes[0x700]{};int refs=1;void retain(){++refs;}void release(){assert(refs>0);--refs;}const OSObject*getMetaClass(){return this;}unsigned getClassSize()const{return sizeof(bytes);}}; +struct IOWorkLoop {} workLoop; +bool inGate=true;bool workLoopInGate(const IOWorkLoop*){return inGate;} +struct IORegistryEntry {static IORegistryEntry*getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,const void*,unsigned){}}; struct MMIO { std::map regs;std::vector writes,reads;bool noConnectorReads=false; bool readable(unsigned r){return regs.count(r);} @@ -22,7 +25,8 @@ struct MMIO { }; struct Context { Display13Power::NativeBridge bridge;Mode tcMode[4];OSObject *tcObject[4]{};MMIO *map=nullptr; - alignas(8) uint8_t controllerBytes[0xc68]{},powerBytes[0x80]{}; + alignas(8) uint8_t controllerBytes[0xe08]{},powerBytes[0x80]{}; + unsigned tcBusy[4]{};IOWorkLoop *workLoop=&::workLoop; void *controller=controllerBytes,*power=powerBytes,*lock=(void*)1;bool tcSignalReady[4]{},clockFaulted=false; } display13FBPower; struct Display13FBPowerIO { @@ -42,8 +46,11 @@ void adlnFault(bool&fault,unsigned){fault=true;} #define FunctionCast(f,p) reinterpret_cast(p) struct XE { Display13OutputWiring adlnOutputWiring;uintptr_t orgDisplay13DPPhyGating=0; + uintptr_t adlnWorkLoopInGate=reinterpret_cast(workLoopInGate); static unsigned display13TCTarget(OSObject*p){return Display13FBMemory::field(p,0x120);} static void wrapDisplay13DPPhyGating(void*,bool); + static unsigned wrapDisplay13TCMode(OSObject*,unsigned); + static void drainDisplay13TCMode(unsigned); } instance,*callbackXE=&instance; // PRODUCTION_GATE struct Transport : Display13FBTCIO { @@ -51,12 +58,18 @@ struct Transport : Display13FBTCIO { using Display13FBTCIO::powerHeld;using Display13FBTCIO::quiescent; bool admissionReady(){return true;}bool inheritedVerified(uintptr_t,uintptr_t,unsigned){return false;} unsigned nativeMode(){return Display13FBMemory::field(object,0x550);} - unsigned nativeSetMode(unsigned mode){memcpy(object->bytes+0x550,&mode,4);return 0;} + unsigned nativeSetMode(unsigned mode){memcpy(object->bytes+0x550,&mode,4);if(!mode)memset(object->bytes+0x558,0,0x1c);return 0;} Display13Power::Result acquireDomains(Display13Power::Lease&l,unsigned d){Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,l,d);} Display13Power::Result releaseDomains(Display13Power::Lease&l){Display13FBPowerIO io{map};return Display13Power::release(io,display13FBPower.bridge.state,l);} bool powerHeld(uintptr_t c,uintptr_t o,unsigned d){return c==reinterpret_cast(display13FBPower.controller)&&o==reinterpret_cast(object)&&d==tc+3&&accessHeld();} - bool quiescent(uintptr_t c,uintptr_t o,unsigned d){return powerHeld(c,o,d)&&quiescent(d)&&!display13FBPower.bridge.nativeDDIRefs(d)&&!(read(0x46038+8*tc)&0xcc000000U);} + bool quiescent(uintptr_t c,uintptr_t o,unsigned d){return modeQuiescent(c,o,d);} }; +unsigned XE::wrapDisplay13TCMode(OSObject*p,unsigned wanted){ + assert(!wanted);auto &c=display13FBPower;unsigned d=Display13FBMemory::field(p,0x120); + Transport io{p,d-3};auto result=c.tcMode[d-3].disconnect(io,reinterpret_cast(c.controller),reinterpret_cast(p),d); + if(result.status==Status::Complete){c.tcObject[d-3]=nullptr;p->release();return 0;}return 1; +} +// PRODUCTION_DRAIN templatevoid put(void*p,unsigned at,T value){memcpy(static_cast(p)+at,&value,sizeof(value));} int main(){ for(unsigned d=3;d<=6;++d)for(unsigned gone:{0U,4U,0x2dU,UINT32_MAX})for(bool cleanup:{false,true}){ @@ -65,14 +78,17 @@ int main(){ unsigned cfg[6]={d};put(port.bytes,0x548,cfg);put(port.bytes,0x120,d); instance.adlnOutputWiring.dp=instance.adlnOutputWiring.typeC=1U<(&map)); + put(c.controller,0xe00,c.workLoop); map.regs={{0x45404,0},{0x45444,0},{0x45454,0},{0x42000,0x8e380000},{p.ready,0x1d},{p.buffer,0x80},{p.pll,0},{p.clock,0},{Clock::Gate,0xa5000000|Clock::gateBit(p.tc)}, {p.laneAssignment(),12U<<(8*(p.tc&1))},{p.pinAssignment(),4U<<(4*(p.tc&1))},{0xc4000,0},{0x44470,1U<<(16+p.tc)}, - {0x60400,0},{0x70008,0},{0x46140,0},{p.hip,0x01010101},{p.phyRegister(0xa0),0}}; + {0x60400,0},{0x70008,0},{0x46140,0},{p.hip,0x01010101},{p.phyRegister(0xa0),0},{0x64310+256*p.tc,0}}; Display13FBPowerIO power{&map};assert(Display13Power::initialize(power,c.bridge.state,Display13Power::nativeDomains(0x7b)));c.bridge.begun=true;c.bridge.selectedPorts=0x78; Transport io{&port,p.tc};auto &mode=c.tcMode[p.tc];const auto who=reinterpret_cast(c.controller),obj=reinterpret_cast(&port); assert(mode.connect(io,who,obj,d,3).status==Status::Complete&&io.owned(d)); assert(c.bridge.getDDI(power,d).status==Display13Power::Status::Complete); map.regs[p.pll]=0xcc000000;map.regs[p.clock]=0x80001234;map.regs[Clock::Gate]&=~Clock::gateBit(p.tc);c.tcSignalReady[p.tc]=true; + put(port.bytes,0x570,1U); // Cached link config survives until native reset. + assert(mode.disconnect(io,who,obj,d).status==Status::InUse&&mode.disconnectPending); map.regs[0x44470]=0;map.regs[p.ready]=gone;map.regs[p.laneAssignment()]=map.regs[p.pinAssignment()]=0; assert(!io.owned(d)&&io.retainedOwned(d)); // The power-down PHY gate keeps the captured assignment after HPD falls. @@ -96,7 +112,16 @@ int main(){ assert(Clock::stopLink(io,p)&&!c.bridge.nativeDDIRefs(d)&&!(map.regs[p.pll]&0xcc000000U)); assert((map.regs[Clock::Gate]&~Clock::gateBit(p.tc))==peer); c.tcSignalReady[p.tc]=false; - assert(mode.disconnect(io,who,obj,d).status==Status::Complete&&!io.nativeMode()); + if(!cleanup) { + inGate=false;XE::drainDisplay13TCMode(d);assert(mode.disconnectPending&&io.nativeMode()==3); + inGate=true;c.tcBusy[p.tc]=1;XE::drainDisplay13TCMode(d);assert(mode.disconnectPending); + c.tcBusy[p.tc]=0;map.regs[0x64310+256*p.tc]=0x80000000; + XE::drainDisplay13TCMode(d);assert(mode.disconnectPending); + map.regs[0x64310+256*p.tc]=0; + assert(Display13FBMemory::field(&port,0x570)==1); + XE::drainDisplay13TCMode(d);assert(!mode.disconnectPending&&!io.nativeMode()&&!c.tcObject[p.tc]&&!port.refs); + port.refs=1;c.tcObject[p.tc]=&port; + } else assert(mode.disconnect(io,who,obj,d).status==Status::Complete&&!io.nativeMode()); assert(!(map.regs[p.buffer]&64)&&mode.phase==Mode::Phase::Idle&&!mode.lanes.mask); for(auto refs:c.bridge.state.refs)assert(!refs); // Reconnect must capture the new assignment, not the previous orientation. From 4a57701948a7b2b1c9e844d53a73961bc3b69a92 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:47:27 +0900 Subject: [PATCH 008/139] Cover deferred TC release in native lifecycle fixtures --- tests/display13_retirement_fixture.cpp | 4 ++++ tests/display13_tc_preparation.py | 1 + tests/display13_tc_preparation_fixture.cpp | 1 + 3 files changed, 6 insertions(+) diff --git a/tests/display13_retirement_fixture.cpp b/tests/display13_retirement_fixture.cpp index 52613ec9..05f6b3bb 100644 --- a/tests/display13_retirement_fixture.cpp +++ b/tests/display13_retirement_fixture.cpp @@ -42,6 +42,10 @@ struct XE { uintptr_t orgAlderLakeNFreePath{},orgDisplay13FreeResources{},orgDisplay13AllocateResources{}; static bool adlnFreeStoppedPipe(IOService*); static void wrapAlderLakeNFreePath(IOService*,OSObject*); + static void drainDisplay13TCMode(unsigned ddi) { + assert(ddi==3 && ctlDepth && !ownerDepth); + assert(!display13FBPower.pipe[0].memoryFramebuffer && !display13FBPower.pipe[0].publishedScanout); + } static uint32_t releaseDisplay13Path(IOService*,OSObject*),retireDisplay13Paths(void*),wrapDisplay13FreeResources(void*); static bool display13RetirementHealthy(),wrapDisplay13AllocateResources(void*),retireDisplay13Controller(void*); }state,*callbackXE=&state; diff --git a/tests/display13_tc_preparation.py b/tests/display13_tc_preparation.py index e0006512..27e5ca05 100644 --- a/tests/display13_tc_preparation.py +++ b/tests/display13_tc_preparation.py @@ -10,6 +10,7 @@ # Admission never substitutes an installed prototype for the real mode route. assert 'display13FBTCHooksReady' in s[s.index('unsigned XE::display13TCTarget('):s.index('uint32_t XE::wrapDisplay13TCMode(')] code=(r/'tests/display13_tc_preparation_fixture.cpp').read_text().replace('// FUNCTIONS',f) +code=code.replace('// MODE_QUIESCENCE',re.search(r' bool modeQuiescent\(.*?\n \}',s,re.S).group()) with tempfile.TemporaryDirectory(prefix='tc-preparation-') as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) diff --git a/tests/display13_tc_preparation_fixture.cpp b/tests/display13_tc_preparation_fixture.cpp index db0f6311..a3656c22 100644 --- a/tests/display13_tc_preparation_fixture.cpp +++ b/tests/display13_tc_preparation_fixture.cpp @@ -46,6 +46,7 @@ struct Display13FBTCIO:Display13FBPowerIO { bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned t){return t==tc && accessHeld() && (read(0x64300+256*t)&64);} bool tcAuxHealthy(unsigned){return true;} bool quiescent(unsigned d){return !(read(0x64000+256*d)&0x80000000);} + // MODE_QUIESCENCE }; struct XE { bool display13FramebufferRequested=true,display13FBTCHooksReady=true;XELP::Profile xeProfile; From 87e1e1d2dcdd3351ca7737d3ab4d99cab76fbef2 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:56:16 +0900 Subject: [PATCH 009/139] Keep disabled primary scanout pending with a retained surface --- WhateverXe/kern_display13_wm.hpp | 14 +++++++------- tests/display13_wm.cpp | 17 +++++++++++++---- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index ecca24a5..01d84feb 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -345,13 +345,13 @@ template Latch latch(IO &io,const Snapshot &s) { if(!Display13FBIRQ::powered(power,s.pipeIndex)) {r.status=Status::Power;return r;} const auto pipe=io.read(0x70008+bank),plane=io.read(0x70180+bank),cursor=io.read(0x70080+bank); r.check=LatchCheck::Controls;r.pipeControl=pipe;r.planeControl=plane;r.cursorControl=cursor; - // Native enablePlane may return before any scanout is attached, while - // programPipeScaler still flushes a zero SURF trigger via armUpdate. - // A disabled primary with no intended surface is not a live latch to - // prove yet, even if the native cursor becomes active first. Keep - // WM/DDB pending; never retire storage or certify the - // shared clock until a later real plane/surface update passes below. - const bool preparing=plane!=UINT32_MAX && !(plane&Enabled) && !s.primarySurface; + // Native enablePlane/scaler can flush WM before enabling scanout. + // Reconnect may retain a nonzero SURF from the previous path, so a + // surface address alone cannot distinguish this from initial setup. + // A disabled primary has no live latch to prove, even if the cursor + // is already active. Keep WM/DDB pending and the shared clock + // unqualified until a later enabled plane passes the full proof. + const bool preparing=plane!=UINT32_MAX && !(plane&Enabled); if(pipe==UINT32_MAX || plane==UINT32_MAX || cursor==UINT32_MAX || (pipe&0xC0000000)!=0xC0000000 || (!preparing && (!(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled)) || bool(cursor&0x27)!=s.cursorActive || (s.cursorActive && !cursorMatches(cursor,s.cursor->width))) {r.status=Status::Conflict;return r;} diff --git a/tests/display13_wm.cpp b/tests/display13_wm.cpp index d342b560..e44a1dcf 100644 --- a/tests/display13_wm.cpp +++ b/tests/display13_wm.cpp @@ -358,14 +358,23 @@ int main() { assert(latch(wrongWM,wrongWM.snapshot(p)).status==Status::Readback); LatchIO wrongLine(p);wrongLine.moveToPipe(pipe);wrongLine.regs[0x45270+4*pipe]=118; assert(latch(wrongLine,wrongLine.snapshot(p)).status==Status::Readback); - // The diagnostic must retain the rejecting branch and the values - // already consumed by it, including pipe C, without probing farther. + // Reconnect keeps the old SURF while native WM/scaler flushes precede + // plane enable. A nonzero address cannot certify an active scanout. LatchIO disabledPlane(p);disabledPlane.moveToPipe(pipe); disabledPlane.regs[0x70180+0x1000*pipe]&=~Enabled; const auto controls=latch(disabledPlane,disabledPlane.snapshot(p)); - assert(controls.status==Status::Conflict && controls.check==LatchCheck::Controls && + assert(controls.status==Status::Unavailable && controls.check==LatchCheck::AwaitingScanout && controls.pipeControl==0xC0000000 && controls.planeControl==0x04000000 && - !controls.cursorControl && disabledPlane.reads.back()==0x70080+0x1000*pipe && disabledPlane.writes.empty()); + !controls.cursorControl && disabledPlane.writes.empty() && !disabledPlane.elapsed && controls.frameAfter==0); + disabledPlane.regs[0x70280+0x1000*pipe]=Enabled; + assert(latch(disabledPlane,disabledPlane.snapshot(p)).status==Status::Conflict); + disabledPlane.regs[0x70280+0x1000*pipe]=0; + disabledPlane.regs[0x70180+0x1000*pipe]|=Enabled; + disabledPlane.frozen=true; + assert(latch(disabledPlane,disabledPlane.snapshot(p)).status==Status::CommitTimeout); + disabledPlane.frozen=false;disabledPlane.elapsed=0; + disabledPlane.regs[0x701AC+0x1000*pipe]=0; + assert(latch(disabledPlane,disabledPlane.snapshot(p)).status==Status::Complete); // Native scaler flush before the first attached scanout: timing and // WM are programmed, but the primary remains disabled and SURF is 0. // This is pending, not success; a later enabled surface must earn the From 1eb795df7f33d0859320de3da062d53124e39936 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:03:24 +0900 Subject: [PATCH 010/139] Capture held Display13 DP link state after LightUp --- WhateverXe/kern_xe.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 6d6dd3cc..ac33c2f4 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6859,6 +6859,35 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c const auto result=io.dp?Display13TC::lightUpDP(io,io.ddi,io.pipeIndex):Display13TC::lightUpTMDS(io,io.ddi,io.pipeIndex); uint32_t record[]={1,io.ddi,io.pipeIndex,uint32_t(result.stage),result.status}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCLightUp",record,sizeof(record)); + // Bounded post-LightUp readout, while the exact port/pipe leases are held. + // Observe only the DP-assigned TX pair; never change signal programming. + uint32_t link[32] {1,io.ddi,io.pipeIndex,result.status};bool captured=false; + IOLockLock(c.lock); + if(!result.status && io.dp && io.ddi>=3 && io.ddi<=6 && io.pipeIndex<3) { + Display13FBTCIO tc{port,io.ddi-3};Display13FBPowerIO power{c.map};Display13TC::Port p; + if(tc.owned(io.ddi) && c.bridge.outputHeld(power,io.ddi,io.pipeIndex) && Display13TC::Port::resolve(io.ddi,p)) { + const auto &mode=c.tcMode[io.ddi-3];const auto bank=io.pipeIndex*0x1000; + link[4]=mode.selectedMode;link[5]=mode.lanes.mask;link[6]=mode.lanes.pin; + link[7]=Display13FBMemory::field(port,0x128);link[8]=Display13FBMemory::field(port,0x124); + const uint32_t regs[]={p.buffer,p.ready,p.clock,p.pll,Display13TC::Clock::laneRegister(p.tc),0x44470, + 0x60400+bank,0x60540+bank,0x46140+4*io.pipeIndex,0x70008+bank, + 0x60030+bank,0x60034+bank,0x60040+bank,0x60044+bank}; + for(unsigned i=0;isetProperty(key,link,sizeof(link));} return result.status; } From 2f31f341673b3460647fd6da31335c63f4c3e529 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:11:18 +0900 Subject: [PATCH 011/139] Release Display13 DP PCS reset before enabling the buffer --- WhateverXe/kern_display13_tc_dp.hpp | 21 +++++++++++++++++++++ WhateverXe/kern_xe.cpp | 13 +++++++++++-- tests/display13_tc_clock.cpp | 21 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_display13_tc_dp.hpp b/WhateverXe/kern_display13_tc_dp.hpp index ec126145..f0942f3d 100644 --- a/WhateverXe/kern_display13_tc_dp.hpp +++ b/WhateverXe/kern_display13_tc_dp.hpp @@ -51,6 +51,27 @@ struct DPRetainedLink { } }; struct DPSignal { + // ADL-P/N, Linux adlp_tbt_to_dp_alt_switch_wa: release both PCS cores + // after selecting DP training and before enabling the DDI buffer. + // This is independent of the current USB-C orientation and pin mask. + template static bool prepareBuffer(IO &io,const Port &p,uint32_t before[2],uint32_t after[2]) { + if(!io.powerHeld() || !io.linkHeld() || !io.owned(p.ddi) || !io.readable(p.buffer))return false; + const auto buffer=io.read(p.buffer);if(buffer==UINT32_MAX)return false; + for(unsigned group=0;group<2;++group) { + if(!PHY::access(io,p,group*0x1000+0x14,UINT32_MAX,before[group],false))return false; + after[group]=before[group]; + } + if(!((before[0]|before[1])&0x800))return true; + // A retained live link must already have released reset. Do not + // alter PCS underneath an enabled or not-yet-idle transmitter. + if((buffer&0x80000080U)!=0x80)return false; + for(unsigned group=0;group<2;++group)if(before[group]&0x800) { + uint32_t value=0; + if(!PHY::access(io,p,group*0x1000+0x14,0x800,value,true))return false; + after[group]=value; + } + return true; + } static bool coefficients(uint32_t rate,uint32_t swing,uint32_t emphasis,uint32_t &value) { if((rate!=6 && rate!=10 && rate!=20 && rate!=30) || swing>3 || emphasis>3 || swing+emphasis>3)return false; constexpr uint8_t starts[]={0,4,7,9},voltage[]={7,5,2,0,5,2,0,2,0,0}; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ac33c2f4..67b8b8ba 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6793,8 +6793,17 @@ void XE::wrapDisplay13DPPortWrite(void *port,uint32_t reg,uint32_t value) { IOLockLock(c.lock);const auto ddi=display13TCTarget(object); if(ddi>=3 && ddi<=6 && (s->adlnOutputWiring.dp&(1U<(port,0x128); - if(io.owned(ddi) && io.linkHeld() && (rate==6 || rate==10 || rate==20 || rate==30)) - value=(value&~0xff00U)|(((rate*27+9)/10)<<8); + Display13TC::Port p;uint32_t before[2]{},after[2]{}; + const bool ready=io.owned(ddi) && io.linkHeld() && (rate==6 || rate==10 || rate==20 || rate==30) && + Display13TC::Port::resolve(ddi,p) && Display13TC::DPSignal::prepareBuffer(io,p,before,after); + uint32_t pcs[]={1,ddi,before[0],before[1],after[0],after[1],ready}; + char key[40];snprintf(key,sizeof(key),"WEXDisplay13TCPCS%u",ddi); + IORegistryEntry::getRegistryRoot()->setProperty(key,pcs,sizeof(pcs)); + if((before[0]|before[1])&0x800) { + snprintf(key,sizeof(key),"WEXDisplay13TCPCSRelease%u",ddi); + IORegistryEntry::getRegistryRoot()->setProperty(key,pcs,sizeof(pcs)); + } + if(ready)value=(value&~0xff00U)|(((rate*27+9)/10)<<8); else {adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return;} } IOLockUnlock(c.lock); diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index 2b4c4a47..b6204301 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -47,6 +47,27 @@ struct IO { void bankFault(){fault=true;} }; int main() { + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned reset=0;reset<4;++reset) { + IO io(ddi);io.link=true; + io.bank[0x14]=0xa5a50001|((reset&1)?0x800:0); + io.bank[0x1014]=0x5a5a0002|((reset&2)?0x800:0); + const auto hip=io.regs[io.p.hip];uint32_t before[2]{},after[2]{}; + assert(DPSignal::prepareBuffer(io,io.p,before,after)); + assert(after[0]==0xa5a50001 && after[1]==0x5a5a0002); + assert(io.bank[0x14]==after[0] && io.bank[0x1014]==after[1] && io.regs[io.p.hip]==hip); + io.regs[io.p.buffer]|=0x80000000; + assert(DPSignal::prepareBuffer(io,io.p,before,after)); // retained, already released + io.bank[0x14]|=0x800;const auto prior=io.bank; + assert(!DPSignal::prepareBuffer(io,io.p,before,after) && io.bank==prior); + } + for(unsigned failure=0;failure<6;++failure) { + IO io(3);io.link=true;io.bank[0x14]=io.bank[0x1014]=0x800; + if(failure==0)io.power=false;if(failure==1)io.owner=false;if(failure==2)io.link=false; + if(failure==3)io.regs[io.p.buffer]=UINT32_MAX;if(failure==4)io.regs[io.p.buffer]&=~128U; + if(failure==5)io.reject=io.p.phyRegister(0x14); + uint32_t before[2]{},after[2]{};assert(!DPSignal::prepareBuffer(io,io.p,before,after)); + assert(io.bank[0x14]==0x800 && io.bank[0x1014]==0x800); + } // Pin D, both physical orientations: do not change the USB pair's // signal coefficients or wake register. Logical main-link lanes stay0/1. for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned physical:{3U,12U}) { From 554e54a72c40d1cbad3cf8e233692dab450e931d Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:15:49 +0900 Subject: [PATCH 012/139] Remove temporary Display13 hotplug register snapshots --- WhateverXe/kern_xe.cpp | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 67b8b8ba..45a16b96 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6796,13 +6796,6 @@ void XE::wrapDisplay13DPPortWrite(void *port,uint32_t reg,uint32_t value) { Display13TC::Port p;uint32_t before[2]{},after[2]{}; const bool ready=io.owned(ddi) && io.linkHeld() && (rate==6 || rate==10 || rate==20 || rate==30) && Display13TC::Port::resolve(ddi,p) && Display13TC::DPSignal::prepareBuffer(io,p,before,after); - uint32_t pcs[]={1,ddi,before[0],before[1],after[0],after[1],ready}; - char key[40];snprintf(key,sizeof(key),"WEXDisplay13TCPCS%u",ddi); - IORegistryEntry::getRegistryRoot()->setProperty(key,pcs,sizeof(pcs)); - if((before[0]|before[1])&0x800) { - snprintf(key,sizeof(key),"WEXDisplay13TCPCSRelease%u",ddi); - IORegistryEntry::getRegistryRoot()->setProperty(key,pcs,sizeof(pcs)); - } if(ready)value=(value&~0xff00U)|(((rate*27+9)/10)<<8); else {adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return;} } @@ -6868,35 +6861,6 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c const auto result=io.dp?Display13TC::lightUpDP(io,io.ddi,io.pipeIndex):Display13TC::lightUpTMDS(io,io.ddi,io.pipeIndex); uint32_t record[]={1,io.ddi,io.pipeIndex,uint32_t(result.stage),result.status}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCLightUp",record,sizeof(record)); - // Bounded post-LightUp readout, while the exact port/pipe leases are held. - // Observe only the DP-assigned TX pair; never change signal programming. - uint32_t link[32] {1,io.ddi,io.pipeIndex,result.status};bool captured=false; - IOLockLock(c.lock); - if(!result.status && io.dp && io.ddi>=3 && io.ddi<=6 && io.pipeIndex<3) { - Display13FBTCIO tc{port,io.ddi-3};Display13FBPowerIO power{c.map};Display13TC::Port p; - if(tc.owned(io.ddi) && c.bridge.outputHeld(power,io.ddi,io.pipeIndex) && Display13TC::Port::resolve(io.ddi,p)) { - const auto &mode=c.tcMode[io.ddi-3];const auto bank=io.pipeIndex*0x1000; - link[4]=mode.selectedMode;link[5]=mode.lanes.mask;link[6]=mode.lanes.pin; - link[7]=Display13FBMemory::field(port,0x128);link[8]=Display13FBMemory::field(port,0x124); - const uint32_t regs[]={p.buffer,p.ready,p.clock,p.pll,Display13TC::Clock::laneRegister(p.tc),0x44470, - 0x60400+bank,0x60540+bank,0x46140+4*io.pipeIndex,0x70008+bank, - 0x60030+bank,0x60034+bank,0x60040+bank,0x60044+bank}; - for(unsigned i=0;isetProperty(key,link,sizeof(link));} return result.status; } From 2fdb93bdb4c68050b6ecff2e3587f3e189270135 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:19:23 +0900 Subject: [PATCH 013/139] Program Display13 DDI PHY link rate for DP --- WhateverXe/kern_display13_tc_dp.hpp | 7 +++++++ WhateverXe/kern_xe.cpp | 2 +- tests/display13_tc_clock.cpp | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_display13_tc_dp.hpp b/WhateverXe/kern_display13_tc_dp.hpp index f0942f3d..57194981 100644 --- a/WhateverXe/kern_display13_tc_dp.hpp +++ b/WhateverXe/kern_display13_tc_dp.hpp @@ -51,6 +51,13 @@ struct DPRetainedLink { } }; struct DPSignal { + // ADL-P/N DDI_BUF_PHY_LINK_RATE, Linux ddi_buf_phy_link_rate(). + // The TGL donor's bits15:8 do not select this Display13 PHY rate. + static bool bufferRate(uint32_t rate,uint32_t &buffer) { + unsigned code; + switch(rate) {case 6:code=0;break;case 10:code=1;break;case 20:code=2;break;case 30:code=3;break;default:return false;} + buffer=(buffer&~0x700000U)|(code<<20);return true; + } // ADL-P/N, Linux adlp_tbt_to_dp_alt_switch_wa: release both PCS cores // after selecting DP training and before enabling the DDI buffer. // This is independent of the current USB-C orientation and pin mask. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 45a16b96..e8b04141 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6794,7 +6794,7 @@ void XE::wrapDisplay13DPPortWrite(void *port,uint32_t reg,uint32_t value) { if(ddi>=3 && ddi<=6 && (s->adlnOutputWiring.dp&(1U<(port,0x128); Display13TC::Port p;uint32_t before[2]{},after[2]{}; - const bool ready=io.owned(ddi) && io.linkHeld() && (rate==6 || rate==10 || rate==20 || rate==30) && + const bool ready=io.owned(ddi) && io.linkHeld() && Display13TC::DPSignal::bufferRate(rate,value) && Display13TC::Port::resolve(ddi,p) && Display13TC::DPSignal::prepareBuffer(io,p,before,after); if(ready)value=(value&~0xff00U)|(((rate*27+9)/10)<<8); else {adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return;} diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index b6204301..0853b6ce 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -47,6 +47,16 @@ struct IO { void bankFault(){fault=true;} }; int main() { + // Independent Linux DP DDI rate encodings; retain ownership, lane width, + // reversal, enable and all unrelated bits, including stale rate bits. + for(const auto &pair:std::map{{6,0},{10,1},{20,2},{30,3}}) { + uint32_t value=0xfff1abcd; + assert(DPSignal::bufferRate(pair.first,value)); + assert(value==((0xfff1abcdU&~0x700000U)|(pair.second<<20))); + } + for(unsigned invalid:{0U,1U,12U,UINT32_MAX}) { + uint32_t value=0xa5a5a5a5;assert(!DPSignal::bufferRate(invalid,value) && value==0xa5a5a5a5); + } for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned reset=0;reset<4;++reset) { IO io(ddi);io.link=true; io.bank[0x14]=0xa5a50001|((reset&1)?0x800:0); From ecec01d6f204a3e99a4b70ca73c3830d0ae5806d Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:20:15 +0900 Subject: [PATCH 014/139] Clarify PHY rate fixture preservation contract --- tests/display13_tc_clock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index 0853b6ce..e669a7e9 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -48,7 +48,7 @@ struct IO { }; int main() { // Independent Linux DP DDI rate encodings; retain ownership, lane width, - // reversal, enable and all unrelated bits, including stale rate bits. + // reversal, enable and unrelated bits; replace the previous rate code. for(const auto &pair:std::map{{6,0},{10,1},{20,2},{30,3}}) { uint32_t value=0xfff1abcd; assert(DPSignal::bufferRate(pair.first,value)); From dbfcb5538f528d68f6c3028a5a3971b69f3a2165 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:25:11 +0900 Subject: [PATCH 015/139] Keep Display13 AUX disconnects from faulting retained ownership --- WhateverXe/kern_xe.cpp | 12 ++++++++---- WhateverXe/kern_xe.hpp | 2 +- tests/display13_aux_fixture.cpp | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e8b04141..91e2e841 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7066,7 +7066,7 @@ bool XE::processDisplay13FramebufferAUX(KernelPatcher &patcher,size_t index,mach // Caller holds the display owner lock. Probe may run before setPortMode, // hence mode0 is allowed; a DDI/main-link lease is deliberately not required. -bool XE::display13AUXReady(void *port,bool command) { +bool XE::display13AUXReady(void *port,bool command,bool retained) { auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); if(!s->display13FramebufferRequested || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBAUXHooksReady || !object || object->getMetaClass()->getClassSize()<0x5AC || !c.controller || @@ -7087,7 +7087,8 @@ bool XE::display13AUXReady(void *port,bool command) { Display13FBMemory::field(c.power,0x78)!=c.access)return false; if(config[0]>=3) { Display13FBTCIO tc{object,config[0]-3}; - if(!(s->adlnOutputWiring.dp&(1U<adlnOutputWiring.dp&(1U<orgDisplay13RunAUX)(port,command,packet,length,flag); IOLockLock(c.lock);Display13FBPowerIO io {c.map};const bool held=c.bridge.endAUXCommand(io,ddi); - const bool complete=display13AUXReady(port,false) && held; - if(!complete)adlnFault(c.bridge.state.faulted,__LINE__); + // HPD/assignment can disappear while the donor waits for the AUX reply. + // Reject that reply, but only lost identity/power/leases poison the owner. + const bool protectedCompletion=held && display13AUXReady(port,false,true); + const bool complete=protectedCompletion && display13AUXReady(port,false); + if(!protectedCompletion)adlnFault(c.bridge.state.faulted,__LINE__); IOLockUnlock(c.lock);return r ? r : complete ? 0 : kIOReturnNotReady; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e61d37aa..7183eb97 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -147,7 +147,7 @@ class XE { mach_vm_address_t display13AUXControllerSlot {}; mach_vm_address_t orgDisplay13ReadAUX {},orgDisplay13WriteAUX {},orgDisplay13I2CAUX {},orgDisplay13RunAUX {}; bool processDisplay13FramebufferAUX(KernelPatcher &,size_t,mach_vm_address_t,size_t); - static bool display13AUXReady(void *port,bool command); + static bool display13AUXReady(void *port,bool command,bool retained=false); static uint32_t wrapDisplay13ReadAUX(void *port,uint32_t address,void *data,uint32_t length); static uint32_t wrapDisplay13WriteAUX(void *port,uint32_t address,void *data,uint32_t length); static uint32_t wrapDisplay13I2CAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,uint8_t flag); diff --git a/tests/display13_aux_fixture.cpp b/tests/display13_aux_fixture.cpp index 8852799c..35a7ce7d 100644 --- a/tests/display13_aux_fixture.cpp +++ b/tests/display13_aux_fixture.cpp @@ -2,6 +2,7 @@ // completion and kernel imports are mocked; optional native callers execute // their original get/put, retry, buffer-copy and result paths. bool failGet=false,failPut=false,failCommand=false;unsigned auxCalls=0,auxResult=0; +bool disconnectDuringCommand=false,loseTCIdentity=false; void getAux(void *power,unsigned ddi){ assert(!locked&&power==display13FBPower.power);auto &c=display13FBPower;Display13FBPowerIO io{c.map}; if(failGet)c.map->drop=0x45444; @@ -26,6 +27,8 @@ unsigned auxTransport(void *port,unsigned,uint32_t *packet,unsigned,bool){ assert(c.bridge.putAUX(io,ddi).status==Display13Power::Status::Complete&&c.bridge.auxBusy()); packet[0]=0;packet[1]=0x12345678; if(failCommand)c.map->regs[0x45444]=0; + if(disconnectDuringCommand)tcConnected=false; + if(loseTCIdentity)tcOwned=false; return auxResult; } unsigned syntheticFront(void *port,unsigned,void *data,unsigned){ @@ -48,6 +51,7 @@ void auxSetup(Fixture &f,Map &map,unsigned ddi,bool hdmi,unsigned mode){ state.orgDisplay13ReadAUX=state.orgDisplay13WriteAUX=reinterpret_cast(syntheticFront); state.orgDisplay13I2CAUX=reinterpret_cast(syntheticI2C);state.orgDisplay13RunAUX=reinterpret_cast(auxTransport); failGet=failPut=failCommand=false;auxCalls=auxResult=0; + disconnectDuringCommand=loseTCIdentity=false; // Probe must work after all DDI/pipe owners are gone. Display13FBPowerIO io{&map};assert(c.bridge.putDDI(io,ddi).status==Display13Power::Status::Complete); assert(c.bridge.putPG(io,1).status==Display13Power::Status::Complete); @@ -66,6 +70,24 @@ int main(int argc,char **argv){ for(unsigned at:{0x1CEABU,0x1D19CU})redirect(image,at,0x300040,reinterpret_cast(stackFail)); } unsigned cases=0; + // Exact production completion checks: a disconnect during AUX is a failed + // transaction, not a permanently faulted bridge. Lost ownership still is. + for(unsigned ddi=3;ddi<=6;++ddi)for(bool lost:{false,true})for(unsigned nativeError:{0U,0xE00002BCU}) { + Fixture f(0,0,ddi);Map map;auxSetup(f,map,ddi,false,3);auto &c=display13FBPower; + tcOwned=true;disconnectDuringCommand=true;loseTCIdentity=lost;auxResult=nativeError; + uint32_t data[5]{}; + assert(XE::wrapDisplay13ReadAUX(f.port,0x202,data,4)==(nativeError?nativeError:kIOReturnNotReady)); + assert(auxCalls==1 && !c.bridge.auxBusy() && c.bridge.state.faulted==lost); + assert(!data[0]); + if(!lost) { + assert(!c.bridge.nativeAUXRefs(ddi)); + const auto calls=auxCalls; + assert(XE::wrapDisplay13ReadAUX(f.port,0x202,data,4)==kIOReturnNotReady && auxCalls==calls); + tcConnected=true;disconnectDuringCommand=false;auxResult=0; + assert(!XE::wrapDisplay13ReadAUX(f.port,0x202,data,4) && data[0]==0x12345678); + assert(!c.bridge.state.faulted && !c.bridge.nativeAUXRefs(ddi)); + } + } for(unsigned ddi=0;ddi<2;++ddi)for(bool hdmi:{false,true})for(unsigned mode:{0U,1U}) for(unsigned op=0;op<3;++op)for(unsigned fault=0;fault<5;++fault){ Fixture f(0,0,ddi);Map map;auxSetup(f,map,ddi,hdmi,mode);auto &c=display13FBPower; From d8b53d8b04a77544aa4c77daf0226c80f26d0455 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 12:56:24 +0900 Subject: [PATCH 016/139] Preserve qualified pending scanout during Display13 DBUF setup --- WhateverXe/kern_xe.cpp | 17 +++- tests/display13_dbuf_setup.py | 14 +++ tests/display13_dbuf_setup_fixture.cpp | 136 +++++++++++++++++++++++++ tools/test.py | 2 +- 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 tests/display13_dbuf_setup.py create mode 100644 tests/display13_dbuf_setup_fixture.cpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 91e2e841..ee4568ff 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10011,7 +10011,7 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo if(pipe>=3 || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; IORecursiveLockLock(mutex);IOLockLock(c.lock); - Display13DBUF::Layout layout;Display13DBUF::Range relativePrimary;OSObject *plane=nullptr;uint64_t record[18] {},resume[6] {}; + Display13DBUF::Layout layout;Display13DBUF::Range relativePrimary;OSObject *plane=nullptr;uint64_t record[20] {},resume[6] {}; auto path=Display13FBMemory::singlePath(fb); const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=Display13FBMemory::boundPipe(c.pipe,fb)==pipe && @@ -10066,7 +10066,18 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo // arm it after this allocation; keep its old slices pending until then. IOLockLock(c.lock); if(!allocated || c.watermarkFaulted)layout.status=Display13DBUF::Status::Faulted; - else if(layout.active && (c.pipe[pipe].wmPending || c.pipe[pipe].wmLatch.status!=Display13WM::Status::Complete))layout.status=Display13DBUF::Status::Conflict; + else if(layout.active && (c.pipe[pipe].wmPending || c.pipe[pipe].wmLatch.status!=Display13WM::Status::Complete)) { + // An enabled timing pipe can still be preparing its primary, or + // waiting for the live surface to catch up. The WM proof explicitly + // leaves those cases pending. Allocation success is not retirement: + // retain the old slices and clock guard until a later native arm + // proves the actual scanout. Other unavailable/failed proofs are not + // permission to continue an active allocation. + const auto &latch=c.pipe[pipe].wmLatch; + const bool awaiting=latch.status==Display13WM::Status::Unavailable && + (latch.check==Display13WM::LatchCheck::AwaitingScanout || latch.check==Display13WM::LatchCheck::AwaitingFlip); + if(!awaiting)layout.status=Display13DBUF::Status::Conflict; + } if(layout.status==Display13DBUF::Status::Complete && !optimized) reinterpret_cast(controller)[0x1AFA]=0; } @@ -10075,8 +10086,10 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo record[5]=c.dbuf.pipe[pipe].primary.start;record[6]=c.dbuf.pipe[pipe].primary.end;record[7]=c.dbuf.pipe[pipe].cursor.start;record[8]=c.dbuf.pipe[pipe].cursor.end; record[9]=c.dbuf.consumers;record[10]=c.dbuf.unretired;record[11]=c.dbuf.requested;record[12]=c.dbuf.powered; record[13]=c.pipe[pipe].wmPending;record[14]=c.pipe[pipe].watermarkClockHz;record[15]=static_cast(c.pipe[pipe].wmLatch.status);record[16]=pipe;record[17]=binding.ddi; + record[18]=static_cast(c.pipe[pipe].wmLatch.check);record[19]=c.dbuf.faulted; IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex); adlnPublishMemory("N97FBDBUFLayout",pipe,record,sizeof(record)); + if(layout.status!=Display13DBUF::Status::Complete)adlnPublishMemory("N97FBDBUFLayoutFailure",pipe,record,sizeof(record),true); if(resume[0])adlnPublishMemory("N97FBDBUFColdResume",pipe,resume,sizeof(resume)); } void XE::wrapAlderLakeNDefaultDBUF(IOService *controller) {adlnSetupDBUF(controller,false);} diff --git a/tests/display13_dbuf_setup.py b/tests/display13_dbuf_setup.py new file mode 100644 index 00000000..bb0e35f1 --- /dev/null +++ b/tests/display13_dbuf_setup.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Exercise the production allocation adapter with real DBUF ownership.""" +from pathlib import Path +import re, subprocess, tempfile +r = Path(__file__).resolve().parents[1] +s = (r / 'WhateverXe/kern_xe.cpp').read_text() +body = re.search(r'void XE::adlnSetupDBUFPipe\(.*?\n\}', s, re.S).group() +code = (r / 'tests/display13_dbuf_setup_fixture.cpp').read_text().replace('// PRODUCTION_SETUP', body) +with tempfile.TemporaryDirectory(prefix='dbuf-setup-') as d: + p = Path(d) + (p / 'test.cpp').write_text(code) + subprocess.run(['clang++', '-std=c++17', '-O1', '-g', '-fsanitize=address,undefined', + '-I' + str(r / 'WhateverXe'), str(p / 'test.cpp'), '-o', str(p / 'test')], check=True) + subprocess.run([str(p / 'test')], check=True) diff --git a/tests/display13_dbuf_setup_fixture.cpp b/tests/display13_dbuf_setup_fixture.cpp new file mode 100644 index 00000000..e954ec0d --- /dev/null +++ b/tests/display13_dbuf_setup_fixture.cpp @@ -0,0 +1,136 @@ +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_dbuf.hpp" +#include "kern_display13_wm.hpp" +#include +#include +#include +using IOLock=int;using IORecursiveLock=int; +static unsigned ownerDepth,fbDepth; +void IOLockLock(int*){assert(fbDepth && !ownerDepth);++ownerDepth;} +void IOLockUnlock(int*){assert(ownerDepth);--ownerDepth;} +void IORecursiveLockLock(int*){assert(!ownerDepth);++fbDepth;} +void IORecursiveLockUnlock(int*){assert(fbDepth && !ownerDepth);--fbDepth;} +struct OSObject {unsigned size;OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; +using IOService=OSObject; +struct Map { + std::map r; + Map() { + r[0x45404]=0xFC0F;r[0x4438C]=0x1C000700; + for(unsigned p=0;p<3;++p){const auto b=p*0x1000; + r[0x70008+b]=p?0:0xC0000000;r[0x70080+b]=r[0x60400+b]=0; + r[0x7027C+b]=p?0:0x07FF0000;r[0x7017C+b]=0; + for(unsigned n=0;n<5;++n)r[0x70180+b+0x100*n]=!p&&!n?0x84000000:0; + for(unsigned n=0;n<3;++n)r[0x70B08+b+0x100*n]=0; + } + for(unsigned i=0;i<4;++i)r[Display13DBUF::regs[i]]=i<2?0xC040C000:0x40C000; + } +}; +struct Display13FBDBUFIO { + Map *map;bool readable(uint32_t reg){return map->r.count(reg);} + uint32_t read(uint32_t reg){assert(readable(reg));return map->r[reg];} + void write(uint32_t reg,uint32_t value){assert(readable(reg));map->r[reg]=(value&~(1U<<30))|((value&(1U<<31))>>1);} + void delayUs(unsigned){} +}; +namespace TGLFBBinding {enum class Status {Unavailable,Complete};struct Readout {Status status{};unsigned pipe{},ddi{};};} +namespace Display13FBTopology {bool multiOutputWiring(unsigned w){return w!=0;}} +struct Pipe { + IOService *memoryFramebuffer{};bool destroyingPath{},wmProgramming{},wmCommitting{}; + unsigned wmPending{};uint64_t watermarkClockHz{};Display13WM::Plan wmProgram[2];Display13WM::Latch wmLatch; +}; +struct Power { + int *lock{};IOService *controller{};Map *map{};void *power{}; + bool engineEnabled{},clockStopped{},memoryFaulted{},clockFaulted{},watermarkFaulted{}; + struct Bridge {bool bootAdopted{},begun{};struct {bool suspended{},faulted{};}state;}bridge; + struct {unsigned domains{};}engine; + Pipe pipe[3];Display13DBUF::State dbuf; +}display13FBPower; +static bool badBinding,failPrimary,failWM;static unsigned armCalls;static Display13WM::Latch nextLatch; +static std::map> records; +void adlnFault(bool &f,unsigned,unsigned){f=true;} +void adlnPublishMemory(const char *name,unsigned p,void *data,unsigned bytes,bool=false){ + if(!strcmp(name,"N97FBDBUFLayout"))records[p]=std::vector((uint64_t*)data,(uint64_t*)data+bytes/8); +} +TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path,void*,unsigned){ + const auto p=Display13FBMemory::boundPipe(display13FBPower.pipe,fb); + return badBinding||!path?TGLFBBinding::Readout{}:TGLFBBinding::Readout{TGLFBBinding::Status::Complete,p,3+p}; +} +struct XE { + bool display13FBDBUFHooksReady=true,display13FBWatermarkHooksReady=true;unsigned adlnOutputWiring=0x78;void *adlnPlatform{}; + static void adlnSetupDBUFPipe(IOService*,IOService*,unsigned,bool); + static void wrapAlderLakeNEngineEnable(void*){assert(false);} + static void wrapAlderLakeNPrimaryDBUF(OSObject*,unsigned,unsigned,bool); + static void wrapDisplay13CursorDBUF(OSObject*); + static void adlnWMEnable(OSObject*,bool,bool){if(failWM)display13FBPower.watermarkFaulted=true;} + static void wrapDisplay13WMArmUpdate(IOService*); +}state,*callbackXE=&state; +templatevoid put(void *p,unsigned off,T v){memcpy(static_cast(p)+off,&v,sizeof(v));} +unsigned planePipe(OSObject *plane){return Display13FBMemory::boundPipe(display13FBPower.pipe,Display13FBMemory::field(plane,0x68));} +void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,unsigned start,unsigned end,bool){ + assert(fbDepth&&!ownerDepth);auto &c=display13FBPower;const auto p=planePipe(plane);Display13FBDBUFIO io{c.map}; + if(failPrimary){c.dbuf.faulted=true;return;} + const auto next=Display13DBUF::toAbsolute(c.dbuf,p,{start,end+1}); + assert(Display13DBUF::preparePrimary(io,c.dbuf,next.start,next.end-1,p)==Display13DBUF::Status::Complete); + c.map->r[0x7027C+0x1000*p]=Display13DBUF::pendingHardwareRaw(c.dbuf,p); + assert(Display13DBUF::finishPrimary(io,c.dbuf,p)==Display13DBUF::Status::Complete); +} +void XE::wrapDisplay13CursorDBUF(OSObject *plane){ + assert(fbDepth&&!ownerDepth);auto &c=display13FBPower;const auto p=planePipe(plane);Display13FBDBUFIO io{c.map}; + assert(Display13DBUF::prepareCursor(io,c.dbuf,p)==Display13DBUF::Status::Complete); + c.map->r[0x7017C+0x1000*p]=Display13DBUF::pendingHardwareRaw(c.dbuf,p); + assert(Display13DBUF::finishCursor(io,c.dbuf,p)==Display13DBUF::Status::Complete); + c.pipe[p].wmPending|=2; +} +void XE::wrapDisplay13WMArmUpdate(IOService *fb){ + assert(fbDepth&&!ownerDepth);++armCalls;auto &c=display13FBPower;const auto p=Display13FBMemory::boundPipe(c.pipe,fb); + c.pipe[p].wmLatch=nextLatch; + if(nextLatch.status==Display13WM::Status::Complete){ + Display13FBDBUFIO io{c.map}; + assert(Display13DBUF::retire(io,c.dbuf,io.read(0x7027C+0x1000*p),io.read(0x7017C+0x1000*p),10,11,p)==Display13DBUF::Status::Complete); + c.pipe[p].wmPending=0;c.pipe[p].watermarkClockHz=614400000; + } +} +// PRODUCTION_SETUP +alignas(16) static uint8_t ctl[0x1c20],fb[3][0x4b90],paths[3][0x3730],planes[3][0x1c8]; +static int ownerMutex,frameMutex; +void setup(Map &map){ + memset(ctl,0,sizeof(ctl));memset(fb,0,sizeof(fb));memset(paths,0,sizeof(paths));memset(planes,0,sizeof(planes)); + display13FBPower={};auto &c=display13FBPower;c.lock=&ownerMutex;c.controller=(IOService*)ctl;c.map=↦c.engineEnabled=true; + for(unsigned p=0;p<3;++p){ + ((OSObject*)fb[p])->size=sizeof(fb[p]);((OSObject*)planes[p])->size=sizeof(planes[p]); + c.pipe[p].memoryFramebuffer=(IOService*)fb[p];put(fb[p],0x4220,&frameMutex);put(fb[p],0x4A00,1U);put(fb[p],0x4A08,(OSObject*)paths[p]); + put(paths[p],0x32C8,(OSObject*)planes[p]);put(planes[p],0x68,(IOService*)fb[p]);put(planes[p],0x70,(OSObject*)paths[p]); + } + Display13FBDBUFIO io{&map};assert(Display13DBUF::begin(io,c.dbuf,7)==Display13DBUF::Status::Complete); + badBinding=failPrimary=failWM=false;armCalls=0;records.clear();nextLatch={}; +} +int main(){ + using S=Display13WM::Status;using C=Display13WM::LatchCheck; + // The caller really programs reservations, but a prepared primary or a + // delayed live surface must retain them until a later successful arm. + for(auto check:{C::AwaitingScanout,C::AwaitingFlip})for(bool optimized:{false,true})for(unsigned p=0;p<3;++p){ + Map map;setup(map);auto &c=display13FBPower;Display13FBDBUFIO io{&map}; + if(p)assert(Display13DBUF::bindOffPipe(io,c.dbuf,p)==Display13DBUF::Status::Complete); + map.r[0x70008+p*0x1000]=0xC0000000;map.r[0x70180+p*0x1000]=0; + nextLatch.check=check;XE::adlnSetupDBUFPipe(c.controller,(IOService*)fb[p],p,optimized); + assert(!c.dbuf.faulted&&armCalls==1&&c.pipe[p].wmPending&&c.dbuf.pipe[p].unretired&&!c.pipe[p].watermarkClockHz); + assert(records[p][2]==unsigned(Display13DBUF::Status::Complete)); + // Another pipe can allocate without retiring this pipe's reservation. + const auto held=c.dbuf.pipe[p].unretired;unsigned peer=p?0:1; + if(!p)XE::adlnSetupDBUFPipe(c.controller,(IOService*)fb[peer],peer,false); + assert(!c.dbuf.faulted&&c.dbuf.pipe[p].unretired==held); + nextLatch.status=S::Complete;IORecursiveLockLock(&frameMutex);XE::wrapDisplay13WMArmUpdate((IOService*)fb[p]);IORecursiveLockUnlock(&frameMutex); + assert(!c.pipe[p].wmPending&&!c.dbuf.pipe[p].unretired&&c.pipe[p].watermarkClockHz); + } + // Generic unavailable results, mismatches, timeouts and allocation failures + // must not be turned into deferred success by the new branch. + for(unsigned fault=0;fault<6;++fault){ + Map map;setup(map);auto &c=display13FBPower; + if(fault==1){nextLatch.status=S::Conflict;nextLatch.check=C::AwaitingScanout;} + if(fault==2){nextLatch.status=S::CommitTimeout;nextLatch.check=C::Frame;} + if(fault==3)failPrimary=true; + if(fault==4)failWM=true; + if(fault==5)badBinding=true; + XE::adlnSetupDBUFPipe(c.controller,(IOService*)fb[0],0,false);assert(c.dbuf.faulted); + } + assert(!ownerDepth&&!fbDepth);puts("production DBUF setup: deferred retirement and failure guards passed"); +} diff --git a/tools/test.py b/tools/test.py index 4bc05c1c..dba1ab46 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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_tc_disconnect','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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)] From 4dca732b13f183316f7248dfc06ffa373d03c8d6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:35:51 +0900 Subject: [PATCH 017/139] Add bounded DSI firmware and command preflight --- README.md | 2 + README_ja.md | 2 + WhateverXe/kern_dsi_host.hpp | 114 ++++++++++++++++++++ WhateverXe/kern_dsi_math.hpp | 67 ++++++++++++ WhateverXe/kern_dsi_profile.hpp | 134 ++++++++++++++++++++++++ WhateverXe/kern_dsi_sequence.hpp | 98 +++++++++++++++++ WhateverXe/kern_firmware.cpp | 19 ++++ tests/dsi_host.cpp | 70 +++++++++++++ tests/dsi_profile.cpp | 91 ++++++++++++++++ tests/dsi_sequence.cpp | 45 ++++++++ tests/firmware_readout.py | 37 ++++++- tests/fixtures/boot_argument_names.json | 1 + 12 files changed, 675 insertions(+), 5 deletions(-) create mode 100644 WhateverXe/kern_dsi_host.hpp create mode 100644 WhateverXe/kern_dsi_math.hpp create mode 100644 WhateverXe/kern_dsi_profile.hpp create mode 100644 WhateverXe/kern_dsi_sequence.hpp create mode 100644 tests/dsi_host.cpp create mode 100644 tests/dsi_profile.cpp create mode 100644 tests/dsi_sequence.cpp diff --git a/README.md b/README.md index 843d855f..c5475ed7 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ No device-ID override or machine-specific AGDP kext is required. The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 DSI-A GOP to Combo B at 1080p60 for development. It stops the internal display and retires the temporary HDMI resources when policy switches to another connected output. It is disabled by default and does not provide DSI support or general cold startup. +The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and publishes a diagnostic summary. It is disabled by default; the audit does not program the display or enable DSI output. + ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. diff --git a/README_ja.md b/README_ja.md index 14d05353..ab9bd8c1 100644 --- a/README_ja.md +++ b/README_ja.md @@ -104,6 +104,8 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 試験用の `-wex-dsi-hdmi-test` は、限定したDisplay13のDSI-A GOPからCombo-Bへ1080p60で移す開発用経路です。内蔵表示を停止し、policyが別の接続済み出力へ切り替える際は暫定HDMI資源を解放します。既定では無効で、DSI対応や汎用のcold起動を提供するものではありません。 +任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 + ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 diff --git a/WhateverXe/kern_dsi_host.hpp b/WhateverXe/kern_dsi_host.hpp new file mode 100644 index 00000000..90c75047 --- /dev/null +++ b/WhateverXe/kern_dsi_host.hpp @@ -0,0 +1,114 @@ +// Pure packet/sequence engine; IO is supplied by an owning, serialized adapter. +// Intel icl_dsi.c/icl_dsi_regs.h (Linux v7.0) define credit and LP drain rules. +// No Apple objects, shared-resource allocation, or hardware MMIO lives here. +#ifndef KERN_DSI_HOST_HPP +#define KERN_DSI_HOST_HPP +#include "kern_dsi_sequence.hpp" +namespace XELP { namespace DSI { +enum class HostError : uint32_t {None, Disabled, Invalid, Cancelled, Unavailable, CreditTimeout, DrainTimeout, GPIO}; +struct Packet { + bool valid {};uint32_t header {};Span payload; +}; +inline Packet packet(uint8_t type,Span data) { + Packet p; + if(!data.data || !data.size || data.size>256)return p; + uint32_t parameters=0; + if(type==0x05 && data.size==1)parameters=data.data[0]; + else if(type==0x15 && data.size==2)parameters=data.data[0]|uint32_t(data.data[1])<<8; + else if(type==0x39){parameters=uint32_t(data.size);p.payload=data;} + else return p; + // Fresh header: VC0, LP, no stale fence/flush or short-packet payload bit. + p.header=(p.payload.data?1U<<31:0)|(1U<<30)|(parameters<<8)|type; + p.valid=true;return p; +} +struct Host { + HostError error {HostError::None}; + bool enabled {},pending {},faulted {}; + uint32_t payloadWords {},headers {},completedOperations {},sequenceId {},operationOffset {}; + uint64_t waitedUs {}; + explicit Host(bool writable=false):enabled(writable) {} + bool fail(HostError e) {if(error==HostError::None)error=e;faulted=true;return false;} + // No implicit recovery/release after failure. Adapter must stop hardware, + // retain uncertain leases, and construct a new Host only after recovery. + template bool ready(IO &io) { + if(faulted)return false; + if(!enabled)return fail(HostError::Disabled); + if(io.cancelled())return fail(HostError::Cancelled); + return true; + } + template bool credits(IO &io,unsigned h,unsigned p) { + for(unsigned us=0;;us+=10) { + if(!ready(io))return false; + uint32_t status=0; + if(!io.credits(status) || status==UINT32_MAX)return fail(HostError::Unavailable); + const auto headers=(status>>8)&31,payload=status&255; + if(headers>16 || payload>64)return fail(HostError::Unavailable); + if(headers>=h && payload>=p)return true; + if(us==100)return fail(HostError::CreditTimeout); + io.delayUs(10);waitedUs+=10; + } + } + template bool send(IO &io,uint8_t type,Span data) { + const auto p=packet(type,data); + if(!p.valid)return fail(HostError::Invalid); + if(!ready(io))return false; + // Reserve sufficient credit before the first payload write. The caller + // serializes the only producer; hardware can only return more credit. + if(!credits(io,1,unsigned((p.payload.size+3)/4)))return false; + for(size_t at=0;at bool drain(IO &io) { + if(!ready(io))return false; + if(!pending)return true; + if(!credits(io,16,64))return false; + const uint8_t nop=0; + if(!send(io,0x05,{&nop,1}) || !credits(io,16,0))return false; + for(unsigned us=0;;us+=2) { + if(!ready(io))return false; + bool busy=false; + if(!io.lpBusy(busy))return fail(HostError::Unavailable); + if(!busy){pending=false;return true;} + if(us==20)return fail(HostError::DrainTimeout); + io.delayUs(2);waitedUs+=2; + } + } + template bool delay(IO &io,uint32_t us) { + while(us) { + if(!ready(io))return false; + const auto chunk=us>1000?1000:us; + io.delayUs(chunk);waitedUs+=chunk;us-=chunk; + } + return ready(io); + } + template bool run(IO &io,const Sequences &sequences,unsigned id) { + if(!sequences.valid() || id>=12 || !sequences.sequence[id].data)return fail(HostError::Invalid); + const auto bytes=sequences.sequence[id];Sequences check; + if(validateSequence(bytes,check)!=SequenceStatus::Valid)return fail(HostError::Invalid); + sequenceId=id; + if(!ready(io))return false; + for(size_t at=0;at300000 || + p.clockPrepareZero=3, <=20 MHz. + auto word=(r.afeKHz+159999)/160000;word|=1;if(word<3)word=3; + r.escapeDivider=word*8;r.phyEscapeDivider=(word-1)/2; + constexpr uint32_t tlpx=50; // byteClock=0: 20 MHz escape timing reference + r.clockTiming=(1U<<31)|(cappedCeil(p.clockPrepare*4,tlpx,7)<<28)|(1U<<27)| + (cappedCeil(p.clockPrepareZero-p.clockPrepare,tlpx,15)<<20)|(1U<<19)|(cappedCeil(p.clockPre,tlpx,3)<<16); + r.dataTiming=(1U<<31)|(cappedCeil(p.hsPrepare*4,tlpx,7)<<24)|(1U<<23)| + (cappedCeil(p.hsPrepareZero-p.hsPrepare,tlpx,15)<<16)|(1U<<7)|cappedCeil(p.hsExit,tlpx,7); + r.htotal=((m.htotal()-1)<<16)|(m.width-1); + r.vtotal=((m.vtotal()-1)<<16)|(m.height-1); + r.hsync=((uint32_t(m.width)+m.hfront+m.hsync-1)<<16)|(uint32_t(m.width)+m.hfront-1); + r.vsync=((uint32_t(m.height)+m.vfront+m.vsync-1)<<16)|(uint32_t(m.height)+m.vfront-1); + r.valid=true;return r; +} +struct PLLPlan { + bool valid {};uint32_t referenceKHz {},dcoKHz {},divider {},p {},q {},k {},integer {},fraction {}; +}; +inline PLLPlan pllPlan(uint32_t portKHz,uint32_t referenceKHz) { + PLLPlan r; + if(!portKHz || portKHz>600000 || (referenceKHz!=19200 && referenceKHz!=24000 && referenceKHz!=38400))return r; + // Hardware divider choices, in the documented tie-breaking order. + static const uint8_t dividers[]={2,4,6,8,10,12,14,16,18,20,24,28,30,32,36,40,42,44,48,50,52,54, + 56,60,64,66,68,70,72,76,78,80,84,88,90,92,96,98,100,102,3,5,7,9,15,21}; + uint32_t distance=UINT32_MAX; + for(auto d:dividers) { + const uint64_t dco=uint64_t(portKHz)*5*d; + if(dco<7998000 || dco>10000000)continue; + const auto diff=uint32_t(dco>8999000?dco-8999000:8999000-dco); + if(diff>15);r.fraction=uint32_t(fixed&0x7FFF); + // Display WA 22010492432: 38.4 MHz uses half the nominal fraction. + if(referenceKHz==38400)r.fraction=(r.fraction+1)/2; + r.valid=true;return r; +} +}} +#endif diff --git a/WhateverXe/kern_dsi_profile.hpp b/WhateverXe/kern_dsi_profile.hpp new file mode 100644 index 00000000..057e6c0e --- /dev/null +++ b/WhateverXe/kern_dsi_profile.hpp @@ -0,0 +1,134 @@ +// Independently implemented VBT wire decoder. Layout reference: Intel's +// intel_vbt_defs.h/intel_bios.c (Linux v6.12), intel_dsi_vbt.c (Linux v7.0). +// This is a description, not permission to program a panel. Borrowed spans +// require immutable backing storage; the Apple adapter owns that storage. +#ifndef KERN_DSI_PROFILE_HPP +#define KERN_DSI_PROFILE_HPP +#include "kern_dsi_sequence.hpp" +#include +namespace XELP { namespace DSI { +inline uint16_t profileU16(const uint8_t *p) {return uint16_t(uint16_t(p[0]) | uint16_t(p[1])<<8);} +enum class ProfileStatus : uint32_t {Bounds, Version, Duplicate, Missing, Child, Config, Timing, Backlight, Sequence, Valid}; +struct Mode { + uint32_t clockKHz {}; + uint16_t width {},hblank {},hfront {},hsync {},height {},vblank {},vfront {},vsync {},widthMM {},heightMM {}; + uint32_t htotal() const {return uint32_t(width)+hblank;} + uint32_t vtotal() const {return uint32_t(height)+vblank;} +}; +struct Profile { + ProfileStatus status {ProfileStatus::Bounds}; + uint16_t version {}; uint8_t checksum {},panel {}; + bool bdbTailQuirk {}; // explicit, caller-approved one-byte final END quirk + Mode mode; + uint8_t lanes {},byteClock {},clockPrepare {},clockPre {},hsPrepare {},hsExit {}; + uint16_t clockPrepareZero {},hsPrepareZero {}; + bool btaDisabled {}; + uint32_t hsTxTimeout {},lpRxTimeout {},turnaroundTimeout {},initTimer {}; + uint32_t ppsUs[5] {},pwmHz {}; + uint16_t brightness {},minimum {}; + uint8_t precision {},controller {}; bool activeLowPWM {}; + Sequences sequences; + bool valid() const {return status==ProfileStatus::Valid;} +}; +// Strict initial transport subset: single DSI A, RGB888, video sync events, +// continuous clock, LP VC0 commands and native reset/AVDD/backlight GPIOs. +// Other panels require a reviewed profile policy, never guessed coercion. +inline Profile parseProfile(Span input,bool allowFinalEndQuirk=false) { + Profile failure,r; + if(!input.data || input.size<48)return failure; + auto p=input.data; + if(memcmp(p,"$VBT",4))return failure; + const size_t vh=profileU16(p+22),vs=profileU16(p+24),bo=sequenceU32(p+28); + if(vh<48 || vsinput.size || bovs || vs-bo<22)return failure; + const auto b=p+bo;const size_t bh=profileU16(b+18),bs=profileU16(b+20); + if(memcmp(b,"BIOS_DATA_BLOCK ",16) || bh<22 || bsvs-bo)return failure; + r.version=profileU16(b+16); + if(r.version!=256){failure.status=ProfileStatus::Version;return failure;} + for(size_t i=0;iavailable) { + // Observed BDB256 tail excludes exactly the final panel END. + // Require the caller's policy, final block 53, and that byte to + // be inside the *declared VBT*, not merely the mapped page. + if(!allowFinalEndQuirk || id!=53 || n!=available+1 || + vs-bo!=bs+1 || b[bs]!=0)return failure; + r.bdbTailQuirk=true; + } + Span *slot=nullptr; + switch(id) { + case 2:slot=&children;break;case 40:slot=&options;break; + case 43:slot=&backlight;break;case 52:slot=&config;break; + case 53:slot=&seq;break;case 58:slot=&dtd;break; + } + if(slot) { + if(slot->data){failure.status=ProfileStatus::Duplicate;return failure;} + *slot={b+at+3,n}; + } + at+=3+n; + } + failure.status=ProfileStatus::Missing; + if(!children.data || !options.data || !config.data || !seq.data || !dtd.data || !backlight.data)return failure; + failure.status=ProfileStatus::Child; + if(children.size<5 || children.data[4]<38 || children.data[4]>64 || + (children.size-5)%children.data[4])return failure; + unsigned mipi=0; + for(size_t at=5;at=6 || config.size<792)return failure; + r.panel=options.data[0];const auto c=config.data+122*r.panel; + const auto general=sequenceU32(c+2);const auto port=profileU16(c+6); + // Bridge, command mode, rotation, dual link, reversal and alternate DCS + // destinations are deliberately outside the first implementation. + if((general&4) || ((general>>3)&3)!=2 || (general&32) || + ((general>>6)&3)!=2 || ((general>>10)&15)!=4 || ((general>>14)&3) || + (port&0xFFF3) || (profileU16(c+24)&7) || c[23]!=0)return failure; + r.lanes=((port>>2)&3)+1;if(r.lanes!=4)return failure; + r.byteClock=c[23];r.btaDisabled=(general>>16)&1; + r.hsTxTimeout=sequenceU32(c+26);r.lpRxTimeout=sequenceU32(c+30); + r.turnaroundTimeout=sequenceU32(c+34);r.initTimer=sequenceU32(c+42); + r.clockPre=c[93];r.clockPrepare=c[94];r.clockPrepareZero=profileU16(c+98); + r.hsExit=c[103];r.hsPrepare=c[104];r.hsPrepareZero=profileU16(c+105); + if(r.clockPrepareZero300000 || !m.width || !m.height || !m.hsync || !m.vsync || + uint32_t(m.hfront)+m.hsync>=m.hblank || uint32_t(m.vfront)+m.vsync>=m.vblank || + m.htotal()>65536 || m.vtotal()>65536)return failure; + failure.status=ProfileStatus::Backlight; + if(backlight.size<273 || backlight.data[0]!=6)return failure; + const auto bl=backlight.data+1+6*r.panel; + if((bl[0]&3)!=2 || backlight.data[113+r.panel]!=2)return failure; + r.activeLowPWM=bl[0]&4;r.pwmHz=profileU16(bl+1);r.controller=0; + r.brightness=profileU16(backlight.data+129+2*r.panel); + r.minimum=profileU16(backlight.data+193+2*r.panel);r.precision=backlight.data[257+r.panel]; + if(!r.pwmHz || r.precision!=8 || r.brightness>255 || r.minimum>r.brightness)return failure; + failure.status=ProfileStatus::Sequence; + r.sequences=parseSequences(seq,r.panel); + if(!r.sequences.valid())return failure; + static const unsigned required[]={1,2,4,5,6,7,10,11}; + for(unsigned id:required) + if(!r.sequences.sequence[id].data)return failure; + r.status=ProfileStatus::Valid;return r; +} +}} +#endif diff --git a/WhateverXe/kern_dsi_sequence.hpp b/WhateverXe/kern_dsi_sequence.hpp new file mode 100644 index 00000000..a4f1f2ca --- /dev/null +++ b/WhateverXe/kern_dsi_sequence.hpp @@ -0,0 +1,98 @@ +// Bounded VBT v3 sequence description. No hardware operations or allocations. +// Layout reference: Intel intel_bios.c/intel_dsi_vbt.c, Linux v7.0. +// Spans borrow immutable VBT storage: its owner must outlive this description. +#ifndef KERN_DSI_SEQUENCE_HPP +#define KERN_DSI_SEQUENCE_HPP +#include +#include +namespace XELP { namespace DSI { +struct Span { const uint8_t *data {}; size_t size {}; }; +enum class SequenceStatus : uint32_t { + Valid, Bounds, Version, Panel, Duplicate, Sequence, Operation, Packet, GPIO +}; +struct Sequences { + SequenceStatus status {SequenceStatus::Bounds}; + Span sequence[12] {}; + uint32_t packets {}, delays {}, gpios {}; + uint64_t delayUs {}; + bool valid() const {return status==SequenceStatus::Valid;} +}; +inline uint32_t sequenceU32(const uint8_t *p) { + return uint32_t(p[0]) | uint32_t(p[1])<<8 | uint32_t(p[2])<<16 | uint32_t(p[3])<<24; +} +inline SequenceStatus validateSequence(Span bytes, Sequences &counts) { + if(!bytes.data || !bytes.size || bytes.size>65536)return SequenceStatus::Bounds; + for(size_t at=0;atbytes.size-at)return SequenceStatus::Bounds; + const auto p=bytes.data+at;at+=n; + switch(op) { + case 1: { + if(n<4)return SequenceStatus::Packet; + const size_t length=uint32_t(p[2]) | uint32_t(p[3])<<8; + // Initial profile: LP commands, VC0, port A. Other transport + // flags require a separate host contract, never silent coercion. + if(p[0] || length!=n-4)return SequenceStatus::Packet; + if(!((p[1]==0x05 && length==1) || (p[1]==0x15 && length==2) || + (p[1]==0x39 && length>=1)))return SequenceStatus::Packet; + ++counts.packets;break; + } + case 2: + if(n!=4)return SequenceStatus::Operation; + ++counts.delays;counts.delayUs+=sequenceU32(p);break; + case 3: + // v3 native display GPIO: index, GPIO role, value. The index + // is retained in the bytes; it is not a gpiochip identifier. + if(n!=3 || p[1]>2 || p[2]>1)return SequenceStatus::GPIO; + ++counts.gpios;break; + default:return SequenceStatus::Operation; + } + } + return SequenceStatus::Bounds; // required END byte absent +} +inline Sequences parseSequences(Span block, uint8_t selectedPanel) { + Sequences r; + if(!block.data || block.size<5 || block.size>65536)return r; + if(block.data[0]!=3){r.status=SequenceStatus::Version;return r;} + // BDB v3 size excludes the version and size header (five bytes). + if(sequenceU32(block.data+1)!=block.size-5)return r; + if(selectedPanel>=6){r.status=SequenceStatus::Panel;return r;} + uint32_t panels=0;Span selected; + for(size_t at=5;at=6){r.status=SequenceStatus::Panel;return r;} + if(panels&(1U<block.size-at)return r; + if(panel==selectedPanel)selected={block.data+at,n}; + at+=n; + } + if(!selected.data){r.status=SequenceStatus::Panel;return r;} + // Parse into a temporary: a failed preflight never publishes executable + // spans or partial counts to a later interpreter. + Sequences candidate; + for(size_t at=0;at=12){r.status=SequenceStatus::Sequence;return r;} + if(candidate.sequence[id].data){r.status=SequenceStatus::Duplicate;return r;} + if(selected.size-at<4)return r; + const size_t n=sequenceU32(selected.data+at);at+=4; + if(!n || n>selected.size-at)return r; + Span bytes {selected.data+at,n}; + r.status=validateSequence(bytes,candidate); + if(r.status!=SequenceStatus::Valid)return r; + r.status=SequenceStatus::Bounds; + candidate.sequence[id]=bytes;at+=n; + } + return r; +} +}} +#endif diff --git a/WhateverXe/kern_firmware.cpp b/WhateverXe/kern_firmware.cpp index 751eb82f..691e4962 100644 --- a/WhateverXe/kern_firmware.cpp +++ b/WhateverXe/kern_firmware.cpp @@ -1,4 +1,5 @@ #include "kern_xe.hpp" +#include "kern_dsi_math.hpp" #include #include @@ -57,4 +58,22 @@ void XE::xelpReadFirmware(IOPCIDevice *pci) { const auto &panel=firmwarePorts.panel; uint32_t panelFacts[]={panel.valid,panel.type,panel.ddi,panel.lowSwing,panel.hobl}; pci->setProperty("WhateverXeFirmwarePanel",panelFacts,sizeof(panelFacts)); + if(checkKernelArgument("-wex-dsi-audit")) { + // Synchronous audit of the existing copied firmware property. Never + // retain borrowed sequence pointers, acquire power, or authorize IO. + const auto vbt=OSDynamicCast(OSData,pci->getProperty("TGLVBT")); + const auto profile=XELP::DSI::parseProfile({vbt?static_cast(vbt->getBytesNoCopy()):nullptr, + vbt?vbt->getLength():0U},true); + const auto link=XELP::DSI::linkPlan(profile); + uint32_t audit[]={1,static_cast(profile.status),profile.bdbTailQuirk, + profile.version,profile.checksum,profile.panel,profile.lanes, + profile.mode.width,profile.mode.height,profile.mode.clockKHz,profile.mode.htotal(),profile.mode.vtotal(), + profile.pwmHz,profile.minimum,profile.brightness,profile.precision, + profile.sequences.packets,profile.sequences.delays,profile.sequences.gpios, + uint32_t(profile.sequences.delayUs),uint32_t(profile.sequences.delayUs>>32), + profile.ppsUs[0],profile.ppsUs[1],profile.ppsUs[2],profile.ppsUs[3],profile.ppsUs[4], + link.valid,link.afeKHz,link.portKHz,link.escapeDivider,link.phyEscapeDivider, + link.clockTiming,link.dataTiming}; + publishBytes(pci,"WEXDSIFirmwareAudit",audit,sizeof(audit)); + } } diff --git a/tests/dsi_host.cpp b/tests/dsi_host.cpp new file mode 100644 index 00000000..daf8d7df --- /dev/null +++ b/tests/dsi_host.cpp @@ -0,0 +1,70 @@ +#include "kern_dsi_host.hpp" +#include "kern_dsi_profile.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +struct Mock { + std::vector h,p; + uint32_t credit=0x1040,elapsed=0,gpios=0,checks=0,cancelAt=UINT32_MAX; + bool busy=false,available=true,gpioOK=true; + bool cancelled(){return ++checks>=cancelAt;} + bool credits(uint32_t &v){v=credit;return available;} + bool lpBusy(bool &v){v=busy;return available;} + void header(uint32_t v){h.push_back(v);} + void payload(uint32_t v){p.push_back(v);} + void delayUs(uint32_t us){elapsed+=us;} + bool gpio(uint8_t role,uint8_t value){assert(role<=2&&value<=1);++gpios;return gpioOK;} +}; +int main(int argc,char **argv) { + uint8_t data[]={0x29,0x12,0x34,0x56,0x78}; + assert(packet(5,{data,1}).header==0x40002905); + assert(packet(0x15,{data,2}).header==0x40122915); + assert(packet(0x39,{data,5}).header==0xC0000539); + assert(!packet(6,{data,1}).valid&&!packet(5,{nullptr,1}).valid&&!packet(0x39,{data,257}).valid); + Mock io;Host off;assert(!off.send(io,5,{data,1})&&off.error==HostError::Disabled&&io.h.empty()); + Host ok(true);assert(ok.send(io,0x39,{data,5})&&ok.pending); + assert(io.p.size()==2&&io.p[0]==0x56341229&&io.p[1]==0x78); + assert(ok.drain(io)&&!ok.pending&&io.h.size()==2&&io.h.back()==0x40000005); + for(auto credit:{0U,0x1000U,0x0040U}) { + Mock stalled;stalled.credit=credit;Host h(true); + assert(!h.send(stalled,0x39,{data,5})&&h.error==HostError::CreditTimeout); + assert(stalled.elapsed==100&&stalled.h.empty()&&stalled.p.empty()); + } + Mock busy;busy.busy=true;Host h(true);assert(h.send(busy,5,{data,1})); + assert(!h.drain(busy)&&h.error==HostError::DrainTimeout&&h.pending&&busy.elapsed==20); + const auto count=busy.h.size();assert(!h.send(busy,5,{data,1})&&busy.h.size()==count); // fault latches + Mock missing;missing.available=false;Host unavailable(true); + assert(!unavailable.send(missing,5,{data,1})&&unavailable.error==HostError::Unavailable); + uint8_t ops[]={1,9,0,0x39,5,0,0x29,0x12,0x34,0x56,0x78,2,4,0xD0,7,0,0,3,3,0,2,1,0}; + Sequences s;s.status=SequenceStatus::Valid;s.sequence[2]={ops,sizeof(ops)}; + Mock complete;Host runner(true);assert(runner.run(complete,s,2)); + assert(runner.completedOperations==3&&complete.elapsed==2000&&complete.gpios==1&&!runner.pending); + // Cancellation at every callback boundary must latch first failure and + // never submit a later GPIO/header after returning an error. + for(unsigned at=1;at<=complete.checks;++at) { + Mock cancelled;cancelled.cancelAt=at;Host stopped(true); + assert(!stopped.run(cancelled,s,2)&&stopped.error==HostError::Cancelled); + const auto headers=cancelled.h.size(),words=cancelled.p.size();const auto gpios=cancelled.gpios; + assert(!stopped.run(cancelled,s,2)); + assert(cancelled.h.size()==headers&&cancelled.p.size()==words&&cancelled.gpios==gpios); + } + Mock invalid;Host rejected(true);ops[0]=4; + assert(!rejected.run(invalid,s,2)&&invalid.h.empty()&&invalid.p.empty()&&invalid.gpios==0);ops[0]=1; + Mock failedGPIO;failedGPIO.gpioOK=false;Host gpio(true); + assert(!gpio.run(failedGPIO,s,2)&&gpio.error==HostError::GPIO&&gpio.completedOperations==2); + if(argc==2) { + std::ifstream f(argv[1],std::ios::binary);assert(f); + std::vector bytes{std::istreambuf_iterator(f),{}}; + const auto profile=parseProfile({bytes.data(),bytes.size()},true);assert(profile.valid()); + Mock saved;Host replay(true); + for(unsigned id:{10U,1U,2U,6U,7U,4U,5U,11U})assert(replay.run(saved,profile.sequences,id)); + assert(saved.gpios==8&&saved.elapsed==486000&&replay.completedOperations==378); + assert(!replay.pending&&!replay.faulted); + printf("saved sequences mock replay: operations=%u GPIO=%u delay=%u us headers=%zu (includes drain NOPs)\n", + replay.completedOperations,saved.gpios,saved.elapsed,saved.h.size()); + } + puts("DSI packet, credit/drain timeout, cancellation, preflight, first error: PASS"); +} diff --git a/tests/dsi_profile.cpp b/tests/dsi_profile.cpp new file mode 100644 index 00000000..a53c07c1 --- /dev/null +++ b/tests/dsi_profile.cpp @@ -0,0 +1,91 @@ +#include "kern_dsi_math.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +using Bytes=std::vector; +void put(Bytes &b,size_t at,uint32_t value,unsigned n=2) {for(unsigned i=0;i>(8*i);} +void add(Bytes &b,uint8_t id,const Bytes &data) { + b.push_back(id);b.push_back(data.size());b.push_back(data.size()>>8); + b.insert(b.end(),data.begin(),data.end()); +} +Bytes fixture() { + Bytes b(70);memcpy(b.data(),"$VBT",4);put(b,22,48);put(b,28,48,4); + memcpy(b.data()+48,"BIOS_DATA_BLOCK ",16);put(b,64,256);put(b,66,22); + Bytes child(45);child[4]=40;put(child,7,0x1400);child[21]=21;add(b,2,child); + add(b,40,Bytes{0}); + Bytes config(792);put(config,2,0x11190,4);put(config,6,12); + config[93]=8;config[94]=50;put(config,98,310);config[103]=110;config[104]=50;put(config,105,200); + for(unsigned i=0;i<5;++i)put(config,732+2*i,i==4?5000:500); + add(b,52,config); + Bytes dtd(30);put(dtd,0,28);put(dtd,2,135255,4); + const unsigned fields[]={1080,46,22,2,1920,82,40,2,86,154}; + for(unsigned i=0;i<10;++i)put(dtd,6+2*i,fields[i]);add(b,58,dtd); + Bytes bl(273);bl[0]=6;bl[1]=2;put(bl,2,40000);bl[113]=2;put(bl,129,255);put(bl,193,6);bl[257]=8;add(b,43,bl); + Bytes seq(10);seq[0]=3;seq[5]=0; + // Empty, terminated sequences prove structure without firmware commands. + for(unsigned id:{1U,2U,4U,5U,6U,7U,10U,11U}) { + seq.push_back(id);seq.push_back(1);seq.insert(seq.end(),4,0); + } + seq.push_back(0);put(seq,6,seq.size()-10,4);put(seq,1,seq.size()-5,4);add(b,53,seq); + put(b,24,b.size());put(b,68,b.size()-48);return b; +} +Profile parse(const Bytes &b,bool quirk=false) {return parseProfile({b.data(),b.size()},quirk);} +void reject(const Bytes &b) {auto r=parse(b);assert(!r.valid());assert(!r.mode.clockKHz&&!r.sequences.packets);} +int main(int argc,char **argv) { + auto b=fixture();auto r=parse(b); + if(!r.valid())printf("status %u\n",unsigned(r.status)); + assert(r.valid()&&r.mode.htotal()==1126&&r.mode.vtotal()==2002&&r.pwmHz==40000); + assert(r.clockPrepare==50&&r.clockPrepareZero==310&&r.hsPrepareZero==200&&r.ppsUs[4]==500000); + const auto link=linkPlan(r);assert(link.valid&&link.afeKHz==811530&&link.portKHz==162306); + assert(link.escapeDivider==56&&link.phyEscapeDivider==3); + // Linux v7.0 readback, reserved bits omitted for the escape divider. + assert(link.clockTiming==0xC8690000&&link.dataTiming==0x84830083); + assert(link.htotal==0x04650437&&link.hsync==0x044F044D&&link.vtotal==0x07D1077F&&link.vsync==0x07A907A7); + const auto pll=pllPlan(link.portKHz,19200); + assert(pll.valid&&pll.divider==12&&pll.p==2&&pll.q==3&&pll.k==2); + assert((pll.fraction<<9|pll.integer)==0x34CDFB); // saved DPLL0_CFGCR0 + const auto doubled=pllPlan(link.portKHz,38400); + assert(doubled.valid&&doubled.integer==pll.integer&&doubled.fraction==(pll.fraction+1)/2); + assert(!pllPlan(0,19200).valid&&!pllPlan(UINT32_MAX,19200).valid&&!pllPlan(162306,27000).valid); + for(unsigned clock=10000;clock<500000;clock+=137) { + auto v=pllPlan(clock,24000);if(!v.valid)continue; + assert(v.divider==v.p*v.q*v.k&&v.dcoKHz>=7998000&&v.dcoKHz<=10000000); + const auto reconstructed=(uint64_t(v.integer)*32768+v.fraction)*24000/32768; + assert(reconstructed<=v.dcoKHz&&v.dcoKHz-reconstructed<2); + } + for(size_t i=0;i(f),{}}; + assert(!parse(real).valid());auto q=parse(real,true); + printf("saved status=%u\n",unsigned(q.status)); + assert(q.valid()&&q.bdbTailQuirk&&q.panel==0&&q.lanes==4); + assert(q.mode.clockKHz==135255&&q.mode.width==1080&&q.mode.height==1920); + assert(q.sequences.packets==359&&q.sequences.gpios==8&&q.sequences.delayUs==486000); + assert(q.clockPrepare==50&&q.hsPrepare==50&&q.clockPrepareZero==310&&q.hsPrepareZero==200); + assert(q.ppsUs[0]==50000&&q.ppsUs[4]==500000&&q.minimum==6&&q.pwmHz==40000); + const auto saved=linkPlan(q);assert(saved.valid&&saved.clockTiming==link.clockTiming&&saved.dataTiming==link.dataTiming); + printf("saved profile: %ux%u clock=%u kHz PWM=%u Hz\n",q.mode.width,q.mode.height,q.mode.clockKHz,q.pwmHz); + } + puts("DSI profile bounds, explicit tail quirk, supported transport: PASS"); +} diff --git a/tests/dsi_sequence.cpp b/tests/dsi_sequence.cpp new file mode 100644 index 00000000..9d2463dd --- /dev/null +++ b/tests/dsi_sequence.cpp @@ -0,0 +1,45 @@ +#include "kern_dsi_sequence.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +using Bytes=std::vector; +void word(Bytes &b,uint32_t n){for(unsigned i=0;i<4;++i)b.push_back(n>>(8*i));} +Bytes panel(const Bytes &ops,uint8_t id=3){Bytes b{id};word(b,ops.size());b.insert(b.end(),ops.begin(),ops.end());b.push_back(0);return b;} +Bytes block(const Bytes &data){Bytes b{3};word(b,data.size()+5);b.push_back(0);word(b,data.size());b.insert(b.end(),data.begin(),data.end());return b;} +Sequences parse(const Bytes &b){return parseSequences({b.data(),b.size()},0);} +void rejected(const Bytes &b){auto r=parse(b);assert(!r.valid());assert(!r.packets&&!r.delays&&!r.gpios);for(auto s:r.sequence)assert(!s.data);} +int main(int argc,char **argv) { + // Synthetic standard DCS command, delay and native GPIO; no vendor blob. + Bytes ops {1,5,0,5,1,0,0x29,2,4,0x10,0x27,0,0,3,3,0,2,1,0}; + auto b=block(panel(ops));auto r=parse(b); + assert(r.valid()&&r.packets==1&&r.delays==1&&r.gpios==1&&r.delayUs==10000); + for(size_t n=0;n>{{0,4},{1,255},{5,6},{10,12},{11,255}}) { + auto bad=b;bad[pair.first]=pair.second;rejected(bad); + } + for(auto op:Bytes{4,5,6,7,255}){auto bad=ops;bad[0]=op;rejected(block(panel(bad)));} + for(auto flags:Bytes{1,2,8,32,255}){auto bad=ops;bad[2]=flags;rejected(block(panel(bad)));} + auto bad=ops;bad[3]=6;rejected(block(panel(bad))); // read unsupported + bad=ops;bad[4]=2;rejected(block(panel(bad))); // wrong packet length + bad=ops;bad.pop_back();rejected(block(panel(bad))); // no operation END + bad=ops;bad.push_back(0);rejected(block(panel(bad))); // bytes after END + bad=ops;bad[16]=3;rejected(block(panel(bad))); // unimplemented GPIO role + auto data=panel(ops);data.pop_back();auto second=panel(ops);data.insert(data.end(),second.begin(),second.end()); + rejected(block(data)); // duplicate sequence + auto duplicate=b;duplicate.insert(duplicate.end(),b.begin()+5,b.end()); + const auto size=duplicate.size()-5;for(unsigned i=0;i<4;++i)duplicate[1+i]=size>>(8*i); + rejected(duplicate); // duplicate panel + assert(parseSequences({b.data(),b.size()},1).status==SequenceStatus::Panel); + assert(!parseSequences({nullptr,5},0).valid()); + if(argc==2) { + std::ifstream f(argv[1],std::ios::binary);assert(f); + Bytes real{std::istreambuf_iterator(f),{}};auto q=parse(real); + assert(q.valid()&&q.packets==359&&q.gpios==8&&q.delays==11&&q.delayUs==486000); + assert(!q.sequence[3].data&&q.sequence[2].data&&q.sequence[10].data&&q.sequence[11].data); + printf("saved profile: packets=%u delays=%u GPIO=%u delayUs=%llu\n",q.packets,q.delays,q.gpios,(unsigned long long)q.delayUs); + } + puts("DSI sequence bounds/unsupported operations/transactional publication: PASS"); +} diff --git a/tests/firmware_readout.py b/tests/firmware_readout.py index 7f55bea1..448558db 100644 --- a/tests/firmware_readout.py +++ b/tests/firmware_readout.py @@ -1,37 +1,64 @@ #!/usr/bin/env python3 """Execute production firmware acquisition with independent Display/Render profiles.""" from pathlib import Path -import subprocess,tempfile +import subprocess,tempfile,sys r=Path(__file__).resolve().parents[1] s=(r/'WhateverXe/kern_firmware.cpp').read_text();body=s[s.index('static void publishBytes'):] code=r''' #include #include +#include +#include +#include +#include +#include #include "kern_xelp_platform.hpp" #include "kern_vbt.hpp" +#include "kern_dsi_math.hpp" +bool auditEnabled=false; +bool checkKernelArgument(const char *arg){assert(!strcmp(arg,"-wex-dsi-audit"));return auditEnabled;} +#define OSDynamicCast(type,ptr) (ptr) constexpr unsigned kIODirectionIn=1,kIOMapReadOnly=2; -struct OSData {static OSData *withBytes(const void*,unsigned){static OSData d;return &d;}void release(){}}; +struct OSData { + std::vector bytes; + static OSData *withBytes(const void *p,unsigned n){auto d=new OSData;d->bytes.assign((const uint8_t*)p,(const uint8_t*)p+n);return d;} + void release(){delete this;}const void *getBytesNoCopy(){return bytes.data();}unsigned getLength(){return bytes.size();} +}; struct IOMemoryDescriptor { struct Map {uintptr_t getVirtualAddress(){return 0;}unsigned getLength(){return 0;}void release(){}}; static IOMemoryDescriptor *withPhysicalAddress(uint64_t,unsigned,unsigned){return nullptr;} Map *map(unsigned){return nullptr;}void release(){} }; struct IOPCIDevice {unsigned reads=0,publications=0;uint32_t configRead32(unsigned off){assert(off==0xfc);++reads;return 0;} - void setProperty(const char*,uint32_t,unsigned){++publications;}void setProperty(const char*,const void*,unsigned){++publications;}void setProperty(const char*,OSData*){++publications;} + std::map properties; + OSData *getProperty(const char *key){auto i=properties.find(key);return i==properties.end()?nullptr:&i->second;} + void setProperty(const char*,uint32_t,unsigned){++publications;}void setProperty(const char*,const void*,unsigned){++publications;} + void setProperty(const char *key,OSData *d){++publications;properties[key]=*d;} }; struct XE {XELP::Profile xeProfile;XELP::VBT::Ports firmwarePorts;void xelpReadFirmware(IOPCIDevice*);}; BODY -int main(){ +int main(int argc,char **argv){ for(unsigned kind=0;kind<5;++kind){XE x;IOPCIDevice pci; x.xeProfile=kind==0?XELP::identify(0x46a68086,12,0x9a498086):kind==1?XELP::identify(0x46a68086,12):kind==2?XELP::identify(0x9a498086,1):kind==3?XELP::identify(0x46d18086,0):XELP::identify(0x4c8a8086,0); const bool expected=kind!=4; if(!kind)assert(x.xeProfile.admitted() && x.xeProfile.displayAdmitted()); x.xelpReadFirmware(&pci);assert(pci.reads==unsigned(expected));assert(bool(pci.publications)==expected); + assert(!pci.getProperty("WEXDSIFirmwareAudit")); x.xelpReadFirmware(nullptr); } + auditEnabled=true;XE x;IOPCIDevice pci;x.xeProfile=XELP::identify(0x46d48086,0); + x.xelpReadFirmware(&pci);auto absent=pci.getProperty("WEXDSIFirmwareAudit");assert(absent&&absent->bytes.size()==33*4); + assert(XELP::VBT::u32(absent->bytes.data()+4)==unsigned(XELP::DSI::ProfileStatus::Bounds)); + if(argc==2){ + std::ifstream f(argv[1],std::ios::binary);assert(f); + pci.properties["TGLVBT"].bytes.assign(std::istreambuf_iterator(f),{}); + x.xelpReadFirmware(&pci);auto data=pci.getProperty("WEXDSIFirmwareAudit");assert(data); + assert(XELP::VBT::u32(data->bytes.data()+4)==unsigned(XELP::DSI::ProfileStatus::Valid)); + assert(XELP::VBT::u32(data->bytes.data()+16*4)==359); + } puts("PASS production VBT acquisition: physical and legacy-selector ADL-P plus TGL/ADL-N read firmware; unsupported RKL remains closed");} '''.replace('BODY',body) with tempfile.TemporaryDirectory(prefix='firmware-readout-') as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) - subprocess.run([str(p/'test')],check=True) + subprocess.run([str(p/'test'),*sys.argv[1:]],check=True) diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index b7bbd7c3..eb60ce93 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -1,4 +1,5 @@ { + "-wex-dsi-audit": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" From 0bb165ec27011c24d3d734aabfc65fc6c0e603cd Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:48:05 +0900 Subject: [PATCH 018/139] Decode DSI ownership and verify native timing contracts --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_apple_timing.hpp | 36 ++++++++++ WhateverXe/kern_dsi_math.hpp | 24 ++++++- WhateverXe/kern_dsi_profile.hpp | 9 ++- WhateverXe/kern_dsi_readout.hpp | 79 +++++++++++++++++++++ WhateverXe/kern_dsi_session.hpp | 83 +++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 16 +++++ tests/dsi_apple_timing.cpp | 16 +++++ tests/dsi_apple_timing_native.py | 21 ++++++ tests/dsi_apple_timing_native_fixture.cpp | 73 ++++++++++++++++++++ tests/dsi_profile.cpp | 7 +- tests/dsi_readout.cpp | 51 ++++++++++++++ tests/dsi_session.cpp | 49 +++++++++++++ tools/test.py | 1 + 15 files changed, 462 insertions(+), 7 deletions(-) create mode 100644 WhateverXe/kern_dsi_apple_timing.hpp create mode 100644 WhateverXe/kern_dsi_readout.hpp create mode 100644 WhateverXe/kern_dsi_session.hpp create mode 100644 tests/dsi_apple_timing.cpp create mode 100644 tests/dsi_apple_timing_native.py create mode 100644 tests/dsi_apple_timing_native_fixture.cpp create mode 100644 tests/dsi_readout.cpp create mode 100644 tests/dsi_session.cpp diff --git a/README.md b/README.md index c5475ed7..574868ef 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ No device-ID override or machine-specific AGDP kext is required. The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 DSI-A GOP to Combo B at 1080p60 for development. It stops the internal display and retires the temporary HDMI resources when policy switches to another connected output. It is disabled by default and does not provide DSI support or general cold startup. -The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and publishes a diagnostic summary. It is disabled by default; the audit does not program the display or enable DSI output. +The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and available cached boot state, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index ab9bd8c1..b9a57030 100644 --- a/README_ja.md +++ b/README_ja.md @@ -104,7 +104,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 試験用の `-wex-dsi-hdmi-test` は、限定したDisplay13のDSI-A GOPからCombo-Bへ1080p60で移す開発用経路です。内蔵表示を停止し、policyが別の接続済み出力へ切り替える際は暫定HDMI資源を解放します。既定では無効で、DSI対応や汎用のcold起動を提供するものではありません。 -任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 +任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式と取得済みのboot状態を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp new file mode 100644 index 00000000..7b9eccc0 --- /dev/null +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -0,0 +1,36 @@ +// Exact TGL donor's IODetailedTimingInformationV2 data contract. Apple-specific +// layout stays outside the DSI host; no native object is owned by this value. +#ifndef KERN_DSI_APPLE_TIMING_HPP +#define KERN_DSI_APPLE_TIMING_HPP +#include "kern_dsi_profile.hpp" +namespace XELP { namespace DSI { namespace AppleAdapter { +struct Timing { + uint32_t reservedA[3] {},horizontalInset {},verticalInset {},scalerFlags {},horizontalScaled {},verticalScaled {}; + uint32_t signalConfig {},signalLevels {}; + uint64_t pixelClock {},minPixelClock {},maxPixelClock {}; + uint32_t horizontalActive {},horizontalBlanking {},horizontalSyncOffset {},horizontalSyncWidth {}; + uint32_t verticalActive {},verticalBlanking {},verticalSyncOffset {},verticalSyncWidth {}; + uint32_t borders[4] {},horizontalSyncConfig {},horizontalSyncLevel {},verticalSyncConfig {},verticalSyncLevel {}; + uint32_t numLinks {},verticalBlankingExtension {}; + uint16_t pixelEncoding {},bitsPerColorComponent {},colorimetry {},dynamicRange {},dsc[3] {},reservedB[5] {}; +}; +static_assert(sizeof(Timing)==160 && offsetof(Timing,pixelClock)==0x28 && + offsetof(Timing,horizontalActive)==0x40 && offsetof(Timing,pixelEncoding)==0x88,"Apple timing ABI"); +constexpr uint32_t FixedModeID=0x10000; +inline bool makeTiming(const Profile &p,Timing &result) { + result={};const auto &m=p.mode; + if(!p.valid() || !m.valid())return false; + result.pixelClock=uint64_t(m.clockKHz)*1000; + const auto error=(result.pixelClock+9999)/10000; // 100 ppm, also used by DSI readout + result.minPixelClock=result.pixelClock-error;result.maxPixelClock=result.pixelClock+error; + result.horizontalActive=m.width;result.horizontalBlanking=m.hblank; + result.horizontalSyncOffset=m.hfront;result.horizontalSyncWidth=m.hsync; + result.verticalActive=m.height;result.verticalBlanking=m.vblank; + result.verticalSyncOffset=m.vfront;result.verticalSyncWidth=m.vsync; + // IOGraphics timing feature fields are bit sets, not literal bit counts. + result.numLinks=1;result.pixelEncoding=1;result.bitsPerColorComponent=2; + result.colorimetry=1;result.dynamicRange=1; + return true; +} +}}} +#endif diff --git a/WhateverXe/kern_dsi_math.hpp b/WhateverXe/kern_dsi_math.hpp index 8ebfed47..90bca5a8 100644 --- a/WhateverXe/kern_dsi_math.hpp +++ b/WhateverXe/kern_dsi_math.hpp @@ -13,7 +13,7 @@ inline uint32_t cappedCeil(uint32_t numerator,uint32_t denominator,uint32_t cap) } inline LinkPlan linkPlan(const Profile &p) { LinkPlan r; - if(!p.valid() || p.lanes!=4 || p.byteClock!=0 || !p.mode.clockKHz || p.mode.clockKHz>300000 || + if(!p.valid() || p.lanes!=4 || p.byteClock!=0 || !p.mode.valid() || p.clockPrepareZero>2)&15,ke=(cfg1>>6)&7; + const auto p=pe==1?2U:pe==2?3U:pe==4?5U:pe==8?7U:0; + const auto k=ke==1?1U:ke==2?2U:ke==4?3U:0; + const auto q=(cfg1&(1U<<9))?((cfg1>>10)&255):1; + if(!p || !k || !q || (k!=2 && q!=1))return 0; + const auto integer=cfg0&1023;auto fraction=(cfg0>>10)&32767; + if(referenceKHz==38400){referenceKHz=19200;fraction*=2;} + const auto dco=uint64_t(integer)*referenceKHz+uint64_t(fraction)*referenceKHz/32768; + if(dco<7998000 || dco>10000000)return 0; + return uint32_t(dco/(p*q*k)); +} inline PLLPlan pllPlan(uint32_t portKHz,uint32_t referenceKHz) { PLLPlan r; if(!portKHz || portKHz>600000 || (referenceKHz!=19200 && referenceKHz!=24000 && referenceKHz!=38400))return r; diff --git a/WhateverXe/kern_dsi_profile.hpp b/WhateverXe/kern_dsi_profile.hpp index 057e6c0e..2f865120 100644 --- a/WhateverXe/kern_dsi_profile.hpp +++ b/WhateverXe/kern_dsi_profile.hpp @@ -14,6 +14,11 @@ struct Mode { uint16_t width {},hblank {},hfront {},hsync {},height {},vblank {},vfront {},vsync {},widthMM {},heightMM {}; uint32_t htotal() const {return uint32_t(width)+hblank;} uint32_t vtotal() const {return uint32_t(height)+vblank;} + bool valid() const { + return clockKHz && clockKHz<=300000 && width && height && hsync && vsync && + uint32_t(hfront)+hsync300000 || !m.width || !m.height || !m.hsync || !m.vsync || - uint32_t(m.hfront)+m.hsync>=m.hblank || uint32_t(m.vfront)+m.vsync>=m.vblank || - m.htotal()>65536 || m.vtotal()>65536)return failure; + if(!m.valid())return failure; failure.status=ProfileStatus::Backlight; if(backlight.size<273 || backlight.data[0]!=6)return failure; const auto bl=backlight.data+1+6*r.panel; diff --git a/WhateverXe/kern_dsi_readout.hpp b/WhateverXe/kern_dsi_readout.hpp new file mode 100644 index 00000000..0b00d6b9 --- /dev/null +++ b/WhateverXe/kern_dsi_readout.hpp @@ -0,0 +1,79 @@ +// Decode a caller-owned, cached Display13 DSI0 snapshot. This does not acquire +// power, read MMIO, prove snapshot stability, or grant resource ownership. +// Registers: Intel i915_reg.h v6.12 and icl_dsi_regs.h/icl_dsi.c v7.0. +#ifndef KERN_DSI_READOUT_HPP +#define KERN_DSI_READOUT_HPP +#include "kern_dsi_math.hpp" +namespace XELP { namespace DSI { +struct Identity { + uint32_t pipe {UINT32_MAX},transcoder {UINT32_MAX},phy {UINT32_MAX},pll {UINT32_MAX}; + bool valid() const {return pipe<3 && transcoder==0 && phy==0 && pll<2;} + bool same(const Identity &b) const {return pipe==b.pipe && transcoder==b.transcoder && phy==b.phy && pll==b.pll;} +}; +enum class ReadoutStatus : uint32_t {Missing, Disabled, Transition, Routing, Power, Transport, Clock, Timing, Complete}; +struct Readout { + uint32_t version {1};ReadoutStatus status {ReadoutStatus::Missing}; + Identity identity; + uint32_t conf {},function {},transport {},link {},mux {},pllEnable {},cfg0 {},cfg1 {},referenceKHz {},afeKHz {}; + uint32_t normalConf {},wells {},buffer {},htotal {},hsync {},vtotal {},vsync {}; + bool complete() const {return status==ReadoutStatus::Complete;} + uint32_t activePipes() const {return complete()?1U<p.afeKHz?afeKHz-p.afeKHz:p.afeKHz-afeKHz; + return uint64_t(difference)*1000000<=uint64_t(p.afeKHz)*tolerancePpm; + } +}; +// get(reg,value) must inspect immutable cached words only. DSI timing belongs +// to transcoder0; frame/IRQ/planes belong to the separately decoded pipe. +template Readout decodeReadout(Get get,uint32_t referenceKHz) { + Readout r;r.referenceKHz=referenceKHz; + const auto value=[&](uint32_t reg,uint32_t &out){return get(reg,out) && out!=UINT32_MAX;}; + if(!value(0x7B008,r.conf))return r; + if(!(r.conf&0xC0000000U)){r.status=ReadoutStatus::Disabled;return r;} + if((r.conf&0xC0000000U)!=0xC0000000U){r.status=ReadoutStatus::Transition;return r;} + if(!value(0x6B400,r.function))return r; + r.status=ReadoutStatus::Routing; + const auto input=(r.function>>12)&7; + const auto pipe=input==0?0U:input==5?1U:input==6?2U:UINT32_MAX; + if(pipe==UINT32_MAX || !(r.function&0x80000000U))return r; + r.identity.pipe=pipe;r.identity.transcoder=0;r.identity.phy=0; + r.status=ReadoutStatus::Missing; + if(!value(0x45404,r.wells) || !value(0x70008+0x1000*pipe,r.normalConf))return r; + r.status=ReadoutStatus::Power; + if(!(r.wells&1) || !(r.wells&(1U<<(10+2*pipe))) || (pipe && !(r.wells&4)))return r; + r.status=ReadoutStatus::Routing; + if(r.normalConf&0xC0000000U)return r; // two timing consumers for one pipe + r.status=ReadoutStatus::Missing;uint32_t secondary=0,command=0; + if(!value(0x6B030,r.transport) || !value(0x6B094,r.link) || + !value(0x6B404,secondary) || !value(0x6B034,command) || !value(0x64000,r.buffer))return r; + r.status=ReadoutStatus::Transport; + if((r.function&0x70000E)!=6 || secondary || command || + (r.transport&0x303F0000U)!=0x20130000U || !(r.link&1) || (r.buffer&0x80000080U)!=0x80000000U)return r; + r.status=ReadoutStatus::Missing; + if(!value(0x164280,r.mux))return r; + r.identity.pll=r.mux&3;r.status=ReadoutStatus::Clock; + // DSI deliberately gates the DDI clock after enabling its PHY/buffer + // (gen11_dsi_enable_port_and_phy). This bit does NOT make its PLL free. + if(r.identity.pll>=2)return r; + r.status=ReadoutStatus::Missing; + const auto pll=r.identity.pll; + if(!value(0x46010+4*pll,r.pllEnable) || !value(0x164284+8*pll,r.cfg0) || + !value(0x164288+8*pll,r.cfg1))return r; + r.status=ReadoutStatus::Clock; + if((r.pllEnable&0xCC000000U)!=0xCC000000U)return r; + r.afeKHz=pllAFE(r.cfg0,r.cfg1,referenceKHz);if(!r.afeKHz)return r; + r.status=ReadoutStatus::Missing; + if(!value(0x6B000,r.htotal) || !value(0x6B008,r.hsync) || + !value(0x6B00C,r.vtotal) || !value(0x6B014,r.vsync))return r; + r.status=ReadoutStatus::Timing; + const auto ordered=[](uint32_t total,uint32_t sync) { + return (total&65535)<(sync&65535) && (sync&65535)<(sync>>16) && (sync>>16)<(total>>16); + }; + if(!ordered(r.htotal,r.hsync) || !ordered(r.vtotal,r.vsync))return r; + r.status=ReadoutStatus::Complete;return r; +} +}} +#endif diff --git a/WhateverXe/kern_dsi_session.hpp b/WhateverXe/kern_dsi_session.hpp new file mode 100644 index 00000000..d44f92bb --- /dev/null +++ b/WhateverXe/kern_dsi_session.hpp @@ -0,0 +1,83 @@ +// P2 adoption journal, separate from Apple objects and MMIO. The adapter owns +// serialization, immutable profile storage, and opaque resource handles. +// This is an internal contract, not the future separately loaded kext ABI. +#ifndef KERN_DSI_SESSION_HPP +#define KERN_DSI_SESSION_HPP +#include "kern_dsi_readout.hpp" +namespace XELP { namespace DSI { +struct Capabilities { + enum Bit : uint32_t { + Profile=1U<<0,Readout=1U<<1,FixedMode=1U<<2,InternalDisplay=1U<<3, + BindResources=1U<<4,SwitchSurface=1U<<5,Stop=1U<<6,IRQ=1U<<7, + Commands=1U<<8,PanelPower=1U<<9,Backlight=1U<<10 + }; + // Independent namespace: never borrow bits from DisplayContract::Mask. + uint32_t version {1},bytes {sizeof(Capabilities)},installed {}; + static constexpr uint32_t Adoption=Profile|Readout|FixedMode|InternalDisplay|BindResources|SwitchSurface|Stop|IRQ; + bool admits(uint32_t required) const {return version==1 && bytes==sizeof(*this) && (installed&required)==required;} +}; +enum class SessionState : uint32_t {Unbound,FirmwareActive,Switching,Streaming,Quiesced,Faulted,Released}; +enum class SessionError : uint32_t {None,Contract,Binding,Transition,Switch,Stop}; +struct Ticket {uint64_t epoch {},serial {};}; +struct SurfaceProof { + Ticket ticket;Identity identity; + uint64_t backing {};bool liveSurfaceMatches {},frameAdvanced {},timingMatches {}; +}; +struct StopProof { + Ticket ticket;Identity identity; + bool planeStopped {},irqDrained {},dsiStopped {},clockDetached {}; +}; +struct Session { + SessionState state {SessionState::Unbound};SessionError firstError {SessionError::None}; + Identity identity; + uint64_t epoch {},serial {},lease {},currentBacking {},pendingBacking {},retiredBacking {}; + Ticket ticket() const {return {epoch,serial};} + bool accepts(Ticket t) const {return t.epoch==epoch && t.serial==serial && epoch && serial;} + bool fail(SessionError error) {if(firstError==SessionError::None)firstError=error;state=SessionState::Faulted;return false;} + bool bind(const Capabilities &caps,const Readout &snapshot,const Profile &profile, + uint64_t generation,uint64_t sharedLease,uint64_t firmwareBacking) { + // A rejected bind must not consume caller-owned handles. + if(state!=SessionState::Unbound || !caps.admits(Capabilities::Adoption) || + !generation || !sharedLease || !firmwareBacking || !snapshot.identity.valid() || !snapshot.matches(profile))return false; + epoch=generation;serial=1;lease=sharedLease;currentBacking=firmwareBacking; + identity=snapshot.identity;state=SessionState::FirmwareActive;return true; + } + bool beginSurface(uint64_t backing) { + if((state!=SessionState::FirmwareActive && state!=SessionState::Streaming) || + !backing || backing==currentBacking || retiredBacking || serial==UINT64_MAX)return false; + pendingBacking=backing;++serial;state=SessionState::Switching;return true; + } + bool completeSurface(const SurfaceProof &proof) { + // Stale callbacks do not mutate the current transaction or its error. + if(state!=SessionState::Switching || !accepts(proof.ticket))return false; + if(!proof.identity.same(identity) || proof.backing!=pendingBacking || + !proof.liveSurfaceMatches || !proof.frameAdvanced || !proof.timingMatches)return fail(SessionError::Switch); + retiredBacking=currentBacking;currentBacking=pendingBacking;pendingBacking=0; + state=SessionState::Streaming;return true; + } + bool abort(Ticket t,SessionError error) { + if(!accepts(t) || error==SessionError::None || state==SessionState::Released || state==SessionState::Quiesced)return false; + return fail(error); // both backings remain held if a switch may have reached hardware + } + // Returns one cleanup obligation to the adapter, at most once. The adapter + // frees backing outside native/owner lock inversions and records completion. + uint64_t takeRetiredBacking() { + if(state!=SessionState::Streaming)return 0; + const auto result=retiredBacking;retiredBacking=0;return result; + } + bool stopped(const StopProof &proof) { + if(state==SessionState::Unbound || state==SessionState::Released || state==SessionState::Quiesced || + !accepts(proof.ticket))return false; + if(!proof.identity.same(identity) || !proof.planeStopped || !proof.irqDrained || + !proof.dsiStopped || !proof.clockDetached)return fail(SessionError::Stop); + state=SessionState::Quiesced;return true; + } + struct Cleanup {uint64_t lease {},currentBacking {},pendingBacking {},retiredBacking {};}; + Cleanup takeCleanup() { + if(state!=SessionState::Quiesced)return {}; + Cleanup result {lease,currentBacking,pendingBacking,retiredBacking}; + lease=currentBacking=pendingBacking=retiredBacking=0;state=SessionState::Released;return result; + } +}; +}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ee4568ff..0b65b918 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -16,6 +16,7 @@ #include "kern_xelp_forcewake_irq.hpp" #include "kern_display13_boot.hpp" #include "kern_display13_dsi_trial.hpp" +#include "kern_dsi_readout.hpp" #include "kern_display13_firmware.hpp" #include "kern_display13_tc_aux.hpp" #include "kern_display13_tc_mode.hpp" @@ -3949,6 +3950,21 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if(words) { for(unsigned i=0;isetProperty("WEXDisplay13DSIBoot",words,sizeof(regs)*2); + if(checkKernelArgument("-wex-dsi-audit")) { + // Decode only already collected words. Overlapping sources + // must agree; this summary is not a stable adoption certificate. + const auto cached=[&](uint32_t reg,uint32_t &value) { + uint32_t bootValue=0;const bool found=AlderLakeN::value(result,reg,bootValue); + for(unsigned i=0;i>29)&7; + auto decoded=XELP::DSI::decodeReadout(cached,index<3?references[index]:0); + pci->setProperty("WEXDSIReadout",&decoded,sizeof(decoded)); + } IOFree(words,sizeof(regs)*2); } } diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp new file mode 100644 index 00000000..04950912 --- /dev/null +++ b/tests/dsi_apple_timing.cpp @@ -0,0 +1,16 @@ +#include "kern_dsi_apple_timing.hpp" +#include +#include +using namespace XELP::DSI; +int main() { + Profile p;p.status=ProfileStatus::Valid;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + AppleAdapter::Timing t;assert(AppleAdapter::makeTiming(p,t)); + assert(t.pixelClock==135255000&&t.minPixelClock==135241474&&t.maxPixelClock==135268526); + assert(t.horizontalActive==1080&&t.horizontalBlanking==46&&t.horizontalSyncOffset==22&&t.horizontalSyncWidth==2); + assert(t.verticalActive==1920&&t.verticalBlanking==82&&t.verticalSyncOffset==40&&t.verticalSyncWidth==2); + assert(t.bitsPerColorComponent==2&&t.pixelEncoding==1&&t.numLinks==1); + assert(!t.scalerFlags&&!t.horizontalScaled&&!t.verticalScaled&&!t.verticalBlankingExtension); + p.mode.hsync=46;assert(!AppleAdapter::makeTiming(p,t)&&!t.pixelClock); + p={};assert(!AppleAdapter::makeTiming(p,t)); + puts("DSI fixed Apple timing: Hz/range, portrait geometry, RGB8 bit fields, no scaling/DSC: PASS"); +} diff --git a/tests/dsi_apple_timing_native.py b/tests/dsi_apple_timing_native.py new file mode 100644 index 00000000..be71f549 --- /dev/null +++ b/tests/dsi_apple_timing_native.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +"""Replay exact donor timing publication/validation with retained mock objects. + +No Apple bytes are stored in the fixture. This checks the native data contract, +not CoreDisplay mode enumeration, native lock behavior, or hardware modesetting. +""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile + +root = Path(__file__).resolve().parents[1] +donor = Path(sys.argv[1]).resolve() +data = donor.read_bytes() +assert hashlib.sha256(data).hexdigest() == 'c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +assert data[0x30e6b] == 0xe8 +assert data[0x30e4c:0x30e52] == bytes.fromhex('81 e6 ff 03 00 00') +with tempfile.TemporaryDirectory(prefix='dsi-timing-') as directory: + executable = Path(directory) / 'test' + subprocess.run(['clang++', '-arch', 'x86_64', '-std=c++17', '-O1', + '-I'+str(root/'WhateverXe'), str(root/'tests/dsi_apple_timing_native_fixture.cpp'), + '-o', str(executable)], check=True) + subprocess.run([str(executable), str(donor)], check=True) diff --git a/tests/dsi_apple_timing_native_fixture.cpp b/tests/dsi_apple_timing_native_fixture.cpp new file mode 100644 index 00000000..aa037e39 --- /dev/null +++ b/tests/dsi_apple_timing_native_fixture.cpp @@ -0,0 +1,73 @@ +#include "kern_dsi_apple_timing.hpp" +#include +#include +#include +#include +#include +#include +using namespace XELP::DSI; +struct Object {void **vtable {};unsigned refs {1};bool data {};}; +struct Data : Object {std::vector bytes;}; +struct Array : Object {Data *entry {};}; +static Array *property=nullptr; +static bool publishOK=true; +void retain(Object *p){assert(p&&p->refs);++p->refs;} +void release(Object *p){assert(p&&p->refs);--p->refs;} +bool setProperty(void *,const char *key,Array *array) { + assert(!strcmp(key,"IOFBDetailedTimings"));if(!publishOK)return false; + retain(array);if(property)release(property);property=array;return true; +} +void removeProperty(void *,const char *key) { + assert(!strcmp(key,"IOFBDetailedTimings"));if(property)release(property);property=nullptr; +} +void *getObject(Array *a,uint32_t index){assert(a->refs);return index==0?a->entry:nullptr;} +void *getBytes(Data *d){assert(d->refs);return d->bytes.data();} +void *cast(Object *o,const void *){return o&&o->data?o:nullptr;} +templatevoid put(void *p,size_t offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} +void jump(uint8_t *p,uintptr_t target){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&target,8);p[10]=0xff;p[11]=0xe0;} +int main(int argc,char **argv) { + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);assert(f); + std::vector binary{std::istreambuf_iterator(f),{}}; + constexpr size_t size=4*1024*1024;assert(binary.size()(mmap(nullptr,size,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0)); + assert(image!=MAP_FAILED);memcpy(image,binary.data(),binary.size()); + // Redirect only the imported meta-cast leaf, retaining all donor branches. + jump(image+0x300000,reinterpret_cast(cast)); + int32_t relative=0x300000-0x30e70;put(image,0x30e6c,relative); + void *meta=nullptr;put(image,0xc8050,&meta); + auto publish=reinterpret_cast(image+0x30d0e); + auto validate=reinterpret_cast(image+0x30de2); + void *fbTable[0x2a8/8] {},*arrayTable[0x1f0/8] {},*dataTable[0x180/8] {}; + fbTable[0x268/8]=reinterpret_cast(setProperty);fbTable[0x2a0/8]=reinterpret_cast(removeProperty); + arrayTable[0x1e8/8]=reinterpret_cast(getObject);dataTable[0x178/8]=reinterpret_cast(getBytes); + alignas(8) uint8_t fb[0x4300] {};put(fb,0,fbTable+0);fb[0x1e0]=1; + Profile p;p.status=ProfileStatus::Valid;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + AppleAdapter::Timing timing;assert(AppleAdapter::makeTiming(p,timing)); + Data data;data.vtable=dataTable;data.data=true; + const auto bytes=reinterpret_cast(&timing);data.bytes.assign(bytes,bytes+sizeof(timing)); + Array array;array.vtable=arrayTable;array.entry=&data;retain(&data); // array retains OSData + assert(!publish(fb,&array)&&property==&array&&array.refs==2); + release(&array);release(&data); // only property/array now retain the data + const void *result=nullptr;void *description=nullptr; + assert(!validate(fb,AppleAdapter::FixedModeID,&description,&result)&&description&&result==data.bytes.data()); + assert(!memcmp(result,&timing,sizeof(timing))&&array.refs==1&&data.refs==1); + assert(validate(fb,0x10001,nullptr,&result)==0xe00002c2U); + assert(validate(fb,0xffff,nullptr,&result)==0xe00002c2U); + data.data=false;assert(validate(fb,0x10000,nullptr,&result)==0xe00002c2U);data.data=true; + // Native mode indexing aliases high bits; the DSI mode dispatcher must + // separately constrain the selected fixed mode rather than relying on it. + assert(!validate(fb,0x10400,nullptr,&result)); + // Native validation does not check OSData length. Publication must always + // carry the complete structure; a type-correct short blob is insufficient. + data.bytes.resize(1);assert(!validate(fb,0x10000,nullptr,&result)); + assert(!publish(fb,nullptr)&&!property&&!array.refs); + assert(validate(fb,0x10000,nullptr,&result)==0xe00002c2U);release(&data); + // Native publication ignores setProperty failure and still stores a raw + // array pointer. The eventual adapter must retain its own backing owner. + Array held;held.vtable=arrayTable;held.entry=nullptr;publishOK=false; + assert(!publish(fb,&held)&&!property&&held.refs==1); + Array *raw=nullptr;memcpy(&raw,fb+0x4260,sizeof(raw));assert(raw==&held); + assert(!publish(fb,nullptr));release(&held); + munmap(image,size); + puts("PASS exact donor timing: retained array/data, type rejection, index alias, short-data and property-failure limits"); +} diff --git a/tests/dsi_profile.cpp b/tests/dsi_profile.cpp index a53c07c1..0f2e482e 100644 --- a/tests/dsi_profile.cpp +++ b/tests/dsi_profile.cpp @@ -46,9 +46,14 @@ int main(int argc,char **argv) { assert(link.htotal==0x04650437&&link.hsync==0x044F044D&&link.vtotal==0x07D1077F&&link.vsync==0x07A907A7); const auto pll=pllPlan(link.portKHz,19200); assert(pll.valid&&pll.divider==12&&pll.p==2&&pll.q==3&&pll.k==2); - assert((pll.fraction<<9|pll.integer)==0x34CDFB); // saved DPLL0_CFGCR0 + assert(pll.cfg0()==0x6999FB&&pll.cfg1()==0xE84); const auto doubled=pllPlan(link.portKHz,38400); assert(doubled.valid&&doubled.integer==pll.integer&&doubled.fraction==(pll.fraction+1)/2); + assert(doubled.cfg0()==0x34CDFB&&doubled.cfg1()==0xE84); // saved DPLL0, 38.4 MHz + WA + assert(pllAFE(doubled.cfg0(),doubled.cfg1(),38400)==811529); + assert(pllAFE(pll.cfg0(),pll.cfg1(),19200)==811529); + assert(!pllAFE(doubled.cfg0(),0,38400)&&!pllAFE(doubled.cfg0(),0xE84,27000)); + assert(!pllAFE(doubled.cfg0()|1U<<29,0xE84,38400)); // SSC outside supported profile assert(!pllPlan(0,19200).valid&&!pllPlan(UINT32_MAX,19200).valid&&!pllPlan(162306,27000).valid); for(unsigned clock=10000;clock<500000;clock+=137) { auto v=pllPlan(clock,24000);if(!v.valid)continue; diff --git a/tests/dsi_readout.cpp b/tests/dsi_readout.cpp new file mode 100644 index 00000000..042cf611 --- /dev/null +++ b/tests/dsi_readout.cpp @@ -0,0 +1,51 @@ +#include "kern_dsi_readout.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +using Registers=std::map; +Readout decode(const Registers ®s,uint32_t ref=38400) { + return decodeReadout([&](uint32_t a,uint32_t &v){auto i=regs.find(a);if(i==regs.end())return false;v=i->second;return true;},ref); +} +Registers firmware() { + // Numeric register facts from a DSI0/pipeA/PLL1 GOP; no firmware code. + return {{0x7B008,0xC0000000},{0x6B400,0x80030006},{0x45404,0xFC0F},{0x70008,0}, + {0x6B030,0x20130300},{0x6B094,1},{0x6B404,0},{0x6B034,0},{0x64000,0x80000000},{0x164280,0x1E07801}, + {0x46014,0xCC000000},{0x16428C,0x3001FB},{0x164290,0xE84}, + {0x6B000,0x04650437},{0x6B008,0x044F044D},{0x6B00C,0x07D1077F},{0x6B014,0x07A907A7}}; +} +int main(int argc,char **argv) { + auto regs=firmware();auto r=decode(regs); + assert(r.complete()&&r.identity.valid()&&r.identity.pipe==0&&r.identity.transcoder==0&&r.identity.phy==0&&r.identity.pll==1); + assert(r.normalConf==0&&r.activePipes()==1&&r.afeKHz==811500); + Profile profile;profile.status=ProfileStatus::Valid;profile.lanes=4; + profile.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + assert(r.matches(profile)&&!r.matches(profile,1)); + for(const auto &entry:regs) { + auto missing=regs;missing.erase(entry.first);assert(!decode(missing).complete()); + auto absent=regs;absent[entry.first]=UINT32_MAX;assert(!decode(absent).complete()); + } + auto linux=regs;linux[0x164280]=0x1E06C04;linux[0x46010]=0xCC000000; + linux[0x164284]=0x34CDFB;linux[0x164288]=0xE84;linux[0x6B030]=0x20130304; + auto l=decode(linux);assert(l.complete()&&l.identity.pll==0&&l.afeKHz==811529&&l.matches(profile)&&l.ddiClockGated()); + // DSI0 may consume pipe B; neither its register bank nor PHY follows pipe. + auto b=regs;b[0x6B400]|=5<<12;b.erase(0x70008);b[0x71008]=0; + auto pipeB=decode(b);assert(pipeB.complete()&&pipeB.activePipes()==2&&pipeB.identity.pipe==1&&pipeB.identity.pll==1); + b[0x71008]=0xC0000000;assert(decode(b).status==ReadoutStatus::Routing); + for(auto pair:Registers{{0x7B008,0},{0x6B400,0x80034006},{0x6B094,0},{0x6B404,1}, + {0x6B034,1},{0x45404,1},{0x164280,0x1E07803},{0x46014,0x80000000},{0x64000,0}, + {0x6B000,0x04370465},{0x6B400,0x80130006}}) { + auto bad=regs;bad[pair.first]=pair.second;assert(!decode(bad).complete()); + } + auto transition=regs;transition[0x7B008]=0x80000000;assert(decode(transition).status==ReadoutStatus::Transition); + assert(!decode(regs,27000).complete()); + if(argc==2) { + std::ifstream f(argv[1]);assert(f);Registers saved;uint32_t a,v; + while(f>>std::hex>>a>>v){assert(saved.emplace(a,v).second);}assert(f.eof()); + auto actual=decode(saved);assert(actual.complete()&&actual.matches(profile)); + printf("saved DSI snapshot: pipe=%u transcoder=%u PHY=%u PLL=%u AFE=%u kHz normalCONF=%x\n", + actual.identity.pipe,actual.identity.transcoder,actual.identity.phy,actual.identity.pll,actual.afeKHz,actual.normalConf); + } + puts("DSI snapshot: separate pipe/transcoder/PHY/PLL, bounded clocks, missing/conflicting state: PASS"); +} diff --git a/tests/dsi_session.cpp b/tests/dsi_session.cpp new file mode 100644 index 00000000..e3ed8d41 --- /dev/null +++ b/tests/dsi_session.cpp @@ -0,0 +1,49 @@ +#include "kern_dsi_session.hpp" +#include "kern_xelp_combo_clock.hpp" +#include +#include +using namespace XELP::DSI; +int main() { + Profile p;p.status=ProfileStatus::Valid;p.lanes=4; + p.mode={135255,1080,46,22,2,1920,82,40,2,86,154};const auto plan=linkPlan(p); + Readout r;r.status=ReadoutStatus::Complete;r.identity={0,0,0,1};r.afeKHz=811500; + r.htotal=plan.htotal;r.hsync=plan.hsync;r.vtotal=plan.vtotal;r.vsync=plan.vsync; + Capabilities caps;caps.installed=Capabilities::Adoption;Session s; + for(unsigned bit=0;bit<8;++bit){auto missing=caps;missing.installed&=~(1U< Date: Thu, 29 Oct 2026 21:58:30 +0900 Subject: [PATCH 019/139] Separate DSI transport and pipe timing consumers --- WhateverXe/kern_display13_cursor.hpp | 4 +- WhateverXe/kern_display13_fb_irq.hpp | 47 ++++++++++++++------- WhateverXe/kern_display13_fb_topology.hpp | 10 +++-- WhateverXe/kern_display13_output_wiring.hpp | 24 ++++++++--- WhateverXe/kern_display13_transcoder.hpp | 39 +++++++++++++++++ WhateverXe/kern_display13_wm.hpp | 12 +++--- WhateverXe/kern_xe.cpp | 27 ++++++------ tests/display13_cursor.cpp | 8 ++++ tests/display13_fb_irq.cpp | 31 ++++++++++++++ tests/display13_fb_topology.cpp | 25 +++++++++++ tests/display13_wm.cpp | 15 +++++++ 11 files changed, 198 insertions(+), 44 deletions(-) create mode 100644 WhateverXe/kern_display13_transcoder.hpp diff --git a/WhateverXe/kern_display13_cursor.hpp b/WhateverXe/kern_display13_cursor.hpp index 1394c213..617e35f1 100644 --- a/WhateverXe/kern_display13_cursor.hpp +++ b/WhateverXe/kern_display13_cursor.hpp @@ -12,14 +12,14 @@ inline uint32_t controlWrite(uint64_t reg,uint32_t value) { } enum class Status : uint32_t {Unavailable,Complete,Power,Conflict,Readback,Timeout}; struct Result {Status status {Status::Unavailable};uint32_t before {},after {},frameBefore {},frameAfter {},writes {},waitUs {};}; -template Result stop(IO &io,unsigned pipeIndex=0) { +template Result stop(IO &io,unsigned pipeIndex=0,const Display13Transcoder::Bindings &transcoders={}) { Result r; // This is a display pipe index, independent of HDMI A/B DDI identity. // The caller proves the path/FB association and limits its writer to this bank. if(pipeIndex>=3) {r.status=Status::Conflict;return r;} const uint32_t bank=pipeIndex*0x1000; if(!io.readable(0x700AC+bank) || !Display13FBIRQ::powered(io.read(0x45404),pipeIndex)) {r.status=Status::Power;return r;} - const auto pipe=io.read(0x70008+bank);r.before=io.read(0x70080+bank); + const auto pipe=Display13Transcoder::control(io,pipeIndex,transcoders);r.before=io.read(0x70080+bank); if(pipe==UINT32_MAX || r.before==UINT32_MAX) {r.status=Status::Readback;return r;} for(unsigned d=0;d<3;++d) { const auto dsb=io.read(Display13FBIRQ::DSBControl(pipeIndex,d)); diff --git a/WhateverXe/kern_display13_fb_irq.hpp b/WhateverXe/kern_display13_fb_irq.hpp index f287bfcb..aaf6ba84 100644 --- a/WhateverXe/kern_display13_fb_irq.hpp +++ b/WhateverXe/kern_display13_fb_irq.hpp @@ -4,6 +4,7 @@ #define KERN_DISPLAY13_FB_IRQ_HPP #include #include "kern_display13_power_bridge.hpp" +#include "kern_display13_transcoder.hpp" namespace Display13FBIRQ { // The IRQ simple lock protects this list. Each node lives on its real native // handler's stack until end(). A later master restore cannot grant DE access @@ -46,6 +47,7 @@ constexpr uint32_t DSBInterrupt(unsigned pipe,unsigned dsb) {return 0x70B28+0x10 constexpr uint32_t DSBControl(unsigned pipe,unsigned dsb) {return DSBInterrupt(pipe,dsb)-0x20;} enum class Status : uint32_t {Unavailable,Complete,Power,ForeignIRQ,Topology,Readback,Faulted}; struct State { + Display13Transcoder::Bindings transcoders; bool begun {},enabled {},faulted {}; Status firstFailure {Status::Unavailable}; uint32_t failureRegister {},failureBits {}; @@ -60,13 +62,15 @@ inline bool powered(uint32_t wells,unsigned pipe) { if(wells==UINT32_MAX || pipe>=3 || (wells&3)!=3)return false; return (wells&(1U<<(10+2*pipe))) && (!pipe || (wells&(1U<<2))); } -template ActivePipes readActivePipes(IO &io,uint32_t allowed) { +template ActivePipes readActivePipes(IO &io,uint32_t allowed,const Display13Transcoder::Bindings &transcoders={}) { ActivePipes r;r.allowed=allowed; - if(!allowed || (allowed&~7U)) {r.status=Status::Topology;return r;} + if(!allowed || (allowed&~7U) || !transcoders.valid()) {r.status=Status::Topology;return r;} const auto wells=io.read(Driver);if(wells==UINT32_MAX) {r.status=Status::Readback;return r;} + if(transcoders.dsi0Pipe<3 && !powered(wells,transcoders.dsi0Pipe)) {r.status=Status::Power;return r;} for(unsigned p=0;p<3;++p)if(powered(wells,p)) { - const auto conf=io.read(0x70008+0x1000*p); + const auto conf=Display13Transcoder::control(io,p,transcoders); if(conf==UINT32_MAX) {r.status=Status::Readback;return r;} + if(transcoders.dsi(p) && (conf&0xC0000000U) && (conf&0xC0000000U)!=0xC0000000U) {r.status=Status::Topology;return r;} if(conf&Enable)r.active|=1U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t sharedGT=0,uint32_t expectedPipes=1,uint32_t ports=3,uint32_t typeC=0) { if(s.faulted)return Status::Faulted; - if(!ports || (ports&~0x7FU) || (typeC&~(ports&0x78)) || (s.begun && (ports!=s.ports || typeC!=s.typeC)))return fail(io,s,Status::Topology); + const bool dsi=s.transcoders.dsi0Pipe!=UINT32_MAX; + if(!s.transcoders.valid() || (!ports && !dsi) || (dsi && (ports&1)) || (ports&~0x7FU) || (typeC&~(ports&0x78)) || (s.begun && (ports!=s.ports || typeC!=s.typeC)))return fail(io,s,Status::Topology); + if(dsi)excludedPCH|=1U<<16; // HPD-A is the panel reset GPIO, not hotplug. s.ports=ports;s.typeC=typeC;excludedPCH|=(typeC>>3)<<24; if((excludedPCH&pchOwned(s)) || (excludedPCH&~(PCHTypeC|PCHCombo)) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology); if(!available(io) || (typeC && !io.readable(NorthControl)) || ((ports&0x78) && !io.readable(TCControl)))return Status::Unavailable; @@ -150,11 +156,13 @@ template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t if(s.enabled && s.active)return (io.read(Master)&Enable) && (io.read(GfxMaster)&Enable) ? Status::Complete : fail(io,s,Status::Readback); auto wells=io.read(Driver); + if(dsi && !powered(wells,s.transcoders.dsi0Pipe))return fail(io,s,Status::Power); for(unsigned p=0;p<3;++p)if((expectedPipes&(1U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t } const auto ddi=io.read(DDIControl); if(ddi==UINT32_MAX)return fail(io,s,Status::Readback); - io.write(DDIControl,(ddi&~0x3333U)|ddiMask(s,8)|(ddi&ddiMask(s,3))); // Clear only owned stale HPD status. + // DSI's HPD-A is an output GPIO: disable its HPD detector, preserve + // OUTPUT_DATA bit2 and leave its W1C status untouched under this IRQ lock. + io.write(DDIControl,(ddi&~(0x3333U|(dsi?8U:0U)))|ddiMask(s,8)|(ddi&ddiMask(s,3))); + if(dsi && (io.read(DDIControl)&12)!=(ddi&4))return fail(io,s,Status::Readback); if(ports&0x78) { const auto tc=io.read(TCControl);s.lastTC=tc;if(tc==UINT32_MAX)return fail(io,s,Status::Readback); io.write(TCControl,(tc&~0x333333U)|tcMask(s,8)|(tc&tcMask(s,3))); @@ -262,7 +273,7 @@ template Status disable(IO &io,State &s) { // stale-status ACK is part of this native activation boundary. template Status extendActivePipes(IO &io,State &s,uint32_t allowed) { if(!s.begun || !s.enabled || s.faulted)return Status::Unavailable; - const auto actual=readActivePipes(io,allowed); + const auto actual=readActivePipes(io,allowed,s.transcoders); if(actual.status!=Status::Complete)return actual.status; if(s.active&~actual.active)return Status::Topology; const auto master=io.read(Master),gfx=io.read(GfxMaster); @@ -315,14 +326,20 @@ inline uint32_t portPowerDomains() { } // PLL bypass transitions retain pending interrupt status. Unlike physical // well release, they only need stopped consumers while the master is masked. -template bool consumersStopped(IO &io,uint32_t pipes) { +template bool consumersStopped(IO &io,uint32_t pipes,const Display13Transcoder::Bindings &transcoders={}) { + if(!transcoders.valid())return false; const auto wells=io.read(Driver); if(wells==UINT32_MAX || (wells&3)!=3 || (wells&(3U<<16)))return false; + if(transcoders.dsi0Pipe<3 && (pipes&(1U< Status parkStoppedPipe(IO &io,State &s,unsigned pipe,unsigned if(master==UINT32_MAX)return Status::Readback; if((master&Enable) || handlers || softwareHandlers || (queued&pipeEvents(pipe)))return Status::Unavailable; if(!powered(io.read(Driver),pipe))return Status::Power; - if(!consumersStopped(io,1U< Status parkStoppedPipe(IO &io,State &s,unsigned pipe,unsigned const auto stale=io.read(iir)&PipeOwned;if(stale)io.write(iir,stale); if((io.read(iir+4)&PipeOwned) || (~io.read(iir-4)&PipeOwned))return fail(io,s,Status::Readback); for(unsigned d=0;d<3;++d)if(io.read(DSBInterrupt(pipe,d))&0xF0000)return fail(io,s,Status::Readback); - if(!consumersStopped(io,1U< Status hotplugOnly(IO &io,State &s,bool preservePipeStatus=fa if(already && (!preservePipeStatus || !s.enabled || s.active))return Status::Unavailable; const auto wells=io.read(Driver); if(wells==UINT32_MAX || (wells&3)!=3)return Status::Power; - if(!consumersStopped(io,7))return Status::Topology; + if(!consumersStopped(io,7,s.transcoders))return Status::Topology; if((io.read(DDIControl)&ddiMask(s,8))!=ddiMask(s,8) || (io.read(0xC400C)&pchOwned(s))!=pchOwned(s) || (io.read(0xC4004)&pchOwned(s)))return Status::Readback; uint32_t ier[3] {},imr[3] {}; @@ -419,7 +436,7 @@ template bool quiescentPipes(IO &io,const State &s,uint32_t domains,un const auto ddi=io.read(0x64000+port*0x100); if(ddi==UINT32_MAX || (ddi&Enable))return false; } - if(!consumersStopped(io,pipes))return false; + if(!consumersStopped(io,pipes,s.transcoders))return false; for(unsigned p=0;p<3;++p)if((pipes&(1U< Status beginPowerRelease(IO &io,State &s,PowerReleaseScope &g if(displayHandlers || softwareHandlers || (queued&events) || auxiliary || (hotplug&hpd))return Status::Unavailable; const auto master=io.read(Master),wells=io.read(Driver); if(master==UINT32_MAX || wells==UINT32_MAX || bool(master&Enable)!=s.enabled)return Status::Readback; - if(!consumersStopped(io,pipes))return Status::Topology; + if(!consumersStopped(io,pipes,s.transcoders))return Status::Topology; // Preflight every bank before parking any. A never-configured bank must // already have all notifications masked; it is not ours to clear. for(unsigned p=0;p<3;++p)if(pipes&(1U<(p+0x20+24*d); if(config[0]!=d || (!(w.selected()&(1U<>3)<<24)) | - ((7U & ~w.selected())<<16); + return (0x3F000000 & ~((w.hotplug()>>3)<<24)) | + ((7U & ~w.hotplug())<<16); } } #endif diff --git a/WhateverXe/kern_display13_output_wiring.hpp b/WhateverXe/kern_display13_output_wiring.hpp index 90e65af8..51489151 100644 --- a/WhateverXe/kern_display13_output_wiring.hpp +++ b/WhateverXe/kern_display13_output_wiring.hpp @@ -6,7 +6,9 @@ struct Display13OutputWiring { static constexpr unsigned Ports=9; static constexpr uint32_t Physical=0x7B; // A/B + TC1..4, no Combo C. - uint32_t hdmi {},ddc[Ports] {},dp {},panel {},typeC {};bool valid {}; + // panel is eDP. DSI uses the donor's panel object shape, never its AUX, + // training or HPD protocol. Initially only DSI0 on physical PHY A exists. + uint32_t hdmi {},ddc[Ports] {},dp {},panel {},typeC {},dsi {};bool valid {}; Display13OutputWiring()=default; Display13OutputWiring(const TGLHDMIWiring &w):hdmi(w.hdmi),valid(w.valid) { for(unsigned d=0;d<3;++d)ddc[d]=w.ddc[d]; @@ -15,10 +17,20 @@ struct Display13OutputWiring { static Display13OutputWiring fromProperty(const void *data,uint32_t bytes) { return TGLHDMIWiring::fromProperty(data,bytes); } - uint32_t selected() const {return hdmi|dp|panel;} + uint32_t selected() const {return hdmi|dp|panel|dsi;} uint32_t links() const {return dp|panel;} + uint32_t internal() const {return panel|dsi;} + uint32_t hotplug() const {return selected()&~dsi;} + enum class Transport {None,TMDS,DP,EDP,DSI}; + Transport transport(unsigned ddi) const { + if(!described() || ddi>=Ports)return Transport::None; + const auto bit=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; @@ -69,7 +81,7 @@ struct Display13OutputWiring { c[1]=ddc[d];c[2]=2;c[4]=0x800;c[5]=panel?0x202:(c[5]&~9U)|2U; } if(!TGLComboDP::apply(dp&3,platform,bytes))return false; - for(unsigned d=0;d<2;++d)if(panel&(1U< +namespace Display13Transcoder { +struct Bindings { + uint32_t dsi0Pipe {UINT32_MAX}; + bool valid() const {return dsi0Pipe==UINT32_MAX || dsi0Pipe<3;} + bool dsi(unsigned pipe) const {return pipe<3 && pipe==dsi0Pipe;} + uint32_t control(unsigned pipe) const {return dsi(pipe)?0x7B008U:0x70008U+0x1000*pipe;} + uint32_t function(unsigned pipe) const {return dsi(pipe)?0x6B400U:0x60400U+0x1000*pipe;} + uint32_t timing(unsigned pipe) const {return dsi(pipe)?0x6B000U:0x60000U+0x1000*pipe;} +}; +// Caller proves the pipe and DSI bank's power lifetime. Normal PIPECONF must +// remain disabled for an owned DSI route. Check the live input selector as +// well as the binding: a stale CPU mapping must not qualify another pipe. +// A partial enable/active pair is returned intact for stop/wait consumers; +// callers requiring running scanout must require both bits. +template uint32_t control(IO &io,unsigned pipe,const Bindings &bindings={}) { + if(pipe>=3 || !bindings.valid())return UINT32_MAX; + const auto normal=io.read(0x70008+0x1000*pipe); + if(!bindings.dsi(pipe) || normal==UINT32_MAX)return normal; + if(normal&0xC0000000U)return UINT32_MAX; + const auto conf=io.read(0x7B008),function=io.read(0x6B400); + const uint32_t inputs[]={0,5,6}; + if(conf==UINT32_MAX || function==UINT32_MAX || ((function>>12)&7)!=inputs[pipe] || + ((conf&0xC0000000U) && !(function&0x80000000U)))return UINT32_MAX; + return conf; +} +template bool running(IO &io,unsigned pipe,const Bindings &bindings={}) { + const auto conf=control(io,pipe,bindings); + const auto mask=bindings.dsi(pipe)?0xC0000000U:0x80000000U; + return conf!=UINT32_MAX && (conf&mask)==mask; +} +} +#endif diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index 01d84feb..05c94101 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -240,6 +240,7 @@ struct Snapshot { uint32_t pipeLineTime {}; uint64_t pipePixelHz {}; unsigned pipeIndex {}; // Display pipe, not physical HDMI DDI or framebuffer index. + Display13Transcoder::Bindings transcoders; }; // FlushTriggerPackets writes these native packets in append order. A flip // queued after WM enable may supersede the surface sampled at enable time. @@ -321,17 +322,18 @@ template bool verifyWM(IO &io,Latch &r,const Plan &p,bool cursor,bool // nor the workloop, so hardware can advance while delayUs sleeps. template Latch latch(IO &io,const Snapshot &s) { Latch r; - if(s.pipeIndex>=3) {r.status=Status::Input;return r;} + if(s.pipeIndex>=3 || !s.transcoders.valid()) {r.status=Status::Input;return r;} const uint32_t bank=s.pipeIndex*0x1000; + const auto timing=s.transcoders.timing(s.pipeIndex); if(!s.primary || s.primary->status!=Status::Complete || !s.primary->normal[0].enable || !s.primary->sagv.enable || !clockFits(s,s.nativeClockHz) || !s.pipeLineTime || s.pipeLineTime>511 || (s.cursorActive && (!s.cursor || s.cursor->status!=Status::Complete || !s.cursor->sagv.enable)))return r; - const uint32_t required[] {0x45404,0x70008+bank,0x70180+bank,0x70080+bank,0x70190+bank,0x60000+bank,0x6000C+bank,0x70040+bank}; + const uint32_t required[] {0x45404,0x70008+bank,0x70180+bank,0x70080+bank,0x70190+bank,timing,timing+0xC,0x70040+bank}; for(auto reg:required) if(!io.readable(reg))return r; const auto wells=io.read(0x45404); if(!Display13FBIRQ::powered(wells,s.pipeIndex)) {r.status=Status::Power;return r;} - const auto ht=io.read(0x60000+bank),vt=io.read(0x6000C+bank); + const auto ht=io.read(timing),vt=io.read(timing+0xC); const auto htotal=((ht>>16)&0x3FFF)+1,vtotal=((vt>>16)&0x3FFF)+1; if(ht==UINT32_MAX || vt==UINT32_MAX || htotal!=s.primary->htotal || !s.primary->height || s.primary->height>8192 || vtotalheight || !s.primary->pixelHz) {r.status=Status::Input;return r;} const auto period=ceilDiv(uint64_t(htotal)*vtotal*1000000,s.pipePixelHz); @@ -343,7 +345,7 @@ template Latch latch(IO &io,const Snapshot &s) { for(;;) { const auto power=io.read(0x45404); if(!Display13FBIRQ::powered(power,s.pipeIndex)) {r.status=Status::Power;return r;} - const auto pipe=io.read(0x70008+bank),plane=io.read(0x70180+bank),cursor=io.read(0x70080+bank); + const auto pipe=Display13Transcoder::control(io,s.pipeIndex,s.transcoders),plane=io.read(0x70180+bank),cursor=io.read(0x70080+bank); r.check=LatchCheck::Controls;r.pipeControl=pipe;r.planeControl=plane;r.cursorControl=cursor; // Native enablePlane/scaler can flush WM before enabling scanout. // Reconnect may retain a nonzero SURF from the previous path, so a @@ -374,7 +376,7 @@ template Latch latch(IO &io,const Snapshot &s) { if(r.clockHz!=s.nativeClockHz) {r.status=Status::Conflict;return r;} if(preparing) {r.check=LatchCheck::AwaitingScanout;return r;} r.check=LatchCheck::Registers; - if(!compare(io,r,0x60000+bank,ht) || !compare(io,r,0x6000C+bank,vt) || !compare(io,r,0x45270+4*s.pipeIndex,s.pipeLineTime) || + if(!compare(io,r,timing,ht) || !compare(io,r,timing+0xC,vt) || !compare(io,r,0x45270+4*s.pipeIndex,s.pipeLineTime) || !compare(io,r,0x70190+bank,((s.primary->height-1)<<16)|(s.primary->width-1)) || !compare(io,r,0x7027C+bank,s.primaryDDB) || !compare(io,r,0x7017C+bank,s.cursorDDB) || !compare(io,r,0x45004,s.ipc) || !compare(io,r,0x7019C+bank,s.primarySurface) || diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 0b65b918..4dcd2297 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4534,6 +4534,9 @@ struct Display13FBPipeContext { uint64_t watermarkClockHz {}; }; struct Display13FBPowerContext { + // Publish with IRQ state only under a quiesced adoption/retirement boundary. + // No DSI binding is installed until the complete adoption backend exists. + Display13Transcoder::Bindings transcoders; IOSimpleLock *tcBankLock {}; bool tcSupported {},tcBankFaulted {}; Display13TC::Mode tcMode[4]; @@ -5050,7 +5053,7 @@ static bool adlnBeginClockGuard(Display13FBClockGuard &g) { if(quiet)break; if(ms<20)IOSleep(1); } - quiet=quiet && !(io.read(Display13FBIRQ::Master)&Display13FBIRQ::Enable) && Display13FBIRQ::consumersStopped(io,7); + quiet=quiet && !(io.read(Display13FBIRQ::Master)&Display13FBIRQ::Enable) && Display13FBIRQ::consumersStopped(io,7,c.transcoders); if(!quiet) {failure(3);adlnEndClockGuard(g,true);} return quiet; } @@ -5930,11 +5933,11 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { for(unsigned i=0;i<4;++i)check[19+i]=io.read(otherBanks[i]+4)&~io.read(otherBanks[i]-4); if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); - const auto actual=Display13FBIRQ::readActivePipes(io,admitted); + const auto actual=Display13FBIRQ::readActivePipes(io,admitted,c.state.transcoders); c.status=c.quiescing ? Display13FBIRQ::Status::Power : actual.status; if(c.status==Display13FBIRQ::Status::Complete)c.status=c.state.begun && c.state.enabled ? - Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnOutputWiring.selected(),callbackXE->adlnOutputWiring.typeC); - } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected(),callbackXE->adlnOutputWiring.typeC); + Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnOutputWiring.hotplug(),callbackXE->adlnOutputWiring.typeC); + } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.hotplug(),callbackXE->adlnOutputWiring.typeC); adlnIRQCache(controller,io);adlnIRQRecord(record,1); check[23]=static_cast(c.status);check[24]=c.state.begun; check[25]=c.state.enabled;check[26]=c.state.faulted;check[27]=c.state.active;check[28]=c.state.configured; @@ -5983,10 +5986,10 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec // Native enablePipe enables PIPECONF before this VBL callback. Re-arm // the full pipe bank only at that real activation boundary after idle HPD. if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && enabled && c.state.begun && c.state.enabled && !c.state.active && !c.state.faulted && - Display13FBIRQ::powered(io.read(Display13FBIRQ::Driver),pipe) && (io.read(0x70008+0x1000*pipe)&Display13FBIRQ::Enable)) { + Display13FBIRQ::powered(io.read(Display13FBIRQ::Driver),pipe) && Display13Transcoder::running(io,pipe,c.state.transcoders)) { const auto excluded=controller==callbackXE->adlnTopologyController ? Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; - c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected(),callbackXE->adlnOutputWiring.typeC); + c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.hotplug(),callbackXE->adlnOutputWiring.typeC); } if(!c.state.faulted)c.status=Display13FBIRQ::vblank(io,c.state,pipe,enabled); } @@ -6255,7 +6258,7 @@ void XE::adlnSysMemOperation(IOService *controller,uint32_t slot,bool release) { bool consumersStopped(unsigned,const Display13FBMemory::Pool &) { if(pipe>=3 || display13FBPower.pipe[pipe].wmProgramming || display13FBPower.pipe[pipe].wmCommitting)return false; Display13FBIRQIO io {display13FBGTT.map->getVirtualAddress()}; - return Display13FBIRQ::consumersStopped(io,1U<getPhysicalSegment(offset,&length,kIOMemoryMapperNone);span=length;return value; @@ -8421,7 +8424,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { swap[5]=transition.aperture;swap[6]=transition.replace;swap[7]=stolen; if(transition.replace && !stolen) { Display13FBIRQIO io {c.map->getVirtualAddress()}; - const bool stopped=Display13FBIRQ::consumersStopped(io,1U<(fb); const bool bound=c.workLoop && Display13FBMemory::field(controller,0xE00)==c.workLoop; @@ -8457,7 +8460,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { IOLockLock(c.lock); const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); swap[15]=static_cast(current.status); - Display13FBIRQIO io {c.map->getVirtualAddress()};swap[16]=Display13FBIRQ::consumersStopped(io,1U<getVirtualAddress()};swap[16]=Display13FBIRQ::consumersStopped(io,1U<(fb)==stopPath && swap[16] && !c.bridge.state.faulted && !c.clockFaulted && !c.memoryFaulted; @@ -9275,7 +9278,7 @@ void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObje void write(uint32_t r,uint32_t v) {if(r==0x70080+bank || r==0x70088+bank || r==0x70084+bank)*reinterpret_cast(map->getVirtualAddress()+r)=v;} void delayUs(unsigned us) {IOSleep(us/1000);} } io {c.map,pipe*0x1000}; - result=Display13Cursor::stop(io,pipe); + result=Display13Cursor::stop(io,pipe,c.transcoders); if(result.status==Display13Cursor::Status::Complete) { // Keep the donor's visible/control cache consistent with the stopped cursor. *reinterpret_cast(reinterpret_cast(path)+0x170)=0; @@ -9416,7 +9419,7 @@ Display13WM::Latch XE::adlnVerifyWMLatch(IOService *fb,uint64_t nativeHz,uint64_ result.status=Display13WM::Status::Conflict;return result; } const bool cursor=readout.read(0x70080+bank)&63; - const auto conf=readout.read(0x70008+bank); + const auto conf=Display13Transcoder::control(readout,pipe,c.transcoders); if(conf==UINT32_MAX) {result.status=Display13WM::Status::Readback;return result;} // Cursor setup may follow primary arm. Keep old slices and pending bits // until every active consumer has actually been programmed. @@ -9438,7 +9441,7 @@ Display13WM::Latch XE::adlnVerifyWMLatch(IOService *fb,uint64_t nativeHz,uint64_ const Display13WM::Snapshot expected { &c.pipe[pipe].wm[0],&c.pipe[pipe].wm[1],primaryRaw,cursorRaw, c.pipe[pipe].wmSurface[0],c.pipe[pipe].wmSurface[1],readout.read(0x45004),nativeHz,c.pipe[pipe].wm0Only[0],cursor, - Display13WM::pipeLineTime(c.pipe[pipe].wm[0].htotal,pipePixel),pipePixel,pipe + Display13WM::pipeLineTime(c.pipe[pipe].wm[0].htotal,pipePixel),pipePixel,pipe,c.transcoders }; if(!Display13WM::clockFits(expected,targetHz)) {result.status=Display13WM::Status::Input;return result;} return Display13WM::latch(io,expected); diff --git a/tests/display13_cursor.cpp b/tests/display13_cursor.cpp index eb337245..d18b6bfc 100644 --- a/tests/display13_cursor.cpp +++ b/tests/display13_cursor.cpp @@ -35,6 +35,14 @@ int main() { IO a;auto r=stop(a);assert(r.status==Status::Complete && r.waitUs==17000 && r.frameAfter==11); assert((a.writes==std::vector{0x70080,0x70088,0x70084}));assert(!a.regs[0x70084]); IO off;off.regs[0x70008]=0;r=stop(off);assert(r.status==Status::Complete && !r.waitUs && r.writes==3); + // PIPECONF=0 on DSI is not evidence of stopped scanout. Cursor retirement + // still needs the separately bound pipe's frame to advance. + {IO io;io.regs[0x70008]=0;io.regs[0x7B008]=0xC0000000;io.regs[0x6B400]=0x80000006; + r=stop(io,0,{0});assert(r.status==Status::Complete && r.waitUs==17000); + IO frozen;frozen.regs=io.regs;frozen.regs[0x70080]=0x27;frozen.frozen=true; + assert(stop(frozen,0,{0}).status==Status::Timeout); + IO wrong;wrong.regs[0x70008]=0;wrong.regs[0x7B008]=0xC0000000;wrong.regs[0x6B400]=0x80005006; + assert(stop(wrong,0,{0}).status==Status::Readback && wrong.writes.empty());} IO empty;empty.regs[0x70080]=0;r=stop(empty);assert(r.status==Status::Complete && empty.writes.empty()); IO busy;busy.regs[0x70C08]=1;r=stop(busy);assert(r.status==Status::Conflict && busy.writes.empty()); IO power;power.regs[0x45404]=3;r=stop(power);assert(r.status==Status::Power && power.writes.empty()); diff --git a/tests/display13_fb_irq.cpp b/tests/display13_fb_irq.cpp index 32684806..8be43e78 100644 --- a/tests/display13_fb_irq.cpp +++ b/tests/display13_fb_irq.cpp @@ -48,6 +48,37 @@ struct IO { } }; int main() { + // DSI0 can feed A/B/C. IRQ/frame/plane banks follow the pipe, while + // control/function come from DSI0. Keep external A active for B/C cases. + for(unsigned pipe=0;pipe<3;++pipe) { + IO io;State s;s.transcoders.dsi0Pipe=pipe; + io.r[0x70008+0x1000*pipe]=0;io.r[0x7B008]=0xC0000000; + const uint32_t inputs[]={0,5,6};io.r[0x6B400]=0x80000006|(inputs[pipe]<<12); + const auto expected=(1U< Date: Thu, 29 Oct 2026 22:08:58 +0900 Subject: [PATCH 020/139] Validate DSI GOP backing and retain its shared PLL --- WhateverXe/kern_display13_fb_memory.hpp | 29 ++++++++++++++------ WhateverXe/kern_dsi_readout.hpp | 28 ++++++++++++++++++++ WhateverXe/kern_xe.cpp | 28 +++++++++++++++++--- WhateverXe/kern_xelp_combo_clock.hpp | 29 +++++++++++++++++--- tests/adls_clock_runtime.py | 25 +++++++++++++++++- tests/display13_fb_memory.cpp | 35 +++++++++++++++++++++++++ tests/dsi_readout.cpp | 13 +++++++++ tests/framebuffer_common.py | 11 ++++++-- tests/xelp_combo_clock.cpp | 10 +++++++ 9 files changed, 191 insertions(+), 17 deletions(-) diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index c4b9ca44..4f0dee98 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -3,6 +3,7 @@ // GGTT entries and is not an accelerator memory manager. #ifndef KERN_DISPLAY13_FB_MEMORY_HPP #define KERN_DISPLAY13_FB_MEMORY_HPP +#include "kern_dsi_readout.hpp" #include #include "kern_xelp_memory_layout.hpp" @@ -329,11 +330,17 @@ template 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,bool panel=false) { + const AlderLakeN::DisplayReadout *dp=nullptr,bool panel=false, + const XELP::DSI::Readout *dsi=nullptr) { Readout r; if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) { r.status=Status::Association;return r; } + // The first DSI GOP handoff is pipe A/PHY A. A validated description is + // not backing ownership: revalidate the leased source on both sides of + // the PTE walk below. Never combine it with DP/AUX/TC source semantics. + if(dsi && (!bootScanout || expectedDDI || dp || tc || panel || dsi->identity.pipe!=0 || + !XELP::DSI::sourceUnchanged(io,*dsi))) {r.status=Status::DisplayState;return r;} if(bootScanout && (expectedDDI>=7 || (expectedDDI>=3 && (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 || !(tc->buffer&64) || (!dp && tc->ownershipOptional) || !tc->unchanged(io))))) { @@ -358,8 +365,10 @@ template Readout inspect(IO &io,const void *controller,size_t controll !io.readable(0x800000+8*(p.firstPage+p.pages)-1)) { r.status=Status::PoolRange;return r; } - // Read only the powered boot pipe. Linear XRGB8888 FHD uses byte GGTT - // offsets, with Display 13's DPT disabled. No fence register is required. + // Read only the powered boot pipe. Linear RGB uses byte GGTT offsets. + // Linux intel_fb_modifier_uses_dpt excludes LINEAR even when the global + // DPT-disable bit is clear, as in the DSI GOP. Keep the established ordinary + // output contract; do not toggle the global bit to inspect DSI backing. constexpr uint32_t regs[]={0x45404,0x42084,0x60400,0x70008,0x70180,0x70188, 0x7018C,0x70190,0x701A4,0x7019C,0x701AC}; if(bootScanout)for(auto reg:regs)if(!io.readable(reg))return r; @@ -367,13 +376,16 @@ template Readout inspect(IO &io,const void *controller,size_t controll const auto wells=io.read(0x45404); if(wells==UINT32_MAX || (wells&(bootScanout ? 0x403U : 3U))!=(bootScanout ? 0x403U : 3U)) {r.status=Status::DisplayState;return r;} uint32_t dpt=0,trans=0,conf=0,position=0,offset=0; + const uint32_t transReg=dsi?0x6B400U:0x60400U,confReg=dsi?0x7B008U:0x70008U; if(bootScanout) { - dpt=io.read(0x42084);trans=io.read(0x60400);conf=io.read(0x70008); + dpt=io.read(0x42084);trans=io.read(transReg);conf=io.read(confReg); position=io.read(0x7018C);offset=io.read(0x701A4); r.surface=io.read(0x7019C);r.control=io.read(0x70180);r.stride=io.read(0x70188);r.size=io.read(0x70190); const auto geometry=XELPGeometry::linear(r.control,r.stride,r.size,position,offset); - if (!(dpt&(1U<<30)) || !(trans&(1U<<31)) || ((trans>>27)&15)!=expectedDDI+1 || - (dp ? ((trans>>24)&7)!=2 : ((trans>>24)&7)>1) || ((trans>>20)&7) || (conf&0xC0000000)!=0xC0000000 || + const bool source=dsi ? !(io.read(0x60400)&(1U<<31)) && XELPGeometry::timing(geometry,dsi->htotal,dsi->vtotal) : + (dpt&(1U<<30)) && ((trans>>27)&15)==expectedDDI+1 && + (dp ? ((trans>>24)&7)==2 : ((trans>>24)&7)<=1) && !((trans>>20)&7); + if (dpt==UINT32_MAX || !source || !(trans&(1U<<31)) || (conf&0xC0000000)!=0xC0000000 || !geometry.valid || position || offset || (r.surface&4095) || r.surface!=io.read(0x701AC) || r.surface Readout inspect(IO &io,const void *controller,size_t controll if(finalWells==UINT32_MAX || (finalWells&(bootScanout ? 0x403U : 3U))!=(bootScanout ? 0x403U : 3U) || !same(p,pool(controller)) || !associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner) || field(framebuffer,0x4288)!=0 || !(field(controller,0xDF0)&1) || - (bootScanout && (io.read(0x42084)!=dpt || io.read(0x60400)!=trans || io.read(0x70008)!=conf || + (bootScanout && (io.read(0x42084)!=dpt || io.read(transReg)!=trans || io.read(confReg)!=conf || io.read(0x7018C)!=position || io.read(0x701A4)!=offset || 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,panel))))) { + (dp && !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc,panel)) || + (dsi && (!XELP::DSI::sourceUnchanged(io,*dsi) || (io.read(0x60400)&(1U<<31))))))) { r.status=Status::StateChanged;return r; } r.status=Status::Complete;return r; diff --git a/WhateverXe/kern_dsi_readout.hpp b/WhateverXe/kern_dsi_readout.hpp index 0b00d6b9..8a077b57 100644 --- a/WhateverXe/kern_dsi_readout.hpp +++ b/WhateverXe/kern_dsi_readout.hpp @@ -19,6 +19,15 @@ struct Readout { bool complete() const {return status==ReadoutStatus::Complete;} uint32_t activePipes() const {return complete()?1U< Readout decodeReadout(Get get,uint32_t referenceKHz) { if(!ordered(r.htotal,r.hsync) || !ordered(r.vtotal,r.vsync))return r; r.status=ReadoutStatus::Complete;return r; } +// Revalidate an already owned source before/after a slow backing proof. The +// caller holds the DSI/pipe power leases and serializes mode changes. Checking +// the wells first avoids a DSI read after a visible power loss; this does not +// itself acquire leases or make unsynchronized firmware MMIO atomic. +template bool sourceUnchanged(IO &io,const Readout &expected) { + if(!expected.complete() || !expected.identity.valid() || !io.readable(0x45404) || !io.readable(0x51004))return false; + const auto powered=[&]() { + const auto wells=io.read(0x45404);const auto pipe=expected.identity.pipe; + return wells!=UINT32_MAX && (wells&1) && (wells&(1U<<(10+2*pipe))) && (!pipe || (wells&4)); + }; + if(!powered())return false; + const auto dssm=io.read(0x51004);const auto index=(dssm>>29)&7; + const uint32_t reference[]={24000,19200,38400}; + if(index>=3 || reference[index]!=expected.referenceKHz)return false; + const auto current=decodeReadout([&](uint32_t reg,uint32_t &v) { + if(!io.readable(reg))return false;v=io.read(reg);return true; + },reference[index]); + return expected.sameSource(current) && powered(); +} }} #endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4dcd2297..4b6d5529 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4537,6 +4537,7 @@ struct Display13FBPowerContext { // Publish with IRQ state only under a quiesced adoption/retirement boundary. // No DSI binding is installed until the complete adoption backend exists. Display13Transcoder::Bindings transcoders; + XELP::DSI::Readout bootDSI; // Captured before handoff; not an implicit admission flag. IOSimpleLock *tcBankLock {}; bool tcSupported {},tcBankFaulted {}; Display13TC::Mode tcMode[4]; @@ -5192,7 +5193,7 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo return Display13FBMemory::inspect(io,controller,controller->getMetaClass()->getClassSize(), fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort, c.bridge.bootPort>=3 ? &tc : nullptr,dp?&source:nullptr, - c.bootPanel); + c.bootPanel,bootScanout && c.transcoders.dsi(0) ? &c.bootDSI : nullptr); } } @@ -11680,8 +11681,13 @@ bool XE::wrapFramebufferStart(IOService *that,IOService *provider) { IORecursiveLockUnlock(s->tglPathPoolLock); if(comboClockTransaction.lock) { IORecursiveLockLock(comboClockTransaction.lock); - comboClockTransaction.allocation={}; + const bool reset=comboClockTransaction.allocation.reset(); IORecursiveLockUnlock(comboClockTransaction.lock); + if(!reset) { + IORecursiveLockLock(s->tglPathPoolLock);s->framebufferStartInProgress=false;IORecursiveLockUnlock(s->tglPathPoolLock); + if(that)that->setProperty("WEXFramebufferRetainedClockOwner",true); + return false; + } } const bool native=s->display13FramebufferRequested ? wrapDisplay13FramebufferStart(that,provider) : wrapTGLFramebufferStart(that,provider); IORecursiveLockLock(s->tglPathPoolLock); @@ -18207,10 +18213,26 @@ bool XE::comboBeginClock(void *controller,void *port,bool enable,bool &keepPLL,v snapshot.mux[0]=comboClockRead(access,0x164280); if(s->adlsDisplaySelected)snapshot.mux[1]=comboClockRead(access,0x1642BC); for(unsigned i=0;idisplay13FramebufferRequested && display13FBPower.transcoders.dsi0Pipe<3) { + // The adoption boundary installs the qualified source/power binding + // before any native clock client. Revalidate here under the shared + // clock mutex, then pin its PLL even when DDI_CLK_OFF_A is set. + const auto &source=display13FBPower.bootDSI; + struct IO { + void *access; + bool readable(uint32_t r) {return r(access,0x60);} + uint32_t read(uint32_t r) {return XE::comboClockRead(access,r);} + } io {access}; + if(source.identity.pipe!=display13FBPower.transcoders.dsi0Pipe || + !XELP::DSI::sourceUnchanged(io,source) || + !t.allocation.retain(source.identity.phy,source.identity.pll,reinterpret_cast(controller),snapshot)) { + comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; + } + } const auto otherConfig=other && other!=port ? *reinterpret_cast(static_cast(other)+0x548):nullptr; const auto choice=enable && otherConfig ? t.allocation.share(XELPComboClock::Port(otherConfig[0]),snapshot): t.allocation.choose(route,snapshot,enable); - if(!choice.valid) { + if(!choice.valid || route.phy>=snapshot.slots() || t.allocation.external[route.phy]) { comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; } if(enable)t.allocation.binding[route.phy]=choice.pll+1; diff --git a/WhateverXe/kern_xelp_combo_clock.hpp b/WhateverXe/kern_xelp_combo_clock.hpp index 0ee42e43..05042673 100644 --- a/WhateverXe/kern_xelp_combo_clock.hpp +++ b/WhateverXe/kern_xelp_combo_clock.hpp @@ -53,16 +53,39 @@ struct ClockChoice { }; struct ClockAllocation { template ClockChoice share(const P &source,const ClockSnapshot &s) const { - if(source.phy>=s.slots() || !s.valid() || !s.active(source))return {}; + if(source.phy>=s.slots() || external[source.phy] || !s.valid() || !s.active(source))return {}; const auto pll=s.selected(source); return pll=s.slots() || pll>=s.pllCount() || !s.valid() || + s.selected(Port(phy))!=pll || (s.enable[pll]&0xCC000000U)!=0xCC000000U)return false; + if(external[phy])return external[phy]==owner && binding[phy]==pll+1; + if(binding[phy] || (s.peers(Port(phy))&(1U<(pll+1);return true; + } + // Caller must first stop/detach this link and drain its consumers under + // the same serialized ownership scope. A closed DDI gate is insufficient. + bool release(unsigned phy,uintptr_t owner) { + if(phy>=3 || !owner || external[phy]!=owner)return false; + external[phy]=0;binding[phy]=0;return true; + } template ClockChoice choose(const P &p,const ClockSnapshot &s,bool on) const { - if(p.phy>=s.slots() || !s.valid())return {}; + if(p.phy>=s.slots() || external[p.phy] || !s.valid())return {}; auto peers=s.peers(p); for(unsigned phy=0;phy<3;++phy) if(phy!=p.phy && binding[phy])peers|=1U<<(binding[phy]-1); diff --git a/tests/adls_clock_runtime.py b/tests/adls_clock_runtime.py index 72ea1b08..d2f7e4c6 100644 --- a/tests/adls_clock_runtime.py +++ b/tests/adls_clock_runtime.py @@ -8,6 +8,8 @@ functions='\n'.join(re.search(r'(?:bool|void) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['comboBeginClock','comboEndClock']) code=r''' #include "kern_adls_clock.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_transcoder.hpp" #include #include #include @@ -18,6 +20,7 @@ void *current_thread(){return reinterpret_cast(thread);} unsigned refs=0,getCount=0,putCount=0,reads=0; std::map mmio; +struct {Display13Transcoder::Bindings transcoders;XELP::DSI::Readout bootDSI;} display13FBPower; alignas(8) uint8_t ctl[0xf00]{},port[0x700]{},access[0x80]{},power[0x100]{}; uint32_t config[6]{}; templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} @@ -83,7 +86,27 @@ XE::comboEndClock(); assert(!refs && !lock.depth); } - puts("PASS production common Combo clock scope: core power, nested rejection, thread/access isolation, failed-enable reservation and balanced teardown"); + // Production transaction discovers/pins DSI's PLL1 before selecting B's + // clock, even with both DDI gates closed. No donor call may own DSI PHY A. + comboClockTransaction.allocation={};config[0]=1;at(access,0x60)=0x1000000; + display13FBPower.transcoders.dsi0Pipe=0; + mmio={{0x45404,0xFC0F},{0x51004,0x40000000},{0x164280,0x1E07C01},{0x46010,0},{0x46014,0xCC000000}, + {0x7B008,0xC0000000},{0x6B400,0x80030006},{0x70008,0},{0x6B030,0x20130300},{0x6B094,1}, + {0x6B404,0},{0x6B034,0},{0x64000,0x80000000},{0x16428C,0x3001FB},{0x164290,0xE84}, + {0x6B000,0x04650437},{0x6B008,0x044F044D},{0x6B00C,0x07D1077F},{0x6B014,0x07A907A7}}; + display13FBPower.bootDSI=XELP::DSI::decodeReadout([&](uint32_t r,uint32_t &v){assert(mmio.count(r));v=mmio.at(r);return true;},38400); + assert(XE::comboBeginClock(ctl,port,true,keep) && comboClockTransaction.translation.pll==0); + XE::comboEndClock();assert(!refs && !lock.depth); + assert(comboClockTransaction.allocation.external[0]==reinterpret_cast(ctl)); + assert(!comboClockTransaction.allocation.reset()); + config[0]=0;assert(!XE::comboBeginClock(ctl,port,true,keep) && !XE::comboBeginClock(ctl,port,false,keep)); + alignas(8) uint8_t otherPort[0x700]{};uint32_t otherConfig[6]{1};at(otherPort,0x548)=otherConfig; + mmio[0x164280]&=~0x80CU;mmio[0x46010]=0xCC000000; + assert(!XE::comboBeginClock(ctl,port,true,keep,otherPort)); // sharing also cannot overwrite DSI's target + assert(comboClockTransaction.allocation.binding[0]==2); + config[0]=1;mmio[0x16428C]^=0x400;assert(!XE::comboBeginClock(ctl,port,true,keep)); + assert(!refs && !lock.depth && getCount==putCount); + puts("PASS production common Combo clock scope: balanced power, failed-enable retention, DSI source revalidation and external PLL ownership"); } '''.replace('STATE',state).replace('FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='adls-clock-runtime-') as d: diff --git a/tests/display13_fb_memory.cpp b/tests/display13_fb_memory.cpp index 3643fdde..6be91486 100644 --- a/tests/display13_fb_memory.cpp +++ b/tests/display13_fb_memory.cpp @@ -39,6 +39,40 @@ struct Fixture { } Readout inspect(unsigned ddi=1,const Display13TC::Boot *tc=nullptr) {return Display13FBMemory::inspect(*this,controller,sizeof(controller),framebuffer,sizeof(framebuffer),0x10000000,memory,true,0,ddi,tc);} }; +static void testDSIBacking() { + const auto setup=[](Fixture &f) { + const std::map source { + {0x7B008,0xC0000000},{0x6B400,0x80030006},{0x70008,0},{0x60400,0}, + {0x6B030,0x20130300},{0x6B094,1},{0x6B404,0},{0x6B034,0},{0x64000,0x80000000}, + {0x164280,0x1E07801},{0x46014,0xCC000000},{0x16428C,0x3001FB},{0x164290,0xE84}, + {0x6B000,0x04650437},{0x6B008,0x044F044D},{0x6B00C,0x07D1077F},{0x6B014,0x07A907A7}, + {0x51004,0x40000000},{0x42084,0},{0x70188,68},{0x70190,0x077F0437}}; + for(auto entry:source)f.registers[entry.first]=entry.second; + return XELP::DSI::decodeReadout([&](uint32_t reg,uint32_t &v) { + auto i=f.registers.find(reg);if(i==f.registers.end())return false;v=i->second;return true; + },38400); + }; + const auto inspect=[](Fixture &f,const XELP::DSI::Readout &source) { + return Display13FBMemory::inspect(f,f.controller,sizeof(f.controller),f.framebuffer,sizeof(f.framebuffer), + 0x10000000,memory,true,0,0,nullptr,nullptr,false,&source); + }; + Fixture good;const auto source=setup(good);assert(source.complete()); + auto result=inspect(good,source);assert(result.status==Status::Complete && result.verifiedPages==good.ptes.size()); + // The GOP has global DPT enabled, but its linear plane still addresses GGTT. + // No acceptance of tiled/rotated geometry or a shortened pool follows. + for(auto pair:std::map{{0x70180,0x94000408},{0x70188,67}, + {0x70190,0x0437077F},{0x42084,UINT32_MAX},{0x60400,0x80000000}}) { + Fixture bad;setup(bad);bad.registers[pair.first]=pair.second; + assert(inspect(bad,source).status==Status::DisplayState && !bad.reads); + } + for(auto reg:{0x7B008U,0x6B400U,0x6B000U,0x16428CU,0x51004U}) { + Fixture changed;setup(changed);changed.changeRegister=reg; + assert(inspect(changed,source).status==Status::StateChanged); + } + Fixture pte;setup(pte);pte.ptes[7]^=2;assert(inspect(pte,source).status==Status::PTE); + Fixture lost;setup(lost);lost.deadPower=true;assert(inspect(lost,source).status==Status::StateChanged); + auto wrong=source;wrong.identity.pipe=1;assert(inspect(good,wrong).status==Status::DisplayState); +} static void testRelocatedPools() { for(auto ggc : {0xFEC1U,0x02C1U,0x03C1U,0x04C1U}) { Fixture f;const auto moved=XELPMemory::decode(ggc,0x91000001,1);assert(moved.valid); @@ -75,6 +109,7 @@ static void testSlotClaims() { } int main() { + testDSIBacking(); for(unsigned ddi=3;ddi<=6;++ddi) { Fixture f;const auto tc=ddi-3; f.registers[0x45454]=3U<<(2*ddi);f.registers[0x161500+4*tc]=0xC; diff --git a/tests/dsi_readout.cpp b/tests/dsi_readout.cpp index 042cf611..3d7223e5 100644 --- a/tests/dsi_readout.cpp +++ b/tests/dsi_readout.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace XELP::DSI; using Registers=std::map; Readout decode(const Registers ®s,uint32_t ref=38400) { @@ -19,6 +20,18 @@ int main(int argc,char **argv) { auto regs=firmware();auto r=decode(regs); assert(r.complete()&&r.identity.valid()&&r.identity.pipe==0&&r.identity.transcoder==0&&r.identity.phy==0&&r.identity.pll==1); assert(r.normalConf==0&&r.activePipes()==1&&r.afeKHz==811500); + struct IO { + Registers regs;std::vector reads; + bool readable(uint32_t reg){return regs.count(reg);} + uint32_t read(uint32_t reg){reads.push_back(reg);return regs.at(reg);} + } io{regs,{}}; + io.regs[0x51004]=0x40000000; + assert(sourceUnchanged(io,r)); + io.regs[0x164280]^=0x808;assert(sourceUnchanged(io,r)); // peer B's PLL/gate only + io.regs[0x164280]^=1;assert(!sourceUnchanged(io,r)); + io.regs[0x164280]=r.mux;io.regs[0x51004]=0;assert(!sourceUnchanged(io,r)); + io.regs[0x51004]=0x40000000;io.regs[0x45404]=0;io.reads.clear(); + assert(!sourceUnchanged(io,r) && io.reads==std::vector{0x45404}); Profile profile;profile.status=ProfileStatus::Valid;profile.lanes=4; profile.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; assert(r.matches(profile)&&!r.matches(profile,1)); diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index 38c9a3b3..b7d06d06 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -97,17 +97,24 @@ if(argc>1)assert(!memcmp(image.data()+span.first,span.second.data(),span.second.size())); else memcpy(image.data()+span.first,span.second.data(),span.second.size()); } - for(bool display13:{false,true})for(unsigned failure=0;failure<13;++failure){ + for(bool display13:{false,true})for(unsigned failure=0;failure<14;++failure){ auto b=image;uintptr_t pure=0x1234;memcpy(b.data()+0xccad0+0x188,&pure,sizeof(pure)); XE x;callbackXE=&x;x.display13FramebufferRequested=display13;KernelPatcher p; surfaceOK=failure!=5;nvramOK=failure!=6;x.runtimeOK=failure!=7;x.agdcOK=failure!=8;x.backendOK=failure!=9;x.trainingOK=failure!=10;x.stopDuringStart=failure==11;x.nativeOK=failure!=12; if(failure>=1&&failure<=4)p.failAt=failure; bool handled=x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); assert(handled==(failure!=1)); - IOService c;assert(XE::wrapFramebufferStart(&c,&c)==(failure==0)); + IOService c; + if(failure==13){XELPComboClock::ClockSnapshot clocks;clocks.layout=XELPComboClock::Layout::Display13; + clocks.mux[0]=0xC01;clocks.enable[1]=0xCC000000; + assert(comboClockTransaction.allocation.retain(0,1,reinterpret_cast(&c),clocks));} + assert(XE::wrapFramebufferStart(&c,&c)==(failure==0)); assert(x.startCalls==((failure==0||failure==11||failure==12)?1U:0U)); assert(x.framebufferStartedController==(failure==0?static_cast(&c):nullptr)); if(failure==0)assert(!XE::wrapFramebufferStart(&c,&c)&&x.startCalls==1); + if(failure==13){assert(!x.framebufferStartInProgress && comboClockTransaction.allocation.binding[0]==2); + assert(comboClockTransaction.allocation.release(0,reinterpret_cast(&c))); + assert(XE::wrapFramebufferStart(&c,&c) && x.startCalls==1);} const auto calls=p.attempts,backends=x.backendCalls; x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); assert(p.attempts==calls&&x.backendCalls==backends); // no second trampoline diff --git a/tests/xelp_combo_clock.cpp b/tests/xelp_combo_clock.cpp index 729c618e..eb917566 100644 --- a/tests/xelp_combo_clock.cpp +++ b/tests/xelp_combo_clock.cpp @@ -8,6 +8,16 @@ int main(){ assert(donorPC(0x100000000,0x20006D77E)==UINT32_MAX); assert(donorPC(0x100000000,0xFFFF0000)==UINT32_MAX); unsigned cases=0; + for(unsigned pll=0;pll<2;++pll)for(bool gated:{false,true}) { + ClockSnapshot s;s.layout=Layout::Display13;s.mux[0]=0x800|pll|(gated?0x400U:0U);s.enable[pll]=0xCC000000; + ClockAllocation a;assert(a.retain(0,pll,0x1234,s)); + assert(a.retain(0,pll,0x1234,s) && !a.retain(0,pll,0x5678,s)); + assert(!a.choose(Port(0),s,true).valid && !a.choose(Port(0),s,false).valid && !a.share(Port(0),s).valid); + auto peer=a.choose(Port(1),s,true);assert(peer.valid && peer.pll==1-pll); + assert(!a.reset() && a.binding[0]==pll+1 && !a.release(0,0x5678)); + assert(a.release(0,0x1234) && !a.release(0,0x1234) && a.reset()); + s.enable[pll]=0x88000000;assert(!a.retain(0,pll,0x1234,s)); + } for(auto layout:{Layout::TGL,Layout::Display13})for(unsigned target=0;target<2;++target) for(unsigned peer=0;peer<2u;++peer)for(unsigned occupied=0;occupied<2;++occupied) { if(target==peer)continue; From 6d8333ef28aabd0029f30a8214d60ecf25020058 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:20:54 +0900 Subject: [PATCH 021/139] Retain and validate DSI native fixed mode publication --- WhateverXe/kern_dsi_apple_timing.hpp | 16 +++ WhateverXe/kern_xe.cpp | 130 ++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 10 ++ tests/dsi_apple_adapter.py | 159 +++++++++++++++++++++++++++ tools/test.py | 3 +- 5 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 tests/dsi_apple_adapter.py diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 7b9eccc0..d9836fb4 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -17,6 +17,22 @@ struct Timing { static_assert(sizeof(Timing)==160 && offsetof(Timing,pixelClock)==0x28 && offsetof(Timing,horizontalActive)==0x40 && offsetof(Timing,pixelEncoding)==0x88,"Apple timing ABI"); constexpr uint32_t FixedModeID=0x10000; +struct ModeInformation { + uint32_t width {},height {},refresh {},depth {},flags {}; + uint16_t widthMM {},heightMM {}; + uint32_t reserved[3] {}; +}; +static_assert(sizeof(ModeInformation)==36,"IODisplayModeInformation ABI"); +inline bool annotateMode(const Mode &m,ModeInformation &info) { + if(!m.valid() || info.width!=m.width || info.height!=m.height)return false; + info.refresh=static_cast((uint64_t(m.clockKHz)*1000*65536+m.htotal()*uint64_t(m.vtotal())/2)/ + (m.htotal()*uint64_t(m.vtotal()))); + info.widthMM=static_cast(m.widthMM);info.heightMM=static_cast(m.heightMM); + // IOGraphics: valid/safe/default + built-in + native. Acceleration is a + // separate runtime qualification, never inferred from a fixed mode. + info.flags|=0x02000407; + return true; +} inline bool makeTiming(const Profile &p,Timing &result) { result={};const auto &m=p.mode; if(!p.valid() || !m.valid())return false; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4b6d5529..40479091 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -17,6 +17,7 @@ #include "kern_display13_boot.hpp" #include "kern_display13_dsi_trial.hpp" #include "kern_dsi_readout.hpp" +#include "kern_dsi_apple_timing.hpp" #include "kern_display13_firmware.hpp" #include "kern_display13_tc_aux.hpp" #include "kern_display13_tc_mode.hpp" @@ -4574,6 +4575,34 @@ struct Display13FBPowerContext { uint64_t powerContext[24] {1}; // Bounded CPU-only execution-context evidence. }; static Display13FBPowerContext display13FBPower; +// One DSI0 panel. Publish only after native path/pipe association; the FB and +// immutable timing array remain retained until a quiesced DSI stop can detach +// them. A donor property failure must never leave its raw array pointer dangling. +struct DSIFixedModeOwner { + IOService *framebuffer {}; + OSArray *array {}; + OSData *data {}; + XELP::DSI::Mode mode; + XELP::DSI::AppleAdapter::Timing timing; + bool ready {}; +}; +static DSIFixedModeOwner dsiFixedMode; +static bool dsiModeTarget(IOService *fb) { + return fb && __atomic_load_n(&dsiFixedMode.framebuffer,__ATOMIC_ACQUIRE)==fb; +} +struct DSIModeScope { + IORecursiveLock *lock; + explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { + if(lock)IORecursiveLockLock(lock); + } + ~DSIModeScope(){if(lock)IORecursiveLockUnlock(lock);} +}; +static bool dsiModeCoherent(IOService *fb) { + const auto &o=dsiFixedMode; + return o.ready && o.array && o.data && o.array->getCount()==1 && o.array->getObject(0)==o.data && + o.data->getLength()==sizeof(o.timing) && !memcmp(o.data->getBytesNoCopy(),&o.timing,sizeof(o.timing)) && + Display13FBMemory::field(fb,0x4260)==o.array && fb->getProperty("IOFBDetailedTimings")==o.array; +} // AlderLakeN start bypasses the generic TGL framebuffer path and its power sampler. // Use the retained, uncached BAR owner. These GT mirrors require no forcewake, // mailbox transaction, MMIO write or native policy intervention. @@ -5407,6 +5436,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBGTTHooksReady=processDisplay13FramebufferGTT(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); + display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); using C=XELP::DisplayContract;const auto installed=display13Available(); display13FBPowerBackendReady=C{C::Power13,installed}.ready(); @@ -6471,6 +6501,106 @@ IOReturn XE::adlnSetupAuxiliaryMemory(IOService *controller,bool dsb) { IOReturn XE::wrapDisplay13CursorSetup(IOService *controller) {return adlnSetupAuxiliaryMemory(controller,false);} IOReturn XE::wrapAlderLakeNDSBSetup(IOService *controller) {return adlnSetupAuxiliaryMemory(controller,true);} +bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer18setDetailedTimingsEP7OSArray", + "__ZN21AppleIntelFramebuffer19validateDisplayModeEiPPKNS_15ModeDescriptionEPPK29IODetailedTimingInformationV2", + "__ZN21AppleIntelFramebuffer15getDisplayModesEPi", + "__ZN21AppleIntelFramebuffer19getDisplayModeCountEv", + "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation"}; + const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xF6,0x48,0x89,0xFB,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0x98,0x93,0x0A,0x00,0x83,0xFE}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x8F,0x84,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xE1,0x79,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}}; + if(!display13FramebufferRequested || size<=0x32D14)return false; + for(unsigned i=0;i(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, + {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, + {symbols[2],wrapDSIGetModes,orgDSIMode[2]}, + {symbols[3],wrapDSIGetModeCount,orgDSIMode[3]}, + {symbols[4],wrapDSIGetModeInformation,orgDSIMode[4]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size); + patcher.clearError();return ready; +} + +bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { + auto &c=display13FBPower;auto &o=dsiFixedMode; + if(!display13DSIModeHooksReady || adlnOutputWiring.dsi!=1 || !c.lock || !fb || + fb->getMetaClass()->getClassSize()<0x4B90 || !profile.valid())return false; + DSIModeScope scope(fb);if(!scope.lock)return false; + XELP::DSI::AppleAdapter::Timing timing; + if(!XELP::DSI::AppleAdapter::makeTiming(profile,timing))return false; + if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return dsiModeTarget(fb) && dsiModeCoherent(fb) && + !memcmp(&timing,&o.timing,sizeof(timing)); + auto data=OSData::withBytes(&timing,sizeof(timing));auto array=OSArray::withCapacity(1); + if(!data || !array || !array->setObject(data)) {OSSafeReleaseNULL(data);OSSafeReleaseNULL(array);return false;} + IOLockLock(c.lock); + const auto path=Display13FBMemory::singlePath(fb); + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); + const bool bound=!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE) && + c.controller==adlnTopologyController && binding.status==TGLFBBinding::Status::Complete && + binding.ddi==0 && c.transcoders.dsi(binding.pipe) && c.bootDSI.matches(profile) && + c.bootDSI.identity.pipe==binding.pipe && !c.memoryFaulted && !c.bridge.state.faulted; + if(bound) { + o.array=array;o.data=data;o.timing=timing;o.mode=profile.mode;o.ready=false; + fb->retain();__atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); + } + IOLockUnlock(c.lock); + if(!bound) {data->release();array->release();return false;} + // The donor ignores setProperty's bool result. Retain our own array/data + // before calling it, then verify both the raw pointer and registry owner. + const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,array); + o.ready=result==kIOReturnSuccess; + o.ready=dsiModeCoherent(fb); + fb->setProperty("WEXDSIFixedModeReady",o.ready); + return o.ready; +} + +uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; + if(!data || data->getLength()!=sizeof(dsiFixedMode.timing) || + memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)))return kIOReturnUnsupported; + const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); + return result==kIOReturnSuccess && !dsiModeCoherent(fb) ? kIOReturnNotReady : result; +} +uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **description,const void **timing) { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); + if(description)*description=nullptr;if(timing)*timing=nullptr; + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; + const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); + if(!result && timing && *timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; + return result; +} +uint32_t XE::wrapDSIGetModes(IOService *fb,int32_t *modes) { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(!modes)return kIOReturnBadArgument; + *modes=XELP::DSI::AppleAdapter::FixedModeID;return kIOReturnSuccess; +} +uint32_t XE::wrapDSIGetModeCount(IOService *fb) { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeCount,callbackXE->orgDSIMode[3])(fb); + DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? 1 : 0; +} +uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(!raw || mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; + const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); + if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, + *static_cast(raw)))return kIOReturnNotReady; + return result; +} + bool XE::processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { const char *os="__ZN31AppleIntelFramebufferController16getOSInformationEv"; const char *state="__ZN14AppleIntelPort14probePortStateEv"; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 7183eb97..89d30304 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -35,6 +35,7 @@ class IOPCIDevice; class IOInterruptEventSource; namespace Display13Clock { struct Readout; } namespace Display13WM { struct Latch; } +namespace XELP { namespace DSI { struct Profile; } } class XE { public: @@ -181,6 +182,15 @@ class XE { bool prepareDisplay13BootScanout(IOPCIDevice *); bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; + bool display13DSIModeHooksReady {}; + mach_vm_address_t orgDSIMode[5] {}; + bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); + static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); + static uint32_t wrapDSIValidateMode(IOService *,int32_t,const void **,const void **); + static uint32_t wrapDSIGetModes(IOService *,int32_t *); + static uint32_t wrapDSIGetModeCount(IOService *); + static uint32_t wrapDSIGetModeInformation(IOService *,int32_t,void *); Display13OutputWiring adlnOutputWiring; mach_vm_address_t adlnComboPLLFractionCaller {}; uint64_t adlnPlatform[0x1C0/8] {}; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py new file mode 100644 index 00000000..7bca6041 --- /dev/null +++ b/tests/dsi_apple_adapter.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""Replay production DSI fixed-mode ownership/dispatch with IOKit leaves mocked.""" +from pathlib import Path +import hashlib, re, subprocess, sys, tempfile + +root = Path(__file__).resolve().parents[1] +source = (root/'WhateverXe/kern_xe.cpp').read_text() +owner = source[source.index('struct DSIFixedModeOwner {'):source.index('// AlderLakeN start bypasses')] +names = ['bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', + 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation'] +functions = '\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}', source, re.S)[0] for name in names) +if len(sys.argv) == 2: + binary = Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(binary).hexdigest() == 'c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + installer = re.search(r'bool XE::processDisplay13DSIModes\(.*?\n\}', source, re.S)[0] + offsets = [int(x, 0) for x in re.search(r'offsets\[\]=\{([^}]+)', installer)[1].split(',')] + entries = re.search(r'entries\[\]\[16\]=\{(.*?)\};', installer, re.S)[1] + entries = [bytes(int(x, 0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}', entries)] + assert len(entries) == len(offsets) == 5 + for offset, entry in zip(offsets, entries): + assert binary[offset:offset+16] == entry, hex(offset) + +code = r''' +#include "kern_dsi_apple_timing.hpp" +#include "kern_dsi_readout.hpp" +#include "kern_display13_transcoder.hpp" +#include "kern_display13_output_wiring.hpp" +#include +#include +#include +#include +#include +constexpr uint32_t kIOReturnSuccess=0,kIOReturnNotReady=0xe00002d8U, + kIOReturnUnsupported=0xe00002c7U,kIOReturnBadArgument=0xe00002c2U; +unsigned alive=0,calls[5]{};bool dataOK=true,arrayOK=true,publishOK=true,geometryOK=true; +struct Meta {unsigned getClassSize(){return 0x4b90;}} meta; +struct OSObject { + unsigned refs=1;OSObject(){++alive;}virtual ~OSObject(){--alive;} + void retain(){assert(refs);++refs;}void release(){assert(refs);if(!--refs)delete this;} + Meta *getMetaClass(){return &meta;} +}; +struct OSData:OSObject { + std::vector bytes; + static OSData *withBytes(const void *p,unsigned n){if(!dataOK)return nullptr;auto d=new OSData; + d->bytes.assign(static_cast(p),static_cast(p)+n);return d;} + unsigned getLength(){return bytes.size();}void *getBytesNoCopy(){return bytes.data();} +}; +struct OSArray:OSObject { + std::vector objects; + static OSArray *withCapacity(unsigned n){assert(n==1);return arrayOK?new OSArray:nullptr;} + bool setObject(OSObject *p){p->retain();objects.push_back(p);return true;} + unsigned getCount(){return objects.size();}OSObject *getObject(unsigned i){return irelease();} +}; +struct IOService:OSObject { + alignas(8) uint8_t storage[0x4c00]{};std::map properties; + bool setProperty(const char *k,OSObject *p){if(!publishOK)return false;p->retain();removeProperty(k);properties[k]=p;return true;} + void setProperty(const char *,bool){} + OSObject *getProperty(const char *k){auto i=properties.find(k);return i==properties.end()?nullptr:i->second;} + void removeProperty(const char *k){auto i=properties.find(k);if(i!=properties.end()){i->second->release();properties.erase(i);}} + ~IOService(){for(auto i:properties)i.second->release();} +}; +struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; +void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} +void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} +#define OSDynamicCast(T,p) dynamic_cast(p) +#define OSSafeReleaseNULL(p) do{if(p){p->release();p=nullptr;}}while(0) +#define FunctionCast(f,p) reinterpret_cast(p) +OSObject *testPath=nullptr; +namespace Display13FBMemory { + templateT &field(void *p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} + templateT singlePath(void *){return static_cast(testPath);} +} +namespace TGLFBBinding {enum class Status {Bad,Complete};} +struct Binding {TGLFBBinding::Status status=TGLFBBinding::Status::Complete;uint32_t ddi=0,pipe=0;} binding; +Binding adlnInspectPathBinding(IOService *,OSObject *path,void *,const Display13OutputWiring &){assert(path);return binding;} +struct {IOLock *lock;void *controller;Display13Transcoder::Bindings transcoders;XELP::DSI::Readout bootDSI; + bool memoryFaulted=false;struct {struct {bool faulted=false;} state;} bridge;} display13FBPower; +struct XE { + bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; + uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[5]{}; + bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); + static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); + static uint32_t wrapDSIValidateMode(IOService *,int32_t,const void **,const void **); + static uint32_t wrapDSIGetModes(IOService *,int32_t *); + static uint32_t wrapDSIGetModeCount(IOService *); + static uint32_t wrapDSIGetModeInformation(IOService *,int32_t,void *); +} xe,*callbackXE=&xe; +OWNER +FUNCTIONS +uint32_t publish(IOService *fb,OSArray *a){++calls[0];fb->setProperty("IOFBDetailedTimings",a); + Display13FBMemory::field(fb,0x4260)=a;return 0;} +uint32_t validate(IOService *fb,int32_t mode,const void **desc,const void **timing){++calls[1]; + auto a=Display13FBMemory::field(fb,0x4260);assert(a && (mode&1023)==0); + if(desc)*desc=reinterpret_cast(1);if(timing)*timing=static_cast(a->getObject(0))->getBytesNoCopy();return 0;} +uint32_t modes(IOService *,int32_t *p){++calls[2];if(p)*p=42;return 0;} +uint32_t count(IOService *){++calls[3];return 3;} +uint32_t information(IOService *fb,int32_t mode,void *p){++calls[4];const void *t=nullptr; + auto r=XE::wrapDSIValidateMode(fb,mode,nullptr,&t);if(r)return r; + auto timing=static_cast(t); + auto info=static_cast(p);*info={}; + info->width=geometryOK?timing->horizontalActive:1;info->height=timing->verticalActive;info->depth=2;return 0;} +int main(){ + using namespace XELP::DSI;using namespace AppleAdapter;IORecursiveLock fbLock;IOLock ownerLock; + auto fb=new IOService;auto peer=new IOService;testPath=peer; + Display13FBMemory::field(fb,0x4220)=&fbLock; + display13FBPower.lock=&ownerLock;display13FBPower.controller=xe.adlnTopologyController=peer; + display13FBPower.transcoders.dsi0Pipe=0;xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.dsi=1; + xe.orgDSIMode[0]=reinterpret_cast(publish);xe.orgDSIMode[1]=reinterpret_cast(validate); + xe.orgDSIMode[2]=reinterpret_cast(modes);xe.orgDSIMode[3]=reinterpret_cast(count); + xe.orgDSIMode[4]=reinterpret_cast(information); + Profile p;p.status=ProfileStatus::Valid;p.lanes=4;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + auto plan=linkPlan(p);auto &r=display13FBPower.bootDSI;r.status=ReadoutStatus::Complete;r.identity={0,0,0,1}; + r.afeKHz=811500;r.htotal=plan.htotal;r.hsync=plan.hsync;r.vtotal=plan.vtotal;r.vsync=plan.vsync; + for(unsigned failure=0;failure<4;++failure){const auto before=alive;dataOK=failure!=0;arrayOK=failure!=1; + binding.status=failure==2?TGLFBBinding::Status::Bad:TGLFBBinding::Status::Complete; + display13FBPower.memoryFaulted=failure==3; + assert(!xe.bindDSIFixedMode(fb,p) && !dsiModeTarget(fb) && alive==before && !calls[0]);} + dataOK=arrayOK=true;display13FBPower.memoryFaulted=false; + assert(xe.bindDSIFixedMode(fb,p) && fb->refs==2 && dsiFixedMode.array->refs==2 && dsiFixedMode.data->refs==2); + auto other=p;++other.mode.clockKHz;assert(!xe.bindDSIFixedMode(fb,other)); + assert(XE::wrapDSIGetModeCount(fb)==1 && XE::wrapDSIGetModeCount(peer)==3); + int32_t peerMode=0;assert(!XE::wrapDSIGetModes(peer,&peerMode) && peerMode==42); + assert(XE::wrapDSIGetModes(fb,nullptr)==kIOReturnBadArgument); + int32_t mode=0;assert(!XE::wrapDSIGetModes(fb,&mode) && mode==FixedModeID); + const void *timing=nullptr,*description=nullptr; + assert(!XE::wrapDSIValidateMode(fb,mode,&description,&timing) && timing==dsiFixedMode.data->getBytesNoCopy()); + auto validations=calls[1];assert(XE::wrapDSIValidateMode(fb,mode+0x400,&description,&timing)==kIOReturnBadArgument); + assert(!description && !timing && calls[1]==validations); + ModeInformation info;assert(!XE::wrapDSIGetModeInformation(fb,mode,&info)); + assert(info.width==1080 && info.height==1920 && info.widthMM==86 && info.heightMM==154 && info.depth==2); + assert(!(info.flags&0x00400000)); + auto invalid=p.mode;invalid.width=0;assert(!annotateMode(invalid,info)); + assert(XE::wrapDSIGetModeInformation(fb,mode,nullptr)==kIOReturnBadArgument); + assert(info.refresh>59*65536 && info.refresh<61*65536 && (info.flags&0x02000407)==0x02000407); + geometryOK=false;assert(XE::wrapDSIGetModeInformation(fb,mode,&info)==kIOReturnNotReady);geometryOK=true; + auto replacement=OSArray::withCapacity(1);auto data=OSData::withBytes(&dsiFixedMode.timing,sizeof(Timing)); + replacement->setObject(data);assert(!XE::wrapDSISetDetailedTimings(fb,replacement)); + assert(Display13FBMemory::field(fb,0x4260)==dsiFixedMode.array && replacement->refs==1); + data->bytes.resize(1);assert(XE::wrapDSISetDetailedTimings(fb,replacement)==kIOReturnUnsupported); + assert(XE::wrapDSISetDetailedTimings(fb,nullptr)==kIOReturnUnsupported); + data->release();replacement->release(); + fb->removeProperty("IOFBDetailedTimings");assert(!XE::wrapDSIGetModeCount(fb) && dsiFixedMode.array->refs==1); + assert(XE::wrapDSIValidateMode(fb,mode,nullptr,nullptr)==kIOReturnNotReady); + const auto cleanup=[&]{Display13FBMemory::field(fb,0x4260)=nullptr; + dsiFixedMode.array->release();dsiFixedMode.data->release();fb->release();dsiFixedMode={};}; + cleanup();publishOK=false;assert(!xe.bindDSIFixedMode(fb,p)); + assert(dsiModeTarget(fb) && dsiFixedMode.array->refs==1 && Display13FBMemory::field(fb,0x4260)==dsiFixedMode.array); + assert(!XE::wrapDSIGetModeCount(fb));cleanup(); + assert(!fbLock.depth && !ownerLock.depth);fb->release();peer->release();assert(!alive); + puts("PASS production DSI fixed mode: retained native array, publication failure, index restriction, fixed enumeration, metadata and foreign FB dispatch"); +} +'''.replace('OWNER', owner).replace('FUNCTIONS', functions) +with tempfile.TemporaryDirectory(prefix='dsi-apple-adapter-') as directory: + path = Path(directory) + (path/'test.cpp').write_text(code) + subprocess.run(['clang++', '-std=c++17', '-O1', '-fsanitize=address,undefined', + '-I'+str(root/'WhateverXe'), str(path/'test.cpp'), '-o', str(path/'test')], check=True) + subprocess.run([str(path/'test')], check=True) diff --git a/tools/test.py b/tools/test.py index bb43c126..f01495b7 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,9 +8,10 @@ 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', '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_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_apple_adapter_native',[sys.executable,str(R/'tests/dsi_apple_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_timing_native',[sys.executable,str(R/'tests/dsi_apple_timing_native.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', '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']] From 351685672ef5c962236522e2f2f97ace6a5e4e4a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:27:28 +0900 Subject: [PATCH 022/139] Qualify DSI native port state without AUX probing --- WhateverXe/kern_display13_tc_native.hpp | 6 + WhateverXe/kern_xe.cpp | 45 +++++++ WhateverXe/kern_xe.hpp | 1 + tests/display13_offline.py | 9 +- tests/display13_tc_preparation_fixture.cpp | 1 + tests/dsi_port_adapter.py | 130 +++++++++++++++++++++ tools/test.py | 3 +- 7 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 tests/dsi_port_adapter.py diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp index 2fad5123..fcf28889 100644 --- a/WhateverXe/kern_display13_tc_native.hpp +++ b/WhateverXe/kern_display13_tc_native.hpp @@ -26,6 +26,12 @@ static constexpr Entry entries[]={ {"__ZN14AppleIntelPort15writeRegister32Ejj",0x1fea8,{0x55,0x48,0x89,0xe5,0x48,0xff,0x5,0x9d,0x73,0xb,0x0,0x48,0x8b,0xbf,0x78,0x5,0x0,0x0,0x89,0xf6}}, }; +// Nonzero base Mode skips the reset callback and records only software mode. +// DSI uses mode1 through this exact branch, never the hardware HAL setter. +static constexpr uint32_t baseModeBranchOffset=0x1b4db; +static constexpr uint8_t baseModeBranch[]={0x85,0xf6,0x75,0x2e}; +static constexpr uint32_t baseModeStoreOffset=0x1b50d; +static constexpr uint8_t baseModeStore[]={0x41,0x89,0x9e,0x50,0x05,0x00,0x00,0x31,0xc0,0x5b,0x41,0x5e,0x5d,0xc3}; // Native clock replacements retain these DDI reference boundaries. // Boot Mode is acquired in PowerWell::init: native ports must already exist. // Full modesets stop the inherited link before LightUpDP programs its PLL. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 40479091..7a01583c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4539,6 +4539,7 @@ struct Display13FBPowerContext { // No DSI binding is installed until the complete adoption backend exists. Display13Transcoder::Bindings transcoders; XELP::DSI::Readout bootDSI; // Captured before handoff; not an implicit admission flag. + OSObject *dsiPort {}; // Retained after source-qualified native mode binding. IOSimpleLock *tcBankLock {}; bool tcSupported {},tcBankFaulted {}; Display13TC::Mode tcMode[4]; @@ -6670,6 +6671,12 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13OfflineProbe",true); return 0; } + if(s->adlnOutputWiring.dsi && config[0]==0) { + // DSI has neither HPD nor a DPCD sink. Only an adopted live source + // may report connected; never enter the donor's eDP probe branch. + const auto state=setDSIPortMode(port,1)==kIOReturnSuccess ? 2U : 0U; + *reinterpret_cast(reinterpret_cast(port)+0x554)=state;return state; + } // CPU binding only. TC bits are added when mode/AUX/PLL/GOP become // qualified together; a board description alone is insufficient. if(!s->xelpBindProbe(s->adlnTopologyController)) { @@ -6707,9 +6714,45 @@ unsigned XE::display13TCTarget(OSObject *port,bool cleanup) { return config[0]; } +// The Display13 native Mode hook has one owner for TC, Combo and DSI. +// Caller must have installed the source/mapping and native power references at +// the adoption boundary. A board record or firmware-on bits alone never admit. +uint32_t XE::setDSIPortMode(OSObject *port,uint32_t wanted) { + auto self=callbackXE;auto &c=display13FBPower; + if(!self->display13FBTCHooksReady || !self->display13TCBaseMode || !c.lock || + !port || port->getMetaClass()->getClassSize()<0x6B0)return kIOReturnNotReady; + // Mode zero must be connected to an actual DSI stop/cleanup transaction. + // Reject it for now, including when the native caller asks only for reset. + if(wanted!=1)return kIOReturnUnsupported; + IOLockLock(c.lock); + auto config=Display13FBMemory::field(port,0x548); + const auto previous=Display13FBMemory::field(port,0x550); + const bool bound=self->adlnOutputWiring.dsi==1 && self->adlnOutputWiring.matches(config) && + config[0]==0 && Display13FBTopology::bound(self->adlnPlatform,config,0) && + c.controller && c.controller==self->adlnTopologyController && c.access && + Display13FBMemory::field(port,0x578)==c.access && + Display13FBMemory::field(port,0x120)==0 && + c.transcoders.dsi(0) && c.bootDSI.complete() && c.bootDSI.identity.pipe==0 && + c.bootDSI.identity.phy==0 && c.bootDSI.identity.transcoder==0 && + (!c.dsiPort || c.dsiPort==port) && previous<=1 && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted; + Display13FBBootIO io{c.map}; + const bool ready=bound && c.bridge.outputHeld(io,0,0) && XELP::DSI::sourceUnchanged(io,c.bootDSI); + uint32_t result=kIOReturnNotReady; + if(ready) { + // Exact donor base setPortMode(1) only records port+0x550. The HAL + // setter and mode0's reset callback must never run on this path. + result=reinterpret_cast(self->display13TCBaseMode)(port,1); + if(result==kIOReturnSuccess && Display13FBMemory::field(port,0x550)!=1)result=kIOReturnNotReady; + if(result==kIOReturnSuccess && !c.dsiPort){port->retain();c.dsiPort=port;} + } + IOLockUnlock(c.lock);return result; +} + uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { auto self=callbackXE;auto &c=display13FBPower; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548) : nullptr; + if(self->adlnOutputWiring.dsi && config && config[0]==0)return setDSIPortMode(port,wanted); if(config && config[0]<3)return FunctionCast(wrapDisplay13TCMode,self->orgDisplay13TCMode)(port,wanted); if(!c.lock)return kIOReturnNotReady; IOLockLock(c.lock);const auto ddi=display13TCTarget(port,wanted==0); @@ -7032,6 +7075,8 @@ bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_ } struct Span {uint32_t offset;const uint8_t *bytes;size_t size;}; const Span spans[]={ + {baseModeBranchOffset,baseModeBranch,sizeof(baseModeBranch)}, + {baseModeStoreOffset,baseModeStore,sizeof(baseModeStore)}, {bootPortInitOffset,bootPortInit,sizeof(bootPortInit)}, {bootPowerCallOffset,bootPowerCall,sizeof(bootPowerCall)}, {bootSetupCallOffset,bootSetupCall,sizeof(bootSetupCall)}, diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 89d30304..a0f0f5f9 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -186,6 +186,7 @@ class XE { mach_vm_address_t orgDSIMode[5] {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); + static uint32_t setDSIPortMode(OSObject *,uint32_t); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); static uint32_t wrapDSIValidateMode(IOService *,int32_t,const void **,const void **); static uint32_t wrapDSIGetModes(IOService *,int32_t *); diff --git a/tests/display13_offline.py b/tests/display13_offline.py index b42f1ca0..858d396a 100644 --- a/tests/display13_offline.py +++ b/tests/display13_offline.py @@ -10,15 +10,16 @@ #include #include #include -struct OSObject { unsigned char bytes[0x580]{}; OSObject *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);} }; +constexpr uint32_t kIOReturnSuccess=0; +struct OSObject { alignas(8) unsigned char bytes[0x580]{}; OSObject *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);} }; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char *,bool){}void setProperty(const char *,const void *,unsigned){}}; -namespace Display13FBMemory {templateT field(void *p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}} -struct Wiring {unsigned selected(){return 8;}}; +namespace Display13FBMemory {templateT &field(void *p,unsigned off){return *reinterpret_cast((char*)p+off);}} +struct Wiring {unsigned dsi=0;unsigned selected(){return 8;}}; namespace Display13FBTopology {constexpr unsigned Ports=9;bool selected(void *p,uint32_t *c,Wiring){return c==(uint32_t*)p;}bool bound(void *,uint32_t *,unsigned){return false;}} static bool display13Offline;static unsigned calls,binds; uint32_t native(OSObject *){++calls;return 1;} #define FunctionCast(type,fn) fn -struct XE {bool display13FBTopologyHooksReady=true;void *adlnTopologyController=(void*)1;void *adlnPlatform;Wiring adlnOutputWiring;uint32_t (*orgAlderLakeNPortState)(OSObject*)=native;bool xelpBindProbe(void*){++binds;return true;}static uint32_t wrapAlderLakeNPortState(OSObject*);}; +struct XE {static uint32_t setDSIPortMode(OSObject*,uint32_t){assert(false);return 1;}bool display13FBTopologyHooksReady=true;void *adlnTopologyController=(void*)1;void *adlnPlatform;Wiring adlnOutputWiring;uint32_t (*orgAlderLakeNPortState)(OSObject*)=native;bool xelpBindProbe(void*){++binds;return true;}static uint32_t wrapAlderLakeNPortState(OSObject*);}; static XE *callbackXE; BODY int main(){XE x;callbackXE=&x;uint32_t config[3]={3,9,1};x.adlnPlatform=config;OSObject port;auto cp=config;memcpy(port.bytes+0x548,&cp,sizeof(cp));uint32_t connected=4;memcpy(port.bytes+0x554,&connected,4); diff --git a/tests/display13_tc_preparation_fixture.cpp b/tests/display13_tc_preparation_fixture.cpp index a3656c22..2fa939b6 100644 --- a/tests/display13_tc_preparation_fixture.cpp +++ b/tests/display13_tc_preparation_fixture.cpp @@ -56,6 +56,7 @@ struct XE { static XELP::LinkRequirements framebufferLinkRequirements(void*,uint32_t); static unsigned display13TCTarget(OSObject*,bool=false); static uint32_t wrapDisplay13TCMode(OSObject*,uint32_t); + static uint32_t setDSIPortMode(OSObject*,uint32_t){assert(false && "TC must not enter DSI");return kIOReturnNotReady;} } instance,*callbackXE=&instance; // FUNCTIONS alignas(8) uint8_t portBytes[0x700]; diff --git a/tests/dsi_port_adapter.py b/tests/dsi_port_adapter.py new file mode 100644 index 00000000..dfebe755 --- /dev/null +++ b/tests/dsi_port_adapter.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Replay production DSI port/probe with real source and power qualification. + +Optional exact donor executes its base mode setter; HAL/AUX/reset are forbidden. +The topology, power leases and register values are synthetic CPU fixtures. +""" +from pathlib import Path +import hashlib, re, subprocess, sys, tempfile +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'uint32_t XE::'+name+r'\(.*?\n\}',source,re.S)[0] + for name in ['setDSIPortMode','wrapAlderLakeNPortState']) +dispatch=re.search(r'uint32_t XE::wrapDisplay13TCMode\(.*?\n\}',source,re.S)[0] +assert dispatch.index('return setDSIPortMode(port,wanted)')orgDisplay13TCMode') +donor=None +if len(sys.argv)>1: + donor=Path(sys.argv[1]).resolve() + assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +code=r''' +#include "kern_dsi_readout.hpp" +#include "kern_display13_power_bridge.hpp" +#include "kern_display13_transcoder.hpp" +#include "kern_display13_fb_topology.hpp" +#include +#include +#include +#include +#include +#include +constexpr uint32_t kIOReturnSuccess=0,kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U; +struct OSObject { + alignas(8) uint8_t bytes[0x6B0]{};unsigned refs=1;unsigned size=sizeof(bytes); + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}void retain(){++refs;} +}; +struct IOLock {bool held=false;};void IOLockLock(IOLock *p){assert(!p->held);p->held=true;} +void IOLockUnlock(IOLock *p){assert(p->held);p->held=false;} +namespace Display13FBMemory {templateT &field(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);}} +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *,bool){}void setProperty(const char *,const void *,unsigned){}}; +using Registers=std::map; +struct Display13FBBootIO { + Registers *map; + bool readable(uint32_t r){return map && map->count(r);} + uint32_t read(uint32_t r){assert(readable(r));return map->at(r);} +}; +struct {IOLock *lock;void *controller,*access;Registers *map;OSObject *dsiPort=nullptr; + Display13Transcoder::Bindings transcoders;XELP::DSI::Readout bootDSI;Display13Power::NativeBridge bridge; + bool memoryFaulted=false,clockFaulted=false;} display13FBPower; +unsigned nativeCalls=0,providerCalls=0,modeCalls=0; +uint32_t nativeProbe(OSObject *){++nativeCalls;return 1;} +uint32_t baseMode(OSObject *p,uint32_t m){assert(m==1);++modeCalls;Display13FBMemory::field(p,0x550)=m;return 0;} +struct XE { + bool display13FBTCHooksReady=true,display13FBTopologyHooksReady=true; + uintptr_t display13TCBaseMode=reinterpret_cast(baseMode); + void *adlnTopologyController;alignas(8) uint8_t adlnPlatform[0x1C0]{};Display13OutputWiring adlnOutputWiring; + uint32_t (*orgAlderLakeNPortState)(OSObject*)=nativeProbe; + bool xelpBindProbe(void *){++providerCalls;return true;} + static uint32_t setDSIPortMode(OSObject *,uint32_t); + static uint32_t wrapAlderLakeNPortState(OSObject *); +} xe,*callbackXE=&xe; +bool display13Offline=false; +#define FunctionCast(f,p) reinterpret_cast(p) +FUNCTIONS +void noReset(void *){assert(false && "DSI must not call native reset");} +int main(int argc,char **argv){ + void *image=nullptr; + if(argc==2){std::ifstream f(argv[1],std::ios::binary);std::vector b((std::istreambuf_iterator(f)),{}); + image=mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);assert(image!=MAP_FAILED); + assert(b.size()<0x400000);memcpy(image,b.data(),b.size());xe.display13TCBaseMode=reinterpret_cast(image)+0x1b4c8;} + Registers r={{0x7B008,0xC0000000},{0x6B400,0x80030006},{0x45404,0xFC0F},{0x70008,0}, + {0x6B030,0x20130300},{0x6B094,1},{0x6B404,0},{0x6B034,0},{0x64000,0x80000000},{0x164280,0x1E07801}, + {0x46014,0xCC000000},{0x16428C,0x3001FB},{0x164290,0xE84}, + {0x6B000,0x04650437},{0x6B008,0x044F044D},{0x6B00C,0x07D1077F},{0x6B014,0x07A907A7}, + {0x51004,0x40000000},{0x45454,3},{0x42000,0x0EFFFFFF}}; + auto &c=display13FBPower;IOLock lock;c.lock=&lock;c.map=&r; + OSObject controller,access,port,peer;uintptr_t vtable[64]{};vtable[0x120/8]=reinterpret_cast(noReset); + Display13FBMemory::field(port.bytes,0)=vtable; + c.controller=xe.adlnTopologyController=&controller;c.access=&access; + xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.dsi=1;xe.adlnOutputWiring.hdmi=2;xe.adlnOutputWiring.ddc[1]=2; + xe.adlnPlatform[0x12]=9; + uint32_t panel[]={0,0,0,0,2,0x18};memcpy(xe.adlnPlatform+0x20,panel,sizeof(panel)); + uint32_t hdmi[]={1,2,2,0,0x800,0x202};memcpy(xe.adlnPlatform+0x38,hdmi,sizeof(hdmi)); + auto config=reinterpret_cast(xe.adlnPlatform+0x20); + Display13FBMemory::field(port.bytes,0x548)=config; + Display13FBMemory::field(port.bytes,0x578)=&access; + Display13FBMemory::field(peer.bytes,0x548)=reinterpret_cast(xe.adlnPlatform+0x38); + c.transcoders.dsi0Pipe=0; + c.bootDSI=XELP::DSI::decodeReadout([&](uint32_t a,uint32_t &v){if(!r.count(a))return false;v=r.at(a);return true;},38400); + assert(c.bootDSI.complete()); + auto &bridge=c.bridge;bridge.begun=true;bridge.selectedPorts=3;bridge.bootPort=0; + auto &state=bridge.state;state.ready=true;state.allowed=Display13Power::All; + const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)); + const auto ddi=Display13Power::dependencies(Display13Power::ddiDomain(0)); + state.borrowed=pg|ddi; + for(unsigned i=0;i(&port,0x550) && port.refs==1 && !c.dsiPort && !lock.held);}; + bridge.ddiRefs[0]=0;reject();bridge.ddiRefs[0]=1; + bridge.pgRefs[0]=0;reject();bridge.pgRefs[0]=1; + c.memoryFaulted=true;reject();c.memoryFaulted=false; + c.clockFaulted=true;reject();c.clockFaulted=false; + c.transcoders.dsi0Pipe=1;reject();c.transcoders.dsi0Pipe=0; + r[0x6B000]^=1;reject();r[0x6B000]^=1; + r[0x164280]^=1;reject();r[0x164280]^=1; + Display13FBMemory::field(&port,0x578)=&controller;reject();Display13FBMemory::field(&port,0x578)=&access; + uint32_t copied[6];memcpy(copied,config,sizeof(copied));Display13FBMemory::field(&port,0x548)=copied; + reject();Display13FBMemory::field(&port,0x548)=config; + assert(XE::setDSIPortMode(&port,0)==kIOReturnUnsupported && !c.dsiPort); + display13Offline=true;assert(!XE::wrapAlderLakeNPortState(&port) && !c.dsiPort);display13Offline=false; + assert(XE::wrapAlderLakeNPortState(&port)==2 && !nativeCalls && !providerCalls); + assert(Display13FBMemory::field(&port,0x550)==1 && Display13FBMemory::field(&port,0x554)==2); + assert(c.dsiPort==&port && port.refs==2); + assert(XE::wrapAlderLakeNPortState(&port)==2 && port.refs==2); + assert(XE::setDSIPortMode(&port,0)==kIOReturnUnsupported && Display13FBMemory::field(&port,0x550)==1); + state.suspended=true;assert(!XE::wrapAlderLakeNPortState(&port));state.suspended=false; + assert(port.refs==2 && c.dsiPort==&port && !nativeCalls && !providerCalls); + assert(XE::wrapAlderLakeNPortState(&peer)==1 && nativeCalls==1 && providerCalls==1); + assert(!lock.held);if(image)munmap(image,0x400000); + puts("PASS production DSI port/probe: source and lease qualification, no HPD/AUX/reset, retained identity, external fallback"); +} +'''.replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-port-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + command=['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')] + if donor: command[1:1]=['-arch','x86_64'] + subprocess.run(command,check=True) + subprocess.run([str(p/'test')]+([str(donor)] if donor else []),check=True) diff --git a/tools/test.py b/tools/test.py index f01495b7..a65050ab 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,9 +8,10 @@ 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', '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 += ['dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_adapter_native',[sys.executable,str(R/'tests/dsi_apple_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_timing_native',[sys.executable,str(R/'tests/dsi_apple_timing_native.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)] From db30ea853e059c58055dba74dccb6cf77dae9b0c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:37:58 +0900 Subject: [PATCH 023/139] Preserve DSI firmware scanout through bootstrap qualification --- WhateverXe/kern_display13_boot.hpp | 7 +- WhateverXe/kern_dsi_boot.hpp | 102 +++++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 40 +++++++++-- WhateverXe/kern_xe.hpp | 5 ++ tests/dsi_boot.cpp | 82 +++++++++++++++++++++++ tests/dsi_boot_adapter.py | 99 ++++++++++++++++++++++++++++ tools/test.py | 2 +- 7 files changed, 327 insertions(+), 10 deletions(-) create mode 100644 WhateverXe/kern_dsi_boot.hpp create mode 100644 tests/dsi_boot.cpp create mode 100644 tests/dsi_boot_adapter.py diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index a59679a2..612bba77 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -3,6 +3,7 @@ #ifndef KERN_DISPLAY13_BOOT_HPP #define KERN_DISPLAY13_BOOT_HPP #include "kern_alderlake_n.hpp" +#include "kern_dsi_readout.hpp" #include "kern_xelp_gop_stop.hpp" #include "kern_display13_fb_irq.hpp" #include "kern_display13_fb_topology.hpp" @@ -56,17 +57,19 @@ struct UnusedTCResult { uint32_t cleared {},writes {},ddi {},buffer {},ready {},clock {},pll {},gate {}; }; template UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::DisplayReadout &boot, - bool admitted,const Display13HDMIWiring &w) { + bool admitted,const Display13HDMIWiring &w,const XELP::DSI::Readout *dsi=nullptr) { UnusedTCResult r; if(!admitted)return r; r.status=Status::Topology; - if(boot.status!=AlderLakeN::ReadoutStatus::Complete || boot.activePipes!=1 || + if((w.dsi && !dsi) || boot.status!=AlderLakeN::ReadoutStatus::Complete || boot.activePipes!=(dsi?0U:1U) || + (dsi && (w.dsi!=1 || dsi->identity.pipe!=0 || !XELP::DSI::sourceUnchanged(io,*dsi))) || !(boot.poweredPipes&1) || !Display13FBTopology::multiOutputWiring(w))return r; uint32_t power=0,auxPower=0; if(!AlderLakeN::value(boot,0x45404,power) || (power&5)!=5 || !AlderLakeN::value(boot,0x45444,auxPower))return r; uint32_t gated=0; const auto stable=[&] { + if(dsi && !XELP::DSI::sourceUnchanged(io,*dsi))return false; for(unsigned i=0;i bool bootPeers(IO &io,uint32_t poweredPipes) { + if(!(poweredPipes&1) || (poweredPipes&~7U))return false; + const auto value=[&](uint32_t reg,uint32_t mask,uint32_t expected) { + if(!io.readable(reg))return false;const auto v=io.read(reg); + return v!=UINT32_MAX && (v&mask)==expected; + }; + if(!io.readable(0x45404))return false; + const auto power=io.read(0x45404); + if(power==UINT32_MAX || (power&3)!=3 || + (((power>>10)&1)|((power>>11)&2)|((power>>12)&4))!=poweredPipes || + ((poweredPipes&6) && (power&5)!=5) || !value(0x7B808,0xC0000000U,0))return false; + for(unsigned p=0;p<3;++p)if(poweredPipes&(1U< bool bootUnchanged(IO &io,const Boot &boot,const XELPMemory::Layout &memory) { + if(!boot.usable() || !XELPMemory::same(boot.memory,memory) || + !sourceUnchanged(io,boot.source) || !bootPeers(io,boot.poweredPipes))return false; + const uint32_t regs[]={0x42084,0x70180,0x70188,0x70190,0x7019C,0x701AC,0x7018C,0x701A4}; + const uint32_t expected[]={boot.dpt,boot.control,boot.stride,boot.size,boot.surface,boot.surface,0,0}; + for(unsigned i=0;i void captureBoot(IO &io,const AlderLakeN::DisplayReadout &normal, + const Profile &profile,const XELPMemory::Layout &memory,bool admitted,Boot &r) { + r={};if(!admitted || !profile.valid())return; + r.status=BootStatus::Memory; + if(!memory.valid || !memory.reservationKnown || !memory.bytes || memory.bytes%4096 || + memory.bytes>XELPMemory::Aperture || memory.bytes>memory.apertureBytes || !io.readable(0x800000+8*(memory.bytes/4096)-1))return; + r.memory=memory;r.poweredPipes=normal.poweredPipes;r.status=BootStatus::Peers; + if(normal.status!=AlderLakeN::ReadoutStatus::Complete || normal.activePipes || + !bootPeers(io,r.poweredPipes))return; + r.status=BootStatus::Source; + if(!io.readable(0x51004))return; + const auto ref=(io.read(0x51004)>>29)&7;const uint32_t references[]={24000,19200,38400}; + if(ref>=3)return; + r.source=decodeReadout([&](uint32_t reg,uint32_t &v) { + if(!io.readable(reg))return false;v=io.read(reg);return true; + },references[ref]); + if(r.source.identity.pipe!=0 || !r.source.matches(profile))return; + r.status=BootStatus::Geometry; + const uint32_t regs[]={0x42084,0x70180,0x70188,0x70190,0x7019C,0x701AC,0x7018C,0x701A4}; + uint32_t words[8]{}; + for(unsigned i=0;i<8;++i){if(!io.readable(regs[i]))return;words[i]=io.read(regs[i]);if(words[i]==UINT32_MAX)return;} + r.dpt=words[0];r.control=words[1];r.stride=words[2];r.size=words[3];r.surface=words[4]; + r.geometry=XELPGeometry::linear(r.control,r.stride,r.size,words[6],words[7]); + // LINEAR uses byte GGTT offsets even with global DPT-disable clear. + if(!XELPGeometry::timing(r.geometry,r.source.htotal,r.source.vtotal) || r.surface!=words[5] || + !memory.range(r.surface,r.geometry.bytes,true))return; + r.status=BootStatus::Candidate; + if(!bootUnchanged(io,r,memory))r.status=BootStatus::Changed; + return; +} +template Boot captureBoot(IO &io,const AlderLakeN::DisplayReadout &normal, + const Profile &profile,const XELPMemory::Layout &memory,bool admitted) { + Boot r;captureBoot(io,normal,profile,memory,admitted,r);return r; +} +// Prove the full stolen prefix that the native allocator will protect, not +// merely the visible pixels. This does not grant an allocator reservation. +template bool verifyBootPrefix(IO &io,Boot &boot,const XELPMemory::Layout &memory) { + if(!bootUnchanged(io,boot,memory)){boot.status=BootStatus::Changed;return false;} + boot.verifiedPages=0; + for(uint32_t i=0;igetLength():0,firmwarePorts,3); } +bool XE::prepareDSIFirmware(IOService *provider) { + auto source=provider ? OSDynamicCast(OSData,provider->getProperty("TGLVBT")) : nullptr; + if(!source || !source->getLength() || source->getLength()>65536)return false; + if(dsiFirmware)return dsiProfile.valid() && dsiFirmware->getLength()==source->getLength() && + !memcmp(dsiFirmware->getBytesNoCopy(),source->getBytesNoCopy(),source->getLength()); + auto copy=OSData::withBytes(source->getBytesNoCopy(),source->getLength()); + if(!copy)return false; + auto parsed=XELP::DSI::parseProfile({static_cast(copy->getBytesNoCopy()),copy->getLength()},true); + if(!parsed.valid()){copy->release();return false;} + dsiProfile=parsed;dsiFirmware=copy;return true; +} + void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); @@ -3881,6 +3893,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if(displayRequested && !Display13FBTopology::supportedWiring(board,xeProfile.display==XELP::Display::XeLPD)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } + if(board.dsi && !prepareDSIFirmware(pci))return; auto bootDDI=displayRequested ? UINT32_MAX : 1U; uint64_t connector[] {1,displayRequested,pci->getProperty("xelp-hdmi-wiring")!=nullptr,board.valid,board.hdmi,bootDDI}; pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); @@ -3989,8 +4002,10 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("WEXDisplay13TCBootPorts",bootTC,sizeof(bootTC)); } - bool bootstrapReady=true; - if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { + if(displayRequested && board.dsi) + XELP::DSI::captureBoot(io,result,dsiProfile,xelpMemory,barReady && reservationReady,dsiBoot); + bool bootstrapReady=!board.dsi || dsiBoot.usable(); + if(!board.dsi && displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory,snapshot->normalize); pci->setProperty("N97DualGOPNormalize",&parked,sizeof(parked)); AlderLakeN::readDisplay(io,barReady,result); @@ -3998,13 +4013,15 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { bootstrapReady=parked.status==Display13Boot::Status::Complete; } if(displayRequested && linearTest && bootstrapReady && Display13FBTopology::multiOutputWiring(board)) { - auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board); + auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board, + board.dsi ? &dsiBoot.source : nullptr); pci->setProperty("WEXDisplay13UnusedTC",&unused,sizeof(unused)); bootstrapReady=unused.status==Display13Boot::Status::Complete; } if(displayRequested && bootstrapReady) { uint32_t trans=UINT32_MAX; - if(AlderLakeN::value(result,0x60400,trans))bootDDI=Display13FBTopology::bootstrapDDI(board, + if(board.dsi)bootDDI=0; + else if(AlderLakeN::value(result,0x60400,trans))bootDDI=Display13FBTopology::bootstrapDDI(board, result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans,true); connector[5]=bootDDI;pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); } @@ -4150,9 +4167,11 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { AlderLakeN::LinearPlan bootPlan; uint32_t sourceProof[]={1,bootDDI,uint32_t(protocol),uint32_t(AlderLakeN::linearPlan( result,bootPlan,bootDDI,protocol,bootDDI>=3?&tc:nullptr)),tc.valid()}; - pci->setProperty("N97NativeBootSource",sourceProof,sizeof(sourceProof)); - const bool prefix=Display13Boot::reservedPrefix(io,result,bootstrapReady,bootDDI,xelpMemory, - bootDDI>=3 ? &tc : nullptr,protocol); + if(!board.dsi)pci->setProperty("N97NativeBootSource",sourceProof,sizeof(sourceProof)); + const bool prefix=board.dsi ? bootstrapReady && XELP::DSI::verifyBootPrefix(io,dsiBoot,xelpMemory) : + Display13Boot::reservedPrefix(io,result,bootstrapReady,bootDDI,xelpMemory, + bootDDI>=3 ? &tc : nullptr,protocol); + if(board.dsi)pci->setProperty("WEXDSIBootPrefix",&dsiBoot,sizeof(dsiBoot)); auto aperture=pci->getDeviceMemoryWithRegister(0x18); const bool memoryReady= aperture && aperture->getLength()==0x10000000 && page_size==4096 && @@ -8271,6 +8290,13 @@ bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { void delay(){IODelay(100);} } io{reinterpret_cast(map->getVirtualAddress()),map->getLength()}; const bool mapped=io.base && io.bytes==0x1000000; + if(adlnOutputWiring.dsi) { + // Preserve the DSI GOP exactly. Native power/path adoption follows; + // normal-plane reinitialization would destroy the inherited source. + const bool ready=mapped && dsiBoot.complete() && XELP::DSI::verifyBootPrefix(io,dsiBoot,xelpMemory); + pci->setProperty("WEXDSIBootHandoff",&dsiBoot,sizeof(dsiBoot)); + map->release();return ready; + } const auto boot=AlderLakeN::readDisplay(io,mapped); uint32_t trans=UINT32_MAX;AlderLakeN::value(boot,0x60400,trans); const auto ddi=Display13FBTopology::bootstrapDDI(adlnOutputWiring, diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index a0f0f5f9..595338df 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -9,6 +9,7 @@ #define kern_xe_hpp #include "kern_tgl_tc_boot.hpp" +#include "kern_dsi_boot.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" @@ -131,6 +132,10 @@ class XE { // Display handoff needs GOP/GGTT, mapper and aperture qualification, // independently of GT fuses or an accelerator being allowed to start. bool display13BootReady {false}; + OSData *dsiFirmware {}; // Private immutable copy backing profile/sequence spans. + XELP::DSI::Profile dsiProfile; + XELP::DSI::Boot dsiBoot; + bool prepareDSIFirmware(IOService *); // Render additionally requires the native GT resource certificate. bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; diff --git a/tests/dsi_boot.cpp b/tests/dsi_boot.cpp new file mode 100644 index 00000000..2cfd7eaf --- /dev/null +++ b/tests/dsi_boot.cpp @@ -0,0 +1,82 @@ +#include "kern_dsi_boot.hpp" +#include "kern_display13_boot.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +struct IO { + std::map regs; + std::vector reads,writes; + XELPMemory::Layout memory=XELPMemory::decode(0x03C1,0x7A800000,0); + uint32_t ptes=0,badPTE=UINT32_MAX,missing=UINT32_MAX,changeAt=UINT32_MAX,changeReg=0; + bool driftOnWrite=false,strict=false; + IO(){ + assert(XELPMemory::reservation(memory,memory.base+memory.bytes-0x100000)); + regs={{0x7B008,0xC0000000},{0x6B400,0x80030006},{0x45404,0xFC0F},{0x70008,0}, + {0x6B030,0x20130300},{0x6B094,1},{0x6B404,0},{0x6B034,0},{0x64000,0x80000000},{0x164280,0x1E07801}, + {0x46014,0xCC000000},{0x16428C,0x3001FB},{0x164290,0xE84}, + {0x6B000,0x04650437},{0x6B008,0x044F044D},{0x6B00C,0x07D1077F},{0x6B014,0x07A907A7}, + {0x51004,0x40000000},{0x70180,0x94000008},{0x70188,68},{0x70190,0x077F0437}}; + } + bool readable(uint32_t reg){return reg<0x1000000 && reg!=missing && (!strict || regs.count(reg) || reg>=0x800000);} + uint32_t read(uint32_t reg){assert(readable(reg));reads.push_back(reg);return regs[reg];} + uint64_t readPTE(uint32_t reg){ + assert(reg==0x800000+8*ptes);const auto index=ptes++; + if(index==changeAt)regs[changeReg]^=1; + return memory.pte(uint64_t(index)*4096)^(index==badPTE?4096:0); + } + void write(uint32_t reg,uint32_t v){writes.push_back(reg);regs[reg]=v;if(driftOnWrite)regs[0x6B000]^=1;} +}; +Profile profile(){Profile p;p.status=ProfileStatus::Valid;p.lanes=4;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154};return p;} +Boot capture(IO &io){return captureBoot(io,AlderLakeN::readDisplay(io,true),profile(),io.memory,true);} +int main(int argc,char **argv){ + IO io;auto boot=capture(io);assert(boot.status==BootStatus::Candidate && !boot.complete()); + assert(boot.geometry.width==1080 && boot.geometry.height==1920 && boot.geometry.pitch==4352 && boot.dpt==0); + assert(verifyBootPrefix(io,boot,io.memory) && boot.complete() && boot.verifiedPages==io.memory.bytes/4096); + assert(io.writes.empty() && io.ptes==boot.verifiedPages); + // Candidate capture accepts no unsupported active peer or scaler/DSC path. + for(auto change:std::map{{0x7B808,0x80000000},{0x71008,0x80000000},{0x61400,0x80000000}, + {0x70080,1},{0x70280,0x80000000},{0x70780,0x80000000},{0x71180,0x80000000}, + {0x68180,0x80000000},{0x78000,0x80000000},{0x70B08,1},{0x71D08,0x80000000}, + {0x70180,0x94000408},{0x7018C,1},{0x701A4,1},{0x701AC,4096},{0x7019C,1},{0x70188,16}, + {0x6B000,0x04650438},{0x51004,0},{0x42084,UINT32_MAX}}){ + IO bad;bad.regs[change.first]=change.second;assert(!capture(bad).usable() && !bad.ptes && bad.writes.empty()); + } + for(unsigned index:{0U,8191U,24575U}){ + IO bad;auto b=capture(bad);bad.badPTE=index;assert(!verifyBootPrefix(bad,b,bad.memory)); + assert(b.status==BootStatus::PTE && b.verifiedPages==index && bad.ptes==index+1 && bad.writes.empty()); + } + for(auto reg:{0x70180U,0x70188U,0x70190U,0x7019CU,0x701ACU,0x42084U,0x6B000U,0x164280U,0x45404U}){ + IO bad;auto b=capture(bad);bad.changeAt=4;bad.changeReg=reg;bad.reads.clear(); + assert(!verifyBootPrefix(bad,b,bad.memory) && b.status==BootStatus::Changed && bad.writes.empty()); + if(reg==0x45404)assert(bad.reads.back()==0x45404); // no DSI/pipe read after power loss + } + for(auto reg:{0x70B08U,0x7B808U,0x701ACU,0x51004U}){IO bad;bad.missing=reg;assert(!capture(bad).usable());} + IO invalid;invalid.memory.reservationKnown=false;assert(capture(invalid).status==BootStatus::Memory); + IO changed;auto b=capture(changed);auto m=changed.memory;m.allocationEnd-=4096; + assert(!verifyBootPrefix(changed,b,m) && !changed.ptes); + // The existing unused-TC cleanup must also retain a DSI source witness. + for(bool drift:{false,true}){ + IO tc;Display13OutputWiring w;w.valid=true;w.dsi=1;w.hdmi=8;w.ddc[3]=9; + tc.regs[0x64300]=0xC0;tc.regs[0x161500]=0xC;tc.regs[0x46110]=0; + auto b=capture(tc);assert(b.usable());auto normal=AlderLakeN::readDisplay(tc,true); + tc.driftOnWrite=drift;auto result=Display13Boot::releaseUnusedTC(tc,normal,true,w,&b.source); + if(drift)assert(result.status==Display13Boot::Status::Readback && tc.writes.size()==1); + else assert(result.status==Display13Boot::Status::Complete && result.cleared==8 && tc.regs[0x64300]==0x80); + assert(tc.regs[0x70180]==b.control && tc.regs[0x7019C]==b.surface && tc.regs[0x164280]==b.source.mux); + } + if(argc==2){ + IO saved;saved.regs.clear();saved.strict=true;std::ifstream f(argv[1]);assert(f);uint32_t a,v; + while(f>>std::hex>>a>>v)assert(saved.regs.emplace(a,v).second);assert(f.eof()); + // The saved report omits DSB CTRL and real PTEs. Supply explicit synthetic + // idle DSBs/PTEs for this geometry replay; never call it a live certificate. + for(unsigned p=0;p<3;++p)for(unsigned d=0;d<3;++d)saved.regs.emplace(0x70B08+0x1000*p+0x100*d,0); + auto actual=capture(saved);assert(actual.usable()); + assert(actual.geometry.width==1080 && actual.geometry.height==1920 && actual.geometry.pitch==4352); + assert(verifyBootPrefix(saved,actual,saved.memory)); + puts("saved GOP register geometry accepted; DSB idle and stolen PTEs are synthetic, not collected evidence"); + } + puts("PASS DSI GOP certificate: native portrait pitch, full prefix, peer exclusion, source drift, unused TC isolation; no scanout/PTE writes"); +} diff --git a/tests/dsi_boot_adapter.py b/tests/dsi_boot_adapter.py new file mode 100644 index 00000000..61aef631 --- /dev/null +++ b/tests/dsi_boot_adapter.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Execute production firmware-copy and DSI pre-native handoff with mock IOKit.""" +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',source,re.S)[0] + for name in ['prepareDSIFirmware','prepareDisplay13BootScanout']) +fixture=(root/'tests/dsi_profile.cpp').read_text() +fixture=fixture[fixture.index('using Bytes='):fixture.index('Profile parse(')] +code=r''' +#include "kern_dsi_boot.hpp" +#include "kern_display13_boot.hpp" +#include +#include +#include +using namespace XELP::DSI; +FIXTURE +bool copyOK=true,deviceOK=true,memoryOK=true;unsigned alive=0,releases=0; +struct OSObject {OSObject(){++alive;}virtual ~OSObject(){--alive;}void release(){delete this;}}; +struct OSData:OSObject { + std::vector bytes; + unsigned getLength(){return bytes.size();}void *getBytesNoCopy(){return bytes.data();} + static OSData *withBytes(const void *p,unsigned n){if(!copyOK)return nullptr;auto d=new OSData; + d->bytes.assign(static_cast(p),static_cast(p)+n);return d;} +}; +struct IOService {OSObject *property=nullptr;OSObject *getProperty(const char *){return property;} + void setProperty(const char *,const void *,unsigned){};}; +struct IOMemoryMap { + std::vector bytes=std::vector(0x1000000); + uintptr_t getVirtualAddress(){return reinterpret_cast(bytes.data());} + size_t getLength(){return bytes.size();}void release(){++releases;} +}; +struct IOPCIDevice:IOService {IOMemoryMap *map;bool mapped=true;unsigned command=6; + uint16_t configRead16(unsigned o){assert(o==4);return command;} + IOMemoryMap *mapDeviceMemoryWithRegister(unsigned reg,unsigned){assert(reg==0x10);return mapped?map:nullptr;} +}; +struct IORegistryEntry:IOService {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry p;return &p;}}; +constexpr unsigned kIOMapInhibitCache=1; +void IODelay(unsigned){assert(false && "preserved DSI handoff must not poll or reinitialize");} +#define SYSLOG(...) +#define OSDynamicCast(T,p) dynamic_cast(p) +struct XE { + OSData *dsiFirmware=nullptr;Profile dsiProfile;Boot dsiBoot;bool display13BootReady=true; + XELPMemory::Layout xelpMemory;Display13OutputWiring adlnOutputWiring; + bool xelpDisplay13Device(IOPCIDevice *){return deviceOK;} + bool xelpMemoryMatches(IOPCIDevice *){return memoryOK;} + bool prepareDSIFirmware(IOService *); + bool prepareDisplay13BootScanout(IOPCIDevice *); +}; +FUNCTIONS +struct IO { + IOMemoryMap ↦ + bool readable(uint32_t r){return r(xe.dsiFirmware->getBytesNoCopy()); + for(auto span:xe.dsiProfile.sequences.sequence)if(span.data)assert(span.data>=start && span.data+span.size<=start+bytes.size()); + auto source=static_cast(provider.property);source->bytes[0]^=1; + assert(!xe.prepareDSIFirmware(&provider) && xe.dsiProfile.valid() && start[0]=='$'); + source->release();provider.property=nullptr; + assert(xe.dsiProfile.mode.width==1080 && xe.dsiProfile.sequences.sequence[1].data[0]==0); + IOMemoryMap map;IOPCIDevice pci;pci.map=↦IO io{map}; + xe.xelpMemory=XELPMemory::decode(0x03C1,0x7A800000,0); + assert(XELPMemory::reservation(xe.xelpMemory,xe.xelpMemory.base+xe.xelpMemory.bytes-0x100000)); + const uint32_t regs[][2]={{0x7B008,0xC0000000},{0x6B400,0x80030006},{0x45404,0xFC0F}, + {0x6B030,0x20130300},{0x6B094,1},{0x64000,0x80000000},{0x164280,0x1E07801}, + {0x46014,0xCC000000},{0x16428C,0x3001FB},{0x164290,0xE84}, + {0x6B000,0x04650437},{0x6B008,0x044F044D},{0x6B00C,0x07D1077F},{0x6B014,0x07A907A7}, + {0x51004,0x40000000},{0x70180,0x94000008},{0x70188,68},{0x70190,0x077F0437}}; + for(auto &r:regs)io.word(r[0],r[1]); + for(unsigned i=0;irelease();assert(!alive); + puts("PASS production DSI bootstrap: immutable firmware spans, stale firmware rejection, full-prefix prerequisite, zero-write handoff and map cleanup"); +} +'''.replace('FIXTURE',fixture).replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-boot-adapter-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index a65050ab..de45ff2d 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] From bc307e44ecc393005a828ce2cc5f3235c3440cb5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:49:05 +0900 Subject: [PATCH 024/139] Bind qualified DSI boot resources and transcoder-aware DBUF --- WhateverXe/kern_display13_dbuf.hpp | 27 +++++---- WhateverXe/kern_xe.cpp | 90 +++++++++++++++++++++++++---- WhateverXe/kern_xe.hpp | 3 + tests/adlp_fb_candidate.py | 5 ++ tests/display13_dbuf.cpp | 13 +++++ tests/dsi_port_adapter.py | 6 +- tests/dsi_resources.py | 91 ++++++++++++++++++++++++++++++ tests/framebuffer_common.py | 2 +- tools/test.py | 2 +- 9 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 tests/dsi_resources.py diff --git a/WhateverXe/kern_display13_dbuf.hpp b/WhateverXe/kern_display13_dbuf.hpp index 9ad9f8dd..5441d9b9 100644 --- a/WhateverXe/kern_display13_dbuf.hpp +++ b/WhateverXe/kern_display13_dbuf.hpp @@ -32,6 +32,7 @@ struct PipeAllocation { bool pendingCursor {}; }; struct State { + Display13Transcoder::Bindings transcoders; bool begun {},faulted {},joined {}; uint32_t initial[4] {},raw[4] {},initialRequests {},requested {},powered {},owned {},consumers {},mbus {}; PipeAllocation pipe[3]; @@ -129,7 +130,7 @@ template Layout singlePipeLayout(IO &io,const State &s) { if(s.joined || io.read(0x4438C)!=s.mbus || (wells&(3U<<16))) {r.status=Status::Topology;return r;} for(unsigned pipe=0;pipe<3;++pipe) { if(!(wells&(1U<<(2*(5+pipe)))))continue; - const auto conf=io.read(0x70008+pipe*0x1000); + const auto conf=Display13Transcoder::control(io,pipe,s.transcoders); if(conf==UINT32_MAX || (pipe && (conf&(Request|On)))) {r.status=Status::Topology;return r;} if(!pipe) { // Transitional enable/disable must settle before changing DDB. @@ -159,9 +160,10 @@ template Layout pipeLayout(IO &io,const State &s,unsigned index) { if(s.updating&(1U< Layout pipeLayout(IO &io,const State &s,unsigned index) { } // Read the measured boot pipe A allocation, not a Linux allocation copied into // the hardware. All four physical slices exist; a single unjoined A uses S0/1. -template Status begin(IO &io,State &s,uint32_t capacity=0) { +template Status begin(IO &io,State &s,uint32_t capacity=0,const Display13Transcoder::Bindings &transcoders={}) { if(s.faulted)return Status::Faulted; - if(s.begun)return capacity==s.capacity ? Status::Complete : Status::Topology; + if(s.begun)return capacity==s.capacity && transcoders.dsi0Pipe==s.transcoders.dsi0Pipe ? Status::Complete : Status::Topology; + if(!transcoders.valid() || (transcoders.dsi0Pipe!=UINT32_MAX && transcoders.dsi0Pipe!=0))return Status::Topology; + s.transcoders=transcoders; if(capacity && capacity!=7)return Status::Topology; s.capacity=capacity; if(!io.readable(0x4438C) || !io.readable(0x7027C) || !io.readable(0x7017C))return Status::Unavailable; const auto wells=io.read(0x45404); if(wells==UINT32_MAX || (wells&0x403)!=0x403)return fail(s,Status::Power); if(wells&(3U<<16))return fail(s,Status::Topology); // Pipe D is outside this native owner. - if((io.read(0x70008)&0xC0000000)!=0xC0000000 || !(io.read(0x70180)&Request))return fail(s,Status::Topology); + if((Display13Transcoder::control(io,0,s.transcoders)&0xC0000000)!=0xC0000000 || !(io.read(0x70180)&Request))return fail(s,Status::Topology); s.mbus=io.read(0x4438C);s.joined=s.mbus&Request; if(s.mbus==UINT32_MAX || (s.joined && (((s.mbus>>26)&7)!=0 || !(s.mbus&On))))return fail(s,Status::Topology); const auto primary=io.read(0x7027C),cursor=io.read(0x7017C); @@ -210,7 +214,7 @@ template Status begin(IO &io,State &s,uint32_t capacity=0) { // the allocation or slice lifetime calculation. for(unsigned pipe=0;pipe<3;++pipe) { if(!(wells&(1U<<(2*(5+pipe)))))continue; - const auto conf=io.read(0x70008+pipe*0x1000); + const auto conf=Display13Transcoder::control(io,pipe,s.transcoders); if(conf==UINT32_MAX || (pipe && (conf&Request)))return fail(s,Status::Topology); for(unsigned plane=pipe ? 0 : 1;plane<5;++plane) { const auto ctl=io.read(0x70180+pipe*0x1000+plane*0x100); @@ -302,7 +306,7 @@ template Status bindOffPipe(IO &io,State &s,unsigned index) { const auto bit=1U< Status resumeEngine(IO &io,State &s) { if(a.primary.start || a.primary.end || a.cursor.start || a.cursor.end || a.heldPrimary.start || a.heldPrimary.end || a.heldCursor.start || a.heldCursor.end || a.pendingRaw || a.pendingCursor)return fail(s,Status::Conflict); - if(!Display13FBIRQ::consumersStopped(io,7))return fail(s,Status::Conflict); + if(!Display13FBIRQ::consumersStopped(io,7,s.transcoders))return fail(s,Status::Conflict); return power(io,s,0,0); } // STOP_ACK, disabled planes/cursor and idle DSB prove only this pipe stopped. @@ -397,7 +401,7 @@ template Status stopPipe(IO &io,State &s,unsigned index) { const auto bit=1U< Status detach(IO &io,State &s,unsigned index) { if(!s.begun || index>=3 || !(s.selected&(1U< Status stop(IO &io,State &s) { if(s.updating)return fail(s,Status::Conflict); const auto wells=io.read(0x45404); if(wells==UINT32_MAX || (wells&3)!=3 || (wells&(3U<<16)))return fail(s,Status::Power); + if(s.transcoders.dsi0Pipe<3 && !Display13FBIRQ::consumersStopped(io,1U<getMetaClass()->getClassSize()<0x6B0)return 0; + auto config=Display13FBMemory::field(port,0x548); + if(self->adlnOutputWiring.dsi && config && config[0]==0) + return !display13Offline && __atomic_load_n(&self->dsiResourcesReady,__ATOMIC_ACQUIRE) && setDSIPortMode(port,1)==kIOReturnSuccess ? 1U : 0U; + return self->xelpProviderProbe ? reinterpret_cast(self->xelpProviderProbe)(port) : 0; +} + uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { auto s=callbackXE; if(!s->display13FBTopologyHooksReady || !s->adlnTopologyController || !port || port->getMetaClass()->getClassSize()<0x580)return 0; @@ -9893,7 +9905,7 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { !c.bridge.state.suspended && acquired.status==Display13Power::Status::Complete && !c.clockFaulted && clock.status==Display13Clock::Status::Complete && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { Display13FBDBUFIO io {c.map}; - status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnOutputWiring.selected())>=3 ? 7U : 0U); + status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnOutputWiring.selected())>=3 ? 7U : 0U,c.transcoders); if(status==Display13DBUF::Status::Complete) { if(acquired.status==Display13Power::Status::Complete)status=Display13DBUF::resumeEngine(io,c.dbuf); else status=Display13DBUF::Status::Power; @@ -10316,6 +10328,41 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo void XE::wrapAlderLakeNDefaultDBUF(IOService *controller) {adlnSetupDBUF(controller,false);} void XE::wrapAlderLakeNOptimizedDBUF(IOService *controller) {adlnSetupDBUF(controller,true);} +// Called outside the owner lock, before native engine/probe/IRQ users start. +// The lock order matches clock clients: Combo clock -> display owner -> IRQ. +// Failure retains any already acquired boot power references for cleanup. +bool XE::bindDSIResources(IOService *controller) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; + if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || + !display13FBIRQReady || !clock.lock || !c.lock)return false; + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + Display13FBBootIO io{c.map}; + const bool owned=adlnOutputWiring.dsi==1 && dsiProfile.valid() && dsiBoot.complete() && + c.controller==controller && controller==adlnTopologyController && !clock.thread && + !dsiResourcesReady && c.transcoders.dsi0Pipe==UINT32_MAX && + !c.engineEnabled && !c.dbuf.begun && !c.memoryFaulted && !c.clockFaulted && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + auto irqLock=Display13FBMemory::field(controller,0x90); + bool ready=false; + if(owned && irqLock) { + XELPComboClock::ClockSnapshot snapshot;snapshot.layout=XELPComboClock::Layout::Display13; + snapshot.mux[0]=io.read(0x164280); + for(unsigned p=0;p(controller),snapshot)) { + c.bootDSI=dsiBoot.source;c.transcoders.dsi0Pipe=0; + irq.state.transcoders=c.transcoders;irq.base=c.map->getVirtualAddress();irq.lock=irqLock; + __atomic_store_n(&irq.admittedPipes,1U,__ATOMIC_RELEASE); + __atomic_store_n(&irq.controller,controller,__ATOMIC_RELEASE); + __atomic_store_n(&dsiResourcesReady,true,__ATOMIC_RELEASE);ready=true; + } + IOSimpleLockUnlockEnableInterrupt(irqLock,saved); + } + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock);return ready; +} + void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { auto &c=display13FBPower; if(!c.lock)return; uint64_t record[32] {}; Display13Power::Result result; @@ -10338,16 +10385,23 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { // Reference failures retain mappings/objects for diagnosis until reboot. service->retain();static_cast(power)->retain();static_cast(c.access)->retain(); 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 auto wells=io.read(Display13Power::Driver); + const bool pipePowered=Display13FBIRQ::powered(wells,0); + const auto a=pipePowered ? io.read(0x60400) : UINT32_MAX; + const auto pipeA=pipePowered ? io.read(0x70008) : UINT32_MAX; + const bool dsi=callbackXE->adlnOutputWiring.dsi==1; + const auto bootPort=dsi ? 0U : Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); const bool dpBoot=bootPort<7 && (callbackXE->adlnOutputWiring.links()&(1U<dsiBoot.complete() && + XELP::DSI::bootUnchanged(sourceIO,callbackXE->dsiBoot,xelpMemory) : + bootPort<7 && wells!=UINT32_MAX && (wells&(1U<<10)) && pipeA!=UINT32_MAX && ddi!=UINT32_MAX && (pipeA&(1U<<31)) && (ddi&(1U<<31)); - for(unsigned pipe=1;pipe<3;++pipe) if(wells&(1U<<(2*(5+pipe)))) { + for(unsigned pipe=1;boot && pipe<3;++pipe) if(Display13FBIRQ::powered(wells,pipe)) { const auto trans=io.read(0x60400+pipe*0x1000); boot=boot && trans!=UINT32_MAX && !(trans&(1U<<31)); } @@ -10373,6 +10427,12 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,power,0x80,controller,0xC60)) { result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__); } + if(result.status==Display13Power::Status::Complete && dsi) { + IOLockUnlock(c.lock); + const bool bound=callbackXE->bindDSIResources(service); + IOLockLock(c.lock); + if(!bound){result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} + } if(result.status==Display13Power::Status::Complete) { // Initial discovery can call the virtual probe directly without // probePortState. Bind at the proven manager/power-owner boundary @@ -10381,7 +10441,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeBound",bound); if(!bound){result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} } - if(result.status==Display13Power::Status::Complete && callbackXE->display13FBIRQReady) { + if(result.status==Display13Power::Status::Complete && !dsi && callbackXE->display13FBIRQReady) { auto irqLock=*reinterpret_cast(static_cast(controller)+0x90); if(irqLock) { display13FBIRQ.base=map->getVirtualAddress();display13FBIRQ.lock=irqLock; @@ -10394,6 +10454,16 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { if(result.status==Display13Power::Status::Complete && dpBoot && bootPort>=3)tcBootDDI=bootPort; } else OSSafeReleaseNULL(map); } + if(result.status==Display13Power::Status::Complete && callbackXE->adlnOutputWiring.dsi) { + auto getPort=reinterpret_cast(callbackXE->tglPortByDDI); + auto port=getPort?getPort(0):nullptr; + bool adopted=false; + if(port) { + port->retain();IOLockUnlock(c.lock);adopted=setDSIPortMode(port,1)==kIOReturnSuccess; + IOLockLock(c.lock);port->release(); + } + if(!adopted){result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} + } if(tcBootDDI<7) { // Exact donor start initializes every port before PowerWell::init. // Establish the same durable Mode used by probing/hotplug before any @@ -11828,7 +11898,7 @@ bool XE::processFramebufferCommon(KernelPatcher &patcher,size_t index,mach_vm_ad patcher.clearError(); if(probe) { // Slot repair is common; the target retains the existing hardware adapter. - auto replacement=display13FramebufferRequested ? xelpProviderProbe : reinterpret_cast(wrapTGLProbePort); + auto replacement=display13FramebufferRequested ? reinterpret_cast(wrapDisplay13ProbeMode) : reinterpret_cast(wrapTGLProbePort); patcher.routeBlock(table+0x188,reinterpret_cast(&replacement),sizeof(replacement)); probe=patcher.getError()==KernelPatcher::Error::NoError && *reinterpret_cast(table+0x188)==replacement; patcher.clearError(); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 595338df..1aa8e41b 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -136,6 +136,9 @@ class XE { XELP::DSI::Profile dsiProfile; XELP::DSI::Boot dsiBoot; bool prepareDSIFirmware(IOService *); + bool dsiResourcesReady {}; + bool bindDSIResources(IOService *); + static uint32_t wrapDisplay13ProbeMode(OSObject *); // Render additionally requires the native GT resource certificate. bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 86d36dad..025b5517 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -23,6 +23,7 @@ XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; bool display13PanelProbeReady=true,display13PanelPowerReady=true; + bool dsiResourcesReady=false; bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; bool display13DPAltProbeReady=true,display13TCDPProbeReady=true,display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); @@ -35,6 +36,10 @@ }; FUNCTIONS int main(){ + XE dsi;dsi.adlnOutputWiring.valid=true;dsi.adlnOutputWiring.dsi=1; + dsi.adlnOutputWiring.hdmi=2;dsi.adlnOutputWiring.ddc[1]=2; + assert(!dsi.xelpBindProbe((void*)1));dsi.dsiResourcesReady=true; + assert(dsi.xelpBindProbe((void*)1) && seen.combo==2); XE x;IOService pci;x.firmwarePorts.status=XELP::VBT::Status::Valid;x.firmwarePorts.present=0x38; for(unsigned d=3;d<=5;++d){auto &p=x.firmwarePorts.port[d];p.type=d==5?4:0x12;p.ddc=d;p.capabilities=3;p.hdmiPreset=7;} auto w=x.display13BoardWiring(&pci);assert(w.described() && w.hdmi==0x18 && w.ddc[3]==9 && w.ddc[4]==10 && !w.dp); diff --git a/tests/display13_dbuf.cpp b/tests/display13_dbuf.cpp index 7439e7a0..6551e7eb 100644 --- a/tests/display13_dbuf.cpp +++ b/tests/display13_dbuf.cpp @@ -31,6 +31,19 @@ struct IO { void delayUs(unsigned us) {delays+=us;} }; int main() { + for(unsigned failure=0;failure<5;++failure) { + IO io;State state;Display13Transcoder::Bindings dsi;dsi.dsi0Pipe=0; + io.r[0x70008]=0;io.r[0x7B008]=0xC0000000;io.r[0x6B400]=0x80030006; + assert(begin(io,state,0,dsi)==Status::Complete && !io.writes); + assert(begin(io,state)==Status::Topology); // cannot erase the active mapping + assert(singlePipeLayout(io,state).active && pipeLayout(io,state,0).active); + if(failure==0)assert(stopPipe(io,state,0)==Status::Conflict && state.pipe[0].consumers); + if(failure==1)assert(stop(io,state)==Status::Conflict && state.pipe[0].consumers); + if(failure==2){io.r[0x6B400]|=5U<<12;assert(singlePipeLayout(io,state).status==Status::Topology);} + if(failure==3){io.r[0x7B008]=0x80000000;assert(pipeLayout(io,state,0).status==Status::Conflict);} + if(failure==4){io.r[0x7B008]=0;io.r[0x6B400]=0;io.r[0x70180]=0; + assert(stopPipe(io,state,0)==Status::Complete && !state.pipe[0].consumers);} + } // Independent Linux/MBUS examples: absolute ownership and relative plane // registers intentionally differ on B, while A remains byte-identical. State address; diff --git a/tests/dsi_port_adapter.py b/tests/dsi_port_adapter.py index dfebe755..ffc91d9b 100644 --- a/tests/dsi_port_adapter.py +++ b/tests/dsi_port_adapter.py @@ -9,7 +9,7 @@ root=Path(__file__).resolve().parents[1] source=(root/'WhateverXe/kern_xe.cpp').read_text() functions='\n'.join(re.search(r'uint32_t XE::'+name+r'\(.*?\n\}',source,re.S)[0] - for name in ['setDSIPortMode','wrapAlderLakeNPortState']) + for name in ['setDSIPortMode','wrapAlderLakeNPortState','wrapDisplay13ProbeMode']) dispatch=re.search(r'uint32_t XE::wrapDisplay13TCMode\(.*?\n\}',source,re.S)[0] assert dispatch.index('return setDSIPortMode(port,wanted)')orgDisplay13TCMode') donor=None @@ -50,12 +50,14 @@ uint32_t nativeProbe(OSObject *){++nativeCalls;return 1;} uint32_t baseMode(OSObject *p,uint32_t m){assert(m==1);++modeCalls;Display13FBMemory::field(p,0x550)=m;return 0;} struct XE { + bool dsiResourcesReady=false;uintptr_t xelpProviderProbe=reinterpret_cast(nativeProbe); bool display13FBTCHooksReady=true,display13FBTopologyHooksReady=true; uintptr_t display13TCBaseMode=reinterpret_cast(baseMode); void *adlnTopologyController;alignas(8) uint8_t adlnPlatform[0x1C0]{};Display13OutputWiring adlnOutputWiring; uint32_t (*orgAlderLakeNPortState)(OSObject*)=nativeProbe; bool xelpBindProbe(void *){++providerCalls;return true;} static uint32_t setDSIPortMode(OSObject *,uint32_t); + static uint32_t wrapDisplay13ProbeMode(OSObject *); static uint32_t wrapAlderLakeNPortState(OSObject *); } xe,*callbackXE=&xe; bool display13Offline=false; @@ -110,6 +112,8 @@ reject();Display13FBMemory::field(&port,0x548)=config; assert(XE::setDSIPortMode(&port,0)==kIOReturnUnsupported && !c.dsiPort); display13Offline=true;assert(!XE::wrapAlderLakeNPortState(&port) && !c.dsiPort);display13Offline=false; + assert(!XE::wrapDisplay13ProbeMode(&port));xe.dsiResourcesReady=true; + assert(XE::wrapDisplay13ProbeMode(&port)==1 && !nativeCalls && !providerCalls); assert(XE::wrapAlderLakeNPortState(&port)==2 && !nativeCalls && !providerCalls); assert(Display13FBMemory::field(&port,0x550)==1 && Display13FBMemory::field(&port,0x554)==2); assert(c.dsiPort==&port && port.refs==2); diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py new file mode 100644 index 00000000..a9900ac5 --- /dev/null +++ b/tests/dsi_resources.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Replay the production DSI resource publication boundary and lock order.""" +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'bool XE::bindDSIResources\(.*?\n\}',source,re.S)[0] +boot=(root/'tests/dsi_boot.cpp').read_text() +io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] +code=r''' +#include "kern_dsi_boot.hpp" +#include "kern_display13_power_bridge.hpp" +#include "kern_display13_fb_irq.hpp" +#include "kern_xelp_combo_clock.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +unsigned lockLevel=0; +struct IORecursiveLock{};struct IOLock{};struct IOSimpleLock{}; +void IORecursiveLockLock(IORecursiveLock*){assert(lockLevel==0);lockLevel=1;} +void IORecursiveLockUnlock(IORecursiveLock*){assert(lockLevel==1);lockLevel=0;} +void IOLockLock(IOLock*){assert(lockLevel==1);lockLevel=2;} +void IOLockUnlock(IOLock*){assert(lockLevel==2);lockLevel=1;} +bool IOSimpleLockLockDisableInterrupt(IOSimpleLock*){assert(lockLevel==2);lockLevel=3;return true;} +void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,bool saved){assert(lockLevel==3 && saved);lockLevel=2;} +struct IOService {alignas(8) uint8_t bytes[0x1C20]{};IOService *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);}}; +namespace Display13FBMemory {templateT field(const void *p,unsigned o){T v;memcpy(&v,static_cast(p)+o,sizeof(v));return v;}} +struct Map:IO {uintptr_t getVirtualAddress(){return 0x1000000;}}; +struct Display13FBBootIO { + Map *map;bool readable(uint32_t r){return map && map->readable(r);} + uint32_t read(uint32_t r){return map->read(r);} +}; +struct {IORecursiveLock *lock;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;} comboClockTransaction; +struct {IOLock *lock;IOService *controller;Map *map;Display13Transcoder::Bindings transcoders; + Readout bootDSI;Display13Power::NativeBridge bridge; + bool engineEnabled=false,memoryFaulted=false,clockFaulted=false;struct {bool begun=false;} dbuf;} display13FBPower; +struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; + unsigned handlers=0,softwareHandlers=0,admittedPipes=0;bool quiescing=false;} display13FBIRQ; +XELPMemory::Layout xelpMemory; +struct XE { + bool display13FBIRQReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; + struct {unsigned dsi=1;} adlnOutputWiring; + Profile dsiProfile;Boot dsiBoot; + bool bindDSIResources(IOService *); +}; +BODY +int main(){ + IORecursiveLock clockLock;IOLock ownerLock;IOSimpleLock irqLock;IOService controller;Map map; + auto ptr=&irqLock;memcpy(controller.bytes+0x90,&ptr,sizeof(ptr)); + auto &c=display13FBPower;auto &irq=display13FBIRQ;auto &clock=comboClockTransaction; + c.lock=&ownerLock;c.controller=&controller;c.map=↦clock.lock=&clockLock; + XE xe;xe.adlnTopologyController=&controller;xe.dsiProfile=profile();xelpMemory=map.memory; + xe.dsiBoot=captureBoot(map,AlderLakeN::readDisplay(map,true),xe.dsiProfile,map.memory,true); + assert(verifyBootPrefix(map,xe.dsiBoot,map.memory)); + map.regs[0x45454]=3;map.regs[0x42000]=0x8E380000; + auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;s.ready=true;s.allowed=Display13Power::All; + const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)); + const auto ddi=Display13Power::dependencies(Display13Power::ddiDomain(0));s.borrowed=pg|ddi; + for(unsigned i=0;i(&controller) && clock.allocation.binding[0]==2); + assert(!xe.bindDSIResources(&controller) && !lockLevel && !map.writes.size()); + assert(b.pgRefs[0]==1 && b.ddiRefs[0]==1); // publication did not acquire/release someone else's lease + puts("PASS production DSI resources: clock-owner-IRQ lock order, retained leases/source, PLL ownership, atomic mapping publication and rejection before publication"); +} +'''.replace('IO_FIXTURE',io).replace('BODY',body) +# No kernel/Apple object contents are loaded; these leaves model publication only. +with tempfile.TemporaryDirectory(prefix='dsi-resources-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index b7d06d06..6eff1f95 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -81,7 +81,7 @@ static bool wrapFramebufferStart(IOService*,IOService*); static bool wrapDisplay13FramebufferStart(IOService*,IOService*); static bool wrapTGLFramebufferStart(IOService*,IOService*); - static void wrapTGLProbePort(){}static void wrapFramebufferInit(){}static void wrapFramebufferComputeLanes(){} + static void wrapDisplay13ProbeMode(){}static void wrapTGLProbePort(){}static void wrapFramebufferInit(){}static void wrapFramebufferComputeLanes(){} }; XE *callbackXE; bool XE::wrapDisplay13FramebufferStart(IOService*,IOService*){assert(callbackXE->display13FramebufferRequested);assert(!callbackXE->framebufferStartedController);if(callbackXE->stopDuringStart)callbackXE->tglLeasesStopping=true;++callbackXE->startCalls;return callbackXE->nativeOK;} diff --git a/tools/test.py b/tools/test.py index de45ff2d..190e2dce 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] From b7b82109dc19b41ed6895616464018ed12a34df3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:55:59 +0900 Subject: [PATCH 025/139] Recognize qualified DSI GOP in native boot pipe discovery --- WhateverXe/kern_xe.cpp | 44 ++++++++++++++ WhateverXe/kern_xe.hpp | 4 ++ tests/dsi_boot_probe.py | 126 ++++++++++++++++++++++++++++++++++++++++ tools/test.py | 3 +- 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tests/dsi_boot_probe.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 1111c594..874d5183 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5460,6 +5460,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); + dsiBootProbeReady=processDisplay13DSIBoot(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); using C=XELP::DisplayContract;const auto installed=display13Available(); display13FBPowerBackendReady=C{C::Power13,installed}.ready(); @@ -6681,6 +6682,49 @@ bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { return true; } +bool XE::processDisplay13DSIBoot(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbol="__ZN31AppleIntelFramebufferController13probeBootPipeEPbPN17AppleIntelPortHAL3DDIE"; + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89}; + // setupBootDisplay consumes the returned pipe, builtin bool and DDI before + // allocating the real native path. Do not replace that allocation with a + // fabricated path. Its DSI cache/stop adapters remain separate prerequisites. + const uint8_t call[]={0xE8,0xA7,0x1D,0x00,0x00}; + if(!display13FramebufferRequested || size<=0x455E8)return false; + const auto fn=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(fn!=address+0x453D6 || memcmp(reinterpret_cast(fn),entry,sizeof(entry)) || + memcmp(reinterpret_cast(address+0x4362A),call,sizeof(call)))return false; + KernelPatcher::RouteRequest route(symbol,wrapDSIBootProbe,orgDSIBootProbe); + const bool ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError();return ready; +} + +uint32_t XE::wrapDSIBootProbe(IOService *controller,bool *builtin,uint32_t *ddi) { + auto self=callbackXE; + if(!self->adlnOutputWiring.dsi) + return FunctionCast(wrapDSIBootProbe,self->orgDSIBootProbe)(controller,builtin,ddi); + // Failure must not leak an old builtin/DDI result to setupBootDisplay. + if(builtin)*builtin=false;if(ddi)*ddi=UINT32_MAX; + auto &c=display13FBPower; + if(!self->dsiBootProbeReady || display13Offline || !c.lock || !controller || + controller->getMetaClass()->getClassSize()<0x1C20 || + !__atomic_load_n(&self->dsiResourcesReady,__ATOMIC_ACQUIRE))return 0xFFFF; + IOLockLock(c.lock);Display13FBBootIO io{c.map}; + auto port=c.dsiPort; + const auto config=port && port->getMetaClass()->getClassSize()>=0x6B0 ? + Display13FBMemory::field(port,0x548) : nullptr; + const bool valid=c.map && self->adlnOutputWiring.dsi==1 && controller==c.controller && + controller==self->adlnTopologyController && c.transcoders.dsi0Pipe==0 && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && + c.access && Display13FBTopology::bound(self->adlnPlatform,config,0) && + self->adlnOutputWiring.matches(config) && config[0]==0 && + Display13FBMemory::field(port,0x120)==0 && + Display13FBMemory::field(port,0x550)==1 && + Display13FBMemory::field(port,0x578)==c.access && + self->dsiBoot.complete() && c.bootDSI.sameSource(self->dsiBoot.source) && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,self->dsiBoot,xelpMemory); + if(valid){if(builtin)*builtin=true;if(ddi)*ddi=0;} + IOLockUnlock(c.lock);return valid?0U:0xFFFFU; +} + uint32_t XE::wrapDisplay13ProbeMode(OSObject *port) { auto self=callbackXE; if(!port || port->getMetaClass()->getClassSize()<0x6B0)return 0; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 1aa8e41b..84c2eb9e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -139,6 +139,10 @@ class XE { bool dsiResourcesReady {}; bool bindDSIResources(IOService *); static uint32_t wrapDisplay13ProbeMode(OSObject *); + bool dsiBootProbeReady {}; + mach_vm_address_t orgDSIBootProbe {}; + bool processDisplay13DSIBoot(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static uint32_t wrapDSIBootProbe(IOService *,bool *,uint32_t *); // Render additionally requires the native GT resource certificate. bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; diff --git a/tests/dsi_boot_probe.py b/tests/dsi_boot_probe.py new file mode 100644 index 00000000..6f701413 --- /dev/null +++ b/tests/dsi_boot_probe.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Qualify DSI boot recognition; optionally replay the exact native consumer.""" +from pathlib import Path +import hashlib, re, subprocess, sys, tempfile +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'uint32_t XE::wrapDSIBootProbe\(.*?\n\}',source,re.S)[0] +boot=(root/'tests/dsi_boot.cpp').read_text() +io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] +donor=Path(sys.argv[1]).resolve() if len(sys.argv)>1 else None +if donor: + b=donor.read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + installer=re.search(r'bool XE::processDisplay13DSIBoot\(.*?\n\}',source,re.S)[0] + for name,offset in [('entry',0x453d6),('call',0x4362a)]: + expected=bytes(int(x,0) for x in re.search(name+r'\[\]=\{([^}]+)',installer)[1].split(',')) + assert b[offset:offset+len(expected)]==expected + for at,target in [(0x4362a,0x453d6),(0x436bc,0x1b1ea),(0x436ea,0x66ea0)]: + assert b[at]==0xe8 and at+5+int.from_bytes(b[at+1:at+5],'little',signed=True)==target +code=r''' +#include "kern_dsi_boot.hpp" +#include "kern_display13_power_bridge.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_display13_transcoder.hpp" +#include +#include +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; +using IOService=OSObject; +struct IOLock {bool held=false;};void IOLockLock(IOLock *l){assert(!l->held);l->held=true;} +void IOLockUnlock(IOLock *l){assert(l->held);l->held=false;} +namespace Display13FBMemory {templateT &field(void *p,unsigned o){return *reinterpret_cast(static_cast(p)+o);}} +struct Display13FBBootIO {IO *map;bool readable(uint32_t r){return map && map->readable(r);}uint32_t read(uint32_t r){return map->read(r);}}; +struct {IOLock *lock;IOService *controller;OSObject *access,*dsiPort;IO *map; + Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge; + bool memoryFaulted=false,clockFaulted=false;} display13FBPower; +XELPMemory::Layout xelpMemory;bool display13Offline=false;unsigned fallbacks=0,allocations=0; +IOService *nativeFB;OSObject *nativePort; +uint32_t nativeProbe(IOService*,bool *internal,uint32_t *ddi){++fallbacks;if(internal)*internal=false;if(ddi)*ddi=4;return 2;} +OSObject *getPort(uint32_t ddi){assert(ddi==0);return nativePort;} +bool allocate(IOService *fb,uint32_t port){assert(fb==nativeFB && port==0);++allocations;return true;} +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE { + bool dsiBootProbeReady=true,dsiResourcesReady=true;IOService *adlnTopologyController; + Display13OutputWiring adlnOutputWiring;alignas(8) uint8_t adlnPlatform[0x1C0]{}; + uintptr_t orgDSIBootProbe=reinterpret_cast(nativeProbe);Boot dsiBoot; + static uint32_t wrapDSIBootProbe(IOService *,bool *,uint32_t *); +} xe,*callbackXE=&xe; +BODY +void jump(void *image,unsigned offset,uintptr_t fn){auto p=static_cast(image)+offset; + const uint8_t prefix[]={0xff,0x25,0,0,0,0};memcpy(p,prefix,6);memcpy(p+6,&fn,8);} +int main(int argc,char **argv){ + IOLock lock;IO io;IOService controller,other,fb;OSObject port,access; + auto &c=display13FBPower;c.lock=&lock;c.map=&io;c.controller=xe.adlnTopologyController=&controller; + c.access=&access;c.dsiPort=&port;c.transcoders.dsi0Pipe=0; + xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.dsi=1;xe.adlnOutputWiring.hdmi=2;xe.adlnOutputWiring.ddc[1]=2; + xe.adlnPlatform[0x12]=9;uint32_t panel[]={0,0,0,0,2,0x18};memcpy(xe.adlnPlatform+0x20,panel,sizeof(panel)); + auto config=reinterpret_cast(xe.adlnPlatform+0x20); + Display13FBMemory::field(&port,0x548)=config; + Display13FBMemory::field(&port,0x578)=&access;Display13FBMemory::field(&port,0x550)=1; + xelpMemory=io.memory;xe.dsiBoot=captureBoot(io,AlderLakeN::readDisplay(io,true),profile(),io.memory,true); + assert(verifyBootPrefix(io,xe.dsiBoot,io.memory));c.bootDSI=xe.dsiBoot.source; + io.regs[0x45454]=3;io.regs[0x42000]=0x8E380000; + auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;s.ready=true;s.allowed=Display13Power::All; + const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)); + const auto ddi=Display13Power::dependencies(Display13Power::ddiDomain(0));s.borrowed=pg|ddi; + for(unsigned i=0;i(&port,0x578)=&other;reject();Display13FBMemory::field(&port,0x578)=&access; + uint32_t copy[6];memcpy(copy,config,sizeof(copy));Display13FBMemory::field(&port,0x548)=copy; + reject();Display13FBMemory::field(&port,0x548)=reinterpret_cast(1); + reject();Display13FBMemory::field(&port,0x548)=config; + io.regs[0x6B000]^=1;reject();io.regs[0x6B000]^=1; + io.regs[0x7019C]=4096;reject();io.regs[0x7019C]=0; + bool internal=false;uint32_t d=3; + assert(XE::wrapDSIBootProbe(&controller,&internal,&d)==0 && internal && d==0); + assert(XE::wrapDSIBootProbe(&controller,nullptr,nullptr)==0 && io.writes.empty()); + xe.adlnOutputWiring.dsi=0;assert(XE::wrapDSIBootProbe(&controller,&internal,&d)==2 && !internal && d==4 && fallbacks==1); + xe.adlnOutputWiring.dsi=1; + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(f)),{}); + void *image=mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);assert(image!=MAP_FAILED); + memcpy(image,bytes.data(),bytes.size());jump(image,0x453d6,reinterpret_cast(XE::wrapDSIBootProbe)); + jump(image,0x1b1ea,reinterpret_cast(getPort));jump(image,0x66ea0,reinterpret_cast(allocate)); + nativeFB=&fb;nativePort=&port; + Display13FBMemory::field(&controller,0xD5C)=1; + Display13FBMemory::field(&controller,0xD60)=&fb; + Display13FBMemory::field(&fb,0x1D0)=&controller; + auto setup=reinterpret_cast(static_cast(image)+0x4360a); + for(unsigned builtin=0;builtin<2;++builtin){ + Display13FBMemory::field(&controller,0xE55)=builtin; + assert(!setup(&controller) && allocations==builtin+1); + assert(Display13FBMemory::field(&controller,0xD58)==0 && fb.bytes[0x44DE]==1 && fb.bytes[0x1E0]==1); + } + xe.dsiResourcesReady=false;assert(!setup(&controller) && allocations==2); + assert(Display13FBMemory::field(&controller,0xD58)==0xffff); + munmap(image,0x400000); + } + assert(!lock.held && io.writes.empty()); + puts("PASS DSI boot probe: qualified source/leases/port, cleared failure outputs, ordinary fallback and zero MMIO writes"); + if(argc>1)puts("PASS exact donor setupBootDisplay: pipe/DDI 0, internal FB selection, native allocation callback and rejected boot skips allocation"); +} +'''.replace('IO_FIXTURE',io).replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='dsi-boot-probe-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+([str(donor)] if donor else []),check=True) diff --git a/tools/test.py b/tools/test.py index 190e2dce..3a40b7e8 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,9 +8,10 @@ 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', '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 += ['dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_boot_probe_native',[sys.executable,str(R/'tests/dsi_boot_probe.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_adapter_native',[sys.executable,str(R/'tests/dsi_apple_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_timing_native',[sys.executable,str(R/'tests/dsi_apple_timing_native.py'),str(a.donor.resolve())],None)] From 3cdafd27524fa7503848f3c78bfff94c4b60485c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:04:49 +0900 Subject: [PATCH 026/139] Read DSI boot state into qualified native display caches --- WhateverXe/kern_dsi_native_cache.hpp | 56 ++++++++++++ WhateverXe/kern_xe.cpp | 89 +++++++++++++++++++ WhateverXe/kern_xe.hpp | 6 ++ tests/dsi_native_cache.py | 123 +++++++++++++++++++++++++++ tools/test.py | 3 +- 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 WhateverXe/kern_dsi_native_cache.hpp create mode 100644 tests/dsi_native_cache.py diff --git a/WhateverXe/kern_dsi_native_cache.hpp b/WhateverXe/kern_dsi_native_cache.hpp new file mode 100644 index 00000000..f49ec076 --- /dev/null +++ b/WhateverXe/kern_dsi_native_cache.hpp @@ -0,0 +1,56 @@ +// Exact donor boot cache view. This is not a register-address translator or a +// restore/programming image: DSI mode/stop must use their own adapter. DP M/N, +// MSA, normal DDI function and unsupported HBLANK have no DSI counterpart here. +#ifndef KERN_DSI_NATIVE_CACHE_HPP +#define KERN_DSI_NATIVE_CACHE_HPP +#include "kern_dsi_boot.hpp" +namespace XELP { namespace DSI { namespace NativeCache { +struct View { + uint32_t timing[6] {},source {},conf {},misc {},shift {},csc {},cursor {}; + uint32_t plane[6] {},scaler[3] {}; +}; +constexpr uint32_t PlaneRegisters[]={0x701A4,0x70180,0x701CC,0x70190,0x70188,0x7019C}; +constexpr uint32_t PlaneAddressFields[]={0xA8,0x98,0x9C,0xB4,0xB0,0xB8}; +constexpr uint32_t PlaneCacheFields[]={0x110,0x100,0x104,0x11C,0x118,0x120}; +// Caller owns the FB and shared power locks. Take two bounded snapshots and +// publish no CPU cache field until both agree and the GOP certificate holds. +template bool read(IO &io,const Boot &boot,const XELPMemory::Layout &memory,uint32_t scaler,View &out) { + if(!boot.complete() || boot.source.identity.pipe!=0 || (scaler && scaler!=0x68180 && scaler!=0x68280))return false; + const auto capture=[&](View &v) { + if(!bootUnchanged(io,boot,memory))return false; + const auto word=[&](uint32_t reg,uint32_t &value) { + if(!io.readable(reg))return false;value=io.read(reg);return value!=UINT32_MAX; + }; + // HBLANK is absent in video DSI. Fixed-mode geometry comes from the + // retained IODetailedTiming array, not this native restore cache. + const uint32_t timing[]={0x6B000,0,0x6B008,0x6B00C,0x6B010,0x6B014}; + for(unsigned i=0;i<6;++i)if(timing[i] && !word(timing[i],v.timing[i]))return false; + if(!word(0x6001C,v.source) || !word(0x7B008,v.conf) || !word(0x70030,v.misc) || + !word(0x6B028,v.shift) || !word(0x4A480,v.csc) || !word(0x70080,v.cursor))return false; + for(unsigned i=0;i<6;++i)if(!word(PlaneRegisters[i],v.plane[i]))return false; + if(scaler && (!word(scaler,v.scaler[0]) || !word(scaler-0x10,v.scaler[1]) || !word(scaler-0xC,v.scaler[2])))return false; + return v.timing[0]==boot.source.htotal && v.timing[2]==boot.source.hsync && + v.timing[3]==boot.source.vtotal && v.timing[4]==v.timing[3] && v.timing[5]==boot.source.vsync && + v.source==((boot.source.htotal&65535)<<16 | (boot.source.vtotal&65535)) && + v.conf==boot.source.conf && !(v.cursor&63) && + v.plane[0]==0 && v.plane[1]==boot.control && v.plane[3]==boot.size && + v.plane[4]==boot.stride && v.plane[5]==boot.surface && !(v.scaler[0]&0x80000000U) && + bootUnchanged(io,boot,memory); + }; + View a,b;if(!capture(a) || !capture(b) || memcmp(&a,&b,sizeof(a)))return false; + out=a;return true; +} +// Class sizes and actual path/plane/scaler associations are checked by the +// kernel adapter. Only cache words change; register addresses/ownership don't. +inline void publish(const View &v,void *path,void *plane,void *scaler) { + const auto put=[](void *object,uint32_t offset,uint32_t value){memcpy(static_cast(object)+offset,&value,4);}; + for(unsigned i=0;i<6;++i)put(path,0x138+4*i,v.timing[i]); + const uint32_t absent[]={0x12C,0x130,0x134,0x160,0x164,0x168,0x16C}; + for(auto offset:absent)put(path,offset,0); + put(path,0x150,v.source);put(path,0x154,v.conf);put(path,0x158,v.misc);put(path,0x15C,v.shift); + put(path,0x18C,v.csc);put(path,0x170,v.cursor&~0x8000U); + for(unsigned i=0;i<6;++i)put(plane,PlaneCacheFields[i],v.plane[i]); + if(scaler)for(unsigned i=0;i<3;++i)put(scaler,0x44+4*i,v.scaler[i]); +} +}}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 874d5183..f4989355 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7,6 +7,7 @@ // Copyright © 2018 vit9696. All rights reserved. // +#include "kern_dsi_native_cache.hpp" #include "kern_xe.hpp" #include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" @@ -5461,6 +5462,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); dsiBootProbeReady=processDisplay13DSIBoot(patcher,index,address,size); + dsiCacheHooksReady=processDisplay13DSICache(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); using C=XELP::DisplayContract;const auto installed=display13Available(); display13FBPowerBackendReady=C{C::Power13,installed}.ready(); @@ -6682,6 +6684,93 @@ bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { return true; } +bool XE::processDisplay13DSICache(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN31AppleIntelFramebufferController16hwUpdateRegCacheEP21AppleIntelFramebufferP21AppleIntelDisplayPath", + "__ZN31AppleIntelFramebufferController9hwGetCRTCEP21AppleIntelFramebufferP21AppleIntelDisplayPath"}; + const uint32_t offsets[]={0x4C4E2,0x4C79A}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xFF,0x05,0x94,0x2A}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0x89,0xF3,0x49,0x89,0xFE,0x48,0xFF,0x05}}; + if(!display13FramebufferRequested || size<=0x4C866)return false; + for(unsigned i=0;i<2;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSIUpdateCache,orgDSICache[0]}, {symbols[1],wrapDSIGetCRTC,orgDSICache[1]}}; + const bool ready=patcher.routeMultiple(index,routes,2,address,size);patcher.clearError();return ready; +} + +// Pipe A belongs to DSI for this adoption phase. Malformed/ambiguous objects +// must not fall into a normal-transcoder read or the donor PWM calculation. +static bool dsiCachePath(OSObject *path) { + if(!path || path->getMetaClass()->getClassSize()<0x3730)return true; + if(Display13FBMemory::field(path,0x3640)==0)return true; + const auto port=Display13FBMemory::field(path,0x3648); + return !port || port->getMetaClass()->getClassSize()<0x580 || Display13FBMemory::field(port,0x120)==0; +} + +bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) { + auto &c=display13FBPower; + if(!c.lock || !controller || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || + controller->getMetaClass()->getClassSize()<0x1C20 || path->getMetaClass()->getClassSize()<0x3730)return false; + DSIModeScope scope(fb);if(!scope.lock)return false; + IOLockLock(c.lock);Display13FBBootIO io{c.map}; + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring,false); + const bool bound=dsiCacheHooksReady && !display13Offline && dsiResourcesReady && c.map && + adlnOutputWiring.dsi==1 && controller==c.controller && controller==adlnTopologyController && + binding.status==TGLFBBinding::Status::Complete && binding.pipe==0 && binding.ddi==0 && + c.transcoders.dsi0Pipe==0 && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && + Display13FBMemory::field(path,0x3648)==c.dsiPort && c.dsiPort && + Display13FBMemory::field(c.dsiPort,0x550)==1 && + Display13FBMemory::field(path,0x34)==0x4A480 && + c.bootDSI.sameSource(dsiBoot.source) && c.bridge.outputHeld(io,0,0); + bool ready=false; + if(bound) { + auto plane=Display13FBMemory::field(path,0x32C8); + auto scaler=Display13FBMemory::field(path,0x32C0); + bool addresses=true; + for(unsigned i=0;i<6;++i)addresses=addresses && + Display13FBMemory::field(plane,XELP::DSI::NativeCache::PlaneAddressFields[i])==XELP::DSI::NativeCache::PlaneRegisters[i]; + uint32_t scale=0; + if(scaler) { + addresses=addresses && scaler->getMetaClass()->getClassSize()>=0x50; + if(addresses) { + scale=Display13FBMemory::field(scaler,0x30); + addresses=Display13FBMemory::field(scaler,0x28)==c.access && + Display13FBMemory::field(scaler,0x18)==0 && (scale==0x68180 || scale==0x68280) && + Display13FBMemory::field(scaler,0x34)==scale-0x10 && + Display13FBMemory::field(scaler,0x38)==scale-0xC; + } + } + XELP::DSI::NativeCache::View view; + ready=addresses && XELP::DSI::NativeCache::read(io,dsiBoot,xelpMemory,scale,view); + if(ready)XELP::DSI::NativeCache::publish(view,path,plane,scaler); + } + IOLockUnlock(c.lock);return ready; +} + +void XE::wrapDSIUpdateCache(IOService *controller,IOService *fb,OSObject *path) { + if(callbackXE->adlnOutputWiring.dsi && dsiCachePath(path)) { + if(!callbackXE->updateDSIBootCache(controller,fb,path)) { + auto &c=display13FBPower; + if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + } + return; + } + FunctionCast(wrapDSIUpdateCache,callbackXE->orgDSICache[0])(controller,fb,path); +} +void XE::wrapDSIGetCRTC(IOService *controller,IOService *fb,OSObject *path) { + if(callbackXE->adlnOutputWiring.dsi && dsiCachePath(path)) { + // DSI brightness belongs to its panel backend. The donor's internal + // FB branch divides by an ordinary PWM period that may be zero here. + wrapDSIUpdateCache(controller,fb,path);return; + } + FunctionCast(wrapDSIGetCRTC,callbackXE->orgDSICache[1])(controller,fb,path); +} + bool XE::processDisplay13DSIBoot(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { const char *symbol="__ZN31AppleIntelFramebufferController13probeBootPipeEPbPN17AppleIntelPortHAL3DDIE"; const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89}; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 84c2eb9e..985b34e8 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -139,6 +139,12 @@ class XE { bool dsiResourcesReady {}; bool bindDSIResources(IOService *); static uint32_t wrapDisplay13ProbeMode(OSObject *); + bool dsiCacheHooksReady {}; + mach_vm_address_t orgDSICache[2] {}; + bool processDisplay13DSICache(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool updateDSIBootCache(IOService *,IOService *,OSObject *); + static void wrapDSIUpdateCache(IOService *,IOService *,OSObject *); + static void wrapDSIGetCRTC(IOService *,IOService *,OSObject *); bool dsiBootProbeReady {}; mach_vm_address_t orgDSIBootProbe {}; bool processDisplay13DSIBoot(KernelPatcher &,size_t,mach_vm_address_t,size_t); diff --git a/tests/dsi_native_cache.py b/tests/dsi_native_cache.py new file mode 100644 index 00000000..da2baa39 --- /dev/null +++ b/tests/dsi_native_cache.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Execute DSI cache hooks with real binding/boot helpers and bounded fake objects.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +names=['updateDSIBootCache','wrapDSIUpdateCache','wrapDSIGetCRTC'] +functions='\n'.join(re.search(r'(?:bool|void) XE::'+n+r'\(.*?\n\}',s,re.S)[0] for n in names) +functions=re.search(r'static bool dsiCachePath\(.*?\n\}',s,re.S)[0]+'\n'+functions +binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S)[0] +boot=(root/'tests/dsi_boot.cpp').read_text();io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] +# Non-source cache-word drift must be caught by the two snapshots as well. +io=io.replace('bool driftOnWrite=false,strict=false;','bool driftOnWrite=false,strict=false,cacheDrift=false;unsigned cacheReads=0;') +io=io.replace('reads.push_back(reg);return regs[reg];','reads.push_back(reg);if(cacheDrift && reg==0x701CC && ++cacheReads==2)++regs[reg];return regs[reg];') +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + install=re.search(r'bool XE::processDisplay13DSICache\(.*?\n\}',s,re.S)[0] + offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',install)[1].split(',')] + entries=re.search(r'entries\[\]\[16\]=\{(.*?)\};',install,re.S)[1] + for offset,row in zip(offsets,re.findall(r'\{([^}]+)\}',entries)): + assert b[offset:offset+16]==bytes(int(x,0) for x in row.split(',')) + # Native path+34 is pipe CSC mode, not DDI_BUF_CTL. Scaler tables use -10/-C. + assert b[0x91315:0x9131e]==bytes.fromhex('8d9080a40400895734') + import struct + assert struct.unpack_from('<2I',b,0x99f30)==(0x68180,0x68280) + assert struct.unpack_from('<2I',b,0x99f48)==(0x68170,0x68270) + assert struct.unpack_from('<2I',b,0x99f60)==(0x68174,0x68274) +code=r''' +#include "kern_dsi_native_cache.hpp" +#include "kern_tgl_fb_binding.hpp" +#include "kern_display13_power_bridge.hpp" +#include "kern_display13_transcoder.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; +using IOService=OSObject; +unsigned level=0,faults=0,nativeCache=0,nativeCRTC=0; +struct IOLock{};struct IORecursiveLock{}; +void IOLockLock(IOLock*){assert(level==0 || level==1);level+=2;}void IOLockUnlock(IOLock*){assert(level>=2);level-=2;} +void adlnFault(bool &flag,unsigned){assert(level>=2);flag=true;++faults;} +struct DSIModeScope {IORecursiveLock *lock;explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)){assert(level==0);if(lock)level=1;} + ~DSIModeScope(){assert(level==(lock?1U:0U));level=0;}}; +struct Display13FBBootIO {IO *map;bool readable(uint32_t r){return map && map->readable(r);}uint32_t read(uint32_t r){return map->read(r);}}; +struct {IOLock *lock;IOService *controller;OSObject *access,*dsiPort;IO *map; + Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge; + bool memoryFaulted=false,clockFaulted=false;} display13FBPower; +uintptr_t display13FBExpectedOwner(IOService*){return 0;} +BINDING +XELPMemory::Layout xelpMemory;bool display13Offline=false; +void originalCache(IOService*,IOService*,OSObject*){++nativeCache;} +void originalCRTC(IOService*,IOService*,OSObject*){++nativeCRTC;} +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE { + bool dsiCacheHooksReady=true,dsiResourcesReady=true;IOService *adlnTopologyController; + Display13OutputWiring adlnOutputWiring;alignas(8) uint8_t adlnPlatform[0x1C0]{}; + uintptr_t orgDSICache[2]{reinterpret_cast(originalCache),reinterpret_cast(originalCRTC)};Boot dsiBoot; + bool updateDSIBootCache(IOService *,IOService *,OSObject *); + static void wrapDSIUpdateCache(IOService *,IOService *,OSObject *); + static void wrapDSIGetCRTC(IOService *,IOService *,OSObject *); +} xe,*callbackXE=&xe; +FUNCTIONS +templatevoid put(void *p,unsigned off,T value){memcpy(static_cast(p)+off,&value,sizeof(value));} +int main(){ + IOLock lock;IORecursiveLock fbLock;IO io;IOService controller,fb;OSObject path,plane,port,scaler,access; + auto &c=display13FBPower;c.lock=&lock;c.map=&io;c.controller=xe.adlnTopologyController=&controller; + c.access=&access;c.dsiPort=&port;c.transcoders.dsi0Pipe=0; + xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.dsi=1;xe.adlnOutputWiring.hdmi=2;xe.adlnOutputWiring.ddc[1]=2; + xe.adlnPlatform[0x12]=9;uint32_t panel[]={0,0,0,0,2,0x18};memcpy(xe.adlnPlatform+0x20,panel,sizeof(panel)); + uint32_t hdmi[]={1,2,2,0,0x800,0x202};memcpy(xe.adlnPlatform+0x38,hdmi,sizeof(hdmi)); + put(&port,0x548,reinterpret_cast(xe.adlnPlatform+0x20));put(&port,0x550,1U); + put(&controller,0xD5C,1U);put(&controller,0xD60,&fb);put(&controller,0xC40,&access);put(&controller,0xF58,&plane); + put(&fb,0x1D0,&controller);put(&fb,0x4A40,&access);put(&fb,0x4220,&fbLock); + put(&path,0x32B0,&fb);put(&path,0x32C8,&plane);put(&path,0x3648,&port);put(&path,0x32C0,&scaler);put(&path,0x34,0x4A480U); + put(&plane,0x68,&fb);put(&plane,0x70,&path);put(&plane,0x90,&access);put(&plane,0xA0,0x7027CU);put(&plane,0xA4,0x7017CU); + for(unsigned i=0;i<6;++i)put(&plane,NativeCache::PlaneAddressFields[i],NativeCache::PlaneRegisters[i]); + put(&scaler,0x28,&access);put(&scaler,0x30,0x68180U);put(&scaler,0x34,0x68170U);put(&scaler,0x38,0x68174U); + xelpMemory=io.memory;xe.dsiBoot=captureBoot(io,AlderLakeN::readDisplay(io,true),profile(),io.memory,true); + assert(verifyBootPrefix(io,xe.dsiBoot,io.memory));c.bootDSI=xe.dsiBoot.source; + io.regs[0x45454]=3;io.regs[0x42000]=0x8E380000; + io.regs[0x6B010]=c.bootDSI.vtotal;io.regs[0x6001C]=0x0437077F;io.regs[0x70030]=0x20; + io.regs[0x4A480]=0x1234;io.regs[0x701CC]=0x5000;io.regs[0x68170]=0x11;io.regs[0x68174]=0x22; + auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;s.ready=true;s.allowed=Display13Power::All; + const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)); + const auto ddi=Display13Power::dependencies(Display13Power::ddiDomain(0));s.borrowed=pg|ddi; + for(unsigned i=0;i(&path,0x138)==c.bootDSI.htotal && Display13FBMemory::field(&path,0x144)==c.bootDSI.vtotal); + assert(!Display13FBMemory::field(&path,0x13C) && !Display13FBMemory::field(&path,0x12C) && !Display13FBMemory::field(&path,0x168)); + assert(Display13FBMemory::field(&path,0x18C)==0x1234 && Display13FBMemory::field(&plane,0x104)==0x5001); + assert(Display13FBMemory::field(&scaler,0x48)==0x11 && Display13FBMemory::field(&scaler,0x4C)==0x22); + XE::wrapDSIGetCRTC(&controller,&fb,&path);assert(!nativeCache && !nativeCRTC && !faults); + b.ddiRefs[0]=0;XE::wrapDSIGetCRTC(&controller,&fb,&path);assert(faults==1 && c.memoryFaulted);b.ddiRefs[0]=1;c.memoryFaulted=false; + put(&path,0x3640,1U);put(&port,0x120,1U);XE::wrapDSIGetCRTC(&controller,&fb,&path);XE::wrapDSIUpdateCache(&controller,&fb,&path); + assert(nativeCRTC==1 && nativeCache==1);xe.adlnOutputWiring.dsi=0; + XE::wrapDSIGetCRTC(&controller,&fb,nullptr);assert(nativeCRTC==2); + assert(io.writes.empty() && !level);for(auto reg:io.reads)assert(reg<0xC7200 || reg>0xC8258); + puts("PASS production DSI cache: native object binding, split timing/pipe fields, coherent snapshots, no partial publication, no PWM, failure latch and external fallback"); +} +'''.replace('IO_FIXTURE',io).replace('BINDING',binding).replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-native-cache-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index 3a40b7e8..937069e5 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,9 +8,10 @@ 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', '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 += ['dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_boot_probe_native',[sys.executable,str(R/'tests/dsi_boot_probe.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_adapter_native',[sys.executable,str(R/'tests/dsi_apple_adapter.py'),str(a.donor.resolve())],None)] From af42f02279ebdfcd425f958e463b42643f35394e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:06:28 +0900 Subject: [PATCH 027/139] Reject invalid pipe identities before native cache fallback --- WhateverXe/kern_xe.cpp | 3 ++- tests/dsi_native_cache.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f4989355..05e3580d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6706,7 +6706,8 @@ bool XE::processDisplay13DSICache(KernelPatcher &patcher,size_t index,mach_vm_ad // must not fall into a normal-transcoder read or the donor PWM calculation. static bool dsiCachePath(OSObject *path) { if(!path || path->getMetaClass()->getClassSize()<0x3730)return true; - if(Display13FBMemory::field(path,0x3640)==0)return true; + const auto pipe=Display13FBMemory::field(path,0x3640); + if(pipe==0 || pipe>=3)return true; const auto port=Display13FBMemory::field(path,0x3648); return !port || port->getMetaClass()->getClassSize()<0x580 || Display13FBMemory::field(port,0x120)==0; } diff --git a/tests/dsi_native_cache.py b/tests/dsi_native_cache.py index da2baa39..31e56b99 100644 --- a/tests/dsi_native_cache.py +++ b/tests/dsi_native_cache.py @@ -111,7 +111,11 @@ XE::wrapDSIGetCRTC(&controller,&fb,&path);assert(!nativeCache && !nativeCRTC && !faults); b.ddiRefs[0]=0;XE::wrapDSIGetCRTC(&controller,&fb,&path);assert(faults==1 && c.memoryFaulted);b.ddiRefs[0]=1;c.memoryFaulted=false; put(&path,0x3640,1U);put(&port,0x120,1U);XE::wrapDSIGetCRTC(&controller,&fb,&path);XE::wrapDSIUpdateCache(&controller,&fb,&path); - assert(nativeCRTC==1 && nativeCache==1);xe.adlnOutputWiring.dsi=0; + assert(nativeCRTC==1 && nativeCache==1); + put(&path,0x3640,3U);XE::wrapDSIGetCRTC(&controller,&fb,&path); + assert(nativeCRTC==1 && c.memoryFaulted && faults==2);c.memoryFaulted=false; + XE::wrapDSIUpdateCache(&controller,&fb,nullptr);assert(nativeCache==1 && c.memoryFaulted && faults==3); + xe.adlnOutputWiring.dsi=0; XE::wrapDSIGetCRTC(&controller,&fb,nullptr);assert(nativeCRTC==2); assert(io.writes.empty() && !level);for(auto reg:io.reads)assert(reg<0xC7200 || reg>0xC8258); puts("PASS production DSI cache: native object binding, split timing/pipe fields, coherent snapshots, no partial publication, no PWM, failure latch and external fallback"); From 437aac2d42611da09b10b842e70e0c83a986697e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:13:25 +0900 Subject: [PATCH 028/139] Connect DSI boot path adoption to retained fixed mode publication --- WhateverXe/kern_xe.cpp | 60 +++++++++++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 1 + tests/dsi_apple_adapter.py | 1 + tests/dsi_boot_adoption.py | 70 ++++++++++++++++++++++++++++++++++++++ tools/test.py | 2 +- 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 tests/dsi_boot_adoption.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 05e3580d..d664f4a0 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -8533,6 +8533,59 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { return accepted; } +// Called only after the real PCI/workloop IRQ source and native memory have +// been qualified. Keep the FB mutex across mode publication; never call the +// native timing setter or registry code with the display-owner lock held. +bool XE::adoptDSIBootPath(IOService *fb,OSObject *path) { + auto &c=display13FBPower;auto &irq=display13FBIRQ; + if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || + path->getMetaClass()->getClassSize()<0x3730)return false; + DSIModeScope scope(fb);if(!scope.lock)return false; + const auto qualified=[&]() { + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiCacheHooksReady || !dsiBootProbeReady || + display13Offline || !c.map || !c.power || !c.controller || c.controller!=adlnTopologyController || + c.pipe[0].memoryFramebuffer!=fb || c.memoryFaulted || c.clockFaulted || + c.bridge.state.faulted || c.bridge.state.suspended || c.transcoders.dsi0Pipe!=0 || !irq.lock || + !Display13Power::objectAssociations(c.power,0x80,c.controller,0x1C20))return false; + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); + if(binding.status!=TGLFBBinding::Status::Complete || binding.pipe!=0 || binding.ddi!=0 || + Display13FBMemory::field(path,0x3648)!=c.dsiPort || !c.dsiPort || + Display13FBMemory::field(c.dsiPort,0x550)!=1 || + Display13FBMemory::field(path,0x3644)!=2 || + Display13FBMemory::field(fb,0x4348) || + !Display13FBMemory::field(fb,0x44DE) || !Display13FBMemory::field(fb,0x1E7))return false; + Display13FBBootIO io{c.map}; + if(!c.bridge.outputHeld(io,0,0) || !dsiBoot.complete() || + !c.bootDSI.sameSource(dsiBoot.source) || !XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory))return false; + const auto memory=display13FBInspectMemory(fb,true,0,false); + if(memory.status!=Display13FBMemory::Status::Complete || memory.control!=dsiBoot.control || + memory.stride!=dsiBoot.stride || memory.size!=dsiBoot.size || memory.surface!=dsiBoot.surface)return false; + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const bool enabled=irq.controller==c.controller && (irq.admittedPipes&1) && !irq.quiescing && + irq.state.begun && irq.state.enabled && !irq.state.faulted && irq.state.transcoders.dsi0Pipe==0; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return enabled; + }; + IOLockLock(c.lock);const bool before=qualified();IOLockUnlock(c.lock); + bool ready=before && bindDSIFixedMode(fb,dsiProfile); + IOLockLock(c.lock); + ready=ready && qualified(); + if(ready) { + const auto key=reinterpret_cast(path); + ready=c.bridge.bootAdopted ? c.bridge.bootPath==key : + c.bridge.adoptBootPath(key,0,0).status==Display13Power::Status::Complete; + ready=ready && Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0x1C20); + } + if(!ready) { + // The setter may already have published its raw array pointer. Retain + // all backing/objects until stop; only withdraw mode readiness here. + if(dsiModeTarget(fb))dsiFixedMode.ready=false; + adlnFault(c.memoryFaulted,__LINE__); + } + IOLockUnlock(c.lock); + if(!ready && dsiModeTarget(fb))fb->setProperty("WEXDSIFixedModeReady",false); + return ready; +} + void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) { FunctionCast(wrapAlderLakeNSetupWorkLoop,callbackXE->orgAlderLakeNSetupWorkLoop)(controller,workLoop); auto &c=display13FBPower;if(!c.lock)return; @@ -8634,7 +8687,12 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) valid=enabled && !c.bridge.state.faulted && !c.memoryFaulted && !c.clockFaulted; } check[37]=valid; - if(valid) { + if(valid && callbackXE->adlnOutputWiring.dsi) { + IOLockUnlock(c.lock); + const bool adopted=callbackXE->adoptDSIBootPath(fb,path); + IOLockLock(c.lock); + result.status=adopted ? Display13Power::Status::Complete : Display13Power::Status::NotReady; + } else if(valid) { if(!c.bridge.bootAdopted) { const auto ddi=c.bridge.bootPort; const bool dp=ddi<7 && (callbackXE->adlnOutputWiring.links()&(1U<refs==2 && dsiFixedMode.array->refs==2 && dsiFixedMode.data->refs==2); + auto callsBeforeRepeat=calls[0];assert(xe.bindDSIFixedMode(fb,p) && calls[0]==callsBeforeRepeat && fb->refs==2 && dsiFixedMode.array->refs==2); auto other=p;++other.mode.clockKHz;assert(!xe.bindDSIFixedMode(fb,other)); assert(XE::wrapDSIGetModeCount(fb)==1 && XE::wrapDSIGetModeCount(peer)==3); int32_t peerMode=0;assert(!XE::wrapDSIGetModes(peer,&peerMode) && peerMode==42); diff --git a/tests/dsi_boot_adoption.py b/tests/dsi_boot_adoption.py new file mode 100644 index 00000000..28bced06 --- /dev/null +++ b/tests/dsi_boot_adoption.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Exercise the production boot adoption transaction; mode setter is a fault-injectable leaf.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'bool XE::adoptDSIBootPath\(.*?\n\}',s,re.S)[0] +binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S)[0] +boot=(root/'tests/dsi_boot.cpp').read_text();io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] +# Reuse the exact native object graph and lease setup from the cache fixture. +base=(root/'tests/dsi_native_cache.py').read_text().split("code=r'''",1)[1].split("'''.replace('IO_FIXTURE'",1)[0] +base=base[:base.index(' const auto unchanged=')] +base=base.replace('#include "kern_dsi_native_cache.hpp"','#include "kern_dsi_native_cache.hpp"\n#include "kern_display13_fb_irq.hpp"') +base=base.replace('unsigned size=sizeof(bytes);','unsigned size=sizeof(bytes);bool readyProperty=true;void setProperty(const char*,bool b){readyProperty=b;}') +base=base.replace('struct IOLock{};struct IORecursiveLock{};','''struct IOLock{};struct IORecursiveLock{};struct IOSimpleLock{}; +unsigned IOSimpleLockLockDisableInterrupt(IOSimpleLock*){assert(level==3);level=4;return 1;} +void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,unsigned v){assert(v==1 && level==4);level=3;}''') +base=base.replace('IOLock *lock;IOService *controller;OSObject *access,*dsiPort;IO *map;','IOLock *lock;IOService *controller;OSObject *access,*dsiPort,*power;IO *map;\n struct {IOService *memoryFramebuffer;} pipe[3];') +base=base.replace('BINDING','''struct {IOSimpleLock *lock;IOService *controller;uint32_t admittedPipes=1;bool quiescing=false;Display13FBIRQ::State state;} display13FBIRQ; +struct {IOService *framebuffer=nullptr;bool ready=false;} dsiFixedMode; +bool dsiModeTarget(IOService *fb){return dsiFixedMode.framebuffer==fb;} +Display13FBMemory::Readout measuredMemory; +Display13FBMemory::Readout display13FBInspectMemory(IOService*,bool boot,unsigned pipe,bool publish){assert(level==3 && boot && !pipe && !publish);return measuredMemory;} +BINDING''') +base=base.replace('bool dsiCacheHooksReady=true,dsiResourcesReady=true;','bool dsiCacheHooksReady=true,dsiResourcesReady=true,dsiBootProbeReady=true;Profile dsiProfile=profile();') +base=base.replace(' bool updateDSIBootCache(', ' bool adoptDSIBootPath(IOService *,OSObject *);\n bool bindDSIFixedMode(IOService *,const Profile &);\n bool updateDSIBootCache(') +base=base.replace('FUNCTIONS','''unsigned modeCalls=0;bool publishMode=true,changeSource=false; +bool XE::bindDSIFixedMode(IOService *fb,const Profile&){assert(level==1);++modeCalls; + dsiFixedMode.framebuffer=fb;dsiFixedMode.ready=publishMode;fb->setProperty("WEXDSIFixedModeReady",publishMode); + if(changeSource)display13FBPower.map->regs[0x6B000]^=1; + return publishMode; +} +FUNCTIONS''') +base+=r''' + IOSimpleLock irqLock;OSObject power; + c.power=&power;put(&controller,0xC8,&power);c.pipe[0].memoryFramebuffer=&fb; + put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&fb,0x44DE,uint8_t(1));put(&fb,0x1E7,uint8_t(1));put(&path,0x3644,2U); + b.bootPort=0;b.bootPGProtected=b.bootDDIProtected=true; + measuredMemory.status=Display13FBMemory::Status::Complete;measuredMemory.control=xe.dsiBoot.control; + measuredMemory.stride=xe.dsiBoot.stride;measuredMemory.size=xe.dsiBoot.size;measuredMemory.surface=xe.dsiBoot.surface; + auto &irq=display13FBIRQ;irq.lock=&irqLock;irq.controller=&controller;irq.state.begun=irq.state.enabled=true;irq.state.transcoders.dsi0Pipe=0; + const auto rejected=[&]{const auto calls=modeCalls;assert(!xe.adoptDSIBootPath(&fb,&path)); + assert(modeCalls==calls && c.memoryFaulted && !b.bootAdopted && !b.bootPath && b.bootPGProtected && b.bootDDIProtected && !level); + c.memoryFaulted=false;}; + irq.state.enabled=false;rejected();irq.state.enabled=true; + irq.quiescing=true;rejected();irq.quiescing=false; + measuredMemory.surface=4096;rejected();measuredMemory.surface=0; + put(&fb,0x4A08,&plane);rejected();put(&fb,0x4A08,&path); + b.ddiRefs[0]=0;rejected();b.ddiRefs[0]=1; + publishMode=false;assert(!xe.adoptDSIBootPath(&fb,&path) && modeCalls==1 && c.memoryFaulted); + assert(!b.bootAdopted && b.bootPGProtected && b.bootDDIProtected && dsiModeTarget(&fb) && !dsiFixedMode.ready && !fb.readyProperty); + c.memoryFaulted=false;publishMode=true;changeSource=true; + assert(!xe.adoptDSIBootPath(&fb,&path) && modeCalls==2 && !dsiFixedMode.ready && !b.bootAdopted); + assert(b.pgRefs[0]==1 && b.ddiRefs[0]==1 && b.bootPGProtected && b.bootDDIProtected); + c.memoryFaulted=false;changeSource=false;io.regs[0x6B000]^=1; + assert(xe.adoptDSIBootPath(&fb,&path) && dsiFixedMode.ready && b.bootAdopted && b.bootPath==reinterpret_cast(&path)); + assert(!b.bootPGProtected && !b.bootDDIProtected && b.pgRefs[0]==1 && b.ddiRefs[0]==1); + assert(Display13FBMemory::field(&power,0x10)==&controller && Display13FBMemory::field(&power,0x78)==&access); + assert(xe.adoptDSIBootPath(&fb,&path) && b.pgRefs[0]==1 && b.ddiRefs[0]==1); + b.bootPath=reinterpret_cast(&plane); + assert(!xe.adoptDSIBootPath(&fb,&path) && c.memoryFaulted && !dsiFixedMode.ready); + assert(b.bootPath==reinterpret_cast(&plane) && b.pgRefs[0]==1 && b.ddiRefs[0]==1 && !level && io.writes.empty()); + puts("PASS production DSI adoption: real path/leases/IRQ and memory prerequisites, mode publication outside owner lock, revalidation, failure retention and single boot transfer"); +} +''' +code=base.replace('IO_FIXTURE',io).replace('BINDING',binding).replace('FUNCTIONS',body) +with tempfile.TemporaryDirectory(prefix='dsi-boot-adoption-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index 937069e5..96a55bdf 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] From b2e5ed831e0d4e38f425b0b211267079431e8775 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:19:54 +0900 Subject: [PATCH 029/139] Guard DSI mode and disable lifecycle entry points --- WhateverXe/kern_xe.cpp | 26 ++++++++++ tests/dsi_lifecycle_dispatch.py | 85 +++++++++++++++++++++++++++++++++ tools/test.py | 2 +- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 tests/dsi_lifecycle_dispatch.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index d664f4a0..58899067 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6712,6 +6712,15 @@ static bool dsiCachePath(OSObject *path) { return !port || port->getMetaClass()->getClassSize()<0x580 || Display13FBMemory::field(port,0x120)==0; } +// Called at the existing mode/disable owners, before any donor notification or +// cursor/MMIO write. A retained panel FB remains ours even if its path fields +// are damaged. Ambiguous objects must not escape into the normal DDI writer. +static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { + if(dsiModeTarget(fb) || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return true; + const auto attached=Display13FBMemory::singlePath(fb); + return attached!=path || dsiCachePath(attached); +} + bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) { auto &c=display13FBPower; if(!c.lock || !controller || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || @@ -7224,6 +7233,15 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c uint32_t XE::wrapDisplay13TCSetMode(void *controller,void *fb,const void *timing) { auto self=callbackXE; + // The GOP-preserving surface adapter must replace this refusal before + // startup admission can open. The original sends a begin notification and + // writes ordinary clock/timing state before reaching LightUpEDP. + if(self->adlnOutputWiring.dsi) { + auto service=static_cast(fb); + const auto path=service && service->getMetaClass()->getClassSize()>=0x4B90 ? + Display13FBMemory::singlePath(service) : nullptr; + if(dsiLifecycleTarget(service,path))return kIOReturnNotReady; + } return Display13TC::finishMode( [&](){return FunctionCast(wrapDisplay13TCSetMode,self->orgDisplay13TCSetMode)(controller,fb,timing);}, [&](){reinterpret_cast(self->display13TCNotify)(controller,fb,1);}); @@ -9667,6 +9685,14 @@ static bool adlnWriterMappingBound() { } void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObject *path,uint8_t mode) { + if(callbackXE->adlnOutputWiring.dsi && dsiLifecycleTarget(fb,path)) { + // This void callback cannot report an unsupported stop. Latch failure so + // resource retirement retains the live backing/leases. Do not run even + // the cursor prefix until the DSI stop transaction owns its rollback. + auto &c=display13FBPower; + if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + return; + } auto &c=display13FBPower;Display13Cursor::Result result;bool selected=false;unsigned pipe=UINT32_MAX; if(c.lock && controller==c.controller && fb && fb->getMetaClass()->getClassSize()>=0x4B90 && path && path->getMetaClass()->getClassSize()>=0x3730) { IOLockLock(c.lock); diff --git a/tests/dsi_lifecycle_dispatch.py b/tests/dsi_lifecycle_dispatch.py new file mode 100644 index 00000000..120100d6 --- /dev/null +++ b/tests/dsi_lifecycle_dispatch.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Run production lifecycle entry points; no DSI request may reach donor writes.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:static bool|uint32_t|void) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDisplay13TCSetMode','wrapAlderLakeNDisableDisplay']) +code=r''' +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_cursor.hpp" +#include "kern_display13_tc_lightup.hpp" +#include +#include +struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; +using IOService=OSObject;using IOLock=int; +unsigned depth=0,bindingCalls=0,modeCalls=0,stopCalls=0,notifications=0,modeResult=0; +void IOLockLock(IOLock*){assert(!depth);++depth;}void IOLockUnlock(IOLock*){assert(depth==1);--depth;} +void adlnFault(bool &v,unsigned){assert(depth==1);v=true;} +void IOSleep(unsigned){assert(false);} +struct IOMemoryMap {uintptr_t getVirtualAddress(){assert(false);return 0;}size_t getLength(){return 0;}}; +struct {IOLock *lock=nullptr;IOService *controller=nullptr;IOMemoryMap *map=nullptr;bool memoryFaulted=false,clockFaulted=false; + struct {struct {bool faulted=false;}state;}bridge;struct {unsigned updating=0;}dbuf; + struct {IOService *memoryFramebuffer=nullptr;bool wmProgramming=false,wmCommitting=false;}pipe[3]; + Display13Transcoder::Bindings transcoders;}display13FBPower; +namespace TGLFBBinding {enum class Status{Unavailable,Complete};struct Readout{Status status{};unsigned pipe=0;};} +TGLFBBinding::Readout adlnInspectPathBinding(IOService*,OSObject*,void*,unsigned){++bindingCalls;return {};} +bool adlnWriterMappingBound(){return false;} +void adlnPublishMemory(const char*,unsigned,void*,size_t,bool=false){} +IOService *panel=nullptr;bool dsiModeTarget(IOService *fb){return fb && fb==panel;} +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +#define FunctionCast(f,p) reinterpret_cast(p) +uint32_t nativeMode(void*,void*,const void*){++modeCalls;return modeResult;} +void nativeStop(IOService*,IOService*,OSObject*,uint8_t){++stopCalls;} +void notify(void*,void*,uint32_t value){assert(value==1);++notifications;} +struct XE { + struct Wiring{unsigned dsi=0;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; + uintptr_t orgDisplay13TCSetMode=reinterpret_cast(nativeMode),display13TCNotify=reinterpret_cast(notify); + uintptr_t orgAlderLakeNDisableDisplay=reinterpret_cast(nativeStop); + static uint32_t wrapDisplay13TCSetMode(void*,void*,const void*); + static void wrapAlderLakeNDisableDisplay(IOService*,IOService*,OSObject*,uint8_t); +}xe,*callbackXE=&xe; +FUNCTIONS +templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +int main(){ + IOService controller,fb;OSObject path,other,port;IOLock lock; + auto &c=display13FBPower;c.lock=&lock;c.controller=&controller; + put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&path,0x3648,&port); + const auto reject=[&](IOService *f,OSObject *p){ + const auto m=modeCalls,n=notifications,d=stopCalls,b=bindingCalls; + assert(XE::wrapDisplay13TCSetMode(&controller,f,nullptr)==kIOReturnNotReady); + assert(modeCalls==m && notifications==n); + c.memoryFaulted=false;XE::wrapAlderLakeNDisableDisplay(&controller,f,p,0); + assert(c.memoryFaulted && stopCalls==d && bindingCalls==b && !depth); + }; + xe.adlnOutputWiring.dsi=1;reject(&fb,&path); // actual pipe A / port A + put(&path,0x3640,1U);put(&port,0x120,3U); + panel=&fb;reject(&fb,&path);panel=nullptr; // retained identity survives corrupted path + put(&path,0x3640,3U);reject(&fb,&path); + put(&path,0x3640,1U);put(&port,0x120,0U);reject(&fb,&path); + put(&port,0x120,3U);port.size=0x100;reject(&fb,&path);port.size=sizeof(port.bytes); + put(&path,0x3648,static_cast(nullptr));reject(&fb,&path);put(&path,0x3648,&port); + path.size=0x100;reject(&fb,&path);path.size=sizeof(path.bytes); + put(&fb,0x4A00,0U);reject(&fb,nullptr);put(&fb,0x4A00,2U);reject(&fb,&path); + put(&fb,0x4A00,1U);fb.size=0x100;reject(&fb,&path);fb.size=sizeof(fb.bytes);reject(nullptr,nullptr); + // An otherwise external FB with a mismatched supplied stop path is ambiguous. + c.memoryFaulted=false;const auto stops=stopCalls; + XE::wrapAlderLakeNDisableDisplay(&controller,&fb,&other,1);assert(c.memoryFaulted && stops==stopCalls); + // Qualified external path keeps native mode return/notification behavior. + c.memoryFaulted=false;auto m=modeCalls,n=notifications,d=stopCalls; + assert(!XE::wrapDisplay13TCSetMode(&controller,&fb,nullptr));assert(modeCalls==m+1 && notifications==n); + modeResult=7;assert(XE::wrapDisplay13TCSetMode(&controller,&fb,nullptr)==7);assert(notifications==n+1); + XE::wrapAlderLakeNDisableDisplay(&controller,&fb,&path,0);assert(stopCalls==d+1 && !c.memoryFaulted); + // With DSI disabled no additional native-object interpretation is introduced. + xe.adlnOutputWiring.dsi=0;panel=&fb;modeResult=0; + assert(!XE::wrapDisplay13TCSetMode(nullptr,nullptr,nullptr)); + XE::wrapAlderLakeNDisableDisplay(nullptr,nullptr,nullptr,0);assert(stopCalls==d+2 && !c.memoryFaulted); + puts("PASS production DSI mode/disable dispatch: no donor entry or notifications, fault retention, ambiguous object rejection, external return behavior"); +} +'''.replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-lifecycle-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index 96a55bdf..039ec89d 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] From 3fd90acaf8097cb628cfef3bb28a4f926a8bb81e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:25:48 +0900 Subject: [PATCH 030/139] Prepare shared GGTT scanout mode before DSI resource publication --- WhateverXe/kern_dsi_ggtt.hpp | 37 +++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 23 ++++++++++++----- WhateverXe/kern_xe.hpp | 2 ++ tests/dsi_ggtt.py | 48 ++++++++++++++++++++++++++++++++++++ tests/dsi_resources.py | 29 ++++++++++++++++++---- tools/test.py | 2 +- 6 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 WhateverXe/kern_dsi_ggtt.hpp create mode 100644 tests/dsi_ggtt.py diff --git a/WhateverXe/kern_dsi_ggtt.hpp b/WhateverXe/kern_dsi_ggtt.hpp new file mode 100644 index 00000000..7554287c --- /dev/null +++ b/WhateverXe/kern_dsi_ggtt.hpp @@ -0,0 +1,37 @@ +// Configure shared Display13 scanout addressing before native display users. +// Linux v6.12 intel_fb_modifier_uses_dpt excludes LINEAR; tiled native surfaces +// use GGTT addresses and need CHICKEN_MISC_DISABLE_DPT (i915_reg.h). +#ifndef KERN_DSI_GGTT_HPP +#define KERN_DSI_GGTT_HPP +#include "kern_dsi_boot.hpp" +namespace XELP { namespace DSI { +struct GGTTMode { + enum class Status : uint32_t {Unprepared,Source,Readback,Changed,Complete}; + Status status {Status::Unprepared};uint32_t before {},after {},writes {}; + bool ready() const {return status==Status::Complete;} +}; +// Caller owns startup serialization, source power/PLL, and the global display +// mode. No external output or native engine may have started. This changes no +// PTE, plane, timing, or DSI register. Preserve every unrelated chicken bit. +// Do not restore the global mode when only DSI stops: external tiled consumers +// share it. A failed write remains recorded for controller-level cleanup. +template bool prepareGGTT(IO &io,Boot &boot,const XELPMemory::Layout &memory,GGTTMode &mode) { + if(mode.status!=GGTTMode::Status::Unprepared)return false; + mode.status=GGTTMode::Status::Source; + if(!boot.complete() || !bootUnchanged(io,boot,memory))return false; + mode.before=boot.dpt;mode.after=mode.before|(1U<<30); + if(mode.before!=mode.after) { + io.write(0x42084,mode.after);++mode.writes; + mode.status=GGTTMode::Status::Readback; + if(!io.readable(0x42084) || io.read(0x42084)!=mode.after) { + boot.status=BootStatus::Changed;return false; + } + } + // Extend the certificate only after exact posting readback. No caller may + // keep using the earlier dpt word as proof once this write has happened. + boot.dpt=mode.after;mode.status=GGTTMode::Status::Changed; + if(!bootUnchanged(io,boot,memory)){boot.status=BootStatus::Changed;return false;} + mode.status=GGTTMode::Status::Complete;return true; +} +}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 58899067..392c1e65 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10554,7 +10554,13 @@ bool XE::bindDSIResources(IOService *controller) { if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || !display13FBIRQReady || !clock.lock || !c.lock)return false; IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - Display13FBBootIO io{c.map}; + struct IO : Display13FBBootIO { + explicit IO(IOMemoryMap *map):Display13FBBootIO{map} {} + void write(uint32_t reg,uint32_t value) { + if(reg==0x42084 && readable(reg)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + } io{c.map}; const bool owned=adlnOutputWiring.dsi==1 && dsiProfile.valid() && dsiBoot.complete() && c.controller==controller && controller==adlnTopologyController && !clock.thread && !dsiResourcesReady && c.transcoders.dsi0Pipe==UINT32_MAX && @@ -10570,11 +10576,16 @@ bool XE::bindDSIResources(IOService *controller) { if(!irq.controller && !irq.handlers && !irq.softwareHandlers && !irq.quiescing && !irq.admittedPipes && irq.state.transcoders.dsi0Pipe==UINT32_MAX && !irq.state.begun && !irq.state.enabled && !irq.state.faulted && clock.allocation.retain(0,dsiBoot.source.identity.pll,reinterpret_cast(controller),snapshot)) { - c.bootDSI=dsiBoot.source;c.transcoders.dsi0Pipe=0; - irq.state.transcoders=c.transcoders;irq.base=c.map->getVirtualAddress();irq.lock=irqLock; - __atomic_store_n(&irq.admittedPipes,1U,__ATOMIC_RELEASE); - __atomic_store_n(&irq.controller,controller,__ATOMIC_RELEASE); - __atomic_store_n(&dsiResourcesReady,true,__ATOMIC_RELEASE);ready=true; + // Native X/Y tiled scanout and every external output use GGTT. + // The live DSI GOP is linear, so this shared mode can be prepared + // without disabling its plane. Retain PLL/power on any failure. + if(XELP::DSI::prepareGGTT(io,dsiBoot,xelpMemory,dsiGGTT)) { + c.bootDSI=dsiBoot.source;c.transcoders.dsi0Pipe=0; + irq.state.transcoders=c.transcoders;irq.base=c.map->getVirtualAddress();irq.lock=irqLock; + __atomic_store_n(&irq.admittedPipes,1U,__ATOMIC_RELEASE); + __atomic_store_n(&irq.controller,controller,__ATOMIC_RELEASE); + __atomic_store_n(&dsiResourcesReady,true,__ATOMIC_RELEASE);ready=true; + } else adlnFault(c.memoryFaulted,__LINE__); } IOSimpleLockUnlockEnableInterrupt(irqLock,saved); } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 4d200d30..f5f7cce1 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -10,6 +10,7 @@ #include "kern_tgl_tc_boot.hpp" #include "kern_dsi_boot.hpp" +#include "kern_dsi_ggtt.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" @@ -135,6 +136,7 @@ class XE { OSData *dsiFirmware {}; // Private immutable copy backing profile/sequence spans. XELP::DSI::Profile dsiProfile; XELP::DSI::Boot dsiBoot; + XELP::DSI::GGTTMode dsiGGTT; bool prepareDSIFirmware(IOService *); bool dsiResourcesReady {}; bool bindDSIResources(IOService *); diff --git a/tests/dsi_ggtt.py b/tests/dsi_ggtt.py new file mode 100644 index 00000000..5e8507c4 --- /dev/null +++ b/tests/dsi_ggtt.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Exercise shared GGTT preparation against the actual GOP certificate.""" +from pathlib import Path +import subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'tests/dsi_boot.cpp').read_text() +io=s[s.index('struct IO {'):s.index('Boot capture(')] +code=r'''#include "kern_dsi_ggtt.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +struct Probe:IO { + bool loseWrite=false,sourceDrift=false; + void write(uint32_t reg,uint32_t value){assert(reg==0x42084);writes.push_back(reg); + if(!loseWrite)regs[reg]=value;if(sourceDrift)regs[0x6B000]^=1;} +}; +Boot certify(Probe &io){auto boot=captureBoot(io,AlderLakeN::readDisplay(io,true),profile(),io.memory,true); + assert(verifyBootPrefix(io,boot,io.memory));return boot;} +int main(){ + for(bool already:{false,true}){ + Probe io;io.regs[0x42084]=0x20|(already?(1U<<30):0);auto b=certify(io);auto before=io.regs;GGTTMode mode; + assert(prepareGGTT(io,b,io.memory,mode) && mode.ready() && b.complete()); + assert(mode.before==before[0x42084] && mode.after==((1U<<30)|0x20) && mode.writes==!already); + assert(io.writes.size()==!already && bootUnchanged(io,b,io.memory)); + before[0x42084]|=1U<<30;assert(io.regs==before); + assert(!prepareGGTT(io,b,io.memory,mode) && mode.ready() && io.writes.size()==!already); + } + for(auto reg:{0x6B000U,0x70180U,0x7019CU,0x71008U,0x70B08U,0x42084U}){ + Probe io;auto b=certify(io);GGTTMode mode;io.regs[reg]^=reg==0x71008?0x80000000U:1; + assert(!prepareGGTT(io,b,io.memory,mode) && !mode.ready() && io.writes.empty()); + } + for(bool lost:{false,true}){ + Probe io;auto b=certify(io);GGTTMode mode;io.loseWrite=lost;io.sourceDrift=!lost; + assert(!prepareGGTT(io,b,io.memory,mode) && !mode.ready() && !b.usable()); + assert(mode.writes==1 && io.writes==std::vector{0x42084}); + assert(mode.status==(lost?GGTTMode::Status::Readback:GGTTMode::Status::Changed)); + assert(!prepareGGTT(io,b,io.memory,mode) && mode.writes==1); + } + puts("PASS shared DSI GGTT mode: linear GOP preserved, exact one-bit write, already configured, source/peer exclusion, readback/drift failure and no retry"); +} +'''.replace('IO_FIXTURE',io) +with tempfile.TemporaryDirectory(prefix='dsi-ggtt-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index a9900ac5..97468b20 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -8,7 +8,7 @@ boot=(root/'tests/dsi_boot.cpp').read_text() io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] code=r''' -#include "kern_dsi_boot.hpp" +#include "kern_dsi_ggtt.hpp" #include "kern_display13_power_bridge.hpp" #include "kern_display13_fb_irq.hpp" #include "kern_xelp_combo_clock.hpp" @@ -28,7 +28,13 @@ void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,bool saved){assert(lockLevel==3 && saved);lockLevel=2;} struct IOService {alignas(8) uint8_t bytes[0x1C20]{};IOService *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);}}; namespace Display13FBMemory {templateT field(const void *p,unsigned o){T v;memcpy(&v,static_cast(p)+o,sizeof(v));return v;}} -struct Map:IO {uintptr_t getVirtualAddress(){return 0x1000000;}}; +struct Map:IO { + uint32_t dpt=0;bool loseWrite=false,sourceDrift=false; + uintptr_t getVirtualAddress(){return reinterpret_cast(&dpt)-0x42084;} + uint32_t read(uint32_t reg){if(reg==0x42084 && regs[reg]!=dpt){writes.push_back(reg);if(loseWrite)dpt=regs[reg];else regs[reg]=dpt;if(sourceDrift)regs[0x6B000]^=1;}return IO::read(reg);} +}; +using IOMemoryMap=Map; +void adlnFault(bool &fault,unsigned){assert(lockLevel==3);fault=true;} struct Display13FBBootIO { Map *map;bool readable(uint32_t r){return map && map->readable(r);} uint32_t read(uint32_t r){return map->read(r);} @@ -43,7 +49,7 @@ struct XE { bool display13FBIRQReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; struct {unsigned dsi=1;} adlnOutputWiring; - Profile dsiProfile;Boot dsiBoot; + Profile dsiProfile;Boot dsiBoot;GGTTMode dsiGGTT; bool bindDSIResources(IOService *); }; BODY @@ -75,13 +81,26 @@ clock.allocation.binding[1]=2;rejected();clock.allocation.binding[1]=0; map.regs[0x6B000]^=1;rejected();map.regs[0x6B000]^=1; map.regs[0x164280]^=1;rejected();map.regs[0x164280]^=1; + const auto initialBoot=xe.dsiBoot; assert(xe.bindDSIResources(&controller) && !lockLevel && xe.dsiResourcesReady); assert(c.transcoders.dsi0Pipe==0 && irq.state.transcoders.dsi0Pipe==0 && c.bootDSI.sameSource(xe.dsiBoot.source)); assert(irq.controller==&controller && irq.lock==&irqLock && irq.base==map.getVirtualAddress() && irq.admittedPipes==1); assert(clock.allocation.external[0]==reinterpret_cast(&controller) && clock.allocation.binding[0]==2); - assert(!xe.bindDSIResources(&controller) && !lockLevel && !map.writes.size()); + assert(!xe.bindDSIResources(&controller) && !lockLevel && map.writes==std::vector{0x42084}); + assert(xe.dsiGGTT.ready() && xe.dsiGGTT.writes==1 && xe.dsiBoot.dpt==(1U<<30)); assert(b.pgRefs[0]==1 && b.ddiRefs[0]==1); // publication did not acquire/release someone else's lease - puts("PASS production DSI resources: clock-owner-IRQ lock order, retained leases/source, PLL ownership, atomic mapping publication and rejection before publication"); + for(bool lost:{false,true}){ + clock.allocation={};irq={};c.transcoders={};c.bootDSI={};c.memoryFaulted=false; + xe.dsiResourcesReady=false;xe.dsiGGTT={};xe.dsiBoot=initialBoot; + map=Map{};map.regs[0x45454]=3;map.regs[0x42000]=0x8E380000; + map.loseWrite=lost;map.sourceDrift=!lost; + assert(!xe.bindDSIResources(&controller) && !lockLevel && c.memoryFaulted && !xe.dsiResourcesReady); + assert(!irq.controller && !irq.admittedPipes && c.transcoders.dsi0Pipe==UINT32_MAX && !xe.dsiBoot.usable()); + assert(clock.allocation.external[0]==reinterpret_cast(&controller) && b.pgRefs[0]==1 && b.ddiRefs[0]==1); + assert(map.writes==std::vector{0x42084} && xe.dsiGGTT.writes==1); + assert(!xe.bindDSIResources(&controller) && map.writes.size()==1); + } + puts("PASS production DSI resources: clock-owner-IRQ lock order, retained leases/source, PLL ownership, GGTT readback, atomic mapping publication and retained leases after write failure"); } '''.replace('IO_FIXTURE',io).replace('BODY',body) # No kernel/Apple object contents are loaded; these leaves model publication only. diff --git a/tools/test.py b/tools/test.py index 039ec89d..00cb2a6d 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] From 54e3c23a209a0f5e30b7f5921d98486c2137a28b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:34:28 +0900 Subject: [PATCH 031/139] Add fixed DSI scanout transaction with WM and live-surface proof --- WhateverXe/kern_dsi_surface.hpp | 112 ++++++++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/dsi_surface.py | 116 ++++++++++++++++++++++++++++++++ tools/test.py | 3 +- 4 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 WhateverXe/kern_dsi_surface.hpp create mode 100644 tests/dsi_surface.py diff --git a/WhateverXe/kern_dsi_surface.hpp b/WhateverXe/kern_dsi_surface.hpp new file mode 100644 index 00000000..10735394 --- /dev/null +++ b/WhateverXe/kern_dsi_surface.hpp @@ -0,0 +1,112 @@ +// GOP-to-native scanout transaction for the fixed DSI0/pipe-A mode. Register +// ordering follows Linux v6.12 icl_plane_update_noarm/arm. This owns no Apple +// objects: the adapter proves/pins actual GGTT backing, leases, IRQ and DDB, +// serializes native writers, and publishes the resulting native caches. +#ifndef KERN_DSI_SURFACE_HPP +#define KERN_DSI_SURFACE_HPP +#include "kern_dsi_session.hpp" +#include "kern_display13_wm.hpp" +#include "kern_display13_dbuf.hpp" +namespace XELP { namespace DSI { namespace Surface { +struct Plane { + uint32_t control {},color {},stride {},size {},surface {},position {},offset {}; +}; +struct Backing {uint64_t handle {},gpu {},bytes {},pitch {};}; +enum class Status : uint32_t {Input,Source,Watermark,Plane,Completion,Complete}; +struct Result { + Status status {Status::Input};uint32_t writes {};Ticket ticket; + Display13WM::Program wm;Display13WM::Latch latch; +}; +constexpr uint32_t Registers[]={0x70188,0x7018C,0x70190,0x701A4,0x701CC,0x70180,0x7019C}; +inline void values(const Plane &p,uint32_t (&v)[7]) { + v[0]=p.stride;v[1]=p.position;v[2]=p.size;v[3]=p.offset;v[4]=p.color;v[5]=p.control;v[6]=p.surface; +} +inline bool shape(const Plane &p,const Profile &profile) { + // The existing Display13 WM backend admits linear and uncompressed Y. + // Keep the P2 fixed RGB8888 geometry; rotation/scaling/color modes are not + // implicitly implemented by copying the donor's register cache. + return profile.valid() && profile.mode.valid() && profile.mode.width<=8192 && profile.mode.height<=8192 && + Display13WM::supportedRGBControl(p.control) && (p.control&(1U<<31)) && + ((p.control>>23)&31)==8 && !(p.control&((3U<<21)|(1U<<9)|(1U<<8))) && + !p.position && !p.offset && p.size==((uint32_t(profile.mode.height)-1)<<16 | (uint32_t(profile.mode.width)-1)) && + p.stride && p.stride<=2047 && !(p.surface&4095); +} +inline bool target(const Plane &p,const Profile &profile,const Backing &b) { + if(!shape(p,profile) || !b.handle || !b.bytes || (b.bytes&4095) || b.gpu!=p.surface || + b.gpu>=0x100000000ULL || b.bytes>0x100000000ULL-b.gpu || + Display13WM::arbitrationControl(p.control)!=p.control)return false; + const bool tiled=((p.control>>10)&7)==4; + const auto pitch=uint64_t(p.stride)*(tiled?128:64); + const auto rows=tiled?((profile.mode.height+31)&~31U):profile.mode.height; + return b.pitch==pitch && pitch>=uint64_t(profile.mode.width)*4 && pitch*rows<=b.bytes; +} +template bool word(IO &io,uint32_t reg,uint32_t mask,uint32_t expected) { + if(!io.readable(reg))return false;const auto v=io.read(reg); + return v!=UINT32_MAX && (v&mask)==expected; +} +template bool matches(IO &io,const Plane &p,bool live) { + uint32_t v[7];values(p,v); + for(unsigned i=0;i<7;++i)if(!word(io,Registers[i],UINT32_MAX,v[i]))return false; + return !live || word(io,0x701AC,UINT32_MAX,p.surface); +} +template bool source(IO &io,const Readout &link) { + if(link.identity.pipe!=0 || !sourceUnchanged(io,link) || !word(io,0x42084,1U<<30,1U<<30) || + !word(io,0x6001C,UINT32_MAX,((link.htotal&65535)<<16)|(link.vtotal&65535)) || + !word(io,0x6B010,UINT32_MAX,link.vtotal) || !word(io,0x6B028,UINT32_MAX,0) || + !word(io,0x60400,1U<<31,0) || !word(io,0x7B808,0xC0000000U,0) || + !word(io,0x70080,63,0) || !word(io,0x78000,0xF0300000U,0) || !word(io,0x78004,0x80008000U,0))return false; + for(unsigned p=1;p<7;++p)if(!word(io,0x70180+0x100*p,1U<<31,0))return false; + for(unsigned s=0;s<2;++s)if(!word(io,0x68180+0x100*s,1U<<31,0))return false; + for(unsigned d=0;d<3;++d)if(!word(io,0x70B08+0x100*d,0x80000001U,0))return false; + return true; +} +// IO::ready revalidates the adapter's actual object/backing/resource binding. +// Hold its mode serialization and power through the final latch; do not hold +// an IRQ spinlock while waiting. Failure after begin retains both backings and +// the lease in Session. This never calls cleanup or reports a successful stop. +template Result change(IO &io,Session &session,const Readout &link,const Profile &profile, + const Plane ¤t,const Plane &next,const Backing &backing, + const Display13WM::Snapshot &wm) { + Result r; + if(!io.ready() || !link.matches(profile) || !session.identity.same(link.identity) || + !shape(current,profile) || !target(next,profile,backing) || next.surface==current.surface || + ((next.control^current.control)&~((7U<<28)|(7U<<10))) || + (next.color!=current.color && next.color!=(current.color|(1U<<13))) || + wm.pipeIndex!=0 || wm.transcoders.dsi0Pipe!=0 || wm.cursorActive || !wm.primary || + wm.primary->width!=profile.mode.width || wm.primary->height!=profile.mode.height || + wm.primary->htotal!=profile.mode.width+profile.mode.hblank || + wm.primary->pixelHz!=uint64_t(profile.mode.clockKHz)*1000 || wm.pipePixelHz!=wm.primary->pixelHz || + wm.primary->yTiled!=(((next.control>>10)&7)==4) || wm.primarySurface!=next.surface || + wm.nativeClockHz!=io.nativeClockHz() || !Display13WM::clockFits(wm,wm.nativeClockHz))return r; + const auto ddb=Display13DBUF::decode(wm.primaryDDB); + if(!Display13DBUF::valid(ddb) || Display13DBUF::encoded(ddb)!=wm.primaryDDB || + ddb.end-ddb.start!=wm.primary->allocation)return r; + r.status=Status::Source; + if(!source(io,link) || !matches(io,current,true) || + !word(io,0x7027C,UINT32_MAX,wm.primaryDDB) || !word(io,0x7017C,UINT32_MAX,wm.cursorDDB) || + !word(io,0x45004,UINT32_MAX,wm.ipc))return r; + for(auto reg:Registers)if(!io.readable(reg))return r; + if(!io.readable(0x45270) || !io.ready() || !session.beginSurface(backing.handle))return r; + r.ticket=session.ticket(); + const auto fail=[&]() {session.abort(r.ticket,SessionError::Switch);return r;}; + r.status=Status::Watermark; + r.wm=Display13WM::program(io,*wm.primary,false,wm.wm0Only,0);r.writes+=r.wm.writes; + if(r.wm.status!=Display13WM::Status::Complete)return fail(); + io.write(0x45270,wm.pipeLineTime);++r.writes; + if(!word(io,0x45270,UINT32_MAX,wm.pipeLineTime) || !source(io,link) || !matches(io,current,true) || !io.ready())return fail(); + r.status=Status::Plane; + uint32_t v[7];values(next,v); + // All non-arming words precede control/surface. The existing enabled + // plane remains enabled; SURF is the final write that arms the change. + for(unsigned i=0;i<7;++i) { + io.write(Registers[i],v[i]);++r.writes; + if(!word(io,Registers[i],UINT32_MAX,v[i]))return fail(); + } + r.status=Status::Completion;r.latch=Display13WM::latch(io,wm); + if(r.latch.status!=Display13WM::Status::Complete || !io.ready() || !source(io,link) || !matches(io,next,true))return fail(); + SurfaceProof proof {r.ticket,link.identity,backing.handle,true,true,true}; + if(!session.completeSurface(proof))return fail(); + r.status=Status::Complete;return r; +} +}}} +#endif diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f5f7cce1..d055fda8 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -11,6 +11,7 @@ #include "kern_tgl_tc_boot.hpp" #include "kern_dsi_boot.hpp" #include "kern_dsi_ggtt.hpp" +#include "kern_dsi_surface.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" diff --git a/tests/dsi_surface.py b/tests/dsi_surface.py new file mode 100644 index 00000000..212cc47a --- /dev/null +++ b/tests/dsi_surface.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Exercise the DSI scanout transaction with real WM and session cores.""" +from pathlib import Path +import hashlib,subprocess,sys,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'tests/dsi_boot.cpp').read_text() +io=s[s.index('struct IO {'):s.index('Boot capture(')] +code=r''' +#include "kern_dsi_boot.hpp" +#include "kern_dsi_surface.hpp" +#include +#include +#include +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +struct Sim:IO { + uint64_t elapsed=0,clock=614400000;uint32_t drop=UINT32_MAX,drift=0; + bool frozen=false,oldLive=false,backward=false,unbound=false,loseBinding=false,immediate=false; + bool ready(){return !unbound && !(loseBinding && !writes.empty());} + uint64_t nativeClockHz(){return clock;}uint64_t nowUs(){return elapsed;} + void write(uint32_t r,uint32_t v){writes.push_back(r);if(r!=drop)regs[r]=v;if(r==0x7019C && immediate){++regs[0x70040];regs[0x701AC]=v;}} + void delayUs(unsigned us){elapsed+=us;if(elapsed==2000){ + if(!frozen)regs[0x70040]=backward?99:regs[0x70040]+1; + if(!oldLive)regs[0x701AC]=regs[0x7019C]; + if(drift)regs[drift]^=drift==0x42084 ? 1U<<30 : 1; + }} +}; +struct Fixture { + Sim io;Profile p=profile();Readout link;Session session; + Surface::Plane current{0x94000008,0x5000,68,0x077F0437,0,0,0}; + Surface::Plane next{0x94001008,0x7000,34,0x077F0437,0x10000000,0,0}; + Surface::Backing backing{201,0x10000000,4352*1920,4352}; + Display13WM::Plan plan;Display13WM::Snapshot snapshot; + Fixture(){ + link=decodeReadout([&](uint32_t r,uint32_t &v){v=io.read(r);return true;},38400);assert(link.matches(p)); + Capabilities caps;caps.installed=Capabilities::Adoption;assert(session.bind(caps,link,p,1,100,200)); + plan=Display13WM::plan(1080,1126,135255000,2048,Display13WM::decodeLatency(0x63503300,0x00909090,30),true,true,1920); + assert(plan.status==Display13WM::Status::Complete); + snapshot.primary=&plan;snapshot.primaryDDB=0x07FF0000;snapshot.primarySurface=next.surface; + snapshot.nativeClockHz=io.clock;snapshot.pipeLineTime=Display13WM::pipeLineTime(1126,135255000); + snapshot.pipePixelHz=135255000;snapshot.transcoders.dsi0Pipe=0; + io.regs[0x42084]=1U<<30;io.regs[0x6001C]=0x0437077F;io.regs[0x6B010]=link.vtotal; + io.regs[0x7027C]=snapshot.primaryDDB;io.regs[0x70040]=100; + uint32_t values[7];Surface::values(current,values); + for(unsigned i=0;i<7;++i)io.regs[Surface::Registers[i]]=values[i]; + } + Surface::Result run(){return Surface::change(io,session,link,p,current,next,backing,snapshot);} +}; +templatevoid put(void *p,unsigned offset,T v){memcpy(static_cast(p)+offset,&v,sizeof(v));} +templateT get(const void *p,unsigned offset){T v;memcpy(&v,static_cast(p)+offset,sizeof(v));return v;} +void native(const char *path){ + std::ifstream file(path,std::ios::binary);std::vector bytes((std::istreambuf_iterator(file)),{});assert(!bytes.empty()); + constexpr size_t size=0x2000000;assert(bytes.size()(mmap(nullptr,size,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0)); + assert(image!=MAP_FAILED);memcpy(image,bytes.data(),bytes.size()); + for(bool y:{false,true}){ + Fixture f;alignas(8) uint8_t fb[0x4B90]{},controller[0x1C20]{},plane[0x1C8]{},display[0x3730]{}; + put(plane,0x68,fb);put(fb,0x1D0,controller);put(fb,0x4330,f.next.surface);put(fb,0x4334,uint32_t(f.backing.pitch)); + put(fb,0x4A18,uint8_t(y));put(plane,0x100,f.current.control);put(plane,0x104,f.current.color); + put(plane,0x118,f.current.stride);put(display,0xF8,uint32_t(f.p.mode.width));put(display,0xFC,uint32_t(f.p.mode.height)); + reinterpret_cast(image+0x1F50)(plane,display); + f.next={get(plane,0x100),get(plane,0x104),get(plane,0x118), + get(plane,0x11C),get(plane,0x120),0,get(plane,0x110)}; + assert(((f.next.control>>10)&7)==(y?4U:1U)); + if(y)assert(f.run().status==Surface::Status::Complete); + else assert(!Surface::target(f.next,f.p,f.backing)); // X is outside the shared WM contract + } + assert(!munmap(image,size));puts("PASS exact donor setupPlane cache feeds the DSI Y surface transaction; X rejected before MMIO"); +} +int main(int argc,char **argv){ + {Fixture f;const auto result=f.run();assert(result.status==Surface::Status::Complete && result.writes==17); + assert(result.latch.status==Display13WM::Status::Complete && result.latch.frameBefore==100 && result.latch.frameAfter==101); + assert(f.session.state==SessionState::Streaming && f.session.currentBacking==201 && f.session.retiredBacking==200 && f.session.lease==100); + assert(f.io.writes.back()==0x7019C && f.io.writes[f.io.writes.size()-2]==0x70180); + for(auto r:f.io.writes)assert((r>=0x70240 && r<=0x70268) || r==0x45270 || + r==0x70188 || r==0x7018C || r==0x70190 || r==0x701A4 || r==0x701CC || r==0x70180 || r==0x7019C); + assert(sourceUnchanged(f.io,f.link) && f.io.regs[0x70008]==0 && f.io.regs[0x42084]==(1U<<30)); + } + for(unsigned problem=0;problem<19;++problem){Fixture f; + switch(problem){ + case 0:f.backing.pitch+=128;break;case 1:f.backing.bytes-=4096;break;case 2:f.next.control|=1U<<15;break; + case 3:f.next.control^=0x1400;break;case 4:f.next.offset=1;break;case 5:f.next.surface+=4096;break; + case 6:f.plan.yTiled=false;break;case 7:f.snapshot.primarySurface=0;break;case 8:f.snapshot.primaryDDB=0;break; + case 9:f.io.regs[0x42084]=0;break;case 10:f.io.regs[0x70080]=7;break;case 11:f.io.regs[0x70780]=1U<<31;break; + case 12:f.io.regs[0x70B08]=1;break;case 13:f.io.regs[0x6001C]^=1;break;case 14:f.io.regs[0x701AC]=4096;break; + case 15:f.io.unbound=true;break;case 16:f.p.mode.height=0;break; + case 17:f.p.mode.height=40000;break;case 18:f.snapshot.nativeClockHz+=1;break; + } + assert(f.run().status!=Surface::Status::Complete && f.io.writes.empty() && f.session.state==SessionState::FirmwareActive); + } + for(auto drop:{0x70240U,0x45270U,0x70188U,0x701CCU,0x70180U,0x7019CU}){ + Fixture f;f.io.drop=drop;const auto r=f.run();assert(r.status!=Surface::Status::Complete && !f.io.writes.empty()); + assert(f.session.state==SessionState::Faulted && f.session.currentBacking==200 && f.session.pendingBacking==201 && !f.session.takeCleanup().lease); + } + for(unsigned failure=0;failure<7;++failure){Fixture f; + switch(failure){case 0:f.io.frozen=true;break;case 1:f.io.oldLive=true;break;case 2:f.io.backward=true;break; + case 3:f.io.drift=0x6B008;break;case 4:f.io.loseBinding=true;break;case 5:f.io.drift=0x42084;break;case 6:f.io.immediate=f.io.frozen=true;break;} + const auto r=f.run();assert(r.status!=Surface::Status::Complete && f.session.state==SessionState::Faulted); + assert(f.session.currentBacking==200 && f.session.pendingBacking==201 && !f.session.takeRetiredBacking()); + } + if(argc==2)native(argv[1]); + puts("PASS DSI surface: fixed native Y geometry, real WM programming/latch, link preservation, live/frame proof, failed writes/latch retain both backings"); +} +'''.replace('IO_FIXTURE',io) +donor=None +if len(sys.argv)==2: + donor=Path(sys.argv[1]).resolve() + assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +with tempfile.TemporaryDirectory(prefix='dsi-surface-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1',*(['-arch','x86_64'] if donor else ['-fsanitize=address,undefined']),'-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test'),*([str(donor)] if donor else [])],check=True) diff --git a/tools/test.py b/tools/test.py index 00cb2a6d..ec0fdeef 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,9 +8,10 @@ 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', '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 += ['dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_boot_probe_native',[sys.executable,str(R/'tests/dsi_boot_probe.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] From a8e688f562514e0bd0bee436ee2ecc1d866316b3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:43:34 +0900 Subject: [PATCH 032/139] Add bounded DSI host quiesce before IRQ and resource retirement --- WhateverXe/kern_dsi_quiesce.hpp | 107 ++++++++++++++++++++++++++++++++ WhateverXe/kern_dsi_surface.hpp | 5 +- WhateverXe/kern_xe.hpp | 1 + tests/dsi_quiesce.py | 78 +++++++++++++++++++++++ tools/test.py | 2 +- 5 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 WhateverXe/kern_dsi_quiesce.hpp create mode 100644 tests/dsi_quiesce.py diff --git a/WhateverXe/kern_dsi_quiesce.hpp b/WhateverXe/kern_dsi_quiesce.hpp new file mode 100644 index 00000000..9d695963 --- /dev/null +++ b/WhateverXe/kern_dsi_quiesce.hpp @@ -0,0 +1,107 @@ +// Minimum P2 host shutdown, following Linux v7.0 gen11_dsi_post_disable: +// stop plane/cursor, transcoder, ULPS, function, then ungate/stop/gate buffer. +// Panel DCS/GPIO/PWM and cold restart remain separate panel/backend operations. +#ifndef KERN_DSI_QUIESCE_HPP +#define KERN_DSI_QUIESCE_HPP +#include "kern_dsi_surface.hpp" +#include "kern_display13_cursor.hpp" +namespace XELP { namespace DSI { namespace Quiesce { +enum class Stage : uint32_t {Source,Cursor,Plane,Transcoder,Commands,ULPS,Function,PortClock,Buffer,Gate,Complete}; +enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Commands,Cursor}; +struct Result { + Stage stage {Stage::Source};Error error {Error::Input};uint32_t writes {},waitUs {},reg {},value {}; + uint32_t frameBefore {},frameAfter {};Display13Cursor::Result cursor; + bool complete() const {return stage==Stage::Complete && error==Error::None;} +}; +// Adapter holds FB/mode and shared clock serialization, keeps power/PLL/backing +// pinned and prevents new IRQ/flip producers. Never hold an IRQ spinlock here. +// IO::ready is an ownership check that remains meaningful after TRANSCONF is +// stopped, not sourceUnchanged(). No IRQ queue or allocator handle is invented +// by this core. Completion permits real IRQ drain/clock detach; it is not a +// Session StopProof and does not release anything. +// P2 has no command producer. Require empty hardware command FIFOs and LP idle +// both before shutdown and before ULPS; a pending panel sequence needs the +// later Host/panel drain backend. Never silently discard such a command. +template Result run(IO &io,const Readout &link,const Profile &profile) { + Result r; + if(!link.matches(profile) || link.identity.pipe!=0 || !profile.mode.valid())return r; + const auto period=(uint64_t(profile.mode.htotal())*profile.mode.vtotal()*1000+profile.mode.clockKHz-1)/profile.mode.clockKHz; + if(period<1000 || period>100000)return r; + const auto held=[&]() { + if(!io.ready() || !io.readable(0x45404))return false; + const auto wells=io.read(0x45404); + return wells!=UINT32_MAX && (wells&0x403)==0x403; + }; + const auto read=[&](uint32_t reg,uint32_t &v) { + r.reg=reg;if(!held()){r.error=Error::Ownership;return false;} + if(!io.readable(reg)){r.error=Error::Readback;return false;} + v=io.read(reg);r.value=v;if(v==UINT32_MAX){r.error=Error::Readback;return false;}return true; + }; + const auto write=[&](uint32_t reg,uint32_t v) { + if(!held() || !io.readable(reg)){r.error=Error::Ownership;return false;} + io.write(reg,v);++r.writes;return true; + }; + const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t value,uint32_t budget,uint32_t step) { + for(uint32_t us=0;;us+=step) { + uint32_t observed=0;if(!read(reg,observed))return false; + if((observed&mask)==value)return true; + if(us>=budget){r.error=Error::Timeout;return false;} + io.delayUs(step);r.waitUs+=step; + } + }; + const auto empty=[&]() { + uint32_t credits=0,lp=0; + if(!read(0x6B0D0,credits) || !read(0x6B0D8,lp))return false; + if((credits&0x1FFF)!=0x1040 || (lp&((1U<<17)|(1U<<16)|1U))) {r.error=Error::Commands;return false;} + return true; + }; + if(!held()){r.error=Error::Ownership;return r;} + if(!Surface::source(io,link,true) || !empty())return r; + const uint32_t registers[]={0x70080,0x70084,0x70088,0x700AC,0x70040,0x70180,0x7019C, + 0x7B008,0x6B0D8,0x6B400,0x164280,0x64000}; + for(auto reg:registers)if(!io.readable(reg))return r; + r.stage=Stage::Cursor; + Display13Transcoder::Bindings bindings;bindings.dsi0Pipe=0; + r.cursor=Display13Cursor::stop(io,0,bindings);r.writes+=r.cursor.writes;r.waitUs+=r.cursor.waitUs; + if(r.cursor.status!=Display13Cursor::Status::Complete){r.error=Error::Cursor;return r;} + r.stage=Stage::Plane; + uint32_t control=0,surface=0; + if(!read(0x70180,control) || !read(0x7019C,surface) || (surface&4095))return r; + if(!write(0x70180,control&~(1U<<31)) || !write(0x7019C,surface) || + !wait(0x70180,1U<<31,0,0,1) || !read(0x70040,r.frameBefore))return r; + // Arm even an already-disabled shadow image and prove a subsequent VBlank + // while DSI timing is still running. SURF/live equality alone cannot prove + // that the plane stopped fetching its old allocation. + for(uint32_t us=0;;us+=1000) { + if(!read(0x70040,r.frameAfter))return r; + const auto frames=uint32_t(r.frameAfter-r.frameBefore); + if(frames && frames<0x80000000U)break; + if(frames || us>=3*period+1000){r.error=Error::Timeout;return r;} + if(!held()){r.error=Error::Ownership;return r;} + io.delayUs(1000);r.waitUs+=1000; + } + if(!held() || !Surface::source(io,link) || !wait(0x70180,1U<<31,0,0,1))return r; + r.stage=Stage::Transcoder; + if(!write(0x7B008,link.conf&~(1U<<31)) || !wait(0x7B008,0xC0000000U,0,50000,1000))return r; + r.stage=Stage::Commands;if(!empty())return r; + r.stage=Stage::ULPS;uint32_t lp=0; + if(!read(0x6B0D8,lp) || !write(0x6B0D8,(lp&~(1U<<8))|1) || !wait(0x6B0D8,1U<<16,1U<<16,10,1))return r; + r.stage=Stage::Function; + if(!write(0x6B400,link.function&~(1U<<31)) || !wait(0x6B400,1U<<31,0,0,1))return r; + r.stage=Stage::PortClock;uint32_t mux=0; + if(!read(0x164280,mux) || (mux&3)!=link.identity.pll || + !write(0x164280,mux&~(1U<<10)) || !wait(0x164280,UINT32_MAX,mux&~(1U<<10),0,1))return r; + r.stage=Stage::Buffer;uint32_t buffer=0; + if(!read(0x64000,buffer) || !write(0x64000,buffer&~(1U<<31)) || !wait(0x64000,0x80000080U,0x80,8,1))return r; + r.stage=Stage::Gate; + if(!read(0x164280,mux) || (mux&3)!=link.identity.pll || + !write(0x164280,mux|(1U<<10)) || !wait(0x164280,UINT32_MAX,mux|(1U<<10),0,1))return r; + // Hardware-only proof. The caller must separately drain actual native IRQ + // handlers/events and detach the shared clock owner before releasing memory. + if(!wait(0x70180,1U<<31,0,0,1) || !wait(0x70080,63,0,0,1) || + !wait(0x7B008,0xC0000000U,0,0,1) || !wait(0x6B400,1U<<31,0,0,1) || + !wait(0x64000,0x80000080U,0x80,0,1) || !wait(0x6B0D8,1U<<16,1U<<16,0,1))return r; + r.stage=Stage::Complete;r.error=Error::None;return r; +} +}}} +#endif diff --git a/WhateverXe/kern_dsi_surface.hpp b/WhateverXe/kern_dsi_surface.hpp index 10735394..d69d8bc0 100644 --- a/WhateverXe/kern_dsi_surface.hpp +++ b/WhateverXe/kern_dsi_surface.hpp @@ -49,12 +49,13 @@ template bool matches(IO &io,const Plane &p,bool live) { for(unsigned i=0;i<7;++i)if(!word(io,Registers[i],UINT32_MAX,v[i]))return false; return !live || word(io,0x701AC,UINT32_MAX,p.surface); } -template bool source(IO &io,const Readout &link) { +template bool source(IO &io,const Readout &link,bool stoppingCursor=false) { if(link.identity.pipe!=0 || !sourceUnchanged(io,link) || !word(io,0x42084,1U<<30,1U<<30) || !word(io,0x6001C,UINT32_MAX,((link.htotal&65535)<<16)|(link.vtotal&65535)) || !word(io,0x6B010,UINT32_MAX,link.vtotal) || !word(io,0x6B028,UINT32_MAX,0) || !word(io,0x60400,1U<<31,0) || !word(io,0x7B808,0xC0000000U,0) || - !word(io,0x70080,63,0) || !word(io,0x78000,0xF0300000U,0) || !word(io,0x78004,0x80008000U,0))return false; + (!stoppingCursor && !word(io,0x70080,63,0)) || + !word(io,0x78000,0xF0300000U,0) || !word(io,0x78004,0x80008000U,0))return false; for(unsigned p=1;p<7;++p)if(!word(io,0x70180+0x100*p,1U<<31,0))return false; for(unsigned s=0;s<2;++s)if(!word(io,0x68180+0x100*s,1U<<31,0))return false; for(unsigned d=0;d<3;++d)if(!word(io,0x70B08+0x100*d,0x80000001U,0))return false; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index d055fda8..ae1508b2 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -12,6 +12,7 @@ #include "kern_dsi_boot.hpp" #include "kern_dsi_ggtt.hpp" #include "kern_dsi_surface.hpp" +#include "kern_dsi_quiesce.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" diff --git a/tests/dsi_quiesce.py b/tests/dsi_quiesce.py new file mode 100644 index 00000000..8d414523 --- /dev/null +++ b/tests/dsi_quiesce.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Fault injection for the DSI host shutdown core; no simulated cleanup proof.""" +from pathlib import Path +import subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'tests/dsi_boot.cpp').read_text();io=s[s.index('struct IO {'):s.index('Boot capture(')] +code=r''' +#include "kern_dsi_quiesce.hpp" +#include "kern_xelp_memory_layout.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +struct Sim:IO { + uint32_t drop=UINT32_MAX,elapsed=0,failStage=0;bool frozen=false,unbound=false,losePower=false,pendingAfterStop=false; + bool ready(){return !unbound;} + void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg!=drop)regs[reg]=value;} + void delayUs(unsigned us){elapsed+=us; + if(losePower){regs[0x45404]=0;return;} + if(!frozen && us>=1000)++regs[0x70040]; + if(!(regs[0x7B008]&(1U<<31)) && failStage!=1){regs[0x7B008]&=~(1U<<30);if(pendingAfterStop)regs[0x6B0D0]=0;} + if((regs[0x6B0D8]&1) && failStage!=2)regs[0x6B0D8]|=1U<<16; + if(!(regs[0x64000]&(1U<<31)) && failStage!=3)regs[0x64000]|=0x80; + } +}; +struct Fixture { + Sim io;Profile p=profile();Readout link; + Fixture(bool cursor=false){ + io.regs[0x42084]=1U<<30;io.regs[0x6B0D0]=0x01001040; + io.regs[0x6001C]=0x0437077F;io.regs[0x6B010]=io.regs[0x6B00C];io.regs[0x70040]=100; + io.regs[0x70080]=cursor?7:0;io.regs[0x70084]=cursor?0x200000:0; + // A live external pipe must survive the target-only stop. + io.regs[0x71008]=0xC0000000;io.regs[0x61400]=0x90000000;io.regs[0x71180]=0x94001000; + io.regs[0x7119C]=0x30000000;io.regs[0x64100]=0x80000000;io.regs[0x164280]|=1U<<10; + link=decodeReadout([&](uint32_t r,uint32_t &v){v=io.read(r);return true;},38400);assert(link.matches(p)); + } + Quiesce::Result run(){return Quiesce::run(io,link,p);} +}; +int main(){ + for(bool cursor:{false,true}){Fixture f(cursor);const auto mux=f.io.regs[0x164280];const auto r=f.run(); + assert(r.complete() && r.writes==f.io.writes.size() && r.writes==(cursor?11U:8U)); + assert(r.frameAfter>r.frameBefore && f.io.regs[0x7B008]==0 && !(f.io.regs[0x6B400]&(1U<<31))); + Display13Transcoder::Bindings map;map.dsi0Pipe=0;assert(Display13FBIRQ::consumersStopped(f.io,1,map)); + assert(f.io.regs[0x164280]==mux && f.io.regs[0x46014]==0xCC000000 && f.io.regs[0x45404]==0xFC0F); + assert(f.io.regs[0x71180]==0x94001000 && f.io.regs[0x7119C]==0x30000000 && f.io.regs[0x64100]==0x80000000); + for(auto reg:f.io.writes)assert(reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || + reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000); + // Host stopped is not an IRQ/clock/panel teardown proof, and cannot be + // silently replayed using the now-stale active-link snapshot. + const auto count=f.io.writes.size();assert(!f.run().complete() && f.io.writes.size()==count); + } + for(auto reg:{0x6B0D0U,0x6B0D8U,0x6B000U,0x70780U,0x70B08U,0x6001CU}){Fixture f; + f.io.regs[reg]^=reg==0x70780?1U<<31:1; + assert(!f.run().complete() && f.io.writes.empty()); + } + {Fixture f;f.io.unbound=true;assert(!f.run().complete() && f.io.writes.empty());} + for(auto reg:{0x70180U,0x7B008U,0x6B0D8U,0x6B400U,0x164280U,0x64000U}){Fixture f;f.io.drop=reg; + const auto r=f.run();assert(!r.complete() && r.error!=Quiesce::Error::None && !f.io.writes.empty()); + assert(f.io.regs[0x46014]==0xCC000000 && f.io.regs[0x45404]==0xFC0F); // no PLL/power release on uncertainty + } + for(unsigned stage=1;stage<=3;++stage){Fixture f;f.io.failStage=stage;const auto r=f.run(); + assert(!r.complete() && r.error==Quiesce::Error::Timeout); + assert(r.stage==(stage==1?Quiesce::Stage::Transcoder:stage==2?Quiesce::Stage::ULPS:Quiesce::Stage::Buffer)); + } + {Fixture f;f.io.pendingAfterStop=true;const auto r=f.run();assert(!r.complete() && r.stage==Quiesce::Stage::Commands && r.error==Quiesce::Error::Commands);assert(!f.io.regs[0x6B0D8]);} + {Fixture f;f.io.frozen=true;const auto r=f.run();assert(!r.complete() && r.stage==Quiesce::Stage::Plane && r.error==Quiesce::Error::Timeout); + assert(f.io.regs[0x7B008]==f.link.conf);} + {Fixture f;f.io.losePower=true;const auto r=f.run();assert(!r.complete() && r.error==Quiesce::Error::Ownership); + assert(f.io.writes==std::vector({0x70180,0x7019C}));} + puts("PASS DSI host quiesce: cursor/plane/frame then transcoder/ULPS/port, peer preservation, FIFO precondition, bounded failures and no resource release"); +} +'''.replace('IO_FIXTURE',io) +with tempfile.TemporaryDirectory(prefix='dsi-quiesce-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index ec0fdeef..e26ef04c 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] From f4655293d98ed795aea50d73ec1b781c98e9c4fa Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Thu, 29 Oct 2026 23:55:36 +0900 Subject: [PATCH 033/139] Connect DSI host quiesce to native IRQ ownership --- WhateverXe/kern_xe.cpp | 125 +++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 3 + tests/dsi_lifecycle_dispatch.py | 7 +- tests/dsi_quiesce_adapter.py | 166 ++++++++++++++++++++++++++++++++ tools/test.py | 2 +- 5 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 tests/dsi_quiesce_adapter.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 392c1e65..53363e98 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9684,11 +9684,130 @@ static bool adlnWriterMappingBound() { native->getPhysicalAddress(),native->getLength(),c.map->getPhysicalAddress(),c.map->getLength()); } +// Host stop and actual native IRQ retirement. This is deliberately separate +// from allocator, PLL-owner and power release: a hardware stop is not a full +// Session StopProof. The one-shot journal retains a partial stop for diagnosis. +bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; + if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || + !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || + !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; + IORecursiveLockLock(mutex); + IOLockLock(c.lock); + auto loop=controller==c.controller ? c.workLoop : nullptr; + if(loop)loop->retain(); + IOLockUnlock(c.lock); + const bool boundLoop=loop && Display13FBMemory::field(controller,0xE00)==loop; + const bool inGate=boundLoop && reinterpret_cast(adlnWorkLoopInGate)(loop); + const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); + if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + uint64_t record[24] {1};bool masked=false,maskOK=false,parked=false,restored=false,enabled=false;uint32_t master=0; + const auto qualified=[&]() { + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiProfile.valid() || + controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || + Display13FBMemory::field(controller,0xE00)!=loop || + c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || + c.transcoders.dsi0Pipe!=0 || c.bridge.state.faulted || c.bridge.state.suspended || c.clockFaulted || + c.pipe[0].destroyingPath || c.dbuf.updating || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || + Display13FBMemory::field(path,0x35F0) || !adlnWriterMappingBound() || + !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || + Display13FBMemory::field(c.dsiPort,0x550)!=1 || + Display13FBMemory::field(path,0x3644)!=2 || + clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || + clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || + !c.bootDSI.sameSource(dsiBoot.source))return false; + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); + Display13FBBootIO io{c.map}; + return binding.status==TGLFBBinding::Status::Complete && binding.pipe==0 && binding.ddi==0 && + c.bridge.outputHeld(io,0,0); + }; + struct IO : Display13FBBootIO { + decltype(qualified) &owns; + IO(IOMemoryMap *m,decltype(qualified) &q):Display13FBBootIO{m},owns(q) {} + bool ready(){return owns();} + void write(uint32_t reg,uint32_t value) { + if(readable(reg) && (reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || + reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} + } io{c.map,qualified}; + record[1]=qualified(); + if(record[1] && !dsiStopAttempted && adlnIRQOwner(controller) && irq.lock && + irq.lock==Display13FBMemory::field(controller,0x90) && irq.base==c.map->getVirtualAddress()) { + Display13FBIRQIO irqIO{irq.base}; + auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto queued=[&](unsigned offset) { + return __atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+offset),__ATOMIC_ACQUIRE); + }; + master=irqIO.read(Display13FBIRQ::Master);enabled=irq.state.enabled; + record[2]=irq.handlers;record[3]=irq.scopes.display;record[4]=irq.softwareHandlers; + record[5]=queued(0xE08);record[6]=queued(0xE0C);record[7]=queued(0xE10); + const bool quiet=irq.scopes.total==irq.handlers && !irq.scopes.display && !irq.softwareHandlers && + !(record[5]&Display13FBIRQ::pipeEvents(0)) && !record[6] && !(record[7]&3); + const bool ready=quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && + irq.state.begun && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && + master!=UINT32_MAX && bool(master&Display13FBIRQ::Enable)==enabled; + if(ready) { + // DE permission is assigned on actual HW-handler entry. New shared + // GT-only handlers can still run but cannot consume display banks. + irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; + irqIO.write(Display13FBIRQ::Master,0); + const auto after=irqIO.read(Display13FBIRQ::Master); + maskOK=after!=UINT32_MAX && !(after&Display13FBIRQ::Enable); + if(!maskOK)irq.state.faulted=true; + } + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + if(maskOK) { + dsiStopAttempted=true;dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile); + } + if(masked) { + saved=IOSimpleLockLockDisableInterrupt(irq.lock); + record[8]=irq.handlers;record[9]=irq.scopes.display;record[10]=irq.softwareHandlers; + record[11]=queued(0xE08);record[12]=queued(0xE0C);record[13]=queued(0xE10); + if(dsiStop.complete() && irq.scopes.total==irq.handlers && !irq.scopes.display && + !irq.softwareHandlers && !record[12] && !(record[13]&3)) { + // Pass the real DE-permitted scope count. A GT-only stack does + // not own a display consumer, even when it spans this stop. + const auto status=Display13FBIRQ::parkStoppedPipe(irqIO,irq.state,0,irq.scopes.display, + irq.softwareHandlers,static_cast(record[11])); + record[14]=static_cast(status);parked=status==Display13FBIRQ::Status::Complete; + } + irq.quiescing=false;irq.blockedPipes=0; + if(!irq.state.faulted) { + irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); + const auto after=irqIO.read(Display13FBIRQ::Master); + restored=after!=UINT32_MAX && bool(after&Display13FBIRQ::Enable)==enabled; + irq.state.enabled=restored && enabled; + if(!restored) {irq.state.faulted=true;irqIO.write(Display13FBIRQ::Master,0);} + } + adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + } + if(masked)dsiIRQParked=parked && restored; + record[15]=masked;record[16]=parked;record[17]=restored;record[18]=dsiStopAttempted; + record[19]=static_cast(dsiStop.stage);record[20]=static_cast(dsiStop.error); + record[21]=dsiStop.writes;record[22]=dsiStop.waitUs;record[23]=dsiIRQParked; + const bool complete=parked && restored; + // Close the existing native writer/free gate before dropping the FB mutex, + // even after host success. Remove this conservative guard only with the + // separate Session/allocator/clock/power retirement transaction in place. + if(masked)adlnFault(c.memoryFaulted,__LINE__); + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); + loop->release();IORecursiveLockUnlock(mutex); + adlnPublishMemory("N97FBDSIHostStop",0,record,sizeof(record)); + return complete; +} + void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObject *path,uint8_t mode) { if(callbackXE->adlnOutputWiring.dsi && dsiLifecycleTarget(fb,path)) { - // This void callback cannot report an unsupported stop. Latch failure so - // resource retirement retains the live backing/leases. Do not run even - // the cursor prefix until the DSI stop transaction owns its rollback. + callbackXE->quiesceDSIPath(controller,fb,path); + // Host/IRQ completion alone does not authorize allocator/PLL/power + // release. Keep the retention guard until the full retirement adapter + // supplies a real Session StopProof; this void donor ABI has no error. auto &c=display13FBPower; if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} return; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ae1508b2..f470c88c 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -142,6 +142,9 @@ class XE { bool prepareDSIFirmware(IOService *); bool dsiResourcesReady {}; bool bindDSIResources(IOService *); + XELP::DSI::Quiesce::Result dsiStop; + bool dsiStopAttempted {},dsiIRQParked {}; + bool quiesceDSIPath(IOService *,IOService *,OSObject *); static uint32_t wrapDisplay13ProbeMode(OSObject *); bool dsiCacheHooksReady {}; mach_vm_address_t orgDSICache[2] {}; diff --git a/tests/dsi_lifecycle_dispatch.py b/tests/dsi_lifecycle_dispatch.py index 120100d6..286dc216 100644 --- a/tests/dsi_lifecycle_dispatch.py +++ b/tests/dsi_lifecycle_dispatch.py @@ -15,7 +15,7 @@ struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; using IOService=OSObject;using IOLock=int; -unsigned depth=0,bindingCalls=0,modeCalls=0,stopCalls=0,notifications=0,modeResult=0; +unsigned depth=0,bindingCalls=0,modeCalls=0,stopCalls=0,notifications=0,modeResult=0,hostStops=0; void IOLockLock(IOLock*){assert(!depth);++depth;}void IOLockUnlock(IOLock*){assert(depth==1);--depth;} void adlnFault(bool &v,unsigned){assert(depth==1);v=true;} void IOSleep(unsigned){assert(false);} @@ -38,6 +38,7 @@ struct Wiring{unsigned dsi=0;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; uintptr_t orgDisplay13TCSetMode=reinterpret_cast(nativeMode),display13TCNotify=reinterpret_cast(notify); uintptr_t orgAlderLakeNDisableDisplay=reinterpret_cast(nativeStop); + bool quiesceDSIPath(IOService*,IOService*,OSObject*){++hostStops;return false;} static uint32_t wrapDisplay13TCSetMode(void*,void*,const void*); static void wrapAlderLakeNDisableDisplay(IOService*,IOService*,OSObject*,uint8_t); }xe,*callbackXE=&xe; @@ -48,11 +49,11 @@ auto &c=display13FBPower;c.lock=&lock;c.controller=&controller; put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&path,0x3648,&port); const auto reject=[&](IOService *f,OSObject *p){ - const auto m=modeCalls,n=notifications,d=stopCalls,b=bindingCalls; + const auto m=modeCalls,n=notifications,d=stopCalls,b=bindingCalls,h=hostStops; assert(XE::wrapDisplay13TCSetMode(&controller,f,nullptr)==kIOReturnNotReady); assert(modeCalls==m && notifications==n); c.memoryFaulted=false;XE::wrapAlderLakeNDisableDisplay(&controller,f,p,0); - assert(c.memoryFaulted && stopCalls==d && bindingCalls==b && !depth); + assert(c.memoryFaulted && stopCalls==d && bindingCalls==b && hostStops==h+1 && !depth); }; xe.adlnOutputWiring.dsi=1;reject(&fb,&path); // actual pipe A / port A put(&path,0x3640,1U);put(&port,0x120,3U); diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py new file mode 100644 index 00000000..1ce990e1 --- /dev/null +++ b/tests/dsi_quiesce_adapter.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Exercise the production native DSI stop boundary with real host/IRQ cores.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'bool XE::quiesceDSIPath\(.*?\n\}',s,re.S)[0] +b=(root/'tests/dsi_boot.cpp').read_text();fixture=b[b.index('struct IO {'):b.index('Boot capture(')] +code=r''' +#include "kern_dsi_quiesce.hpp" +#include "kern_dsi_boot.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_power_bridge.hpp" +#include "kern_xelp_combo_clock.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +FIXTURE +std::vector locks; +struct IORecursiveLock{unsigned rank;};struct IOLock{};struct IOSimpleLock{}; +void IORecursiveLockLock(IORecursiveLock *p){assert(locks.empty() || locks.back()rank);locks.push_back(p->rank);} +void IORecursiveLockUnlock(IORecursiveLock *p){assert(locks.back()==p->rank);locks.pop_back();} +void IOLockLock(IOLock*){assert(!locks.empty() && locks.back()<3);locks.push_back(3);} +void IOLockUnlock(IOLock*){assert(locks.back()==3);locks.pop_back();} +bool IOSimpleLockLockDisableInterrupt(IOSimpleLock*){assert(locks.back()==3);locks.push_back(4);return true;} +void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,bool v){assert(v && locks.back()==4);locks.pop_back();} +struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned refs=1,size=sizeof(bytes); + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} + void retain(){++refs;}void release(){assert(refs>1);--refs;}}; +using IOService=OSObject; +struct IOWorkLoop:OSObject {bool gate=false,busy=false;unsigned closes=0,opens=0;}; +bool inGate(const IOWorkLoop *p){assert(locks==std::vector{1});return p->gate;} +bool tryClose(IOWorkLoop *p){assert(locks==std::vector{1});++p->closes;if(p->busy)return false;return p->gate=true;} +void open(IOWorkLoop *p){assert(locks==std::vector{1} && p->gate);p->gate=false;++p->opens;} +struct IOMemoryMap { + std::vector bar=std::vector(0x1000000/4);bool freeze=false,pending=false,failMasterRestore=false,failMasterMask=false; + unsigned masterWrites=0;bool gtOnly=false; + uintptr_t getVirtualAddress(){return reinterpret_cast(bar.data());}size_t getLength(){return 0x1000000;} + uint32_t ®(uint32_t r){return bar[r/4];} +}*activeMap; +struct Display13FBBootIO { + IOMemoryMap *map;bool readable(uint32_t r){return map && r<0x1000000 && !(r&3);} + uint32_t read(uint32_t r){assert(locks.back()==3);return map->reg(r);} +}; +struct Display13FBIRQIO { + uintptr_t base;bool readable(uint32_t r){return base && r<0x1000000 && !(r&3);} + uint32_t read(uint32_t r){assert(locks.back()==4);return *reinterpret_cast(base+r);} + void write(uint32_t r,uint32_t v){assert(locks.back()==4); + if(r==Display13FBIRQ::Master){++activeMap->masterWrites; + if((activeMap->masterWrites==1 && activeMap->failMasterMask) || (activeMap->masterWrites==2 && activeMap->failMasterRestore))return; + } + auto ®=*reinterpret_cast(base+r); + if(r==Display13FBIRQ::PipeIIR(0))reg&=~v;else reg=v; + } +}; +struct {IORecursiveLock *lock=nullptr;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;}comboClockTransaction; +struct {IOLock *lock=nullptr;IOService *controller=nullptr;IOMemoryMap *map=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr; + Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge;bool clockFaulted=false,memoryFaulted=false; + struct {unsigned updating=0;}dbuf; + struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false,wmProgramming=false,wmCommitting=false;}pipe[3]; +}display13FBPower; +struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; + unsigned handlers=0,softwareHandlers=0,admittedPipes=0,blockedPipes=0;bool quiescing=false; + Display13FBIRQ::HandlerScopes scopes;}display13FBIRQ; +void adlnFault(bool &fault,unsigned){assert(locks==std::vector({1,2,3}));fault=true;} +bool mappingBound=true; +bool adlnWriterMappingBound(){return mappingBound;} +bool adlnIRQOwner(IOService *p){return p && p==display13FBIRQ.controller;} +namespace TGLFBBinding {enum class Status{Complete,Unavailable};struct Binding{Status status=Status::Complete;unsigned pipe=0,ddi=0;};} +TGLFBBinding::Binding pathBinding; +TGLFBBinding::Binding adlnInspectPathBinding(IOService*,OSObject*,void*,unsigned){return pathBinding;} +void adlnIRQCache(IOService*,Display13FBIRQIO&){assert(locks.back()==4);} +void adlnPublishMemory(const char*,unsigned,void*,size_t){assert(locks.empty());} +void tick(unsigned us){assert(locks==std::vector({1,2,3}));auto &m=*activeMap; + if(!m.freeze && us>=1000)++m.reg(0x70040); + if(!(m.reg(0x7B008)&(1U<<31)))m.reg(0x7B008)&=~(1U<<30); + if(m.reg(0x6B0D8)&1)m.reg(0x6B0D8)|=1U<<16; + if(!(m.reg(0x64000)&(1U<<31)))m.reg(0x64000)|=0x80; + if(m.pending){auto c=display13FBPower.controller;*reinterpret_cast(c->bytes+0xE08)|=1;} + if(m.gtOnly){display13FBIRQ.handlers=1;display13FBIRQ.scopes.total=1;} +} +void IOSleep(unsigned ms){tick(ms*1000);}void IODelay(unsigned us){tick(us);} +struct XE { + uintptr_t adlnWorkLoopInGate=reinterpret_cast(inGate),adlnWorkLoopTryClose=reinterpret_cast(tryClose),adlnWorkLoopOpen=reinterpret_cast(open); + struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; + bool dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false; + IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop; + bool quiesceDSIPath(IOService*,IOService*,OSObject*); +}; +BODY +templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +struct Case { + XE xe;IOService controller,fb;OSObject path,port;IOWorkLoop loop;IOMemoryMap map; + IORecursiveLock fbLock{1},clockLock{2};IOLock ownerLock;IOSimpleLock irqLock; + Case(){ + using Power=decltype(display13FBPower);display13FBPower.~Power();new (&display13FBPower) Power; + display13FBIRQ={};comboClockTransaction={};mappingBound=true;pathBinding={};activeMap=↦ + IO defaults;for(const auto &v:defaults.regs)map.reg(v.first)=v.second; + map.reg(0x42084)=1U<<30;map.reg(0x6B0D0)=0x01001040;map.reg(0x6001C)=0x0437077F;map.reg(0x6B010)=map.reg(0x6B00C); + map.reg(0x45454)=3;map.reg(0x42000)=0x8E380000;map.reg(0x70040)=100; + // Keep a real peer IRQ/plane image live and make preservation observable. + map.reg(0x71008)=0xC0000000;map.reg(0x71180)=0x94001000;map.reg(0x61400)=0x90000000; + map.reg(Display13FBIRQ::Master)=Display13FBIRQ::Enable; + map.reg(Display13FBIRQ::PipeIIR(0)+4)=Display13FBIRQ::PipeOwned; + map.reg(Display13FBIRQ::PipeIIR(1)+4)=Display13FBIRQ::PipeOwned; + auto &c=display13FBPower;auto &irq=display13FBIRQ;auto &clock=comboClockTransaction; + c.lock=&ownerLock;c.controller=&controller;c.map=↦c.workLoop=&loop;c.dsiPort=&port; + c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0; + xe.adlnTopologyController=&controller;xe.dsiProfile=profile(); + c.bootDSI=decodeReadout([&](uint32_t r,uint32_t &v){v=map.reg(r);return true;},38400);assert(c.bootDSI.matches(xe.dsiProfile));xe.dsiBoot.source=c.bootDSI; + clock.lock=&clockLock;clock.allocation.external[0]=reinterpret_cast(&controller);clock.allocation.binding[0]=2; + irq.controller=&controller;irq.lock=&irqLock;irq.base=map.getVirtualAddress();irq.admittedPipes=3; + irq.state.begun=true;irq.state.enabled=true;irq.state.active=3;irq.state.configured=3;irq.state.transcoders=c.transcoders; + auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;s.ready=true;s.allowed=Display13Power::All; + const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)),ddi=Display13Power::dependencies(Display13Power::ddiDomain(0));s.borrowed=pg|ddi; + for(unsigned i=0;i(&controller)); + assert(map.reg(0x46014)==0xCC000000 && map.reg(0x45404)==0xFC0F); + assert(Display13FBMemory::field(&path,0x3644)==2); + } +}; +int main(){ + for(bool own:{false,true}){Case c;c.loop.gate=own;c.map.gtOnly=true; + put(&c.controller,0xE08,2U);put(&c.controller,0xE10,1U<<6); + assert(c.run() && c.xe.dsiStop.complete() && c.xe.dsiIRQParked); + assert(display13FBPower.memoryFaulted); + assert(c.xe.dsiStop.writes==8 && display13FBIRQ.state.active==2 && display13FBIRQ.state.enabled); + assert(!display13FBIRQ.quiescing && !display13FBIRQ.blockedPipes && c.loop.gate==own); + assert(c.loop.opens==unsigned(!own) && c.loop.closes==unsigned(!own)); + assert(c.map.reg(0x71180)==0x94001000 && c.map.reg(Display13FBIRQ::PipeIIR(1)+4)==Display13FBIRQ::PipeOwned); + assert(Display13FBMemory::field(&c.controller,0xE08)==2 && Display13FBMemory::field(&c.controller,0xE10)==(1U<<6)); + c.retained();const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && c.xe.dsiIRQParked); // one-shot journal + } + for(unsigned bad=0;bad<12;++bad){Case c; + switch(bad){case 0:c.loop.busy=true;break;case 1:mappingBound=false;break; + case 2:display13FBIRQ.softwareHandlers=1;break;case 3:display13FBIRQ.handlers=1;display13FBIRQ.scopes.total=1;display13FBIRQ.scopes.display=1;break; + case 4:put(&c.controller,0xE08,1U);break;case 5:put(&c.controller,0xE0C,1U);break; + case 6:put(&c.path,0x35F0,1U);break;case 7:comboClockTransaction.allocation.external[0]=0;break; + case 8:display13FBPower.bridge.ddiRefs[0]=0;break;case 9:display13FBIRQ.scopes.total=1;break; + case 10:put(&c.controller,0xE10,1U);break;case 11:pathBinding.ddi=1;break;} + const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && !c.xe.dsiStopAttempted && !c.loop.gate); + } + {Case c;c.map.failMasterMask=true;const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && !c.xe.dsiStopAttempted); + assert(display13FBIRQ.state.faulted && !display13FBIRQ.state.enabled);c.retained();} + {Case c;c.map.freeze=true;assert(!c.run() && c.xe.dsiStopAttempted && !c.xe.dsiIRQParked); + assert(c.xe.dsiStop.stage==Quiesce::Stage::Plane && display13FBIRQ.state.enabled && display13FBIRQ.state.active==3);c.retained();} + {Case c;c.map.pending=true;assert(!c.run() && c.xe.dsiStop.complete() && !c.xe.dsiIRQParked); + assert(display13FBIRQ.state.active==3 && Display13FBMemory::field(&c.controller,0xE08)==1);c.retained();} + {Case c;c.map.failMasterRestore=true;assert(!c.run() && c.xe.dsiStop.complete() && !c.xe.dsiIRQParked); + assert(display13FBIRQ.state.faulted && !display13FBIRQ.state.enabled && !c.map.reg(Display13FBIRQ::Master));c.retained();} + puts("PASS production DSI quiesce: real host/IRQ cores, FB-gate-clock-owner ordering, no wait under IRQ lock, actual queues/scopes, GT/peer preservation, retained resources and one-shot failure journal"); +} +'''.replace('FIXTURE',fixture).replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='dsi-quiesce-adapter-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index e26ef04c..439ba5da 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] From 1a0a46a4ba144a530cab2eb6ef92ab675ca99149 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:10:20 +0900 Subject: [PATCH 034/139] Retire DSI PLL and native DDI lease after quiesce --- WhateverXe/kern_dsi_clock.hpp | 80 ++++++++++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 53 ++++++++++++++++++++--- WhateverXe/kern_xe.hpp | 4 +- tests/dsi_clock.py | 68 +++++++++++++++++++++++++++++ tests/dsi_quiesce_adapter.py | 81 +++++++++++++++++++++++++++-------- tools/test.py | 2 +- 6 files changed, 262 insertions(+), 26 deletions(-) create mode 100644 WhateverXe/kern_dsi_clock.hpp create mode 100644 tests/dsi_clock.py diff --git a/WhateverXe/kern_dsi_clock.hpp b/WhateverXe/kern_dsi_clock.hpp new file mode 100644 index 00000000..3f4b4512 --- /dev/null +++ b/WhateverXe/kern_dsi_clock.hpp @@ -0,0 +1,80 @@ +// Retire an exclusively retained Display13 DSI Combo PLL after host/IRQ stop. +// Sequence: Linux v6.12 icl_pll_disable; register bits from i915_reg.h. +#ifndef KERN_DSI_CLOCK_HPP +#define KERN_DSI_CLOCK_HPP +#include "kern_dsi_quiesce.hpp" +#include "kern_xelp_combo_clock.hpp" +namespace XELP { namespace DSI { namespace Clock { +enum class Status : uint32_t {Input,Ownership,Consumer,Peer,Source,Disable,Power,Complete}; +struct Result { + Status status {Status::Input};uint32_t reg {},before {},after {},writes {},waitUs {}; + bool complete() const {return status==Status::Complete;} +}; +// Caller holds shared Combo/FB/owner serialization and has parked the actual +// native IRQ bank under its workloop gate. No waits under the IRQ spinlock. +// IO::ready checks retained ownership, not the original running source image. +// Keep the external allocation on every failure, including a half-disabled PLL. +// Never commandeer a peer PLL or clear a firmware power-well request here. +template Result retire(IO &io,const Readout &link,const Quiesce::Result &host,bool irqParked, + XELPComboClock::ClockAllocation &allocation,uintptr_t owner) { + Result r; + if(!owner || !link.complete() || !link.identity.valid() || link.identity.pipe!=0 || !host.complete() || !irqParked)return r; + XELPComboClock::PLL pll;if(!XELPComboClock::PLL::resolve(link.identity.pll,pll))return r; + r.reg=pll.enable; + const auto value=[&](uint32_t reg,uint32_t &out) { + if(!io.readable(reg))return false; + out=io.read(reg);return out!=UINT32_MAX; + }; + const auto owned=[&]() { + uint32_t wells=0; + return io.ready() && allocation.external[0]==owner && allocation.binding[0]==link.identity.pll+1 && + value(0x45404,wells) && (wells&0x403)==0x403; + }; + const auto stopped=[&]() { + Display13Transcoder::Bindings bindings;bindings.dsi0Pipe=0; + uint32_t lp=0,buffer=0,secondary=0,mux=0; + return Display13FBIRQ::consumersStopped(io,1,bindings) && value(0x6B0D8,lp) && + (lp&((1U<<17)|(1U<<16)|1))==((1U<<16)|1) && value(0x64000,buffer) && + (buffer&0x80000080U)==0x80 && value(0x7B808,secondary) && !(secondary&0xC0000000U) && + value(0x6BC00,secondary) && !(secondary&(1U<<31)) && + value(0x164280,mux) && (mux&0x403U)==(0x400U|link.identity.pll); + }; + const auto exclusive=[&]() { + XELPComboClock::ClockSnapshot s;s.layout=XELPComboClock::Layout::Display13; + if(!value(0x164280,s.mux[0]))return false; + if(s.peers(XELPComboClock::Port(0))&(1U<=1000)return false; + io.delayUs(10);r.waitUs+=10; + } + }; + // Require both request and state bits clear. A lost write must not be + // accepted merely because its status bit happened to read unlocked. + r.status=Status::Disable;io.write(pll.enable,r.before&~(1U<<31));++r.writes; + if(!wait(0xC0000000U))return r; + r.status=Status::Power;io.write(pll.enable,r.after&~(1U<<27));++r.writes; + if(!wait(0xCC000000U) || !held())return r; + if(!allocation.release(0,owner)){r.status=Status::Ownership;return r;} + r.status=Status::Complete;return r; +} +}}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 53363e98..2578ff51 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9684,8 +9684,8 @@ static bool adlnWriterMappingBound() { native->getPhysicalAddress(),native->getLength(),c.map->getPhysicalAddress(),c.map->getLength()); } -// Host stop and actual native IRQ retirement. This is deliberately separate -// from allocator, PLL-owner and power release: a hardware stop is not a full +// Host/IRQ/PLL retirement; the native DDI reference follows the stopped clock. +// Keep the real stopped source separate from Session cleanup: it is not a full // Session StopProof. The one-shot journal retains a partial stop for diagnosis. bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; @@ -9703,7 +9703,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - uint64_t record[24] {1};bool masked=false,maskOK=false,parked=false,restored=false,enabled=false;uint32_t master=0; + uint64_t record[33] {3};bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; const auto qualified=[&]() { if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || @@ -9729,7 +9729,8 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { bool ready(){return owns();} void write(uint32_t reg,uint32_t value) { if(readable(reg) && (reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || - reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000)) + reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000 || + reg==0x46010+4*display13FBPower.bootDSI.identity.pll)) *reinterpret_cast(map->getVirtualAddress()+reg)=value; } void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} @@ -9775,7 +9776,23 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { irq.softwareHandlers,static_cast(record[11])); record[14]=static_cast(status);parked=status==Display13FBIRQ::Status::Complete; } + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + if(parked) { + dsiClockStop=XELP::DSI::Clock::retire(io,c.bootDSI,dsiStop,true,clock.allocation, + reinterpret_cast(controller)); + } + saved=IOSimpleLockLockDisableInterrupt(irq.lock); + if(dsiClockStop.complete()) { + // The host and its PLL are now stopped. Withdraw both maps at + // the same masked IRQ boundary, so an external clock client + // no longer tries to re-adopt the obsolete running DSI source. + c.transcoders.dsi0Pipe=UINT32_MAX;irq.state.transcoders=c.transcoders;c.dbuf.transcoders=c.transcoders; + __atomic_fetch_and(&irq.admittedPipes,~1U,__ATOMIC_RELEASE); + __atomic_store_n(&dsiResourcesReady,false,__ATOMIC_RELEASE); + } irq.quiescing=false;irq.blockedPipes=0; + const auto wells=irqIO.read(Display13FBIRQ::Driver); + if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; if(!irq.state.faulted) { irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); const auto after=irqIO.read(Display13FBIRQ::Master); @@ -9790,12 +9807,36 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { record[15]=masked;record[16]=parked;record[17]=restored;record[18]=dsiStopAttempted; record[19]=static_cast(dsiStop.stage);record[20]=static_cast(dsiStop.error); record[21]=dsiStop.writes;record[22]=dsiStop.waitUs;record[23]=dsiIRQParked; - const bool complete=parked && restored; + record[24]=static_cast(dsiClockStop.status);record[25]=dsiClockStop.reg;record[26]=dsiClockStop.after; + record[27]=dsiClockStop.writes;record[28]=dsiClockStop.waitUs;record[29]=dsiClockStop.complete(); + // The donor's disableClocks(port,peer) puts this DDI reference at 6db04; + // PG1 belongs to freeDisplayPathResources and must remain until memory / + // DDB / path destruction. Match that ownership without running DDI/DP code. + const bool stopped=parked && restored && dsiClockStop.complete(); + record[30]=c.bridge.ddiRefs[0]; + if(stopped && c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && + c.bridge.bootPort==0 && !c.bridge.bootDDIProtected && c.bridge.ddiRefs[0]==1 && c.power) { + auto power=c.power; + IOLockUnlock(c.lock);const bool put=adlnPowerReference(power,4,0);IOLockLock(c.lock); + dsiDDIReleased=put && c.power==power && !c.bridge.ddiRefs[0] && !c.bridge.ddiLease[0].owner; + } + record[31]=c.bridge.ddiRefs[0];record[32]=dsiDDIReleased; + const bool complete=stopped && dsiDDIReleased; + if(complete) { + // Exact donor disableDisplay publishes path state 1 only after its + // clock-disable call (4dbba -> 4dbc6). Mirror that CPU completion now, + // never on a partial host/IRQ/PLL stop or by replaying the donor body. + *reinterpret_cast(reinterpret_cast(path)+0x3644)=1; + *reinterpret_cast(reinterpret_cast(path)+0x170)=0; + *reinterpret_cast(reinterpret_cast(fb)+0x4270)=0; + if(dsiModeTarget(fb)){dsiFixedMode.ready=false;modeWithdrawn=true;} + } // Close the existing native writer/free gate before dropping the FB mutex, // even after host success. Remove this conservative guard only with the // separate Session/allocator/clock/power retirement transaction in place. if(masked)adlnFault(c.memoryFaulted,__LINE__); IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(modeWithdrawn)fb->setProperty("WEXDSIFixedModeReady",false); if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); loop->release();IORecursiveLockUnlock(mutex); adlnPublishMemory("N97FBDSIHostStop",0,record,sizeof(record)); @@ -9805,7 +9846,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObject *path,uint8_t mode) { if(callbackXE->adlnOutputWiring.dsi && dsiLifecycleTarget(fb,path)) { callbackXE->quiesceDSIPath(controller,fb,path); - // Host/IRQ completion alone does not authorize allocator/PLL/power + // Host/IRQ/PLL/DDI completion alone does not authorize allocator/PG // release. Keep the retention guard until the full retirement adapter // supplies a real Session StopProof; this void donor ABI has no error. auto &c=display13FBPower; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f470c88c..f6e8281d 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -13,6 +13,7 @@ #include "kern_dsi_ggtt.hpp" #include "kern_dsi_surface.hpp" #include "kern_dsi_quiesce.hpp" +#include "kern_dsi_clock.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" @@ -143,7 +144,8 @@ class XE { bool dsiResourcesReady {}; bool bindDSIResources(IOService *); XELP::DSI::Quiesce::Result dsiStop; - bool dsiStopAttempted {},dsiIRQParked {}; + XELP::DSI::Clock::Result dsiClockStop; + bool dsiStopAttempted {},dsiIRQParked {},dsiDDIReleased {}; bool quiesceDSIPath(IOService *,IOService *,OSObject *); static uint32_t wrapDisplay13ProbeMode(OSObject *); bool dsiCacheHooksReady {}; diff --git a/tests/dsi_clock.py b/tests/dsi_clock.py new file mode 100644 index 00000000..b18205d9 --- /dev/null +++ b/tests/dsi_clock.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Fault injection for retiring the retained DSI PLL after actual host stop.""" +from pathlib import Path +import subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'tests/dsi_boot.cpp').read_text();fixture=s[s.index('struct IO {'):s.index('Boot capture(')] +code=r''' +#include "kern_dsi_clock.hpp" +#include "kern_dsi_boot.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +FIXTURE +struct Sim:IO { + unsigned dropped=0,pllWrites=0,ackFailure=0;bool held=true; + bool ready(){return held;} + void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg==0x46014 && ++pllWrites==dropped)return;regs[reg]=value;} + void delayUs(unsigned us){ + if(us>=1000)++regs[0x70040]; + if(!(regs[0x7B008]&(1U<<31)))regs[0x7B008]&=~(1U<<30); + if(regs[0x6B0D8]&1)regs[0x6B0D8]|=1U<<16; + if(!(regs[0x64000]&(1U<<31)))regs[0x64000]|=0x80; + if(!(regs[0x46014]&(1U<<31)) && ackFailure!=1)regs[0x46014]&=~(1U<<30); + if(!(regs[0x46014]&(1U<<27)) && ackFailure!=2)regs[0x46014]&=~(1U<<26); + } +}; +struct Case { + Sim io;Readout link;Quiesce::Result host;XELPComboClock::ClockAllocation allocation; + Case(){ + io.regs[0x42084]=1U<<30;io.regs[0x6B0D0]=0x01001040;io.regs[0x6001C]=0x0437077F; + io.regs[0x6B010]=io.regs[0x6B00C];io.regs[0x70040]=100; + io.regs[0x164280]&=~(1U<<11);io.regs[0x46010]=0xCC000000; // another live PLL + link=decodeReadout([&](uint32_t r,uint32_t &v){v=io.read(r);return true;},38400);assert(link.complete()); + host=Quiesce::run(io,link,profile());assert(host.complete());io.writes.clear(); + allocation.external[0]=123;allocation.binding[0]=2; + } + Clock::Result run(bool irq=true){return Clock::retire(io,link,host,irq,allocation,123);} + void retained(){assert(allocation.external[0]==123 && allocation.binding[0]==2);} +}; +int main(){ + {Case c;const auto cfg0=c.io.regs[0x16428C],mux=c.io.regs[0x164280];const auto r=c.run(); + assert(r.complete() && r.writes==2 && r.after==0 && r.waitUs<=2000); + assert(c.io.writes==std::vector({0x46014,0x46014}) && !c.allocation.external[0] && !c.allocation.binding[0]); + assert(c.io.regs[0x46010]==0xCC000000 && c.io.regs[0x16428C]==cfg0 && c.io.regs[0x164280]==mux && c.io.regs[0x45404]==0xFC0F); + assert(!c.run().complete() && c.io.writes.size()==2); + } + for(unsigned drop:{1U,2U}){Case c;c.io.dropped=drop;const auto r=c.run();assert(!r.complete() && r.writes==drop && r.waitUs>=1000);c.retained();} + for(unsigned ack:{1U,2U}){Case c;c.io.ackFailure=ack;const auto r=c.run();assert(!r.complete() && r.writes==ack && r.waitUs>=1000);c.retained();} + for(auto reg:{0x45404U,0x7B008U,0x6B400U,0x70180U,0x70080U,0x6B0D8U,0x64000U,0x7B808U,0x6BC00U,0x164280U,0x16428CU,0x164290U}){ + Case c;c.io.regs[reg]^=(reg==0x7B008 || reg==0x7B808 || reg==0x6B400 || reg==0x6BC00 || reg==0x64000 || reg==0x70180)?1U<<31:1; + assert(!c.run().complete() && c.io.writes.empty());c.retained(); + } + {Case c;assert(!c.run(false).complete() && c.io.writes.empty());c.retained();} + {Case c;c.host.error=Quiesce::Error::Timeout;assert(!c.run().complete() && c.io.writes.empty());c.retained();} + {Case c;c.io.held=false;assert(!c.run().complete() && c.io.writes.empty());c.retained();} + {Case c;c.io.missing=0x46014;assert(!c.run().complete() && c.io.writes.empty());c.retained();} + {Case c;c.io.regs[0x46014]=0x88000000;assert(!c.run().complete() && c.io.writes.empty());c.retained();} + for(bool software:{false,true}){Case c;if(software)c.allocation.binding[1]=2;else c.io.regs[0x164280]|=1U<<2; + assert(c.run().status==Clock::Status::Peer && c.io.writes.empty());c.retained();} + puts("PASS DSI PLL retirement: real host stop required, exclusive owner, peer exclusion, ordered unlock/power ACK, lost-write/timeouts retain allocation, no power-well/peer writes"); +} +'''.replace('FIXTURE',fixture) +with tempfile.TemporaryDirectory(prefix='dsi-clock-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 1ce990e1..306a1f7f 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -8,6 +8,7 @@ b=(root/'tests/dsi_boot.cpp').read_text();fixture=b[b.index('struct IO {'):b.index('Boot capture(')] code=r''' #include "kern_dsi_quiesce.hpp" +#include "kern_dsi_clock.hpp" #include "kern_dsi_boot.hpp" #include "kern_display13_fb_memory.hpp" #include "kern_display13_power_bridge.hpp" @@ -29,6 +30,7 @@ void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,bool v){assert(v && locks.back()==4);locks.pop_back();} struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned refs=1,size=sizeof(bytes); OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} + void setProperty(const char*,bool value){assert(locks==std::vector{1} && !value);} void retain(){++refs;}void release(){assert(refs>1);--refs;}}; using IOService=OSObject; struct IOWorkLoop:OSObject {bool gate=false,busy=false;unsigned closes=0,opens=0;}; @@ -37,7 +39,7 @@ void open(IOWorkLoop *p){assert(locks==std::vector{1} && p->gate);p->gate=false;++p->opens;} struct IOMemoryMap { std::vector bar=std::vector(0x1000000/4);bool freeze=false,pending=false,failMasterRestore=false,failMasterMask=false; - unsigned masterWrites=0;bool gtOnly=false; + unsigned masterWrites=0,pllFail=0;bool gtOnly=false; uintptr_t getVirtualAddress(){return reinterpret_cast(bar.data());}size_t getLength(){return 0x1000000;} uint32_t ®(uint32_t r){return bar[r/4];} }*activeMap; @@ -57,16 +59,18 @@ } }; struct {IORecursiveLock *lock=nullptr;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;}comboClockTransaction; -struct {IOLock *lock=nullptr;IOService *controller=nullptr;IOMemoryMap *map=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr; +struct {IOLock *lock=nullptr;IOService *controller=nullptr;void *power=nullptr;IOMemoryMap *map=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr; Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge;bool clockFaulted=false,memoryFaulted=false; - struct {unsigned updating=0;}dbuf; + struct {unsigned updating=0;Display13Transcoder::Bindings transcoders;}dbuf; struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false,wmProgramming=false,wmCommitting=false;}pipe[3]; }display13FBPower; struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; unsigned handlers=0,softwareHandlers=0,admittedPipes=0,blockedPipes=0;bool quiescing=false; Display13FBIRQ::HandlerScopes scopes;}display13FBIRQ; void adlnFault(bool &fault,unsigned){assert(locks==std::vector({1,2,3}));fault=true;} -bool mappingBound=true; +struct {bool ready=true;}dsiFixedMode; +bool dsiModeTarget(IOService *fb){return fb==display13FBPower.pipe[0].memoryFramebuffer;} +bool mappingBound=true,failDDIPut=false; bool adlnWriterMappingBound(){return mappingBound;} bool adlnIRQOwner(IOService *p){return p && p==display13FBIRQ.controller;} namespace TGLFBBinding {enum class Status{Complete,Unavailable};struct Binding{Status status=Status::Complete;unsigned pipe=0,ddi=0;};} @@ -79,6 +83,10 @@ if(!(m.reg(0x7B008)&(1U<<31)))m.reg(0x7B008)&=~(1U<<30); if(m.reg(0x6B0D8)&1)m.reg(0x6B0D8)|=1U<<16; if(!(m.reg(0x64000)&(1U<<31)))m.reg(0x64000)|=0x80; + if(!(m.reg(0x46014)&(1U<<31)) && m.pllFail!=1)m.reg(0x46014)&=~(1U<<30); + if(!(m.reg(0x46014)&(1U<<27)) && m.pllFail!=2)m.reg(0x46014)&=~(1U<<26); + if(us==10 && m.pllFail==3)m.reg(0x45404)=0; + if(us==10 && m.pllFail==4)m.reg(0x16428C)^=1; if(m.pending){auto c=display13FBPower.controller;*reinterpret_cast(c->bytes+0xE08)|=1;} if(m.gtOnly){display13FBIRQ.handlers=1;display13FBIRQ.scopes.total=1;} } @@ -86,8 +94,18 @@ struct XE { uintptr_t adlnWorkLoopInGate=reinterpret_cast(inGate),adlnWorkLoopTryClose=reinterpret_cast(tryClose),adlnWorkLoopOpen=reinterpret_cast(open); struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; - bool dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false; - IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop; + bool dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false; + IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop; + static bool adlnPowerReference(void *power,unsigned operation,unsigned domain){ + assert(locks==std::vector({1,2}) && operation==4 && domain==0 && power==display13FBPower.power); + if(failDDIPut)return false; + IOLockLock(display13FBPower.lock);Display13FBBootIO io{display13FBPower.map}; + // Initial firmware DDI power is borrowed; releasing its actual native + // reference cannot manufacture a power-well write in this fixture. + struct PowerIO:Display13FBBootIO {void write(uint32_t,uint32_t){assert(false);}void delayUs(unsigned){assert(false);}} pio{io}; + const auto result=display13FBPower.bridge.putDDI(pio,0); + IOLockUnlock(display13FBPower.lock);return result.status==Display13Power::Status::Complete; + } bool quiesceDSIPath(IOService*,IOService*,OSObject*); }; BODY @@ -97,35 +115,36 @@ IORecursiveLock fbLock{1},clockLock{2};IOLock ownerLock;IOSimpleLock irqLock; Case(){ using Power=decltype(display13FBPower);display13FBPower.~Power();new (&display13FBPower) Power; - display13FBIRQ={};comboClockTransaction={};mappingBound=true;pathBinding={};activeMap=↦ + display13FBIRQ={};comboClockTransaction={};mappingBound=true;pathBinding={};activeMap=↦dsiFixedMode.ready=true;failDDIPut=false; IO defaults;for(const auto &v:defaults.regs)map.reg(v.first)=v.second; map.reg(0x42084)=1U<<30;map.reg(0x6B0D0)=0x01001040;map.reg(0x6001C)=0x0437077F;map.reg(0x6B010)=map.reg(0x6B00C); map.reg(0x45454)=3;map.reg(0x42000)=0x8E380000;map.reg(0x70040)=100; // Keep a real peer IRQ/plane image live and make preservation observable. + map.reg(0x164280)&=~(1U<<11);map.reg(0x46010)=0xCC000000; map.reg(0x71008)=0xC0000000;map.reg(0x71180)=0x94001000;map.reg(0x61400)=0x90000000; map.reg(Display13FBIRQ::Master)=Display13FBIRQ::Enable; map.reg(Display13FBIRQ::PipeIIR(0)+4)=Display13FBIRQ::PipeOwned; map.reg(Display13FBIRQ::PipeIIR(1)+4)=Display13FBIRQ::PipeOwned; auto &c=display13FBPower;auto &irq=display13FBIRQ;auto &clock=comboClockTransaction; - c.lock=&ownerLock;c.controller=&controller;c.map=↦c.workLoop=&loop;c.dsiPort=&port; - c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0; + c.lock=&ownerLock;c.controller=&controller;c.power=&controller;c.map=↦c.workLoop=&loop;c.dsiPort=&port; + c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0;c.dbuf.transcoders=c.transcoders; xe.adlnTopologyController=&controller;xe.dsiProfile=profile(); c.bootDSI=decodeReadout([&](uint32_t r,uint32_t &v){v=map.reg(r);return true;},38400);assert(c.bootDSI.matches(xe.dsiProfile));xe.dsiBoot.source=c.bootDSI; clock.lock=&clockLock;clock.allocation.external[0]=reinterpret_cast(&controller);clock.allocation.binding[0]=2; irq.controller=&controller;irq.lock=&irqLock;irq.base=map.getVirtualAddress();irq.admittedPipes=3; irq.state.begun=true;irq.state.enabled=true;irq.state.active=3;irq.state.configured=3;irq.state.transcoders=c.transcoders; - auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;s.ready=true;s.allowed=Display13Power::All; + auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;b.bootPort=0;b.bootAdopted=true;b.bootPath=reinterpret_cast(&path);s.ready=true;s.allowed=Display13Power::All; const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)),ddi=Display13Power::dependencies(Display13Power::ddiDomain(0));s.borrowed=pg|ddi; - for(unsigned i=0;i(&controller)); - assert(map.reg(0x46014)==0xCC000000 && map.reg(0x45404)==0xFC0F); - assert(Display13FBMemory::field(&path,0x3644)==2); + void retained(bool clockGone=false){auto &b=display13FBPower.bridge;assert(b.pgRefs[0]==1 && b.ddiRefs[0]==unsigned(!xe.dsiDDIReleased)); + assert(comboClockTransaction.allocation.external[0]==(clockGone?0:reinterpret_cast(&controller))); + assert(map.reg(0x46010)==0xCC000000 && map.reg(0x45404)==0xFC0F); + assert(Display13FBMemory::field(&path,0x3644)==(xe.dsiIRQParked && xe.dsiClockStop.complete() && xe.dsiDDIReleased?1U:2U)); } }; int main(){ @@ -138,7 +157,15 @@ assert(c.loop.opens==unsigned(!own) && c.loop.closes==unsigned(!own)); assert(c.map.reg(0x71180)==0x94001000 && c.map.reg(Display13FBIRQ::PipeIIR(1)+4)==Display13FBIRQ::PipeOwned); assert(Display13FBMemory::field(&c.controller,0xE08)==2 && Display13FBMemory::field(&c.controller,0xE10)==(1U<<6)); - c.retained();const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && c.xe.dsiIRQParked); // one-shot journal + assert(!dsiFixedMode.ready && c.xe.dsiDDIReleased); + assert(c.xe.dsiClockStop.complete() && c.xe.dsiClockStop.writes==2 && !c.map.reg(0x46014)); + assert(display13FBPower.transcoders.dsi0Pipe==UINT32_MAX && display13FBIRQ.state.transcoders.dsi0Pipe==UINT32_MAX); + assert(display13FBIRQ.admittedPipes==2 && display13FBPower.dbuf.transcoders.dsi0Pipe==UINT32_MAX && !c.xe.dsiResourcesReady); + XELPComboClock::ClockSnapshot snapshot;snapshot.layout=XELPComboClock::Layout::Display13; + snapshot.mux[0]=c.map.reg(0x164280);snapshot.enable[0]=c.map.reg(0x46010);snapshot.enable[1]=c.map.reg(0x46014); + const auto reused=comboClockTransaction.allocation.choose(XELPComboClock::Port(0),snapshot,true); + assert(reused.valid && reused.pll==1 && !reused.keepPLL); + c.retained(true);const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && c.xe.dsiIRQParked); // one-shot journal } for(unsigned bad=0;bad<12;++bad){Case c; switch(bad){case 0:c.loop.busy=true;break;case 1:mappingBound=false;break; @@ -156,8 +183,26 @@ {Case c;c.map.pending=true;assert(!c.run() && c.xe.dsiStop.complete() && !c.xe.dsiIRQParked); assert(display13FBIRQ.state.active==3 && Display13FBMemory::field(&c.controller,0xE08)==1);c.retained();} {Case c;c.map.failMasterRestore=true;assert(!c.run() && c.xe.dsiStop.complete() && !c.xe.dsiIRQParked); - assert(display13FBIRQ.state.faulted && !display13FBIRQ.state.enabled && !c.map.reg(Display13FBIRQ::Master));c.retained();} - puts("PASS production DSI quiesce: real host/IRQ cores, FB-gate-clock-owner ordering, no wait under IRQ lock, actual queues/scopes, GT/peer preservation, retained resources and one-shot failure journal"); + assert(display13FBIRQ.state.faulted && !display13FBIRQ.state.enabled && !c.map.reg(Display13FBIRQ::Master));c.retained(true);} + {Case c;failDDIPut=true;assert(!c.run() && c.xe.dsiClockStop.complete() && !c.xe.dsiDDIReleased); + assert(dsiFixedMode.ready && display13FBPower.bridge.pgRefs[0]==1 && display13FBPower.bridge.ddiRefs[0]==1);c.retained(true);} + {Case c;display13FBPower.bridge.bootAdopted=false;assert(!c.run() && c.xe.dsiClockStop.complete() && !c.xe.dsiDDIReleased); + assert(display13FBPower.bridge.ddiRefs[0]==1);c.retained(true);} + for(unsigned failure=1;failure<=4;++failure){Case c;c.map.pllFail=failure; + assert(!c.run() && c.xe.dsiStop.complete() && c.xe.dsiIRQParked==(failure!=3) && !c.xe.dsiClockStop.complete()); + assert(dsiFixedMode.ready); + assert(c.xe.dsiClockStop.status==(failure==1?Clock::Status::Disable:failure==2?Clock::Status::Power:failure==3?Clock::Status::Ownership:Clock::Status::Source)); + assert(comboClockTransaction.allocation.external[0]==reinterpret_cast(&c.controller)); + assert(display13FBPower.transcoders.dsi0Pipe==0 && display13FBIRQ.state.transcoders.dsi0Pipe==0 && c.xe.dsiResourcesReady); + assert(c.map.reg(0x46010)==0xCC000000); + const auto before=c.map.bar;assert(!c.run() && c.map.bar==before); + } + for(bool software:{false,true}){Case c; + if(software)comboClockTransaction.allocation.binding[1]=2;else c.map.reg(0x164280)|=1U<<2; + assert(!c.run() && c.xe.dsiStop.complete() && c.xe.dsiClockStop.status==Clock::Status::Peer); + assert(!c.xe.dsiClockStop.writes && c.map.reg(0x46014)==0xCC000000);c.retained(); + } + puts("PASS production DSI quiesce: real host/IRQ cores, FB-gate-clock-owner ordering, no wait under IRQ lock, actual queues/scopes, PLL stop/owner withdrawal, GT/peer preservation, retained resources on failure and one-shot journal"); } '''.replace('FIXTURE',fixture).replace('BODY',body) with tempfile.TemporaryDirectory(prefix='dsi-quiesce-adapter-') as directory: diff --git a/tools/test.py b/tools/test.py index 439ba5da..45ac4d97 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] From ba177cf2ff945184a87defa9094a7a3320da44e7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:21:18 +0900 Subject: [PATCH 035/139] Retire native DSI scanout after stopped host proof --- WhateverXe/kern_xe.cpp | 43 +++++++++++++++++++++++--- WhateverXe/kern_xe.hpp | 2 +- tests/dsi_quiesce_adapter.py | 59 ++++++++++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 17 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2578ff51..a2d1221d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9703,7 +9703,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - uint64_t record[33] {3};bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; + uint64_t record[38] {4};bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; const auto qualified=[&]() { if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || @@ -9821,7 +9821,41 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { dsiDDIReleased=put && c.power==power && !c.bridge.ddiRefs[0] && !c.bridge.ddiLease[0].owner; } record[31]=c.bridge.ddiRefs[0];record[32]=dsiDDIReleased; - const bool complete=stopped && dsiDDIReleased; + const bool stoppedPath=stopped && dsiDDIReleased; + if(stoppedPath && !c.memoryFaulted && !c.dbuf.faulted) { + // Keep the real FB mutex and workloop gate while the shared native + // retirement path acquires its own allocator serializer. Neither the + // Combo mutex nor the owner lock may span native allocator callbacks. + // PG1 remains held for PTE/DDB validation; the stopped DSI PLL is free. + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + const bool released=adlnFreeStoppedPipe(fb); + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + Display13FBDBUFIO dbufIO{c.map}; + // A native no-resource return can precede path destruction. Retire + // that path's DDB consumers too, even if no allocator call was needed. + const auto dbufStatus=released && !c.memoryFaulted ? Display13DBUF::stopPipe(dbufIO,c.dbuf,0) : Display13DBUF::Status::Unavailable; + record[35]=released;record[36]=static_cast(dbufStatus); + const bool dbufStopped=dbufStatus==Display13DBUF::Status::Complete; + bool irqRestored=false; + if(released && irq.controller==controller && irq.lock && irq.base) { + Display13FBIRQIO irqIO{irq.base};const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto wells=irqIO.read(Display13FBIRQ::Driver); + const bool powered=wells!=UINT32_MAX && (wells&3)==3; + const auto after=powered ? irqIO.read(Display13FBIRQ::Master) : UINT32_MAX; + irqRestored=!irq.state.faulted && !irq.quiescing && !irq.blockedPipes && !(irq.state.active&1) && + after!=UINT32_MAX && bool(after&Display13FBIRQ::Enable)==irq.state.enabled; + if(!irqRestored){irq.state.faulted=true;irq.state.enabled=false;if(powered)irqIO.write(Display13FBIRQ::Master,0);} + adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + record[37]=irqRestored; + dsiMemoryReleased=dbufStopped && irqRestored && c.controller==controller && c.pipe[0].memoryFramebuffer==fb && + Display13FBMemory::singlePath(fb)==path && !c.memoryFaulted && + Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && + !c.pipe[0].scanout.record && !__atomic_load_n(&c.pipe[0].publishedScanout,__ATOMIC_ACQUIRE) && + !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired; + } + record[33]=dsiMemoryReleased;record[34]=c.memoryFaulted; + const bool complete=stoppedPath && dsiMemoryReleased; if(complete) { // Exact donor disableDisplay publishes path state 1 only after its // clock-disable call (4dbba -> 4dbc6). Mirror that CPU completion now, @@ -9832,8 +9866,9 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { if(dsiModeTarget(fb)){dsiFixedMode.ready=false;modeWithdrawn=true;} } // Close the existing native writer/free gate before dropping the FB mutex, - // even after host success. Remove this conservative guard only with the - // separate Session/allocator/clock/power retirement transaction in place. + // even after backing release. Keep it until the remaining PG/path/object + // retirement and Session cleanup transaction is connected. Never clear an + // earlier resource fault to force the allocator through its own guard. if(masked)adlnFault(c.memoryFaulted,__LINE__); IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(modeWithdrawn)fb->setProperty("WEXDSIFixedModeReady",false); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f6e8281d..515e4f5c 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -145,7 +145,7 @@ class XE { bool bindDSIResources(IOService *); XELP::DSI::Quiesce::Result dsiStop; XELP::DSI::Clock::Result dsiClockStop; - bool dsiStopAttempted {},dsiIRQParked {},dsiDDIReleased {}; + bool dsiStopAttempted {},dsiIRQParked {},dsiDDIReleased {},dsiMemoryReleased {}; bool quiesceDSIPath(IOService *,IOService *,OSObject *); static uint32_t wrapDisplay13ProbeMode(OSObject *); bool dsiCacheHooksReady {}; diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 306a1f7f..7eb86f9d 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -47,11 +47,12 @@ IOMemoryMap *map;bool readable(uint32_t r){return map && r<0x1000000 && !(r&3);} uint32_t read(uint32_t r){assert(locks.back()==3);return map->reg(r);} }; +struct Display13FBDBUFIO:Display13FBBootIO {void write(uint32_t,uint32_t){assert(false);}void delayUs(unsigned){assert(false);}}; struct Display13FBIRQIO { uintptr_t base;bool readable(uint32_t r){return base && r<0x1000000 && !(r&3);} - uint32_t read(uint32_t r){assert(locks.back()==4);return *reinterpret_cast(base+r);} + uint32_t read(uint32_t r){assert(locks.back()==4);if(r==Display13FBIRQ::Master)assert((activeMap->reg(0x45404)&3)==3);return *reinterpret_cast(base+r);} void write(uint32_t r,uint32_t v){assert(locks.back()==4); - if(r==Display13FBIRQ::Master){++activeMap->masterWrites; + if(r==Display13FBIRQ::Master){assert((activeMap->reg(0x45404)&3)==3);++activeMap->masterWrites; if((activeMap->masterWrites==1 && activeMap->failMasterMask) || (activeMap->masterWrites==2 && activeMap->failMasterRestore))return; } auto ®=*reinterpret_cast(base+r); @@ -61,8 +62,8 @@ struct {IORecursiveLock *lock=nullptr;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;}comboClockTransaction; struct {IOLock *lock=nullptr;IOService *controller=nullptr;void *power=nullptr;IOMemoryMap *map=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr; Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge;bool clockFaulted=false,memoryFaulted=false; - struct {unsigned updating=0;Display13Transcoder::Bindings transcoders;}dbuf; - struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false,wmProgramming=false,wmCommitting=false;}pipe[3]; + Display13DBUF::State dbuf; + struct {IOService *memoryFramebuffer=nullptr;uintptr_t publishedScanout=0;struct {uintptr_t record=0;}scanout;bool destroyingPath=false,wmProgramming=false,wmCommitting=false;}pipe[3]; }display13FBPower; struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; unsigned handlers=0,softwareHandlers=0,admittedPipes=0,blockedPipes=0;bool quiescing=false; @@ -70,7 +71,7 @@ void adlnFault(bool &fault,unsigned){assert(locks==std::vector({1,2,3}));fault=true;} struct {bool ready=true;}dsiFixedMode; bool dsiModeTarget(IOService *fb){return fb==display13FBPower.pipe[0].memoryFramebuffer;} -bool mappingBound=true,failDDIPut=false; +bool mappingBound=true,failDDIPut=false,failMemoryFree=false,lieMemoryFree=false,loseDBUF=false,loseIRQRestore=false,losePowerFree=false;unsigned memoryFrees=0; bool adlnWriterMappingBound(){return mappingBound;} bool adlnIRQOwner(IOService *p){return p && p==display13FBIRQ.controller;} namespace TGLFBBinding {enum class Status{Complete,Unavailable};struct Binding{Status status=Status::Complete;unsigned pipe=0,ddi=0;};} @@ -94,8 +95,20 @@ struct XE { uintptr_t adlnWorkLoopInGate=reinterpret_cast(inGate),adlnWorkLoopTryClose=reinterpret_cast(tryClose),adlnWorkLoopOpen=reinterpret_cast(open); struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; - bool dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false; + bool dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false,dsiMemoryReleased=false; IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop; + static bool adlnFreeStoppedPipe(IOService *fb){ + assert(locks==std::vector{1} && display13FBPower.workLoop->gate); + assert(!display13FBPower.memoryFaulted && display13FBPower.bridge.pgRefs[0]==1); + assert(!comboClockTransaction.allocation.external[0] && !display13FBPower.bridge.ddiRefs[0]); + ++memoryFrees;if(losePowerFree)activeMap->reg(0x45404)=0;if(loseIRQRestore)activeMap->reg(Display13FBIRQ::Master)=0;if(loseDBUF)activeMap->reg(0x4438C)^=1;if(failMemoryFree)return false;if(lieMemoryFree)return true; + *reinterpret_cast(fb->bytes+0x4288)=0xFF; + *reinterpret_cast(fb->bytes+0x4348)=0; + display13FBPower.pipe[0].publishedScanout=0;display13FBPower.pipe[0].scanout.record=0; + display13FBPower.dbuf.pipe[0].consumers=display13FBPower.dbuf.pipe[0].unretired=0; + Display13DBUF::syncMasks(display13FBPower.dbuf); + return true; + } static bool adlnPowerReference(void *power,unsigned operation,unsigned domain){ assert(locks==std::vector({1,2}) && operation==4 && domain==0 && power==display13FBPower.power); if(failDDIPut)return false; @@ -115,7 +128,7 @@ IORecursiveLock fbLock{1},clockLock{2};IOLock ownerLock;IOSimpleLock irqLock; Case(){ using Power=decltype(display13FBPower);display13FBPower.~Power();new (&display13FBPower) Power; - display13FBIRQ={};comboClockTransaction={};mappingBound=true;pathBinding={};activeMap=↦dsiFixedMode.ready=true;failDDIPut=false; + display13FBIRQ={};comboClockTransaction={};mappingBound=true;pathBinding={};activeMap=↦dsiFixedMode.ready=true;failDDIPut=failMemoryFree=lieMemoryFree=loseDBUF=loseIRQRestore=losePowerFree=false;memoryFrees=0; IO defaults;for(const auto &v:defaults.regs)map.reg(v.first)=v.second; map.reg(0x42084)=1U<<30;map.reg(0x6B0D0)=0x01001040;map.reg(0x6001C)=0x0437077F;map.reg(0x6B010)=map.reg(0x6B00C); map.reg(0x45454)=3;map.reg(0x42000)=0x8E380000;map.reg(0x70040)=100; @@ -127,7 +140,10 @@ map.reg(Display13FBIRQ::PipeIIR(1)+4)=Display13FBIRQ::PipeOwned; auto &c=display13FBPower;auto &irq=display13FBIRQ;auto &clock=comboClockTransaction; c.lock=&ownerLock;c.controller=&controller;c.power=&controller;c.map=↦c.workLoop=&loop;c.dsiPort=&port; - c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0;c.dbuf.transcoders=c.transcoders; + c.pipe[0].memoryFramebuffer=&fb;c.pipe[0].scanout.record=0x1000;c.pipe[0].publishedScanout=0x1000;c.transcoders.dsi0Pipe=0;c.dbuf.transcoders=c.transcoders; + c.dbuf.begun=true;c.dbuf.selected=3;c.dbuf.initialRequests=c.dbuf.requested=c.dbuf.powered=5; + c.dbuf.pipe[0].consumers=c.dbuf.pipe[0].unretired=1;c.dbuf.pipe[1].consumers=4;Display13DBUF::syncMasks(c.dbuf); + map.reg(Display13DBUF::regs[0])=map.reg(Display13DBUF::regs[2])=0xC0000000; xe.adlnTopologyController=&controller;xe.dsiProfile=profile(); c.bootDSI=decodeReadout([&](uint32_t r,uint32_t &v){v=map.reg(r);return true;},38400);assert(c.bootDSI.matches(xe.dsiProfile));xe.dsiBoot.source=c.bootDSI; clock.lock=&clockLock;clock.allocation.external[0]=reinterpret_cast(&controller);clock.allocation.binding[0]=2; @@ -137,14 +153,14 @@ const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)),ddi=Display13Power::dependencies(Display13Power::ddiDomain(0));s.borrowed=pg|ddi; for(unsigned i=0;i(&controller))); assert(map.reg(0x46010)==0xCC000000 && map.reg(0x45404)==0xFC0F); - assert(Display13FBMemory::field(&path,0x3644)==(xe.dsiIRQParked && xe.dsiClockStop.complete() && xe.dsiDDIReleased?1U:2U)); + assert(Display13FBMemory::field(&path,0x3644)==(xe.dsiIRQParked && xe.dsiClockStop.complete() && xe.dsiDDIReleased && xe.dsiMemoryReleased?1U:2U)); } }; int main(){ @@ -152,12 +168,13 @@ put(&c.controller,0xE08,2U);put(&c.controller,0xE10,1U<<6); assert(c.run() && c.xe.dsiStop.complete() && c.xe.dsiIRQParked); assert(display13FBPower.memoryFaulted); + assert(display13FBPower.dbuf.consumers==4 && !display13FBPower.dbuf.pipe[0].unretired); assert(c.xe.dsiStop.writes==8 && display13FBIRQ.state.active==2 && display13FBIRQ.state.enabled); assert(!display13FBIRQ.quiescing && !display13FBIRQ.blockedPipes && c.loop.gate==own); assert(c.loop.opens==unsigned(!own) && c.loop.closes==unsigned(!own)); assert(c.map.reg(0x71180)==0x94001000 && c.map.reg(Display13FBIRQ::PipeIIR(1)+4)==Display13FBIRQ::PipeOwned); assert(Display13FBMemory::field(&c.controller,0xE08)==2 && Display13FBMemory::field(&c.controller,0xE10)==(1U<<6)); - assert(!dsiFixedMode.ready && c.xe.dsiDDIReleased); + assert(!dsiFixedMode.ready && c.xe.dsiDDIReleased && c.xe.dsiMemoryReleased && memoryFrees==1); assert(c.xe.dsiClockStop.complete() && c.xe.dsiClockStop.writes==2 && !c.map.reg(0x46014)); assert(display13FBPower.transcoders.dsi0Pipe==UINT32_MAX && display13FBIRQ.state.transcoders.dsi0Pipe==UINT32_MAX); assert(display13FBIRQ.admittedPipes==2 && display13FBPower.dbuf.transcoders.dsi0Pipe==UINT32_MAX && !c.xe.dsiResourcesReady); @@ -188,6 +205,24 @@ assert(dsiFixedMode.ready && display13FBPower.bridge.pgRefs[0]==1 && display13FBPower.bridge.ddiRefs[0]==1);c.retained(true);} {Case c;display13FBPower.bridge.bootAdopted=false;assert(!c.run() && c.xe.dsiClockStop.complete() && !c.xe.dsiDDIReleased); assert(display13FBPower.bridge.ddiRefs[0]==1);c.retained(true);} + for(unsigned failure=0;failure<3;++failure){Case c; + if(!failure)display13FBPower.memoryFaulted=true;else if(failure==1)failMemoryFree=true;else lieMemoryFree=true; + assert(!c.run() && c.xe.dsiDDIReleased && !c.xe.dsiMemoryReleased); + assert(display13FBPower.memoryFaulted && memoryFrees==unsigned(failure!=0)); + assert(display13FBPower.pipe[0].scanout.record==0x1000 && Display13FBMemory::field(&c.fb,0x4348)==0x1000); + c.retained(true); + } + {Case c;loseDBUF=true;assert(!c.run() && c.xe.dsiDDIReleased && !c.xe.dsiMemoryReleased); + assert(memoryFrees==1 && !Display13FBMemory::field(&c.fb,0x4348) && display13FBPower.dbuf.faulted); + assert(dsiFixedMode.ready && display13FBPower.bridge.pgRefs[0]==1);c.retained(true); + } + {Case c;loseIRQRestore=true;assert(!c.run() && !c.xe.dsiMemoryReleased && memoryFrees==1); + assert(display13FBIRQ.state.faulted && !display13FBIRQ.state.enabled && !Display13FBMemory::field(&c.fb,0x4348)); + assert(dsiFixedMode.ready && display13FBPower.bridge.pgRefs[0]==1);c.retained(true); + } + {Case c;losePowerFree=true;assert(!c.run() && !c.xe.dsiMemoryReleased && memoryFrees==1); + assert(display13FBIRQ.state.faulted && !display13FBIRQ.state.enabled && display13FBPower.bridge.pgRefs[0]==1); + } for(unsigned failure=1;failure<=4;++failure){Case c;c.map.pllFail=failure; assert(!c.run() && c.xe.dsiStop.complete() && c.xe.dsiIRQParked==(failure!=3) && !c.xe.dsiClockStop.complete()); assert(dsiFixedMode.ready); @@ -202,7 +237,7 @@ assert(!c.run() && c.xe.dsiStop.complete() && c.xe.dsiClockStop.status==Clock::Status::Peer); assert(!c.xe.dsiClockStop.writes && c.map.reg(0x46014)==0xCC000000);c.retained(); } - puts("PASS production DSI quiesce: real host/IRQ cores, FB-gate-clock-owner ordering, no wait under IRQ lock, actual queues/scopes, PLL stop/owner withdrawal, GT/peer preservation, retained resources on failure and one-shot journal"); + puts("PASS production DSI quiesce: real host/IRQ cores, FB-gate-clock-owner ordering, no wait under IRQ lock, actual queues/scopes, PLL/DDI/backing retirement, allocator lock boundary, actual empty-slot/DDB proof, GT/peer preservation and retained failures"); } '''.replace('FIXTURE',fixture).replace('BODY',body) with tempfile.TemporaryDirectory(prefix='dsi-quiesce-adapter-') as directory: From 5be2b18bc2c09bae674aedc9ca60e667a09e878a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:32:46 +0900 Subject: [PATCH 036/139] Add DSI native path disposal after PG release --- WhateverXe/kern_xe.cpp | 135 +++++++++++++++++++++- WhateverXe/kern_xe.hpp | 4 + tests/display13_retirement_fixture.cpp | 19 +++- tests/dsi_path_disposal.py | 152 +++++++++++++++++++++++++ tools/test.py | 3 +- 5 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 tests/dsi_path_disposal.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a2d1221d..2c4a863d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5539,6 +5539,7 @@ bool XE::processDisplay13FramebufferLifecycle(KernelPatcher &patcher,size_t inde const uint8_t removeCall[] {0xE8,0x67,0x02,0x00,0x00}; const bool pathKnown=pathFn==address+0x68AAE && !memcmp(reinterpret_cast(pathFn),pathEntry,sizeof(pathEntry)) && !memcmp(reinterpret_cast(address+0x68C38),removeCall,sizeof(removeCall)); + if(adlnOutputWiring.dsi && !processDSIPathDisposal(patcher,index,address,size))return false; KernelPatcher::RouteRequest pathRoute {pathSymbol,wrapAlderLakeNFreePath,orgAlderLakeNFreePath}; const bool pathRouted=disableRouted && pathKnown && patcher.routeMultipleShort(index,&pathRoute,1,address,size); patcher.clearError(); @@ -9106,8 +9107,131 @@ bool XE::adlnFreeStoppedPipe(IOService *fb) { return freed; } +// The whole freeDisplayPathResources body also touches global audio and can +// reset a normal DDI port. Keep only its audited CPU leaves for the DSI path. +bool XE::processDSIPathDisposal(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN31AppleIntelFramebufferController9freePlaneEP15AppleIntelPlane", + "__ZN31AppleIntelFramebufferController10freeScalerEP16AppleIntelScaler", + "__ZN21AppleIntelFramebuffer17detachDisplayPathEP21AppleIntelDisplayPath", + "__ZN21AppleIntelFramebuffer17removeDisplayPathEP21AppleIntelDisplayPath"}; + const uint32_t offsets[]={0x5751C,0x5739A,0x67FB4,0x68EA4}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xC9,0x96,0x08,0x00,0x48,0x85,0xF6,0x74,0x50}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xEB,0x97,0x08,0x00,0x48,0x85,0xF6,0x74,0x20}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x19,0xC5,0x07,0x00,0x48,0x8B,0x05,0x0A,0xC5}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0xEE,0xB8,0x07,0x00,0x48,0x8B}}; + if(size<=0x138BB8)return false; + mach_vm_address_t functions[4] {}; + for(unsigned i=0;i<4;++i) { + functions[i]=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(functions[i]!=address+offsets[i] || memcmp(reinterpret_cast(functions[i]),entries[i],16))return false; + } + const auto map=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController12fPipeToFBMapE",address,size); + patcher.clearError();if(map!=address+0x138BB0)return false; + dsiFreePlane=functions[0];dsiFreeScaler=functions[1];dsiDetachPath=functions[2];dsiRemovePath=functions[3];dsiPipeMap=map; + return true; +} + +// Caller retains FB/path, owns its recursive mutex, and has detached its DDB +// reservation. Gate ownership excludes queued native work across PG put and +// CPU disposal. A failed put leaves the native path arrays and objects intact. +bool XE::disposeDSIPath(IOService *fb,OSObject *path) { + auto &c=display13FBPower; + if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || + path->getMetaClass()->getClassSize()<0x3730 || !dsiFreePlane || !dsiFreeScaler || + !dsiDetachPath || !dsiRemovePath || !dsiPipeMap || + !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; + IOLockLock(c.lock);auto loop=c.workLoop;if(loop)loop->retain();IOLockUnlock(c.lock); + if(!loop)return false; + const bool inGate=reinterpret_cast(adlnWorkLoopInGate)(loop); + const bool acquired=!inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); + if(!inGate && !acquired){loop->release();return false;} + uint64_t record[14] {1};IOLockLock(c.lock); + auto controller=static_cast(c.controller); + auto port=c.dsiPort; + auto plane=Display13FBMemory::field(path,0x32C8); + auto scaler=Display13FBMemory::field(path,0x32C0); + auto planeScaler=plane && plane->getMetaClass()->getClassSize()>=0x1C8 ? Display13FBMemory::field(plane,0x88) : nullptr; + auto pipeMap=*reinterpret_cast(dsiPipeMap); + const auto scalerOwned=[&](OSObject *o) { + return !o || (o->getMetaClass()->getClassSize()>=0x58 && + Display13FBMemory::field(o,0x10)==fb && Display13FBMemory::field(o,0x20)==1 && + Display13FBMemory::field(o,0x18)==0); + }; + bool planeTailEmpty=true;for(unsigned i=1;i<5;++i)if(Display13FBMemory::field(path,0x32C8+8*i))planeTailEmpty=false; + const auto attached=Display13FBMemory::field(fb,0x4A00); + const auto irqIdle=[&]() { + auto &irq=display13FBIRQ; + if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || !adlnIRQOwner(controller) || !irq.lock)return false; + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const bool idle=!irq.state.faulted && !irq.quiescing && !irq.blockedPipes && + !(irq.state.active&1) && !(irq.admittedPipes&1) && irq.scopes.total==irq.handlers && + !irq.scopes.display && !irq.softwareHandlers && + !(__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE08),__ATOMIC_ACQUIRE)&Display13FBIRQ::pipeEvents(0)) && + !__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE0C),__ATOMIC_ACQUIRE) && + !(__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE10),__ATOMIC_ACQUIRE)&3U); + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return idle; + }; + const bool irqBefore=irqIdle();record[12]=irqBefore; + const bool ready=irqBefore && adlnOutputWiring.dsi==1 && controller && controller==adlnTopologyController && + controller->getMetaClass()->getClassSize()>=0x1C20 && c.workLoop==loop && + Display13FBMemory::field(controller,0xE00)==loop && + Display13FBMemory::field(fb,0x1D0)==controller && + Display13FBMemory::field(path,0x32B0)==fb && + Display13FBMemory::field(path,0x3640)==0 && Display13FBMemory::field(path,0x3644)==1 && + c.pipe[0].memoryFramebuffer==fb && c.pipe[0].destroyingPath && !c.pipe[0].publishedScanout && !c.pipe[0].scanout.record && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && + !(c.dbuf.selected&1) && !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired && + dsiStop.complete() && dsiClockStop.complete() && dsiIRQParked && dsiDDIReleased && dsiMemoryReleased && !dsiPGReleased && + c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && c.bridge.bootPort==0 && + !c.bridge.bootPGProtected && c.bridge.pgRefs[0]==1 && !c.bridge.ddiRefs[0] && + Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && + port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(path,0x3648)==port && + Display13FBMemory::field(port,0x550)==1 && Display13FBMemory::field(port,0x554)<=3 && + !Display13FBMemory::field(path,0x32B8) && planeTailEmpty && + plane && plane->getMetaClass()->getClassSize()>=0x1C8 && Display13FBMemory::field(plane,0x68)==fb && + Display13FBMemory::field(plane,0x70)==path && Display13FBMemory::field(plane,0x78)==0 && + scalerOwned(scaler) && scalerOwned(planeScaler) && + Display13FBMemory::field(fb,0x49E8)==1 && Display13FBMemory::field(fb,0x49F0)==path && + attached<=1 && Display13FBMemory::field(fb,0x4A08)==(attached ? path : nullptr) && pipeMap && pipeMap[0]==fb; + record[1]=ready;record[2]=c.bridge.pgRefs[0];record[3]=c.dbuf.selected;record[4]=attached; + auto power=c.power;IOLockUnlock(c.lock); + const bool put=ready && power && adlnPowerReference(power,2,1); + IOLockLock(c.lock); + if(put)dsiPGReleased=c.power==power && c.controller==controller && !c.bridge.state.faulted && + !c.bridge.pgRefs[0] && !c.bridge.pgLease[0].owner; + record[5]=put;record[6]=dsiPGReleased;record[7]=c.bridge.pgRefs[0]; + bool removed=false;const bool irqAfter=put && irqIdle();record[13]=irqAfter; + if(irqAfter && dsiPGReleased && !c.memoryFaulted && !c.dbuf.faulted && !c.clockFaulted && + c.pipe[0].memoryFramebuffer==fb && *reinterpret_cast(dsiPipeMap)==pipeMap && pipeMap[0]==fb) { + // These leaves only update CPU objects; no power or MMIO callback. + reinterpret_cast(dsiFreePlane)(controller,plane); + reinterpret_cast(dsiFreeScaler)(controller,scaler); + if(Display13FBMemory::field(port,0x554)==3) + *reinterpret_cast(reinterpret_cast(port)+0x554)=2; + // The normal setPortMode(0) could reset DDI. Hardware is already stopped. + if(!Display13FBMemory::field(port,0x554)) + *reinterpret_cast(reinterpret_cast(port)+0x550)=0; + *reinterpret_cast(reinterpret_cast(path)+0x3648)=nullptr; + reinterpret_cast(dsiDetachPath)(fb,path); + reinterpret_cast(dsiRemovePath)(fb,path); + const auto freedScaler=[](OSObject *o) {return !o || (!Display13FBMemory::field(o,0x10) && !Display13FBMemory::field(o,0x20));}; + removed=!Display13FBMemory::field(fb,0x49E8) && !Display13FBMemory::field(fb,0x49F0) && + !Display13FBMemory::field(fb,0x4A00) && !Display13FBMemory::field(fb,0x4A08) && !pipeMap[0] && + !Display13FBMemory::field(path,0x3644) && !Display13FBMemory::field(path,0x32B0) && + !Display13FBMemory::field(path,0x3648) && !Display13FBMemory::field(plane,0x68) && + freedScaler(scaler) && freedScaler(planeScaler); + } + record[8]=removed;record[9]=Display13FBMemory::field(fb,0x49E8); + record[10]=Display13FBMemory::field(fb,0x4A00);record[11]=Display13FBMemory::field(path,0x3644); + IOLockUnlock(c.lock); + if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); + loop->release();adlnPublishMemory("N97FBDSIPathDispose",0,record,sizeof(record));return removed; +} + uint32_t XE::releaseDisplay13Path(IOService *fb,OSObject *path) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && !callbackXE->adlnOutputWiring.dsi) { FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return 0; } auto &c=display13FBPower; @@ -9146,6 +9270,15 @@ uint32_t XE::releaseDisplay13Path(IOService *fb,OSObject *path) { adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock); IORecursiveLockUnlock(mutex);path->release();fb->release();return kIOReturnNotReady; } + // DSI has no audio link or normal-DDI port reset. Dispose only its proven + // stopped native CPU associations and PG lease, never the full donor body. + if(callbackXE->adlnOutputWiring.dsi && dsiLifecycleTarget(fb,path)) { + if(!selected || !callbackXE->disposeDSIPath(fb,path)) { + IOLockLock(c.lock);if(pipe<3)c.pipe[pipe].destroyingPath=false; + adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock); + IORecursiveLockUnlock(mutex);path->release();fb->release();return kIOReturnNotReady; + } + } else FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path); IOLockLock(c.lock); record[6]=countOffset ? Display13FBMemory::field(fb,countOffset) : UINT32_MAX; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 515e4f5c..cefb0551 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -147,6 +147,10 @@ class XE { XELP::DSI::Clock::Result dsiClockStop; bool dsiStopAttempted {},dsiIRQParked {},dsiDDIReleased {},dsiMemoryReleased {}; bool quiesceDSIPath(IOService *,IOService *,OSObject *); + bool processDSIPathDisposal(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool disposeDSIPath(IOService *,OSObject *); + mach_vm_address_t dsiFreePlane {},dsiFreeScaler {},dsiDetachPath {},dsiRemovePath {},dsiPipeMap {}; + bool dsiPGReleased {}; static uint32_t wrapDisplay13ProbeMode(OSObject *); bool dsiCacheHooksReady {}; mach_vm_address_t orgDSICache[2] {}; diff --git a/tests/display13_retirement_fixture.cpp b/tests/display13_retirement_fixture.cpp index 05f6b3bb..d030b4d3 100644 --- a/tests/display13_retirement_fixture.cpp +++ b/tests/display13_retirement_fixture.cpp @@ -38,8 +38,9 @@ struct Display13FBDBUFIO {void *map;}; void adlnFault(bool &fault,unsigned){fault=true;} void adlnPublishMemory(const char*,unsigned,void*,size_t){} struct XE { - bool display13FramebufferRequested=true;unsigned adlnOutputWiring=0x78;void *adlnPlatform{}; + bool display13FramebufferRequested=true;struct Wiring{unsigned value=0x78,dsi=0;operator unsigned()const{return value;}Wiring &operator=(unsigned v){value=v;return *this;}}adlnOutputWiring;void *adlnPlatform{}; uintptr_t orgAlderLakeNFreePath{},orgDisplay13FreeResources{},orgDisplay13AllocateResources{}; + bool disposeDSIPath(IOService*,OSObject*); static bool adlnFreeStoppedPipe(IOService*); static void wrapAlderLakeNFreePath(IOService*,OSObject*); static void drainDisplay13TCMode(unsigned ddi) { @@ -55,6 +56,7 @@ TGLFBBinding::Readout adlnInspectPathBinding(IOService*f,OSObject*p,void*,unsign if(!p||Display13FBMemory::field(p,0x32b0)!=f)return {}; return {TGLFBBinding::Status::Complete,pipe,3}; } +bool dsiLifecycleTarget(IOService*,OSObject*){return true;} // FUNCTIONS alignas(16) static uint8_t ctl[0x1c20],fb[0x4b90],peer[0x4b90],path[0x3730]; static unsigned failure,memoryCalls,pathCalls,postCalls,transactionStops,allocCalls; @@ -76,6 +78,13 @@ void nativePath(IOService*f,OSObject*p){ put(f,0x49e8,0U);put(f,0x49f0,static_cast(nullptr)); put(p,0x3644,0U);put(p,0x32b0,static_cast(nullptr));put(p,0x3648,static_cast(nullptr)); } +unsigned dsiDisposals=0;bool dsiRefuse=false; +bool XE::disposeDSIPath(IOService *f,OSObject *p){ + assert(ctlDepth && fbDepth && !ownerDepth);++dsiDisposals; + if(dsiRefuse)return false; + put(f,0x49e8,0U);put(f,0x49f0,uintptr_t(0));put(p,0x3644,0U);put(p,0x32b0,uintptr_t(0));put(p,0x3648,uintptr_t(0)); + return true; +} void transactions(void*){assert(ctlDepth&&!fbDepth);++transactionStops;} void disable(void*f,bool){assert(ctlDepth&&!fbDepth);put(path,0x3644,failure==5?2U:1U);} void post(void*){assert(ctlDepth&&!fbDepth);++postCalls;} @@ -143,6 +152,14 @@ int main(int argc,char**argv){ // completes. Clear only that synthetic peer for the empty-controller case. assert(!XE::retireDisplay13Controller(ctl));assert(pathCalls==1); display13FBPower.pipe[1]={};assert(XE::retireDisplay13Controller(ctl));assert(pathCalls==1); + // DSI selected at the real release boundary must never fall through to the + // normal audio/DDI cleanup body, including when its dedicated disposal fails. + setup(native);state.adlnOutputWiring.dsi=1;dsiRefuse=true;dsiDisposals=0; + assert(XE::wrapDisplay13FreeResources(fb)==kIOReturnNotReady && dsiDisposals==1 && !pathCalls && !postCalls); + assert(Display13FBMemory::field(path,0x32b0)==fb && display13FBPower.pipe[0].memoryFramebuffer==reinterpret_cast(fb)); + setup(native);state.adlnOutputWiring.dsi=1;dsiRefuse=false;dsiDisposals=0; + assert(!XE::wrapDisplay13FreeResources(fb) && dsiDisposals==1 && !pathCalls && postCalls==4); + assert(!display13FBPower.pipe[0].memoryFramebuffer); setup(native);state.display13FramebufferRequested=false;display13FBPower.memoryFaulted=true; assert(XE::display13RetirementHealthy()&&XE::retireDisplay13Controller(nullptr)); // other backends unchanged if(image)munmap(image,bytes); diff --git a/tests/dsi_path_disposal.py b/tests/dsi_path_disposal.py new file mode 100644 index 00000000..0aa6a5d4 --- /dev/null +++ b/tests/dsi_path_disposal.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Exercise production DSI PG/path disposal, optionally replay exact CPU leaves.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'bool XE::disposeDSIPath\(.*?\n\}',s,re.S)[0] +code=r''' +#include "kern_dsi_quiesce.hpp" +#include "kern_dsi_clock.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_power_bridge.hpp" +#include +#include +#include +#include +#include +#include +#include +using namespace XELP::DSI; +struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes),refs=1; + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} + void retain(){++refs;}void release(){assert(refs>1);--refs;}}; +using IOService=OSObject;using IOLock=int; +unsigned depth=0;bool gate=false,busy=false,queueOnPut=false;unsigned opens=0,closes=0,putCalls=0,leaves=0;bool failPut=false,liePut=false,failLeaf=false; +void IOLockLock(IOLock*){assert(!depth);++depth;}void IOLockUnlock(IOLock*){assert(depth==1);--depth;} +struct IOWorkLoop:OSObject{}; +bool inGate(const IOWorkLoop*){assert(!depth);return gate;} +bool tryClose(IOWorkLoop*){assert(!depth);++closes;return !busy && (gate=true);} +void open(IOWorkLoop*){assert(!depth && gate);gate=false;++opens;} +struct {IOLock *lock=nullptr;IOService *controller=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr;void *power=nullptr; + bool memoryFaulted=false,clockFaulted=false;Display13Power::NativeBridge bridge;Display13DBUF::State dbuf; + struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=true;uintptr_t publishedScanout=0;struct {uintptr_t record=0;}scanout;}pipe[3]; +}display13FBPower; +uint64_t receipt[14]{}; +struct {IOLock *lock=nullptr;Display13FBIRQ::State state;Display13FBIRQ::HandlerScopes scopes; + bool quiescing=false;unsigned blockedPipes=0,admittedPipes=2,handlers=0,softwareHandlers=0;}display13FBIRQ; +bool adlnIRQOwner(IOService *c){return c==display13FBPower.controller;} +bool IOSimpleLockLockDisableInterrupt(IOLock*){assert(depth==1);++depth;return true;} +void IOSimpleLockUnlockEnableInterrupt(IOLock*,bool){assert(depth==2);--depth;} +void adlnPublishMemory(const char*,unsigned,void *p,size_t n){assert(!depth && n==sizeof(receipt));memcpy(receipt,p,n);} +templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +IOService *mapEntries[3]{},**pipeMap=mapEntries; +void freeScaler(IOService*,OSObject *s){assert(depth && gate);if(s){put(s,0x10,uintptr_t(0));put(s,0x20,uint8_t(0));}} +void freePlane(IOService *c,OSObject *p){++leaves;assert(depth && gate);freeScaler(c,Display13FBMemory::field(p,0x88));put(p,0x68,uintptr_t(0));} +void detach(IOService *fb,OSObject*){assert(depth && gate);put(fb,0x4A00,0U);put(fb,0x4A08,uintptr_t(0));} +void removePath(IOService *fb,OSObject *p){assert(depth && gate);if(failLeaf)return;pipeMap[0]=nullptr; + put(fb,0x49E8,0U);put(fb,0x49F0,uintptr_t(0));put(p,0x32B0,uintptr_t(0));put(p,0x3644,0U);} +struct XE { + uintptr_t dsiFreePlane=reinterpret_cast(freePlane),dsiFreeScaler=reinterpret_cast(freeScaler), + dsiDetachPath=reinterpret_cast(detach),dsiRemovePath=reinterpret_cast(removePath),dsiPipeMap=reinterpret_cast(&pipeMap); + uintptr_t adlnWorkLoopInGate=reinterpret_cast(inGate),adlnWorkLoopTryClose=reinterpret_cast(tryClose),adlnWorkLoopOpen=reinterpret_cast(open); + struct {unsigned dsi=1;}adlnOutputWiring;IOService *adlnTopologyController=nullptr; + bool dsiIRQParked=true,dsiDDIReleased=true,dsiMemoryReleased=true,dsiPGReleased=false; + Quiesce::Result dsiStop;Clock::Result dsiClockStop; + static bool adlnPowerReference(void *p,unsigned op,unsigned domain){ + assert(!depth && gate && p==display13FBPower.power && op==2 && domain==1);++putCalls; + if(queueOnPut)*reinterpret_cast(display13FBPower.controller->bytes+0xE08)=1; + if(failPut)return false;if(liePut)return true; + // Real NativeBridge balances the borrowed firmware PG1, preserving PG3. + struct IO {uint32_t read(uint32_t r){return r==0x45404?0xC0000003U:0;}bool readable(uint32_t){return true;} + void write(uint32_t,uint32_t){assert(false);}void delayUs(unsigned){assert(false);}}io; + return display13FBPower.bridge.putPG(io,1).status==Display13Power::Status::Complete; + } + bool disposeDSIPath(IOService*,OSObject*); +}; +BODY +uint8_t *image=nullptr; +struct Case { + XE xe;IOService controller,fb,peer;OSObject path,port,plane,scaler,planeScaler;IOWorkLoop loop;IOLock lock; + Case(){ + using Power=decltype(display13FBPower);display13FBPower.~Power();new (&display13FBPower) Power;auto &c=display13FBPower;c.lock=&lock;c.controller=&controller;c.workLoop=&loop;c.dsiPort=&port;c.power=&controller; + c.pipe[0].memoryFramebuffer=&fb;c.pipe[0].destroyingPath=true;c.dbuf.selected=2; + c.bridge.begun=true;c.bridge.bootAdopted=true;c.bridge.bootPath=reinterpret_cast(&path);c.bridge.bootPort=0; + c.bridge.pgRefs[0]=1;c.bridge.pgRefs[2]=1; + auto &state=c.bridge.state;state.ready=true;state.allowed=Display13Power::All; + const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)),peerPG=Display13Power::dependencies(Display13Power::nativeScope(3)); + state.borrowed=pg|peerPG; + for(unsigned i=0;i(image+0x5751C);xe.dsiFreeScaler=reinterpret_cast(image+0x5739A); + xe.dsiDetachPath=reinterpret_cast(image+0x67FB4);xe.dsiRemovePath=reinterpret_cast(image+0x68EA4); + put(image,0x138BB0,pipeMap);xe.dsiPipeMap=reinterpret_cast(image+0x138BB0);} + } + void retained(){assert(!xe.disposeDSIPath(&fb,&path));assert(!putCalls && !leaves && !xe.dsiPGReleased); + assert(Display13FBMemory::field(&fb,0x49F0)==&path && pipeMap[0]==&fb && loop.refs==1 && !depth && !gate);} + void success(){assert(xe.disposeDSIPath(&fb,&path));assert(xe.dsiPGReleased && putCalls==1 && loop.refs==1 && !depth); + assert(!pipeMap[0] && pipeMap[1]==&peer && !display13FBPower.bridge.pgRefs[0] && display13FBPower.bridge.pgRefs[2]==1); + assert(Display13FBMemory::field(&port,0x554)==2 && !Display13FBMemory::field(&plane,0x68)); + assert(!xe.disposeDSIPath(&fb,&path) && xe.dsiPGReleased && putCalls==1);} +}; +void run(){ + {Case c;c.success();assert(opens==closes && !gate);} + {Case c;gate=true;c.success();assert(!opens && !closes && gate);gate=false;} + {Case c;busy=true;c.retained();} + {Case c;c.xe.dsiMemoryReleased=false;c.retained();} + {Case c;display13FBPower.memoryFaulted=true;c.retained();} + {Case c;display13FBIRQ.state.active|=1;c.retained();} + {Case c;display13FBIRQ.softwareHandlers=1;c.retained();} + {Case c;put(&c.controller,0xE08,1U);c.retained();} + {Case c;put(&c.controller,0xE10,1U);c.retained();} + {Case c;put(&c.controller,0xE08,Display13FBIRQ::pipeEvents(1));put(&c.controller,0xE10,0xC0000U);c.success();} + {Case c;queueOnPut=true;assert(!c.xe.disposeDSIPath(&c.fb,&c.path));assert(c.xe.dsiPGReleased && pipeMap[0]==&c.fb && !leaves);} + {Case c;display13FBPower.dbuf.selected|=1;c.retained();} + {Case c;display13FBPower.bridge.pgRefs[0]=2;c.retained();} + {Case c;display13FBPower.bridge.bootPGProtected=true;c.retained();} + {Case c;put(&c.path,0x3644,2U);c.retained();} + {Case c;put(&c.path,0x32B8,&c.peer);c.retained();} + {Case c;put(&c.planeScaler,0x10,&c.peer);c.retained();} + {Case c;put(&c.fb,0x49E8,2U);c.retained();} + {Case c;put(&c.fb,0x4A08,&c.peer);c.retained();} + {Case c;put(&c.fb,0x4A00,0U);c.retained();} + {Case c;c.controller.size=0x100;c.retained();} + {Case c;put(&c.fb,0x4A00,0U);put(&c.fb,0x4A08,uintptr_t(0));c.success();} + {Case c;put(&c.path,0x32D0,&c.peer);c.retained();} + {Case c;failPut=true;assert(!c.xe.disposeDSIPath(&c.fb,&c.path));assert(putCalls==1 && !c.xe.dsiPGReleased && !leaves && pipeMap[0]==&c.fb);} + {Case c;liePut=true;assert(!c.xe.disposeDSIPath(&c.fb,&c.path));assert(putCalls==1 && !c.xe.dsiPGReleased && !leaves && pipeMap[0]==&c.fb);} + if(!image){Case c;failLeaf=true;assert(!c.xe.disposeDSIPath(&c.fb,&c.path));assert(c.xe.dsiPGReleased && pipeMap[0]==&c.fb);} +} +int main(int argc,char **argv){ + run(); + if(argc==2){std::ifstream f(argv[1],std::ios::binary);std::vector b{std::istreambuf_iterator(f),{}}; + image=static_cast(mmap(nullptr,4*1024*1024,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0));assert(image!=MAP_FAILED); + memcpy(image,b.data(),b.size()); + // freePath -> resetSoftwareState remains native; only imported bzero is supplied. + image[0x300000]=0x48;image[0x300001]=0xB8;put(image,0x300002,reinterpret_cast(bzero));image[0x30000A]=0xFF;image[0x30000B]=0xE0; + for(unsigned call:{0x9161FU,0x91630U,0x91641U,0x91652U,0x9166CU})put(image,call+1,int32_t(0x300000-call-5)); + run();munmap(image,4*1024*1024); + } + std::puts("PASS production DSI PG/path disposal: ownership, gate, failed put retention, CPU arrays/scalers and peer preservation"); +} +'''.replace('BODY',body) +args=[];arch=[] +if len(sys.argv)>1: + donor=Path(sys.argv[1]).resolve();data=donor.read_bytes() + assert hashlib.sha256(data).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + args=[str(donor)];arch=['-arch','x86_64'] +with tempfile.TemporaryDirectory(prefix='dsi-dispose-') as d: + source=Path(d)/'test.cpp';source.write_text(code);binary=Path(d)/'test' + subprocess.run(['clang++',*arch,'-std=c++17','-O1','-I'+str(root/'WhateverXe'),str(source),'-o',str(binary)],check=True) + subprocess.run([str(binary),*args],check=True) diff --git a/tools/test.py b/tools/test.py index 45ac4d97..ce9cd283 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,9 +8,10 @@ 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', '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 += ['dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_path_disposal_native',[sys.executable,str(R/'tests/dsi_path_disposal.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_boot_probe_native',[sys.executable,str(R/'tests/dsi_boot_probe.py'),str(a.donor.resolve())],None)] From 63bc525714d25f5fe6e5bcb2b057af064482074d Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:42:17 +0900 Subject: [PATCH 037/139] Guard DSI public mode and native update entry points --- WhateverXe/kern_xe.cpp | 59 ++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 5 ++- tests/dsi_apple_adapter.py | 2 +- tests/dsi_update_dispatch.py | 87 ++++++++++++++++++++++++++++++++++++ tools/check_boot_stack.py | 3 +- tools/test.py | 2 +- 6 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 tests/dsi_update_dispatch.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2c4a863d..df3eb9aa 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6534,14 +6534,16 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad "__ZN21AppleIntelFramebuffer19validateDisplayModeEiPPKNS_15ModeDescriptionEPPK29IODetailedTimingInformationV2", "__ZN21AppleIntelFramebuffer15getDisplayModesEPi", "__ZN21AppleIntelFramebuffer19getDisplayModeCountEv", - "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation"}; - const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010}; + "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation", + "__ZN21AppleIntelFramebuffer14setDisplayModeEii"}; + const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10}; const uint8_t entries[][16]={ {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xF6,0x48,0x89,0xFB,0x48,0xFF,0x05}, {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0x98,0x93,0x0A,0x00,0x83,0xFE}, {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x8F,0x84,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xE1,0x79,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}}; + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}}; if(!display13FramebufferRequested || size<=0x32D14)return false; for(unsigned i=0;i(index,symbols[i],address,size);patcher.clearError(); @@ -6552,7 +6554,8 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, {symbols[2],wrapDSIGetModes,orgDSIMode[2]}, {symbols[3],wrapDSIGetModeCount,orgDSIMode[3]}, - {symbols[4],wrapDSIGetModeInformation,orgDSIMode[4]}}; + {symbols[4],wrapDSIGetModeInformation,orgDSIMode[4]}, + {symbols[5],wrapDSISetDisplayMode,orgDSIMode[5]}}; const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size); patcher.clearError();return ready; } @@ -6722,6 +6725,38 @@ static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { return attached!=path || dsiCachePath(attached); } +// The native public mode entry disables/reallocates before hwSetMode. Refuse +// here until the GOP-preserving mode transaction can own all those steps. +uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb)))return kIOReturnNotReady; + } + return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); +} + +// Caller holds the native FB mutex through the admitted writer. quiesce uses +// the same mutex, so a stop cannot interleave after this check. This is one +// update boundary, not a certificate for other native writers or Session Stop. +bool XE::dsiNativeUpdateAllowed(IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi)return true; + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; + const auto path=Display13FBMemory::singlePath(fb); + if(!dsiLifecycleTarget(fb,path))return true; + auto &c=display13FBPower;if(!c.lock)return false; + IOLockLock(c.lock); + const bool ready=!callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && + !c.dbuf.faulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && + c.transcoders.dsi0Pipe==0 && path && path->getMetaClass()->getClassSize()>=0x3730 && + Display13FBMemory::field(fb,0x1D0)==c.controller && + Display13FBMemory::field(path,0x32B0)==fb && Display13FBMemory::field(path,0x3640)==0 && + Display13FBMemory::field(path,0x3644)==2 && c.dsiPort && c.dsiPort->getMetaClass()->getClassSize()>=0x580 && + Display13FBMemory::field(path,0x3648)==c.dsiPort && Display13FBMemory::field(c.dsiPort,0x550)==1; + IOLockUnlock(c.lock);return ready; +} + bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) { auto &c=display13FBPower; if(!c.lock || !controller || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || @@ -10258,6 +10293,22 @@ static bool adlnObserveScanoutPixels(unsigned pipe,uint64_t (&q)[88]) { } void XE::wrapDisplay13WMArmUpdate(IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi){display13WMArmUpdate(fb);return;} + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return; + { + DSIModeScope scope(fb);if(!scope.lock)return; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // armUpdate flushes native packets even without pending WM work. + // Keep FB serialization from admission through flush/sendPush. + if(dsiNativeUpdateAllowed(fb))display13WMArmUpdate(fb); + return; + } + } + // Preserve the original external body's locking and callback boundaries. + display13WMArmUpdate(fb); +} + +void XE::display13WMArmUpdate(IOService *fb) { auto &c=display13FBPower;bool owner=false; const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); Display13WM::TriggerSurfaces intent;OSObject *path=nullptr;uint32_t packetCount=0; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index cefb0551..5750c668 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -215,11 +215,13 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; bool display13DSIModeHooksReady {}; - mach_vm_address_t orgDSIMode[5] {}; + mach_vm_address_t orgDSIMode[6] {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t setDSIPortMode(OSObject *,uint32_t); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); + static uint32_t wrapDSISetDisplayMode(IOService *,int32_t,int32_t); + static bool dsiNativeUpdateAllowed(IOService *); static uint32_t wrapDSIValidateMode(IOService *,int32_t,const void **,const void **); static uint32_t wrapDSIGetModes(IOService *,int32_t *); static uint32_t wrapDSIGetModeCount(IOService *); @@ -332,6 +334,7 @@ class XE { static void wrapAlderLakeNPlaneWMEnable(OSObject *plane,bool arm); static void wrapDisplay13CursorWMEnable(OSObject *plane,bool arm); static void wrapDisplay13WMArmUpdate(IOService *framebuffer); + static void display13WMArmUpdate(IOService *framebuffer); static Display13WM::Latch adlnVerifyWMLatch(IOService *framebuffer,uint64_t nativeHz,uint64_t targetHz); mach_vm_address_t orgDisplay13WMArmUpdate {}; static void wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,bool wait); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 2901492d..9c9d495d 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -16,7 +16,7 @@ offsets = [int(x, 0) for x in re.search(r'offsets\[\]=\{([^}]+)', installer)[1].split(',')] entries = re.search(r'entries\[\]\[16\]=\{(.*?)\};', installer, re.S)[1] entries = [bytes(int(x, 0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}', entries)] - assert len(entries) == len(offsets) == 5 + assert len(entries) == len(offsets) == 6 for offset, entry in zip(offsets, entries): assert binary[offset:offset+16] == entry, hex(offset) diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py new file mode 100644 index 00000000..edb6b6e7 --- /dev/null +++ b/tests/dsi_update_dispatch.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Execute production public-mode/armUpdate dispatch before native side effects.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','wrapDisplay13WMArmUpdate']) +scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] +code=r''' +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_transcoder.hpp" +#include +#include +struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; +using IOService=OSObject;using IOLock=int; +struct IORecursiveLock {unsigned depth=0;}; +unsigned owner=0,modeCalls=0,armCalls=0;IORecursiveLock fbLock;bool expectDSI=false; +void IOLockLock(IOLock*){assert(fbLock.depth && !owner);++owner;}void IOLockUnlock(IOLock*){assert(owner==1);--owner;} +void IORecursiveLockLock(IORecursiveLock *p){assert(!owner);++p->depth;} +void IORecursiveLockUnlock(IORecursiveLock *p){assert(!owner && p->depth);--p->depth;} +struct {IOLock *lock=nullptr;IOService *controller=nullptr;OSObject *dsiPort=nullptr; + bool memoryFaulted=false,clockFaulted=false; + struct {struct {bool faulted=false,suspended=false;}state;}bridge;struct {bool faulted=false;}dbuf; + struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[3]; + Display13Transcoder::Bindings transcoders;}display13FBPower; +IOService *panel=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} +constexpr uint32_t kIOReturnNotReady=0xe00002d8U; +#define FunctionCast(f,p) reinterpret_cast(p) +uint32_t nativeMode(IOService*,int32_t m,int32_t d){assert(!fbLock.depth && !owner);assert(m==0x10000 && d==2);++modeCalls;return 7;} +struct XE { + struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; + uintptr_t orgDSIMode[6]{}; + static uint32_t wrapDSISetDisplayMode(IOService*,int32_t,int32_t); + static bool dsiNativeUpdateAllowed(IOService*); + static void wrapDisplay13WMArmUpdate(IOService*); + static void display13WMArmUpdate(IOService*){assert(!owner);assert(bool(fbLock.depth)==expectDSI);++armCalls;} +}xe,*callbackXE=&xe; +SCOPE +FUNCTIONS +templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +struct Case { + IOService fb,ctl,peer;OSObject path,port;IOLock lock; + Case(){xe={};xe.orgDSIMode[5]=reinterpret_cast(nativeMode);display13FBPower={};auto &c=display13FBPower; + c.lock=&lock;c.controller=&ctl;c.dsiPort=&port;c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0;panel=&fb; + put(&fb,0x4220,&fbLock);put(&fb,0x1D0,&ctl);put(&fb,0x4A00,1U);put(&fb,0x4A08,&path); + put(&path,0x3640,0U);put(&path,0x3644,2U);put(&path,0x32B0,&fb);put(&path,0x3648,&port);put(&port,0x550,1U); + expectDSI=true; + } + void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} +}; +int main(){ + // Public mode rejection precedes SaveNVRAMMode, disable, timing/backing and + // native notifications; even the selected fixed mode is not yet admitted. + {Case c;auto before=modeCalls;assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,2)==kIOReturnNotReady); + assert(modeCalls==before && !fbLock.depth && !owner);} + {Case c;auto before=armCalls;XE::wrapDisplay13WMArmUpdate(&c.fb);assert(armCalls==before+1 && !fbLock.depth);} + {Case c;xe.dsiStopAttempted=true;c.refuse();} + {Case c;xe.dsiResourcesReady=false;c.refuse();} + {Case c;display13FBPower.memoryFaulted=true;c.refuse();} + {Case c;display13FBPower.bridge.state.suspended=true;c.refuse();} + {Case c;display13FBPower.pipe[0].destroyingPath=true;c.refuse();} + {Case c;display13FBPower.transcoders={};c.refuse();} + {Case c;display13FBPower.pipe[0].memoryFramebuffer=nullptr;c.refuse();} + {Case c;put(&c.path,0x3644,1U);c.refuse();} + {Case c;put(&c.path,0x3640,1U);c.refuse();} + {Case c;put(&c.path,0x32B0,&c.peer);c.refuse();} + {Case c;put(&c.port,0x550,2U);c.refuse();} + {Case c;put(&c.fb,0x4A00,0U);c.refuse();} + {Case c;c.path.size=0x100;c.refuse();} + {Case c;put(&c.fb,0x4220,static_cast(nullptr));c.refuse();} + // Real external identity stays admitted during DSI stop/fault. The existing + // body gets its original return/arguments; DSI does not poison peer updates. + {Case c;panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U);xe.dsiStopAttempted=true;display13FBPower.memoryFaulted=true; + auto a=armCalls,m=modeCalls;expectDSI=false;XE::wrapDisplay13WMArmUpdate(&c.fb); + assert(armCalls==a+1 && XE::wrapDSISetDisplayMode(&c.fb,0x10000,2)==7 && modeCalls==m+1 && !fbLock.depth);} + // Without DSI, forward unchanged even when this adapter cannot interpret FB. + {Case c;xe.adlnOutputWiring.dsi=0;auto a=armCalls,m=modeCalls;expectDSI=false; + XE::wrapDisplay13WMArmUpdate(nullptr);assert(armCalls==a+1); + assert(XE::wrapDSISetDisplayMode(nullptr,0x10000,2)==7 && modeCalls==m+1);} + puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, peer and disabled-DSI forwarding"); +} +'''.replace('SCOPE',scope).replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-update-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/check_boot_stack.py b/tools/check_boot_stack.py index 75eadf25..54cc9204 100644 --- a/tools/check_boot_stack.py +++ b/tools/check_boot_stack.py @@ -20,7 +20,8 @@ '_ZN2XE21processTGLAcceleratorER13KernelPatchermym': 6144, '_ZN2XE15processKextFullER13KernelPatchermym': 6144, '_ZL22tglStartOrderSerializePvS_P11OSSerialize': 3328, - '_ZN2XE24wrapDisplay13WMArmUpdateEP9IOService': 2816, + '_ZN2XE20display13WMArmUpdateEP9IOService': 2816, # Existing body behind the DSI entry guard. + '_ZN2XE24wrapDisplay13WMArmUpdateEP9IOService': 256, '_ZN12_GLOBAL__N_120tglCaptureHangDetailERKN14TGLHangPending7CaptureEPKhRKN13TGLHangSelect9SelectionE': 2688, '_ZN2XE17tglObserveScanoutEPvS0_jbj': 2304, '_ZN12_GLOBAL__N_120tglRecoverySerializeEPvS0_P11OSSerialize': 2304, diff --git a/tools/test.py b/tools/test.py index ce9cd283..d87f96f5 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_path_disposal_native',[sys.executable,str(R/'tests/dsi_path_disposal.py'),str(a.donor.resolve())],None)] From 8f30877b31351d033f79dd677b5b06913ed9a431 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:50:54 +0900 Subject: [PATCH 038/139] Guard DSI cursor writers against host and path retirement --- WhateverXe/kern_xe.cpp | 60 ++++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 7 +++ tests/dsi_path_disposal.py | 3 +- tests/dsi_quiesce_adapter.py | 9 +++- tests/dsi_resources.py | 3 +- tests/dsi_update_dispatch.py | 82 +++++++++++++++++++++++++++++++----- tools/test.py | 1 + 7 files changed, 146 insertions(+), 19 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index df3eb9aa..cbccca50 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4563,6 +4563,7 @@ struct Display13FBPowerContext { Display13Transcoder::Bindings transcoders; XELP::DSI::Readout bootDSI; // Captured before handoff; not an implicit admission flag. OSObject *dsiPort {}; // Retained after source-qualified native mode binding. + uint32_t dsiNativeWriters {}; // Owner-lock count; cursor bodies may wait without the FB mutex. IOSimpleLock *tcBankLock {}; bool tcSupported {},tcBankFaulted {}; Display13TC::Mode tcMode[4]; @@ -5461,6 +5462,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); + dsiNativeWriterHooksReady=!adlnOutputWiring.dsi || processDSINativeWriters(patcher,index,address,size); dsiBootProbeReady=processDisplay13DSIBoot(patcher,index,address,size); dsiCacheHooksReady=processDisplay13DSICache(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); @@ -6757,6 +6759,56 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { IOLockUnlock(c.lock);return ready; } +// Cursor image/state bodies can wait for a frame, so do not extend their +// native FB-lock intervals. Count admitted DSI calls instead. Stop takes the +// same FB mutex before testing the count and cannot start until it is zero. +int XE::beginDSINativeWriter(IOService *controller,IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi)return 0; + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return -1; + DSIModeScope scope(fb);if(!scope.lock)return -1; + if(!dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb)))return 0; + if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return -1; + auto &c=display13FBPower;IOLockLock(c.lock); + const bool admitted=controller==c.controller && c.dsiNativeWriters!=UINT32_MAX; + if(admitted)++c.dsiNativeWriters; + IOLockUnlock(c.lock);return admitted ? 1 : -1; +} +void XE::endDSINativeWriter(int token) { + if(token!=1)return; + auto &c=display13FBPower;IOLockLock(c.lock); + if(c.dsiNativeWriters)--c.dsiNativeWriters; + else adlnFault(c.memoryFaulted,__LINE__); + IOLockUnlock(c.lock); +} +uint32_t XE::wrapDSICursorImage(IOService *controller,IOService *fb,void *info) { + const int token=beginDSINativeWriter(controller,fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSICursorImage,callbackXE->orgDSICursor[0])(controller,fb,info); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSICursorState(IOService *controller,IOService *fb,int32_t x,int32_t y,bool visible,bool commit) { + const int token=beginDSINativeWriter(controller,fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSICursorState,callbackXE->orgDSICursor[1])(controller,fb,x,y,visible,commit); + endDSINativeWriter(token);return result; +} +bool XE::processDSINativeWriters(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN31AppleIntelFramebufferController16hwSetCursorImageEP21AppleIntelFramebufferP20IOHardwareCursorInfo", + "__ZN31AppleIntelFramebufferController16hwSetCursorStateEP21AppleIntelFramebufferiibb"}; + const uint32_t offsets[]={0x558F6,0x55DA8}; + const uint8_t entries[][20]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28,0x49,0x89,0xD4}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38,0x45,0x89,0xCE}}; + if(!display13FramebufferRequested || size<=0x55DBC)return false; + for(unsigned i=0;i<2;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSICursorImage,orgDSICursor[0]}, + {symbols[1],wrapDSICursorState,orgDSICursor[1]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} + bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) { auto &c=display13FBPower; if(!c.lock || !controller || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || @@ -9209,7 +9261,7 @@ bool XE::disposeDSIPath(IOService *fb,OSObject *path) { IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return idle; }; const bool irqBefore=irqIdle();record[12]=irqBefore; - const bool ready=irqBefore && adlnOutputWiring.dsi==1 && controller && controller==adlnTopologyController && + const bool ready=irqBefore && !c.dsiNativeWriters && adlnOutputWiring.dsi==1 && controller && controller==adlnTopologyController && controller->getMetaClass()->getClassSize()>=0x1C20 && c.workLoop==loop && Display13FBMemory::field(controller,0xE00)==loop && Display13FBMemory::field(fb,0x1D0)==controller && @@ -9871,9 +9923,9 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - uint64_t record[38] {4};bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; + uint64_t record[39] {5};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; const auto qualified=[&]() { - if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiProfile.valid() || + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || Display13FBMemory::field(controller,0xE00)!=loop || c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || @@ -10931,7 +10983,7 @@ void XE::wrapAlderLakeNOptimizedDBUF(IOService *controller) {adlnSetupDBUF(contr bool XE::bindDSIResources(IOService *controller) { auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !display13FBIRQReady || !clock.lock || !c.lock)return false; + !display13FBIRQReady || !dsiNativeWriterHooksReady || !clock.lock || !c.lock)return false; IORecursiveLockLock(clock.lock);IOLockLock(c.lock); struct IO : Display13FBBootIO { explicit IO(IOMemoryMap *map):Display13FBBootIO{map} {} diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 5750c668..74a819af 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -222,6 +222,13 @@ class XE { static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); static uint32_t wrapDSISetDisplayMode(IOService *,int32_t,int32_t); static bool dsiNativeUpdateAllowed(IOService *); + bool processDSINativeWriters(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool dsiNativeWriterHooksReady {}; + mach_vm_address_t orgDSICursor[2] {}; + static int beginDSINativeWriter(IOService *,IOService *); + static void endDSINativeWriter(int); + static uint32_t wrapDSICursorImage(IOService *,IOService *,void *); + static uint32_t wrapDSICursorState(IOService *,IOService *,int32_t,int32_t,bool,bool); static uint32_t wrapDSIValidateMode(IOService *,int32_t,const void **,const void **); static uint32_t wrapDSIGetModes(IOService *,int32_t *); static uint32_t wrapDSIGetModeCount(IOService *); diff --git a/tests/dsi_path_disposal.py b/tests/dsi_path_disposal.py index 0aa6a5d4..a3702aee 100644 --- a/tests/dsi_path_disposal.py +++ b/tests/dsi_path_disposal.py @@ -29,7 +29,7 @@ bool tryClose(IOWorkLoop*){assert(!depth);++closes;return !busy && (gate=true);} void open(IOWorkLoop*){assert(!depth && gate);gate=false;++opens;} struct {IOLock *lock=nullptr;IOService *controller=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr;void *power=nullptr; - bool memoryFaulted=false,clockFaulted=false;Display13Power::NativeBridge bridge;Display13DBUF::State dbuf; + unsigned dsiNativeWriters=0;bool memoryFaulted=false,clockFaulted=false;Display13Power::NativeBridge bridge;Display13DBUF::State dbuf; struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=true;uintptr_t publishedScanout=0;struct {uintptr_t record=0;}scanout;}pipe[3]; }display13FBPower; uint64_t receipt[14]{}; @@ -106,6 +106,7 @@ {Case c;busy=true;c.retained();} {Case c;c.xe.dsiMemoryReleased=false;c.retained();} {Case c;display13FBPower.memoryFaulted=true;c.retained();} + {Case c;display13FBPower.dsiNativeWriters=1;c.retained();} {Case c;display13FBIRQ.state.active|=1;c.retained();} {Case c;display13FBIRQ.softwareHandlers=1;c.retained();} {Case c;put(&c.controller,0xE08,1U);c.retained();} diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 7eb86f9d..54ee411b 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -62,7 +62,7 @@ struct {IORecursiveLock *lock=nullptr;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;}comboClockTransaction; struct {IOLock *lock=nullptr;IOService *controller=nullptr;void *power=nullptr;IOMemoryMap *map=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr; Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge;bool clockFaulted=false,memoryFaulted=false; - Display13DBUF::State dbuf; + Display13DBUF::State dbuf;unsigned dsiNativeWriters=0; struct {IOService *memoryFramebuffer=nullptr;uintptr_t publishedScanout=0;struct {uintptr_t record=0;}scanout;bool destroyingPath=false,wmProgramming=false,wmCommitting=false;}pipe[3]; }display13FBPower; struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; @@ -95,7 +95,7 @@ struct XE { uintptr_t adlnWorkLoopInGate=reinterpret_cast(inGate),adlnWorkLoopTryClose=reinterpret_cast(tryClose),adlnWorkLoopOpen=reinterpret_cast(open); struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; - bool dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false,dsiMemoryReleased=false; + bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false,dsiMemoryReleased=false; IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop; static bool adlnFreeStoppedPipe(IOService *fb){ assert(locks==std::vector{1} && display13FBPower.workLoop->gate); @@ -184,6 +184,11 @@ assert(reused.valid && reused.pll==1 && !reused.keepPLL); c.retained(true);const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && c.xe.dsiIRQParked); // one-shot journal } + {Case c;display13FBPower.dsiNativeWriters=1;const auto before=c.map.bar; + assert(!c.run() && c.map.bar==before && !c.xe.dsiStopAttempted && !memoryFrees); + display13FBPower.dsiNativeWriters=0;assert(c.run() && c.xe.dsiMemoryReleased);} + {Case c;c.xe.dsiNativeWriterHooksReady=false;const auto before=c.map.bar; + assert(!c.run() && c.map.bar==before && !c.xe.dsiStopAttempted);} for(unsigned bad=0;bad<12;++bad){Case c; switch(bad){case 0:c.loop.busy=true;break;case 1:mappingBound=false;break; case 2:display13FBIRQ.softwareHandlers=1;break;case 3:display13FBIRQ.handlers=1;display13FBIRQ.scopes.total=1;display13FBIRQ.scopes.display=1;break; diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index 97468b20..b78c4062 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -47,7 +47,7 @@ unsigned handlers=0,softwareHandlers=0,admittedPipes=0;bool quiescing=false;} display13FBIRQ; XELPMemory::Layout xelpMemory; struct XE { - bool display13FBIRQReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; + bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; struct {unsigned dsi=1;} adlnOutputWiring; Profile dsiProfile;Boot dsiBoot;GGTTMode dsiGGTT; bool bindDSIResources(IOService *); @@ -70,6 +70,7 @@ b.ddiRefs[0]=1;b.ddiLease[0].owner=&s;b.ddiLease[0].domains=ddi; const auto rejected=[&]{assert(!xe.bindDSIResources(&controller));assert(!lockLevel && !xe.dsiResourcesReady && !irq.controller && c.transcoders.dsi0Pipe==UINT32_MAX && !clock.allocation.external[0]);}; + xe.dsiNativeWriterHooksReady=false;rejected();xe.dsiNativeWriterHooksReady=true; b.ddiRefs[0]=0;rejected();b.ddiRefs[0]=1; clock.thread=1;rejected();clock.thread=0; c.engineEnabled=true;rejected();c.engineEnabled=false; diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index edb6b6e7..9dc32006 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -1,11 +1,21 @@ #!/usr/bin/env python3 -"""Execute production public-mode/armUpdate dispatch before native side effects.""" +"""Execute production mode/update/cursor admission and stop serialization.""" from pathlib import Path -import re,subprocess,tempfile +import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() -functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] - for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','wrapDisplay13WMArmUpdate']) +functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState']) +functions=functions.replace('void XE::endDSINativeWriter(int token) {', 'void XE::endDSINativeWriter(int token) {\n struct EndScope {EndScope(){endingWriter=true;}~EndScope(){endingWriter=false;}} endScope;') scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] +if len(sys.argv)==2: + binary=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(binary).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + installer=re.search(r'bool XE::processDSINativeWriters\(.*?\n\}',s,re.S)[0] + offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] + entries=re.search(r'entries\[\]\[20\]=\{(.*?)\};',installer,re.S)[1] + entries=[bytes(int(x,0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}',entries)] + assert len(offsets)==len(entries)==2 + for offset,entry in zip(offsets,entries):assert binary[offset:offset+20]==entry,hex(offset) code=r''' #include "kern_display13_fb_memory.hpp" #include "kern_display13_transcoder.hpp" @@ -15,22 +25,31 @@ OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; using IOService=OSObject;using IOLock=int; struct IORecursiveLock {unsigned depth=0;}; -unsigned owner=0,modeCalls=0,armCalls=0;IORecursiveLock fbLock;bool expectDSI=false; -void IOLockLock(IOLock*){assert(fbLock.depth && !owner);++owner;}void IOLockUnlock(IOLock*){assert(owner==1);--owner;} +unsigned owner=0,modeCalls=0,armCalls=0;IORecursiveLock fbLock;bool expectDSI=false,endingWriter=false; +void IOLockLock(IOLock*){assert((fbLock.depth || endingWriter) && !owner);++owner;}void IOLockUnlock(IOLock*){assert(owner==1);--owner;} void IORecursiveLockLock(IORecursiveLock *p){assert(!owner);++p->depth;} void IORecursiveLockUnlock(IORecursiveLock *p){assert(!owner && p->depth);--p->depth;} struct {IOLock *lock=nullptr;IOService *controller=nullptr;OSObject *dsiPort=nullptr; - bool memoryFaulted=false,clockFaulted=false; + unsigned dsiNativeWriters=0;bool memoryFaulted=false,clockFaulted=false; struct {struct {bool faulted=false,suspended=false;}state;}bridge;struct {bool faulted=false;}dbuf; struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[3]; Display13Transcoder::Bindings transcoders;}display13FBPower; -IOService *panel=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} +IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} constexpr uint32_t kIOReturnNotReady=0xe00002d8U; #define FunctionCast(f,p) reinterpret_cast(p) uint32_t nativeMode(IOService*,int32_t m,int32_t d){assert(!fbLock.depth && !owner);assert(m==0x10000 && d==2);++modeCalls;return 7;} +void adlnFault(bool &f,unsigned){f=true;} +unsigned imageCalls=0,stateCalls=0,expectedWriters=1;bool nestedCursor=false; +uint32_t nativeImage(IOService*,IOService*,void*); +uint32_t nativeState(IOService*,IOService*,int32_t,int32_t,bool,bool); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; + bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; + static int beginDSINativeWriter(IOService*,IOService*); + static void endDSINativeWriter(int); + static uint32_t wrapDSICursorImage(IOService*,IOService*,void*); + static uint32_t wrapDSICursorState(IOService*,IOService*,int32_t,int32_t,bool,bool); static uint32_t wrapDSISetDisplayMode(IOService*,int32_t,int32_t); static bool dsiNativeUpdateAllowed(IOService*); static void wrapDisplay13WMArmUpdate(IOService*); @@ -38,15 +57,33 @@ }xe,*callbackXE=&xe; SCOPE FUNCTIONS +uint32_t nativeState(IOService *ctl,IOService *fb,int32_t x,int32_t y,bool visible,bool commit){ + assert(!fbLock.depth && !owner);assert(ctl==display13FBPower.controller && fb==nativeFB); + assert(x==-17 && y==42 && visible && !commit);assert(display13FBPower.dsiNativeWriters==expectedWriters); + ++stateCalls;return 0xe0000001U; +} +uint32_t nativeImage(IOService *ctl,IOService *fb,void *info){ + assert(!fbLock.depth && !owner);assert(ctl==display13FBPower.controller && fb==nativeFB && info==&imageCalls); + assert(display13FBPower.dsiNativeWriters==expectedWriters);++imageCalls; + if(nestedCursor){++expectedWriters;assert(XE::wrapDSICursorState(ctl,fb,-17,42,true,false)==0xe0000001U); + --expectedWriters;assert(display13FBPower.dsiNativeWriters==expectedWriters);} + return 0xe0000002U; +} templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} struct Case { IOService fb,ctl,peer;OSObject path,port;IOLock lock; - Case(){xe={};xe.orgDSIMode[5]=reinterpret_cast(nativeMode);display13FBPower={};auto &c=display13FBPower; - c.lock=&lock;c.controller=&ctl;c.dsiPort=&port;c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0;panel=&fb; + Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=false;xe={}; + xe.orgDSICursor[0]=reinterpret_cast(nativeImage);xe.orgDSICursor[1]=reinterpret_cast(nativeState);xe.orgDSIMode[5]=reinterpret_cast(nativeMode);display13FBPower={};auto &c=display13FBPower; + c.lock=&lock;c.controller=&ctl;c.dsiPort=&port;c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0;panel=nativeFB=&fb; put(&fb,0x4220,&fbLock);put(&fb,0x1D0,&ctl);put(&fb,0x4A00,1U);put(&fb,0x4A08,&path); put(&path,0x3640,0U);put(&path,0x3644,2U);put(&path,0x32B0,&fb);put(&path,0x3648,&port);put(&port,0x550,1U); expectDSI=true; } + void refuseCursor(){auto i=imageCalls,s=stateCalls; + assert(XE::wrapDSICursorImage(&ctl,&fb,&imageCalls)==kIOReturnNotReady); + assert(XE::wrapDSICursorState(&ctl,&fb,-17,42,true,false)==kIOReturnNotReady); + assert(i==imageCalls && s==stateCalls && !fbLock.depth && !owner); + } void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ @@ -78,7 +115,30 @@ {Case c;xe.adlnOutputWiring.dsi=0;auto a=armCalls,m=modeCalls;expectDSI=false; XE::wrapDisplay13WMArmUpdate(nullptr);assert(armCalls==a+1); assert(XE::wrapDSISetDisplayMode(nullptr,0x10000,2)==7 && modeCalls==m+1);} - puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, peer and disabled-DSI forwarding"); + // Actual writer wrappers count nesting and release counts on native errors. + {Case c;nestedCursor=true;auto i=imageCalls,s=stateCalls; + assert(XE::wrapDSICursorImage(&c.ctl,&c.fb,&imageCalls)==0xe0000002U); + assert(imageCalls==i+1 && stateCalls==s+1 && !display13FBPower.dsiNativeWriters);} + {Case c;assert(XE::wrapDSICursorState(&c.ctl,&c.fb,-17,42,true,false)==0xe0000001U); + assert(!display13FBPower.dsiNativeWriters);} + {Case c;xe.dsiStopAttempted=true;c.refuseCursor();} + {Case c;xe.dsiResourcesReady=false;c.refuseCursor();} + {Case c;xe.dsiNativeWriterHooksReady=false;c.refuseCursor();} + {Case c;display13FBPower.memoryFaulted=true;c.refuseCursor();} + {Case c;display13FBPower.dsiNativeWriters=UINT32_MAX;c.refuseCursor(); + assert(display13FBPower.dsiNativeWriters==UINT32_MAX);} + {Case c;auto i=imageCalls;assert(XE::wrapDSICursorImage(&c.peer,&c.fb,&imageCalls)==kIOReturnNotReady); + assert(i==imageCalls && !display13FBPower.dsiNativeWriters);} + {Case c;put(&c.fb,0x4220,static_cast(nullptr));c.refuseCursor();} + {Case c;panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U); + xe.dsiStopAttempted=true;expectedWriters=0; + assert(XE::wrapDSICursorState(&c.ctl,nullptr,-17,42,true,false)==kIOReturnNotReady); + assert(XE::wrapDSICursorState(&c.ctl,&c.fb,-17,42,true,false)==0xe0000001U); + assert(!display13FBPower.dsiNativeWriters); + panel=&c.fb; // Identity predicate is irrelevant for disabled DSI. + xe.adlnOutputWiring.dsi=0; + assert(XE::wrapDSICursorImage(&c.ctl,&c.fb,&imageCalls)==0xe0000002U);} + puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor counts, native wait lock boundaries, peer and disabled-DSI forwarding"); } '''.replace('SCOPE',scope).replace('FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='dsi-update-') as d: diff --git a/tools/test.py b/tools/test.py index d87f96f5..22e60b87 100644 --- a/tools/test.py +++ b/tools/test.py @@ -11,6 +11,7 @@ python_tests += ['dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_path_disposal_native',[sys.executable,str(R/'tests/dsi_path_disposal.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] From 6ec2d6fedc8ff311547b0def47dacf1aff356c9f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:56:02 +0900 Subject: [PATCH 039/139] Reject late shared writers after DSI retirement without faulting peers --- WhateverXe/kern_xe.cpp | 17 +++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/display13_dbuf_setup.py | 4 ++- tests/display13_dbuf_setup_fixture.cpp | 41 ++++++++++++++++++++++---- tests/dsi_update_dispatch.py | 12 +++++++- 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index cbccca50..fac44487 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6759,6 +6759,14 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { IOLockUnlock(c.lock);return ready; } +// Owner lock held. P2 reserves pipe A for DSI even after its mapping is +// withdrawn. Late shared callbacks must not revive it or fault peer outputs. +// An unbound/ambiguous callback cannot establish a different output owner. +bool XE::dsiSharedWriterStopped(IOService *fb,unsigned pipe) { + return callbackXE->adlnOutputWiring.dsi && callbackXE->dsiStopAttempted && + (pipe==0 || pipe>=3 || dsiModeTarget(fb)); +} + // Cursor image/state bodies can wait for a frame, so do not extend their // native FB-lock intervals. Count admitted DSI calls instead. Stop takes the // same FB mutex before testing the count and cannot start until it is zero. @@ -8846,6 +8854,7 @@ bool XE::wrapAlderLakeNScanoutAllocate(IOService *fb) { binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); pipe=binding.pipe; } + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return false;} record[12]=pipe;record[13]=static_cast(binding.status);record[14]=dual; if(pipe<3 && !c.pipe[pipe].destroyingPath && (!dual || binding.status==TGLFBBinding::Status::Complete) && (c.bridge.bootAdopted || !pipe) && callbackXE->display13FBMemoryHooksReady && controller && c.bridge.begun && @@ -8913,6 +8922,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { IOWorkLoop *acquiredSwapGate=nullptr;OSObject *stopPath=nullptr;bool stopSwap=false; IOLockLock(c.lock); pipe=Display13FBMemory::boundPipe(c.pipe,fb);record[18]=pipe; + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); record[19]=static_cast(binding.status); if(callbackXE->display13FBMemoryHooksReady && pipe<3 && !c.pipe[pipe].destroyingPath && !c.memoryFaulted && @@ -9769,6 +9779,7 @@ void XE::adlnWMSetup(OSObject *plane,OSObject *path,bool cursor) { uint64_t bindingRecord[] {1,static_cast(binding.status),binding.framebuffer,binding.pipe,binding.ddi,binding.plane, binding.status==TGLFBBinding::Status::Complete ? Display13FBMemory::allocatedSlot(controller,fb) : UINT32_MAX}; const auto pipe=binding.status==TGLFBBinding::Status::Complete ? binding.pipe : UINT32_MAX; + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);return;} const auto bank=pipe<3 ? 0x1000*pipe : 0U; // Use the real path's pipe and a unique published memory owner. Neither // framebuffer index, physical DDI nor aperture slot selects a WM bank. @@ -9860,6 +9871,7 @@ void XE::wrapDisplay13CursorWMSetup(OSObject *plane,OSObject *path) {adlnWMSetup void XE::wrapAlderLakeNPipeWMSetup(IOService *controller,IOService *fb,OSObject *path,void *params) { auto &c=display13FBPower;if(!c.lock)return; uint64_t record[8] {};IOLockLock(c.lock); + if(dsiSharedWriterStopped(fb,Display13FBMemory::boundPipe(c.pipe,fb))) {IOLockUnlock(c.lock);return;} const bool associated=callbackXE->display13FBWatermarkHooksReady && controller==c.controller && fb && path && controller->getMetaClass()->getClassSize()>=0x1C20 && fb->getMetaClass()->getClassSize()>=0x4B90 && path->getMetaClass()->getClassSize()>=0x3730 && Display13FBMemory::field(fb,0x1D0)==controller && @@ -10155,6 +10167,7 @@ void XE::adlnWMEnable(OSObject *plane,bool arm,bool cursor) { uint64_t record[16] {},inputs[24] {};Display13WM::Program r;uint32_t surface=0,queueIndex=0;bool queued=false; IOLockLock(c.lock); const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} if(pipe>=3) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} const auto bank=0x1000*pipe; const auto &p=c.pipe[pipe].wm[cursor ? 1 : 0]; @@ -10745,6 +10758,7 @@ void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,b IORecursiveLockLock(mutex); uint64_t record[20] {};IOLockLock(c.lock); const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} if(pipe>=3 || c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} Display13DBUF::Status status=Display13DBUF::Status::Unavailable; auto path=fb ? Display13FBMemory::field(plane,0x70) : nullptr; @@ -10794,6 +10808,7 @@ void XE::wrapAlderLakeNPlanarDBUFWrite(void *access,uint64_t reg,uint32_t value) if(fb->getMetaClass()->getClassSize()<0x4B90) {fb->release();return;} auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex) {fb->release();return;} IORecursiveLockLock(mutex);IOLockLock(c.lock); + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);fb->release();return;} if(c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);fb->release();return;} uint64_t record[14] {2,reg,value};auto status=Display13DBUF::Status::Unavailable; auto path=Display13FBMemory::singlePath(fb); @@ -10832,6 +10847,7 @@ void XE::wrapDisplay13CursorDBUF(OSObject *plane) { IORecursiveLockLock(mutex);IOLockLock(c.lock); Display13DBUF::Status status=Display13DBUF::Status::Unavailable;uint64_t record[16] {}; const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} if(pipe>=3 || c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} auto path=Display13FBMemory::field(plane,0x70); const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); @@ -10893,6 +10909,7 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo if(pipe>=3 || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; IORecursiveLockLock(mutex);IOLockLock(c.lock); + if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} Display13DBUF::Layout layout;Display13DBUF::Range relativePrimary;OSObject *plane=nullptr;uint64_t record[20] {},resume[6] {}; auto path=Display13FBMemory::singlePath(fb); const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 74a819af..94aa05c1 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -222,6 +222,7 @@ class XE { static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); static uint32_t wrapDSISetDisplayMode(IOService *,int32_t,int32_t); static bool dsiNativeUpdateAllowed(IOService *); + static bool dsiSharedWriterStopped(IOService *,unsigned); bool processDSINativeWriters(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool dsiNativeWriterHooksReady {}; mach_vm_address_t orgDSICursor[2] {}; diff --git a/tests/display13_dbuf_setup.py b/tests/display13_dbuf_setup.py index bb0e35f1..1af29821 100644 --- a/tests/display13_dbuf_setup.py +++ b/tests/display13_dbuf_setup.py @@ -4,8 +4,10 @@ import re, subprocess, tempfile r = Path(__file__).resolve().parents[1] s = (r / 'WhateverXe/kern_xe.cpp').read_text() +guard = re.search(r'bool XE::dsiSharedWriterStopped\(.*?\n\}', s, re.S).group() body = re.search(r'void XE::adlnSetupDBUFPipe\(.*?\n\}', s, re.S).group() -code = (r / 'tests/display13_dbuf_setup_fixture.cpp').read_text().replace('// PRODUCTION_SETUP', body) +pipewm = re.search(r'void XE::wrapAlderLakeNPipeWMSetup\(.*?\n\}', s, re.S).group() +code = (r / 'tests/display13_dbuf_setup_fixture.cpp').read_text().replace('// PRODUCTION_SETUP', guard+'\n'+body+'\n'+pipewm) with tempfile.TemporaryDirectory(prefix='dbuf-setup-') as d: p = Path(d) (p / 'test.cpp').write_text(code) diff --git a/tests/display13_dbuf_setup_fixture.cpp b/tests/display13_dbuf_setup_fixture.cpp index e954ec0d..7e3d2f85 100644 --- a/tests/display13_dbuf_setup_fixture.cpp +++ b/tests/display13_dbuf_setup_fixture.cpp @@ -13,7 +13,7 @@ void IORecursiveLockUnlock(int*){assert(fbDepth && !ownerDepth);--fbDepth;} struct OSObject {unsigned size;OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; using IOService=OSObject; struct Map { - std::map r; + unsigned reads=0;std::map r; Map() { r[0x45404]=0xFC0F;r[0x4438C]=0x1C000700; for(unsigned p=0;p<3;++p){const auto b=p*0x1000; @@ -27,12 +27,12 @@ struct Map { }; struct Display13FBDBUFIO { Map *map;bool readable(uint32_t reg){return map->r.count(reg);} - uint32_t read(uint32_t reg){assert(readable(reg));return map->r[reg];} + uint32_t read(uint32_t reg){assert(readable(reg));++map->reads;return map->r[reg];} void write(uint32_t reg,uint32_t value){assert(readable(reg));map->r[reg]=(value&~(1U<<30))|((value&(1U<<31))>>1);} void delayUs(unsigned){} }; namespace TGLFBBinding {enum class Status {Unavailable,Complete};struct Readout {Status status{};unsigned pipe{},ddi{};};} -namespace Display13FBTopology {bool multiOutputWiring(unsigned w){return w!=0;}} +namespace Display13FBTopology {bool multiOutputWiring(unsigned w){return w!=0;}bool selected(void*,uint32_t*,unsigned){return true;}} struct Pipe { IOService *memoryFramebuffer{};bool destroyingPath{},wmProgramming{},wmCommitting{}; unsigned wmPending{};uint64_t watermarkClockHz{};Display13WM::Plan wmProgram[2];Display13WM::Latch wmLatch; @@ -46,7 +46,8 @@ struct Power { }display13FBPower; static bool badBinding,failPrimary,failWM;static unsigned armCalls;static Display13WM::Latch nextLatch; static std::map> records; -void adlnFault(bool &f,unsigned,unsigned){f=true;} +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry root;return &root;}void setProperty(const char*,void*,unsigned){}}; +void adlnFault(bool &f,unsigned,unsigned=0){f=true;} void adlnPublishMemory(const char *name,unsigned p,void *data,unsigned bytes,bool=false){ if(!strcmp(name,"N97FBDBUFLayout"))records[p]=std::vector((uint64_t*)data,(uint64_t*)data+bytes/8); } @@ -54,8 +55,14 @@ TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path,void*, const auto p=Display13FBMemory::boundPipe(display13FBPower.pipe,fb); return badBinding||!path?TGLFBBinding::Readout{}:TGLFBBinding::Readout{TGLFBBinding::Status::Complete,p,3+p}; } +static IOService *dsiFB=nullptr; +bool dsiModeTarget(IOService *fb){return fb && fb==dsiFB;} struct XE { - bool display13FBDBUFHooksReady=true,display13FBWatermarkHooksReady=true;unsigned adlnOutputWiring=0x78;void *adlnPlatform{}; + bool display13FBDBUFHooksReady=true,display13FBWatermarkHooksReady=true;struct {unsigned dsi=0;operator unsigned()const{return 0x78;}}adlnOutputWiring;void *adlnPlatform{}; + bool dsiStopAttempted=false; + uintptr_t adlnAdjustPixelClock=0; + static bool dsiSharedWriterStopped(IOService*,unsigned); + static void wrapAlderLakeNPipeWMSetup(IOService*,IOService*,OSObject*,void*); static void adlnSetupDBUFPipe(IOService*,IOService*,unsigned,bool); static void wrapAlderLakeNEngineEnable(void*){assert(false);} static void wrapAlderLakeNPrimaryDBUF(OSObject*,unsigned,unsigned,bool); @@ -94,7 +101,7 @@ alignas(16) static uint8_t ctl[0x1c20],fb[3][0x4b90],paths[3][0x3730],planes[3][ static int ownerMutex,frameMutex; void setup(Map &map){ memset(ctl,0,sizeof(ctl));memset(fb,0,sizeof(fb));memset(paths,0,sizeof(paths));memset(planes,0,sizeof(planes)); - display13FBPower={};auto &c=display13FBPower;c.lock=&ownerMutex;c.controller=(IOService*)ctl;c.map=↦c.engineEnabled=true; + state={};dsiFB=nullptr;display13FBPower={};auto &c=display13FBPower;c.lock=&ownerMutex;c.controller=(IOService*)ctl;c.map=↦c.engineEnabled=true; for(unsigned p=0;p<3;++p){ ((OSObject*)fb[p])->size=sizeof(fb[p]);((OSObject*)planes[p])->size=sizeof(planes[p]); c.pipe[p].memoryFramebuffer=(IOService*)fb[p];put(fb[p],0x4220,&frameMutex);put(fb[p],0x4A00,1U);put(fb[p],0x4A08,(OSObject*)paths[p]); @@ -121,6 +128,28 @@ int main(){ nextLatch.status=S::Complete;IORecursiveLockLock(&frameMutex);XE::wrapDisplay13WMArmUpdate((IOService*)fb[p]);IORecursiveLockUnlock(&frameMutex); assert(!c.pipe[p].wmPending&&!c.dbuf.pipe[p].unretired&&c.pipe[p].watermarkClockHz); } + // A late callback after DSI stop does not touch MMIO, revive the engine, + // allocate a DDB range, or turn an ordinary stop into a shared fault. + for(bool cold:{false,true}) { + Map map;setup(map);auto &c=display13FBPower;state.adlnOutputWiring.dsi=1;state.dsiStopAttempted=true; + dsiFB=(IOService*)fb[0]; + if(cold){c.engineEnabled=false;c.clockStopped=true;c.bridge.bootAdopted=c.bridge.begun=true;} + const auto registers=map.r;const auto reads=map.reads;const auto dbuf=c.dbuf; + XE::adlnSetupDBUFPipe(c.controller,(IOService*)fb[0],0,false); + IORecursiveLockLock(&frameMutex);XE::wrapAlderLakeNPipeWMSetup(c.controller,(IOService*)fb[0],(OSObject*)paths[0],nullptr);IORecursiveLockUnlock(&frameMutex); + assert(map.r==registers && map.reads==reads && !armCalls && records.empty()); + assert(!memcmp(&dbuf,&c.dbuf,sizeof(dbuf)) && !c.memoryFaulted && !c.dbuf.faulted && !c.watermarkFaulted); + assert(!ownerDepth && !fbDepth); + if(!cold){ + Display13FBDBUFIO io{&map};assert(Display13DBUF::bindOffPipe(io,c.dbuf,1)==Display13DBUF::Status::Complete); + map.r[0x71008]=0xC0000000;map.r[0x71180]=0; + nextLatch.check=C::AwaitingScanout; + XE::adlnSetupDBUFPipe(c.controller,(IOService*)fb[1],1,false); + assert(armCalls==1 && records.count(1) && !c.dbuf.faulted); + IORecursiveLockLock(&frameMutex);XE::wrapAlderLakeNPipeWMSetup(c.controller,(IOService*)fb[1],(OSObject*)paths[1],nullptr);IORecursiveLockUnlock(&frameMutex); + assert(c.watermarkFaulted); // Peer malformed association still reports its real fault. + } + } // Generic unavailable results, mismatches, timeouts and allocation failures // must not be turned into deferred success by the new branch. for(unsigned fault=0;fault<6;++fault){ diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 9dc32006..91b5b3a6 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -4,7 +4,7 @@ import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] - for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState']) + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState']) functions=functions.replace('void XE::endDSINativeWriter(int token) {', 'void XE::endDSINativeWriter(int token) {\n struct EndScope {EndScope(){endingWriter=true;}~EndScope(){endingWriter=false;}} endScope;') scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] if len(sys.argv)==2: @@ -52,6 +52,7 @@ static uint32_t wrapDSICursorState(IOService*,IOService*,int32_t,int32_t,bool,bool); static uint32_t wrapDSISetDisplayMode(IOService*,int32_t,int32_t); static bool dsiNativeUpdateAllowed(IOService*); + static bool dsiSharedWriterStopped(IOService*,unsigned); static void wrapDisplay13WMArmUpdate(IOService*); static void display13WMArmUpdate(IOService*){assert(!owner);assert(bool(fbLock.depth)==expectDSI);++armCalls;} }xe,*callbackXE=&xe; @@ -115,6 +116,15 @@ {Case c;xe.adlnOutputWiring.dsi=0;auto a=armCalls,m=modeCalls;expectDSI=false; XE::wrapDisplay13WMArmUpdate(nullptr);assert(armCalls==a+1); assert(XE::wrapDSISetDisplayMode(nullptr,0x10000,2)==7 && modeCalls==m+1);} + // Shared callback policy is read under the owner lock. A retired DSI + // cannot revive through pipe A, retained FB identity, or an unknown pipe. + {Case c;IORecursiveLockLock(&fbLock);IOLockLock(display13FBPower.lock); + assert(!XE::dsiSharedWriterStopped(&c.fb,0));xe.dsiStopAttempted=true; + assert(XE::dsiSharedWriterStopped(&c.fb,0) && XE::dsiSharedWriterStopped(&c.fb,1)); + assert(XE::dsiSharedWriterStopped(&c.peer,UINT32_MAX)); + assert(!XE::dsiSharedWriterStopped(&c.peer,1) && !XE::dsiSharedWriterStopped(&c.peer,2)); + xe.adlnOutputWiring.dsi=0;assert(!XE::dsiSharedWriterStopped(&c.fb,0)); + assert(!display13FBPower.memoryFaulted);IOLockUnlock(display13FBPower.lock);IORecursiveLockUnlock(&fbLock);} // Actual writer wrappers count nesting and release counts on native errors. {Case c;nestedCursor=true;auto i=imageCalls,s=stateCalls; assert(XE::wrapDSICursorImage(&c.ctl,&c.fb,&imageCalls)==0xe0000002U); From f38cd76fb8f50d24dadbda4a2f6d98e703c1ce67 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:02:09 +0900 Subject: [PATCH 040/139] Count native DSI flip and color transactions through completion --- WhateverXe/kern_xe.cpp | 64 +++++++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 8 ++++ tests/dsi_update_dispatch.py | 81 ++++++++++++++++++++++++++++++++---- 3 files changed, 143 insertions(+), 10 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index fac44487..ad79947f 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5462,7 +5462,8 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); - dsiNativeWriterHooksReady=!adlnOutputWiring.dsi || processDSINativeWriters(patcher,index,address,size); + dsiNativeWriterHooksReady=!adlnOutputWiring.dsi || (processDSINativeWriters(patcher,index,address,size) && + processDSINativeTransactions(patcher,index,address,size)); dsiBootProbeReady=processDisplay13DSIBoot(patcher,index,address,size); dsiCacheHooksReady=processDisplay13DSICache(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); @@ -6798,6 +6799,67 @@ uint32_t XE::wrapDSICursorState(IOService *controller,IOService *fb,int32_t x,in const auto result=FunctionCast(wrapDSICursorState,callbackXE->orgDSICursor[1])(controller,fb,x,y,visible,commit); endDSINativeWriter(token);return result; } +// Transaction bodies may wait and may call armUpdate, cursor or color hooks. +// Count the complete body without changing its native lock/wait intervals. +int XE::beginDSIFramebufferWriter(IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi)return 0; + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return -1; + return beginDSINativeWriter(Display13FBMemory::field(fb,0x1D0),fb); +} +uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t options,void *params) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIFlipTransaction,callbackXE->orgDSITransaction[0])(fb,transaction,options,params); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSIGammaTransaction(IOService *fb,void *transaction,void *params) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIGammaTransaction,callbackXE->orgDSITransaction[1])(fb,transaction,params); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSICscTransaction(IOService *fb,void *transaction,void *matrix,uint64_t options,bool final) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSICscTransaction,callbackXE->orgDSITransaction[2])(fb,transaction,matrix,options,final); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSIPlaneGammaTransaction(IOService *fb,void *transaction,bool post) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIPlaneGammaTransaction,callbackXE->orgDSITransaction[3])(fb,transaction,post); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSISetGammaTable(IOService *fb,uint32_t channels,uint32_t count,uint32_t bits,void *data,bool sync) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSISetGammaTable,callbackXE->orgDSITransaction[4])(fb,channels,count,bits,data,sync); + endDSINativeWriter(token);return result; +} +bool XE::processDSINativeTransactions(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer22PerformFlipTransactionEP30IOAccelDisplayPipeTransaction2yP21FlipTransactionParams", + "__ZN21AppleIntelFramebuffer23PerformGammaTransactionEP30IOAccelDisplayPipeTransaction2P20GammaTransactionArgs", + "__ZN21AppleIntelFramebuffer21PerformCscTransactionEP30IOAccelDisplayPipeTransaction2P29IOAccelDisplayPipeColorMatrixyb", + "__ZN21AppleIntelFramebuffer28PerformPlaneGammaTransactionEP30IOAccelDisplayPipeTransaction2b", + "__ZN21AppleIntelFramebuffer13setGammaTableEjjjPvb"}; + const uint32_t offsets[]={0x2B602,0x2C800,0x2D03C,0x2D838,0x36E72}; + // Whole prologues; no relocated RIP-relative counter in these 17 bytes. + const uint8_t entries[][17]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x48}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x68}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x58}}; + if(!display13FramebufferRequested || size<0x36E83)return false; + for(unsigned i=0;i<5;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],17))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSIFlipTransaction,orgDSITransaction[0]}, + {symbols[1],wrapDSIGammaTransaction,orgDSITransaction[1]}, + {symbols[2],wrapDSICscTransaction,orgDSITransaction[2]}, + {symbols[3],wrapDSIPlaneGammaTransaction,orgDSITransaction[3]}, + {symbols[4],wrapDSISetGammaTable,orgDSITransaction[4]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} + bool XE::processDSINativeWriters(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { const char *symbols[]={ "__ZN31AppleIntelFramebufferController16hwSetCursorImageEP21AppleIntelFramebufferP20IOHardwareCursorInfo", diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 94aa05c1..e21fe489 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -224,6 +224,14 @@ class XE { static bool dsiNativeUpdateAllowed(IOService *); static bool dsiSharedWriterStopped(IOService *,unsigned); bool processDSINativeWriters(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool processDSINativeTransactions(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgDSITransaction[5] {}; + static int beginDSIFramebufferWriter(IOService *); + static uint32_t wrapDSIFlipTransaction(IOService *,void *,uint64_t,void *); + static uint32_t wrapDSIGammaTransaction(IOService *,void *,void *); + static uint32_t wrapDSICscTransaction(IOService *,void *,void *,uint64_t,bool); + static uint32_t wrapDSIPlaneGammaTransaction(IOService *,void *,bool); + static uint32_t wrapDSISetGammaTable(IOService *,uint32_t,uint32_t,uint32_t,void *,bool); bool dsiNativeWriterHooksReady {}; mach_vm_address_t orgDSICursor[2] {}; static int beginDSINativeWriter(IOService *,IOService *); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 91b5b3a6..f79c2a29 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -4,18 +4,19 @@ import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] - for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState']) + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState','beginDSIFramebufferWriter','wrapDSIFlipTransaction','wrapDSIGammaTransaction','wrapDSICscTransaction','wrapDSIPlaneGammaTransaction','wrapDSISetGammaTable']) functions=functions.replace('void XE::endDSINativeWriter(int token) {', 'void XE::endDSINativeWriter(int token) {\n struct EndScope {EndScope(){endingWriter=true;}~EndScope(){endingWriter=false;}} endScope;') scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] if len(sys.argv)==2: binary=Path(sys.argv[1]).read_bytes() assert hashlib.sha256(binary).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' - installer=re.search(r'bool XE::processDSINativeWriters\(.*?\n\}',s,re.S)[0] - offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] - entries=re.search(r'entries\[\]\[20\]=\{(.*?)\};',installer,re.S)[1] - entries=[bytes(int(x,0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}',entries)] - assert len(offsets)==len(entries)==2 - for offset,entry in zip(offsets,entries):assert binary[offset:offset+20]==entry,hex(offset) + for name,width,count in [('processDSINativeWriters',20,2),('processDSINativeTransactions',17,5)]: + installer=re.search(r'bool XE::'+name+r'\(.*?\n\}',s,re.S)[0] + offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] + entries=re.search(r'entries\[\]\['+str(width)+r'\]=\{(.*?)\};',installer,re.S)[1] + entries=[bytes(int(x,0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}',entries)] + assert len(offsets)==len(entries)==count + for offset,entry in zip(offsets,entries):assert binary[offset:offset+width]==entry,hex(offset) code=r''' #include "kern_display13_fb_memory.hpp" #include "kern_display13_transcoder.hpp" @@ -42,10 +43,24 @@ unsigned imageCalls=0,stateCalls=0,expectedWriters=1;bool nestedCursor=false; uint32_t nativeImage(IOService*,IOService*,void*); uint32_t nativeState(IOService*,IOService*,int32_t,int32_t,bool,bool); +unsigned txCalls[5]{},txAnchor=0,paramAnchor=0;bool nestedTx=false; +constexpr uint64_t txOptions=0xFEDCBA9876543210ULL; +uint32_t nativeFlip(IOService*,void*,uint64_t,void*); +uint32_t nativeGamma(IOService*,void*,void*); +uint32_t nativeCsc(IOService*,void*,void*,uint64_t,bool); +uint32_t nativePlaneGamma(IOService*,void*,bool); +uint32_t nativeGammaTable(IOService*,uint32_t,uint32_t,uint32_t,void*,bool); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; + uintptr_t orgDSITransaction[5]{}; + static int beginDSIFramebufferWriter(IOService*); + static uint32_t wrapDSIFlipTransaction(IOService*,void*,uint64_t,void*); + static uint32_t wrapDSIGammaTransaction(IOService*,void*,void*); + static uint32_t wrapDSICscTransaction(IOService*,void*,void*,uint64_t,bool); + static uint32_t wrapDSIPlaneGammaTransaction(IOService*,void*,bool); + static uint32_t wrapDSISetGammaTable(IOService*,uint32_t,uint32_t,uint32_t,void*,bool); static int beginDSINativeWriter(IOService*,IOService*); static void endDSINativeWriter(int); static uint32_t wrapDSICursorImage(IOService*,IOService*,void*); @@ -70,10 +85,41 @@ --expectedWriters;assert(display13FBPower.dsiNativeWriters==expectedWriters);} return 0xe0000002U; } +uint32_t txResult(unsigned i){return i==3?0:0xe0000110U+i;} +uint32_t txLeaf(unsigned i,IOService *fb,void *tx){ + assert(fb==nativeFB && tx==&txAnchor && !owner && !fbLock.depth); + assert(display13FBPower.dsiNativeWriters==expectedWriters);++txCalls[i];return txResult(i); +} +uint32_t nativeFlip(IOService *fb,void *tx,uint64_t options,void *params){ + assert(options==txOptions && params==¶mAnchor);const auto result=txLeaf(0,fb,tx); + if(nestedTx){++expectedWriters;assert(XE::wrapDSICscTransaction(fb,tx,params,options,true)==txResult(2));--expectedWriters; + assert(display13FBPower.dsiNativeWriters==expectedWriters);} + return result; +} +uint32_t nativeGamma(IOService *fb,void *tx,void *params){assert(params==¶mAnchor);return txLeaf(1,fb,tx);} +uint32_t nativeCsc(IOService *fb,void *tx,void *matrix,uint64_t options,bool final){ + assert(matrix==¶mAnchor && options==txOptions && final);return txLeaf(2,fb,tx);} +uint32_t nativePlaneGamma(IOService *fb,void *tx,bool post){assert(!post);return txLeaf(3,fb,tx);} +uint32_t nativeGammaTable(IOService *fb,uint32_t channels,uint32_t count,uint32_t bits,void *data,bool sync){ + assert(channels==3 && count==1024 && bits==16 && data==¶mAnchor && sync);return txLeaf(4,fb,&txAnchor);} +uint32_t callTx(unsigned i,IOService *fb){ + switch(i){ + case 0:return XE::wrapDSIFlipTransaction(fb,&txAnchor,txOptions,¶mAnchor); + case 1:return XE::wrapDSIGammaTransaction(fb,&txAnchor,¶mAnchor); + case 2:return XE::wrapDSICscTransaction(fb,&txAnchor,¶mAnchor,txOptions,true); + case 3:return XE::wrapDSIPlaneGammaTransaction(fb,&txAnchor,false); + case 4:return XE::wrapDSISetGammaTable(fb,3,1024,16,¶mAnchor,true); + }assert(false);return 0; +} templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} struct Case { IOService fb,ctl,peer;OSObject path,port;IOLock lock; - Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=false;xe={}; + Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=nestedTx=false;xe={}; + xe.orgDSITransaction[0]=reinterpret_cast(nativeFlip); + xe.orgDSITransaction[1]=reinterpret_cast(nativeGamma); + xe.orgDSITransaction[2]=reinterpret_cast(nativeCsc); + xe.orgDSITransaction[3]=reinterpret_cast(nativePlaneGamma); + xe.orgDSITransaction[4]=reinterpret_cast(nativeGammaTable); xe.orgDSICursor[0]=reinterpret_cast(nativeImage);xe.orgDSICursor[1]=reinterpret_cast(nativeState);xe.orgDSIMode[5]=reinterpret_cast(nativeMode);display13FBPower={};auto &c=display13FBPower; c.lock=&lock;c.controller=&ctl;c.dsiPort=&port;c.pipe[0].memoryFramebuffer=&fb;c.transcoders.dsi0Pipe=0;panel=nativeFB=&fb; put(&fb,0x4220,&fbLock);put(&fb,0x1D0,&ctl);put(&fb,0x4A00,1U);put(&fb,0x4A08,&path); @@ -148,7 +194,24 @@ panel=&c.fb; // Identity predicate is irrelevant for disabled DSI. xe.adlnOutputWiring.dsi=0; assert(XE::wrapDSICursorImage(&c.ctl,&c.fb,&imageCalls)==0xe0000002U);} - puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor counts, native wait lock boundaries, peer and disabled-DSI forwarding"); + for(unsigned i=0;i<5;++i){ + {Case c;auto before=txCalls[i];assert(callTx(i,&c.fb)==txResult(i)); + assert(txCalls[i]==before+1 && !display13FBPower.dsiNativeWriters && !owner && !fbLock.depth);} + for(unsigned bad=0;bad<5;++bad){Case c;auto before=txCalls[i]; + if(bad==0)xe.dsiStopAttempted=true; + if(bad==1)xe.dsiResourcesReady=false; + if(bad==2)xe.dsiNativeWriterHooksReady=false; + if(bad==3)display13FBPower.memoryFaulted=true; + if(bad==4)put(&c.fb,0x1D0,&c.peer); + assert(callTx(i,&c.fb)==kIOReturnNotReady && txCalls[i]==before && !display13FBPower.dsiNativeWriters);} + {Case c;panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U);xe.dsiStopAttempted=true; + expectedWriters=0;assert(callTx(i,&c.fb)==txResult(i) && !display13FBPower.dsiNativeWriters);} + {Case c;xe.adlnOutputWiring.dsi=0;nativeFB=nullptr;expectedWriters=0; + assert(callTx(i,nullptr)==txResult(i) && !display13FBPower.dsiNativeWriters);} + } + {Case c;nestedTx=true;auto before=txCalls[2];assert(callTx(0,&c.fb)==txResult(0)); + assert(txCalls[2]==before+1 && !display13FBPower.dsiNativeWriters && !fbLock.depth && !owner);} + puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor/transaction counts, full-width arguments, native wait lock boundaries, peer and disabled-DSI forwarding"); } '''.replace('SCOPE',scope).replace('FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='dsi-update-') as d: From 1c4a9f684f92a185ae49208825a691068ebacd09 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:11:04 +0900 Subject: [PATCH 041/139] Gate DSI native attributes, power transitions and stream setup for P2 --- WhateverXe/kern_xe.cpp | 93 +++++++++++++++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 6 +++ tests/dsi_update_dispatch.py | 88 ++++++++++++++++++++++++++++++++-- 3 files changed, 181 insertions(+), 6 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ad79947f..3f704ff0 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5463,7 +5463,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); display13DSIModeHooksReady=processDisplay13DSIModes(patcher,index,address,size); dsiNativeWriterHooksReady=!adlnOutputWiring.dsi || (processDSINativeWriters(patcher,index,address,size) && - processDSINativeTransactions(patcher,index,address,size)); + processDSINativeTransactions(patcher,index,address,size) && processDSINativeAttributes(patcher,index,address,size)); dsiBootProbeReady=processDisplay13DSIBoot(patcher,index,address,size); dsiCacheHooksReady=processDisplay13DSICache(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); @@ -6831,6 +6831,97 @@ uint32_t XE::wrapDSISetGammaTable(IOService *fb,uint32_t channels,uint32_t count const auto result=FunctionCast(wrapDSISetGammaTable,callbackXE->orgDSITransaction[4])(fb,channels,count,bits,data,sync); endDSINativeWriter(token);return result; } +// P2 preserves the firmware link. Unknown/native panel and power setters do +// not inherit eDP behavior. The admitted CPU/color operations remain counted +// through their native bodies; P5 will replace this policy with DSI controls. +uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t value) { + int token=0; + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(attribute==0x706F7772) { // powr: native entry passes an unsigned state. + if(value>UINT32_MAX)return kIOReturnUnsupported; + return wrapDSISetPowerState(fb,static_cast(value)); + } + // wsrv and capd: retain native server/capture bookkeeping and the + // routed plane/transaction work. Other selectors need admission. + if(attribute!=0x77737276 && attribute!=0x63617064)return kIOReturnUnsupported; + token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + } + } + const auto result=FunctionCast(wrapDSISetAttribute,callbackXE->orgDSIAttributes[0])(fb,attribute,value); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint32_t attribute,uintptr_t value) { + int token=0; + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(connection!=0)return kIOReturnUnsupported; + // Controller depth and gamma scales/flush have qualified native + // CPU/color paths. In particular ignore, bklt, pnlt, psr?, powr, + // probe and DP-link configuration must never reach native DSI. + switch(attribute) { + case 0x00647064: // controller depth + case 0x72677363: case 0x67677363: case 0x62677363: // RGB gamma scale + case 0x67736320: case 0x666C7573: break; // gamma scale / flush + default:return kIOReturnUnsupported; + } + token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + } + } + const auto result=FunctionCast(wrapDSISetConnectionAttribute,callbackXE->orgDSIAttributes[1])(fb,connection,attribute,value); + endDSINativeWriter(token);return result; +} +uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // Match only the donor's already-Wake no-op. Do not publish a new + // native power state without executing a DSI power transaction. + if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; + return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; + } + } + return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); +} +uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path,const void *timing) { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,path))return kIOReturnNotReady; + } + return FunctionCast(wrapDSISetStream,callbackXE->orgDSIAttributes[3])(controller,fb,path,timing); +} +bool XE::processDSINativeAttributes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer12setAttributeEjm", + "__ZN21AppleIntelFramebuffer25setAttributeForConnectionEijm", + "__ZN21AppleIntelFramebuffer24setFramebufferPowerStateEj", + "__ZN31AppleIntelFramebufferController11hwSetStreamEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"}; + const uint32_t offsets[]={0x3331E,0x352D4,0x24DE8,0x50DB0}; + const uint8_t entries[][20]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x50,0x41,0x89,0xF6,0x49,0x89,0xFF,0x48,0xFF,0x05,0xF1}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0x68,0x01,0x00,0x00}}; + if(!display13FramebufferRequested || size<0x50DC4)return false; + for(unsigned i=0;i<4;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; + } + // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, + {symbols[1],wrapDSISetConnectionAttribute,orgDSIAttributes[1]}, + {symbols[2],wrapDSISetPowerState,orgDSIAttributes[2]}, + {symbols[3],wrapDSISetStream,orgDSIAttributes[3]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} + bool XE::processDSINativeTransactions(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { const char *symbols[]={ "__ZN21AppleIntelFramebuffer22PerformFlipTransactionEP30IOAccelDisplayPipeTransaction2yP21FlipTransactionParams", diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e21fe489..506e7b27 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -225,6 +225,12 @@ class XE { static bool dsiSharedWriterStopped(IOService *,unsigned); bool processDSINativeWriters(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool processDSINativeTransactions(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool processDSINativeAttributes(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgDSIAttributes[4] {}; + static uint32_t wrapDSISetStream(IOService *,IOService *,OSObject *,const void *); + static uint32_t wrapDSISetAttribute(IOService *,uint32_t,uintptr_t); + static uint32_t wrapDSISetConnectionAttribute(IOService *,int32_t,uint32_t,uintptr_t); + static uint32_t wrapDSISetPowerState(IOService *,uint32_t); mach_vm_address_t orgDSITransaction[5] {}; static int beginDSIFramebufferWriter(IOService *); static uint32_t wrapDSIFlipTransaction(IOService *,void *,uint64_t,void *); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index f79c2a29..25fe388f 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -4,13 +4,13 @@ import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] - for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState','beginDSIFramebufferWriter','wrapDSIFlipTransaction','wrapDSIGammaTransaction','wrapDSICscTransaction','wrapDSIPlaneGammaTransaction','wrapDSISetGammaTable']) + for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState','beginDSIFramebufferWriter','wrapDSIFlipTransaction','wrapDSIGammaTransaction','wrapDSICscTransaction','wrapDSIPlaneGammaTransaction','wrapDSISetGammaTable','wrapDSISetAttribute','wrapDSISetConnectionAttribute','wrapDSISetPowerState','wrapDSISetStream']) functions=functions.replace('void XE::endDSINativeWriter(int token) {', 'void XE::endDSINativeWriter(int token) {\n struct EndScope {EndScope(){endingWriter=true;}~EndScope(){endingWriter=false;}} endScope;') scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] if len(sys.argv)==2: binary=Path(sys.argv[1]).read_bytes() assert hashlib.sha256(binary).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' - for name,width,count in [('processDSINativeWriters',20,2),('processDSINativeTransactions',17,5)]: + for name,width,count in [('processDSINativeWriters',20,2),('processDSINativeTransactions',17,5),('processDSINativeAttributes',20,4)]: installer=re.search(r'bool XE::'+name+r'\(.*?\n\}',s,re.S)[0] offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] entries=re.search(r'entries\[\]\['+str(width)+r'\]=\{(.*?)\};',installer,re.S)[1] @@ -22,6 +22,7 @@ #include "kern_display13_transcoder.hpp" #include #include +#include struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; using IOService=OSObject;using IOLock=int; @@ -36,7 +37,7 @@ struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[3]; Display13Transcoder::Bindings transcoders;}display13FBPower; IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} -constexpr uint32_t kIOReturnNotReady=0xe00002d8U; +constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) uint32_t nativeMode(IOService*,int32_t m,int32_t d){assert(!fbLock.depth && !owner);assert(m==0x10000 && d==2);++modeCalls;return 7;} void adlnFault(bool &f,unsigned){f=true;} @@ -50,10 +51,22 @@ uint32_t nativeCsc(IOService*,void*,void*,uint64_t,bool); uint32_t nativePlaneGamma(IOService*,void*,bool); uint32_t nativeGammaTable(IOService*,uint32_t,uint32_t,uint32_t,void*,bool); +unsigned attrCalls[3]{},expectedAttribute=0,expectedPower=0;int32_t expectedConnection=0; +uintptr_t expectedValue=0;bool nestedAttribute=false; +uint32_t nativeAttribute(IOService*,uint32_t,uintptr_t); +uint32_t nativeConnectionAttribute(IOService*,int32_t,uint32_t,uintptr_t); +uint32_t nativePower(IOService*,uint32_t); +unsigned streamCalls=0;OSObject *expectedPath=nullptr; +uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; + uintptr_t orgDSIAttributes[4]{}; + static uint32_t wrapDSISetStream(IOService*,IOService*,OSObject*,const void*); + static uint32_t wrapDSISetAttribute(IOService*,uint32_t,uintptr_t); + static uint32_t wrapDSISetConnectionAttribute(IOService*,int32_t,uint32_t,uintptr_t); + static uint32_t wrapDSISetPowerState(IOService*,uint32_t); uintptr_t orgDSITransaction[5]{}; static int beginDSIFramebufferWriter(IOService*); static uint32_t wrapDSIFlipTransaction(IOService*,void*,uint64_t,void*); @@ -111,10 +124,32 @@ case 4:return XE::wrapDSISetGammaTable(fb,3,1024,16,¶mAnchor,true); }assert(false);return 0; } +uint32_t nativeAttribute(IOService *fb,uint32_t attribute,uintptr_t value){ + assert(fb==nativeFB && attribute==expectedAttribute && value==expectedValue && !owner && !fbLock.depth); + assert(display13FBPower.dsiNativeWriters==expectedWriters);++attrCalls[0]; + if(nestedAttribute){++expectedWriters;assert(callTx(0,fb)==txResult(0));--expectedWriters; + assert(display13FBPower.dsiNativeWriters==expectedWriters);} + return 0xe0000121U; +} +uint32_t nativeConnectionAttribute(IOService *fb,int32_t connection,uint32_t attribute,uintptr_t value){ + assert(fb==nativeFB && connection==expectedConnection && attribute==expectedAttribute && value==expectedValue && !owner && !fbLock.depth); + assert(display13FBPower.dsiNativeWriters==expectedWriters);++attrCalls[1];return 0xe0000122U; +} +uint32_t nativePower(IOService *fb,uint32_t state){ + assert(fb==nativeFB && state==expectedPower && !owner && !fbLock.depth);++attrCalls[2];return 0xe0000123U; +} +uint32_t nativeStream(IOService *ctl,IOService *fb,OSObject *path,const void *timing){ + assert(fb==nativeFB && ctl==display13FBPower.controller && path==expectedPath && timing==¶mAnchor && !fbLock.depth && !owner); + ++streamCalls;return 0xe0000124U; +} templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} struct Case { IOService fb,ctl,peer;OSObject path,port;IOLock lock; - Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=nestedTx=false;xe={}; + Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=nestedTx=nestedAttribute=false;xe={}; + xe.orgDSIAttributes[0]=reinterpret_cast(nativeAttribute); + xe.orgDSIAttributes[1]=reinterpret_cast(nativeConnectionAttribute); + xe.orgDSIAttributes[2]=reinterpret_cast(nativePower); + xe.orgDSIAttributes[3]=reinterpret_cast(nativeStream); xe.orgDSITransaction[0]=reinterpret_cast(nativeFlip); xe.orgDSITransaction[1]=reinterpret_cast(nativeGamma); xe.orgDSITransaction[2]=reinterpret_cast(nativeCsc); @@ -211,7 +246,50 @@ } {Case c;nestedTx=true;auto before=txCalls[2];assert(callTx(0,&c.fb)==txResult(0)); assert(txCalls[2]==before+1 && !display13FBPower.dsiNativeWriters && !fbLock.depth && !owner);} - puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor/transaction counts, full-width arguments, native wait lock boundaries, peer and disabled-DSI forwarding"); + // Only qualified P2 attributes reach native; lifetime accounting spans + // their nested flip/color work and preserves full pointer-sized values. + for(unsigned attr:{0x77737276U,0x63617064U}){Case c;expectedAttribute=attr;expectedValue=txOptions;nestedAttribute=true; + const auto before=attrCalls[0];assert(XE::wrapDSISetAttribute(&c.fb,attr,expectedValue)==0xe0000121U); + assert(attrCalls[0]==before+1 && !display13FBPower.dsiNativeWriters && !owner && !fbLock.depth);} + for(unsigned attr:{0x00647064U,0x72677363U,0x67677363U,0x62677363U,0x67736320U,0x666C7573U}){ + Case c;expectedAttribute=attr;expectedValue=txOptions;expectedConnection=0; + assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,expectedValue)==0xe0000122U && !display13FBPower.dsiNativeWriters); + assert(XE::wrapDSISetConnectionAttribute(&c.fb,1,attr,expectedValue)==kIOReturnUnsupported);} + for(unsigned attr:{0x706F7772U,0x70777273U,0x73707772U,0x64706F77U,0x706E6C70U,0x626B6C74U,0x706E6C74U, + 0x00696772U,0x7073723FU,0x70726F62U,0x64706972U,0xDEADBEEFU}){ + Case c;const auto before=attrCalls[0]+attrCalls[1]+attrCalls[2];const auto original=c.fb; + assert(XE::wrapDSISetAttribute(&c.fb,attr,0)==kIOReturnUnsupported); + assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,0)==kIOReturnUnsupported); + assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !memcmp(&c.fb,&original,sizeof(c.fb)) && !display13FBPower.memoryFaulted);} + for(unsigned old=0;old<3;++old)for(unsigned state=0;state<4;++state){Case c;put(&c.fb,0x4284,old);const auto before=attrCalls[2]; + const auto expected=old==2 && state==2?kIOReturnSuccess:kIOReturnUnsupported; + assert(XE::wrapDSISetPowerState(&c.fb,state)==expected && attrCalls[2]==before); + assert(Display13FBMemory::field(&c.fb,0x4284)==old); + assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,state)==expected);} + {Case c;put(&c.fb,0x4284,2U);assert(XE::wrapDSISetAttribute(&c.fb,0x706F7772,0x100000002ULL)==kIOReturnUnsupported);} + for(unsigned bad=0;bad<4;++bad){Case c;put(&c.fb,0x4284,2U);const auto before=attrCalls[0]+attrCalls[1]+attrCalls[2]; + if(bad==0)xe.dsiStopAttempted=true;if(bad==1)xe.dsiResourcesReady=false; + if(bad==2)xe.dsiNativeWriterHooksReady=false;if(bad==3)display13FBPower.memoryFaulted=true; + assert(XE::wrapDSISetAttribute(&c.fb,0x77737276,1)==kIOReturnNotReady); + assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x72677363,1)==kIOReturnNotReady); + assert(XE::wrapDSISetPowerState(&c.fb,2)==kIOReturnNotReady); + assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !display13FBPower.dsiNativeWriters);} + for(bool disabled:{false,true}){Case c;expectedWriters=0;expectedValue=txOptions;expectedAttribute=0x706F7772;expectedConnection=4;expectedPower=0; + IOService *fb=&c.fb; + if(disabled){xe.adlnOutputWiring.dsi=0;fb=nativeFB=nullptr;} + else{panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U);xe.dsiStopAttempted=true;} + assert(XE::wrapDSISetAttribute(fb,expectedAttribute,expectedValue)==0xe0000121U); + assert(XE::wrapDSISetConnectionAttribute(fb,expectedConnection,expectedAttribute,expectedValue)==0xe0000122U); + assert(XE::wrapDSISetPowerState(fb,expectedPower)==0xe0000123U);} + {Case c;auto before=streamCalls;assert(XE::wrapDSISetStream(&c.ctl,&c.fb,&c.path,¶mAnchor)==kIOReturnNotReady); + assert(streamCalls==before && !fbLock.depth && !owner);} + {Case c;panel=nullptr;put(&c.path,0x3640,1U);put(&c.port,0x120,3U);expectedPath=&c.path; + assert(XE::wrapDSISetStream(&c.ctl,&c.fb,&c.path,¶mAnchor)==0xe0000124U); + const auto before=streamCalls;assert(XE::wrapDSISetStream(&c.ctl,&c.fb,&c.port,¶mAnchor)==kIOReturnNotReady); + assert(streamCalls==before);} + {Case c;xe.adlnOutputWiring.dsi=0;nativeFB=nullptr;expectedPath=nullptr; + assert(XE::wrapDSISetStream(&c.ctl,nullptr,nullptr,¶mAnchor)==0xe0000124U);} + puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor/transaction counts, P2 attribute/power admission, full-width arguments, native wait lock boundaries, peer and disabled-DSI forwarding"); } '''.replace('SCOPE',scope).replace('FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='dsi-update-') as d: From 07091215e091bffbd844e5ffe4de16e984ea9bac Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:22:19 +0900 Subject: [PATCH 042/139] Validate pending DSI scanout backing without replacing GOP ownership --- WhateverXe/kern_display13_fb_memory.hpp | 40 +++++++++++-- WhateverXe/kern_dsi_native_backing.hpp | 37 ++++++++++++ tests/dsi_backing_native.py | 79 +++++++++++++++++++++++++ tests/dsi_native_backing.cpp | 64 ++++++++++++++++++++ tools/test.py | 1 + 5 files changed, 217 insertions(+), 4 deletions(-) create mode 100644 WhateverXe/kern_dsi_native_backing.hpp create mode 100644 tests/dsi_backing_native.py create mode 100644 tests/dsi_native_backing.cpp diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index 4f0dee98..01def8ee 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -281,13 +281,18 @@ struct SurfaceReadout { uint64_t record {},map {},memory {},gpu {},bytes {},pitch {},refs {},kind {},flags {}; uint64_t verifiedPages {},firstBadGPU {},expectedPhysical {},actualPTE {},reserve {}; }; -template SurfaceReadout inspectSurface(IO &io,const void *controller,size_t controllerBytes, - const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit,const XELPMemory::Layout &memory) { +// Caller pins the record and native memory/map through the PTE walk. A pending +// record need not be published into FB+4348; its existing pointer is checked +// separately so publication during the inspection is detected. +template SurfaceReadout inspectSurfaceRecord(IO &io,const void *controller,size_t controllerBytes, + const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit, + const XELPMemory::Layout &memory,const void *surface,const void *attached) { SurfaceReadout r;r.reserve=reserve; if(!owner || !associations(controller,controllerBytes,fb,fbBytes,owner,true)) { r.status=SurfaceStatus::Association;return r; } - const auto surface=field(fb,0x4348);if(!surface)return r; + if(field(fb,0x4348)!=attached) {r.status=SurfaceStatus::Association;return r;} + if(!surface)return r; uint8_t snapshot[64];memcpy(snapshot,surface,sizeof(snapshot)); r.record=reinterpret_cast(surface);r.map=field(snapshot,0x10); r.memory=field(snapshot,0x18);r.gpu=field(snapshot,0x20); @@ -321,11 +326,38 @@ template SurfaceReadout inspectSurface(IO &io,const void *controller,s ++r.verifiedPages; } if(!associations(controller,controllerBytes,fb,fbBytes,owner,true) || - field(fb,0x4348)!=surface || memcmp(snapshot,surface,sizeof(snapshot)) || !io.memoryReady(r.map,r.memory,owner,r.bytes)) { + field(fb,0x4348)!=attached || memcmp(snapshot,surface,sizeof(snapshot)) || !io.memoryReady(r.map,r.memory,owner,r.bytes)) { r.status=SurfaceStatus::StateChanged;return r; } r.status=SurfaceStatus::Complete;return r; } +template SurfaceReadout inspectSurface(IO &io,const void *controller,size_t controllerBytes, + const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit,const XELPMemory::Layout &memory) { + const auto surface=fb && fbBytes>=0x4B90 ? field(fb,0x4348) : nullptr; + return inspectSurfaceRecord(io,controller,controllerBytes,fb,fbBytes,owner,reserve,gpuLimit,memory,surface,surface); +} +// First DSI handoff: the real FB still owns stolen slot zero while the GPU +// allocator owns a pending record beyond the reserved prefix. This proves +// backing, not display-pipe ownership, serialization, or permission to arm. +template SurfaceReadout inspectPendingSurface(IO &io,const void *controller,size_t controllerBytes, + const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit, + const XELPMemory::Layout &memory,const void *pending) { + SurfaceReadout failure;failure.status=SurfaceStatus::Association; + if(!pending || !owner || !associations(controller,controllerBytes,fb,fbBytes,owner) || + allocatedSlot(controller,fb)!=0)return failure; + const auto before=pool(controller); + const auto mask=field(controller,0xDF0); + const auto used=field(controller,0xDF4),count=field(controller,0xDF5); + if(before.state!=1 || before.descriptor || before.base || !before.bytes || (before.bytes&4095) || + before.firstPage || before.pages!=before.bytes/4096 || !memory.valid || + !memory.range(before.base,before.bytes,true) || before.physical!=memory.base+before.base || + uint64_t(before.base)+before.bytes>reserve)return failure; + auto r=inspectSurfaceRecord(io,controller,controllerBytes,fb,fbBytes,owner,reserve,gpuLimit,memory,pending,nullptr); + if(r.status==SurfaceStatus::Complete && (allocatedSlot(controller,fb)!=0 || + field(controller,0xDF0)!=mask || field(controller,0xDF4)!=used || + field(controller,0xDF5)!=count || !same(before,pool(controller))))r.status=SurfaceStatus::StateChanged; + return r; +} template Readout inspect(IO &io,const void *controller,size_t controllerBytes, const void *framebuffer,size_t framebufferBytes, uint64_t apertureBytes,const XELPMemory::Layout &memory,bool bootScanout=true,uintptr_t expectedOwner=0, diff --git a/WhateverXe/kern_dsi_native_backing.hpp b/WhateverXe/kern_dsi_native_backing.hpp new file mode 100644 index 00000000..8d6dc612 --- /dev/null +++ b/WhateverXe/kern_dsi_native_backing.hpp @@ -0,0 +1,37 @@ +// Exact TGL IntelScaledModeData wire record for a pending DSI scanout. +// Allocation/free use the native allocscanmem/freescanmem platform functions. +// The adapter must retain the record and serialize the native display pipe; +// allocating also changes its memory/map references, before FB publication. +#ifndef KERN_DSI_NATIVE_BACKING_HPP +#define KERN_DSI_NATIVE_BACKING_HPP +#include "kern_dsi_profile.hpp" +#include "kern_display13_fb_memory.hpp" +#include +namespace XELP { namespace DSI { namespace NativeBacking { +struct Record { + uint32_t reserved {},columns {},rows {},flags {}; + uint64_t map {},memory {},gpu {}; + uint32_t bytes {},pitch {},reserved30 {},reserved34 {},refs {},kind {}; +}; +static_assert(sizeof(Record)==64 && offsetof(Record,map)==0x10 && offsetof(Record,refs)==0x38,"native scanout ABI"); +// Native tile mode 3 gives a 128-byte pitch unit and 32-row tiles. This is +// uncompressed Y, the format admitted by the shared Display13 WM backend. +// Flags zero avoids the optional fence allocation/free path. +inline bool request(const Profile &p,uint32_t nativeTileMode,Record &out) { + if(!p.valid() || !p.mode.valid() || p.mode.width>8192 || p.mode.height>8192 || nativeTileMode!=3)return false; + Record r;r.columns=(uint32_t(p.mode.width)*4+127)/128;r.rows=(uint32_t(p.mode.height)+31)/32;r.kind=1; + // Leave bytes zero as the native FB does: allocator computes tiles*4096. + out=r;return true; +} +inline bool allocated(const Record &r,const Profile &p,const Display13FBMemory::SurfaceReadout &proof) { + Record expected;if(!request(p,3,expected))return false; + return r.columns==expected.columns && r.rows==expected.rows && !r.flags && r.kind==1 && r.refs==1 && + !r.reserved && !r.reserved30 && !r.reserved34 && !r.bytes && + proof.status==Display13FBMemory::SurfaceStatus::Complete && + proof.record==reinterpret_cast(&r) && proof.refs==r.refs && proof.kind==r.kind && proof.flags==r.flags && + proof.map==r.map && proof.memory==r.memory && + proof.gpu==r.gpu && proof.bytes==uint64_t(r.columns)*r.rows*4096 && + proof.pitch==r.pitch && r.pitch==r.columns*128 && proof.verifiedPages==uint64_t(r.columns)*r.rows; +} +}}} +#endif diff --git a/tests/dsi_backing_native.py b/tests/dsi_backing_native.py new file mode 100644 index 00000000..b923287d --- /dev/null +++ b/tests/dsi_backing_native.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Replay native pending scanout allocation/free; allocator leaves are mocked. + +Requires the exact accelerator; stores no Apple bytes. This establishes record +and display-pipe reference behavior, not GPU allocation or live PTE validity. +""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile +root=Path(__file__).resolve().parents[1] +donor=Path(sys.argv[1]).resolve() +assert hashlib.sha256(donor.read_bytes()).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' +code=r''' +#include "kern_dsi_native_backing.hpp" +#include +#include +#include +#include +#include +#include +using namespace XELP::DSI; +templatevoid put(void *p,unsigned off,T v){memcpy(static_cast(p)+off,&v,sizeof(v));} +templateT get(const void *p,unsigned off){return Display13FBMemory::field(p,off);} +alignas(8) uint8_t owner[0x1500]{},pipe[0x1300]{},memory[0x198]{},mapping[0x128]{}; +uintptr_t memVT[64]{},mapVT[64]{}; +unsigned allocations=0,releases=0,unmaps=0;bool allocationFails=false,mapFails=false; +void *allocate(void *o,void *,void *shared,void *resource,uint32_t options,uint64_t bytes){ + assert(o==owner && !shared && !resource && options==0x23 && bytes==8355840);++allocations; + return allocationFails?nullptr:memory; +} +void *createMap(void *m,void *addressSpace,uint32_t flags){ + assert(m==memory && addressSpace==reinterpret_cast(0x1234) && flags==0x800000B); + return mapFails?nullptr:mapping; +} +uint64_t address(void *p){assert(p==mapping);return 0x10000000;} +bool ready(void *p){assert(p==mapping);return true;} +void release(void *p){assert(p==mapping || p==memory);++releases;} +void unmap(void *p){assert(p==mapping);++unmaps;} +int main(int argc,char **argv){ + assert(argc==2);std::ifstream file(argv[1],std::ios::binary); + std::vector bytes((std::istreambuf_iterator(file)),{}); + constexpr size_t size=0x2000000;assert(bytes.size()(mmap(nullptr,size,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0)); + assert(image!=MAP_FAILED);memcpy(image,bytes.data(),bytes.size()); + // Replace the imported IOAccelSysMemory::withOptions call with a bounded + // allocation leaf; execute native allocation/map/publication/free bodies. + constexpr unsigned stub=0x1000000,call=0x7F674; + assert(image[call]==0xE8);image[stub]=0x48;image[stub+1]=0xB8; + put(image,stub+2,reinterpret_cast(allocate));image[stub+10]=0xFF;image[stub+11]=0xE0; + put(image,call+1,int32_t(stub-call-5)); + uint64_t pageSize=4096,kernelTask=0; + put(image,0xC8238,reinterpret_cast(&pageSize));put(image,0xC8228,reinterpret_cast(&kernelTask)); + put(pipe,0x88,owner);put(owner,0x1470,3U);put(owner,0x150,uintptr_t(0x1234)); + put(memory,0,memVT);put(mapping,0,mapVT);mapping[0x10]=1; + memVT[0x138/8]=reinterpret_cast(createMap);memVT[0x28/8]=reinterpret_cast(release); + mapVT[0x128/8]=reinterpret_cast(address);mapVT[0x138/8]=reinterpret_cast(ready); + mapVT[0x140/8]=reinterpret_cast(unmap);mapVT[0x28/8]=reinterpret_cast(release); + Profile p;p.status=ProfileStatus::Valid;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + auto alloc=reinterpret_cast(image+0x7E5B2); + auto free=reinterpret_cast(image+0x7E772); + NativeBacking::Record record;assert(NativeBacking::request(p,3,record)); + assert(!alloc(pipe,&record,0)); + assert(record.map==reinterpret_cast(mapping) && record.memory==reinterpret_cast(memory)); + assert(record.gpu==0x10000000 && record.pitch==4352 && record.refs==1 && !record.bytes); + assert(get(pipe,0x1298)==memory && get(pipe,0x12A0)==mapping && !get(pipe,0x12A8)); + assert(!free(pipe,&record,0) && releases==2 && unmaps==1 && !record.refs && !record.map && !record.memory && !record.gpu && !record.pitch); + assert(!get(pipe,0x1298) && !get(pipe,0x12A0)); + assert(!free(pipe,&record,0) && releases==2 && unmaps==1); + allocationFails=true;assert(NativeBacking::request(p,3,record));assert(alloc(pipe,&record,0)==0xE00002BC); + assert(!record.refs && !record.map && !get(pipe,0x1298)); + allocationFails=false;mapFails=true;assert(alloc(pipe,&record,0)==0xE00002BC && releases==3); + assert(!record.refs && !record.map && !record.memory && !get(pipe,0x1298)); + assert(allocations==3);assert(!munmap(image,size)); + puts("PASS exact native DSI Y allocation/free: request geometry, pipe references, failed allocation/map cleanup"); +} +''' +with tempfile.TemporaryDirectory(prefix='dsi-backing-native-') as directory: + d=Path(directory);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-I'+str(root/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test'),str(donor)],check=True) diff --git a/tests/dsi_native_backing.cpp b/tests/dsi_native_backing.cpp new file mode 100644 index 00000000..adbf5a5c --- /dev/null +++ b/tests/dsi_native_backing.cpp @@ -0,0 +1,64 @@ +#include "kern_dsi_native_backing.hpp" +#include +#include +#include +using namespace XELP::DSI; +using namespace Display13FBMemory; +template void put(void *p,unsigned off,T v){memcpy(static_cast(p)+off,&v,sizeof(v));} +struct Fixture { + alignas(8) uint8_t controller[0x1C20]{},fb[0x4B90]{}; + Profile profile;NativeBacking::Record record; + XELPMemory::Layout layout=XELPMemory::decode(0x03C1,0x7A800001,0); + uintptr_t owner=0x1000;uint64_t reserve=0x06192000; + unsigned drift=0,reads=0;bool badPage=false; + Fixture(){ + profile.status=ProfileStatus::Valid;profile.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + assert(NativeBacking::request(profile,3,record)); + record.map=0x2000;record.memory=0x3000;record.gpu=0x10000000;record.pitch=4352;record.refs=1; + put(controller,0xC0,owner);put(controller,0xD5C,1U);put(controller,0xD60,fb); + put(fb,0x1D0,controller);put(controller,0xDF0,1U);put(controller,0xDF4,uint8_t(1));put(controller,0xDF5,uint8_t(3)); + put(controller,0x1A10,1U);put(controller,0x1A28,layout.base); + put(controller,0x1A30,0x800000U);put(controller,0x1A34,0x800U); + } + bool memoryReady(uintptr_t map,uintptr_t memory,uintptr_t o,uint64_t n){ + return map==record.map && memory==record.memory && o==owner && n==8355840; + } + uint64_t physical(uintptr_t,uint64_t off,uint64_t &span){span=4096;return 0x180000000ULL+off;} + uint64_t readPTE(uint32_t reg){ + const uint64_t off=(uint64_t(reg)-0x800000)*512-record.gpu; + assert(off<8355840);++reads; + if(reads==2)switch(drift){ + case 1:put(fb,0x4348,&record);break; + case 2:put(controller,0xDF0,3U);put(controller,0xDF4,uint8_t(2));break; + case 3:put(controller,0x1A30,0x400000U);break; + case 4:record.pitch+=128;break; + case 5:put(controller,0xC0,uintptr_t(0));break; + case 6:put(fb,0x4288,uint8_t(0xFF));break; + } + return (0x180000000ULL+off+(badPage && reads==3?4096:0))|0x99; + } + SurfaceReadout inspect(){return inspectPendingSurface(*this,controller,sizeof(controller),fb,sizeof(fb),owner,reserve,0x100000000ULL,layout,&record);} +}; +int main(){ + Fixture f;const auto proof=f.inspect();assert(proof.status==SurfaceStatus::Complete); + assert(NativeBacking::allocated(f.record,f.profile,proof) && f.reads==2040); + assert(!field(f.fb,0x4348) && allocatedSlot(f.controller,f.fb)==0); + assert(inspectSurface(f,f.controller,sizeof(f.controller),f.fb,sizeof(f.fb),f.owner,f.reserve,0x100000000ULL,f.layout).status==SurfaceStatus::Unavailable); + for(unsigned drift=1;drift<=6;++drift){Fixture changed;changed.drift=drift;assert(changed.inspect().status==SurfaceStatus::StateChanged);} + for(unsigned fault=0;fault<6;++fault){Fixture bad; + switch(fault){case 0:put(bad.fb,0x4348,&bad.record);break;case 1:put(bad.fb,0x4288,uint8_t(1));break; + case 2:put(bad.controller,0x1A28,uint64_t(0));break;case 3:bad.record.gpu=0;break; + case 4:bad.record.gpu=0x100000000ULL-4096;break;case 5:bad.badPage=true;break;} + assert(bad.inspect().status!=SurfaceStatus::Complete); + } + // A validated record is transferred only in exactly the requested shape. + for(unsigned fault=0;fault<5;++fault){auto r=f.record; + switch(fault){case 0:r.refs=2;break;case 1:r.flags=4;break;case 2:r.kind=2;break;case 3:r.bytes=8355840;break;case 4:r.rows=61;break;} + auto p=proof;p.record=reinterpret_cast(&r);assert(!NativeBacking::allocated(r,f.profile,p)); + } + NativeBacking::Record request; + assert(NativeBacking::request(f.profile,3,request) && request.columns==34 && request.rows==60 && !request.bytes); + const auto previous=request;assert(!NativeBacking::request(f.profile,2,request) && !memcmp(&request,&previous,sizeof(request))); + f.profile.mode.height=0;assert(!NativeBacking::request(f.profile,3,request)); + puts("PASS pending native DSI backing: GOP slot preserved, exact Y request, PTE proof and publication/ownership drift"); +} diff --git a/tools/test.py b/tools/test.py index 22e60b87..e0d74b7e 100644 --- a/tools/test.py +++ b/tools/test.py @@ -21,6 +21,7 @@ if a.donor:jobs += [('dsi_apple_timing_native',[sys.executable,str(R/'tests/dsi_apple_timing_native.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', '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 += [('dsi_backing_native',[sys.executable,str(R/'tests/dsi_backing_native.py'),str(a.accelerator.resolve())],None)] 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 From d68b5ab3a207fed79ad244ee8e44cc682df4957a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:39:55 +0900 Subject: [PATCH 043/139] Connect pending DSI backing to native GPU allocation and discard --- WhateverXe/kern_xe.cpp | 241 +++++++++++++++++++++---- WhateverXe/kern_xe.hpp | 8 + tests/display13_dbuf_setup_fixture.cpp | 8 +- tests/dsi_backing_adapter.py | 149 +++++++++++++++ tests/dsi_quiesce_adapter.py | 2 + tests/dsi_update_dispatch.py | 4 +- tools/test.py | 3 +- 7 files changed, 377 insertions(+), 38 deletions(-) create mode 100644 tests/dsi_backing_adapter.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3f704ff0..a767e306 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -8,6 +8,7 @@ // #include "kern_dsi_native_cache.hpp" +#include "kern_dsi_native_backing.hpp" #include "kern_xe.hpp" #include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" @@ -2976,7 +2977,7 @@ void tglHostPublish() { // No added forcewake, sleeps, allocation, or registry work in the write bridge. void tglGuCDMABoundary(uint32_t stage, volatile uint8_t *base) { auto &o=tglGuCObservation; - if (stage>1 || o.calls!=1 || o.thread!=reinterpret_cast(current_thread()) || + if (stage>1 || o.calls!=1 || __atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || !o.accelerator || !base || o.barSize<0x1000000 || o.boundary[stage][0]) return; auto a=static_cast(o.accelerator); if (base!=*reinterpret_cast(a+0x1240)) return; @@ -2995,7 +2996,7 @@ void tglGuCDMABoundary(uint32_t stage, volatile uint8_t *base) { // r15,r14,r13,r12,r11,r10,r9,r8,rdi,rsi,rdx,rcx,rax,RFLAGS (14 QWords). void tglGuCDMAPreStart(volatile uint8_t *base, const uint64_t *saved) { auto &o=tglGuCObservation; - if (o.calls!=1 || o.thread!=reinterpret_cast(current_thread()) || + if (o.calls!=1 || __atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || !o.accelerator || !base || !saved || o.barSize<0x1000000 || o.preStart[0]) return; if (base!=*reinterpret_cast(static_cast(o.accelerator)+0x1240)) return; auto r=o.preStart; uint64_t ticks; @@ -4600,6 +4601,24 @@ struct Display13FBPowerContext { uint64_t powerContext[24] {1}; // Bounded CPU-only execution-context evidence. }; static Display13FBPowerContext display13FBPower; +// The FB mutex serializes prepare/discard/publication. Native allocation also +// publishes GPU-pipe references before this record becomes the FB scanout. +// Keep both owners retained, including on partial failure, until proven free. +struct DSIPendingBackingOwner { + IOService *framebuffer {},*accelerator {}; + OSObject *pipe {},*pinnedMap {},*pinnedMemory {}; + XELP::DSI::NativeBacking::Record *record {}; + uintptr_t thread {}; + bool allocating {},freeing {},allocated {},freed {},armed {}; + uint32_t nativeResult {}; + Display13FBMemory::SurfaceReadout proof; +}; +static DSIPendingBackingOwner dsiPendingBacking; +static bool dsiPendingTarget(IOService *fb) { + return fb && __atomic_load_n(&dsiPendingBacking.framebuffer,__ATOMIC_ACQUIRE)==fb; +} + + // One DSI0 panel. Publish only after native path/pipe association; the FB and // immutable timing array remain retained until a quiesced DSI stop can detach // them. A donor property failure must never leave its raw array pointer dangling. @@ -5153,6 +5172,34 @@ static void adlnPublishMemory(const char *name,unsigned pipe,void *data,unsigned if(!first || !root->getProperty(key))root->setProperty(key,data,bytes); } +struct Display13SurfaceIO { + IOMemoryMap *map;unsigned pipe;IOMemoryDescriptor *descriptor {};uint32_t options {}; + bool memoryReady(uintptr_t mapping,uintptr_t memory,uintptr_t owner,uint64_t bytes) { + auto m=reinterpret_cast(mapping),s=reinterpret_cast(memory); + uint64_t q[16] {1,0,m->getMetaClass()->getClassSize(),s->getMetaClass()->getClassSize()}; + auto publish=[&](uint64_t reason) {q[1]=reason;adlnPublishMemory("N97FBScanoutMemoryOwner",pipe,q,sizeof(q));return !reason;}; + if(!m->metaCast("IGAccelMemoryMap") || q[2]!=0x128 || + !s->metaCast("IGAccelSysMemory") || q[3]!=0x198)return publish(1); + q[4]=Display13FBMemory::field(m,0x88);q[5]=Display13FBMemory::field(s,0x28); + q[6]=Display13FBMemory::field(m,0x18);q[7]=Display13FBMemory::field(m,0x90); + q[8]=Display13FBMemory::field(reinterpret_cast(owner),0x150);q[9]=Display13FBMemory::field(s,0x40); + if(q[4]!=owner || q[5]!=owner || q[6]!=memory || q[7]!=q[8] || q[9](s,0xD0)); + auto manager=Display13FBMemory::field(reinterpret_cast(owner),0x1260); + q[10]=descriptor ? descriptor->getLength() : 0;q[11]=descriptor!=nullptr; + q[12]=manager ? manager->getMetaClass()->getClassSize() : 0;q[14]=reinterpret_cast(descriptor); + if(!descriptor || q[10]metaCast("IGMemoryManager") || q[12]<0x8C)return publish(3); + // Match native getPhysicalSegment's mapper policy. + options=Display13FBMemory::field(manager,0x88);q[13]=options;return publish(0); + } + + uint64_t physical(uintptr_t,uint64_t off,uint64_t &span) { + IOByteCount length=0;const auto p=descriptor->getPhysicalSegment(off,&length,options);span=length;return p; + } + uint64_t readPTE(uint32_t reg) {return *reinterpret_cast(map->getVirtualAddress()+reg);} +}; + // Caller holds the display-owner lock. Mapping is owned by PowerInit, not a // register-access callback with an unknown lifetime or an on-read serializer. static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bootScanout=true,unsigned pipe=0,bool publish=true) { @@ -5172,33 +5219,7 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo if(Display13FBMemory::field(fb,0x4288)!=0xFF || !adlnSharedBinding(controller,reinterpret_cast(owner)) || !Display13FBMemory::identityAvailable(c.pipe,fb,pipe))return r; - struct SurfaceIO { - IOMemoryMap *map;unsigned pipe;IOMemoryDescriptor *descriptor {};uint32_t options {}; - bool memoryReady(uintptr_t mapping,uintptr_t memory,uintptr_t owner,uint64_t bytes) { - auto m=reinterpret_cast(mapping),s=reinterpret_cast(memory); - uint64_t q[16] {1,0,m->getMetaClass()->getClassSize(),s->getMetaClass()->getClassSize()}; - auto publish=[&](uint64_t reason) {q[1]=reason;adlnPublishMemory("N97FBScanoutMemoryOwner",pipe,q,sizeof(q));return !reason;}; - if(!m->metaCast("IGAccelMemoryMap") || q[2]!=0x128 || - !s->metaCast("IGAccelSysMemory") || q[3]!=0x198)return publish(1); - q[4]=Display13FBMemory::field(m,0x88);q[5]=Display13FBMemory::field(s,0x28); - q[6]=Display13FBMemory::field(m,0x18);q[7]=Display13FBMemory::field(m,0x90); - q[8]=Display13FBMemory::field(reinterpret_cast(owner),0x150);q[9]=Display13FBMemory::field(s,0x40); - if(q[4]!=owner || q[5]!=owner || q[6]!=memory || q[7]!=q[8] || q[9](s,0xD0)); - auto manager=Display13FBMemory::field(reinterpret_cast(owner),0x1260); - q[10]=descriptor ? descriptor->getLength() : 0;q[11]=descriptor!=nullptr; - q[12]=manager ? manager->getMetaClass()->getClassSize() : 0;q[14]=reinterpret_cast(descriptor); - if(!descriptor || q[10]metaCast("IGMemoryManager") || q[12]<0x8C)return publish(3); - // Match native getPhysicalSegment's mapper policy. - options=Display13FBMemory::field(manager,0x88);q[13]=options;return publish(0); - } - - uint64_t physical(uintptr_t,uint64_t off,uint64_t &span) { - IOByteCount length=0;const auto p=descriptor->getPhysicalSegment(off,&length,options);span=length;return p; - } - uint64_t readPTE(uint32_t reg) {return *reinterpret_cast(map->getVirtualAddress()+reg);} - } io {c.map,pipe}; + Display13SurfaceIO io {c.map,pipe}; const auto reserve=owner ? Display13FBMemory::field(reinterpret_cast(owner),0x13C0) : 0; c.pipe[pipe].scanout=Display13FBMemory::inspectSurface(io,controller,controller->getMetaClass()->getClassSize(), fb,fb->getMetaClass()->getClassSize(),owner,reserve,(c.map->getLength()-0x800000)/8*4096,xelpMemory); @@ -5251,6 +5272,154 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo } } +bool XE::processDSIBackingHooks(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={"__ZN18IGAccelDisplayPipe21allocateScanoutMemoryEP19IntelScaledModeDataj", + "__ZN18IGAccelDisplayPipe23deallocateScanoutMemoryEP19IntelScaledModeDataj"}; + const uintptr_t offsets[]={0x7E5B2,0x7E772}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x8B,0x46,0x38,0x85,0xC0,0x74,0x62,0x49,0x89}}; + bool known=size>0x7F80A; + for(unsigned i=0;i<2;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),entries[i],16); + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSIBackingAllocate,orgDSIBacking[0]}, + {symbols[1],wrapDSIBackingFree,orgDSIBacking[1]}}; + // Short routes copy only the six-byte push/mov/push prologue. The free + // entry has a relative branch at byte 12; never copy it into a trampoline. + const bool ready=known && patcher.routeMultipleShort(index,routes,2,address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingHooksReady",ready);return ready; +} +uint32_t XE::wrapDSIBackingAllocate(OSObject *pipe,void *record,uint32_t flags) { + auto &o=dsiPendingBacking; + if(!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)) + return FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); + if(__atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || record!=o.record) { + if(pipe && pipe==__atomic_load_n(&o.pipe,__ATOMIC_ACQUIRE))return kIOReturnNotReady; + return FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); + } + if(!o.allocating || flags || o.pipe || !pipe || !pipe->metaCast("IGAccelDisplayPipe") || + pipe->getMetaClass()->getClassSize()<0x12B0 || Display13FBMemory::field(pipe,0x88)!=o.accelerator || + Display13FBMemory::field(pipe,0x1298) || Display13FBMemory::field(pipe,0x12A0) || + Display13FBMemory::field(pipe,0x12A8))return kIOReturnNotReady; + pipe->retain();__atomic_store_n(&o.pipe,pipe,__ATOMIC_RELEASE); + const auto result=FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); + o.nativeResult=result;const auto &r=*o.record; + o.allocated=!result && r.refs==1 && r.memory && r.map && + Display13FBMemory::field(pipe,0x1298)==r.memory && + Display13FBMemory::field(pipe,0x12A0)==r.map && !Display13FBMemory::field(pipe,0x12A8); + if(o.allocated) { + // Native init/destroyScanoutResource can replace these pipe fields. + // Pin the objects while still inside the native GPU lock, before + // returning to the PTE inspector. Pins alone do not preserve GGTT. + o.pinnedMap=reinterpret_cast(r.map);o.pinnedMemory=reinterpret_cast(r.memory); + o.pinnedMap->retain();o.pinnedMemory->retain(); + } + return o.allocated ? result : kIOReturnNotReady; +} +uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { + auto &o=dsiPendingBacking; + if(!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)) + return FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); + if(__atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || record!=o.record) { + if(pipe && pipe==__atomic_load_n(&o.pipe,__ATOMIC_ACQUIRE))return kIOReturnNotReady; + return FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); + } + if(!o.freeing || flags || o.armed || !pipe || pipe!=o.pipe || + Display13FBMemory::field(pipe,0x88)!=o.accelerator)return kIOReturnNotReady; + const auto &r=*o.record; + if(r.refs>1 || Display13FBMemory::field(pipe,0x1298)!=r.memory || + Display13FBMemory::field(pipe,0x12A0)!=r.map || Display13FBMemory::field(pipe,0x12A8) || + (!r.refs && (r.map || r.memory || r.gpu || r.pitch)))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); + o.nativeResult=result;o.freed=!result && !r.refs && !r.map && !r.memory && !r.gpu && !r.pitch && + !Display13FBMemory::field(pipe,0x1298) && !Display13FBMemory::field(pipe,0x12A0); + return o.freed ? result : kIOReturnNotReady; +} + +bool XE::prepareDSINativeBacking(IOService *fb) { + if(adlnOutputWiring.dsi!=1 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || + !dsiModeTarget(fb) || !dsiBackingHooksReady || !dsiProfile.valid())return false; + DSIModeScope scope(fb);if(!scope.lock || !dsiNativeUpdateAllowed(fb))return false; + auto &o=dsiPendingBacking;auto &c=display13FBPower; + if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return false; + IOLockLock(c.lock); + auto controller=static_cast(c.controller); + auto owner=reinterpret_cast(display13FBExpectedOwner(controller)); + const auto reserve=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); + auto machine=owner && owner->getMetaClass()->getClassSize()>=0x1500 ? Display13FBMemory::field(owner,0x378) : nullptr; + XELP::DSI::NativeBacking::Record request; + const bool ready=adlnOutputWiring.dsi==1 && !c.dsiNativeWriters && owner && machine && + machine->metaCast("IGAccelDisplayMachine") && machine->getMetaClass()->getClassSize()>=0x130 && + Display13FBMemory::field(machine,0x128)==owner && adlnSharedBinding(controller,owner) && + reserve && reserve==Display13FBMemory::field(owner,0x13C0) && + adlnSysMemOwner(controller,Display13FBMemory::pool(controller)) && + Display13FBMemory::allocatedSlot(controller,fb)==0 && + XELP::DSI::NativeBacking::request(dsiProfile,Display13FBMemory::field(owner,0x1470),request) && + display13FBInspectMemory(fb,true,0,false).status==Display13FBMemory::Status::Complete; + if(ready){fb->retain();owner->retain();} + IOLockUnlock(c.lock);if(!ready)return false; + auto record=static_cast(IOMalloc(sizeof(request))); + if(!record){owner->release();fb->release();return false;} + memcpy(record,&request,sizeof(request));o.accelerator=owner;o.record=record; + __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.allocating=true; + __atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); + uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; + // Native callPlatformFunction takes the GPU lock. Never hold c.lock here. + const auto result=owner->callPlatformFunction("allocscanmem",false,record,&index,&flags,nullptr); + o.allocating=false; + if(result || !o.allocated)return false; // Retain partial state for discard. + IOLockLock(c.lock); + bool verified=false; + const auto pipeBound=[&]() { + return o.pipe && Display13FBMemory::field(o.pipe,0x88)==owner && + Display13FBMemory::field(o.pipe,0x1298)==record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==record->map && + !Display13FBMemory::field(o.pipe,0x12A8); + }; + if(pipeBound() && c.controller==controller && c.map && c.map->getVirtualAddress() && c.map->getLength()==0x1000000 && + adlnSharedBinding(controller,owner) && !c.memoryFaulted && !c.clockFaulted && + reserve==__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE) && + reserve==Display13FBMemory::field(owner,0x13C0)) { + Display13SurfaceIO io {c.map,0}; + o.proof=Display13FBMemory::inspectPendingSurface(io,controller,controller->getMetaClass()->getClassSize(), + fb,fb->getMetaClass()->getClassSize(),reinterpret_cast(owner),reserve, + (c.map->getLength()-0x800000)/8*4096,xelpMemory,record); + verified=pipeBound() && XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); + } + IOLockUnlock(c.lock);return verified; +} + +// Only a never-armed pending surface may take this path. After SURF is armed, +// retain it through Session's stop proof; freeing an unlatched target is unsafe. +bool XE::discardDSINativeBacking(IOService *fb) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; + IOService *owner=nullptr;OSObject *pipe=nullptr,*mapping=nullptr,*memory=nullptr;void *record=nullptr; + { + DSIModeScope scope(fb);if(!scope.lock)return false; + auto &o=dsiPendingBacking; + if(!dsiPendingTarget(fb) || o.armed || o.allocating || o.freeing || !o.record)return false; + if(!adlnSharedBinding(display13FBPower.controller,o.accelerator))return false; + if(o.pipe) { + __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.freeing=true;o.freed=false; + uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; + o.accelerator->callPlatformFunction("freescanmem",false,o.record,&index,&flags,nullptr); + o.freeing=false; + // DisplayMachine's free discards the pipe method's return value. + // Only our observed native record AND pipe cleanup prove success. + if(!o.freed)return false; + } else if(o.record->refs || o.record->map || o.record->memory || o.record->gpu || o.record->pitch)return false; + owner=o.accelerator;pipe=o.pipe;record=o.record;mapping=o.pinnedMap;memory=o.pinnedMemory; + __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); + __atomic_store_n(&o.pipe,static_cast(nullptr),__ATOMIC_RELEASE); + o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=false;o.nativeResult=0;o.proof={}; + } + IOFree(record,sizeof(XELP::DSI::NativeBacking::Record)); + if(mapping)mapping->release();if(memory)memory->release();if(pipe)pipe->release();owner->release();fb->release();return true; +} + bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // All offsets and entry bytes are from the exact c004fc1a donor. Do not // fall through into the large TGL patcher or admit Display12 operations. @@ -6749,7 +6918,7 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { if(!dsiLifecycleTarget(fb,path))return true; auto &c=display13FBPower;if(!c.lock)return false; IOLockLock(c.lock); - const bool ready=!callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && + const bool ready=!dsiPendingTarget(fb) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && c.transcoders.dsi0Pipe==0 && path && path->getMetaClass()->getClassSize()>=0x3730 && @@ -6764,8 +6933,8 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { // withdrawn. Late shared callbacks must not revive it or fault peer outputs. // An unbound/ambiguous callback cannot establish a different output owner. bool XE::dsiSharedWriterStopped(IOService *fb,unsigned pipe) { - return callbackXE->adlnOutputWiring.dsi && callbackXE->dsiStopAttempted && - (pipe==0 || pipe>=3 || dsiModeTarget(fb)); + return callbackXE->adlnOutputWiring.dsi && (dsiPendingTarget(fb) || + (callbackXE->dsiStopAttempted && (pipe==0 || pipe>=3 || dsiModeTarget(fb)))); } // Cursor image/state bodies can wait for a frame, so do not extend their @@ -9181,6 +9350,7 @@ void XE::wrapAlderLakeNScanoutFree(IOService *fb) { auto &c=display13FBPower;if(!c.lock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; IORecursiveLockLock(mutex); + if(dsiPendingTarget(fb)){IORecursiveLockUnlock(mutex);return;} uint64_t record[30] {},handoff[8] {1};bool freed=false;IOLockLock(c.lock); const auto pool=c.controller ? Display13FBMemory::pool(c.controller) : Display13FBMemory::Pool {}; const bool associated=fb==c.pipe[0].memoryFramebuffer && display13FBAssociations(c.controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(c.controller)); @@ -9257,7 +9427,9 @@ bool XE::adlnFreeStoppedPipe(IOService *fb) { auto &c=display13FBPower;auto &irq=display13FBIRQ; if(!c.lock || !display13FBSysMem.retireLock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; - IORecursiveLockLock(mutex);IORecursiveLockLock(display13FBSysMem.retireLock);IOLockLock(c.lock); + IORecursiveLockLock(mutex); + if(dsiPendingTarget(fb)){IORecursiveLockUnlock(mutex);return false;} + IORecursiveLockLock(display13FBSysMem.retireLock);IOLockLock(c.lock); const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); uint64_t record[24] {1,pipe};bool freed=false,held=false;uint32_t master=0,blocked=0;bool enabled=false; auto controller=static_cast(c.controller); @@ -10090,7 +10262,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { IORecursiveLockLock(clock.lock);IOLockLock(c.lock); uint64_t record[39] {5};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; const auto qualified=[&]() { - if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || !dsiProfile.valid() || + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || dsiPendingTarget(fb) || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || Display13FBMemory::field(controller,0xE00)!=loop || c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || @@ -11774,6 +11946,7 @@ bool XE::processAlderLakeNTaskBoundary(KernelPatcher &patcher, size_t index, mac } bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { + if(adlnDetected && tglAcceleratorRequested)dsiBackingHooksReady=processDSIBackingHooks(patcher,index,address,size); // Framebuffer-only mode must never enter the donor accelerator probe. // The bring-up payload omits this bundle; also reject it if already present. if (!tglAcceleratorRequested) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 506e7b27..67db658e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -239,6 +239,14 @@ class XE { static uint32_t wrapDSIPlaneGammaTransaction(IOService *,void *,bool); static uint32_t wrapDSISetGammaTable(IOService *,uint32_t,uint32_t,uint32_t,void *,bool); bool dsiNativeWriterHooksReady {}; + bool dsiBackingHooksReady {}; + mach_vm_address_t orgDSIBacking[2] {}; + bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool prepareDSINativeBacking(IOService *); + bool discardDSINativeBacking(IOService *); + static uint32_t wrapDSIBackingAllocate(OSObject *,void *,uint32_t); + static uint32_t wrapDSIBackingFree(OSObject *,void *,uint32_t); + mach_vm_address_t orgDSICursor[2] {}; static int beginDSINativeWriter(IOService *,IOService *); static void endDSINativeWriter(int); diff --git a/tests/display13_dbuf_setup_fixture.cpp b/tests/display13_dbuf_setup_fixture.cpp index 7e3d2f85..4dd6ae8f 100644 --- a/tests/display13_dbuf_setup_fixture.cpp +++ b/tests/display13_dbuf_setup_fixture.cpp @@ -96,6 +96,8 @@ void XE::wrapDisplay13WMArmUpdate(IOService *fb){ c.pipe[p].wmPending=0;c.pipe[p].watermarkClockHz=614400000; } } +IOService *pendingBackingFB=nullptr; +bool dsiPendingTarget(IOService *fb){return fb && fb==pendingBackingFB;} // PRODUCTION_SETUP alignas(16) static uint8_t ctl[0x1c20],fb[3][0x4b90],paths[3][0x3730],planes[3][0x1c8]; static int ownerMutex,frameMutex; @@ -130,8 +132,9 @@ int main(){ } // A late callback after DSI stop does not touch MMIO, revive the engine, // allocate a DDB range, or turn an ordinary stop into a shared fault. - for(bool cold:{false,true}) { - Map map;setup(map);auto &c=display13FBPower;state.adlnOutputWiring.dsi=1;state.dsiStopAttempted=true; + for(bool pending:{false,true})for(bool cold:{false,true}) { + Map map;setup(map);auto &c=display13FBPower;state.adlnOutputWiring.dsi=1;state.dsiStopAttempted=!pending; + pendingBackingFB=pending?(IOService*)fb[0]:nullptr; dsiFB=(IOService*)fb[0]; if(cold){c.engineEnabled=false;c.clockStopped=true;c.bridge.bootAdopted=c.bridge.begun=true;} const auto registers=map.r;const auto reads=map.reads;const auto dbuf=c.dbuf; @@ -149,6 +152,7 @@ int main(){ IORecursiveLockLock(&frameMutex);XE::wrapAlderLakeNPipeWMSetup(c.controller,(IOService*)fb[1],(OSObject*)paths[1],nullptr);IORecursiveLockUnlock(&frameMutex); assert(c.watermarkFaulted); // Peer malformed association still reports its real fault. } + pendingBackingFB=nullptr; } // Generic unavailable results, mismatches, timeouts and allocation failures // must not be turned into deferred success by the new branch. diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py new file mode 100644 index 00000000..74c974ee --- /dev/null +++ b/tests/dsi_backing_adapter.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Exercise the production pending-backing adapter with bounded native leaves.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in + ['wrapDSIBackingAllocate','wrapDSIBackingFree','prepareDSINativeBacking','discardDSINativeBacking']) +state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' + installer=re.search(r'bool XE::processDSIBackingHooks\(.*?\n\}',s,re.S)[0] + offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] + entries=re.search(r'entries\[\]\[16\]=\{(.*?)\};',installer,re.S)[1] + for offset,row in zip(offsets,re.findall(r'\{([^}]+)\}',entries)): + assert b[offset:offset+16]==bytes(int(x,0) for x in row.split(',')) + assert 'routeMultipleShort' in installer +code=r''' +#include "kern_dsi_native_backing.hpp" +#include +#include +#include +#include +using namespace XELP::DSI; +using namespace Display13FBMemory; +constexpr uint32_t kIOReturnNotReady=0xE00002D8; +templatevoid put(void *p,unsigned off,T v){memcpy(static_cast(p)+off,&v,sizeof(v));} +unsigned fbDepth=0,ownerDepth=0,gpuDepth=0,allocCalls=0,freeCalls=0,buffers=0,expectedFlags=0; +uintptr_t thread=1;void *current_thread(){return reinterpret_cast(thread);} +struct IOLock{};struct IORecursiveLock{}; +void IOLockLock(IOLock*){assert(fbDepth && !ownerDepth && !gpuDepth);++ownerDepth;} +void IOLockUnlock(IOLock*){assert(ownerDepth==1);--ownerDepth;} +void *IOMalloc(size_t n){assert(!ownerDepth && !gpuDepth);++buffers;return calloc(1,n);} +void IOFree(void *p,size_t n){assert(!fbDepth && !ownerDepth && !gpuDepth && n==64 && buffers);--buffers;free(p);} +struct OSObject { + alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes),refs=1;const char *kind="object"; + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} + bool metaCast(const char *name){return !strcmp(kind,name);}void retain(){++refs;}void release(){assert(refs>1);--refs;} + uint32_t callPlatformFunction(const char*,bool,void*,void*,void*,void*); +}; +using IOService=OSObject; +OSObject nativeMapping,nativeMemory; +IOService *modeFB=nullptr;bool dsiModeTarget(IOService *fb){return fb && modeFB==fb;} +struct DSIModeScope {IORecursiveLock *lock;explicit DSIModeScope(IOService *fb):lock(field(fb,0x4220)){if(lock)++fbDepth;} + ~DSIModeScope(){assert(!ownerDepth && !gpuDepth);if(lock){assert(fbDepth);--fbDepth;}}}; +STATE +DSIPendingBackingOwner dsiPendingBacking; +bool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;} +struct Map {uintptr_t getVirtualAddress(){return 1;}uint64_t getLength(){return 0x1000000;}}; +struct {IOLock *lock;IOService *controller;Map *map;unsigned dsiNativeWriters=0;bool memoryFaulted=false,clockFaulted=false;}display13FBPower; +struct {uint32_t sharedPoolPrefix=0x6192000;}display13FBGTT; +XELPMemory::Layout xelpMemory=XELPMemory::decode(0x03C1,0x7A800001,0); +bool binding=true,source=true,available=true,badPTE=false,failAllocate=false,failFree=false,dropMap=false,changePipe=false,detachAfterAllocate=false; +OSObject *nativePipe=nullptr,*alternatePipe=nullptr; +uintptr_t display13FBExpectedOwner(IOService *controller){return field(controller,0xC0);} +bool adlnSharedBinding(void *controller,void *owner){return binding && controller==display13FBPower.controller && field(controller,0xC0)==owner;} +bool adlnSysMemOwner(void*,const Pool&){assert(ownerDepth);return true;} +Display13FBMemory::Readout display13FBInspectMemory(IOService*,bool,unsigned,bool){assert(ownerDepth);Display13FBMemory::Readout r;r.status=source?Status::Complete:Status::PTE;return r;} +struct Display13SurfaceIO { + Map *map;unsigned pipe;bool memoryReady(uintptr_t m,uintptr_t mem,uintptr_t owner,uint64_t n){ + assert(ownerDepth && !gpuDepth && map);return m==reinterpret_cast(&nativeMapping) && mem==reinterpret_cast(&nativeMemory) && owner && n==8355840;} + uint64_t physical(uintptr_t,uint64_t off,uint64_t &span){span=4096;return 0x180000000ULL+off;} + uint64_t readPTE(uint32_t reg){const uint64_t off=(uint64_t(reg)-0x800000)*512-0x10000000; + return (0x180000000ULL+off+(badPTE?4096:0))|0x99;} +}; +uint32_t nativeAllocate(OSObject *pipe,void *p,uint32_t flags){ + assert(gpuDepth && !ownerDepth && flags==expectedFlags);++allocCalls;if(failAllocate)return 0xE00002BC; + auto &r=*static_cast(p);r.refs=1;r.map=reinterpret_cast(&nativeMapping);r.memory=reinterpret_cast(&nativeMemory);r.gpu=0x10000000;r.pitch=4352; + nativeMapping.retain();nativeMemory.retain(); + put(pipe,0x1298,r.memory);put(pipe,0x12A0,r.map);if(dropMap)display13FBPower.map=nullptr;return 0; +} +uint32_t nativeFree(OSObject *pipe,void *p,uint32_t flags){ + assert(gpuDepth && !ownerDepth && flags==expectedFlags);++freeCalls;if(failFree)return 0xE00002BC; + auto &r=*static_cast(p);if(r.refs){nativeMapping.release();nativeMemory.release();}r.refs=0;r.map=r.memory=r.gpu=0;r.pitch=0; + put(pipe,0x1298,uintptr_t(0));put(pipe,0x12A0,uintptr_t(0));return 0; +} +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE { + bool dsiBackingHooksReady=true;Profile dsiProfile;struct{unsigned dsi=1;}adlnOutputWiring; + uintptr_t orgDSIBacking[2]{reinterpret_cast(nativeAllocate),reinterpret_cast(nativeFree)}; + static bool dsiNativeUpdateAllowed(IOService *fb){assert(fbDepth);return available && !dsiPendingTarget(fb);} + static uint32_t wrapDSIBackingAllocate(OSObject*,void*,uint32_t); + static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); + bool prepareDSINativeBacking(IOService*);bool discardDSINativeBacking(IOService*); +}*callbackXE; +uint32_t OSObject::callPlatformFunction(const char *name,bool wait,void *record,void *index,void *flags,void *extra){ + assert(fbDepth && !ownerDepth && !gpuDepth && !wait && !extra && *static_cast(index)==0); + ++gpuDepth;uint32_t r; + if(!strcmp(name,"allocscanmem"))r=XE::wrapDSIBackingAllocate(nativePipe,record,*static_cast(flags)); + else {assert(!strcmp(name,"freescanmem"));XE::wrapDSIBackingFree(changePipe?alternatePipe:nativePipe,record,*static_cast(flags));r=0;} + --gpuDepth;if(detachAfterAllocate && !strcmp(name,"allocscanmem"))put(nativePipe,0x1298,uintptr_t(0));return r; +} +FUNCTIONS +struct Case { + IOService fb,owner,controller,machine,pipe,other;IOLock lock;IORecursiveLock fbLock;Map map;XE xe; + Case(){ + assert(!dsiPendingBacking.framebuffer && !buffers);callbackXE=&xe;modeFB=&fb;nativePipe=&pipe;alternatePipe=&other; + binding=source=available=true;badPTE=failAllocate=failFree=dropMap=changePipe=detachAfterAllocate=false;thread=1; + display13FBPower={&lock,&controller,&map};display13FBGTT.sharedPoolPrefix=0x6192000; + xe.dsiProfile.status=ProfileStatus::Valid;xe.dsiProfile.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + owner.size=0x1600;machine.kind="IGAccelDisplayMachine";pipe.kind="IGAccelDisplayPipe"; + put(&owner,0x378,&machine);put(&machine,0x128,&owner);put(&owner,0x1470,3U);put(&owner,0x13C0,display13FBGTT.sharedPoolPrefix); + put(&controller,0xC0,&owner);put(&controller,0xD5C,1U);put(&controller,0xD60,&fb);put(&fb,0x1D0,&controller); + put(&controller,0xDF0,1U);put(&controller,0xDF4,uint8_t(1));put(&controller,0xDF5,uint8_t(3)); + put(&controller,0x1A10,1U);put(&controller,0x1A28,xelpMemory.base);put(&controller,0x1A30,0x800000U);put(&controller,0x1A34,0x800U); + put(&fb,0x4220,&fbLock);put(&pipe,0x88,&owner);put(&other,0x88,&owner); + } + ~Case(){assert(!dsiPendingBacking.framebuffer && !buffers && fb.refs==1 && owner.refs==1 && pipe.refs==1 && nativeMapping.refs==1 && nativeMemory.refs==1 && !fbDepth && !ownerDepth && !gpuDepth);} + void discard(){assert(xe.discardDSINativeBacking(&fb));} +}; +int main(){ + {Case c;NativeBacking::Record r;assert(NativeBacking::request(c.xe.dsiProfile,3,r)); + gpuDepth=1;expectedFlags=4;assert(!XE::wrapDSIBackingAllocate(&c.pipe,&r,4)); + expectedFlags=2;assert(!XE::wrapDSIBackingFree(&c.pipe,&r,2)); + expectedFlags=0;failAllocate=true;assert(XE::wrapDSIBackingAllocate(&c.pipe,&r,0)==0xE00002BC);gpuDepth=0;} + {Case c;assert(c.xe.prepareDSINativeBacking(&c.fb));assert(dsiPendingBacking.proof.verifiedPages==2040); + assert(nativeMapping.refs==3 && nativeMemory.refs==3); + assert(dsiPendingBacking.pipe==&c.pipe && c.fb.refs==2 && c.owner.refs==2 && c.pipe.refs==2); + assert(!field(&c.fb,0x4348) && allocatedSlot(&c.controller,&c.fb)==0); + assert(!c.xe.prepareDSINativeBacking(&c.fb));c.discard();assert(!c.xe.discardDSINativeBacking(&c.fb));} + for(unsigned failure=0;failure<11;++failure){Case c;const auto before=allocCalls; + switch(failure){case 0:c.xe.dsiBackingHooksReady=false;break;case 1:binding=false;break;case 2:source=false;break; + case 3:display13FBPower.dsiNativeWriters=1;break;case 4:put(&c.owner,0x1470,2U);break; + case 5:c.machine.kind="wrong";break;case 6:put(&c.machine,0x128,&c.other);break; + case 7:put(&c.fb,0x4220,static_cast(nullptr));break;case 8:put(&c.owner,0x13C0,0U);break;case 9:modeFB=&c.other;break;case 10:c.xe.adlnOutputWiring.dsi=0;break;} + assert(!c.xe.prepareDSINativeBacking(&c.fb) && allocCalls==before && !dsiPendingBacking.framebuffer);} + for(unsigned failure=0;failure<4;++failure){Case c; + switch(failure){case 0:failAllocate=true;break;case 1:badPTE=true;break;case 2:dropMap=true;break;case 3:put(&c.pipe,0x1298,uintptr_t(7));break;} + assert(!c.xe.prepareDSINativeBacking(&c.fb) && dsiPendingBacking.framebuffer==&c.fb);c.discard();} + {Case c;detachAfterAllocate=true;assert(!c.xe.prepareDSINativeBacking(&c.fb)); + assert(nativeMapping.refs==3 && nativeMemory.refs==3 && !c.xe.discardDSINativeBacking(&c.fb)); + put(&c.pipe,0x1298,reinterpret_cast(&nativeMemory));c.discard();} + {Case c;assert(c.xe.prepareDSINativeBacking(&c.fb));failFree=true; + assert(!c.xe.discardDSINativeBacking(&c.fb) && dsiPendingBacking.record && c.pipe.refs==2); + failFree=false;changePipe=true;const auto before=freeCalls;assert(!c.xe.discardDSINativeBacking(&c.fb) && freeCalls==before); + changePipe=false;dsiPendingBacking.armed=true;assert(!c.xe.discardDSINativeBacking(&c.fb));dsiPendingBacking.armed=false;c.discard();} + {Case c;assert(c.xe.prepareDSINativeBacking(&c.fb));NativeBacking::Record foreign;assert(NativeBacking::request(c.xe.dsiProfile,3,foreign)); + thread=2;fbDepth=gpuDepth=1;const auto before=allocCalls; + assert(XE::wrapDSIBackingAllocate(&c.pipe,&foreign,0)==kIOReturnNotReady && allocCalls==before); + assert(XE::wrapDSIBackingFree(&c.pipe,&foreign,0)==kIOReturnNotReady); + assert(!XE::wrapDSIBackingAllocate(&c.other,&foreign,0) && allocCalls==before+1); + assert(!XE::wrapDSIBackingFree(&c.other,&foreign,0));fbDepth=gpuDepth=0;thread=1;c.discard();} + puts("PASS production DSI backing adapter: actual native pipe capture, pending PTE proof, retained failures, discard, peer forwarding and lock boundaries"); +} +'''.replace('STATE',state).replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-backing-adapter-') as directory: + d=Path(directory);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 54ee411b..a22d8d70 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -33,6 +33,7 @@ void setProperty(const char*,bool value){assert(locks==std::vector{1} && !value);} void retain(){++refs;}void release(){assert(refs>1);--refs;}}; using IOService=OSObject; +IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;} struct IOWorkLoop:OSObject {bool gate=false,busy=false;unsigned closes=0,opens=0;}; bool inGate(const IOWorkLoop *p){assert(locks==std::vector{1});return p->gate;} bool tryClose(IOWorkLoop *p){assert(locks==std::vector{1});++p->closes;if(p->busy)return false;return p->gate=true;} @@ -184,6 +185,7 @@ assert(reused.valid && reused.pll==1 && !reused.keepPLL); c.retained(true);const auto before=c.map.bar;assert(!c.run() && c.map.bar==before && c.xe.dsiIRQParked); // one-shot journal } + {Case c;pendingFB=&c.fb;const auto before=c.map.bar;assert(!c.run() && c.map.bar==before);pendingFB=nullptr;} {Case c;display13FBPower.dsiNativeWriters=1;const auto before=c.map.bar; assert(!c.run() && c.map.bar==before && !c.xe.dsiStopAttempted && !memoryFrees); display13FBPower.dsiNativeWriters=0;assert(c.run() && c.xe.dsiMemoryReleased);} diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 25fe388f..e88c2523 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -25,7 +25,8 @@ #include struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; -using IOService=OSObject;using IOLock=int; +using IOService=OSObject; +IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}using IOLock=int; struct IORecursiveLock {unsigned depth=0;}; unsigned owner=0,modeCalls=0,armCalls=0;IORecursiveLock fbLock;bool expectDSI=false,endingWriter=false; void IOLockLock(IOLock*){assert((fbLock.depth || endingWriter) && !owner);++owner;}void IOLockUnlock(IOLock*){assert(owner==1);--owner;} @@ -216,6 +217,7 @@ {Case c;xe.dsiResourcesReady=false;c.refuseCursor();} {Case c;xe.dsiNativeWriterHooksReady=false;c.refuseCursor();} {Case c;display13FBPower.memoryFaulted=true;c.refuseCursor();} + {Case c;pendingFB=&c.fb;c.refuseCursor();pendingFB=nullptr;} {Case c;display13FBPower.dsiNativeWriters=UINT32_MAX;c.refuseCursor(); assert(display13FBPower.dsiNativeWriters==UINT32_MAX);} {Case c;auto i=imageCalls;assert(XE::wrapDSICursorImage(&c.peer,&c.fb,&imageCalls)==kIOReturnNotReady); diff --git a/tools/test.py b/tools/test.py index e0d74b7e..cefb327b 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] @@ -21,6 +21,7 @@ if a.donor:jobs += [('dsi_apple_timing_native',[sys.executable,str(R/'tests/dsi_apple_timing_native.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', '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 += [('dsi_backing_adapter_native',[sys.executable,str(R/'tests/dsi_backing_adapter.py'),str(a.accelerator.resolve())],None)] if a.accelerator:jobs += [('dsi_backing_native',[sys.executable,str(R/'tests/dsi_backing_native.py'),str(a.accelerator.resolve())],None)] 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): From ecedbbd63c5e5e60da9d01f95c197249ac949808 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:00:54 +0900 Subject: [PATCH 044/139] Audit DSI GOP prerequisites before external handoff --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_boot.hpp | 30 ++++++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 27 +++++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 1 + tests/dsi_boot.cpp | 30 ++++++++++++++++++++++++++++++ tests/dsi_boot_adapter.py | 36 +++++++++++++++++++++++++++++++++--- 7 files changed, 121 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 574868ef..fd0c0187 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ No device-ID override or machine-specific AGDP kext is required. The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 DSI-A GOP to Combo B at 1080p60 for development. It stops the internal display and retires the temporary HDMI resources when policy switches to another connected output. It is disabled by default and does not provide DSI support or general cold startup. -The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and available cached boot state, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. +The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index b9a57030..6246547a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -104,7 +104,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 試験用の `-wex-dsi-hdmi-test` は、限定したDisplay13のDSI-A GOPからCombo-Bへ1080p60で移す開発用経路です。内蔵表示を停止し、policyが別の接続済み出力へ切り替える際は暫定HDMI資源を解放します。既定では無効で、DSI対応や汎用のcold起動を提供するものではありません。 -任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式と取得済みのboot状態を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 +任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_boot.hpp b/WhateverXe/kern_dsi_boot.hpp index 6430f286..a22c54d9 100644 --- a/WhateverXe/kern_dsi_boot.hpp +++ b/WhateverXe/kern_dsi_boot.hpp @@ -98,5 +98,35 @@ template bool verifyBootPrefix(IO &io,Boot &boot,const XELPMemory::Lay if(!bootUnchanged(io,boot,memory)){boot.status=BootStatus::Changed;return false;} boot.status=BootStatus::Complete;return true; } +// Fixed-width diagnostic receipt. Deliberately separate from the live Boot +// owner: success here does not publish capabilities or adopt any resource. +struct BootAudit { uint64_t words[32] {}; }; +template void auditBoot(IO &io,const AlderLakeN::DisplayReadout &normal, + const Profile &profile,const XELPMemory::Layout &memory, + bool admitted,Boot &scratch,BootAudit &audit) { + audit={};auto q=audit.words;q[0]=1;q[2]=admitted;q[3]=uint32_t(profile.status); + q[4]=uint32_t(normal.status);q[5]=normal.activePipes;q[6]=normal.poweredPipes; + q[7]=q[9]=UINT32_MAX;q[13]=memory.bytes/4096;q[14]=memory.base; + q[15]=memory.bytes;q[16]=memory.allocationEnd; + // Only read operations are exposed, including when the caller's IO can + // write. Preserve the last attempted access for a bounded failure report. + struct ReadOnly { + IO &io;uint64_t *q; + bool readable(uint32_t reg) { + if(io.readable(reg))return true;q[7]=reg;q[8]=UINT32_MAX;return false; + } + uint32_t read(uint32_t reg) {q[7]=reg;q[8]=io.read(reg);return uint32_t(q[8]);} + uint64_t readPTE(uint32_t reg) {q[9]=reg;q[10]=io.readPTE(reg);return q[10];} + } ro {io,q}; + captureBoot(ro,normal,profile,memory,admitted,scratch); + if(scratch.usable())verifyBootPrefix(ro,scratch,memory); + q[1]=uint32_t(scratch.status);q[12]=scratch.verifiedPages; + if(scratch.status==BootStatus::PTE)q[11]=memory.pte(uint64_t(scratch.verifiedPages)*4096); + q[17]=scratch.control;q[18]=scratch.stride;q[19]=scratch.size;q[20]=scratch.surface;q[21]=scratch.dpt; + q[22]=scratch.geometry.width;q[23]=scratch.geometry.height;q[24]=scratch.geometry.pitch;q[25]=scratch.geometry.bytes; + q[26]=scratch.source.identity.pipe;q[27]=scratch.source.identity.transcoder; + q[28]=scratch.source.identity.phy;q[29]=scratch.source.identity.pll; + q[30]=scratch.source.afeKHz;q[31]=uint32_t(scratch.source.status); +} }} #endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a767e306..3e4a1a2d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3874,6 +3874,28 @@ bool XE::prepareDSIFirmware(IOService *provider) { dsiProfile=parsed;dsiFirmware=copy;return true; } +// Keep profile parsing temporaries out of the already large bootstrap frame. +__attribute__((noinline)) void XE::auditDSIBoot(IOPCIDevice *pci,volatile const uint8_t *base,uint64_t length, + const AlderLakeN::DisplayReadout &normal,bool admitted) { + if(!checkKernelArgument("-wex-dsi-audit"))return; + struct Scratch {XELP::DSI::Profile profile;XELP::DSI::Boot boot;XELP::DSI::BootAudit audit;}; + auto scratch=static_cast(IOMalloc(sizeof(Scratch)));if(!scratch)return; + // Pre-native, before the optional external-output handoff. Read only; + // the scratch certificate must never become the live dsiBoot owner. + auto vbt=OSDynamicCast(OSData,pci->getProperty("TGLVBT"));scratch->profile={}; + if(vbt && vbt->getLength() && vbt->getLength()<=65536) + scratch->profile=XELP::DSI::parseProfile({static_cast(vbt->getBytesNoCopy()),vbt->getLength()},true); + struct ReadIO { + volatile const uint8_t *base;uint64_t length; + bool readable(uint32_t reg) const {return base && length>=4 && reg<=length-4;} + uint32_t read(uint32_t reg) {return *reinterpret_cast(base+reg);} + uint64_t readPTE(uint32_t reg) {return *reinterpret_cast(base+reg);} + } io {base,length}; + XELP::DSI::auditBoot(io,normal,scratch->profile,xelpMemory,admitted,scratch->boot,scratch->audit); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBootAudit",&scratch->audit,sizeof(scratch->audit)); + IOFree(scratch,sizeof(Scratch)); +} + void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); @@ -3984,6 +4006,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { IOFree(words,sizeof(regs)*2); } } + auditDSIBoot(pci,io.base,io.length,result,barReady && reservationReady); if(checkKernelArgument("-wex-dsi-hdmi-test")) { auto handoff=Display13DSITrial::transfer(io,barReady && xelpMemoryMatches(pci),result,xelpMemory,board.hdmi); pci->setProperty("WEXDisplay13DSIHandoff",&handoff,sizeof(handoff)); @@ -6897,8 +6920,8 @@ static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { return attached!=path || dsiCachePath(attached); } -// The native public mode entry disables/reallocates before hwSetMode. Refuse -// here until the GOP-preserving mode transaction can own all those steps. +// Native timing/resource setup can disable or reallocate before hwSetMode. +// Refuse DSI here until its mode transaction owns those conditional steps. uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 67db658e..c4874254 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -418,6 +418,7 @@ class XE { static void wrapDisplay13FBProcessDE(IOService *controller,uint32_t *pipe,uint32_t *aux,uint32_t *hotplug); static bool wrapDisplay13FBProcessIRQ(IOService *controller); static void wrapDisplay13FBSWIRQ(IOService *controller,IOInterruptEventSource *source,int count); + void auditDSIBoot(IOPCIDevice *,volatile const uint8_t *,uint64_t,const AlderLakeN::DisplayReadout &,bool); void adlnBootstrap(IOPCIDevice *pci); uint16_t tglDeviceId {0}; // Physical PCI ID, not a DeviceProperties override. mach_vm_address_t orgTGLEnableCombo {}, orgTGLEngineEnable {}, orgTGLEngineDisable {}; diff --git a/tests/dsi_boot.cpp b/tests/dsi_boot.cpp index 2cfd7eaf..8f0ca090 100644 --- a/tests/dsi_boot.cpp +++ b/tests/dsi_boot.cpp @@ -32,6 +32,36 @@ struct IO { Profile profile(){Profile p;p.status=ProfileStatus::Valid;p.lanes=4;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154};return p;} Boot capture(IO &io){return captureBoot(io,AlderLakeN::readDisplay(io,true),profile(),io.memory,true);} int main(int argc,char **argv){ + // The pre-handoff audit reports real qualification failures without writes + // or promoting its scratch certificate into an active owner. + { + IO observed;auto normal=AlderLakeN::readDisplay(observed,true);Boot scratch;BootAudit audit; + observed.reads.clear();auditBoot(observed,normal,profile(),observed.memory,false,scratch,audit); + assert(audit.words[0]==1 && audit.words[1]==uint32_t(BootStatus::Unavailable)); + assert(observed.reads.empty() && !observed.ptes && observed.writes.empty()); + Profile invalid; + auditBoot(observed,normal,invalid,observed.memory,true,scratch,audit); + assert(observed.reads.empty() && !observed.ptes); + auditBoot(observed,normal,profile(),observed.memory,true,scratch,audit); + assert(scratch.complete() && audit.words[12]==observed.memory.bytes/4096); + assert(audit.words[22]==1080 && audit.words[23]==1920 && audit.words[24]==4352); + assert(audit.words[29]==1 && audit.words[30]==811500 && observed.writes.empty()); + } + for(bool dsb:{false,true}) { + IO observed;auto normal=AlderLakeN::readDisplay(observed,true);Boot scratch;BootAudit audit; + if(dsb)observed.regs[0x70B08]=1;else observed.badPTE=17; + auditBoot(observed,normal,profile(),observed.memory,true,scratch,audit); + if(dsb)assert(audit.words[1]==uint32_t(BootStatus::Peers) && audit.words[7]==0x70B08 && audit.words[8]==1 && !observed.ptes); + else assert(audit.words[1]==uint32_t(BootStatus::PTE) && audit.words[9]==0x800000+8*17 && + audit.words[12]==17 && audit.words[10]!=audit.words[11] && audit.words[11]==observed.memory.pte(17*4096)); + assert(observed.writes.empty()); + } + { + IO observed;auto normal=AlderLakeN::readDisplay(observed,true);Boot scratch;BootAudit audit; + observed.changeAt=4;observed.changeReg=0x45404; + auditBoot(observed,normal,profile(),observed.memory,true,scratch,audit); + assert(audit.words[1]==uint32_t(BootStatus::Changed) && observed.reads.back()==0x45404 && observed.writes.empty()); + } IO io;auto boot=capture(io);assert(boot.status==BootStatus::Candidate && !boot.complete()); assert(boot.geometry.width==1080 && boot.geometry.height==1920 && boot.geometry.pitch==4352 && boot.dpt==0); assert(verifyBootPrefix(io,boot,io.memory) && boot.complete() && boot.verifiedPages==io.memory.bytes/4096); diff --git a/tests/dsi_boot_adapter.py b/tests/dsi_boot_adapter.py index 61aef631..72900150 100644 --- a/tests/dsi_boot_adapter.py +++ b/tests/dsi_boot_adapter.py @@ -4,7 +4,17 @@ import re, subprocess, tempfile root=Path(__file__).resolve().parents[1] source=(root/'WhateverXe/kern_xe.cpp').read_text() -functions='\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',source,re.S)[0] +# Observation must precede the destructive external-output trial and must +# use scratch storage, not publish the certificate to the live DSI owner. +audit_start=source.index('void XE::auditDSIBoot(') +audit_end=source.index('void XE::adlnBootstrap(',audit_start) +audit_source=source[audit_start:audit_end] +assert 'if(!checkKernelArgument("-wex-dsi-audit"))return;' in audit_source +assert 'scratch->boot,scratch->audit' in audit_source and '"WEXDSIBootAudit"' in audit_source +assert 'dsiBoot=' not in audit_source and 'write(' not in audit_source +bootstrap=source[audit_end:] +assert bootstrap.index('auditDSIBoot(pci,') < bootstrap.index('Display13DSITrial::transfer(') +functions=audit_source+'\n'+'\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',source,re.S)[0] for name in ['prepareDSIFirmware','prepareDisplay13BootScanout']) fixture=(root/'tests/dsi_profile.cpp').read_text() fixture=fixture[fixture.index('using Bytes='):fixture.index('Profile parse(')] @@ -16,7 +26,11 @@ #include using namespace XELP::DSI; FIXTURE -bool copyOK=true,deviceOK=true,memoryOK=true;unsigned alive=0,releases=0; +bool copyOK=true,deviceOK=true,memoryOK=true,auditEnabled=false;unsigned alive=0,releases=0,audits=0,allocations=0; +BootAudit lastAudit; +bool checkKernelArgument(const char *key){assert(!strcmp(key,"-wex-dsi-audit"));return auditEnabled;} +void *IOMalloc(size_t bytes){++allocations;return calloc(1,bytes);} +void IOFree(void *p,size_t){--allocations;free(p);} struct OSObject {OSObject(){++alive;}virtual ~OSObject(){--alive;}void release(){delete this;}}; struct OSData:OSObject { std::vector bytes; @@ -25,7 +39,9 @@ d->bytes.assign(static_cast(p),static_cast(p)+n);return d;} }; struct IOService {OSObject *property=nullptr;OSObject *getProperty(const char *){return property;} - void setProperty(const char *,const void *,unsigned){};}; + void setProperty(const char *key,const void *p,unsigned bytes){ + if(!strcmp(key,"WEXDSIBootAudit")){assert(bytes==sizeof(lastAudit));memcpy(&lastAudit,p,bytes);++audits;} + };}; struct IOMemoryMap { std::vector bytes=std::vector(0x1000000); uintptr_t getVirtualAddress(){return reinterpret_cast(bytes.data());} @@ -45,6 +61,7 @@ XELPMemory::Layout xelpMemory;Display13OutputWiring adlnOutputWiring; bool xelpDisplay13Device(IOPCIDevice *){return deviceOK;} bool xelpMemoryMatches(IOPCIDevice *){return memoryOK;} + void auditDSIBoot(IOPCIDevice *,volatile const uint8_t *,uint64_t,const AlderLakeN::DisplayReadout &,bool); bool prepareDSIFirmware(IOService *); bool prepareDisplay13BootScanout(IOPCIDevice *); }; @@ -79,6 +96,19 @@ for(auto &r:regs)io.word(r[0],r[1]); for(unsigned i=0;irelease();pci.property=nullptr; + xe.auditDSIBoot(&pci,map.bytes.data(),map.bytes.size(),normal,true); + assert(audits==3 && !allocations && lastAudit.words[1]==uint32_t(BootStatus::Unavailable)); xe.dsiBoot=captureBoot(io,normal,xe.dsiProfile,xe.xelpMemory,true); xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.dsi=1; auto before=map.bytes; From 97c0695f81753cbd2ab12f03334a80de05fab438 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:22:24 +0900 Subject: [PATCH 045/139] Connect stopped DSI GOP path to timing and port disposal --- WhateverXe/kern_xe.cpp | 50 +++++++++++++++++++++++++-------- WhateverXe/kern_xe.hpp | 1 + tests/dsi_apple_adapter.py | 18 ++++++++---- tests/dsi_lifecycle_dispatch.py | 5 +++- tests/dsi_path_disposal.py | 10 +++++-- tests/dsi_quiesce_adapter.py | 2 +- 6 files changed, 65 insertions(+), 21 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3e4a1a2d..1eef9df2 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6788,6 +6788,25 @@ bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { return o.ready; } +// Called with the FB mutex and provider gate held, after stopped path/PG +// disposal. The donor's qualified null setter removes only the timing property +// and its raw +4260 pointer; it performs no display or allocator operation. +bool XE::releaseDSIFixedMode(IOService *fb) { + auto &o=dsiFixedMode; + if(!o.framebuffer)return true; + if(!dsiModeTarget(fb) || o.ready || !orgDSIMode[0])return false; + auto raw=Display13FBMemory::field(fb,0x4260); + auto property=fb->getProperty("IOFBDetailedTimings"); + if((raw && raw!=o.array) || (property && property!=o.array))return false; + const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,nullptr); + if(result!=kIOReturnSuccess || Display13FBMemory::field(fb,0x4260) || + fb->getProperty("IOFBDetailedTimings"))return false; + auto array=o.array;auto data=o.data; + __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); + o.array=nullptr;o.data=nullptr;o.mode={};o.timing={}; + OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; +} + uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; @@ -9671,6 +9690,17 @@ bool XE::disposeDSIPath(IOService *fb,OSObject *path) { record[8]=removed;record[9]=Display13FBMemory::field(fb,0x49E8); record[10]=Display13FBMemory::field(fb,0x4A00);record[11]=Display13FBMemory::field(path,0x3644); IOLockUnlock(c.lock); + if(removed) { + removed=releaseDSIFixedMode(fb); + if(removed) { + IOLockLock(c.lock); + removed=c.dsiPort==port; + if(removed)c.dsiPort=nullptr; + IOLockUnlock(c.lock); + if(removed)port->release(); + } + } + record[8]=removed; if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); loop->release();adlnPublishMemory("N97FBDSIPathDispose",0,record,sizeof(record));return removed; } @@ -10445,11 +10475,10 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { *reinterpret_cast(reinterpret_cast(fb)+0x4270)=0; if(dsiModeTarget(fb)){dsiFixedMode.ready=false;modeWithdrawn=true;} } - // Close the existing native writer/free gate before dropping the FB mutex, - // even after backing release. Keep it until the remaining PG/path/object - // retirement and Session cleanup transaction is connected. Never clear an - // earlier resource fault to force the allocator through its own guard. - if(masked)adlnFault(c.memoryFaulted,__LINE__); + // This GOP-only path cannot have a pending/armed native backing. A full + // stop leaves PG/path disposal to releaseDisplay13Path; only partial stops + // need the global retention guard. Never clear a pre-existing real fault. + if(masked && !complete)adlnFault(c.memoryFaulted,__LINE__); IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(modeWithdrawn)fb->setProperty("WEXDSIFixedModeReady",false); if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); @@ -10460,12 +10489,11 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObject *path,uint8_t mode) { if(callbackXE->adlnOutputWiring.dsi && dsiLifecycleTarget(fb,path)) { - callbackXE->quiesceDSIPath(controller,fb,path); - // Host/IRQ/PLL/DDI completion alone does not authorize allocator/PG - // release. Keep the retention guard until the full retirement adapter - // supplies a real Session StopProof; this void donor ABI has no error. - auto &c=display13FBPower; - if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + const bool stopped=callbackXE->quiesceDSIPath(controller,fb,path); + // Failed stops retain resources. Successful GOP-only stops can now + // reach the qualified PG/path/timing disposal at the native free entry. + auto &c=display13FBPower; + if(!stopped && c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} return; } auto &c=display13FBPower;Display13Cursor::Result result;bool selected=false;unsigned pipe=UINT32_MAX; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index c4874254..76918afb 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -217,6 +217,7 @@ class XE { bool display13DSIModeHooksReady {}; mach_vm_address_t orgDSIMode[6] {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t setDSIPortMode(OSObject *,uint32_t); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 9c9d495d..49e7e4cf 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -6,7 +6,7 @@ root = Path(__file__).resolve().parents[1] source = (root/'WhateverXe/kern_xe.cpp').read_text() owner = source[source.index('struct DSIFixedModeOwner {'):source.index('// AlderLakeN start bypasses')] -names = ['bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', +names = ['releaseDSIFixedMode', 'bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation'] functions = '\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}', source, re.S)[0] for name in names) if len(sys.argv) == 2: @@ -32,7 +32,7 @@ #include constexpr uint32_t kIOReturnSuccess=0,kIOReturnNotReady=0xe00002d8U, kIOReturnUnsupported=0xe00002c7U,kIOReturnBadArgument=0xe00002c2U; -unsigned alive=0,calls[5]{};bool dataOK=true,arrayOK=true,publishOK=true,geometryOK=true; +unsigned alive=0,calls[5]{};bool dataOK=true,arrayOK=true,publishOK=true,geometryOK=true,clearOK=true; struct Meta {unsigned getClassSize(){return 0x4b90;}} meta; struct OSObject { unsigned refs=1;OSObject(){++alive;}virtual ~OSObject(){--alive;} @@ -79,6 +79,7 @@ struct XE { bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[5]{}; + bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); static uint32_t wrapDSIValidateMode(IOService *,int32_t,const void **,const void **); @@ -88,7 +89,7 @@ } xe,*callbackXE=&xe; OWNER FUNCTIONS -uint32_t publish(IOService *fb,OSArray *a){++calls[0];fb->setProperty("IOFBDetailedTimings",a); +uint32_t publish(IOService *fb,OSArray *a){++calls[0];if(a)fb->setProperty("IOFBDetailedTimings",a);else {if(!clearOK)return 0;fb->removeProperty("IOFBDetailedTimings");} Display13FBMemory::field(fb,0x4260)=a;return 0;} uint32_t validate(IOService *fb,int32_t mode,const void **desc,const void **timing){++calls[1]; auto a=Display13FBMemory::field(fb,0x4260);assert(a && (mode&1023)==0); @@ -143,8 +144,15 @@ data->release();replacement->release(); fb->removeProperty("IOFBDetailedTimings");assert(!XE::wrapDSIGetModeCount(fb) && dsiFixedMode.array->refs==1); assert(XE::wrapDSIValidateMode(fb,mode,nullptr,nullptr)==kIOReturnNotReady); - const auto cleanup=[&]{Display13FBMemory::field(fb,0x4260)=nullptr; - dsiFixedMode.array->release();dsiFixedMode.data->release();fb->release();dsiFixedMode={};}; + const auto cleanup=[&]{ + assert(!xe.releaseDSIFixedMode(peer));dsiFixedMode.ready=true; + assert(!xe.releaseDSIFixedMode(fb));dsiFixedMode.ready=false; + const auto refs=dsiFixedMode.array->refs;clearOK=false; + assert(!xe.releaseDSIFixedMode(fb) && dsiModeTarget(fb) && dsiFixedMode.array->refs==refs && fb->refs==2); + clearOK=true; + assert(xe.releaseDSIFixedMode(fb) && !dsiModeTarget(fb) && fb->refs==1); + assert(!Display13FBMemory::field(fb,0x4260) && !fb->getProperty("IOFBDetailedTimings")); + assert(xe.releaseDSIFixedMode(fb));}; cleanup();publishOK=false;assert(!xe.bindDSIFixedMode(fb,p)); assert(dsiModeTarget(fb) && dsiFixedMode.array->refs==1 && Display13FBMemory::field(fb,0x4260)==dsiFixedMode.array); assert(!XE::wrapDSIGetModeCount(fb));cleanup(); diff --git a/tests/dsi_lifecycle_dispatch.py b/tests/dsi_lifecycle_dispatch.py index 286dc216..7a6ea98b 100644 --- a/tests/dsi_lifecycle_dispatch.py +++ b/tests/dsi_lifecycle_dispatch.py @@ -15,6 +15,7 @@ struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; using IOService=OSObject;using IOLock=int; +bool hostStopOK=false; unsigned depth=0,bindingCalls=0,modeCalls=0,stopCalls=0,notifications=0,modeResult=0,hostStops=0; void IOLockLock(IOLock*){assert(!depth);++depth;}void IOLockUnlock(IOLock*){assert(depth==1);--depth;} void adlnFault(bool &v,unsigned){assert(depth==1);v=true;} @@ -38,7 +39,7 @@ struct Wiring{unsigned dsi=0;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; uintptr_t orgDisplay13TCSetMode=reinterpret_cast(nativeMode),display13TCNotify=reinterpret_cast(notify); uintptr_t orgAlderLakeNDisableDisplay=reinterpret_cast(nativeStop); - bool quiesceDSIPath(IOService*,IOService*,OSObject*){++hostStops;return false;} + bool quiesceDSIPath(IOService*,IOService*,OSObject*){++hostStops;return hostStopOK;} static uint32_t wrapDisplay13TCSetMode(void*,void*,const void*); static void wrapAlderLakeNDisableDisplay(IOService*,IOService*,OSObject*,uint8_t); }xe,*callbackXE=&xe; @@ -56,6 +57,8 @@ assert(c.memoryFaulted && stopCalls==d && bindingCalls==b && hostStops==h+1 && !depth); }; xe.adlnOutputWiring.dsi=1;reject(&fb,&path); // actual pipe A / port A + hostStopOK=true;c.memoryFaulted=false;XE::wrapAlderLakeNDisableDisplay(&controller,&fb,&path,0); + assert(!c.memoryFaulted && stopCalls==0);hostStopOK=false; put(&path,0x3640,1U);put(&port,0x120,3U); panel=&fb;reject(&fb,&path);panel=nullptr; // retained identity survives corrupted path put(&path,0x3640,3U);reject(&fb,&path); diff --git a/tests/dsi_path_disposal.py b/tests/dsi_path_disposal.py index a3702aee..b8905f23 100644 --- a/tests/dsi_path_disposal.py +++ b/tests/dsi_path_disposal.py @@ -22,7 +22,7 @@ OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} void retain(){++refs;}void release(){assert(refs>1);--refs;}}; using IOService=OSObject;using IOLock=int; -unsigned depth=0;bool gate=false,busy=false,queueOnPut=false;unsigned opens=0,closes=0,putCalls=0,leaves=0;bool failPut=false,liePut=false,failLeaf=false; +unsigned depth=0;bool gate=false,busy=false,queueOnPut=false;unsigned opens=0,closes=0,putCalls=0,leaves=0;bool failPut=false,liePut=false,failLeaf=false,failTiming=false; void IOLockLock(IOLock*){assert(!depth);++depth;}void IOLockUnlock(IOLock*){assert(depth==1);--depth;} struct IOWorkLoop:OSObject{}; bool inGate(const IOWorkLoop*){assert(!depth);return gate;} @@ -62,6 +62,7 @@ void write(uint32_t,uint32_t){assert(false);}void delayUs(unsigned){assert(false);}}io; return display13FBPower.bridge.putPG(io,1).status==Display13Power::Status::Complete; } + bool releaseDSIFixedMode(IOService*){assert(!depth && gate);return !failTiming;} bool disposeDSIPath(IOService*,OSObject*); }; BODY @@ -70,7 +71,7 @@ XE xe;IOService controller,fb,peer;OSObject path,port,plane,scaler,planeScaler;IOWorkLoop loop;IOLock lock; Case(){ using Power=decltype(display13FBPower);display13FBPower.~Power();new (&display13FBPower) Power;auto &c=display13FBPower;c.lock=&lock;c.controller=&controller;c.workLoop=&loop;c.dsiPort=&port;c.power=&controller; - c.pipe[0].memoryFramebuffer=&fb;c.pipe[0].destroyingPath=true;c.dbuf.selected=2; + port.retain();c.pipe[0].memoryFramebuffer=&fb;c.pipe[0].destroyingPath=true;c.dbuf.selected=2; c.bridge.begun=true;c.bridge.bootAdopted=true;c.bridge.bootPath=reinterpret_cast(&path);c.bridge.bootPort=0; c.bridge.pgRefs[0]=1;c.bridge.pgRefs[2]=1; auto &state=c.bridge.state;state.ready=true;state.allowed=Display13Power::All; @@ -88,7 +89,7 @@ for(auto s:{&scaler,&planeScaler}){put(s,0x10,&fb);put(s,0x20,uint8_t(1));} pipeMap=mapEntries;pipeMap[0]=&fb;pipeMap[1]=&peer;pipeMap[2]=nullptr; display13FBIRQ={};display13FBIRQ.lock=&lock;display13FBIRQ.state.active=2; - depth=0;queueOnPut=gate=busy=failPut=liePut=failLeaf=false;opens=closes=putCalls=leaves=0; + depth=0;queueOnPut=gate=busy=failPut=liePut=failLeaf=failTiming=false;opens=closes=putCalls=leaves=0; if(image){xe.dsiFreePlane=reinterpret_cast(image+0x5751C);xe.dsiFreeScaler=reinterpret_cast(image+0x5739A); xe.dsiDetachPath=reinterpret_cast(image+0x67FB4);xe.dsiRemovePath=reinterpret_cast(image+0x68EA4); put(image,0x138BB0,pipeMap);xe.dsiPipeMap=reinterpret_cast(image+0x138BB0);} @@ -105,6 +106,9 @@ {Case c;gate=true;c.success();assert(!opens && !closes && gate);gate=false;} {Case c;busy=true;c.retained();} {Case c;c.xe.dsiMemoryReleased=false;c.retained();} + {Case c;failTiming=true;assert(!c.xe.disposeDSIPath(&c.fb,&c.path)); + assert(display13FBPower.dsiPort==&c.port && c.port.refs==2 && c.xe.dsiPGReleased);} + {Case c;display13FBPower.memoryFaulted=true;c.retained();} {Case c;display13FBPower.dsiNativeWriters=1;c.retained();} {Case c;display13FBIRQ.state.active|=1;c.retained();} diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index a22d8d70..12231c05 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -168,7 +168,7 @@ for(bool own:{false,true}){Case c;c.loop.gate=own;c.map.gtOnly=true; put(&c.controller,0xE08,2U);put(&c.controller,0xE10,1U<<6); assert(c.run() && c.xe.dsiStop.complete() && c.xe.dsiIRQParked); - assert(display13FBPower.memoryFaulted); + assert(!display13FBPower.memoryFaulted); assert(display13FBPower.dbuf.consumers==4 && !display13FBPower.dbuf.pipe[0].unretired); assert(c.xe.dsiStop.writes==8 && display13FBIRQ.state.active==2 && display13FBIRQ.state.enabled); assert(!display13FBIRQ.quiescing && !display13FBIRQ.blockedPipes && c.loop.gate==own); From 0eaafa4c6f7fcbc95efb9a475ecc1a8fd8ee9e0b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:28:50 +0900 Subject: [PATCH 046/139] Select explicit DSI GOP adoption and observe native mode entry --- README.md | 2 + README_ja.md | 2 + WhateverXe/kern_display13_fb_topology.hpp | 8 ++-- WhateverXe/kern_xe.cpp | 50 ++++++++++++++++++----- tests/adlp_fb_candidate.py | 13 +++++- tests/dsi_update_dispatch.py | 5 +++ tests/fixtures/boot_argument_names.json | 1 + 7 files changed, 66 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index fd0c0187..aaef7453 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and enables native path and fixed-mode setup; a complete accelerated desktop is not yet supported. Do not combine it with `-wex-dsi-hdmi-test`. + ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. diff --git a/README_ja.md b/README_ja.md index 6246547a..bfe1b3b7 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,6 +106,8 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、native pathと固定modeの設定を有効にします。完全なアクセラレーション付きデスクトップには未対応です。`-wex-dsi-hdmi-test` とは併用できません。 + ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 6443363c..d005c9b1 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -15,12 +15,12 @@ inline bool selectedWiring(const Display13OutputWiring &w) { inline bool adlnWiring(const Display13OutputWiring &w) {return selectedWiring(w) && !(w.selected()&~3U);} // DP topology is composable, but runtime admission stays closed until its // complete signal/training/bootstrap operation contract is installed. -inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false) { +inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false,bool dsiAdoption=false) { // Fixed HDMI is composable across the physical Combo A/B and TC1..4. // TC provider capability, legacy mode and clock hooks are checked separately. - // DSI can describe native objects offline, but must not reach the donor's - // eDP implementation before the complete DSI operation contract exists. - return !w.dsi && !(w.dp&~0x78U) && w.described() && !(w.selected()&~(fixedTC ? Display13OutputWiring::Physical : 3U)); + // DSI requires explicit GOP adoption selection. Native start separately + // checks its mode, writer, probe, cache and disposal hooks. + return (!w.dsi || dsiAdoption) && !(w.dp&~0x78U) && w.described() && !(w.selected()&~(fixedTC ? Display13OutputWiring::Physical : 3U)); } // A TC route uses the same validated handoff regardless of the GT family. // Keep the established Combo-only ADL-N startup ordering unchanged. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 1eef9df2..a8b87228 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3854,12 +3854,24 @@ 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,xeProfile.family); - // Preserve the older ADL-N Combo property contract. It cannot select TC. - if(adlpDisplaySelected)return {}; - auto wiring=OSDynamicCast(OSData,property); - return XELP::VBT::selectHDMI(true,wiring?wiring->getBytesNoCopy():nullptr, - wiring?wiring->getLength():0,firmwarePorts,3); + Display13OutputWiring board; + if(!property)board=XELP::VBT::display13Fixed(firmwarePorts,Display13OutputWiring::Physical,xeProfile.family); + else { + // Preserve the older ADL-N Combo property contract. It cannot select TC. + if(adlpDisplaySelected)return {}; + auto wiring=OSDynamicCast(OSData,property); + board=XELP::VBT::selectHDMI(true,wiring?wiring->getBytesNoCopy():nullptr, + wiring?wiring->getLength():0,firmwarePorts,3); + } + if(checkKernelArgument("-wex-dsi-adopt")) { + // The old HDMI experiment tears down the firmware DSI link. Never + // combine that handoff with adoption, or replace an existing A route. + if(checkKernelArgument("-wex-dsi-hdmi-test") || !board.valid || board.panel || + (board.selected()&1) || !prepareDSIFirmware(provider))return {}; + board.dsi=1; + if(!board.described())return {}; + } + return board; } bool XE::prepareDSIFirmware(IOService *provider) { @@ -3914,7 +3926,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { // Bootstrap runs before framebuffer start loads its board policy. Native // GOP qualification must already use that physical port, not assume B. const auto board=display13BoardWiring(pci);adlnOutputWiring=board; - if(displayRequested && !Display13FBTopology::supportedWiring(board,xeProfile.display==XELP::Display::XeLPD)) { + if(displayRequested && !Display13FBTopology::supportedWiring(board,xeProfile.display==XELP::Display::XeLPD,checkKernelArgument("-wex-dsi-adopt"))) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } if(board.dsi && !prepareDSIFirmware(pci))return; @@ -6874,7 +6886,7 @@ bool XE::processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { auto s=callbackXE; if(!s->display13FBTopologyHooksReady || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->xeProfile.display==XELP::Display::XeLPD) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->xeProfile.display==XELP::Display::XeLPD,checkKernelArgument("-wex-dsi-adopt")) || !checkKernelArgument("-disablegfxfirmware"))return false; if(!FunctionCast(wrapAlderLakeNGetOSInformation,s->orgAlderLakeNGetOSInformation)(controller))return false; auto platform=Display13FBMemory::field(controller,0xD40); if(!Display13FBTopology::apply(s->adlnPlatform,sizeof(s->adlnPlatform),platform,s->adlnOutputWiring))return false; @@ -6945,7 +6957,16 @@ uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb)))return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // A bounded receipt of the real request. Keep the donor's mode + // reallocation path closed until the DSI surface transaction owns it. + uint64_t record[] {1,Display13FBMemory::field(fb,0x1DC), + Display13FBMemory::field(fb,0x1E0),static_cast(mode), + static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, + Display13FBMemory::field(fb,0x420C)}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); + return kIOReturnNotReady; + } } return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); } @@ -8943,8 +8964,17 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { if(s->tglObservationController==that)s->tglObservationController=nullptr; if(!pci)return false; const auto board=s->display13BoardWiring(provider); - if(!Display13FBTopology::supportedWiring(board,s->xeProfile.display==XELP::Display::XeLPD) || + if(!Display13FBTopology::supportedWiring(board,s->xeProfile.display==XELP::Display::XeLPD,checkKernelArgument("-wex-dsi-adopt")) || board.selected()!=s->adlnOutputWiring.selected())return false; + if(board.dsi) { + const bool disposal=s->dsiFreePlane && s->dsiFreeScaler && s->dsiDetachPath && s->dsiRemovePath && s->dsiPipeMap; + const bool ready=s->display13DSIModeHooksReady && s->dsiNativeWriterHooksReady && + s->dsiBootProbeReady && s->dsiCacheHooksReady && disposal; + uint64_t record[] {1,ready,s->display13DSIModeHooksReady,s->dsiNativeWriterHooksReady, + s->dsiBootProbeReady,s->dsiCacheHooksReady,disposal}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIStartupHooks",record,sizeof(record)); + if(!ready)return false; + } if((board.selected()&0x78) && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBTCUnavailable",true);return false; } diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 025b5517..f0bcbb62 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -12,6 +12,8 @@ #include "kern_display13_firmware.hpp" #include "kern_display13_fb_topology.hpp" #include "kern_alderlake_s.hpp" +bool adopt=false,hdmiTrial=false; +bool checkKernelArgument(const char *s){return !strcmp(s,"-wex-dsi-adopt")?adopt:!strcmp(s,"-wex-dsi-hdmi-test")?hdmiTrial:false;} struct OSData {void *data=nullptr;unsigned bytes=0;void *getBytesNoCopy(){return data;}unsigned getLength(){return bytes;}}; #define OSDynamicCast(T,p) static_cast(p) struct IOService {OSData *property=nullptr;OSData *getProperty(const char *){return property;}}; @@ -23,7 +25,8 @@ XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; bool display13PanelProbeReady=true,display13PanelPowerReady=true; - bool dsiResourcesReady=false; + bool dsiResourcesReady=false,profileReady=true; + bool prepareDSIFirmware(IOService *){return profileReady;} bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; bool display13DPAltProbeReady=true,display13TCDPProbeReady=true,display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); @@ -44,6 +47,14 @@ for(unsigned d=3;d<=5;++d){auto &p=x.firmwarePorts.port[d];p.type=d==5?4:0x12;p.ddc=d;p.capabilities=3;p.hdmiPreset=7;} auto w=x.display13BoardWiring(&pci);assert(w.described() && w.hdmi==0x18 && w.ddc[3]==9 && w.ddc[4]==10 && !w.dp); assert(!Display13FBTopology::supportedWiring(w) && Display13FBTopology::supportedWiring(w,true)); + adopt=true; + auto dw=x.display13BoardWiring(&pci);assert(dw.described() && dw.dsi==1 && dw.hdmi==w.hdmi); + assert(!Display13FBTopology::supportedWiring(dw,true) && Display13FBTopology::supportedWiring(dw,true,true)); + hdmiTrial=true;assert(!x.display13BoardWiring(&pci).described());hdmiTrial=false; + x.profileReady=false;assert(!x.display13BoardWiring(&pci).described());x.profileReady=true; + x.firmwarePorts.present|=1;x.firmwarePorts.port[0].type=0x12;x.firmwarePorts.port[0].ddc=1; + assert(!x.display13BoardWiring(&pci).described());x.firmwarePorts.present&=~1U; + adopt=false;assert(!x.display13BoardWiring(&pci).dsi); x.adlnOutputWiring=w;assert(x.xelpBindProbe((void*)1)); assert(seen.valid() && !seen.combo && seen.comboBackend==3 && seen.legacy==0x18 && seen.legacyBackend==0x78); assert(seen.profile.family==XELP::Family::AlderLakeP && seen.profile.admitted()); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index e88c2523..d7cff2ee 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -26,6 +26,11 @@ struct OSObject {alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes); OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; using IOService=OSObject; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *name,const void *data,unsigned bytes){ + assert(!strcmp(name,"WEXDSIModeRequest") && bytes==64); + const auto words=static_cast(data);assert(words[0]==1); + }}; IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}using IOLock=int; struct IORecursiveLock {unsigned depth=0;}; unsigned owner=0,modeCalls=0,armCalls=0;IORecursiveLock fbLock;bool expectDSI=false,endingWriter=false; diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index eb60ce93..6ea73ef9 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -1,5 +1,6 @@ { "-wex-dsi-audit": [], + "-wex-dsi-adopt": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" From 210576cf229c23e9708a76d12b0c16892a680289 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:39:44 +0900 Subject: [PATCH 047/139] Capture bounded DSI mode and client callback state --- WhateverXe/kern_xe.cpp | 232 ++++++++++++++++++++++------------- WhateverXe/kern_xe.hpp | 1 + tests/dsi_apple_adapter.py | 7 +- tests/dsi_update_dispatch.py | 1 + 4 files changed, 157 insertions(+), 84 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a8b87228..f5d1570b 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6767,6 +6767,28 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad patcher.clearError();return ready; } +// Bounded, copied CPU evidence from the real FB0 client callbacks. Also +// observe calls before the fixed-mode owner is bound; do not treat the trace +// as admission or invoke any driver/hardware operation while collecting it. +void XE::recordDSIClient(IOService *fb,unsigned event,uint64_t argument,uint32_t result) { + if(!callbackXE->adlnOutputWiring.dsi || event>=10 || !fb || + fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1DC))return; + DSIModeScope scope(fb);if(!scope.lock)return; + static uint64_t counts[10] {}; + const char *keys[] {"WEXDSIClientBind","WEXDSIClientTimings","WEXDSIClientValidate", + "WEXDSIClientModes","WEXDSIClientCount","WEXDSIClientInfo","WEXDSIClientSetMode", + "WEXDSIClientAttribute","WEXDSIClientConnection","WEXDSIClientPower"}; + const auto count=__atomic_add_fetch(&counts[event],1ULL,__ATOMIC_RELAXED); + uint64_t words[] {1,event,count,argument,result, + Display13FBMemory::field(fb,0x1E0),Display13FBMemory::field(fb,0x44DE), + Display13FBMemory::field(fb,0x1E7),Display13FBMemory::field(fb,0x4250), + Display13FBMemory::field(fb,0x4254),Display13FBMemory::field(fb,0x4284), + Display13FBMemory::field(fb,0x420C),dsiModeTarget(fb), + dsiModeTarget(fb) && dsiFixedMode.ready,Display13FBMemory::field(fb,0x4260)!=nullptr, + Display13FBMemory::field(fb,0x4348)!=nullptr}; + IORegistryEntry::getRegistryRoot()->setProperty(keys[event],words,sizeof(words)); +} + bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { auto &c=display13FBPower;auto &o=dsiFixedMode; if(!display13DSIModeHooksReady || adlnOutputWiring.dsi!=1 || !c.lock || !fb || @@ -6797,6 +6819,7 @@ bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { o.ready=result==kIOReturnSuccess; o.ready=dsiModeCoherent(fb); fb->setProperty("WEXDSIFixedModeReady",o.ready); + recordDSIClient(fb,0,XELP::DSI::AppleAdapter::FixedModeID,o.ready); return o.ready; } @@ -6820,40 +6843,65 @@ bool XE::releaseDSIFixedMode(IOService *fb) { } uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; - if(!data || data->getLength()!=sizeof(dsiFixedMode.timing) || - memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)))return kIOReturnUnsupported; - const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); - return result==kIOReturnSuccess && !dsiModeCoherent(fb) ? kIOReturnNotReady : result; + recordDSIClient(fb,1,array!=nullptr,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; + if(!data || data->getLength()!=sizeof(dsiFixedMode.timing) || + memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)))return kIOReturnUnsupported; + const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); + return result==kIOReturnSuccess && !dsiModeCoherent(fb) ? kIOReturnNotReady : result; + }(); + recordDSIClient(fb,1,array!=nullptr,result); + return result; } uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **description,const void **timing) { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); - if(description)*description=nullptr;if(timing)*timing=nullptr; - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; - const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); - if(!result && timing && *timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; + recordDSIClient(fb,2,static_cast(mode),UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); + if(description)*description=nullptr;if(timing)*timing=nullptr; + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; + const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); + if(!result && timing && *timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; + return result; + }(); + recordDSIClient(fb,2,static_cast(mode),result); return result; } uint32_t XE::wrapDSIGetModes(IOService *fb,int32_t *modes) { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!modes)return kIOReturnBadArgument; - *modes=XELP::DSI::AppleAdapter::FixedModeID;return kIOReturnSuccess; + recordDSIClient(fb,3,modes!=nullptr,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(!modes)return kIOReturnBadArgument; + *modes=XELP::DSI::AppleAdapter::FixedModeID;return kIOReturnSuccess; + }(); + recordDSIClient(fb,3,modes!=nullptr,result); + return result; } uint32_t XE::wrapDSIGetModeCount(IOService *fb) { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeCount,callbackXE->orgDSIMode[3])(fb); - DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? 1 : 0; + recordDSIClient(fb,4,0,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeCount,callbackXE->orgDSIMode[3])(fb); + DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? 1 : 0; + }(); + recordDSIClient(fb,4,0,result); + return result; } uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!raw || mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; - const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); - if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, - *static_cast(raw)))return kIOReturnNotReady; + recordDSIClient(fb,5,static_cast(mode),UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(!raw || mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; + const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); + if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, + *static_cast(raw)))return kIOReturnNotReady; + return result; + }(); + recordDSIClient(fb,5,static_cast(mode),result); return result; } @@ -6954,21 +7002,26 @@ static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { // Native timing/resource setup can disable or reallocate before hwSetMode. // Refuse DSI here until its mode transaction owns those conditional steps. uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - // A bounded receipt of the real request. Keep the donor's mode - // reallocation path closed until the DSI surface transaction owns it. - uint64_t record[] {1,Display13FBMemory::field(fb,0x1DC), - Display13FBMemory::field(fb,0x1E0),static_cast(mode), - static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, - Display13FBMemory::field(fb,0x420C)}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); - return kIOReturnNotReady; + recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // A bounded receipt of the real request. Keep the donor's mode + // reallocation path closed until the DSI surface transaction owns it. + uint64_t record[] {1,Display13FBMemory::field(fb,0x1DC), + Display13FBMemory::field(fb,0x1E0),static_cast(mode), + static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, + Display13FBMemory::field(fb,0x420C)}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); + return kIOReturnNotReady; + } } - } - return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); + return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); + }(); + recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),result); + return result; } // Caller holds the native FB mutex through the admitted writer. quiesce uses @@ -7067,58 +7120,73 @@ uint32_t XE::wrapDSISetGammaTable(IOService *fb,uint32_t channels,uint32_t count // not inherit eDP behavior. The admitted CPU/color operations remain counted // through their native bodies; P5 will replace this policy with DSI controls. uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t value) { - int token=0; - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(attribute==0x706F7772) { // powr: native entry passes an unsigned state. - if(value>UINT32_MAX)return kIOReturnUnsupported; - return wrapDSISetPowerState(fb,static_cast(value)); + recordDSIClient(fb,7,attribute,UINT32_MAX); + const auto result=[&]() -> uint32_t { + int token=0; + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(attribute==0x706F7772) { // powr: native entry passes an unsigned state. + if(value>UINT32_MAX)return kIOReturnUnsupported; + return wrapDSISetPowerState(fb,static_cast(value)); + } + // wsrv and capd: retain native server/capture bookkeeping and the + // routed plane/transaction work. Other selectors need admission. + if(attribute!=0x77737276 && attribute!=0x63617064)return kIOReturnUnsupported; + token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; } - // wsrv and capd: retain native server/capture bookkeeping and the - // routed plane/transaction work. Other selectors need admission. - if(attribute!=0x77737276 && attribute!=0x63617064)return kIOReturnUnsupported; - token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; } - } - const auto result=FunctionCast(wrapDSISetAttribute,callbackXE->orgDSIAttributes[0])(fb,attribute,value); - endDSINativeWriter(token);return result; + const auto result=FunctionCast(wrapDSISetAttribute,callbackXE->orgDSIAttributes[0])(fb,attribute,value); + endDSINativeWriter(token);return result; + }(); + recordDSIClient(fb,7,attribute,result); + return result; } uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint32_t attribute,uintptr_t value) { - int token=0; - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(connection!=0)return kIOReturnUnsupported; - // Controller depth and gamma scales/flush have qualified native - // CPU/color paths. In particular ignore, bklt, pnlt, psr?, powr, - // probe and DP-link configuration must never reach native DSI. - switch(attribute) { - case 0x00647064: // controller depth - case 0x72677363: case 0x67677363: case 0x62677363: // RGB gamma scale - case 0x67736320: case 0x666C7573: break; // gamma scale / flush - default:return kIOReturnUnsupported; + recordDSIClient(fb,8,attribute,UINT32_MAX); + const auto result=[&]() -> uint32_t { + int token=0; + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(connection!=0)return kIOReturnUnsupported; + // Controller depth and gamma scales/flush have qualified native + // CPU/color paths. In particular ignore, bklt, pnlt, psr?, powr, + // probe and DP-link configuration must never reach native DSI. + switch(attribute) { + case 0x00647064: // controller depth + case 0x72677363: case 0x67677363: case 0x62677363: // RGB gamma scale + case 0x67736320: case 0x666C7573: break; // gamma scale / flush + default:return kIOReturnUnsupported; + } + token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; } - token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; } - } - const auto result=FunctionCast(wrapDSISetConnectionAttribute,callbackXE->orgDSIAttributes[1])(fb,connection,attribute,value); - endDSINativeWriter(token);return result; + const auto result=FunctionCast(wrapDSISetConnectionAttribute,callbackXE->orgDSIAttributes[1])(fb,connection,attribute,value); + endDSINativeWriter(token);return result; + }(); + recordDSIClient(fb,8,attribute,result); + return result; } uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - // Match only the donor's already-Wake no-op. Do not publish a new - // native power state without executing a DSI power transaction. - if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; - return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; + recordDSIClient(fb,9,state,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // Match only the donor's already-Wake no-op. Do not publish a new + // native power state without executing a DSI power transaction. + if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; + return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; + } } - } - return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); + return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); + }(); + recordDSIClient(fb,9,state,result); + return result; } uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path,const void *timing) { if(callbackXE->adlnOutputWiring.dsi) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 76918afb..77bc1e6a 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -217,6 +217,7 @@ class XE { bool display13DSIModeHooksReady {}; mach_vm_address_t orgDSIMode[6] {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t setDSIPortMode(OSObject *,uint32_t); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 49e7e4cf..8338161b 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -6,9 +6,9 @@ root = Path(__file__).resolve().parents[1] source = (root/'WhateverXe/kern_xe.cpp').read_text() owner = source[source.index('struct DSIFixedModeOwner {'):source.index('// AlderLakeN start bypasses')] -names = ['releaseDSIFixedMode', 'bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', +names = ['recordDSIClient', 'releaseDSIFixedMode', 'bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation'] -functions = '\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}', source, re.S)[0] for name in names) +functions = '\n'.join(re.search(r'(?:void|bool|uint32_t) XE::'+name+r'\(.*?\n\}', source, re.S)[0] for name in names) if len(sys.argv) == 2: binary = Path(sys.argv[1]).read_bytes() assert hashlib.sha256(binary).hexdigest() == 'c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' @@ -60,6 +60,8 @@ void removeProperty(const char *k){auto i=properties.find(k);if(i!=properties.end()){i->second->release();properties.erase(i);}} ~IOService(){for(auto i:properties)i.second->release();} }; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *,const void *p,unsigned n){assert(n==128);const auto w=static_cast(p);assert(w[0]==1 && w[1]<10 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} @@ -79,6 +81,7 @@ struct XE { bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[5]{}; + static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index d7cff2ee..9e505b45 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -68,6 +68,7 @@ struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; + static void recordDSIClient(IOService*,unsigned,uint64_t,uint32_t){}; uintptr_t orgDSIAttributes[4]{}; static uint32_t wrapDSISetStream(IOService*,IOService*,OSObject*,const void*); static uint32_t wrapDSISetAttribute(IOService*,uint32_t,uintptr_t); From ce1648578f07c1678e5ffda8cb896e08cec3d826 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:46:30 +0900 Subject: [PATCH 048/139] Use adopted DSI identity for online and current mode queries --- WhateverXe/kern_xe.cpp | 56 ++++++++++++++++++++++++++++++++++---- WhateverXe/kern_xe.hpp | 4 ++- tests/dsi_apple_adapter.py | 41 +++++++++++++++++++++++++--- 3 files changed, 90 insertions(+), 11 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f5d1570b..1f0b3028 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6742,14 +6742,18 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad "__ZN21AppleIntelFramebuffer15getDisplayModesEPi", "__ZN21AppleIntelFramebuffer19getDisplayModeCountEv", "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation", - "__ZN21AppleIntelFramebuffer14setDisplayModeEii"}; - const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10}; + "__ZN21AppleIntelFramebuffer14setDisplayModeEii", + "__ZN21AppleIntelFramebuffer21getCurrentDisplayModeEPiS0_", + "__ZN21AppleIntelFramebuffer13getOnlineInfoEP21AppleIntelDisplayPathPhS2_"}; + const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10,0x32A88,0x2E232}; const uint8_t entries[][16]={ {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xF6,0x48,0x89,0xFB,0x48,0xFF,0x05}, {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0x98,0x93,0x0A,0x00,0x83,0xFE}, {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x8F,0x84,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xE1,0x79,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}}; if(!display13FramebufferRequested || size<=0x32D14)return false; for(unsigned i=0;iadlnOutputWiring.dsi || event>=10 || !fb || + if(!callbackXE->adlnOutputWiring.dsi || event>=12 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1DC))return; DSIModeScope scope(fb);if(!scope.lock)return; - static uint64_t counts[10] {}; + static uint64_t counts[12] {}; const char *keys[] {"WEXDSIClientBind","WEXDSIClientTimings","WEXDSIClientValidate", "WEXDSIClientModes","WEXDSIClientCount","WEXDSIClientInfo","WEXDSIClientSetMode", - "WEXDSIClientAttribute","WEXDSIClientConnection","WEXDSIClientPower"}; + "WEXDSIClientAttribute","WEXDSIClientConnection","WEXDSIClientPower", + "WEXDSIClientCurrent","WEXDSIClientOnline"}; const auto count=__atomic_add_fetch(&counts[event],1ULL,__ATOMIC_RELAXED); uint64_t words[] {1,event,count,argument,result, Display13FBMemory::field(fb,0x1E0),Display13FBMemory::field(fb,0x44DE), @@ -6842,6 +6849,43 @@ bool XE::releaseDSIFixedMode(IOService *fb) { OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; } +// Native online detection assumes eDP AUX/EDID. A DSI panel has no such +// sink. Answer only for the real adopted path and its immutable fixed mode; +// returning an empty EDID does not manufacture panel data or touch AUX power. +uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,uint8_t *edid) { + if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); + if(!online || !edid)return kIOReturnBadArgument; + *online=0;bzero(edid,128); + DSIModeScope scope(fb); + const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && + path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); + if(ready)*online=1; + const auto result=ready ? kIOReturnSuccess : kIOReturnNotReady; + recordDSIClient(fb,11,*online,result);return result; +} + +// The donor initializes current mode to -5, then tries to recover it through +// normal-transcoder timing conversion. DSI already has a qualified GOP and +// fixed timing. Publish that same current mode under the native FB mutex. +uint32_t XE::wrapDSIGetCurrentMode(IOService *fb,int32_t *mode,int32_t *depth) { + if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); + DSIModeScope scope(fb); + const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && + Display13FBMemory::field(fb,0x1E0) && dsiNativeUpdateAllowed(fb); + if(ready) { + const uint32_t fixed=XELP::DSI::AppleAdapter::FixedModeID; + memcpy(reinterpret_cast(fb)+0x4250,&fixed,sizeof(fixed)); + if(mode)*mode=fixed; + if(depth)*depth=Display13FBMemory::field(fb,0x4254); + } + const auto result=ready ? kIOReturnSuccess : kIOReturnNotReady; + recordDSIClient(fb,10,ready ? XELP::DSI::AppleAdapter::FixedModeID : 0,result);return result; +} + uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { recordDSIClient(fb,1,array!=nullptr,UINT32_MAX); const auto result=[&]() -> uint32_t { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 77bc1e6a..bdc4875a 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -215,12 +215,14 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; bool display13DSIModeHooksReady {}; - mach_vm_address_t orgDSIMode[6] {}; + mach_vm_address_t orgDSIMode[8] {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t setDSIPortMode(OSObject *,uint32_t); + static uint32_t wrapDSIGetCurrentMode(IOService *,int32_t *,int32_t *); + static uint32_t wrapDSIGetOnlineInfo(IOService *,OSObject *,uint8_t *,uint8_t *); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); static uint32_t wrapDSISetDisplayMode(IOService *,int32_t,int32_t); static bool dsiNativeUpdateAllowed(IOService *); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 8338161b..800b704b 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -7,7 +7,7 @@ source = (root/'WhateverXe/kern_xe.cpp').read_text() owner = source[source.index('struct DSIFixedModeOwner {'):source.index('// AlderLakeN start bypasses')] names = ['recordDSIClient', 'releaseDSIFixedMode', 'bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', - 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation'] + 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation', 'wrapDSIGetCurrentMode', 'wrapDSIGetOnlineInfo'] functions = '\n'.join(re.search(r'(?:void|bool|uint32_t) XE::'+name+r'\(.*?\n\}', source, re.S)[0] for name in names) if len(sys.argv) == 2: binary = Path(sys.argv[1]).read_bytes() @@ -16,7 +16,7 @@ offsets = [int(x, 0) for x in re.search(r'offsets\[\]=\{([^}]+)', installer)[1].split(',')] entries = re.search(r'entries\[\]\[16\]=\{(.*?)\};', installer, re.S)[1] entries = [bytes(int(x, 0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}', entries)] - assert len(entries) == len(offsets) == 6 + assert len(entries) == len(offsets) == 8 for offset, entry in zip(offsets, entries): assert binary[offset:offset+16] == entry, hex(offset) @@ -61,7 +61,7 @@ ~IOService(){for(auto i:properties)i.second->release();} }; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *,const void *p,unsigned n){assert(n==128);const auto w=static_cast(p);assert(w[0]==1 && w[1]<10 && w[2]);}}; + void setProperty(const char *,const void *p,unsigned n){assert(n==128);const auto w=static_cast(p);assert(w[0]==1 && w[1]<12 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} @@ -80,7 +80,11 @@ bool memoryFaulted=false;struct {struct {bool faulted=false;} state;} bridge;} display13FBPower; struct XE { bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; - uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[5]{}; + uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[8]{}; + static bool updateReady; + static bool dsiNativeUpdateAllowed(IOService *){return updateReady;} + static uint32_t wrapDSIGetCurrentMode(IOService *,int32_t *,int32_t *); + static uint32_t wrapDSIGetOnlineInfo(IOService *,OSObject *,uint8_t *,uint8_t *); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); @@ -90,6 +94,7 @@ static uint32_t wrapDSIGetModeCount(IOService *); static uint32_t wrapDSIGetModeInformation(IOService *,int32_t,void *); } xe,*callbackXE=&xe; +bool XE::updateReady=true; OWNER FUNCTIONS uint32_t publish(IOService *fb,OSArray *a){++calls[0];if(a)fb->setProperty("IOFBDetailedTimings",a);else {if(!clearOK)return 0;fb->removeProperty("IOFBDetailedTimings");} @@ -104,6 +109,9 @@ auto timing=static_cast(t); auto info=static_cast(p);*info={}; info->width=geometryOK?timing->horizontalActive:1;info->height=timing->verticalActive;info->depth=2;return 0;} +unsigned nativeCurrentCalls=0,nativeOnlineCalls=0; +uint32_t nativeCurrent(IOService *,int32_t *m,int32_t *d){++nativeCurrentCalls;if(m)*m=77;if(d)*d=3;return 7;} +uint32_t nativeOnline(IOService *,OSObject *,uint8_t *on,uint8_t *edid){++nativeOnlineCalls;if(on)*on=9;if(edid)edid[0]=42;return 8;} int main(){ using namespace XELP::DSI;using namespace AppleAdapter;IORecursiveLock fbLock;IOLock ownerLock; auto fb=new IOService;auto peer=new IOService;testPath=peer; @@ -113,6 +121,8 @@ xe.orgDSIMode[0]=reinterpret_cast(publish);xe.orgDSIMode[1]=reinterpret_cast(validate); xe.orgDSIMode[2]=reinterpret_cast(modes);xe.orgDSIMode[3]=reinterpret_cast(count); xe.orgDSIMode[4]=reinterpret_cast(information); + xe.orgDSIMode[6]=reinterpret_cast(nativeCurrent);xe.orgDSIMode[7]=reinterpret_cast(nativeOnline); + Display13FBMemory::field(peer,0x1DC)=1; Profile p;p.status=ProfileStatus::Valid;p.lanes=4;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; auto plan=linkPlan(p);auto &r=display13FBPower.bootDSI;r.status=ReadoutStatus::Complete;r.identity={0,0,0,1}; r.afeKHz=811500;r.htotal=plan.htotal;r.hsync=plan.hsync;r.vtotal=plan.vtotal;r.vsync=plan.vsync; @@ -122,6 +132,29 @@ assert(!xe.bindDSIFixedMode(fb,p) && !dsiModeTarget(fb) && alive==before && !calls[0]);} dataOK=arrayOK=true;display13FBPower.memoryFaulted=false; assert(xe.bindDSIFixedMode(fb,p) && fb->refs==2 && dsiFixedMode.array->refs==2 && dsiFixedMode.data->refs==2); + // Adopted DSI presence does not call native EDID/AUX or alter the EDID identity. + uint8_t online=0,edid[128];memset(edid,0xCC,sizeof(edid)); + assert(!XE::wrapDSIGetOnlineInfo(fb,peer,&online,edid) && online==1 && !nativeOnlineCalls); + for(auto b:edid)assert(b==0); + assert(XE::wrapDSIGetOnlineInfo(fb,fb,&online,edid)==kIOReturnNotReady && !online); + assert(XE::wrapDSIGetOnlineInfo(fb,peer,nullptr,edid)==kIOReturnBadArgument); + int32_t current=-9,depth=-9; + Display13FBMemory::field(fb,0x1E0)=1; + Display13FBMemory::field(fb,0x4250)=uint32_t(-5); + Display13FBMemory::field(fb,0x4254)=0; + assert(!XE::wrapDSIGetCurrentMode(fb,¤t,&depth) && current==FixedModeID && !depth && !nativeCurrentCalls); + assert(Display13FBMemory::field(fb,0x4250)==FixedModeID); + XE::updateReady=false;current=-9; + assert(XE::wrapDSIGetCurrentMode(fb,¤t,&depth)==kIOReturnNotReady && current==-9); + assert(XE::wrapDSIGetOnlineInfo(fb,peer,&online,edid)==kIOReturnNotReady && !online);XE::updateReady=true; + Display13FBMemory::field(fb,0x1E0)=0; + assert(XE::wrapDSIGetCurrentMode(fb,¤t,&depth)==kIOReturnNotReady); + Display13FBMemory::field(fb,0x1E0)=1; + assert(XE::wrapDSIGetCurrentMode(peer,¤t,&depth)==7 && current==77 && depth==3); + assert(XE::wrapDSIGetOnlineInfo(peer,peer,&online,edid)==8 && online==9 && edid[0]==42); + xe.adlnOutputWiring.dsi=0;assert(XE::wrapDSIGetCurrentMode(fb,¤t,&depth)==7); + assert(XE::wrapDSIGetOnlineInfo(fb,peer,&online,edid)==8);xe.adlnOutputWiring.dsi=1; + assert(nativeCurrentCalls==2 && nativeOnlineCalls==2); auto callsBeforeRepeat=calls[0];assert(xe.bindDSIFixedMode(fb,p) && calls[0]==callsBeforeRepeat && fb->refs==2 && dsiFixedMode.array->refs==2); auto other=p;++other.mode.clockKHz;assert(!xe.bindDSIFixedMode(fb,other)); assert(XE::wrapDSIGetModeCount(fb)==1 && XE::wrapDSIGetModeCount(peer)==3); From 6e325c6ac7ea652809bd68aada16d5020872a9d5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:51:58 +0900 Subject: [PATCH 049/139] Reject DSI DDC requests before native AUX power acquisition --- WhateverXe/kern_xe.cpp | 28 +++++++++++++++++++++------- WhateverXe/kern_xe.hpp | 3 ++- tests/dsi_apple_adapter.py | 21 ++++++++++++++------- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 1f0b3028..36e0d012 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6744,8 +6744,9 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation", "__ZN21AppleIntelFramebuffer14setDisplayModeEii", "__ZN21AppleIntelFramebuffer21getCurrentDisplayModeEPiS0_", - "__ZN21AppleIntelFramebuffer13getOnlineInfoEP21AppleIntelDisplayPathPhS2_"}; - const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10,0x32A88,0x2E232}; + "__ZN21AppleIntelFramebuffer13getOnlineInfoEP21AppleIntelDisplayPathPhS2_", + "__ZN21AppleIntelFramebuffer13fbGetDDCBlockEjPhPyjP21AppleIntelDisplayPath"}; + const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10,0x32A88,0x2E232,0x2F1B4}; const uint8_t entries[][16]={ {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xF6,0x48,0x89,0xFB,0x48,0xFF,0x05}, {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0x98,0x93,0x0A,0x00,0x83,0xFE}, @@ -6754,7 +6755,8 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}, {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}}; + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x44,0x89}}; if(!display13FramebufferRequested || size<=0x32D14)return false; for(unsigned i=0;i(index,symbols[i],address,size);patcher.clearError(); @@ -6768,7 +6770,8 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad {symbols[4],wrapDSIGetModeInformation,orgDSIMode[4]}, {symbols[5],wrapDSISetDisplayMode,orgDSIMode[5]}, {symbols[6],wrapDSIGetCurrentMode,orgDSIMode[6]}, - {symbols[7],wrapDSIGetOnlineInfo,orgDSIMode[7]}}; + {symbols[7],wrapDSIGetOnlineInfo,orgDSIMode[7]}, + {symbols[8],wrapDSIDDC,orgDSIMode[8]}}; const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size); patcher.clearError();return ready; } @@ -6777,14 +6780,14 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad // observe calls before the fixed-mode owner is bound; do not treat the trace // as admission or invoke any driver/hardware operation while collecting it. void XE::recordDSIClient(IOService *fb,unsigned event,uint64_t argument,uint32_t result) { - if(!callbackXE->adlnOutputWiring.dsi || event>=12 || !fb || + if(!callbackXE->adlnOutputWiring.dsi || event>=13 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1DC))return; DSIModeScope scope(fb);if(!scope.lock)return; - static uint64_t counts[12] {}; + static uint64_t counts[13] {}; const char *keys[] {"WEXDSIClientBind","WEXDSIClientTimings","WEXDSIClientValidate", "WEXDSIClientModes","WEXDSIClientCount","WEXDSIClientInfo","WEXDSIClientSetMode", "WEXDSIClientAttribute","WEXDSIClientConnection","WEXDSIClientPower", - "WEXDSIClientCurrent","WEXDSIClientOnline"}; + "WEXDSIClientCurrent","WEXDSIClientOnline","WEXDSIClientDDC"}; const auto count=__atomic_add_fetch(&counts[event],1ULL,__ATOMIC_RELAXED); uint64_t words[] {1,event,count,argument,result, Display13FBMemory::field(fb,0x1E0),Display13FBMemory::field(fb,0x44DE), @@ -6849,6 +6852,17 @@ bool XE::releaseDSIFixedMode(IOService *fb) { OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; } +// IOGraphics also requests EDID independently of online detection. Stop at +// the shared internal DDC entry, before it acquires AUX power on behalf of +// the built-in panel. DSI exposes its profile through the fixed-mode API. +uint32_t XE::wrapDSIDDC(IOService *fb,uint32_t block,uint8_t *data,uint64_t *length,uint32_t flags,OSObject *path) { + if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); + if(length)*length=0; + recordDSIClient(fb,12,block,kIOReturnUnsupported);return kIOReturnUnsupported; +} + // Native online detection assumes eDP AUX/EDID. A DSI panel has no such // sink. Answer only for the real adopted path and its immutable fixed mode; // returning an empty EDID does not manufacture panel data or touch AUX power. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index bdc4875a..ca6f5d94 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -215,12 +215,13 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; bool display13DSIModeHooksReady {}; - mach_vm_address_t orgDSIMode[8] {}; + mach_vm_address_t orgDSIMode[9] {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); bool bindDSIFixedMode(IOService *,const XELP::DSI::Profile &); static uint32_t setDSIPortMode(OSObject *,uint32_t); + static uint32_t wrapDSIDDC(IOService *,uint32_t,uint8_t *,uint64_t *,uint32_t,OSObject *); static uint32_t wrapDSIGetCurrentMode(IOService *,int32_t *,int32_t *); static uint32_t wrapDSIGetOnlineInfo(IOService *,OSObject *,uint8_t *,uint8_t *); static uint32_t wrapDSISetDetailedTimings(IOService *,OSArray *); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 800b704b..7f6bdcdc 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -7,7 +7,7 @@ source = (root/'WhateverXe/kern_xe.cpp').read_text() owner = source[source.index('struct DSIFixedModeOwner {'):source.index('// AlderLakeN start bypasses')] names = ['recordDSIClient', 'releaseDSIFixedMode', 'bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', - 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation', 'wrapDSIGetCurrentMode', 'wrapDSIGetOnlineInfo'] + 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation', 'wrapDSIGetCurrentMode', 'wrapDSIGetOnlineInfo', 'wrapDSIDDC'] functions = '\n'.join(re.search(r'(?:void|bool|uint32_t) XE::'+name+r'\(.*?\n\}', source, re.S)[0] for name in names) if len(sys.argv) == 2: binary = Path(sys.argv[1]).read_bytes() @@ -16,7 +16,7 @@ offsets = [int(x, 0) for x in re.search(r'offsets\[\]=\{([^}]+)', installer)[1].split(',')] entries = re.search(r'entries\[\]\[16\]=\{(.*?)\};', installer, re.S)[1] entries = [bytes(int(x, 0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}', entries)] - assert len(entries) == len(offsets) == 8 + assert len(entries) == len(offsets) == 9 for offset, entry in zip(offsets, entries): assert binary[offset:offset+16] == entry, hex(offset) @@ -61,7 +61,7 @@ ~IOService(){for(auto i:properties)i.second->release();} }; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *,const void *p,unsigned n){assert(n==128);const auto w=static_cast(p);assert(w[0]==1 && w[1]<12 && w[2]);}}; + void setProperty(const char *,const void *p,unsigned n){assert(n==128);const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} @@ -80,9 +80,10 @@ bool memoryFaulted=false;struct {struct {bool faulted=false;} state;} bridge;} display13FBPower; struct XE { bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; - uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[8]{}; + uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[9]{}; static bool updateReady; static bool dsiNativeUpdateAllowed(IOService *){return updateReady;} + static uint32_t wrapDSIDDC(IOService *,uint32_t,uint8_t *,uint64_t *,uint32_t,OSObject *); static uint32_t wrapDSIGetCurrentMode(IOService *,int32_t *,int32_t *); static uint32_t wrapDSIGetOnlineInfo(IOService *,OSObject *,uint8_t *,uint8_t *); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); @@ -109,7 +110,10 @@ auto timing=static_cast(t); auto info=static_cast(p);*info={}; info->width=geometryOK?timing->horizontalActive:1;info->height=timing->verticalActive;info->depth=2;return 0;} -unsigned nativeCurrentCalls=0,nativeOnlineCalls=0; +unsigned nativeCurrentCalls=0,nativeOnlineCalls=0,nativeDDCCalls=0; +uint32_t nativeDDC(IOService *,uint32_t block,uint8_t *data,uint64_t *length,uint32_t flags,OSObject *path){ + assert(block==2 && flags==1 && path==testPath);++nativeDDCCalls;*length=128;data[0]=42;return 9;} + uint32_t nativeCurrent(IOService *,int32_t *m,int32_t *d){++nativeCurrentCalls;if(m)*m=77;if(d)*d=3;return 7;} uint32_t nativeOnline(IOService *,OSObject *,uint8_t *on,uint8_t *edid){++nativeOnlineCalls;if(on)*on=9;if(edid)edid[0]=42;return 8;} int main(){ @@ -120,7 +124,7 @@ display13FBPower.transcoders.dsi0Pipe=0;xe.adlnOutputWiring.valid=true;xe.adlnOutputWiring.dsi=1; xe.orgDSIMode[0]=reinterpret_cast(publish);xe.orgDSIMode[1]=reinterpret_cast(validate); xe.orgDSIMode[2]=reinterpret_cast(modes);xe.orgDSIMode[3]=reinterpret_cast(count); - xe.orgDSIMode[4]=reinterpret_cast(information); + xe.orgDSIMode[4]=reinterpret_cast(information);xe.orgDSIMode[8]=reinterpret_cast(nativeDDC); xe.orgDSIMode[6]=reinterpret_cast(nativeCurrent);xe.orgDSIMode[7]=reinterpret_cast(nativeOnline); Display13FBMemory::field(peer,0x1DC)=1; Profile p;p.status=ProfileStatus::Valid;p.lanes=4;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; @@ -133,7 +137,10 @@ dataOK=arrayOK=true;display13FBPower.memoryFaulted=false; assert(xe.bindDSIFixedMode(fb,p) && fb->refs==2 && dsiFixedMode.array->refs==2 && dsiFixedMode.data->refs==2); // Adopted DSI presence does not call native EDID/AUX or alter the EDID identity. - uint8_t online=0,edid[128];memset(edid,0xCC,sizeof(edid)); + uint8_t online=0,edid[128];memset(edid,0xCC,sizeof(edid));uint64_t length=128; + assert(XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr)==kIOReturnUnsupported && !length && edid[0]==0xCC && !nativeDDCCalls); + assert(XE::wrapDSIDDC(peer,2,edid,&length,1,testPath)==9 && length==128 && nativeDDCCalls==1); + xe.adlnOutputWiring.dsi=0;assert(XE::wrapDSIDDC(fb,2,edid,&length,1,testPath)==9 && nativeDDCCalls==2);xe.adlnOutputWiring.dsi=1; assert(!XE::wrapDSIGetOnlineInfo(fb,peer,&online,edid) && online==1 && !nativeOnlineCalls); for(auto b:edid)assert(b==0); assert(XE::wrapDSIGetOnlineInfo(fb,fb,&online,edid)==kIOReturnNotReady && !online); From 157af19eb2d77fbd4529b85146191803621ea029 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 02:59:46 +0900 Subject: [PATCH 050/139] Record the first native DSI AUX power caller --- WhateverXe/kern_xe.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 36e0d012..4533fee0 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11864,7 +11864,27 @@ void XE::wrapAlderLakeNPowerGetPG(void *p,uint32_t d) { adlnPowerReference(p,1,d void XE::wrapAlderLakeNPowerPutPG(void *p,uint32_t d) { adlnPowerReference(p,2,d); } void XE::wrapAlderLakeNPowerGetDDI(void *p,uint32_t d) { adlnPowerReference(p,3,d); } void XE::wrapAlderLakeNPowerPutDDI(void *p,uint32_t d) { adlnPowerReference(p,4,d); } -void XE::wrapAlderLakeNPowerGetAUX(void *p,uint32_t d) { adlnPowerReference(p,5,d); } +void XE::wrapAlderLakeNPowerGetAUX(void *p,uint32_t d) { + // Capture only the first DSI AUX-A caller. Port's forwarding entry tail + // calls this method, so frame zero also identifies its native caller. + static bool captured=false; + const bool capture=callbackXE && callbackXE->adlnOutputWiring.dsi==1 && d==0 && + __sync_bool_compare_and_swap(&captured,false,true); + uint64_t record[6] {1,d,0,UINT64_MAX,0,0}; + if(capture) { + const auto caller=reinterpret_cast(__builtin_return_address(0)); + const auto base=callbackXE->display13DPImage; + record[2]=base && caller>=base && caller-base<0xC8000; + if(record[2])record[3]=caller-base; + record[4]=p==display13FBPower.power; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIFirstAUXCaller",record,sizeof(record)); + } + const bool complete=adlnPowerReference(p,5,d); + if(capture) { + record[5]=complete ? 1 : 2; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIFirstAUXCaller",record,sizeof(record)); + } +} void XE::wrapAlderLakeNPowerPutAUX(void *p,uint32_t d) { adlnPowerReference(p,6,d); } IOService *XE::wrapAlderLakeNRejectedAcceleratorProbe(IOService *,IOService *,SInt32 *) { From f915265c66f9212aa20ce4cc6f3f6fbba98b7b6e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:02:43 +0900 Subject: [PATCH 051/139] Exclude DSI paths from native AUX transport admission --- WhateverXe/kern_xe.cpp | 3 +++ tests/display13_aux_fixture.cpp | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4533fee0..16304510 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -8065,6 +8065,7 @@ bool XE::display13AUXReady(void *port,bool command,bool retained) { !Display13FBMemory::field(c.controller,0xC30))return false; auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || config[0]>=7 || config[0]==2 || + (s->adlnOutputWiring.dsi & (1U<(p,0x120)!=config[0] || (config[0]<3 && Display13FBMemory::field(p,0x550)>1) || Display13FBMemory::field(p,0x578)!=c.access || @@ -8075,6 +8076,8 @@ bool XE::display13AUXReady(void *port,bool command,bool retained) { if(!(s->adlnOutputWiring.dp&(1U<(p,0x594+4*n)!=ctl+4*n)return false; diff --git a/tests/display13_aux_fixture.cpp b/tests/display13_aux_fixture.cpp index 35a7ce7d..fba40ad3 100644 --- a/tests/display13_aux_fixture.cpp +++ b/tests/display13_aux_fixture.cpp @@ -70,6 +70,19 @@ int main(int argc,char **argv){ for(unsigned at:{0x1CEABU,0x1D19CU})redirect(image,at,0x300040,reinterpret_cast(stackFail)); } unsigned cases=0; + // A selected DSI path must never reach native AUX get/put or HAL, even if + // its port object has the donor's AUX-A register coordinates initialized. + {Fixture f(0,0,0);Map map;auxSetup(f,map,0,false,1); + state.adlnOutputWiring.dp&=~1U;state.adlnOutputWiring.panel=0;state.adlnOutputWiring.dsi=1; + assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,state.adlnOutputWiring)); + const auto reads=map.reads.size(),writes=map.writes.size();uint32_t data[5]{0xabcdef01}; + assert(XE::wrapDisplay13ReadAUX(f.port,0x100,data,4)==kIOReturnNotReady); + assert(XE::wrapDisplay13WriteAUX(f.port,0x100,data,4)==kIOReturnNotReady); + assert(XE::wrapDisplay13I2CAUX(f.port,0,data,4,0)==kIOReturnNotReady); + assert(XE::wrapDisplay13RunAUX(f.port,0,data,4,false)==kIOReturnNotReady); + assert(!auxCalls && data[0]==0xabcdef01 && map.reads.size()==reads && map.writes.size()==writes); + assert(!display13FBPower.bridge.state.faulted && !display13FBPower.bridge.nativeAUXRefs(0)); + } // Exact production completion checks: a disconnect during AUX is a failed // transaction, not a permanently faulted bridge. Lost ownership still is. for(unsigned ddi=3;ddi<=6;++ddi)for(bool lost:{false,true})for(unsigned nativeError:{0U,0xE00002BCU}) { From 1f513b934c2fe30a5c29b4168d29b8564fb6b388 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:06:49 +0900 Subject: [PATCH 052/139] Keep unpublished DSI apertures from faulting shared memory --- WhateverXe/kern_xe.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 16304510..692b0b53 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9962,6 +9962,14 @@ IOMemoryDescriptor *XE::wrapAlderLakeNAperture(IOService *fb,int apertureIndex) const auto layout=Display13FBMemory::poolLayout(c.controller,0x1C20,0x10000000); backing=adlnInspectSysMem(static_cast(c.controller),fb,slot,layout).status==Display13FBSysMem::Status::Complete; } + // IOGraphics may ask before the first DSI mode transaction has set + // dimensions. The validated pool is still owned, but no aperture was + // published. Return unavailable without poisoning that shared owner. + // Nonzero invalid ranges and lost backing retain the fault path below. + if(!base && !bytes && parent && backing && !slot && callbackXE->adlnOutputWiring.dsi==1 && + dsiModeTarget(fb) && !dsiPendingTarget(fb) && !callbackXE->dsiStopAttempted) { + IOLockUnlock(c.lock);return nullptr; + } if(!parent || !backing || !Display13FBMemory::apertureRange(c.pipe[pipe].memory.pool,base,bytes,parent->getLength()))parent=nullptr; if(parent)parent->retain();else { From d8fab38d8b0ebd7bfc755e05ec8496d6bc35852d Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:13:19 +0900 Subject: [PATCH 053/139] Exercise native DSI backing allocation from the first fixed mode request --- WhateverXe/kern_xe.cpp | 35 ++++++++++++++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 3 +++ tests/dsi_backing_adapter.py | 22 +++++++++++++++++++--- tests/dsi_update_dispatch.py | 11 ++++++++++- 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 692b0b53..8b9b7cec 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5375,11 +5375,14 @@ uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { } bool XE::prepareDSINativeBacking(IOService *fb) { + dsiBackingStage=0; if(adlnOutputWiring.dsi!=1 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !dsiModeTarget(fb) || !dsiBackingHooksReady || !dsiProfile.valid())return false; + dsiBackingStage=1; DSIModeScope scope(fb);if(!scope.lock || !dsiNativeUpdateAllowed(fb))return false; auto &o=dsiPendingBacking;auto &c=display13FBPower; if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return false; + dsiBackingStage=2; IOLockLock(c.lock); auto controller=static_cast(c.controller); auto owner=reinterpret_cast(display13FBExpectedOwner(controller)); @@ -5396,15 +5399,18 @@ bool XE::prepareDSINativeBacking(IOService *fb) { display13FBInspectMemory(fb,true,0,false).status==Display13FBMemory::Status::Complete; if(ready){fb->retain();owner->retain();} IOLockUnlock(c.lock);if(!ready)return false; + dsiBackingStage=3; auto record=static_cast(IOMalloc(sizeof(request))); if(!record){owner->release();fb->release();return false;} memcpy(record,&request,sizeof(request));o.accelerator=owner;o.record=record; __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.allocating=true; __atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); + dsiBackingStage=4; uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; // Native callPlatformFunction takes the GPU lock. Never hold c.lock here. const auto result=owner->callPlatformFunction("allocscanmem",false,record,&index,&flags,nullptr); o.allocating=false; + dsiBackingStage=5; if(result || !o.allocated)return false; // Retain partial state for discard. IOLockLock(c.lock); bool verified=false; @@ -5424,7 +5430,32 @@ bool XE::prepareDSINativeBacking(IOService *fb) { (c.map->getLength()-0x800000)/8*4096,xelpMemory,record); verified=pipeBound() && XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); } - IOLockUnlock(c.lock);return verified; + IOLockUnlock(c.lock);if(verified)dsiBackingStage=6;return verified; +} + +// P2's first real fixed-mode request exercises allocation and unarmed cleanup +// while the native FB mutex is held. Do this once after allocation starts; +// an unavailable accelerator may be retried by a later mode request. Never +// publish FB backing or arm SURF here, nor report the mode as established. +void XE::probeDSINativeBacking(IOService *fb) { + if(dsiBackingAttempted)return; + dsiBackingAttempted=true; + uint64_t receipt[16] {1,1}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); + const bool verified=prepareDSINativeBacking(fb); + auto &o=dsiPendingBacking; + const bool pending=dsiPendingTarget(fb); + receipt[1]=2;receipt[2]=dsiBackingStage;receipt[3]=verified;receipt[4]=pending; + if(pending) { + receipt[5]=o.allocated;receipt[6]=o.nativeResult;receipt[7]=static_cast(o.proof.status); + receipt[8]=o.proof.bytes;receipt[9]=o.proof.pitch;receipt[10]=o.proof.gpu;receipt[11]=o.proof.verifiedPages; + receipt[12]=o.pinnedMap && o.pinnedMemory; + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); + if(pending)receipt[13]=discardDSINativeBacking(fb); + receipt[14]=dsiPendingTarget(fb);receipt[15]=pending && !receipt[13];receipt[1]=3; + if(!pending)dsiBackingAttempted=false; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); } // Only a never-armed pending surface may take this path. After SURF is armed, @@ -7073,6 +7104,8 @@ uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, Display13FBMemory::field(fb,0x420C)}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); + if(mode==XELP::DSI::AppleAdapter::FixedModeID && depth==0 && dsiModeTarget(fb) && + dsiNativeUpdateAllowed(fb))callbackXE->probeDSINativeBacking(fb); return kIOReturnNotReady; } } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ca6f5d94..ee095bb2 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -249,6 +249,9 @@ class XE { bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool prepareDSINativeBacking(IOService *); bool discardDSINativeBacking(IOService *); + void probeDSINativeBacking(IOService *); + bool dsiBackingAttempted {}; + uint32_t dsiBackingStage {}; static uint32_t wrapDSIBackingAllocate(OSObject *,void *,uint32_t); static uint32_t wrapDSIBackingFree(OSObject *,void *,uint32_t); diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py index 74c974ee..5a2fbece 100644 --- a/tests/dsi_backing_adapter.py +++ b/tests/dsi_backing_adapter.py @@ -4,8 +4,8 @@ import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1] s=(root/'WhateverXe/kern_xe.cpp').read_text() -functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in - ['wrapDSIBackingAllocate','wrapDSIBackingFree','prepareDSINativeBacking','discardDSINativeBacking']) +functions='\n'.join(re.search(r'(?:bool|uint32_t|void) XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in + ['wrapDSIBackingAllocate','wrapDSIBackingFree','prepareDSINativeBacking','discardDSINativeBacking','probeDSINativeBacking']) state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' @@ -31,7 +31,7 @@ void IOLockLock(IOLock*){assert(fbDepth && !ownerDepth && !gpuDepth);++ownerDepth;} void IOLockUnlock(IOLock*){assert(ownerDepth==1);--ownerDepth;} void *IOMalloc(size_t n){assert(!ownerDepth && !gpuDepth);++buffers;return calloc(1,n);} -void IOFree(void *p,size_t n){assert(!fbDepth && !ownerDepth && !gpuDepth && n==64 && buffers);--buffers;free(p);} +void IOFree(void *p,size_t n){assert(!ownerDepth && !gpuDepth && n==64 && buffers);--buffers;free(p);} struct OSObject { alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes),refs=1;const char *kind="object"; OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} @@ -39,6 +39,9 @@ uint32_t callPlatformFunction(const char*,bool,void*,void*,void*,void*); }; using IOService=OSObject; +uint64_t receipt[16]{}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry root;return &root;} + void setProperty(const char *name,const void *p,unsigned n){assert(!strcmp(name,"WEXDSIBackingTrial") && n==sizeof(receipt));memcpy(receipt,p,n);}}; OSObject nativeMapping,nativeMemory; IOService *modeFB=nullptr;bool dsiModeTarget(IOService *fb){return fb && modeFB==fb;} struct DSIModeScope {IORecursiveLock *lock;explicit DSIModeScope(IOService *fb):lock(field(fb,0x4220)){if(lock)++fbDepth;} @@ -82,6 +85,7 @@ static uint32_t wrapDSIBackingAllocate(OSObject*,void*,uint32_t); static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); bool prepareDSINativeBacking(IOService*);bool discardDSINativeBacking(IOService*); + void probeDSINativeBacking(IOService*);bool dsiBackingAttempted=false;uint32_t dsiBackingStage=0; }*callbackXE; uint32_t OSObject::callPlatformFunction(const char *name,bool wait,void *record,void *index,void *flags,void *extra){ assert(fbDepth && !ownerDepth && !gpuDepth && !wait && !extra && *static_cast(index)==0); @@ -109,6 +113,18 @@ void discard(){assert(xe.discardDSINativeBacking(&fb));} }; int main(){ + {Case c;const auto a=allocCalls,f=freeCalls;DSIModeScope scope(&c.fb); + available=false;c.xe.probeDSINativeBacking(&c.fb);assert(!c.xe.dsiBackingAttempted && allocCalls==a && receipt[2]==1); + available=true;c.xe.probeDSINativeBacking(&c.fb); + assert(c.xe.dsiBackingAttempted && allocCalls==a+1 && freeCalls==f+1 && !dsiPendingTarget(&c.fb)); + assert(receipt[1]==3 && receipt[2]==6 && receipt[3] && receipt[11]==2040 && receipt[13] && !receipt[14]); + c.xe.probeDSINativeBacking(&c.fb);assert(allocCalls==a+1 && freeCalls==f+1); + assert(!field(&c.fb,0x4348));} + {Case c;failFree=true;const auto a=allocCalls;{DSIModeScope scope(&c.fb);c.xe.probeDSINativeBacking(&c.fb); + assert(c.xe.dsiBackingAttempted && receipt[14] && receipt[15] && !receipt[13]); + c.xe.probeDSINativeBacking(&c.fb);assert(allocCalls==a+1);} + failFree=false;c.discard();} + {Case c;NativeBacking::Record r;assert(NativeBacking::request(c.xe.dsiProfile,3,r)); gpuDepth=1;expectedFlags=4;assert(!XE::wrapDSIBackingAllocate(&c.pipe,&r,4)); expectedFlags=2;assert(!XE::wrapDSIBackingFree(&c.pipe,&r,2)); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 9e505b45..fab3a787 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -19,6 +19,7 @@ for offset,entry in zip(offsets,entries):assert binary[offset:offset+width]==entry,hex(offset) code=r''' #include "kern_display13_fb_memory.hpp" +#include "kern_dsi_apple_timing.hpp" #include "kern_display13_transcoder.hpp" #include #include @@ -33,7 +34,7 @@ }}; IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}using IOLock=int; struct IORecursiveLock {unsigned depth=0;}; -unsigned owner=0,modeCalls=0,armCalls=0;IORecursiveLock fbLock;bool expectDSI=false,endingWriter=false; +unsigned owner=0,modeCalls=0,armCalls=0,backingCalls=0;IORecursiveLock fbLock;bool expectDSI=false,endingWriter=false; void IOLockLock(IOLock*){assert((fbLock.depth || endingWriter) && !owner);++owner;}void IOLockUnlock(IOLock*){assert(owner==1);--owner;} void IORecursiveLockLock(IORecursiveLock *p){assert(!owner);++p->depth;} void IORecursiveLockUnlock(IORecursiveLock *p){assert(!owner && p->depth);--p->depth;} @@ -86,6 +87,7 @@ static uint32_t wrapDSICursorImage(IOService*,IOService*,void*); static uint32_t wrapDSICursorState(IOService*,IOService*,int32_t,int32_t,bool,bool); static uint32_t wrapDSISetDisplayMode(IOService*,int32_t,int32_t); + void probeDSINativeBacking(IOService*){assert(fbLock.depth && !owner);++backingCalls;} static bool dsiNativeUpdateAllowed(IOService*); static bool dsiSharedWriterStopped(IOService*,unsigned); static void wrapDisplay13WMArmUpdate(IOService*); @@ -176,6 +178,13 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ + {Case c;auto before=backingCalls; + assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,0)==kIOReturnNotReady && backingCalls==before+1); + assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,2)==kIOReturnNotReady && backingCalls==before+1); + assert(XE::wrapDSISetDisplayMode(&c.fb,0x10001,0)==kIOReturnNotReady && backingCalls==before+1); + xe.dsiStopAttempted=true; + assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,0)==kIOReturnNotReady && backingCalls==before+1);} + // Public mode rejection precedes SaveNVRAMMode, disable, timing/backing and // native notifications; even the selected fixed mode is not yet admitted. {Case c;auto before=modeCalls;assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,2)==kIOReturnNotReady); From 7ae1b1a30437bc768c4b3d8502b96a647bb04290 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:24:05 +0900 Subject: [PATCH 054/139] Prepare DSI identification images under native GPU serialization --- WhateverXe/kern_dsi_test_image.hpp | 54 ++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 67 +++++++++++++++++++++++++++--- WhateverXe/kern_xe.hpp | 2 + tests/dsi_backing_adapter.py | 37 +++++++++++++---- tests/dsi_test_image.cpp | 34 +++++++++++++++ 5 files changed, 181 insertions(+), 13 deletions(-) create mode 100644 WhateverXe/kern_dsi_test_image.hpp create mode 100644 tests/dsi_test_image.cpp diff --git a/WhateverXe/kern_dsi_test_image.hpp b/WhateverXe/kern_dsi_test_image.hpp new file mode 100644 index 00000000..1f8cae5b --- /dev/null +++ b/WhateverXe/kern_dsi_test_image.hpp @@ -0,0 +1,54 @@ +// P2 identification image for an uncompressed Y-tiled RGB8888 allocation. +// Fill every 4 KiB tile with one color, avoiding assumptions about its internal +// pixel swizzle. The last tiles include the padding beyond the visible mode. +#ifndef KERN_DSI_TEST_IMAGE_HPP +#define KERN_DSI_TEST_IMAGE_HPP +#include +#include +namespace XELP { namespace DSI { namespace TestImage { +struct Layout { + uint32_t columns {},rows {};uint64_t bytes {}; + bool valid() const {return columns>=2 && columns<=256 && rows>=2 && rows<=256 && + bytes==uint64_t(columns)*rows*4096;} +}; +inline Layout layout(uint32_t width,uint32_t height,uint32_t pitch,uint64_t bytes) { + Layout result; + if(width<33 || height<33 || width>8192 || height>8192 || pitch!=((width*4+127)&~127U))return result; + result={(width+31)/32,(height+31)/32,bytes};return result.valid()?result:Layout{}; +} +// Phase changes all cells. Red/green vertical edges and blue/yellow horizontal +// edges identify the portrait orientation; four distinct corners identify crop. +inline uint32_t color(const Layout &l,uint32_t x,uint32_t y,unsigned phase) { + uint32_t rgb=(((x/3+y/3)&1)?0x204060U:0xC08020U); + if(y==0)rgb=0x2040FF; + if(y+1==l.rows)rgb=0xFFFF20; + if(x==0)rgb=0xFF2020; + if(x+1==l.columns)rgb=0x20FF20; + if(x==0 && y==0)rgb=0xFFFFFF; + if(x+1==l.columns && y==0)rgb=0xFF20FF; + if(x==0 && y+1==l.rows)rgb=0x20FFFF; + if(x+1==l.columns && y+1==l.rows)rgb=0x808080; + return 0xFF000000U|(phase ? rgb^0xFFFFFFU : rgb); +} +struct Result {bool complete {};uint64_t bytes {};uint32_t digest {2166136261U};}; +// The adapter maps the verified SysMemory descriptor, owns GPU serialization, +// and flushes CPU cache lines before scanout. This core never owns a mapping. +inline Result paint(volatile uint32_t *memory,uint64_t length,const Layout &l,unsigned phase) { + Result r;if(!memory || (reinterpret_cast(memory)&3) || !l.valid() || length1)return r; + for(uint32_t y=0;y(r.map);o.pinnedMemory=reinterpret_cast(r.memory); o.pinnedMap->retain();o.pinnedMemory->retain(); } - return o.allocated ? result : kIOReturnNotReady; + return o.allocated && callbackXE->validateDSINativeBacking() && + callbackXE->paintDSINativeBacking(0) ? result : kIOReturnNotReady; } uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { auto &o=dsiPendingBacking; @@ -5413,6 +5416,30 @@ bool XE::prepareDSINativeBacking(IOService *fb) { dsiBackingStage=5; if(result || !o.allocated)return false; // Retain partial state for discard. IOLockLock(c.lock); + const bool verified=c.controller==controller && adlnSharedBinding(controller,owner) && + o.pipe && o.pinnedMap && o.pinnedMemory && + Display13FBMemory::field(o.pipe,0x88)==owner && + Display13FBMemory::field(o.pipe,0x1298)==record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==record->map && + !Display13FBMemory::field(o.pipe,0x12A8) && + reserve==__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE) && + reserve==Display13FBMemory::field(owner,0x13C0) && + !c.memoryFaulted && !c.clockFaulted && + XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); + IOLockUnlock(c.lock);if(verified)dsiBackingStage=6;return verified; +} + +// Called by allocateScanoutMemory's routed entry while the native display +// machine still holds the GPU lock. Keep object/PTE validation in that scope; +// retained map/memory references alone do not serialize GGTT replacement. +bool XE::validateDSINativeBacking() { + auto &o=dsiPendingBacking;auto &c=display13FBPower; + auto fb=o.framebuffer;auto owner=o.accelerator;auto record=o.record; + if(!fb || !owner || !record || !o.allocating || !o.allocated || !o.pinnedMap || !o.pinnedMemory || + o.thread!=reinterpret_cast(current_thread()) || !c.lock)return false; + auto controller=static_cast(c.controller); + const auto reserve=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); + IOLockLock(c.lock); bool verified=false; const auto pipeBound=[&]() { return o.pipe && Display13FBMemory::field(o.pipe,0x88)==owner && @@ -5422,15 +5449,45 @@ bool XE::prepareDSINativeBacking(IOService *fb) { }; if(pipeBound() && c.controller==controller && c.map && c.map->getVirtualAddress() && c.map->getLength()==0x1000000 && adlnSharedBinding(controller,owner) && !c.memoryFaulted && !c.clockFaulted && - reserve==__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE) && - reserve==Display13FBMemory::field(owner,0x13C0)) { + reserve && reserve==Display13FBMemory::field(owner,0x13C0)) { Display13SurfaceIO io {c.map,0}; o.proof=Display13FBMemory::inspectPendingSurface(io,controller,controller->getMetaClass()->getClassSize(), fb,fb->getMetaClass()->getClassSize(),reinterpret_cast(owner),reserve, (c.map->getLength()-0x800000)/8*4096,xelpMemory,record); verified=pipeBound() && XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); } - IOLockUnlock(c.lock);if(verified)dsiBackingStage=6;return verified; + IOLockUnlock(c.lock);return verified; +} + +// Mapping and writes remain inside the same native GPU lock as the allocator +// and full PTE proof. The GPU map is IGAccelMemoryMap, not IOMemoryMap: create +// a separate CPU mapping from its validated SysMemory descriptor. +bool XE::paintDSINativeBacking(unsigned phase) { + auto &o=dsiPendingBacking; + o.image={}; + if(!o.allocating || !o.allocated || o.armed || !o.record || !o.pinnedMemory || + o.thread!=reinterpret_cast(current_thread()) || + !XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof))return false; + auto descriptor=OSDynamicCast(IOMemoryDescriptor,Display13FBMemory::field(o.pinnedMemory,0xD0)); + if(!descriptor || descriptor->getLength()(o.proof.pitch),o.proof.bytes); + if(!shape.valid())return false; + auto map=descriptor->createMappingInTask(kernel_task,0,kIOMapAnywhere,0,o.proof.bytes); + if(!map)return false; + const auto address=map->getVirtualAddress(); + if(address && map->getLength()==o.proof.bytes && !(address&4095)) { + o.image=XELP::DSI::TestImage::paint(reinterpret_cast(address),map->getLength(),shape,phase); + if(o.image.complete) { + for(uint64_t offset=0;offsetrelease(); + uint64_t receipt[] {1,phase,o.image.complete,o.image.bytes,o.image.digest,o.proof.gpu}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIImage",receipt,sizeof(receipt)); + return o.image.complete; } // P2's first real fixed-mode request exercises allocation and unarmed cleanup @@ -5480,7 +5537,7 @@ bool XE::discardDSINativeBacking(IOService *fb) { owner=o.accelerator;pipe=o.pipe;record=o.record;mapping=o.pinnedMap;memory=o.pinnedMemory; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); __atomic_store_n(&o.pipe,static_cast(nullptr),__ATOMIC_RELEASE); - o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=false;o.nativeResult=0;o.proof={}; + o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=false;o.nativeResult=0;o.proof={};o.image={}; } IOFree(record,sizeof(XELP::DSI::NativeBacking::Record)); if(mapping)mapping->release();if(memory)memory->release();if(pipe)pipe->release();owner->release();fb->release();return true; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ee095bb2..7beb576f 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -248,6 +248,8 @@ class XE { mach_vm_address_t orgDSIBacking[2] {}; bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool prepareDSINativeBacking(IOService *); + bool validateDSINativeBacking(); + bool paintDSINativeBacking(unsigned); bool discardDSINativeBacking(IOService *); void probeDSINativeBacking(IOService *); bool dsiBackingAttempted {}; diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py index 5a2fbece..aa7a3099 100644 --- a/tests/dsi_backing_adapter.py +++ b/tests/dsi_backing_adapter.py @@ -5,7 +5,7 @@ root=Path(__file__).resolve().parents[1] s=(root/'WhateverXe/kern_xe.cpp').read_text() functions='\n'.join(re.search(r'(?:bool|uint32_t|void) XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in - ['wrapDSIBackingAllocate','wrapDSIBackingFree','prepareDSINativeBacking','discardDSINativeBacking','probeDSINativeBacking']) + ['wrapDSIBackingAllocate','wrapDSIBackingFree','prepareDSINativeBacking','validateDSINativeBacking','paintDSINativeBacking','discardDSINativeBacking','probeDSINativeBacking']) state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' @@ -17,6 +17,7 @@ assert 'routeMultipleShort' in installer code=r''' #include "kern_dsi_native_backing.hpp" +#include "kern_dsi_test_image.hpp" #include #include #include @@ -28,21 +29,35 @@ unsigned fbDepth=0,ownerDepth=0,gpuDepth=0,allocCalls=0,freeCalls=0,buffers=0,expectedFlags=0; uintptr_t thread=1;void *current_thread(){return reinterpret_cast(thread);} struct IOLock{};struct IORecursiveLock{}; -void IOLockLock(IOLock*){assert(fbDepth && !ownerDepth && !gpuDepth);++ownerDepth;} +void IOLockLock(IOLock*){assert(fbDepth && !ownerDepth && gpuDepth<=1);++ownerDepth;} void IOLockUnlock(IOLock*){assert(ownerDepth==1);--ownerDepth;} void *IOMalloc(size_t n){assert(!ownerDepth && !gpuDepth);++buffers;return calloc(1,n);} void IOFree(void *p,size_t n){assert(!ownerDepth && !gpuDepth && n==64 && buffers);--buffers;free(p);} +alignas(4096) uint32_t cpuImage[8355840/4]; +bool failCPUMap=false;unsigned cpuMaps=0; +constexpr unsigned kIOMapAnywhere=1;void *kernel_task=reinterpret_cast(1); +struct CPUMap { + uintptr_t getVirtualAddress(){return reinterpret_cast(cpuImage);} + uint64_t getLength(){return sizeof(cpuImage);} + void release(){assert(gpuDepth==1 && !ownerDepth && cpuMaps==1);--cpuMaps;} +}cpuMap; struct OSObject { alignas(8) uint8_t bytes[0x4B90]{};unsigned size=sizeof(bytes),refs=1;const char *kind="object"; OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} bool metaCast(const char *name){return !strcmp(kind,name);}void retain(){++refs;}void release(){assert(refs>1);--refs;} uint32_t callPlatformFunction(const char*,bool,void*,void*,void*,void*); + uint64_t getLength(){return sizeof(cpuImage);} + CPUMap *createMappingInTask(void *task,uintptr_t address,unsigned options,uint64_t offset,uint64_t bytes){ + assert(gpuDepth==1 && fbDepth && !ownerDepth && !cpuMaps && task==kernel_task && !address && options==kIOMapAnywhere && !offset && bytes==sizeof(cpuImage)); + if(failCPUMap)return nullptr;++cpuMaps;return &cpuMap;} + }; -using IOService=OSObject; +using IOService=OSObject;using IOMemoryDescriptor=OSObject; +#define OSDynamicCast(T,p) static_cast(p) uint64_t receipt[16]{}; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry root;return &root;} - void setProperty(const char *name,const void *p,unsigned n){assert(!strcmp(name,"WEXDSIBackingTrial") && n==sizeof(receipt));memcpy(receipt,p,n);}}; -OSObject nativeMapping,nativeMemory; + void setProperty(const char *name,const void *p,unsigned n){if(!strcmp(name,"WEXDSIImage")){assert(n==48);return;}assert(!strcmp(name,"WEXDSIBackingTrial") && n==sizeof(receipt));memcpy(receipt,p,n);}}; +OSObject nativeMapping,nativeMemory,nativeDescriptor; IOService *modeFB=nullptr;bool dsiModeTarget(IOService *fb){return fb && modeFB==fb;} struct DSIModeScope {IORecursiveLock *lock;explicit DSIModeScope(IOService *fb):lock(field(fb,0x4220)){if(lock)++fbDepth;} ~DSIModeScope(){assert(!ownerDepth && !gpuDepth);if(lock){assert(fbDepth);--fbDepth;}}}; @@ -61,7 +76,7 @@ Display13FBMemory::Readout display13FBInspectMemory(IOService*,bool,unsigned,bool){assert(ownerDepth);Display13FBMemory::Readout r;r.status=source?Status::Complete:Status::PTE;return r;} struct Display13SurfaceIO { Map *map;unsigned pipe;bool memoryReady(uintptr_t m,uintptr_t mem,uintptr_t owner,uint64_t n){ - assert(ownerDepth && !gpuDepth && map);return m==reinterpret_cast(&nativeMapping) && mem==reinterpret_cast(&nativeMemory) && owner && n==8355840;} + assert(ownerDepth && gpuDepth==1 && map);return m==reinterpret_cast(&nativeMapping) && mem==reinterpret_cast(&nativeMemory) && owner && n==8355840;} uint64_t physical(uintptr_t,uint64_t off,uint64_t &span){span=4096;return 0x180000000ULL+off;} uint64_t readPTE(uint32_t reg){const uint64_t off=(uint64_t(reg)-0x800000)*512-0x10000000; return (0x180000000ULL+off+(badPTE?4096:0))|0x99;} @@ -84,7 +99,8 @@ static bool dsiNativeUpdateAllowed(IOService *fb){assert(fbDepth);return available && !dsiPendingTarget(fb);} static uint32_t wrapDSIBackingAllocate(OSObject*,void*,uint32_t); static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); - bool prepareDSINativeBacking(IOService*);bool discardDSINativeBacking(IOService*); + bool paintDSINativeBacking(unsigned); + bool prepareDSINativeBacking(IOService*);bool validateDSINativeBacking();bool discardDSINativeBacking(IOService*); void probeDSINativeBacking(IOService*);bool dsiBackingAttempted=false;uint32_t dsiBackingStage=0; }*callbackXE; uint32_t OSObject::callPlatformFunction(const char *name,bool wait,void *record,void *index,void *flags,void *extra){ @@ -98,7 +114,7 @@ struct Case { IOService fb,owner,controller,machine,pipe,other;IOLock lock;IORecursiveLock fbLock;Map map;XE xe; Case(){ - assert(!dsiPendingBacking.framebuffer && !buffers);callbackXE=&xe;modeFB=&fb;nativePipe=&pipe;alternatePipe=&other; + assert(!dsiPendingBacking.framebuffer && !buffers && !cpuMaps);failCPUMap=false;put(&nativeMemory,0xD0,&nativeDescriptor);callbackXE=&xe;modeFB=&fb;nativePipe=&pipe;alternatePipe=&other; binding=source=available=true;badPTE=failAllocate=failFree=dropMap=changePipe=detachAfterAllocate=false;thread=1; display13FBPower={&lock,&controller,&map};display13FBGTT.sharedPoolPrefix=0x6192000; xe.dsiProfile.status=ProfileStatus::Valid;xe.dsiProfile.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; @@ -113,6 +129,11 @@ void discard(){assert(xe.discardDSINativeBacking(&fb));} }; int main(){ + {Case c;failCPUMap=true;assert(!c.xe.prepareDSINativeBacking(&c.fb));assert(!dsiPendingBacking.image.complete && !cpuMaps);c.discard();} + {Case c;assert(c.xe.prepareDSINativeBacking(&c.fb)); + assert(dsiPendingBacking.image.complete && dsiPendingBacking.image.bytes==sizeof(cpuImage) && dsiPendingBacking.image.digest==0xf89939c5 && !cpuMaps); + c.discard();assert(!dsiPendingBacking.image.complete);} + {Case c;const auto a=allocCalls,f=freeCalls;DSIModeScope scope(&c.fb); available=false;c.xe.probeDSINativeBacking(&c.fb);assert(!c.xe.dsiBackingAttempted && allocCalls==a && receipt[2]==1); available=true;c.xe.probeDSINativeBacking(&c.fb); diff --git a/tests/dsi_test_image.cpp b/tests/dsi_test_image.cpp new file mode 100644 index 00000000..f27a5375 --- /dev/null +++ b/tests/dsi_test_image.cpp @@ -0,0 +1,34 @@ +#include "kern_dsi_test_image.hpp" +#include "kern_display13_fb_memory.hpp" +#include +#include +#include +using namespace XELP::DSI::TestImage; +int main() { + const auto l=layout(1080,1920,4352,8355840); + assert(l.valid() && l.columns==34 && l.rows==60); + std::vector memory(l.bytes/4+2,0x12345678); + uint32_t digests[2]{}; + for(unsigned phase=0;phase<2;++phase) { + const auto result=paint(memory.data()+1,l.bytes,l,phase); + assert(result.complete && result.bytes==l.bytes);digests[phase]=result.digest; + assert(memory.front()==0x12345678 && memory.back()==0x12345678); + // Cross-check tile addressing against the existing scanout sampler; + // inspect both tile extremes and every tile at the visible edges. + for(unsigned y=0;y<1920;y+=32)for(unsigned x=0;x<1080;x+=32) { + uint64_t offset=0; + assert(Display13FBMemory::scanoutSampleOffset(true,34,1080,1920,0,x,y,offset)); + const auto expected=color(l,x/32,y/32,phase); + for(unsigned i=0;i<1024;++i)assert(memory[1+offset/4+i]==expected); + } + } + assert(digests[0]!=digests[1]); + const auto unchanged=memory; + assert(!paint(memory.data()+1,l.bytes-4,l,0).complete && memory==unchanged); + assert(!paint(memory.data()+1,l.bytes,l,2).complete && memory==unchanged); + assert(!paint(nullptr,l.bytes,l,0).complete); + assert(!layout(1080,1920,4320,8355840).valid()); + assert(!layout(1080,1920,4352,8351744).valid()); + assert(!layout(UINT32_MAX,1920,4352,8355840).valid()); + printf("PASS DSI full allocation image: 2040 tiles, edges, two phases, padding and bounds; digests %08x/%08x\n",digests[0],digests[1]); +} From 08a0adae0dbe8e48656d14b439ddc6cd45f2d15a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:35:30 +0900 Subject: [PATCH 055/139] Switch the adopted DSI stream to a retained identification surface --- WhateverXe/kern_xe.cpp | 167 ++++++++++++++++++++++++++++++++--- WhateverXe/kern_xe.hpp | 5 ++ tests/dsi_backing_adapter.py | 5 +- tests/dsi_quiesce_adapter.py | 16 +++- tests/dsi_surface_adapter.py | 92 +++++++++++++++++++ tools/test.py | 1 + 6 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 tests/dsi_surface_adapter.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2e2b29e6..0d8ba157 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5355,7 +5355,7 @@ uint32_t XE::wrapDSIBackingAllocate(OSObject *pipe,void *record,uint32_t flags) o.pinnedMap->retain();o.pinnedMemory->retain(); } return o.allocated && callbackXE->validateDSINativeBacking() && - callbackXE->paintDSINativeBacking(0) ? result : kIOReturnNotReady; + callbackXE->paintDSINativeBacking(0) && callbackXE->switchDSINativeSurface() ? result : kIOReturnNotReady; } uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { auto &o=dsiPendingBacking; @@ -5465,7 +5465,7 @@ bool XE::validateDSINativeBacking() { bool XE::paintDSINativeBacking(unsigned phase) { auto &o=dsiPendingBacking; o.image={}; - if(!o.allocating || !o.allocated || o.armed || !o.record || !o.pinnedMemory || + if(!o.allocating || !o.allocated || (o.armed && dsiSession.state!=XELP::DSI::SessionState::Streaming) || !o.record || !o.pinnedMemory || o.thread!=reinterpret_cast(current_thread()) || !XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof))return false; auto descriptor=OSDynamicCast(IOMemoryDescriptor,Display13FBMemory::field(o.pinnedMemory,0xD0)); @@ -5490,10 +5490,155 @@ bool XE::paintDSINativeBacking(unsigned phase) { return o.image.complete; } -// P2's first real fixed-mode request exercises allocation and unarmed cleanup -// while the native FB mutex is held. Do this once after allocation starts; +// The allocator's native GPU lock and FB mutex span this transaction. The +// owner lock protects shared clock/DDB/IRQ association; no IRQ spinlock spans +// the bounded frame wait. Keep backing private in P2: normal native flips and +// mode-success publication belong to the desktop adapter. +bool XE::switchDSINativeSurface() { + using namespace XELP::DSI; + auto &o=dsiPendingBacking;auto &c=display13FBPower;auto &irq=display13FBIRQ; + auto fb=o.framebuffer; + if(!fb || !o.image.complete || !o.allocating || !o.allocated || o.armed || !o.record || !c.lock || + o.thread!=reinterpret_cast(current_thread()) || dsiSession.state!=SessionState::Unbound)return false; + uint64_t receipt[24] {1,1}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); + auto path=Display13FBMemory::singlePath(fb); + IOLockLock(c.lock); + const auto owned=[&]() { + return dsiResourcesReady && dsiNativeWriterHooksReady && dsiCacheHooksReady && dsiBackingHooksReady && + !dsiStopAttempted && !c.dsiNativeWriters && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && !c.dbuf.updating && + !c.pipe[0].destroyingPath && !c.pipe[0].wmProgramming && !c.pipe[0].wmCommitting && + c.pipe[0].memoryFramebuffer==fb && c.transcoders.dsi0Pipe==0 && path && + path->getMetaClass()->getClassSize()>=0x3730 && + Display13FBMemory::field(path,0x32B0)==fb && + Display13FBMemory::field(path,0x3640)==0 && + Display13FBMemory::field(path,0x3644)==2 && + Display13FBMemory::field(path,0x3648)==c.dsiPort && + c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && + adlnSharedBinding(c.controller,o.accelerator) && adlnWriterMappingBound() && + o.pipe && Display13FBMemory::field(o.pipe,0x1298)==o.record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==o.record->map && + XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof); + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns; + IO(IOMemoryMap *m,decltype(owned) &f):Display13FBBootIO{m},owns(f){} + bool ready(){return owns();} + uint64_t nativeClockHz(){Display13FBClockIO clock{map};return Display13Clock::read(clock).nativeHz();} + uint64_t nowUs(){uint64_t t=0,ns=0;clock_get_uptime(&t);absolutetime_to_nanoseconds(t,&ns);return ns/1000;} + void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} + void write(uint32_t reg,uint32_t value) { + const bool wm=(reg>=0x70240 && reg<=0x7025C && !(reg&3)) || reg==0x70268; + bool plane=false;for(auto r:Surface::Registers)plane|=r==reg; + if(readable(reg) && (wm || plane || reg==0x45270)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + } io{c.map,owned}; + bool irqReady=false; + if(owned() && irq.lock && adlnIRQOwner(static_cast(c.controller))) { + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + irqReady=!irq.quiescing && !irq.state.faulted && irq.state.enabled && (irq.admittedPipes&1) && + irq.state.transcoders.dsi0Pipe==0; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + receipt[2]=irqReady; + Surface::Result result; + if(irqReady && dsiBoot.complete() && c.bootDSI.sameSource(dsiBoot.source) && + c.bridge.outputHeld(io,0,0) && c.latency.status==Display13WM::Status::Complete) { + const auto ddb=Display13DBUF::hardwareRaw(c.dbuf,0,c.dbuf.pipe[0].primary); + const auto cursorDDB=Display13DBUF::hardwareRaw(c.dbuf,0,c.dbuf.pipe[0].cursor); + const auto range=Display13DBUF::decode(ddb);const auto ipc=io.read(0x45004); + auto wm=Display13WM::plan(dsiProfile.mode.width,dsiProfile.mode.htotal(),uint64_t(dsiProfile.mode.clockKHz)*1000, + Display13DBUF::valid(range)?range.end-range.start:0,c.latency, + (ipc&8) || Display13FBMemory::field(c.controller,0x1B4C),true,dsiProfile.mode.height); + const auto hz=io.nativeClockHz();const auto pixel=uint64_t(dsiProfile.mode.clockKHz)*1000; + const Display13WM::Snapshot snapshot{&wm,nullptr,ddb,cursorDDB,static_cast(o.proof.gpu),0,ipc,hz,false,false, + Display13WM::pipeLineTime(dsiProfile.mode.htotal(),pixel),pixel,0,c.transcoders}; + Surface::Plane current{dsiBoot.control,io.read(0x701CC),dsiBoot.stride,dsiBoot.size,dsiBoot.surface,0,0}; + auto next=current;next.control=Display13WM::arbitrationControl((current.control&~(7U<<10))|(4U<<10)); + next.color|=1U<<13;next.stride=static_cast(o.proof.pitch/128);next.surface=static_cast(o.proof.gpu); + Surface::Backing backing{reinterpret_cast(o.record),o.proof.gpu,o.proof.bytes,o.proof.pitch}; + Capabilities caps;caps.installed=Capabilities::Adoption; + if(ddb!=UINT32_MAX && cursorDDB!=UINT32_MAX && Display13DBUF::valid(range) && + dsiSession.bind(caps,c.bootDSI,dsiProfile,1,reinterpret_cast(path),reinterpret_cast(&dsiBoot))) { + result=Surface::change(io,dsiSession,c.bootDSI,dsiProfile,current,next,backing,snapshot); + o.armed=result.writes!=0 || dsiSession.state==SessionState::Switching || dsiSession.state==SessionState::Faulted || + dsiSession.state==SessionState::Streaming; + if(!o.armed)dsiSession={}; + } + receipt[3]=ddb;receipt[4]=cursorDDB;receipt[5]=hz;receipt[6]=wm.allocation; + receipt[7]=next.control;receipt[8]=next.stride;receipt[9]=next.surface;receipt[10]=o.image.digest; + } + receipt[11]=static_cast(result.status);receipt[12]=result.writes; + receipt[13]=static_cast(result.wm.status);receipt[14]=static_cast(result.latch.status); + receipt[15]=static_cast(dsiSession.state);receipt[16]=o.armed; + const bool switched=result.status==Surface::Status::Complete; + IOLockUnlock(c.lock); + // A second pattern changes the contents of the same owned live surface. + // Keep native GPU serialization, flush it, then prove scanout advances. + bool updated=false; + if(switched && paintDSINativeBacking(1)) { + IOLockLock(c.lock); + receipt[17]=o.image.digest;receipt[18]=io.read(0x70040); + for(unsigned us=0;us<=100000;us+=1000) { + receipt[19]=io.read(0x70040); + const uint32_t frames=static_cast(receipt[19]-receipt[18]); + if(frames && frames<0x80000000U) { + updated=owned() && Surface::source(io,c.bootDSI) && + Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu));break; + } + if(!owned() || receipt[19]==UINT32_MAX)break; + io.delayUs(1000); + } + IOLockUnlock(c.lock); + } + if(switched && !updated)dsiSession.abort(dsiSession.ticket(),SessionError::Switch); + receipt[1]=2;receipt[20]=updated;receipt[21]=static_cast(dsiSession.state); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); + return switched && updated; +} + +// A stopped stream may retain a native backing after partial cleanup. Its +// ownership is distinct from the GOP pool, which stays reserved until later. +bool XE::dsiArmedBackingOwned(IOService *fb,OSObject *path) { + auto &o=dsiPendingBacking; + if(!dsiPendingTarget(fb) || !o.armed || o.allocating || o.freeing || !o.record || !o.allocated || + !o.pipe || !o.pinnedMap || !o.pinnedMemory || !path)return false; + const auto handle=reinterpret_cast(o.record),lease=reinterpret_cast(path); + const bool session=dsiSession.lease==lease && + (dsiSession.currentBacking==handle || dsiSession.pendingBacking==handle) && + (dsiSession.state==XELP::DSI::SessionState::Streaming || dsiSession.state==XELP::DSI::SessionState::Faulted); + const bool cleanup=dsiSession.state==XELP::DSI::SessionState::Released && dsiCleanup.lease==lease && + (dsiCleanup.currentBacking==handle || dsiCleanup.pendingBacking==handle); + return (session || cleanup) && dsiSession.identity.same(dsiBoot.source.identity) && + adlnSharedBinding(display13FBPower.controller,o.accelerator) && + Display13FBMemory::field(o.pipe,0x88)==o.accelerator && + Display13FBMemory::field(o.pipe,0x1298)==o.record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==o.record->map; +} + +// Caller has completed host/IRQ/clock stop and dropped owner/clock locks, but +// keeps FB serialization and the provider gate across native GPU cleanup. +bool XE::retireDSINativeBacking(IOService *fb,OSObject *path) { + if(!dsiPendingTarget(fb))return true; + if(!dsiStop.complete() || !dsiIRQParked || !dsiClockStop.complete() || !dsiDDIReleased || + !dsiArmedBackingOwned(fb,path))return false; + if(dsiSession.state!=XELP::DSI::SessionState::Released) { + const XELP::DSI::StopProof proof{dsiSession.ticket(),dsiBoot.source.identity,true,true,true,true}; + if(!dsiSession.stopped(proof))return false; + dsiCleanup=dsiSession.takeCleanup(); + } + auto &o=dsiPendingBacking;o.armed=false; + if(discardDSINativeBacking(fb))return true; + o.armed=true;return false; +} + +// P2's first real fixed-mode request tries a private identification surface +// while the native FB mutex is held. Retain any armed backing until stop. +// Do this once after allocation starts; // an unavailable accelerator may be retried by a later mode request. Never -// publish FB backing or arm SURF here, nor report the mode as established. +// publish FB backing or report a WindowServer mode as established here. void XE::probeDSINativeBacking(IOService *fb) { if(dsiBackingAttempted)return; dsiBackingAttempted=true; @@ -5509,8 +5654,8 @@ void XE::probeDSINativeBacking(IOService *fb) { receipt[12]=o.pinnedMap && o.pinnedMemory; } IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); - if(pending)receipt[13]=discardDSINativeBacking(fb); - receipt[14]=dsiPendingTarget(fb);receipt[15]=pending && !receipt[13];receipt[1]=3; + if(pending && !o.armed)receipt[13]=discardDSINativeBacking(fb); + receipt[14]=dsiPendingTarget(fb);receipt[15]=pending && !o.armed && !receipt[13];receipt[1]=3; if(!pending)dsiBackingAttempted=false; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); } @@ -10572,7 +10717,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { IORecursiveLockLock(clock.lock);IOLockLock(c.lock); uint64_t record[39] {5};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; const auto qualified=[&]() { - if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || dsiPendingTarget(fb) || !dsiProfile.valid() || + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || (dsiPendingTarget(fb) && !dsiArmedBackingOwned(fb,path)) || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || Display13FBMemory::field(controller,0xE00)!=loop || c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || @@ -10695,7 +10840,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { // Combo mutex nor the owner lock may span native allocator callbacks. // PG1 remains held for PTE/DDB validation; the stopped DSI PLL is free. IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - const bool released=adlnFreeStoppedPipe(fb); + const bool released=retireDSINativeBacking(fb,path) && adlnFreeStoppedPipe(fb); IORecursiveLockLock(clock.lock);IOLockLock(c.lock); Display13FBDBUFIO dbufIO{c.map}; // A native no-resource return can precede path destruction. Retire @@ -10732,8 +10877,8 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { *reinterpret_cast(reinterpret_cast(fb)+0x4270)=0; if(dsiModeTarget(fb)){dsiFixedMode.ready=false;modeWithdrawn=true;} } - // This GOP-only path cannot have a pending/armed native backing. A full - // stop leaves PG/path disposal to releaseDisplay13Path; only partial stops + // Native backing and the GOP pool have been retired before a full stop + // leaves PG/path disposal to releaseDisplay13Path; only partial stops // need the global retention guard. Never clear a pre-existing real fault. if(masked && !complete)adlnFault(c.memoryFaulted,__LINE__); IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 7beb576f..c70ea9bd 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -250,6 +250,11 @@ class XE { bool prepareDSINativeBacking(IOService *); bool validateDSINativeBacking(); bool paintDSINativeBacking(unsigned); + bool switchDSINativeSurface(); + bool dsiArmedBackingOwned(IOService *,OSObject *); + bool retireDSINativeBacking(IOService *,OSObject *); + XELP::DSI::Session dsiSession; + XELP::DSI::Session::Cleanup dsiCleanup; bool discardDSINativeBacking(IOService *); void probeDSINativeBacking(IOService *); bool dsiBackingAttempted {}; diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py index aa7a3099..848f88dd 100644 --- a/tests/dsi_backing_adapter.py +++ b/tests/dsi_backing_adapter.py @@ -18,6 +18,8 @@ code=r''' #include "kern_dsi_native_backing.hpp" #include "kern_dsi_test_image.hpp" +#include "kern_dsi_quiesce.hpp" +#include "kern_dsi_clock.hpp" #include #include #include @@ -99,7 +101,8 @@ static bool dsiNativeUpdateAllowed(IOService *fb){assert(fbDepth);return available && !dsiPendingTarget(fb);} static uint32_t wrapDSIBackingAllocate(OSObject*,void*,uint32_t); static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); - bool paintDSINativeBacking(unsigned); + bool paintDSINativeBacking(unsigned);Session dsiSession; + bool switchDSINativeSurface(){assert(gpuDepth==1 && fbDepth && !ownerDepth);return true;} bool prepareDSINativeBacking(IOService*);bool validateDSINativeBacking();bool discardDSINativeBacking(IOService*); void probeDSINativeBacking(IOService*);bool dsiBackingAttempted=false;uint32_t dsiBackingStage=0; }*callbackXE; diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 12231c05..1978e8e3 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -33,7 +33,7 @@ void setProperty(const char*,bool value){assert(locks==std::vector{1} && !value);} void retain(){++refs;}void release(){assert(refs>1);--refs;}}; using IOService=OSObject; -IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;} +bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;} struct IOWorkLoop:OSObject {bool gate=false,busy=false;unsigned closes=0,opens=0;}; bool inGate(const IOWorkLoop *p){assert(locks==std::vector{1});return p->gate;} bool tryClose(IOWorkLoop *p){assert(locks==std::vector{1});++p->closes;if(p->busy)return false;return p->gate=true;} @@ -98,7 +98,15 @@ struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false,dsiMemoryReleased=false; IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop; + bool dsiArmedBackingOwned(IOService *fb,OSObject*){return armedOwner && pendingFB==fb;} + bool retireDSINativeBacking(IOService *fb,OSObject*){ + if(!dsiPendingTarget(fb))return true; + assert(armedOwner && dsiStop.complete() && dsiIRQParked && dsiClockStop.complete() && dsiDDIReleased); + assert(locks==std::vector{1} && display13FBPower.workLoop->gate);++nativeRetires; + if(failNativeRetire)return false;pendingFB=nullptr;return true; + } static bool adlnFreeStoppedPipe(IOService *fb){ + assert(!pendingFB); assert(locks==std::vector{1} && display13FBPower.workLoop->gate); assert(!display13FBPower.memoryFaulted && display13FBPower.bridge.pgRefs[0]==1); assert(!comboClockTransaction.allocation.external[0] && !display13FBPower.bridge.ddiRefs[0]); @@ -165,6 +173,12 @@ } }; int main(){ + {Case c;pendingFB=&c.fb;armedOwner=true;nativeRetires=0; + assert(c.run() && nativeRetires==1 && !pendingFB && memoryFrees==1);armedOwner=false;} + {Case c;pendingFB=&c.fb;armedOwner=true;failNativeRetire=true;nativeRetires=0; + assert(!c.run() && nativeRetires==1 && pendingFB==&c.fb && !memoryFrees); + pendingFB=nullptr;armedOwner=failNativeRetire=false;} + for(bool own:{false,true}){Case c;c.loop.gate=own;c.map.gtOnly=true; put(&c.controller,0xE08,2U);put(&c.controller,0xE10,1U<<6); assert(c.run() && c.xe.dsiStop.complete() && c.xe.dsiIRQParked); diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py new file mode 100644 index 00000000..79945264 --- /dev/null +++ b/tests/dsi_surface_adapter.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Exercise the production surface/retirement adapters on the stop fixture's BAR.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in + ('switchDSINativeSurface','dsiArmedBackingOwned','retireDSINativeBacking')) +state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] +# Share the established real power/IRQ/clock fixture; no implementation bodies +# are rewritten. Allocation/PTE proof and CPU mapping have their own adapter test. +q=(root/'tests/dsi_quiesce_adapter.py').read_text() +q=q.split("code=r'''",1)[1].split("'''.replace",1)[0] +prelude=q[:q.index('struct XE {')] +prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_test_image.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"') +b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) +prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') +prelude=prelude[:prelude.index('void tick(unsigned us)')]+r''' +uint64_t elapsed=0;unsigned gpuDepth=1;bool failPaint=false,failDiscard=false;unsigned discards=0; +templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} +void *current_thread(){return reinterpret_cast(1);} +void clock_get_uptime(uint64_t *v){*v=elapsed*1000;}void absolutetime_to_nanoseconds(uint64_t t,uint64_t *v){*v=t;} +void tick(unsigned us){assert(locks==std::vector({1,3}) && gpuDepth==1);elapsed+=us; + if(!activeMap->freeze && us>=1000)++activeMap->reg(0x70040); + activeMap->reg(0x701AC)=activeMap->reg(0x7019C); +} +void IOSleep(unsigned ms){tick(ms*1000);}void IODelay(unsigned us){tick(us);} +struct Display13FBClockIO:Display13FBBootIO {}; +bool adlnSharedBinding(void *controller,void *owner){return controller==display13FBPower.controller && owner && mappingBound;} +uint64_t receipt[24]{}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *name,const void *p,unsigned n){assert(!strcmp(name,"WEXDSISurface") && n==sizeof(receipt));memcpy(receipt,p,n);}}; +struct XE { + bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiCacheHooksReady=true,dsiBackingHooksReady=true; + bool dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false; + Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop;Session dsiSession;Session::Cleanup dsiCleanup; + bool paintDSINativeBacking(unsigned phase){assert(phase==1 && locks==std::vector{1} && gpuDepth==1); + if(failPaint)return false;dsiPendingBacking.image.digest=0x686999c5;return true;} + bool discardDSINativeBacking(IOService *fb){assert(locks==std::vector{1} && !gpuDepth && !dsiPendingBacking.armed && dsiPendingTarget(fb)); + ++discards;if(failDiscard)return false;dsiPendingBacking={};return true;} + bool switchDSINativeSurface();bool dsiArmedBackingOwned(IOService*,OSObject*);bool retireDSINativeBacking(IOService*,OSObject*); +}; +''' +prelude=prelude.replace('Display13DBUF::State dbuf;','Display13DBUF::State dbuf;Display13WM::Latency latency;') +# Reuse just initialization, leaving stop tests and stop adapter out of this TU. +case=q[q.index('struct Case {'):q.index(' bool run(){')] +case=case.replace('IOWorkLoop loop;','IOWorkLoop loop;IOService accelerator,pipe;OSObject mapping,memory;NativeBacking::Record record;') +case=case.replace('xe.adlnTopologyController=&controller;','').replace('put(&controller,0xE00,&loop);put(&controller,0x90,&irqLock);',r''' + put(&controller,0xE00,&loop);put(&controller,0x90,&irqLock); + put(&path,0x32B0,&fb);put(&path,0x3640,0U);put(&pipe,0x88,&accelerator); + map.reg(0x46070)=0xC0000010;map.reg(0x46000)=612;map.reg(0x46008)=0; + map.reg(0x701CC)=0x5000;map.reg(0x701AC)=map.reg(0x7019C)=0; + c.latency=Display13WM::decodeLatency(0x63503300,0x00909090,30); + c.dbuf.pipe[0].primary={0,2048};map.reg(0x7027C)=0x07FF0000; + xe.dsiBoot.status=BootStatus::Complete;xe.dsiBoot.control=map.reg(0x70180); + xe.dsiBoot.stride=map.reg(0x70188);xe.dsiBoot.size=map.reg(0x70190);xe.dsiBoot.surface=0; + NativeBacking::request(xe.dsiProfile,3,record);record.refs=1;record.map=reinterpret_cast(&mapping); + record.memory=reinterpret_cast(&memory);record.gpu=0x10000000;record.pitch=4352; + put(&pipe,0x1298,record.memory);put(&pipe,0x12A0,record.map); + auto &o=dsiPendingBacking;o={};o.framebuffer=&fb;o.accelerator=&accelerator;o.pipe=&pipe; + o.pinnedMap=&mapping;o.pinnedMemory=&memory;o.record=&record;o.thread=1;o.allocating=o.allocated=true; + auto &p=o.proof;p.status=Display13FBMemory::SurfaceStatus::Complete;p.record=reinterpret_cast(&record); + p.refs=p.kind=1;p.map=record.map;p.memory=record.memory;p.gpu=record.gpu;p.bytes=8355840;p.pitch=4352;p.verifiedPages=2040; + o.image.complete=true;o.image.digest=0xf89939c5;elapsed=0;gpuDepth=1;failPaint=failDiscard=false;discards=0;memset(receipt,0,sizeof(receipt)); +''') +code=prelude+functions+'\n'+case+r''' + bool run(){locks={1};bool result=xe.switchDSINativeSurface();assert(locks==std::vector{1});locks.clear();dsiPendingBacking.allocating=false;gpuDepth=0;return result;} + bool retire(){locks={1};bool r=xe.retireDSINativeBacking(&fb,&path);assert(locks==std::vector{1});locks.clear();return r;} + void stopped(){xe.dsiStop.stage=Quiesce::Stage::Complete;xe.dsiStop.error=Quiesce::Error::None;xe.dsiIRQParked=xe.dsiDDIReleased=true;xe.dsiClockStop.status=Clock::Status::Complete;} + ~Case(){assert(locks.empty());dsiPendingBacking={};} +}; +int main(){ + {Case c;assert(c.run());assert(receipt[11]==unsigned(Surface::Status::Complete) && receipt[12]==17 && receipt[20]); + assert(receipt[10]==0xf89939c5 && receipt[17]==0x686999c5 && receipt[19]>receipt[18]); + assert(c.map.reg(0x7019C)==c.record.gpu && c.map.reg(0x701AC)==c.record.gpu && c.map.reg(0x70188)==34); + assert(c.xe.dsiSession.state==SessionState::Streaming && dsiPendingBacking.armed && c.xe.dsiArmedBackingOwned(&c.fb,&c.path)); + assert(!c.retire() && !discards);c.stopped();assert(c.retire() && discards==1 && !dsiPendingTarget(&c.fb)); + assert(c.xe.dsiSession.state==SessionState::Released && c.xe.dsiCleanup.retiredBacking==reinterpret_cast(&c.xe.dsiBoot));} + for(unsigned fail=0;fail<3;++fail){Case c; + if(fail==0)c.map.freeze=true;else if(fail==1)failPaint=true;else display13FBIRQ.state.enabled=false; + const auto before=c.map.bar;assert(!c.run()); + if(fail==2){assert(!dsiPendingBacking.armed && c.map.bar==before && c.xe.dsiSession.state==SessionState::Unbound);continue;} + assert(dsiPendingBacking.armed && c.xe.dsiSession.state==SessionState::Faulted && !c.retire() && !discards); + c.stopped();failDiscard=true;assert(!c.retire() && dsiPendingBacking.armed && c.xe.dsiSession.state==SessionState::Released); + assert(c.xe.dsiArmedBackingOwned(&c.fb,&c.path));failDiscard=false;assert(c.retire() && discards==2);} + puts("PASS production DSI surface: actual MMIO allowlist/WM/latch, second image frame, GPU/owner/IRQ locking, retained partial switches and stopped cleanup retry"); +} +''' +with tempfile.TemporaryDirectory(prefix='dsi-surface-adapter-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index cefb327b..e359a58c 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,6 +9,7 @@ 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', '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 += ['dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_surface_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From 6569b767bd1c8a31a040f92a04f88ec19d57d105 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 03:39:09 +0900 Subject: [PATCH 056/139] Keep an unpublished DSI aperture unavailable during private scanout --- WhateverXe/kern_xe.cpp | 7 ++-- tests/dsi_aperture_adapter.py | 61 +++++++++++++++++++++++++++++++++++ tools/test.py | 2 +- 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/dsi_aperture_adapter.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 0d8ba157..7550cce5 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10198,11 +10198,12 @@ IOMemoryDescriptor *XE::wrapAlderLakeNAperture(IOService *fb,int apertureIndex) backing=adlnInspectSysMem(static_cast(c.controller),fb,slot,layout).status==Display13FBSysMem::Status::Complete; } // IOGraphics may ask before the first DSI mode transaction has set - // dimensions. The validated pool is still owned, but no aperture was - // published. Return unavailable without poisoning that shared owner. + // dimensions, including while P2's private identification backing is + // armed. The validated GOP pool is still owned, but no client aperture + // was published. Return unavailable without poisoning that shared owner. // Nonzero invalid ranges and lost backing retain the fault path below. if(!base && !bytes && parent && backing && !slot && callbackXE->adlnOutputWiring.dsi==1 && - dsiModeTarget(fb) && !dsiPendingTarget(fb) && !callbackXE->dsiStopAttempted) { + dsiModeTarget(fb) && !callbackXE->dsiStopAttempted) { IOLockUnlock(c.lock);return nullptr; } if(!parent || !backing || diff --git a/tests/dsi_aperture_adapter.py b/tests/dsi_aperture_adapter.py new file mode 100644 index 00000000..9ce0ef61 --- /dev/null +++ b/tests/dsi_aperture_adapter.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""An unpublished DSI aperture stays unavailable while private scanout is armed.""" +from pathlib import Path +import re,subprocess,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'IOMemoryDescriptor \*XE::wrapAlderLakeNAperture\(.*?\n\}',s,re.S)[0] +code=r''' +#include "kern_display13_fb_memory.hpp" +#include +#include +#include +using namespace Display13FBMemory; +using IOByteCount=uint64_t; +unsigned depth=0;struct IOLock{};void IOLockLock(IOLock*){assert(!depth++);}void IOLockUnlock(IOLock*){assert(depth--==1);} +struct OSObject {alignas(8) uint8_t data[0x4B90]{};unsigned refs=1; + OSObject *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(data);} + uint64_t getLength(){return 0x10000000;}uint64_t getPhysicalSegment(uint64_t off,uint64_t *span,unsigned){*span=getLength();return 0x80000000+off;} + void retain(){++refs;}void release(){assert(refs>1);--refs;} +}; +using IOService=OSObject;using IOMemoryDescriptor=OSObject; +#define OSDynamicCast(T,p) static_cast(p) +#define OSSafeReleaseNULL(p) do{if(p)(p)->release();p=nullptr;}while(0) +constexpr unsigned kIODirectionInOut=1,kIOMemoryThreadSafe=2,kIOMemoryMapperNone=0; +struct IOSubMemoryDescriptor {static IOMemoryDescriptor *withSubRange(IOMemoryDescriptor*,unsigned,unsigned,unsigned){assert(false);return nullptr;}}; +struct {IOLock *lock;void *controller;bool memoryFaulted=false; + struct {IOService *memoryFramebuffer=nullptr;struct {Pool pool;}memory;}pipe[3];}display13FBPower; +bool associationOK=true,target=true,pending=false; +bool display13FBAssociations(void*,unsigned,void*,unsigned,uintptr_t){return associationOK;} +uintptr_t display13FBExpectedOwner(void*){return 1;} +bool dsiModeTarget(IOService*){return target;}bool dsiPendingTarget(IOService*){return pending;} +namespace Display13FBSysMem {enum class Status{Complete};} +struct Inspection{Display13FBSysMem::Status status=Display13FBSysMem::Status::Complete;}; +Inspection adlnInspectSysMem(IOService*,IOService*,unsigned,const PoolLayout&){assert(false);return {};} +void adlnPublishMemory(const char*,unsigned,void*,unsigned,bool){} +void adlnFault(bool &v,unsigned){v=true;} +struct XE {bool display13FBMemoryHooksReady=true,dsiStopAttempted=false;struct {unsigned dsi=1;}adlnOutputWiring; + static IOMemoryDescriptor *wrapAlderLakeNAperture(IOService*,int); +}*callbackXE; +BODY +templatevoid put(void *p,unsigned off,T v){memcpy(static_cast(p)+off,&v,sizeof(v));} +int main(){ + for(unsigned problem=0;problem<7;++problem){IOService fb,controller,parent;IOLock lock;XE xe;callbackXE=&xe; + display13FBPower={};auto &c=display13FBPower;c.lock=&lock;c.controller=&controller;c.pipe[0].memoryFramebuffer=&fb; + put(&fb,0x1E0,uint8_t(1));put(&fb,0x4288,uint8_t(0));put(&controller,0xDF0,1U); + put(&controller,0xDF4,uint8_t(1));put(&controller,0xDF5,uint8_t(3));put(&controller,0x1A10,1U); + put(&controller,0x1A28,uint64_t(0x80000000));put(&controller,0x1A30,0x800000U);put(&controller,0x1A34,0x800U); + put(&controller,0xC80,&parent);c.pipe[0].memory.pool=pool(&controller); + pending=problem!=0;target=true; + switch(problem){case 2:put(&fb,0x4314,1U);break;case 3:put(&fb,0x4330,1U);break; + case 4:c.pipe[0].memory.pool.pages++;break;case 5:target=false;break;case 6:xe.dsiStopAttempted=true;break;} + assert(!xe.wrapAlderLakeNAperture(&fb,0) && !depth && parent.refs==1); + assert(c.memoryFaulted==(problem>=2)); + } + puts("PASS production DSI aperture: zero unpublished range before/after private surface stays unavailable; invalid nonzero/backing/target/stop still faults"); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='dsi-aperture-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index e359a58c..555637fb 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,7 +9,7 @@ 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', '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 += ['dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] -python_tests += ['dsi_surface_adapter'] +python_tests += ['dsi_surface_adapter','dsi_aperture_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From 90bf2e49f5ac843414a15834af7afd691d599ea7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:13:29 +0900 Subject: [PATCH 057/139] Qualify native fixed-mode geometry before DSI backing publication --- WhateverXe/kern_dsi_native_mode.hpp | 43 ++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 57 +++++++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 2 + tests/dsi_backing_adapter.py | 1 + tests/dsi_native_mode.py | 15 ++++++++ tests/dsi_native_mode_fixture.cpp | 50 +++++++++++++++++++++++++ tools/test.py | 2 +- 7 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 WhateverXe/kern_dsi_native_mode.hpp create mode 100644 tests/dsi_native_mode.py create mode 100644 tests/dsi_native_mode_fixture.cpp diff --git a/WhateverXe/kern_dsi_native_mode.hpp b/WhateverXe/kern_dsi_native_mode.hpp new file mode 100644 index 00000000..469cf11e --- /dev/null +++ b/WhateverXe/kern_dsi_native_mode.hpp @@ -0,0 +1,43 @@ +// CPU-only fixed-mode preparation for the exact TGL framebuffer donor. +// Host programming, backing publication and transaction admission are separate. +#ifndef KERN_DSI_NATIVE_MODE_HPP +#define KERN_DSI_NATIVE_MODE_HPP +#include "kern_dsi_apple_timing.hpp" +#include "kern_display13_fb_memory.hpp" +namespace XELP { namespace DSI { namespace AppleAdapter { +// The caller holds the FB mutex and validates object classes/ownership. No +// scaled mode is admitted: native allocation would need a different contract. +template bool prepareGeometry(void *fb,void *path,const Profile &profile, + const Timing &timing,Compute compute) { + using Display13FBMemory::field; + Timing expected; + if(!fb || !path || !makeTiming(profile,expected) || memcmp(&expected,&timing,sizeof(timing)) || + Display13FBMemory::singlePath(fb)!=path || field(fb,0x4340) || + field(fb,0x4348))return false; + // Preserve the native CPU state if the donor produces an unsupported + // transform. The helper writes only these geometry fields for this mode. + uint8_t savedFB[0x30],savedPath[0x20]; + memcpy(savedFB,static_cast(fb)+0x430C,sizeof(savedFB)); + memcpy(savedPath,static_cast(path)+0xF0,sizeof(savedPath)); + const auto bpp=field(fb,0x4350);const auto tiled=field(fb,0x4A18); + const auto put=[](void *o,unsigned offset,uint32_t v){memcpy(static_cast(o)+offset,&v,4);}; + put(fb,0x4350,4);*(static_cast(fb)+0x4A18)=1; + compute(fb,&timing); + const auto &m=profile.mode;const uint32_t pitch=((m.width*4+127)/128)*128; + const bool valid=field(fb,0x430C)==m.width && field(fb,0x4310)==m.height && + field(fb,0x4314)==pitch*m.height && field(fb,0x4320)==m.width && + field(fb,0x4324)==m.height && field(fb,0x4328)==m.width && + field(fb,0x432C)==m.height && field(fb,0x4334)==pitch && + field(fb,0x4338)==1 && !field(path,0xF0) && + field(path,0xF8)==m.width && field(path,0xFC)==m.height && + !field(path,0x100) && field(path,0x108)==m.width && + field(path,0x10C)==m.height; + if(!valid) { + memcpy(static_cast(fb)+0x430C,savedFB,sizeof(savedFB)); + memcpy(static_cast(path)+0xF0,savedPath,sizeof(savedPath)); + put(fb,0x4350,bpp);*(static_cast(fb)+0x4A18)=tiled; + } + return valid; +} +}}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 7550cce5..f620bdc3 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9,6 +9,7 @@ #include "kern_dsi_native_cache.hpp" #include "kern_dsi_native_backing.hpp" +#include "kern_dsi_native_mode.hpp" #include "kern_dsi_test_image.hpp" #include "kern_xe.hpp" #include "kern_framebuffer_donor.hpp" @@ -5402,6 +5403,9 @@ bool XE::prepareDSINativeBacking(IOService *fb) { display13FBInspectMemory(fb,true,0,false).status==Display13FBMemory::Status::Complete; if(ready){fb->retain();owner->retain();} IOLockUnlock(c.lock);if(!ready)return false; + // Prepare the real native dimensions before handing this allocation to + // its later public mode owner. No mode-success or backing is published. + if(!probeDSIModeGeometry(fb)) {owner->release();fb->release();return false;} dsiBackingStage=3; auto record=static_cast(IOMalloc(sizeof(request))); if(!record){owner->release();fb->release();return false;} @@ -5429,6 +5433,54 @@ bool XE::prepareDSINativeBacking(IOService *fb) { IOLockUnlock(c.lock);if(verified)dsiBackingStage=6;return verified; } +// The first P3 step uses the donor's CPU geometry helper. The selected Y +// layout is the same native tile-mode-3 allocation validated by the caller. +// Retain a bounded receipt of the remaining native transaction prerequisites. +bool XE::probeDSIModeGeometry(IOService *fb) { + if(!dsiComputeGeometry || !dsiModeTarget(fb) || !dsiModeCoherent(fb))return false; + auto path=Display13FBMemory::singlePath(fb); + auto &c=display13FBPower;IOLockLock(c.lock); + auto controller=static_cast(c.controller); + uint64_t receipt[16] {1,0,Display13FBMemory::field(fb,0x4A18), + Display13FBMemory::field(fb,0x4350),Display13FBMemory::field(fb,0x420C)}; + const bool bound=path && path->getMetaClass()->getClassSize()>=0x3730 && controller && + Display13FBMemory::field(fb,0x1D0)==controller && + Display13FBMemory::field(path,0x32B0)==fb && !c.dsiNativeWriters && + !c.memoryFaulted && !c.clockFaulted && !dsiStopAttempted; + uint8_t savedFB[0x30],savedPath[0x20]; + memcpy(savedFB,reinterpret_cast(fb)+0x430C,sizeof(savedFB)); + if(bound)memcpy(savedPath,reinterpret_cast(path)+0xF0,sizeof(savedPath)); + const bool prepared=bound && XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,dsiFixedMode.timing, + reinterpret_cast(dsiComputeGeometry)); + receipt[1]=prepared;receipt[5]=Display13FBMemory::field(fb,0x430C); + receipt[6]=Display13FBMemory::field(fb,0x4310);receipt[7]=Display13FBMemory::field(fb,0x4314); + receipt[8]=Display13FBMemory::field(fb,0x4334);receipt[9]=Display13FBMemory::field(fb,0x4338); + if(controller) { + receipt[10]=Display13FBMemory::field(controller,0xC10)!=nullptr; + receipt[11]=Display13FBMemory::field(controller,0x1BB4); + auto panel=Display13FBMemory::field(controller,0xD0); + receipt[12]=panel!=nullptr; + if(panel && panel->getMetaClass()->getClassSize()>=0x70) { + receipt[13]=Display13FBMemory::field(panel,0x68); + receipt[14]=Display13FBMemory::field(panel,0x50)==fb; + } + receipt[15]=Display13FBMemory::field(controller,0xE53); + } + // Until FB+4348 publishes the native backing, leave public aperture + // dimensions untouched. Otherwise IOGraphics could map the old GOP while + // the identification plane reads the new tiled allocation. + if(bound) { + memcpy(reinterpret_cast(fb)+0x430C,savedFB,sizeof(savedFB)); + memcpy(reinterpret_cast(path)+0xF0,savedPath,sizeof(savedPath)); + const uint32_t bpp=static_cast(receipt[3]); + memcpy(reinterpret_cast(fb)+0x4350,&bpp,sizeof(bpp)); + *(reinterpret_cast(fb)+0x4A18)=static_cast(receipt[2]); + } + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIGeometry",receipt,sizeof(receipt)); + return prepared; +} + // Called by allocateScanoutMemory's routed entry while the native display // machine still holds the GPU lock. Keep object/PTE validation in that scope; // retained map/memory references alone do not serialize GGTT replacement. @@ -6995,6 +7047,11 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; } + const char *geometry="__ZN21AppleIntelFramebuffer32computeTransformAndSetDimensionsEPK29IODetailedTimingInformationV2"; + const auto compute=patcher.solveSymbol(index,geometry,address,size);patcher.clearError(); + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}; + if(compute!=address+0x31484 || memcmp(reinterpret_cast(compute),entry,sizeof(entry)))return false; + dsiComputeGeometry=compute; KernelPatcher::RouteRequest routes[]={ {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index c70ea9bd..6541c806 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -247,6 +247,8 @@ class XE { bool dsiBackingHooksReady {}; mach_vm_address_t orgDSIBacking[2] {}; bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t dsiComputeGeometry {}; + bool probeDSIModeGeometry(IOService *); bool prepareDSINativeBacking(IOService *); bool validateDSINativeBacking(); bool paintDSINativeBacking(unsigned); diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py index 848f88dd..7554bc00 100644 --- a/tests/dsi_backing_adapter.py +++ b/tests/dsi_backing_adapter.py @@ -103,6 +103,7 @@ static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); bool paintDSINativeBacking(unsigned);Session dsiSession; bool switchDSINativeSurface(){assert(gpuDepth==1 && fbDepth && !ownerDepth);return true;} + bool probeDSIModeGeometry(IOService*){assert(fbDepth && !ownerDepth && !gpuDepth);return true;} bool prepareDSINativeBacking(IOService*);bool validateDSINativeBacking();bool discardDSINativeBacking(IOService*); void probeDSINativeBacking(IOService*);bool dsiBackingAttempted=false;uint32_t dsiBackingStage=0; }*callbackXE; diff --git a/tests/dsi_native_mode.py b/tests/dsi_native_mode.py new file mode 100644 index 00000000..cae71166 --- /dev/null +++ b/tests/dsi_native_mode.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +"""Check geometry rollback and optionally replay the exact donor CPU helpers.""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile +root=Path(__file__).resolve().parents[1] +args=[] +if len(sys.argv)>1: + donor=Path(sys.argv[1]).resolve() + assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + args=[str(donor)] +with tempfile.TemporaryDirectory(prefix='dsi-mode-') as directory: + executable=Path(directory)/'test' + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-I'+str(root/'WhateverXe'), + str(root/'tests/dsi_native_mode_fixture.cpp'),'-o',str(executable)],check=True) + subprocess.run([str(executable),*args],check=True) diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp new file mode 100644 index 00000000..30f560aa --- /dev/null +++ b/tests/dsi_native_mode_fixture.cpp @@ -0,0 +1,50 @@ +// Execute the real donor CPU helper when its exact binary is supplied. +#include "kern_dsi_native_mode.hpp" +#include +#include +#include +#include +#include +#include +using namespace XELP::DSI; +using Display13FBMemory::field; +templatevoid put(void *p,unsigned offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} +int main(int argc,char **argv) { + alignas(8) uint8_t fb[0x4B90]{},controller[0x1C20]{},path[0x3730]{}; + put(fb,0x1D0,controller);put(fb,0x4A00,1U);put(fb,0x4A08,path); + Profile p;p.status=ProfileStatus::Valid;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; + AppleAdapter::Timing t;assert(AppleAdapter::makeTiming(p,t)); + unsigned calls=0; + auto unsupported=[&](void *f,const AppleAdapter::Timing*) { + ++calls;memset(static_cast(f)+0x430C,0x55,0x30);memset(path+0xF0,0x33,0x20); + }; + // The rejected helper must leave its complete CPU geometry unchanged. + put(fb,0x4350,8U);put(path,0xF8,640U); + const std::vector savedFB(fb,fb+sizeof(fb)),savedPath(path,path+sizeof(path)); + assert(!AppleAdapter::prepareGeometry(fb,path,p,t,unsupported));assert(calls==1); + assert(!memcmp(fb,savedFB.data(),sizeof(fb)) && !memcmp(path,savedPath.data(),sizeof(path))); + auto scaled=t;scaled.horizontalScaled=720; + assert(!AppleAdapter::prepareGeometry(fb,path,p,scaled,unsupported));assert(calls==1); + put(fb,0x4348,uintptr_t(1));assert(!AppleAdapter::prepareGeometry(fb,path,p,t,unsupported));assert(calls==1); + put(fb,0x4348,uintptr_t(0));put(fb,0x4A00,2U); + assert(!AppleAdapter::prepareGeometry(fb,path,p,t,unsupported));assert(calls==1);put(fb,0x4A00,1U); + if(argc==2) { + std::ifstream f(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(f)),{}); + assert(bytes.size()<0x400000); + auto image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0)); + assert(image!=MAP_FAILED);memcpy(image,bytes.data(),bytes.size()); + const auto compute=reinterpret_cast(image+0x31484); + for(int joined:{-1,0})for(bool owner:{false,true}) { + put(fb,0x3FD8,joined);put(fb,0x49E8,1U);put(fb,0x49F0,path); + put(controller,0xC0,owner?uintptr_t(1):uintptr_t(0)); + assert(AppleAdapter::prepareGeometry(fb,path,p,t,compute)); + assert(field(fb,0x4334)==4352 && field(fb,0x4314)==8355840); + assert(!field(fb,0x4348) && !field(fb,0x4340)); + // Exact unscaled logical-buffer path must be a successful no-op. + assert(reinterpret_cast(image+0x31726)(fb)==0); + assert(!field(fb,0x4340)); + } + munmap(image,0x400000);puts("DSI native mode geometry and logical-buffer replay passed"); + } + puts("DSI native mode preparation rollback and admission passed"); +} diff --git a/tools/test.py b/tools/test.py index 555637fb..53897f40 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_surface_adapter','dsi_aperture_adapter'] 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)] From 84337e286f5dccb83fbe8b744606387ca61baba4 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:22:47 +0900 Subject: [PATCH 058/139] Publish the adopted DSI backing through the native fixed-mode path --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_native_cache.hpp | 26 +++- WhateverXe/kern_xe.cpp | 191 +++++++++++++++++++++--- WhateverXe/kern_xe.hpp | 5 +- tests/adlp_fb_candidate.py | 1 + tests/display13_dbuf_setup_fixture.cpp | 1 + tests/dsi_apple_adapter.py | 5 +- tests/dsi_backing_adapter.py | 1 + tests/dsi_native_cache.py | 7 + tests/dsi_native_mode_fixture.cpp | 22 ++- tests/dsi_surface_adapter.py | 61 +++++++- tests/dsi_update_dispatch.py | 4 +- tests/fixtures/boot_argument_names.json | 1 + 14 files changed, 300 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index aaef7453..94a1e22d 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and enables native path and fixed-mode setup; a complete accelerated desktop is not yet supported. Do not combine it with `-wex-dsi-hdmi-test`. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and enables native path and fixed-mode setup; a complete accelerated desktop is not yet supported. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index bfe1b3b7..0c34fc86 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、native pathと固定modeの設定を有効にします。完全なアクセラレーション付きデスクトップには未対応です。`-wex-dsi-hdmi-test` とは併用できません。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、native pathと固定modeの設定を有効にします。完全なアクセラレーション付きデスクトップには未対応です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_native_cache.hpp b/WhateverXe/kern_dsi_native_cache.hpp index f49ec076..56b4c4e8 100644 --- a/WhateverXe/kern_dsi_native_cache.hpp +++ b/WhateverXe/kern_dsi_native_cache.hpp @@ -1,4 +1,4 @@ -// Exact donor boot cache view. This is not a register-address translator or a +// Exact donor boot/active cache view. This is not a register-address translator or a // restore/programming image: DSI mode/stop must use their own adapter. DP M/N, // MSA, normal DDI function and unsupported HBLANK have no DSI counterpart here. #ifndef KERN_DSI_NATIVE_CACHE_HPP @@ -40,6 +40,30 @@ template bool read(IO &io,const Boot &boot,const XELPMemory::Layout &m View a,b;if(!capture(a) || !capture(b) || memcmp(&a,&b,sizeof(a)))return false; out=a;return true; } +// Once native backing is public, plane surfaces and cursor/color caches +// belong to ordinary native transactions. Check the retained DSI link and +// fixed source geometry, but do not require the obsolete GOP plane image. +template bool readActive(IO &io,const Boot &boot,uint32_t scaler,View &out) { + if(!boot.complete() || boot.source.identity.pipe!=0 || (scaler && scaler!=0x68180 && scaler!=0x68280))return false; + const auto capture=[&](View &v) { + if(!sourceUnchanged(io,boot.source))return false; + const auto word=[&](uint32_t r,uint32_t &value) { + if(!io.readable(r))return false;value=io.read(r);return value!=UINT32_MAX; + }; + const uint32_t timing[]={0x6B000,0,0x6B008,0x6B00C,0x6B010,0x6B014}; + for(unsigned i=0;i<6;++i)if(timing[i] && !word(timing[i],v.timing[i]))return false; + if(!word(0x6001C,v.source) || !word(0x7B008,v.conf) || !word(0x70030,v.misc) || + !word(0x6B028,v.shift) || !word(0x4A480,v.csc) || !word(0x70080,v.cursor))return false; + for(unsigned i=0;i<6;++i)if(!word(PlaneRegisters[i],v.plane[i]))return false; + if(scaler && (!word(scaler,v.scaler[0]) || !word(scaler-0x10,v.scaler[1]) || !word(scaler-0xC,v.scaler[2])))return false; + return v.timing[0]==boot.source.htotal && v.timing[2]==boot.source.hsync && + v.timing[3]==boot.source.vtotal && v.timing[4]==v.timing[3] && v.timing[5]==boot.source.vsync && + v.source==((boot.source.htotal&65535)<<16 | (boot.source.vtotal&65535)) && + v.conf==boot.source.conf && sourceUnchanged(io,boot.source); + }; + View a,b;if(!capture(a) || !capture(b) || memcmp(&a,&b,sizeof(a)))return false; + out=a;return true; +} // Class sizes and actual path/plane/scaler associations are checked by the // kernel adapter. Only cache words change; register addresses/ownership don't. inline void publish(const View &v,void *path,void *plane,void *scaler) { diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f620bdc3..e4a90c93 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3871,6 +3871,8 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { if(checkKernelArgument("-wex-dsi-hdmi-test") || !board.valid || board.panel || (board.selected()&1) || !prepareDSIFirmware(provider))return {}; board.dsi=1; + dsiPanelOnly=checkKernelArgument("-wex-dsi-panel-only"); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelOnly",dsiPanelOnly); if(!board.described())return {}; } return board; @@ -4646,7 +4648,7 @@ struct DSIPendingBackingOwner { OSObject *pipe {},*pinnedMap {},*pinnedMemory {}; XELP::DSI::NativeBacking::Record *record {}; uintptr_t thread {}; - bool allocating {},freeing {},allocated {},freed {},armed {}; + bool allocating {},freeing {},allocated {},freed {},armed {},published {},modeReady {}; uint32_t nativeResult {}; Display13FBMemory::SurfaceReadout proof; XELP::DSI::TestImage::Result image; @@ -4656,6 +4658,11 @@ static bool dsiPendingTarget(IOService *fb) { return fb && __atomic_load_n(&dsiPendingBacking.framebuffer,__ATOMIC_ACQUIRE)==fb; } +static bool dsiPublishedTarget(IOService *fb) { + return dsiPendingTarget(fb) && dsiPendingBacking.published && dsiPendingBacking.record && + Display13FBMemory::field(fb,0x4348)==dsiPendingBacking.record; +} + // One DSI0 panel. Publish only after native path/pipe association; the FB and // immutable timing array remain retained until a quiesced DSI stop can detach @@ -5356,7 +5363,8 @@ uint32_t XE::wrapDSIBackingAllocate(OSObject *pipe,void *record,uint32_t flags) o.pinnedMap->retain();o.pinnedMemory->retain(); } return o.allocated && callbackXE->validateDSINativeBacking() && - callbackXE->paintDSINativeBacking(0) && callbackXE->switchDSINativeSurface() ? result : kIOReturnNotReady; + callbackXE->paintDSINativeBacking(0) && callbackXE->switchDSINativeSurface() && + callbackXE->publishDSINativeBacking() ? result : kIOReturnNotReady; } uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { auto &o=dsiPendingBacking; @@ -5651,6 +5659,120 @@ bool XE::switchDSINativeSurface() { return switched && updated; } +// Still inside allocateScanoutMemory's GPU lock and the FB mutex. Transfer +// the already latched allocation to the native FB without reallocating it. +// Returning stolen slot zero changes bookkeeping only; its GGTT reservation +// and the firmware retired-backing record remain held through Session stop. +bool XE::publishDSINativeBacking() { + auto &o=dsiPendingBacking;auto &c=display13FBPower;auto fb=o.framebuffer; + if(!fb || !o.allocating || o.thread!=reinterpret_cast(current_thread()) || + !o.armed || !o.allocated || o.published || dsiSession.state!=XELP::DSI::SessionState::Streaming || + !dsiComputeGeometry || !orgAlderLakeNScanoutFree)return false; + IOLockLock(c.lock); + auto controller=static_cast(c.controller);auto path=Display13FBMemory::singlePath(fb); + const auto pool=Display13FBMemory::pool(controller); + const auto count=Display13FBMemory::field(controller,0xDF5); + const auto used=Display13FBMemory::field(controller,0xDF4); + const auto mask=Display13FBMemory::field(controller,0xDF0); + uint64_t receipt[12] {1,1,count,used,mask}; + uint8_t savedFB[0x30],savedPath[0x20]; + memcpy(savedFB,reinterpret_cast(fb)+0x430C,sizeof(savedFB)); + if(!path) {IOLockUnlock(c.lock);return false;} + memcpy(savedPath,reinterpret_cast(path)+0xF0,sizeof(savedPath)); + const auto oldBpp=Display13FBMemory::field(fb,0x4350); + const auto oldTiled=Display13FBMemory::field(fb,0x4A18); + Display13FBBootIO io{c.map}; + bool ready=dsiResourcesReady && !dsiStopAttempted && !c.memoryFaulted && !c.clockFaulted && + !c.dsiNativeWriters && !c.bridge.state.faulted && dsiModeCoherent(fb) && + Display13FBMemory::allocatedSlot(controller,fb)==0 && pool.state==1 && !pool.descriptor && + adlnSharedBinding(controller,o.accelerator) && + XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof) && + XELP::DSI::Surface::source(io,c.bootDSI) && + XELP::DSI::Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu)) && + XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,dsiFixedMode.timing, + reinterpret_cast(dsiComputeGeometry)); + IOLockUnlock(c.lock); + if(ready) { + // FB+4348 is still null, so the exact donor takes only the slot-zero + // FreeSysMem/freeFBMemory branch; no GPU free is reachable here. + FunctionCast(wrapAlderLakeNScanoutFree,orgAlderLakeNScanoutFree)(fb); + IOLockLock(c.lock); + receipt[5]=Display13FBMemory::field(controller,0xDF4); + receipt[6]=Display13FBMemory::field(controller,0xDF0); + receipt[7]=Display13FBMemory::field(fb,0x4288); + ready=Display13FBMemory::slotReturned(count,0,used,mask,receipt[5],receipt[6],receipt[7]) && + Display13FBMemory::poolReturned(pool,Display13FBMemory::pool(controller),0) && + !Display13FBMemory::field(fb,0x4348) && !c.memoryFaulted && + Display13FBMemory::field(fb,0x4334)==o.proof.pitch; + if(ready) { + *reinterpret_cast(reinterpret_cast(fb)+0x4348)=o.record; + *reinterpret_cast(reinterpret_cast(fb)+0x4330)=static_cast(o.proof.gpu); + c.pipe[0].scanout=o.proof; + __atomic_store_n(&c.pipe[0].publishedScanout,reinterpret_cast(o.record),__ATOMIC_RELEASE); + o.published=true;receipt[1]=2; + } + IOLockUnlock(c.lock); + if(ready)ready=updateDSIBootCache(controller,fb,path); + } + if(!o.published) { + IOLockLock(c.lock); + memcpy(reinterpret_cast(fb)+0x430C,savedFB,sizeof(savedFB)); + memcpy(reinterpret_cast(path)+0xF0,savedPath,sizeof(savedPath)); + memcpy(reinterpret_cast(fb)+0x4350,&oldBpp,sizeof(oldBpp)); + *(reinterpret_cast(fb)+0x4A18)=oldTiled; + IOLockUnlock(c.lock); + } + receipt[8]=ready;receipt[9]=o.published;receipt[10]=o.proof.gpu;receipt[11]=o.proof.pitch; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicBacking",receipt,sizeof(receipt)); + return ready; +} + +// The FB mutex spans native mode completion. These donor CPU leaves preserve +// transaction/notification semantics; the ordinary DDI modesetter is bypassed +// because the DSI link and the new primary plane are already running. +bool XE::publishDSINativeMode(IOService *fb) { + auto &o=dsiPendingBacking;auto &c=display13FBPower; + if(!dsiPublishedTarget(fb) || !dsiModeCoherent(fb) || !dsiModeCPU[0] || !dsiModeCPU[1] || !dsiModeCPU[2])return false; + if(o.modeReady)return dsiNativeUpdateAllowed(fb); + uint64_t receipt[12] {1,1}; + IOLockLock(c.lock);auto controller=static_cast(c.controller); + auto path=Display13FBMemory::singlePath(fb); + const bool ready=!dsiStopAttempted && dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.dsiNativeWriters && dsiArmedBackingOwned(fb,path) && + dsiSession.state==XELP::DSI::SessionState::Streaming && + !Display13FBMemory::field(controller,0xD0) && + !Display13FBMemory::field(fb,0x4340) && Display13FBMemory::field(fb,0x4338)==1; + IOLockUnlock(c.lock); + receipt[2]=ready;receipt[3]=ready && updateDSIBootCache(controller,fb,path); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); + if(!receipt[2] || !receipt[3])return false; + uint32_t tile=2; + receipt[2]=o.accelerator->callPlatformFunction("EFITilingMode",true,&tile,nullptr,nullptr,nullptr); + if(!receipt[2]) { + *reinterpret_cast(reinterpret_cast(controller)+0xE53)=0; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=XELP::DSI::AppleAdapter::FixedModeID; + *reinterpret_cast(reinterpret_cast(fb)+0x4254)=0; + receipt[3]=reinterpret_cast(dsiModeCPU[0])(fb); + if(!receipt[3] && !Display13FBMemory::field(fb,0x4340)) { + reinterpret_cast(dsiModeCPU[1])(fb,0); + reinterpret_cast(dsiModeCPU[2])(fb); + IOLockLock(c.lock); + receipt[4]=Display13FBMemory::field(fb,0x4000); + receipt[5]=Display13FBMemory::field(fb,0x4008); + receipt[6]=Display13FBMemory::field(fb,0x400C); + receipt[7]=Display13FBMemory::field(fb,0x4200); + o.modeReady=!receipt[4] && receipt[5]==UINT32_MAX && receipt[6]==UINT32_MAX && receipt[7]==1 && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !dsiStopAttempted && + dsiArmedBackingOwned(fb,path) && dsiPublishedTarget(fb); + receipt[8]=o.modeReady;receipt[9]=Display13FBMemory::field(fb,0x402C); + receipt[10]=Display13FBMemory::field(fb,0x4334);receipt[11]=Display13FBMemory::field(fb,0x4314); + IOLockUnlock(c.lock); + } + } + receipt[1]=2;IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); + return o.modeReady; +} + // A stopped stream may retain a native backing after partial cleanup. Its // ownership is distinct from the GOP pool, which stays reserved until later. bool XE::dsiArmedBackingOwned(IOService *fb,OSObject *path) { @@ -5681,16 +5803,28 @@ bool XE::retireDSINativeBacking(IOService *fb,OSObject *path) { if(!dsiSession.stopped(proof))return false; dsiCleanup=dsiSession.takeCleanup(); } - auto &o=dsiPendingBacking;o.armed=false; + auto &o=dsiPendingBacking;auto &c=display13FBPower; + const bool published=o.published;auto record=o.record; + IOLockLock(c.lock); + if(published && !dsiPublishedTarget(fb)) {IOLockUnlock(c.lock);return false;} + o.modeReady=false; + if(published) { + *reinterpret_cast(reinterpret_cast(fb)+0x4348)=nullptr; + __atomic_store_n(&c.pipe[0].publishedScanout,uintptr_t(0),__ATOMIC_RELEASE); + c.pipe[0].scanout={};o.published=false; + } + IOLockUnlock(c.lock);o.armed=false; if(discardDSINativeBacking(fb))return true; - o.armed=true;return false; + IOLockLock(c.lock); + if(published) {*reinterpret_cast(reinterpret_cast(fb)+0x4348)=record;o.published=true;} + o.armed=true;IOLockUnlock(c.lock);return false; } -// P2's first real fixed-mode request tries a private identification surface +// The first real fixed-mode request establishes an identification surface // while the native FB mutex is held. Retain any armed backing until stop. -// Do this once after allocation starts; +// Backing publication shares the native allocation serializer. Do this once after allocation starts; // an unavailable accelerator may be retried by a later mode request. Never -// publish FB backing or report a WindowServer mode as established here. +// report a WindowServer mode before its subsequent CPU completion succeeds. void XE::probeDSINativeBacking(IOService *fb) { if(dsiBackingAttempted)return; dsiBackingAttempted=true; @@ -5734,7 +5868,7 @@ bool XE::discardDSINativeBacking(IOService *fb) { owner=o.accelerator;pipe=o.pipe;record=o.record;mapping=o.pinnedMap;memory=o.pinnedMemory; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); __atomic_store_n(&o.pipe,static_cast(nullptr),__ATOMIC_RELEASE); - o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=false;o.nativeResult=0;o.proof={};o.image={}; + o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=o.published=o.modeReady=false;o.nativeResult=0;o.proof={};o.image={}; } IOFree(record,sizeof(XELP::DSI::NativeBacking::Record)); if(mapping)mapping->release();if(memory)memory->release();if(pipe)pipe->release();owner->release();fb->release();return true; @@ -7052,6 +7186,15 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}; if(compute!=address+0x31484 || memcmp(reinterpret_cast(compute),entry,sizeof(entry)))return false; dsiComputeGeometry=compute; + const char *modeSymbols[]={"__ZN21AppleIntelFramebuffer21allocateLogicalBufferEv", + "__ZN21AppleIntelFramebuffer28setupInitialTransactionStateEj", + "__ZN21AppleIntelFramebuffer18setTransactionCapsEv"}; + const uint32_t modeOffsets[]={0x31726,0x318E8,0x319D2}; + for(unsigned i=0;i<3;++i) { + const auto fn=patcher.solveSymbol(index,modeSymbols[i],address,size);patcher.clearError(); + if(fn!=address+modeOffsets[i] || memcmp(reinterpret_cast(fn),entry,4))return false; + dsiModeCPU[i]=fn; + } KernelPatcher::RouteRequest routes[]={ {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, @@ -7159,7 +7302,15 @@ uint32_t XE::wrapDSIDDC(IOService *fb,uint32_t block,uint8_t *data,uint64_t *len uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,uint8_t *edid) { if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); + if(Display13FBMemory::field(fb,0x1DC)) { + // Explicit development isolation; leave physical wiring and the + // ordinary external-output backend intact for coexistence testing. + if(callbackXE->dsiPanelOnly) { + if(!online || !edid)return kIOReturnBadArgument; + *online=0;bzero(edid,128);return kIOReturnSuccess; + } + return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); + } if(!online || !edid)return kIOReturnBadArgument; *online=0;bzero(edid,128); DSIModeScope scope(fb); @@ -7348,12 +7499,13 @@ static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { } // Native timing/resource setup can disable or reallocate before hwSetMode. -// Refuse DSI here until its mode transaction owns those conditional steps. +// DSI uses its own fixed-mode transaction to retain the adopted host link. uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),UINT32_MAX); const auto result=[&]() -> uint32_t { if(callbackXE->adlnOutputWiring.dsi) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(callbackXE->dsiPanelOnly && Display13FBMemory::field(fb,0x1DC))return kIOReturnNotReady; DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { // A bounded receipt of the real request. Keep the donor's mode @@ -7363,9 +7515,9 @@ uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, Display13FBMemory::field(fb,0x420C)}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); - if(mode==XELP::DSI::AppleAdapter::FixedModeID && depth==0 && dsiModeTarget(fb) && - dsiNativeUpdateAllowed(fb))callbackXE->probeDSINativeBacking(fb); - return kIOReturnNotReady; + if(mode!=XELP::DSI::AppleAdapter::FixedModeID || depth || !dsiModeTarget(fb))return kIOReturnNotReady; + if(dsiNativeUpdateAllowed(fb) && !dsiPendingTarget(fb))callbackXE->probeDSINativeBacking(fb); + return callbackXE->publishDSINativeMode(fb) ? kIOReturnSuccess : kIOReturnNotReady; } } return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); @@ -7384,7 +7536,7 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { if(!dsiLifecycleTarget(fb,path))return true; auto &c=display13FBPower;if(!c.lock)return false; IOLockLock(c.lock); - const bool ready=!dsiPendingTarget(fb) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && + const bool ready=(!dsiPendingTarget(fb) || (dsiPublishedTarget(fb) && dsiPendingBacking.modeReady)) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && c.transcoders.dsi0Pipe==0 && path && path->getMetaClass()->getClassSize()>=0x3730 && @@ -7395,11 +7547,11 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { IOLockUnlock(c.lock);return ready; } -// Owner lock held. P2 reserves pipe A for DSI even after its mapping is +// Owner lock held. DSI reserves pipe A even after its mapping is // withdrawn. Late shared callbacks must not revive it or fault peer outputs. // An unbound/ambiguous callback cannot establish a different output owner. bool XE::dsiSharedWriterStopped(IOService *fb,unsigned pipe) { - return callbackXE->adlnOutputWiring.dsi && (dsiPendingTarget(fb) || + return callbackXE->adlnOutputWiring.dsi && ((dsiPendingTarget(fb) && !dsiPendingBacking.modeReady) || (callbackXE->dsiStopAttempted && (pipe==0 || pipe>=3 || dsiModeTarget(fb)))); } @@ -7655,7 +7807,8 @@ bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) } } XELP::DSI::NativeCache::View view; - ready=addresses && XELP::DSI::NativeCache::read(io,dsiBoot,xelpMemory,scale,view); + ready=addresses && (dsiPublishedTarget(fb) ? XELP::DSI::NativeCache::readActive(io,dsiBoot,scale,view) : + XELP::DSI::NativeCache::read(io,dsiBoot,xelpMemory,scale,view)); if(ready)XELP::DSI::NativeCache::publish(view,path,plane,scaler); } IOLockUnlock(c.lock);return ready; @@ -9669,7 +9822,7 @@ bool XE::wrapAlderLakeNScanoutAllocate(IOService *fb) { binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); pipe=binding.pipe; } - if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return false;} + if(dsiPendingTarget(fb) || dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return false;} record[12]=pipe;record[13]=static_cast(binding.status);record[14]=dual; if(pipe<3 && !c.pipe[pipe].destroyingPath && (!dual || binding.status==TGLFBBinding::Status::Complete) && (c.bridge.bootAdopted || !pipe) && callbackXE->display13FBMemoryHooksReady && controller && c.bridge.begun && @@ -9737,7 +9890,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { IOWorkLoop *acquiredSwapGate=nullptr;OSObject *stopPath=nullptr;bool stopSwap=false; IOLockLock(c.lock); pipe=Display13FBMemory::boundPipe(c.pipe,fb);record[18]=pipe; - if(dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} + if(dsiPendingTarget(fb) || dsiSharedWriterStopped(fb,pipe)) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); record[19]=static_cast(binding.status); if(callbackXE->display13FBMemoryHooksReady && pipe<3 && !c.pipe[pipe].destroyingPath && !c.memoryFaulted && diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 6541c806..531d9412 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -245,9 +245,12 @@ class XE { static uint32_t wrapDSISetGammaTable(IOService *,uint32_t,uint32_t,uint32_t,void *,bool); bool dsiNativeWriterHooksReady {}; bool dsiBackingHooksReady {}; + bool dsiPanelOnly {}; mach_vm_address_t orgDSIBacking[2] {}; bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); - mach_vm_address_t dsiComputeGeometry {}; + mach_vm_address_t dsiComputeGeometry {},dsiModeCPU[3] {}; + bool publishDSINativeBacking(); + bool publishDSINativeMode(IOService *); bool probeDSIModeGeometry(IOService *); bool prepareDSINativeBacking(IOService *); bool validateDSINativeBacking(); diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index f0bcbb62..030ebb2c 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -21,6 +21,7 @@ XELP::ProbeConfig seen;unsigned calls=0; bool configure(void *c,const XELP::ProbeConfig *p){assert(c==(void*)1);seen=*p;++calls;return p->valid();} struct XE { + bool dsiPanelOnly=false; XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; diff --git a/tests/display13_dbuf_setup_fixture.cpp b/tests/display13_dbuf_setup_fixture.cpp index 4dd6ae8f..2304114f 100644 --- a/tests/display13_dbuf_setup_fixture.cpp +++ b/tests/display13_dbuf_setup_fixture.cpp @@ -97,6 +97,7 @@ void XE::wrapDisplay13WMArmUpdate(IOService *fb){ } } IOService *pendingBackingFB=nullptr; +struct {bool modeReady=false;} dsiPendingBacking; bool dsiPendingTarget(IOService *fb){return fb && fb==pendingBackingFB;} // PRODUCTION_SETUP alignas(16) static uint8_t ctl[0x1c20],fb[3][0x4b90],paths[3][0x3730],planes[3][0x1c8]; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 7f6bdcdc..7e8ea1d7 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -79,7 +79,7 @@ struct {IOLock *lock;void *controller;Display13Transcoder::Bindings transcoders;XELP::DSI::Readout bootDSI; bool memoryFaulted=false;struct {struct {bool faulted=false;} state;} bridge;} display13FBPower; struct XE { - bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; + bool dsiPanelOnly=false;bool display13DSIModeHooksReady=true;Display13OutputWiring adlnOutputWiring;void *adlnTopologyController=nullptr; uint8_t adlnPlatform[0x1c0]{};uintptr_t orgDSIMode[9]{}; static bool updateReady; static bool dsiNativeUpdateAllowed(IOService *){return updateReady;} @@ -159,6 +159,9 @@ Display13FBMemory::field(fb,0x1E0)=1; assert(XE::wrapDSIGetCurrentMode(peer,¤t,&depth)==7 && current==77 && depth==3); assert(XE::wrapDSIGetOnlineInfo(peer,peer,&online,edid)==8 && online==9 && edid[0]==42); + xe.dsiPanelOnly=true;auto beforeOnline=nativeOnlineCalls; + assert(!XE::wrapDSIGetOnlineInfo(peer,peer,&online,edid) && !online && !edid[0] && nativeOnlineCalls==beforeOnline); + assert(XE::wrapDSIGetOnlineInfo(peer,peer,nullptr,edid)==kIOReturnBadArgument);xe.dsiPanelOnly=false; xe.adlnOutputWiring.dsi=0;assert(XE::wrapDSIGetCurrentMode(fb,¤t,&depth)==7); assert(XE::wrapDSIGetOnlineInfo(fb,peer,&online,edid)==8);xe.adlnOutputWiring.dsi=1; assert(nativeCurrentCalls==2 && nativeOnlineCalls==2); diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py index 7554bc00..fb685eec 100644 --- a/tests/dsi_backing_adapter.py +++ b/tests/dsi_backing_adapter.py @@ -102,6 +102,7 @@ static uint32_t wrapDSIBackingAllocate(OSObject*,void*,uint32_t); static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); bool paintDSINativeBacking(unsigned);Session dsiSession; + bool publishDSINativeBacking(){assert(gpuDepth==1 && fbDepth && !ownerDepth);return true;} bool switchDSINativeSurface(){assert(gpuDepth==1 && fbDepth && !ownerDepth);return true;} bool probeDSIModeGeometry(IOService*){assert(fbDepth && !ownerDepth && !gpuDepth);return true;} bool prepareDSINativeBacking(IOService*);bool validateDSINativeBacking();bool discardDSINativeBacking(IOService*); diff --git a/tests/dsi_native_cache.py b/tests/dsi_native_cache.py index 31e56b99..acfcce23 100644 --- a/tests/dsi_native_cache.py +++ b/tests/dsi_native_cache.py @@ -55,6 +55,7 @@ void originalCache(IOService*,IOService*,OSObject*){++nativeCache;} void originalCRTC(IOService*,IOService*,OSObject*){++nativeCRTC;} #define FunctionCast(f,p) reinterpret_cast(p) +bool publishedBacking=false;bool dsiPublishedTarget(IOService*){return publishedBacking;} struct XE { bool dsiCacheHooksReady=true,dsiResourcesReady=true;IOService *adlnTopologyController; Display13OutputWiring adlnOutputWiring;alignas(8) uint8_t adlnPlatform[0x1C0]{}; @@ -109,6 +110,12 @@ assert(Display13FBMemory::field(&path,0x18C)==0x1234 && Display13FBMemory::field(&plane,0x104)==0x5001); assert(Display13FBMemory::field(&scaler,0x48)==0x11 && Display13FBMemory::field(&scaler,0x4C)==0x22); XE::wrapDSIGetCRTC(&controller,&fb,&path);assert(!nativeCache && !nativeCRTC && !faults); + auto originalRegs=io.regs;publishedBacking=true; + io.regs[0x7019C]=0x40400000;io.regs[0x70180]=0x94001008;io.regs[0x70188]=34;io.regs[0x70080]=0x27; + assert(xe.updateDSIBootCache(&controller,&fb,&path)); + assert(Display13FBMemory::field(&plane,0x120)==0x40400000 && Display13FBMemory::field(&path,0x170)==0x27); + io.regs[0x6B000]^=1;unchanged();io.regs=originalRegs;publishedBacking=false; + assert(xe.updateDSIBootCache(&controller,&fb,&path)); b.ddiRefs[0]=0;XE::wrapDSIGetCRTC(&controller,&fb,&path);assert(faults==1 && c.memoryFaulted);b.ddiRefs[0]=1;c.memoryFaulted=false; put(&path,0x3640,1U);put(&port,0x120,1U);XE::wrapDSIGetCRTC(&controller,&fb,&path);XE::wrapDSIUpdateCache(&controller,&fb,&path); assert(nativeCRTC==1 && nativeCache==1); diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index 30f560aa..4efd5d62 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -9,6 +9,14 @@ using namespace XELP::DSI; using Display13FBMemory::field; templatevoid put(void *p,unsigned offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} +unsigned notifications=0; +void notify(void *){++notifications;} +void zero(void *p,size_t n){memset(p,0,n);} +void patchCall(uint8_t *image,unsigned call,uintptr_t function) { + const unsigned stub=0x200000;image[stub]=0x48;image[stub+1]=0xB8; + memcpy(image+stub+2,&function,8);image[stub+10]=0xFF;image[stub+11]=0xE0; + const int32_t delta=stub-call-5;assert(image[call]==0xE8);memcpy(image+call+1,&delta,4); +} int main(int argc,char **argv) { alignas(8) uint8_t fb[0x4B90]{},controller[0x1C20]{},path[0x3730]{}; put(fb,0x1D0,controller);put(fb,0x4A00,1U);put(fb,0x4A08,path); @@ -33,6 +41,12 @@ int main(int argc,char **argv) { assert(bytes.size()<0x400000); auto image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0)); assert(image!=MAP_FAILED);memcpy(image,bytes.data(),bytes.size()); + patchCall(image,0x3B841,reinterpret_cast(zero)); + uintptr_t vtable[0x220/8]{};vtable[0x218/8]=reinterpret_cast(notify); + auto object=vtable;put(controller,0xC10,&object);put(controller,0x1BB4,uint8_t(1)); + put(controller,0xD5C,1U);put(controller,0xD60,fb);put(fb,0x1E0,uint8_t(1)); + alignas(8) uint8_t port[0x580]{},config[0x30]{}; + put(path,0x3648,port);put(port,0x548,config); const auto compute=reinterpret_cast(image+0x31484); for(int joined:{-1,0})for(bool owner:{false,true}) { put(fb,0x3FD8,joined);put(fb,0x49E8,1U);put(fb,0x49F0,path); @@ -43,8 +57,14 @@ int main(int argc,char **argv) { // Exact unscaled logical-buffer path must be a successful no-op. assert(reinterpret_cast(image+0x31726)(fb)==0); assert(!field(fb,0x4340)); + reinterpret_cast(image+0x318E8)(fb,0); + assert(!field(fb,0x4000) && field(fb,0x4008)==UINT32_MAX && + field(fb,0x400C)==UINT32_MAX && field(fb,0x4200)==1); + reinterpret_cast(image+0x319D2)(fb); + assert(field(fb,0x402C)>=1 && field(fb,0x402C)<=2); + } - munmap(image,0x400000);puts("DSI native mode geometry and logical-buffer replay passed"); + assert(notifications==4);munmap(image,0x400000);puts("DSI native mode geometry and logical-buffer replay passed"); } puts("DSI native mode preparation rollback and admission passed"); } diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index 79945264..4f1f52e9 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -5,8 +5,9 @@ root=Path(__file__).resolve().parents[1] s=(root/'WhateverXe/kern_xe.cpp').read_text() functions='\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in - ('switchDSINativeSurface','dsiArmedBackingOwned','retireDSINativeBacking')) + ('switchDSINativeSurface','dsiArmedBackingOwned','retireDSINativeBacking','publishDSINativeBacking','publishDSINativeMode')) state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] +published=re.search(r'static bool dsiPublishedTarget\(.*?\n\}',s,re.S)[0] # Share the established real power/IRQ/clock fixture; no implementation bodies # are rewritten. Allocation/PTE proof and CPU mapping have their own adapter test. q=(root/'tests/dsi_quiesce_adapter.py').read_text() @@ -15,7 +16,11 @@ prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_test_image.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"') b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') -prelude=prelude[:prelude.index('void tick(unsigned us)')]+r''' +prelude=prelude.replace('#include "kern_dsi_native_backing.hpp"','#include "kern_dsi_native_backing.hpp"\n#include "kern_dsi_native_mode.hpp"') +prelude=prelude.replace('struct {uintptr_t record=0;}scanout;','Display13FBMemory::SurfaceReadout scanout;') +prelude=prelude.replace('struct {bool ready=true;}dsiFixedMode;','struct {bool ready=true;AppleAdapter::Timing timing;}dsiFixedMode;') +prelude=prelude.replace(' void retain(){',' uint32_t callPlatformFunction(const char*,bool,void*,void*,void*,void*);\n void retain(){') +prelude=prelude[:prelude.index('void tick(unsigned us)')]+published+r''' uint64_t elapsed=0;unsigned gpuDepth=1;bool failPaint=false,failDiscard=false;unsigned discards=0; templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} void *current_thread(){return reinterpret_cast(1);} @@ -27,9 +32,34 @@ void IOSleep(unsigned ms){tick(ms*1000);}void IODelay(unsigned us){tick(us);} struct Display13FBClockIO:Display13FBBootIO {}; bool adlnSharedBinding(void *controller,void *owner){return controller==display13FBPower.controller && owner && mappingBound;} +bool failSlotReturn=false,failCache=false,failLogical=false;unsigned slotReturns=0,cpuInitial=0,cpuCaps=0; +#define FunctionCast(f,p) reinterpret_cast(p) +bool dsiModeCoherent(IOService*){return dsiFixedMode.ready;} +uint32_t OSObject::callPlatformFunction(const char *name,bool wait,void *arg,void *b,void *c,void *d) { + assert(locks==std::vector{1} && !gpuDepth && !strcmp(name,"EFITilingMode") && wait && !b && !c && !d); + assert(*static_cast(arg)==2);return 0; +} +void compute(void *fb,const AppleAdapter::Timing*) { + assert(locks==std::vector({1,3}) && gpuDepth==1); + for(auto off:{0x430CU,0x4320U,0x4328U})put(fb,off,1080U); + for(auto off:{0x4310U,0x4324U,0x432CU})put(fb,off,1920U); + put(fb,0x4314,8355840U);put(fb,0x4334,4352U);put(fb,0x4338,1U); + auto path=Display13FBMemory::singlePath(fb); + put(path,0xF0,uint64_t(0));put(path,0x100,uint64_t(0)); + put(path,0xF8,1080U);put(path,0xFC,1920U);put(path,0x108,1080U);put(path,0x10C,1920U); +} +void freeSlot(IOService *fb) { + assert(locks==std::vector{1} && gpuDepth==1 && !Display13FBMemory::field(fb,0x4348)); + ++slotReturns;if(failSlotReturn)return; + auto controller=display13FBPower.controller;put(controller,0xDF0,0U);put(controller,0xDF4,uint8_t(0));put(fb,0x4288,uint8_t(0xFF)); +} +uint32_t logical(IOService*){assert(!gpuDepth && locks==std::vector{1});return failLogical?1:0;} +void initial(IOService *fb,uint32_t arg){assert(!gpuDepth && locks==std::vector{1} && !arg);++cpuInitial; + put(fb,0x4000,0U);put(fb,0x4008,UINT32_MAX);put(fb,0x400C,UINT32_MAX);put(fb,0x4200,1U);} +void caps(IOService *fb){assert(!gpuDepth && locks==std::vector{1});++cpuCaps;put(fb,0x402C,1U);} uint64_t receipt[24]{}; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *name,const void *p,unsigned n){assert(!strcmp(name,"WEXDSISurface") && n==sizeof(receipt));memcpy(receipt,p,n);}}; + void setProperty(const char *name,const void *p,unsigned n){if(!strcmp(name,"WEXDSISurface")){assert(n==sizeof(receipt));memcpy(receipt,p,n);}else assert(n==96);}}; struct XE { bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiCacheHooksReady=true,dsiBackingHooksReady=true; bool dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false; @@ -38,6 +68,12 @@ if(failPaint)return false;dsiPendingBacking.image.digest=0x686999c5;return true;} bool discardDSINativeBacking(IOService *fb){assert(locks==std::vector{1} && !gpuDepth && !dsiPendingBacking.armed && dsiPendingTarget(fb)); ++discards;if(failDiscard)return false;dsiPendingBacking={};return true;} + uintptr_t dsiComputeGeometry=reinterpret_cast(compute),orgAlderLakeNScanoutFree=reinterpret_cast(freeSlot); + uintptr_t dsiModeCPU[3]{reinterpret_cast(logical),reinterpret_cast(initial),reinterpret_cast(caps)}; + static void wrapAlderLakeNScanoutFree(IOService*){} + bool updateDSIBootCache(IOService*,IOService*,OSObject*){assert(locks==std::vector{1});return !failCache;} + bool dsiNativeUpdateAllowed(IOService*){return dsiPendingBacking.modeReady && !display13FBPower.memoryFaulted;} + bool publishDSINativeBacking();bool publishDSINativeMode(IOService*); bool switchDSINativeSurface();bool dsiArmedBackingOwned(IOService*,OSObject*);bool retireDSINativeBacking(IOService*,OSObject*); }; ''' @@ -61,6 +97,9 @@ o.pinnedMap=&mapping;o.pinnedMemory=&memory;o.record=&record;o.thread=1;o.allocating=o.allocated=true; auto &p=o.proof;p.status=Display13FBMemory::SurfaceStatus::Complete;p.record=reinterpret_cast(&record); p.refs=p.kind=1;p.map=record.map;p.memory=record.memory;p.gpu=record.gpu;p.bytes=8355840;p.pitch=4352;p.verifiedPages=2040; + assert(AppleAdapter::makeTiming(xe.dsiProfile,dsiFixedMode.timing)); + put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&controller,0xDF5,uint8_t(3));put(&controller,0xDF4,uint8_t(1));put(&controller,0xDF0,1U); + put(&controller,0x1A10,1U);put(&fb,0x4288,uint8_t(0));failSlotReturn=failCache=failLogical=false;slotReturns=cpuInitial=cpuCaps=0; o.image.complete=true;o.image.digest=0xf89939c5;elapsed=0;gpuDepth=1;failPaint=failDiscard=false;discards=0;memset(receipt,0,sizeof(receipt)); ''') code=prelude+functions+'\n'+case+r''' @@ -83,6 +122,22 @@ assert(dsiPendingBacking.armed && c.xe.dsiSession.state==SessionState::Faulted && !c.retire() && !discards); c.stopped();failDiscard=true;assert(!c.retire() && dsiPendingBacking.armed && c.xe.dsiSession.state==SessionState::Released); assert(c.xe.dsiArmedBackingOwned(&c.fb,&c.path));failDiscard=false;assert(c.retire() && discards==2);} + {Case c;assert(c.run());auto &o=dsiPendingBacking;o.published=o.modeReady=true; + put(&c.fb,0x4348,&c.record);display13FBPower.pipe[0].publishedScanout=reinterpret_cast(&c.record); + assert(!c.retire() && dsiPublishedTarget(&c.fb));c.stopped();failDiscard=true; + assert(!c.retire() && dsiPublishedTarget(&c.fb) && !o.modeReady && o.armed); + failDiscard=false;assert(c.retire() && !Display13FBMemory::field(&c.fb,0x4348) && !display13FBPower.pipe[0].publishedScanout);} + for(unsigned failure=0;failure<4;++failure) {Case c;assert(c.run()); + put(&c.fb,0x4348,uintptr_t(0));put(&c.fb,0x4288,uint8_t(0)); + failSlotReturn=failure==1;failCache=failure==2;failLogical=failure==3; + locks={1};gpuDepth=1;dsiPendingBacking.allocating=true; + const bool published=c.xe.publishDSINativeBacking();dsiPendingBacking.allocating=false;gpuDepth=0; + assert(published==(failure==0 || failure==3) && slotReturns==1); + const bool ready=c.xe.publishDSINativeMode(&c.fb); + assert(ready==(failure==0));assert(cpuInitial==(failure==0) && cpuCaps==(failure==0)); + if(!failure)assert(dsiPublishedTarget(&c.fb) && dsiPendingBacking.modeReady && Display13FBMemory::field(&c.fb,0x4330)==c.record.gpu); + locks.clear(); + } puts("PASS production DSI surface: actual MMIO allowlist/WM/latch, second image frame, GPU/owner/IRQ locking, retained partial switches and stopped cleanup retry"); } ''' diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index fab3a787..402a70af 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -32,7 +32,8 @@ assert(!strcmp(name,"WEXDSIModeRequest") && bytes==64); const auto words=static_cast(data);assert(words[0]==1); }}; -IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}using IOLock=int; +IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}using IOLock=int;struct {bool modeReady=false;} dsiPendingBacking; +bool dsiPublishedTarget(IOService *fb){return dsiPendingTarget(fb) && dsiPendingBacking.modeReady;} struct IORecursiveLock {unsigned depth=0;}; unsigned owner=0,modeCalls=0,armCalls=0,backingCalls=0;IORecursiveLock fbLock;bool expectDSI=false,endingWriter=false; void IOLockLock(IOLock*){assert((fbLock.depth || endingWriter) && !owner);++owner;}void IOLockUnlock(IOLock*){assert(owner==1);--owner;} @@ -67,6 +68,7 @@ uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; + bool dsiPanelOnly=false;bool publishDSINativeMode(IOService*){return false;} bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; static void recordDSIClient(IOService*,unsigned,uint64_t,uint32_t){}; diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index 6ea73ef9..37798fe8 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -1,6 +1,7 @@ { "-wex-dsi-audit": [], "-wex-dsi-adopt": [], + "-wex-dsi-panel-only": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" From 26ab0f3b710146725686ca8a519b3dcd6030d6dc Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:30:42 +0900 Subject: [PATCH 059/139] Supply profile-derived DSI EDID for standard mode enumeration --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_apple_timing.hpp | 33 +++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 39 +++++++++++++++++++++++----- tests/dsi_apple_adapter.py | 12 ++++++--- tests/dsi_apple_timing.cpp | 16 ++++++++++++ 6 files changed, 92 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 94a1e22d..6e4b3c03 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and enables native path and fixed-mode setup; a complete accelerated desktop is not yet supported. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode setup; a complete accelerated desktop is not yet supported. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 0c34fc86..0dc0567d 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、native pathと固定modeの設定を有効にします。完全なアクセラレーション付きデスクトップには未対応です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeの設定を行います。完全なアクセラレーション付きデスクトップには未対応です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index d9836fb4..883aef56 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -33,6 +33,39 @@ inline bool annotateMode(const Mode &m,ModeInformation &info) { info.flags|=0x02000407; return true; } +// Synthetic Apple enumeration descriptor derived only from the panel profile. +// EDID wire layout reference: Linux v6.12 include/drm/drm_edid.h. This is not +// an EDID read from the panel and does not advertise DP/HDMI, HDR or DPMS. +inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128]) { + memset(edid,0,sizeof(edid)); + const uint32_t clock=(m.clockKHz+5)/10; + if(!m.valid() || !clock || clock>65535 || m.width>4095 || m.height>4095 || + m.hblank>4095 || m.vblank>4095 || m.hfront>1023 || m.hsync>1023 || + m.vfront>63 || m.vsync>63 || m.widthMM>4095 || m.heightMM>4095 || + m.widthMM>2550 || m.heightMM>2550)return false; + const uint32_t rounded=clock*10; + const uint32_t error=rounded>m.clockKHz ? rounded-m.clockKHz : m.clockKHz-rounded; + if(uint64_t(error)*10000>m.clockKHz)return false; // retained link: at most 100 ppm + const uint8_t header[]={0,255,255,255,255,255,255,0};memcpy(edid,header,8); + edid[8]=0x5C;edid[9]=0xB8; // synthetic WEX identity, no panel serial + edid[10]=0x10;edid[11]=0xD5; + edid[18]=1;edid[19]=4;edid[20]=0xA0; // digital RGB8, transport unspecified + edid[21]=static_cast((m.widthMM+5)/10); + edid[22]=static_cast((m.heightMM+5)/10); + edid[23]=0xFF;edid[24]=2; // gamma unspecified, preferred DTD, fixed frequency + for(unsigned i=38;i<54;++i)edid[i]=1; // no standard/established timings + auto d=edid+54;d[0]=clock;d[1]=clock>>8; + d[2]=m.width;d[3]=m.hblank;d[4]=((m.width>>8)<<4)|(m.hblank>>8); + d[5]=m.height;d[6]=m.vblank;d[7]=((m.height>>8)<<4)|(m.vblank>>8); + d[8]=m.hfront;d[9]=m.hsync;d[10]=((m.vfront&15)<<4)|(m.vsync&15); + d[11]=((m.hfront>>8)<<6)|((m.hsync>>8)<<4)|((m.vfront>>4)<<2)|(m.vsync>>4); + d[12]=m.widthMM;d[13]=m.heightMM;d[14]=((m.widthMM>>8)<<4)|(m.heightMM>>8); + d[17]=0x18; // separate negative sync, matching the fixed Apple timing + edid[75]=0xFC;memcpy(edid+77,"WEX DSI VBT\n ",13); + edid[93]=0x10;edid[111]=0x10; // unused descriptor slots + uint8_t sum=0;for(unsigned i=0;i<127;++i)sum+=edid[i];edid[127]=static_cast(0-sum); + return true; +} inline bool makeTiming(const Profile &p,Timing &result) { result={};const auto &m=p.mode; if(!p.valid() || !m.valid())return false; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e4a90c93..f09c59fb 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7287,18 +7287,29 @@ bool XE::releaseDSIFixedMode(IOService *fb) { // IOGraphics also requests EDID independently of online detection. Stop at // the shared internal DDC entry, before it acquires AUX power on behalf of -// the built-in panel. DSI exposes its profile through the fixed-mode API. +// the built-in panel. Supply one synthetic profile descriptor from CPU memory. uint32_t XE::wrapDSIDDC(IOService *fb,uint32_t block,uint8_t *data,uint64_t *length,uint32_t flags,OSObject *path) { if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); - if(length)*length=0; - recordDSIClient(fb,12,block,kIOReturnUnsupported);return kIOReturnUnsupported; + const auto result=[&]() -> uint32_t { + if(!length || !data)return kIOReturnBadArgument; + const auto capacity=*length;*length=0; + if(block!=1 || flags)return kIOReturnUnsupported; + if(capacity<128)return kIOReturnBadArgument; + DSIModeScope scope(fb); + if(!scope.lock || !dsiModeTarget(fb) || !dsiModeCoherent(fb) || + (path && path!=Display13FBMemory::singlePath(fb)))return kIOReturnNotReady; + uint8_t edid[128]; + if(!XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,edid))return kIOReturnNotReady; + memcpy(data,edid,sizeof(edid));*length=sizeof(edid);return kIOReturnSuccess; + }(); + recordDSIClient(fb,12,block,result);return result; } // Native online detection assumes eDP AUX/EDID. A DSI panel has no such // sink. Answer only for the real adopted path and its immutable fixed mode; -// returning an empty EDID does not manufacture panel data or touch AUX power. +// its synthetic profile descriptor is supplied without touching AUX power. uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,uint8_t *edid) { if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; @@ -7316,8 +7327,10 @@ uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,u DSIModeScope scope(fb); const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); - if(ready)*online=1; - const auto result=ready ? kIOReturnSuccess : kIOReturnNotReady; + uint8_t descriptor[128]; + const bool described=ready && XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,descriptor); + if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));} + const auto result=described ? kIOReturnSuccess : kIOReturnNotReady; recordDSIClient(fb,11,*online,result);return result; } @@ -7346,6 +7359,20 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { const auto result=[&]() -> uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + // Observe the actual standard enumeration offer before extending ID + // admission. The retained hardware mode and array remain unchanged. + uint64_t offer[16] {1,array ? array->getCount() : 0,UINT32_MAX}; + if(array && array->getCount()<=1024)for(unsigned i=0;igetCount();++i) { + auto bytes=OSDynamicCast(OSData,array->getObject(i)); + if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; + const auto &t=*static_cast(bytes->getBytesNoCopy()); + if(t.horizontalActive!=dsiFixedMode.mode.width || t.verticalActive!=dsiFixedMode.mode.height)continue; + offer[2]=i;offer[3]=t.reservedA[0];offer[4]=t.pixelClock; + offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; + offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; + offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled;break; + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingOffer",offer,sizeof(offer)); auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; if(!data || data->getLength()!=sizeof(dsiFixedMode.timing) || memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)))return kIOReturnUnsupported; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 7e8ea1d7..f2fc5ee4 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -61,7 +61,7 @@ ~IOService(){for(auto i:properties)i.second->release();} }; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *,const void *p,unsigned n){assert(n==128);const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; + void setProperty(const char *key,const void *p,unsigned n){assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} @@ -136,13 +136,17 @@ assert(!xe.bindDSIFixedMode(fb,p) && !dsiModeTarget(fb) && alive==before && !calls[0]);} dataOK=arrayOK=true;display13FBPower.memoryFaulted=false; assert(xe.bindDSIFixedMode(fb,p) && fb->refs==2 && dsiFixedMode.array->refs==2 && dsiFixedMode.data->refs==2); - // Adopted DSI presence does not call native EDID/AUX or alter the EDID identity. + // Profile-derived enumeration never calls native EDID/AUX. uint8_t online=0,edid[128];memset(edid,0xCC,sizeof(edid));uint64_t length=128; - assert(XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr)==kIOReturnUnsupported && !length && edid[0]==0xCC && !nativeDDCCalls); + assert(!XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr) && length==128 && edid[0]==0 && edid[1]==255 && !nativeDDCCalls); + uint8_t expected[128];assert(makePanelEDID(p.mode,expected) && !memcmp(edid,expected,128)); + length=127;memset(edid,0xCC,128);assert(XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr)==kIOReturnBadArgument && !length && edid[0]==0xCC); + length=128;assert(XE::wrapDSIDDC(fb,2,edid,&length,0,nullptr)==kIOReturnUnsupported && !length); + length=128;assert(XE::wrapDSIDDC(fb,1,edid,&length,1,nullptr)==kIOReturnUnsupported && !length); assert(XE::wrapDSIDDC(peer,2,edid,&length,1,testPath)==9 && length==128 && nativeDDCCalls==1); xe.adlnOutputWiring.dsi=0;assert(XE::wrapDSIDDC(fb,2,edid,&length,1,testPath)==9 && nativeDDCCalls==2);xe.adlnOutputWiring.dsi=1; assert(!XE::wrapDSIGetOnlineInfo(fb,peer,&online,edid) && online==1 && !nativeOnlineCalls); - for(auto b:edid)assert(b==0); + assert(!memcmp(edid,expected,128)); assert(XE::wrapDSIGetOnlineInfo(fb,fb,&online,edid)==kIOReturnNotReady && !online); assert(XE::wrapDSIGetOnlineInfo(fb,peer,nullptr,edid)==kIOReturnBadArgument); int32_t current=-9,depth=-9; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 04950912..7f8192e4 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -10,6 +10,22 @@ int main() { assert(t.verticalActive==1920&&t.verticalBlanking==82&&t.verticalSyncOffset==40&&t.verticalSyncWidth==2); assert(t.bitsPerColorComponent==2&&t.pixelEncoding==1&&t.numLinks==1); assert(!t.scalerFlags&&!t.horizontalScaled&&!t.verticalScaled&&!t.verticalBlankingExtension); + uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); + unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); + assert(edid[18]==1 && edid[19]==4 && edid[20]==0xA0 && edid[24]==2 && !edid[126]); + const auto d=edid+54; + assert((unsigned(d[0])+(unsigned(d[1])<<8))*10==135260); + assert((d[2]|((d[4]&0xF0)<<4))==1080 && (d[3]|((d[4]&15)<<8))==46); + assert((d[5]|((d[7]&0xF0)<<4))==1920 && (d[6]|((d[7]&15)<<8))==82); + assert((d[8]|((d[11]>>6)<<8))==22 && (d[9]|(((d[11]>>4)&3)<<8))==2); + assert(((d[10]>>4)|(((d[11]>>2)&3)<<4))==40 && ((d[10]&15)|((d[11]&3)<<4))==2); + assert(d[12]==86 && d[13]==154 && !d[14] && d[17]==0x18); + for(unsigned i=35;i<38;i++)assert(!edid[i]);for(unsigned i=38;i<54;i++)assert(edid[i]==1); + assert(edid[75]==0xFC && !memcmp(edid+77,"WEX DSI VBT",11)); + auto bad=p.mode;bad.width=4096;assert(!AppleAdapter::makePanelEDID(bad,edid)); + bad=p.mode;bad.vfront=64;assert(!AppleAdapter::makePanelEDID(bad,edid)); + bad=p.mode;bad.clockKHz=10001;assert(AppleAdapter::makePanelEDID(bad,edid)); + bad.clockKHz=10005;assert(!AppleAdapter::makePanelEDID(bad,edid)); p.mode.hsync=46;assert(!AppleAdapter::makeTiming(p,t)&&!t.pixelClock); p={};assert(!AppleAdapter::makeTiming(p,t)); puts("DSI fixed Apple timing: Hz/range, portrait geometry, RGB8 bit fields, no scaling/DSC: PASS"); From 9fa739d03aa79451f58c33f0a7fb9115920a6488 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:35:24 +0900 Subject: [PATCH 060/139] Bind qualified client timing IDs to the retained DSI mode --- WhateverXe/kern_dsi_apple_timing.hpp | 16 +++++++++++ WhateverXe/kern_xe.cpp | 43 +++++++++++++++++----------- tests/dsi_apple_adapter.py | 18 +++++++++++- tests/dsi_apple_timing.cpp | 10 +++++++ tests/dsi_update_dispatch.py | 12 +++++--- 5 files changed, 77 insertions(+), 22 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 883aef56..037f98d5 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -27,12 +27,28 @@ inline bool annotateMode(const Mode &m,ModeInformation &info) { if(!m.valid() || info.width!=m.width || info.height!=m.height)return false; info.refresh=static_cast((uint64_t(m.clockKHz)*1000*65536+m.htotal()*uint64_t(m.vtotal())/2)/ (m.htotal()*uint64_t(m.vtotal()))); + info.depth=0; // the adopted DSI surface is RGB8 only info.widthMM=static_cast(m.widthMM);info.heightMM=static_cast(m.heightMM); // IOGraphics: valid/safe/default + built-in + native. Acceleration is a // separate runtime qualification, never inferred from a fixed mode. info.flags|=0x02000407; return true; } +// The observed Big Sur detailed-mode namespace carries the array index in +// the low ten bits. Admit a client ID only after its full timing is matched. +inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned index) { + if(index>=1024 || offered.reservedA[0]!=(0x80001000U|index) || offered.signalLevels>1)return false; + const uint64_t rounded=((fixed.pixelClock+5000)/10000)*10000; + if(offered.pixelClock!=rounded || offered.minPixelClock!=rounded || offered.maxPixelClock!=rounded || + roundedfixed.maxPixelClock)return false; + auto normalized=offered; + normalized.reservedA[0]=fixed.reservedA[0];normalized.signalLevels=fixed.signalLevels; + normalized.pixelClock=fixed.pixelClock;normalized.minPixelClock=fixed.minPixelClock;normalized.maxPixelClock=fixed.maxPixelClock; + return !memcmp(&normalized,&fixed,sizeof(fixed)); +} +inline bool acceptsMode(uint32_t mode,uint32_t clientMode) { + return mode==FixedModeID || mode==clientMode; +} // Synthetic Apple enumeration descriptor derived only from the panel profile. // EDID wire layout reference: Linux v6.12 include/drm/drm_edid.h. This is not // an EDID read from the panel and does not advertise DP/HDMI, HDR or DPMS. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f09c59fb..ad18544a 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4673,6 +4673,7 @@ struct DSIFixedModeOwner { OSData *data {}; XELP::DSI::Mode mode; XELP::DSI::AppleAdapter::Timing timing; + uint32_t clientMode {XELP::DSI::AppleAdapter::FixedModeID}; bool ready {}; }; static DSIFixedModeOwner dsiFixedMode; @@ -7281,7 +7282,7 @@ bool XE::releaseDSIFixedMode(IOService *fb) { fb->getProperty("IOFBDetailedTimings"))return false; auto array=o.array;auto data=o.data; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); - o.array=nullptr;o.data=nullptr;o.mode={};o.timing={}; + o.array=nullptr;o.data=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; } @@ -7345,13 +7346,13 @@ uint32_t XE::wrapDSIGetCurrentMode(IOService *fb,int32_t *mode,int32_t *depth) { const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && Display13FBMemory::field(fb,0x1E0) && dsiNativeUpdateAllowed(fb); if(ready) { - const uint32_t fixed=XELP::DSI::AppleAdapter::FixedModeID; + const uint32_t fixed=dsiFixedMode.clientMode; memcpy(reinterpret_cast(fb)+0x4250,&fixed,sizeof(fixed)); if(mode)*mode=fixed; if(depth)*depth=Display13FBMemory::field(fb,0x4254); } const auto result=ready ? kIOReturnSuccess : kIOReturnNotReady; - recordDSIClient(fb,10,ready ? XELP::DSI::AppleAdapter::FixedModeID : 0,result);return result; + recordDSIClient(fb,10,ready ? dsiFixedMode.clientMode : 0,result);return result; } uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { @@ -7359,25 +7360,30 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { const auto result=[&]() -> uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - // Observe the actual standard enumeration offer before extending ID - // admission. The retained hardware mode and array remain unchanged. - uint64_t offer[16] {1,array ? array->getCount() : 0,UINT32_MAX}; + uint32_t client=0;uint64_t offer[16] {2,array ? array->getCount() : 0,UINT32_MAX}; if(array && array->getCount()<=1024)for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto &t=*static_cast(bytes->getBytesNoCopy()); - if(t.horizontalActive!=dsiFixedMode.mode.width || t.verticalActive!=dsiFixedMode.mode.height)continue; - offer[2]=i;offer[3]=t.reservedA[0];offer[4]=t.pixelClock; + if(!XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i))continue; + client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled;break; } IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingOffer",offer,sizeof(offer)); auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; - if(!data || data->getLength()!=sizeof(dsiFixedMode.timing) || - memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)))return kIOReturnUnsupported; + const bool canonical=data && data->getLength()==sizeof(dsiFixedMode.timing) && + !memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)); + if(!client && !canonical)return kIOReturnUnsupported; + // Preserve the immutable native timing/array. The client ID is only + // an alias of that exact retained link; other offered modes fail validation. const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); - return result==kIOReturnSuccess && !dsiModeCoherent(fb) ? kIOReturnNotReady : result; + if(result==kIOReturnSuccess) { + if(!dsiModeCoherent(fb))return kIOReturnNotReady; + if(client)dsiFixedMode.clientMode=client; + } + return result; }(); recordDSIClient(fb,1,array!=nullptr,result); return result; @@ -7388,8 +7394,9 @@ uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **descrip if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); if(description)*description=nullptr;if(timing)*timing=nullptr; DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; - const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); + if(!XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode))return kIOReturnBadArgument; + const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])( + fb,XELP::DSI::AppleAdapter::FixedModeID,description,timing); if(!result && timing && *timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; return result; }(); @@ -7402,7 +7409,7 @@ uint32_t XE::wrapDSIGetModes(IOService *fb,int32_t *modes) { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; if(!modes)return kIOReturnBadArgument; - *modes=XELP::DSI::AppleAdapter::FixedModeID;return kIOReturnSuccess; + *modes=dsiFixedMode.clientMode;return kIOReturnSuccess; }(); recordDSIClient(fb,3,modes!=nullptr,result); return result; @@ -7421,7 +7428,7 @@ uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { const auto result=[&]() -> uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!raw || mode!=XELP::DSI::AppleAdapter::FixedModeID)return kIOReturnBadArgument; + if(!raw || !XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode))return kIOReturnBadArgument; const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, *static_cast(raw)))return kIOReturnNotReady; @@ -7542,9 +7549,11 @@ uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, Display13FBMemory::field(fb,0x420C)}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); - if(mode!=XELP::DSI::AppleAdapter::FixedModeID || depth || !dsiModeTarget(fb))return kIOReturnNotReady; + if(!XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode) || depth || !dsiModeTarget(fb))return kIOReturnNotReady; if(dsiNativeUpdateAllowed(fb) && !dsiPendingTarget(fb))callbackXE->probeDSINativeBacking(fb); - return callbackXE->publishDSINativeMode(fb) ? kIOReturnSuccess : kIOReturnNotReady; + if(!callbackXE->publishDSINativeMode(fb))return kIOReturnNotReady; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=dsiFixedMode.clientMode; + return kIOReturnSuccess; } } return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index f2fc5ee4..e533bc35 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -180,7 +180,7 @@ auto validations=calls[1];assert(XE::wrapDSIValidateMode(fb,mode+0x400,&description,&timing)==kIOReturnBadArgument); assert(!description && !timing && calls[1]==validations); ModeInformation info;assert(!XE::wrapDSIGetModeInformation(fb,mode,&info)); - assert(info.width==1080 && info.height==1920 && info.widthMM==86 && info.heightMM==154 && info.depth==2); + assert(info.width==1080 && info.height==1920 && info.widthMM==86 && info.heightMM==154 && info.depth==0); assert(!(info.flags&0x00400000)); auto invalid=p.mode;invalid.width=0;assert(!annotateMode(invalid,info)); assert(XE::wrapDSIGetModeInformation(fb,mode,nullptr)==kIOReturnBadArgument); @@ -192,6 +192,22 @@ data->bytes.resize(1);assert(XE::wrapDSISetDetailedTimings(fb,replacement)==kIOReturnUnsupported); assert(XE::wrapDSISetDetailedTimings(fb,nullptr)==kIOReturnUnsupported); data->release();replacement->release(); + auto offered=dsiFixedMode.timing;offered.reservedA[0]=0x80001001;offered.signalLevels=1; + offered.pixelClock=offered.minPixelClock=offered.maxPixelClock=135260000; + auto list=OSArray::withCapacity(1);auto wrong=offered;wrong.horizontalActive=640; + auto wrongData=OSData::withBytes(&wrong,sizeof(wrong));auto offeredData=OSData::withBytes(&offered,sizeof(offered)); + list->setObject(wrongData);list->setObject(offeredData); + assert(!XE::wrapDSISetDetailedTimings(fb,list));assert(dsiFixedMode.clientMode==0x80001001 && list->refs==1); + assert(dsiModeCoherent(fb) && Display13FBMemory::field(fb,0x4260)==dsiFixedMode.array); + assert(!XE::wrapDSIValidateMode(fb,static_cast(0x80001001),nullptr,&timing)); + assert(timing==dsiFixedMode.data->getBytesNoCopy()); + assert(XE::wrapDSIValidateMode(fb,static_cast(0x80001000),nullptr,&timing)==kIOReturnBadArgument); + assert(!XE::wrapDSIGetModes(fb,&mode) && static_cast(mode)==0x80001001); + assert(!XE::wrapDSIGetCurrentMode(fb,¤t,&depth) && static_cast(current)==0x80001001); + assert(!XE::wrapDSIGetModeInformation(fb,mode,&info) && info.width==1080 && info.height==1920 && !info.depth); + offeredData->bytes[64]^=1; + assert(XE::wrapDSISetDetailedTimings(fb,list)==kIOReturnUnsupported && dsiFixedMode.clientMode==0x80001001); + wrongData->release();offeredData->release();list->release(); fb->removeProperty("IOFBDetailedTimings");assert(!XE::wrapDSIGetModeCount(fb) && dsiFixedMode.array->refs==1); assert(XE::wrapDSIValidateMode(fb,mode,nullptr,nullptr)==kIOReturnNotReady); const auto cleanup=[&]{ diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 7f8192e4..4cf73b18 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -10,6 +10,16 @@ int main() { assert(t.verticalActive==1920&&t.verticalBlanking==82&&t.verticalSyncOffset==40&&t.verticalSyncWidth==2); assert(t.bitsPerColorComponent==2&&t.pixelEncoding==1&&t.numLinks==1); assert(!t.scalerFlags&&!t.horizontalScaled&&!t.verticalScaled&&!t.verticalBlankingExtension); + auto offered=t;offered.reservedA[0]=0x80001000;offered.signalLevels=1; + offered.pixelClock=offered.minPixelClock=offered.maxPixelClock=135260000; + assert(AppleAdapter::clientTiming(t,offered,0)); + for(unsigned offset=4;offset(&corrupt)[offset]^=0x80; + assert(!AppleAdapter::clientTiming(t,corrupt,0)); + } + assert(!AppleAdapter::clientTiming(t,offered,1)); + offered.reservedA[0]=0x80001001;assert(AppleAdapter::clientTiming(t,offered,1)); + assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); assert(edid[18]==1 && edid[19]==4 && edid[20]==0xA0 && edid[24]==2 && !edid[126]); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 402a70af..fadc1779 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -44,6 +44,7 @@ struct {struct {bool faulted=false,suspended=false;}state;}bridge;struct {bool faulted=false;}dbuf; struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[3]; Display13Transcoder::Bindings transcoders;}display13FBPower; +struct {uint32_t clientMode=0x10000;}dsiFixedMode; IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) @@ -68,7 +69,7 @@ uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; - bool dsiPanelOnly=false;bool publishDSINativeMode(IOService*){return false;} + bool dsiPanelOnly=false,publicationReady=false;bool publishDSINativeMode(IOService*){return publicationReady;} bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; static void recordDSIClient(IOService*,unsigned,uint64_t,uint32_t){}; @@ -156,7 +157,7 @@ templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} struct Case { IOService fb,ctl,peer;OSObject path,port;IOLock lock; - Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=nestedTx=nestedAttribute=false;xe={}; + Case(){assert(!fbLock.depth && !owner);expectedWriters=1;nestedCursor=nestedTx=nestedAttribute=false;xe={};dsiFixedMode={}; xe.orgDSIAttributes[0]=reinterpret_cast(nativeAttribute); xe.orgDSIAttributes[1]=reinterpret_cast(nativeConnectionAttribute); xe.orgDSIAttributes[2]=reinterpret_cast(nativePower); @@ -187,8 +188,11 @@ xe.dsiStopAttempted=true; assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,0)==kIOReturnNotReady && backingCalls==before+1);} - // Public mode rejection precedes SaveNVRAMMode, disable, timing/backing and - // native notifications; even the selected fixed mode is not yet admitted. + {Case c;dsiFixedMode.clientMode=0x80001000;xe.publicationReady=true; + assert(!XE::wrapDSISetDisplayMode(&c.fb,static_cast(0x80001000),0)); + assert(Display13FBMemory::field(&c.fb,0x4250)==0x80001000); + assert(XE::wrapDSISetDisplayMode(&c.fb,static_cast(0x80001001),0)==kIOReturnNotReady);} + // Unsupported depth never reaches ordinary mode writes. {Case c;auto before=modeCalls;assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,2)==kIOReturnNotReady); assert(modeCalls==before && !fbLock.depth && !owner);} {Case c;auto before=armCalls;XE::wrapDisplay13WMArmUpdate(&c.fb);assert(armCalls==before+1 && !fbLock.depth);} From aebd9d9ec8aa75a1560c01e99f2506eda03aba19 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:43:58 +0900 Subject: [PATCH 061/139] Complete adopted DSI mode with the shared DBUF transaction --- WhateverXe/kern_xe.cpp | 23 +++++++++++++++++++++-- tests/dsi_surface_adapter.py | 21 +++++++++++++++------ tests/dsi_update_dispatch.py | 3 ++- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ad18544a..26660f8a 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5739,7 +5739,7 @@ bool XE::publishDSINativeMode(IOService *fb) { IOLockLock(c.lock);auto controller=static_cast(c.controller); auto path=Display13FBMemory::singlePath(fb); const bool ready=!dsiStopAttempted && dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !c.dsiNativeWriters && dsiArmedBackingOwned(fb,path) && + !c.bridge.state.faulted && !c.dbuf.faulted && !c.watermarkFaulted && !c.dsiNativeWriters && dsiArmedBackingOwned(fb,path) && dsiSession.state==XELP::DSI::SessionState::Streaming && !Display13FBMemory::field(controller,0xD0) && !Display13FBMemory::field(fb,0x4340) && Display13FBMemory::field(fb,0x4338)==1; @@ -5770,6 +5770,25 @@ bool XE::publishDSINativeMode(IOService *fb) { IOLockUnlock(c.lock); } } + if(o.modeReady) { + // Still under the native FB mutex: admit the existing DDB transaction, + // then require its latch before returning mode success to any client. + // GOP adoption may have no cursor reservation and a full primary DDB. + adlnSetupDBUFPipe(controller,fb,0,false); + IOLockLock(c.lock); + const auto &allocation=c.dbuf.pipe[0];const auto &pipe=c.pipe[0]; + o.modeReady=!c.dbuf.faulted && !c.watermarkFaulted && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !dsiStopAttempted && !pipe.wmPending && + allocation.primary.start==40 && allocation.primary.end==2048 && + allocation.cursor.start==0 && allocation.cursor.end==40 && + pipe.wmLatch.status==Display13WM::Status::Complete && dsiPublishedTarget(fb); + uint64_t layout[] {1,allocation.primary.start,allocation.primary.end,allocation.cursor.start,allocation.cursor.end, + pipe.wmPending,static_cast(pipe.wmLatch.status),static_cast(pipe.wmLatch.check), + c.watermarkFaulted,c.dbuf.faulted,pipe.watermarkClockHz,o.modeReady}; + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeDBUF",layout,sizeof(layout)); + } + receipt[8]=o.modeReady; receipt[1]=2;IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); return o.modeReady; } @@ -7574,7 +7593,7 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { IOLockLock(c.lock); const bool ready=(!dsiPendingTarget(fb) || (dsiPublishedTarget(fb) && dsiPendingBacking.modeReady)) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && - !c.dbuf.faulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && + !c.dbuf.faulted && !c.watermarkFaulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && c.transcoders.dsi0Pipe==0 && path && path->getMetaClass()->getClassSize()>=0x3730 && Display13FBMemory::field(fb,0x1D0)==c.controller && Display13FBMemory::field(path,0x32B0)==fb && Display13FBMemory::field(path,0x3640)==0 && diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index 4f1f52e9..7da57805 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -17,6 +17,8 @@ b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') prelude=prelude.replace('#include "kern_dsi_native_backing.hpp"','#include "kern_dsi_native_backing.hpp"\n#include "kern_dsi_native_mode.hpp"') +prelude=prelude.replace('bool clockFaulted=false,memoryFaulted=false;', 'bool clockFaulted=false,memoryFaulted=false,watermarkFaulted=false;') +prelude=prelude.replace('bool destroyingPath=false,wmProgramming=false,wmCommitting=false;', 'bool destroyingPath=false,wmProgramming=false,wmCommitting=false;unsigned wmPending=0;uint64_t watermarkClockHz=0;Display13WM::Latch wmLatch;') prelude=prelude.replace('struct {uintptr_t record=0;}scanout;','Display13FBMemory::SurfaceReadout scanout;') prelude=prelude.replace('struct {bool ready=true;}dsiFixedMode;','struct {bool ready=true;AppleAdapter::Timing timing;}dsiFixedMode;') prelude=prelude.replace(' void retain(){',' uint32_t callPlatformFunction(const char*,bool,void*,void*,void*,void*);\n void retain(){') @@ -32,7 +34,7 @@ void IOSleep(unsigned ms){tick(ms*1000);}void IODelay(unsigned us){tick(us);} struct Display13FBClockIO:Display13FBBootIO {}; bool adlnSharedBinding(void *controller,void *owner){return controller==display13FBPower.controller && owner && mappingBound;} -bool failSlotReturn=false,failCache=false,failLogical=false;unsigned slotReturns=0,cpuInitial=0,cpuCaps=0; +bool failSlotReturn=false,failCache=false,failLogical=false,failModeDBUF=false;unsigned slotReturns=0,cpuInitial=0,cpuCaps=0; #define FunctionCast(f,p) reinterpret_cast(p) bool dsiModeCoherent(IOService*){return dsiFixedMode.ready;} uint32_t OSObject::callPlatformFunction(const char *name,bool wait,void *arg,void *b,void *c,void *d) { @@ -73,6 +75,13 @@ static void wrapAlderLakeNScanoutFree(IOService*){} bool updateDSIBootCache(IOService*,IOService*,OSObject*){assert(locks==std::vector{1});return !failCache;} bool dsiNativeUpdateAllowed(IOService*){return dsiPendingBacking.modeReady && !display13FBPower.memoryFaulted;} + static void adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,bool optimized) { + assert(!gpuDepth && locks==std::vector{1} && controller==display13FBPower.controller && dsiPublishedTarget(fb) && !pipe && !optimized); + assert(dsiPendingBacking.modeReady);auto &c=display13FBPower; + if(failModeDBUF){c.dbuf.faulted=true;return;} + c.dbuf.pipe[0].primary={40,2048};c.dbuf.pipe[0].cursor={0,40};c.pipe[0].wmPending=0; + c.pipe[0].wmLatch.status=Display13WM::Status::Complete;c.pipe[0].watermarkClockHz=358400000; + } bool publishDSINativeBacking();bool publishDSINativeMode(IOService*); bool switchDSINativeSurface();bool dsiArmedBackingOwned(IOService*,OSObject*);bool retireDSINativeBacking(IOService*,OSObject*); }; @@ -99,7 +108,7 @@ p.refs=p.kind=1;p.map=record.map;p.memory=record.memory;p.gpu=record.gpu;p.bytes=8355840;p.pitch=4352;p.verifiedPages=2040; assert(AppleAdapter::makeTiming(xe.dsiProfile,dsiFixedMode.timing)); put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&controller,0xDF5,uint8_t(3));put(&controller,0xDF4,uint8_t(1));put(&controller,0xDF0,1U); - put(&controller,0x1A10,1U);put(&fb,0x4288,uint8_t(0));failSlotReturn=failCache=failLogical=false;slotReturns=cpuInitial=cpuCaps=0; + put(&controller,0x1A10,1U);put(&fb,0x4288,uint8_t(0));failSlotReturn=failCache=failLogical=failModeDBUF=false;slotReturns=cpuInitial=cpuCaps=0; o.image.complete=true;o.image.digest=0xf89939c5;elapsed=0;gpuDepth=1;failPaint=failDiscard=false;discards=0;memset(receipt,0,sizeof(receipt)); ''') code=prelude+functions+'\n'+case+r''' @@ -127,14 +136,14 @@ assert(!c.retire() && dsiPublishedTarget(&c.fb));c.stopped();failDiscard=true; assert(!c.retire() && dsiPublishedTarget(&c.fb) && !o.modeReady && o.armed); failDiscard=false;assert(c.retire() && !Display13FBMemory::field(&c.fb,0x4348) && !display13FBPower.pipe[0].publishedScanout);} - for(unsigned failure=0;failure<4;++failure) {Case c;assert(c.run()); + for(unsigned failure=0;failure<5;++failure) {Case c;assert(c.run()); put(&c.fb,0x4348,uintptr_t(0));put(&c.fb,0x4288,uint8_t(0)); - failSlotReturn=failure==1;failCache=failure==2;failLogical=failure==3; + failSlotReturn=failure==1;failCache=failure==2;failLogical=failure==3;failModeDBUF=failure==4; locks={1};gpuDepth=1;dsiPendingBacking.allocating=true; const bool published=c.xe.publishDSINativeBacking();dsiPendingBacking.allocating=false;gpuDepth=0; - assert(published==(failure==0 || failure==3) && slotReturns==1); + assert(published==(failure==0 || failure>=3) && slotReturns==1); const bool ready=c.xe.publishDSINativeMode(&c.fb); - assert(ready==(failure==0));assert(cpuInitial==(failure==0) && cpuCaps==(failure==0)); + assert(ready==(failure==0));assert(cpuInitial==(failure==0 || failure==4) && cpuCaps==(failure==0 || failure==4)); if(!failure)assert(dsiPublishedTarget(&c.fb) && dsiPendingBacking.modeReady && Display13FBMemory::field(&c.fb,0x4330)==c.record.gpu); locks.clear(); } diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index fadc1779..df1ab826 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -40,7 +40,7 @@ void IORecursiveLockLock(IORecursiveLock *p){assert(!owner);++p->depth;} void IORecursiveLockUnlock(IORecursiveLock *p){assert(!owner && p->depth);--p->depth;} struct {IOLock *lock=nullptr;IOService *controller=nullptr;OSObject *dsiPort=nullptr; - unsigned dsiNativeWriters=0;bool memoryFaulted=false,clockFaulted=false; + unsigned dsiNativeWriters=0;bool memoryFaulted=false,clockFaulted=false,watermarkFaulted=false; struct {struct {bool faulted=false,suspended=false;}state;}bridge;struct {bool faulted=false;}dbuf; struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[3]; Display13Transcoder::Bindings transcoders;}display13FBPower; @@ -198,6 +198,7 @@ {Case c;auto before=armCalls;XE::wrapDisplay13WMArmUpdate(&c.fb);assert(armCalls==before+1 && !fbLock.depth);} {Case c;xe.dsiStopAttempted=true;c.refuse();} {Case c;xe.dsiResourcesReady=false;c.refuse();} + {Case c;display13FBPower.watermarkFaulted=true;c.refuse();} {Case c;display13FBPower.memoryFaulted=true;c.refuse();} {Case c;display13FBPower.bridge.state.suspended=true;c.refuse();} {Case c;display13FBPower.pipe[0].destroyingPath=true;c.refuse();} From 92f95c609fa184bded9abdd1eb6c6066d8d71a0c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:52:46 +0900 Subject: [PATCH 062/139] Initialize the retained DSI pixel clock for native watermarks --- WhateverXe/kern_xe.cpp | 4 ++++ tests/dsi_native_mode_fixture.cpp | 13 +++++++++++++ tests/dsi_surface_adapter.py | 5 ++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 26660f8a..05383d2d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5771,6 +5771,10 @@ bool XE::publishDSINativeMode(IOService *fb) { } } if(o.modeReady) { + // setupClocks normally copies the detailed timing's Hz into this CPU + // path field before WM calculation. DSI retains its live PLL, so supply + // that fixed-mode cache without entering the native DDI clock setup. + memcpy(reinterpret_cast(path)+0x118,&dsiFixedMode.timing.pixelClock,sizeof(uint64_t)); // Still under the native FB mutex: admit the existing DDB transaction, // then require its latch before returning mode success to any client. // GOP adoption may have no cursor reservation and a full primary DDB. diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index 4efd5d62..04f2050c 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -64,6 +64,19 @@ int main(int argc,char **argv) { assert(field(fb,0x402C)>=1 && field(fb,0x402C)<=2); } + // setupClocks' CPU assignment at 0x6C719 reads Timing+0x28 and + // stores path+0x118. Replay both WM consumers without a DDI modeset. + const uint8_t cacheCopy[]={0x49,0x8B,0x77,0x28,0x48,0x89,0xB3,0x18,0x01,0x00,0x00}; + assert(!memcmp(image+0x6C719,cacheCopy,sizeof(cacheCopy))); + alignas(8) uint8_t scaler[0x80]{},plane[0x1C8]{}; + put(path,0x32C0,scaler);put(path,0x32C8,plane); + put(scaler,0x4C,uint32_t(p.mode.width<<16 | p.mode.height)); + put(plane,0x88,uintptr_t(0));put(path,0x118,t.pixelClock); + const auto adjust=reinterpret_cast(image+0x4ACE4); + assert(adjust(controller,fb,path,false,nullptr)==135255000); + assert(adjust(controller,fb,path,true,nullptr)==135255000); + put(scaler,0x4C,0U); // Disabled boot scaler also leaves Hz unchanged. + assert(adjust(controller,fb,path,false,nullptr)==135255000); assert(notifications==4);munmap(image,0x400000);puts("DSI native mode geometry and logical-buffer replay passed"); } puts("DSI native mode preparation rollback and admission passed"); diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index 7da57805..a0d645da 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -77,7 +77,10 @@ bool dsiNativeUpdateAllowed(IOService*){return dsiPendingBacking.modeReady && !display13FBPower.memoryFaulted;} static void adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,bool optimized) { assert(!gpuDepth && locks==std::vector{1} && controller==display13FBPower.controller && dsiPublishedTarget(fb) && !pipe && !optimized); - assert(dsiPendingBacking.modeReady);auto &c=display13FBPower; + assert(dsiPendingBacking.modeReady); + auto path=Display13FBMemory::singlePath(fb); + assert(Display13FBMemory::field(path,0x118)==dsiFixedMode.timing.pixelClock && dsiFixedMode.timing.pixelClock==135255000); + auto &c=display13FBPower; if(failModeDBUF){c.dbuf.faulted=true;return;} c.dbuf.pipe[0].primary={40,2048};c.dbuf.pipe[0].cursor={0,40};c.pipe[0].wmPending=0; c.pipe[0].wmLatch.status=Display13WM::Status::Complete;c.pipe[0].watermarkClockHz=358400000; From ec2d7f0e16b9a6d53aba21b5ea207f6707cd966e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:56:15 +0900 Subject: [PATCH 063/139] Accept qualified DSI aliases across client mode list rebuilds --- WhateverXe/kern_dsi_apple_timing.hpp | 7 ++++--- tests/dsi_apple_adapter.py | 11 +++++++++++ tests/dsi_apple_timing.cpp | 10 +++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 037f98d5..e7412b17 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -34,10 +34,11 @@ inline bool annotateMode(const Mode &m,ModeInformation &info) { info.flags|=0x02000407; return true; } -// The observed Big Sur detailed-mode namespace carries the array index in -// the low ten bits. Admit a client ID only after its full timing is matched. +// CoreDisplay can replace the reserved client IDs when rebuilding its mode +// list. Treat reserved IDs as opaque: the complete +// timing, not an assumed generation/index bit layout, qualifies this alias. inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned index) { - if(index>=1024 || offered.reservedA[0]!=(0x80001000U|index) || offered.signalLevels>1)return false; + if(index>=1024 || offered.reservedA[0]<0x80000000U || offered.reservedA[0]==UINT32_MAX || offered.signalLevels>1)return false; const uint64_t rounded=((fixed.pixelClock+5000)/10000)*10000; if(offered.pixelClock!=rounded || offered.minPixelClock!=rounded || offered.maxPixelClock!=rounded || roundedfixed.maxPixelClock)return false; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index e533bc35..bea61202 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -207,6 +207,17 @@ assert(!XE::wrapDSIGetModeInformation(fb,mode,&info) && info.width==1080 && info.height==1920 && !info.depth); offeredData->bytes[64]^=1; assert(XE::wrapDSISetDetailedTimings(fb,list)==kIOReturnUnsupported && dsiFixedMode.clientMode==0x80001001); + offered.reservedA[0]=0x80003000; + auto renewed=OSArray::withCapacity(1);auto renewedData=OSData::withBytes(&offered,sizeof(offered));renewed->setObject(renewedData); + assert(!XE::wrapDSISetDetailedTimings(fb,renewed) && dsiFixedMode.clientMode==0x80003000); + assert(dsiModeCoherent(fb) && renewed->refs==1); + assert(XE::wrapDSIValidateMode(fb,static_cast(0x80001001),nullptr,&timing)==kIOReturnBadArgument); + assert(!XE::wrapDSIValidateMode(fb,static_cast(0x80003000),nullptr,&timing) && timing==dsiFixedMode.data->getBytesNoCopy()); + assert(!XE::wrapDSIGetModes(fb,&mode) && static_cast(mode)==0x80003000); + assert(!XE::wrapDSIGetCurrentMode(fb,¤t,&depth) && static_cast(current)==0x80003000); + renewedData->bytes[64]^=1; + assert(XE::wrapDSISetDetailedTimings(fb,renewed)==kIOReturnUnsupported && dsiFixedMode.clientMode==0x80003000); + renewedData->release();renewed->release(); wrongData->release();offeredData->release();list->release(); fb->removeProperty("IOFBDetailedTimings");assert(!XE::wrapDSIGetModeCount(fb) && dsiFixedMode.array->refs==1); assert(XE::wrapDSIValidateMode(fb,mode,nullptr,nullptr)==kIOReturnNotReady); diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 4cf73b18..ba8fdd04 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -1,6 +1,7 @@ #include "kern_dsi_apple_timing.hpp" #include #include +#include using namespace XELP::DSI; int main() { Profile p;p.status=ProfileStatus::Valid;p.mode={135255,1080,46,22,2,1920,82,40,2,86,154}; @@ -17,7 +18,14 @@ int main() { auto corrupt=offered;reinterpret_cast(&corrupt)[offset]^=0x80; assert(!AppleAdapter::clientTiming(t,corrupt,0)); } - assert(!AppleAdapter::clientTiming(t,offered,1)); + assert(AppleAdapter::clientTiming(t,offered,1)); // IDs are opaque, not array positions. + assert(!AppleAdapter::clientTiming(t,offered,1024)); + for(uint32_t id:{0x80002000U,0x80003000U,0x80005007U}) { + offered.reservedA[0]=id;assert(AppleAdapter::clientTiming(t,offered,0)); + } + for(uint32_t id:{0U,AppleAdapter::FixedModeID,0x7FFFFFFFU,UINT32_MAX}) { + offered.reservedA[0]=id;assert(!AppleAdapter::clientTiming(t,offered,0)); + } offered.reservedA[0]=0x80001001;assert(AppleAdapter::clientTiming(t,offered,1)); assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); From 5288d8516dc973e5d190c4cae929ee26d7479075 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:05:06 +0900 Subject: [PATCH 064/139] Document fixed-mode DSI accelerated desktop support --- README.md | 2 +- README_ja.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e4b3c03..143cc787 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode setup; a complete accelerated desktop is not yet supported. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 0dc0567d..1597c630 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeの設定を行います。完全なアクセラレーション付きデスクトップには未対応です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 ### 内蔵パネルの輝度 From 6a7f6e9865272812e675c509dc2b842b18d4516b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:08:20 +0900 Subject: [PATCH 065/139] Expose memory-only DSI IRQ counters for runtime validation --- WhateverXe/kern_xe.cpp | 25 +++++++++++++++++- tests/dsi_irq_live.py | 58 ++++++++++++++++++++++++++++++++++++++++++ tests/dsi_resources.py | 5 +++- tools/test.py | 2 +- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 tests/dsi_irq_live.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 05383d2d..8eb430ec 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4929,6 +4929,27 @@ static void adlnIRQRecord(uint64_t (&r)[32],unsigned event) { r[29]=reinterpret_cast(c.lock);r[30]=c.handlers;r[31]=c.quiescing|(c.blockedPipes<<8)|(uint64_t(c.softwareHandlers)<<32); } +// PowerInit retains the native controller (including its IRQ lock) until reboot. +// Read only the counters already collected by the IRQ handler. Never refresh +// the register cache, power a well, or call a native driver from this serializer. +static bool dsiIRQLiveSerialize(void *,void *,OSSerialize *serializer) { + auto &c=display13FBIRQ; + if(!__atomic_load_n(&c.controller,__ATOMIC_ACQUIRE) || !c.lock)return false; + uint64_t record[32] {}; + const auto saved=IOSimpleLockLockDisableInterrupt(c.lock); + adlnIRQRecord(record,3); + IOSimpleLockUnlockEnableInterrupt(c.lock,saved); + auto data=OSData::withBytes(record,sizeof(record));if(!data)return false; + const bool ok=data->serialize(serializer);data->release();return ok; +} +static void dsiPublishIRQLive() { + auto property=OSSerializer::forTarget(nullptr,dsiIRQLiveSerialize); + if(property) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIIRQLive",property); + property->release(); + } +} + // These operations run under the display owner lock. AUX power qualification // needs the TC bank lock but never enters the native port or power callbacks. struct Display13FBTCAuxIO { @@ -12085,7 +12106,9 @@ bool XE::bindDSIResources(IOService *controller) { } IOSimpleLockUnlockEnableInterrupt(irqLock,saved); } - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock);return ready; + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(ready)dsiPublishIRQLive(); + return ready; } void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { diff --git a/tests/dsi_irq_live.py b/tests/dsi_irq_live.py new file mode 100644 index 00000000..042ad61f --- /dev/null +++ b/tests/dsi_irq_live.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Exercise the production memory-only DSI IRQ snapshot and lock lifetime.""" +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +parts=[re.search(pattern,s,re.S)[0] for pattern in [ + r'struct Display13FBIRQContext \{.*?\n\};', + r'static void adlnIRQRecord\(.*?\n\}', + r'static bool dsiIRQLiveSerialize\(.*?\n\}']] +code=r''' +#include "kern_display13_fb_irq.hpp" +#include +#include +#include +struct IOSimpleLock {};struct IOService {};struct OSSerialize {}; +bool held=false,failAllocation=false,failSerialize=false;unsigned reads=0,releases=0; +uint64_t output[32]{}; +unsigned IOSimpleLockLockDisableInterrupt(IOSimpleLock *p){assert(p && !held);held=true;++reads;return 7;} +void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock *p,unsigned v){assert(p && held && v==7);held=false;} +struct OSData { + static OSData *withBytes(const void *p,unsigned n){assert(!held && n==sizeof(output));memcpy(output,p,n);static OSData data;return failAllocation?nullptr:&data;} + bool serialize(OSSerialize*){assert(!held);return !failSerialize;} + void release(){assert(!held);++releases;} +}; +CONTEXT +static Display13FBIRQContext display13FBIRQ; +RECORD +SERIALIZE +int main(){ + auto &c=display13FBIRQ;OSSerialize serializer; + assert(!dsiIRQLiveSerialize(nullptr,nullptr,&serializer) && !reads); + IOService controller;c.controller=&controller; + assert(!dsiIRQLiveSerialize(nullptr,nullptr,&serializer) && !reads); + IOSimpleLock lock;c.lock=&lock;c.state.begun=true;c.state.enabled=true; + c.status=Display13FBIRQ::Status::Complete;c.state.active=1;c.state.configured=1; + c.state.interrupts=100;c.state.vblanks[0]=70;c.state.flips[0]=20; + c.state.underruns[0]=2;c.state.dsbErrors[2]=4;c.handlers=1; + auto before=c; + assert(dsiIRQLiveSerialize(nullptr,nullptr,&serializer) && !held && reads==1 && releases==1); + assert(!memcmp(&before,&c,sizeof(c))); + assert(output[0]==1 && output[1]==3 && output[2]==1 && output[4]==1); + assert(output[10]==100 && output[11]==70 && output[14]==20 && output[17]==2 && output[22]==4 && output[30]==1); + c.state.interrupts+=10;c.state.vblanks[0]+=8;c.state.flips[0]+=2; + assert(dsiIRQLiveSerialize(nullptr,nullptr,&serializer)); + assert(output[10]==110 && output[11]==78 && output[14]==22 && output[17]==2); + c.quiescing=true;c.blockedPipes=1;c.softwareHandlers=2;c.state.enabled=false; + assert(dsiIRQLiveSerialize(nullptr,nullptr,&serializer)); + assert(output[4]==0 && output[31]==(1ULL|256|(2ULL<<32))); + failAllocation=true;assert(!dsiIRQLiveSerialize(nullptr,nullptr,&serializer) && !held && releases==3); + failAllocation=false;failSerialize=true;assert(!dsiIRQLiveSerialize(nullptr,nullptr,&serializer) && !held && releases==4); + puts("PASS DSI IRQ live: current counters, read-only state, unlock before allocation/serialization, failure cleanup"); +} +'''.replace('CONTEXT',parts[0]).replace('RECORD',parts[1]).replace('SERIALIZE',parts[2]) +with tempfile.TemporaryDirectory(prefix='dsi-irq-live-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index b78c4062..eec9b5e2 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -46,6 +46,8 @@ struct {uintptr_t base=0;IOSimpleLock *lock=nullptr;IOService *controller=nullptr;Display13FBIRQ::State state; unsigned handlers=0,softwareHandlers=0,admittedPipes=0;bool quiescing=false;} display13FBIRQ; XELPMemory::Layout xelpMemory; +unsigned published=0; +void dsiPublishIRQLive(){assert(lockLevel==0 && display13FBIRQ.controller && display13FBIRQ.lock);++published;} struct XE { bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; struct {unsigned dsi=1;} adlnOutputWiring; @@ -68,7 +70,7 @@ for(unsigned i=0;i(&controller) && clock.allocation.binding[0]==2); diff --git a/tools/test.py b/tools/test.py index 53897f40..62f984aa 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,7 +9,7 @@ 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', '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 += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] -python_tests += ['dsi_surface_adapter','dsi_aperture_adapter'] +python_tests += ['dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From 8b9d4d66b68cd991a874769346b6b4ff2ecf8cbe Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:14:35 +0900 Subject: [PATCH 066/139] Add an owned DSI command transport and opt-in NOP trial --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_commands.hpp | 29 ++++++++++++ WhateverXe/kern_xe.cpp | 58 ++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 4 ++ tests/adlp_fb_candidate.py | 2 +- tests/dsi_command_adapter.py | 60 +++++++++++++++++++++++++ tests/dsi_commands.cpp | 45 +++++++++++++++++++ tests/dsi_resources.py | 1 + tests/fixtures/boot_argument_names.json | 1 + tools/test.py | 2 +- 11 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 WhateverXe/kern_dsi_commands.hpp create mode 100644 tests/dsi_command_adapter.py create mode 100644 tests/dsi_commands.cpp diff --git a/README.md b/README.md index 143cc787..819354dd 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 1597c630..8e100417 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_commands.hpp b/WhateverXe/kern_dsi_commands.hpp new file mode 100644 index 00000000..e2a0199f --- /dev/null +++ b/WhateverXe/kern_dsi_commands.hpp @@ -0,0 +1,29 @@ +// Intel icl_dsi.c / icl_dsi_regs.h v7.0 command transport. The owning adapter +// serializes the sole producer and holds the host's power/clock throughout. +#ifndef KERN_DSI_COMMANDS_HPP +#define KERN_DSI_COMMANDS_HPP +#include "kern_dsi_host.hpp" +namespace XELP { namespace DSI { +template struct CommandPort { + IO &io; + bool failed {};uint32_t writes {}; + bool cancelled(){return failed || !io.ready();} + bool read(uint32_t reg,uint32_t &value) { + if(cancelled() || !io.readable(reg))return false; + value=io.read(reg);return value!=UINT32_MAX; + } + void write(uint32_t reg,uint32_t value) { + if(cancelled() || !io.readable(reg)){failed=true;return;} + io.write(reg,value);++writes; + } + bool credits(uint32_t &value){return read(0x6B0D0,value);} + bool lpBusy(bool &busy) { + uint32_t value=0;if(!read(0x6B0D8,value) || (value&((1U<<16)|1U)))return false; + busy=value&(1U<<17);return true; + } + void payload(uint32_t value){write(0x6B104,value);} + void header(uint32_t value){write(0x6B100,value);} + void delayUs(unsigned us){io.delayUs(us);} +}; +}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 8eb430ec..526352ff 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3872,6 +3872,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { (board.selected()&1) || !prepareDSIFirmware(provider))return {}; board.dsi=1; dsiPanelOnly=checkKernelArgument("-wex-dsi-panel-only"); + dsiCommandTest=checkKernelArgument("-wex-dsi-command-test"); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelOnly",dsiPanelOnly); if(!board.described())return {}; } @@ -12063,6 +12064,62 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo void XE::wrapAlderLakeNDefaultDBUF(IOService *controller) {adlnSetupDBUF(controller,false);} void XE::wrapAlderLakeNOptimizedDBUF(IOService *controller) {adlnSetupDBUF(controller,true);} +// One optional bootstrap NOP/drain trial before native engine and IRQ users. +// This exercises the same packet engine used by panel sequences, without +// changing panel power/reset, timing, brightness, or any framebuffer state. +bool XE::probeDSICommands(IOService *controller) { + auto &c=display13FBPower;auto &clock=comboClockTransaction; + if(!controller || !c.lock || !clock.lock)return false; + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + const auto owned=[&]() { + return controller==c.controller && controller==adlnTopologyController && + dsiResourcesReady && !dsiStopAttempted && adlnOutputWiring.dsi==1 && + !c.engineEnabled && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !clock.thread && + clock.allocation.external[0]==reinterpret_cast(controller) && + clock.allocation.binding[0]==dsiBoot.source.identity.pll+1; + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns; + IO(IOMemoryMap *m,decltype(owned) &q):Display13FBBootIO{m},owns(q) {} + bool ready() { + if(!owns() || !readable(0x45404))return false; + const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; + } + void write(uint32_t reg,uint32_t value) { + if(reg==0x6B100 || reg==0x6B104) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + void delayUs(unsigned us){IODelay(us);} + } io{c.map,owned}; + XELP::DSI::CommandPort port{io};uint64_t record[16] {1}; + uint32_t credits=0,lp=0; + record[1]=io.ready() && !dsiCommandHost.enabled && dsiProfile.valid() && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + if(record[1]) { + const bool empty=port.credits(credits) && port.read(0x6B0D8,lp) && + (credits&0x1FFF)==0x1040 && !(lp&((1U<<17)|(1U<<16)|1U)); + record[2]=credits;record[3]=lp;record[4]=empty; + if(empty) { + dsiCommandHost.enabled=true;const uint8_t nop=0; + record[5]=dsiCommandHost.send(port,0x05,{&nop,1}); + record[6]=record[5] && dsiCommandHost.drain(port); + } + record[7]=port.credits(credits)?credits:UINT32_MAX; + record[8]=port.read(0x6B0D8,lp)?lp:UINT32_MAX; + record[9]=io.ready() && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + } + record[10]=static_cast(dsiCommandHost.error);record[11]=dsiCommandHost.pending; + record[12]=dsiCommandHost.headers;record[13]=dsiCommandHost.payloadWords; + record[14]=dsiCommandHost.waitedUs;record[15]=port.writes; + const bool complete=record[1] && record[4] && record[6] && record[9] && + !record[10] && !record[11] && !port.failed && (record[7]&0x1FFF)==0x1040 && + record[8]!=UINT32_MAX && !(record[8]&((1U<<17)|(1U<<16)|1U)); + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSICommandTrial",record,sizeof(record)); + return complete; +} + // Called outside the owner lock, before native engine/probe/IRQ users start. // The lock order matches clock clients: Combo clock -> display owner -> IRQ. // Failure retains any already acquired boot power references for cleanup. @@ -12108,6 +12165,7 @@ bool XE::bindDSIResources(IOService *controller) { } IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(ready)dsiPublishIRQLive(); + if(ready && dsiCommandTest)return probeDSICommands(controller); return ready; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 531d9412..0526f294 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -14,6 +14,7 @@ #include "kern_dsi_surface.hpp" #include "kern_dsi_quiesce.hpp" #include "kern_dsi_clock.hpp" +#include "kern_dsi_commands.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" @@ -246,6 +247,9 @@ class XE { bool dsiNativeWriterHooksReady {}; bool dsiBackingHooksReady {}; bool dsiPanelOnly {}; + bool dsiCommandTest {}; + XELP::DSI::Host dsiCommandHost; + bool probeDSICommands(IOService *); mach_vm_address_t orgDSIBacking[2] {}; bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t dsiComputeGeometry {},dsiModeCPU[3] {}; diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 030ebb2c..225f6ceb 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -21,7 +21,7 @@ XELP::ProbeConfig seen;unsigned calls=0; bool configure(void *c,const XELP::ProbeConfig *p){assert(c==(void*)1);seen=*p;++calls;return p->valid();} struct XE { - bool dsiPanelOnly=false; + bool dsiPanelOnly=false,dsiCommandTest=false; XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; diff --git a/tests/dsi_command_adapter.py b/tests/dsi_command_adapter.py new file mode 100644 index 00000000..c9145a2c --- /dev/null +++ b/tests/dsi_command_adapter.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Replay the bootstrap command trial's real ownership and MMIO boundary.""" +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'bool XE::probeDSICommands\(.*?\n\}',s,re.S)[0] +code=r''' +#include "kern_dsi_commands.hpp" +#include +#include +#include +#include +unsigned level=0;struct IOService{};struct IOLock{};struct IORecursiveLock{}; +void IORecursiveLockLock(IORecursiveLock*){assert(!level);level=1;} +void IORecursiveLockUnlock(IORecursiveLock*){assert(level==1);level=0;} +void IOLockLock(IOLock*){assert(level==1);level=2;} +void IOLockUnlock(IOLock*){assert(level==2);level=1;} +void IODelay(unsigned){assert(level==2);} +struct IOMemoryMap {std::vector bar=std::vector(0x1000000/4); + uintptr_t getVirtualAddress(){return reinterpret_cast(bar.data());}}; +struct Display13FBBootIO {IOMemoryMap *map;bool readable(uint32_t r){return map && r<0x1000000 && !(r&3);} + uint32_t read(uint32_t r){assert(level==2 && readable(r));return map->bar[r/4];}}; +struct Profile {bool valid(){return true;}}; +struct Boot {struct {struct {unsigned pll=1;}identity;}source;}; +bool unchanged=true;int xelpMemory; +namespace XELP {namespace DSI {templatebool bootUnchanged(IO&,const Boot&,int){return unchanged;}}} +struct {IORecursiveLock *lock;uintptr_t thread=0;struct {uintptr_t external[3]{};unsigned binding[3]{};}allocation;}comboClockTransaction; +struct {IOLock *lock;IOService *controller;IOMemoryMap *map; + bool engineEnabled=false,memoryFaulted=false,clockFaulted=false; + struct {struct {bool faulted=false,suspended=false;}state;bool held=true; + templatebool outputHeld(IO&,unsigned,unsigned){assert(level==2);return held;}}bridge;}display13FBPower; +uint64_t receipt[16]{};unsigned published=0; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *key,const void *p,unsigned size){assert(!level && !strcmp(key,"WEXDSICommandTrial") && size==sizeof(receipt));memcpy(receipt,p,size);++published;}}; +struct XE {IOService *adlnTopologyController;bool dsiResourcesReady=true,dsiStopAttempted=false; + struct {unsigned dsi=1;}adlnOutputWiring;Profile dsiProfile;Boot dsiBoot;XELP::DSI::Host dsiCommandHost; + bool probeDSICommands(IOService*);}; +BODY +int main(){ + IOService controller;IOLock owner;IORecursiveLock clock;IOMemoryMap map; + auto &c=display13FBPower;c.lock=&owner;c.controller=&controller;c.map=↦ + auto &k=comboClockTransaction;k.lock=&clock;k.allocation.external[0]=reinterpret_cast(&controller);k.allocation.binding[0]=2; + map.bar[0x45404/4]=0x403;map.bar[0x6B0D0/4]=0x1040; + XE xe;xe.adlnTopologyController=&controller; + c.engineEnabled=true;assert(!xe.probeDSICommands(&controller) && !map.bar[0x6B100/4]);c.engineEnabled=false; + unchanged=false;assert(!xe.probeDSICommands(&controller) && !map.bar[0x6B100/4]);unchanged=true; + map.bar[0x6B0D8/4]=1;assert(!xe.probeDSICommands(&controller) && !map.bar[0x6B100/4]);map.bar[0x6B0D8/4]=0; + assert(xe.probeDSICommands(&controller) && map.bar[0x6B100/4]==0x40000005 && !level); + assert(receipt[1] && receipt[4] && receipt[5] && receipt[6] && receipt[9]); + assert(receipt[7]==0x1040 && !receipt[8] && !receipt[10] && !receipt[11] && receipt[12]==2 && !receipt[13] && receipt[15]==2); + assert(!xe.probeDSICommands(&controller) && receipt[15]==0); // no replay + unsigned nonzero=0;for(auto word:map.bar)nonzero+=bool(word);assert(nonzero==3); + puts("PASS DSI command trial: owner/clock locks, no writes before admission, unchanged source, bootstrap-only and one-shot"); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='dsi-command-adapter-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/dsi_commands.cpp b/tests/dsi_commands.cpp new file mode 100644 index 00000000..0721b32c --- /dev/null +++ b/tests/dsi_commands.cpp @@ -0,0 +1,45 @@ +#include "kern_dsi_commands.hpp" +#include +#include +#include +using namespace XELP::DSI; +struct IO { + bool owned=true,missingHeader=false,ulps=false,stuckCredits=false,stuckLP=false; + unsigned delay=0,creditReads=0,lpReads=0,pending=0; + std::vector regs,values; + bool ready(){return owned;} + bool readable(uint32_t r){return !(missingHeader && r==0x6B100);} + uint32_t read(uint32_t r){ + if(r==0x6B0D0){++creditReads;return stuckCredits || pending?0:0x1040;} + assert(r==0x6B0D8);++lpReads;return ulps?0x10001:stuckLP?0x20000:0; + } + void write(uint32_t r,uint32_t v){assert(owned);regs.push_back(r);values.push_back(v);if(r==0x6B100)pending=10;} + void delayUs(unsigned us){delay+=us;pending=pending>us?pending-us:0;} +}; +int main(){ + IO io;CommandPort port{io};Host host(true);uint8_t nop=0; + assert(host.send(port,0x05,{&nop,1}) && host.drain(port)); + assert(io.regs==std::vector({0x6B100,0x6B100})); + assert(io.values==std::vector({0x40000005,0x40000005})); + assert(port.writes==2 && host.headers==2 && !host.pending && host.waitedUs==20); + IO longIO;CommandPort longPort{longIO};Host longHost(true);uint8_t bytes[]={0xB0,1,2,3,4}; + assert(longHost.send(longPort,0x39,{bytes,5}) && longHost.drain(longPort)); + assert(longIO.regs==std::vector({0x6B104,0x6B104,0x6B100,0x6B100})); + assert(longIO.values==std::vector({0x030201B0,4,0xC0000539,0x40000005})); + for(unsigned failure=0;failure<5;++failure){ + IO bad;CommandPort p{bad};Host h(true); + if(failure==0)bad.owned=false; + if(failure==1)bad.missingHeader=true; + if(failure==2)bad.stuckCredits=true; + if(failure==3)bad.ulps=true; + if(failure==4)bad.stuckLP=true; + assert(!(h.send(p,0x05,{&nop,1}) && h.drain(p))); + assert(h.faulted);const auto count=p.writes; + assert(!h.send(p,0x05,{&nop,1}) && p.writes==count); + if(failure<3)assert(!p.writes); + if(failure==2)assert(h.error==HostError::CreditTimeout && bad.delay==100); + if(failure==3)assert(h.error==HostError::Unavailable); + if(failure==4)assert(h.error==HostError::DrainTimeout && bad.delay==40); + } + puts("PASS command MMIO adapter: exact LP packet words, payload before header, credit/drain waits, ownership and sticky failures"); +} diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index eec9b5e2..e0de1a57 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -49,6 +49,7 @@ unsigned published=0; void dsiPublishIRQLive(){assert(lockLevel==0 && display13FBIRQ.controller && display13FBIRQ.lock);++published;} struct XE { + bool dsiCommandTest=false;bool probeDSICommands(IOService*){assert(lockLevel==0);return true;} bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; struct {unsigned dsi=1;} adlnOutputWiring; Profile dsiProfile;Boot dsiBoot;GGTTMode dsiGGTT; diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index 37798fe8..ddedfb53 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -2,6 +2,7 @@ "-wex-dsi-audit": [], "-wex-dsi-adopt": [], "-wex-dsi-panel-only": [], + "-wex-dsi-command-test": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" diff --git a/tools/test.py b/tools/test.py index 62f984aa..6fc2873f 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,7 +9,7 @@ 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', '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 += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] -python_tests += ['dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] +python_tests += ['dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From 22f7024e5622dcaa5975431665257c84520bb216 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:16:51 +0900 Subject: [PATCH 067/139] Bound active-video DSI command waits by the fixed frame period --- WhateverXe/kern_dsi_host.hpp | 11 ++++++----- WhateverXe/kern_xe.cpp | 16 ++++++++++++---- tests/dsi_command_adapter.py | 6 +++--- tests/dsi_commands.cpp | 6 ++++-- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/WhateverXe/kern_dsi_host.hpp b/WhateverXe/kern_dsi_host.hpp index 90c75047..e81a242a 100644 --- a/WhateverXe/kern_dsi_host.hpp +++ b/WhateverXe/kern_dsi_host.hpp @@ -36,7 +36,8 @@ struct Host { if(io.cancelled())return fail(HostError::Cancelled); return true; } - template bool credits(IO &io,unsigned h,unsigned p) { + template bool credits(IO &io,unsigned h,unsigned p,unsigned budgetUs=100) { + if(budgetUs>100000 || budgetUs%10)return fail(HostError::Invalid); for(unsigned us=0;;us+=10) { if(!ready(io))return false; uint32_t status=0; @@ -44,7 +45,7 @@ struct Host { const auto headers=(status>>8)&31,payload=status&255; if(headers>16 || payload>64)return fail(HostError::Unavailable); if(headers>=h && payload>=p)return true; - if(us==100)return fail(HostError::CreditTimeout); + if(us==budgetUs)return fail(HostError::CreditTimeout); io.delayUs(10);waitedUs+=10; } } @@ -64,12 +65,12 @@ struct Host { if(!ready(io))return false; pending=true;io.header(p.header);++headers;return true; } - template bool drain(IO &io) { + template bool drain(IO &io,unsigned creditBudgetUs=100) { if(!ready(io))return false; if(!pending)return true; - if(!credits(io,16,64))return false; + if(!credits(io,16,64,creditBudgetUs))return false; const uint8_t nop=0; - if(!send(io,0x05,{&nop,1}) || !credits(io,16,0))return false; + if(!send(io,0x05,{&nop,1}) || !credits(io,16,0,creditBudgetUs))return false; for(unsigned us=0;;us+=2) { if(!ready(io))return false; bool busy=false; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 526352ff..3b35731e 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -12092,18 +12092,26 @@ bool XE::probeDSICommands(IOService *controller) { } void delayUs(unsigned us){IODelay(us);} } io{c.map,owned}; - XELP::DSI::CommandPort port{io};uint64_t record[16] {1}; + XELP::DSI::CommandPort port{io};uint64_t record[20] {2}; + // Active video can defer an LP packet until its blanking interval. The + // cold panel-sequence engine keeps its Linux 100 us default. Bound this + // running-link trial to three frames per credit wait, rounded to 10 us. + const auto &mode=dsiProfile.mode; + const uint64_t period=mode.clockKHz ? (uint64_t(mode.htotal())*mode.vtotal()*1000+mode.clockKHz-1)/mode.clockKHz : 0; + record[16]=period ? ((3*period+9)/10)*10 : 0; uint32_t credits=0,lp=0; - record[1]=io.ready() && !dsiCommandHost.enabled && dsiProfile.valid() && + record[1]=io.ready() && !dsiCommandHost.enabled && record[16]>=100 && record[16]<=100000 && dsiProfile.valid() && c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); if(record[1]) { const bool empty=port.credits(credits) && port.read(0x6B0D8,lp) && (credits&0x1FFF)==0x1040 && !(lp&((1U<<17)|(1U<<16)|1U)); record[2]=credits;record[3]=lp;record[4]=empty; if(empty) { + record[17]=io.read(0x70040); dsiCommandHost.enabled=true;const uint8_t nop=0; record[5]=dsiCommandHost.send(port,0x05,{&nop,1}); - record[6]=record[5] && dsiCommandHost.drain(port); + record[6]=record[5] && dsiCommandHost.drain(port,static_cast(record[16])); + record[18]=io.read(0x70040); } record[7]=port.credits(credits)?credits:UINT32_MAX; record[8]=port.read(0x6B0D8,lp)?lp:UINT32_MAX; @@ -12111,7 +12119,7 @@ bool XE::probeDSICommands(IOService *controller) { } record[10]=static_cast(dsiCommandHost.error);record[11]=dsiCommandHost.pending; record[12]=dsiCommandHost.headers;record[13]=dsiCommandHost.payloadWords; - record[14]=dsiCommandHost.waitedUs;record[15]=port.writes; + record[14]=dsiCommandHost.waitedUs;record[15]=port.writes;record[19]=dsiBoot.source.conf; const bool complete=record[1] && record[4] && record[6] && record[9] && !record[10] && !record[11] && !port.failed && (record[7]&0x1FFF)==0x1040 && record[8]!=UINT32_MAX && !(record[8]&((1U<<17)|(1U<<16)|1U)); diff --git a/tests/dsi_command_adapter.py b/tests/dsi_command_adapter.py index c9145a2c..c7ab8126 100644 --- a/tests/dsi_command_adapter.py +++ b/tests/dsi_command_adapter.py @@ -21,8 +21,8 @@ uintptr_t getVirtualAddress(){return reinterpret_cast(bar.data());}}; struct Display13FBBootIO {IOMemoryMap *map;bool readable(uint32_t r){return map && r<0x1000000 && !(r&3);} uint32_t read(uint32_t r){assert(level==2 && readable(r));return map->bar[r/4];}}; -struct Profile {bool valid(){return true;}}; -struct Boot {struct {struct {unsigned pll=1;}identity;}source;}; +struct Profile {struct {unsigned clockKHz=135255;unsigned htotal() const {return 1126;}unsigned vtotal() const {return 2002;}}mode;bool valid(){return true;}}; +struct Boot {struct {struct {unsigned pll=1;}identity;unsigned conf=0xC0000000;}source;}; bool unchanged=true;int xelpMemory; namespace XELP {namespace DSI {templatebool bootUnchanged(IO&,const Boot&,int){return unchanged;}}} struct {IORecursiveLock *lock;uintptr_t thread=0;struct {uintptr_t external[3]{};unsigned binding[3]{};}allocation;}comboClockTransaction; @@ -30,7 +30,7 @@ bool engineEnabled=false,memoryFaulted=false,clockFaulted=false; struct {struct {bool faulted=false,suspended=false;}state;bool held=true; templatebool outputHeld(IO&,unsigned,unsigned){assert(level==2);return held;}}bridge;}display13FBPower; -uint64_t receipt[16]{};unsigned published=0; +uint64_t receipt[20]{};unsigned published=0; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char *key,const void *p,unsigned size){assert(!level && !strcmp(key,"WEXDSICommandTrial") && size==sizeof(receipt));memcpy(receipt,p,size);++published;}}; struct XE {IOService *adlnTopologyController;bool dsiResourcesReady=true,dsiStopAttempted=false; diff --git a/tests/dsi_commands.cpp b/tests/dsi_commands.cpp index 0721b32c..6eea7e36 100644 --- a/tests/dsi_commands.cpp +++ b/tests/dsi_commands.cpp @@ -5,7 +5,7 @@ using namespace XELP::DSI; struct IO { bool owned=true,missingHeader=false,ulps=false,stuckCredits=false,stuckLP=false; - unsigned delay=0,creditReads=0,lpReads=0,pending=0; + unsigned delay=0,creditReads=0,lpReads=0,pending=0,deliveryUs=10; std::vector regs,values; bool ready(){return owned;} bool readable(uint32_t r){return !(missingHeader && r==0x6B100);} @@ -13,7 +13,7 @@ struct IO { if(r==0x6B0D0){++creditReads;return stuckCredits || pending?0:0x1040;} assert(r==0x6B0D8);++lpReads;return ulps?0x10001:stuckLP?0x20000:0; } - void write(uint32_t r,uint32_t v){assert(owned);regs.push_back(r);values.push_back(v);if(r==0x6B100)pending=10;} + void write(uint32_t r,uint32_t v){assert(owned);regs.push_back(r);values.push_back(v);if(r==0x6B100)pending=deliveryUs;} void delayUs(unsigned us){delay+=us;pending=pending>us?pending-us:0;} }; int main(){ @@ -26,6 +26,8 @@ int main(){ assert(longHost.send(longPort,0x39,{bytes,5}) && longHost.drain(longPort)); assert(longIO.regs==std::vector({0x6B104,0x6B104,0x6B100,0x6B100})); assert(longIO.values==std::vector({0x030201B0,4,0xC0000539,0x40000005})); + IO video;video.deliveryUs=16000;CommandPort vp{video};Host vh(true); + assert(vh.send(vp,0x05,{&nop,1}) && vh.drain(vp,50010) && vh.waitedUs==32000); for(unsigned failure=0;failure<5;++failure){ IO bad;CommandPort p{bad};Host h(true); if(failure==0)bad.owned=false; From f03726ec0a535417aaa758b44607f216903bc4c0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:28:59 +0900 Subject: [PATCH 068/139] Connect DSI panel sequences to a bounded bootstrap power cycle --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_panel.hpp | 135 ++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 75 ++++++++++++- WhateverXe/kern_xe.hpp | 4 + tests/adlp_fb_candidate.py | 2 +- tests/dsi_panel.py | 106 +++++++++++++++++++ tests/dsi_resources.py | 1 + tests/fixtures/boot_argument_names.json | 1 + tools/test.py | 2 +- 10 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 WhateverXe/kern_dsi_panel.hpp create mode 100644 tests/dsi_panel.py diff --git a/README.md b/README.md index 819354dd..35a1f5d8 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 8e100417..23e4f102 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp new file mode 100644 index 00000000..3f6c0160 --- /dev/null +++ b/WhateverXe/kern_dsi_panel.hpp @@ -0,0 +1,135 @@ +// Native display GPIOs used by Intel VBT DSI sequences (intel_dsi_vbt.c v7.0). +// GPIO role is a display function, not an OS gpiochip number. The adapter must +// hold the display IRQ lock across RESET's HPD control read/modify/write only. +#ifndef KERN_DSI_PANEL_HPP +#define KERN_DSI_PANEL_HPP +#include "kern_dsi_commands.hpp" +#include "kern_dsi_surface.hpp" +namespace XELP { namespace DSI { namespace Panel { +struct GPIOResult {uint32_t role {},value {},reg {},before {},after {},writes {};bool complete {};}; +template GPIOResult gpio(IO &io,unsigned role,unsigned value) { + GPIOResult r;r.role=role;r.value=value; + if(role>2 || value>1 || !io.ready())return r; + r.reg=role==0?0xC4030:0xC7204; + if(!io.readable(r.reg))return r; + r.before=io.read(r.reg);if(r.before==UINT32_MAX)return r; + const uint32_t mask=role==0?12U:role==1?1U:4U; + const uint32_t bits=value?(role==1?1U:4U):0; + // HPD status is W1C. Reset must not acknowledge another port's event. + const auto next=(r.before&~(mask|(role==0?0x3333U:0U)))|bits; + if(!io.ready())return r; + io.write(r.reg,next);++r.writes; + r.after=io.read(r.reg); + r.complete=r.after!=UINT32_MAX && (r.after&mask)==bits && io.ready();return r; +} +enum class Stage : uint32_t { + Input,BacklightOff,PlaneOff,TranscoderOff,DisplayOff,AssertReset,PowerOff, + PowerCycle,PowerOn,DeassertReset,Init,TranscoderOn,PlaneOn,BacklightOn,Complete +}; +enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Sequence}; +struct Result { + Stage stage {Stage::Input};Error error {Error::Input}; + uint32_t reg {},value {},writes {},waitUs {},frameBefore {},frameAfter {}; + Host host;bool attempted {}; + bool complete() const {return stage==Stage::Complete && error==Error::None;} +}; +// Bounded bootstrap panel experiment. Keep PLL/PHY/host and existing scanout +// backing pinned. Stop pixel transfer, execute the complete panel off/reset/on +// sequences, and restart the same scanout. This is NOT a cold host restart or +// a runtime DPMS implementation. No allocation, PLL or shared power release. +// IO::gpio serializes HPD writes; no delays may run under that IRQ lock. +// A failed transaction is never retried and must retain all resources. +template void cycle(IO &io,const Readout &link,const Profile &profile, + uint32_t planeControl,uint32_t surface,Result &r) { + if(r.attempted)return; + r.attempted=true; + if(!profile.valid() || !link.matches(profile) || link.identity.pipe!=0 || + !(planeControl&(1U<<31)) || (surface&4095) || !profile.sequences.valid())return; + constexpr unsigned required[]={1,2,4,5,6,7,10,11}; + for(auto id:required) { + Sequences count; + if(validateSequence(profile.sequences.sequence[id],count)!=SequenceStatus::Valid || count.delayUs>5000000)return; + } + if(profile.sequences.sequence[3].data) { + Sequences count; + if(validateSequence(profile.sequences.sequence[3],count)!=SequenceStatus::Valid || count.delayUs>5000000)return; + } + for(auto us:profile.ppsUs)if(us>5000000)return; + const uint64_t period=(uint64_t(profile.mode.htotal())*profile.mode.vtotal()*1000+profile.mode.clockKHz-1)/profile.mode.clockKHz; + if(period<1000 || period>100000)return; + const auto held=[&]() {if(!io.ready()){r.error=Error::Ownership;return false;}return true;}; + const auto read=[&](uint32_t reg,uint32_t &v) { + r.reg=reg;if(!held())return false; + if(!io.readable(reg)){r.error=Error::Readback;return false;} + v=io.read(reg);r.value=v;if(v==UINT32_MAX){r.error=Error::Readback;return false;}return true; + }; + const auto write=[&](uint32_t reg,uint32_t v) { + if(!held() || !io.readable(reg)){r.error=Error::Ownership;return false;} + io.write(reg,v);++r.writes;return true; + }; + const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t value,uint32_t budget) { + for(uint32_t us=0;;us+=1000) { + uint32_t observed=0;if(!read(reg,observed))return false; + if((observed&mask)==value)return true; + if(us>=budget){r.error=Error::Timeout;return false;} + io.delayUs(1000);r.waitUs+=1000; + } + }; + const auto frame=[&]() { + if(!read(0x70040,r.frameBefore))return false; + for(uint32_t us=0;;us+=1000) { + if(!read(0x70040,r.frameAfter))return false; + const auto n=uint32_t(r.frameAfter-r.frameBefore); + if(n && n<0x80000000U)return true; + if(n || us>=3*period+1000){r.error=Error::Timeout;return false;} + io.delayUs(1000);r.waitUs+=1000; + } + }; + struct Port : CommandPort { + explicit Port(IO &q):CommandPort{q} {} + bool gpio(unsigned role,unsigned value){return this->io.gpio(role,value);} + } port{io}; + const auto sequence=[&](unsigned id) { + if(!r.host.run(port,profile.sequences,id)){r.error=Error::Sequence;return false;}return true; + }; + const auto delay=[&](unsigned index) { + if(!r.host.delay(port,profile.ppsUs[index])){r.error=Error::Sequence;return false;}return true; + }; + r.error=Error::Readback; + uint32_t credits=0,lp=0; + if(!held() || !Surface::source(io,link) || + !Surface::word(io,0x70180,UINT32_MAX,planeControl) || + !Surface::word(io,0x7019C,UINT32_MAX,surface) || + !read(0x6B0D0,credits) || !read(0x6B0D8,lp) || + (credits&0x1FFF)!=0x1040 || (lp&0x30001))return; + r.host.enabled=true; + r.stage=Stage::BacklightOff;if(!sequence(7) || !delay(2))return; + r.stage=Stage::PlaneOff; + if(!write(0x70180,planeControl&~(1U<<31)) || !write(0x7019C,surface) || + !wait(0x70180,1U<<31,0,0) || !frame())return; + r.stage=Stage::TranscoderOff; + if(!write(0x7B008,link.conf&~(1U<<31)) || !wait(0x7B008,0xC0000000U,0,50000))return; + r.stage=Stage::DisplayOff;if(!sequence(4))return; + r.stage=Stage::AssertReset;if(!sequence(5) || !delay(3))return; + r.stage=Stage::PowerOff;if(!sequence(11))return; + r.stage=Stage::PowerCycle;if(!delay(4))return; + r.stage=Stage::PowerOn;if(!sequence(10) || !delay(0))return; + r.stage=Stage::DeassertReset;if(!sequence(1))return; + r.stage=Stage::Init;if(!sequence(2))return; + // Sequences drain before returning; never enable video over queued data. + if(port.failed || r.host.pending || !held())return; + r.stage=Stage::TranscoderOn; + if(!write(0x7B008,link.conf) || !wait(0x7B008,0xC0000000U,0xC0000000U,10000))return; + if(profile.sequences.sequence[3].data && !sequence(3))return; + r.stage=Stage::PlaneOn; + if(!write(0x70180,planeControl) || !write(0x7019C,surface) || + !wait(0x70180,UINT32_MAX,planeControl,0) || !frame() || + !wait(0x701AC,UINT32_MAX,surface,0))return; + r.stage=Stage::BacklightOn;if(!delay(1) || !sequence(6))return; + if(!held() || !Surface::source(io,link) || port.failed || r.host.pending || + !read(0x6B0D0,credits) || !read(0x6B0D8,lp) || + (credits&0x1FFF)!=0x1040 || (lp&0x30001))return; + r.stage=Stage::Complete;r.error=Error::None; +} +}}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3b35731e..a936c4e1 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3873,6 +3873,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { board.dsi=1; dsiPanelOnly=checkKernelArgument("-wex-dsi-panel-only"); dsiCommandTest=checkKernelArgument("-wex-dsi-command-test"); + dsiPanelTest=checkKernelArgument("-wex-dsi-panel-test"); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelOnly",dsiPanelOnly); if(!board.described())return {}; } @@ -12128,6 +12129,77 @@ bool XE::probeDSICommands(IOService *controller) { return complete; } +// Bootstrap-only panel sequence experiment. Keep the host/PHY/PLL alive so +// GPIO and long-packet failures are distinguishable from cold host startup. +bool XE::probeDSIPanel(IOService *controller) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; + if(!controller || !c.lock || !clock.lock || !irq.lock)return false; + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + const auto owned=[&]() { + return controller==c.controller && controller==adlnTopologyController && + dsiResourcesReady && !dsiStopAttempted && adlnOutputWiring.dsi==1 && + !c.engineEnabled && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !clock.thread && + clock.allocation.external[0]==reinterpret_cast(controller) && + clock.allocation.binding[0]==dsiBoot.source.identity.pll+1 && + irq.controller==controller && !irq.state.begun && !irq.state.enabled && + !irq.quiescing && !irq.handlers && !irq.softwareHandlers; + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns;IOSimpleLock *irqLock; + uint32_t writes {},gpios {};XELP::DSI::Panel::GPIOResult lastGPIO; + IO(IOMemoryMap *m,decltype(owned) &q,IOSimpleLock *l):Display13FBBootIO{m},owns(q),irqLock(l) {} + bool ready() { + if(!owns() || !readable(0x45404))return false; + const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; + } + void write(uint32_t reg,uint32_t value) { + switch(reg) { + case 0x6B100:case 0x6B104:case 0xC4030:case 0xC7204: + case 0x70180:case 0x7019C:case 0x7B008: + *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;break; + } + } + bool gpio(unsigned role,unsigned value) { + // Only this short RMW is under the IRQ lock. All VBT delays and + // FIFO/frame polling execute with interrupts enabled. + const auto saved=IOSimpleLockLockDisableInterrupt(irqLock); + lastGPIO=XELP::DSI::Panel::gpio(*this,role,value); + IOSimpleLockUnlockEnableInterrupt(irqLock,saved); + if(lastGPIO.complete)++gpios; + return lastGPIO.complete; + } + void delayUs(unsigned us){IODelay(us);} + } io{c.map,owned,irq.lock}; + uint64_t record[32] {1}; + record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && + !dsiCommandHost.faulted && !dsiCommandHost.pending && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + if(record[1]) { + record[20]=io.read(0xC7204);record[22]=io.read(0xC4030); + record[24]=io.read(0xC7200);record[28]=io.read(0x70040); + XELP::DSI::Panel::cycle(io,dsiBoot.source,dsiProfile,dsiBoot.control,dsiBoot.surface,dsiPanelTrial); + record[21]=io.ready()?io.read(0xC7204):UINT32_MAX; + record[23]=io.ready()?io.read(0xC4030):UINT32_MAX; + record[25]=io.ready()?io.read(0xC7200):UINT32_MAX; + record[26]=io.ready()?io.read(0x6B0D0):UINT32_MAX; + record[27]=io.ready()?io.read(0x6B0D8):UINT32_MAX; + record[29]=io.ready()?io.read(0x70040):UINT32_MAX; + record[30]=io.ready() && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + } + const auto &r=dsiPanelTrial;const auto &h=r.host; + record[2]=static_cast(r.stage);record[3]=static_cast(r.error); + record[4]=r.reg;record[5]=r.value;record[6]=io.writes;record[7]=r.waitUs; + record[8]=static_cast(h.error);record[9]=h.pending;record[10]=h.sequenceId; + record[11]=h.operationOffset;record[12]=h.headers;record[13]=h.payloadWords; + record[14]=h.waitedUs;record[15]=h.completedOperations;record[16]=io.gpios; + record[17]=io.lastGPIO.role;record[18]=io.lastGPIO.value;record[19]=io.lastGPIO.complete; + const bool complete=record[1] && r.complete() && record[30];record[31]=complete; + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); + return complete; +} + // Called outside the owner lock, before native engine/probe/IRQ users start. // The lock order matches clock clients: Combo clock -> display owner -> IRQ. // Failure retains any already acquired boot power references for cleanup. @@ -12173,7 +12245,8 @@ bool XE::bindDSIResources(IOService *controller) { } IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(ready)dsiPublishIRQLive(); - if(ready && dsiCommandTest)return probeDSICommands(controller); + if(ready && dsiCommandTest)ready=probeDSICommands(controller); + if(ready && dsiPanelTest)ready=probeDSIPanel(controller); return ready; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 0526f294..81303c93 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -15,6 +15,7 @@ #include "kern_dsi_quiesce.hpp" #include "kern_dsi_clock.hpp" #include "kern_dsi_commands.hpp" +#include "kern_dsi_panel.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" @@ -248,6 +249,9 @@ class XE { bool dsiBackingHooksReady {}; bool dsiPanelOnly {}; bool dsiCommandTest {}; + bool dsiPanelTest {}; + XELP::DSI::Panel::Result dsiPanelTrial; + bool probeDSIPanel(IOService *); XELP::DSI::Host dsiCommandHost; bool probeDSICommands(IOService *); mach_vm_address_t orgDSIBacking[2] {}; diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 225f6ceb..9bb7af12 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -21,7 +21,7 @@ XELP::ProbeConfig seen;unsigned calls=0; bool configure(void *c,const XELP::ProbeConfig *p){assert(c==(void*)1);seen=*p;++calls;return p->valid();} struct XE { - bool dsiPanelOnly=false,dsiCommandTest=false; + bool dsiPanelOnly=false,dsiCommandTest=false,dsiPanelTest=false; XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; diff --git a/tests/dsi_panel.py b/tests/dsi_panel.py new file mode 100644 index 00000000..5d26827c --- /dev/null +++ b/tests/dsi_panel.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Panel cycle ordering, GPIO side effects and bounded failure; optional saved VBT.""" +from pathlib import Path +import subprocess, tempfile, sys +root=Path(__file__).resolve().parents[1] +boot=(root/'tests/dsi_boot.cpp').read_text() +io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] +profile=(root/'tests/dsi_profile.cpp').read_text() +fixture=profile[profile.index('using Bytes='):profile.index('int main(')] +code=r''' +#include "kern_dsi_panel.hpp" +#include "kern_xelp_memory_layout.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +PROFILE_FIXTURE +struct Sim:IO { + bool owned=true,freezeFrame=false,stuckStop=false,stuckStart=false,stuckCommand=false; + uint32_t drop=UINT32_MAX,elapsed=0,headers=0,payload=0;unsigned irqDepth=0; + std::vector packets,gpioTrace; + bool ready(){return owned && (regs[0x45404]&0x403)==0x403;} + uint32_t read(uint32_t reg){ + if(reg==0x6B0D0 && stuckCommand && headers)return 0; + return IO::read(reg); + } + void write(uint32_t reg,uint32_t value){ + assert(ready());writes.push_back(reg); + if(reg==drop)return; + if(reg==0xC4030){assert(irqDepth);regs[reg]=(value&~0x3333U)|(regs[reg]&0x3333U&~value);return;} + if(reg==0xC7204){assert(irqDepth);regs[reg]=value;return;} + assert(!irqDepth); + if(reg==0x6B100){++headers;packets.push_back(value);return;} + if(reg==0x6B104){++payload;return;} + if(reg==0x7B008){regs[reg]=(value&~(1U<<30))|(regs[reg]&(1U<<30));return;} + regs[reg]=value; + } + void delayUs(unsigned us){ + assert(!irqDepth);elapsed+=us; + auto &conf=regs[0x7B008]; + if(!(conf&(1U<<31)) && !stuckStop)conf&=~(1U<<30); + if((conf&(1U<<31)) && !stuckStart)conf|=1U<<30; + if(!freezeFrame && us>=1000 && (conf&0xC0000000U)==0xC0000000U){++regs[0x70040];regs[0x701AC]=regs[0x7019C];} + } + bool gpio(unsigned role,unsigned value){ + ++irqDepth;const auto r=Panel::gpio(*this,role,value);--irqDepth; + if(r.complete)gpioTrace.push_back(role*2+value);return r.complete; + } + Sim(){regs[0x42084]=1U<<30;regs[0x6B0D0]=0x1040;regs[0x6001C]=0x0437077F; + regs[0x6B010]=regs[0x6B00C];regs[0xC4030]=0xA7;regs[0xC7204]=0xABCD0015; + regs[0x71180]=0x94000000;regs[0x64100]=0x80000000;} +}; +struct Fixture { + Sim io;Bytes vbt;Profile p;Readout link;Panel::Result result; + std::vector seq=std::vector(12); + Fixture(){vbt=fixture();p=parse(vbt); + seq[1]={3,3,0,0,1,0};seq[2]={1,9,0,0x39,5,0,0xB0,1,2,3,4,1,5,0,5,1,0,0x11,0}; + seq[4]={1,5,0,5,1,0,0x10,1,5,0,5,1,0,0x28,0};seq[5]={3,3,0,0,0,0}; + seq[6]={3,3,0,2,1,0};seq[7]={3,3,0,2,0,0};seq[10]={3,3,0,1,1,0};seq[11]={3,3,0,1,0,0}; + for(unsigned id:{1U,2U,4U,5U,6U,7U,10U,11U})p.sequences.sequence[id]={seq[id].data(),seq[id].size()}; + link=decodeReadout([&](uint32_t r,uint32_t &v){v=io.read(r);return true;},38400);assert(link.matches(p)); + } + void run(){Panel::cycle(io,link,p,0x94000008,0,result);} +}; +int main(int argc,char **argv){ + {Fixture f;f.run();assert(f.result.complete()); + assert(f.io.gpioTrace==std::vector({4,0,2,3,1,5})); + assert(f.io.packets==std::vector({0x40001005,0x40002805,0x40000005,0xC0000539,0x40001105,0x40000005})); + assert(f.io.payload==2 && f.io.regs[0xC4030]==0xA7 && f.io.regs[0xC7204]==0xABCD0015); + assert(f.io.regs[0x70180]==0x94000008 && f.io.regs[0x7B008]==0xC0000000); + assert(f.io.regs[0x71180]==0x94000000 && f.io.regs[0x64100]==0x80000000 && f.io.regs[0x46014]==0xCC000000); + const auto writes=f.io.writes.size();f.run();assert(f.io.writes.size()==writes); + for(auto reg:f.io.writes)assert(reg==0xC4030 || reg==0xC7204 || reg==0x70180 || reg==0x7019C || reg==0x7B008 || reg==0x6B100 || reg==0x6B104); + } + {Fixture f;f.p.sequences.sequence[2].size-=1;f.run();assert(!f.result.complete() && f.io.writes.empty());} + {Fixture f;f.io.owned=false;f.run();assert(!f.result.complete() && f.io.writes.empty());} + for(unsigned fault=0;fault<6;++fault){Fixture f; + if(fault==0)f.io.freezeFrame=true; + if(fault==1)f.io.stuckStop=true; + if(fault==2)f.io.stuckCommand=true; + if(fault==3)f.io.drop=0xC4030; + if(fault==4)f.io.drop=0xC7204; + if(fault==5)f.io.stuckStart=true; + f.run();assert(!f.result.complete()); + assert(f.io.elapsed<1000000); // bounded failure, no repeated power cycles + assert(f.io.regs[0x46014]==0xCC000000); // retain ownership/clock on failure + const auto writes=f.io.writes.size();f.run();assert(f.io.writes.size()==writes); + if(fault<3)assert(f.io.gpioTrace==std::vector{4}); // no reset after stop/drain failure + if(fault==5)assert(f.result.stage==Panel::Stage::TranscoderOn && !(f.io.regs[0x70180]&(1U<<31))); + } + if(argc==2){Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in); + f.vbt=Bytes{std::istreambuf_iterator(in),{}};f.p=parseProfile({f.vbt.data(),f.vbt.size()},true);assert(f.p.valid()); + f.run();assert(f.result.complete() && f.result.host.completedOperations==f.p.sequences.packets+f.p.sequences.delays+f.p.sequences.gpios); + assert(f.io.gpioTrace==std::vector({4,0,2,3,1,0,1,5})); + printf("saved VBT replay: packets=%u payloadWords=%u operations=%u gpio=%zu delayUs=%llu; simulation only\n",f.result.host.headers,f.result.host.payloadWords,f.result.host.completedOperations,f.io.gpioTrace.size(),static_cast(f.result.host.waitedUs)); + } + puts("PASS panel cycle: packet/GPIO order, retained host, restart after drain, HPD W1C preservation, bounded failures and no retry"); +} +'''.replace('IO_FIXTURE',io).replace('PROFILE_FIXTURE',fixture) +with tempfile.TemporaryDirectory(prefix='dsi-panel-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test'),*sys.argv[1:]],check=True) diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index e0de1a57..eb51cb3d 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -49,6 +49,7 @@ unsigned published=0; void dsiPublishIRQLive(){assert(lockLevel==0 && display13FBIRQ.controller && display13FBIRQ.lock);++published;} struct XE { + bool dsiPanelTest=false;bool probeDSIPanel(IOService*){assert(lockLevel==0);return true;} bool dsiCommandTest=false;bool probeDSICommands(IOService*){assert(lockLevel==0);return true;} bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; struct {unsigned dsi=1;} adlnOutputWiring; diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index ddedfb53..ebe5afe1 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -3,6 +3,7 @@ "-wex-dsi-adopt": [], "-wex-dsi-panel-only": [], "-wex-dsi-command-test": [], + "-wex-dsi-panel-test": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" diff --git a/tools/test.py b/tools/test.py index 6fc2873f..3b9ea700 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,7 +9,7 @@ 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', '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 += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] -python_tests += ['dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] +python_tests += ['dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From c72a0189bf0e78279e175ec75d13c1456efbb5ae Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:40:04 +0900 Subject: [PATCH 069/139] Rebuild the retained DSI host from the panel profile --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_panel.hpp | 34 ++-- WhateverXe/kern_dsi_profile.hpp | 2 + WhateverXe/kern_dsi_restart.hpp | 211 ++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 43 ++++- WhateverXe/kern_xe.hpp | 3 + tests/adlp_fb_candidate.py | 2 +- tests/dsi_profile.cpp | 3 +- tests/dsi_restart.py | 166 +++++++++++++++++++ tests/fixtures/boot_argument_names.json | 1 + tools/test.py | 2 +- 12 files changed, 447 insertions(+), 24 deletions(-) create mode 100644 WhateverXe/kern_dsi_restart.hpp create mode 100644 tests/dsi_restart.py diff --git a/README.md b/README.md index 35a1f5d8..8d337c04 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 23e4f102..31216967 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 3f6c0160..45f82572 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -24,23 +24,25 @@ template GPIOResult gpio(IO &io,unsigned role,unsigned value) { } enum class Stage : uint32_t { Input,BacklightOff,PlaneOff,TranscoderOff,DisplayOff,AssertReset,PowerOff, - PowerCycle,PowerOn,DeassertReset,Init,TranscoderOn,PlaneOn,BacklightOn,Complete + PowerCycle,PowerOn,DeassertReset,Init,TranscoderOn,PlaneOn,BacklightOn,Complete,HostOff,HostOn }; -enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Sequence}; +enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Sequence,Host}; struct Result { Stage stage {Stage::Input};Error error {Error::Input}; uint32_t reg {},value {},writes {},waitUs {},frameBefore {},frameAfter {}; - Host host;bool attempted {}; + Host host;Readout source;bool attempted {}; bool complete() const {return stage==Stage::Complete && error==Error::None;} }; -// Bounded bootstrap panel experiment. Keep PLL/PHY/host and existing scanout -// backing pinned. Stop pixel transfer, execute the complete panel off/reset/on -// sequences, and restart the same scanout. This is NOT a cold host restart or -// a runtime DPMS implementation. No allocation, PLL or shared power release. +// Bounded bootstrap panel experiment. Keep existing scanout backing pinned. +// Stop pixel transfer, execute the complete panel off/reset/on sequences, and +// restart the same scanout. Lifecycle may stop/rebuild the host between panel +// operations; RetainedHost leaves its settings intact. Neither variant is a +// runtime DPMS implementation or releases allocation/shared power ownership. // IO::gpio serializes HPD writes; no delays may run under that IRQ lock. // A failed transaction is never retried and must retain all resources. -template void cycle(IO &io,const Readout &link,const Profile &profile, - uint32_t planeControl,uint32_t surface,Result &r) { +template void cycle(IO &io,const Readout &link,const Profile &profile, + uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle) { + Readout current=link; if(r.attempted)return; r.attempted=true; if(!profile.valid() || !link.matches(profile) || link.identity.pipe!=0 || @@ -110,11 +112,13 @@ template void cycle(IO &io,const Readout &link,const Profile &profile, r.stage=Stage::TranscoderOff; if(!write(0x7B008,link.conf&~(1U<<31)) || !wait(0x7B008,0xC0000000U,0,50000))return; r.stage=Stage::DisplayOff;if(!sequence(4))return; + r.stage=Stage::HostOff;if(!lifecycle.stop(io,link,profile)){r.error=Error::Host;return;} r.stage=Stage::AssertReset;if(!sequence(5) || !delay(3))return; r.stage=Stage::PowerOff;if(!sequence(11))return; r.stage=Stage::PowerCycle;if(!delay(4))return; r.stage=Stage::PowerOn;if(!sequence(10) || !delay(0))return; r.stage=Stage::DeassertReset;if(!sequence(1))return; + r.stage=Stage::HostOn;if(!lifecycle.start(io,current)){r.error=Error::Host;return;} r.stage=Stage::Init;if(!sequence(2))return; // Sequences drain before returning; never enable video over queued data. if(port.failed || r.host.pending || !held())return; @@ -126,10 +130,18 @@ template void cycle(IO &io,const Readout &link,const Profile &profile, !wait(0x70180,UINT32_MAX,planeControl,0) || !frame() || !wait(0x701AC,UINT32_MAX,surface,0))return; r.stage=Stage::BacklightOn;if(!delay(1) || !sequence(6))return; - if(!held() || !Surface::source(io,link) || port.failed || r.host.pending || + if(!held() || !Surface::source(io,current) || port.failed || r.host.pending || !read(0x6B0D0,credits) || !read(0x6B0D8,lp) || (credits&0x1FFF)!=0x1040 || (lp&0x30001))return; - r.stage=Stage::Complete;r.error=Error::None; + r.source=current;r.stage=Stage::Complete;r.error=Error::None; +} +struct RetainedHost { + template bool stop(IO &,const Readout &,const Profile &){return true;} + template bool start(IO &,Readout &){return true;} +}; +template void cycle(IO &io,const Readout &link,const Profile &profile, + uint32_t planeControl,uint32_t surface,Result &r) { + RetainedHost retained;cycle(io,link,profile,planeControl,surface,r,retained); } }}} #endif diff --git a/WhateverXe/kern_dsi_profile.hpp b/WhateverXe/kern_dsi_profile.hpp index 2f865120..e559dfb8 100644 --- a/WhateverXe/kern_dsi_profile.hpp +++ b/WhateverXe/kern_dsi_profile.hpp @@ -12,6 +12,7 @@ enum class ProfileStatus : uint32_t {Bounds, Version, Duplicate, Missing, Child, struct Mode { uint32_t clockKHz {}; uint16_t width {},hblank {},hfront {},hsync {},height {},vblank {},vfront {},vsync {},widthMM {},heightMM {}; + bool hsyncPositive {},vsyncPositive {}; uint32_t htotal() const {return uint32_t(width)+hblank;} uint32_t vtotal() const {return uint32_t(height)+vblank;} bool valid() const { @@ -116,6 +117,7 @@ inline Profile parseProfile(Span input,bool allowFinalEndQuirk=false) { m.hfront=profileU16(t+8);m.hsync=profileU16(t+10);m.height=profileU16(t+12); m.vblank=profileU16(t+14);m.vfront=profileU16(t+16);m.vsync=profileU16(t+18); m.widthMM=profileU16(t+20);m.heightMM=profileU16(t+22); + m.hsyncPositive=t[24]&0x80;m.vsyncPositive=t[24]&0x40; if(!m.valid())return failure; failure.status=ProfileStatus::Backlight; if(backlight.size<273 || backlight.data[0]!=6)return failure; diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp new file mode 100644 index 00000000..5ffd6575 --- /dev/null +++ b/WhateverXe/kern_dsi_restart.hpp @@ -0,0 +1,211 @@ +// Display13 DSI0/Combo-A host restart. Register ordering: Intel icl_dsi.c +// (Linux v7.0), intel_combo_phy.c / intel_dpll_mgr.c (v6.12). Panel sequences, +// framebuffer lifetime and OS power references belong to the calling adapter. +#ifndef KERN_DSI_RESTART_HPP +#define KERN_DSI_RESTART_HPP +#include "kern_dsi_readout.hpp" +#include "kern_xelp_combo_clock.hpp" +namespace XELP { namespace DSI { namespace Restart { +enum class Stage : uint32_t { + Input,ULPS,FunctionOff,BufferOff,ModeOff,PLLDisable,PLLPowerOff,Stopped, + ModeOn,Escape,PLLPowerOn,PLLConfig,PLLEnable,Map,PHY,BufferOn,Timing,Protocol,Link,Ready +}; +enum class Error : uint32_t {None,Input,Ownership,Consumer,Peer,Readback,Timeout}; +struct Plan { + bool valid {};LinkPlan link;PLLPlan pll; + uint32_t init {},hsTimeout {},lpTimeout {},taTimeout {},function {},transport {}; +}; +inline Plan plan(const Profile &p,uint32_t referenceKHz) { + Plan r;r.link=linkPlan(p);if(!r.link.valid || p.initTimer>65535)return r; + r.pll=pllPlan(r.link.portKHz,referenceKHz);if(!r.pll.valid)return r; + r.init=p.initTimer; + // Linux's byte-count -> escape-count expression, evaluated in 64 bits. + // Its u32 intermediates overflow for this profile. Do not silently rely + // on that overflow (or copy previously programmed timeout registers). + const uint64_t divisor=uint64_t(50)*r.link.afeKHz*1000; + const auto timeout=[&](uint32_t bytes){return (uint64_t(bytes)*8000000+divisor-1)/divisor;}; + const auto hs=timeout(p.hsTxTimeout),lp=timeout(p.lpRxTimeout),ta=timeout(p.turnaroundTimeout); + if(hs>65535 || lp>65535 || ta>65535)return r; + r.hsTimeout=uint32_t(hs);r.lpTimeout=uint32_t(lp);r.taTimeout=uint32_t(ta); + r.function=0x80000006U|(p.mode.hsyncPositive?1U<<16:0)|(p.mode.vsyncPositive?1U<<17:0); + // RGB888, VC0, sync events, continuous HS clock, blanking packets, EOTP. + r.transport=0x20030304;r.valid=true;return r; +} +struct State { + Stage stage {Stage::Input};Error error {Error::None};Plan config; + uint32_t pll {UINT32_MAX},reg {},value {},writes {},waitUs {}; + bool attempted {},stopped {},started {}; + bool fail(Error e){if(error==Error::None)error=e;return false;} +}; +// Registers writable by this backend; used by the owning MMIO adapter. +inline bool writable(uint32_t reg,unsigned pllId) { + XELPComboClock::PLL pll;if(pllId>=2 || !XELPComboClock::PLL::resolve(pllId,pll))return false; + if(reg==pll.enable || reg==pll.cfg0 || reg==pll.cfg1)return true; + switch(reg) { + case 0x6B0D8:case 0x6B400:case 0x164280:case 0x64000:case 0x6B094: + case 0x6B090:case 0x162190:case 0x1621A0:case 0x162028:case 0x162390: + case 0x162388:case 0x162688:case 0x162304:case 0x162604:case 0x162014: + case 0x162694:case 0x162394:case 0x162180:case 0x162184:case 0x6B088: + case 0x6B080:case 0x6B084:case 0x6B000:case 0x6B008:case 0x6B00C: + case 0x6B014:case 0x6B010:case 0x6B028:case 0x6B044:case 0x6B048: + case 0x6B04C:case 0x6B030:case 0x6B034:case 0x6B404:case 0x6B0C0:return true; + } + for(unsigned lane=0;lane<4;++lane)if(reg==0x162890+0x100*lane)return true; + return false; +} +inline Readout expected(const Readout &previous,const State &s) { + if(!s.started || s.stage!=Stage::Ready || s.error!=Error::None || !s.config.valid || + !previous.complete() || previous.identity.pll!=s.pll)return {}; + auto r=previous;const auto &p=s.config; + r.cfg0=p.pll.cfg0();r.cfg1=p.pll.cfg1();r.afeKHz=pllAFE(r.cfg0,r.cfg1,r.referenceKHz); + r.function=p.function;r.transport=p.transport|(1U<<20);r.link|=1;r.mux|=1U<<10; + r.buffer=(r.buffer&~0x80U)|(1U<<31); + r.htotal=p.link.htotal;r.hsync=p.link.hsync;r.vtotal=p.link.vtotal;r.vsync=p.link.vsync; + return r; +} +// Caller keeps Combo-clock -> display-owner serialization and IRQ producers +// parked for the entire operation. io.ready() verifies its real leases and +// object identity, including DDI IO power; this does not acquire any lease. +// Retain the explicit external clock owner through off/on, including failure. +// Do not clear shared power-well requests or release allocator state here. +template struct Access { + IO &io;XELPComboClock::ClockAllocation &allocation;uintptr_t owner;State &s; + bool raw(uint32_t reg,uint32_t &v) { + s.reg=reg; + if(!io.readable(reg))return s.fail(Error::Readback); + v=io.read(reg);s.value=v;return v!=UINT32_MAX || s.fail(Error::Readback); + } + bool bits(uint32_t reg,uint32_t mask,uint32_t expected) { + uint32_t v=0;return raw(reg,v) && (v&mask)==expected; + } + bool held() { + if(s.error!=Error::None)return false; + if(!io.ready() || !owner || s.pll>=2 || allocation.external[0]!=owner || + allocation.binding[0]!=s.pll+1 || !bits(0x45404,0x403,0x403))return s.fail(Error::Ownership); + // Pixel/DSB consumers must remain stopped while clocks/PHY change. + if(!bits(0x7B008,0xC0000000U,0) || !bits(0x70008,0xC0000000U,0) || + !bits(0x60400,1U<<31,0) || !bits(0x70080,63,0) || + !bits(0x7B808,0xC0000000U,0) || !bits(0x6BC00,1U<<31,0))return s.fail(Error::Consumer); + if(!bits(0x78000,0xF0300000U,0) || !bits(0x78004,0x80008000U,0) || + !bits(0x68180,1U<<31,0) || !bits(0x68280,1U<<31,0))return s.fail(Error::Consumer); + for(unsigned n=0;n<7;++n)if(!bits(0x70180+0x100*n,1U<<31,0))return s.fail(Error::Consumer); + for(unsigned n=0;n<3;++n)if(!bits(0x70B08+0x100*n,0x80000001U,0))return s.fail(Error::Consumer); + XELPComboClock::ClockSnapshot snapshot;snapshot.layout=XELPComboClock::Layout::Display13; + if(!raw(0x164280,snapshot.mux[0]))return false; + if((snapshot.mux[0]&3)!=s.pll || (snapshot.peers(XELPComboClock::Port(0))&(1U<=budget)return s.fail(Error::Timeout); + io.delayUs(step);s.waitUs+=step; + } + } + bool set(uint32_t reg,uint32_t value,uint32_t mask=UINT32_MAX) { + return write(reg,value) && wait(reg,mask,value&mask,0); + } + bool rmw(uint32_t reg,uint32_t clear,uint32_t value) { + uint32_t v=0;return read(reg,v) && set(reg,(v&~clear)|value,clear|value); + } + bool group(uint32_t lane0,uint32_t broadcast,uint32_t clear,uint32_t value) { + uint32_t v=0;if(!read(lane0,v) || !write(broadcast,(v&~clear)|value))return false; + // Broadcast registers are write aliases; inspect the four real lanes. + for(unsigned lane=0;lane<4;++lane)if(!wait(lane0+0x100*lane,clear|value,value,0))return false; + return true; + } +}; +// Invoke only after plane/cursor/transcoder stop and DISPLAY_OFF/drain. +// Hardware readbacks recheck those preconditions; a caller assertion alone +// must not permit clock writes. This stop deliberately retains the owner. +template bool stop(IO &io,const Readout &source,const Profile &profile, + XELPComboClock::ClockAllocation &allocation,uintptr_t owner,State &s) { + if(s.attempted)return false;s.attempted=true; + if(!source.matches(profile) || source.identity.pipe!=0)return s.fail(Error::Input); + s.config=plan(profile,source.referenceKHz);s.pll=source.identity.pll; + if(!s.config.valid)return s.fail(Error::Input); + Access a{io,allocation,owner,s};XELPComboClock::PLL pll; + if(!XELPComboClock::PLL::resolve(s.pll,pll) || !a.held())return false; + if(!a.wait(pll.enable,0xCC000000U,0xCC000000U,0) || + !a.wait(pll.cfg0,UINT32_MAX,source.cfg0,0) || !a.wait(pll.cfg1,UINT32_MAX,source.cfg1,0) || + !a.wait(0x6B400,UINT32_MAX,source.function,0) || + !a.wait(0x6B0D0,0x1FFF,0x1040,0) || !a.wait(0x6B0D8,0x30001,0,0))return false; + s.stage=Stage::ULPS; + if(!a.rmw(0x6B0D8,1U<<8,1) || !a.wait(0x6B0D8,1U<<16,1U<<16,10))return false; + s.stage=Stage::FunctionOff;if(!a.rmw(0x6B400,1U<<31,0))return false; + s.stage=Stage::BufferOff; + if(!a.rmw(0x164280,1U<<10,0) || !a.rmw(0x64000,1U<<31,0) || + !a.wait(0x64000,0x80000080U,0x80,8) || !a.rmw(0x164280,0,1U<<10))return false; + s.stage=Stage::ModeOff;if(!a.rmw(0x6B094,1,0))return false; + s.stage=Stage::PLLDisable; + if(!a.rmw(pll.enable,1U<<31,0) || !a.wait(pll.enable,0xC0000000U,0,1000,10))return false; + s.stage=Stage::PLLPowerOff; + if(!a.rmw(pll.enable,1U<<27,0) || !a.wait(pll.enable,0xCC000000U,0,1000,10))return false; + s.stage=Stage::Stopped;s.stopped=true;return true; +} +// Invoke after panel POWER_ON/PPS/DEASSERT_RESET, before INIT_OTP. Timing and +// transport derive from the profile; no firmware PLL/PHY register dump replay. +// Leaves pixel transfer off, ready for command sequence initialization. The +// caller must enable video/plane, validate and publish the new source identity. +template bool start(IO &io,XELPComboClock::ClockAllocation &allocation,uintptr_t owner,State &s) { + if(!s.stopped || s.started || s.stage!=Stage::Stopped || s.error!=Error::None || !s.config.valid)return false; + Access a{io,allocation,owner,s};XELPComboClock::PLL pll; + if(!XELPComboClock::PLL::resolve(s.pll,pll) || !a.held())return false; + if(!a.wait(pll.enable,0xCC000000U,0,0) || !a.wait(0x6B400,1U<<31,0,0) || + !a.wait(0x64000,0x80000080U,0x80,0) || !a.wait(0x164280,0x403,0x400|s.pll,0))return false; + const auto &p=s.config; + s.stage=Stage::ModeOn;if(!a.rmw(0x6B094,0,1))return false; + s.stage=Stage::Escape; + if(!a.set(0x6B090,p.link.escapeDivider,0x1FF) || !a.set(0x162190,p.link.escapeDivider,0x1FF) || + // Linux ADL_MIPIO_DW8 writes zero (unshifted divider & shifted mask), + // matching the saved Linux state. Do not enable an unverified override. + !a.set(0x1621A0,0))return false; + s.stage=Stage::PLLPowerOn; + if(!a.rmw(pll.enable,0,1U<<27) || !a.wait(pll.enable,1U<<26,1U<<26,1000,10))return false; + s.stage=Stage::PLLConfig;if(!a.set(pll.cfg0,p.pll.cfg0()) || !a.set(pll.cfg1,p.pll.cfg1()))return false; + s.stage=Stage::PLLEnable; + if(!a.rmw(pll.enable,0,1U<<31) || !a.wait(pll.enable,0xCC000000U,0xCC000000U,1000,10))return false; + s.stage=Stage::Map;if(!a.rmw(0x164280,0x403,s.pll))return false; + s.stage=Stage::PHY; + if(!a.rmw(0x162028,0xF0,0) || !a.rmw(0x162390,1U<<31,0))return false; + for(unsigned lane=0;lane<4;++lane)if(!a.rmw(0x162890+0x100*lane,1U<<31,lane==2?0:1U<<31))return false; + if(!a.rmw(0x162388,0x700,0x500) || !a.group(0x162888,0x162688,0x700,0x500) || + !a.rmw(0x162304,12,0) || !a.group(0x162804,0x162604,12,4) || + !a.group(0x162804,0x162604,1U<<26,0) || !a.rmw(0x162304,1U<<26,0) || + !a.rmw(0x162014,0,3) || + !a.group(0x162894,0x162694,1U<<31,0) || !a.rmw(0x162394,1U<<31,0) || + !a.group(0x162894,0x162694,0x1C0038,0x60080030) || !a.rmw(0x162394,0x1C0038,0x60080030) || + !a.group(0x162888,0x162688,0xB8FF,0x1098) || !a.rmw(0x162388,0xB8FF,0x1098) || + !a.rmw(0x162390,0x3FFFF,0x3F))return false; + for(unsigned lane=0;lane<4;++lane)if(!a.rmw(0x162890+0x100*lane,0x3FFFF,0x3F))return false; + if(!a.group(0x162894,0x162694,0,1U<<31) || !a.rmw(0x162394,0,1U<<31) || + !a.set(0x162180,p.link.clockTiming) || !a.set(0x162184,p.link.dataTiming))return false; + s.stage=Stage::BufferOn; + if(!a.rmw(0x64000,0,1U<<31) || !a.wait(0x64000,0x80000080U,0x80000000U,500) || + !a.rmw(0x164280,0,1U<<10))return false; + s.stage=Stage::Timing; + if(!a.rmw(0x6B088,65535,p.init) || !a.set(0x6B080,p.link.clockTiming) || !a.set(0x6B084,p.link.dataTiming) || + !a.set(0x6B000,p.link.htotal) || !a.set(0x6B008,p.link.hsync) || + !a.set(0x6B00C,p.link.vtotal) || !a.set(0x6B014,p.link.vsync) || + !a.set(0x6B010,p.link.vtotal) || !a.set(0x6B028,0))return false; + // VSYNCSHIFT is relevant to interlace; this supported profile is progressive. + // Linux's unconditional interlace calculation is explicitly marked FIXME. + s.stage=Stage::Protocol; + // Timeout status bits are W1C; write only the new timeout field. + if(!a.set(0x6B044,p.hsTimeout<<16,0xFFFF0000U) || !a.set(0x6B048,p.lpTimeout,65535) || + !a.set(0x6B04C,p.taTimeout,65535) || !a.set(0x6B034,0) || !a.set(0x6B404,0) || + !a.rmw(0x6B0C0,0xF000,0x4000) || !a.set(0x6B030,p.transport,~(1U<<20)) || + !a.set(0x6B400,p.function))return false; + s.stage=Stage::Link; + if(!a.wait(0x6B030,1U<<20,1U<<20,2500,10) || !a.wait(0x6B0D8,0x30001,0,2500,10) || + !a.wait(0x6B0D0,0x1FFF,0x1040,100,10))return false; + s.stage=Stage::Ready;s.started=true;return true; +} +}}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a936c4e1..bb6d39a8 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3873,7 +3873,8 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { board.dsi=1; dsiPanelOnly=checkKernelArgument("-wex-dsi-panel-only"); dsiCommandTest=checkKernelArgument("-wex-dsi-command-test"); - dsiPanelTest=checkKernelArgument("-wex-dsi-panel-test"); + dsiHostTest=checkKernelArgument("-wex-dsi-host-test"); + dsiPanelTest=checkKernelArgument("-wex-dsi-panel-test") || dsiHostTest; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelOnly",dsiPanelOnly); if(!board.described())return {}; } @@ -12129,8 +12130,9 @@ bool XE::probeDSICommands(IOService *controller) { return complete; } -// Bootstrap-only panel sequence experiment. Keep the host/PHY/PLL alive so -// GPIO and long-packet failures are distinguishable from cold host startup. +// Bootstrap-only panel sequence experiment. By default retain host/PHY/PLL; +// the optional host trial rebuilds them before panel INIT_OTP. Neither path +// releases shared resources or enables runtime power transitions. bool XE::probeDSIPanel(IOService *controller) { auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!controller || !c.lock || !clock.lock || !irq.lock)return false; @@ -12147,13 +12149,16 @@ bool XE::probeDSIPanel(IOService *controller) { }; struct IO : Display13FBBootIO { decltype(owned) &owns;IOSimpleLock *irqLock; - uint32_t writes {},gpios {};XELP::DSI::Panel::GPIOResult lastGPIO; - IO(IOMemoryMap *m,decltype(owned) &q,IOSimpleLock *l):Display13FBBootIO{m},owns(q),irqLock(l) {} + bool restart {};unsigned pll {};uint32_t writes {},gpios {};XELP::DSI::Panel::GPIOResult lastGPIO; + IO(IOMemoryMap *m,decltype(owned) &q,IOSimpleLock *l,bool cold,unsigned id):Display13FBBootIO{m},owns(q),irqLock(l),restart(cold),pll(id) {} bool ready() { if(!owns() || !readable(0x45404))return false; const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; } void write(uint32_t reg,uint32_t value) { + if(restart && XELP::DSI::Restart::writable(reg,pll)) { + *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;return; + } switch(reg) { case 0x6B100:case 0x6B104:case 0xC4030:case 0xC7204: case 0x70180:case 0x7019C:case 0x7B008: @@ -12170,7 +12175,18 @@ bool XE::probeDSIPanel(IOService *controller) { return lastGPIO.complete; } void delayUs(unsigned us){IODelay(us);} - } io{c.map,owned,irq.lock}; + } io{c.map,owned,irq.lock,dsiHostTest,dsiBoot.source.identity.pll}; + struct Lifecycle { + bool enabled;XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state; + bool stop(IO &io,const XELP::DSI::Readout &source,const XELP::DSI::Profile &profile) { + return !enabled || XELP::DSI::Restart::stop(io,source,profile,allocation,owner,state); + } + bool start(IO &io,XELP::DSI::Readout &source) { + if(!enabled)return true; + if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; + source=XELP::DSI::Restart::expected(source,state);return source.complete(); + } + } lifecycle{dsiHostTest,clock.allocation,reinterpret_cast(controller),dsiHostRestart}; uint64_t record[32] {1}; record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && !dsiCommandHost.faulted && !dsiCommandHost.pending && @@ -12178,14 +12194,19 @@ bool XE::probeDSIPanel(IOService *controller) { if(record[1]) { record[20]=io.read(0xC7204);record[22]=io.read(0xC4030); record[24]=io.read(0xC7200);record[28]=io.read(0x70040); - XELP::DSI::Panel::cycle(io,dsiBoot.source,dsiProfile,dsiBoot.control,dsiBoot.surface,dsiPanelTrial); + XELP::DSI::Panel::cycle(io,dsiBoot.source,dsiProfile,dsiBoot.control,dsiBoot.surface,dsiPanelTrial,lifecycle); record[21]=io.ready()?io.read(0xC7204):UINT32_MAX; record[23]=io.ready()?io.read(0xC4030):UINT32_MAX; record[25]=io.ready()?io.read(0xC7200):UINT32_MAX; record[26]=io.ready()?io.read(0x6B0D0):UINT32_MAX; record[27]=io.ready()?io.read(0x6B0D8):UINT32_MAX; record[29]=io.ready()?io.read(0x70040):UINT32_MAX; - record[30]=io.ready() && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + // Publish the new clock/transport witness only after the complete + // cycle and old backing/geometry have been revalidated together. + auto nextBoot=dsiBoot; + if(dsiPanelTrial.complete())nextBoot.source=dsiPanelTrial.source; + record[30]=io.ready() && dsiPanelTrial.complete() && XELP::DSI::bootUnchanged(io,nextBoot,xelpMemory); + if(record[30]){dsiBoot=nextBoot;c.bootDSI=nextBoot.source;} } const auto &r=dsiPanelTrial;const auto &h=r.host; record[2]=static_cast(r.stage);record[3]=static_cast(r.error); @@ -12195,8 +12216,14 @@ bool XE::probeDSIPanel(IOService *controller) { record[14]=h.waitedUs;record[15]=h.completedOperations;record[16]=io.gpios; record[17]=io.lastGPIO.role;record[18]=io.lastGPIO.value;record[19]=io.lastGPIO.complete; const bool complete=record[1] && r.complete() && record[30];record[31]=complete; + const auto &host=dsiHostRestart;const auto &p=host.config; + uint64_t hostRecord[20] {1,dsiHostTest,static_cast(host.stage),static_cast(host.error), + host.reg,host.value,host.writes,host.waitUs,host.stopped,host.started,host.pll, + p.pll.cfg0(),p.pll.cfg1(),p.link.clockTiming,p.link.dataTiming,p.hsTimeout,p.lpTimeout,p.taTimeout, + record[30],complete}; IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); + if(dsiHostTest)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); return complete; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 81303c93..c2eb039c 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -16,6 +16,7 @@ #include "kern_dsi_clock.hpp" #include "kern_dsi_commands.hpp" #include "kern_dsi_panel.hpp" +#include "kern_dsi_restart.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" @@ -250,6 +251,8 @@ class XE { bool dsiPanelOnly {}; bool dsiCommandTest {}; bool dsiPanelTest {}; + bool dsiHostTest {}; + XELP::DSI::Restart::State dsiHostRestart; XELP::DSI::Panel::Result dsiPanelTrial; bool probeDSIPanel(IOService *); XELP::DSI::Host dsiCommandHost; diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 9bb7af12..f0dd52d8 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -21,7 +21,7 @@ XELP::ProbeConfig seen;unsigned calls=0; bool configure(void *c,const XELP::ProbeConfig *p){assert(c==(void*)1);seen=*p;++calls;return p->valid();} struct XE { - bool dsiPanelOnly=false,dsiCommandTest=false,dsiPanelTest=false; + bool dsiPanelOnly=false,dsiCommandTest=false,dsiPanelTest=false,dsiHostTest=false; XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; diff --git a/tests/dsi_profile.cpp b/tests/dsi_profile.cpp index 0f2e482e..06b63ee0 100644 --- a/tests/dsi_profile.cpp +++ b/tests/dsi_profile.cpp @@ -22,7 +22,7 @@ Bytes fixture() { add(b,52,config); Bytes dtd(30);put(dtd,0,28);put(dtd,2,135255,4); const unsigned fields[]={1080,46,22,2,1920,82,40,2,86,154}; - for(unsigned i=0;i<10;++i)put(dtd,6+2*i,fields[i]);add(b,58,dtd); + for(unsigned i=0;i<10;++i)put(dtd,6+2*i,fields[i]);dtd[26]=0xC0;add(b,58,dtd); Bytes bl(273);bl[0]=6;bl[1]=2;put(bl,2,40000);bl[113]=2;put(bl,129,255);put(bl,193,6);bl[257]=8;add(b,43,bl); Bytes seq(10);seq[0]=3;seq[5]=0; // Empty, terminated sequences prove structure without firmware commands. @@ -38,6 +38,7 @@ int main(int argc,char **argv) { auto b=fixture();auto r=parse(b); if(!r.valid())printf("status %u\n",unsigned(r.status)); assert(r.valid()&&r.mode.htotal()==1126&&r.mode.vtotal()==2002&&r.pwmHz==40000); + assert(r.mode.hsyncPositive && r.mode.vsyncPositive); assert(r.clockPrepare==50&&r.clockPrepareZero==310&&r.hsPrepareZero==200&&r.ppsUs[4]==500000); const auto link=linkPlan(r);assert(link.valid&&link.afeKHz==811530&&link.portKHz==162306); assert(link.escapeDivider==56&&link.phyEscapeDivider==3); diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py new file mode 100644 index 00000000..3de8b999 --- /dev/null +++ b/tests/dsi_restart.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Stopped-host restart with register side effects and optional saved VBT input.""" +from pathlib import Path +import subprocess, tempfile, sys +root=Path(__file__).resolve().parents[1] +boot=(root/'tests/dsi_boot.cpp').read_text() +io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] +profile=(root/'tests/dsi_profile.cpp').read_text() +fixture=profile[profile.index('using Bytes='):profile.index('int main(')] +code=r''' +#include "kern_dsi_restart.hpp" +#include "kern_dsi_panel.hpp" +#include "kern_xelp_memory_layout.hpp" +#include +#include +#include +#include +#include +using namespace XELP::DSI; +IO_FIXTURE +PROFILE_FIXTURE +struct Sim:IO { + bool owned=true,dropPLLDisable=false,dropPLLPower=false,dropPLLLock=false,dropBuffer=false,dropLink=false; + uint32_t drop=UINT32_MAX,elapsed=0; + std::vector gpioTrace; + bool gpio(unsigned role,unsigned value){const auto r=Panel::gpio(*this,role,value);if(r.complete)gpioTrace.push_back(role*2+value);return r.complete;} + bool ready(){return owned;} + void write(uint32_t reg,uint32_t value){ + assert(owned);writes.push_back(reg);if(reg==drop)return; + if(reg==0x7B008){regs[reg]=(value&~(1U<<30))|(regs[reg]&(1U<<30));return;} + if(reg==0x46014){regs[reg]=(value&~0x44000000U)|(regs[reg]&0x44000000U);return;} + if(reg==0x64000){regs[reg]=(value&~0x80U)|(regs[reg]&0x80);return;} + if(reg==0x6B0D8){regs[reg]=(value&~0x30000U)|(regs[reg]&0x30000);return;} + if(reg==0x6B400 && !(value&(1U<<31))){regs[0x6B0D8]=0;regs[0x6B030]&=~(1U<<20);} + if(reg==0x6B030){regs[reg]=(value&~(1U<<20))|(regs[reg]&(1U<<20));return;} + if(reg==0x162604 || reg==0x162688 || reg==0x162694){ + for(unsigned lane=0;lane<4;++lane)regs[reg+0x200+0x100*lane]=value; + return; + } + regs[reg]=value; + } + void delayUs(unsigned us){ + elapsed+=us; + auto &conf=regs[0x7B008];if(conf&(1U<<31))conf|=1U<<30;else conf&=~(1U<<30); + if(us>=1000 && (conf&0xC0000000U)==0xC0000000U){++regs[0x70040];regs[0x701AC]=regs[0x7019C];} + auto &pll=regs[0x46014]; + if(pll&(1U<<27)){if(!dropPLLPower)pll|=1U<<26;}else if(!dropPLLPower)pll&=~(1U<<26); + if(pll&(1U<<31)){if(!dropPLLLock)pll|=1U<<30;}else if(!dropPLLDisable)pll&=~(1U<<30); + if(!dropBuffer){if(regs[0x64000]&(1U<<31))regs[0x64000]&=~0x80U;else regs[0x64000]|=0x80;} + if(regs[0x6B0D8]&1)regs[0x6B0D8]|=1U<<16; + if(!dropLink && (regs[0x6B400]&(1U<<31)))regs[0x6B030]|=1U<<20; + } +}; +struct Fixture { + Sim io;Bytes vbt;Profile p;Readout source;Restart::State state; + XELPComboClock::ClockAllocation allocation; + Fixture(){vbt=fixture();p=parse(vbt);source=decodeReadout([&](uint32_t r,uint32_t &v){v=io.read(r);return true;},38400); + assert(source.matches(p));allocation.external[0]=1;allocation.binding[0]=2; + io.regs[0x7B008]=0;io.regs[0x70180]&=~(1U<<31);io.regs[0x6B0D0]=0x1040; + io.regs[0x164280]|=1U<<10;io.regs[0x71180]=0x94000000;io.regs[0x64100]=0x80000000; + } + bool stop(){return Restart::stop(io,source,p,allocation,1,state);} + bool start(){return Restart::start(io,allocation,1,state);} +}; +struct Lifecycle { + XELPComboClock::ClockAllocation &allocation;Restart::State &state;bool breakStart=false; + bool stop(Sim &io,const Readout &source,const Profile &p){return Restart::stop(io,source,p,allocation,1,state);} + bool start(Sim &io,Readout &source){ + if(breakStart)io.dropPLLPower=true; + if(!Restart::start(io,allocation,1,state))return false; + source=Restart::expected(source,state);return source.complete(); + } +}; +void panelCycle(Fixture &f,bool fail=false){ + f.io.regs[0x7B008]=0xC0000000;f.io.regs[0x70180]=0x94000008; + f.io.regs[0x42084]=1U<<30;f.io.regs[0x6001C]=0x0437077F;f.io.regs[0x6B010]=f.io.regs[0x6B00C]; + f.io.regs[0xC7204]=15;f.io.regs[0xC4030]=0x84; + f.source.mux=f.io.regs[0x164280]; + Panel::Result panel;Lifecycle lifecycle{f.allocation,f.state,fail}; + Panel::cycle(f.io,f.source,f.p,0x94000008,0,panel,lifecycle); + if(fail){assert(!panel.complete() && panel.stage==Panel::Stage::HostOn && panel.error==Panel::Error::Host); + assert(!(f.io.regs[0x70180]&(1U<<31)) && !f.io.regs[0x7B008]);return;} + if(!panel.complete())fprintf(stderr,"panel stage=%u error=%u reg=%x value=%x host=%u seq=%u restartStage=%u error=%u reg=%x value=%x\n",unsigned(panel.stage),unsigned(panel.error),panel.reg,panel.value,unsigned(panel.host.error),panel.host.sequenceId,unsigned(f.state.stage),unsigned(f.state.error),f.state.reg,f.state.value); + assert(panel.complete() && panel.source.matches(f.p) && panel.source.cfg0!=f.source.cfg0); + assert(Surface::source(f.io,panel.source) && !Surface::source(f.io,f.source)); + assert(panel.host.completedOperations==f.p.sequences.packets+f.p.sequences.delays+f.p.sequences.gpios); + assert(f.io.regs[0x70180]==0x94000008 && f.io.regs[0x7B008]==0xC0000000); + assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); +} +void successful(Fixture &f){ + assert(f.stop() && f.state.stopped && !f.state.started); + assert(f.io.regs[0x46014]==0 && f.io.regs[0x6B400]==0x30006 && f.io.regs[0x64000]==0x80 && f.io.regs[0x6B094]==0); + const auto oldCfg=f.source.cfg0;f.io.regs[0x16428C]=0x12345; // prove source PLL dump is not replayed + assert(f.start() && f.state.stage==Restart::Stage::Ready); + assert(f.io.regs[0x16428C]==f.state.config.pll.cfg0() && f.io.regs[0x16428C]!=oldCfg); + assert(f.io.regs[0x46014]==0xCC000000 && f.io.regs[0x64000]==0x80000000 && f.io.regs[0x6B094]==1); + assert(f.io.regs[0x6B030]==0x20130304 && f.io.regs[0x6B400]==f.state.config.function); + assert(f.io.regs[0x6B090]==56 && f.io.regs[0x162190]==56 && f.io.regs[0x1621A0]==0); + assert(f.io.regs[0x162180]==0xC8690000 && f.io.regs[0x162184]==0x84830083); + assert(f.io.regs[0x162894]==0xE0080030 && f.io.regs[0x162888]==0x1598); + for(unsigned lane=0;lane<4;++lane){assert(f.io.regs[0x162804+0x100*lane]==4); + assert(f.io.regs[0x162890+0x100*lane]==((lane==2?0:1U<<31)|0x3F));} + assert(f.io.regs[0x70180]==0x14000008 && !f.io.regs[0x7B008]); // video remains off for INIT_OTP + assert(f.io.regs[0x71180]==0x94000000 && f.io.regs[0x64100]==0x80000000); + assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); + for(auto reg:f.io.writes){if(!Restart::writable(reg,1))fprintf(stderr,"unlisted write %x\n",reg);assert(Restart::writable(reg,1) && reg!=0x46010 && reg!=0x164284);} + auto count=f.io.writes.size();assert(!f.start() && !f.stop() && f.io.writes.size()==count); + // Once the separate panel/plane adapter starts video, the decoder must + // accept the new source with profile-derived PLL/timing/polarity. + f.io.regs[0x7B008]=0xC0000000; + auto rebuilt=decodeReadout([&](uint32_t r,uint32_t &v){v=f.io.read(r);return true;},38400); + assert(rebuilt.matches(f.p) && rebuilt.afeKHz==811529); +} +int main(int argc,char **argv){ + {Fixture f;successful(f);} + {Fixture f;panelCycle(f);} + {Fixture f;panelCycle(f,true);} + for(unsigned fault=0;fault<6;++fault){Fixture f; + if(fault==0)f.io.owned=false; + if(fault==1)f.allocation.binding[1]=2; + if(fault==2)f.io.regs[0x7B008]=0xC0000000; + if(fault==3)f.io.regs[0x6B0D0]=0; + if(fault==4)f.io.regs[0x68180]=1U<<31; + if(fault==5)f.io.regs[0x78000]=1U<<31; + assert(!f.stop() && f.io.writes.empty() && !f.start()); + } + for(unsigned fault=0;fault<4;++fault){Fixture f; + if(fault==0)f.io.dropPLLDisable=true; + if(fault==1)f.io.dropPLLPower=true; + if(fault==2)f.io.dropBuffer=true; + if(fault==3)f.io.drop=0x6B400; + assert(!f.stop() && f.state.error!=Restart::Error::None && !f.start()); + const auto count=f.io.writes.size();assert(!f.stop() && f.io.writes.size()==count); + assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2 && f.io.elapsed<=1020); + } + for(unsigned fault=0;fault<6;++fault){Fixture f;assert(f.stop()); + if(fault==0)f.io.dropPLLPower=true; + if(fault==1)f.io.dropPLLLock=true; + if(fault==2)f.io.dropBuffer=true; + if(fault==3)f.io.dropLink=true; + if(fault==4)f.io.drop=0x162694; + if(fault==5)f.allocation.binding[1]=2; + assert(!f.start() && f.state.error!=Restart::Error::None); + const auto count=f.io.writes.size();assert(!f.start() && f.io.writes.size()==count); + assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2 && !f.io.regs[0x7B008]); + assert(f.io.elapsed<5000); + } + if(argc==2){ + for(bool fail:{false,true}){Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in); + f.vbt=Bytes{std::istreambuf_iterator(in),{}};f.p=parseProfile({f.vbt.data(),f.vbt.size()},true); + panelCycle(f,fail); + assert(f.io.gpioTrace==std::vector(fail?std::vector{4,0,2,3,1,0,1}:std::vector{4,0,2,3,1,0,1,5})); + } + Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in);f.vbt=Bytes{std::istreambuf_iterator(in),{}}; + f.p=parseProfile({f.vbt.data(),f.vbt.size()},true);assert(f.p.valid());successful(f); + printf("saved VBT restart plan: init=%u timeout=%u/%u/%u function=%08x cfg0=%08x writes=%u pollUs=%u; simulation only\n", + f.state.config.init,f.state.config.hsTimeout,f.state.config.lpTimeout,f.state.config.taTimeout, + f.state.config.function,f.state.config.pll.cfg0(),f.state.writes,f.state.waitUs); + } + puts("PASS DSI host restart: exclusive ownership, off/on sequence, profile reconstruction, peer preservation, retained resources on bounded failure"); +} +'''.replace('IO_FIXTURE',io).replace('PROFILE_FIXTURE',fixture) +with tempfile.TemporaryDirectory(prefix='dsi-restart-') as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test'),*sys.argv[1:]],check=True) diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index ebe5afe1..f4ac5e7b 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -4,6 +4,7 @@ "-wex-dsi-panel-only": [], "-wex-dsi-command-test": [], "-wex-dsi-panel-test": [], + "-wex-dsi-host-test": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" diff --git a/tools/test.py b/tools/test.py index 3b9ea700..f5586c82 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,7 +9,7 @@ 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', '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 += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] -python_tests += ['dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] +python_tests += ['dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From 408aa671cc79c71ff9dc19649d683a4271e23bc5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:53:17 +0900 Subject: [PATCH 070/139] Observe ULPS completion independently of its self-clearing request --- WhateverXe/kern_dsi_restart.hpp | 7 ++++++- tests/dsi_restart.py | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp index 5ffd6575..c41c5957 100644 --- a/WhateverXe/kern_dsi_restart.hpp +++ b/WhateverXe/kern_dsi_restart.hpp @@ -137,7 +137,12 @@ template bool stop(IO &io,const Readout &source,const Profile &profile !a.wait(0x6B400,UINT32_MAX,source.function,0) || !a.wait(0x6B0D0,0x1FFF,0x1040,0) || !a.wait(0x6B0D8,0x30001,0,0))return false; s.stage=Stage::ULPS; - if(!a.rmw(0x6B0D8,1U<<8,1) || !a.wait(0x6B0D8,1U<<16,1U<<16,10))return false; + // LINK_ENTER_ULPS is a self-clearing request on the live Display13 host. + // Completion is LINK_IN_ULPS, not an echoed request bit. Do not reject a + // transition which already completed before the first posting read. + uint32_t lp=0; + if(!a.read(0x6B0D8,lp) || !a.write(0x6B0D8,(lp&~(1U<<8))|1U) || + !a.wait(0x6B0D8,1U<<16,1U<<16,10))return false; s.stage=Stage::FunctionOff;if(!a.rmw(0x6B400,1U<<31,0))return false; s.stage=Stage::BufferOff; if(!a.rmw(0x164280,1U<<10,0) || !a.rmw(0x64000,1U<<31,0) || diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index 3de8b999..07009e96 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -21,6 +21,7 @@ PROFILE_FIXTURE struct Sim:IO { bool owned=true,dropPLLDisable=false,dropPLLPower=false,dropPLLLock=false,dropBuffer=false,dropLink=false; + bool ulpsRequest=false; uint32_t drop=UINT32_MAX,elapsed=0; std::vector gpioTrace; bool gpio(unsigned role,unsigned value){const auto r=Panel::gpio(*this,role,value);if(r.complete)gpioTrace.push_back(role*2+value);return r.complete;} @@ -30,8 +31,8 @@ if(reg==0x7B008){regs[reg]=(value&~(1U<<30))|(regs[reg]&(1U<<30));return;} if(reg==0x46014){regs[reg]=(value&~0x44000000U)|(regs[reg]&0x44000000U);return;} if(reg==0x64000){regs[reg]=(value&~0x80U)|(regs[reg]&0x80);return;} - if(reg==0x6B0D8){regs[reg]=(value&~0x30000U)|(regs[reg]&0x30000);return;} - if(reg==0x6B400 && !(value&(1U<<31))){regs[0x6B0D8]=0;regs[0x6B030]&=~(1U<<20);} + if(reg==0x6B0D8){ulpsRequest=value&1;regs[reg]=(value&~0x30001U)|(regs[reg]&0x30000);return;} + if(reg==0x6B400 && !(value&(1U<<31))){ulpsRequest=false;regs[0x6B0D8]=0;regs[0x6B030]&=~(1U<<20);} if(reg==0x6B030){regs[reg]=(value&~(1U<<20))|(regs[reg]&(1U<<20));return;} if(reg==0x162604 || reg==0x162688 || reg==0x162694){ for(unsigned lane=0;lane<4;++lane)regs[reg+0x200+0x100*lane]=value; @@ -47,7 +48,7 @@ if(pll&(1U<<27)){if(!dropPLLPower)pll|=1U<<26;}else if(!dropPLLPower)pll&=~(1U<<26); if(pll&(1U<<31)){if(!dropPLLLock)pll|=1U<<30;}else if(!dropPLLDisable)pll&=~(1U<<30); if(!dropBuffer){if(regs[0x64000]&(1U<<31))regs[0x64000]&=~0x80U;else regs[0x64000]|=0x80;} - if(regs[0x6B0D8]&1)regs[0x6B0D8]|=1U<<16; + if(ulpsRequest)regs[0x6B0D8]|=1U<<16; if(!dropLink && (regs[0x6B400]&(1U<<31)))regs[0x6B030]|=1U<<20; } }; From 38ea5bf620ec70c658515f86d55c520572aaee75 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:55:42 +0900 Subject: [PATCH 071/139] Check only the defined PHY escape divider field --- WhateverXe/kern_dsi_restart.hpp | 2 +- tests/dsi_restart.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp index c41c5957..0903581c 100644 --- a/WhateverXe/kern_dsi_restart.hpp +++ b/WhateverXe/kern_dsi_restart.hpp @@ -170,7 +170,7 @@ template bool start(IO &io,XELPComboClock::ClockAllocation &allocation if(!a.set(0x6B090,p.link.escapeDivider,0x1FF) || !a.set(0x162190,p.link.escapeDivider,0x1FF) || // Linux ADL_MIPIO_DW8 writes zero (unshifted divider & shifted mask), // matching the saved Linux state. Do not enable an unverified override. - !a.set(0x1621A0,0))return false; + !a.set(0x1621A0,0,0xFF0000))return false; s.stage=Stage::PLLPowerOn; if(!a.rmw(pll.enable,0,1U<<27) || !a.wait(pll.enable,1U<<26,1U<<26,1000,10))return false; s.stage=Stage::PLLConfig;if(!a.set(pll.cfg0,p.pll.cfg0()) || !a.set(pll.cfg1,p.pll.cfg1()))return false; diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index 07009e96..a1517c19 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -29,6 +29,7 @@ void write(uint32_t reg,uint32_t value){ assert(owned);writes.push_back(reg);if(reg==drop)return; if(reg==0x7B008){regs[reg]=(value&~(1U<<30))|(regs[reg]&(1U<<30));return;} + if(reg==0x1621A0){regs[reg]=value|0x400;return;} // non-field status survives a zero write if(reg==0x46014){regs[reg]=(value&~0x44000000U)|(regs[reg]&0x44000000U);return;} if(reg==0x64000){regs[reg]=(value&~0x80U)|(regs[reg]&0x80);return;} if(reg==0x6B0D8){ulpsRequest=value&1;regs[reg]=(value&~0x30001U)|(regs[reg]&0x30000);return;} @@ -96,7 +97,7 @@ assert(f.io.regs[0x16428C]==f.state.config.pll.cfg0() && f.io.regs[0x16428C]!=oldCfg); assert(f.io.regs[0x46014]==0xCC000000 && f.io.regs[0x64000]==0x80000000 && f.io.regs[0x6B094]==1); assert(f.io.regs[0x6B030]==0x20130304 && f.io.regs[0x6B400]==f.state.config.function); - assert(f.io.regs[0x6B090]==56 && f.io.regs[0x162190]==56 && f.io.regs[0x1621A0]==0); + assert(f.io.regs[0x6B090]==56 && f.io.regs[0x162190]==56 && (f.io.regs[0x1621A0]&0xFF0000)==0 && (f.io.regs[0x1621A0]&0x400)); assert(f.io.regs[0x162180]==0xC8690000 && f.io.regs[0x162184]==0x84830083); assert(f.io.regs[0x162894]==0xE0080030 && f.io.regs[0x162888]==0x1598); for(unsigned lane=0;lane<4;++lane){assert(f.io.regs[0x162804+0x100*lane]==4); From b906ce0d2b3aaeb41d3e080016af675cbd03a1ad Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:12:36 +0900 Subject: [PATCH 072/139] Connect one-way DSI panel shutdown to native power callback --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_clock.hpp | 7 +++-- WhateverXe/kern_dsi_panel.hpp | 31 +++++++++++++++++++ WhateverXe/kern_dsi_quiesce.hpp | 26 +++++++++++----- WhateverXe/kern_xe.cpp | 41 +++++++++++++++++++++---- WhateverXe/kern_xe.hpp | 2 ++ tests/dsi_clock.py | 6 ++-- tests/dsi_panel.py | 23 ++++++++++++++ tests/dsi_quiesce.py | 4 +-- tests/dsi_update_dispatch.py | 18 +++++++++++ tests/fixtures/boot_argument_names.json | 1 + 12 files changed, 140 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8d337c04..df4e280a 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 31216967..3d007348 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_clock.hpp b/WhateverXe/kern_dsi_clock.hpp index 3f4b4512..991e4991 100644 --- a/WhateverXe/kern_dsi_clock.hpp +++ b/WhateverXe/kern_dsi_clock.hpp @@ -32,9 +32,10 @@ template Result retire(IO &io,const Readout &link,const Quiesce::Resul }; const auto stopped=[&]() { Display13Transcoder::Bindings bindings;bindings.dsi0Pipe=0; - uint32_t lp=0,buffer=0,secondary=0,mux=0; - return Display13FBIRQ::consumersStopped(io,1,bindings) && value(0x6B0D8,lp) && - (lp&((1U<<17)|(1U<<16)|1))==((1U<<16)|1) && value(0x64000,buffer) && + uint32_t buffer=0,secondary=0,mux=0; + // host.complete() witnessed ULPS before disabling the function. The + // request self-clears and host disable may reset its status. + return Display13FBIRQ::consumersStopped(io,1,bindings) && value(0x64000,buffer) && (buffer&0x80000080U)==0x80 && value(0x7B808,secondary) && !(secondary&0xC0000000U) && value(0x6BC00,secondary) && !(secondary&(1U<<31)) && value(0x164280,mux) && (mux&0x403U)==(0x400U|link.identity.pll); diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 45f82572..f85b5db8 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -33,6 +33,37 @@ struct Result { Host host;Readout source;bool attempted {}; bool complete() const {return stage==Stage::Complete && error==Error::None;} }; +// Panel half of a serialized runtime shutdown. Host/IRQ/clock/memory remain +// owned by the caller. Reset/power-off must contain no packets after ULPS. +struct Shutdown { + Host host {true};unsigned stage {};bool complete {}; + bool preflight(const Profile &p) { + if(!p.valid())return false; + constexpr unsigned ids[]={7,4,5,11}; + for(auto id:ids) { + Sequences n; + if(validateSequence(p.sequences.sequence[id],n)!=SequenceStatus::Valid || + n.delayUs>5000000 || ((id==5 || id==11) && n.packets))return false; + } + return p.ppsUs[2]<=5000000 && p.ppsUs[3]<=5000000; + } + template bool step(IO &io,const Profile &p,unsigned next) { + if(next!=stage+1 || complete)return false; + stage=next; + struct Port:CommandPort { + explicit Port(IO &q):CommandPort{q} {} + bool gpio(unsigned role,unsigned value){return this->io.gpio(role,value);} + } port{io}; + if(next==1)return host.run(port,p.sequences,7) && host.delay(port,p.ppsUs[2]); + if(next==2)return host.run(port,p.sequences,4); + if(next==3) { + complete=host.run(port,p.sequences,5) && host.delay(port,p.ppsUs[3]) && + host.run(port,p.sequences,11) && !host.pending && !port.failed; + return complete; + } + return false; + } +}; // Bounded bootstrap panel experiment. Keep existing scanout backing pinned. // Stop pixel transfer, execute the complete panel off/reset/on sequences, and // restart the same scanout. Lifecycle may stop/rebuild the host between panel diff --git a/WhateverXe/kern_dsi_quiesce.hpp b/WhateverXe/kern_dsi_quiesce.hpp index 9d695963..f1c4a9bd 100644 --- a/WhateverXe/kern_dsi_quiesce.hpp +++ b/WhateverXe/kern_dsi_quiesce.hpp @@ -1,13 +1,13 @@ -// Minimum P2 host shutdown, following Linux v7.0 gen11_dsi_post_disable: +// Host shutdown, following Linux v7.0 gen11_dsi_post_disable: // stop plane/cursor, transcoder, ULPS, function, then ungate/stop/gate buffer. -// Panel DCS/GPIO/PWM and cold restart remain separate panel/backend operations. +// Optional panel lifecycle supplies backlight, DISPLAY_OFF and reset/power-off. #ifndef KERN_DSI_QUIESCE_HPP #define KERN_DSI_QUIESCE_HPP #include "kern_dsi_surface.hpp" #include "kern_display13_cursor.hpp" namespace XELP { namespace DSI { namespace Quiesce { -enum class Stage : uint32_t {Source,Cursor,Plane,Transcoder,Commands,ULPS,Function,PortClock,Buffer,Gate,Complete}; -enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Commands,Cursor}; +enum class Stage : uint32_t {Source,Cursor,Plane,Transcoder,Commands,ULPS,Function,PortClock,Buffer,Gate,Complete,Panel}; +enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Commands,Cursor,Panel}; struct Result { Stage stage {Stage::Source};Error error {Error::Input};uint32_t writes {},waitUs {},reg {},value {}; uint32_t frameBefore {},frameAfter {};Display13Cursor::Result cursor; @@ -22,9 +22,13 @@ struct Result { // P2 has no command producer. Require empty hardware command FIFOs and LP idle // both before shutdown and before ULPS; a pending panel sequence needs the // later Host/panel drain backend. Never silently discard such a command. -template Result run(IO &io,const Readout &link,const Profile &profile) { +struct RetainedPanel { + bool preflight(const Profile &){return true;} + template bool step(IO &,const Profile &,unsigned){return true;} +}; +template Result run(IO &io,const Readout &link,const Profile &profile,Panel &panel) { Result r; - if(!link.matches(profile) || link.identity.pipe!=0 || !profile.mode.valid())return r; + if(!link.matches(profile) || link.identity.pipe!=0 || !profile.mode.valid() || !panel.preflight(profile))return r; const auto period=(uint64_t(profile.mode.htotal())*profile.mode.vtotal()*1000+profile.mode.clockKHz-1)/profile.mode.clockKHz; if(period<1000 || period>100000)return r; const auto held=[&]() { @@ -60,6 +64,7 @@ template Result run(IO &io,const Readout &link,const Profile &profile) const uint32_t registers[]={0x70080,0x70084,0x70088,0x700AC,0x70040,0x70180,0x7019C, 0x7B008,0x6B0D8,0x6B400,0x164280,0x64000}; for(auto reg:registers)if(!io.readable(reg))return r; + r.stage=Stage::Panel;if(!panel.step(io,profile,1)){r.error=Error::Panel;return r;} r.stage=Stage::Cursor; Display13Transcoder::Bindings bindings;bindings.dsi0Pipe=0; r.cursor=Display13Cursor::stop(io,0,bindings);r.writes+=r.cursor.writes;r.waitUs+=r.cursor.waitUs; @@ -83,6 +88,7 @@ template Result run(IO &io,const Readout &link,const Profile &profile) if(!held() || !Surface::source(io,link) || !wait(0x70180,1U<<31,0,0,1))return r; r.stage=Stage::Transcoder; if(!write(0x7B008,link.conf&~(1U<<31)) || !wait(0x7B008,0xC0000000U,0,50000,1000))return r; + r.stage=Stage::Panel;if(!panel.step(io,profile,2)){r.error=Error::Panel;return r;} r.stage=Stage::Commands;if(!empty())return r; r.stage=Stage::ULPS;uint32_t lp=0; if(!read(0x6B0D8,lp) || !write(0x6B0D8,(lp&~(1U<<8))|1) || !wait(0x6B0D8,1U<<16,1U<<16,10,1))return r; @@ -96,12 +102,18 @@ template Result run(IO &io,const Readout &link,const Profile &profile) r.stage=Stage::Gate; if(!read(0x164280,mux) || (mux&3)!=link.identity.pll || !write(0x164280,mux|(1U<<10)) || !wait(0x164280,UINT32_MAX,mux|(1U<<10),0,1))return r; + r.stage=Stage::Panel;if(!panel.step(io,profile,3)){r.error=Error::Panel;return r;} + // ULPS was witnessed before function disable; its status may reset when + // the host is disabled. Do not require a disabled host to retain it. // Hardware-only proof. The caller must separately drain actual native IRQ // handlers/events and detach the shared clock owner before releasing memory. if(!wait(0x70180,1U<<31,0,0,1) || !wait(0x70080,63,0,0,1) || !wait(0x7B008,0xC0000000U,0,0,1) || !wait(0x6B400,1U<<31,0,0,1) || - !wait(0x64000,0x80000080U,0x80,0,1) || !wait(0x6B0D8,1U<<16,1U<<16,0,1))return r; + !wait(0x64000,0x80000080U,0x80,0,1))return r; r.stage=Stage::Complete;r.error=Error::None;return r; } +template Result run(IO &io,const Readout &link,const Profile &profile) { + RetainedPanel panel;return run(io,link,profile,panel); +} }}} #endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index bb6d39a8..7d02fb18 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3874,6 +3874,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { dsiPanelOnly=checkKernelArgument("-wex-dsi-panel-only"); dsiCommandTest=checkKernelArgument("-wex-dsi-command-test"); dsiHostTest=checkKernelArgument("-wex-dsi-host-test"); + dsiStopTest=checkKernelArgument("-wex-dsi-stop-test"); dsiPanelTest=checkKernelArgument("-wex-dsi-panel-test") || dsiHostTest; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelOnly",dsiPanelOnly); if(!board.described())return {}; @@ -7762,6 +7763,19 @@ uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // One-way runtime stop experiment through the real power callback. + // Recovery is a reboot; state 2 never replays the donor's DDI path. + if(callbackXE->dsiStopTest && state<2 && dsiModeTarget(fb)) { + const bool stopped=callbackXE->dsiMemoryReleased && callbackXE->dsiDDIReleased && + callbackXE->dsiClockStop.complete() && callbackXE->dsiPanelStop.complete; + if(stopped)return kIOReturnSuccess; + if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return kIOReturnNotReady; + auto controller=Display13FBMemory::field(fb,0x1D0); + auto path=Display13FBMemory::singlePath(fb); + if(!callbackXE->quiesceDSIPath(controller,fb,path))return kIOReturnNotReady; + *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; + return kIOReturnSuccess; + } // Match only the donor's already-Wake no-op. Do not publish a new // native power state without executing a DSI power transaction. if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; @@ -11009,7 +11023,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - uint64_t record[39] {5};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; + uint64_t record[51] {6};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; const auto qualified=[&]() { if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || (dsiPendingTarget(fb) && !dsiArmedBackingOwned(fb,path)) || !dsiProfile.valid() || controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || @@ -11030,17 +11044,24 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { c.bridge.outputHeld(io,0,0); }; struct IO : Display13FBBootIO { - decltype(qualified) &owns; - IO(IOMemoryMap *m,decltype(qualified) &q):Display13FBBootIO{m},owns(q) {} + decltype(qualified) &owns;IOSimpleLock *irqLock;bool panel; + IO(IOMemoryMap *m,decltype(qualified) &q,IOSimpleLock *l,bool p):Display13FBBootIO{m},owns(q),irqLock(l),panel(p) {} bool ready(){return owns();} void write(uint32_t reg,uint32_t value) { if(readable(reg) && (reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000 || - reg==0x46010+4*display13FBPower.bootDSI.identity.pll)) + reg==0x46010+4*display13FBPower.bootDSI.identity.pll || + (panel && (reg==0x6B100 || reg==0x6B104 || reg==0xC4030 || reg==0xC7204)))) *reinterpret_cast(map->getVirtualAddress()+reg)=value; } + bool gpio(unsigned role,unsigned value) { + if(!panel || !irqLock)return false; + const auto saved=IOSimpleLockLockDisableInterrupt(irqLock); + const auto r=XELP::DSI::Panel::gpio(*this,role,value); + IOSimpleLockUnlockEnableInterrupt(irqLock,saved);return r.complete; + } void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} - } io{c.map,qualified}; + } io{c.map,qualified,irq.lock,dsiStopTest}; record[1]=qualified(); if(record[1] && !dsiStopAttempted && adlnIRQOwner(controller) && irq.lock && irq.lock==Display13FBMemory::field(controller,0x90) && irq.base==c.map->getVirtualAddress()) { @@ -11068,7 +11089,9 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { } IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); if(maskOK) { - dsiStopAttempted=true;dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile); + dsiStopAttempted=true; + if(dsiStopTest)dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile,dsiPanelStop); + else dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile); } if(masked) { saved=IOSimpleLockLockDisableInterrupt(irq.lock); @@ -11160,6 +11183,12 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { !c.pipe[0].scanout.record && !__atomic_load_n(&c.pipe[0].publishedScanout,__ATOMIC_ACQUIRE) && !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired; } + record[39]=dsiStop.reg;record[40]=dsiStop.value; + record[41]=dsiPanelStop.stage;record[42]=dsiPanelStop.complete; + record[43]=static_cast(dsiPanelStop.host.error);record[44]=dsiPanelStop.host.sequenceId; + record[45]=dsiPanelStop.host.operationOffset;record[46]=dsiPanelStop.host.headers; + record[47]=dsiPanelStop.host.payloadWords;record[48]=dsiPanelStop.host.completedOperations; + record[49]=dsiPanelStop.host.waitedUs;record[50]=dsiPanelStop.host.pending; record[33]=dsiMemoryReleased;record[34]=c.memoryFaulted; const bool complete=stoppedPath && dsiMemoryReleased; if(complete) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index c2eb039c..066d6ca1 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -252,6 +252,8 @@ class XE { bool dsiCommandTest {}; bool dsiPanelTest {}; bool dsiHostTest {}; + bool dsiStopTest {}; + XELP::DSI::Panel::Shutdown dsiPanelStop; XELP::DSI::Restart::State dsiHostRestart; XELP::DSI::Panel::Result dsiPanelTrial; bool probeDSIPanel(IOService *); diff --git a/tests/dsi_clock.py b/tests/dsi_clock.py index b18205d9..e333a0f0 100644 --- a/tests/dsi_clock.py +++ b/tests/dsi_clock.py @@ -16,11 +16,11 @@ struct Sim:IO { unsigned dropped=0,pllWrites=0,ackFailure=0;bool held=true; bool ready(){return held;} - void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg==0x46014 && ++pllWrites==dropped)return;regs[reg]=value;} + void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg==0x46014 && ++pllWrites==dropped)return;regs[reg]=value;if(reg==0x6B400 && !(value&(1U<<31)))regs[0x6B0D8]=0;} void delayUs(unsigned us){ if(us>=1000)++regs[0x70040]; if(!(regs[0x7B008]&(1U<<31)))regs[0x7B008]&=~(1U<<30); - if(regs[0x6B0D8]&1)regs[0x6B0D8]|=1U<<16; + if(regs[0x6B0D8]&1)regs[0x6B0D8]=(regs[0x6B0D8]&~1U)|(1U<<16); if(!(regs[0x64000]&(1U<<31)))regs[0x64000]|=0x80; if(!(regs[0x46014]&(1U<<31)) && ackFailure!=1)regs[0x46014]&=~(1U<<30); if(!(regs[0x46014]&(1U<<27)) && ackFailure!=2)regs[0x46014]&=~(1U<<26); @@ -48,7 +48,7 @@ } for(unsigned drop:{1U,2U}){Case c;c.io.dropped=drop;const auto r=c.run();assert(!r.complete() && r.writes==drop && r.waitUs>=1000);c.retained();} for(unsigned ack:{1U,2U}){Case c;c.io.ackFailure=ack;const auto r=c.run();assert(!r.complete() && r.writes==ack && r.waitUs>=1000);c.retained();} - for(auto reg:{0x45404U,0x7B008U,0x6B400U,0x70180U,0x70080U,0x6B0D8U,0x64000U,0x7B808U,0x6BC00U,0x164280U,0x16428CU,0x164290U}){ + for(auto reg:{0x45404U,0x7B008U,0x6B400U,0x70180U,0x70080U,0x64000U,0x7B808U,0x6BC00U,0x164280U,0x16428CU,0x164290U}){ Case c;c.io.regs[reg]^=(reg==0x7B008 || reg==0x7B808 || reg==0x6B400 || reg==0x6BC00 || reg==0x64000 || reg==0x70180)?1U<<31:1; assert(!c.run().complete() && c.io.writes.empty());c.retained(); } diff --git a/tests/dsi_panel.py b/tests/dsi_panel.py index 5d26827c..8a6eba64 100644 --- a/tests/dsi_panel.py +++ b/tests/dsi_panel.py @@ -9,6 +9,7 @@ fixture=profile[profile.index('using Bytes='):profile.index('int main(')] code=r''' #include "kern_dsi_panel.hpp" +#include "kern_dsi_clock.hpp" #include "kern_xelp_memory_layout.hpp" #include #include @@ -36,10 +37,15 @@ if(reg==0x6B100){++headers;packets.push_back(value);return;} if(reg==0x6B104){++payload;return;} if(reg==0x7B008){regs[reg]=(value&~(1U<<30))|(regs[reg]&(1U<<30));return;} + if(reg==0x6B400 && !(value&(1U<<31)))regs[0x6B0D8]=0; regs[reg]=value; } void delayUs(unsigned us){ assert(!irqDepth);elapsed+=us; + if(regs[0x6B0D8]&1)regs[0x6B0D8]=(1U<<16); + if(!(regs[0x64000]&(1U<<31)))regs[0x64000]|=0x80; + if(!(regs[0x46014]&(1U<<31)))regs[0x46014]&=~(1U<<30); + if(!(regs[0x46014]&(1U<<27)))regs[0x46014]&=~(1U<<26); auto &conf=regs[0x7B008]; if(!(conf&(1U<<31)) && !stuckStop)conf&=~(1U<<30); if((conf&(1U<<31)) && !stuckStart)conf|=1U<<30; @@ -65,7 +71,23 @@ } void run(){Panel::cycle(io,link,p,0x94000008,0,result);} }; +void stop(Fixture &f,bool success=true) { + Panel::Shutdown panel; + const auto host=Quiesce::run(f.io,f.link,f.p,panel); + if(!success){assert(!host.complete() && !panel.complete);return;} + assert(host.complete() && panel.complete && !panel.host.pending && !panel.host.faulted); + assert(f.io.gpioTrace==std::vector({4,0,2})); + assert(!(f.io.regs[0x70180]&(1U<<31)) && !f.io.regs[0x7B008] && !f.io.regs[0x6B0D8]); + XELPComboClock::ClockAllocation allocation;allocation.external[0]=123;allocation.binding[0]=2; + assert(Clock::retire(f.io,f.link,host,true,allocation,123).complete()); + assert(!allocation.external[0] && !allocation.binding[0] && !f.io.regs[0x46014]); + assert(f.io.regs[0x71180]==0x94000000 && f.io.regs[0x64100]==0x80000000); +} int main(int argc,char **argv){ + {Fixture f;stop(f);} + {Fixture f;f.io.stuckCommand=true;stop(f,false);assert(f.io.gpioTrace==std::vector({4}));} + {Fixture f;f.p.sequences.sequence[11]=f.p.sequences.sequence[4];stop(f,false);assert(f.io.writes.empty());} + {Fixture f;f.run();assert(f.result.complete()); assert(f.io.gpioTrace==std::vector({4,0,2,3,1,5})); assert(f.io.packets==std::vector({0x40001005,0x40002805,0x40000005,0xC0000539,0x40001105,0x40000005})); @@ -93,6 +115,7 @@ } if(argc==2){Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in); f.vbt=Bytes{std::istreambuf_iterator(in),{}};f.p=parseProfile({f.vbt.data(),f.vbt.size()},true);assert(f.p.valid()); + {Fixture stopped;stopped.vbt=f.vbt;stopped.p=parseProfile({stopped.vbt.data(),stopped.vbt.size()},true);stop(stopped);} f.run();assert(f.result.complete() && f.result.host.completedOperations==f.p.sequences.packets+f.p.sequences.delays+f.p.sequences.gpios); assert(f.io.gpioTrace==std::vector({4,0,2,3,1,0,1,5})); printf("saved VBT replay: packets=%u payloadWords=%u operations=%u gpio=%zu delayUs=%llu; simulation only\n",f.result.host.headers,f.result.host.payloadWords,f.result.host.completedOperations,f.io.gpioTrace.size(),static_cast(f.result.host.waitedUs)); diff --git a/tests/dsi_quiesce.py b/tests/dsi_quiesce.py index 8d414523..13e3cf3a 100644 --- a/tests/dsi_quiesce.py +++ b/tests/dsi_quiesce.py @@ -16,12 +16,12 @@ struct Sim:IO { uint32_t drop=UINT32_MAX,elapsed=0,failStage=0;bool frozen=false,unbound=false,losePower=false,pendingAfterStop=false; bool ready(){return !unbound;} - void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg!=drop)regs[reg]=value;} + void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg!=drop){regs[reg]=value;if(reg==0x6B400 && !(value&(1U<<31)))regs[0x6B0D8]=0;}} void delayUs(unsigned us){elapsed+=us; if(losePower){regs[0x45404]=0;return;} if(!frozen && us>=1000)++regs[0x70040]; if(!(regs[0x7B008]&(1U<<31)) && failStage!=1){regs[0x7B008]&=~(1U<<30);if(pendingAfterStop)regs[0x6B0D0]=0;} - if((regs[0x6B0D8]&1) && failStage!=2)regs[0x6B0D8]|=1U<<16; + if((regs[0x6B0D8]&1) && failStage!=2)regs[0x6B0D8]=(regs[0x6B0D8]&~1U)|(1U<<16); if(!(regs[0x64000]&(1U<<31)) && failStage!=3)regs[0x64000]|=0x80; } }; diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index df1ab826..bd104b37 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -69,6 +69,15 @@ uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; + bool dsiStopTest=false,dsiMemoryReleased=false,dsiDDIReleased=false,stopOK=false;unsigned stopCalls=0; + struct {bool done=false;bool complete(){return done;}}dsiClockStop; + struct {bool complete=false;}dsiPanelStop; + bool quiesceDSIPath(IOService *ctl,IOService *fb,OSObject *path){ + assert(fbLock.depth && !owner && ctl==display13FBPower.controller && fb==panel); + assert(path==Display13FBMemory::singlePath(fb));++stopCalls; + if(stopOK){dsiStopAttempted=true;dsiMemoryReleased=dsiDDIReleased=dsiPanelStop.complete=dsiClockStop.done=true;} + return stopOK; + } bool dsiPanelOnly=false,publicationReady=false;bool publishDSINativeMode(IOService*){return publicationReady;} bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; @@ -181,6 +190,15 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ + for(unsigned state:{0U,1U}){Case c;xe.dsiStopTest=true;put(&c.fb,0x4284,2U); + assert(XE::wrapDSISetPowerState(&c.fb,state)==kIOReturnNotReady && xe.stopCalls==1); + assert(Display13FBMemory::field(&c.fb,0x4284)==2); + xe.stopOK=true;assert(XE::wrapDSISetPowerState(&c.fb,state)==kIOReturnSuccess && xe.stopCalls==2); + assert(Display13FBMemory::field(&c.fb,0x4284)==state); + assert(XE::wrapDSISetPowerState(&c.fb,state)==kIOReturnSuccess && xe.stopCalls==2); + assert(XE::wrapDSISetPowerState(&c.fb,2)==kIOReturnUnsupported && xe.stopCalls==2); + } + {Case c;auto before=backingCalls; assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,0)==kIOReturnNotReady && backingCalls==before+1); assert(XE::wrapDSISetDisplayMode(&c.fb,0x10000,2)==kIOReturnNotReady && backingCalls==before+1); diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index f4ac5e7b..34337ecb 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -5,6 +5,7 @@ "-wex-dsi-command-test": [], "-wex-dsi-panel-test": [], "-wex-dsi-host-test": [], + "-wex-dsi-stop-test": [], "-wex-adln-config-stage": [ "-adlnconfigstage", "-n97configstage" From 568e4d48f061c46958dc28a7c9a75583e2613491 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:16:22 +0900 Subject: [PATCH 073/139] Route IOGraphics power-state attributes to DSI power handling --- WhateverXe/kern_xe.cpp | 2 +- tests/dsi_update_dispatch.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 7d02fb18..f6a4b025 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7713,7 +7713,7 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(attribute==0x706F7772) { // powr: native entry passes an unsigned state. + if(attribute==0x706F7772 || attribute==0x70777273) { // powr / IOGraphics pwrs relay. if(value>UINT32_MAX)return kIOReturnUnsupported; return wrapDSISetPowerState(fb,static_cast(value)); } diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index bd104b37..d728f0f8 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -190,13 +190,13 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ - for(unsigned state:{0U,1U}){Case c;xe.dsiStopTest=true;put(&c.fb,0x4284,2U); - assert(XE::wrapDSISetPowerState(&c.fb,state)==kIOReturnNotReady && xe.stopCalls==1); + for(unsigned attr:{0x706F7772U,0x70777273U})for(unsigned state:{0U,1U}){Case c;xe.dsiStopTest=true;put(&c.fb,0x4284,2U); + assert(XE::wrapDSISetAttribute(&c.fb,attr,state)==kIOReturnNotReady && xe.stopCalls==1); assert(Display13FBMemory::field(&c.fb,0x4284)==2); - xe.stopOK=true;assert(XE::wrapDSISetPowerState(&c.fb,state)==kIOReturnSuccess && xe.stopCalls==2); + xe.stopOK=true;assert(XE::wrapDSISetAttribute(&c.fb,attr,state)==kIOReturnSuccess && xe.stopCalls==2); assert(Display13FBMemory::field(&c.fb,0x4284)==state); - assert(XE::wrapDSISetPowerState(&c.fb,state)==kIOReturnSuccess && xe.stopCalls==2); - assert(XE::wrapDSISetPowerState(&c.fb,2)==kIOReturnUnsupported && xe.stopCalls==2); + assert(XE::wrapDSISetAttribute(&c.fb,attr,state)==kIOReturnSuccess && xe.stopCalls==2); + assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnUnsupported && xe.stopCalls==2); } {Case c;auto before=backingCalls; From 78e73be6999e6c00fdff54143fa65130e2627e0b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:40:59 +0900 Subject: [PATCH 074/139] Add retained DSI display off and resume experiment --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_panel.hpp | 51 ++++--- WhateverXe/kern_dsi_restart.hpp | 36 ++++- WhateverXe/kern_xe.cpp | 193 ++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 11 +- tests/dsi_restart.py | 26 ++++ tests/dsi_update_dispatch.py | 30 +++- tests/fixtures/boot_argument_names.json | 3 +- 9 files changed, 312 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index df4e280a..4cf86725 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 3d007348..74990342 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index f85b5db8..0948bea5 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -30,7 +30,7 @@ enum class Error : uint32_t {None,Input,Ownership,Readback,Timeout,Sequence,Host struct Result { Stage stage {Stage::Input};Error error {Error::Input}; uint32_t reg {},value {},writes {},waitUs {},frameBefore {},frameAfter {}; - Host host;Readout source;bool attempted {}; + Host host {};Readout source;bool attempted {}; bool complete() const {return stage==Stage::Complete && error==Error::None;} }; // Panel half of a serialized runtime shutdown. Host/IRQ/clock/memory remain @@ -68,15 +68,18 @@ struct Shutdown { // Stop pixel transfer, execute the complete panel off/reset/on sequences, and // restart the same scanout. Lifecycle may stop/rebuild the host between panel // operations; RetainedHost leaves its settings intact. Neither variant is a -// runtime DPMS implementation or releases allocation/shared power ownership. +// complete OS DPMS implementation or releases allocation/shared power ownership. +// A verified Shutdown permits the same power-on/init tail on runtime resume; +// native backing, cursor and IRQ lifetime still belong to the owning adapter. // IO::gpio serializes HPD writes; no delays may run under that IRQ lock. // A failed transaction is never retried and must retain all resources. template void cycle(IO &io,const Readout &link,const Profile &profile, - uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle) { + uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle,const Shutdown *shutdown=nullptr) { Readout current=link; if(r.attempted)return; r.attempted=true; - if(!profile.valid() || !link.matches(profile) || link.identity.pipe!=0 || + if((shutdown && (!shutdown->complete || shutdown->host.faulted || shutdown->host.pending)) || + !profile.valid() || !link.matches(profile) || link.identity.pipe!=0 || !(planeControl&(1U<<31)) || (surface&4095) || !profile.sequences.valid())return; constexpr unsigned required[]={1,2,4,5,6,7,10,11}; for(auto id:required) { @@ -130,22 +133,30 @@ template void cycle(IO &io,const Readout &link,const P }; r.error=Error::Readback; uint32_t credits=0,lp=0; - if(!held() || !Surface::source(io,link) || - !Surface::word(io,0x70180,UINT32_MAX,planeControl) || - !Surface::word(io,0x7019C,UINT32_MAX,surface) || - !read(0x6B0D0,credits) || !read(0x6B0D8,lp) || - (credits&0x1FFF)!=0x1040 || (lp&0x30001))return; - r.host.enabled=true; - r.stage=Stage::BacklightOff;if(!sequence(7) || !delay(2))return; - r.stage=Stage::PlaneOff; - if(!write(0x70180,planeControl&~(1U<<31)) || !write(0x7019C,surface) || - !wait(0x70180,1U<<31,0,0) || !frame())return; - r.stage=Stage::TranscoderOff; - if(!write(0x7B008,link.conf&~(1U<<31)) || !wait(0x7B008,0xC0000000U,0,50000))return; - r.stage=Stage::DisplayOff;if(!sequence(4))return; - r.stage=Stage::HostOff;if(!lifecycle.stop(io,link,profile)){r.error=Error::Host;return;} - r.stage=Stage::AssertReset;if(!sequence(5) || !delay(3))return; - r.stage=Stage::PowerOff;if(!sequence(11))return; + if(shutdown) { + // The adapter retains the native backing and all display leases. + // Restart::start separately proves the stopped PLL/host and its owner. + if(!held() || !wait(0x7B008,0xC0000000U,0,0) || !wait(0x70180,1U<<31,0,0) || + !wait(0x70080,63,0,0))return; + r.host=shutdown->host; + } else { + if(!held() || !Surface::source(io,link) || + !Surface::word(io,0x70180,UINT32_MAX,planeControl) || + !Surface::word(io,0x7019C,UINT32_MAX,surface) || + !read(0x6B0D0,credits) || !read(0x6B0D8,lp) || + (credits&0x1FFF)!=0x1040 || (lp&0x30001))return; + r.host.enabled=true; + r.stage=Stage::BacklightOff;if(!sequence(7) || !delay(2))return; + r.stage=Stage::PlaneOff; + if(!write(0x70180,planeControl&~(1U<<31)) || !write(0x7019C,surface) || + !wait(0x70180,1U<<31,0,0) || !frame())return; + r.stage=Stage::TranscoderOff; + if(!write(0x7B008,link.conf&~(1U<<31)) || !wait(0x7B008,0xC0000000U,0,50000))return; + r.stage=Stage::DisplayOff;if(!sequence(4))return; + r.stage=Stage::HostOff;if(!lifecycle.stop(io,link,profile)){r.error=Error::Host;return;} + r.stage=Stage::AssertReset;if(!sequence(5) || !delay(3))return; + r.stage=Stage::PowerOff;if(!sequence(11))return; + } r.stage=Stage::PowerCycle;if(!delay(4))return; r.stage=Stage::PowerOn;if(!sequence(10) || !delay(0))return; r.stage=Stage::DeassertReset;if(!sequence(1))return; diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp index 0903581c..12379ce8 100644 --- a/WhateverXe/kern_dsi_restart.hpp +++ b/WhateverXe/kern_dsi_restart.hpp @@ -4,6 +4,7 @@ #ifndef KERN_DSI_RESTART_HPP #define KERN_DSI_RESTART_HPP #include "kern_dsi_readout.hpp" +#include "kern_dsi_quiesce.hpp" #include "kern_xelp_combo_clock.hpp" namespace XELP { namespace DSI { namespace Restart { enum class Stage : uint32_t { @@ -121,6 +122,34 @@ template struct Access { return true; } }; +// Shared tail for bootstrap stop and a runtime Quiesce proof. The latter +// retains backing, DDI power and external PLL ownership for display wake. +template bool powerOff(Access &a,const XELPComboClock::PLL &pll) { + auto &s=a.s; + s.stage=Stage::ModeOff;if(!a.rmw(0x6B094,1,0))return false; + s.stage=Stage::PLLDisable; + if(!a.rmw(pll.enable,1U<<31,0) || !a.wait(pll.enable,0xC0000000U,0,1000,10))return false; + s.stage=Stage::PLLPowerOff; + if(!a.rmw(pll.enable,1U<<27,0) || !a.wait(pll.enable,0xCC000000U,0,1000,10))return false; + s.stage=Stage::Stopped;s.stopped=true;return true; +} +template bool stopQuiesced(IO &io,const Readout &source,const Profile &profile, + const Quiesce::Result &host,XELPComboClock::ClockAllocation &allocation, + uintptr_t owner,State &s) { + if(s.attempted)return false;s.attempted=true; + if(!host.complete() || !source.matches(profile) || source.identity.pipe!=0)return s.fail(Error::Input); + s.config=plan(profile,source.referenceKHz);s.pll=source.identity.pll; + if(!s.config.valid)return s.fail(Error::Input); + Access a{io,allocation,owner,s};XELPComboClock::PLL pll; + if(!XELPComboClock::PLL::resolve(s.pll,pll) || !a.held())return false; + // Re-read consumers and ownership: a completed software receipt alone + // cannot authorize stopping a currently active or reassigned PLL. + if(!a.wait(pll.enable,0xCC000000U,0xCC000000U,0) || + !a.wait(pll.cfg0,UINT32_MAX,source.cfg0,0) || !a.wait(pll.cfg1,UINT32_MAX,source.cfg1,0) || + !a.wait(0x6B400,1U<<31,0,0) || !a.wait(0x64000,0x80000080U,0x80,0) || + !a.wait(0x164280,0x403,0x400|s.pll,0))return false; + return powerOff(a,pll); +} // Invoke only after plane/cursor/transcoder stop and DISPLAY_OFF/drain. // Hardware readbacks recheck those preconditions; a caller assertion alone // must not permit clock writes. This stop deliberately retains the owner. @@ -147,12 +176,7 @@ template bool stop(IO &io,const Readout &source,const Profile &profile s.stage=Stage::BufferOff; if(!a.rmw(0x164280,1U<<10,0) || !a.rmw(0x64000,1U<<31,0) || !a.wait(0x64000,0x80000080U,0x80,8) || !a.rmw(0x164280,0,1U<<10))return false; - s.stage=Stage::ModeOff;if(!a.rmw(0x6B094,1,0))return false; - s.stage=Stage::PLLDisable; - if(!a.rmw(pll.enable,1U<<31,0) || !a.wait(pll.enable,0xC0000000U,0,1000,10))return false; - s.stage=Stage::PLLPowerOff; - if(!a.rmw(pll.enable,1U<<27,0) || !a.wait(pll.enable,0xCC000000U,0,1000,10))return false; - s.stage=Stage::Stopped;s.stopped=true;return true; + return powerOff(a,pll); } // Invoke after panel POWER_ON/PPS/DEASSERT_RESET, before INIT_OTP. Timing and // transport derive from the profile; no firmware PLL/PHY register dump replay. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f6a4b025..ce67e6de 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3875,6 +3875,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { dsiCommandTest=checkKernelArgument("-wex-dsi-command-test"); dsiHostTest=checkKernelArgument("-wex-dsi-host-test"); dsiStopTest=checkKernelArgument("-wex-dsi-stop-test"); + dsiPowerTest=checkKernelArgument("-wex-dsi-power-test"); dsiPanelTest=checkKernelArgument("-wex-dsi-panel-test") || dsiHostTest; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelOnly",dsiPanelOnly); if(!board.described())return {}; @@ -7763,6 +7764,16 @@ uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(callbackXE->dsiPowerTest && state<=2 && dsiModeTarget(fb)) { + auto controller=Display13FBMemory::field(fb,0x1D0); + auto path=Display13FBMemory::singlePath(fb); + auto &power=callbackXE->dsiRuntimePower; + const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : + (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); + if(!ready)return kIOReturnNotReady; + *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; + return kIOReturnSuccess; + } // One-way runtime stop experiment through the real power callback. // Recovery is a reboot; state 2 never replays the donor's DDI path. if(callbackXE->dsiStopTest && state<2 && dsiModeTarget(fb)) { @@ -11007,7 +11018,7 @@ static bool adlnWriterMappingBound() { // Host/IRQ/PLL retirement; the native DDI reference follows the stopped clock. // Keep the real stopped source separate from Session cleanup: it is not a full // Session StopProof. The one-shot journal retains a partial stop for diagnosis. -bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { +bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool retain) { auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || @@ -11044,13 +11055,14 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { c.bridge.outputHeld(io,0,0); }; struct IO : Display13FBBootIO { - decltype(qualified) &owns;IOSimpleLock *irqLock;bool panel; - IO(IOMemoryMap *m,decltype(qualified) &q,IOSimpleLock *l,bool p):Display13FBBootIO{m},owns(q),irqLock(l),panel(p) {} + decltype(qualified) &owns;IOSimpleLock *irqLock;bool panel,retain; + IO(IOMemoryMap *m,decltype(qualified) &q,IOSimpleLock *l,bool p,bool r):Display13FBBootIO{m},owns(q),irqLock(l),panel(p),retain(r) {} bool ready(){return owns();} void write(uint32_t reg,uint32_t value) { if(readable(reg) && (reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000 || reg==0x46010+4*display13FBPower.bootDSI.identity.pll || + (retain && XELP::DSI::Restart::writable(reg,display13FBPower.bootDSI.identity.pll)) || (panel && (reg==0x6B100 || reg==0x6B104 || reg==0xC4030 || reg==0xC7204)))) *reinterpret_cast(map->getVirtualAddress()+reg)=value; } @@ -11061,8 +11073,24 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { IOSimpleLockUnlockEnableInterrupt(irqLock,saved);return r.complete; } void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} - } io{c.map,qualified,irq.lock,dsiStopTest}; + } io{c.map,qualified,irq.lock,dsiStopTest || dsiPowerTest || retain,retain}; + auto &power=dsiRuntimePower; record[1]=qualified(); + bool snapshot=!retain; + if(retain && record[1] && !dsiStopAttempted && !power.sleeping && !c.memoryFaulted && !c.dbuf.faulted && !c.watermarkFaulted && + dsiPublishedTarget(fb) && dsiPendingBacking.modeReady && dsiArmedBackingOwned(fb,path)) { + auto &p=power.plane; + p.control=io.read(0x70180);p.surface=io.read(0x7019C);p.stride=io.read(0x70188);p.size=io.read(0x70190); + p.position=io.read(0x7018C);p.offset=io.read(0x701A4);p.color=io.read(0x701CC); + power.cursorControl=io.read(0x70080);power.cursorPosition=io.read(0x70088);power.cursorBase=io.read(0x70084); + const auto &proof=dsiPendingBacking.proof; + const XELP::DSI::Surface::Backing backing{reinterpret_cast(dsiPendingBacking.record),proof.gpu,proof.bytes,proof.pitch}; + snapshot=XELP::DSI::Surface::target(p,dsiProfile,backing) && XELP::DSI::Surface::matches(io,p,true) && + power.cursorControl!=UINT32_MAX && power.cursorBase!=UINT32_MAX && power.cursorPosition!=UINT32_MAX && + Display13Cursor::controlWrite(0x70080,power.cursorControl)==power.cursorControl; + if(snapshot){power.host=XELP::DSI::Restart::State{};power.panel=XELP::DSI::Panel::Result{};dsiPanelStop={};dsiStop={};} + } + record[1]=record[1] && snapshot; if(record[1] && !dsiStopAttempted && adlnIRQOwner(controller) && irq.lock && irq.lock==Display13FBMemory::field(controller,0x90) && irq.base==c.map->getVirtualAddress()) { Display13FBIRQIO irqIO{irq.base}; @@ -11090,7 +11118,8 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); if(maskOK) { dsiStopAttempted=true; - if(dsiStopTest)dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile,dsiPanelStop); + if(!retain)dsiPanelStop={}; + if(dsiStopTest || dsiPowerTest || retain)dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile,dsiPanelStop); else dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile); } if(masked) { @@ -11107,11 +11136,13 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { } IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); if(parked) { - dsiClockStop=XELP::DSI::Clock::retire(io,c.bootDSI,dsiStop,true,clock.allocation, + if(retain)XELP::DSI::Restart::stopQuiesced(io,c.bootDSI,dsiProfile,dsiStop,clock.allocation, + reinterpret_cast(controller),power.host); + else dsiClockStop=XELP::DSI::Clock::retire(io,c.bootDSI,dsiStop,true,clock.allocation, reinterpret_cast(controller)); } saved=IOSimpleLockLockDisableInterrupt(irq.lock); - if(dsiClockStop.complete()) { + if(!retain && dsiClockStop.complete()) { // The host and its PLL are now stopped. Withdraw both maps at // the same masked IRQ boundary, so an external clock client // no longer tries to re-adopt the obsolete running DSI source. @@ -11141,7 +11172,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { // The donor's disableClocks(port,peer) puts this DDI reference at 6db04; // PG1 belongs to freeDisplayPathResources and must remain until memory / // DDB / path destruction. Match that ownership without running DDI/DP code. - const bool stopped=parked && restored && dsiClockStop.complete(); + const bool stopped=!retain && parked && restored && dsiClockStop.complete(); record[30]=c.bridge.ddiRefs[0]; if(stopped && c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && c.bridge.bootPort==0 && !c.bridge.bootDDIProtected && c.bridge.ddiRefs[0]==1 && c.power) { @@ -11190,8 +11221,11 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { record[47]=dsiPanelStop.host.payloadWords;record[48]=dsiPanelStop.host.completedOperations; record[49]=dsiPanelStop.host.waitedUs;record[50]=dsiPanelStop.host.pending; record[33]=dsiMemoryReleased;record[34]=c.memoryFaulted; - const bool complete=stoppedPath && dsiMemoryReleased; - if(complete) { + const bool complete=retain ? (parked && restored && power.host.stopped && !power.host.started && + power.host.error==XELP::DSI::Restart::Error::None && dsiPanelStop.complete && qualified() && + dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); + if(retain && complete)power.sleeping=true; + if(complete && !retain) { // Exact donor disableDisplay publishes path state 1 only after its // clock-disable call (4dbba -> 4dbc6). Mirror that CPU completion now, // never on a partial host/IRQ/PLL stop or by replaying the donor body. @@ -11203,12 +11237,149 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path) { // Native backing and the GOP pool have been retired before a full stop // leaves PG/path disposal to releaseDisplay13Path; only partial stops // need the global retention guard. Never clear a pre-existing real fault. - if(masked && !complete)adlnFault(c.memoryFaulted,__LINE__); + if(masked && !complete){if(retain)power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(modeWithdrawn)fb->setProperty("WEXDSIFixedModeReady",false); if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); loop->release();IORecursiveLockUnlock(mutex); adlnPublishMemory("N97FBDSIHostStop",0,record,sizeof(record)); + if(retain) { + const auto &h=power.host; + uint64_t receipt[] {1,0,complete,power.cycles,power.sleeping,snapshot, + power.plane.control,power.plane.surface,power.cursorControl,power.cursorBase, + static_cast(h.stage),static_cast(h.error),h.reg,h.value,h.writes,h.waitUs, + parked,restored,dsiPanelStop.complete,dsiMemoryReleased,dsiDDIReleased}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); + } + return complete; +} + +// Runtime display wake keeps the native scanout allocation and every display +// lease from the matching off transaction. System power loss is not covered. +bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ;auto &power=dsiRuntimePower; + if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || + !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || + !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; + IORecursiveLockLock(mutex);IOLockLock(c.lock); + auto loop=controller==c.controller ? c.workLoop : nullptr;if(loop)loop->retain();IOLockUnlock(c.lock); + const bool boundLoop=loop && Display13FBMemory::field(controller,0xE00)==loop; + const bool inGate=boundLoop && reinterpret_cast(adlnWorkLoopInGate)(loop); + const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); + if(!inGate && !acquired){if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + const auto owned=[&]() { + if(!power.sleeping || power.failed || !dsiStopAttempted || !dsiIRQParked || !dsiResourcesReady || + !dsiStop.complete() || !dsiPanelStop.complete || !dsiNativeWriterHooksReady || !dsiProfile.valid() || + controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || + Display13FBMemory::field(controller,0xE00)!=loop || adlnOutputWiring.dsi!=1 || + c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || + c.transcoders.dsi0Pipe!=0 || c.dsiNativeWriters || c.memoryFaulted || c.clockFaulted || c.watermarkFaulted || + c.bridge.state.faulted || c.bridge.state.suspended || c.dbuf.faulted || c.dbuf.updating || + c.pipe[0].destroyingPath || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || + !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || + Display13FBMemory::field(c.dsiPort,0x550)!=1 || Display13FBMemory::field(path,0x3644)!=2 || + Display13FBMemory::field(path,0x35F0) || !adlnWriterMappingBound() || + clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || + clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || !c.bootDSI.sameSource(dsiBoot.source) || + !dsiPublishedTarget(fb) || !dsiPendingBacking.modeReady || !dsiArmedBackingOwned(fb,path))return false; + Display13FBBootIO io{c.map};return c.bridge.outputHeld(io,0,0); + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns;IOSimpleLock *lock;unsigned pll; + IO(IOMemoryMap *m,decltype(owned) &o,IOSimpleLock *l,unsigned p):Display13FBBootIO{m},owns(o),lock(l),pll(p) {} + bool ready(){return owns();} + void write(uint32_t r,uint32_t v) { + if(readable(r) && (XELP::DSI::Restart::writable(r,pll) || r==0x6B100 || r==0x6B104 || + r==0xC4030 || r==0xC7204 || r==0x70180 || r==0x7019C || r==0x7B008 || + r==0x70080 || r==0x70088 || r==0x70084)) + *reinterpret_cast(map->getVirtualAddress()+r)=v; + } + bool gpio(unsigned role,unsigned value) { + const auto saved=IOSimpleLockLockDisableInterrupt(lock);const auto r=XELP::DSI::Panel::gpio(*this,role,value); + IOSimpleLockUnlockEnableInterrupt(lock,saved);return r.complete; + } + void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} + } io{c.map,owned,irq.lock,dsiBoot.source.identity.pll}; + uint64_t receipt[24] {1,1,0,power.cycles,power.sleeping}; + bool masked=false,restored=false,rearmed=false,cursor=false,complete=false;uint32_t master=0; + auto stoppedPlane=power.plane;stoppedPlane.control&=~(1U<<31); + receipt[5]=owned() && power.host.stopped && !power.host.started && !power.panel.attempted && + power.host.error==XELP::DSI::Restart::Error::None && XELP::DSI::Surface::matches(io,stoppedPlane,false); + if(receipt[5] && adlnIRQOwner(controller) && irq.lock && irq.lock==Display13FBMemory::field(controller,0x90) && + irq.base==c.map->getVirtualAddress()) { + Display13FBIRQIO irqIO{irq.base};auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto queued=[&](unsigned offset) { + return __atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+offset),__ATOMIC_ACQUIRE); + }; + master=irqIO.read(Display13FBIRQ::Master); + const bool quiet=irq.scopes.total==irq.handlers && !irq.scopes.display && !irq.softwareHandlers && + !(queued(0xE08)&Display13FBIRQ::pipeEvents(0)) && !queued(0xE0C) && !(queued(0xE10)&3); + if(quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && irq.state.begun && irq.state.enabled && + !(irq.state.active&1) && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && + master!=UINT32_MAX && (master&Display13FBIRQ::Enable)) { + irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; + irqIO.write(Display13FBIRQ::Master,0); + const auto after=irqIO.read(Display13FBIRQ::Master); + if(after==UINT32_MAX || (after&Display13FBIRQ::Enable))irq.state.faulted=true; + } + const bool canStart=masked && !irq.state.faulted; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + if(canStart) { + struct Lifecycle { + XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state; + bool stop(IO &,const XELP::DSI::Readout &,const XELP::DSI::Profile &){return false;} + bool start(IO &io,XELP::DSI::Readout &source) { + if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; + source=XELP::DSI::Restart::expected(source,state);return source.complete(); + } + } lifecycle{clock.allocation,reinterpret_cast(controller),power.host}; + XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop); + if(power.panel.complete() && owned() && XELP::DSI::Surface::matches(io,power.plane,true)) { + io.write(0x70080,power.cursorControl);io.write(0x70088,power.cursorPosition);io.write(0x70084,power.cursorBase); + const auto before=io.read(0x70040); + for(unsigned us=0;us<=50000;us+=1000) { + const auto after=io.read(0x70040); + if(before!=UINT32_MAX && after!=UINT32_MAX && after!=before) { + cursor=XELP::DSI::Surface::word(io,0x70080,UINT32_MAX,power.cursorControl) && + XELP::DSI::Surface::word(io,0x70088,UINT32_MAX,power.cursorPosition) && + XELP::DSI::Surface::word(io,0x70084,UINT32_MAX,power.cursorBase);break; + } + if(us<50000)io.delayUs(1000); + } + if(cursor){dsiBoot.source=power.panel.source;c.bootDSI=power.panel.source;} + } + } + if(masked) { + saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto wells=irqIO.read(Display13FBIRQ::Driver); + if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; + if(!irq.state.faulted) { + irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); + const auto after=irqIO.read(Display13FBIRQ::Master); + restored=after!=UINT32_MAX && (after&Display13FBIRQ::Enable);irq.state.enabled=restored; + if(!restored)irq.state.faulted=true; + if(restored && cursor && owned())rearmed=Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes)==Display13FBIRQ::Status::Complete && (irq.state.active&1); + } + irq.quiescing=false;irq.blockedPipes=0; + adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + complete=restored && rearmed && cursor && power.panel.complete() && owned(); + if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;} + else{power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} + } + } + const auto &h=power.host;const auto &p=power.panel; + receipt[2]=complete;receipt[3]=power.cycles;receipt[4]=power.sleeping; + receipt[6]=power.plane.control;receipt[7]=power.plane.surface;receipt[8]=power.cursorControl;receipt[9]=power.cursorBase; + receipt[10]=static_cast(h.stage);receipt[11]=static_cast(h.error); + receipt[12]=h.reg;receipt[13]=h.value;receipt[14]=h.writes;receipt[15]=h.waitUs; + receipt[16]=masked;receipt[17]=restored;receipt[18]=rearmed;receipt[19]=cursor; + receipt[20]=static_cast(p.stage);receipt[21]=static_cast(p.error);receipt[22]=p.reg;receipt[23]=p.value; + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); + loop->release();IORecursiveLockUnlock(mutex); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); return complete; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 066d6ca1..319619a9 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -149,7 +149,8 @@ class XE { XELP::DSI::Quiesce::Result dsiStop; XELP::DSI::Clock::Result dsiClockStop; bool dsiStopAttempted {},dsiIRQParked {},dsiDDIReleased {},dsiMemoryReleased {}; - bool quiesceDSIPath(IOService *,IOService *,OSObject *); + bool quiesceDSIPath(IOService *,IOService *,OSObject *,bool retain=false); + bool resumeDSIPath(IOService *,IOService *,OSObject *); bool processDSIPathDisposal(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool disposeDSIPath(IOService *,OSObject *); mach_vm_address_t dsiFreePlane {},dsiFreeScaler {},dsiDetachPath {},dsiRemovePath {},dsiPipeMap {}; @@ -253,6 +254,14 @@ class XE { bool dsiPanelTest {}; bool dsiHostTest {}; bool dsiStopTest {}; + bool dsiPowerTest {}; + struct { + bool sleeping {},failed {};uint32_t cycles {}; + XELP::DSI::Surface::Plane plane; + uint32_t cursorControl {},cursorPosition {},cursorBase {}; + XELP::DSI::Restart::State host; + XELP::DSI::Panel::Result panel; + } dsiRuntimePower; XELP::DSI::Panel::Shutdown dsiPanelStop; XELP::DSI::Restart::State dsiHostRestart; XELP::DSI::Panel::Result dsiPanelTrial; diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index a1517c19..1f88fad4 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -89,6 +89,29 @@ assert(f.io.regs[0x70180]==0x94000008 && f.io.regs[0x7B008]==0xC0000000); assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); } +void retainedCycle(Fixture &f,unsigned fault=0){ + f.io.regs[0x7B008]=0xC0000000;f.io.regs[0x70180]=0x94000008; + f.io.regs[0x42084]=1U<<30;f.io.regs[0x6001C]=0x0437077F;f.io.regs[0x6B010]=f.io.regs[0x6B00C]; + f.io.regs[0xC7204]=15;f.io.regs[0xC4030]=0x84;f.source.mux=f.io.regs[0x164280]; + Panel::Shutdown shutdown; + const auto stopped=Quiesce::run(f.io,f.source,f.p,shutdown); + assert(stopped.complete() && shutdown.complete); + auto count=f.io.writes.size(); + if(fault==1)f.allocation.external[0]=2; + if(fault==2)f.io.regs[0x7B008]=0xC0000000; + if(fault==3)f.io.regs[0x16428C]^=1; + if(fault){assert(!Restart::stopQuiesced(f.io,f.source,f.p,stopped,f.allocation,1,f.state)); + assert(f.io.writes.size()==count && !f.state.stopped);return;} + assert(Restart::stopQuiesced(f.io,f.source,f.p,stopped,f.allocation,1,f.state)); + assert(f.state.stopped && !f.io.regs[0x46014] && f.allocation.external[0]==1 && f.allocation.binding[0]==2); + Panel::Result panel;Lifecycle lifecycle{f.allocation,f.state}; + Panel::cycle(f.io,f.source,f.p,0x94000008,0,panel,lifecycle,&shutdown); + if(!panel.complete())fprintf(stderr,"resume stage=%u error=%u reg=%x host=%u restart=%u/%u\n",unsigned(panel.stage),unsigned(panel.error),panel.reg,unsigned(panel.host.error),unsigned(f.state.stage),unsigned(f.state.error)); + assert(panel.complete() && f.state.started && Surface::source(f.io,panel.source)); + assert(f.io.regs[0x70180]==0x94000008 && f.io.regs[0x46014]==0xCC000000); + assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); + assert(f.io.regs[0x71180]==0x94000000 && f.io.regs[0x64100]==0x80000000); +} void successful(Fixture &f){ assert(f.stop() && f.state.stopped && !f.state.started); assert(f.io.regs[0x46014]==0 && f.io.regs[0x6B400]==0x30006 && f.io.regs[0x64000]==0x80 && f.io.regs[0x6B094]==0); @@ -115,6 +138,7 @@ } int main(int argc,char **argv){ {Fixture f;successful(f);} + for(unsigned fault=0;fault<4;++fault){Fixture f;retainedCycle(f,fault);} {Fixture f;panelCycle(f);} {Fixture f;panelCycle(f,true);} for(unsigned fault=0;fault<6;++fault){Fixture f; @@ -148,6 +172,8 @@ assert(f.io.elapsed<5000); } if(argc==2){ + {Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in);f.vbt=Bytes{std::istreambuf_iterator(in),{}}; + f.p=parseProfile({f.vbt.data(),f.vbt.size()},true);retainedCycle(f);} for(bool fail:{false,true}){Fixture f;std::ifstream in(argv[1],std::ios::binary);assert(in); f.vbt=Bytes{std::istreambuf_iterator(in),{}};f.p=parseProfile({f.vbt.data(),f.vbt.size()},true); panelCycle(f,fail); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index d728f0f8..1f5842ae 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -69,15 +69,23 @@ uint32_t nativeStream(IOService*,IOService*,OSObject*,const void*); struct XE { struct {unsigned dsi=1;}adlnOutputWiring;bool dsiStopAttempted=false,dsiResourcesReady=true; + bool dsiPowerTest=false;struct {bool sleeping=false,failed=false;}dsiRuntimePower;unsigned wakeCalls=0;bool wakeOK=false; bool dsiStopTest=false,dsiMemoryReleased=false,dsiDDIReleased=false,stopOK=false;unsigned stopCalls=0; struct {bool done=false;bool complete(){return done;}}dsiClockStop; struct {bool complete=false;}dsiPanelStop; - bool quiesceDSIPath(IOService *ctl,IOService *fb,OSObject *path){ + bool quiesceDSIPath(IOService *ctl,IOService *fb,OSObject *path,bool retain=false){ assert(fbLock.depth && !owner && ctl==display13FBPower.controller && fb==panel); assert(path==Display13FBMemory::singlePath(fb));++stopCalls; + if(stopOK && retain){dsiStopAttempted=true;dsiRuntimePower.sleeping=true;return true;} if(stopOK){dsiStopAttempted=true;dsiMemoryReleased=dsiDDIReleased=dsiPanelStop.complete=dsiClockStop.done=true;} return stopOK; } + bool resumeDSIPath(IOService *ctl,IOService *fb,OSObject *path){ + assert(fbLock.depth && !owner && ctl==display13FBPower.controller && fb==panel && dsiRuntimePower.sleeping); + assert(path==Display13FBMemory::singlePath(fb));++wakeCalls; + if(wakeOK){dsiRuntimePower.sleeping=false;dsiStopAttempted=false;} + return wakeOK; + } bool dsiPanelOnly=false,publicationReady=false;bool publishDSINativeMode(IOService*){return publicationReady;} bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; @@ -190,6 +198,26 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ + for(unsigned attr:{0x706F7772U,0x70777273U}){Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); + assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==1); + assert(Display13FBMemory::field(&c.fb,0x4284)==2); + xe.stopOK=true; + for(unsigned cycle=0;cycle<3;++cycle){ + assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnSuccess && xe.dsiRuntimePower.sleeping); + auto stops=xe.stopCalls; + assert(XE::wrapDSISetAttribute(&c.fb,attr,0)==kIOReturnSuccess && xe.stopCalls==stops); + assert(Display13FBMemory::field(&c.fb,0x4284)==0 && !xe.dsiMemoryReleased && !xe.dsiDDIReleased); + c.refuse();c.refuseCursor();xe.wakeOK=false; + assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnNotReady); + assert(Display13FBMemory::field(&c.fb,0x4284)==0); + xe.wakeOK=true;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess); + assert(!xe.dsiRuntimePower.sleeping && !xe.dsiStopAttempted && Display13FBMemory::field(&c.fb,0x4284)==2); + auto wakes=xe.wakeCalls;assert(XE::wrapDSISetAttribute(&c.fb,attr,2)==kIOReturnSuccess && xe.wakeCalls==wakes); + } + xe.dsiRuntimePower.failed=true;auto calls=xe.stopCalls; + assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==calls); + } + for(unsigned attr:{0x706F7772U,0x70777273U})for(unsigned state:{0U,1U}){Case c;xe.dsiStopTest=true;put(&c.fb,0x4284,2U); assert(XE::wrapDSISetAttribute(&c.fb,attr,state)==kIOReturnNotReady && xe.stopCalls==1); assert(Display13FBMemory::field(&c.fb,0x4284)==2); diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index 34337ecb..9bef982c 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -146,5 +146,6 @@ "-wex-adlp-readout": [], "-wex-adlp-load-only": [], "-wex-display13-offline": [], - "-wex-dsi-hdmi-test": [] + "-wex-dsi-hdmi-test": [], + "-wex-dsi-power-test": [] } From ce8713a522be72fd417ab58a780fc227840e6708 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:47:19 +0900 Subject: [PATCH 075/139] Preserve the active native flip surface across DSI display power --- WhateverXe/kern_xe.cpp | 9 ++++++--- tests/dsi_restart.py | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ce67e6de..cda7015a 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11083,9 +11083,12 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool p.control=io.read(0x70180);p.surface=io.read(0x7019C);p.stride=io.read(0x70188);p.size=io.read(0x70190); p.position=io.read(0x7018C);p.offset=io.read(0x701A4);p.color=io.read(0x701CC); power.cursorControl=io.read(0x70080);power.cursorPosition=io.read(0x70088);power.cursorBase=io.read(0x70084); - const auto &proof=dsiPendingBacking.proof; - const XELP::DSI::Surface::Backing backing{reinterpret_cast(dsiPendingBacking.record),proof.gpu,proof.bytes,proof.pitch}; - snapshot=XELP::DSI::Surface::target(p,dsiProfile,backing) && XELP::DSI::Surface::matches(io,p,true) && + // The published aperture is not the current native flip surface. + // As in NativeCache::readActive, retain the native mode/display pipe + // and its transaction ownership; do not free/rebind it or substitute + // the initial backing. Writers are excluded through wake. Restore only + // the latched native plane image, with the supported fixed geometry. + snapshot=XELP::DSI::Surface::shape(p,dsiProfile) && XELP::DSI::Surface::matches(io,p,true) && power.cursorControl!=UINT32_MAX && power.cursorBase!=UINT32_MAX && power.cursorPosition!=UINT32_MAX && Display13Cursor::controlWrite(0x70080,power.cursorControl)==power.cursorControl; if(snapshot){power.host=XELP::DSI::Restart::State{};power.panel=XELP::DSI::Panel::Result{};dsiPanelStop={};dsiStop={};} diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index 1f88fad4..42a8c8e2 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -90,7 +90,7 @@ assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); } void retainedCycle(Fixture &f,unsigned fault=0){ - f.io.regs[0x7B008]=0xC0000000;f.io.regs[0x70180]=0x94000008; + f.io.regs[0x7B008]=0xC0000000;f.io.regs[0x70180]=0x94001000;f.io.regs[0x7019C]=f.io.regs[0x701AC]=0x44E00000; f.io.regs[0x42084]=1U<<30;f.io.regs[0x6001C]=0x0437077F;f.io.regs[0x6B010]=f.io.regs[0x6B00C]; f.io.regs[0xC7204]=15;f.io.regs[0xC4030]=0x84;f.source.mux=f.io.regs[0x164280]; Panel::Shutdown shutdown; @@ -105,10 +105,10 @@ assert(Restart::stopQuiesced(f.io,f.source,f.p,stopped,f.allocation,1,f.state)); assert(f.state.stopped && !f.io.regs[0x46014] && f.allocation.external[0]==1 && f.allocation.binding[0]==2); Panel::Result panel;Lifecycle lifecycle{f.allocation,f.state}; - Panel::cycle(f.io,f.source,f.p,0x94000008,0,panel,lifecycle,&shutdown); + Panel::cycle(f.io,f.source,f.p,0x94001000,0x44E00000,panel,lifecycle,&shutdown); if(!panel.complete())fprintf(stderr,"resume stage=%u error=%u reg=%x host=%u restart=%u/%u\n",unsigned(panel.stage),unsigned(panel.error),panel.reg,unsigned(panel.host.error),unsigned(f.state.stage),unsigned(f.state.error)); assert(panel.complete() && f.state.started && Surface::source(f.io,panel.source)); - assert(f.io.regs[0x70180]==0x94000008 && f.io.regs[0x46014]==0xCC000000); + assert(f.io.regs[0x70180]==0x94001000 && f.io.regs[0x7019C]==0x44E00000 && f.io.regs[0x701AC]==0x44E00000 && f.io.regs[0x46014]==0xCC000000); assert(f.allocation.external[0]==1 && f.allocation.binding[0]==2); assert(f.io.regs[0x71180]==0x94000000 && f.io.regs[0x64100]==0x80000000); } From ebe342ce2589a2d1e9605aac26b18ecc6a80331c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:29:56 +0900 Subject: [PATCH 076/139] Connect DSI PWM brightness and restore deferred requests before backlight enable --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_backlight.hpp | 43 ++++++++++++++++++++ WhateverXe/kern_dsi_panel.hpp | 8 +++- WhateverXe/kern_xe.cpp | 57 ++++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 4 ++ tests/dsi_backlight.cpp | 41 +++++++++++++++++++ tests/dsi_backlight_adapter.py | 66 +++++++++++++++++++++++++++++++ tests/dsi_panel.py | 10 +++++ tests/dsi_update_dispatch.py | 8 +++- tools/test.py | 2 +- 11 files changed, 234 insertions(+), 9 deletions(-) create mode 100644 WhateverXe/kern_dsi_backlight.hpp create mode 100644 tests/dsi_backlight.cpp create mode 100644 tests/dsi_backlight_adapter.py diff --git a/README.md b/README.md index 4cf86725..57f69f2a 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Brightness control, display/system sleep, and complete panel reinitialization remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 74990342..ecac23d1 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。輝度調整、画面・システムのsleep、パネルの完全再初期化は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_backlight.hpp b/WhateverXe/kern_dsi_backlight.hpp new file mode 100644 index 00000000..96142972 --- /dev/null +++ b/WhateverXe/kern_dsi_backlight.hpp @@ -0,0 +1,43 @@ +// Native PWM backlight for the supported DSI panel profile. The Apple adapter +// owns serialization/power and keeps requested brightness across panel-off. +// PWM register layout and VBT minimum scaling follow Linux intel_backlight.c. +#ifndef KERN_DSI_BACKLIGHT_HPP +#define KERN_DSI_BACKLIGHT_HPP +#include "kern_dsi_profile.hpp" +#include "kern_xelp_panel_pwm.hpp" +namespace XELP { namespace DSI { +struct Backlight { + static constexpr uint32_t Control=0xC8250,Period=0xC8254,Duty=0xC8258,Maximum=65535; + PanelPWM pwm; + uint32_t control {},minimum {},requested {},applied {},requests {},writes {}; + bool initialized {},faulted {}; + bool capture(const Profile &p,uint32_t ctl,uint32_t period,uint32_t duty) { + if(initialized || !p.valid() || p.controller || p.precision!=8 || + ctl==UINT32_MAX || !(ctl&(1U<<31)) || !period || period==UINT32_MAX || duty>period)return false; + pwm={period,ctl&(1U<<29)};control=ctl; + const auto vbtMin=p.minimum>64?64:p.minimum; + minimum=uint32_t((uint64_t(period)*vbtMin+127)/255); + applied=duty;requested=uint32_t((uint64_t(duty)*Maximum+period/2)/period); + initialized=true;return true; + } + bool request(uintptr_t value) { + if(!initialized || faulted || value>Maximum)return false; + requested=static_cast(value);++requests;return true; + } + uint32_t target() const { + uint32_t duty=0;pwm.duty(requested,duty); + // Apple brightness zero is dark. Positive requests respect the panel's + // VBT minimum; turning the panel off is a separate lifecycle operation. + return requested && duty bool apply(IO &io) { + if(!initialized || faulted || !io.ready() || !io.readable(Control) || + !io.readable(Period) || !io.readable(Duty))return false; + if(io.read(Control)!=control || io.read(Period)!=pwm.period){faulted=true;return false;} + const auto duty=target();io.write(Duty,duty);++writes; + if(!io.ready() || io.read(Duty)!=duty){faulted=true;return false;} + applied=duty;return true; + } +}; +}} +#endif diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 0948bea5..c05b6ea1 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -5,6 +5,7 @@ #define KERN_DSI_PANEL_HPP #include "kern_dsi_commands.hpp" #include "kern_dsi_surface.hpp" +#include "kern_dsi_backlight.hpp" namespace XELP { namespace DSI { namespace Panel { struct GPIOResult {uint32_t role {},value {},reg {},before {},after {},writes {};bool complete {};}; template GPIOResult gpio(IO &io,unsigned role,unsigned value) { @@ -74,7 +75,7 @@ struct Shutdown { // IO::gpio serializes HPD writes; no delays may run under that IRQ lock. // A failed transaction is never retried and must retain all resources. template void cycle(IO &io,const Readout &link,const Profile &profile, - uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle,const Shutdown *shutdown=nullptr) { + uint32_t planeControl,uint32_t surface,Result &r,Lifecycle &lifecycle,const Shutdown *shutdown=nullptr,Backlight *backlight=nullptr) { Readout current=link; if(r.attempted)return; r.attempted=true; @@ -171,7 +172,10 @@ template void cycle(IO &io,const Readout &link,const P if(!write(0x70180,planeControl) || !write(0x7019C,surface) || !wait(0x70180,UINT32_MAX,planeControl,0) || !frame() || !wait(0x701AC,UINT32_MAX,surface,0))return; - r.stage=Stage::BacklightOn;if(!delay(1) || !sequence(6))return; + r.stage=Stage::BacklightOn; + // Restore a deferred brightness before enabling the panel's backlight GPIO. + if(backlight && !backlight->apply(io)){r.error=Error::Readback;return;} + if(!delay(1) || !sequence(6))return; if(!held() || !Surface::source(io,current) || port.failed || r.host.pending || !read(0x6B0D0,credits) || !read(0x6B0D8,lp) || (credits&0x1FFF)!=0x1040 || (lp&0x30001))return; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index cda7015a..6a632b7c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7730,6 +7730,45 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu recordDSIClient(fb,7,attribute,result); return result; } +void XE::publishDSIBacklight() { + auto &c=display13FBPower;if(!c.lock)return; + IOLockLock(c.lock);const auto &b=dsiBacklight; + uint64_t receipt[]={1,b.initialized,b.faulted,b.requested,b.applied,b.pwm.period,b.minimum, + b.control,b.requests,b.writes,dsiRuntimePower.sleeping}; + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBacklight",receipt,sizeof(receipt)); +} +// The caller holds the same framebuffer mutex as stop/resume. Deferred requests +// update only software; the panel-on sequence applies PWM before its enable GPIO. +uint32_t XE::setDSIBrightness(IOService *fb,uintptr_t value) { + auto &c=display13FBPower; + if(value>XELP::DSI::Backlight::Maximum)return kIOReturnBadArgument; + if(!c.lock || !dsiModeTarget(fb))return kIOReturnNotReady; + const bool awake=dsiNativeUpdateAllowed(fb); + IOLockLock(c.lock); + const bool owned=dsiResourcesReady && dsiBacklight.initialized && !dsiRuntimePower.failed && + !dsiBacklight.faulted && !c.memoryFaulted && !c.clockFaulted && !c.watermarkFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && + c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && + Display13FBMemory::field(fb,0x1D0)==c.controller && + c.controller==adlnTopologyController && adlnWriterMappingBound(); + const bool deferred=owned && dsiPowerTest && dsiRuntimePower.sleeping && dsiStopAttempted && + dsiStop.complete() && dsiPanelStop.complete && dsiIRQParked && !dsiMemoryReleased && !dsiDDIReleased; + struct IO:Display13FBBootIO { + bool owned;IO(IOMemoryMap *m,bool o):Display13FBBootIO{m},owned(o){} + bool ready(){return owned;} + void write(uint32_t reg,uint32_t value) { + if(owned && reg==XELP::DSI::Backlight::Duty && readable(reg)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + } io{c.map,owned && awake && !dsiStopAttempted}; + const bool accepted=owned && (deferred || (io.ready() && c.bridge.outputHeld(io,0,0))) && dsiBacklight.request(value); + const bool complete=accepted && (deferred || dsiBacklight.apply(io)); + // The donor getter returns this 16-bit requested value with range 0..65535. + if(complete)*reinterpret_cast(static_cast(c.controller)+0xE58)=dsiBacklight.requested; + IOLockUnlock(c.lock);publishDSIBacklight(); + return complete?kIOReturnSuccess:kIOReturnNotReady; +} uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint32_t attribute,uintptr_t value) { recordDSIClient(fb,8,attribute,UINT32_MAX); const auto result=[&]() -> uint32_t { @@ -7739,8 +7778,9 @@ uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { if(connection!=0)return kIOReturnUnsupported; + if(attribute==0x626B6C74)return callbackXE->setDSIBrightness(fb,value); // Controller depth and gamma scales/flush have qualified native - // CPU/color paths. In particular ignore, bklt, pnlt, psr?, powr, + // CPU/color paths. In particular ignore, pnlt, psr?, powr, // probe and DP-link configuration must never reach native DSI. switch(attribute) { case 0x00647064: // controller depth @@ -11296,7 +11336,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { void write(uint32_t r,uint32_t v) { if(readable(r) && (XELP::DSI::Restart::writable(r,pll) || r==0x6B100 || r==0x6B104 || r==0xC4030 || r==0xC7204 || r==0x70180 || r==0x7019C || r==0x7B008 || - r==0x70080 || r==0x70088 || r==0x70084)) + r==0x70080 || r==0x70088 || r==0x70084 || r==XELP::DSI::Backlight::Duty)) *reinterpret_cast(map->getVirtualAddress()+r)=v; } bool gpio(unsigned role,unsigned value) { @@ -11338,7 +11378,8 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { source=XELP::DSI::Restart::expected(source,state);return source.complete(); } } lifecycle{clock.allocation,reinterpret_cast(controller),power.host}; - XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop); + XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop, + dsiBacklight.initialized?&dsiBacklight:nullptr); if(power.panel.complete() && owned() && XELP::DSI::Surface::matches(io,power.plane,true)) { io.write(0x70080,power.cursorControl);io.write(0x70088,power.cursorPosition);io.write(0x70084,power.cursorBase); const auto before=io.read(0x70040); @@ -11383,6 +11424,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); loop->release();IORecursiveLockUnlock(mutex); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); + publishDSIBacklight(); return complete; } @@ -12477,6 +12519,15 @@ bool XE::bindDSIResources(IOService *controller) { if(ready)dsiPublishIRQLive(); if(ready && dsiCommandTest)ready=probeDSICommands(controller); if(ready && dsiPanelTest)ready=probeDSIPanel(controller); + if(ready) { + IOLockLock(c.lock); + using Light=XELP::DSI::Backlight; + if(dsiBacklight.capture(dsiProfile,io.read(Light::Control),io.read(Light::Period),io.read(Light::Duty))) { + *reinterpret_cast(reinterpret_cast(controller)+0xE58)=dsiBacklight.requested; + *reinterpret_cast(reinterpret_cast(controller)+0xE60)=dsiBacklight.pwm.period; + } + IOLockUnlock(c.lock);publishDSIBacklight(); + } return ready; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 319619a9..260cb813 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -16,6 +16,7 @@ #include "kern_dsi_clock.hpp" #include "kern_dsi_commands.hpp" #include "kern_dsi_panel.hpp" +#include "kern_dsi_backlight.hpp" #include "kern_dsi_restart.hpp" #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" @@ -151,6 +152,9 @@ class XE { bool dsiStopAttempted {},dsiIRQParked {},dsiDDIReleased {},dsiMemoryReleased {}; bool quiesceDSIPath(IOService *,IOService *,OSObject *,bool retain=false); bool resumeDSIPath(IOService *,IOService *,OSObject *); + XELP::DSI::Backlight dsiBacklight; + uint32_t setDSIBrightness(IOService *,uintptr_t); + void publishDSIBacklight(); bool processDSIPathDisposal(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool disposeDSIPath(IOService *,OSObject *); mach_vm_address_t dsiFreePlane {},dsiFreeScaler {},dsiDetachPath {},dsiRemovePath {},dsiPipeMap {}; diff --git a/tests/dsi_backlight.cpp b/tests/dsi_backlight.cpp new file mode 100644 index 00000000..95a6d223 --- /dev/null +++ b/tests/dsi_backlight.cpp @@ -0,0 +1,41 @@ +#include "kern_dsi_backlight.hpp" +#include +#include +#include +using namespace XELP::DSI; +struct IO { + bool owned=true,drop=false;unsigned writes=0; + std::map regs{{Backlight::Control,0x80000000},{Backlight::Period,480},{Backlight::Duty,154}}; + bool ready(){return owned;} + bool readable(uint32_t reg){return regs.count(reg);} + uint32_t read(uint32_t reg){return regs.at(reg);} + void write(uint32_t reg,uint32_t value){assert(reg==Backlight::Duty && owned);++writes;if(!drop)regs[reg]=value;} +}; +int main() { + Profile p;p.status=ProfileStatus::Valid;p.precision=8;p.minimum=6; + Backlight b;IO io; + assert(b.capture(p,0x80000000,480,154) && b.requested==21026 && b.minimum==11); + assert(!b.capture(p,0x80000000,480,154)); + for(auto value:{0U,1U,32768U,65535U}) { + assert(b.request(value));const auto before=io.writes; + // Deferred requests have no IO dependency and must not relight the panel. + assert(io.writes==before);assert(b.apply(io)); + assert(b.applied==(value==0?0U:value==1?11U:value==32768?240U:480U)); + } + const auto requested=b.requested; + assert(!b.request(65536) && !b.request(0x100000001ULL) && b.requested==requested); + io.owned=false;auto writes=io.writes;assert(!b.apply(io) && io.writes==writes && !b.faulted); + io.owned=true;io.regs[Backlight::Period]=481; + assert(!b.apply(io) && b.faulted && io.writes==writes && !b.request(123)); + Backlight dropped;IO bad;assert(dropped.capture(p,0x80000000,480,154)); + assert(dropped.request(0));bad.drop=true; + assert(!dropped.apply(bad) && dropped.faulted && dropped.applied==154); + for(unsigned which=0;which<5;++which) { + Backlight invalid;auto profile=p; + if(which==0)profile.controller=1;if(which==1)profile.precision=16; + assert(!invalid.capture(profile,which==2?0:0x80000000,which==3?0:480,which==4?481:154)); + } + Backlight wide;assert(wide.capture(p,0x80000000,1000000,500000)); + assert(wide.request(65535) && wide.target()==1000000); + puts("PASS DSI backlight: Apple range, VBT floor, zero duty, deferred request, 64-bit scaling, ownership/readback failure"); +} diff --git a/tests/dsi_backlight_adapter.py b/tests/dsi_backlight_adapter.py new file mode 100644 index 00000000..d152c637 --- /dev/null +++ b/tests/dsi_backlight_adapter.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Execute the production brightness adapter across retained display-off.""" +from pathlib import Path +import re, subprocess, tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'uint32_t XE::setDSIBrightness\(.*?\n\}',s,re.S)[0] +code=r''' +#include "kern_dsi_backlight.hpp" +#include +#include +#include +#include +struct IOService {alignas(8) unsigned char bytes[0x4B90]{};}; +namespace Display13FBMemory {templateT field(void *p,unsigned offset){T v;memcpy(&v,(char*)p+offset,sizeof(v));return v;}} +struct IOMemoryMap {std::vector words=std::vector(0x1000000/4);uintptr_t getVirtualAddress(){return (uintptr_t)words.data();}}; +struct Display13FBBootIO {IOMemoryMap *map;bool readable(uint32_t r){return map && r<0x1000000;}uint32_t read(uint32_t r){return map->words[r/4];}}; +unsigned depth=0,heldQueries=0;using IOLock=int; +void IOLockLock(IOLock*){assert(!depth);++depth;}void IOLockUnlock(IOLock*){assert(depth==1);--depth;} +struct Context { + IOLock *lock=nullptr;IOMemoryMap *map=nullptr;void *controller=nullptr; + bool memoryFaulted=false,clockFaulted=false,watermarkFaulted=false; + struct {struct {bool faulted=false,suspended=false;}state;bool outputHeld(Display13FBBootIO&,int,int){assert(depth);++heldQueries;return true;}}bridge; + struct {bool faulted=false;}dbuf; + struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[1]; +}display13FBPower; +bool mapping=true,awake=true;IOService *target=nullptr; +bool adlnWriterMappingBound(){return mapping;}bool dsiModeTarget(IOService *fb){return fb==target;} +constexpr uint32_t kIOReturnSuccess=0,kIOReturnBadArgument=1,kIOReturnNotReady=2; +struct XE { + XELP::DSI::Backlight dsiBacklight;bool dsiResourcesReady=true,dsiPowerTest=true,dsiStopAttempted=false; + bool dsiIRQParked=false,dsiMemoryReleased=false,dsiDDIReleased=false;void *adlnTopologyController=nullptr; + struct {bool sleeping=false,failed=false;}dsiRuntimePower; + struct {bool done=false;bool complete(){return done;}}dsiStop; + struct {bool complete=false;}dsiPanelStop; + unsigned published=0;void publishDSIBacklight(){assert(!depth);++published;} + bool dsiNativeUpdateAllowed(IOService*){assert(!depth);return awake;} + uint32_t setDSIBrightness(IOService*,uintptr_t); +}; +BODY +int main(){ + IOService fb,controller;IOLock lock;IOMemoryMap map;XE xe;auto &c=display13FBPower; + c.lock=&lock;c.map=↦c.controller=&controller;c.pipe[0].memoryFramebuffer=&fb; + xe.adlnTopologyController=&controller;target=&fb;memcpy(fb.bytes+0x1D0,&c.controller,sizeof(void*)); + XELP::DSI::Profile p;p.status=XELP::DSI::ProfileStatus::Valid;p.precision=8;p.minimum=6; + using Light=XELP::DSI::Backlight; + map.words[Light::Control/4]=0x80000000;map.words[Light::Period/4]=480;map.words[Light::Duty/4]=154; + assert(xe.dsiBacklight.capture(p,0x80000000,480,154)); + assert(xe.setDSIBrightness(&fb,32768)==0 && map.words[Light::Duty/4]==240); + assert(Display13FBMemory::field(&controller,0xE58)==32768); + awake=false;xe.dsiRuntimePower.sleeping=true;xe.dsiStopAttempted=xe.dsiIRQParked=xe.dsiStop.done=xe.dsiPanelStop.complete=true; + auto queries=heldQueries,writes=xe.dsiBacklight.writes; + assert(xe.setDSIBrightness(&fb,65535)==0 && map.words[Light::Duty/4]==240); + assert(heldQueries==queries && xe.dsiBacklight.writes==writes); + assert(Display13FBMemory::field(&controller,0xE58)==65535 && xe.dsiBacklight.requested==65535); + xe.dsiDDIReleased=true;assert(xe.setDSIBrightness(&fb,0)==kIOReturnNotReady); + xe.dsiDDIReleased=false;c.bridge.state.suspended=true;assert(xe.setDSIBrightness(&fb,0)==kIOReturnNotReady); + c.bridge.state.suspended=false;xe.dsiRuntimePower.failed=true;assert(xe.setDSIBrightness(&fb,0)==kIOReturnNotReady); + assert(xe.setDSIBrightness(&fb,0x100000001ULL)==kIOReturnBadArgument && !depth); + puts("PASS actual DSI brightness adapter: active PWM, native readback, deferred value without MMIO, retired/suspended/failed rejection"); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='dsi-backlight-adapter-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/dsi_panel.py b/tests/dsi_panel.py index 8a6eba64..9f216848 100644 --- a/tests/dsi_panel.py +++ b/tests/dsi_panel.py @@ -20,6 +20,7 @@ IO_FIXTURE PROFILE_FIXTURE struct Sim:IO { + uint32_t expectedDuty=UINT32_MAX; bool owned=true,freezeFrame=false,stuckStop=false,stuckStart=false,stuckCommand=false; uint32_t drop=UINT32_MAX,elapsed=0,headers=0,payload=0;unsigned irqDepth=0; std::vector packets,gpioTrace; @@ -52,6 +53,7 @@ if(!freezeFrame && us>=1000 && (conf&0xC0000000U)==0xC0000000U){++regs[0x70040];regs[0x701AC]=regs[0x7019C];} } bool gpio(unsigned role,unsigned value){ + if(role==2 && value && expectedDuty!=UINT32_MAX)assert(regs[Backlight::Duty]==expectedDuty); ++irqDepth;const auto r=Panel::gpio(*this,role,value);--irqDepth; if(r.complete)gpioTrace.push_back(role*2+value);return r.complete; } @@ -85,6 +87,14 @@ } int main(int argc,char **argv){ {Fixture f;stop(f);} + for(bool drop:{false,true}){Fixture f;Backlight light;Panel::RetainedHost host; + f.io.regs[Backlight::Control]=0x80000000;f.io.regs[Backlight::Period]=480;f.io.regs[Backlight::Duty]=154; + assert(light.capture(f.p,0x80000000,480,154));assert(light.request(32768)); + f.io.expectedDuty=240;if(drop)f.io.drop=Backlight::Duty; + Panel::cycle(f.io,f.link,f.p,0x94000008,0,f.result,host,nullptr,&light); + assert(f.result.complete()==!drop); + if(drop)assert(light.faulted && f.io.gpioTrace.back()!=5); + } {Fixture f;f.io.stuckCommand=true;stop(f,false);assert(f.io.gpioTrace==std::vector({4}));} {Fixture f;f.p.sequences.sequence[11]=f.p.sequences.sequence[4];stop(f,false);assert(f.io.writes.empty());} diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 1f5842ae..5c738f4e 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -93,6 +93,7 @@ uintptr_t orgDSIAttributes[4]{}; static uint32_t wrapDSISetStream(IOService*,IOService*,OSObject*,const void*); static uint32_t wrapDSISetAttribute(IOService*,uint32_t,uintptr_t); + uint32_t setDSIBrightness(IOService*,uintptr_t value){assert(fbLock.depth && !owner);return value<=65535?0U:kIOReturnUnsupported;} static uint32_t wrapDSISetConnectionAttribute(IOService*,int32_t,uint32_t,uintptr_t); static uint32_t wrapDSISetPowerState(IOService*,uint32_t); uintptr_t orgDSITransaction[5]{}; @@ -325,12 +326,17 @@ Case c;expectedAttribute=attr;expectedValue=txOptions;expectedConnection=0; assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,expectedValue)==0xe0000122U && !display13FBPower.dsiNativeWriters); assert(XE::wrapDSISetConnectionAttribute(&c.fb,1,attr,expectedValue)==kIOReturnUnsupported);} - for(unsigned attr:{0x706F7772U,0x70777273U,0x73707772U,0x64706F77U,0x706E6C70U,0x626B6C74U,0x706E6C74U, + for(unsigned attr:{0x706F7772U,0x70777273U,0x73707772U,0x64706F77U,0x706E6C70U,0x706E6C74U, 0x00696772U,0x7073723FU,0x70726F62U,0x64706972U,0xDEADBEEFU}){ Case c;const auto before=attrCalls[0]+attrCalls[1]+attrCalls[2];const auto original=c.fb; assert(XE::wrapDSISetAttribute(&c.fb,attr,0)==kIOReturnUnsupported); assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,0)==kIOReturnUnsupported); assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !memcmp(&c.fb,&original,sizeof(c.fb)) && !display13FBPower.memoryFaulted);} + {Case c;const auto before=attrCalls[1]; + assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x626B6C74,12345)==0); + assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x626B6C74,0x100000001ULL)==kIOReturnUnsupported); + assert(XE::wrapDSISetConnectionAttribute(&c.fb,1,0x626B6C74,0)==kIOReturnUnsupported); + assert(attrCalls[1]==before && !display13FBPower.dsiNativeWriters);} for(unsigned old=0;old<3;++old)for(unsigned state=0;state<4;++state){Case c;put(&c.fb,0x4284,old);const auto before=attrCalls[2]; const auto expected=old==2 && state==2?kIOReturnSuccess:kIOReturnUnsupported; assert(XE::wrapDSISetPowerState(&c.fb,state)==expected && attrCalls[2]==before); diff --git a/tools/test.py b/tools/test.py index f5586c82..1dc69b7b 100644 --- a/tools/test.py +++ b/tools/test.py @@ -9,7 +9,7 @@ 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', '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 += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] -python_tests += ['dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] +python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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 += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] From 33bf8e7b8816bddbf69b4764c6619d97f9949316 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:47:45 +0900 Subject: [PATCH 077/139] Preserve DSI user backlight preference through the native save attribute --- WhateverXe/kern_xe.cpp | 13 +++++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/dsi_backlight_adapter.py | 7 ++++++- tests/dsi_update_dispatch.py | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 6a632b7c..8ae8fa62 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7718,6 +7718,7 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu if(value>UINT32_MAX)return kIOReturnUnsupported; return wrapDSISetPowerState(fb,static_cast(value)); } + if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); // wsrv and capd: retain native server/capture bookkeeping and the // routed plane/transaction work. Other selectors need admission. if(attribute!=0x77737276 && attribute!=0x63617064)return kIOReturnUnsupported; @@ -7738,6 +7739,18 @@ void XE::publishDSIBacklight() { IOLockUnlock(c.lock); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBacklight",receipt,sizeof(receipt)); } +// Exact donor setAttribute('bksv') stores its 32-bit value at controller+E5C. +// This is user preference bookkeeping, not a hardware brightness command. +uint32_t XE::saveDSIBrightness(IOService *fb,uintptr_t value) { + auto &c=display13FBPower; + if(value>UINT32_MAX)return kIOReturnBadArgument; + if(!c.lock || !dsiModeTarget(fb))return kIOReturnNotReady; + IOLockLock(c.lock); + const bool owned=dsiResourcesReady && c.controller && c.controller==adlnTopologyController && + Display13FBMemory::field(fb,0x1D0)==c.controller && c.pipe[0].memoryFramebuffer==fb; + if(owned)*reinterpret_cast(static_cast(c.controller)+0xE5C)=static_cast(value); + IOLockUnlock(c.lock);return owned?kIOReturnSuccess:kIOReturnNotReady; +} // The caller holds the same framebuffer mutex as stop/resume. Deferred requests // update only software; the panel-on sequence applies PWM before its enable GPIO. uint32_t XE::setDSIBrightness(IOService *fb,uintptr_t value) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 260cb813..f617db31 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -154,6 +154,7 @@ class XE { bool resumeDSIPath(IOService *,IOService *,OSObject *); XELP::DSI::Backlight dsiBacklight; uint32_t setDSIBrightness(IOService *,uintptr_t); + uint32_t saveDSIBrightness(IOService *,uintptr_t); void publishDSIBacklight(); bool processDSIPathDisposal(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool disposeDSIPath(IOService *,OSObject *); diff --git a/tests/dsi_backlight_adapter.py b/tests/dsi_backlight_adapter.py index d152c637..7de98ad3 100644 --- a/tests/dsi_backlight_adapter.py +++ b/tests/dsi_backlight_adapter.py @@ -4,7 +4,7 @@ import re, subprocess, tempfile r=Path(__file__).resolve().parents[1] s=(r/'WhateverXe/kern_xe.cpp').read_text() -body=re.search(r'uint32_t XE::setDSIBrightness\(.*?\n\}',s,re.S)[0] +body='\n'.join(re.search(r'uint32_t XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in ['setDSIBrightness','saveDSIBrightness']) code=r''' #include "kern_dsi_backlight.hpp" #include @@ -36,6 +36,7 @@ unsigned published=0;void publishDSIBacklight(){assert(!depth);++published;} bool dsiNativeUpdateAllowed(IOService*){assert(!depth);return awake;} uint32_t setDSIBrightness(IOService*,uintptr_t); + uint32_t saveDSIBrightness(IOService*,uintptr_t); }; BODY int main(){ @@ -53,6 +54,10 @@ assert(xe.setDSIBrightness(&fb,65535)==0 && map.words[Light::Duty/4]==240); assert(heldQueries==queries && xe.dsiBacklight.writes==writes); assert(Display13FBMemory::field(&controller,0xE58)==65535 && xe.dsiBacklight.requested==65535); + assert(xe.saveDSIBrightness(&fb,12345)==0); + assert(Display13FBMemory::field(&controller,0xE5C)==12345); + assert(xe.dsiBacklight.requested==65535 && xe.dsiBacklight.writes==writes); + assert(xe.saveDSIBrightness(&fb,0x100000001ULL)==kIOReturnBadArgument); xe.dsiDDIReleased=true;assert(xe.setDSIBrightness(&fb,0)==kIOReturnNotReady); xe.dsiDDIReleased=false;c.bridge.state.suspended=true;assert(xe.setDSIBrightness(&fb,0)==kIOReturnNotReady); c.bridge.state.suspended=false;xe.dsiRuntimePower.failed=true;assert(xe.setDSIBrightness(&fb,0)==kIOReturnNotReady); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 5c738f4e..83201031 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -93,6 +93,7 @@ uintptr_t orgDSIAttributes[4]{}; static uint32_t wrapDSISetStream(IOService*,IOService*,OSObject*,const void*); static uint32_t wrapDSISetAttribute(IOService*,uint32_t,uintptr_t); + uint32_t saveDSIBrightness(IOService*,uintptr_t value){assert(fbLock.depth && !owner);return value<=UINT32_MAX?0U:kIOReturnUnsupported;} uint32_t setDSIBrightness(IOService*,uintptr_t value){assert(fbLock.depth && !owner);return value<=65535?0U:kIOReturnUnsupported;} static uint32_t wrapDSISetConnectionAttribute(IOService*,int32_t,uint32_t,uintptr_t); static uint32_t wrapDSISetPowerState(IOService*,uint32_t); @@ -332,6 +333,10 @@ assert(XE::wrapDSISetAttribute(&c.fb,attr,0)==kIOReturnUnsupported); assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,0)==kIOReturnUnsupported); assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !memcmp(&c.fb,&original,sizeof(c.fb)) && !display13FBPower.memoryFaulted);} + {Case c;xe.dsiStopAttempted=true;const auto before=attrCalls[0]; + assert(XE::wrapDSISetAttribute(&c.fb,0x626B7376,123)==0); + assert(XE::wrapDSISetAttribute(&c.fb,0x626B7376,0x100000001ULL)==kIOReturnUnsupported); + assert(attrCalls[0]==before && !display13FBPower.dsiNativeWriters);} {Case c;const auto before=attrCalls[1]; assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x626B6C74,12345)==0); assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,0x626B6C74,0x100000001ULL)==kIOReturnUnsupported); From fd2507cc4737e27046d835473911c6a412d16e8b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 12:55:35 +0900 Subject: [PATCH 078/139] Observe bounded DSI attribute history for brightness UI routing --- WhateverXe/kern_xe.cpp | 21 +++++++++++++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/dsi_update_dispatch.py | 1 + 3 files changed, 23 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 8ae8fa62..e986573b 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7729,8 +7729,28 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu endDSINativeWriter(token);return result; }(); recordDSIClient(fb,7,attribute,result); + recordDSIAttribute(fb,7,attribute,value,result); return result; } +// Bounded CPU-only history: the last attribute alone hides earlier rejected UI +// requests behind a successful gamma flush. No MMIO or object pointers are exposed. +void XE::recordDSIAttribute(IOService *fb,unsigned event,uint32_t attribute,uintptr_t value,uint32_t result) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->adlnOutputWiring.dsi || !dsiModeTarget(fb) || !c.lock)return; + DSIModeScope scope(fb);if(!scope.lock)return; + static uint64_t history[2+16*8] {1,0}; + IOLockLock(c.lock); + auto controller=Display13FBMemory::field(fb,0x1D0); + if(controller && controller==c.controller && controller==s->adlnTopologyController) { + const auto sequence=++history[1];auto row=history+2+((sequence-1)%16)*8; + row[0]=sequence;row[1]=event;row[2]=attribute;row[3]=value<=UINT32_MAX?value:UINT64_MAX;row[4]=result; + row[5]=Display13FBMemory::field(controller,0xE58); + row[6]=Display13FBMemory::field(controller,0xE5C); + row[7]=Display13FBMemory::field(fb,0x44E0); + } + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIAttributeHistory",history,sizeof(history)); +} void XE::publishDSIBacklight() { auto &c=display13FBPower;if(!c.lock)return; IOLockLock(c.lock);const auto &b=dsiBacklight; @@ -7808,6 +7828,7 @@ uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint endDSINativeWriter(token);return result; }(); recordDSIClient(fb,8,attribute,result); + recordDSIAttribute(fb,8,attribute,value,result); return result; } uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f617db31..5f30475b 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -156,6 +156,7 @@ class XE { uint32_t setDSIBrightness(IOService *,uintptr_t); uint32_t saveDSIBrightness(IOService *,uintptr_t); void publishDSIBacklight(); + static void recordDSIAttribute(IOService *,unsigned,uint32_t,uintptr_t,uint32_t); bool processDSIPathDisposal(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool disposeDSIPath(IOService *,OSObject *); mach_vm_address_t dsiFreePlane {},dsiFreeScaler {},dsiDetachPath {},dsiRemovePath {},dsiPipeMap {}; diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 83201031..b67fda31 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -90,6 +90,7 @@ bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; static void recordDSIClient(IOService*,unsigned,uint64_t,uint32_t){}; + static void recordDSIAttribute(IOService*,unsigned,uint32_t,uintptr_t,uint32_t){}; uintptr_t orgDSIAttributes[4]{}; static uint32_t wrapDSISetStream(IOService*,IOService*,OSObject*,const void*); static uint32_t wrapDSISetAttribute(IOService*,uint32_t,uintptr_t); From b06317e4fdb0db8ca9ff5879004b89bf4f1e06a5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:05:10 +0900 Subject: [PATCH 079/139] Declare DSI descriptor model year for native brightness controls --- WhateverXe/kern_dsi_apple_timing.hpp | 5 ++++- tests/dsi_apple_timing.cpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index e7412b17..9fd5ab8e 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -50,7 +50,7 @@ inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned inde inline bool acceptsMode(uint32_t mode,uint32_t clientMode) { return mode==FixedModeID || mode==clientMode; } -// Synthetic Apple enumeration descriptor derived only from the panel profile. +// Synthetic Apple enumeration descriptor with timing from the panel profile. // EDID wire layout reference: Linux v6.12 include/drm/drm_edid.h. This is not // an EDID read from the panel and does not advertise DP/HDMI, HDR or DPMS. inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128]) { @@ -66,6 +66,9 @@ inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128]) { const uint8_t header[]={0,255,255,255,255,255,255,0};memcpy(edid,header,8); edid[8]=0x5C;edid[9]=0xB8; // synthetic WEX identity, no panel serial edid[10]=0x10;edid[11]=0xD5; + // EDID 1.4 model year of this synthetic descriptor, not panel manufacture. + // A zero year selects Apple's pre-2007 non-linear-only backlight path. + edid[16]=0xFF;edid[17]=2026-1990; edid[18]=1;edid[19]=4;edid[20]=0xA0; // digital RGB8, transport unspecified edid[21]=static_cast((m.widthMM+5)/10); edid[22]=static_cast((m.heightMM+5)/10); diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index ba8fdd04..d78f292e 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -30,6 +30,7 @@ int main() { assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); + assert(edid[16]==0xFF && edid[17]>=17); // Model year enables native linear brightness. assert(edid[18]==1 && edid[19]==4 && edid[20]==0xA0 && edid[24]==2 && !edid[126]); const auto d=edid+54; assert((unsigned(d[0])+(unsigned(d[1])<<8))*10==135260); From 258365af39906cc02fb0972565d25d4e46064e50 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:36:01 +0900 Subject: [PATCH 080/139] Test DSI quarter-turn geometry and logical buffer ownership --- tests/dsi_native_mode_fixture.cpp | 63 ++++++++++++++++++++++++++++++- tools/test.py | 1 + 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index 04f2050c..0784a4ac 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -1,6 +1,8 @@ // Execute the real donor CPU helper when its exact binary is supplied. #include "kern_dsi_native_mode.hpp" +#include "kern_dsi_native_backing.hpp" #include +#include #include #include #include @@ -12,8 +14,27 @@ templatevoid put(void *p,unsigned offset,T value){memcpy(static_cast(raw); + if(selector==reinterpret_cast(1)) { + ++logicalAllocations;assert(r.kind==2 && r.columns && r.rows); + if(logicalResult)return logicalResult; + r.pitch=r.columns*128;r.gpu=0x100000;r.map=0x200000;r.memory=0x300000; + } else {assert(selector==reinterpret_cast(2));++logicalFrees;} + return 0; +} void patchCall(uint8_t *image,unsigned call,uintptr_t function) { - const unsigned stub=0x200000;image[stub]=0x48;image[stub+1]=0xB8; + static unsigned nextStub=0x200000; + const unsigned stub=nextStub;nextStub+=16;assert(nextStub<0x201000); + image[stub]=0x48;image[stub+1]=0xB8; memcpy(image+stub+2,&function,8);image[stub+10]=0xFF;image[stub+11]=0xE0; const int32_t delta=stub-call-5;assert(image[call]==0xE8);memcpy(image+call+1,&delta,4); } @@ -77,7 +98,45 @@ int main(int argc,char **argv) { assert(adjust(controller,fb,path,true,nullptr)==135255000); put(scaler,0x4C,0U); // Disabled boot scaler also leaves Hz unchanged. assert(adjust(controller,fb,path,false,nullptr)==135255000); - assert(notifications==4);munmap(image,0x400000);puts("DSI native mode geometry and logical-buffer replay passed"); + assert(notifications==4); + // Quarter-turns use a separate GPU logical buffer, not a rotated + // hardware scanout. Exercise the exact geometry/allocation/release + // bodies; only kernel heap and accelerator allocation are mocked. + patchCall(image,0x31790,reinterpret_cast(allocateRecord)); + patchCall(image,0x317A5,reinterpret_cast(zero)); + patchCall(image,0x330E3,reinterpret_cast(freeRecord)); + uintptr_t gpuVtable[0x6C0/8]{};gpuVtable[0x6B8/8]=reinterpret_cast(logicalCallback); + auto gpuObject=gpuVtable;put(controller,0xC0,&gpuObject); + put(fb,0x420C,2U);put(fb,0x4AD8,uintptr_t(1));put(fb,0x4AE0,uintptr_t(2)); + put(scaler,0x10,fb); + const auto allocateLogical=reinterpret_cast(image+0x31726); + const auto releaseLogical=reinterpret_cast(image+0x33062); + for(uint32_t rotation:{0x30U,0x50U})for(bool hidpi:{false,true}) { + auto presentation=t;presentation.scalerFlags=rotation; + if(hidpi){presentation.horizontalScaled=1620;presentation.verticalScaled=2880;} + compute(fb,&presentation); + assert(field(fb,0x430C)==(hidpi?1620U:1080U)); + assert(field(fb,0x4310)==(hidpi?2880U:1920U)); + for(unsigned offset:{0x4320U,0x4328U})assert(field(fb,offset)==1080); + for(unsigned offset:{0x4324U,0x432CU})assert(field(fb,offset)==1920); + assert(field(fb,0x4338)==(hidpi?0x30U:0x10U)); + assert(field(fb,0x4334)==(hidpi?6528U:4352U)); + assert(field(path,0xF8)==1080 && field(path,0xFC)==1920); + assert(allocateLogical(fb)==0); + auto record=field(fb,0x4340); + assert(record && record->pitch==(hidpi?6528U:4352U)); + assert(record->bytes==record->pitch*(hidpi?2880U:1920U)); + assert(!field(fb,0x4348)); // Logical allocation does not own the scanout. + releaseLogical(fb);assert(!field(fb,0x4340)); + } + // A failing accelerator callback still publishes the heap record. + // A future mode adapter must explicitly retire it before rollback. + logicalResult=0xE00002BD;assert(allocateLogical(fb)==logicalResult); + assert(field(fb,0x4340));releaseLogical(fb); + logicalResult=0;failRecord=true;assert(allocateLogical(fb)==0xE00002BD); + assert(!field(fb,0x4340)); + assert(logicalAllocations==5 && logicalFrees==5 && recordAllocations==5 && recordFrees==5); + munmap(image,0x400000);puts("DSI native geometry: fixed scanout, quarter-turn logical buffers, HiDPI and failure cleanup replay passed"); } puts("DSI native mode preparation rollback and admission passed"); } diff --git a/tools/test.py b/tools/test.py index 1dc69b7b..95cff162 100644 --- a/tools/test.py +++ b/tools/test.py @@ -16,6 +16,7 @@ if a.donor:jobs += [('dsi_path_disposal_native',[sys.executable,str(R/'tests/dsi_path_disposal.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_native_cache_native',[sys.executable,str(R/'tests/dsi_native_cache.py'),str(a.donor.resolve())],None)] +if a.donor:jobs += [('dsi_native_mode_native',[sys.executable,str(R/'tests/dsi_native_mode.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_boot_probe_native',[sys.executable,str(R/'tests/dsi_boot_probe.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_port_adapter_native',[sys.executable,str(R/'tests/dsi_port_adapter.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_apple_adapter_native',[sys.executable,str(R/'tests/dsi_apple_adapter.py'),str(a.donor.resolve())],None)] From ac23108985dfc0bd5fb0c094c61ed8820e73d64a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 13:56:32 +0900 Subject: [PATCH 081/139] Capture display geometry on first watermark latch failure --- WhateverXe/kern_xe.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e986573b..ee72c876 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11861,6 +11861,31 @@ void XE::display13WMArmUpdate(IOService *fb) { result.pipeControl,result.planeControl,result.cursorControl,result.checkRegister,result.checkValue, result.clockHz,actualHz,result.frameBefore,result.frameAfter,result.lastReg,result.expected,result.observed}; adlnPublishMemory("N97FBWMLatchFailure",pipe,proof,sizeof(proof),true); + // Retain the software source/destination geometry with the first + // failed latch. OS orientation is independent of DSI wire timing; + // distinguish a native scaling request from a link/stride mismatch. + uint64_t geometry[48] {1,pipe,c.pipe[pipe].wm[0].width,c.pipe[pipe].wm[0].height, + c.pipe[pipe].wm[0].htotal,c.pipe[pipe].wm[0].pixelHz,actualHz}; + const unsigned fbOffsets[]={0x430C,0x4310,0x4314,0x4320,0x4324,0x4328,0x432C,0x4334,0x4338}; + for(unsigned i=0;i(fb,fbOffsets[i]); + if(path && path->getMetaClass()->getClassSize()>=0x3730) { + const unsigned pathOffsets[]={0xF8,0xFC,0x108,0x10C,0x138,0x144}; + for(unsigned i=0;i(path,pathOffsets[i]); + auto plane=Display13FBMemory::field(path,0x32C8); + auto scaler=Display13FBMemory::field(path,0x32C0); + if(plane && plane->getMetaClass()->getClassSize()>=0x1C8) { + const unsigned planeOffsets[]={0x100,0x104,0x108,0x10C,0x110,0x114,0x118,0x11C,0x120,0x124}; + for(unsigned i=0;i(plane,planeOffsets[i]); + } + if(scaler && scaler->getMetaClass()->getClassSize()>=0x58) + for(unsigned i=0;i<6;++i)geometry[32+i]=Display13FBMemory::field(scaler,0x40+4*i); + } + Display13FBDBUFIO geometryIO {c.map}; + if(Display13FBIRQ::powered(geometryIO.read(0x45404),pipe)) { + const unsigned registers[]={0x70180,0x70188,0x7018C,0x70190,0x7019C,0x701A4,0x701AC,0x6001C}; + for(unsigned i=0;i Date: Fri, 30 Oct 2026 14:12:18 +0900 Subject: [PATCH 082/139] Preserve DSI rotation requests through native GPU presentation --- WhateverXe/kern_display13_fb_memory.hpp | 15 ++- WhateverXe/kern_dsi_apple_timing.hpp | 9 ++ WhateverXe/kern_dsi_native_mode.hpp | 13 +- WhateverXe/kern_xe.cpp | 167 ++++++++++++++++++++---- WhateverXe/kern_xe.hpp | 5 +- tests/display13_fb_memory.cpp | 5 + tests/dsi_apple_adapter.py | 18 ++- tests/dsi_apple_timing.cpp | 10 ++ tests/dsi_native_mode_fixture.cpp | 8 +- tests/dsi_presentation.py | 96 ++++++++++++++ tests/dsi_surface_adapter.py | 1 + tests/dsi_update_dispatch.py | 8 +- tools/test.py | 2 +- 13 files changed, 311 insertions(+), 46 deletions(-) create mode 100644 tests/dsi_presentation.py diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index 01def8ee..52c2a0d5 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -101,11 +101,11 @@ template inline T singlePath(const void *framebuffer) { // private object. Pool zero is the donor's pre-existing stolen allocation. inline bool associations(const void *controller, size_t controllerBytes, const void *framebuffer, size_t framebufferBytes, uintptr_t expectedOwner=0, - bool nativeSurface=false) { + bool nativeSurface=false,uintptr_t logicalSurface=0) { if (!controller || !framebuffer || controllerBytes<0x1C20 || framebufferBytes<0x4B90 || field(framebuffer,0x1D0)!=reinterpret_cast(controller) || field(controller,0xC0)!=expectedOwner || field(controller,0xE49) || - field(framebuffer,0x4340) || + field(framebuffer,0x4340)!=logicalSurface || (logicalSurface && !expectedOwner) || (field(framebuffer,0x4348) && (!nativeSurface || !expectedOwner))) return false; const auto index=field(framebuffer,0x1DC),count=field(controller,0xD5C); return count && count<=3 && index SurfaceReadout inspectSurfaceRecord(IO &io,const void *controller,size_t controllerBytes, const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit, - const XELPMemory::Layout &memory,const void *surface,const void *attached) { + const XELPMemory::Layout &memory,const void *surface,const void *attached,uintptr_t logicalSurface=0) { SurfaceReadout r;r.reserve=reserve; - if(!owner || !associations(controller,controllerBytes,fb,fbBytes,owner,true)) { + if(!owner || !associations(controller,controllerBytes,fb,fbBytes,owner,true,logicalSurface)) { r.status=SurfaceStatus::Association;return r; } if(field(fb,0x4348)!=attached) {r.status=SurfaceStatus::Association;return r;} @@ -325,16 +325,17 @@ template SurfaceReadout inspectSurfaceRecord(IO &io,const void *contro } ++r.verifiedPages; } - if(!associations(controller,controllerBytes,fb,fbBytes,owner,true) || + if(!associations(controller,controllerBytes,fb,fbBytes,owner,true,logicalSurface) || field(fb,0x4348)!=attached || memcmp(snapshot,surface,sizeof(snapshot)) || !io.memoryReady(r.map,r.memory,owner,r.bytes)) { r.status=SurfaceStatus::StateChanged;return r; } r.status=SurfaceStatus::Complete;return r; } template SurfaceReadout inspectSurface(IO &io,const void *controller,size_t controllerBytes, - const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit,const XELPMemory::Layout &memory) { + const void *fb,size_t fbBytes,uintptr_t owner,uint64_t reserve,uint64_t gpuLimit,const XELPMemory::Layout &memory, + uintptr_t logicalSurface=0) { const auto surface=fb && fbBytes>=0x4B90 ? field(fb,0x4348) : nullptr; - return inspectSurfaceRecord(io,controller,controllerBytes,fb,fbBytes,owner,reserve,gpuLimit,memory,surface,surface); + return inspectSurfaceRecord(io,controller,controllerBytes,fb,fbBytes,owner,reserve,gpuLimit,memory,surface,surface,logicalSurface); } // First DSI handoff: the real FB still owns stolen slot zero while the GPU // allocator owns a pending record beyond the reserved prefix. This proves diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 9fd5ab8e..492c57d4 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -50,6 +50,15 @@ inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned inde inline bool acceptsMode(uint32_t mode,uint32_t clientMode) { return mode==FixedModeID || mode==clientMode; } +// Presentation changes never alter the qualified DSI wire timing. Initially +// admit only native-size quarter-turns; scaled sources are a separate stage. +inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigned index) { + if(offered.scalerFlags!=0x30 && offered.scalerFlags!=0x50)return false; + if((offered.horizontalScaled || offered.verticalScaled) && + (offered.horizontalScaled!=fixed.horizontalActive || offered.verticalScaled!=fixed.verticalActive))return false; + auto native=offered;native.scalerFlags=0;native.horizontalScaled=0;native.verticalScaled=0; + return clientTiming(fixed,native,index); +} // Synthetic Apple enumeration descriptor with timing from the panel profile. // EDID wire layout reference: Linux v6.12 include/drm/drm_edid.h. This is not // an EDID read from the panel and does not advertise DP/HDMI, HDR or DPMS. diff --git a/WhateverXe/kern_dsi_native_mode.hpp b/WhateverXe/kern_dsi_native_mode.hpp index 469cf11e..d7ffa13b 100644 --- a/WhateverXe/kern_dsi_native_mode.hpp +++ b/WhateverXe/kern_dsi_native_mode.hpp @@ -6,14 +6,17 @@ #include "kern_display13_fb_memory.hpp" namespace XELP { namespace DSI { namespace AppleAdapter { // The caller holds the FB mutex and validates object classes/ownership. No -// scaled mode is admitted: native allocation would need a different contract. +// resized mode is admitted. Quarter-turn preparation retains the physical +// scanout; its separate logical buffer must be retired by the caller first. template bool prepareGeometry(void *fb,void *path,const Profile &profile, - const Timing &timing,Compute compute) { + const Timing &timing,Compute compute,bool presentation=false) { using Display13FBMemory::field; Timing expected; - if(!fb || !path || !makeTiming(profile,expected) || memcmp(&expected,&timing,sizeof(timing)) || + if(!makeTiming(profile,expected))return false; + if(presentation && (timing.scalerFlags==0x30 || timing.scalerFlags==0x50))expected.scalerFlags=timing.scalerFlags; + if(!fb || !path || memcmp(&expected,&timing,sizeof(timing)) || Display13FBMemory::singlePath(fb)!=path || field(fb,0x4340) || - field(fb,0x4348))return false; + (!presentation && field(fb,0x4348)))return false; // Preserve the native CPU state if the donor produces an unsupported // transform. The helper writes only these geometry fields for this mode. uint8_t savedFB[0x30],savedPath[0x20]; @@ -28,7 +31,7 @@ template bool prepareGeometry(void *fb,void *path,const Profile & field(fb,0x4314)==pitch*m.height && field(fb,0x4320)==m.width && field(fb,0x4324)==m.height && field(fb,0x4328)==m.width && field(fb,0x432C)==m.height && field(fb,0x4334)==pitch && - field(fb,0x4338)==1 && !field(path,0xF0) && + field(fb,0x4338)==(timing.scalerFlags ? 0x10U : 1U) && !field(path,0xF0) && field(path,0xF8)==m.width && field(path,0xFC)==m.height && !field(path,0x100) && field(path,0x108)==m.width && field(path,0x10C)==m.height; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ee72c876..69afe725 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4678,6 +4678,11 @@ struct DSIFixedModeOwner { OSData *data {}; XELP::DSI::Mode mode; XELP::DSI::AppleAdapter::Timing timing; + XELP::DSI::AppleAdapter::Timing presentation,offered[8]; + unsigned offerCount {}; + uint32_t activeMode {XELP::DSI::AppleAdapter::FixedModeID}; + uintptr_t changingThread {},logical {}; + bool presentationReady {},presentationFault {}; uint32_t clientMode {XELP::DSI::AppleAdapter::FixedModeID}; bool ready {}; }; @@ -4685,6 +4690,24 @@ static DSIFixedModeOwner dsiFixedMode; static bool dsiModeTarget(IOService *fb) { return fb && __atomic_load_n(&dsiFixedMode.framebuffer,__ATOMIC_ACQUIRE)==fb; } +static uintptr_t dsiLogicalSurface(const void *fb) { + const auto &o=dsiFixedMode; + return fb && __atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)==fb ? + __atomic_load_n(&o.logical,__ATOMIC_ACQUIRE) : 0; +} +static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { + auto &o=dsiFixedMode; + if(o.changingThread && o.presentationReady && mode==o.activeMode)return &o.presentation; + for(unsigned i=0;igetProperty("IOFBScalerInfo")); + if(!data || data->getLength()!=48)return; + uint32_t scaler[12];memcpy(scaler,data->getBytesNoCopy(),sizeof(scaler)); + if(!(scaler[4]&0x20)){scaler[4]|=0x20;fb->setProperty("IOFBScalerInfo",scaler,sizeof(scaler));} +} struct DSIModeScope { IORecursiveLock *lock; explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { @@ -4795,7 +4818,7 @@ static uint32_t adlnSharedGT(const void *controller) { // validation; they never follow native map objects or read GGTT entries. static bool display13FBAssociations(const void *controller,size_t controllerBytes, const void *fb,size_t fbBytes,uintptr_t owner) { - if(!Display13FBMemory::associations(controller,controllerBytes,fb,fbBytes,owner,true))return false; + if(!Display13FBMemory::associations(controller,controllerBytes,fb,fbBytes,owner,true,dsiLogicalSurface(fb)))return false; const auto surface=Display13FBMemory::field(fb,0x4348); return Display13FBMemory::publishedSurface(display13FBPower.pipe,fb,surface); } @@ -5294,7 +5317,7 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo Display13SurfaceIO io {c.map,pipe}; const auto reserve=owner ? Display13FBMemory::field(reinterpret_cast(owner),0x13C0) : 0; c.pipe[pipe].scanout=Display13FBMemory::inspectSurface(io,controller,controller->getMetaClass()->getClassSize(), - fb,fb->getMetaClass()->getClassSize(),owner,reserve,(c.map->getLength()-0x800000)/8*4096,xelpMemory); + fb,fb->getMetaClass()->getClassSize(),owner,reserve,(c.map->getLength()-0x800000)/8*4096,xelpMemory,dsiLogicalSurface(fb)); adlnPublishMemory("N97FBAcceleratorBacking",pipe,&c.pipe[pipe].scanout,sizeof(c.pipe[pipe].scanout)); const auto prefix=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); const bool dualReservation=prefix && prefix==reserve && adlnSysMemOwner(controller,Display13FBMemory::pool(controller)); @@ -5847,7 +5870,7 @@ bool XE::dsiArmedBackingOwned(IOService *fb,OSObject *path) { bool XE::retireDSINativeBacking(IOService *fb,OSObject *path) { if(!dsiPendingTarget(fb))return true; if(!dsiStop.complete() || !dsiIRQParked || !dsiClockStop.complete() || !dsiDDIReleased || - !dsiArmedBackingOwned(fb,path))return false; + !dsiArmedBackingOwned(fb,path) || !releaseDSILogical(fb))return false; if(dsiSession.state!=XELP::DSI::SessionState::Released) { const XELP::DSI::StopProof proof{dsiSession.ticket(),dsiBoot.source.identity,true,true,true,true}; if(!dsiSession.stopped(proof))return false; @@ -7238,9 +7261,10 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad dsiComputeGeometry=compute; const char *modeSymbols[]={"__ZN21AppleIntelFramebuffer21allocateLogicalBufferEv", "__ZN21AppleIntelFramebuffer28setupInitialTransactionStateEj", - "__ZN21AppleIntelFramebuffer18setTransactionCapsEv"}; - const uint32_t modeOffsets[]={0x31726,0x318E8,0x319D2}; - for(unsigned i=0;i<3;++i) { + "__ZN21AppleIntelFramebuffer18setTransactionCapsEv", + "__ZN21AppleIntelFramebuffer20releaseLogicalBufferEv"}; + const uint32_t modeOffsets[]={0x31726,0x318E8,0x319D2,0x33062}; + for(unsigned i=0;i(index,modeSymbols[i],address,size);patcher.clearError(); if(fn!=address+modeOffsets[i] || memcmp(reinterpret_cast(fn),entry,4))return false; dsiModeCPU[i]=fn; @@ -7322,7 +7346,7 @@ bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { bool XE::releaseDSIFixedMode(IOService *fb) { auto &o=dsiFixedMode; if(!o.framebuffer)return true; - if(!dsiModeTarget(fb) || o.ready || !orgDSIMode[0])return false; + if(!dsiModeTarget(fb) || o.ready || o.logical || !orgDSIMode[0])return false; auto raw=Display13FBMemory::field(fb,0x4260); auto property=fb->getProperty("IOFBDetailedTimings"); if((raw && raw!=o.array) || (property && property!=o.array))return false; @@ -7332,6 +7356,7 @@ bool XE::releaseDSIFixedMode(IOService *fb) { auto array=o.array;auto data=o.data; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); o.array=nullptr;o.data=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; + o.presentation={};o.offerCount=0;o.activeMode=o.clientMode;o.presentationReady=false;o.presentationFault=false;o.changingThread=0; OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; } @@ -7379,7 +7404,7 @@ uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,u path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); uint8_t descriptor[128]; const bool described=ready && XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,descriptor); - if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));} + if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));dsiAdvertiseRotation(fb);} const auto result=described ? kIOReturnSuccess : kIOReturnNotReady; recordDSIClient(fb,11,*online,result);return result; } @@ -7393,9 +7418,10 @@ uint32_t XE::wrapDSIGetCurrentMode(IOService *fb,int32_t *mode,int32_t *depth) { if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); DSIModeScope scope(fb); const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && - Display13FBMemory::field(fb,0x1E0) && dsiNativeUpdateAllowed(fb); + Display13FBMemory::field(fb,0x1E0) && + ((dsiFixedMode.changingThread && dsiFixedMode.changingThread==reinterpret_cast(current_thread())) || dsiNativeUpdateAllowed(fb)); if(ready) { - const uint32_t fixed=dsiFixedMode.clientMode; + const uint32_t fixed=dsiFixedMode.presentationReady ? dsiFixedMode.activeMode : dsiFixedMode.clientMode; memcpy(reinterpret_cast(fb)+0x4250,&fixed,sizeof(fixed)); if(mode)*mode=fixed; if(depth)*depth=Display13FBMemory::field(fb,0x4254); @@ -7409,27 +7435,33 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { const auto result=[&]() -> uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - uint32_t client=0;uint64_t offer[16] {2,array ? array->getCount() : 0,UINT32_MAX}; + uint32_t client=0;unsigned count=0; + XELP::DSI::AppleAdapter::Timing accepted[8]; + uint64_t offer[16] {3,array ? array->getCount() : 0,UINT32_MAX}; if(array && array->getCount()<=1024)for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto &t=*static_cast(bytes->getBytesNoCopy()); - if(!XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i))continue; - client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; + const bool native=XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i); + if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i))continue; + if(count==arrsize(accepted))return kIOReturnNoResources; + accepted[count++]=t;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; - offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled;break; + offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled; } IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingOffer",offer,sizeof(offer)); auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; const bool canonical=data && data->getLength()==sizeof(dsiFixedMode.timing) && !memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)); - if(!client && !canonical)return kIOReturnUnsupported; - // Preserve the immutable native timing/array. The client ID is only - // an alias of that exact retained link; other offered modes fail validation. + if(!count && !canonical)return kIOReturnUnsupported; + // Native storage retains the immutable wire mode. Qualified presentation + // timings are returned by validation, without changing the DSI link. const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); if(result==kIOReturnSuccess) { if(!dsiModeCoherent(fb))return kIOReturnNotReady; + dsiFixedMode.offerCount=count; + memcpy(dsiFixedMode.offered,accepted,count*sizeof(accepted[0])); if(client)dsiFixedMode.clientMode=client; } return result; @@ -7443,10 +7475,13 @@ uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **descrip if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); if(description)*description=nullptr;if(timing)*timing=nullptr; DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode))return kIOReturnBadArgument; + const auto selected=dsiPresentation(mode);if(!selected)return kIOReturnBadArgument; const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])( fb,XELP::DSI::AppleAdapter::FixedModeID,description,timing); - if(!result && timing && *timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; + if(!result && timing) { + if(*timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; + if(selected->scalerFlags)*timing=selected; + } return result; }(); recordDSIClient(fb,2,static_cast(mode),result); @@ -7458,7 +7493,8 @@ uint32_t XE::wrapDSIGetModes(IOService *fb,int32_t *modes) { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; if(!modes)return kIOReturnBadArgument; - *modes=dsiFixedMode.clientMode;return kIOReturnSuccess; + if(dsiFixedMode.offerCount)for(unsigned i=0;i uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeCount,callbackXE->orgDSIMode[3])(fb); - DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? 1 : 0; + DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? (dsiFixedMode.offerCount ? dsiFixedMode.offerCount : 1) : 0; }(); recordDSIClient(fb,4,0,result); return result; @@ -7477,7 +7513,7 @@ uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { const auto result=[&]() -> uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!raw || !XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode))return kIOReturnBadArgument; + if(!raw || !dsiPresentation(mode))return kIOReturnBadArgument; const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, *static_cast(raw)))return kIOReturnNotReady; @@ -7581,6 +7617,77 @@ static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { return attached!=path || dsiCachePath(attached); } +// Native logical storage belongs to the same GPU pipe as the retained scanout. +// Never free the scanout or modify the DSI host while changing presentation. +bool XE::releaseDSILogical(IOService *fb) { + auto &o=dsiFixedMode;auto pipe=dsiPendingBacking.pipe; + if(!dsiModeTarget(fb) || !pipe || !dsiModeCPU[3] || + Display13FBMemory::field(fb,0x4340)!=o.logical)return false; + if(!o.logical)return true; + reinterpret_cast(dsiModeCPU[3])(fb); + const bool released=!Display13FBMemory::field(fb,0x4340) && + !Display13FBMemory::field(pipe,0x12B8) && !Display13FBMemory::field(pipe,0x12C0); + if(released)__atomic_store_n(&o.logical,uintptr_t(0),__ATOMIC_RELEASE); + else o.presentationFault=true; + return released; +} + +uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::AppleAdapter::Timing &requested) { + using Display13FBMemory::field; + auto &o=dsiFixedMode;auto &c=display13FBPower; + if(!dsiModeTarget(fb) || !dsiModeCoherent(fb) || !dsiNativeUpdateAllowed(fb) || o.changingThread)return kIOReturnNotReady; + auto path=Display13FBMemory::singlePath(fb);auto pipe=dsiPendingBacking.pipe; + if(!path || !pipe || !dsiArmedBackingOwned(fb,path))return kIOReturnNotReady; + const auto old=o.presentationReady ? o.presentation : o.timing; + const uint32_t oldMode=o.presentationReady ? o.activeMode : o.clientMode; + if(requested.scalerFlags==old.scalerFlags) { + o.presentation=requested;o.activeMode=mode;o.presentationReady=true; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=mode; + return kIOReturnSuccess; + } + IOLockLock(c.lock); + const bool idle=!c.dsiNativeWriters && !c.pipe[0].wmPending && !c.pipe[0].wmCommitting && + !field(path,0x35F0) && !c.dbuf.updating; + IOLockUnlock(c.lock);if(!idle)return kIOReturnNotReady; + o.changingThread=reinterpret_cast(current_thread()); + uint64_t receipt[16] {1,mode,requested.scalerFlags,oldMode,old.scalerFlags}; + const auto apply=[&](uint32_t selected,const XELP::DSI::AppleAdapter::Timing &timing) -> bool { + if(!releaseDSILogical(fb))return false; + auto cpu=o.timing;cpu.scalerFlags=timing.scalerFlags; + if(!XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,cpu, + reinterpret_cast(dsiComputeGeometry),true))return false; + // The native allocator asks getCurrentDisplayMode/getPixelInformation + // through its GPU callback. Publish the selected CPU contract first, + // while other display writers remain excluded by changingThread. + o.presentation=timing;o.activeMode=selected;o.presentationReady=true; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=selected; + const auto result=reinterpret_cast(dsiModeCPU[0])(fb); + __atomic_store_n(&o.logical,field(fb,0x4340),__ATOMIC_RELEASE); + receipt[5]=result;receipt[6]=o.logical;receipt[7]=field(pipe,0x12B8);receipt[8]=field(pipe,0x12C0); + if(result)return false; // native failure can still leave a record to release + if(timing.scalerFlags) { + auto record=reinterpret_cast(o.logical); + if(!record || record->kind!=2 || record->columns!=(uint32_t(o.mode.width)*4+127)/128 || record->rows!=(uint32_t(o.mode.height)+31)/32 || + record->pitch!=field(fb,0x4334) || record->bytes!=field(fb,0x4314) || !record->memory || !record->map || + field(pipe,0x12B8)!=record->memory || field(pipe,0x12C0)!=record->map)return false; + } else if(o.logical || field(pipe,0x12B8) || field(pipe,0x12C0))return false; + reinterpret_cast(dsiModeCPU[1])(fb,0); + reinterpret_cast(dsiModeCPU[2])(fb); + return dsiArmedBackingOwned(fb,path) && dsiPublishedTarget(fb); + }; + const bool changed=apply(mode,requested); + receipt[9]=changed; + if(!changed) { + const bool restored=!o.presentationFault && apply(oldMode,old); + receipt[10]=restored;if(!restored)o.presentationFault=true; + } + receipt[11]=field(fb,0x4338);receipt[12]=field(fb,0x4348); + receipt[13]=o.presentationFault;receipt[14]=field(path,0xF8);receipt[15]=field(path,0xFC); + o.changingThread=0; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentation",receipt,sizeof(receipt)); + return changed ? kIOReturnSuccess : kIOReturnNotReady; +} + // Native timing/resource setup can disable or reallocate before hwSetMode. // DSI uses its own fixed-mode transaction to retain the adopted host link. uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { @@ -7598,11 +7705,12 @@ uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, Display13FBMemory::field(fb,0x420C)}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); - if(!XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode) || depth || !dsiModeTarget(fb))return kIOReturnNotReady; + const auto selected=dsiPresentation(mode); + if(!selected || depth || !dsiModeTarget(fb))return kIOReturnNotReady; + const auto presentation=*selected; if(dsiNativeUpdateAllowed(fb) && !dsiPendingTarget(fb))callbackXE->probeDSINativeBacking(fb); if(!callbackXE->publishDSINativeMode(fb))return kIOReturnNotReady; - *reinterpret_cast(reinterpret_cast(fb)+0x4250)=dsiFixedMode.clientMode; - return kIOReturnSuccess; + return callbackXE->setDSIPresentation(fb,mode,presentation); } } return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); @@ -7619,7 +7727,7 @@ bool XE::dsiNativeUpdateAllowed(IOService *fb) { if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; const auto path=Display13FBMemory::singlePath(fb); if(!dsiLifecycleTarget(fb,path))return true; - auto &c=display13FBPower;if(!c.lock)return false; + auto &c=display13FBPower;if(!c.lock || dsiFixedMode.changingThread || dsiFixedMode.presentationFault)return false; IOLockLock(c.lock); const bool ready=(!dsiPendingTarget(fb) || (dsiPublishedTarget(fb) && dsiPendingBacking.modeReady)) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && @@ -11864,7 +11972,7 @@ void XE::display13WMArmUpdate(IOService *fb) { // Retain the software source/destination geometry with the first // failed latch. OS orientation is independent of DSI wire timing; // distinguish a native scaling request from a link/stride mismatch. - uint64_t geometry[48] {1,pipe,c.pipe[pipe].wm[0].width,c.pipe[pipe].wm[0].height, + uint64_t geometry[48] {2,pipe,c.pipe[pipe].wm[0].width,c.pipe[pipe].wm[0].height, c.pipe[pipe].wm[0].htotal,c.pipe[pipe].wm[0].pixelHz,actualHz}; const unsigned fbOffsets[]={0x430C,0x4310,0x4314,0x4320,0x4324,0x4328,0x432C,0x4334,0x4338}; for(unsigned i=0;i(fb,fbOffsets[i]); @@ -11876,6 +11984,11 @@ void XE::display13WMArmUpdate(IOService *fb) { if(plane && plane->getMetaClass()->getClassSize()>=0x1C8) { const unsigned planeOffsets[]={0x100,0x104,0x108,0x10C,0x110,0x114,0x118,0x11C,0x120,0x124}; for(unsigned i=0;i(plane,planeOffsets[i]); + auto planeScaler=Display13FBMemory::field(plane,0x88); + if(planeScaler && planeScaler->getMetaClass()->getClassSize()>=0x50) { + geometry[46]=Display13FBMemory::field(planeScaler,0x44); + geometry[47]=Display13FBMemory::field(planeScaler,0x4C); + } } if(scaler && scaler->getMetaClass()->getClassSize()>=0x58) for(unsigned i=0;i<6;++i)geometry[32+i]=Display13FBMemory::field(scaler,0x40+4*i); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 5f30475b..0b8d5a95 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -10,6 +10,7 @@ #include "kern_tgl_tc_boot.hpp" #include "kern_dsi_boot.hpp" +#include "kern_dsi_apple_timing.hpp" #include "kern_dsi_ggtt.hpp" #include "kern_dsi_surface.hpp" #include "kern_dsi_quiesce.hpp" @@ -276,9 +277,11 @@ class XE { bool probeDSICommands(IOService *); mach_vm_address_t orgDSIBacking[2] {}; bool processDSIBackingHooks(KernelPatcher &,size_t,mach_vm_address_t,size_t); - mach_vm_address_t dsiComputeGeometry {},dsiModeCPU[3] {}; + mach_vm_address_t dsiComputeGeometry {},dsiModeCPU[4] {}; bool publishDSINativeBacking(); bool publishDSINativeMode(IOService *); + uint32_t setDSIPresentation(IOService *,uint32_t,const XELP::DSI::AppleAdapter::Timing &); + bool releaseDSILogical(IOService *); bool probeDSIModeGeometry(IOService *); bool prepareDSINativeBacking(IOService *); bool validateDSINativeBacking(); diff --git a/tests/display13_fb_memory.cpp b/tests/display13_fb_memory.cpp index 6be91486..642997e0 100644 --- a/tests/display13_fb_memory.cpp +++ b/tests/display13_fb_memory.cpp @@ -303,6 +303,11 @@ int main() { assert(!associations(shared.controller,sizeof(shared.controller),shared.framebuffer,sizeof(shared.framebuffer),owner+8)); put(shared.framebuffer,0x4348,uintptr_t(1)); assert(!associations(shared.controller,sizeof(shared.controller),shared.framebuffer,sizeof(shared.framebuffer),owner)); + put(shared.framebuffer,0x4340,uintptr_t(0x543200)); + assert(!associations(shared.controller,sizeof(shared.controller),shared.framebuffer,sizeof(shared.framebuffer),owner,true)); + assert(!associations(shared.controller,sizeof(shared.controller),shared.framebuffer,sizeof(shared.framebuffer),owner,true,0x543208)); + assert(associations(shared.controller,sizeof(shared.controller),shared.framebuffer,sizeof(shared.framebuffer),owner,true,0x543200)); + assert(!associations(shared.controller,sizeof(shared.controller),shared.framebuffer,sizeof(shared.framebuffer),0,true,0x543200)); Fixture changedOwner;put(changedOwner.controller,0xC0,owner);changedOwner.changeOwner=true; assert(Display13FBMemory::inspect(changedOwner,changedOwner.controller,sizeof(changedOwner.controller),changedOwner.framebuffer,sizeof(changedOwner.framebuffer),0x10000000,memory,true,owner).status==Status::StateChanged); Fixture sysmem;put(sysmem.controller,0xE49,uint8_t(1));assert(sysmem.inspect().status==Status::Association); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index bea61202..513148e4 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -31,7 +31,9 @@ #include #include constexpr uint32_t kIOReturnSuccess=0,kIOReturnNotReady=0xe00002d8U, - kIOReturnUnsupported=0xe00002c7U,kIOReturnBadArgument=0xe00002c2U; + kIOReturnUnsupported=0xe00002c7U,kIOReturnBadArgument=0xe00002c2U,kIOReturnNoResources=0xe00002bdU; +void *current_thread(){return reinterpret_cast(1);} +#define arrsize(x) (sizeof(x)/sizeof(*(x))) unsigned alive=0,calls[5]{};bool dataOK=true,arrayOK=true,publishOK=true,geometryOK=true,clearOK=true; struct Meta {unsigned getClassSize(){return 0x4b90;}} meta; struct OSObject { @@ -47,7 +49,7 @@ }; struct OSArray:OSObject { std::vector objects; - static OSArray *withCapacity(unsigned n){assert(n==1);return arrayOK?new OSArray:nullptr;} + static OSArray *withCapacity(unsigned n){assert(n>=1 && n<=2);return arrayOK?new OSArray:nullptr;} bool setObject(OSObject *p){p->retain();objects.push_back(p);return true;} unsigned getCount(){return objects.size();}OSObject *getObject(unsigned i){return irelease();} @@ -56,6 +58,7 @@ alignas(8) uint8_t storage[0x4c00]{};std::map properties; bool setProperty(const char *k,OSObject *p){if(!publishOK)return false;p->retain();removeProperty(k);properties[k]=p;return true;} void setProperty(const char *,bool){} + bool setProperty(const char *k,const void *p,unsigned n){auto d=OSData::withBytes(p,n);bool ok=setProperty(k,d);d->release();return ok;} OSObject *getProperty(const char *k){auto i=properties.find(k);return i==properties.end()?nullptr:i->second;} void removeProperty(const char *k){auto i=properties.find(k);if(i!=properties.end()){i->second->release();properties.erase(i);}} ~IOService(){for(auto i:properties)i.second->release();} @@ -215,6 +218,17 @@ assert(!XE::wrapDSIValidateMode(fb,static_cast(0x80003000),nullptr,&timing) && timing==dsiFixedMode.data->getBytesNoCopy()); assert(!XE::wrapDSIGetModes(fb,&mode) && static_cast(mode)==0x80003000); assert(!XE::wrapDSIGetCurrentMode(fb,¤t,&depth) && static_cast(current)==0x80003000); + // Rotation offers must survive validation without changing the retained wire mode. + auto rotated=offered;rotated.reservedA[0]=0x80005000;rotated.scalerFlags=0x30; + auto rotationList=OSArray::withCapacity(2);auto rotationData=OSData::withBytes(&rotated,sizeof(rotated)); + rotationList->setObject(renewedData);rotationList->setObject(rotationData); + assert(!XE::wrapDSISetDetailedTimings(fb,rotationList)); + assert(XE::wrapDSIGetModeCount(fb)==2);int32_t pair[2];assert(!XE::wrapDSIGetModes(fb,pair)); + assert(static_cast(pair[1])==rotated.reservedA[0]); + assert(!XE::wrapDSIValidateMode(fb,pair[1],nullptr,&timing)); + assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); + assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1080 && info.height==1920); + rotationData->release();rotationList->release(); renewedData->bytes[64]^=1; assert(XE::wrapDSISetDetailedTimings(fb,renewed)==kIOReturnUnsupported && dsiFixedMode.clientMode==0x80003000); renewedData->release();renewed->release(); diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index d78f292e..cb1e27c1 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -27,6 +27,16 @@ int main() { offered.reservedA[0]=id;assert(!AppleAdapter::clientTiming(t,offered,0)); } offered.reservedA[0]=0x80001001;assert(AppleAdapter::clientTiming(t,offered,1)); + for(unsigned rotation:{0x30U,0x50U}) { + auto r=offered;r.scalerFlags=rotation; + assert(AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=t.horizontalActive;r.verticalScaled=t.verticalActive; + assert(AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=1620;r.verticalScaled=2880; + assert(!AppleAdapter::presentationTiming(t,r,1)); + r=offered;r.scalerFlags=rotation;r.horizontalActive=1920;r.verticalActive=1080; + assert(!AppleAdapter::presentationTiming(t,r,1)); + } assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index 0784a4ac..301b3cae 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -114,7 +114,11 @@ int main(int argc,char **argv) { for(uint32_t rotation:{0x30U,0x50U})for(bool hidpi:{false,true}) { auto presentation=t;presentation.scalerFlags=rotation; if(hidpi){presentation.horizontalScaled=1620;presentation.verticalScaled=2880;} - compute(fb,&presentation); + put(fb,0x4348,uintptr_t(0x12345000)); + if(hidpi) { + assert(!AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); + compute(fb,&presentation); // reference only; not admitted by the production adapter + } else assert(AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); assert(field(fb,0x430C)==(hidpi?1620U:1080U)); assert(field(fb,0x4310)==(hidpi?2880U:1920U)); for(unsigned offset:{0x4320U,0x4328U})assert(field(fb,offset)==1080); @@ -126,7 +130,7 @@ int main(int argc,char **argv) { auto record=field(fb,0x4340); assert(record && record->pitch==(hidpi?6528U:4352U)); assert(record->bytes==record->pitch*(hidpi?2880U:1920U)); - assert(!field(fb,0x4348)); // Logical allocation does not own the scanout. + assert(field(fb,0x4348)==0x12345000); // Logical allocation preserves the scanout. releaseLogical(fb);assert(!field(fb,0x4340)); } // A failing accelerator callback still publishes the heap record. diff --git a/tests/dsi_presentation.py b/tests/dsi_presentation.py new file mode 100644 index 00000000..c9315605 --- /dev/null +++ b/tests/dsi_presentation.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Exercise the production presentation transaction with native allocation leaves mocked.""" +from pathlib import Path +import re, subprocess, tempfile +root=Path(__file__).resolve().parents[1] +source=(root/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}',source,re.S)[0] for name in ['releaseDSILogical','setDSIPresentation']) +code=r''' +#include "kern_dsi_native_mode.hpp" +#include "kern_dsi_native_backing.hpp" +#include +#include +#include +using Display13FBMemory::field; +using XELP::DSI::AppleAdapter::Timing; +using XELP::DSI::NativeBacking::Record; +struct IOService {alignas(8) uint8_t bytes[0x4B90]{};}; +using OSObject=IOService; +templatevoid put(void *p,unsigned offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} +struct IOLock {unsigned depth=0;}; +void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} +void *current_thread(){return reinterpret_cast(1);} +constexpr uint32_t kIOReturnNotReady=0xE00002D8,kIOReturnSuccess=0; +uint64_t receiptCopy[16]{}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *,const void *p,unsigned n){assert(n==sizeof(receiptCopy));memcpy(receiptCopy,p,n);}}; +struct {IOService *framebuffer=nullptr;Timing timing,presentation;XELP::DSI::Mode mode; + uint32_t clientMode=0x80001000,activeMode=0;uintptr_t logical=0,changingThread=0; + bool presentationReady=false,presentationFault=false;}dsiFixedMode; +struct {IOService *pipe=nullptr;}dsiPendingBacking; +struct {IOLock *lock=nullptr;unsigned dsiNativeWriters=0;struct {unsigned wmPending=0;bool wmCommitting=false;}pipe[1]; + struct {bool updating=false;}dbuf;}display13FBPower; +bool dsiModeTarget(IOService *f){return f==dsiFixedMode.framebuffer;} +bool dsiModeCoherent(IOService *){return true;} +bool dsiPublishedTarget(IOService *f){return field(f,0x4348)==0x100000;} +struct XE { + XELP::DSI::Profile dsiProfile;uintptr_t dsiModeCPU[4]{},dsiComputeGeometry=0; + bool dsiArmedBackingOwned(IOService *f,OSObject *){return dsiPublishedTarget(f);} + bool dsiNativeUpdateAllowed(IOService *){return !dsiFixedMode.changingThread && !dsiFixedMode.presentationFault;} + bool releaseDSILogical(IOService *); + uint32_t setDSIPresentation(IOService *,uint32_t,const Timing &); +}xe; +FUNCTIONS +unsigned allocations=0,frees=0,caps=0;bool failAllocation=false,failRelease=false; +void compute(void *f,const Timing *t){ + auto path=Display13FBMemory::singlePath(f); + const unsigned pitch=(t->horizontalActive*4+127)&~127U; + for(unsigned off:{0x430CU,0x4320U,0x4328U})put(f,off,t->horizontalActive); + for(unsigned off:{0x4310U,0x4324U,0x432CU})put(f,off,t->verticalActive); + put(f,0x4314,pitch*t->verticalActive);put(f,0x4334,pitch);put(f,0x4338,t->scalerFlags?0x10U:1U); + put(path,0xF0,uint64_t(0));put(path,0x100,uint64_t(0)); + for(unsigned off:{0xF8U,0x108U})put(path,off,t->horizontalActive); + for(unsigned off:{0xFCU,0x10CU})put(path,off,t->verticalActive); +} +uint32_t allocate(IOService *f){ + assert(dsiFixedMode.changingThread && !field(f,0x4340)); + assert(field(f,0x4250)==dsiFixedMode.activeMode); + if(!dsiFixedMode.presentation.scalerFlags)return 0; + ++allocations;auto r=new Record;r->kind=2;r->columns=34;r->rows=60;r->pitch=4352;r->bytes=8355840; + r->memory=0x200000;r->map=0x300000; + put(f,0x4340,r);put(dsiPendingBacking.pipe,0x12B8,r->memory);put(dsiPendingBacking.pipe,0x12C0,r->map); + return failAllocation?0xE00002BD:0; +} +void release(IOService *f){auto r=field(f,0x4340);assert(r);++frees;delete r; + put(f,0x4340,uintptr_t(0));if(!failRelease){put(dsiPendingBacking.pipe,0x12B8,uintptr_t(0));put(dsiPendingBacking.pipe,0x12C0,uintptr_t(0));}} +void initial(IOService *,uint32_t){}void capabilities(IOService *){++caps;} +int main(){ + IOService fb,path,pipe,other;IOLock lock;display13FBPower.lock=&lock;dsiPendingBacking.pipe=&pipe; + auto &o=dsiFixedMode;o.framebuffer=&fb;xe.dsiProfile.status=XELP::DSI::ProfileStatus::Valid; + xe.dsiProfile.mode={135255,1080,46,22,2,1920,82,40,2,86,154};o.mode=xe.dsiProfile.mode; + assert(XELP::DSI::AppleAdapter::makeTiming(xe.dsiProfile,o.timing)); + put(&fb,0x3FD8,0U);put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&fb,0x4348,uintptr_t(0x100000)); + compute(&fb,&o.timing);xe.dsiComputeGeometry=reinterpret_cast(compute); + xe.dsiModeCPU[0]=reinterpret_cast(allocate);xe.dsiModeCPU[1]=reinterpret_cast(initial); + xe.dsiModeCPU[2]=reinterpret_cast(capabilities);xe.dsiModeCPU[3]=reinterpret_cast(release); + auto turn=o.timing;turn.scalerFlags=0x30; + assert(xe.setDSIPresentation(&other,2,turn)==kIOReturnNotReady && !allocations); + display13FBPower.pipe[0].wmPending=1;assert(xe.setDSIPresentation(&fb,2,turn)==kIOReturnNotReady && !allocations); + display13FBPower.pipe[0].wmPending=0; + assert(!xe.setDSIPresentation(&fb,2,turn) && o.logical && o.activeMode==2 && !o.changingThread); + assert(field(&fb,0x4338)==0x10 && dsiPublishedTarget(&fb)); + turn.scalerFlags=0x50;assert(!xe.setDSIPresentation(&fb,3,turn) && allocations==2 && frees==1); + assert(!xe.setDSIPresentation(&fb,4,o.timing) && !o.logical && frees==2 && field(&fb,0x4338)==1); + failAllocation=true;assert(xe.setDSIPresentation(&fb,5,turn)==kIOReturnNotReady); + assert(o.activeMode==4 && !o.logical && !o.presentationFault && !o.changingThread && allocations==3 && frees==3); + assert(field(&fb,0x4338)==1 && dsiPublishedTarget(&fb) && receiptCopy[10]==1); + failAllocation=false;assert(!xe.setDSIPresentation(&fb,6,turn));failRelease=true; + assert(xe.setDSIPresentation(&fb,7,o.timing)==kIOReturnNotReady && o.presentationFault && !o.changingThread); + assert(allocations==frees && !lock.depth && dsiPublishedTarget(&fb)); + puts("PASS production presentation: quarter-turns, native restore, partial allocation cleanup and failed-release admission closure"); +} +'''.replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='dsi-presentation-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index a0d645da..6fa0a789 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -86,6 +86,7 @@ c.pipe[0].wmLatch.status=Display13WM::Status::Complete;c.pipe[0].watermarkClockHz=358400000; } bool publishDSINativeBacking();bool publishDSINativeMode(IOService*); + bool releaseDSILogical(IOService *fb){return !Display13FBMemory::field(fb,0x4340);} bool switchDSINativeSurface();bool dsiArmedBackingOwned(IOService*,OSObject*);bool retireDSINativeBacking(IOService*,OSObject*); }; ''' diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index b67fda31..d4a79333 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -44,7 +44,11 @@ struct {struct {bool faulted=false,suspended=false;}state;}bridge;struct {bool faulted=false;}dbuf; struct {IOService *memoryFramebuffer=nullptr;bool destroyingPath=false;}pipe[3]; Display13Transcoder::Bindings transcoders;}display13FBPower; -struct {uint32_t clientMode=0x10000;}dsiFixedMode; +struct {uint32_t clientMode=0x10000;uintptr_t changingThread=0;bool presentationFault=false;}dsiFixedMode; +const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode){ + static XELP::DSI::AppleAdapter::Timing t; + return XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode)?&t:nullptr; +} IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) @@ -87,6 +91,8 @@ return wakeOK; } bool dsiPanelOnly=false,publicationReady=false;bool publishDSINativeMode(IOService*){return publicationReady;} + uint32_t setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::AppleAdapter::Timing &){ + assert(fbLock.depth && !owner);memcpy(reinterpret_cast(fb)+0x4250,&mode,sizeof(mode));return 0;} bool dsiNativeWriterHooksReady=true;uintptr_t orgDSICursor[2]{}; uintptr_t orgDSIMode[6]{}; static void recordDSIClient(IOService*,unsigned,uint64_t,uint32_t){}; diff --git a/tools/test.py b/tools/test.py index 95cff162..c89206d3 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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)] From 748e743da6a3674e3de8cac88b99442cba430ba4 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:18:42 +0900 Subject: [PATCH 083/139] Translate IOGraphics orientation into DSI presentation timing --- WhateverXe/kern_xe.cpp | 21 ++++++++++++++++----- tests/dsi_apple_adapter.py | 12 ++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 69afe725..f5759d94 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4678,7 +4678,7 @@ struct DSIFixedModeOwner { OSData *data {}; XELP::DSI::Mode mode; XELP::DSI::AppleAdapter::Timing timing; - XELP::DSI::AppleAdapter::Timing presentation,offered[8]; + XELP::DSI::AppleAdapter::Timing presentation,requested,offered[8]; unsigned offerCount {}; uint32_t activeMode {XELP::DSI::AppleAdapter::FixedModeID}; uintptr_t changingThread {},logical {}; @@ -4698,9 +4698,20 @@ static uintptr_t dsiLogicalSurface(const void *fb) { static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { auto &o=dsiFixedMode; if(o.changingThread && o.presentationReady && mode==o.activeMode)return &o.presentation; - for(unsigned i=0;igetProperty("IOFBTransform")) : nullptr; + if(!rotation)return selected; + const auto transform=rotation->unsigned64BitValue(); + if(transform!=0 && transform!=3 && transform!=5)return nullptr; + o.requested=*selected;o.requested.scalerFlags=static_cast(transform<<4); + return &o.requested; } static void dsiAdvertiseRotation(IOService *fb) { auto data=OSDynamicCast(OSData,fb->getProperty("IOFBScalerInfo")); @@ -7356,7 +7367,7 @@ bool XE::releaseDSIFixedMode(IOService *fb) { auto array=o.array;auto data=o.data; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); o.array=nullptr;o.data=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; - o.presentation={};o.offerCount=0;o.activeMode=o.clientMode;o.presentationReady=false;o.presentationFault=false;o.changingThread=0; + o.presentation={};o.requested={};o.offerCount=0;o.activeMode=o.clientMode;o.presentationReady=false;o.presentationFault=false;o.changingThread=0; OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; } diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 513148e4..10971a37 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -41,6 +41,7 @@ void retain(){assert(refs);++refs;}void release(){assert(refs);if(!--refs)delete this;} Meta *getMetaClass(){return &meta;} }; +struct OSNumber:OSObject {uint64_t value=0;uint64_t unsigned64BitValue(){return value;}}; struct OSData:OSObject { std::vector bytes; static OSData *withBytes(const void *p,unsigned n){if(!dataOK)return nullptr;auto d=new OSData; @@ -229,6 +230,17 @@ assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1080 && info.height==1920); rotationData->release();rotationList->release(); + // Big Sur keeps timing flags zero and publishes UI rotation separately. + auto orientation=new OSNumber;fb->setProperty("IOFBTransform",orientation); + for(auto transform:{3ULL,5ULL,0ULL}) { + orientation->value=transform; + assert(!XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)); + assert(static_cast(timing)->scalerFlags==transform*16); + assert(!dsiFixedMode.timing.scalerFlags && static_cast(timing)->horizontalActive==1080); + } + orientation->value=7; + assert(XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)==kIOReturnBadArgument); + fb->removeProperty("IOFBTransform");orientation->release(); renewedData->bytes[64]^=1; assert(XE::wrapDSISetDetailedTimings(fb,renewed)==kIOReturnUnsupported && dsiFixedMode.clientMode==0x80003000); renewedData->release();renewed->release(); From 8c7d1c0927fe8e252db6d536ed3411abc8460955 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:34:17 +0900 Subject: [PATCH 084/139] Carry retained DSI rotation surface into native path binding --- WhateverXe/kern_tgl_fb_binding.hpp | 4 ++-- WhateverXe/kern_xe.cpp | 4 ++-- tests/display13_sst_fixture.cpp | 1 + tests/dsi_native_cache.py | 1 + tests/tgl_fb_binding.cpp | 12 ++++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_tgl_fb_binding.hpp b/WhateverXe/kern_tgl_fb_binding.hpp index 63432ef9..894658ac 100644 --- a/WhateverXe/kern_tgl_fb_binding.hpp +++ b/WhateverXe/kern_tgl_fb_binding.hpp @@ -6,7 +6,7 @@ #include "kern_display13_fb_topology.hpp" namespace TGLFBBinding { struct Object {const void *pointer {};size_t bytes {};}; -struct Objects {Object controller,framebuffer,path,plane,port;uintptr_t accelerator {};}; +struct Objects {Object controller,framebuffer,path,plane,port;uintptr_t accelerator {},logicalSurface {};}; enum class Status : uint32_t {Unavailable,Association,Path,Port,Plane,Complete}; struct Readout { Status status {Status::Unavailable}; @@ -58,7 +58,7 @@ inline Readout inspect(const Objects &o,const XELP::NativePorts &ports,bool atta plane=o.plane.pointer,port=o.port.pointer; if(!c || !fb || !path || !plane || !port || o.controller.bytes<0x1C20 || o.framebuffer.bytes<0x4B90 || o.path.bytes<0x3730 || o.plane.bytes<0x1C8 || o.port.bytes<0x580)return r; - if(!Display13FBMemory::associations(c,o.controller.bytes,fb,o.framebuffer.bytes,o.accelerator,true)) { + if(!Display13FBMemory::associations(c,o.controller.bytes,fb,o.framebuffer.bytes,o.accelerator,true,o.logicalSurface)) { r.status=Status::Association;return r; } r.framebuffer=field(fb,0x1DC);r.pipe=field(path,0x3640); diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f5759d94..2150d4bd 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4847,7 +4847,7 @@ static TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path const TGLFBBinding::Objects objects { {controller,controller->getMetaClass()->getClassSize()}, {fb,fb->getMetaClass()->getClassSize()}, {path,path->getMetaClass()->getClassSize()}, {plane,plane ? plane->getMetaClass()->getClassSize() : 0}, - {port,port ? port->getMetaClass()->getClassSize() : 0}, display13FBExpectedOwner(controller) + {port,port ? port->getMetaClass()->getClassSize() : 0}, display13FBExpectedOwner(controller),dsiLogicalSurface(fb) }; return TGLFBBinding::inspect(objects,platform,wiring,attached); } @@ -11065,7 +11065,7 @@ void XE::adlnWMSetup(OSObject *plane,OSObject *path,bool cursor) { {fb,fb ? fb->getMetaClass()->getClassSize() : 0}, {path,path ? path->getMetaClass()->getClassSize() : 0}, {plane,plane ? plane->getMetaClass()->getClassSize() : 0}, - {port,port ? port->getMetaClass()->getClassSize() : 0},display13FBExpectedOwner(controller) + {port,port ? port->getMetaClass()->getClassSize() : 0},display13FBExpectedOwner(controller),dsiLogicalSurface(fb) }; const auto binding=TGLFBBinding::inspect(objects,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); uint64_t bindingRecord[] {1,static_cast(binding.status),binding.framebuffer,binding.pipe,binding.ddi,binding.plane, diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index c014527b..3beb1ee7 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -73,6 +73,7 @@ struct XE { static uint32_t wrapDisplay13SignalLevels(void*,uint32_t,uint32_t,void*); static uint32_t wrapDisplay13TrainingPattern(void*,uint32_t); }state,*callbackXE=&state; +uintptr_t dsiLogicalSurface(const void*){return 0;} // BINDING // FUNCTIONS unsigned nativeSignal(void*,unsigned,unsigned,void*){assert(!locked);++calls;return nativeResult;} diff --git a/tests/dsi_native_cache.py b/tests/dsi_native_cache.py index acfcce23..9a55e763 100644 --- a/tests/dsi_native_cache.py +++ b/tests/dsi_native_cache.py @@ -50,6 +50,7 @@ Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge; bool memoryFaulted=false,clockFaulted=false;} display13FBPower; uintptr_t display13FBExpectedOwner(IOService*){return 0;} +uintptr_t dsiLogicalSurface(const void*){return 0;} BINDING XELPMemory::Layout xelpMemory;bool display13Offline=false; void originalCache(IOService*,IOService*,OSObject*){++nativeCache;} diff --git a/tests/tgl_fb_binding.cpp b/tests/tgl_fb_binding.cpp index b4c496c5..af001a3c 100644 --- a/tests/tgl_fb_binding.cpp +++ b/tests/tgl_fb_binding.cpp @@ -113,6 +113,18 @@ int main() { {Fixture x(2,1,0);put(x.ctl,0xD60+8*2,static_cast(x.plane));assert(x.inspect().status==Status::Association);} {Fixture x(2,1,0);put(x.ctl,0xC0,uintptr_t(0x123400));put(x.fb,0x4348,uintptr_t(0x41300000)); assert(x.inspect().status==Status::Association && x.inspect(false,0x123400).status==Status::Complete);} + // A rotation surface is valid only with the separately retained handle + // and the matching accelerator owner. Non-DSI callers retain zero. + {Fixture x(2,1,0);x.attach();put(x.ctl,0xC0,uintptr_t(0x123400)); + put(x.fb,0x4348,uintptr_t(0x41300000));put(x.fb,0x4340,uintptr_t(0x41400000)); + auto o=x.objects(0x123400); + const auto inspect=[&]{return TGLFBBinding::inspect(o,x.platform,x.wiring,true).status;}; + assert(inspect()==Status::Association); + o.logicalSurface=0x41400000;assert(inspect()==Status::Complete); + o.logicalSurface^=8;assert(inspect()==Status::Association);o.logicalSurface^=8; + o.accelerator^=8;assert(inspect()==Status::Association);o.accelerator^=8; + put(x.ctl,0xC0,uintptr_t(0));o.accelerator=0;assert(inspect()==Status::Association); + } {Fixture x(2,1,0);auto objects=x.objects();objects.plane.bytes=0x1C7;assert(TGLFBBinding::inspect(objects,x.platform,x.wiring).status==Status::Unavailable);} puts("PASS native FB binding: independent FB/pipe/DDI/slot identities, attached versus pending paths, native primary table/registers, foreign config and malformed owner rejection. CPU only."); } From 832069f8e628d489a26bc7f8861601a563c1d3f5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:43:11 +0900 Subject: [PATCH 085/139] Separate pre-rotation image dimensions from DSI wire timing --- WhateverXe/kern_dsi_apple_timing.hpp | 9 ++++----- WhateverXe/kern_dsi_native_mode.hpp | 11 +++++++---- WhateverXe/kern_xe.cpp | 14 ++++++++++---- tests/dsi_apple_adapter.py | 8 +++++--- tests/dsi_apple_timing.cpp | 4 ++-- tests/dsi_native_mode_fixture.cpp | 13 +++++++------ tests/dsi_presentation.py | 13 ++++++++----- 7 files changed, 43 insertions(+), 29 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 492c57d4..734ed84e 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -23,12 +23,12 @@ struct ModeInformation { uint32_t reserved[3] {}; }; static_assert(sizeof(ModeInformation)==36,"IODisplayModeInformation ABI"); -inline bool annotateMode(const Mode &m,ModeInformation &info) { - if(!m.valid() || info.width!=m.width || info.height!=m.height)return false; +inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false) { + if(!m.valid() || info.width!=(rotated?m.height:m.width) || info.height!=(rotated?m.width:m.height))return false; info.refresh=static_cast((uint64_t(m.clockKHz)*1000*65536+m.htotal()*uint64_t(m.vtotal())/2)/ (m.htotal()*uint64_t(m.vtotal()))); info.depth=0; // the adopted DSI surface is RGB8 only - info.widthMM=static_cast(m.widthMM);info.heightMM=static_cast(m.heightMM); + info.widthMM=static_cast(rotated?m.heightMM:m.widthMM);info.heightMM=static_cast(rotated?m.widthMM:m.heightMM); // IOGraphics: valid/safe/default + built-in + native. Acceleration is a // separate runtime qualification, never inferred from a fixed mode. info.flags|=0x02000407; @@ -54,8 +54,7 @@ inline bool acceptsMode(uint32_t mode,uint32_t clientMode) { // admit only native-size quarter-turns; scaled sources are a separate stage. inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigned index) { if(offered.scalerFlags!=0x30 && offered.scalerFlags!=0x50)return false; - if((offered.horizontalScaled || offered.verticalScaled) && - (offered.horizontalScaled!=fixed.horizontalActive || offered.verticalScaled!=fixed.verticalActive))return false; + if(offered.horizontalScaled!=fixed.verticalActive || offered.verticalScaled!=fixed.horizontalActive)return false; auto native=offered;native.scalerFlags=0;native.horizontalScaled=0;native.verticalScaled=0; return clientTiming(fixed,native,index); } diff --git a/WhateverXe/kern_dsi_native_mode.hpp b/WhateverXe/kern_dsi_native_mode.hpp index d7ffa13b..81be1089 100644 --- a/WhateverXe/kern_dsi_native_mode.hpp +++ b/WhateverXe/kern_dsi_native_mode.hpp @@ -13,7 +13,9 @@ template bool prepareGeometry(void *fb,void *path,const Profile & using Display13FBMemory::field; Timing expected; if(!makeTiming(profile,expected))return false; - if(presentation && (timing.scalerFlags==0x30 || timing.scalerFlags==0x50))expected.scalerFlags=timing.scalerFlags; + const bool rotated=presentation && (timing.scalerFlags==0x30 || timing.scalerFlags==0x50); + if(rotated) {expected.scalerFlags=timing.scalerFlags; + expected.horizontalScaled=expected.verticalActive;expected.verticalScaled=expected.horizontalActive;} if(!fb || !path || memcmp(&expected,&timing,sizeof(timing)) || Display13FBMemory::singlePath(fb)!=path || field(fb,0x4340) || (!presentation && field(fb,0x4348)))return false; @@ -26,12 +28,13 @@ template bool prepareGeometry(void *fb,void *path,const Profile & const auto put=[](void *o,unsigned offset,uint32_t v){memcpy(static_cast(o)+offset,&v,4);}; put(fb,0x4350,4);*(static_cast(fb)+0x4A18)=1; compute(fb,&timing); - const auto &m=profile.mode;const uint32_t pitch=((m.width*4+127)/128)*128; - const bool valid=field(fb,0x430C)==m.width && field(fb,0x4310)==m.height && + const auto &m=profile.mode;const uint32_t width=rotated?m.height:m.width,height=rotated?m.width:m.height; + const uint32_t pitch=((width*4+127)/128)*128; + const bool valid=field(fb,0x430C)==width && field(fb,0x4310)==height && field(fb,0x4314)==pitch*m.height && field(fb,0x4320)==m.width && field(fb,0x4324)==m.height && field(fb,0x4328)==m.width && field(fb,0x432C)==m.height && field(fb,0x4334)==pitch && - field(fb,0x4338)==(timing.scalerFlags ? 0x10U : 1U) && !field(path,0xF0) && + field(fb,0x4338)==(rotated ? 0x30U : 1U) && !field(path,0xF0) && field(path,0xF8)==m.width && field(path,0xFC)==m.height && !field(path,0x100) && field(path,0x108)==m.width && field(path,0x10C)==m.height; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2150d4bd..9823e28c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4711,6 +4711,10 @@ static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { const auto transform=rotation->unsigned64BitValue(); if(transform!=0 && transform!=3 && transform!=5)return nullptr; o.requested=*selected;o.requested.scalerFlags=static_cast(transform<<4); + // IODetailedTimingInformationV2 scaled dimensions describe the image + // before rotation. Active dimensions remain the portrait wire mode. + o.requested.horizontalScaled=transform ? o.timing.verticalActive : 0; + o.requested.verticalScaled=transform ? o.timing.horizontalActive : 0; return &o.requested; } static void dsiAdvertiseRotation(IOService *fb) { @@ -7524,10 +7528,11 @@ uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { const auto result=[&]() -> uint32_t { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!raw || !dsiPresentation(mode))return kIOReturnBadArgument; + const auto selected=dsiPresentation(mode);if(!raw || !selected)return kIOReturnBadArgument; + const bool rotated=selected->scalerFlags!=0; const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, - *static_cast(raw)))return kIOReturnNotReady; + *static_cast(raw),rotated))return kIOReturnNotReady; return result; }(); recordDSIClient(fb,5,static_cast(mode),result); @@ -7665,6 +7670,7 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App const auto apply=[&](uint32_t selected,const XELP::DSI::AppleAdapter::Timing &timing) -> bool { if(!releaseDSILogical(fb))return false; auto cpu=o.timing;cpu.scalerFlags=timing.scalerFlags; + cpu.horizontalScaled=timing.horizontalScaled;cpu.verticalScaled=timing.verticalScaled; if(!XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,cpu, reinterpret_cast(dsiComputeGeometry),true))return false; // The native allocator asks getCurrentDisplayMode/getPixelInformation @@ -7678,8 +7684,8 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App if(result)return false; // native failure can still leave a record to release if(timing.scalerFlags) { auto record=reinterpret_cast(o.logical); - if(!record || record->kind!=2 || record->columns!=(uint32_t(o.mode.width)*4+127)/128 || record->rows!=(uint32_t(o.mode.height)+31)/32 || - record->pitch!=field(fb,0x4334) || record->bytes!=field(fb,0x4314) || !record->memory || !record->map || + if(!record || record->kind!=2 || record->columns!=(field(fb,0x430C)*4+127)/128 || record->rows!=(field(fb,0x4310)+31)/32 || + record->pitch!=field(fb,0x4334) || record->bytes!=uint64_t(record->pitch)*field(fb,0x4310) || !record->memory || !record->map || field(pipe,0x12B8)!=record->memory || field(pipe,0x12C0)!=record->map)return false; } else if(o.logical || field(pipe,0x12B8) || field(pipe,0x12C0))return false; reinterpret_cast(dsiModeCPU[1])(fb,0); diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 10971a37..104a4225 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -113,7 +113,7 @@ auto r=XE::wrapDSIValidateMode(fb,mode,nullptr,&t);if(r)return r; auto timing=static_cast(t); auto info=static_cast(p);*info={}; - info->width=geometryOK?timing->horizontalActive:1;info->height=timing->verticalActive;info->depth=2;return 0;} + info->width=geometryOK?(timing->horizontalScaled?timing->horizontalScaled:timing->horizontalActive):1;info->height=timing->verticalScaled?timing->verticalScaled:timing->verticalActive;info->depth=2;return 0;} unsigned nativeCurrentCalls=0,nativeOnlineCalls=0,nativeDDCCalls=0; uint32_t nativeDDC(IOService *,uint32_t block,uint8_t *data,uint64_t *length,uint32_t flags,OSObject *path){ assert(block==2 && flags==1 && path==testPath);++nativeDDCCalls;*length=128;data[0]=42;return 9;} @@ -220,7 +220,7 @@ assert(!XE::wrapDSIGetModes(fb,&mode) && static_cast(mode)==0x80003000); assert(!XE::wrapDSIGetCurrentMode(fb,¤t,&depth) && static_cast(current)==0x80003000); // Rotation offers must survive validation without changing the retained wire mode. - auto rotated=offered;rotated.reservedA[0]=0x80005000;rotated.scalerFlags=0x30; + auto rotated=offered;rotated.reservedA[0]=0x80005000;rotated.scalerFlags=0x30;rotated.horizontalScaled=1920;rotated.verticalScaled=1080; auto rotationList=OSArray::withCapacity(2);auto rotationData=OSData::withBytes(&rotated,sizeof(rotated)); rotationList->setObject(renewedData);rotationList->setObject(rotationData); assert(!XE::wrapDSISetDetailedTimings(fb,rotationList)); @@ -228,7 +228,7 @@ assert(static_cast(pair[1])==rotated.reservedA[0]); assert(!XE::wrapDSIValidateMode(fb,pair[1],nullptr,&timing)); assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); - assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1080 && info.height==1920); + assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1920 && info.height==1080); rotationData->release();rotationList->release(); // Big Sur keeps timing flags zero and publishes UI rotation separately. auto orientation=new OSNumber;fb->setProperty("IOFBTransform",orientation); @@ -236,6 +236,8 @@ orientation->value=transform; assert(!XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)); assert(static_cast(timing)->scalerFlags==transform*16); + assert(static_cast(timing)->horizontalScaled==(transform?1920U:0U)); + assert(static_cast(timing)->verticalScaled==(transform?1080U:0U)); assert(!dsiFixedMode.timing.scalerFlags && static_cast(timing)->horizontalActive==1080); } orientation->value=7; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index cb1e27c1..762705f4 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -29,8 +29,8 @@ int main() { offered.reservedA[0]=0x80001001;assert(AppleAdapter::clientTiming(t,offered,1)); for(unsigned rotation:{0x30U,0x50U}) { auto r=offered;r.scalerFlags=rotation; - assert(AppleAdapter::presentationTiming(t,r,1)); - r.horizontalScaled=t.horizontalActive;r.verticalScaled=t.verticalActive; + assert(!AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=t.verticalActive;r.verticalScaled=t.horizontalActive; assert(AppleAdapter::presentationTiming(t,r,1)); r.horizontalScaled=1620;r.verticalScaled=2880; assert(!AppleAdapter::presentationTiming(t,r,1)); diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index 301b3cae..5677bd63 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -113,23 +113,24 @@ int main(int argc,char **argv) { const auto releaseLogical=reinterpret_cast(image+0x33062); for(uint32_t rotation:{0x30U,0x50U})for(bool hidpi:{false,true}) { auto presentation=t;presentation.scalerFlags=rotation; + presentation.horizontalScaled=1920;presentation.verticalScaled=1080; if(hidpi){presentation.horizontalScaled=1620;presentation.verticalScaled=2880;} put(fb,0x4348,uintptr_t(0x12345000)); if(hidpi) { assert(!AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); compute(fb,&presentation); // reference only; not admitted by the production adapter } else assert(AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); - assert(field(fb,0x430C)==(hidpi?1620U:1080U)); - assert(field(fb,0x4310)==(hidpi?2880U:1920U)); + assert(field(fb,0x430C)==(hidpi?1620U:1920U)); + assert(field(fb,0x4310)==(hidpi?2880U:1080U)); for(unsigned offset:{0x4320U,0x4328U})assert(field(fb,offset)==1080); for(unsigned offset:{0x4324U,0x432CU})assert(field(fb,offset)==1920); - assert(field(fb,0x4338)==(hidpi?0x30U:0x10U)); - assert(field(fb,0x4334)==(hidpi?6528U:4352U)); + assert(field(fb,0x4338)==0x30U); + assert(field(fb,0x4334)==(hidpi?6528U:7680U)); assert(field(path,0xF8)==1080 && field(path,0xFC)==1920); assert(allocateLogical(fb)==0); auto record=field(fb,0x4340); - assert(record && record->pitch==(hidpi?6528U:4352U)); - assert(record->bytes==record->pitch*(hidpi?2880U:1920U)); + assert(record && record->pitch==(hidpi?6528U:7680U)); + assert(record->bytes==record->pitch*(hidpi?2880U:1080U)); assert(field(fb,0x4348)==0x12345000); // Logical allocation preserves the scanout. releaseLogical(fb);assert(!field(fb,0x4340)); } diff --git a/tests/dsi_presentation.py b/tests/dsi_presentation.py index c9315605..45c9eaf5 100644 --- a/tests/dsi_presentation.py +++ b/tests/dsi_presentation.py @@ -44,10 +44,13 @@ unsigned allocations=0,frees=0,caps=0;bool failAllocation=false,failRelease=false; void compute(void *f,const Timing *t){ auto path=Display13FBMemory::singlePath(f); - const unsigned pitch=(t->horizontalActive*4+127)&~127U; + const unsigned width=t->horizontalScaled?t->horizontalScaled:t->horizontalActive; + const unsigned height=t->verticalScaled?t->verticalScaled:t->verticalActive; + const unsigned pitch=(width*4+127)&~127U; for(unsigned off:{0x430CU,0x4320U,0x4328U})put(f,off,t->horizontalActive); for(unsigned off:{0x4310U,0x4324U,0x432CU})put(f,off,t->verticalActive); - put(f,0x4314,pitch*t->verticalActive);put(f,0x4334,pitch);put(f,0x4338,t->scalerFlags?0x10U:1U); + put(f,0x430C,width);put(f,0x4310,height); + put(f,0x4314,pitch*t->verticalActive);put(f,0x4334,pitch);put(f,0x4338,t->scalerFlags?0x30U:1U); put(path,0xF0,uint64_t(0));put(path,0x100,uint64_t(0)); for(unsigned off:{0xF8U,0x108U})put(path,off,t->horizontalActive); for(unsigned off:{0xFCU,0x10CU})put(path,off,t->verticalActive); @@ -56,7 +59,7 @@ assert(dsiFixedMode.changingThread && !field(f,0x4340)); assert(field(f,0x4250)==dsiFixedMode.activeMode); if(!dsiFixedMode.presentation.scalerFlags)return 0; - ++allocations;auto r=new Record;r->kind=2;r->columns=34;r->rows=60;r->pitch=4352;r->bytes=8355840; + ++allocations;auto r=new Record;r->kind=2;r->columns=60;r->rows=34;r->pitch=7680;r->bytes=8294400; r->memory=0x200000;r->map=0x300000; put(f,0x4340,r);put(dsiPendingBacking.pipe,0x12B8,r->memory);put(dsiPendingBacking.pipe,0x12C0,r->map); return failAllocation?0xE00002BD:0; @@ -73,12 +76,12 @@ compute(&fb,&o.timing);xe.dsiComputeGeometry=reinterpret_cast(compute); xe.dsiModeCPU[0]=reinterpret_cast(allocate);xe.dsiModeCPU[1]=reinterpret_cast(initial); xe.dsiModeCPU[2]=reinterpret_cast(capabilities);xe.dsiModeCPU[3]=reinterpret_cast(release); - auto turn=o.timing;turn.scalerFlags=0x30; + auto turn=o.timing;turn.scalerFlags=0x30;turn.horizontalScaled=1920;turn.verticalScaled=1080; assert(xe.setDSIPresentation(&other,2,turn)==kIOReturnNotReady && !allocations); display13FBPower.pipe[0].wmPending=1;assert(xe.setDSIPresentation(&fb,2,turn)==kIOReturnNotReady && !allocations); display13FBPower.pipe[0].wmPending=0; assert(!xe.setDSIPresentation(&fb,2,turn) && o.logical && o.activeMode==2 && !o.changingThread); - assert(field(&fb,0x4338)==0x10 && dsiPublishedTarget(&fb)); + assert(field(&fb,0x4338)==0x30 && dsiPublishedTarget(&fb)); turn.scalerFlags=0x50;assert(!xe.setDSIPresentation(&fb,3,turn) && allocations==2 && frees==1); assert(!xe.setDSIPresentation(&fb,4,o.timing) && !o.logical && frees==2 && field(&fb,0x4338)==1); failAllocation=true;assert(xe.setDSIPresentation(&fb,5,turn)==kIOReturnNotReady); From 22adb10981e5a746335f0c89a65615f7e3a9ffa9 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:48:29 +0900 Subject: [PATCH 086/139] Observe GPU presentation cache at DSI mode change and first latch failure --- WhateverXe/kern_xe.cpp | 17 ++++++++++++++++- tests/dsi_presentation.py | 5 +++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 9823e28c..f856df09 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7667,6 +7667,13 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App IOLockUnlock(c.lock);if(!idle)return kIOReturnNotReady; o.changingThread=reinterpret_cast(current_thread()); uint64_t receipt[16] {1,mode,requested.scalerFlags,oldMode,old.scalerFlags}; + uint64_t gpuState[13] {1}; + const auto captureGPU=[&](unsigned base) { + const unsigned offsets[]={0x1264,0x1268,0x128C,0x1290,0x1294,0x12D8}; + if(pipe->getMetaClass()->getClassSize()>=0x12DC) + for(unsigned i=0;i(pipe,offsets[i]); + }; + captureGPU(1); const auto apply=[&](uint32_t selected,const XELP::DSI::AppleAdapter::Timing &timing) -> bool { if(!releaseDSILogical(fb))return false; auto cpu=o.timing;cpu.scalerFlags=timing.scalerFlags; @@ -7700,6 +7707,8 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App } receipt[11]=field(fb,0x4338);receipt[12]=field(fb,0x4348); receipt[13]=o.presentationFault;receipt[14]=field(path,0xF8);receipt[15]=field(path,0xFC); + captureGPU(7); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentationGPU",gpuState,sizeof(gpuState)); o.changingThread=0; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentation",receipt,sizeof(receipt)); return changed ? kIOReturnSuccess : kIOReturnNotReady; @@ -11989,7 +11998,7 @@ void XE::display13WMArmUpdate(IOService *fb) { // Retain the software source/destination geometry with the first // failed latch. OS orientation is independent of DSI wire timing; // distinguish a native scaling request from a link/stride mismatch. - uint64_t geometry[48] {2,pipe,c.pipe[pipe].wm[0].width,c.pipe[pipe].wm[0].height, + uint64_t geometry[56] {3,pipe,c.pipe[pipe].wm[0].width,c.pipe[pipe].wm[0].height, c.pipe[pipe].wm[0].htotal,c.pipe[pipe].wm[0].pixelHz,actualHz}; const unsigned fbOffsets[]={0x430C,0x4310,0x4314,0x4320,0x4324,0x4328,0x432C,0x4334,0x4338}; for(unsigned i=0;i(fb,fbOffsets[i]); @@ -12015,6 +12024,12 @@ void XE::display13WMArmUpdate(IOService *fb) { const unsigned registers[]={0x70180,0x70188,0x7018C,0x70190,0x7019C,0x701A4,0x701AC,0x6001C}; for(unsigned i=0;igetMetaClass()->getClassSize()>=0x12DC) { + const unsigned offsets[]={0x1264,0x1268,0x128C,0x1290,0x1294,0x12D8}; + for(unsigned i=0;i(dsiPendingBacking.pipe,offsets[i]); + } adlnPublishMemory("N97FBWMGeometryFailure",pipe,geometry,sizeof(geometry),true); uint64_t cursor[8] {1,pipe};Display13FBDBUFIO readout {c.map}; if(Display13FBIRQ::powered(readout.read(0x45404),pipe)) { diff --git a/tests/dsi_presentation.py b/tests/dsi_presentation.py index 45c9eaf5..8d4d28ba 100644 --- a/tests/dsi_presentation.py +++ b/tests/dsi_presentation.py @@ -14,7 +14,8 @@ using Display13FBMemory::field; using XELP::DSI::AppleAdapter::Timing; using XELP::DSI::NativeBacking::Record; -struct IOService {alignas(8) uint8_t bytes[0x4B90]{};}; +struct IOService {alignas(8) uint8_t bytes[0x4B90]{};IOService *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);}}; +templateconstexpr unsigned arrsize(T(&)[N]){return N;} using OSObject=IOService; templatevoid put(void *p,unsigned offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} struct IOLock {unsigned depth=0;}; @@ -23,7 +24,7 @@ constexpr uint32_t kIOReturnNotReady=0xE00002D8,kIOReturnSuccess=0; uint64_t receiptCopy[16]{}; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *,const void *p,unsigned n){assert(n==sizeof(receiptCopy));memcpy(receiptCopy,p,n);}}; + void setProperty(const char *,const void *p,unsigned n){assert(n==sizeof(receiptCopy) || n==13*sizeof(uint64_t));if(n==sizeof(receiptCopy))memcpy(receiptCopy,p,n);}}; struct {IOService *framebuffer=nullptr;Timing timing,presentation;XELP::DSI::Mode mode; uint32_t clientMode=0x80001000,activeMode=0;uintptr_t logical=0,changingThread=0; bool presentationReady=false,presentationFault=false;}dsiFixedMode; From 86bb7c733090e3c6f73db66bdbbb7925d5a76fd9 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 15:00:38 +0900 Subject: [PATCH 087/139] Adapt DSI full-frame flip destination to physical rotation space --- WhateverXe/kern_dsi_flip.hpp | 35 +++++++++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 18 +++++++++++++++++ tests/dsi_flip.cpp | 38 ++++++++++++++++++++++++++++++++++++ tests/dsi_update_dispatch.py | 18 ++++++++++++++++- 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 WhateverXe/kern_dsi_flip.hpp create mode 100644 tests/dsi_flip.cpp diff --git a/WhateverXe/kern_dsi_flip.hpp b/WhateverXe/kern_dsi_flip.hpp new file mode 100644 index 00000000..a9eb1541 --- /dev/null +++ b/WhateverXe/kern_dsi_flip.hpp @@ -0,0 +1,35 @@ +// Exact TGL FlipTransactionParams adapter. Wire timing and GPU mapping stay native. +#ifndef KERN_DSI_FLIP_HPP +#define KERN_DSI_FLIP_HPP +#include "kern_display13_fb_memory.hpp" +namespace XELP { namespace DSI { namespace AppleAdapter { +constexpr unsigned flipParamsBytes=0xB8; +// Integer-only IEEE754 encoding: kernel code need not borrow SIMD state. +inline uint32_t flipDimensionBits(uint32_t value) { + if(!value || value>8192)return 0; + unsigned exponent=0;for(auto n=value;n>1;n>>=1)++exponent; + return ((127+exponent)<<23) | ((value<<(23-exponent))&0x7FFFFF); +} +// The rotated resource is already in physical coordinates. Adapt only the +// observed single-plane, full-screen mismatch; do not reinterpret arbitrary +// crops, scaling, multiple planes, or a destination already in physical space. +// Native FB copies all 0xB8 bytes before retaining transaction state. +inline bool normalizeFullFrameFlip(const void *params,void *copy,uint32_t transform, + uint32_t physicalWidth,uint32_t physicalHeight) { + using Display13FBMemory::field; + if(!params || !copy || transform!=0x30 || physicalWidth==physicalHeight || + !physicalWidth || !physicalHeight || physicalWidth>8192 || physicalHeight>8192 || + field(params,0)!=1)return false; + const auto width=flipDimensionBits(physicalWidth),height=flipDimensionBits(physicalHeight); + const auto args=static_cast(params)+8; + if((field(args,0xC)&0x7FFFFFFF) || (field(args,0x10)&0x7FFFFFFF) || + (field(args,0x1C)&0x7FFFFFFF) || (field(args,0x20)&0x7FFFFFFF) || + field(args,0x14)!=width || field(args,0x18)!=height || + field(args,0x24)!=height || field(args,0x28)!=width)return false; + memcpy(copy,params,flipParamsBytes); + memcpy(static_cast(copy)+8+0x24,&width,4); + memcpy(static_cast(copy)+8+0x28,&height,4); + return true; +} +}}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f856df09..8a61f0f8 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10,6 +10,7 @@ #include "kern_dsi_native_cache.hpp" #include "kern_dsi_native_backing.hpp" #include "kern_dsi_native_mode.hpp" +#include "kern_dsi_flip.hpp" #include "kern_dsi_test_image.hpp" #include "kern_xe.hpp" #include "kern_framebuffer_donor.hpp" @@ -7814,6 +7815,23 @@ int XE::beginDSIFramebufferWriter(IOService *fb) { } uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t options,void *params) { const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + uint8_t adapted[XELP::DSI::AppleAdapter::flipParamsBytes]; + if(token==1 && params) { + DSIModeScope scope(fb); + const auto transform=Display13FBMemory::field(fb,0x4338); + const auto width=Display13FBMemory::field(fb,0x4320); + const auto height=Display13FBMemory::field(fb,0x4324); + const bool changed=XELP::DSI::AppleAdapter::normalizeFullFrameFlip(params,adapted,transform,width,height); + static unsigned observed=0; + if(observed<8 && transform==0x30) { + uint64_t record[16] {1,++observed,changed,transform,width,height, + Display13FBMemory::field(params,0)}; + for(unsigned i=0;i<8;++i)record[7+i]=Display13FBMemory::field(params,8+0xC+4*i); + char key[40];snprintf(key,sizeof(key),"WEXDSIFlipGeometry%u",observed); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + if(changed)params=adapted; + } const auto result=FunctionCast(wrapDSIFlipTransaction,callbackXE->orgDSITransaction[0])(fb,transaction,options,params); endDSINativeWriter(token);return result; } diff --git a/tests/dsi_flip.cpp b/tests/dsi_flip.cpp new file mode 100644 index 00000000..4dccd6c6 --- /dev/null +++ b/tests/dsi_flip.cpp @@ -0,0 +1,38 @@ +#include "kern_dsi_flip.hpp" +#include +#include +#include +using namespace XELP::DSI::AppleAdapter; +int main() { + for(unsigned n=1;n<=8192;++n) { + float f=n;uint32_t bits;memcpy(&bits,&f,4);assert(flipDimensionBits(n)==bits); + } + uint8_t input[flipParamsBytes],copy[flipParamsBytes],original[flipParamsBytes]; + memset(input,0xA5,sizeof(input));input[0]=1; + const float rects[]={0,0,1080,1920,0,0,1920,1080}; + memcpy(input+0x14,rects,sizeof(rects));memcpy(original,input,sizeof(input)); + assert(normalizeFullFrameFlip(input,copy,0x30,1080,1920)); + assert(!memcmp(input,original,sizeof(input))); + memcpy(original+0x2C,input+0x1C,8);assert(!memcmp(copy,original,sizeof(copy))); + // Already normalized, crops, offsets and invalid floating point values are + // left to the native path. No partial writes on an unsupported request. + assert(!normalizeFullFrameFlip(copy,original,0x30,1080,1920)); + for(unsigned off=0x14;off<=0x30;off+=4) { + uint8_t malformed[flipParamsBytes];memcpy(malformed,input,sizeof(input)); + const uint32_t values[]={0x7FC00000,0x7F800000,0xBF800000,0x3F000000}; + for(auto bits:values) { + memcpy(malformed+off,&bits,4);memset(copy,0x5A,sizeof(copy)); + assert(!normalizeFullFrameFlip(malformed,copy,0x30,1080,1920)); + for(auto byte:copy)assert(byte==0x5A); + } + } + for(unsigned count:{0U,2U,255U}) { + input[0]=count;assert(!normalizeFullFrameFlip(input,copy,0x30,1080,1920)); + } + input[0]=1; + assert(!normalizeFullFrameFlip(input,copy,1,1080,1920)); + assert(!normalizeFullFrameFlip(input,copy,0x30,0,1920)); + assert(!normalizeFullFrameFlip(input,copy,0x30,1080,8193)); + assert(!normalizeFullFrameFlip(nullptr,copy,0x30,1080,1920)); + puts("PASS full-frame DSI flip normalization, byte preservation, unsupported geometry passthrough"); +} diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index d4a79333..cb04835c 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -20,6 +20,7 @@ code=r''' #include "kern_display13_fb_memory.hpp" #include "kern_dsi_apple_timing.hpp" +#include "kern_dsi_flip.hpp" #include "kern_display13_transcoder.hpp" #include #include @@ -29,6 +30,7 @@ using IOService=OSObject; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char *name,const void *data,unsigned bytes){ + if(!strncmp(name,"WEXDSIFlipGeometry",18)){assert(bytes==128);return;} assert(!strcmp(name,"WEXDSIModeRequest") && bytes==64); const auto words=static_cast(data);assert(words[0]==1); }}; @@ -58,6 +60,7 @@ uint32_t nativeImage(IOService*,IOService*,void*); uint32_t nativeState(IOService*,IOService*,int32_t,int32_t,bool,bool); unsigned txCalls[5]{},txAnchor=0,paramAnchor=0;bool nestedTx=false; +uint8_t flipInput[0xB8]{},flipExpected[0xB8]{};bool adaptedFlip=false; constexpr uint64_t txOptions=0xFEDCBA9876543210ULL; uint32_t nativeFlip(IOService*,void*,uint64_t,void*); uint32_t nativeGamma(IOService*,void*,void*); @@ -142,7 +145,10 @@ assert(display13FBPower.dsiNativeWriters==expectedWriters);++txCalls[i];return txResult(i); } uint32_t nativeFlip(IOService *fb,void *tx,uint64_t options,void *params){ - assert(options==txOptions && params==¶mAnchor);const auto result=txLeaf(0,fb,tx); + assert(options==txOptions); + if(adaptedFlip){assert(params!=flipInput && !memcmp(params,flipExpected,sizeof(flipExpected)));} + else assert(params==¶mAnchor); + const auto result=txLeaf(0,fb,tx); if(nestedTx){++expectedWriters;assert(XE::wrapDSICscTransaction(fb,tx,params,options,true)==txResult(2));--expectedWriters; assert(display13FBPower.dsiNativeWriters==expectedWriters);} return result; @@ -207,6 +213,16 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ + {Case c;put(&c.fb,0x4338,0x30U);put(&c.fb,0x4320,1080U);put(&c.fb,0x4324,1920U); + flipInput[0]=1; + const float rects[]={0,0,1080,1920,0,0,1920,1080}; + memcpy(flipInput+0x14,rects,sizeof(rects));memcpy(flipExpected,flipInput,sizeof(flipInput)); + memcpy(flipExpected+0x2C,flipInput+0x1C,8);adaptedFlip=true; + assert(XE::wrapDSIFlipTransaction(&c.fb,&txAnchor,txOptions,flipInput)==txResult(0)); + assert(!memcmp(flipInput+0x14,rects,sizeof(rects)) && !display13FBPower.dsiNativeWriters); + adaptedFlip=false; + } + for(unsigned attr:{0x706F7772U,0x70777273U}){Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==1); assert(Display13FBMemory::field(&c.fb,0x4284)==2); From 18e7dcca2a257981c4a0177040a9badf54530ae4 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:15:44 +0900 Subject: [PATCH 088/139] Keep compositor orientation separate from requested DSI timing --- WhateverXe/kern_dsi_flip.hpp | 5 +++-- WhateverXe/kern_xe.cpp | 30 +++++++++++++++--------------- tests/dsi_apple_adapter.py | 9 +++++---- tests/dsi_update_dispatch.py | 5 +++-- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/WhateverXe/kern_dsi_flip.hpp b/WhateverXe/kern_dsi_flip.hpp index a9eb1541..9bfbd8bd 100644 --- a/WhateverXe/kern_dsi_flip.hpp +++ b/WhateverXe/kern_dsi_flip.hpp @@ -10,8 +10,9 @@ inline uint32_t flipDimensionBits(uint32_t value) { unsigned exponent=0;for(auto n=value;n>1;n>>=1)++exponent; return ((127+exponent)<<23) | ((value<<(23-exponent))&0x7FFFFF); } -// The rotated resource is already in physical coordinates. Adapt only the -// observed single-plane, full-screen mismatch; do not reinterpret arbitrary +// Source dimensions match physical coordinates; this does not prove that its +// pixels have been rotated. Adapt only the observed single-plane, full-screen +// mismatch while qualifying the compositor path; do not reinterpret arbitrary // crops, scaling, multiple planes, or a destination already in physical space. // Native FB copies all 0xB8 bytes before retaining transaction state. inline bool normalizeFullFrameFlip(const void *params,void *copy,uint32_t transform, diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 8a61f0f8..d1a6b967 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4704,20 +4704,17 @@ static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { if(!selected && XELP::DSI::AppleAdapter::acceptsMode(mode,o.clientMode))selected=&o.timing; if(!selected && o.presentationReady && mode==o.activeMode)selected=&o.presentation; if(!selected)return nullptr; - // Big Sur supplies UI rotation separately from its detailed-timing list. - // Convert that selected orientation to the donor's pixel-info/geometry - // contract without altering the immutable portrait wire timing. - auto rotation=o.framebuffer ? OSDynamicCast(OSNumber,o.framebuffer->getProperty("IOFBTransform")) : nullptr; - if(!rotation)return selected; - const auto transform=rotation->unsigned64BitValue(); - if(transform!=0 && transform!=3 && transform!=5)return nullptr; - o.requested=*selected;o.requested.scalerFlags=static_cast(transform<<4); - // IODetailedTimingInformationV2 scaled dimensions describe the image - // before rotation. Active dimensions remain the portrait wire mode. - o.requested.horizontalScaled=transform ? o.timing.verticalActive : 0; - o.requested.verticalScaled=transform ? o.timing.horizontalActive : 0; - return &o.requested; + // UI orientation is not a request for driver-side scaling/rotation. + // Preserve the timing actually supplied by CoreDisplay. Fabricating a + // landscape logical buffer here changes the compositor's target geometry. + return selected; } +static uint32_t dsiUITransform(IOService *fb) { + auto rotation=fb ? OSDynamicCast(OSNumber,fb->getProperty("IOFBTransform")) : nullptr; + const auto value=rotation ? rotation->unsigned64BitValue() : 0; + return value<=UINT32_MAX ? static_cast(value) : UINT32_MAX; +} + static void dsiAdvertiseRotation(IOService *fb) { auto data=OSDynamicCast(OSData,fb->getProperty("IOFBScalerInfo")); if(!data || data->getLength()!=48)return; @@ -7821,12 +7818,15 @@ uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t opt const auto transform=Display13FBMemory::field(fb,0x4338); const auto width=Display13FBMemory::field(fb,0x4320); const auto height=Display13FBMemory::field(fb,0x4324); - const bool changed=XELP::DSI::AppleAdapter::normalizeFullFrameFlip(params,adapted,transform,width,height); + const auto uiTransform=dsiUITransform(fb); + const auto quarterTurn=(uiTransform==3 || uiTransform==5) ? 0x30U : 0U; + const bool changed=XELP::DSI::AppleAdapter::normalizeFullFrameFlip(params,adapted,quarterTurn,width,height); static unsigned observed=0; - if(observed<8 && transform==0x30) { + if(observed<8 && quarterTurn) { uint64_t record[16] {1,++observed,changed,transform,width,height, Display13FBMemory::field(params,0)}; for(unsigned i=0;i<8;++i)record[7+i]=Display13FBMemory::field(params,8+0xC+4*i); + record[15]=uiTransform; char key[40];snprintf(key,sizeof(key),"WEXDSIFlipGeometry%u",observed); IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); } diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 104a4225..49301082 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -235,13 +235,14 @@ for(auto transform:{3ULL,5ULL,0ULL}) { orientation->value=transform; assert(!XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)); - assert(static_cast(timing)->scalerFlags==transform*16); - assert(static_cast(timing)->horizontalScaled==(transform?1920U:0U)); - assert(static_cast(timing)->verticalScaled==(transform?1080U:0U)); + assert(static_cast(timing)->scalerFlags==0); + assert(!static_cast(timing)->horizontalScaled); + assert(!static_cast(timing)->verticalScaled); assert(!dsiFixedMode.timing.scalerFlags && static_cast(timing)->horizontalActive==1080); } orientation->value=7; - assert(XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)==kIOReturnBadArgument); + assert(!XE::wrapDSIValidateMode(fb,static_cast(offered.reservedA[0]),nullptr,&timing)); + assert(!static_cast(timing)->scalerFlags); fb->removeProperty("IOFBTransform");orientation->release(); renewedData->bytes[64]^=1; assert(XE::wrapDSISetDetailedTimings(fb,renewed)==kIOReturnUnsupported && dsiFixedMode.clientMode==0x80003000); diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index cb04835c..274f946d 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -51,6 +51,7 @@ static XELP::DSI::AppleAdapter::Timing t; return XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode)?&t:nullptr; } +unsigned uiTransform=0;uint32_t dsiUITransform(IOService*){return uiTransform;} IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) @@ -213,14 +214,14 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ - {Case c;put(&c.fb,0x4338,0x30U);put(&c.fb,0x4320,1080U);put(&c.fb,0x4324,1920U); + {Case c;uiTransform=3;put(&c.fb,0x4338,1U);put(&c.fb,0x4320,1080U);put(&c.fb,0x4324,1920U); flipInput[0]=1; const float rects[]={0,0,1080,1920,0,0,1920,1080}; memcpy(flipInput+0x14,rects,sizeof(rects));memcpy(flipExpected,flipInput,sizeof(flipInput)); memcpy(flipExpected+0x2C,flipInput+0x1C,8);adaptedFlip=true; assert(XE::wrapDSIFlipTransaction(&c.fb,&txAnchor,txOptions,flipInput)==txResult(0)); assert(!memcmp(flipInput+0x14,rects,sizeof(rects)) && !display13FBPower.dsiNativeWriters); - adaptedFlip=false; + adaptedFlip=false;uiTransform=0; } for(unsigned attr:{0x706F7772U,0x70777273U}){Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); From 3447a22843b68da99cdcad3b5fce13f9890d0f89 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:23:13 +0900 Subject: [PATCH 089/139] Stop advertising unimplemented DSI framebuffer rotation capability --- WhateverXe/kern_xe.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index d1a6b967..9d89ab8b 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4715,12 +4715,6 @@ static uint32_t dsiUITransform(IOService *fb) { return value<=UINT32_MAX ? static_cast(value) : UINT32_MAX; } -static void dsiAdvertiseRotation(IOService *fb) { - auto data=OSDynamicCast(OSData,fb->getProperty("IOFBScalerInfo")); - if(!data || data->getLength()!=48)return; - uint32_t scaler[12];memcpy(scaler,data->getBytesNoCopy(),sizeof(scaler)); - if(!(scaler[4]&0x20)){scaler[4]|=0x20;fb->setProperty("IOFBScalerInfo",scaler,sizeof(scaler));} -} struct DSIModeScope { IORecursiveLock *lock; explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { @@ -7417,7 +7411,7 @@ uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,u path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); uint8_t descriptor[128]; const bool described=ready && XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,descriptor); - if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));dsiAdvertiseRotation(fb);} + if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));} const auto result=described ? kIOReturnSuccess : kIOReturnNotReady; recordDSIClient(fb,11,*online,result);return result; } From a2b32597748d67da918dafb16a36532a633a7f93 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:29:48 +0900 Subject: [PATCH 090/139] Remove speculative DSI flip rectangle rewriting --- WhateverXe/kern_dsi_flip.hpp | 36 ---------------------------------- WhateverXe/kern_xe.cpp | 27 ------------------------- tests/dsi_flip.cpp | 38 ------------------------------------ tests/dsi_update_dispatch.py | 18 +---------------- 4 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 WhateverXe/kern_dsi_flip.hpp delete mode 100644 tests/dsi_flip.cpp diff --git a/WhateverXe/kern_dsi_flip.hpp b/WhateverXe/kern_dsi_flip.hpp deleted file mode 100644 index 9bfbd8bd..00000000 --- a/WhateverXe/kern_dsi_flip.hpp +++ /dev/null @@ -1,36 +0,0 @@ -// Exact TGL FlipTransactionParams adapter. Wire timing and GPU mapping stay native. -#ifndef KERN_DSI_FLIP_HPP -#define KERN_DSI_FLIP_HPP -#include "kern_display13_fb_memory.hpp" -namespace XELP { namespace DSI { namespace AppleAdapter { -constexpr unsigned flipParamsBytes=0xB8; -// Integer-only IEEE754 encoding: kernel code need not borrow SIMD state. -inline uint32_t flipDimensionBits(uint32_t value) { - if(!value || value>8192)return 0; - unsigned exponent=0;for(auto n=value;n>1;n>>=1)++exponent; - return ((127+exponent)<<23) | ((value<<(23-exponent))&0x7FFFFF); -} -// Source dimensions match physical coordinates; this does not prove that its -// pixels have been rotated. Adapt only the observed single-plane, full-screen -// mismatch while qualifying the compositor path; do not reinterpret arbitrary -// crops, scaling, multiple planes, or a destination already in physical space. -// Native FB copies all 0xB8 bytes before retaining transaction state. -inline bool normalizeFullFrameFlip(const void *params,void *copy,uint32_t transform, - uint32_t physicalWidth,uint32_t physicalHeight) { - using Display13FBMemory::field; - if(!params || !copy || transform!=0x30 || physicalWidth==physicalHeight || - !physicalWidth || !physicalHeight || physicalWidth>8192 || physicalHeight>8192 || - field(params,0)!=1)return false; - const auto width=flipDimensionBits(physicalWidth),height=flipDimensionBits(physicalHeight); - const auto args=static_cast(params)+8; - if((field(args,0xC)&0x7FFFFFFF) || (field(args,0x10)&0x7FFFFFFF) || - (field(args,0x1C)&0x7FFFFFFF) || (field(args,0x20)&0x7FFFFFFF) || - field(args,0x14)!=width || field(args,0x18)!=height || - field(args,0x24)!=height || field(args,0x28)!=width)return false; - memcpy(copy,params,flipParamsBytes); - memcpy(static_cast(copy)+8+0x24,&width,4); - memcpy(static_cast(copy)+8+0x28,&height,4); - return true; -} -}}} -#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 9d89ab8b..577e53ad 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10,7 +10,6 @@ #include "kern_dsi_native_cache.hpp" #include "kern_dsi_native_backing.hpp" #include "kern_dsi_native_mode.hpp" -#include "kern_dsi_flip.hpp" #include "kern_dsi_test_image.hpp" #include "kern_xe.hpp" #include "kern_framebuffer_donor.hpp" @@ -4709,12 +4708,6 @@ static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { // landscape logical buffer here changes the compositor's target geometry. return selected; } -static uint32_t dsiUITransform(IOService *fb) { - auto rotation=fb ? OSDynamicCast(OSNumber,fb->getProperty("IOFBTransform")) : nullptr; - const auto value=rotation ? rotation->unsigned64BitValue() : 0; - return value<=UINT32_MAX ? static_cast(value) : UINT32_MAX; -} - struct DSIModeScope { IORecursiveLock *lock; explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { @@ -7806,26 +7799,6 @@ int XE::beginDSIFramebufferWriter(IOService *fb) { } uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t options,void *params) { const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - uint8_t adapted[XELP::DSI::AppleAdapter::flipParamsBytes]; - if(token==1 && params) { - DSIModeScope scope(fb); - const auto transform=Display13FBMemory::field(fb,0x4338); - const auto width=Display13FBMemory::field(fb,0x4320); - const auto height=Display13FBMemory::field(fb,0x4324); - const auto uiTransform=dsiUITransform(fb); - const auto quarterTurn=(uiTransform==3 || uiTransform==5) ? 0x30U : 0U; - const bool changed=XELP::DSI::AppleAdapter::normalizeFullFrameFlip(params,adapted,quarterTurn,width,height); - static unsigned observed=0; - if(observed<8 && quarterTurn) { - uint64_t record[16] {1,++observed,changed,transform,width,height, - Display13FBMemory::field(params,0)}; - for(unsigned i=0;i<8;++i)record[7+i]=Display13FBMemory::field(params,8+0xC+4*i); - record[15]=uiTransform; - char key[40];snprintf(key,sizeof(key),"WEXDSIFlipGeometry%u",observed); - IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); - } - if(changed)params=adapted; - } const auto result=FunctionCast(wrapDSIFlipTransaction,callbackXE->orgDSITransaction[0])(fb,transaction,options,params); endDSINativeWriter(token);return result; } diff --git a/tests/dsi_flip.cpp b/tests/dsi_flip.cpp deleted file mode 100644 index 4dccd6c6..00000000 --- a/tests/dsi_flip.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "kern_dsi_flip.hpp" -#include -#include -#include -using namespace XELP::DSI::AppleAdapter; -int main() { - for(unsigned n=1;n<=8192;++n) { - float f=n;uint32_t bits;memcpy(&bits,&f,4);assert(flipDimensionBits(n)==bits); - } - uint8_t input[flipParamsBytes],copy[flipParamsBytes],original[flipParamsBytes]; - memset(input,0xA5,sizeof(input));input[0]=1; - const float rects[]={0,0,1080,1920,0,0,1920,1080}; - memcpy(input+0x14,rects,sizeof(rects));memcpy(original,input,sizeof(input)); - assert(normalizeFullFrameFlip(input,copy,0x30,1080,1920)); - assert(!memcmp(input,original,sizeof(input))); - memcpy(original+0x2C,input+0x1C,8);assert(!memcmp(copy,original,sizeof(copy))); - // Already normalized, crops, offsets and invalid floating point values are - // left to the native path. No partial writes on an unsupported request. - assert(!normalizeFullFrameFlip(copy,original,0x30,1080,1920)); - for(unsigned off=0x14;off<=0x30;off+=4) { - uint8_t malformed[flipParamsBytes];memcpy(malformed,input,sizeof(input)); - const uint32_t values[]={0x7FC00000,0x7F800000,0xBF800000,0x3F000000}; - for(auto bits:values) { - memcpy(malformed+off,&bits,4);memset(copy,0x5A,sizeof(copy)); - assert(!normalizeFullFrameFlip(malformed,copy,0x30,1080,1920)); - for(auto byte:copy)assert(byte==0x5A); - } - } - for(unsigned count:{0U,2U,255U}) { - input[0]=count;assert(!normalizeFullFrameFlip(input,copy,0x30,1080,1920)); - } - input[0]=1; - assert(!normalizeFullFrameFlip(input,copy,1,1080,1920)); - assert(!normalizeFullFrameFlip(input,copy,0x30,0,1920)); - assert(!normalizeFullFrameFlip(input,copy,0x30,1080,8193)); - assert(!normalizeFullFrameFlip(nullptr,copy,0x30,1080,1920)); - puts("PASS full-frame DSI flip normalization, byte preservation, unsupported geometry passthrough"); -} diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index 274f946d..fb2b578a 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -20,7 +20,6 @@ code=r''' #include "kern_display13_fb_memory.hpp" #include "kern_dsi_apple_timing.hpp" -#include "kern_dsi_flip.hpp" #include "kern_display13_transcoder.hpp" #include #include @@ -30,7 +29,6 @@ using IOService=OSObject; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char *name,const void *data,unsigned bytes){ - if(!strncmp(name,"WEXDSIFlipGeometry",18)){assert(bytes==128);return;} assert(!strcmp(name,"WEXDSIModeRequest") && bytes==64); const auto words=static_cast(data);assert(words[0]==1); }}; @@ -51,7 +49,6 @@ static XELP::DSI::AppleAdapter::Timing t; return XELP::DSI::AppleAdapter::acceptsMode(mode,dsiFixedMode.clientMode)?&t:nullptr; } -unsigned uiTransform=0;uint32_t dsiUITransform(IOService*){return uiTransform;} IOService *panel=nullptr,*nativeFB=nullptr;bool dsiModeTarget(IOService *f){return f && f==panel;} constexpr uint32_t kIOReturnNotReady=0xe00002d8U,kIOReturnUnsupported=0xe00002c7U,kIOReturnSuccess=0; #define FunctionCast(f,p) reinterpret_cast(p) @@ -61,7 +58,6 @@ uint32_t nativeImage(IOService*,IOService*,void*); uint32_t nativeState(IOService*,IOService*,int32_t,int32_t,bool,bool); unsigned txCalls[5]{},txAnchor=0,paramAnchor=0;bool nestedTx=false; -uint8_t flipInput[0xB8]{},flipExpected[0xB8]{};bool adaptedFlip=false; constexpr uint64_t txOptions=0xFEDCBA9876543210ULL; uint32_t nativeFlip(IOService*,void*,uint64_t,void*); uint32_t nativeGamma(IOService*,void*,void*); @@ -146,10 +142,7 @@ assert(display13FBPower.dsiNativeWriters==expectedWriters);++txCalls[i];return txResult(i); } uint32_t nativeFlip(IOService *fb,void *tx,uint64_t options,void *params){ - assert(options==txOptions); - if(adaptedFlip){assert(params!=flipInput && !memcmp(params,flipExpected,sizeof(flipExpected)));} - else assert(params==¶mAnchor); - const auto result=txLeaf(0,fb,tx); + assert(options==txOptions && params==¶mAnchor);const auto result=txLeaf(0,fb,tx); if(nestedTx){++expectedWriters;assert(XE::wrapDSICscTransaction(fb,tx,params,options,true)==txResult(2));--expectedWriters; assert(display13FBPower.dsiNativeWriters==expectedWriters);} return result; @@ -214,15 +207,6 @@ void refuse(){const auto calls=armCalls;XE::wrapDisplay13WMArmUpdate(&fb);assert(calls==armCalls && !fbLock.depth && !owner);} }; int main(){ - {Case c;uiTransform=3;put(&c.fb,0x4338,1U);put(&c.fb,0x4320,1080U);put(&c.fb,0x4324,1920U); - flipInput[0]=1; - const float rects[]={0,0,1080,1920,0,0,1920,1080}; - memcpy(flipInput+0x14,rects,sizeof(rects));memcpy(flipExpected,flipInput,sizeof(flipInput)); - memcpy(flipExpected+0x2C,flipInput+0x1C,8);adaptedFlip=true; - assert(XE::wrapDSIFlipTransaction(&c.fb,&txAnchor,txOptions,flipInput)==txResult(0)); - assert(!memcmp(flipInput+0x14,rects,sizeof(rects)) && !display13FBPower.dsiNativeWriters); - adaptedFlip=false;uiTransform=0; - } for(unsigned attr:{0x706F7772U,0x70777273U}){Case c;xe.dsiPowerTest=true;put(&c.fb,0x4284,2U); assert(XE::wrapDSISetAttribute(&c.fb,attr,1)==kIOReturnNotReady && xe.stopCalls==1); From 114f0f80ddf58bc17f5268351021c4c6ec3952c5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:00:21 +0900 Subject: [PATCH 091/139] Support scaled GPU presentation with fixed DSI panel timing --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_apple_timing.hpp | 19 +++++++++++++------ WhateverXe/kern_dsi_native_mode.hpp | 7 ++++--- WhateverXe/kern_xe.cpp | 5 +++-- tests/dsi_apple_adapter.py | 2 +- tests/dsi_apple_timing.cpp | 9 +++++++-- tests/dsi_native_mode_fixture.cpp | 17 +++++++---------- tests/dsi_presentation.py | 12 +++++++++--- 9 files changed, 46 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 57f69f2a..80f9a33b 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index ecac23d1..bf4cdffe 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 734ed84e..b6814215 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -23,15 +23,19 @@ struct ModeInformation { uint32_t reserved[3] {}; }; static_assert(sizeof(ModeInformation)==36,"IODisplayModeInformation ABI"); -inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false) { - if(!m.valid() || info.width!=(rotated?m.height:m.width) || info.height!=(rotated?m.width:m.height))return false; +inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false,uint32_t drawWidth=0,uint32_t drawHeight=0) { + const auto width=drawWidth?drawWidth:(rotated?m.height:m.width); + const auto height=drawHeight?drawHeight:(rotated?m.width:m.height); + if(!m.valid() || info.width!=width || info.height!=height)return false; info.refresh=static_cast((uint64_t(m.clockKHz)*1000*65536+m.htotal()*uint64_t(m.vtotal())/2)/ (m.htotal()*uint64_t(m.vtotal()))); info.depth=0; // the adopted DSI surface is RGB8 only info.widthMM=static_cast(rotated?m.heightMM:m.widthMM);info.heightMM=static_cast(rotated?m.widthMM:m.heightMM); // IOGraphics: valid/safe/default + built-in + native. Acceleration is a // separate runtime qualification, never inferred from a fixed mode. - info.flags|=0x02000407; + info.flags|=0x403; + if(width==(rotated?m.height:m.width) && height==(rotated?m.width:m.height))info.flags|=0x02000004; + else info.flags&=~0x02000004U; // A scaled source is not the native/default mode. return true; } // CoreDisplay can replace the reserved client IDs when rebuilding its mode @@ -50,11 +54,14 @@ inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned inde inline bool acceptsMode(uint32_t mode,uint32_t clientMode) { return mode==FixedModeID || mode==clientMode; } -// Presentation changes never alter the qualified DSI wire timing. Initially -// admit only native-size quarter-turns; scaled sources are a separate stage. +// The donor advertises an 8191-pixel source limit through IOFBScalerInfo. +// Rotation and HiDPI change the GPU source, never the qualified DSI wire timing. +inline bool validPresentationSize(uint32_t width,uint32_t height) { + return width && height && width<=8191 && height<=8191; +} inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigned index) { if(offered.scalerFlags!=0x30 && offered.scalerFlags!=0x50)return false; - if(offered.horizontalScaled!=fixed.verticalActive || offered.verticalScaled!=fixed.horizontalActive)return false; + if(!validPresentationSize(offered.horizontalScaled,offered.verticalScaled))return false; auto native=offered;native.scalerFlags=0;native.horizontalScaled=0;native.verticalScaled=0; return clientTiming(fixed,native,index); } diff --git a/WhateverXe/kern_dsi_native_mode.hpp b/WhateverXe/kern_dsi_native_mode.hpp index 81be1089..d74abd56 100644 --- a/WhateverXe/kern_dsi_native_mode.hpp +++ b/WhateverXe/kern_dsi_native_mode.hpp @@ -6,7 +6,7 @@ #include "kern_display13_fb_memory.hpp" namespace XELP { namespace DSI { namespace AppleAdapter { // The caller holds the FB mutex and validates object classes/ownership. No -// resized mode is admitted. Quarter-turn preparation retains the physical +// wire timing changes are admitted. Quarter-turn preparation retains the physical // scanout; its separate logical buffer must be retired by the caller first. template bool prepareGeometry(void *fb,void *path,const Profile &profile, const Timing &timing,Compute compute,bool presentation=false) { @@ -15,7 +15,8 @@ template bool prepareGeometry(void *fb,void *path,const Profile & if(!makeTiming(profile,expected))return false; const bool rotated=presentation && (timing.scalerFlags==0x30 || timing.scalerFlags==0x50); if(rotated) {expected.scalerFlags=timing.scalerFlags; - expected.horizontalScaled=expected.verticalActive;expected.verticalScaled=expected.horizontalActive;} + if(!validPresentationSize(timing.horizontalScaled,timing.verticalScaled))return false; + expected.horizontalScaled=timing.horizontalScaled;expected.verticalScaled=timing.verticalScaled;} if(!fb || !path || memcmp(&expected,&timing,sizeof(timing)) || Display13FBMemory::singlePath(fb)!=path || field(fb,0x4340) || (!presentation && field(fb,0x4348)))return false; @@ -28,7 +29,7 @@ template bool prepareGeometry(void *fb,void *path,const Profile & const auto put=[](void *o,unsigned offset,uint32_t v){memcpy(static_cast(o)+offset,&v,4);}; put(fb,0x4350,4);*(static_cast(fb)+0x4A18)=1; compute(fb,&timing); - const auto &m=profile.mode;const uint32_t width=rotated?m.height:m.width,height=rotated?m.width:m.height; + const auto &m=profile.mode;const uint32_t width=rotated?timing.horizontalScaled:m.width,height=rotated?timing.verticalScaled:m.height; const uint32_t pitch=((width*4+127)/128)*128; const bool valid=field(fb,0x430C)==width && field(fb,0x4310)==height && field(fb,0x4314)==pitch*m.height && field(fb,0x4320)==m.width && diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 577e53ad..57ac5fa7 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7517,7 +7517,7 @@ uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { const bool rotated=selected->scalerFlags!=0; const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, - *static_cast(raw),rotated))return kIOReturnNotReady; + *static_cast(raw),rotated,selected->horizontalScaled,selected->verticalScaled))return kIOReturnNotReady; return result; }(); recordDSIClient(fb,5,static_cast(mode),result); @@ -7641,7 +7641,8 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App if(!path || !pipe || !dsiArmedBackingOwned(fb,path))return kIOReturnNotReady; const auto old=o.presentationReady ? o.presentation : o.timing; const uint32_t oldMode=o.presentationReady ? o.activeMode : o.clientMode; - if(requested.scalerFlags==old.scalerFlags) { + if(requested.scalerFlags==old.scalerFlags && requested.horizontalScaled==old.horizontalScaled && + requested.verticalScaled==old.verticalScaled) { o.presentation=requested;o.activeMode=mode;o.presentationReady=true; *reinterpret_cast(reinterpret_cast(fb)+0x4250)=mode; return kIOReturnSuccess; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 49301082..75e797e8 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -230,7 +230,7 @@ assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1920 && info.height==1080); rotationData->release();rotationList->release(); - // Big Sur keeps timing flags zero and publishes UI rotation separately. + // Legacy UI attributes alone must never fabricate a presentation timing. auto orientation=new OSNumber;fb->setProperty("IOFBTransform",orientation); for(auto transform:{3ULL,5ULL,0ULL}) { orientation->value=transform; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 762705f4..401a55f5 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -32,8 +32,13 @@ int main() { assert(!AppleAdapter::presentationTiming(t,r,1)); r.horizontalScaled=t.verticalActive;r.verticalScaled=t.horizontalActive; assert(AppleAdapter::presentationTiming(t,r,1)); - r.horizontalScaled=1620;r.verticalScaled=2880; - assert(!AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=2880;r.verticalScaled=1620; + assert(AppleAdapter::presentationTiming(t,r,1)); + AppleAdapter::ModeInformation info;info.width=2880;info.height=1620;info.flags=0x02000004; + assert(AppleAdapter::annotateMode(p.mode,info,true,2880,1620)); + assert(!(info.flags&0x02000004) && info.widthMM==154 && info.heightMM==86); + r.horizontalScaled=8192;assert(!AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=2880;r.verticalScaled=0;assert(!AppleAdapter::presentationTiming(t,r,1)); r=offered;r.scalerFlags=rotation;r.horizontalActive=1920;r.verticalActive=1080; assert(!AppleAdapter::presentationTiming(t,r,1)); } diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index 5677bd63..b0a73071 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -114,23 +114,20 @@ int main(int argc,char **argv) { for(uint32_t rotation:{0x30U,0x50U})for(bool hidpi:{false,true}) { auto presentation=t;presentation.scalerFlags=rotation; presentation.horizontalScaled=1920;presentation.verticalScaled=1080; - if(hidpi){presentation.horizontalScaled=1620;presentation.verticalScaled=2880;} + if(hidpi){presentation.horizontalScaled=2880;presentation.verticalScaled=1620;} put(fb,0x4348,uintptr_t(0x12345000)); - if(hidpi) { - assert(!AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); - compute(fb,&presentation); // reference only; not admitted by the production adapter - } else assert(AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); - assert(field(fb,0x430C)==(hidpi?1620U:1920U)); - assert(field(fb,0x4310)==(hidpi?2880U:1080U)); + assert(AppleAdapter::prepareGeometry(fb,path,p,presentation,compute,true)); + assert(field(fb,0x430C)==(hidpi?2880U:1920U)); + assert(field(fb,0x4310)==(hidpi?1620U:1080U)); for(unsigned offset:{0x4320U,0x4328U})assert(field(fb,offset)==1080); for(unsigned offset:{0x4324U,0x432CU})assert(field(fb,offset)==1920); assert(field(fb,0x4338)==0x30U); - assert(field(fb,0x4334)==(hidpi?6528U:7680U)); + assert(field(fb,0x4334)==(hidpi?11520U:7680U)); assert(field(path,0xF8)==1080 && field(path,0xFC)==1920); assert(allocateLogical(fb)==0); auto record=field(fb,0x4340); - assert(record && record->pitch==(hidpi?6528U:7680U)); - assert(record->bytes==record->pitch*(hidpi?2880U:1080U)); + assert(record && record->pitch==(hidpi?11520U:7680U)); + assert(record->bytes==record->pitch*(hidpi?1620U:1080U)); assert(field(fb,0x4348)==0x12345000); // Logical allocation preserves the scanout. releaseLogical(fb);assert(!field(fb,0x4340)); } diff --git a/tests/dsi_presentation.py b/tests/dsi_presentation.py index 8d4d28ba..dfa962e3 100644 --- a/tests/dsi_presentation.py +++ b/tests/dsi_presentation.py @@ -60,7 +60,8 @@ assert(dsiFixedMode.changingThread && !field(f,0x4340)); assert(field(f,0x4250)==dsiFixedMode.activeMode); if(!dsiFixedMode.presentation.scalerFlags)return 0; - ++allocations;auto r=new Record;r->kind=2;r->columns=60;r->rows=34;r->pitch=7680;r->bytes=8294400; + ++allocations;auto r=new Record;r->kind=2;r->columns=(field(f,0x430C)*4+127)/128;r->rows=(field(f,0x4310)+31)/32; + r->pitch=field(f,0x4334);r->bytes=r->pitch*field(f,0x4310); r->memory=0x200000;r->map=0x300000; put(f,0x4340,r);put(dsiPendingBacking.pipe,0x12B8,r->memory);put(dsiPendingBacking.pipe,0x12C0,r->map); return failAllocation?0xE00002BD:0; @@ -84,9 +85,14 @@ assert(!xe.setDSIPresentation(&fb,2,turn) && o.logical && o.activeMode==2 && !o.changingThread); assert(field(&fb,0x4338)==0x30 && dsiPublishedTarget(&fb)); turn.scalerFlags=0x50;assert(!xe.setDSIPresentation(&fb,3,turn) && allocations==2 && frees==1); - assert(!xe.setDSIPresentation(&fb,4,o.timing) && !o.logical && frees==2 && field(&fb,0x4338)==1); + auto scaled=turn;scaled.horizontalScaled=2880;scaled.verticalScaled=1620; + assert(!xe.setDSIPresentation(&fb,8,scaled) && allocations==3 && frees==2); + assert(field(&fb,0x430C)==2880 && field(&fb,0x4340)->bytes==18662400); + assert(!xe.setDSIPresentation(&fb,9,scaled) && allocations==3 && frees==2); + assert(!xe.setDSIPresentation(&fb,3,turn) && allocations==4 && frees==3); + assert(!xe.setDSIPresentation(&fb,4,o.timing) && !o.logical && frees==4 && field(&fb,0x4338)==1); failAllocation=true;assert(xe.setDSIPresentation(&fb,5,turn)==kIOReturnNotReady); - assert(o.activeMode==4 && !o.logical && !o.presentationFault && !o.changingThread && allocations==3 && frees==3); + assert(o.activeMode==4 && !o.logical && !o.presentationFault && !o.changingThread && allocations==5 && frees==5); assert(field(&fb,0x4338)==1 && dsiPublishedTarget(&fb) && receiptCopy[10]==1); failAllocation=false;assert(!xe.setDSIPresentation(&fb,6,turn));failRelease=true; assert(xe.setDSIPresentation(&fb,7,o.timing)==kIOReturnNotReady && o.presentationFault && !o.changingThread); From fd343995188ccacd4d24b8f016b14f372dcbb4ae Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:07:27 +0900 Subject: [PATCH 092/139] Record rejected DSI timing requests for mode admission diagnosis --- WhateverXe/kern_xe.cpp | 7 ++++++- tests/dsi_apple_adapter.py | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 57ac5fa7..ff5eff7a 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7443,7 +7443,12 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto &t=*static_cast(bytes->getBytesNoCopy()); const bool native=XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i); - if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i))continue; + if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { + // Keep the last rejected OS request across later successful + // enumeration calls, so clock/geometry admission is observable. + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingRejected",bytes); + continue; + } if(count==arrsize(accepted))return kIOReturnNoResources; accepted[count++]=t;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 75e797e8..fe253619 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -64,8 +64,9 @@ void removeProperty(const char *k){auto i=properties.find(k);if(i!=properties.end()){i->second->release();properties.erase(i);}} ~IOService(){for(auto i:properties)i.second->release();} }; -struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *key,const void *p,unsigned n){assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; +struct IORegistryEntry {void setProperty(const char *key,OSData *d){assert(!strcmp(key,"WEXDSITimingRejected") && d->getLength()==160);} + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *key,const void *p,unsigned n){if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} From 9dc2ea1cc25d03368d87f198714c0e3d03a8c1e3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:12:57 +0900 Subject: [PATCH 093/139] Expose DSI GPU presentation support during mode enumeration --- WhateverXe/kern_xe.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ff5eff7a..e269b1d8 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4708,6 +4708,14 @@ static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { // landscape logical buffer here changes the compositor's target geometry. return selected; } +// The DSI adapter now accepts actual OS quarter-turn timings and allocates +// their scaled GPU source. Expose that support when CoreDisplay builds modes. +static void dsiAdvertisePresentation(IOService *fb) { + auto data=OSDynamicCast(OSData,fb->getProperty("IOFBScalerInfo")); + if(!data || data->getLength()!=48)return; + uint32_t scaler[12];memcpy(scaler,data->getBytesNoCopy(),sizeof(scaler)); + if(!(scaler[4]&0x20)){scaler[4]|=0x20;fb->setProperty("IOFBScalerInfo",scaler,sizeof(scaler));} +} struct DSIModeScope { IORecursiveLock *lock; explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { @@ -7404,7 +7412,7 @@ uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,u path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); uint8_t descriptor[128]; const bool described=ready && XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,descriptor); - if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));} + if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));dsiAdvertisePresentation(fb);} const auto result=described ? kIOReturnSuccess : kIOReturnNotReady; recordDSIClient(fb,11,*online,result);return result; } From c90fbb53dc39198bb5a29541506ddb7fdf755e2f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:16:58 +0900 Subject: [PATCH 094/139] Accept complete scaled DSI mode lists and exact panel clocks --- WhateverXe/kern_dsi_apple_timing.hpp | 5 +++-- WhateverXe/kern_xe.cpp | 21 +++++++++++++++------ tests/dsi_apple_adapter.py | 16 ++++++++++++++++ tests/dsi_apple_timing.cpp | 3 +++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index b6814215..65d35853 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -44,8 +44,9 @@ inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false, inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned index) { if(index>=1024 || offered.reservedA[0]<0x80000000U || offered.reservedA[0]==UINT32_MAX || offered.signalLevels>1)return false; const uint64_t rounded=((fixed.pixelClock+5000)/10000)*10000; - if(offered.pixelClock!=rounded || offered.minPixelClock!=rounded || offered.maxPixelClock!=rounded || - roundedfixed.maxPixelClock)return false; + if((offered.pixelClock!=rounded && offered.pixelClock!=fixed.pixelClock) || + offered.minPixelClock!=offered.pixelClock || offered.maxPixelClock!=offered.pixelClock || + offered.pixelClockfixed.maxPixelClock)return false; auto normalized=offered; normalized.reservedA[0]=fixed.reservedA[0];normalized.signalLevels=fixed.signalLevels; normalized.pixelClock=fixed.pixelClock;normalized.minPixelClock=fixed.minPixelClock;normalized.maxPixelClock=fixed.maxPixelClock; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e269b1d8..b18ceb02 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4678,7 +4678,7 @@ struct DSIFixedModeOwner { OSData *data {}; XELP::DSI::Mode mode; XELP::DSI::AppleAdapter::Timing timing; - XELP::DSI::AppleAdapter::Timing presentation,requested,offered[8]; + XELP::DSI::AppleAdapter::Timing presentation,requested,offered[32]; unsigned offerCount {}; uint32_t activeMode {XELP::DSI::AppleAdapter::FixedModeID}; uintptr_t changingThread {},logical {}; @@ -7444,7 +7444,6 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; uint32_t client=0;unsigned count=0; - XELP::DSI::AppleAdapter::Timing accepted[8]; uint64_t offer[16] {3,array ? array->getCount() : 0,UINT32_MAX}; if(array && array->getCount()<=1024)for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); @@ -7457,8 +7456,8 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingRejected",bytes); continue; } - if(count==arrsize(accepted))return kIOReturnNoResources; - accepted[count++]=t;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; + if(count==arrsize(dsiFixedMode.offered))return kIOReturnNoResources; + ++count;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled; @@ -7473,8 +7472,18 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); if(result==kIOReturnSuccess) { if(!dsiModeCoherent(fb))return kIOReturnNotReady; - dsiFixedMode.offerCount=count; - memcpy(dsiFixedMode.offered,accepted,count*sizeof(accepted[0])); + // Validate the whole list before publishing it; avoid a timing + // array on the kernel stack. The caller retains this immutable + // array and the FB mutex serializes both passes. + unsigned copied=0; + if(array)for(unsigned i=0;igetCount();++i) { + auto bytes=OSDynamicCast(OSData,array->getObject(i)); + if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; + const auto &t=*static_cast(bytes->getBytesNoCopy()); + if(XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i) || + XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i))dsiFixedMode.offered[copied++]=t; + } + dsiFixedMode.offerCount=copied; if(client)dsiFixedMode.clientMode=client; } return result; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index fe253619..c153e3d2 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -230,6 +230,22 @@ assert(!XE::wrapDSIValidateMode(fb,pair[1],nullptr,&timing)); assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1920 && info.height==1080); + // CoreDisplay offers both exact-clock and EDID-rounded scaled modes. + auto many=OSArray::withCapacity(1); + for(unsigned i=0;i<24;++i){auto t=rotated;t.reservedA[0]=0x80006000+i; + t.horizontalScaled=2880;t.verticalScaled=1620; + t.pixelClock=t.minPixelClock=t.maxPixelClock=(i&1)?135255000:135260000; + auto data=OSData::withBytes(&t,sizeof(t));many->setObject(data);data->release();} + assert(!XE::wrapDSISetDetailedTimings(fb,many) && XE::wrapDSIGetModeCount(fb)==24); + int32_t modes[32];assert(!XE::wrapDSIGetModes(fb,modes)); + for(unsigned i=0;i<24;++i){assert(static_cast(modes[i])==0x80006000+i); + assert(!XE::wrapDSIValidateMode(fb,modes[i],nullptr,&timing)); + assert(static_cast(timing)->horizontalScaled==2880); + assert(!XE::wrapDSIGetModeInformation(fb,modes[i],&info) && info.width==2880 && info.height==1620);} + for(unsigned i=24;i<33;++i){auto t=rotated;t.reservedA[0]=0x80006000+i; + auto data=OSData::withBytes(&t,sizeof(t));many->setObject(data);data->release();} + assert(XE::wrapDSISetDetailedTimings(fb,many)==kIOReturnNoResources && XE::wrapDSIGetModeCount(fb)==24); + many->release();assert(!XE::wrapDSISetDetailedTimings(fb,rotationList)); rotationData->release();rotationList->release(); // Legacy UI attributes alone must never fabricate a presentation timing. auto orientation=new OSNumber;fb->setProperty("IOFBTransform",orientation); diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 401a55f5..8bbd6c34 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -18,6 +18,9 @@ int main() { auto corrupt=offered;reinterpret_cast(&corrupt)[offset]^=0x80; assert(!AppleAdapter::clientTiming(t,corrupt,0)); } + auto exact=offered;exact.pixelClock=exact.minPixelClock=exact.maxPixelClock=t.pixelClock; + assert(AppleAdapter::clientTiming(t,exact,1)); + exact.minPixelClock--;assert(!AppleAdapter::clientTiming(t,exact,1)); assert(AppleAdapter::clientTiming(t,offered,1)); // IDs are opaque, not array positions. assert(!AppleAdapter::clientTiming(t,offered,1024)); for(uint32_t id:{0x80002000U,0x80003000U,0x80005007U}) { From b4038835e48b0f9759ff3ba69c0742c291f63486 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:24:19 +0900 Subject: [PATCH 095/139] Expose bounded DSI presentation mode list for diagnostics --- WhateverXe/kern_xe.cpp | 2 ++ tests/dsi_apple_adapter.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index b18ceb02..bb688125 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7484,6 +7484,8 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i))dsiFixedMode.offered[copied++]=t; } dsiFixedMode.offerCount=copied; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingList",dsiFixedMode.offered, + copied*sizeof(dsiFixedMode.offered[0])); if(client)dsiFixedMode.clientMode=client; } return result; diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index c153e3d2..9b91f060 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -66,7 +66,7 @@ }; struct IORegistryEntry {void setProperty(const char *key,OSData *d){assert(!strcmp(key,"WEXDSITimingRejected") && d->getLength()==160);} static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *key,const void *p,unsigned n){if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; + void setProperty(const char *key,const void *p,unsigned n){if(!strcmp(key,"WEXDSITimingList")){assert(n<=32*160 && !(n%160));return;}if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} From dad83869d84ead1d8eae9bfec988a212fe0f0d7b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:34:09 +0900 Subject: [PATCH 096/139] Use native GPU scaling for portrait DSI presentation --- WhateverXe/kern_dsi_apple_timing.hpp | 2 +- WhateverXe/kern_dsi_native_mode.hpp | 11 +++++++---- WhateverXe/kern_xe.cpp | 21 +++++++++++++++++++-- WhateverXe/kern_xe.hpp | 3 ++- tests/dsi_apple_timing.cpp | 3 +++ tests/dsi_native_mode_fixture.cpp | 14 +++++++++++++- tests/dsi_presentation.py | 12 +++++++++--- 7 files changed, 54 insertions(+), 12 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 65d35853..f8a9681b 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -61,7 +61,7 @@ inline bool validPresentationSize(uint32_t width,uint32_t height) { return width && height && width<=8191 && height<=8191; } inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigned index) { - if(offered.scalerFlags!=0x30 && offered.scalerFlags!=0x50)return false; + if(offered.scalerFlags!=0 && offered.scalerFlags!=0x30 && offered.scalerFlags!=0x50)return false; if(!validPresentationSize(offered.horizontalScaled,offered.verticalScaled))return false; auto native=offered;native.scalerFlags=0;native.horizontalScaled=0;native.verticalScaled=0; return clientTiming(fixed,native,index); diff --git a/WhateverXe/kern_dsi_native_mode.hpp b/WhateverXe/kern_dsi_native_mode.hpp index d74abd56..f09a7f32 100644 --- a/WhateverXe/kern_dsi_native_mode.hpp +++ b/WhateverXe/kern_dsi_native_mode.hpp @@ -6,7 +6,7 @@ #include "kern_display13_fb_memory.hpp" namespace XELP { namespace DSI { namespace AppleAdapter { // The caller holds the FB mutex and validates object classes/ownership. No -// wire timing changes are admitted. Quarter-turn preparation retains the physical +// wire timing changes are admitted. GPU presentation preparation retains the physical // scanout; its separate logical buffer must be retired by the caller first. template bool prepareGeometry(void *fb,void *path,const Profile &profile, const Timing &timing,Compute compute,bool presentation=false) { @@ -14,7 +14,10 @@ template bool prepareGeometry(void *fb,void *path,const Profile & Timing expected; if(!makeTiming(profile,expected))return false; const bool rotated=presentation && (timing.scalerFlags==0x30 || timing.scalerFlags==0x50); - if(rotated) {expected.scalerFlags=timing.scalerFlags; + const bool scaled=presentation && (timing.horizontalScaled || timing.verticalScaled); + if(rotated || scaled) { + if(timing.scalerFlags && !rotated)return false; + expected.scalerFlags=timing.scalerFlags; if(!validPresentationSize(timing.horizontalScaled,timing.verticalScaled))return false; expected.horizontalScaled=timing.horizontalScaled;expected.verticalScaled=timing.verticalScaled;} if(!fb || !path || memcmp(&expected,&timing,sizeof(timing)) || @@ -29,13 +32,13 @@ template bool prepareGeometry(void *fb,void *path,const Profile & const auto put=[](void *o,unsigned offset,uint32_t v){memcpy(static_cast(o)+offset,&v,4);}; put(fb,0x4350,4);*(static_cast(fb)+0x4A18)=1; compute(fb,&timing); - const auto &m=profile.mode;const uint32_t width=rotated?timing.horizontalScaled:m.width,height=rotated?timing.verticalScaled:m.height; + const auto &m=profile.mode;const uint32_t width=scaled?timing.horizontalScaled:m.width,height=scaled?timing.verticalScaled:m.height; const uint32_t pitch=((width*4+127)/128)*128; const bool valid=field(fb,0x430C)==width && field(fb,0x4310)==height && field(fb,0x4314)==pitch*m.height && field(fb,0x4320)==m.width && field(fb,0x4324)==m.height && field(fb,0x4328)==m.width && field(fb,0x432C)==m.height && field(fb,0x4334)==pitch && - field(fb,0x4338)==(rotated ? 0x30U : 1U) && !field(path,0xF0) && + field(fb,0x4338)==(rotated ? 0x30U : (scaled && (width!=m.width || height!=m.height) ? 0x20U : 1U)) && !field(path,0xF0) && field(path,0xF8)==m.width && field(path,0xFC)==m.height && !field(path,0x100) && field(path,0x108)==m.width && field(path,0x10C)==m.height; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index bb688125..6dde8d18 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7277,7 +7277,13 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad if(fn!=address+modeOffsets[i] || memcmp(reinterpret_cast(fn),entry,4))return false; dsiModeCPU[i]=fn; } + const char *scaler="__ZN16AppleIntelScaler19isSupportedByScalerEjjjjyjb"; + const auto scalerFn=patcher.solveSymbol(index,scaler,address,size);patcher.clearError(); + const uint8_t scalerEntry[]={0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xD7,0xD7,0x07,0x00,0x48,0x8B,0x47,0x10}; + if(size<=0x6686D || scalerFn!=address+0x6685E || + memcmp(reinterpret_cast(scalerFn),scalerEntry,sizeof(scalerEntry)))return false; KernelPatcher::RouteRequest routes[]={ + {scaler,wrapDSIScalerSupport,orgDSIScalerSupport}, {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, {symbols[2],wrapDSIGetModes,orgDSIMode[2]}, @@ -7504,7 +7510,7 @@ uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **descrip fb,XELP::DSI::AppleAdapter::FixedModeID,description,timing); if(!result && timing) { if(*timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; - if(selected->scalerFlags)*timing=selected; + if(selected->scalerFlags || selected->horizontalScaled || selected->verticalScaled)*timing=selected; } return result; }(); @@ -7532,6 +7538,17 @@ uint32_t XE::wrapDSIGetModeCount(IOService *fb) { recordDSIClient(fb,4,0,result); return result; } +// The adopted DSI scanout is fixed-size. It has no panel-fitter resource +// transaction, so let the donor select its existing GPU scaling path. Preserve +// the real OS timing and transform; other framebuffer owners keep native policy. +bool XE::wrapDSIScalerSupport(void *scaler,uint32_t outputW,uint32_t outputH, + uint32_t sourceW,uint32_t sourceH,uint64_t clock,uint32_t flags,bool format) { + auto fb=scaler ? Display13FBMemory::field(scaler,0x10) : nullptr; + if(dsiModeTarget(fb))return false; + return FunctionCast(wrapDSIScalerSupport,callbackXE->orgDSIScalerSupport)( + scaler,outputW,outputH,sourceW,sourceH,clock,flags,format); +} + uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { recordDSIClient(fb,5,static_cast(mode),UINT32_MAX); const auto result=[&]() -> uint32_t { @@ -7699,7 +7716,7 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App __atomic_store_n(&o.logical,field(fb,0x4340),__ATOMIC_RELEASE); receipt[5]=result;receipt[6]=o.logical;receipt[7]=field(pipe,0x12B8);receipt[8]=field(pipe,0x12C0); if(result)return false; // native failure can still leave a record to release - if(timing.scalerFlags) { + if(field(fb,0x4338)&0x30) { auto record=reinterpret_cast(o.logical); if(!record || record->kind!=2 || record->columns!=(field(fb,0x430C)*4+127)/128 || record->rows!=(field(fb,0x4310)+31)/32 || record->pitch!=field(fb,0x4334) || record->bytes!=uint64_t(record->pitch)*field(fb,0x4310) || !record->memory || !record->map || diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 0b8d5a95..989f8205 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -226,7 +226,7 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; bool display13DSIModeHooksReady {}; - mach_vm_address_t orgDSIMode[9] {}; + mach_vm_address_t orgDSIMode[9] {},orgDSIScalerSupport {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); @@ -307,6 +307,7 @@ class XE { static uint32_t wrapDSIGetModes(IOService *,int32_t *); static uint32_t wrapDSIGetModeCount(IOService *); static uint32_t wrapDSIGetModeInformation(IOService *,int32_t,void *); + static bool wrapDSIScalerSupport(void *,uint32_t,uint32_t,uint32_t,uint32_t,uint64_t,uint32_t,bool); Display13OutputWiring adlnOutputWiring; mach_vm_address_t adlnComboPLLFractionCaller {}; uint64_t adlnPlatform[0x1C0/8] {}; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 8bbd6c34..080738aa 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -45,6 +45,9 @@ int main() { r=offered;r.scalerFlags=rotation;r.horizontalActive=1920;r.verticalActive=1080; assert(!AppleAdapter::presentationTiming(t,r,1)); } + auto portrait=offered;portrait.horizontalScaled=1620;portrait.verticalScaled=2880; + assert(AppleAdapter::presentationTiming(t,portrait,0)); + portrait.scalerFlags=1;assert(!AppleAdapter::presentationTiming(t,portrait,0)); assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index b0a73071..cd01e111 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -12,6 +12,7 @@ using namespace XELP::DSI; using Display13FBMemory::field; templatevoid put(void *p,unsigned offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} unsigned notifications=0; +bool noPanelFitter(void *,uint32_t,uint32_t,uint32_t,uint32_t,uint64_t,uint32_t,bool){return false;} void notify(void *){++notifications;} void zero(void *p,size_t n){memset(p,0,n);} unsigned logicalAllocations=0,logicalFrees=0,recordAllocations=0,recordFrees=0; @@ -131,13 +132,24 @@ int main(int argc,char **argv) { assert(field(fb,0x4348)==0x12345000); // Logical allocation preserves the scanout. releaseLogical(fb);assert(!field(fb,0x4340)); } + // Replay the native GPU fallback for a portrait HiDPI source. Only + // hardware-scaler admission is declined, as for the adopted DSI owner. + patchCall(image,0x32909,reinterpret_cast(noPanelFitter)); + auto portrait=t;portrait.horizontalScaled=1620;portrait.verticalScaled=2880; + assert(AppleAdapter::prepareGeometry(fb,path,p,portrait,compute,true)); + assert(field(fb,0x4338)==0x20 && field(fb,0x4334)==6528); + assert(field(path,0xF8)==1080 && field(path,0xFC)==1920); + assert(allocateLogical(fb)==0); + auto portraitRecord=field(fb,0x4340); + assert(portraitRecord && portraitRecord->bytes==6528U*2880); + releaseLogical(fb);assert(!field(fb,0x4340)); // A failing accelerator callback still publishes the heap record. // A future mode adapter must explicitly retire it before rollback. logicalResult=0xE00002BD;assert(allocateLogical(fb)==logicalResult); assert(field(fb,0x4340));releaseLogical(fb); logicalResult=0;failRecord=true;assert(allocateLogical(fb)==0xE00002BD); assert(!field(fb,0x4340)); - assert(logicalAllocations==5 && logicalFrees==5 && recordAllocations==5 && recordFrees==5); + assert(logicalAllocations==6 && logicalFrees==6 && recordAllocations==6 && recordFrees==6); munmap(image,0x400000);puts("DSI native geometry: fixed scanout, quarter-turn logical buffers, HiDPI and failure cleanup replay passed"); } puts("DSI native mode preparation rollback and admission passed"); diff --git a/tests/dsi_presentation.py b/tests/dsi_presentation.py index dfa962e3..875aceae 100644 --- a/tests/dsi_presentation.py +++ b/tests/dsi_presentation.py @@ -51,7 +51,7 @@ for(unsigned off:{0x430CU,0x4320U,0x4328U})put(f,off,t->horizontalActive); for(unsigned off:{0x4310U,0x4324U,0x432CU})put(f,off,t->verticalActive); put(f,0x430C,width);put(f,0x4310,height); - put(f,0x4314,pitch*t->verticalActive);put(f,0x4334,pitch);put(f,0x4338,t->scalerFlags?0x30U:1U); + put(f,0x4314,pitch*t->verticalActive);put(f,0x4334,pitch);put(f,0x4338,t->scalerFlags?0x30U:((width!=t->horizontalActive || height!=t->verticalActive)?0x20U:1U)); put(path,0xF0,uint64_t(0));put(path,0x100,uint64_t(0)); for(unsigned off:{0xF8U,0x108U})put(path,off,t->horizontalActive); for(unsigned off:{0xFCU,0x10CU})put(path,off,t->verticalActive); @@ -59,7 +59,7 @@ uint32_t allocate(IOService *f){ assert(dsiFixedMode.changingThread && !field(f,0x4340)); assert(field(f,0x4250)==dsiFixedMode.activeMode); - if(!dsiFixedMode.presentation.scalerFlags)return 0; + if(!(field(f,0x4338)&0x30))return 0; ++allocations;auto r=new Record;r->kind=2;r->columns=(field(f,0x430C)*4+127)/128;r->rows=(field(f,0x4310)+31)/32; r->pitch=field(f,0x4334);r->bytes=r->pitch*field(f,0x4310); r->memory=0x200000;r->map=0x300000; @@ -94,7 +94,13 @@ failAllocation=true;assert(xe.setDSIPresentation(&fb,5,turn)==kIOReturnNotReady); assert(o.activeMode==4 && !o.logical && !o.presentationFault && !o.changingThread && allocations==5 && frees==5); assert(field(&fb,0x4338)==1 && dsiPublishedTarget(&fb) && receiptCopy[10]==1); - failAllocation=false;assert(!xe.setDSIPresentation(&fb,6,turn));failRelease=true; + failAllocation=false; + auto portrait=o.timing;portrait.horizontalScaled=1620;portrait.verticalScaled=2880; + assert(!xe.setDSIPresentation(&fb,10,portrait) && o.logical); + assert(field(&fb,0x4338)==0x20 && field(&fb,0x4340)->bytes==18800640); + assert(!xe.setDSIPresentation(&fb,11,portrait) && allocations==6 && frees==5); + assert(!xe.setDSIPresentation(&fb,4,o.timing) && !o.logical && allocations==frees); + assert(!xe.setDSIPresentation(&fb,6,turn));failRelease=true; assert(xe.setDSIPresentation(&fb,7,o.timing)==kIOReturnNotReady && o.presentationFault && !o.changingThread); assert(allocations==frees && !lock.depth && dsiPublishedTarget(&fb)); puts("PASS production presentation: quarter-turns, native restore, partial allocation cleanup and failed-release admission closure"); From 05587deee2983e271a39c3dc221e30b4e664cfe2 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:36:14 +0900 Subject: [PATCH 097/139] Delegate DSI presentation requests without angle or size allowlists --- WhateverXe/kern_dsi_apple_timing.hpp | 10 +++------- WhateverXe/kern_dsi_native_mode.hpp | 18 +++++++++++------- WhateverXe/kern_xe.cpp | 6 ++++-- tests/dsi_apple_timing.cpp | 9 +++++---- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index f8a9681b..22778e7b 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -55,15 +55,11 @@ inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned inde inline bool acceptsMode(uint32_t mode,uint32_t clientMode) { return mode==FixedModeID || mode==clientMode; } -// The donor advertises an 8191-pixel source limit through IOFBScalerInfo. -// Rotation and HiDPI change the GPU source, never the qualified DSI wire timing. -inline bool validPresentationSize(uint32_t width,uint32_t height) { - return width && height && width<=8191 && height<=8191; -} inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigned index) { - if(offered.scalerFlags!=0 && offered.scalerFlags!=0x30 && offered.scalerFlags!=0x50)return false; - if(!validPresentationSize(offered.horizontalScaled,offered.verticalScaled))return false; + // Presentation is supplied by CoreDisplay. Do not whitelist rotation, + // scaling sizes or mode-ID generations here; qualify only the wire timing. auto native=offered;native.scalerFlags=0;native.horizontalScaled=0;native.verticalScaled=0; + native.horizontalInset=0;native.verticalInset=0; return clientTiming(fixed,native,index); } // Synthetic Apple enumeration descriptor with timing from the panel profile. diff --git a/WhateverXe/kern_dsi_native_mode.hpp b/WhateverXe/kern_dsi_native_mode.hpp index f09a7f32..33d8e9e7 100644 --- a/WhateverXe/kern_dsi_native_mode.hpp +++ b/WhateverXe/kern_dsi_native_mode.hpp @@ -13,13 +13,12 @@ template bool prepareGeometry(void *fb,void *path,const Profile & using Display13FBMemory::field; Timing expected; if(!makeTiming(profile,expected))return false; - const bool rotated=presentation && (timing.scalerFlags==0x30 || timing.scalerFlags==0x50); const bool scaled=presentation && (timing.horizontalScaled || timing.verticalScaled); - if(rotated || scaled) { - if(timing.scalerFlags && !rotated)return false; + if(presentation) { expected.scalerFlags=timing.scalerFlags; - if(!validPresentationSize(timing.horizontalScaled,timing.verticalScaled))return false; - expected.horizontalScaled=timing.horizontalScaled;expected.verticalScaled=timing.verticalScaled;} + expected.horizontalScaled=timing.horizontalScaled;expected.verticalScaled=timing.verticalScaled; + expected.horizontalInset=timing.horizontalInset;expected.verticalInset=timing.verticalInset; + } if(!fb || !path || memcmp(&expected,&timing,sizeof(timing)) || Display13FBMemory::singlePath(fb)!=path || field(fb,0x4340) || (!presentation && field(fb,0x4348)))return false; @@ -33,12 +32,17 @@ template bool prepareGeometry(void *fb,void *path,const Profile & put(fb,0x4350,4);*(static_cast(fb)+0x4A18)=1; compute(fb,&timing); const auto &m=profile.mode;const uint32_t width=scaled?timing.horizontalScaled:m.width,height=scaled?timing.verticalScaled:m.height; + // Check the allocated geometry, not an OS rotation/mode whitelist. This + // transaction retains the scanout and can own a separate GPU source only. const uint32_t pitch=((width*4+127)/128)*128; - const bool valid=field(fb,0x430C)==width && field(fb,0x4310)==height && + const auto transform=field(fb,0x4338); + const bool gpuTransform=(transform&0x30) && !(transform&~0x30U); + const bool valid=width && height && uint64_t(width)*4+127<=UINT32_MAX && + uint64_t(pitch)*height<=UINT32_MAX && field(fb,0x430C)==width && field(fb,0x4310)==height && field(fb,0x4314)==pitch*m.height && field(fb,0x4320)==m.width && field(fb,0x4324)==m.height && field(fb,0x4328)==m.width && field(fb,0x432C)==m.height && field(fb,0x4334)==pitch && - field(fb,0x4338)==(rotated ? 0x30U : (scaled && (width!=m.width || height!=m.height) ? 0x20U : 1U)) && !field(path,0xF0) && + (gpuTransform || (transform==1 && width==m.width && height==m.height)) && !field(path,0xF0) && field(path,0xF8)==m.width && field(path,0xFC)==m.height && !field(path,0x100) && field(path,0x108)==m.width && field(path,0x10C)==m.height; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 6dde8d18..e681f26b 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7555,7 +7555,7 @@ uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; const auto selected=dsiPresentation(mode);if(!raw || !selected)return kIOReturnBadArgument; - const bool rotated=selected->scalerFlags!=0; + const bool rotated=(selected->scalerFlags&0x10)!=0; const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, *static_cast(raw),rotated,selected->horizontalScaled,selected->verticalScaled))return kIOReturnNotReady; @@ -7683,7 +7683,8 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App const auto old=o.presentationReady ? o.presentation : o.timing; const uint32_t oldMode=o.presentationReady ? o.activeMode : o.clientMode; if(requested.scalerFlags==old.scalerFlags && requested.horizontalScaled==old.horizontalScaled && - requested.verticalScaled==old.verticalScaled) { + requested.verticalScaled==old.verticalScaled && requested.horizontalInset==old.horizontalInset && + requested.verticalInset==old.verticalInset) { o.presentation=requested;o.activeMode=mode;o.presentationReady=true; *reinterpret_cast(reinterpret_cast(fb)+0x4250)=mode; return kIOReturnSuccess; @@ -7705,6 +7706,7 @@ uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::App if(!releaseDSILogical(fb))return false; auto cpu=o.timing;cpu.scalerFlags=timing.scalerFlags; cpu.horizontalScaled=timing.horizontalScaled;cpu.verticalScaled=timing.verticalScaled; + cpu.horizontalInset=timing.horizontalInset;cpu.verticalInset=timing.verticalInset; if(!XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,cpu, reinterpret_cast(dsiComputeGeometry),true))return false; // The native allocator asks getCurrentDisplayMode/getPixelInformation diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 080738aa..15e224cf 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -32,7 +32,7 @@ int main() { offered.reservedA[0]=0x80001001;assert(AppleAdapter::clientTiming(t,offered,1)); for(unsigned rotation:{0x30U,0x50U}) { auto r=offered;r.scalerFlags=rotation; - assert(!AppleAdapter::presentationTiming(t,r,1)); + assert(AppleAdapter::presentationTiming(t,r,1)); r.horizontalScaled=t.verticalActive;r.verticalScaled=t.horizontalActive; assert(AppleAdapter::presentationTiming(t,r,1)); r.horizontalScaled=2880;r.verticalScaled=1620; @@ -40,14 +40,15 @@ int main() { AppleAdapter::ModeInformation info;info.width=2880;info.height=1620;info.flags=0x02000004; assert(AppleAdapter::annotateMode(p.mode,info,true,2880,1620)); assert(!(info.flags&0x02000004) && info.widthMM==154 && info.heightMM==86); - r.horizontalScaled=8192;assert(!AppleAdapter::presentationTiming(t,r,1)); - r.horizontalScaled=2880;r.verticalScaled=0;assert(!AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=8192;assert(AppleAdapter::presentationTiming(t,r,1)); + r.horizontalScaled=2880;r.verticalScaled=0;assert(AppleAdapter::presentationTiming(t,r,1)); r=offered;r.scalerFlags=rotation;r.horizontalActive=1920;r.verticalActive=1080; assert(!AppleAdapter::presentationTiming(t,r,1)); } auto portrait=offered;portrait.horizontalScaled=1620;portrait.verticalScaled=2880; assert(AppleAdapter::presentationTiming(t,portrait,0)); - portrait.scalerFlags=1;assert(!AppleAdapter::presentationTiming(t,portrait,0)); + portrait.scalerFlags=1;assert(AppleAdapter::presentationTiming(t,portrait,0)); + portrait.scalerFlags=0x20;portrait.horizontalInset=4;assert(AppleAdapter::presentationTiming(t,portrait,0)); assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); From 6f88501c9da9165978f4965febd605d3514f0f24 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:40:35 +0900 Subject: [PATCH 098/139] Observe DSI mode and pixel metadata returned to CoreDisplay --- WhateverXe/kern_xe.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e681f26b..f7a0853d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7561,6 +7561,15 @@ uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { *static_cast(raw),rotated,selected->horizontalScaled,selected->verticalScaled))return kIOReturnNotReady; return result; }(); + if(dsiModeTarget(fb)) { + DSIModeScope scope(fb); + for(unsigned i=0;scope.lock && i(mode)) { + uint32_t receipt[12] {1,static_cast(mode),result}; + if(raw && !result)memcpy(receipt+3,raw,36); + char key[40];snprintf(key,sizeof(key),"WEXDSIModeInfo%u",i); + IORegistryEntry::getRegistryRoot()->setProperty(key,receipt,sizeof(receipt));break; + } + } recordDSIClient(fb,5,static_cast(mode),result); return result; } @@ -21471,6 +21480,13 @@ uint32_t XE::wrapTGLPixelInfo(void *fb, int mode, int depth, int aperture, void if (!result && info) memcpy(r + 6, info, 172); char key[48]; snprintf(key, sizeof(key), "TGLPixelFB%uDepth%d", *reinterpret_cast(static_cast(fb) + 0x1DC), depth); IORegistryEntry::getRegistryRoot()->setProperty(key, r, sizeof(r)); + if(dsiModeTarget(static_cast(fb))) { + DSIModeScope scope(static_cast(fb)); + for(unsigned i=0;scope.lock && i(mode)) { + snprintf(key,sizeof(key),"WEXDSIPixelInfo%u",i); + IORegistryEntry::getRegistryRoot()->setProperty(key,r,sizeof(r));break; + } + } return result; } IOMemoryDescriptor *XE::wrapTGLAperture(void *fb, int aperture) { From 79642c0d7052696a052d9806f5d1d21a9b70f317 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:41:20 +0900 Subject: [PATCH 099/139] Accept bounded mode metadata receipts in adapter fixture --- tests/dsi_apple_adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 9b91f060..1e15fcd5 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -66,7 +66,7 @@ }; struct IORegistryEntry {void setProperty(const char *key,OSData *d){assert(!strcmp(key,"WEXDSITimingRejected") && d->getLength()==160);} static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *key,const void *p,unsigned n){if(!strcmp(key,"WEXDSITimingList")){assert(n<=32*160 && !(n%160));return;}if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; + void setProperty(const char *key,const void *p,unsigned n){if(!strncmp(key,"WEXDSIModeInfo",14)){assert(n==48);return;}if(!strcmp(key,"WEXDSITimingList")){assert(n<=32*160 && !(n%160));return;}if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} From e8b5f57921bb752aa6ae802e690fde5c5eafd483 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:44:50 +0900 Subject: [PATCH 100/139] Trace native DSI detailed timing and source validation --- WhateverXe/kern_xe.cpp | 38 ++++++++++++++++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 4 +++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index f7a0853d..e9809d8f 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7282,7 +7282,18 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad const uint8_t scalerEntry[]={0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xD7,0xD7,0x07,0x00,0x48,0x8B,0x47,0x10}; if(size<=0x6686D || scalerFn!=address+0x6685E || memcmp(reinterpret_cast(scalerFn),scalerEntry,sizeof(scalerEntry)))return false; + const char *detailed="__ZN21AppleIntelFramebuffer22validateDetailedTimingEPvy"; + const char *source="__ZN21AppleIntelFramebuffer18ValidateSourceSizeEP29IODetailedTimingInformationV2"; + const auto detailedFn=patcher.solveSymbol(index,detailed,address,size);patcher.clearError(); + const auto sourceFn=patcher.solveSymbol(index,source,address,size);patcher.clearError(); + const uint8_t detailedEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}; + const uint8_t sourceEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x20}; + if(detailedFn!=address+0x2FFCA || sourceFn!=address+0x30AD8 || + memcmp(reinterpret_cast(detailedFn),detailedEntry,sizeof(detailedEntry)) || + memcmp(reinterpret_cast(sourceFn),sourceEntry,sizeof(sourceEntry)))return false; KernelPatcher::RouteRequest routes[]={ + {detailed,wrapDSIValidateDetailed,orgDSIDetailedValidation}, + {source,wrapDSIValidateSource,orgDSISourceValidation}, {scaler,wrapDSIScalerSupport,orgDSIScalerSupport}, {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, @@ -7538,6 +7549,33 @@ uint32_t XE::wrapDSIGetModeCount(IOService *fb) { recordDSIClient(fb,4,0,result); return result; } +// Observe native mode admission separately from the later published mode list. +uint32_t XE::wrapDSIValidateDetailed(IOService *fb,void *raw,uint64_t size) { + uint64_t record[60] {};const bool observe=dsiModeTarget(fb) && raw && size==204; + if(observe)memcpy(record+8,raw,204); + const auto result=FunctionCast(wrapDSIValidateDetailed,callbackXE->orgDSIDetailedValidation)(fb,raw,size); + if(observe) { + static uint32_t sequence=0;record[0]=1;record[1]=__sync_add_and_fetch(&sequence,1); + record[2]=result;record[3]=Display13FBMemory::field(fb,0x4318); + record[4]=Display13FBMemory::field(fb,0x431C); + memcpy(reinterpret_cast(record)+268,raw,204); + char key[40];snprintf(key,sizeof(key),"WEXDSIDetailedValidation%u",static_cast(record[1])%32); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + return result; +} +bool XE::wrapDSIValidateSource(IOService *fb,void *raw) { + const auto result=FunctionCast(wrapDSIValidateSource,callbackXE->orgDSISourceValidation)(fb,raw); + if(dsiModeTarget(fb) && raw) { + static uint32_t sequence=0;uint64_t record[25] {1,__sync_add_and_fetch(&sequence,1),result, + Display13FBMemory::field(fb,0x4318),Display13FBMemory::field(fb,0x431C)}; + memcpy(record+5,raw,160); + char key[40];snprintf(key,sizeof(key),"WEXDSISourceValidation%u",static_cast(record[1])%32); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + return result; +} + // The adopted DSI scanout is fixed-size. It has no panel-fitter resource // transaction, so let the donor select its existing GPU scaling path. Preserve // the real OS timing and transform; other framebuffer owners keep native policy. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 989f8205..af56ff61 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -226,7 +226,7 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; bool display13DSIModeHooksReady {}; - mach_vm_address_t orgDSIMode[9] {},orgDSIScalerSupport {}; + mach_vm_address_t orgDSIMode[9] {},orgDSIScalerSupport {},orgDSIDetailedValidation {},orgDSISourceValidation {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); @@ -307,6 +307,8 @@ class XE { static uint32_t wrapDSIGetModes(IOService *,int32_t *); static uint32_t wrapDSIGetModeCount(IOService *); static uint32_t wrapDSIGetModeInformation(IOService *,int32_t,void *); + static uint32_t wrapDSIValidateDetailed(IOService *,void *,uint64_t); + static bool wrapDSIValidateSource(IOService *,void *); static bool wrapDSIScalerSupport(void *,uint32_t,uint32_t,uint32_t,uint32_t,uint64_t,uint32_t,bool); Display13OutputWiring adlnOutputWiring; mach_vm_address_t adlnComboPLLFractionCaller {}; From 7ab4e55711f87402cdda4f83ce08f2ff6c8f9a84 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:49:43 +0900 Subject: [PATCH 101/139] Observe pixel information on the DSI framebuffer route --- WhateverXe/kern_xe.cpp | 16 ++++++++++++++++ WhateverXe/kern_xe.hpp | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e9809d8f..4fb80b04 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7291,7 +7291,11 @@ bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_ad if(detailedFn!=address+0x2FFCA || sourceFn!=address+0x30AD8 || memcmp(reinterpret_cast(detailedFn),detailedEntry,sizeof(detailedEntry)) || memcmp(reinterpret_cast(sourceFn),sourceEntry,sizeof(sourceEntry)))return false; + const char *pixel="__ZN21AppleIntelFramebuffer19getPixelInformationEiiiP18IOPixelInformation"; + const auto pixelFn=patcher.solveSymbol(index,pixel,address,size);patcher.clearError(); + if(pixelFn!=address+0x324A4 || memcmp(reinterpret_cast(pixelFn),entry,sizeof(entry)))return false; KernelPatcher::RouteRequest routes[]={ + {pixel,wrapDSIPixelInfo,orgDSIPixelInfo}, {detailed,wrapDSIValidateDetailed,orgDSIDetailedValidation}, {source,wrapDSIValidateSource,orgDSISourceValidation}, {scaler,wrapDSIScalerSupport,orgDSIScalerSupport}, @@ -7549,6 +7553,18 @@ uint32_t XE::wrapDSIGetModeCount(IOService *fb) { recordDSIClient(fb,4,0,result); return result; } +uint32_t XE::wrapDSIPixelInfo(IOService *fb,int32_t mode,int32_t depth,int32_t aperture,void *info) { + const auto result=FunctionCast(wrapDSIPixelInfo,callbackXE->orgDSIPixelInfo)(fb,mode,depth,aperture,info); + if(dsiModeTarget(fb)) { + static uint32_t sequence=0;uint64_t record[28] {1,__sync_add_and_fetch(&sequence,1), + static_cast(mode),static_cast(depth),static_cast(aperture),result}; + if(info && !result)memcpy(record+6,info,172); + char key[40];snprintf(key,sizeof(key),"WEXDSIPixelCall%u",static_cast(record[1])%32); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + return result; +} + // Observe native mode admission separately from the later published mode list. uint32_t XE::wrapDSIValidateDetailed(IOService *fb,void *raw,uint64_t size) { uint64_t record[60] {};const bool observe=dsiModeTarget(fb) && raw && size==204; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index af56ff61..64b0d50e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -226,7 +226,7 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; bool display13DSIModeHooksReady {}; - mach_vm_address_t orgDSIMode[9] {},orgDSIScalerSupport {},orgDSIDetailedValidation {},orgDSISourceValidation {}; + mach_vm_address_t orgDSIMode[9] {},orgDSIScalerSupport {},orgDSIDetailedValidation {},orgDSISourceValidation {},orgDSIPixelInfo {}; bool processDisplay13DSIModes(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void recordDSIClient(IOService *,unsigned,uint64_t,uint32_t); bool releaseDSIFixedMode(IOService *); @@ -307,6 +307,7 @@ class XE { static uint32_t wrapDSIGetModes(IOService *,int32_t *); static uint32_t wrapDSIGetModeCount(IOService *); static uint32_t wrapDSIGetModeInformation(IOService *,int32_t,void *); + static uint32_t wrapDSIPixelInfo(IOService *,int32_t,int32_t,int32_t,void *); static uint32_t wrapDSIValidateDetailed(IOService *,void *,uint64_t); static bool wrapDSIValidateSource(IOService *,void *); static bool wrapDSIScalerSupport(void *,uint32_t,uint32_t,uint32_t,uint32_t,uint64_t,uint32_t,bool); From aa867693dad69b3a8d18a295ebd024530ef50764 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:55:26 +0900 Subject: [PATCH 102/139] Publish consistent RGB8 depth in DSI detailed mode metadata --- WhateverXe/kern_xe.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4fb80b04..ac8fa749 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7570,6 +7570,9 @@ uint32_t XE::wrapDSIValidateDetailed(IOService *fb,void *raw,uint64_t size) { uint64_t record[60] {};const bool observe=dsiModeTarget(fb) && raw && size==204; if(observe)memcpy(record+8,raw,204); const auto result=FunctionCast(wrapDSIValidateDetailed,callbackXE->orgDSIDetailedValidation)(fb,raw,size); + // Match getInformationForDisplayMode: the retained scanout is RGB8. + // CoreDisplay consumes this metadata before it asks for a mode's info. + if(observe && !result)static_cast(raw)->depth=0; if(observe) { static uint32_t sequence=0;record[0]=1;record[1]=__sync_add_and_fetch(&sequence,1); record[2]=result;record[3]=Display13FBMemory::field(fb,0x4318); From b3d0419cdfa95cbe603379bd1a5c27c4fd5d0b25 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 20:34:39 +0900 Subject: [PATCH 103/139] Prepare shared display clock while DSI bootstrap consumers are stopped --- WhateverXe/kern_xe.cpp | 29 +++++++++++++++++++++++++++-- tests/display13_clock_set.cpp | 9 +++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ac8fa749..03c85e28 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -12720,17 +12720,40 @@ bool XE::probeDSIPanel(IOService *controller) { } void delayUs(unsigned us){IODelay(us);} } io{c.map,owned,irq.lock,dsiHostTest,dsiBoot.source.identity.pll}; + Display13Clock::Change bootCDClock; struct Lifecycle { - bool enabled;XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state; + bool enabled,prepareExternal;XELPComboClock::ClockAllocation &allocation;uintptr_t owner; + XELP::DSI::Restart::State &state;Display13Clock::Change &cdclk; bool stop(IO &io,const XELP::DSI::Readout &source,const XELP::DSI::Profile &profile) { return !enabled || XELP::DSI::Restart::stop(io,source,profile,allocation,owner,state); } bool start(IO &io,XELP::DSI::Readout &source) { if(!enabled)return true; + // Native external modes request the maximum CDCLK. Prepare that + // shared clock while the bootstrap cycle has stopped DSI and all + // other consumers, before native mode setting/IRQ users start. + // The DSI link PLL remains a separate retained allocation. + if(prepareExternal) { + auto &c=display13FBPower; + Display13FBClockIO clockIO {c.map}; + const auto current=Display13Clock::read(clockIO); + cdclk.before=current;cdclk.requestedHz=current.nativeMaxHz(); + const bool stopped=io.ready() && state.stopped && !state.started && + !c.engineEnabled && Display13FBIRQ::consumersStopped(io,7,c.transcoders); + if(!stopped || current.status!=Display13Clock::Status::Complete)return false; + cdclk=Display13Clock::set(clockIO,current.nativeMaxHz(),{true,true,0}); + if(cdclk.status!=Display13Clock::ChangeStatus::Complete) { + adlnFault(c.clockFaulted,__LINE__);return false; + } + c.clock=cdclk.after;c.clockStopped=false;c.resumeClockHz=cdclk.after.nativeHz(); + auto controller=reinterpret_cast(owner); + *reinterpret_cast(reinterpret_cast(controller)+0xE98)=cdclk.after.nativeHz(); + *reinterpret_cast(reinterpret_cast(controller)+0xEA0)=cdclk.after.nativeHz(); + } if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; source=XELP::DSI::Restart::expected(source,state);return source.complete(); } - } lifecycle{dsiHostTest,clock.allocation,reinterpret_cast(controller),dsiHostRestart}; + } lifecycle{dsiHostTest,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; uint64_t record[32] {1}; record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && !dsiCommandHost.faulted && !dsiCommandHost.pending && @@ -12768,6 +12791,8 @@ bool XE::probeDSIPanel(IOService *controller) { IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); if(dsiHostTest)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); + if(dsiHostTest && !dsiPanelOnly) + IORegistryEntry::getRegistryRoot()->setProperty("N97FBDSIBootCDClock",&bootCDClock,sizeof(bootCDClock)); return complete; } diff --git a/tests/display13_clock_set.cpp b/tests/display13_clock_set.cpp index 67719263..e5c8b7a5 100644 --- a/tests/display13_clock_set.cpp +++ b/tests/display13_clock_set.cpp @@ -43,6 +43,15 @@ int main() { assert(select(38400,0,true)==1305600000 && select(24000,0,true)==1296000000); assert(select(38400,345600000,false)==358400000 && select(38400,358400000,false)==384000000); assert(!select(38400,1305600000,false) && !select(0,0,true)); + // DSI firmware clock (38.4 MHz reference, /3) to the external-output + // maximum (/2): prepare only in the stopped bootstrap interval. + for (unsigned active : {0U,1U}) { + IO io;io.regs[DSSM]=0x40000000;io.regs[PLL]=0xC000000E;io.regs[Control]=0x780164; + auto r=set(io,1305600000,{true,true,active}); + assert(r.before.nativeHz()==358400000); + if(active)assert(r.status==ChangeStatus::ActiveDividerChange && io.writes.empty()); + else assert(r.status==ChangeStatus::Complete && r.after.nativeHz()==1305600000 && r.after.divider==2); + } {IO io;auto r=stop(io,{false,false,0});assert(r.status==ChangeStatus::OwnershipRequired && io.writes.empty());} {IO io;auto r=stop(io,ready);assert(r.status==ChangeStatus::ActiveDividerChange && io.writes.empty());} {IO io;auto r=stop(io,{true,false,0});assert(r.status==ChangeStatus::Complete && r.after.status==Status::Disabled && From 08fb69813c8ce08294826be318dbb0e81e535b77 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 20:51:36 +0900 Subject: [PATCH 104/139] Preserve parked DSI clock ownership across peer output transactions --- WhateverXe/kern_dsi_restart.hpp | 15 +++++++++++++++ WhateverXe/kern_xe.cpp | 15 +++++++++++---- tests/dsi_restart.py | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_dsi_restart.hpp b/WhateverXe/kern_dsi_restart.hpp index 12379ce8..b232ef2f 100644 --- a/WhateverXe/kern_dsi_restart.hpp +++ b/WhateverXe/kern_dsi_restart.hpp @@ -178,6 +178,21 @@ template bool stop(IO &io,const Readout &source,const Profile &profile !a.wait(0x64000,0x80000080U,0x80,8) || !a.rmw(0x164280,0,1U<<10))return false; return powerOff(a,pll); } +// A peer clock transaction may run while this host is parked. Revalidate the +// retained allocation and stopped hardware, without demanding the old live +// source or reacquiring an allocation whose PLL is intentionally powered off. +// Keep the stop receipt immutable; failed observation cannot advance it. +template bool retainedStopped(IO &io,const Readout &source, + XELPComboClock::ClockAllocation &allocation,uintptr_t owner,const State &state) { + if(!source.complete() || source.identity.pipe!=0 || source.identity.phy!=0 || + source.identity.pll!=state.pll || !state.stopped || state.started || + state.stage!=Stage::Stopped || state.error!=Error::None || !state.config.valid)return false; + State observed=state;Access a{io,allocation,owner,observed};XELPComboClock::PLL pll; + return XELPComboClock::PLL::resolve(state.pll,pll) && a.held() && + a.bits(pll.enable,0xCC000000U,0) && a.bits(0x6B400,1U<<31,0) && + a.bits(0x64000,0x80000080U,0x80) && a.bits(0x6B094,1,0) && + a.bits(0x164280,0x403,0x400|state.pll); +} // Invoke after panel POWER_ON/PPS/DEASSERT_RESET, before INIT_OTP. Timing and // transport derive from the profile; no firmware PLL/PHY register dump replay. // Leaves pixel transfer off, ready for command sequence initialization. The diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 03c85e28..bd32e030 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -21003,13 +21003,20 @@ bool XE::comboBeginClock(void *controller,void *port,bool enable,bool &keepPLL,v // clock mutex, then pin its PLL even when DDI_CLK_OFF_A is set. const auto &source=display13FBPower.bootDSI; struct IO { - void *access; + void *access;bool parked; + bool ready(){return parked;} bool readable(uint32_t r) {return r(access,0x60);} uint32_t read(uint32_t r) {return XE::comboClockRead(access,r);} - } io {access}; + } io {access,s->dsiRuntimePower.sleeping && !s->dsiRuntimePower.failed && + s->dsiStopAttempted && s->dsiIRQParked && s->dsiResourcesReady && + s->dsiStop.complete() && s->dsiPanelStop.complete}; + const auto owner=reinterpret_cast(controller); + const bool retained=io.parked ? XELP::DSI::Restart::retainedStopped( + io,source,t.allocation,owner,s->dsiRuntimePower.host) : + (XELP::DSI::sourceUnchanged(io,source) && + t.allocation.retain(source.identity.phy,source.identity.pll,owner,snapshot)); if(source.identity.pipe!=display13FBPower.transcoders.dsi0Pipe || - !XELP::DSI::sourceUnchanged(io,source) || - !t.allocation.retain(source.identity.phy,source.identity.pll,reinterpret_cast(controller),snapshot)) { + !retained) { comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; } } diff --git a/tests/dsi_restart.py b/tests/dsi_restart.py index 42a8c8e2..75291f8a 100644 --- a/tests/dsi_restart.py +++ b/tests/dsi_restart.py @@ -137,6 +137,24 @@ assert(rebuilt.matches(f.p) && rebuilt.afeKHz==811529); } int main(int argc,char **argv){ + for(unsigned fault=0;fault<7;++fault){Fixture f; + assert(!Restart::retainedStopped(f.io,f.source,f.allocation,1,f.state)); + assert(f.stop());const auto count=f.io.writes.size();const auto state=f.state; + if(fault==1)f.allocation.external[0]=2; + if(fault==2)f.io.regs[0x46014]=0xCC000000; + if(fault==3)f.io.regs[0x6B400]|=1U<<31; + if(fault==4)f.io.regs[0x7B008]=0xC0000000; + if(fault==5)f.allocation.binding[1]=2; + if(fault==6)f.io.owned=false; + assert(Restart::retainedStopped(f.io,f.source,f.allocation,1,f.state)==!fault); + assert(f.io.writes.size()==count && !memcmp(&state,&f.state,sizeof(state))); + if(!fault){XELPComboClock::ClockSnapshot snapshot;snapshot.layout=XELPComboClock::Layout::Display13; + snapshot.mux[0]=f.io.regs[0x164280]|(1U<<11);snapshot.enable[1]=0; + const auto choice=f.allocation.choose(XELPComboClock::Port(1),snapshot,true); + assert(choice.valid && choice.pll==0); // powered-off DSI PLL1 remains reserved + assert(f.start()); + } + } {Fixture f;successful(f);} for(unsigned fault=0;fault<4;++fault){Fixture f;retainedCycle(f,fault);} {Fixture f;panelCycle(f);} From a1854b8f4b2431e31f7cb416f9240431afb1d519 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:06:13 +0900 Subject: [PATCH 105/139] Capture DSI wake IRQ admission and bank state --- WhateverXe/kern_xe.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index bd32e030..42309de0 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11608,7 +11608,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { } void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} } io{c.map,owned,irq.lock,dsiBoot.source.identity.pll}; - uint64_t receipt[24] {1,1,0,power.cycles,power.sleeping}; + uint64_t receipt[24] {1,1,0,power.cycles,power.sleeping},irqReceipt[24] {1}; bool masked=false,restored=false,rearmed=false,cursor=false,complete=false;uint32_t master=0; auto stoppedPlane=power.plane;stoppedPlane.control&=~(1U<<31); receipt[5]=owned() && power.host.stopped && !power.host.started && !power.panel.attempted && @@ -11667,7 +11667,22 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { const auto after=irqIO.read(Display13FBIRQ::Master); restored=after!=UINT32_MAX && (after&Display13FBIRQ::Enable);irq.state.enabled=restored; if(!restored)irq.state.faulted=true; - if(restored && cursor && owned())rearmed=Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes)==Display13FBIRQ::Status::Complete && (irq.state.active&1); + if(restored && cursor && owned()) { + const auto actual=Display13FBIRQ::readActivePipes(irqIO,irq.admittedPipes,irq.state.transcoders); + irqReceipt[1]=irq.admittedPipes;irqReceipt[2]=irq.state.active;irqReceipt[3]=actual.active; + irqReceipt[4]=static_cast(actual.status);irqReceipt[5]=wells; + for(unsigned p=0;p<3;++p)if(Display13FBIRQ::powered(wells,p)) { + irqReceipt[6+3*p]=Display13Transcoder::control(irqIO,p,irq.state.transcoders); + irqReceipt[7+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)+4); + irqReceipt[8+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)-4); + } + const auto status=Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes); + irqReceipt[15]=static_cast(status);irqReceipt[16]=irq.state.active; + irqReceipt[17]=static_cast(irq.state.firstFailure); + irqReceipt[18]=irq.state.failureRegister;irqReceipt[19]=irq.state.failureBits; + irqReceipt[20]=irq.state.enabled;irqReceipt[21]=irq.state.faulted; + rearmed=status==Display13FBIRQ::Status::Complete && (irq.state.active&1); + } } irq.quiescing=false;irq.blockedPipes=0; adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); @@ -11687,6 +11702,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); loop->release();IORecursiveLockUnlock(mutex); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); publishDSIBacklight(); return complete; } From 6bebfff53663f1292a99cf2b08fef5998f3778f1 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:12:20 +0900 Subject: [PATCH 106/139] Enable owned aperture backing for native display transitions --- WhateverXe/kern_xe.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 42309de0..b34cc191 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -249,9 +249,10 @@ static XELPMemory::Layout xelpPhysicalMemory(IOPCIDevice *pci) { pci->configRead32(0xC4),aperture->getLength()) : XELPMemory::Layout {}; } static bool xelpMemoryMatches(IOPCIDevice *pci) {return xelpMemory.reservationKnown && XELPMemory::samePhysical(xelpMemory,xelpPhysicalMemory(pci));} -// Normal dual-output profile uses native accelerator surfaces. Fixed FBMemMgr -// slot1/2 scatter backing is unqualified and must not reserve task aperture. -static constexpr bool adlnFixedSysMemEnabled=false; +// Native server-state transitions can request aperture backing even after +// accelerator binding. Protect slot1/2 through the existing registration +// contract before admitting their owned scatter mappings. +static constexpr bool adlnFixedSysMemEnabled=true; static bool tglPcodeGateSerialize(void *,void *,OSSerialize *); static bool tglPcodeEDRAMSerialize(void *,void *,OSSerialize *); static bool tglTCBootstrapSerialize(void *,void *,OSSerialize *); From c502f53ddf70cb8d5b13fb77dfafc71b1c0d169f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:16:16 +0900 Subject: [PATCH 107/139] Initialize owned empty SysMem windows regardless of residual PTE bits --- WhateverXe/kern_display13_fb_sysmem.hpp | 11 +++++------ tests/display13_fb_sysmem.cpp | 12 ++++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/WhateverXe/kern_display13_fb_sysmem.hpp b/WhateverXe/kern_display13_fb_sysmem.hpp index 8553be79..f7514f08 100644 --- a/WhateverXe/kern_display13_fb_sysmem.hpp +++ b/WhateverXe/kern_display13_fb_sysmem.hpp @@ -39,14 +39,14 @@ template Result install(IO &io,const Display13FBMemory::PoolLayout &la const auto &p=layout.slots[slot]; if(p.state || p.descriptor) {r.status=Status::Layout;return r;} if(!io.ownsWindow(slot,p) || !io.consumersStopped(slot,p)) {r.status=Status::Ownership;return r;} - // Cache the complete physical page list, checking even a late invalid page - // and every prior PTE before exposing any RAM to the display/GPU. + // An unallocated window has no defined PTE contents: firmware may leave + // arbitrary bits here. Ownership comes from the reserved allocator range + // and the empty native slot, not from zero/scratch entries. Preflight all + // physical pages, then initialize only that owned, stopped window. for(unsigned i=0;i Result install(IO &io,const Display13FBMemory::PoolLayout &la if(!io.ownsWindow(slot,p) || !io.consumersStopped(slot,p)) {r.status=Status::Ownership;return r;} uint64_t span=0;const auto physical=io.physical(uint64_t(i)*4096,span); if(physical!=pages[i] || span<4096) {r.status=Status::BackingChanged;return r;} - const auto address=reg(p,i);const auto old=io.readPTE(address);r.observed=old; - if(old && old!=scratchPTE) {r.status=Status::Occupied;return r;} + const auto address=reg(p,i); r.expected=Display13FBGTT::pte(pages[i],0); // A possibly delivered write requires backing retention even when // the posting read fails. The caller owns that reference through error. diff --git a/tests/display13_fb_sysmem.cpp b/tests/display13_fb_sysmem.cpp index 14a86a67..f5501ee3 100644 --- a/tests/display13_fb_sysmem.cpp +++ b/tests/display13_fb_sysmem.cpp @@ -56,7 +56,15 @@ int main() { } {IO io;io.badPhysical=0x3FFF;const auto r=io.map();assert(r.status==Status::Physical && !r.written && !r.mustRetainBacking);} {IO io;io.shortPhysical=0x3FFF;assert(io.map().status==Status::Physical && !io.writes);} - {IO io;io.ptes.back()=0x300000001ULL;assert(io.map().status==Status::Occupied && !io.writes);} + // Empty native slots can contain undefined firmware GGTT words. Include + // the observed nonpresent value and present-looking residual entries. + {IO io;for(unsigned i=0;i Date: Fri, 30 Oct 2026 21:21:27 +0900 Subject: [PATCH 108/139] Keep stopped peer IRQ banks while rearming display wake --- WhateverXe/kern_display13_fb_irq.hpp | 5 ++++- tests/display13_fb_irq.cpp | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_display13_fb_irq.hpp b/WhateverXe/kern_display13_fb_irq.hpp index aaf6ba84..48be1ac3 100644 --- a/WhateverXe/kern_display13_fb_irq.hpp +++ b/WhateverXe/kern_display13_fb_irq.hpp @@ -275,7 +275,10 @@ template Status extendActivePipes(IO &io,State &s,uint32_t allowed) { if(!s.begun || !s.enabled || s.faulted)return Status::Unavailable; const auto actual=readActivePipes(io,allowed,s.transcoders); if(actual.status!=Status::Complete)return actual.status; - if(s.active&~actual.active)return Status::Topology; + // s.active tracks armed IRQ banks, not currently running transcoders. + // Native display-off may stop a peer while retaining its powered bank. + // Preserve that bank and pending completions until explicit retirement; + // it must not prevent another pipe from being rearmed during wake. const auto master=io.read(Master),gfx=io.read(GfxMaster); if(master==UINT32_MAX || gfx==UINT32_MAX || !(master&Enable) || !(gfx&Enable))return Status::Readback; const auto added=actual.active&~s.active; diff --git a/tests/display13_fb_irq.cpp b/tests/display13_fb_irq.cpp index 8be43e78..9cfa073d 100644 --- a/tests/display13_fb_irq.cpp +++ b/tests/display13_fb_irq.cpp @@ -195,7 +195,24 @@ int main() { reg==DSBInterrupt(1,0) || reg==DSBInterrupt(1,1) || reg==DSBInterrupt(1,2)); for(auto pair:before)if(std::find(io.writes.begin(),io.writes.end(),pair.first)==io.writes.end())assert(io.r[pair.first]==pair.second); io.writes.clear();assert(extendActivePipes(io,state,3)==Status::Complete && io.writes.empty()); - io.r[0x71008]=0;assert(extendActivePipes(io,state,3)==Status::Topology && io.writes.empty()); + io.r[0x71008]=0;assert(extendActivePipes(io,state,3)==Status::Complete && state.active==3 && io.writes.empty()); + } + // Wake DSI A while a stopped USB-C peer C retains its armed bank. + // No peer notification, pending completion, master or GT may be reset. + {IO io;State state;state.transcoders.dsi0Pipe=0;io.r[0x70008]=0; + io.r[0x7B008]=0;io.r[0x6B400]=6;io.r[0x72008]=0xC0000000; + assert(enable(io,state,0,NativeGT,4,2)==Status::Complete); + io.r[0x72008]=0;io.r[0x7B008]=0xC0000000;io.r[0x6B400]=0x80000006; + io.r[PipeIIR(2)]=8;io.r[DSBInterrupt(2,0)]|=1; + io.r[Master]|=1U<<18;io.r[GfxMaster]|=NativeGT; + const auto before=io.r;io.writes.clear(); + assert(readActivePipes(io,7,state.transcoders).active==1); + assert(extendActivePipes(io,state,7)==Status::Complete && state.active==5 && !state.faulted); + for(auto reg:io.writes)assert(reg==PipeIIR(0)-4 || reg==PipeIIR(0)+4 || + reg==DSBInterrupt(0,0) || reg==DSBInterrupt(0,1) || reg==DSBInterrupt(0,2)); + for(auto pair:before)if(std::find(io.writes.begin(),io.writes.end(),pair.first)==io.writes.end())assert(io.r[pair.first]==pair.second); + io.r[0x71008]=0xC0000000;io.r[0x72008]=0xC0000000; + assert(extendActivePipes(io,state,7)==Status::Complete && state.active==7); } {IO io;io.wells=0x403;io.reads.clear();assert(readActivePipes(io,3).active==1); assert(std::find(io.reads.begin(),io.reads.end(),0x71008)==io.reads.end()); From 4f688dbb21a73553163d7496db75dea638beb206 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:23:13 +0900 Subject: [PATCH 109/139] Select fixed display pools from available aperture capacity --- WhateverXe/kern_xe.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index b34cc191..52cf9c5a 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -252,7 +252,7 @@ static bool xelpMemoryMatches(IOPCIDevice *pci) {return xelpMemory.reservationKn // Native server-state transitions can request aperture backing even after // accelerator binding. Protect slot1/2 through the existing registration // contract before admitting their owned scatter mappings. -static constexpr bool adlnFixedSysMemEnabled=true; +static bool adlnFixedSysMemEnabled=true; // Latched from the native layout before accelerator registration. static bool tglPcodeGateSerialize(void *,void *,OSSerialize *); static bool tglPcodeEDRAMSerialize(void *,void *,OSSerialize *); static bool tglTCBootstrapSerialize(void *,void *,OSSerialize *); @@ -7185,6 +7185,13 @@ void XE::wrapDisplay13FBMemoryInit(IOService *controller) { for(uint32_t i=0;valid && igetMetaClass()->getClassSize(),UINT32_MAX); + adlnFixedSysMemEnabled=valid && Display13FBTopology::multiOutputWiring(s->adlnOutputWiring) && + Display13FBMemory::sharedReservation(layout,Display13FBGTT::Aperture,true).valid; + IORegistryEntry::getRegistryRoot()->setProperty("N97FixedSysMemEnabled",adlnFixedSysMemEnabled); } else OSSafeReleaseNULL(map); if(!valid) {g.faulted=true;adlnFault(display13FBPower.memoryFaulted,__LINE__);} uint64_t record[] {1,valid,g.reads,g.faulted}; From 509a6cc905b4a591e3d12243c4d293cee3cffac5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:37:34 +0900 Subject: [PATCH 110/139] Document Big Sur DSI and external display coexistence --- CHANGELOG.md | 1 + README.md | 2 +- README_ja.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e3d37d..c63f4f2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed - Fix DEBUG boot freezes and restart loops caused by excessive kernel stack usage. +- Fix multi-display startup and display wake with an experimental MIPI DSI panel alongside HDMI or USB-C outputs on macOS Big Sur. ## 0.6.0 diff --git a/README.md b/README.md index 80f9a33b..146a572c 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. On macOS Big Sur, the internal HiDPI panel has been tested with up to two external HDMI/USB-C displays, including accelerated rendering and display off/on. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index bf4cdffe..fb03a779 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。macOS Big Surでは、内蔵HiDPIパネルとHDMI/USB-Cの外部2画面までの同時表示・加速・画面消灯復帰を確認しています。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 From 8e07535e07615750cd5a8e13ec6eb59fc4d4f7d3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 22:19:22 +0900 Subject: [PATCH 111/139] Remove arbitrary DSI mode and OS attribute restrictions --- CHANGELOG.md | 2 + README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_apple_timing.hpp | 4 +- WhateverXe/kern_dsi_attributes.hpp | 35 +++++++++++++ WhateverXe/kern_dsi_profile.hpp | 9 ++-- WhateverXe/kern_xe.cpp | 77 +++++++++++++++------------- tests/dsi_apple_adapter.py | 40 +++++++++------ tests/dsi_apple_timing.cpp | 2 +- tests/dsi_native_mode_fixture.cpp | 16 ++++++ tests/dsi_profile.cpp | 7 ++- tests/dsi_update_dispatch.py | 22 +++++--- 12 files changed, 150 insertions(+), 68 deletions(-) create mode 100644 WhateverXe/kern_dsi_attributes.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index c63f4f2d..703c1bdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Remove arbitrary MIPI DSI mode-list limits, forward ordinary OS attributes to the native driver, and accept firmware panel orientation metadata without changing physical link timing. + - Fix DEBUG boot freezes and restart loops caused by excessive kernel stack usage. - Fix multi-display startup and display wake with an experimental MIPI DSI panel alongside HDMI or USB-C outputs on macOS Big Sur. diff --git a/README.md b/README.md index 146a572c..8d9323d4 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. On macOS Big Sur, the internal HiDPI panel has been tested with up to two external HDMI/USB-C displays, including accelerated rendering and display off/on. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. +The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. OS-provided scaled modes are not restricted to a preferred resolution or a fixed list capacity; available HiDPI sizes depend on the macOS display configuration. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. On macOS Big Sur, the internal HiDPI panel has been tested with up to two external HDMI/USB-C displays, including accelerated rendering and display off/on. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index fb03a779..8043af2c 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,7 +106,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。macOS Big Surでは、内蔵HiDPIパネルとHDMI/USB-Cの外部2画面までの同時表示・加速・画面消灯復帰を確認しています。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 +任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。OSが提示する拡縮モードを特定の解像度や一覧の固定件数に制限せず、選択できるHiDPIサイズはmacOS側のディスプレイ設定に依存します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。macOS Big Surでは、内蔵HiDPIパネルとHDMI/USB-Cの外部2画面までの同時表示・加速・画面消灯復帰を確認しています。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 22778e7b..328eb826 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -41,8 +41,8 @@ inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false, // CoreDisplay can replace the reserved client IDs when rebuilding its mode // list. Treat reserved IDs as opaque: the complete // timing, not an assumed generation/index bit layout, qualifies this alias. -inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned index) { - if(index>=1024 || offered.reservedA[0]<0x80000000U || offered.reservedA[0]==UINT32_MAX || offered.signalLevels>1)return false; +inline bool clientTiming(const Timing &fixed,const Timing &offered,unsigned /*index*/) { + if(offered.reservedA[0]<0x80000000U || offered.reservedA[0]==UINT32_MAX || offered.signalLevels>1)return false; const uint64_t rounded=((fixed.pixelClock+5000)/10000)*10000; if((offered.pixelClock!=rounded && offered.pixelClock!=fixed.pixelClock) || offered.minPixelClock!=offered.pixelClock || offered.maxPixelClock!=offered.pixelClock || diff --git a/WhateverXe/kern_dsi_attributes.hpp b/WhateverXe/kern_dsi_attributes.hpp new file mode 100644 index 00000000..cee316ef --- /dev/null +++ b/WhateverXe/kern_dsi_attributes.hpp @@ -0,0 +1,35 @@ +// Attribute routing for the exact TGL framebuffer donor. Keep transport-only +// operations away from the adopted DSI path; leave OS bookkeeping and native +// Unsupported results to their original implementations. +#ifndef KERN_DSI_ATTRIBUTES_HPP +#define KERN_DSI_ATTRIBUTES_HPP +#include +namespace XELP { namespace DSI { namespace AppleAdapter { +inline bool transportAttribute(uint32_t attribute) { + switch(attribute) { + // AGPM selectors belong to the shared GPU power implementation and + // are not DSI transport requests. Do not filter them here. + case 0x68646370U: // hdcp: DisplayPath::initHDCP (DDI/AUX) + case 0x73707772U: // spwr: controller hwShutdown, outside DSI stop + return true; + default:return false; + } +} +inline bool transportConnectionAttribute(uint32_t attribute) { + switch(attribute) { + case 0x00696772U: // igr: direct native hwSetBacklight + case 0x706E6C74U: // pnlt: native panel power configuration + case 0x7073723FU: // psr?: eDP panel self-refresh + case 0x70726F62U: // prob: native physical connection probe + case 0x64707461U: // dpta: Port::setLinkConfig (DP) + case 0x7073636EU: // pscn: hardware panel-fitter update, not GPU scaling + // Native setAttributeForUpstream reaches PAVP/HDCP link operations. + case 0x00617572U: case 0x00617573U: case 0x00617577U: + case 0x61757063U: case 0x61757068U: case 0x61757070U: case 0x61757072U: + case 0x61756D73U: case 0x61756363U: + return true; + default:return false; + } +} +}}} +#endif diff --git a/WhateverXe/kern_dsi_profile.hpp b/WhateverXe/kern_dsi_profile.hpp index e559dfb8..45e007f2 100644 --- a/WhateverXe/kern_dsi_profile.hpp +++ b/WhateverXe/kern_dsi_profile.hpp @@ -26,7 +26,7 @@ struct Profile { uint16_t version {}; uint8_t checksum {},panel {}; bool bdbTailQuirk {}; // explicit, caller-approved one-byte final END quirk Mode mode; - uint8_t lanes {},byteClock {},clockPrepare {},clockPre {},hsPrepare {},hsExit {}; + uint8_t orientationQuarterTurns {},lanes {},byteClock {},clockPrepare {},clockPre {},hsPrepare {},hsExit {}; uint16_t clockPrepareZero {},hsPrepareZero {}; bool btaDisabled {}; uint32_t hsTxTimeout {},lpRxTimeout {},turnaroundTimeout {},initTimer {}; @@ -97,11 +97,14 @@ inline Profile parseProfile(Span input,bool allowFinalEndQuirk=false) { if(options.size<1 || options.data[0]>=6 || config.size<792)return failure; r.panel=options.data[0];const auto c=config.data+122*r.panel; const auto general=sequenceU32(c+2);const auto port=profileU16(c+6); - // Bridge, command mode, rotation, dual link, reversal and alternate DCS + // Bridge, command mode, dual link, reversal and alternate DCS // destinations are deliberately outside the first implementation. if((general&4) || ((general>>3)&3)!=2 || (general&32) || - ((general>>6)&3)!=2 || ((general>>10)&15)!=4 || ((general>>14)&3) || + ((general>>6)&3)!=2 || ((general>>10)&15)!=4 || (port&0xFFF3) || (profileU16(c+24)&7) || c[23]!=0)return failure; + // VBT rotation describes panel mounting, not the DSI wire protocol. + // Keep the metadata without synthesizing or restricting OS transforms. + r.orientationQuarterTurns=(general>>14)&3; r.lanes=((port>>2)&3)+1;if(r.lanes!=4)return failure; r.byteClock=c[23];r.btaDisabled=(general>>16)&1; r.hsTxTimeout=sequenceU32(c+26);r.lpRxTimeout=sequenceU32(c+30); diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 52cf9c5a..6c7d4297 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -21,6 +21,7 @@ #include "kern_display13_boot.hpp" #include "kern_display13_dsi_trial.hpp" #include "kern_dsi_readout.hpp" +#include "kern_dsi_attributes.hpp" #include "kern_dsi_apple_timing.hpp" #include "kern_display13_firmware.hpp" #include "kern_display13_tc_aux.hpp" @@ -4679,7 +4680,9 @@ struct DSIFixedModeOwner { OSData *data {}; XELP::DSI::Mode mode; XELP::DSI::AppleAdapter::Timing timing; - XELP::DSI::AppleAdapter::Timing presentation,requested,offered[32]; + XELP::DSI::AppleAdapter::Timing presentation,requested; + OSData *offeredData {}; + const XELP::DSI::AppleAdapter::Timing *offered {}; unsigned offerCount {}; uint32_t activeMode {XELP::DSI::AppleAdapter::FixedModeID}; uintptr_t changingThread {},logical {}; @@ -7390,11 +7393,11 @@ bool XE::releaseDSIFixedMode(IOService *fb) { const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,nullptr); if(result!=kIOReturnSuccess || Display13FBMemory::field(fb,0x4260) || fb->getProperty("IOFBDetailedTimings"))return false; - auto array=o.array;auto data=o.data; + auto array=o.array;auto data=o.data;auto offered=o.offeredData; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); - o.array=nullptr;o.data=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; + o.array=nullptr;o.data=nullptr;o.offeredData=nullptr;o.offered=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; o.presentation={};o.requested={};o.offerCount=0;o.activeMode=o.clientMode;o.presentationReady=false;o.presentationFault=false;o.changingThread=0; - OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);fb->release();return true; + OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);OSSafeReleaseNULL(offered);fb->release();return true; } // IOGraphics also requests EDID independently of online detection. Stop at @@ -7474,7 +7477,7 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; uint32_t client=0;unsigned count=0; uint64_t offer[16] {3,array ? array->getCount() : 0,UINT32_MAX}; - if(array && array->getCount()<=1024)for(unsigned i=0;igetCount();++i) { + if(array)for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto &t=*static_cast(bytes->getBytesNoCopy()); @@ -7485,7 +7488,6 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingRejected",bytes); continue; } - if(count==arrsize(dsiFixedMode.offered))return kIOReturnNoResources; ++count;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; @@ -7496,26 +7498,41 @@ uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { const bool canonical=data && data->getLength()==sizeof(dsiFixedMode.timing) && !memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)); if(!count && !canonical)return kIOReturnUnsupported; - // Native storage retains the immutable wire mode. Qualified presentation - // timings are returned by validation, without changing the DSI link. - const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); - if(result==kIOReturnSuccess) { - if(!dsiModeCoherent(fb))return kIOReturnNotReady; - // Validate the whole list before publishing it; avoid a timing - // array on the kernel stack. The caller retains this immutable - // array and the FB mutex serializes both passes. - unsigned copied=0; - if(array)for(unsigned i=0;igetCount();++i) { + // Size storage from the accepted OS list, not a fixed number of modes. + // Prepare the complete replacement before touching native state so an + // allocation failure preserves the previous list and its timing pointers. + OSData *replacement=nullptr; + if(count) { + if(count>UINT32_MAX/sizeof(dsiFixedMode.timing))return kIOReturnNoResources; + replacement=OSData::withCapacity(count*sizeof(dsiFixedMode.timing)); + if(!replacement)return kIOReturnNoResources; + for(unsigned i=0;igetCount();++i) { auto bytes=OSDynamicCast(OSData,array->getObject(i)); if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; const auto &t=*static_cast(bytes->getBytesNoCopy()); if(XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i) || - XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i))dsiFixedMode.offered[copied++]=t; + XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { + if(!replacement->appendBytes(&t,sizeof(t))) { + replacement->release();return kIOReturnNoResources; + } + } } - dsiFixedMode.offerCount=copied; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingList",dsiFixedMode.offered, - copied*sizeof(dsiFixedMode.offered[0])); + } + // Native storage retains the immutable wire mode. Qualified presentation + // timings are returned by validation, without changing the DSI link. + const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); + if(result==kIOReturnSuccess && dsiModeCoherent(fb)) { + auto previous=dsiFixedMode.offeredData; + dsiFixedMode.offeredData=replacement; + dsiFixedMode.offered=replacement ? static_cast(replacement->getBytesNoCopy()) : nullptr; + dsiFixedMode.offerCount=count; + if(replacement)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingList",replacement); + else IORegistryEntry::getRegistryRoot()->removeProperty("WEXDSITimingList"); if(client)dsiFixedMode.clientMode=client; + OSSafeReleaseNULL(previous); + } else { + OSSafeReleaseNULL(replacement); + if(result==kIOReturnSuccess)return kIOReturnNotReady; } return result; }(); @@ -7940,9 +7957,9 @@ uint32_t XE::wrapDSISetGammaTable(IOService *fb,uint32_t channels,uint32_t count const auto result=FunctionCast(wrapDSISetGammaTable,callbackXE->orgDSITransaction[4])(fb,channels,count,bits,data,sync); endDSINativeWriter(token);return result; } -// P2 preserves the firmware link. Unknown/native panel and power setters do -// not inherit eDP behavior. The admitted CPU/color operations remain counted -// through their native bodies; P5 will replace this policy with DSI controls. +// Power/backlight use DSI owners. Block only the donor branches that enter +// unsupported native transport controls; let ordinary OS attributes reach +// their native implementations under the existing writer lifetime. uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t value) { recordDSIClient(fb,7,attribute,UINT32_MAX); const auto result=[&]() -> uint32_t { @@ -7956,9 +7973,7 @@ uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t valu return wrapDSISetPowerState(fb,static_cast(value)); } if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); - // wsrv and capd: retain native server/capture bookkeeping and the - // routed plane/transaction work. Other selectors need admission. - if(attribute!=0x77737276 && attribute!=0x63617064)return kIOReturnUnsupported; + if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; } } @@ -8049,15 +8064,7 @@ uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { if(connection!=0)return kIOReturnUnsupported; if(attribute==0x626B6C74)return callbackXE->setDSIBrightness(fb,value); - // Controller depth and gamma scales/flush have qualified native - // CPU/color paths. In particular ignore, pnlt, psr?, powr, - // probe and DP-link configuration must never reach native DSI. - switch(attribute) { - case 0x00647064: // controller depth - case 0x72677363: case 0x67677363: case 0x62677363: // RGB gamma scale - case 0x67736320: case 0x666C7573: break; // gamma scale / flush - default:return kIOReturnUnsupported; - } + if(XELP::DSI::AppleAdapter::transportConnectionAttribute(attribute))return kIOReturnUnsupported; token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; } } diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 1e15fcd5..9945fda2 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -34,7 +34,7 @@ kIOReturnUnsupported=0xe00002c7U,kIOReturnBadArgument=0xe00002c2U,kIOReturnNoResources=0xe00002bdU; void *current_thread(){return reinterpret_cast(1);} #define arrsize(x) (sizeof(x)/sizeof(*(x))) -unsigned alive=0,calls[5]{};bool dataOK=true,arrayOK=true,publishOK=true,geometryOK=true,clearOK=true; +unsigned alive=0,calls[5]{};bool dataOK=true,arrayOK=true,publishOK=true,geometryOK=true,clearOK=true,capacityOK=true,appendOK=true; struct Meta {unsigned getClassSize(){return 0x4b90;}} meta; struct OSObject { unsigned refs=1;OSObject(){++alive;}virtual ~OSObject(){--alive;} @@ -44,9 +44,11 @@ struct OSNumber:OSObject {uint64_t value=0;uint64_t unsigned64BitValue(){return value;}}; struct OSData:OSObject { std::vector bytes; + static OSData *withCapacity(unsigned n){if(!capacityOK)return nullptr;auto d=new OSData;d->bytes.reserve(n);return d;} + bool appendBytes(const void *p,unsigned n){if(!appendOK)return false;auto b=static_cast(p);bytes.insert(bytes.end(),b,b+n);return true;} static OSData *withBytes(const void *p,unsigned n){if(!dataOK)return nullptr;auto d=new OSData; d->bytes.assign(static_cast(p),static_cast(p)+n);return d;} - unsigned getLength(){return bytes.size();}void *getBytesNoCopy(){return bytes.data();} + unsigned getLength(){return bytes.size();}const void *getBytesNoCopy(){return bytes.data();} }; struct OSArray:OSObject { std::vector objects; @@ -64,9 +66,10 @@ void removeProperty(const char *k){auto i=properties.find(k);if(i!=properties.end()){i->second->release();properties.erase(i);}} ~IOService(){for(auto i:properties)i.second->release();} }; -struct IORegistryEntry {void setProperty(const char *key,OSData *d){assert(!strcmp(key,"WEXDSITimingRejected") && d->getLength()==160);} +struct IORegistryEntry {void setProperty(const char *key,OSData *d){assert((!strcmp(key,"WEXDSITimingRejected") && d->getLength()==160) || (!strcmp(key,"WEXDSITimingList") && !(d->getLength()%160)));} + void removeProperty(const char *key){assert(!strcmp(key,"WEXDSITimingList"));} static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} - void setProperty(const char *key,const void *p,unsigned n){if(!strncmp(key,"WEXDSIModeInfo",14)){assert(n==48);return;}if(!strcmp(key,"WEXDSITimingList")){assert(n<=32*160 && !(n%160));return;}if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; + void setProperty(const char *key,const void *p,unsigned n){if(!strncmp(key,"WEXDSIModeInfo",14)){assert(n==48);return;}if(!strcmp(key,"WEXDSITimingList")){assert(!(n%160));return;}if(!strcmp(key,"WEXDSITimingRejected")){assert(n==160);return;}assert(n==128);if(!strcmp(key,"WEXDSITimingOffer"))return;const auto w=static_cast(p);assert(w[0]==1 && w[1]<13 && w[2]);}}; struct IORecursiveLock {unsigned depth=0;};struct IOLock {unsigned depth=0;}; void IORecursiveLockLock(IORecursiveLock *l){++l->depth;}void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} void IOLockLock(IOLock *l){++l->depth;}void IOLockUnlock(IOLock *l){assert(l->depth);--l->depth;} @@ -230,21 +233,26 @@ assert(!XE::wrapDSIValidateMode(fb,pair[1],nullptr,&timing)); assert(static_cast(timing)->scalerFlags==0x30 && !dsiFixedMode.timing.scalerFlags); assert(!XE::wrapDSIGetModeInformation(fb,pair[1],&info) && info.width==1920 && info.height==1080); - // CoreDisplay offers both exact-clock and EDID-rounded scaled modes. + // A large OS list with many distinct scaled dimensions must not be + // rejected by the former 32-mode capacity or 1024-index gates. auto many=OSArray::withCapacity(1); - for(unsigned i=0;i<24;++i){auto t=rotated;t.reservedA[0]=0x80006000+i; - t.horizontalScaled=2880;t.verticalScaled=1620; + for(unsigned i=0;i<1100;++i){auto t=rotated;t.reservedA[0]=0x80006000+i; + t.horizontalScaled=1280+2*i;t.verticalScaled=720+i; t.pixelClock=t.minPixelClock=t.maxPixelClock=(i&1)?135255000:135260000; auto data=OSData::withBytes(&t,sizeof(t));many->setObject(data);data->release();} - assert(!XE::wrapDSISetDetailedTimings(fb,many) && XE::wrapDSIGetModeCount(fb)==24); - int32_t modes[32];assert(!XE::wrapDSIGetModes(fb,modes)); - for(unsigned i=0;i<24;++i){assert(static_cast(modes[i])==0x80006000+i); + auto previous=dsiFixedMode.offeredData;const auto beforePublish=calls[0]; + capacityOK=false; + assert(XE::wrapDSISetDetailedTimings(fb,many)==kIOReturnNoResources); + capacityOK=true;appendOK=false; + assert(XE::wrapDSISetDetailedTimings(fb,many)==kIOReturnNoResources); + appendOK=true; + assert(dsiFixedMode.offeredData==previous && XE::wrapDSIGetModeCount(fb)==2 && calls[0]==beforePublish); + assert(!XE::wrapDSISetDetailedTimings(fb,many) && XE::wrapDSIGetModeCount(fb)==1100); + int32_t modes[1100];assert(!XE::wrapDSIGetModes(fb,modes)); + for(unsigned i=0;i<1100;++i){assert(static_cast(modes[i])==0x80006000+i); assert(!XE::wrapDSIValidateMode(fb,modes[i],nullptr,&timing)); - assert(static_cast(timing)->horizontalScaled==2880); - assert(!XE::wrapDSIGetModeInformation(fb,modes[i],&info) && info.width==2880 && info.height==1620);} - for(unsigned i=24;i<33;++i){auto t=rotated;t.reservedA[0]=0x80006000+i; - auto data=OSData::withBytes(&t,sizeof(t));many->setObject(data);data->release();} - assert(XE::wrapDSISetDetailedTimings(fb,many)==kIOReturnNoResources && XE::wrapDSIGetModeCount(fb)==24); + assert(static_cast(timing)->horizontalScaled==1280+2*i); + assert(!XE::wrapDSIGetModeInformation(fb,modes[i],&info) && info.width==1280+2*i && info.height==720+i);} many->release();assert(!XE::wrapDSISetDetailedTimings(fb,rotationList)); rotationData->release();rotationList->release(); // Legacy UI attributes alone must never fabricate a presentation timing. @@ -280,7 +288,7 @@ assert(dsiModeTarget(fb) && dsiFixedMode.array->refs==1 && Display13FBMemory::field(fb,0x4260)==dsiFixedMode.array); assert(!XE::wrapDSIGetModeCount(fb));cleanup(); assert(!fbLock.depth && !ownerLock.depth);fb->release();peer->release();assert(!alive); - puts("PASS production DSI fixed mode: retained native array, publication failure, index restriction, fixed enumeration, metadata and foreign FB dispatch"); + puts("PASS production DSI fixed mode: retained native array, publication failure, dynamic enumeration, metadata and foreign FB dispatch"); } '''.replace('OWNER', owner).replace('FUNCTIONS', functions) with tempfile.TemporaryDirectory(prefix='dsi-apple-adapter-') as directory: diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 15e224cf..fcf51f96 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -22,7 +22,7 @@ int main() { assert(AppleAdapter::clientTiming(t,exact,1)); exact.minPixelClock--;assert(!AppleAdapter::clientTiming(t,exact,1)); assert(AppleAdapter::clientTiming(t,offered,1)); // IDs are opaque, not array positions. - assert(!AppleAdapter::clientTiming(t,offered,1024)); + assert(AppleAdapter::clientTiming(t,offered,1024)); for(uint32_t id:{0x80002000U,0x80003000U,0x80005007U}) { offered.reservedA[0]=id;assert(AppleAdapter::clientTiming(t,offered,0)); } diff --git a/tests/dsi_native_mode_fixture.cpp b/tests/dsi_native_mode_fixture.cpp index cd01e111..bb09afa9 100644 --- a/tests/dsi_native_mode_fixture.cpp +++ b/tests/dsi_native_mode_fixture.cpp @@ -150,6 +150,22 @@ int main(int argc,char **argv) { logicalResult=0;failRecord=true;assert(allocateLogical(fb)==0xE00002BD); assert(!field(fb,0x4340)); assert(logicalAllocations==6 && logicalFrees==6 && recordAllocations==6 && recordFrees==6); + // Different render sizes and all ordinary rotations use the same + // donor contract. No preferred HiDPI size is an admission condition. + failRecord=false; + for(uint32_t rotation:{0U,0x30U,0x40U,0x50U})for(unsigned width:{1280U,2560U,3200U}) { + auto other=t;other.scalerFlags=rotation; + const auto height=width*9/16; + other.horizontalScaled=(rotation&0x10)?width:height; + other.verticalScaled=(rotation&0x10)?height:width; + assert(AppleAdapter::prepareGeometry(fb,path,p,other,compute,true)); + assert(allocateLogical(fb)==0); + auto record=field(fb,0x4340); + assert(record && record->bytes==uint64_t(record->pitch)*other.verticalScaled); + assert(field(fb,0x4348)==0x12345000); + releaseLogical(fb);assert(!field(fb,0x4340)); + } + assert(logicalAllocations==18 && logicalFrees==18 && recordAllocations==18 && recordFrees==18); munmap(image,0x400000);puts("DSI native geometry: fixed scanout, quarter-turn logical buffers, HiDPI and failure cleanup replay passed"); } puts("DSI native mode preparation rollback and admission passed"); diff --git a/tests/dsi_profile.cpp b/tests/dsi_profile.cpp index 06b63ee0..8c79eeaa 100644 --- a/tests/dsi_profile.cpp +++ b/tests/dsi_profile.cpp @@ -73,7 +73,12 @@ int main(int argc,char **argv) { bad=b; if(id==2){bad[p+21]=22;reject(bad);} if(id==40){bad[p]=6;reject(bad);} - if(id==52){bad[p+6]=8;reject(bad);bad=b;put(bad,p+98,49);reject(bad);} + if(id==52){ + for(unsigned turn=0;turn<4;++turn){auto oriented=b;put(oriented,p+2,0x11190|(turn<<14),4); + auto profile=parse(oriented);assert(profile.valid() && profile.orientationQuarterTurns==turn); + auto plan=linkPlan(profile);assert(plan.valid && plan.htotal==link.htotal && plan.vtotal==link.vtotal && plan.afeKHz==link.afeKHz);} + bad[p+6]=8;reject(bad);bad=b;put(bad,p+98,49);reject(bad); + } if(id==58){put(bad,p+8,1);reject(bad);} if(id==43){bad[p+257]=16;reject(bad);} // Duplicate required blocks reject before publication. diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index fb2b578a..fb46e78f 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -20,6 +20,7 @@ code=r''' #include "kern_display13_fb_memory.hpp" #include "kern_dsi_apple_timing.hpp" +#include "kern_dsi_attributes.hpp" #include "kern_display13_transcoder.hpp" #include #include @@ -326,21 +327,26 @@ } {Case c;nestedTx=true;auto before=txCalls[2];assert(callTx(0,&c.fb)==txResult(0)); assert(txCalls[2]==before+1 && !display13FBPower.dsiNativeWriters && !fbLock.depth && !owner);} - // Only qualified P2 attributes reach native; lifetime accounting spans + // Ordinary OS attributes reach native; lifetime accounting spans // their nested flip/color work and preserves full pointer-sized values. - for(unsigned attr:{0x77737276U,0x63617064U}){Case c;expectedAttribute=attr;expectedValue=txOptions;nestedAttribute=true; + for(unsigned attr:{0x77737276U,0x63617064U,0x63727363U,0x706E6C70U,0x73484443U,0x2070656EU,0x20637463U,0xDEADBEEFU}){Case c;expectedAttribute=attr;expectedValue=txOptions;nestedAttribute=true; const auto before=attrCalls[0];assert(XE::wrapDSISetAttribute(&c.fb,attr,expectedValue)==0xe0000121U); assert(attrCalls[0]==before+1 && !display13FBPower.dsiNativeWriters && !owner && !fbLock.depth);} - for(unsigned attr:{0x00647064U,0x72677363U,0x67677363U,0x62677363U,0x67736320U,0x666C7573U}){ + for(unsigned attr:{0x00647064U,0x72677363U,0x67677363U,0x62677363U,0x67736320U,0x666C7573U,0x76626C6DU,0x64666C67U,0x00677264U,0x20627063U,0x706F7772U,0xDEADBEEFU}){ Case c;expectedAttribute=attr;expectedValue=txOptions;expectedConnection=0; assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,expectedValue)==0xe0000122U && !display13FBPower.dsiNativeWriters); assert(XE::wrapDSISetConnectionAttribute(&c.fb,1,attr,expectedValue)==kIOReturnUnsupported);} - for(unsigned attr:{0x706F7772U,0x70777273U,0x73707772U,0x64706F77U,0x706E6C70U,0x706E6C74U, - 0x00696772U,0x7073723FU,0x70726F62U,0x64706972U,0xDEADBEEFU}){ - Case c;const auto before=attrCalls[0]+attrCalls[1]+attrCalls[2];const auto original=c.fb; + // Transport-only routes remain blocked, independently of the selector + // namespace. Native/IOGraphics handles all other attributes and return values. + for(unsigned attr:{0x68646370U,0x73707772U}) { + Case c;const auto before=attrCalls[0];const auto original=c.fb; assert(XE::wrapDSISetAttribute(&c.fb,attr,0)==kIOReturnUnsupported); + assert(attrCalls[0]==before && !memcmp(&c.fb,&original,sizeof(c.fb)));} + for(unsigned attr:{0x706E6C74U,0x00696772U,0x7073723FU,0x70726F62U,0x64707461U,0x7073636EU, + 0x00617572U,0x61756D73U,0x61757063U}) { + Case c;const auto before=attrCalls[1];const auto original=c.fb; assert(XE::wrapDSISetConnectionAttribute(&c.fb,0,attr,0)==kIOReturnUnsupported); - assert(attrCalls[0]+attrCalls[1]+attrCalls[2]==before && !memcmp(&c.fb,&original,sizeof(c.fb)) && !display13FBPower.memoryFaulted);} + assert(attrCalls[1]==before && !memcmp(&c.fb,&original,sizeof(c.fb)) && !display13FBPower.memoryFaulted);} {Case c;xe.dsiStopAttempted=true;const auto before=attrCalls[0]; assert(XE::wrapDSISetAttribute(&c.fb,0x626B7376,123)==0); assert(XE::wrapDSISetAttribute(&c.fb,0x626B7376,0x100000001ULL)==kIOReturnUnsupported); @@ -378,7 +384,7 @@ assert(streamCalls==before);} {Case c;xe.adlnOutputWiring.dsi=0;nativeFB=nullptr;expectedPath=nullptr; assert(XE::wrapDSISetStream(&c.ctl,nullptr,nullptr,¶mAnchor)==0xe0000124U);} - puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor/transaction counts, P2 attribute/power admission, full-width arguments, native wait lock boundaries, peer and disabled-DSI forwarding"); + puts("PASS DSI public mode preflight and native update dispatch: stopped/faulted rejection, held FB serialization, nested cursor/transaction counts, transport-specific attribute/power admission, full-width arguments, native wait lock boundaries, peer and disabled-DSI forwarding"); } '''.replace('SCOPE',scope).replace('FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='dsi-update-') as d: From 0e34f3bb8ec4b76bf045549dd8a73c0dba507f66 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:06:02 +0900 Subject: [PATCH 112/139] Prepare cursor watermarks for the requested size before control update --- CHANGELOG.md | 2 + WhateverXe/kern_display13_wm.hpp | 5 +- WhateverXe/kern_xe.cpp | 6 +- tests/display13_cursor_resize.py | 12 ++++ tests/display13_cursor_resize_fixture.cpp | 88 +++++++++++++++++++++++ tools/test.py | 2 +- 6 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 tests/display13_cursor_resize.py create mode 100644 tests/display13_cursor_resize_fixture.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 703c1bdf..fdd5d629 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Fix display freezes during hardware cursor size changes by preparing watermarks for the requested size before the native control update. + - Remove arbitrary MIPI DSI mode-list limits, forward ordinary OS attributes to the native driver, and accept firmware panel orientation metadata without changing physical link timing. - Fix DEBUG boot freezes and restart loops caused by excessive kernel stack usage. diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index 05c94101..b4cc4584 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -155,8 +155,11 @@ inline uint32_t cursorWidth(uint32_t control) { default:return 0; } } +inline bool cursorWidthSupported(uint32_t width) { + return width==64 || width==128 || width==256; +} inline bool cursorMatches(uint32_t control,uint32_t width,bool allowDisabled=false) { - if(control==UINT32_MAX || (width!=64 && width!=128 && width!=256))return false; + if(control==UINT32_MAX || !cursorWidthSupported(width))return false; return (!(control&0x27) && allowDisabled) || cursorWidth(control)==width; } inline Level yTiledLevel(uint32_t width,uint32_t htotal,uint64_t pixelHz,uint32_t latencyUs,uint32_t allocation) { diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 6c7d4297..593bd0b1 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11228,8 +11228,12 @@ void XE::adlnWMSetup(OSObject *plane,OSObject *path,bool cursor) { inputs[14]=wells;inputs[15]=rangeRaw;inputs[16]=ctl;inputs[17]=ipc;inputs[18]=cursorMode; inputs[19]=width;inputs[20]=htotal;inputs[21]=rgbSupported;inputs[22]=powered; inputs[23]=Display13FBMemory::field(plane,0x10C); + // Native cursor changes prepare watermarks for FB +0x4278 before + // writing the new CURCNTR. The live register can still describe the + // previous size here. Validate the requested size now; latch() checks + // the applied control against the prepared plan after the update. if(powered && rangeRaw!=UINT32_MAX && Display13DBUF::valid(range) && rgbSupported && ipc!=UINT32_MAX && - (!cursor || Display13WM::cursorMatches(cursorMode,width,true)) && + (!cursor || Display13WM::cursorWidthSupported(width)) && !(Display13DBUF::slices(range)&~Display13DBUF::allowed(c.dbuf,pipe)) && (cursor || !Display13DBUF::overlap(range,c.dbuf.pipe[pipe].cursor))) { // Audited native pixel/scaling calculation is CPU-only. Preserve its diff --git a/tests/display13_cursor_resize.py b/tests/display13_cursor_resize.py new file mode 100644 index 00000000..0af6901a --- /dev/null +++ b/tests/display13_cursor_resize.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +"""Replay production WM preparation while CURCNTR still holds the old size.""" +from pathlib import Path +import re, subprocess, tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'void XE::adlnWMSetup\(.*?\n\}',s,re.S).group() +code=(r/'tests/display13_cursor_resize_fixture.cpp').read_text().replace('// PRODUCTION_SETUP',body) +with tempfile.TemporaryDirectory(prefix='cursor-resize-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_cursor_resize_fixture.cpp b/tests/display13_cursor_resize_fixture.cpp new file mode 100644 index 00000000..3bb1f58f --- /dev/null +++ b/tests/display13_cursor_resize_fixture.cpp @@ -0,0 +1,88 @@ +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_dbuf.hpp" +#include "kern_display13_wm.hpp" +#include +#include +#include +#include +#include +using IOLock=int; +static unsigned locks; +void IOLockLock(int*){assert(!locks);++locks;} +void IOLockUnlock(int*){assert(locks);--locks;} +struct OSObject {unsigned size;OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;}}; +using IOService=OSObject; +struct Map {std::map r;}; +struct Display13FBDBUFIO {Map *map;uint32_t read(uint32_t reg){assert(map->r.count(reg));return map->r[reg];}}; +static unsigned selectedPipe; +namespace TGLFBBinding { + enum class Status {Unavailable,Complete}; + struct Object {void *p;unsigned size;}; + struct Objects {Object controller,fb,path,plane,port;unsigned owner;bool logical;}; + struct Result {Status status;unsigned framebuffer,pipe,ddi,plane;}; + Result inspect(const Objects&,void*,unsigned){return {Status::Complete,selectedPipe,selectedPipe,selectedPipe,0};} +} +struct Pipe {IOService *memoryFramebuffer{};uint64_t watermarkClockHz{};Display13WM::Plan wm[2];}; +struct Power { + int *lock{};IOService *controller{};Map *map{}; + bool engineEnabled{},clockFaulted{},memoryFaulted{},watermarkFaulted{}; + struct {struct {bool faulted{};}state;}bridge; + Display13DBUF::State dbuf;Display13WM::Latency latency;Pipe pipe[3]; +}display13FBPower; +struct IORegistryEntry { + std::map> records; + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry root;return &root;} + void setProperty(const char *name,const void *p,unsigned size){records[name]=std::vector((const uint8_t*)p,(const uint8_t*)p+size);} + void *getProperty(const char *name){return records.count(name)?this:nullptr;} +}; +unsigned display13FBExpectedOwner(void*){return 0;} +bool dsiLogicalSurface(void*){return true;} +void adlnFault(bool &f,unsigned){f=true;} +struct XE { + bool display13FBWatermarkHooksReady=true;void *adlnPlatform{};unsigned adlnOutputWiring=0; + uintptr_t adlnAdjustPixelClock{}; + static bool dsiSharedWriterStopped(void*,unsigned){return false;} + static void adlnWMSetup(OSObject*,OSObject*,bool); +}state,*callbackXE=&state; +uint64_t adjust(void*,void*,void*,bool cursor,void*){assert(cursor);return 135255000;} +templatevoid put(void *p,unsigned off,T v){memcpy((uint8_t*)p+off,&v,sizeof(v));} +// PRODUCTION_SETUP +alignas(16) static uint8_t ctl[0x1c20],fb[0x4b90],path[0x3730],plane[0x1c8]; +static int mutex; +void setup(Map &map,unsigned pipe,uint32_t oldControl,uint32_t width){ + memset(ctl,0,sizeof(ctl));memset(fb,0,sizeof(fb));memset(path,0,sizeof(path));memset(plane,0,sizeof(plane)); + ((OSObject*)ctl)->size=sizeof(ctl);((OSObject*)fb)->size=sizeof(fb); + ((OSObject*)path)->size=sizeof(path);((OSObject*)plane)->size=sizeof(plane); + selectedPipe=pipe;display13FBPower={};auto &c=display13FBPower; + c.lock=&mutex;c.controller=(IOService*)ctl;c.map=↦c.engineEnabled=true;c.dbuf.begun=true; + c.pipe[pipe].memoryFramebuffer=(IOService*)fb; + c.dbuf.pipe[pipe].cursor=pipe?Display13DBUF::Range{2048,2088}:Display13DBUF::Range{0,40}; + c.latency=Display13WM::decodeLatency(0x63503300,0x00909090,30); + assert(c.latency.status==Display13WM::Status::Complete); + put(plane,0x68,(IOService*)fb);put(plane,0x100,0x94001000U); + put(fb,0x4278,width);put(path,0x13A,uint16_t(1125)); + map.r={{0x45404,0xFC0F},{0x45004,8},{0x70080+0x1000*pipe,oldControl}}; + state.adlnAdjustPixelClock=(uintptr_t)adjust;IORegistryEntry::getRegistryRoot()->records.clear(); +} +int main(){ + const unsigned widths[]={64,128,256},controls[]={0x10000027,0x10000022,0x10000023}; + for(unsigned p=0;p<3;++p)for(unsigned old=0;old<3;++old)for(unsigned next=0;next<3;++next){ + Map map;setup(map,p,controls[old],widths[next]);auto before=map.r; + XE::adlnWMSetup((OSObject*)plane,(OSObject*)path,true); + auto &c=display13FBPower;const auto &plan=c.pipe[p].wm[1]; + assert(!c.watermarkFaulted && plan.status==Display13WM::Status::Complete && plan.width==widths[next]); + assert(map.r==before); // Preparation does not change CURCNTR to make the check pass. + const auto expected=Display13WM::plan(widths[next],1126,135255000,40,c.latency,true,false,widths[next]); + assert(plan.normal[0].encoded()==expected.normal[0].encoded()); + // After application the old size must still fail the strict match. + assert(Display13WM::cursorMatches(controls[old],plan.width)==(old==next)); + assert(Display13WM::cursorMatches(controls[next],plan.width)); + } + for(unsigned invalid:{0U,32U,512U}){ + Map map;setup(map,0,0x10000027,invalid);XE::adlnWMSetup((OSObject*)plane,(OSObject*)path,true); + assert(display13FBPower.watermarkFaulted); + } + Map map;setup(map,0,0x10000027,256);display13FBPower.dbuf.pipe[0].cursor={0,0}; + XE::adlnWMSetup((OSObject*)plane,(OSObject*)path,true);assert(display13FBPower.watermarkFaulted); + assert(!locks);puts("production cursor WM: 27 size transitions, post-apply mismatch and invalid input checks passed"); +} diff --git a/tools/test.py b/tools/test.py index c89206d3..47d9408e 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,7 @@ 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', '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 += ['dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] +python_tests += ['display13_cursor_resize','dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] 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)] From 94a6b396bdd23342661fb54c6fbf14bed6d804d8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:22:20 +0900 Subject: [PATCH 113/139] Add opt-in persistent shutdown milestone observer for qualified Big Sur kernel --- README.md | 2 + README_ja.md | 2 + WhateverXe/kern_host.cpp | 2 + WhateverXe/kern_shutdown_observer.cpp | 113 ++++++++++++++++++++++++++ WhateverXe/kern_shutdown_observer.hpp | 3 + 5 files changed, 122 insertions(+) create mode 100644 WhateverXe/kern_shutdown_observer.cpp create mode 100644 WhateverXe/kern_shutdown_observer.hpp diff --git a/README.md b/README.md index 8d9323d4..0d562886 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. +The optional `-wex-shutdown-observe` argument installs explicitly armed shutdown diagnostics on the qualified Big Sur 20G1443 kernel. It records bounded shutdown milestones in dedicated NVRAM variables for recovery after power-off. It is disabled by default, requires a per-boot diagnostic arm token, and does not change shutdown policy or suppress waits. + The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. OS-provided scaled modes are not restricted to a preferred resolution or a fixed list capacity; available HiDPI sizes depend on the macOS display configuration. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. On macOS Big Sur, the internal HiDPI panel has been tested with up to two external HDMI/USB-C displays, including accelerated rendering and display off/on. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index 8043af2c..472120ba 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,6 +106,8 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 +任意の `-wex-shutdown-observe` 引数は、適合するBig Sur 20G1443 kernelに明示的な有効化が必要な終了診断を組み込みます。終了段階を専用NVRAM変数へ有限件記録し、電源断後の回収に使用します。既定では無効で、起動ごとの診断用arm tokenが必要です。終了方針や待機処理は変更しません。 + 任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。OSが提示する拡縮モードを特定の解像度や一覧の固定件数に制限せず、選択できるHiDPIサイズはmacOS側のディスプレイ設定に依存します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。macOS Big Surでは、内蔵HiDPIパネルとHDMI/USB-Cの外部2画面までの同時表示・加速・画面消灯復帰を確認しています。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index db79068d..98030b15 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -3,6 +3,7 @@ #include "kern_xelp_device.hpp" #include "kern_xe_data_access.hpp" #include "kern_backlight_profile.hpp" +#include "kern_shutdown_observer.hpp" #include #include #include @@ -26,6 +27,7 @@ void XeHost::init() { XeDataAccess::init(); } void XeHost::processKernel(KernelPatcher &patcher) { + ShutdownObserver::install(patcher); auto info=DeviceInfo::create();if(!info)return; auto gpu=OSDynamicCast(IOPCIDevice,info->videoBuiltin); // Match WEG ordering: PCI config reads require a published IOPCIDevice. diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp new file mode 100644 index 00000000..8d13b9af --- /dev/null +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -0,0 +1,113 @@ +// Optional, explicitly armed diagnostics for the qualified Big Sur kernel. +// No timeout, panic, power-policy or shutdown-return-value changes. +#include "kern_shutdown_observer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace ShutdownObserver { +static const char *traceKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-trace"; +static const char *armKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-arm"; +struct Event { uint32_t code, detail; uint64_t time, pc; char label[24]; }; +struct Journal { uint32_t magic, version; uint64_t run; uint32_t count, dropped, failed, reserved; Event events[32]; }; +static Journal journal {}; +static IODTNVRAM *nvram; +static OSData *snapshots[2]; +static const OSSymbol *traceSymbol; +static IOLock *lock; +static bool armed; +static uint64_t ready[8] {}; +static void (*syncNvram)(IODTNVRAM *,bool); +static int (*orgReboot)(int,char *); +static void (*orgNotify)(int); +static int (*orgSync)(proc_t,void *,int *); +static void (*orgHaltLog)(const char *,const void *,uint64_t); +static int (*orgPEHalt)(unsigned int,uint32_t); +static void (*orgPlatform)(void *,unsigned int); +static void publish() { IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownObserver",ready,sizeof(ready)); } +// All call sites precede the final platform power-off. Refuse an interrupt-off +// context; never take a blocking diagnostic lock or allocate journal storage here. +static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,const void *pc=nullptr) { + if(!armed || !ml_get_interrupts_enabled())return; + if(!IOLockTryLock(lock)){OSIncrementAtomic(reinterpret_cast(&journal.dropped));return;} + if(journal.count>=128){IOLockUnlock(lock);return;} + auto &e=journal.events[journal.count%32];bzero(&e,sizeof(e)); + e.code=code;e.detail=detail;e.time=mach_absolute_time();e.pc=reinterpret_cast(pc); + if(label)strlcpy(e.label,label,sizeof(e.label)); + ++journal.count; + auto data=snapshots[journal.count%2]; + memcpy(const_cast(data->getBytesNoCopy()),&journal,sizeof(journal)); + const bool written=nvram->setProperty(traceSymbol,data); + if(written)syncNvram(nvram,false); // qualified syncInternal(false): no 15-minute throttle + auto read=OSDynamicCast(OSData,nvram->getProperty(traceSymbol)); + const bool same=read && read->getLength()==sizeof(journal) && !memcmp(read->getBytesNoCopy(),&journal,sizeof(journal)); + if(!written || !same)++journal.failed; + ready[3]=journal.count;ready[4]=journal.failed;ready[5]=journal.dropped; + IOLockUnlock(lock); +} +static void tryArm() { + if(armed || ready[2]!=63)return; + auto d=OSDynamicCast(OSData,nvram->getProperty(armKey)); + if(!d || d->getLength()!=sizeof(uint64_t) || memcmp(d->getBytesNoCopy(),&journal.run,sizeof(uint64_t)))return; + armed=true;record(1,0,"probe"); +} +static int wrapSync(proc_t p,void *a,int *r) { bool before=armed;tryArm();record(30);int v=orgSync(p,a,r);record(31,v);if(armed && !before)publish();return v; } +static int wrapReboot(int howto,char *message) { record(10,howto);return orgReboot(howto,message); } +static void wrapNotify(int stage) { record(20,stage);orgNotify(stage);record(21,stage); } +static void wrapPlatform(void *self,unsigned int type) { record(60,type);orgPlatform(self,type);record(61,type); } +static int wrapPEHalt(unsigned int type,uint32_t details) { if(type<=2)record(50,type);int v=orgPEHalt(type,details);if(type<=2)record(51,type);return v; } +static void wrapHaltLog(const char *what,const void *pc,uint64_t time) { + orgHaltLog(what,pc,time); + if(!what)return; + // Per-driver successful callbacks are numerous; retain slow/outstanding ones. + if(!strcmp(what,"PowerOff/Restart handler completed"))return; + record(40,0,what,pc); +} +void install(KernelPatcher &p) { + if(!checkKernelArgument("-wex-shutdown-observe"))return; + ready[0]=1; + const auto base=p.solveSymbol(KernelPatcher::KernelID,"__mh_execute_header"); + if(!base){p.clearError();publish();return;} + auto h=reinterpret_cast(base); + const uint8_t expected[]={0x51,0x9a,0x08,0x98,0xed,0x4b,0x3e,0x72,0x8f,0x00,0x1b,0x49,0xf7,0x15,0xa4,0x74}; + bool match=false; + if(h->magic!=MH_MAGIC_64 || h->sizeofcmds>65536 || h->ncmds>128){publish();return;} + auto at=reinterpret_cast(h+1);auto end=at+h->sizeofcmds; + for(uint32_t i=0;incmds && at+sizeof(load_command)<=end;i++) { + auto c=reinterpret_cast(at); + if(c->cmdsizecmdsize>end)break; + if(c->cmd==LC_UUID && c->cmdsize==sizeof(uuid_command))match=!memcmp(reinterpret_cast(c)->uuid,expected,16); + at+=c->cmdsize; + } + if(!match){publish();return;} + const auto sync=p.solveSymbol(KernelPatcher::KernelID,"__ZN9IODTNVRAM12syncInternalEb"); + // Match the exact function entry as well as the kernel UUID. + const uint8_t syncBytes[]={0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x83,0xbf,0x88,0,0,0,0}; + if(!sync || memcmp(reinterpret_cast(sync),syncBytes,sizeof(syncBytes))){p.clearError();publish();return;} + syncNvram=reinterpret_cast(sync); + nvram=OSDynamicCast(IODTNVRAM,IORegistryEntry::fromPath("/options",gIODTPlane)); + lock=IOLockAlloc();traceSymbol=OSSymbol::withCString(traceKey); + journal.magic=0x57585344;journal.version=1;journal.run=mach_absolute_time()^base;ready[1]=journal.run; + snapshots[0]=OSData::withBytes(&journal,sizeof(journal));snapshots[1]=OSData::withBytes(&journal,sizeof(journal)); + if(!nvram || !lock || !traceSymbol || !snapshots[0] || !snapshots[1]){publish();return;} + KernelPatcher::RouteRequest routes[]={ + {"_reboot_kernel",wrapReboot,orgReboot}, + {"_IOSystemShutdownNotification",wrapNotify,orgNotify}, + {"_sync",wrapSync,orgSync}, + {"_halt_log_enter",wrapHaltLog,orgHaltLog}, + {"_PEHaltRestartInternal",wrapPEHalt,orgPEHalt}, + {"__ZN14IOPMrootDomain25handlePlatformHaltRestartEj",wrapPlatform,orgPlatform} + }; + // Resolve all targets before any routing. A partial install stays unarmed. + for(auto &r:routes)if(!p.solveSymbol(KernelPatcher::KernelID,r.symbol)){p.clearError();publish();return;} + if(p.routeMultiple(KernelPatcher::KernelID,routes,arrsize(routes)))ready[2]=63; + else p.clearError(); + publish(); +} +} diff --git a/WhateverXe/kern_shutdown_observer.hpp b/WhateverXe/kern_shutdown_observer.hpp new file mode 100644 index 00000000..9e4b1207 --- /dev/null +++ b/WhateverXe/kern_shutdown_observer.hpp @@ -0,0 +1,3 @@ +#pragma once +#include +namespace ShutdownObserver { void install(KernelPatcher &patcher); } From edd4d06fc7069cc418adc827a5930c474cc4e039 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:27:28 +0900 Subject: [PATCH 114/139] Resolve shutdown NVRAM endpoint at explicit arm time --- WhateverXe/kern_shutdown_observer.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp index 8d13b9af..4979be4f 100644 --- a/WhateverXe/kern_shutdown_observer.cpp +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -53,6 +53,8 @@ static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,con } static void tryArm() { if(armed || ready[2]!=63)return; + if(!nvram)nvram=OSDynamicCast(IODTNVRAM,IORegistryEntry::fromPath("IODeviceTree:/options")); + if(!nvram){ready[6]=2;publish();return;} auto d=OSDynamicCast(OSData,nvram->getProperty(armKey)); if(!d || d->getLength()!=sizeof(uint64_t) || memcmp(d->getBytesNoCopy(),&journal.run,sizeof(uint64_t)))return; armed=true;record(1,0,"probe"); @@ -91,11 +93,11 @@ void install(KernelPatcher &p) { const uint8_t syncBytes[]={0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x83,0xbf,0x88,0,0,0,0}; if(!sync || memcmp(reinterpret_cast(sync),syncBytes,sizeof(syncBytes))){p.clearError();publish();return;} syncNvram=reinterpret_cast(sync); - nvram=OSDynamicCast(IODTNVRAM,IORegistryEntry::fromPath("/options",gIODTPlane)); + // NVRAM may not be published when Lilu routes the kernel; resolve it on arm. lock=IOLockAlloc();traceSymbol=OSSymbol::withCString(traceKey); journal.magic=0x57585344;journal.version=1;journal.run=mach_absolute_time()^base;ready[1]=journal.run; snapshots[0]=OSData::withBytes(&journal,sizeof(journal));snapshots[1]=OSData::withBytes(&journal,sizeof(journal)); - if(!nvram || !lock || !traceSymbol || !snapshots[0] || !snapshots[1]){publish();return;} + if(!lock || !traceSymbol || !snapshots[0] || !snapshots[1]){ready[6]=1;publish();return;} KernelPatcher::RouteRequest routes[]={ {"_reboot_kernel",wrapReboot,orgReboot}, {"_IOSystemShutdownNotification",wrapNotify,orgNotify}, @@ -105,9 +107,9 @@ void install(KernelPatcher &p) { {"__ZN14IOPMrootDomain25handlePlatformHaltRestartEj",wrapPlatform,orgPlatform} }; // Resolve all targets before any routing. A partial install stays unarmed. - for(auto &r:routes)if(!p.solveSymbol(KernelPatcher::KernelID,r.symbol)){p.clearError();publish();return;} + for(auto &r:routes)if(!p.solveSymbol(KernelPatcher::KernelID,r.symbol)){ready[6]=3;ready[7]=static_cast(p.getError());p.clearError();publish();return;} if(p.routeMultiple(KernelPatcher::KernelID,routes,arrsize(routes)))ready[2]=63; - else p.clearError(); + else {ready[6]=4;ready[7]=static_cast(p.getError());p.clearError();} publish(); } } From 837607ff98af145056488999112cc91e099ee629 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:41:34 +0900 Subject: [PATCH 115/139] Preserve RIP-relative sync entry and require explicit sysctl arming --- WhateverXe/kern_shutdown_observer.cpp | 57 ++++++++++++++++++++------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp index 4979be4f..8c5c8d45 100644 --- a/WhateverXe/kern_shutdown_observer.cpp +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -10,10 +10,11 @@ #include #include #include +#include +#include #include namespace ShutdownObserver { static const char *traceKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-trace"; -static const char *armKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-arm"; struct Event { uint32_t code, detail; uint64_t time, pc; char label[24]; }; struct Journal { uint32_t magic, version; uint64_t run; uint32_t count, dropped, failed, reserved; Event events[32]; }; static Journal journal {}; @@ -30,6 +31,14 @@ static int (*orgSync)(proc_t,void *,int *); static void (*orgHaltLog)(const char *,const void *,uint64_t); static int (*orgPEHalt)(unsigned int,uint32_t); static void (*orgPlatform)(void *,unsigned int); +// _sync starts with a RIP-relative LEA. Lilu copies prologues verbatim, +// so replay that exact prefix with its original callback address explicitly. +extern "C" { +uintptr_t wexShutdownSyncCallback=0, wexShutdownSyncContinue=0; +} +__attribute__((naked,noinline)) static int syncBridge(proc_t,void *,int *) { + __asm__("pushq %rbp\n\tmovq %rsp, %rbp\n\tmovq _wexShutdownSyncCallback(%rip), %rsi\n\tjmpq *_wexShutdownSyncContinue(%rip)"); +} static void publish() { IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownObserver",ready,sizeof(ready)); } // All call sites precede the final platform power-off. Refuse an interrupt-off // context; never take a blocking diagnostic lock or allocate journal storage here. @@ -51,19 +60,27 @@ static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,con ready[3]=journal.count;ready[4]=journal.failed;ready[5]=journal.dropped; IOLockUnlock(lock); } -static void tryArm() { - if(armed || ready[2]!=63)return; - if(!nvram)nvram=OSDynamicCast(IODTNVRAM,IORegistryEntry::fromPath("IODeviceTree:/options")); - if(!nvram){ready[6]=2;publish();return;} - auto d=OSDynamicCast(OSData,nvram->getProperty(armKey)); - if(!d || d->getLength()!=sizeof(uint64_t) || memcmp(d->getBytesNoCopy(),&journal.run,sizeof(uint64_t)))return; - armed=true;record(1,0,"probe"); +// No registry access from normal sync calls. Only this root-only control +// operation resolves NVRAM and arms the observer after userspace is online. +static int armControl SYSCTL_HANDLER_ARGS { + uint64_t token=0; + int error=sysctl_handle_quad(oidp,&token,0,req); + if(error || !req->newptr)return error; + if(ready[2]!=63 || token!=journal.run)return EINVAL; + if(armed)return 0; + auto entry=IORegistryEntry::fromPath("IODeviceTree:/options"); + nvram=OSDynamicCast(IODTNVRAM,entry); + if(!nvram){if(entry)entry->release();ready[6]=2;publish();return ENXIO;} + armed=true;record(1,0,"probe");publish(); + return journal.failed ? EIO : 0; } -static int wrapSync(proc_t p,void *a,int *r) { bool before=armed;tryArm();record(30);int v=orgSync(p,a,r);record(31,v);if(armed && !before)publish();return v; } +SYSCTL_PROC(_debug, OID_AUTO, wex_shutdown_arm, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED, + nullptr, 0, armControl, "Q", "Arm bounded shutdown diagnostics for this boot"); +static int wrapSync(proc_t p,void *a,int *r) { record(30);int v=orgSync(p,a,r);record(31,static_cast(v));return v; } static int wrapReboot(int howto,char *message) { record(10,howto);return orgReboot(howto,message); } static void wrapNotify(int stage) { record(20,stage);orgNotify(stage);record(21,stage); } static void wrapPlatform(void *self,unsigned int type) { record(60,type);orgPlatform(self,type);record(61,type); } -static int wrapPEHalt(unsigned int type,uint32_t details) { if(type<=2)record(50,type);int v=orgPEHalt(type,details);if(type<=2)record(51,type);return v; } +static int wrapPEHalt(unsigned int type,uint32_t details) { if(type==0 || type==1 || type==3)record(50,type);int v=orgPEHalt(type,details);if(type==0 || type==1 || type==3)record(51,type);return v; } static void wrapHaltLog(const char *what,const void *pc,uint64_t time) { orgHaltLog(what,pc,time); if(!what)return; @@ -101,14 +118,26 @@ void install(KernelPatcher &p) { KernelPatcher::RouteRequest routes[]={ {"_reboot_kernel",wrapReboot,orgReboot}, {"_IOSystemShutdownNotification",wrapNotify,orgNotify}, - {"_sync",wrapSync,orgSync}, + {"_sync",wrapSync}, {"_halt_log_enter",wrapHaltLog,orgHaltLog}, {"_PEHaltRestartInternal",wrapPEHalt,orgPEHalt}, {"__ZN14IOPMrootDomain25handlePlatformHaltRestartEj",wrapPlatform,orgPlatform} }; - // Resolve all targets before any routing. A partial install stays unarmed. - for(auto &r:routes)if(!p.solveSymbol(KernelPatcher::KernelID,r.symbol)){ready[6]=3;ready[7]=static_cast(p.getError());p.clearError();publish();return;} - if(p.routeMultiple(KernelPatcher::KernelID,routes,arrsize(routes)))ready[2]=63; + // Every automatic trampoline copies exactly six position-independent + // bytes. Reject changed entries or out-of-range rel32 jumps before patching. + const uint8_t simple[]={0x55,0x48,0x89,0xe5,0x41,0x57}; + const uint8_t syncEntry[]={0x55,0x48,0x89,0xe5,0x48,0x8d,0x35,0x25,0,0,0,0xbf,1,0,0,0}; + for(auto &r:routes) { + auto address=p.solveSymbol(KernelPatcher::KernelID,r.symbol); + if(!address){ready[6]=3;p.clearError();publish();return;} + const auto delta=static_cast(r.to-(address+5)); + if(delta!=static_cast(delta)){ready[6]=5;publish();return;} + if(!strcmp(r.symbol,"_sync")) { + if(memcmp(reinterpret_cast(address),syncEntry,sizeof(syncEntry))){ready[6]=6;publish();return;} + wexShutdownSyncCallback=address+0x30;wexShutdownSyncContinue=address+11;orgSync=syncBridge; + } else if(memcmp(reinterpret_cast(address),simple,sizeof(simple))){ready[6]=7;publish();return;} + } + if(p.routeMultipleShort(KernelPatcher::KernelID,routes,arrsize(routes))){ready[2]=63;sysctl_register_oid(&sysctl__debug_wex_shutdown_arm);} else {ready[6]=4;ready[7]=static_cast(p.getError());p.clearError();} publish(); } From 73a31261a6fc4d1030d109f80be49719b63cf216 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 31 Oct 2026 02:49:46 +0900 Subject: [PATCH 116/139] refactor: organize DSI attachment code without changing dispatch --- WhateverXe/kern_dsi_adapter_backing.inc | 584 +++++ WhateverXe/kern_dsi_adapter_boot.inc | 306 +++ WhateverXe/kern_dsi_adapter_diagnostics.inc | 212 ++ WhateverXe/kern_dsi_adapter_lifecycle.inc | 876 +++++++ WhateverXe/kern_dsi_adapter_modes.inc | 552 ++++ WhateverXe/kern_dsi_adapter_state.inc | 91 + WhateverXe/kern_xe.cpp | 2595 +------------------ tests/adlp_fb_candidate.py | 5 +- tests/adls_clock_runtime.py | 5 + tests/boot_arguments.py | 3 +- tests/display13_dbuf_setup.py | 3 +- tests/display13_offline.py | 3 +- tests/display_backend_dispatch.py | 2 +- tests/dsi_aperture_adapter.py | 3 +- tests/dsi_apple_adapter.py | 3 +- tests/dsi_backing_adapter.py | 3 +- tests/dsi_backlight_adapter.py | 3 +- tests/dsi_boot_adapter.py | 9 +- tests/dsi_boot_adoption.py | 3 +- tests/dsi_boot_probe.py | 3 +- tests/dsi_command_adapter.py | 3 +- tests/dsi_irq_live.py | 3 +- tests/dsi_lifecycle_dispatch.py | 5 +- tests/dsi_native_cache.py | 11 +- tests/dsi_path_disposal.py | 3 +- tests/dsi_port_adapter.py | 3 +- tests/dsi_presentation.py | 3 +- tests/dsi_quiesce_adapter.py | 22 +- tests/dsi_resources.py | 14 +- tests/dsi_surface_adapter.py | 5 +- tests/dsi_update_dispatch.py | 7 +- tests/fixtures/boot_argument_names.json | 3 +- tests/host_publication.py | 2 + tests/production_source.py | 14 + tools/build.py | 2 +- 35 files changed, 2767 insertions(+), 2597 deletions(-) create mode 100644 WhateverXe/kern_dsi_adapter_backing.inc create mode 100644 WhateverXe/kern_dsi_adapter_boot.inc create mode 100644 WhateverXe/kern_dsi_adapter_diagnostics.inc create mode 100644 WhateverXe/kern_dsi_adapter_lifecycle.inc create mode 100644 WhateverXe/kern_dsi_adapter_modes.inc create mode 100644 WhateverXe/kern_dsi_adapter_state.inc create mode 100644 tests/production_source.py diff --git a/WhateverXe/kern_dsi_adapter_backing.inc b/WhateverXe/kern_dsi_adapter_backing.inc new file mode 100644 index 00000000..c4b30483 --- /dev/null +++ b/WhateverXe/kern_dsi_adapter_backing.inc @@ -0,0 +1,584 @@ +// Native scanout allocation, ownership, publication and surface handoff. +// Included only by kern_xe.cpp; retains its private owners and lock ordering. + +bool XE::processDSIBackingHooks(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={"__ZN18IGAccelDisplayPipe21allocateScanoutMemoryEP19IntelScaledModeDataj", + "__ZN18IGAccelDisplayPipe23deallocateScanoutMemoryEP19IntelScaledModeDataj"}; + const uintptr_t offsets[]={0x7E5B2,0x7E772}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x8B,0x46,0x38,0x85,0xC0,0x74,0x62,0x49,0x89}}; + bool known=size>0x7F80A; + for(unsigned i=0;i<2;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),entries[i],16); + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSIBackingAllocate,orgDSIBacking[0]}, + {symbols[1],wrapDSIBackingFree,orgDSIBacking[1]}}; + // Short routes copy only the six-byte push/mov/push prologue. The free + // entry has a relative branch at byte 12; never copy it into a trampoline. + const bool ready=known && patcher.routeMultipleShort(index,routes,2,address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingHooksReady",ready);return ready; +} + +uint32_t XE::wrapDSIBackingAllocate(OSObject *pipe,void *record,uint32_t flags) { + auto &o=dsiPendingBacking; + if(!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)) + return FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); + if(__atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || record!=o.record) { + if(pipe && pipe==__atomic_load_n(&o.pipe,__ATOMIC_ACQUIRE))return kIOReturnNotReady; + return FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); + } + if(!o.allocating || flags || o.pipe || !pipe || !pipe->metaCast("IGAccelDisplayPipe") || + pipe->getMetaClass()->getClassSize()<0x12B0 || Display13FBMemory::field(pipe,0x88)!=o.accelerator || + Display13FBMemory::field(pipe,0x1298) || Display13FBMemory::field(pipe,0x12A0) || + Display13FBMemory::field(pipe,0x12A8))return kIOReturnNotReady; + pipe->retain();__atomic_store_n(&o.pipe,pipe,__ATOMIC_RELEASE); + const auto result=FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); + o.nativeResult=result;const auto &r=*o.record; + o.allocated=!result && r.refs==1 && r.memory && r.map && + Display13FBMemory::field(pipe,0x1298)==r.memory && + Display13FBMemory::field(pipe,0x12A0)==r.map && !Display13FBMemory::field(pipe,0x12A8); + if(o.allocated) { + // Native init/destroyScanoutResource can replace these pipe fields. + // Pin the objects while still inside the native GPU lock, before + // returning to the PTE inspector. Pins alone do not preserve GGTT. + o.pinnedMap=reinterpret_cast(r.map);o.pinnedMemory=reinterpret_cast(r.memory); + o.pinnedMap->retain();o.pinnedMemory->retain(); + } + return o.allocated && callbackXE->validateDSINativeBacking() && + callbackXE->paintDSINativeBacking(0) && callbackXE->switchDSINativeSurface() && + callbackXE->publishDSINativeBacking() ? result : kIOReturnNotReady; +} + +uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { + auto &o=dsiPendingBacking; + if(!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)) + return FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); + if(__atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || record!=o.record) { + if(pipe && pipe==__atomic_load_n(&o.pipe,__ATOMIC_ACQUIRE))return kIOReturnNotReady; + return FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); + } + if(!o.freeing || flags || o.armed || !pipe || pipe!=o.pipe || + Display13FBMemory::field(pipe,0x88)!=o.accelerator)return kIOReturnNotReady; + const auto &r=*o.record; + if(r.refs>1 || Display13FBMemory::field(pipe,0x1298)!=r.memory || + Display13FBMemory::field(pipe,0x12A0)!=r.map || Display13FBMemory::field(pipe,0x12A8) || + (!r.refs && (r.map || r.memory || r.gpu || r.pitch)))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); + o.nativeResult=result;o.freed=!result && !r.refs && !r.map && !r.memory && !r.gpu && !r.pitch && + !Display13FBMemory::field(pipe,0x1298) && !Display13FBMemory::field(pipe,0x12A0); + return o.freed ? result : kIOReturnNotReady; +} + +bool XE::prepareDSINativeBacking(IOService *fb) { + dsiBackingStage=0; + if(adlnOutputWiring.dsi!=1 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || + !dsiModeTarget(fb) || !dsiBackingHooksReady || !dsiProfile.valid())return false; + dsiBackingStage=1; + DSIModeScope scope(fb);if(!scope.lock || !dsiNativeUpdateAllowed(fb))return false; + auto &o=dsiPendingBacking;auto &c=display13FBPower; + if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return false; + dsiBackingStage=2; + IOLockLock(c.lock); + auto controller=static_cast(c.controller); + auto owner=reinterpret_cast(display13FBExpectedOwner(controller)); + const auto reserve=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); + auto machine=owner && owner->getMetaClass()->getClassSize()>=0x1500 ? Display13FBMemory::field(owner,0x378) : nullptr; + XELP::DSI::NativeBacking::Record request; + const bool ready=adlnOutputWiring.dsi==1 && !c.dsiNativeWriters && owner && machine && + machine->metaCast("IGAccelDisplayMachine") && machine->getMetaClass()->getClassSize()>=0x130 && + Display13FBMemory::field(machine,0x128)==owner && adlnSharedBinding(controller,owner) && + reserve && reserve==Display13FBMemory::field(owner,0x13C0) && + adlnSysMemOwner(controller,Display13FBMemory::pool(controller)) && + Display13FBMemory::allocatedSlot(controller,fb)==0 && + XELP::DSI::NativeBacking::request(dsiProfile,Display13FBMemory::field(owner,0x1470),request) && + display13FBInspectMemory(fb,true,0,false).status==Display13FBMemory::Status::Complete; + if(ready){fb->retain();owner->retain();} + IOLockUnlock(c.lock);if(!ready)return false; + // Prepare the real native dimensions before handing this allocation to + // its later public mode owner. No mode-success or backing is published. + if(!probeDSIModeGeometry(fb)) {owner->release();fb->release();return false;} + dsiBackingStage=3; + auto record=static_cast(IOMalloc(sizeof(request))); + if(!record){owner->release();fb->release();return false;} + memcpy(record,&request,sizeof(request));o.accelerator=owner;o.record=record; + __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.allocating=true; + __atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); + dsiBackingStage=4; + uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; + // Native callPlatformFunction takes the GPU lock. Never hold c.lock here. + const auto result=owner->callPlatformFunction("allocscanmem",false,record,&index,&flags,nullptr); + o.allocating=false; + dsiBackingStage=5; + if(result || !o.allocated)return false; // Retain partial state for discard. + IOLockLock(c.lock); + const bool verified=c.controller==controller && adlnSharedBinding(controller,owner) && + o.pipe && o.pinnedMap && o.pinnedMemory && + Display13FBMemory::field(o.pipe,0x88)==owner && + Display13FBMemory::field(o.pipe,0x1298)==record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==record->map && + !Display13FBMemory::field(o.pipe,0x12A8) && + reserve==__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE) && + reserve==Display13FBMemory::field(owner,0x13C0) && + !c.memoryFaulted && !c.clockFaulted && + XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); + IOLockUnlock(c.lock);if(verified)dsiBackingStage=6;return verified; +} + +// The first P3 step uses the donor's CPU geometry helper. The selected Y +// layout is the same native tile-mode-3 allocation validated by the caller. +// Retain a bounded receipt of the remaining native transaction prerequisites. +bool XE::probeDSIModeGeometry(IOService *fb) { + if(!dsiComputeGeometry || !dsiModeTarget(fb) || !dsiModeCoherent(fb))return false; + auto path=Display13FBMemory::singlePath(fb); + auto &c=display13FBPower;IOLockLock(c.lock); + auto controller=static_cast(c.controller); + uint64_t receipt[16] {1,0,Display13FBMemory::field(fb,0x4A18), + Display13FBMemory::field(fb,0x4350),Display13FBMemory::field(fb,0x420C)}; + const bool bound=path && path->getMetaClass()->getClassSize()>=0x3730 && controller && + Display13FBMemory::field(fb,0x1D0)==controller && + Display13FBMemory::field(path,0x32B0)==fb && !c.dsiNativeWriters && + !c.memoryFaulted && !c.clockFaulted && !dsiStopAttempted; + uint8_t savedFB[0x30],savedPath[0x20]; + memcpy(savedFB,reinterpret_cast(fb)+0x430C,sizeof(savedFB)); + if(bound)memcpy(savedPath,reinterpret_cast(path)+0xF0,sizeof(savedPath)); + const bool prepared=bound && XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,dsiFixedMode.timing, + reinterpret_cast(dsiComputeGeometry)); + receipt[1]=prepared;receipt[5]=Display13FBMemory::field(fb,0x430C); + receipt[6]=Display13FBMemory::field(fb,0x4310);receipt[7]=Display13FBMemory::field(fb,0x4314); + receipt[8]=Display13FBMemory::field(fb,0x4334);receipt[9]=Display13FBMemory::field(fb,0x4338); + if(controller) { + receipt[10]=Display13FBMemory::field(controller,0xC10)!=nullptr; + receipt[11]=Display13FBMemory::field(controller,0x1BB4); + auto panel=Display13FBMemory::field(controller,0xD0); + receipt[12]=panel!=nullptr; + if(panel && panel->getMetaClass()->getClassSize()>=0x70) { + receipt[13]=Display13FBMemory::field(panel,0x68); + receipt[14]=Display13FBMemory::field(panel,0x50)==fb; + } + receipt[15]=Display13FBMemory::field(controller,0xE53); + } + // Until FB+4348 publishes the native backing, leave public aperture + // dimensions untouched. Otherwise IOGraphics could map the old GOP while + // the identification plane reads the new tiled allocation. + if(bound) { + memcpy(reinterpret_cast(fb)+0x430C,savedFB,sizeof(savedFB)); + memcpy(reinterpret_cast(path)+0xF0,savedPath,sizeof(savedPath)); + const uint32_t bpp=static_cast(receipt[3]); + memcpy(reinterpret_cast(fb)+0x4350,&bpp,sizeof(bpp)); + *(reinterpret_cast(fb)+0x4A18)=static_cast(receipt[2]); + } + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIGeometry",receipt,sizeof(receipt)); + return prepared; +} + +// Called by allocateScanoutMemory's routed entry while the native display +// machine still holds the GPU lock. Keep object/PTE validation in that scope; +// retained map/memory references alone do not serialize GGTT replacement. +bool XE::validateDSINativeBacking() { + auto &o=dsiPendingBacking;auto &c=display13FBPower; + auto fb=o.framebuffer;auto owner=o.accelerator;auto record=o.record; + if(!fb || !owner || !record || !o.allocating || !o.allocated || !o.pinnedMap || !o.pinnedMemory || + o.thread!=reinterpret_cast(current_thread()) || !c.lock)return false; + auto controller=static_cast(c.controller); + const auto reserve=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); + IOLockLock(c.lock); + bool verified=false; + const auto pipeBound=[&]() { + return o.pipe && Display13FBMemory::field(o.pipe,0x88)==owner && + Display13FBMemory::field(o.pipe,0x1298)==record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==record->map && + !Display13FBMemory::field(o.pipe,0x12A8); + }; + if(pipeBound() && c.controller==controller && c.map && c.map->getVirtualAddress() && c.map->getLength()==0x1000000 && + adlnSharedBinding(controller,owner) && !c.memoryFaulted && !c.clockFaulted && + reserve && reserve==Display13FBMemory::field(owner,0x13C0)) { + Display13SurfaceIO io {c.map,0}; + o.proof=Display13FBMemory::inspectPendingSurface(io,controller,controller->getMetaClass()->getClassSize(), + fb,fb->getMetaClass()->getClassSize(),reinterpret_cast(owner),reserve, + (c.map->getLength()-0x800000)/8*4096,xelpMemory,record); + verified=pipeBound() && XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); + } + IOLockUnlock(c.lock);return verified; +} + +// Mapping and writes remain inside the same native GPU lock as the allocator +// and full PTE proof. The GPU map is IGAccelMemoryMap, not IOMemoryMap: create +// a separate CPU mapping from its validated SysMemory descriptor. +bool XE::paintDSINativeBacking(unsigned phase) { + auto &o=dsiPendingBacking; + o.image={}; + if(!o.allocating || !o.allocated || (o.armed && dsiSession.state!=XELP::DSI::SessionState::Streaming) || !o.record || !o.pinnedMemory || + o.thread!=reinterpret_cast(current_thread()) || + !XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof))return false; + auto descriptor=OSDynamicCast(IOMemoryDescriptor,Display13FBMemory::field(o.pinnedMemory,0xD0)); + if(!descriptor || descriptor->getLength()(o.proof.pitch),o.proof.bytes); + if(!shape.valid())return false; + auto map=descriptor->createMappingInTask(kernel_task,0,kIOMapAnywhere,0,o.proof.bytes); + if(!map)return false; + const auto address=map->getVirtualAddress(); + if(address && map->getLength()==o.proof.bytes && !(address&4095)) { + o.image=XELP::DSI::TestImage::paint(reinterpret_cast(address),map->getLength(),shape,phase); + if(o.image.complete) { + for(uint64_t offset=0;offsetrelease(); + uint64_t receipt[] {1,phase,o.image.complete,o.image.bytes,o.image.digest,o.proof.gpu}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIImage",receipt,sizeof(receipt)); + return o.image.complete; +} + +// The allocator's native GPU lock and FB mutex span this transaction. The +// owner lock protects shared clock/DDB/IRQ association; no IRQ spinlock spans +// the bounded frame wait. Keep backing private in P2: normal native flips and +// mode-success publication belong to the desktop adapter. +bool XE::switchDSINativeSurface() { + using namespace XELP::DSI; + auto &o=dsiPendingBacking;auto &c=display13FBPower;auto &irq=display13FBIRQ; + auto fb=o.framebuffer; + if(!fb || !o.image.complete || !o.allocating || !o.allocated || o.armed || !o.record || !c.lock || + o.thread!=reinterpret_cast(current_thread()) || dsiSession.state!=SessionState::Unbound)return false; + uint64_t receipt[24] {1,1}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); + auto path=Display13FBMemory::singlePath(fb); + IOLockLock(c.lock); + const auto owned=[&]() { + return dsiResourcesReady && dsiNativeWriterHooksReady && dsiCacheHooksReady && dsiBackingHooksReady && + !dsiStopAttempted && !c.dsiNativeWriters && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && !c.dbuf.updating && + !c.pipe[0].destroyingPath && !c.pipe[0].wmProgramming && !c.pipe[0].wmCommitting && + c.pipe[0].memoryFramebuffer==fb && c.transcoders.dsi0Pipe==0 && path && + path->getMetaClass()->getClassSize()>=0x3730 && + Display13FBMemory::field(path,0x32B0)==fb && + Display13FBMemory::field(path,0x3640)==0 && + Display13FBMemory::field(path,0x3644)==2 && + Display13FBMemory::field(path,0x3648)==c.dsiPort && + c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && + adlnSharedBinding(c.controller,o.accelerator) && adlnWriterMappingBound() && + o.pipe && Display13FBMemory::field(o.pipe,0x1298)==o.record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==o.record->map && + XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof); + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns; + IO(IOMemoryMap *m,decltype(owned) &f):Display13FBBootIO{m},owns(f){} + bool ready(){return owns();} + uint64_t nativeClockHz(){Display13FBClockIO clock{map};return Display13Clock::read(clock).nativeHz();} + uint64_t nowUs(){uint64_t t=0,ns=0;clock_get_uptime(&t);absolutetime_to_nanoseconds(t,&ns);return ns/1000;} + void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} + void write(uint32_t reg,uint32_t value) { + const bool wm=(reg>=0x70240 && reg<=0x7025C && !(reg&3)) || reg==0x70268; + bool plane=false;for(auto r:Surface::Registers)plane|=r==reg; + if(readable(reg) && (wm || plane || reg==0x45270)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + } io{c.map,owned}; + bool irqReady=false; + if(owned() && irq.lock && adlnIRQOwner(static_cast(c.controller))) { + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + irqReady=!irq.quiescing && !irq.state.faulted && irq.state.enabled && (irq.admittedPipes&1) && + irq.state.transcoders.dsi0Pipe==0; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + receipt[2]=irqReady; + Surface::Result result; + if(irqReady && dsiBoot.complete() && c.bootDSI.sameSource(dsiBoot.source) && + c.bridge.outputHeld(io,0,0) && c.latency.status==Display13WM::Status::Complete) { + const auto ddb=Display13DBUF::hardwareRaw(c.dbuf,0,c.dbuf.pipe[0].primary); + const auto cursorDDB=Display13DBUF::hardwareRaw(c.dbuf,0,c.dbuf.pipe[0].cursor); + const auto range=Display13DBUF::decode(ddb);const auto ipc=io.read(0x45004); + auto wm=Display13WM::plan(dsiProfile.mode.width,dsiProfile.mode.htotal(),uint64_t(dsiProfile.mode.clockKHz)*1000, + Display13DBUF::valid(range)?range.end-range.start:0,c.latency, + (ipc&8) || Display13FBMemory::field(c.controller,0x1B4C),true,dsiProfile.mode.height); + const auto hz=io.nativeClockHz();const auto pixel=uint64_t(dsiProfile.mode.clockKHz)*1000; + const Display13WM::Snapshot snapshot{&wm,nullptr,ddb,cursorDDB,static_cast(o.proof.gpu),0,ipc,hz,false,false, + Display13WM::pipeLineTime(dsiProfile.mode.htotal(),pixel),pixel,0,c.transcoders}; + Surface::Plane current{dsiBoot.control,io.read(0x701CC),dsiBoot.stride,dsiBoot.size,dsiBoot.surface,0,0}; + auto next=current;next.control=Display13WM::arbitrationControl((current.control&~(7U<<10))|(4U<<10)); + next.color|=1U<<13;next.stride=static_cast(o.proof.pitch/128);next.surface=static_cast(o.proof.gpu); + Surface::Backing backing{reinterpret_cast(o.record),o.proof.gpu,o.proof.bytes,o.proof.pitch}; + Capabilities caps;caps.installed=Capabilities::Adoption; + if(ddb!=UINT32_MAX && cursorDDB!=UINT32_MAX && Display13DBUF::valid(range) && + dsiSession.bind(caps,c.bootDSI,dsiProfile,1,reinterpret_cast(path),reinterpret_cast(&dsiBoot))) { + result=Surface::change(io,dsiSession,c.bootDSI,dsiProfile,current,next,backing,snapshot); + o.armed=result.writes!=0 || dsiSession.state==SessionState::Switching || dsiSession.state==SessionState::Faulted || + dsiSession.state==SessionState::Streaming; + if(!o.armed)dsiSession={}; + } + receipt[3]=ddb;receipt[4]=cursorDDB;receipt[5]=hz;receipt[6]=wm.allocation; + receipt[7]=next.control;receipt[8]=next.stride;receipt[9]=next.surface;receipt[10]=o.image.digest; + } + receipt[11]=static_cast(result.status);receipt[12]=result.writes; + receipt[13]=static_cast(result.wm.status);receipt[14]=static_cast(result.latch.status); + receipt[15]=static_cast(dsiSession.state);receipt[16]=o.armed; + const bool switched=result.status==Surface::Status::Complete; + IOLockUnlock(c.lock); + // A second pattern changes the contents of the same owned live surface. + // Keep native GPU serialization, flush it, then prove scanout advances. + bool updated=false; + if(switched && paintDSINativeBacking(1)) { + IOLockLock(c.lock); + receipt[17]=o.image.digest;receipt[18]=io.read(0x70040); + for(unsigned us=0;us<=100000;us+=1000) { + receipt[19]=io.read(0x70040); + const uint32_t frames=static_cast(receipt[19]-receipt[18]); + if(frames && frames<0x80000000U) { + updated=owned() && Surface::source(io,c.bootDSI) && + Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu));break; + } + if(!owned() || receipt[19]==UINT32_MAX)break; + io.delayUs(1000); + } + IOLockUnlock(c.lock); + } + if(switched && !updated)dsiSession.abort(dsiSession.ticket(),SessionError::Switch); + receipt[1]=2;receipt[20]=updated;receipt[21]=static_cast(dsiSession.state); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); + return switched && updated; +} + +// Still inside allocateScanoutMemory's GPU lock and the FB mutex. Transfer +// the already latched allocation to the native FB without reallocating it. +// Returning stolen slot zero changes bookkeeping only; its GGTT reservation +// and the firmware retired-backing record remain held through Session stop. +bool XE::publishDSINativeBacking() { + auto &o=dsiPendingBacking;auto &c=display13FBPower;auto fb=o.framebuffer; + if(!fb || !o.allocating || o.thread!=reinterpret_cast(current_thread()) || + !o.armed || !o.allocated || o.published || dsiSession.state!=XELP::DSI::SessionState::Streaming || + !dsiComputeGeometry || !orgAlderLakeNScanoutFree)return false; + IOLockLock(c.lock); + auto controller=static_cast(c.controller);auto path=Display13FBMemory::singlePath(fb); + const auto pool=Display13FBMemory::pool(controller); + const auto count=Display13FBMemory::field(controller,0xDF5); + const auto used=Display13FBMemory::field(controller,0xDF4); + const auto mask=Display13FBMemory::field(controller,0xDF0); + uint64_t receipt[12] {1,1,count,used,mask}; + uint8_t savedFB[0x30],savedPath[0x20]; + memcpy(savedFB,reinterpret_cast(fb)+0x430C,sizeof(savedFB)); + if(!path) {IOLockUnlock(c.lock);return false;} + memcpy(savedPath,reinterpret_cast(path)+0xF0,sizeof(savedPath)); + const auto oldBpp=Display13FBMemory::field(fb,0x4350); + const auto oldTiled=Display13FBMemory::field(fb,0x4A18); + Display13FBBootIO io{c.map}; + bool ready=dsiResourcesReady && !dsiStopAttempted && !c.memoryFaulted && !c.clockFaulted && + !c.dsiNativeWriters && !c.bridge.state.faulted && dsiModeCoherent(fb) && + Display13FBMemory::allocatedSlot(controller,fb)==0 && pool.state==1 && !pool.descriptor && + adlnSharedBinding(controller,o.accelerator) && + XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof) && + XELP::DSI::Surface::source(io,c.bootDSI) && + XELP::DSI::Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu)) && + XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,dsiFixedMode.timing, + reinterpret_cast(dsiComputeGeometry)); + IOLockUnlock(c.lock); + if(ready) { + // FB+4348 is still null, so the exact donor takes only the slot-zero + // FreeSysMem/freeFBMemory branch; no GPU free is reachable here. + FunctionCast(wrapAlderLakeNScanoutFree,orgAlderLakeNScanoutFree)(fb); + IOLockLock(c.lock); + receipt[5]=Display13FBMemory::field(controller,0xDF4); + receipt[6]=Display13FBMemory::field(controller,0xDF0); + receipt[7]=Display13FBMemory::field(fb,0x4288); + ready=Display13FBMemory::slotReturned(count,0,used,mask,receipt[5],receipt[6],receipt[7]) && + Display13FBMemory::poolReturned(pool,Display13FBMemory::pool(controller),0) && + !Display13FBMemory::field(fb,0x4348) && !c.memoryFaulted && + Display13FBMemory::field(fb,0x4334)==o.proof.pitch; + if(ready) { + *reinterpret_cast(reinterpret_cast(fb)+0x4348)=o.record; + *reinterpret_cast(reinterpret_cast(fb)+0x4330)=static_cast(o.proof.gpu); + c.pipe[0].scanout=o.proof; + __atomic_store_n(&c.pipe[0].publishedScanout,reinterpret_cast(o.record),__ATOMIC_RELEASE); + o.published=true;receipt[1]=2; + } + IOLockUnlock(c.lock); + if(ready)ready=updateDSIBootCache(controller,fb,path); + } + if(!o.published) { + IOLockLock(c.lock); + memcpy(reinterpret_cast(fb)+0x430C,savedFB,sizeof(savedFB)); + memcpy(reinterpret_cast(path)+0xF0,savedPath,sizeof(savedPath)); + memcpy(reinterpret_cast(fb)+0x4350,&oldBpp,sizeof(oldBpp)); + *(reinterpret_cast(fb)+0x4A18)=oldTiled; + IOLockUnlock(c.lock); + } + receipt[8]=ready;receipt[9]=o.published;receipt[10]=o.proof.gpu;receipt[11]=o.proof.pitch; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicBacking",receipt,sizeof(receipt)); + return ready; +} + +// The FB mutex spans native mode completion. These donor CPU leaves preserve +// transaction/notification semantics; the ordinary DDI modesetter is bypassed +// because the DSI link and the new primary plane are already running. +bool XE::publishDSINativeMode(IOService *fb) { + auto &o=dsiPendingBacking;auto &c=display13FBPower; + if(!dsiPublishedTarget(fb) || !dsiModeCoherent(fb) || !dsiModeCPU[0] || !dsiModeCPU[1] || !dsiModeCPU[2])return false; + if(o.modeReady)return dsiNativeUpdateAllowed(fb); + uint64_t receipt[12] {1,1}; + IOLockLock(c.lock);auto controller=static_cast(c.controller); + auto path=Display13FBMemory::singlePath(fb); + const bool ready=!dsiStopAttempted && dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.dbuf.faulted && !c.watermarkFaulted && !c.dsiNativeWriters && dsiArmedBackingOwned(fb,path) && + dsiSession.state==XELP::DSI::SessionState::Streaming && + !Display13FBMemory::field(controller,0xD0) && + !Display13FBMemory::field(fb,0x4340) && Display13FBMemory::field(fb,0x4338)==1; + IOLockUnlock(c.lock); + receipt[2]=ready;receipt[3]=ready && updateDSIBootCache(controller,fb,path); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); + if(!receipt[2] || !receipt[3])return false; + uint32_t tile=2; + receipt[2]=o.accelerator->callPlatformFunction("EFITilingMode",true,&tile,nullptr,nullptr,nullptr); + if(!receipt[2]) { + *reinterpret_cast(reinterpret_cast(controller)+0xE53)=0; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=XELP::DSI::AppleAdapter::FixedModeID; + *reinterpret_cast(reinterpret_cast(fb)+0x4254)=0; + receipt[3]=reinterpret_cast(dsiModeCPU[0])(fb); + if(!receipt[3] && !Display13FBMemory::field(fb,0x4340)) { + reinterpret_cast(dsiModeCPU[1])(fb,0); + reinterpret_cast(dsiModeCPU[2])(fb); + IOLockLock(c.lock); + receipt[4]=Display13FBMemory::field(fb,0x4000); + receipt[5]=Display13FBMemory::field(fb,0x4008); + receipt[6]=Display13FBMemory::field(fb,0x400C); + receipt[7]=Display13FBMemory::field(fb,0x4200); + o.modeReady=!receipt[4] && receipt[5]==UINT32_MAX && receipt[6]==UINT32_MAX && receipt[7]==1 && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !dsiStopAttempted && + dsiArmedBackingOwned(fb,path) && dsiPublishedTarget(fb); + receipt[8]=o.modeReady;receipt[9]=Display13FBMemory::field(fb,0x402C); + receipt[10]=Display13FBMemory::field(fb,0x4334);receipt[11]=Display13FBMemory::field(fb,0x4314); + IOLockUnlock(c.lock); + } + } + if(o.modeReady) { + // setupClocks normally copies the detailed timing's Hz into this CPU + // path field before WM calculation. DSI retains its live PLL, so supply + // that fixed-mode cache without entering the native DDI clock setup. + memcpy(reinterpret_cast(path)+0x118,&dsiFixedMode.timing.pixelClock,sizeof(uint64_t)); + // Still under the native FB mutex: admit the existing DDB transaction, + // then require its latch before returning mode success to any client. + // GOP adoption may have no cursor reservation and a full primary DDB. + adlnSetupDBUFPipe(controller,fb,0,false); + IOLockLock(c.lock); + const auto &allocation=c.dbuf.pipe[0];const auto &pipe=c.pipe[0]; + o.modeReady=!c.dbuf.faulted && !c.watermarkFaulted && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !dsiStopAttempted && !pipe.wmPending && + allocation.primary.start==40 && allocation.primary.end==2048 && + allocation.cursor.start==0 && allocation.cursor.end==40 && + pipe.wmLatch.status==Display13WM::Status::Complete && dsiPublishedTarget(fb); + uint64_t layout[] {1,allocation.primary.start,allocation.primary.end,allocation.cursor.start,allocation.cursor.end, + pipe.wmPending,static_cast(pipe.wmLatch.status),static_cast(pipe.wmLatch.check), + c.watermarkFaulted,c.dbuf.faulted,pipe.watermarkClockHz,o.modeReady}; + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeDBUF",layout,sizeof(layout)); + } + receipt[8]=o.modeReady; + receipt[1]=2;IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); + return o.modeReady; +} + +// A stopped stream may retain a native backing after partial cleanup. Its +// ownership is distinct from the GOP pool, which stays reserved until later. +bool XE::dsiArmedBackingOwned(IOService *fb,OSObject *path) { + auto &o=dsiPendingBacking; + if(!dsiPendingTarget(fb) || !o.armed || o.allocating || o.freeing || !o.record || !o.allocated || + !o.pipe || !o.pinnedMap || !o.pinnedMemory || !path)return false; + const auto handle=reinterpret_cast(o.record),lease=reinterpret_cast(path); + const bool session=dsiSession.lease==lease && + (dsiSession.currentBacking==handle || dsiSession.pendingBacking==handle) && + (dsiSession.state==XELP::DSI::SessionState::Streaming || dsiSession.state==XELP::DSI::SessionState::Faulted); + const bool cleanup=dsiSession.state==XELP::DSI::SessionState::Released && dsiCleanup.lease==lease && + (dsiCleanup.currentBacking==handle || dsiCleanup.pendingBacking==handle); + return (session || cleanup) && dsiSession.identity.same(dsiBoot.source.identity) && + adlnSharedBinding(display13FBPower.controller,o.accelerator) && + Display13FBMemory::field(o.pipe,0x88)==o.accelerator && + Display13FBMemory::field(o.pipe,0x1298)==o.record->memory && + Display13FBMemory::field(o.pipe,0x12A0)==o.record->map; +} + +// Caller has completed host/IRQ/clock stop and dropped owner/clock locks, but +// keeps FB serialization and the provider gate across native GPU cleanup. +bool XE::retireDSINativeBacking(IOService *fb,OSObject *path) { + if(!dsiPendingTarget(fb))return true; + if(!dsiStop.complete() || !dsiIRQParked || !dsiClockStop.complete() || !dsiDDIReleased || + !dsiArmedBackingOwned(fb,path) || !releaseDSILogical(fb))return false; + if(dsiSession.state!=XELP::DSI::SessionState::Released) { + const XELP::DSI::StopProof proof{dsiSession.ticket(),dsiBoot.source.identity,true,true,true,true}; + if(!dsiSession.stopped(proof))return false; + dsiCleanup=dsiSession.takeCleanup(); + } + auto &o=dsiPendingBacking;auto &c=display13FBPower; + const bool published=o.published;auto record=o.record; + IOLockLock(c.lock); + if(published && !dsiPublishedTarget(fb)) {IOLockUnlock(c.lock);return false;} + o.modeReady=false; + if(published) { + *reinterpret_cast(reinterpret_cast(fb)+0x4348)=nullptr; + __atomic_store_n(&c.pipe[0].publishedScanout,uintptr_t(0),__ATOMIC_RELEASE); + c.pipe[0].scanout={};o.published=false; + } + IOLockUnlock(c.lock);o.armed=false; + if(discardDSINativeBacking(fb))return true; + IOLockLock(c.lock); + if(published) {*reinterpret_cast(reinterpret_cast(fb)+0x4348)=record;o.published=true;} + o.armed=true;IOLockUnlock(c.lock);return false; +} + +// The first real fixed-mode request establishes an identification surface +// while the native FB mutex is held. Retain any armed backing until stop. +// Backing publication shares the native allocation serializer. Do this once after allocation starts; +// an unavailable accelerator may be retried by a later mode request. Never +// report a WindowServer mode before its subsequent CPU completion succeeds. +void XE::probeDSINativeBacking(IOService *fb) { + if(dsiBackingAttempted)return; + dsiBackingAttempted=true; + uint64_t receipt[16] {1,1}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); + const bool verified=prepareDSINativeBacking(fb); + auto &o=dsiPendingBacking; + const bool pending=dsiPendingTarget(fb); + receipt[1]=2;receipt[2]=dsiBackingStage;receipt[3]=verified;receipt[4]=pending; + if(pending) { + receipt[5]=o.allocated;receipt[6]=o.nativeResult;receipt[7]=static_cast(o.proof.status); + receipt[8]=o.proof.bytes;receipt[9]=o.proof.pitch;receipt[10]=o.proof.gpu;receipt[11]=o.proof.verifiedPages; + receipt[12]=o.pinnedMap && o.pinnedMemory; + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); + if(pending && !o.armed)receipt[13]=discardDSINativeBacking(fb); + receipt[14]=dsiPendingTarget(fb);receipt[15]=pending && !o.armed && !receipt[13];receipt[1]=3; + if(!pending)dsiBackingAttempted=false; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); +} + +// Only a never-armed pending surface may take this path. After SURF is armed, +// retain it through Session's stop proof; freeing an unlatched target is unsafe. +bool XE::discardDSINativeBacking(IOService *fb) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; + IOService *owner=nullptr;OSObject *pipe=nullptr,*mapping=nullptr,*memory=nullptr;void *record=nullptr; + { + DSIModeScope scope(fb);if(!scope.lock)return false; + auto &o=dsiPendingBacking; + if(!dsiPendingTarget(fb) || o.armed || o.allocating || o.freeing || !o.record)return false; + if(!adlnSharedBinding(display13FBPower.controller,o.accelerator))return false; + if(o.pipe) { + __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.freeing=true;o.freed=false; + uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; + o.accelerator->callPlatformFunction("freescanmem",false,o.record,&index,&flags,nullptr); + o.freeing=false; + // DisplayMachine's free discards the pipe method's return value. + // Only our observed native record AND pipe cleanup prove success. + if(!o.freed)return false; + } else if(o.record->refs || o.record->map || o.record->memory || o.record->gpu || o.record->pitch)return false; + owner=o.accelerator;pipe=o.pipe;record=o.record;mapping=o.pinnedMap;memory=o.pinnedMemory; + __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); + __atomic_store_n(&o.pipe,static_cast(nullptr),__ATOMIC_RELEASE); + o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=o.published=o.modeReady=false;o.nativeResult=0;o.proof={};o.image={}; + } + IOFree(record,sizeof(XELP::DSI::NativeBacking::Record)); + if(mapping)mapping->release();if(memory)memory->release();if(pipe)pipe->release();owner->release();fb->release();return true; +} diff --git a/WhateverXe/kern_dsi_adapter_boot.inc b/WhateverXe/kern_dsi_adapter_boot.inc new file mode 100644 index 00000000..a2fe43ad --- /dev/null +++ b/WhateverXe/kern_dsi_adapter_boot.inc @@ -0,0 +1,306 @@ +// Firmware profile, native boot/cache binding and resource qualification. +// Included only by kern_xe.cpp; retains its private owners and lock ordering. + +bool XE::prepareDSIFirmware(IOService *provider) { + auto source=provider ? OSDynamicCast(OSData,provider->getProperty("TGLVBT")) : nullptr; + if(!source || !source->getLength() || source->getLength()>65536)return false; + if(dsiFirmware)return dsiProfile.valid() && dsiFirmware->getLength()==source->getLength() && + !memcmp(dsiFirmware->getBytesNoCopy(),source->getBytesNoCopy(),source->getLength()); + auto copy=OSData::withBytes(source->getBytesNoCopy(),source->getLength()); + if(!copy)return false; + auto parsed=XELP::DSI::parseProfile({static_cast(copy->getBytesNoCopy()),copy->getLength()},true); + if(!parsed.valid()){copy->release();return false;} + dsiProfile=parsed;dsiFirmware=copy;return true; +} + +// Keep profile parsing temporaries out of the already large bootstrap frame. +__attribute__((noinline)) void XE::auditDSIBoot(IOPCIDevice *pci,volatile const uint8_t *base,uint64_t length, + const AlderLakeN::DisplayReadout &normal,bool admitted) { + if(!checkKernelArgument("-wex-dsi-audit"))return; + struct Scratch {XELP::DSI::Profile profile;XELP::DSI::Boot boot;XELP::DSI::BootAudit audit;}; + auto scratch=static_cast(IOMalloc(sizeof(Scratch)));if(!scratch)return; + // Pre-native, before the optional external-output handoff. Read only; + // the scratch certificate must never become the live dsiBoot owner. + auto vbt=OSDynamicCast(OSData,pci->getProperty("TGLVBT"));scratch->profile={}; + if(vbt && vbt->getLength() && vbt->getLength()<=65536) + scratch->profile=XELP::DSI::parseProfile({static_cast(vbt->getBytesNoCopy()),vbt->getLength()},true); + struct ReadIO { + volatile const uint8_t *base;uint64_t length; + bool readable(uint32_t reg) const {return base && length>=4 && reg<=length-4;} + uint32_t read(uint32_t reg) {return *reinterpret_cast(base+reg);} + uint64_t readPTE(uint32_t reg) {return *reinterpret_cast(base+reg);} + } io {base,length}; + XELP::DSI::auditBoot(io,normal,scratch->profile,xelpMemory,admitted,scratch->boot,scratch->audit); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBootAudit",&scratch->audit,sizeof(scratch->audit)); + IOFree(scratch,sizeof(Scratch)); +} + +bool XE::processDisplay13DSICache(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN31AppleIntelFramebufferController16hwUpdateRegCacheEP21AppleIntelFramebufferP21AppleIntelDisplayPath", + "__ZN31AppleIntelFramebufferController9hwGetCRTCEP21AppleIntelFramebufferP21AppleIntelDisplayPath"}; + const uint32_t offsets[]={0x4C4E2,0x4C79A}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xFF,0x05,0x94,0x2A}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0x89,0xF3,0x49,0x89,0xFE,0x48,0xFF,0x05}}; + if(!display13FramebufferRequested || size<=0x4C866)return false; + for(unsigned i=0;i<2;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSIUpdateCache,orgDSICache[0]}, {symbols[1],wrapDSIGetCRTC,orgDSICache[1]}}; + const bool ready=patcher.routeMultiple(index,routes,2,address,size);patcher.clearError();return ready; +} + +bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) { + auto &c=display13FBPower; + if(!c.lock || !controller || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || + controller->getMetaClass()->getClassSize()<0x1C20 || path->getMetaClass()->getClassSize()<0x3730)return false; + DSIModeScope scope(fb);if(!scope.lock)return false; + IOLockLock(c.lock);Display13FBBootIO io{c.map}; + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring,false); + const bool bound=dsiCacheHooksReady && !display13Offline && dsiResourcesReady && c.map && + adlnOutputWiring.dsi==1 && controller==c.controller && controller==adlnTopologyController && + binding.status==TGLFBBinding::Status::Complete && binding.pipe==0 && binding.ddi==0 && + c.transcoders.dsi0Pipe==0 && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && + Display13FBMemory::field(path,0x3648)==c.dsiPort && c.dsiPort && + Display13FBMemory::field(c.dsiPort,0x550)==1 && + Display13FBMemory::field(path,0x34)==0x4A480 && + c.bootDSI.sameSource(dsiBoot.source) && c.bridge.outputHeld(io,0,0); + bool ready=false; + if(bound) { + auto plane=Display13FBMemory::field(path,0x32C8); + auto scaler=Display13FBMemory::field(path,0x32C0); + bool addresses=true; + for(unsigned i=0;i<6;++i)addresses=addresses && + Display13FBMemory::field(plane,XELP::DSI::NativeCache::PlaneAddressFields[i])==XELP::DSI::NativeCache::PlaneRegisters[i]; + uint32_t scale=0; + if(scaler) { + addresses=addresses && scaler->getMetaClass()->getClassSize()>=0x50; + if(addresses) { + scale=Display13FBMemory::field(scaler,0x30); + addresses=Display13FBMemory::field(scaler,0x28)==c.access && + Display13FBMemory::field(scaler,0x18)==0 && (scale==0x68180 || scale==0x68280) && + Display13FBMemory::field(scaler,0x34)==scale-0x10 && + Display13FBMemory::field(scaler,0x38)==scale-0xC; + } + } + XELP::DSI::NativeCache::View view; + ready=addresses && (dsiPublishedTarget(fb) ? XELP::DSI::NativeCache::readActive(io,dsiBoot,scale,view) : + XELP::DSI::NativeCache::read(io,dsiBoot,xelpMemory,scale,view)); + if(ready)XELP::DSI::NativeCache::publish(view,path,plane,scaler); + } + IOLockUnlock(c.lock);return ready; +} + +void XE::wrapDSIUpdateCache(IOService *controller,IOService *fb,OSObject *path) { + if(callbackXE->adlnOutputWiring.dsi && dsiCachePath(path)) { + if(!callbackXE->updateDSIBootCache(controller,fb,path)) { + auto &c=display13FBPower; + if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + } + return; + } + FunctionCast(wrapDSIUpdateCache,callbackXE->orgDSICache[0])(controller,fb,path); +} + +void XE::wrapDSIGetCRTC(IOService *controller,IOService *fb,OSObject *path) { + if(callbackXE->adlnOutputWiring.dsi && dsiCachePath(path)) { + // DSI brightness belongs to its panel backend. The donor's internal + // FB branch divides by an ordinary PWM period that may be zero here. + wrapDSIUpdateCache(controller,fb,path);return; + } + FunctionCast(wrapDSIGetCRTC,callbackXE->orgDSICache[1])(controller,fb,path); +} + +bool XE::processDisplay13DSIBoot(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbol="__ZN31AppleIntelFramebufferController13probeBootPipeEPbPN17AppleIntelPortHAL3DDIE"; + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89}; + // setupBootDisplay consumes the returned pipe, builtin bool and DDI before + // allocating the real native path. Do not replace that allocation with a + // fabricated path. Its DSI cache/stop adapters remain separate prerequisites. + const uint8_t call[]={0xE8,0xA7,0x1D,0x00,0x00}; + if(!display13FramebufferRequested || size<=0x455E8)return false; + const auto fn=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(fn!=address+0x453D6 || memcmp(reinterpret_cast(fn),entry,sizeof(entry)) || + memcmp(reinterpret_cast(address+0x4362A),call,sizeof(call)))return false; + KernelPatcher::RouteRequest route(symbol,wrapDSIBootProbe,orgDSIBootProbe); + const bool ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError();return ready; +} + +uint32_t XE::wrapDSIBootProbe(IOService *controller,bool *builtin,uint32_t *ddi) { + auto self=callbackXE; + if(!self->adlnOutputWiring.dsi) + return FunctionCast(wrapDSIBootProbe,self->orgDSIBootProbe)(controller,builtin,ddi); + // Failure must not leak an old builtin/DDI result to setupBootDisplay. + if(builtin)*builtin=false;if(ddi)*ddi=UINT32_MAX; + auto &c=display13FBPower; + if(!self->dsiBootProbeReady || display13Offline || !c.lock || !controller || + controller->getMetaClass()->getClassSize()<0x1C20 || + !__atomic_load_n(&self->dsiResourcesReady,__ATOMIC_ACQUIRE))return 0xFFFF; + IOLockLock(c.lock);Display13FBBootIO io{c.map}; + auto port=c.dsiPort; + const auto config=port && port->getMetaClass()->getClassSize()>=0x6B0 ? + Display13FBMemory::field(port,0x548) : nullptr; + const bool valid=c.map && self->adlnOutputWiring.dsi==1 && controller==c.controller && + controller==self->adlnTopologyController && c.transcoders.dsi0Pipe==0 && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && + c.access && Display13FBTopology::bound(self->adlnPlatform,config,0) && + self->adlnOutputWiring.matches(config) && config[0]==0 && + Display13FBMemory::field(port,0x120)==0 && + Display13FBMemory::field(port,0x550)==1 && + Display13FBMemory::field(port,0x578)==c.access && + self->dsiBoot.complete() && c.bootDSI.sameSource(self->dsiBoot.source) && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,self->dsiBoot,xelpMemory); + if(valid){if(builtin)*builtin=true;if(ddi)*ddi=0;} + IOLockUnlock(c.lock);return valid?0U:0xFFFFU; +} + +// The Display13 native Mode hook has one owner for TC, Combo and DSI. +// Caller must have installed the source/mapping and native power references at +// the adoption boundary. A board record or firmware-on bits alone never admit. +uint32_t XE::setDSIPortMode(OSObject *port,uint32_t wanted) { + auto self=callbackXE;auto &c=display13FBPower; + if(!self->display13FBTCHooksReady || !self->display13TCBaseMode || !c.lock || + !port || port->getMetaClass()->getClassSize()<0x6B0)return kIOReturnNotReady; + // Mode zero must be connected to an actual DSI stop/cleanup transaction. + // Reject it for now, including when the native caller asks only for reset. + if(wanted!=1)return kIOReturnUnsupported; + IOLockLock(c.lock); + auto config=Display13FBMemory::field(port,0x548); + const auto previous=Display13FBMemory::field(port,0x550); + const bool bound=self->adlnOutputWiring.dsi==1 && self->adlnOutputWiring.matches(config) && + config[0]==0 && Display13FBTopology::bound(self->adlnPlatform,config,0) && + c.controller && c.controller==self->adlnTopologyController && c.access && + Display13FBMemory::field(port,0x578)==c.access && + Display13FBMemory::field(port,0x120)==0 && + c.transcoders.dsi(0) && c.bootDSI.complete() && c.bootDSI.identity.pipe==0 && + c.bootDSI.identity.phy==0 && c.bootDSI.identity.transcoder==0 && + (!c.dsiPort || c.dsiPort==port) && previous<=1 && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted; + Display13FBBootIO io{c.map}; + const bool ready=bound && c.bridge.outputHeld(io,0,0) && XELP::DSI::sourceUnchanged(io,c.bootDSI); + uint32_t result=kIOReturnNotReady; + if(ready) { + // Exact donor base setPortMode(1) only records port+0x550. The HAL + // setter and mode0's reset callback must never run on this path. + result=reinterpret_cast(self->display13TCBaseMode)(port,1); + if(result==kIOReturnSuccess && Display13FBMemory::field(port,0x550)!=1)result=kIOReturnNotReady; + if(result==kIOReturnSuccess && !c.dsiPort){port->retain();c.dsiPort=port;} + } + IOLockUnlock(c.lock);return result; +} + +// Called only after the real PCI/workloop IRQ source and native memory have +// been qualified. Keep the FB mutex across mode publication; never call the +// native timing setter or registry code with the display-owner lock held. +bool XE::adoptDSIBootPath(IOService *fb,OSObject *path) { + auto &c=display13FBPower;auto &irq=display13FBIRQ; + if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || + path->getMetaClass()->getClassSize()<0x3730)return false; + DSIModeScope scope(fb);if(!scope.lock)return false; + const auto qualified=[&]() { + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiCacheHooksReady || !dsiBootProbeReady || + display13Offline || !c.map || !c.power || !c.controller || c.controller!=adlnTopologyController || + c.pipe[0].memoryFramebuffer!=fb || c.memoryFaulted || c.clockFaulted || + c.bridge.state.faulted || c.bridge.state.suspended || c.transcoders.dsi0Pipe!=0 || !irq.lock || + !Display13Power::objectAssociations(c.power,0x80,c.controller,0x1C20))return false; + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); + if(binding.status!=TGLFBBinding::Status::Complete || binding.pipe!=0 || binding.ddi!=0 || + Display13FBMemory::field(path,0x3648)!=c.dsiPort || !c.dsiPort || + Display13FBMemory::field(c.dsiPort,0x550)!=1 || + Display13FBMemory::field(path,0x3644)!=2 || + Display13FBMemory::field(fb,0x4348) || + !Display13FBMemory::field(fb,0x44DE) || !Display13FBMemory::field(fb,0x1E7))return false; + Display13FBBootIO io{c.map}; + if(!c.bridge.outputHeld(io,0,0) || !dsiBoot.complete() || + !c.bootDSI.sameSource(dsiBoot.source) || !XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory))return false; + const auto memory=display13FBInspectMemory(fb,true,0,false); + if(memory.status!=Display13FBMemory::Status::Complete || memory.control!=dsiBoot.control || + memory.stride!=dsiBoot.stride || memory.size!=dsiBoot.size || memory.surface!=dsiBoot.surface)return false; + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const bool enabled=irq.controller==c.controller && (irq.admittedPipes&1) && !irq.quiescing && + irq.state.begun && irq.state.enabled && !irq.state.faulted && irq.state.transcoders.dsi0Pipe==0; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return enabled; + }; + IOLockLock(c.lock);const bool before=qualified();IOLockUnlock(c.lock); + bool ready=before && bindDSIFixedMode(fb,dsiProfile); + IOLockLock(c.lock); + ready=ready && qualified(); + if(ready) { + const auto key=reinterpret_cast(path); + ready=c.bridge.bootAdopted ? c.bridge.bootPath==key : + c.bridge.adoptBootPath(key,0,0).status==Display13Power::Status::Complete; + ready=ready && Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0x1C20); + } + if(!ready) { + // The setter may already have published its raw array pointer. Retain + // all backing/objects until stop; only withdraw mode readiness here. + if(dsiModeTarget(fb))dsiFixedMode.ready=false; + adlnFault(c.memoryFaulted,__LINE__); + } + IOLockUnlock(c.lock); + if(!ready && dsiModeTarget(fb))fb->setProperty("WEXDSIFixedModeReady",false); + return ready; +} + +// Called outside the owner lock, before native engine/probe/IRQ users start. +// The lock order matches clock clients: Combo clock -> display owner -> IRQ. +// Failure retains any already acquired boot power references for cleanup. +bool XE::bindDSIResources(IOService *controller) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; + if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || + !display13FBIRQReady || !dsiNativeWriterHooksReady || !clock.lock || !c.lock)return false; + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + struct IO : Display13FBBootIO { + explicit IO(IOMemoryMap *map):Display13FBBootIO{map} {} + void write(uint32_t reg,uint32_t value) { + if(reg==0x42084 && readable(reg)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + } io{c.map}; + const bool owned=adlnOutputWiring.dsi==1 && dsiProfile.valid() && dsiBoot.complete() && + c.controller==controller && controller==adlnTopologyController && !clock.thread && + !dsiResourcesReady && c.transcoders.dsi0Pipe==UINT32_MAX && + !c.engineEnabled && !c.dbuf.begun && !c.memoryFaulted && !c.clockFaulted && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + auto irqLock=Display13FBMemory::field(controller,0x90); + bool ready=false; + if(owned && irqLock) { + XELPComboClock::ClockSnapshot snapshot;snapshot.layout=XELPComboClock::Layout::Display13; + snapshot.mux[0]=io.read(0x164280); + for(unsigned p=0;p(controller),snapshot)) { + // Native X/Y tiled scanout and every external output use GGTT. + // The live DSI GOP is linear, so this shared mode can be prepared + // without disabling its plane. Retain PLL/power on any failure. + if(XELP::DSI::prepareGGTT(io,dsiBoot,xelpMemory,dsiGGTT)) { + c.bootDSI=dsiBoot.source;c.transcoders.dsi0Pipe=0; + irq.state.transcoders=c.transcoders;irq.base=c.map->getVirtualAddress();irq.lock=irqLock; + __atomic_store_n(&irq.admittedPipes,1U,__ATOMIC_RELEASE); + __atomic_store_n(&irq.controller,controller,__ATOMIC_RELEASE); + __atomic_store_n(&dsiResourcesReady,true,__ATOMIC_RELEASE);ready=true; + } else adlnFault(c.memoryFaulted,__LINE__); + } + IOSimpleLockUnlockEnableInterrupt(irqLock,saved); + } + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(ready)dsiPublishIRQLive(); + if(ready && dsiCommandTest)ready=probeDSICommands(controller); + if(ready && dsiPanelTest)ready=probeDSIPanel(controller); + if(ready) { + IOLockLock(c.lock); + using Light=XELP::DSI::Backlight; + if(dsiBacklight.capture(dsiProfile,io.read(Light::Control),io.read(Light::Period),io.read(Light::Duty))) { + *reinterpret_cast(reinterpret_cast(controller)+0xE58)=dsiBacklight.requested; + *reinterpret_cast(reinterpret_cast(controller)+0xE60)=dsiBacklight.pwm.period; + } + IOLockUnlock(c.lock);publishDSIBacklight(); + } + return ready; +} diff --git a/WhateverXe/kern_dsi_adapter_diagnostics.inc b/WhateverXe/kern_dsi_adapter_diagnostics.inc new file mode 100644 index 00000000..b837e93f --- /dev/null +++ b/WhateverXe/kern_dsi_adapter_diagnostics.inc @@ -0,0 +1,212 @@ +// Opt-in command/panel experiments and read-only IRQ diagnostics. +// Included only by kern_xe.cpp; retains its private owners and lock ordering. + +// One optional bootstrap NOP/drain trial before native engine and IRQ users. +// This exercises the same packet engine used by panel sequences, without +// changing panel power/reset, timing, brightness, or any framebuffer state. +bool XE::probeDSICommands(IOService *controller) { + auto &c=display13FBPower;auto &clock=comboClockTransaction; + if(!controller || !c.lock || !clock.lock)return false; + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + const auto owned=[&]() { + return controller==c.controller && controller==adlnTopologyController && + dsiResourcesReady && !dsiStopAttempted && adlnOutputWiring.dsi==1 && + !c.engineEnabled && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !clock.thread && + clock.allocation.external[0]==reinterpret_cast(controller) && + clock.allocation.binding[0]==dsiBoot.source.identity.pll+1; + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns; + IO(IOMemoryMap *m,decltype(owned) &q):Display13FBBootIO{m},owns(q) {} + bool ready() { + if(!owns() || !readable(0x45404))return false; + const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; + } + void write(uint32_t reg,uint32_t value) { + if(reg==0x6B100 || reg==0x6B104) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + void delayUs(unsigned us){IODelay(us);} + } io{c.map,owned}; + XELP::DSI::CommandPort port{io};uint64_t record[20] {2}; + // Active video can defer an LP packet until its blanking interval. The + // cold panel-sequence engine keeps its Linux 100 us default. Bound this + // running-link trial to three frames per credit wait, rounded to 10 us. + const auto &mode=dsiProfile.mode; + const uint64_t period=mode.clockKHz ? (uint64_t(mode.htotal())*mode.vtotal()*1000+mode.clockKHz-1)/mode.clockKHz : 0; + record[16]=period ? ((3*period+9)/10)*10 : 0; + uint32_t credits=0,lp=0; + record[1]=io.ready() && !dsiCommandHost.enabled && record[16]>=100 && record[16]<=100000 && dsiProfile.valid() && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + if(record[1]) { + const bool empty=port.credits(credits) && port.read(0x6B0D8,lp) && + (credits&0x1FFF)==0x1040 && !(lp&((1U<<17)|(1U<<16)|1U)); + record[2]=credits;record[3]=lp;record[4]=empty; + if(empty) { + record[17]=io.read(0x70040); + dsiCommandHost.enabled=true;const uint8_t nop=0; + record[5]=dsiCommandHost.send(port,0x05,{&nop,1}); + record[6]=record[5] && dsiCommandHost.drain(port,static_cast(record[16])); + record[18]=io.read(0x70040); + } + record[7]=port.credits(credits)?credits:UINT32_MAX; + record[8]=port.read(0x6B0D8,lp)?lp:UINT32_MAX; + record[9]=io.ready() && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + } + record[10]=static_cast(dsiCommandHost.error);record[11]=dsiCommandHost.pending; + record[12]=dsiCommandHost.headers;record[13]=dsiCommandHost.payloadWords; + record[14]=dsiCommandHost.waitedUs;record[15]=port.writes;record[19]=dsiBoot.source.conf; + const bool complete=record[1] && record[4] && record[6] && record[9] && + !record[10] && !record[11] && !port.failed && (record[7]&0x1FFF)==0x1040 && + record[8]!=UINT32_MAX && !(record[8]&((1U<<17)|(1U<<16)|1U)); + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSICommandTrial",record,sizeof(record)); + return complete; +} + +// Bootstrap-only panel sequence experiment. By default retain host/PHY/PLL; +// the optional host trial rebuilds them before panel INIT_OTP. Neither path +// releases shared resources or enables runtime power transitions. +bool XE::probeDSIPanel(IOService *controller) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; + if(!controller || !c.lock || !clock.lock || !irq.lock)return false; + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + const auto owned=[&]() { + return controller==c.controller && controller==adlnTopologyController && + dsiResourcesReady && !dsiStopAttempted && adlnOutputWiring.dsi==1 && + !c.engineEnabled && !c.memoryFaulted && !c.clockFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !clock.thread && + clock.allocation.external[0]==reinterpret_cast(controller) && + clock.allocation.binding[0]==dsiBoot.source.identity.pll+1 && + irq.controller==controller && !irq.state.begun && !irq.state.enabled && + !irq.quiescing && !irq.handlers && !irq.softwareHandlers; + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns;IOSimpleLock *irqLock; + bool restart {};unsigned pll {};uint32_t writes {},gpios {};XELP::DSI::Panel::GPIOResult lastGPIO; + IO(IOMemoryMap *m,decltype(owned) &q,IOSimpleLock *l,bool cold,unsigned id):Display13FBBootIO{m},owns(q),irqLock(l),restart(cold),pll(id) {} + bool ready() { + if(!owns() || !readable(0x45404))return false; + const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; + } + void write(uint32_t reg,uint32_t value) { + if(restart && XELP::DSI::Restart::writable(reg,pll)) { + *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;return; + } + switch(reg) { + case 0x6B100:case 0x6B104:case 0xC4030:case 0xC7204: + case 0x70180:case 0x7019C:case 0x7B008: + *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;break; + } + } + bool gpio(unsigned role,unsigned value) { + // Only this short RMW is under the IRQ lock. All VBT delays and + // FIFO/frame polling execute with interrupts enabled. + const auto saved=IOSimpleLockLockDisableInterrupt(irqLock); + lastGPIO=XELP::DSI::Panel::gpio(*this,role,value); + IOSimpleLockUnlockEnableInterrupt(irqLock,saved); + if(lastGPIO.complete)++gpios; + return lastGPIO.complete; + } + void delayUs(unsigned us){IODelay(us);} + } io{c.map,owned,irq.lock,dsiHostTest,dsiBoot.source.identity.pll}; + Display13Clock::Change bootCDClock; + struct Lifecycle { + bool enabled,prepareExternal;XELPComboClock::ClockAllocation &allocation;uintptr_t owner; + XELP::DSI::Restart::State &state;Display13Clock::Change &cdclk; + bool stop(IO &io,const XELP::DSI::Readout &source,const XELP::DSI::Profile &profile) { + return !enabled || XELP::DSI::Restart::stop(io,source,profile,allocation,owner,state); + } + bool start(IO &io,XELP::DSI::Readout &source) { + if(!enabled)return true; + // Native external modes request the maximum CDCLK. Prepare that + // shared clock while the bootstrap cycle has stopped DSI and all + // other consumers, before native mode setting/IRQ users start. + // The DSI link PLL remains a separate retained allocation. + if(prepareExternal) { + auto &c=display13FBPower; + Display13FBClockIO clockIO {c.map}; + const auto current=Display13Clock::read(clockIO); + cdclk.before=current;cdclk.requestedHz=current.nativeMaxHz(); + const bool stopped=io.ready() && state.stopped && !state.started && + !c.engineEnabled && Display13FBIRQ::consumersStopped(io,7,c.transcoders); + if(!stopped || current.status!=Display13Clock::Status::Complete)return false; + cdclk=Display13Clock::set(clockIO,current.nativeMaxHz(),{true,true,0}); + if(cdclk.status!=Display13Clock::ChangeStatus::Complete) { + adlnFault(c.clockFaulted,__LINE__);return false; + } + c.clock=cdclk.after;c.clockStopped=false;c.resumeClockHz=cdclk.after.nativeHz(); + auto controller=reinterpret_cast(owner); + *reinterpret_cast(reinterpret_cast(controller)+0xE98)=cdclk.after.nativeHz(); + *reinterpret_cast(reinterpret_cast(controller)+0xEA0)=cdclk.after.nativeHz(); + } + if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; + source=XELP::DSI::Restart::expected(source,state);return source.complete(); + } + } lifecycle{dsiHostTest,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; + uint64_t record[32] {1}; + record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && + !dsiCommandHost.faulted && !dsiCommandHost.pending && + c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); + if(record[1]) { + record[20]=io.read(0xC7204);record[22]=io.read(0xC4030); + record[24]=io.read(0xC7200);record[28]=io.read(0x70040); + XELP::DSI::Panel::cycle(io,dsiBoot.source,dsiProfile,dsiBoot.control,dsiBoot.surface,dsiPanelTrial,lifecycle); + record[21]=io.ready()?io.read(0xC7204):UINT32_MAX; + record[23]=io.ready()?io.read(0xC4030):UINT32_MAX; + record[25]=io.ready()?io.read(0xC7200):UINT32_MAX; + record[26]=io.ready()?io.read(0x6B0D0):UINT32_MAX; + record[27]=io.ready()?io.read(0x6B0D8):UINT32_MAX; + record[29]=io.ready()?io.read(0x70040):UINT32_MAX; + // Publish the new clock/transport witness only after the complete + // cycle and old backing/geometry have been revalidated together. + auto nextBoot=dsiBoot; + if(dsiPanelTrial.complete())nextBoot.source=dsiPanelTrial.source; + record[30]=io.ready() && dsiPanelTrial.complete() && XELP::DSI::bootUnchanged(io,nextBoot,xelpMemory); + if(record[30]){dsiBoot=nextBoot;c.bootDSI=nextBoot.source;} + } + const auto &r=dsiPanelTrial;const auto &h=r.host; + record[2]=static_cast(r.stage);record[3]=static_cast(r.error); + record[4]=r.reg;record[5]=r.value;record[6]=io.writes;record[7]=r.waitUs; + record[8]=static_cast(h.error);record[9]=h.pending;record[10]=h.sequenceId; + record[11]=h.operationOffset;record[12]=h.headers;record[13]=h.payloadWords; + record[14]=h.waitedUs;record[15]=h.completedOperations;record[16]=io.gpios; + record[17]=io.lastGPIO.role;record[18]=io.lastGPIO.value;record[19]=io.lastGPIO.complete; + const bool complete=record[1] && r.complete() && record[30];record[31]=complete; + const auto &host=dsiHostRestart;const auto &p=host.config; + uint64_t hostRecord[20] {1,dsiHostTest,static_cast(host.stage),static_cast(host.error), + host.reg,host.value,host.writes,host.waitUs,host.stopped,host.started,host.pll, + p.pll.cfg0(),p.pll.cfg1(),p.link.clockTiming,p.link.dataTiming,p.hsTimeout,p.lpTimeout,p.taTimeout, + record[30],complete}; + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); + if(dsiHostTest)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); + if(dsiHostTest && !dsiPanelOnly) + IORegistryEntry::getRegistryRoot()->setProperty("N97FBDSIBootCDClock",&bootCDClock,sizeof(bootCDClock)); + return complete; +} + +namespace { +// PowerInit retains the native controller (including its IRQ lock) until reboot. +// Read only the counters already collected by the IRQ handler. Never refresh +// the register cache, power a well, or call a native driver from this serializer. +static bool dsiIRQLiveSerialize(void *,void *,OSSerialize *serializer) { + auto &c=display13FBIRQ; + if(!__atomic_load_n(&c.controller,__ATOMIC_ACQUIRE) || !c.lock)return false; + uint64_t record[32] {}; + const auto saved=IOSimpleLockLockDisableInterrupt(c.lock); + adlnIRQRecord(record,3); + IOSimpleLockUnlockEnableInterrupt(c.lock,saved); + auto data=OSData::withBytes(record,sizeof(record));if(!data)return false; + const bool ok=data->serialize(serializer);data->release();return ok; +} + +static void dsiPublishIRQLive() { + auto property=OSSerializer::forTarget(nullptr,dsiIRQLiveSerialize); + if(property) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIIRQLive",property); + property->release(); + } +} +} diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc new file mode 100644 index 00000000..573d0b7c --- /dev/null +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -0,0 +1,876 @@ +// Native writer admission, brightness, display power and path retirement. +// Included only by kern_xe.cpp; retains its private owners and lock ordering. + +// Caller holds the native FB mutex through the admitted writer. quiesce uses +// the same mutex, so a stop cannot interleave after this check. This is one +// update boundary, not a certificate for other native writers or Session Stop. +bool XE::dsiNativeUpdateAllowed(IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi)return true; + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; + const auto path=Display13FBMemory::singlePath(fb); + if(!dsiLifecycleTarget(fb,path))return true; + auto &c=display13FBPower;if(!c.lock || dsiFixedMode.changingThread || dsiFixedMode.presentationFault)return false; + IOLockLock(c.lock); + const bool ready=(!dsiPendingTarget(fb) || (dsiPublishedTarget(fb) && dsiPendingBacking.modeReady)) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && + !c.dbuf.faulted && !c.watermarkFaulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && + c.transcoders.dsi0Pipe==0 && path && path->getMetaClass()->getClassSize()>=0x3730 && + Display13FBMemory::field(fb,0x1D0)==c.controller && + Display13FBMemory::field(path,0x32B0)==fb && Display13FBMemory::field(path,0x3640)==0 && + Display13FBMemory::field(path,0x3644)==2 && c.dsiPort && c.dsiPort->getMetaClass()->getClassSize()>=0x580 && + Display13FBMemory::field(path,0x3648)==c.dsiPort && Display13FBMemory::field(c.dsiPort,0x550)==1; + IOLockUnlock(c.lock);return ready; +} + +// Owner lock held. DSI reserves pipe A even after its mapping is +// withdrawn. Late shared callbacks must not revive it or fault peer outputs. +// An unbound/ambiguous callback cannot establish a different output owner. +bool XE::dsiSharedWriterStopped(IOService *fb,unsigned pipe) { + return callbackXE->adlnOutputWiring.dsi && ((dsiPendingTarget(fb) && !dsiPendingBacking.modeReady) || + (callbackXE->dsiStopAttempted && (pipe==0 || pipe>=3 || dsiModeTarget(fb)))); +} + +// Cursor image/state bodies can wait for a frame, so do not extend their +// native FB-lock intervals. Count admitted DSI calls instead. Stop takes the +// same FB mutex before testing the count and cannot start until it is zero. +int XE::beginDSINativeWriter(IOService *controller,IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi)return 0; + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return -1; + DSIModeScope scope(fb);if(!scope.lock)return -1; + if(!dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb)))return 0; + if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return -1; + auto &c=display13FBPower;IOLockLock(c.lock); + const bool admitted=controller==c.controller && c.dsiNativeWriters!=UINT32_MAX; + if(admitted)++c.dsiNativeWriters; + IOLockUnlock(c.lock);return admitted ? 1 : -1; +} + +void XE::endDSINativeWriter(int token) { + if(token!=1)return; + auto &c=display13FBPower;IOLockLock(c.lock); + if(c.dsiNativeWriters)--c.dsiNativeWriters; + else adlnFault(c.memoryFaulted,__LINE__); + IOLockUnlock(c.lock); +} + +uint32_t XE::wrapDSICursorImage(IOService *controller,IOService *fb,void *info) { + const int token=beginDSINativeWriter(controller,fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSICursorImage,callbackXE->orgDSICursor[0])(controller,fb,info); + endDSINativeWriter(token);return result; +} + +uint32_t XE::wrapDSICursorState(IOService *controller,IOService *fb,int32_t x,int32_t y,bool visible,bool commit) { + const int token=beginDSINativeWriter(controller,fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSICursorState,callbackXE->orgDSICursor[1])(controller,fb,x,y,visible,commit); + endDSINativeWriter(token);return result; +} + +// Transaction bodies may wait and may call armUpdate, cursor or color hooks. +// Count the complete body without changing its native lock/wait intervals. +int XE::beginDSIFramebufferWriter(IOService *fb) { + if(!callbackXE->adlnOutputWiring.dsi)return 0; + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return -1; + return beginDSINativeWriter(Display13FBMemory::field(fb,0x1D0),fb); +} + +uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t options,void *params) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIFlipTransaction,callbackXE->orgDSITransaction[0])(fb,transaction,options,params); + endDSINativeWriter(token);return result; +} + +uint32_t XE::wrapDSIGammaTransaction(IOService *fb,void *transaction,void *params) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIGammaTransaction,callbackXE->orgDSITransaction[1])(fb,transaction,params); + endDSINativeWriter(token);return result; +} + +uint32_t XE::wrapDSICscTransaction(IOService *fb,void *transaction,void *matrix,uint64_t options,bool final) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSICscTransaction,callbackXE->orgDSITransaction[2])(fb,transaction,matrix,options,final); + endDSINativeWriter(token);return result; +} + +uint32_t XE::wrapDSIPlaneGammaTransaction(IOService *fb,void *transaction,bool post) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSIPlaneGammaTransaction,callbackXE->orgDSITransaction[3])(fb,transaction,post); + endDSINativeWriter(token);return result; +} + +uint32_t XE::wrapDSISetGammaTable(IOService *fb,uint32_t channels,uint32_t count,uint32_t bits,void *data,bool sync) { + const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + const auto result=FunctionCast(wrapDSISetGammaTable,callbackXE->orgDSITransaction[4])(fb,channels,count,bits,data,sync); + endDSINativeWriter(token);return result; +} + +// Power/backlight use DSI owners. Block only the donor branches that enter +// unsupported native transport controls; let ordinary OS attributes reach +// their native implementations under the existing writer lifetime. +uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t value) { + recordDSIClient(fb,7,attribute,UINT32_MAX); + const auto result=[&]() -> uint32_t { + int token=0; + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(attribute==0x706F7772 || attribute==0x70777273) { // powr / IOGraphics pwrs relay. + if(value>UINT32_MAX)return kIOReturnUnsupported; + return wrapDSISetPowerState(fb,static_cast(value)); + } + if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); + if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; + token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + } + } + const auto result=FunctionCast(wrapDSISetAttribute,callbackXE->orgDSIAttributes[0])(fb,attribute,value); + endDSINativeWriter(token);return result; + }(); + recordDSIClient(fb,7,attribute,result); + recordDSIAttribute(fb,7,attribute,value,result); + return result; +} + +// Bounded CPU-only history: the last attribute alone hides earlier rejected UI +// requests behind a successful gamma flush. No MMIO or object pointers are exposed. +void XE::recordDSIAttribute(IOService *fb,unsigned event,uint32_t attribute,uintptr_t value,uint32_t result) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->adlnOutputWiring.dsi || !dsiModeTarget(fb) || !c.lock)return; + DSIModeScope scope(fb);if(!scope.lock)return; + static uint64_t history[2+16*8] {1,0}; + IOLockLock(c.lock); + auto controller=Display13FBMemory::field(fb,0x1D0); + if(controller && controller==c.controller && controller==s->adlnTopologyController) { + const auto sequence=++history[1];auto row=history+2+((sequence-1)%16)*8; + row[0]=sequence;row[1]=event;row[2]=attribute;row[3]=value<=UINT32_MAX?value:UINT64_MAX;row[4]=result; + row[5]=Display13FBMemory::field(controller,0xE58); + row[6]=Display13FBMemory::field(controller,0xE5C); + row[7]=Display13FBMemory::field(fb,0x44E0); + } + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIAttributeHistory",history,sizeof(history)); +} + +void XE::publishDSIBacklight() { + auto &c=display13FBPower;if(!c.lock)return; + IOLockLock(c.lock);const auto &b=dsiBacklight; + uint64_t receipt[]={1,b.initialized,b.faulted,b.requested,b.applied,b.pwm.period,b.minimum, + b.control,b.requests,b.writes,dsiRuntimePower.sleeping}; + IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBacklight",receipt,sizeof(receipt)); +} + +// Exact donor setAttribute('bksv') stores its 32-bit value at controller+E5C. +// This is user preference bookkeeping, not a hardware brightness command. +uint32_t XE::saveDSIBrightness(IOService *fb,uintptr_t value) { + auto &c=display13FBPower; + if(value>UINT32_MAX)return kIOReturnBadArgument; + if(!c.lock || !dsiModeTarget(fb))return kIOReturnNotReady; + IOLockLock(c.lock); + const bool owned=dsiResourcesReady && c.controller && c.controller==adlnTopologyController && + Display13FBMemory::field(fb,0x1D0)==c.controller && c.pipe[0].memoryFramebuffer==fb; + if(owned)*reinterpret_cast(static_cast(c.controller)+0xE5C)=static_cast(value); + IOLockUnlock(c.lock);return owned?kIOReturnSuccess:kIOReturnNotReady; +} + +// The caller holds the same framebuffer mutex as stop/resume. Deferred requests +// update only software; the panel-on sequence applies PWM before its enable GPIO. +uint32_t XE::setDSIBrightness(IOService *fb,uintptr_t value) { + auto &c=display13FBPower; + if(value>XELP::DSI::Backlight::Maximum)return kIOReturnBadArgument; + if(!c.lock || !dsiModeTarget(fb))return kIOReturnNotReady; + const bool awake=dsiNativeUpdateAllowed(fb); + IOLockLock(c.lock); + const bool owned=dsiResourcesReady && dsiBacklight.initialized && !dsiRuntimePower.failed && + !dsiBacklight.faulted && !c.memoryFaulted && !c.clockFaulted && !c.watermarkFaulted && + !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && + c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && + Display13FBMemory::field(fb,0x1D0)==c.controller && + c.controller==adlnTopologyController && adlnWriterMappingBound(); + const bool deferred=owned && dsiPowerTest && dsiRuntimePower.sleeping && dsiStopAttempted && + dsiStop.complete() && dsiPanelStop.complete && dsiIRQParked && !dsiMemoryReleased && !dsiDDIReleased; + struct IO:Display13FBBootIO { + bool owned;IO(IOMemoryMap *m,bool o):Display13FBBootIO{m},owned(o){} + bool ready(){return owned;} + void write(uint32_t reg,uint32_t value) { + if(owned && reg==XELP::DSI::Backlight::Duty && readable(reg)) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + } io{c.map,owned && awake && !dsiStopAttempted}; + const bool accepted=owned && (deferred || (io.ready() && c.bridge.outputHeld(io,0,0))) && dsiBacklight.request(value); + const bool complete=accepted && (deferred || dsiBacklight.apply(io)); + // The donor getter returns this 16-bit requested value with range 0..65535. + if(complete)*reinterpret_cast(static_cast(c.controller)+0xE58)=dsiBacklight.requested; + IOLockUnlock(c.lock);publishDSIBacklight(); + return complete?kIOReturnSuccess:kIOReturnNotReady; +} + +uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint32_t attribute,uintptr_t value) { + recordDSIClient(fb,8,attribute,UINT32_MAX); + const auto result=[&]() -> uint32_t { + int token=0; + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(connection!=0)return kIOReturnUnsupported; + if(attribute==0x626B6C74)return callbackXE->setDSIBrightness(fb,value); + if(XELP::DSI::AppleAdapter::transportConnectionAttribute(attribute))return kIOReturnUnsupported; + token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; + } + } + const auto result=FunctionCast(wrapDSISetConnectionAttribute,callbackXE->orgDSIAttributes[1])(fb,connection,attribute,value); + endDSINativeWriter(token);return result; + }(); + recordDSIClient(fb,8,attribute,result); + recordDSIAttribute(fb,8,attribute,value,result); + return result; +} + +uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { + recordDSIClient(fb,9,state,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + if(callbackXE->dsiPowerTest && state<=2 && dsiModeTarget(fb)) { + auto controller=Display13FBMemory::field(fb,0x1D0); + auto path=Display13FBMemory::singlePath(fb); + auto &power=callbackXE->dsiRuntimePower; + const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : + (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); + if(!ready)return kIOReturnNotReady; + *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; + return kIOReturnSuccess; + } + // One-way runtime stop experiment through the real power callback. + // Recovery is a reboot; state 2 never replays the donor's DDI path. + if(callbackXE->dsiStopTest && state<2 && dsiModeTarget(fb)) { + const bool stopped=callbackXE->dsiMemoryReleased && callbackXE->dsiDDIReleased && + callbackXE->dsiClockStop.complete() && callbackXE->dsiPanelStop.complete; + if(stopped)return kIOReturnSuccess; + if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return kIOReturnNotReady; + auto controller=Display13FBMemory::field(fb,0x1D0); + auto path=Display13FBMemory::singlePath(fb); + if(!callbackXE->quiesceDSIPath(controller,fb,path))return kIOReturnNotReady; + *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; + return kIOReturnSuccess; + } + // Match only the donor's already-Wake no-op. Do not publish a new + // native power state without executing a DSI power transaction. + if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; + return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; + } + } + return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); + }(); + recordDSIClient(fb,9,state,result); + return result; +} + +uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path,const void *timing) { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,path))return kIOReturnNotReady; + } + return FunctionCast(wrapDSISetStream,callbackXE->orgDSIAttributes[3])(controller,fb,path,timing); +} + +bool XE::processDSINativeAttributes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer12setAttributeEjm", + "__ZN21AppleIntelFramebuffer25setAttributeForConnectionEijm", + "__ZN21AppleIntelFramebuffer24setFramebufferPowerStateEj", + "__ZN31AppleIntelFramebufferController11hwSetStreamEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"}; + const uint32_t offsets[]={0x3331E,0x352D4,0x24DE8,0x50DB0}; + const uint8_t entries[][20]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x50,0x41,0x89,0xF6,0x49,0x89,0xFF,0x48,0xFF,0x05,0xF1}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0x68,0x01,0x00,0x00}}; + if(!display13FramebufferRequested || size<0x50DC4)return false; + for(unsigned i=0;i<4;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; + } + // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, + {symbols[1],wrapDSISetConnectionAttribute,orgDSIAttributes[1]}, + {symbols[2],wrapDSISetPowerState,orgDSIAttributes[2]}, + {symbols[3],wrapDSISetStream,orgDSIAttributes[3]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} + +bool XE::processDSINativeTransactions(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer22PerformFlipTransactionEP30IOAccelDisplayPipeTransaction2yP21FlipTransactionParams", + "__ZN21AppleIntelFramebuffer23PerformGammaTransactionEP30IOAccelDisplayPipeTransaction2P20GammaTransactionArgs", + "__ZN21AppleIntelFramebuffer21PerformCscTransactionEP30IOAccelDisplayPipeTransaction2P29IOAccelDisplayPipeColorMatrixyb", + "__ZN21AppleIntelFramebuffer28PerformPlaneGammaTransactionEP30IOAccelDisplayPipeTransaction2b", + "__ZN21AppleIntelFramebuffer13setGammaTableEjjjPvb"}; + const uint32_t offsets[]={0x2B602,0x2C800,0x2D03C,0x2D838,0x36E72}; + // Whole prologues; no relocated RIP-relative counter in these 17 bytes. + const uint8_t entries[][17]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x48}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x68}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x58}}; + if(!display13FramebufferRequested || size<0x36E83)return false; + for(unsigned i=0;i<5;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],17))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSIFlipTransaction,orgDSITransaction[0]}, + {symbols[1],wrapDSIGammaTransaction,orgDSITransaction[1]}, + {symbols[2],wrapDSICscTransaction,orgDSITransaction[2]}, + {symbols[3],wrapDSIPlaneGammaTransaction,orgDSITransaction[3]}, + {symbols[4],wrapDSISetGammaTable,orgDSITransaction[4]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} + +bool XE::processDSINativeWriters(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN31AppleIntelFramebufferController16hwSetCursorImageEP21AppleIntelFramebufferP20IOHardwareCursorInfo", + "__ZN31AppleIntelFramebufferController16hwSetCursorStateEP21AppleIntelFramebufferiibb"}; + const uint32_t offsets[]={0x558F6,0x55DA8}; + const uint8_t entries[][20]={ + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28,0x49,0x89,0xD4}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38,0x45,0x89,0xCE}}; + if(!display13FramebufferRequested || size<=0x55DBC)return false; + for(unsigned i=0;i<2;++i) { + const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; + } + KernelPatcher::RouteRequest routes[]={ + {symbols[0],wrapDSICursorImage,orgDSICursor[0]}, + {symbols[1],wrapDSICursorState,orgDSICursor[1]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; +} + +// The whole freeDisplayPathResources body also touches global audio and can +// reset a normal DDI port. Keep only its audited CPU leaves for the DSI path. +bool XE::processDSIPathDisposal(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN31AppleIntelFramebufferController9freePlaneEP15AppleIntelPlane", + "__ZN31AppleIntelFramebufferController10freeScalerEP16AppleIntelScaler", + "__ZN21AppleIntelFramebuffer17detachDisplayPathEP21AppleIntelDisplayPath", + "__ZN21AppleIntelFramebuffer17removeDisplayPathEP21AppleIntelDisplayPath"}; + const uint32_t offsets[]={0x5751C,0x5739A,0x67FB4,0x68EA4}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xC9,0x96,0x08,0x00,0x48,0x85,0xF6,0x74,0x50}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xEB,0x97,0x08,0x00,0x48,0x85,0xF6,0x74,0x20}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x19,0xC5,0x07,0x00,0x48,0x8B,0x05,0x0A,0xC5}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0xEE,0xB8,0x07,0x00,0x48,0x8B}}; + if(size<=0x138BB8)return false; + mach_vm_address_t functions[4] {}; + for(unsigned i=0;i<4;++i) { + functions[i]=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); + if(functions[i]!=address+offsets[i] || memcmp(reinterpret_cast(functions[i]),entries[i],16))return false; + } + const auto map=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController12fPipeToFBMapE",address,size); + patcher.clearError();if(map!=address+0x138BB0)return false; + dsiFreePlane=functions[0];dsiFreeScaler=functions[1];dsiDetachPath=functions[2];dsiRemovePath=functions[3];dsiPipeMap=map; + return true; +} + +// Caller retains FB/path, owns its recursive mutex, and has detached its DDB +// reservation. Gate ownership excludes queued native work across PG put and +// CPU disposal. A failed put leaves the native path arrays and objects intact. +bool XE::disposeDSIPath(IOService *fb,OSObject *path) { + auto &c=display13FBPower; + if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || + path->getMetaClass()->getClassSize()<0x3730 || !dsiFreePlane || !dsiFreeScaler || + !dsiDetachPath || !dsiRemovePath || !dsiPipeMap || + !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; + IOLockLock(c.lock);auto loop=c.workLoop;if(loop)loop->retain();IOLockUnlock(c.lock); + if(!loop)return false; + const bool inGate=reinterpret_cast(adlnWorkLoopInGate)(loop); + const bool acquired=!inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); + if(!inGate && !acquired){loop->release();return false;} + uint64_t record[14] {1};IOLockLock(c.lock); + auto controller=static_cast(c.controller); + auto port=c.dsiPort; + auto plane=Display13FBMemory::field(path,0x32C8); + auto scaler=Display13FBMemory::field(path,0x32C0); + auto planeScaler=plane && plane->getMetaClass()->getClassSize()>=0x1C8 ? Display13FBMemory::field(plane,0x88) : nullptr; + auto pipeMap=*reinterpret_cast(dsiPipeMap); + const auto scalerOwned=[&](OSObject *o) { + return !o || (o->getMetaClass()->getClassSize()>=0x58 && + Display13FBMemory::field(o,0x10)==fb && Display13FBMemory::field(o,0x20)==1 && + Display13FBMemory::field(o,0x18)==0); + }; + bool planeTailEmpty=true;for(unsigned i=1;i<5;++i)if(Display13FBMemory::field(path,0x32C8+8*i))planeTailEmpty=false; + const auto attached=Display13FBMemory::field(fb,0x4A00); + const auto irqIdle=[&]() { + auto &irq=display13FBIRQ; + if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || !adlnIRQOwner(controller) || !irq.lock)return false; + const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const bool idle=!irq.state.faulted && !irq.quiescing && !irq.blockedPipes && + !(irq.state.active&1) && !(irq.admittedPipes&1) && irq.scopes.total==irq.handlers && + !irq.scopes.display && !irq.softwareHandlers && + !(__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE08),__ATOMIC_ACQUIRE)&Display13FBIRQ::pipeEvents(0)) && + !__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE0C),__ATOMIC_ACQUIRE) && + !(__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE10),__ATOMIC_ACQUIRE)&3U); + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return idle; + }; + const bool irqBefore=irqIdle();record[12]=irqBefore; + const bool ready=irqBefore && !c.dsiNativeWriters && adlnOutputWiring.dsi==1 && controller && controller==adlnTopologyController && + controller->getMetaClass()->getClassSize()>=0x1C20 && c.workLoop==loop && + Display13FBMemory::field(controller,0xE00)==loop && + Display13FBMemory::field(fb,0x1D0)==controller && + Display13FBMemory::field(path,0x32B0)==fb && + Display13FBMemory::field(path,0x3640)==0 && Display13FBMemory::field(path,0x3644)==1 && + c.pipe[0].memoryFramebuffer==fb && c.pipe[0].destroyingPath && !c.pipe[0].publishedScanout && !c.pipe[0].scanout.record && + !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && + !(c.dbuf.selected&1) && !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired && + dsiStop.complete() && dsiClockStop.complete() && dsiIRQParked && dsiDDIReleased && dsiMemoryReleased && !dsiPGReleased && + c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && c.bridge.bootPort==0 && + !c.bridge.bootPGProtected && c.bridge.pgRefs[0]==1 && !c.bridge.ddiRefs[0] && + Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && + port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(path,0x3648)==port && + Display13FBMemory::field(port,0x550)==1 && Display13FBMemory::field(port,0x554)<=3 && + !Display13FBMemory::field(path,0x32B8) && planeTailEmpty && + plane && plane->getMetaClass()->getClassSize()>=0x1C8 && Display13FBMemory::field(plane,0x68)==fb && + Display13FBMemory::field(plane,0x70)==path && Display13FBMemory::field(plane,0x78)==0 && + scalerOwned(scaler) && scalerOwned(planeScaler) && + Display13FBMemory::field(fb,0x49E8)==1 && Display13FBMemory::field(fb,0x49F0)==path && + attached<=1 && Display13FBMemory::field(fb,0x4A08)==(attached ? path : nullptr) && pipeMap && pipeMap[0]==fb; + record[1]=ready;record[2]=c.bridge.pgRefs[0];record[3]=c.dbuf.selected;record[4]=attached; + auto power=c.power;IOLockUnlock(c.lock); + const bool put=ready && power && adlnPowerReference(power,2,1); + IOLockLock(c.lock); + if(put)dsiPGReleased=c.power==power && c.controller==controller && !c.bridge.state.faulted && + !c.bridge.pgRefs[0] && !c.bridge.pgLease[0].owner; + record[5]=put;record[6]=dsiPGReleased;record[7]=c.bridge.pgRefs[0]; + bool removed=false;const bool irqAfter=put && irqIdle();record[13]=irqAfter; + if(irqAfter && dsiPGReleased && !c.memoryFaulted && !c.dbuf.faulted && !c.clockFaulted && + c.pipe[0].memoryFramebuffer==fb && *reinterpret_cast(dsiPipeMap)==pipeMap && pipeMap[0]==fb) { + // These leaves only update CPU objects; no power or MMIO callback. + reinterpret_cast(dsiFreePlane)(controller,plane); + reinterpret_cast(dsiFreeScaler)(controller,scaler); + if(Display13FBMemory::field(port,0x554)==3) + *reinterpret_cast(reinterpret_cast(port)+0x554)=2; + // The normal setPortMode(0) could reset DDI. Hardware is already stopped. + if(!Display13FBMemory::field(port,0x554)) + *reinterpret_cast(reinterpret_cast(port)+0x550)=0; + *reinterpret_cast(reinterpret_cast(path)+0x3648)=nullptr; + reinterpret_cast(dsiDetachPath)(fb,path); + reinterpret_cast(dsiRemovePath)(fb,path); + const auto freedScaler=[](OSObject *o) {return !o || (!Display13FBMemory::field(o,0x10) && !Display13FBMemory::field(o,0x20));}; + removed=!Display13FBMemory::field(fb,0x49E8) && !Display13FBMemory::field(fb,0x49F0) && + !Display13FBMemory::field(fb,0x4A00) && !Display13FBMemory::field(fb,0x4A08) && !pipeMap[0] && + !Display13FBMemory::field(path,0x3644) && !Display13FBMemory::field(path,0x32B0) && + !Display13FBMemory::field(path,0x3648) && !Display13FBMemory::field(plane,0x68) && + freedScaler(scaler) && freedScaler(planeScaler); + } + record[8]=removed;record[9]=Display13FBMemory::field(fb,0x49E8); + record[10]=Display13FBMemory::field(fb,0x4A00);record[11]=Display13FBMemory::field(path,0x3644); + IOLockUnlock(c.lock); + if(removed) { + removed=releaseDSIFixedMode(fb); + if(removed) { + IOLockLock(c.lock); + removed=c.dsiPort==port; + if(removed)c.dsiPort=nullptr; + IOLockUnlock(c.lock); + if(removed)port->release(); + } + } + record[8]=removed; + if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); + loop->release();adlnPublishMemory("N97FBDSIPathDispose",0,record,sizeof(record));return removed; +} + +// Host/IRQ/PLL retirement; the native DDI reference follows the stopped clock. +// Keep the real stopped source separate from Session cleanup: it is not a full +// Session StopProof. The one-shot journal retains a partial stop for diagnosis. +bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool retain) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; + if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || + !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || + !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; + IORecursiveLockLock(mutex); + IOLockLock(c.lock); + auto loop=controller==c.controller ? c.workLoop : nullptr; + if(loop)loop->retain(); + IOLockUnlock(c.lock); + const bool boundLoop=loop && Display13FBMemory::field(controller,0xE00)==loop; + const bool inGate=boundLoop && reinterpret_cast(adlnWorkLoopInGate)(loop); + const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); + if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + uint64_t record[51] {6};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; + const auto qualified=[&]() { + if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || (dsiPendingTarget(fb) && !dsiArmedBackingOwned(fb,path)) || !dsiProfile.valid() || + controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || + Display13FBMemory::field(controller,0xE00)!=loop || + c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || + c.transcoders.dsi0Pipe!=0 || c.bridge.state.faulted || c.bridge.state.suspended || c.clockFaulted || + c.pipe[0].destroyingPath || c.dbuf.updating || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || + Display13FBMemory::field(path,0x35F0) || !adlnWriterMappingBound() || + !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || + Display13FBMemory::field(c.dsiPort,0x550)!=1 || + Display13FBMemory::field(path,0x3644)!=2 || + clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || + clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || + !c.bootDSI.sameSource(dsiBoot.source))return false; + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); + Display13FBBootIO io{c.map}; + return binding.status==TGLFBBinding::Status::Complete && binding.pipe==0 && binding.ddi==0 && + c.bridge.outputHeld(io,0,0); + }; + struct IO : Display13FBBootIO { + decltype(qualified) &owns;IOSimpleLock *irqLock;bool panel,retain; + IO(IOMemoryMap *m,decltype(qualified) &q,IOSimpleLock *l,bool p,bool r):Display13FBBootIO{m},owns(q),irqLock(l),panel(p),retain(r) {} + bool ready(){return owns();} + void write(uint32_t reg,uint32_t value) { + if(readable(reg) && (reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || + reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000 || + reg==0x46010+4*display13FBPower.bootDSI.identity.pll || + (retain && XELP::DSI::Restart::writable(reg,display13FBPower.bootDSI.identity.pll)) || + (panel && (reg==0x6B100 || reg==0x6B104 || reg==0xC4030 || reg==0xC7204)))) + *reinterpret_cast(map->getVirtualAddress()+reg)=value; + } + bool gpio(unsigned role,unsigned value) { + if(!panel || !irqLock)return false; + const auto saved=IOSimpleLockLockDisableInterrupt(irqLock); + const auto r=XELP::DSI::Panel::gpio(*this,role,value); + IOSimpleLockUnlockEnableInterrupt(irqLock,saved);return r.complete; + } + void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} + } io{c.map,qualified,irq.lock,dsiStopTest || dsiPowerTest || retain,retain}; + auto &power=dsiRuntimePower; + record[1]=qualified(); + bool snapshot=!retain; + if(retain && record[1] && !dsiStopAttempted && !power.sleeping && !c.memoryFaulted && !c.dbuf.faulted && !c.watermarkFaulted && + dsiPublishedTarget(fb) && dsiPendingBacking.modeReady && dsiArmedBackingOwned(fb,path)) { + auto &p=power.plane; + p.control=io.read(0x70180);p.surface=io.read(0x7019C);p.stride=io.read(0x70188);p.size=io.read(0x70190); + p.position=io.read(0x7018C);p.offset=io.read(0x701A4);p.color=io.read(0x701CC); + power.cursorControl=io.read(0x70080);power.cursorPosition=io.read(0x70088);power.cursorBase=io.read(0x70084); + // The published aperture is not the current native flip surface. + // As in NativeCache::readActive, retain the native mode/display pipe + // and its transaction ownership; do not free/rebind it or substitute + // the initial backing. Writers are excluded through wake. Restore only + // the latched native plane image, with the supported fixed geometry. + snapshot=XELP::DSI::Surface::shape(p,dsiProfile) && XELP::DSI::Surface::matches(io,p,true) && + power.cursorControl!=UINT32_MAX && power.cursorBase!=UINT32_MAX && power.cursorPosition!=UINT32_MAX && + Display13Cursor::controlWrite(0x70080,power.cursorControl)==power.cursorControl; + if(snapshot){power.host=XELP::DSI::Restart::State{};power.panel=XELP::DSI::Panel::Result{};dsiPanelStop={};dsiStop={};} + } + record[1]=record[1] && snapshot; + if(record[1] && !dsiStopAttempted && adlnIRQOwner(controller) && irq.lock && + irq.lock==Display13FBMemory::field(controller,0x90) && irq.base==c.map->getVirtualAddress()) { + Display13FBIRQIO irqIO{irq.base}; + auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto queued=[&](unsigned offset) { + return __atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+offset),__ATOMIC_ACQUIRE); + }; + master=irqIO.read(Display13FBIRQ::Master);enabled=irq.state.enabled; + record[2]=irq.handlers;record[3]=irq.scopes.display;record[4]=irq.softwareHandlers; + record[5]=queued(0xE08);record[6]=queued(0xE0C);record[7]=queued(0xE10); + const bool quiet=irq.scopes.total==irq.handlers && !irq.scopes.display && !irq.softwareHandlers && + !(record[5]&Display13FBIRQ::pipeEvents(0)) && !record[6] && !(record[7]&3); + const bool ready=quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && + irq.state.begun && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && + master!=UINT32_MAX && bool(master&Display13FBIRQ::Enable)==enabled; + if(ready) { + // DE permission is assigned on actual HW-handler entry. New shared + // GT-only handlers can still run but cannot consume display banks. + irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; + irqIO.write(Display13FBIRQ::Master,0); + const auto after=irqIO.read(Display13FBIRQ::Master); + maskOK=after!=UINT32_MAX && !(after&Display13FBIRQ::Enable); + if(!maskOK)irq.state.faulted=true; + } + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + if(maskOK) { + dsiStopAttempted=true; + if(!retain)dsiPanelStop={}; + if(dsiStopTest || dsiPowerTest || retain)dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile,dsiPanelStop); + else dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile); + } + if(masked) { + saved=IOSimpleLockLockDisableInterrupt(irq.lock); + record[8]=irq.handlers;record[9]=irq.scopes.display;record[10]=irq.softwareHandlers; + record[11]=queued(0xE08);record[12]=queued(0xE0C);record[13]=queued(0xE10); + if(dsiStop.complete() && irq.scopes.total==irq.handlers && !irq.scopes.display && + !irq.softwareHandlers && !record[12] && !(record[13]&3)) { + // Pass the real DE-permitted scope count. A GT-only stack does + // not own a display consumer, even when it spans this stop. + const auto status=Display13FBIRQ::parkStoppedPipe(irqIO,irq.state,0,irq.scopes.display, + irq.softwareHandlers,static_cast(record[11])); + record[14]=static_cast(status);parked=status==Display13FBIRQ::Status::Complete; + } + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + if(parked) { + if(retain)XELP::DSI::Restart::stopQuiesced(io,c.bootDSI,dsiProfile,dsiStop,clock.allocation, + reinterpret_cast(controller),power.host); + else dsiClockStop=XELP::DSI::Clock::retire(io,c.bootDSI,dsiStop,true,clock.allocation, + reinterpret_cast(controller)); + } + saved=IOSimpleLockLockDisableInterrupt(irq.lock); + if(!retain && dsiClockStop.complete()) { + // The host and its PLL are now stopped. Withdraw both maps at + // the same masked IRQ boundary, so an external clock client + // no longer tries to re-adopt the obsolete running DSI source. + c.transcoders.dsi0Pipe=UINT32_MAX;irq.state.transcoders=c.transcoders;c.dbuf.transcoders=c.transcoders; + __atomic_fetch_and(&irq.admittedPipes,~1U,__ATOMIC_RELEASE); + __atomic_store_n(&dsiResourcesReady,false,__ATOMIC_RELEASE); + } + irq.quiescing=false;irq.blockedPipes=0; + const auto wells=irqIO.read(Display13FBIRQ::Driver); + if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; + if(!irq.state.faulted) { + irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); + const auto after=irqIO.read(Display13FBIRQ::Master); + restored=after!=UINT32_MAX && bool(after&Display13FBIRQ::Enable)==enabled; + irq.state.enabled=restored && enabled; + if(!restored) {irq.state.faulted=true;irqIO.write(Display13FBIRQ::Master,0);} + } + adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + } + if(masked)dsiIRQParked=parked && restored; + record[15]=masked;record[16]=parked;record[17]=restored;record[18]=dsiStopAttempted; + record[19]=static_cast(dsiStop.stage);record[20]=static_cast(dsiStop.error); + record[21]=dsiStop.writes;record[22]=dsiStop.waitUs;record[23]=dsiIRQParked; + record[24]=static_cast(dsiClockStop.status);record[25]=dsiClockStop.reg;record[26]=dsiClockStop.after; + record[27]=dsiClockStop.writes;record[28]=dsiClockStop.waitUs;record[29]=dsiClockStop.complete(); + // The donor's disableClocks(port,peer) puts this DDI reference at 6db04; + // PG1 belongs to freeDisplayPathResources and must remain until memory / + // DDB / path destruction. Match that ownership without running DDI/DP code. + const bool stopped=!retain && parked && restored && dsiClockStop.complete(); + record[30]=c.bridge.ddiRefs[0]; + if(stopped && c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && + c.bridge.bootPort==0 && !c.bridge.bootDDIProtected && c.bridge.ddiRefs[0]==1 && c.power) { + auto power=c.power; + IOLockUnlock(c.lock);const bool put=adlnPowerReference(power,4,0);IOLockLock(c.lock); + dsiDDIReleased=put && c.power==power && !c.bridge.ddiRefs[0] && !c.bridge.ddiLease[0].owner; + } + record[31]=c.bridge.ddiRefs[0];record[32]=dsiDDIReleased; + const bool stoppedPath=stopped && dsiDDIReleased; + if(stoppedPath && !c.memoryFaulted && !c.dbuf.faulted) { + // Keep the real FB mutex and workloop gate while the shared native + // retirement path acquires its own allocator serializer. Neither the + // Combo mutex nor the owner lock may span native allocator callbacks. + // PG1 remains held for PTE/DDB validation; the stopped DSI PLL is free. + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + const bool released=retireDSINativeBacking(fb,path) && adlnFreeStoppedPipe(fb); + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + Display13FBDBUFIO dbufIO{c.map}; + // A native no-resource return can precede path destruction. Retire + // that path's DDB consumers too, even if no allocator call was needed. + const auto dbufStatus=released && !c.memoryFaulted ? Display13DBUF::stopPipe(dbufIO,c.dbuf,0) : Display13DBUF::Status::Unavailable; + record[35]=released;record[36]=static_cast(dbufStatus); + const bool dbufStopped=dbufStatus==Display13DBUF::Status::Complete; + bool irqRestored=false; + if(released && irq.controller==controller && irq.lock && irq.base) { + Display13FBIRQIO irqIO{irq.base};const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto wells=irqIO.read(Display13FBIRQ::Driver); + const bool powered=wells!=UINT32_MAX && (wells&3)==3; + const auto after=powered ? irqIO.read(Display13FBIRQ::Master) : UINT32_MAX; + irqRestored=!irq.state.faulted && !irq.quiescing && !irq.blockedPipes && !(irq.state.active&1) && + after!=UINT32_MAX && bool(after&Display13FBIRQ::Enable)==irq.state.enabled; + if(!irqRestored){irq.state.faulted=true;irq.state.enabled=false;if(powered)irqIO.write(Display13FBIRQ::Master,0);} + adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + } + record[37]=irqRestored; + dsiMemoryReleased=dbufStopped && irqRestored && c.controller==controller && c.pipe[0].memoryFramebuffer==fb && + Display13FBMemory::singlePath(fb)==path && !c.memoryFaulted && + Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && + !c.pipe[0].scanout.record && !__atomic_load_n(&c.pipe[0].publishedScanout,__ATOMIC_ACQUIRE) && + !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired; + } + record[39]=dsiStop.reg;record[40]=dsiStop.value; + record[41]=dsiPanelStop.stage;record[42]=dsiPanelStop.complete; + record[43]=static_cast(dsiPanelStop.host.error);record[44]=dsiPanelStop.host.sequenceId; + record[45]=dsiPanelStop.host.operationOffset;record[46]=dsiPanelStop.host.headers; + record[47]=dsiPanelStop.host.payloadWords;record[48]=dsiPanelStop.host.completedOperations; + record[49]=dsiPanelStop.host.waitedUs;record[50]=dsiPanelStop.host.pending; + record[33]=dsiMemoryReleased;record[34]=c.memoryFaulted; + const bool complete=retain ? (parked && restored && power.host.stopped && !power.host.started && + power.host.error==XELP::DSI::Restart::Error::None && dsiPanelStop.complete && qualified() && + dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); + if(retain && complete)power.sleeping=true; + if(complete && !retain) { + // Exact donor disableDisplay publishes path state 1 only after its + // clock-disable call (4dbba -> 4dbc6). Mirror that CPU completion now, + // never on a partial host/IRQ/PLL stop or by replaying the donor body. + *reinterpret_cast(reinterpret_cast(path)+0x3644)=1; + *reinterpret_cast(reinterpret_cast(path)+0x170)=0; + *reinterpret_cast(reinterpret_cast(fb)+0x4270)=0; + if(dsiModeTarget(fb)){dsiFixedMode.ready=false;modeWithdrawn=true;} + } + // Native backing and the GOP pool have been retired before a full stop + // leaves PG/path disposal to releaseDisplay13Path; only partial stops + // need the global retention guard. Never clear a pre-existing real fault. + if(masked && !complete){if(retain)power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(modeWithdrawn)fb->setProperty("WEXDSIFixedModeReady",false); + if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); + loop->release();IORecursiveLockUnlock(mutex); + adlnPublishMemory("N97FBDSIHostStop",0,record,sizeof(record)); + if(retain) { + const auto &h=power.host; + uint64_t receipt[] {1,0,complete,power.cycles,power.sleeping,snapshot, + power.plane.control,power.plane.surface,power.cursorControl,power.cursorBase, + static_cast(h.stage),static_cast(h.error),h.reg,h.value,h.writes,h.waitUs, + parked,restored,dsiPanelStop.complete,dsiMemoryReleased,dsiDDIReleased}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); + } + return complete; +} + +// Runtime display wake keeps the native scanout allocation and every display +// lease from the matching off transaction. System power loss is not covered. +bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { + auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ;auto &power=dsiRuntimePower; + if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || + !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || + !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; + IORecursiveLockLock(mutex);IOLockLock(c.lock); + auto loop=controller==c.controller ? c.workLoop : nullptr;if(loop)loop->retain();IOLockUnlock(c.lock); + const bool boundLoop=loop && Display13FBMemory::field(controller,0xE00)==loop; + const bool inGate=boundLoop && reinterpret_cast(adlnWorkLoopInGate)(loop); + const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); + if(!inGate && !acquired){if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} + IORecursiveLockLock(clock.lock);IOLockLock(c.lock); + const auto owned=[&]() { + if(!power.sleeping || power.failed || !dsiStopAttempted || !dsiIRQParked || !dsiResourcesReady || + !dsiStop.complete() || !dsiPanelStop.complete || !dsiNativeWriterHooksReady || !dsiProfile.valid() || + controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || + Display13FBMemory::field(controller,0xE00)!=loop || adlnOutputWiring.dsi!=1 || + c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || + c.transcoders.dsi0Pipe!=0 || c.dsiNativeWriters || c.memoryFaulted || c.clockFaulted || c.watermarkFaulted || + c.bridge.state.faulted || c.bridge.state.suspended || c.dbuf.faulted || c.dbuf.updating || + c.pipe[0].destroyingPath || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || + !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || + Display13FBMemory::field(c.dsiPort,0x550)!=1 || Display13FBMemory::field(path,0x3644)!=2 || + Display13FBMemory::field(path,0x35F0) || !adlnWriterMappingBound() || + clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || + clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || !c.bootDSI.sameSource(dsiBoot.source) || + !dsiPublishedTarget(fb) || !dsiPendingBacking.modeReady || !dsiArmedBackingOwned(fb,path))return false; + Display13FBBootIO io{c.map};return c.bridge.outputHeld(io,0,0); + }; + struct IO : Display13FBBootIO { + decltype(owned) &owns;IOSimpleLock *lock;unsigned pll; + IO(IOMemoryMap *m,decltype(owned) &o,IOSimpleLock *l,unsigned p):Display13FBBootIO{m},owns(o),lock(l),pll(p) {} + bool ready(){return owns();} + void write(uint32_t r,uint32_t v) { + if(readable(r) && (XELP::DSI::Restart::writable(r,pll) || r==0x6B100 || r==0x6B104 || + r==0xC4030 || r==0xC7204 || r==0x70180 || r==0x7019C || r==0x7B008 || + r==0x70080 || r==0x70088 || r==0x70084 || r==XELP::DSI::Backlight::Duty)) + *reinterpret_cast(map->getVirtualAddress()+r)=v; + } + bool gpio(unsigned role,unsigned value) { + const auto saved=IOSimpleLockLockDisableInterrupt(lock);const auto r=XELP::DSI::Panel::gpio(*this,role,value); + IOSimpleLockUnlockEnableInterrupt(lock,saved);return r.complete; + } + void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} + } io{c.map,owned,irq.lock,dsiBoot.source.identity.pll}; + uint64_t receipt[24] {1,1,0,power.cycles,power.sleeping},irqReceipt[24] {1}; + bool masked=false,restored=false,rearmed=false,cursor=false,complete=false;uint32_t master=0; + auto stoppedPlane=power.plane;stoppedPlane.control&=~(1U<<31); + receipt[5]=owned() && power.host.stopped && !power.host.started && !power.panel.attempted && + power.host.error==XELP::DSI::Restart::Error::None && XELP::DSI::Surface::matches(io,stoppedPlane,false); + if(receipt[5] && adlnIRQOwner(controller) && irq.lock && irq.lock==Display13FBMemory::field(controller,0x90) && + irq.base==c.map->getVirtualAddress()) { + Display13FBIRQIO irqIO{irq.base};auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto queued=[&](unsigned offset) { + return __atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+offset),__ATOMIC_ACQUIRE); + }; + master=irqIO.read(Display13FBIRQ::Master); + const bool quiet=irq.scopes.total==irq.handlers && !irq.scopes.display && !irq.softwareHandlers && + !(queued(0xE08)&Display13FBIRQ::pipeEvents(0)) && !queued(0xE0C) && !(queued(0xE10)&3); + if(quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && irq.state.begun && irq.state.enabled && + !(irq.state.active&1) && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && + master!=UINT32_MAX && (master&Display13FBIRQ::Enable)) { + irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; + irqIO.write(Display13FBIRQ::Master,0); + const auto after=irqIO.read(Display13FBIRQ::Master); + if(after==UINT32_MAX || (after&Display13FBIRQ::Enable))irq.state.faulted=true; + } + const bool canStart=masked && !irq.state.faulted; + IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + if(canStart) { + struct Lifecycle { + XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state; + bool stop(IO &,const XELP::DSI::Readout &,const XELP::DSI::Profile &){return false;} + bool start(IO &io,XELP::DSI::Readout &source) { + if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; + source=XELP::DSI::Restart::expected(source,state);return source.complete(); + } + } lifecycle{clock.allocation,reinterpret_cast(controller),power.host}; + XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop, + dsiBacklight.initialized?&dsiBacklight:nullptr); + if(power.panel.complete() && owned() && XELP::DSI::Surface::matches(io,power.plane,true)) { + io.write(0x70080,power.cursorControl);io.write(0x70088,power.cursorPosition);io.write(0x70084,power.cursorBase); + const auto before=io.read(0x70040); + for(unsigned us=0;us<=50000;us+=1000) { + const auto after=io.read(0x70040); + if(before!=UINT32_MAX && after!=UINT32_MAX && after!=before) { + cursor=XELP::DSI::Surface::word(io,0x70080,UINT32_MAX,power.cursorControl) && + XELP::DSI::Surface::word(io,0x70088,UINT32_MAX,power.cursorPosition) && + XELP::DSI::Surface::word(io,0x70084,UINT32_MAX,power.cursorBase);break; + } + if(us<50000)io.delayUs(1000); + } + if(cursor){dsiBoot.source=power.panel.source;c.bootDSI=power.panel.source;} + } + } + if(masked) { + saved=IOSimpleLockLockDisableInterrupt(irq.lock); + const auto wells=irqIO.read(Display13FBIRQ::Driver); + if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; + if(!irq.state.faulted) { + irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); + const auto after=irqIO.read(Display13FBIRQ::Master); + restored=after!=UINT32_MAX && (after&Display13FBIRQ::Enable);irq.state.enabled=restored; + if(!restored)irq.state.faulted=true; + if(restored && cursor && owned()) { + const auto actual=Display13FBIRQ::readActivePipes(irqIO,irq.admittedPipes,irq.state.transcoders); + irqReceipt[1]=irq.admittedPipes;irqReceipt[2]=irq.state.active;irqReceipt[3]=actual.active; + irqReceipt[4]=static_cast(actual.status);irqReceipt[5]=wells; + for(unsigned p=0;p<3;++p)if(Display13FBIRQ::powered(wells,p)) { + irqReceipt[6+3*p]=Display13Transcoder::control(irqIO,p,irq.state.transcoders); + irqReceipt[7+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)+4); + irqReceipt[8+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)-4); + } + const auto status=Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes); + irqReceipt[15]=static_cast(status);irqReceipt[16]=irq.state.active; + irqReceipt[17]=static_cast(irq.state.firstFailure); + irqReceipt[18]=irq.state.failureRegister;irqReceipt[19]=irq.state.failureBits; + irqReceipt[20]=irq.state.enabled;irqReceipt[21]=irq.state.faulted; + rearmed=status==Display13FBIRQ::Status::Complete && (irq.state.active&1); + } + } + irq.quiescing=false;irq.blockedPipes=0; + adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); + complete=restored && rearmed && cursor && power.panel.complete() && owned(); + if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;} + else{power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} + } + } + const auto &h=power.host;const auto &p=power.panel; + receipt[2]=complete;receipt[3]=power.cycles;receipt[4]=power.sleeping; + receipt[6]=power.plane.control;receipt[7]=power.plane.surface;receipt[8]=power.cursorControl;receipt[9]=power.cursorBase; + receipt[10]=static_cast(h.stage);receipt[11]=static_cast(h.error); + receipt[12]=h.reg;receipt[13]=h.value;receipt[14]=h.writes;receipt[15]=h.waitUs; + receipt[16]=masked;receipt[17]=restored;receipt[18]=rearmed;receipt[19]=cursor; + receipt[20]=static_cast(p.stage);receipt[21]=static_cast(p.error);receipt[22]=p.reg;receipt[23]=p.value; + IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); + if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); + loop->release();IORecursiveLockUnlock(mutex); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); + publishDSIBacklight(); + return complete; +} diff --git a/WhateverXe/kern_dsi_adapter_modes.inc b/WhateverXe/kern_dsi_adapter_modes.inc new file mode 100644 index 00000000..d3a7c22a --- /dev/null +++ b/WhateverXe/kern_dsi_adapter_modes.inc @@ -0,0 +1,552 @@ +// Apple fixed-link mode adapter and native GPU presentation transactions. +// Rotation/scaling remain Apple operations; this is their DSI attachment. +// Included only by kern_xe.cpp; retains its private owners and lock ordering. + +// Pipe A belongs to DSI for this adoption phase. Malformed/ambiguous objects +// must not fall into a normal-transcoder read or the donor PWM calculation. +static bool dsiCachePath(OSObject *path) { + if(!path || path->getMetaClass()->getClassSize()<0x3730)return true; + const auto pipe=Display13FBMemory::field(path,0x3640); + if(pipe==0 || pipe>=3)return true; + const auto port=Display13FBMemory::field(path,0x3648); + return !port || port->getMetaClass()->getClassSize()<0x580 || Display13FBMemory::field(port,0x120)==0; +} + +// Called at the existing mode/disable owners, before any donor notification or +// cursor/MMIO write. A retained panel FB remains ours even if its path fields +// are damaged. Ambiguous objects must not escape into the normal DDI writer. +static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { + if(dsiModeTarget(fb) || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return true; + const auto attached=Display13FBMemory::singlePath(fb); + return attached!=path || dsiCachePath(attached); +} + +bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *symbols[]={ + "__ZN21AppleIntelFramebuffer18setDetailedTimingsEP7OSArray", + "__ZN21AppleIntelFramebuffer19validateDisplayModeEiPPKNS_15ModeDescriptionEPPK29IODetailedTimingInformationV2", + "__ZN21AppleIntelFramebuffer15getDisplayModesEPi", + "__ZN21AppleIntelFramebuffer19getDisplayModeCountEv", + "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation", + "__ZN21AppleIntelFramebuffer14setDisplayModeEii", + "__ZN21AppleIntelFramebuffer21getCurrentDisplayModeEPiS0_", + "__ZN21AppleIntelFramebuffer13getOnlineInfoEP21AppleIntelDisplayPathPhS2_", + "__ZN21AppleIntelFramebuffer13fbGetDDCBlockEjPhPyjP21AppleIntelDisplayPath"}; + const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10,0x32A88,0x2E232,0x2F1B4}; + const uint8_t entries[][16]={ + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xF6,0x48,0x89,0xFB,0x48,0xFF,0x05}, + {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0x98,0x93,0x0A,0x00,0x83,0xFE}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x8F,0x84,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, + {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xE1,0x79,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x44,0x89}}; + if(!display13FramebufferRequested || size<=0x32D14)return false; + for(unsigned i=0;i(index,symbols[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; + } + const char *geometry="__ZN21AppleIntelFramebuffer32computeTransformAndSetDimensionsEPK29IODetailedTimingInformationV2"; + const auto compute=patcher.solveSymbol(index,geometry,address,size);patcher.clearError(); + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}; + if(compute!=address+0x31484 || memcmp(reinterpret_cast(compute),entry,sizeof(entry)))return false; + dsiComputeGeometry=compute; + const char *modeSymbols[]={"__ZN21AppleIntelFramebuffer21allocateLogicalBufferEv", + "__ZN21AppleIntelFramebuffer28setupInitialTransactionStateEj", + "__ZN21AppleIntelFramebuffer18setTransactionCapsEv", + "__ZN21AppleIntelFramebuffer20releaseLogicalBufferEv"}; + const uint32_t modeOffsets[]={0x31726,0x318E8,0x319D2,0x33062}; + for(unsigned i=0;i(index,modeSymbols[i],address,size);patcher.clearError(); + if(fn!=address+modeOffsets[i] || memcmp(reinterpret_cast(fn),entry,4))return false; + dsiModeCPU[i]=fn; + } + const char *scaler="__ZN16AppleIntelScaler19isSupportedByScalerEjjjjyjb"; + const auto scalerFn=patcher.solveSymbol(index,scaler,address,size);patcher.clearError(); + const uint8_t scalerEntry[]={0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xD7,0xD7,0x07,0x00,0x48,0x8B,0x47,0x10}; + if(size<=0x6686D || scalerFn!=address+0x6685E || + memcmp(reinterpret_cast(scalerFn),scalerEntry,sizeof(scalerEntry)))return false; + const char *detailed="__ZN21AppleIntelFramebuffer22validateDetailedTimingEPvy"; + const char *source="__ZN21AppleIntelFramebuffer18ValidateSourceSizeEP29IODetailedTimingInformationV2"; + const auto detailedFn=patcher.solveSymbol(index,detailed,address,size);patcher.clearError(); + const auto sourceFn=patcher.solveSymbol(index,source,address,size);patcher.clearError(); + const uint8_t detailedEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}; + const uint8_t sourceEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x20}; + if(detailedFn!=address+0x2FFCA || sourceFn!=address+0x30AD8 || + memcmp(reinterpret_cast(detailedFn),detailedEntry,sizeof(detailedEntry)) || + memcmp(reinterpret_cast(sourceFn),sourceEntry,sizeof(sourceEntry)))return false; + const char *pixel="__ZN21AppleIntelFramebuffer19getPixelInformationEiiiP18IOPixelInformation"; + const auto pixelFn=patcher.solveSymbol(index,pixel,address,size);patcher.clearError(); + if(pixelFn!=address+0x324A4 || memcmp(reinterpret_cast(pixelFn),entry,sizeof(entry)))return false; + KernelPatcher::RouteRequest routes[]={ + {pixel,wrapDSIPixelInfo,orgDSIPixelInfo}, + {detailed,wrapDSIValidateDetailed,orgDSIDetailedValidation}, + {source,wrapDSIValidateSource,orgDSISourceValidation}, + {scaler,wrapDSIScalerSupport,orgDSIScalerSupport}, + {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, + {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, + {symbols[2],wrapDSIGetModes,orgDSIMode[2]}, + {symbols[3],wrapDSIGetModeCount,orgDSIMode[3]}, + {symbols[4],wrapDSIGetModeInformation,orgDSIMode[4]}, + {symbols[5],wrapDSISetDisplayMode,orgDSIMode[5]}, + {symbols[6],wrapDSIGetCurrentMode,orgDSIMode[6]}, + {symbols[7],wrapDSIGetOnlineInfo,orgDSIMode[7]}, + {symbols[8],wrapDSIDDC,orgDSIMode[8]}}; + const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size); + patcher.clearError();return ready; +} + +// Bounded, copied CPU evidence from the real FB0 client callbacks. Also +// observe calls before the fixed-mode owner is bound; do not treat the trace +// as admission or invoke any driver/hardware operation while collecting it. +void XE::recordDSIClient(IOService *fb,unsigned event,uint64_t argument,uint32_t result) { + if(!callbackXE->adlnOutputWiring.dsi || event>=13 || !fb || + fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1DC))return; + DSIModeScope scope(fb);if(!scope.lock)return; + static uint64_t counts[13] {}; + const char *keys[] {"WEXDSIClientBind","WEXDSIClientTimings","WEXDSIClientValidate", + "WEXDSIClientModes","WEXDSIClientCount","WEXDSIClientInfo","WEXDSIClientSetMode", + "WEXDSIClientAttribute","WEXDSIClientConnection","WEXDSIClientPower", + "WEXDSIClientCurrent","WEXDSIClientOnline","WEXDSIClientDDC"}; + const auto count=__atomic_add_fetch(&counts[event],1ULL,__ATOMIC_RELAXED); + uint64_t words[] {1,event,count,argument,result, + Display13FBMemory::field(fb,0x1E0),Display13FBMemory::field(fb,0x44DE), + Display13FBMemory::field(fb,0x1E7),Display13FBMemory::field(fb,0x4250), + Display13FBMemory::field(fb,0x4254),Display13FBMemory::field(fb,0x4284), + Display13FBMemory::field(fb,0x420C),dsiModeTarget(fb), + dsiModeTarget(fb) && dsiFixedMode.ready,Display13FBMemory::field(fb,0x4260)!=nullptr, + Display13FBMemory::field(fb,0x4348)!=nullptr}; + IORegistryEntry::getRegistryRoot()->setProperty(keys[event],words,sizeof(words)); +} + +bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { + auto &c=display13FBPower;auto &o=dsiFixedMode; + if(!display13DSIModeHooksReady || adlnOutputWiring.dsi!=1 || !c.lock || !fb || + fb->getMetaClass()->getClassSize()<0x4B90 || !profile.valid())return false; + DSIModeScope scope(fb);if(!scope.lock)return false; + XELP::DSI::AppleAdapter::Timing timing; + if(!XELP::DSI::AppleAdapter::makeTiming(profile,timing))return false; + if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return dsiModeTarget(fb) && dsiModeCoherent(fb) && + !memcmp(&timing,&o.timing,sizeof(timing)); + auto data=OSData::withBytes(&timing,sizeof(timing));auto array=OSArray::withCapacity(1); + if(!data || !array || !array->setObject(data)) {OSSafeReleaseNULL(data);OSSafeReleaseNULL(array);return false;} + IOLockLock(c.lock); + const auto path=Display13FBMemory::singlePath(fb); + const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); + const bool bound=!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE) && + c.controller==adlnTopologyController && binding.status==TGLFBBinding::Status::Complete && + binding.ddi==0 && c.transcoders.dsi(binding.pipe) && c.bootDSI.matches(profile) && + c.bootDSI.identity.pipe==binding.pipe && !c.memoryFaulted && !c.bridge.state.faulted; + if(bound) { + o.array=array;o.data=data;o.timing=timing;o.mode=profile.mode;o.ready=false; + fb->retain();__atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); + } + IOLockUnlock(c.lock); + if(!bound) {data->release();array->release();return false;} + // The donor ignores setProperty's bool result. Retain our own array/data + // before calling it, then verify both the raw pointer and registry owner. + const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,array); + o.ready=result==kIOReturnSuccess; + o.ready=dsiModeCoherent(fb); + fb->setProperty("WEXDSIFixedModeReady",o.ready); + recordDSIClient(fb,0,XELP::DSI::AppleAdapter::FixedModeID,o.ready); + return o.ready; +} + +// Called with the FB mutex and provider gate held, after stopped path/PG +// disposal. The donor's qualified null setter removes only the timing property +// and its raw +4260 pointer; it performs no display or allocator operation. +bool XE::releaseDSIFixedMode(IOService *fb) { + auto &o=dsiFixedMode; + if(!o.framebuffer)return true; + if(!dsiModeTarget(fb) || o.ready || o.logical || !orgDSIMode[0])return false; + auto raw=Display13FBMemory::field(fb,0x4260); + auto property=fb->getProperty("IOFBDetailedTimings"); + if((raw && raw!=o.array) || (property && property!=o.array))return false; + const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,nullptr); + if(result!=kIOReturnSuccess || Display13FBMemory::field(fb,0x4260) || + fb->getProperty("IOFBDetailedTimings"))return false; + auto array=o.array;auto data=o.data;auto offered=o.offeredData; + __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); + o.array=nullptr;o.data=nullptr;o.offeredData=nullptr;o.offered=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; + o.presentation={};o.requested={};o.offerCount=0;o.activeMode=o.clientMode;o.presentationReady=false;o.presentationFault=false;o.changingThread=0; + OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);OSSafeReleaseNULL(offered);fb->release();return true; +} + +// IOGraphics also requests EDID independently of online detection. Stop at +// the shared internal DDC entry, before it acquires AUX power on behalf of +// the built-in panel. Supply one synthetic profile descriptor from CPU memory. +uint32_t XE::wrapDSIDDC(IOService *fb,uint32_t block,uint8_t *data,uint64_t *length,uint32_t flags,OSObject *path) { + if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); + const auto result=[&]() -> uint32_t { + if(!length || !data)return kIOReturnBadArgument; + const auto capacity=*length;*length=0; + if(block!=1 || flags)return kIOReturnUnsupported; + if(capacity<128)return kIOReturnBadArgument; + DSIModeScope scope(fb); + if(!scope.lock || !dsiModeTarget(fb) || !dsiModeCoherent(fb) || + (path && path!=Display13FBMemory::singlePath(fb)))return kIOReturnNotReady; + uint8_t edid[128]; + if(!XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,edid))return kIOReturnNotReady; + memcpy(data,edid,sizeof(edid));*length=sizeof(edid);return kIOReturnSuccess; + }(); + recordDSIClient(fb,12,block,result);return result; +} + +// Native online detection assumes eDP AUX/EDID. A DSI panel has no such +// sink. Answer only for the real adopted path and its immutable fixed mode; +// its synthetic profile descriptor is supplied without touching AUX power. +uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,uint8_t *edid) { + if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(Display13FBMemory::field(fb,0x1DC)) { + // Explicit development isolation; leave physical wiring and the + // ordinary external-output backend intact for coexistence testing. + if(callbackXE->dsiPanelOnly) { + if(!online || !edid)return kIOReturnBadArgument; + *online=0;bzero(edid,128);return kIOReturnSuccess; + } + return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); + } + if(!online || !edid)return kIOReturnBadArgument; + *online=0;bzero(edid,128); + DSIModeScope scope(fb); + const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && + path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); + uint8_t descriptor[128]; + const bool described=ready && XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,descriptor); + if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));dsiAdvertisePresentation(fb);} + const auto result=described ? kIOReturnSuccess : kIOReturnNotReady; + recordDSIClient(fb,11,*online,result);return result; +} + +// The donor initializes current mode to -5, then tries to recover it through +// normal-transcoder timing conversion. DSI already has a qualified GOP and +// fixed timing. Publish that same current mode under the native FB mutex. +uint32_t XE::wrapDSIGetCurrentMode(IOService *fb,int32_t *mode,int32_t *depth) { + if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); + DSIModeScope scope(fb); + const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && + Display13FBMemory::field(fb,0x1E0) && + ((dsiFixedMode.changingThread && dsiFixedMode.changingThread==reinterpret_cast(current_thread())) || dsiNativeUpdateAllowed(fb)); + if(ready) { + const uint32_t fixed=dsiFixedMode.presentationReady ? dsiFixedMode.activeMode : dsiFixedMode.clientMode; + memcpy(reinterpret_cast(fb)+0x4250,&fixed,sizeof(fixed)); + if(mode)*mode=fixed; + if(depth)*depth=Display13FBMemory::field(fb,0x4254); + } + const auto result=ready ? kIOReturnSuccess : kIOReturnNotReady; + recordDSIClient(fb,10,ready ? dsiFixedMode.clientMode : 0,result);return result; +} + +uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { + recordDSIClient(fb,1,array!=nullptr,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + uint32_t client=0;unsigned count=0; + uint64_t offer[16] {3,array ? array->getCount() : 0,UINT32_MAX}; + if(array)for(unsigned i=0;igetCount();++i) { + auto bytes=OSDynamicCast(OSData,array->getObject(i)); + if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; + const auto &t=*static_cast(bytes->getBytesNoCopy()); + const bool native=XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i); + if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { + // Keep the last rejected OS request across later successful + // enumeration calls, so clock/geometry admission is observable. + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingRejected",bytes); + continue; + } + ++count;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; + offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; + offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; + offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled; + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingOffer",offer,sizeof(offer)); + auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; + const bool canonical=data && data->getLength()==sizeof(dsiFixedMode.timing) && + !memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)); + if(!count && !canonical)return kIOReturnUnsupported; + // Size storage from the accepted OS list, not a fixed number of modes. + // Prepare the complete replacement before touching native state so an + // allocation failure preserves the previous list and its timing pointers. + OSData *replacement=nullptr; + if(count) { + if(count>UINT32_MAX/sizeof(dsiFixedMode.timing))return kIOReturnNoResources; + replacement=OSData::withCapacity(count*sizeof(dsiFixedMode.timing)); + if(!replacement)return kIOReturnNoResources; + for(unsigned i=0;igetCount();++i) { + auto bytes=OSDynamicCast(OSData,array->getObject(i)); + if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; + const auto &t=*static_cast(bytes->getBytesNoCopy()); + if(XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i) || + XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { + if(!replacement->appendBytes(&t,sizeof(t))) { + replacement->release();return kIOReturnNoResources; + } + } + } + } + // Native storage retains the immutable wire mode. Qualified presentation + // timings are returned by validation, without changing the DSI link. + const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); + if(result==kIOReturnSuccess && dsiModeCoherent(fb)) { + auto previous=dsiFixedMode.offeredData; + dsiFixedMode.offeredData=replacement; + dsiFixedMode.offered=replacement ? static_cast(replacement->getBytesNoCopy()) : nullptr; + dsiFixedMode.offerCount=count; + if(replacement)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingList",replacement); + else IORegistryEntry::getRegistryRoot()->removeProperty("WEXDSITimingList"); + if(client)dsiFixedMode.clientMode=client; + OSSafeReleaseNULL(previous); + } else { + OSSafeReleaseNULL(replacement); + if(result==kIOReturnSuccess)return kIOReturnNotReady; + } + return result; + }(); + recordDSIClient(fb,1,array!=nullptr,result); + return result; +} + +uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **description,const void **timing) { + recordDSIClient(fb,2,static_cast(mode),UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); + if(description)*description=nullptr;if(timing)*timing=nullptr; + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + const auto selected=dsiPresentation(mode);if(!selected)return kIOReturnBadArgument; + const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])( + fb,XELP::DSI::AppleAdapter::FixedModeID,description,timing); + if(!result && timing) { + if(*timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; + if(selected->scalerFlags || selected->horizontalScaled || selected->verticalScaled)*timing=selected; + } + return result; + }(); + recordDSIClient(fb,2,static_cast(mode),result); + return result; +} + +uint32_t XE::wrapDSIGetModes(IOService *fb,int32_t *modes) { + recordDSIClient(fb,3,modes!=nullptr,UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + if(!modes)return kIOReturnBadArgument; + if(dsiFixedMode.offerCount)for(unsigned i=0;i uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeCount,callbackXE->orgDSIMode[3])(fb); + DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? (dsiFixedMode.offerCount ? dsiFixedMode.offerCount : 1) : 0; + }(); + recordDSIClient(fb,4,0,result); + return result; +} + +uint32_t XE::wrapDSIPixelInfo(IOService *fb,int32_t mode,int32_t depth,int32_t aperture,void *info) { + const auto result=FunctionCast(wrapDSIPixelInfo,callbackXE->orgDSIPixelInfo)(fb,mode,depth,aperture,info); + if(dsiModeTarget(fb)) { + static uint32_t sequence=0;uint64_t record[28] {1,__sync_add_and_fetch(&sequence,1), + static_cast(mode),static_cast(depth),static_cast(aperture),result}; + if(info && !result)memcpy(record+6,info,172); + char key[40];snprintf(key,sizeof(key),"WEXDSIPixelCall%u",static_cast(record[1])%32); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + return result; +} + +// Observe native mode admission separately from the later published mode list. +uint32_t XE::wrapDSIValidateDetailed(IOService *fb,void *raw,uint64_t size) { + uint64_t record[60] {};const bool observe=dsiModeTarget(fb) && raw && size==204; + if(observe)memcpy(record+8,raw,204); + const auto result=FunctionCast(wrapDSIValidateDetailed,callbackXE->orgDSIDetailedValidation)(fb,raw,size); + // Match getInformationForDisplayMode: the retained scanout is RGB8. + // CoreDisplay consumes this metadata before it asks for a mode's info. + if(observe && !result)static_cast(raw)->depth=0; + if(observe) { + static uint32_t sequence=0;record[0]=1;record[1]=__sync_add_and_fetch(&sequence,1); + record[2]=result;record[3]=Display13FBMemory::field(fb,0x4318); + record[4]=Display13FBMemory::field(fb,0x431C); + memcpy(reinterpret_cast(record)+268,raw,204); + char key[40];snprintf(key,sizeof(key),"WEXDSIDetailedValidation%u",static_cast(record[1])%32); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + return result; +} + +bool XE::wrapDSIValidateSource(IOService *fb,void *raw) { + const auto result=FunctionCast(wrapDSIValidateSource,callbackXE->orgDSISourceValidation)(fb,raw); + if(dsiModeTarget(fb) && raw) { + static uint32_t sequence=0;uint64_t record[25] {1,__sync_add_and_fetch(&sequence,1),result, + Display13FBMemory::field(fb,0x4318),Display13FBMemory::field(fb,0x431C)}; + memcpy(record+5,raw,160); + char key[40];snprintf(key,sizeof(key),"WEXDSISourceValidation%u",static_cast(record[1])%32); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); + } + return result; +} + +// The adopted DSI scanout is fixed-size. It has no panel-fitter resource +// transaction, so let the donor select its existing GPU scaling path. Preserve +// the real OS timing and transform; other framebuffer owners keep native policy. +bool XE::wrapDSIScalerSupport(void *scaler,uint32_t outputW,uint32_t outputH, + uint32_t sourceW,uint32_t sourceH,uint64_t clock,uint32_t flags,bool format) { + auto fb=scaler ? Display13FBMemory::field(scaler,0x10) : nullptr; + if(dsiModeTarget(fb))return false; + return FunctionCast(wrapDSIScalerSupport,callbackXE->orgDSIScalerSupport)( + scaler,outputW,outputH,sourceW,sourceH,clock,flags,format); +} + +uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { + recordDSIClient(fb,5,static_cast(mode),UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); + DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; + const auto selected=dsiPresentation(mode);if(!raw || !selected)return kIOReturnBadArgument; + const bool rotated=(selected->scalerFlags&0x10)!=0; + const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); + if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, + *static_cast(raw),rotated,selected->horizontalScaled,selected->verticalScaled))return kIOReturnNotReady; + return result; + }(); + if(dsiModeTarget(fb)) { + DSIModeScope scope(fb); + for(unsigned i=0;scope.lock && i(mode)) { + uint32_t receipt[12] {1,static_cast(mode),result}; + if(raw && !result)memcpy(receipt+3,raw,36); + char key[40];snprintf(key,sizeof(key),"WEXDSIModeInfo%u",i); + IORegistryEntry::getRegistryRoot()->setProperty(key,receipt,sizeof(receipt));break; + } + } + recordDSIClient(fb,5,static_cast(mode),result); + return result; +} + +// Native logical storage belongs to the same GPU pipe as the retained scanout. +// Never free the scanout or modify the DSI host while changing presentation. +bool XE::releaseDSILogical(IOService *fb) { + auto &o=dsiFixedMode;auto pipe=dsiPendingBacking.pipe; + if(!dsiModeTarget(fb) || !pipe || !dsiModeCPU[3] || + Display13FBMemory::field(fb,0x4340)!=o.logical)return false; + if(!o.logical)return true; + reinterpret_cast(dsiModeCPU[3])(fb); + const bool released=!Display13FBMemory::field(fb,0x4340) && + !Display13FBMemory::field(pipe,0x12B8) && !Display13FBMemory::field(pipe,0x12C0); + if(released)__atomic_store_n(&o.logical,uintptr_t(0),__ATOMIC_RELEASE); + else o.presentationFault=true; + return released; +} + +uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::AppleAdapter::Timing &requested) { + using Display13FBMemory::field; + auto &o=dsiFixedMode;auto &c=display13FBPower; + if(!dsiModeTarget(fb) || !dsiModeCoherent(fb) || !dsiNativeUpdateAllowed(fb) || o.changingThread)return kIOReturnNotReady; + auto path=Display13FBMemory::singlePath(fb);auto pipe=dsiPendingBacking.pipe; + if(!path || !pipe || !dsiArmedBackingOwned(fb,path))return kIOReturnNotReady; + const auto old=o.presentationReady ? o.presentation : o.timing; + const uint32_t oldMode=o.presentationReady ? o.activeMode : o.clientMode; + if(requested.scalerFlags==old.scalerFlags && requested.horizontalScaled==old.horizontalScaled && + requested.verticalScaled==old.verticalScaled && requested.horizontalInset==old.horizontalInset && + requested.verticalInset==old.verticalInset) { + o.presentation=requested;o.activeMode=mode;o.presentationReady=true; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=mode; + return kIOReturnSuccess; + } + IOLockLock(c.lock); + const bool idle=!c.dsiNativeWriters && !c.pipe[0].wmPending && !c.pipe[0].wmCommitting && + !field(path,0x35F0) && !c.dbuf.updating; + IOLockUnlock(c.lock);if(!idle)return kIOReturnNotReady; + o.changingThread=reinterpret_cast(current_thread()); + uint64_t receipt[16] {1,mode,requested.scalerFlags,oldMode,old.scalerFlags}; + uint64_t gpuState[13] {1}; + const auto captureGPU=[&](unsigned base) { + const unsigned offsets[]={0x1264,0x1268,0x128C,0x1290,0x1294,0x12D8}; + if(pipe->getMetaClass()->getClassSize()>=0x12DC) + for(unsigned i=0;i(pipe,offsets[i]); + }; + captureGPU(1); + const auto apply=[&](uint32_t selected,const XELP::DSI::AppleAdapter::Timing &timing) -> bool { + if(!releaseDSILogical(fb))return false; + auto cpu=o.timing;cpu.scalerFlags=timing.scalerFlags; + cpu.horizontalScaled=timing.horizontalScaled;cpu.verticalScaled=timing.verticalScaled; + cpu.horizontalInset=timing.horizontalInset;cpu.verticalInset=timing.verticalInset; + if(!XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,cpu, + reinterpret_cast(dsiComputeGeometry),true))return false; + // The native allocator asks getCurrentDisplayMode/getPixelInformation + // through its GPU callback. Publish the selected CPU contract first, + // while other display writers remain excluded by changingThread. + o.presentation=timing;o.activeMode=selected;o.presentationReady=true; + *reinterpret_cast(reinterpret_cast(fb)+0x4250)=selected; + const auto result=reinterpret_cast(dsiModeCPU[0])(fb); + __atomic_store_n(&o.logical,field(fb,0x4340),__ATOMIC_RELEASE); + receipt[5]=result;receipt[6]=o.logical;receipt[7]=field(pipe,0x12B8);receipt[8]=field(pipe,0x12C0); + if(result)return false; // native failure can still leave a record to release + if(field(fb,0x4338)&0x30) { + auto record=reinterpret_cast(o.logical); + if(!record || record->kind!=2 || record->columns!=(field(fb,0x430C)*4+127)/128 || record->rows!=(field(fb,0x4310)+31)/32 || + record->pitch!=field(fb,0x4334) || record->bytes!=uint64_t(record->pitch)*field(fb,0x4310) || !record->memory || !record->map || + field(pipe,0x12B8)!=record->memory || field(pipe,0x12C0)!=record->map)return false; + } else if(o.logical || field(pipe,0x12B8) || field(pipe,0x12C0))return false; + reinterpret_cast(dsiModeCPU[1])(fb,0); + reinterpret_cast(dsiModeCPU[2])(fb); + return dsiArmedBackingOwned(fb,path) && dsiPublishedTarget(fb); + }; + const bool changed=apply(mode,requested); + receipt[9]=changed; + if(!changed) { + const bool restored=!o.presentationFault && apply(oldMode,old); + receipt[10]=restored;if(!restored)o.presentationFault=true; + } + receipt[11]=field(fb,0x4338);receipt[12]=field(fb,0x4348); + receipt[13]=o.presentationFault;receipt[14]=field(path,0xF8);receipt[15]=field(path,0xFC); + captureGPU(7); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentationGPU",gpuState,sizeof(gpuState)); + o.changingThread=0; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentation",receipt,sizeof(receipt)); + return changed ? kIOReturnSuccess : kIOReturnNotReady; +} + +// Native timing/resource setup can disable or reallocate before hwSetMode. +// DSI uses its own fixed-mode transaction to retain the adopted host link. +uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { + recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),UINT32_MAX); + const auto result=[&]() -> uint32_t { + if(callbackXE->adlnOutputWiring.dsi) { + if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; + if(callbackXE->dsiPanelOnly && Display13FBMemory::field(fb,0x1DC))return kIOReturnNotReady; + DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; + if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { + // A bounded receipt of the real request. Keep the donor's mode + // reallocation path closed until the DSI surface transaction owns it. + uint64_t record[] {1,Display13FBMemory::field(fb,0x1DC), + Display13FBMemory::field(fb,0x1E0),static_cast(mode), + static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, + Display13FBMemory::field(fb,0x420C)}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); + const auto selected=dsiPresentation(mode); + if(!selected || depth || !dsiModeTarget(fb))return kIOReturnNotReady; + const auto presentation=*selected; + if(dsiNativeUpdateAllowed(fb) && !dsiPendingTarget(fb))callbackXE->probeDSINativeBacking(fb); + if(!callbackXE->publishDSINativeMode(fb))return kIOReturnNotReady; + return callbackXE->setDSIPresentation(fb,mode,presentation); + } + } + return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); + }(); + recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),result); + return result; +} diff --git a/WhateverXe/kern_dsi_adapter_state.inc b/WhateverXe/kern_dsi_adapter_state.inc new file mode 100644 index 00000000..e2bb4191 --- /dev/null +++ b/WhateverXe/kern_dsi_adapter_state.inc @@ -0,0 +1,91 @@ +// DSI attachment owners and identity predicates. Included inside the +// existing anonymous namespace; no new owner, lock or initialization phase. + +static void dsiPublishIRQLive(); + +// The FB mutex serializes prepare/discard/publication. Native allocation also +// publishes GPU-pipe references before this record becomes the FB scanout. +// Keep both owners retained, including on partial failure, until proven free. +struct DSIPendingBackingOwner { + IOService *framebuffer {},*accelerator {}; + OSObject *pipe {},*pinnedMap {},*pinnedMemory {}; + XELP::DSI::NativeBacking::Record *record {}; + uintptr_t thread {}; + bool allocating {},freeing {},allocated {},freed {},armed {},published {},modeReady {}; + uint32_t nativeResult {}; + Display13FBMemory::SurfaceReadout proof; + XELP::DSI::TestImage::Result image; +}; +static DSIPendingBackingOwner dsiPendingBacking; +static bool dsiPendingTarget(IOService *fb) { + return fb && __atomic_load_n(&dsiPendingBacking.framebuffer,__ATOMIC_ACQUIRE)==fb; +} + +static bool dsiPublishedTarget(IOService *fb) { + return dsiPendingTarget(fb) && dsiPendingBacking.published && dsiPendingBacking.record && + Display13FBMemory::field(fb,0x4348)==dsiPendingBacking.record; +} + + +// One DSI0 panel. Publish only after native path/pipe association; the FB and +// immutable timing array remain retained until a quiesced DSI stop can detach +// them. A donor property failure must never leave its raw array pointer dangling. +struct DSIFixedModeOwner { + IOService *framebuffer {}; + OSArray *array {}; + OSData *data {}; + XELP::DSI::Mode mode; + XELP::DSI::AppleAdapter::Timing timing; + XELP::DSI::AppleAdapter::Timing presentation,requested; + OSData *offeredData {}; + const XELP::DSI::AppleAdapter::Timing *offered {}; + unsigned offerCount {}; + uint32_t activeMode {XELP::DSI::AppleAdapter::FixedModeID}; + uintptr_t changingThread {},logical {}; + bool presentationReady {},presentationFault {}; + uint32_t clientMode {XELP::DSI::AppleAdapter::FixedModeID}; + bool ready {}; +}; +static DSIFixedModeOwner dsiFixedMode; +static bool dsiModeTarget(IOService *fb) { + return fb && __atomic_load_n(&dsiFixedMode.framebuffer,__ATOMIC_ACQUIRE)==fb; +} +static uintptr_t dsiLogicalSurface(const void *fb) { + const auto &o=dsiFixedMode; + return fb && __atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)==fb ? + __atomic_load_n(&o.logical,__ATOMIC_ACQUIRE) : 0; +} +static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { + auto &o=dsiFixedMode; + if(o.changingThread && o.presentationReady && mode==o.activeMode)return &o.presentation; + const XELP::DSI::AppleAdapter::Timing *selected=nullptr; + for(unsigned i=0;igetProperty("IOFBScalerInfo")); + if(!data || data->getLength()!=48)return; + uint32_t scaler[12];memcpy(scaler,data->getBytesNoCopy(),sizeof(scaler)); + if(!(scaler[4]&0x20)){scaler[4]|=0x20;fb->setProperty("IOFBScalerInfo",scaler,sizeof(scaler));} +} +struct DSIModeScope { + IORecursiveLock *lock; + explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { + if(lock)IORecursiveLockLock(lock); + } + ~DSIModeScope(){if(lock)IORecursiveLockUnlock(lock);} +}; +static bool dsiModeCoherent(IOService *fb) { + const auto &o=dsiFixedMode; + return o.ready && o.array && o.data && o.array->getCount()==1 && o.array->getObject(0)==o.data && + o.data->getLength()==sizeof(o.timing) && !memcmp(o.data->getBytesNoCopy(),&o.timing,sizeof(o.timing)) && + Display13FBMemory::field(fb,0x4260)==o.array && fb->getProperty("IOFBDetailedTimings")==o.array; +} diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 593bd0b1..77734c06 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3885,39 +3885,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { return board; } -bool XE::prepareDSIFirmware(IOService *provider) { - auto source=provider ? OSDynamicCast(OSData,provider->getProperty("TGLVBT")) : nullptr; - if(!source || !source->getLength() || source->getLength()>65536)return false; - if(dsiFirmware)return dsiProfile.valid() && dsiFirmware->getLength()==source->getLength() && - !memcmp(dsiFirmware->getBytesNoCopy(),source->getBytesNoCopy(),source->getLength()); - auto copy=OSData::withBytes(source->getBytesNoCopy(),source->getLength()); - if(!copy)return false; - auto parsed=XELP::DSI::parseProfile({static_cast(copy->getBytesNoCopy()),copy->getLength()},true); - if(!parsed.valid()){copy->release();return false;} - dsiProfile=parsed;dsiFirmware=copy;return true; -} - -// Keep profile parsing temporaries out of the already large bootstrap frame. -__attribute__((noinline)) void XE::auditDSIBoot(IOPCIDevice *pci,volatile const uint8_t *base,uint64_t length, - const AlderLakeN::DisplayReadout &normal,bool admitted) { - if(!checkKernelArgument("-wex-dsi-audit"))return; - struct Scratch {XELP::DSI::Profile profile;XELP::DSI::Boot boot;XELP::DSI::BootAudit audit;}; - auto scratch=static_cast(IOMalloc(sizeof(Scratch)));if(!scratch)return; - // Pre-native, before the optional external-output handoff. Read only; - // the scratch certificate must never become the live dsiBoot owner. - auto vbt=OSDynamicCast(OSData,pci->getProperty("TGLVBT"));scratch->profile={}; - if(vbt && vbt->getLength() && vbt->getLength()<=65536) - scratch->profile=XELP::DSI::parseProfile({static_cast(vbt->getBytesNoCopy()),vbt->getLength()},true); - struct ReadIO { - volatile const uint8_t *base;uint64_t length; - bool readable(uint32_t reg) const {return base && length>=4 && reg<=length-4;} - uint32_t read(uint32_t reg) {return *reinterpret_cast(base+reg);} - uint64_t readPTE(uint32_t reg) {return *reinterpret_cast(base+reg);} - } io {base,length}; - XELP::DSI::auditBoot(io,normal,scratch->profile,xelpMemory,admitted,scratch->boot,scratch->audit); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBootAudit",&scratch->audit,sizeof(scratch->audit)); - IOFree(scratch,sizeof(Scratch)); -} + void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); @@ -4565,6 +4533,8 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add return false; } +static bool dsiCachePath(OSObject *); +static bool dsiLifecycleTarget(IOService *,OSObject *); static bool adlnWriterMappingBound(); namespace { struct Display13GMBusScope { @@ -4647,92 +4617,8 @@ struct Display13FBPowerContext { uint64_t powerContext[24] {1}; // Bounded CPU-only execution-context evidence. }; static Display13FBPowerContext display13FBPower; -// The FB mutex serializes prepare/discard/publication. Native allocation also -// publishes GPU-pipe references before this record becomes the FB scanout. -// Keep both owners retained, including on partial failure, until proven free. -struct DSIPendingBackingOwner { - IOService *framebuffer {},*accelerator {}; - OSObject *pipe {},*pinnedMap {},*pinnedMemory {}; - XELP::DSI::NativeBacking::Record *record {}; - uintptr_t thread {}; - bool allocating {},freeing {},allocated {},freed {},armed {},published {},modeReady {}; - uint32_t nativeResult {}; - Display13FBMemory::SurfaceReadout proof; - XELP::DSI::TestImage::Result image; -}; -static DSIPendingBackingOwner dsiPendingBacking; -static bool dsiPendingTarget(IOService *fb) { - return fb && __atomic_load_n(&dsiPendingBacking.framebuffer,__ATOMIC_ACQUIRE)==fb; -} - -static bool dsiPublishedTarget(IOService *fb) { - return dsiPendingTarget(fb) && dsiPendingBacking.published && dsiPendingBacking.record && - Display13FBMemory::field(fb,0x4348)==dsiPendingBacking.record; -} - - -// One DSI0 panel. Publish only after native path/pipe association; the FB and -// immutable timing array remain retained until a quiesced DSI stop can detach -// them. A donor property failure must never leave its raw array pointer dangling. -struct DSIFixedModeOwner { - IOService *framebuffer {}; - OSArray *array {}; - OSData *data {}; - XELP::DSI::Mode mode; - XELP::DSI::AppleAdapter::Timing timing; - XELP::DSI::AppleAdapter::Timing presentation,requested; - OSData *offeredData {}; - const XELP::DSI::AppleAdapter::Timing *offered {}; - unsigned offerCount {}; - uint32_t activeMode {XELP::DSI::AppleAdapter::FixedModeID}; - uintptr_t changingThread {},logical {}; - bool presentationReady {},presentationFault {}; - uint32_t clientMode {XELP::DSI::AppleAdapter::FixedModeID}; - bool ready {}; -}; -static DSIFixedModeOwner dsiFixedMode; -static bool dsiModeTarget(IOService *fb) { - return fb && __atomic_load_n(&dsiFixedMode.framebuffer,__ATOMIC_ACQUIRE)==fb; -} -static uintptr_t dsiLogicalSurface(const void *fb) { - const auto &o=dsiFixedMode; - return fb && __atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)==fb ? - __atomic_load_n(&o.logical,__ATOMIC_ACQUIRE) : 0; -} -static const XELP::DSI::AppleAdapter::Timing *dsiPresentation(uint32_t mode) { - auto &o=dsiFixedMode; - if(o.changingThread && o.presentationReady && mode==o.activeMode)return &o.presentation; - const XELP::DSI::AppleAdapter::Timing *selected=nullptr; - for(unsigned i=0;igetProperty("IOFBScalerInfo")); - if(!data || data->getLength()!=48)return; - uint32_t scaler[12];memcpy(scaler,data->getBytesNoCopy(),sizeof(scaler)); - if(!(scaler[4]&0x20)){scaler[4]|=0x20;fb->setProperty("IOFBScalerInfo",scaler,sizeof(scaler));} -} -struct DSIModeScope { - IORecursiveLock *lock; - explicit DSIModeScope(IOService *fb):lock(Display13FBMemory::field(fb,0x4220)) { - if(lock)IORecursiveLockLock(lock); - } - ~DSIModeScope(){if(lock)IORecursiveLockUnlock(lock);} -}; -static bool dsiModeCoherent(IOService *fb) { - const auto &o=dsiFixedMode; - return o.ready && o.array && o.data && o.array->getCount()==1 && o.array->getObject(0)==o.data && - o.data->getLength()==sizeof(o.timing) && !memcmp(o.data->getBytesNoCopy(),&o.timing,sizeof(o.timing)) && - Display13FBMemory::field(fb,0x4260)==o.array && fb->getProperty("IOFBDetailedTimings")==o.array; -} +#include "kern_dsi_adapter_state.inc" + // AlderLakeN start bypasses the generic TGL framebuffer path and its power sampler. // Use the retained, uncached BAR owner. These GT mirrors require no forcewake, // mailbox transaction, MMIO write or native policy intervention. @@ -4969,26 +4855,6 @@ static void adlnIRQRecord(uint64_t (&r)[32],unsigned event) { r[29]=reinterpret_cast(c.lock);r[30]=c.handlers;r[31]=c.quiescing|(c.blockedPipes<<8)|(uint64_t(c.softwareHandlers)<<32); } -// PowerInit retains the native controller (including its IRQ lock) until reboot. -// Read only the counters already collected by the IRQ handler. Never refresh -// the register cache, power a well, or call a native driver from this serializer. -static bool dsiIRQLiveSerialize(void *,void *,OSSerialize *serializer) { - auto &c=display13FBIRQ; - if(!__atomic_load_n(&c.controller,__ATOMIC_ACQUIRE) || !c.lock)return false; - uint64_t record[32] {}; - const auto saved=IOSimpleLockLockDisableInterrupt(c.lock); - adlnIRQRecord(record,3); - IOSimpleLockUnlockEnableInterrupt(c.lock,saved); - auto data=OSData::withBytes(record,sizeof(record));if(!data)return false; - const bool ok=data->serialize(serializer);data->release();return ok; -} -static void dsiPublishIRQLive() { - auto property=OSSerializer::forTarget(nullptr,dsiIRQLiveSerialize); - if(property) { - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIIRQLive",property); - property->release(); - } -} // These operations run under the display owner lock. AUX power qualification // needs the TC bank lock but never enters the native port or power callbacks. @@ -5379,585 +5245,17 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo } } -bool XE::processDSIBackingHooks(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={"__ZN18IGAccelDisplayPipe21allocateScanoutMemoryEP19IntelScaledModeDataj", - "__ZN18IGAccelDisplayPipe23deallocateScanoutMemoryEP19IntelScaledModeDataj"}; - const uintptr_t offsets[]={0x7E5B2,0x7E772}; - const uint8_t entries[][16]={ - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}, - {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x8B,0x46,0x38,0x85,0xC0,0x74,0x62,0x49,0x89}}; - bool known=size>0x7F80A; - for(unsigned i=0;i<2;++i) { - const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),entries[i],16); - } - KernelPatcher::RouteRequest routes[]={ - {symbols[0],wrapDSIBackingAllocate,orgDSIBacking[0]}, - {symbols[1],wrapDSIBackingFree,orgDSIBacking[1]}}; - // Short routes copy only the six-byte push/mov/push prologue. The free - // entry has a relative branch at byte 12; never copy it into a trampoline. - const bool ready=known && patcher.routeMultipleShort(index,routes,2,address,size);patcher.clearError(); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingHooksReady",ready);return ready; -} -uint32_t XE::wrapDSIBackingAllocate(OSObject *pipe,void *record,uint32_t flags) { - auto &o=dsiPendingBacking; - if(!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)) - return FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); - if(__atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || record!=o.record) { - if(pipe && pipe==__atomic_load_n(&o.pipe,__ATOMIC_ACQUIRE))return kIOReturnNotReady; - return FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); - } - if(!o.allocating || flags || o.pipe || !pipe || !pipe->metaCast("IGAccelDisplayPipe") || - pipe->getMetaClass()->getClassSize()<0x12B0 || Display13FBMemory::field(pipe,0x88)!=o.accelerator || - Display13FBMemory::field(pipe,0x1298) || Display13FBMemory::field(pipe,0x12A0) || - Display13FBMemory::field(pipe,0x12A8))return kIOReturnNotReady; - pipe->retain();__atomic_store_n(&o.pipe,pipe,__ATOMIC_RELEASE); - const auto result=FunctionCast(wrapDSIBackingAllocate,callbackXE->orgDSIBacking[0])(pipe,record,flags); - o.nativeResult=result;const auto &r=*o.record; - o.allocated=!result && r.refs==1 && r.memory && r.map && - Display13FBMemory::field(pipe,0x1298)==r.memory && - Display13FBMemory::field(pipe,0x12A0)==r.map && !Display13FBMemory::field(pipe,0x12A8); - if(o.allocated) { - // Native init/destroyScanoutResource can replace these pipe fields. - // Pin the objects while still inside the native GPU lock, before - // returning to the PTE inspector. Pins alone do not preserve GGTT. - o.pinnedMap=reinterpret_cast(r.map);o.pinnedMemory=reinterpret_cast(r.memory); - o.pinnedMap->retain();o.pinnedMemory->retain(); - } - return o.allocated && callbackXE->validateDSINativeBacking() && - callbackXE->paintDSINativeBacking(0) && callbackXE->switchDSINativeSurface() && - callbackXE->publishDSINativeBacking() ? result : kIOReturnNotReady; -} -uint32_t XE::wrapDSIBackingFree(OSObject *pipe,void *record,uint32_t flags) { - auto &o=dsiPendingBacking; - if(!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE)) - return FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); - if(__atomic_load_n(&o.thread,__ATOMIC_ACQUIRE)!=reinterpret_cast(current_thread()) || record!=o.record) { - if(pipe && pipe==__atomic_load_n(&o.pipe,__ATOMIC_ACQUIRE))return kIOReturnNotReady; - return FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); - } - if(!o.freeing || flags || o.armed || !pipe || pipe!=o.pipe || - Display13FBMemory::field(pipe,0x88)!=o.accelerator)return kIOReturnNotReady; - const auto &r=*o.record; - if(r.refs>1 || Display13FBMemory::field(pipe,0x1298)!=r.memory || - Display13FBMemory::field(pipe,0x12A0)!=r.map || Display13FBMemory::field(pipe,0x12A8) || - (!r.refs && (r.map || r.memory || r.gpu || r.pitch)))return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSIBackingFree,callbackXE->orgDSIBacking[1])(pipe,record,flags); - o.nativeResult=result;o.freed=!result && !r.refs && !r.map && !r.memory && !r.gpu && !r.pitch && - !Display13FBMemory::field(pipe,0x1298) && !Display13FBMemory::field(pipe,0x12A0); - return o.freed ? result : kIOReturnNotReady; -} - -bool XE::prepareDSINativeBacking(IOService *fb) { - dsiBackingStage=0; - if(adlnOutputWiring.dsi!=1 || !fb || fb->getMetaClass()->getClassSize()<0x4B90 || - !dsiModeTarget(fb) || !dsiBackingHooksReady || !dsiProfile.valid())return false; - dsiBackingStage=1; - DSIModeScope scope(fb);if(!scope.lock || !dsiNativeUpdateAllowed(fb))return false; - auto &o=dsiPendingBacking;auto &c=display13FBPower; - if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return false; - dsiBackingStage=2; - IOLockLock(c.lock); - auto controller=static_cast(c.controller); - auto owner=reinterpret_cast(display13FBExpectedOwner(controller)); - const auto reserve=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); - auto machine=owner && owner->getMetaClass()->getClassSize()>=0x1500 ? Display13FBMemory::field(owner,0x378) : nullptr; - XELP::DSI::NativeBacking::Record request; - const bool ready=adlnOutputWiring.dsi==1 && !c.dsiNativeWriters && owner && machine && - machine->metaCast("IGAccelDisplayMachine") && machine->getMetaClass()->getClassSize()>=0x130 && - Display13FBMemory::field(machine,0x128)==owner && adlnSharedBinding(controller,owner) && - reserve && reserve==Display13FBMemory::field(owner,0x13C0) && - adlnSysMemOwner(controller,Display13FBMemory::pool(controller)) && - Display13FBMemory::allocatedSlot(controller,fb)==0 && - XELP::DSI::NativeBacking::request(dsiProfile,Display13FBMemory::field(owner,0x1470),request) && - display13FBInspectMemory(fb,true,0,false).status==Display13FBMemory::Status::Complete; - if(ready){fb->retain();owner->retain();} - IOLockUnlock(c.lock);if(!ready)return false; - // Prepare the real native dimensions before handing this allocation to - // its later public mode owner. No mode-success or backing is published. - if(!probeDSIModeGeometry(fb)) {owner->release();fb->release();return false;} - dsiBackingStage=3; - auto record=static_cast(IOMalloc(sizeof(request))); - if(!record){owner->release();fb->release();return false;} - memcpy(record,&request,sizeof(request));o.accelerator=owner;o.record=record; - __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.allocating=true; - __atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); - dsiBackingStage=4; - uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; - // Native callPlatformFunction takes the GPU lock. Never hold c.lock here. - const auto result=owner->callPlatformFunction("allocscanmem",false,record,&index,&flags,nullptr); - o.allocating=false; - dsiBackingStage=5; - if(result || !o.allocated)return false; // Retain partial state for discard. - IOLockLock(c.lock); - const bool verified=c.controller==controller && adlnSharedBinding(controller,owner) && - o.pipe && o.pinnedMap && o.pinnedMemory && - Display13FBMemory::field(o.pipe,0x88)==owner && - Display13FBMemory::field(o.pipe,0x1298)==record->memory && - Display13FBMemory::field(o.pipe,0x12A0)==record->map && - !Display13FBMemory::field(o.pipe,0x12A8) && - reserve==__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE) && - reserve==Display13FBMemory::field(owner,0x13C0) && - !c.memoryFaulted && !c.clockFaulted && - XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); - IOLockUnlock(c.lock);if(verified)dsiBackingStage=6;return verified; -} - -// The first P3 step uses the donor's CPU geometry helper. The selected Y -// layout is the same native tile-mode-3 allocation validated by the caller. -// Retain a bounded receipt of the remaining native transaction prerequisites. -bool XE::probeDSIModeGeometry(IOService *fb) { - if(!dsiComputeGeometry || !dsiModeTarget(fb) || !dsiModeCoherent(fb))return false; - auto path=Display13FBMemory::singlePath(fb); - auto &c=display13FBPower;IOLockLock(c.lock); - auto controller=static_cast(c.controller); - uint64_t receipt[16] {1,0,Display13FBMemory::field(fb,0x4A18), - Display13FBMemory::field(fb,0x4350),Display13FBMemory::field(fb,0x420C)}; - const bool bound=path && path->getMetaClass()->getClassSize()>=0x3730 && controller && - Display13FBMemory::field(fb,0x1D0)==controller && - Display13FBMemory::field(path,0x32B0)==fb && !c.dsiNativeWriters && - !c.memoryFaulted && !c.clockFaulted && !dsiStopAttempted; - uint8_t savedFB[0x30],savedPath[0x20]; - memcpy(savedFB,reinterpret_cast(fb)+0x430C,sizeof(savedFB)); - if(bound)memcpy(savedPath,reinterpret_cast(path)+0xF0,sizeof(savedPath)); - const bool prepared=bound && XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,dsiFixedMode.timing, - reinterpret_cast(dsiComputeGeometry)); - receipt[1]=prepared;receipt[5]=Display13FBMemory::field(fb,0x430C); - receipt[6]=Display13FBMemory::field(fb,0x4310);receipt[7]=Display13FBMemory::field(fb,0x4314); - receipt[8]=Display13FBMemory::field(fb,0x4334);receipt[9]=Display13FBMemory::field(fb,0x4338); - if(controller) { - receipt[10]=Display13FBMemory::field(controller,0xC10)!=nullptr; - receipt[11]=Display13FBMemory::field(controller,0x1BB4); - auto panel=Display13FBMemory::field(controller,0xD0); - receipt[12]=panel!=nullptr; - if(panel && panel->getMetaClass()->getClassSize()>=0x70) { - receipt[13]=Display13FBMemory::field(panel,0x68); - receipt[14]=Display13FBMemory::field(panel,0x50)==fb; - } - receipt[15]=Display13FBMemory::field(controller,0xE53); - } - // Until FB+4348 publishes the native backing, leave public aperture - // dimensions untouched. Otherwise IOGraphics could map the old GOP while - // the identification plane reads the new tiled allocation. - if(bound) { - memcpy(reinterpret_cast(fb)+0x430C,savedFB,sizeof(savedFB)); - memcpy(reinterpret_cast(path)+0xF0,savedPath,sizeof(savedPath)); - const uint32_t bpp=static_cast(receipt[3]); - memcpy(reinterpret_cast(fb)+0x4350,&bpp,sizeof(bpp)); - *(reinterpret_cast(fb)+0x4A18)=static_cast(receipt[2]); - } - IOLockUnlock(c.lock); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIGeometry",receipt,sizeof(receipt)); - return prepared; -} - -// Called by allocateScanoutMemory's routed entry while the native display -// machine still holds the GPU lock. Keep object/PTE validation in that scope; -// retained map/memory references alone do not serialize GGTT replacement. -bool XE::validateDSINativeBacking() { - auto &o=dsiPendingBacking;auto &c=display13FBPower; - auto fb=o.framebuffer;auto owner=o.accelerator;auto record=o.record; - if(!fb || !owner || !record || !o.allocating || !o.allocated || !o.pinnedMap || !o.pinnedMemory || - o.thread!=reinterpret_cast(current_thread()) || !c.lock)return false; - auto controller=static_cast(c.controller); - const auto reserve=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); - IOLockLock(c.lock); - bool verified=false; - const auto pipeBound=[&]() { - return o.pipe && Display13FBMemory::field(o.pipe,0x88)==owner && - Display13FBMemory::field(o.pipe,0x1298)==record->memory && - Display13FBMemory::field(o.pipe,0x12A0)==record->map && - !Display13FBMemory::field(o.pipe,0x12A8); - }; - if(pipeBound() && c.controller==controller && c.map && c.map->getVirtualAddress() && c.map->getLength()==0x1000000 && - adlnSharedBinding(controller,owner) && !c.memoryFaulted && !c.clockFaulted && - reserve && reserve==Display13FBMemory::field(owner,0x13C0)) { - Display13SurfaceIO io {c.map,0}; - o.proof=Display13FBMemory::inspectPendingSurface(io,controller,controller->getMetaClass()->getClassSize(), - fb,fb->getMetaClass()->getClassSize(),reinterpret_cast(owner),reserve, - (c.map->getLength()-0x800000)/8*4096,xelpMemory,record); - verified=pipeBound() && XELP::DSI::NativeBacking::allocated(*record,dsiProfile,o.proof); - } - IOLockUnlock(c.lock);return verified; -} - -// Mapping and writes remain inside the same native GPU lock as the allocator -// and full PTE proof. The GPU map is IGAccelMemoryMap, not IOMemoryMap: create -// a separate CPU mapping from its validated SysMemory descriptor. -bool XE::paintDSINativeBacking(unsigned phase) { - auto &o=dsiPendingBacking; - o.image={}; - if(!o.allocating || !o.allocated || (o.armed && dsiSession.state!=XELP::DSI::SessionState::Streaming) || !o.record || !o.pinnedMemory || - o.thread!=reinterpret_cast(current_thread()) || - !XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof))return false; - auto descriptor=OSDynamicCast(IOMemoryDescriptor,Display13FBMemory::field(o.pinnedMemory,0xD0)); - if(!descriptor || descriptor->getLength()(o.proof.pitch),o.proof.bytes); - if(!shape.valid())return false; - auto map=descriptor->createMappingInTask(kernel_task,0,kIOMapAnywhere,0,o.proof.bytes); - if(!map)return false; - const auto address=map->getVirtualAddress(); - if(address && map->getLength()==o.proof.bytes && !(address&4095)) { - o.image=XELP::DSI::TestImage::paint(reinterpret_cast(address),map->getLength(),shape,phase); - if(o.image.complete) { - for(uint64_t offset=0;offsetrelease(); - uint64_t receipt[] {1,phase,o.image.complete,o.image.bytes,o.image.digest,o.proof.gpu}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIImage",receipt,sizeof(receipt)); - return o.image.complete; -} - -// The allocator's native GPU lock and FB mutex span this transaction. The -// owner lock protects shared clock/DDB/IRQ association; no IRQ spinlock spans -// the bounded frame wait. Keep backing private in P2: normal native flips and -// mode-success publication belong to the desktop adapter. -bool XE::switchDSINativeSurface() { - using namespace XELP::DSI; - auto &o=dsiPendingBacking;auto &c=display13FBPower;auto &irq=display13FBIRQ; - auto fb=o.framebuffer; - if(!fb || !o.image.complete || !o.allocating || !o.allocated || o.armed || !o.record || !c.lock || - o.thread!=reinterpret_cast(current_thread()) || dsiSession.state!=SessionState::Unbound)return false; - uint64_t receipt[24] {1,1}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); - auto path=Display13FBMemory::singlePath(fb); - IOLockLock(c.lock); - const auto owned=[&]() { - return dsiResourcesReady && dsiNativeWriterHooksReady && dsiCacheHooksReady && dsiBackingHooksReady && - !dsiStopAttempted && !c.dsiNativeWriters && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && !c.dbuf.updating && - !c.pipe[0].destroyingPath && !c.pipe[0].wmProgramming && !c.pipe[0].wmCommitting && - c.pipe[0].memoryFramebuffer==fb && c.transcoders.dsi0Pipe==0 && path && - path->getMetaClass()->getClassSize()>=0x3730 && - Display13FBMemory::field(path,0x32B0)==fb && - Display13FBMemory::field(path,0x3640)==0 && - Display13FBMemory::field(path,0x3644)==2 && - Display13FBMemory::field(path,0x3648)==c.dsiPort && - c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && - adlnSharedBinding(c.controller,o.accelerator) && adlnWriterMappingBound() && - o.pipe && Display13FBMemory::field(o.pipe,0x1298)==o.record->memory && - Display13FBMemory::field(o.pipe,0x12A0)==o.record->map && - XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof); - }; - struct IO : Display13FBBootIO { - decltype(owned) &owns; - IO(IOMemoryMap *m,decltype(owned) &f):Display13FBBootIO{m},owns(f){} - bool ready(){return owns();} - uint64_t nativeClockHz(){Display13FBClockIO clock{map};return Display13Clock::read(clock).nativeHz();} - uint64_t nowUs(){uint64_t t=0,ns=0;clock_get_uptime(&t);absolutetime_to_nanoseconds(t,&ns);return ns/1000;} - void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} - void write(uint32_t reg,uint32_t value) { - const bool wm=(reg>=0x70240 && reg<=0x7025C && !(reg&3)) || reg==0x70268; - bool plane=false;for(auto r:Surface::Registers)plane|=r==reg; - if(readable(reg) && (wm || plane || reg==0x45270)) - *reinterpret_cast(map->getVirtualAddress()+reg)=value; - } - } io{c.map,owned}; - bool irqReady=false; - if(owned() && irq.lock && adlnIRQOwner(static_cast(c.controller))) { - const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - irqReady=!irq.quiescing && !irq.state.faulted && irq.state.enabled && (irq.admittedPipes&1) && - irq.state.transcoders.dsi0Pipe==0; - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - } - receipt[2]=irqReady; - Surface::Result result; - if(irqReady && dsiBoot.complete() && c.bootDSI.sameSource(dsiBoot.source) && - c.bridge.outputHeld(io,0,0) && c.latency.status==Display13WM::Status::Complete) { - const auto ddb=Display13DBUF::hardwareRaw(c.dbuf,0,c.dbuf.pipe[0].primary); - const auto cursorDDB=Display13DBUF::hardwareRaw(c.dbuf,0,c.dbuf.pipe[0].cursor); - const auto range=Display13DBUF::decode(ddb);const auto ipc=io.read(0x45004); - auto wm=Display13WM::plan(dsiProfile.mode.width,dsiProfile.mode.htotal(),uint64_t(dsiProfile.mode.clockKHz)*1000, - Display13DBUF::valid(range)?range.end-range.start:0,c.latency, - (ipc&8) || Display13FBMemory::field(c.controller,0x1B4C),true,dsiProfile.mode.height); - const auto hz=io.nativeClockHz();const auto pixel=uint64_t(dsiProfile.mode.clockKHz)*1000; - const Display13WM::Snapshot snapshot{&wm,nullptr,ddb,cursorDDB,static_cast(o.proof.gpu),0,ipc,hz,false,false, - Display13WM::pipeLineTime(dsiProfile.mode.htotal(),pixel),pixel,0,c.transcoders}; - Surface::Plane current{dsiBoot.control,io.read(0x701CC),dsiBoot.stride,dsiBoot.size,dsiBoot.surface,0,0}; - auto next=current;next.control=Display13WM::arbitrationControl((current.control&~(7U<<10))|(4U<<10)); - next.color|=1U<<13;next.stride=static_cast(o.proof.pitch/128);next.surface=static_cast(o.proof.gpu); - Surface::Backing backing{reinterpret_cast(o.record),o.proof.gpu,o.proof.bytes,o.proof.pitch}; - Capabilities caps;caps.installed=Capabilities::Adoption; - if(ddb!=UINT32_MAX && cursorDDB!=UINT32_MAX && Display13DBUF::valid(range) && - dsiSession.bind(caps,c.bootDSI,dsiProfile,1,reinterpret_cast(path),reinterpret_cast(&dsiBoot))) { - result=Surface::change(io,dsiSession,c.bootDSI,dsiProfile,current,next,backing,snapshot); - o.armed=result.writes!=0 || dsiSession.state==SessionState::Switching || dsiSession.state==SessionState::Faulted || - dsiSession.state==SessionState::Streaming; - if(!o.armed)dsiSession={}; - } - receipt[3]=ddb;receipt[4]=cursorDDB;receipt[5]=hz;receipt[6]=wm.allocation; - receipt[7]=next.control;receipt[8]=next.stride;receipt[9]=next.surface;receipt[10]=o.image.digest; - } - receipt[11]=static_cast(result.status);receipt[12]=result.writes; - receipt[13]=static_cast(result.wm.status);receipt[14]=static_cast(result.latch.status); - receipt[15]=static_cast(dsiSession.state);receipt[16]=o.armed; - const bool switched=result.status==Surface::Status::Complete; - IOLockUnlock(c.lock); - // A second pattern changes the contents of the same owned live surface. - // Keep native GPU serialization, flush it, then prove scanout advances. - bool updated=false; - if(switched && paintDSINativeBacking(1)) { - IOLockLock(c.lock); - receipt[17]=o.image.digest;receipt[18]=io.read(0x70040); - for(unsigned us=0;us<=100000;us+=1000) { - receipt[19]=io.read(0x70040); - const uint32_t frames=static_cast(receipt[19]-receipt[18]); - if(frames && frames<0x80000000U) { - updated=owned() && Surface::source(io,c.bootDSI) && - Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu));break; - } - if(!owned() || receipt[19]==UINT32_MAX)break; - io.delayUs(1000); - } - IOLockUnlock(c.lock); - } - if(switched && !updated)dsiSession.abort(dsiSession.ticket(),SessionError::Switch); - receipt[1]=2;receipt[20]=updated;receipt[21]=static_cast(dsiSession.state); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); - return switched && updated; -} - -// Still inside allocateScanoutMemory's GPU lock and the FB mutex. Transfer -// the already latched allocation to the native FB without reallocating it. -// Returning stolen slot zero changes bookkeeping only; its GGTT reservation -// and the firmware retired-backing record remain held through Session stop. -bool XE::publishDSINativeBacking() { - auto &o=dsiPendingBacking;auto &c=display13FBPower;auto fb=o.framebuffer; - if(!fb || !o.allocating || o.thread!=reinterpret_cast(current_thread()) || - !o.armed || !o.allocated || o.published || dsiSession.state!=XELP::DSI::SessionState::Streaming || - !dsiComputeGeometry || !orgAlderLakeNScanoutFree)return false; - IOLockLock(c.lock); - auto controller=static_cast(c.controller);auto path=Display13FBMemory::singlePath(fb); - const auto pool=Display13FBMemory::pool(controller); - const auto count=Display13FBMemory::field(controller,0xDF5); - const auto used=Display13FBMemory::field(controller,0xDF4); - const auto mask=Display13FBMemory::field(controller,0xDF0); - uint64_t receipt[12] {1,1,count,used,mask}; - uint8_t savedFB[0x30],savedPath[0x20]; - memcpy(savedFB,reinterpret_cast(fb)+0x430C,sizeof(savedFB)); - if(!path) {IOLockUnlock(c.lock);return false;} - memcpy(savedPath,reinterpret_cast(path)+0xF0,sizeof(savedPath)); - const auto oldBpp=Display13FBMemory::field(fb,0x4350); - const auto oldTiled=Display13FBMemory::field(fb,0x4A18); - Display13FBBootIO io{c.map}; - bool ready=dsiResourcesReady && !dsiStopAttempted && !c.memoryFaulted && !c.clockFaulted && - !c.dsiNativeWriters && !c.bridge.state.faulted && dsiModeCoherent(fb) && - Display13FBMemory::allocatedSlot(controller,fb)==0 && pool.state==1 && !pool.descriptor && - adlnSharedBinding(controller,o.accelerator) && - XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof) && - XELP::DSI::Surface::source(io,c.bootDSI) && - XELP::DSI::Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu)) && - XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,dsiFixedMode.timing, - reinterpret_cast(dsiComputeGeometry)); - IOLockUnlock(c.lock); - if(ready) { - // FB+4348 is still null, so the exact donor takes only the slot-zero - // FreeSysMem/freeFBMemory branch; no GPU free is reachable here. - FunctionCast(wrapAlderLakeNScanoutFree,orgAlderLakeNScanoutFree)(fb); - IOLockLock(c.lock); - receipt[5]=Display13FBMemory::field(controller,0xDF4); - receipt[6]=Display13FBMemory::field(controller,0xDF0); - receipt[7]=Display13FBMemory::field(fb,0x4288); - ready=Display13FBMemory::slotReturned(count,0,used,mask,receipt[5],receipt[6],receipt[7]) && - Display13FBMemory::poolReturned(pool,Display13FBMemory::pool(controller),0) && - !Display13FBMemory::field(fb,0x4348) && !c.memoryFaulted && - Display13FBMemory::field(fb,0x4334)==o.proof.pitch; - if(ready) { - *reinterpret_cast(reinterpret_cast(fb)+0x4348)=o.record; - *reinterpret_cast(reinterpret_cast(fb)+0x4330)=static_cast(o.proof.gpu); - c.pipe[0].scanout=o.proof; - __atomic_store_n(&c.pipe[0].publishedScanout,reinterpret_cast(o.record),__ATOMIC_RELEASE); - o.published=true;receipt[1]=2; - } - IOLockUnlock(c.lock); - if(ready)ready=updateDSIBootCache(controller,fb,path); - } - if(!o.published) { - IOLockLock(c.lock); - memcpy(reinterpret_cast(fb)+0x430C,savedFB,sizeof(savedFB)); - memcpy(reinterpret_cast(path)+0xF0,savedPath,sizeof(savedPath)); - memcpy(reinterpret_cast(fb)+0x4350,&oldBpp,sizeof(oldBpp)); - *(reinterpret_cast(fb)+0x4A18)=oldTiled; - IOLockUnlock(c.lock); - } - receipt[8]=ready;receipt[9]=o.published;receipt[10]=o.proof.gpu;receipt[11]=o.proof.pitch; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicBacking",receipt,sizeof(receipt)); - return ready; -} -// The FB mutex spans native mode completion. These donor CPU leaves preserve -// transaction/notification semantics; the ordinary DDI modesetter is bypassed -// because the DSI link and the new primary plane are already running. -bool XE::publishDSINativeMode(IOService *fb) { - auto &o=dsiPendingBacking;auto &c=display13FBPower; - if(!dsiPublishedTarget(fb) || !dsiModeCoherent(fb) || !dsiModeCPU[0] || !dsiModeCPU[1] || !dsiModeCPU[2])return false; - if(o.modeReady)return dsiNativeUpdateAllowed(fb); - uint64_t receipt[12] {1,1}; - IOLockLock(c.lock);auto controller=static_cast(c.controller); - auto path=Display13FBMemory::singlePath(fb); - const bool ready=!dsiStopAttempted && dsiResourcesReady && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !c.dbuf.faulted && !c.watermarkFaulted && !c.dsiNativeWriters && dsiArmedBackingOwned(fb,path) && - dsiSession.state==XELP::DSI::SessionState::Streaming && - !Display13FBMemory::field(controller,0xD0) && - !Display13FBMemory::field(fb,0x4340) && Display13FBMemory::field(fb,0x4338)==1; - IOLockUnlock(c.lock); - receipt[2]=ready;receipt[3]=ready && updateDSIBootCache(controller,fb,path); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); - if(!receipt[2] || !receipt[3])return false; - uint32_t tile=2; - receipt[2]=o.accelerator->callPlatformFunction("EFITilingMode",true,&tile,nullptr,nullptr,nullptr); - if(!receipt[2]) { - *reinterpret_cast(reinterpret_cast(controller)+0xE53)=0; - *reinterpret_cast(reinterpret_cast(fb)+0x4250)=XELP::DSI::AppleAdapter::FixedModeID; - *reinterpret_cast(reinterpret_cast(fb)+0x4254)=0; - receipt[3]=reinterpret_cast(dsiModeCPU[0])(fb); - if(!receipt[3] && !Display13FBMemory::field(fb,0x4340)) { - reinterpret_cast(dsiModeCPU[1])(fb,0); - reinterpret_cast(dsiModeCPU[2])(fb); - IOLockLock(c.lock); - receipt[4]=Display13FBMemory::field(fb,0x4000); - receipt[5]=Display13FBMemory::field(fb,0x4008); - receipt[6]=Display13FBMemory::field(fb,0x400C); - receipt[7]=Display13FBMemory::field(fb,0x4200); - o.modeReady=!receipt[4] && receipt[5]==UINT32_MAX && receipt[6]==UINT32_MAX && receipt[7]==1 && - !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !dsiStopAttempted && - dsiArmedBackingOwned(fb,path) && dsiPublishedTarget(fb); - receipt[8]=o.modeReady;receipt[9]=Display13FBMemory::field(fb,0x402C); - receipt[10]=Display13FBMemory::field(fb,0x4334);receipt[11]=Display13FBMemory::field(fb,0x4314); - IOLockUnlock(c.lock); - } - } - if(o.modeReady) { - // setupClocks normally copies the detailed timing's Hz into this CPU - // path field before WM calculation. DSI retains its live PLL, so supply - // that fixed-mode cache without entering the native DDI clock setup. - memcpy(reinterpret_cast(path)+0x118,&dsiFixedMode.timing.pixelClock,sizeof(uint64_t)); - // Still under the native FB mutex: admit the existing DDB transaction, - // then require its latch before returning mode success to any client. - // GOP adoption may have no cursor reservation and a full primary DDB. - adlnSetupDBUFPipe(controller,fb,0,false); - IOLockLock(c.lock); - const auto &allocation=c.dbuf.pipe[0];const auto &pipe=c.pipe[0]; - o.modeReady=!c.dbuf.faulted && !c.watermarkFaulted && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !dsiStopAttempted && !pipe.wmPending && - allocation.primary.start==40 && allocation.primary.end==2048 && - allocation.cursor.start==0 && allocation.cursor.end==40 && - pipe.wmLatch.status==Display13WM::Status::Complete && dsiPublishedTarget(fb); - uint64_t layout[] {1,allocation.primary.start,allocation.primary.end,allocation.cursor.start,allocation.cursor.end, - pipe.wmPending,static_cast(pipe.wmLatch.status),static_cast(pipe.wmLatch.check), - c.watermarkFaulted,c.dbuf.faulted,pipe.watermarkClockHz,o.modeReady}; - IOLockUnlock(c.lock); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeDBUF",layout,sizeof(layout)); - } - receipt[8]=o.modeReady; - receipt[1]=2;IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPublicMode",receipt,sizeof(receipt)); - return o.modeReady; -} - -// A stopped stream may retain a native backing after partial cleanup. Its -// ownership is distinct from the GOP pool, which stays reserved until later. -bool XE::dsiArmedBackingOwned(IOService *fb,OSObject *path) { - auto &o=dsiPendingBacking; - if(!dsiPendingTarget(fb) || !o.armed || o.allocating || o.freeing || !o.record || !o.allocated || - !o.pipe || !o.pinnedMap || !o.pinnedMemory || !path)return false; - const auto handle=reinterpret_cast(o.record),lease=reinterpret_cast(path); - const bool session=dsiSession.lease==lease && - (dsiSession.currentBacking==handle || dsiSession.pendingBacking==handle) && - (dsiSession.state==XELP::DSI::SessionState::Streaming || dsiSession.state==XELP::DSI::SessionState::Faulted); - const bool cleanup=dsiSession.state==XELP::DSI::SessionState::Released && dsiCleanup.lease==lease && - (dsiCleanup.currentBacking==handle || dsiCleanup.pendingBacking==handle); - return (session || cleanup) && dsiSession.identity.same(dsiBoot.source.identity) && - adlnSharedBinding(display13FBPower.controller,o.accelerator) && - Display13FBMemory::field(o.pipe,0x88)==o.accelerator && - Display13FBMemory::field(o.pipe,0x1298)==o.record->memory && - Display13FBMemory::field(o.pipe,0x12A0)==o.record->map; -} - -// Caller has completed host/IRQ/clock stop and dropped owner/clock locks, but -// keeps FB serialization and the provider gate across native GPU cleanup. -bool XE::retireDSINativeBacking(IOService *fb,OSObject *path) { - if(!dsiPendingTarget(fb))return true; - if(!dsiStop.complete() || !dsiIRQParked || !dsiClockStop.complete() || !dsiDDIReleased || - !dsiArmedBackingOwned(fb,path) || !releaseDSILogical(fb))return false; - if(dsiSession.state!=XELP::DSI::SessionState::Released) { - const XELP::DSI::StopProof proof{dsiSession.ticket(),dsiBoot.source.identity,true,true,true,true}; - if(!dsiSession.stopped(proof))return false; - dsiCleanup=dsiSession.takeCleanup(); - } - auto &o=dsiPendingBacking;auto &c=display13FBPower; - const bool published=o.published;auto record=o.record; - IOLockLock(c.lock); - if(published && !dsiPublishedTarget(fb)) {IOLockUnlock(c.lock);return false;} - o.modeReady=false; - if(published) { - *reinterpret_cast(reinterpret_cast(fb)+0x4348)=nullptr; - __atomic_store_n(&c.pipe[0].publishedScanout,uintptr_t(0),__ATOMIC_RELEASE); - c.pipe[0].scanout={};o.published=false; - } - IOLockUnlock(c.lock);o.armed=false; - if(discardDSINativeBacking(fb))return true; - IOLockLock(c.lock); - if(published) {*reinterpret_cast(reinterpret_cast(fb)+0x4348)=record;o.published=true;} - o.armed=true;IOLockUnlock(c.lock);return false; -} - -// The first real fixed-mode request establishes an identification surface -// while the native FB mutex is held. Retain any armed backing until stop. -// Backing publication shares the native allocation serializer. Do this once after allocation starts; -// an unavailable accelerator may be retried by a later mode request. Never -// report a WindowServer mode before its subsequent CPU completion succeeds. -void XE::probeDSINativeBacking(IOService *fb) { - if(dsiBackingAttempted)return; - dsiBackingAttempted=true; - uint64_t receipt[16] {1,1}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); - const bool verified=prepareDSINativeBacking(fb); - auto &o=dsiPendingBacking; - const bool pending=dsiPendingTarget(fb); - receipt[1]=2;receipt[2]=dsiBackingStage;receipt[3]=verified;receipt[4]=pending; - if(pending) { - receipt[5]=o.allocated;receipt[6]=o.nativeResult;receipt[7]=static_cast(o.proof.status); - receipt[8]=o.proof.bytes;receipt[9]=o.proof.pitch;receipt[10]=o.proof.gpu;receipt[11]=o.proof.verifiedPages; - receipt[12]=o.pinnedMap && o.pinnedMemory; - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); - if(pending && !o.armed)receipt[13]=discardDSINativeBacking(fb); - receipt[14]=dsiPendingTarget(fb);receipt[15]=pending && !o.armed && !receipt[13];receipt[1]=3; - if(!pending)dsiBackingAttempted=false; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBackingTrial",receipt,sizeof(receipt)); -} - -// Only a never-armed pending surface may take this path. After SURF is armed, -// retain it through Session's stop proof; freeing an unlatched target is unsafe. -bool XE::discardDSINativeBacking(IOService *fb) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; - IOService *owner=nullptr;OSObject *pipe=nullptr,*mapping=nullptr,*memory=nullptr;void *record=nullptr; - { - DSIModeScope scope(fb);if(!scope.lock)return false; - auto &o=dsiPendingBacking; - if(!dsiPendingTarget(fb) || o.armed || o.allocating || o.freeing || !o.record)return false; - if(!adlnSharedBinding(display13FBPower.controller,o.accelerator))return false; - if(o.pipe) { - __atomic_store_n(&o.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE);o.freeing=true;o.freed=false; - uint32_t index=Display13FBMemory::field(fb,0x1DC),flags=0; - o.accelerator->callPlatformFunction("freescanmem",false,o.record,&index,&flags,nullptr); - o.freeing=false; - // DisplayMachine's free discards the pipe method's return value. - // Only our observed native record AND pipe cleanup prove success. - if(!o.freed)return false; - } else if(o.record->refs || o.record->map || o.record->memory || o.record->gpu || o.record->pitch)return false; - owner=o.accelerator;pipe=o.pipe;record=o.record;mapping=o.pinnedMap;memory=o.pinnedMemory; - __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); - __atomic_store_n(&o.pipe,static_cast(nullptr),__ATOMIC_RELEASE); - o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=o.published=o.modeReady=false;o.nativeResult=0;o.proof={};o.image={}; - } - IOFree(record,sizeof(XELP::DSI::NativeBacking::Record)); - if(mapping)mapping->release();if(memory)memory->release();if(pipe)pipe->release();owner->release();fb->release();return true; -} + + + + + + + + + + bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // All offsets and entry bytes are from the exact c004fc1a donor. Do not @@ -7246,415 +6544,16 @@ IOReturn XE::adlnSetupAuxiliaryMemory(IOService *controller,bool dsb) { IOReturn XE::wrapDisplay13CursorSetup(IOService *controller) {return adlnSetupAuxiliaryMemory(controller,false);} IOReturn XE::wrapAlderLakeNDSBSetup(IOService *controller) {return adlnSetupAuxiliaryMemory(controller,true);} -bool XE::processDisplay13DSIModes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN21AppleIntelFramebuffer18setDetailedTimingsEP7OSArray", - "__ZN21AppleIntelFramebuffer19validateDisplayModeEiPPKNS_15ModeDescriptionEPPK29IODetailedTimingInformationV2", - "__ZN21AppleIntelFramebuffer15getDisplayModesEPi", - "__ZN21AppleIntelFramebuffer19getDisplayModeCountEv", - "__ZN21AppleIntelFramebuffer28getInformationForDisplayModeEiP24IODisplayModeInformation", - "__ZN21AppleIntelFramebuffer14setDisplayModeEii", - "__ZN21AppleIntelFramebuffer21getCurrentDisplayModeEPiS0_", - "__ZN21AppleIntelFramebuffer13getOnlineInfoEP21AppleIntelDisplayPathPhS2_", - "__ZN21AppleIntelFramebuffer13fbGetDDCBlockEjPhPyjP21AppleIntelDisplayPath"}; - const uint32_t offsets[]={0x30D0E,0x30DE2,0x31FE6,0x32D04,0x32010,0x30F10,0x32A88,0x2E232,0x2F1B4}; - const uint8_t entries[][16]={ - {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xF6,0x48,0x89,0xFB,0x48,0xFF,0x05}, - {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0x98,0x93,0x0A,0x00,0x83,0xFE}, - {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x8F,0x84,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, - {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xE1,0x79,0x0A,0x00,0x80,0xBF,0xE0,0x01,0x00}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x48,0x83,0xEC,0x18,0x49,0x89,0xD6}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x44,0x89}}; - if(!display13FramebufferRequested || size<=0x32D14)return false; - for(unsigned i=0;i(index,symbols[i],address,size);patcher.clearError(); - if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; - } - const char *geometry="__ZN21AppleIntelFramebuffer32computeTransformAndSetDimensionsEPK29IODetailedTimingInformationV2"; - const auto compute=patcher.solveSymbol(index,geometry,address,size);patcher.clearError(); - const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC}; - if(compute!=address+0x31484 || memcmp(reinterpret_cast(compute),entry,sizeof(entry)))return false; - dsiComputeGeometry=compute; - const char *modeSymbols[]={"__ZN21AppleIntelFramebuffer21allocateLogicalBufferEv", - "__ZN21AppleIntelFramebuffer28setupInitialTransactionStateEj", - "__ZN21AppleIntelFramebuffer18setTransactionCapsEv", - "__ZN21AppleIntelFramebuffer20releaseLogicalBufferEv"}; - const uint32_t modeOffsets[]={0x31726,0x318E8,0x319D2,0x33062}; - for(unsigned i=0;i(index,modeSymbols[i],address,size);patcher.clearError(); - if(fn!=address+modeOffsets[i] || memcmp(reinterpret_cast(fn),entry,4))return false; - dsiModeCPU[i]=fn; - } - const char *scaler="__ZN16AppleIntelScaler19isSupportedByScalerEjjjjyjb"; - const auto scalerFn=patcher.solveSymbol(index,scaler,address,size);patcher.clearError(); - const uint8_t scalerEntry[]={0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xD7,0xD7,0x07,0x00,0x48,0x8B,0x47,0x10}; - if(size<=0x6686D || scalerFn!=address+0x6685E || - memcmp(reinterpret_cast(scalerFn),scalerEntry,sizeof(scalerEntry)))return false; - const char *detailed="__ZN21AppleIntelFramebuffer22validateDetailedTimingEPvy"; - const char *source="__ZN21AppleIntelFramebuffer18ValidateSourceSizeEP29IODetailedTimingInformationV2"; - const auto detailedFn=patcher.solveSymbol(index,detailed,address,size);patcher.clearError(); - const auto sourceFn=patcher.solveSymbol(index,source,address,size);patcher.clearError(); - const uint8_t detailedEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC}; - const uint8_t sourceEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x20}; - if(detailedFn!=address+0x2FFCA || sourceFn!=address+0x30AD8 || - memcmp(reinterpret_cast(detailedFn),detailedEntry,sizeof(detailedEntry)) || - memcmp(reinterpret_cast(sourceFn),sourceEntry,sizeof(sourceEntry)))return false; - const char *pixel="__ZN21AppleIntelFramebuffer19getPixelInformationEiiiP18IOPixelInformation"; - const auto pixelFn=patcher.solveSymbol(index,pixel,address,size);patcher.clearError(); - if(pixelFn!=address+0x324A4 || memcmp(reinterpret_cast(pixelFn),entry,sizeof(entry)))return false; - KernelPatcher::RouteRequest routes[]={ - {pixel,wrapDSIPixelInfo,orgDSIPixelInfo}, - {detailed,wrapDSIValidateDetailed,orgDSIDetailedValidation}, - {source,wrapDSIValidateSource,orgDSISourceValidation}, - {scaler,wrapDSIScalerSupport,orgDSIScalerSupport}, - {symbols[0],wrapDSISetDetailedTimings,orgDSIMode[0]}, - {symbols[1],wrapDSIValidateMode,orgDSIMode[1]}, - {symbols[2],wrapDSIGetModes,orgDSIMode[2]}, - {symbols[3],wrapDSIGetModeCount,orgDSIMode[3]}, - {symbols[4],wrapDSIGetModeInformation,orgDSIMode[4]}, - {symbols[5],wrapDSISetDisplayMode,orgDSIMode[5]}, - {symbols[6],wrapDSIGetCurrentMode,orgDSIMode[6]}, - {symbols[7],wrapDSIGetOnlineInfo,orgDSIMode[7]}, - {symbols[8],wrapDSIDDC,orgDSIMode[8]}}; - const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size); - patcher.clearError();return ready; -} - -// Bounded, copied CPU evidence from the real FB0 client callbacks. Also -// observe calls before the fixed-mode owner is bound; do not treat the trace -// as admission or invoke any driver/hardware operation while collecting it. -void XE::recordDSIClient(IOService *fb,unsigned event,uint64_t argument,uint32_t result) { - if(!callbackXE->adlnOutputWiring.dsi || event>=13 || !fb || - fb->getMetaClass()->getClassSize()<0x4B90 || Display13FBMemory::field(fb,0x1DC))return; - DSIModeScope scope(fb);if(!scope.lock)return; - static uint64_t counts[13] {}; - const char *keys[] {"WEXDSIClientBind","WEXDSIClientTimings","WEXDSIClientValidate", - "WEXDSIClientModes","WEXDSIClientCount","WEXDSIClientInfo","WEXDSIClientSetMode", - "WEXDSIClientAttribute","WEXDSIClientConnection","WEXDSIClientPower", - "WEXDSIClientCurrent","WEXDSIClientOnline","WEXDSIClientDDC"}; - const auto count=__atomic_add_fetch(&counts[event],1ULL,__ATOMIC_RELAXED); - uint64_t words[] {1,event,count,argument,result, - Display13FBMemory::field(fb,0x1E0),Display13FBMemory::field(fb,0x44DE), - Display13FBMemory::field(fb,0x1E7),Display13FBMemory::field(fb,0x4250), - Display13FBMemory::field(fb,0x4254),Display13FBMemory::field(fb,0x4284), - Display13FBMemory::field(fb,0x420C),dsiModeTarget(fb), - dsiModeTarget(fb) && dsiFixedMode.ready,Display13FBMemory::field(fb,0x4260)!=nullptr, - Display13FBMemory::field(fb,0x4348)!=nullptr}; - IORegistryEntry::getRegistryRoot()->setProperty(keys[event],words,sizeof(words)); -} - -bool XE::bindDSIFixedMode(IOService *fb,const XELP::DSI::Profile &profile) { - auto &c=display13FBPower;auto &o=dsiFixedMode; - if(!display13DSIModeHooksReady || adlnOutputWiring.dsi!=1 || !c.lock || !fb || - fb->getMetaClass()->getClassSize()<0x4B90 || !profile.valid())return false; - DSIModeScope scope(fb);if(!scope.lock)return false; - XELP::DSI::AppleAdapter::Timing timing; - if(!XELP::DSI::AppleAdapter::makeTiming(profile,timing))return false; - if(__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE))return dsiModeTarget(fb) && dsiModeCoherent(fb) && - !memcmp(&timing,&o.timing,sizeof(timing)); - auto data=OSData::withBytes(&timing,sizeof(timing));auto array=OSArray::withCapacity(1); - if(!data || !array || !array->setObject(data)) {OSSafeReleaseNULL(data);OSSafeReleaseNULL(array);return false;} - IOLockLock(c.lock); - const auto path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); - const bool bound=!__atomic_load_n(&o.framebuffer,__ATOMIC_ACQUIRE) && - c.controller==adlnTopologyController && binding.status==TGLFBBinding::Status::Complete && - binding.ddi==0 && c.transcoders.dsi(binding.pipe) && c.bootDSI.matches(profile) && - c.bootDSI.identity.pipe==binding.pipe && !c.memoryFaulted && !c.bridge.state.faulted; - if(bound) { - o.array=array;o.data=data;o.timing=timing;o.mode=profile.mode;o.ready=false; - fb->retain();__atomic_store_n(&o.framebuffer,fb,__ATOMIC_RELEASE); - } - IOLockUnlock(c.lock); - if(!bound) {data->release();array->release();return false;} - // The donor ignores setProperty's bool result. Retain our own array/data - // before calling it, then verify both the raw pointer and registry owner. - const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,array); - o.ready=result==kIOReturnSuccess; - o.ready=dsiModeCoherent(fb); - fb->setProperty("WEXDSIFixedModeReady",o.ready); - recordDSIClient(fb,0,XELP::DSI::AppleAdapter::FixedModeID,o.ready); - return o.ready; -} - -// Called with the FB mutex and provider gate held, after stopped path/PG -// disposal. The donor's qualified null setter removes only the timing property -// and its raw +4260 pointer; it performs no display or allocator operation. -bool XE::releaseDSIFixedMode(IOService *fb) { - auto &o=dsiFixedMode; - if(!o.framebuffer)return true; - if(!dsiModeTarget(fb) || o.ready || o.logical || !orgDSIMode[0])return false; - auto raw=Display13FBMemory::field(fb,0x4260); - auto property=fb->getProperty("IOFBDetailedTimings"); - if((raw && raw!=o.array) || (property && property!=o.array))return false; - const auto result=FunctionCast(wrapDSISetDetailedTimings,orgDSIMode[0])(fb,nullptr); - if(result!=kIOReturnSuccess || Display13FBMemory::field(fb,0x4260) || - fb->getProperty("IOFBDetailedTimings"))return false; - auto array=o.array;auto data=o.data;auto offered=o.offeredData; - __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); - o.array=nullptr;o.data=nullptr;o.offeredData=nullptr;o.offered=nullptr;o.mode={};o.timing={};o.clientMode=XELP::DSI::AppleAdapter::FixedModeID; - o.presentation={};o.requested={};o.offerCount=0;o.activeMode=o.clientMode;o.presentationReady=false;o.presentationFault=false;o.changingThread=0; - OSSafeReleaseNULL(array);OSSafeReleaseNULL(data);OSSafeReleaseNULL(offered);fb->release();return true; -} - -// IOGraphics also requests EDID independently of online detection. Stop at -// the shared internal DDC entry, before it acquires AUX power on behalf of -// the built-in panel. Supply one synthetic profile descriptor from CPU memory. -uint32_t XE::wrapDSIDDC(IOService *fb,uint32_t block,uint8_t *data,uint64_t *length,uint32_t flags,OSObject *path) { - if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIDDC,callbackXE->orgDSIMode[8])(fb,block,data,length,flags,path); - const auto result=[&]() -> uint32_t { - if(!length || !data)return kIOReturnBadArgument; - const auto capacity=*length;*length=0; - if(block!=1 || flags)return kIOReturnUnsupported; - if(capacity<128)return kIOReturnBadArgument; - DSIModeScope scope(fb); - if(!scope.lock || !dsiModeTarget(fb) || !dsiModeCoherent(fb) || - (path && path!=Display13FBMemory::singlePath(fb)))return kIOReturnNotReady; - uint8_t edid[128]; - if(!XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,edid))return kIOReturnNotReady; - memcpy(data,edid,sizeof(edid));*length=sizeof(edid);return kIOReturnSuccess; - }(); - recordDSIClient(fb,12,block,result);return result; -} - -// Native online detection assumes eDP AUX/EDID. A DSI panel has no such -// sink. Answer only for the real adopted path and its immutable fixed mode; -// its synthetic profile descriptor is supplied without touching AUX power. -uint32_t XE::wrapDSIGetOnlineInfo(IOService *fb,OSObject *path,uint8_t *online,uint8_t *edid) { - if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - if(Display13FBMemory::field(fb,0x1DC)) { - // Explicit development isolation; leave physical wiring and the - // ordinary external-output backend intact for coexistence testing. - if(callbackXE->dsiPanelOnly) { - if(!online || !edid)return kIOReturnBadArgument; - *online=0;bzero(edid,128);return kIOReturnSuccess; - } - return FunctionCast(wrapDSIGetOnlineInfo,callbackXE->orgDSIMode[7])(fb,path,online,edid); - } - if(!online || !edid)return kIOReturnBadArgument; - *online=0;bzero(edid,128); - DSIModeScope scope(fb); - const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && - path && path==Display13FBMemory::singlePath(fb) && dsiNativeUpdateAllowed(fb); - uint8_t descriptor[128]; - const bool described=ready && XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,descriptor); - if(described){*online=1;memcpy(edid,descriptor,sizeof(descriptor));dsiAdvertisePresentation(fb);} - const auto result=described ? kIOReturnSuccess : kIOReturnNotReady; - recordDSIClient(fb,11,*online,result);return result; -} - -// The donor initializes current mode to -5, then tries to recover it through -// normal-transcoder timing conversion. DSI already has a qualified GOP and -// fixed timing. Publish that same current mode under the native FB mutex. -uint32_t XE::wrapDSIGetCurrentMode(IOService *fb,int32_t *mode,int32_t *depth) { - if(!callbackXE->adlnOutputWiring.dsi)return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - if(Display13FBMemory::field(fb,0x1DC))return FunctionCast(wrapDSIGetCurrentMode,callbackXE->orgDSIMode[6])(fb,mode,depth); - DSIModeScope scope(fb); - const bool ready=scope.lock && dsiModeTarget(fb) && dsiModeCoherent(fb) && - Display13FBMemory::field(fb,0x1E0) && - ((dsiFixedMode.changingThread && dsiFixedMode.changingThread==reinterpret_cast(current_thread())) || dsiNativeUpdateAllowed(fb)); - if(ready) { - const uint32_t fixed=dsiFixedMode.presentationReady ? dsiFixedMode.activeMode : dsiFixedMode.clientMode; - memcpy(reinterpret_cast(fb)+0x4250,&fixed,sizeof(fixed)); - if(mode)*mode=fixed; - if(depth)*depth=Display13FBMemory::field(fb,0x4254); - } - const auto result=ready ? kIOReturnSuccess : kIOReturnNotReady; - recordDSIClient(fb,10,ready ? dsiFixedMode.clientMode : 0,result);return result; -} - -uint32_t XE::wrapDSISetDetailedTimings(IOService *fb,OSArray *array) { - recordDSIClient(fb,1,array!=nullptr,UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,array); - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - uint32_t client=0;unsigned count=0; - uint64_t offer[16] {3,array ? array->getCount() : 0,UINT32_MAX}; - if(array)for(unsigned i=0;igetCount();++i) { - auto bytes=OSDynamicCast(OSData,array->getObject(i)); - if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; - const auto &t=*static_cast(bytes->getBytesNoCopy()); - const bool native=XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i); - if(!native && !XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { - // Keep the last rejected OS request across later successful - // enumeration calls, so clock/geometry admission is observable. - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingRejected",bytes); - continue; - } - ++count;if(native)client=t.reservedA[0];offer[2]=i;offer[3]=client;offer[4]=t.pixelClock; - offer[5]=t.horizontalActive;offer[6]=t.horizontalBlanking;offer[7]=t.horizontalSyncOffset;offer[8]=t.horizontalSyncWidth; - offer[9]=t.verticalActive;offer[10]=t.verticalBlanking;offer[11]=t.verticalSyncOffset;offer[12]=t.verticalSyncWidth; - offer[13]=t.scalerFlags;offer[14]=t.horizontalScaled;offer[15]=t.verticalScaled; - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingOffer",offer,sizeof(offer)); - auto data=array && array->getCount()==1 ? OSDynamicCast(OSData,array->getObject(0)) : nullptr; - const bool canonical=data && data->getLength()==sizeof(dsiFixedMode.timing) && - !memcmp(data->getBytesNoCopy(),&dsiFixedMode.timing,sizeof(dsiFixedMode.timing)); - if(!count && !canonical)return kIOReturnUnsupported; - // Size storage from the accepted OS list, not a fixed number of modes. - // Prepare the complete replacement before touching native state so an - // allocation failure preserves the previous list and its timing pointers. - OSData *replacement=nullptr; - if(count) { - if(count>UINT32_MAX/sizeof(dsiFixedMode.timing))return kIOReturnNoResources; - replacement=OSData::withCapacity(count*sizeof(dsiFixedMode.timing)); - if(!replacement)return kIOReturnNoResources; - for(unsigned i=0;igetCount();++i) { - auto bytes=OSDynamicCast(OSData,array->getObject(i)); - if(!bytes || bytes->getLength()!=sizeof(dsiFixedMode.timing))continue; - const auto &t=*static_cast(bytes->getBytesNoCopy()); - if(XELP::DSI::AppleAdapter::clientTiming(dsiFixedMode.timing,t,i) || - XELP::DSI::AppleAdapter::presentationTiming(dsiFixedMode.timing,t,i)) { - if(!replacement->appendBytes(&t,sizeof(t))) { - replacement->release();return kIOReturnNoResources; - } - } - } - } - // Native storage retains the immutable wire mode. Qualified presentation - // timings are returned by validation, without changing the DSI link. - const auto result=FunctionCast(wrapDSISetDetailedTimings,callbackXE->orgDSIMode[0])(fb,dsiFixedMode.array); - if(result==kIOReturnSuccess && dsiModeCoherent(fb)) { - auto previous=dsiFixedMode.offeredData; - dsiFixedMode.offeredData=replacement; - dsiFixedMode.offered=replacement ? static_cast(replacement->getBytesNoCopy()) : nullptr; - dsiFixedMode.offerCount=count; - if(replacement)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSITimingList",replacement); - else IORegistryEntry::getRegistryRoot()->removeProperty("WEXDSITimingList"); - if(client)dsiFixedMode.clientMode=client; - OSSafeReleaseNULL(previous); - } else { - OSSafeReleaseNULL(replacement); - if(result==kIOReturnSuccess)return kIOReturnNotReady; - } - return result; - }(); - recordDSIClient(fb,1,array!=nullptr,result); - return result; -} -uint32_t XE::wrapDSIValidateMode(IOService *fb,int32_t mode,const void **description,const void **timing) { - recordDSIClient(fb,2,static_cast(mode),UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])(fb,mode,description,timing); - if(description)*description=nullptr;if(timing)*timing=nullptr; - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - const auto selected=dsiPresentation(mode);if(!selected)return kIOReturnBadArgument; - const auto result=FunctionCast(wrapDSIValidateMode,callbackXE->orgDSIMode[1])( - fb,XELP::DSI::AppleAdapter::FixedModeID,description,timing); - if(!result && timing) { - if(*timing!=dsiFixedMode.data->getBytesNoCopy())return kIOReturnNotReady; - if(selected->scalerFlags || selected->horizontalScaled || selected->verticalScaled)*timing=selected; - } - return result; - }(); - recordDSIClient(fb,2,static_cast(mode),result); - return result; -} -uint32_t XE::wrapDSIGetModes(IOService *fb,int32_t *modes) { - recordDSIClient(fb,3,modes!=nullptr,UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModes,callbackXE->orgDSIMode[2])(fb,modes); - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - if(!modes)return kIOReturnBadArgument; - if(dsiFixedMode.offerCount)for(unsigned i=0;i uint32_t { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeCount,callbackXE->orgDSIMode[3])(fb); - DSIModeScope scope(fb);return scope.lock && dsiModeCoherent(fb) ? (dsiFixedMode.offerCount ? dsiFixedMode.offerCount : 1) : 0; - }(); - recordDSIClient(fb,4,0,result); - return result; -} -uint32_t XE::wrapDSIPixelInfo(IOService *fb,int32_t mode,int32_t depth,int32_t aperture,void *info) { - const auto result=FunctionCast(wrapDSIPixelInfo,callbackXE->orgDSIPixelInfo)(fb,mode,depth,aperture,info); - if(dsiModeTarget(fb)) { - static uint32_t sequence=0;uint64_t record[28] {1,__sync_add_and_fetch(&sequence,1), - static_cast(mode),static_cast(depth),static_cast(aperture),result}; - if(info && !result)memcpy(record+6,info,172); - char key[40];snprintf(key,sizeof(key),"WEXDSIPixelCall%u",static_cast(record[1])%32); - IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); - } - return result; -} -// Observe native mode admission separately from the later published mode list. -uint32_t XE::wrapDSIValidateDetailed(IOService *fb,void *raw,uint64_t size) { - uint64_t record[60] {};const bool observe=dsiModeTarget(fb) && raw && size==204; - if(observe)memcpy(record+8,raw,204); - const auto result=FunctionCast(wrapDSIValidateDetailed,callbackXE->orgDSIDetailedValidation)(fb,raw,size); - // Match getInformationForDisplayMode: the retained scanout is RGB8. - // CoreDisplay consumes this metadata before it asks for a mode's info. - if(observe && !result)static_cast(raw)->depth=0; - if(observe) { - static uint32_t sequence=0;record[0]=1;record[1]=__sync_add_and_fetch(&sequence,1); - record[2]=result;record[3]=Display13FBMemory::field(fb,0x4318); - record[4]=Display13FBMemory::field(fb,0x431C); - memcpy(reinterpret_cast(record)+268,raw,204); - char key[40];snprintf(key,sizeof(key),"WEXDSIDetailedValidation%u",static_cast(record[1])%32); - IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); - } - return result; -} -bool XE::wrapDSIValidateSource(IOService *fb,void *raw) { - const auto result=FunctionCast(wrapDSIValidateSource,callbackXE->orgDSISourceValidation)(fb,raw); - if(dsiModeTarget(fb) && raw) { - static uint32_t sequence=0;uint64_t record[25] {1,__sync_add_and_fetch(&sequence,1),result, - Display13FBMemory::field(fb,0x4318),Display13FBMemory::field(fb,0x431C)}; - memcpy(record+5,raw,160); - char key[40];snprintf(key,sizeof(key),"WEXDSISourceValidation%u",static_cast(record[1])%32); - IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); - } - return result; -} -// The adopted DSI scanout is fixed-size. It has no panel-fitter resource -// transaction, so let the donor select its existing GPU scaling path. Preserve -// the real OS timing and transform; other framebuffer owners keep native policy. -bool XE::wrapDSIScalerSupport(void *scaler,uint32_t outputW,uint32_t outputH, - uint32_t sourceW,uint32_t sourceH,uint64_t clock,uint32_t flags,bool format) { - auto fb=scaler ? Display13FBMemory::field(scaler,0x10) : nullptr; - if(dsiModeTarget(fb))return false; - return FunctionCast(wrapDSIScalerSupport,callbackXE->orgDSIScalerSupport)( - scaler,outputW,outputH,sourceW,sourceH,clock,flags,format); -} - -uint32_t XE::wrapDSIGetModeInformation(IOService *fb,int32_t mode,void *raw) { - recordDSIClient(fb,5,static_cast(mode),UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(!dsiModeTarget(fb))return FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); - DSIModeScope scope(fb);if(!scope.lock || !dsiModeCoherent(fb))return kIOReturnNotReady; - const auto selected=dsiPresentation(mode);if(!raw || !selected)return kIOReturnBadArgument; - const bool rotated=(selected->scalerFlags&0x10)!=0; - const auto result=FunctionCast(wrapDSIGetModeInformation,callbackXE->orgDSIMode[4])(fb,mode,raw); - if(!result && !XELP::DSI::AppleAdapter::annotateMode(dsiFixedMode.mode, - *static_cast(raw),rotated,selected->horizontalScaled,selected->verticalScaled))return kIOReturnNotReady; - return result; - }(); - if(dsiModeTarget(fb)) { - DSIModeScope scope(fb); - for(unsigned i=0;scope.lock && i(mode)) { - uint32_t receipt[12] {1,static_cast(mode),result}; - if(raw && !result)memcpy(receipt+3,raw,36); - char key[40];snprintf(key,sizeof(key),"WEXDSIModeInfo%u",i); - IORegistryEntry::getRegistryRoot()->setProperty(key,receipt,sizeof(receipt));break; - } - } - recordDSIClient(fb,5,static_cast(mode),result); - return result; -} + + + + + + + + bool XE::processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { const char *os="__ZN31AppleIntelFramebufferController16getOSInformationEv"; @@ -7713,594 +6612,20 @@ bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { return true; } -bool XE::processDisplay13DSICache(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN31AppleIntelFramebufferController16hwUpdateRegCacheEP21AppleIntelFramebufferP21AppleIntelDisplayPath", - "__ZN31AppleIntelFramebufferController9hwGetCRTCEP21AppleIntelFramebufferP21AppleIntelDisplayPath"}; - const uint32_t offsets[]={0x4C4E2,0x4C79A}; - const uint8_t entries[][16]={ - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xFF,0x05,0x94,0x2A}, - {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0x89,0xF3,0x49,0x89,0xFE,0x48,0xFF,0x05}}; - if(!display13FramebufferRequested || size<=0x4C866)return false; - for(unsigned i=0;i<2;++i) { - const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],16))return false; - } - KernelPatcher::RouteRequest routes[]={ - {symbols[0],wrapDSIUpdateCache,orgDSICache[0]}, {symbols[1],wrapDSIGetCRTC,orgDSICache[1]}}; - const bool ready=patcher.routeMultiple(index,routes,2,address,size);patcher.clearError();return ready; -} - -// Pipe A belongs to DSI for this adoption phase. Malformed/ambiguous objects -// must not fall into a normal-transcoder read or the donor PWM calculation. -static bool dsiCachePath(OSObject *path) { - if(!path || path->getMetaClass()->getClassSize()<0x3730)return true; - const auto pipe=Display13FBMemory::field(path,0x3640); - if(pipe==0 || pipe>=3)return true; - const auto port=Display13FBMemory::field(path,0x3648); - return !port || port->getMetaClass()->getClassSize()<0x580 || Display13FBMemory::field(port,0x120)==0; -} - -// Called at the existing mode/disable owners, before any donor notification or -// cursor/MMIO write. A retained panel FB remains ours even if its path fields -// are damaged. Ambiguous objects must not escape into the normal DDI writer. -static bool dsiLifecycleTarget(IOService *fb,OSObject *path) { - if(dsiModeTarget(fb) || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return true; - const auto attached=Display13FBMemory::singlePath(fb); - return attached!=path || dsiCachePath(attached); -} - -// Native logical storage belongs to the same GPU pipe as the retained scanout. -// Never free the scanout or modify the DSI host while changing presentation. -bool XE::releaseDSILogical(IOService *fb) { - auto &o=dsiFixedMode;auto pipe=dsiPendingBacking.pipe; - if(!dsiModeTarget(fb) || !pipe || !dsiModeCPU[3] || - Display13FBMemory::field(fb,0x4340)!=o.logical)return false; - if(!o.logical)return true; - reinterpret_cast(dsiModeCPU[3])(fb); - const bool released=!Display13FBMemory::field(fb,0x4340) && - !Display13FBMemory::field(pipe,0x12B8) && !Display13FBMemory::field(pipe,0x12C0); - if(released)__atomic_store_n(&o.logical,uintptr_t(0),__ATOMIC_RELEASE); - else o.presentationFault=true; - return released; -} - -uint32_t XE::setDSIPresentation(IOService *fb,uint32_t mode,const XELP::DSI::AppleAdapter::Timing &requested) { - using Display13FBMemory::field; - auto &o=dsiFixedMode;auto &c=display13FBPower; - if(!dsiModeTarget(fb) || !dsiModeCoherent(fb) || !dsiNativeUpdateAllowed(fb) || o.changingThread)return kIOReturnNotReady; - auto path=Display13FBMemory::singlePath(fb);auto pipe=dsiPendingBacking.pipe; - if(!path || !pipe || !dsiArmedBackingOwned(fb,path))return kIOReturnNotReady; - const auto old=o.presentationReady ? o.presentation : o.timing; - const uint32_t oldMode=o.presentationReady ? o.activeMode : o.clientMode; - if(requested.scalerFlags==old.scalerFlags && requested.horizontalScaled==old.horizontalScaled && - requested.verticalScaled==old.verticalScaled && requested.horizontalInset==old.horizontalInset && - requested.verticalInset==old.verticalInset) { - o.presentation=requested;o.activeMode=mode;o.presentationReady=true; - *reinterpret_cast(reinterpret_cast(fb)+0x4250)=mode; - return kIOReturnSuccess; - } - IOLockLock(c.lock); - const bool idle=!c.dsiNativeWriters && !c.pipe[0].wmPending && !c.pipe[0].wmCommitting && - !field(path,0x35F0) && !c.dbuf.updating; - IOLockUnlock(c.lock);if(!idle)return kIOReturnNotReady; - o.changingThread=reinterpret_cast(current_thread()); - uint64_t receipt[16] {1,mode,requested.scalerFlags,oldMode,old.scalerFlags}; - uint64_t gpuState[13] {1}; - const auto captureGPU=[&](unsigned base) { - const unsigned offsets[]={0x1264,0x1268,0x128C,0x1290,0x1294,0x12D8}; - if(pipe->getMetaClass()->getClassSize()>=0x12DC) - for(unsigned i=0;i(pipe,offsets[i]); - }; - captureGPU(1); - const auto apply=[&](uint32_t selected,const XELP::DSI::AppleAdapter::Timing &timing) -> bool { - if(!releaseDSILogical(fb))return false; - auto cpu=o.timing;cpu.scalerFlags=timing.scalerFlags; - cpu.horizontalScaled=timing.horizontalScaled;cpu.verticalScaled=timing.verticalScaled; - cpu.horizontalInset=timing.horizontalInset;cpu.verticalInset=timing.verticalInset; - if(!XELP::DSI::AppleAdapter::prepareGeometry(fb,path,dsiProfile,cpu, - reinterpret_cast(dsiComputeGeometry),true))return false; - // The native allocator asks getCurrentDisplayMode/getPixelInformation - // through its GPU callback. Publish the selected CPU contract first, - // while other display writers remain excluded by changingThread. - o.presentation=timing;o.activeMode=selected;o.presentationReady=true; - *reinterpret_cast(reinterpret_cast(fb)+0x4250)=selected; - const auto result=reinterpret_cast(dsiModeCPU[0])(fb); - __atomic_store_n(&o.logical,field(fb,0x4340),__ATOMIC_RELEASE); - receipt[5]=result;receipt[6]=o.logical;receipt[7]=field(pipe,0x12B8);receipt[8]=field(pipe,0x12C0); - if(result)return false; // native failure can still leave a record to release - if(field(fb,0x4338)&0x30) { - auto record=reinterpret_cast(o.logical); - if(!record || record->kind!=2 || record->columns!=(field(fb,0x430C)*4+127)/128 || record->rows!=(field(fb,0x4310)+31)/32 || - record->pitch!=field(fb,0x4334) || record->bytes!=uint64_t(record->pitch)*field(fb,0x4310) || !record->memory || !record->map || - field(pipe,0x12B8)!=record->memory || field(pipe,0x12C0)!=record->map)return false; - } else if(o.logical || field(pipe,0x12B8) || field(pipe,0x12C0))return false; - reinterpret_cast(dsiModeCPU[1])(fb,0); - reinterpret_cast(dsiModeCPU[2])(fb); - return dsiArmedBackingOwned(fb,path) && dsiPublishedTarget(fb); - }; - const bool changed=apply(mode,requested); - receipt[9]=changed; - if(!changed) { - const bool restored=!o.presentationFault && apply(oldMode,old); - receipt[10]=restored;if(!restored)o.presentationFault=true; - } - receipt[11]=field(fb,0x4338);receipt[12]=field(fb,0x4348); - receipt[13]=o.presentationFault;receipt[14]=field(path,0xF8);receipt[15]=field(path,0xFC); - captureGPU(7); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentationGPU",gpuState,sizeof(gpuState)); - o.changingThread=0; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPresentation",receipt,sizeof(receipt)); - return changed ? kIOReturnSuccess : kIOReturnNotReady; -} - -// Native timing/resource setup can disable or reallocate before hwSetMode. -// DSI uses its own fixed-mode transaction to retain the adopted host link. -uint32_t XE::wrapDSISetDisplayMode(IOService *fb,int32_t mode,int32_t depth) { - recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - if(callbackXE->dsiPanelOnly && Display13FBMemory::field(fb,0x1DC))return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - // A bounded receipt of the real request. Keep the donor's mode - // reallocation path closed until the DSI surface transaction owns it. - uint64_t record[] {1,Display13FBMemory::field(fb,0x1DC), - Display13FBMemory::field(fb,0x1E0),static_cast(mode), - static_cast(depth),dsiModeTarget(fb),callbackXE->dsiResourcesReady, - Display13FBMemory::field(fb,0x420C)}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIModeRequest",record,sizeof(record)); - const auto selected=dsiPresentation(mode); - if(!selected || depth || !dsiModeTarget(fb))return kIOReturnNotReady; - const auto presentation=*selected; - if(dsiNativeUpdateAllowed(fb) && !dsiPendingTarget(fb))callbackXE->probeDSINativeBacking(fb); - if(!callbackXE->publishDSINativeMode(fb))return kIOReturnNotReady; - return callbackXE->setDSIPresentation(fb,mode,presentation); - } - } - return FunctionCast(wrapDSISetDisplayMode,callbackXE->orgDSIMode[5])(fb,mode,depth); - }(); - recordDSIClient(fb,6,(uint64_t(static_cast(mode))<<32)|static_cast(depth),result); - return result; -} -// Caller holds the native FB mutex through the admitted writer. quiesce uses -// the same mutex, so a stop cannot interleave after this check. This is one -// update boundary, not a certificate for other native writers or Session Stop. -bool XE::dsiNativeUpdateAllowed(IOService *fb) { - if(!callbackXE->adlnOutputWiring.dsi)return true; - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; - const auto path=Display13FBMemory::singlePath(fb); - if(!dsiLifecycleTarget(fb,path))return true; - auto &c=display13FBPower;if(!c.lock || dsiFixedMode.changingThread || dsiFixedMode.presentationFault)return false; - IOLockLock(c.lock); - const bool ready=(!dsiPendingTarget(fb) || (dsiPublishedTarget(fb) && dsiPendingBacking.modeReady)) && !callbackXE->dsiStopAttempted && callbackXE->dsiResourcesReady && - !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && - !c.dbuf.faulted && !c.watermarkFaulted && c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && - c.transcoders.dsi0Pipe==0 && path && path->getMetaClass()->getClassSize()>=0x3730 && - Display13FBMemory::field(fb,0x1D0)==c.controller && - Display13FBMemory::field(path,0x32B0)==fb && Display13FBMemory::field(path,0x3640)==0 && - Display13FBMemory::field(path,0x3644)==2 && c.dsiPort && c.dsiPort->getMetaClass()->getClassSize()>=0x580 && - Display13FBMemory::field(path,0x3648)==c.dsiPort && Display13FBMemory::field(c.dsiPort,0x550)==1; - IOLockUnlock(c.lock);return ready; -} - -// Owner lock held. DSI reserves pipe A even after its mapping is -// withdrawn. Late shared callbacks must not revive it or fault peer outputs. -// An unbound/ambiguous callback cannot establish a different output owner. -bool XE::dsiSharedWriterStopped(IOService *fb,unsigned pipe) { - return callbackXE->adlnOutputWiring.dsi && ((dsiPendingTarget(fb) && !dsiPendingBacking.modeReady) || - (callbackXE->dsiStopAttempted && (pipe==0 || pipe>=3 || dsiModeTarget(fb)))); -} - -// Cursor image/state bodies can wait for a frame, so do not extend their -// native FB-lock intervals. Count admitted DSI calls instead. Stop takes the -// same FB mutex before testing the count and cannot start until it is zero. -int XE::beginDSINativeWriter(IOService *controller,IOService *fb) { - if(!callbackXE->adlnOutputWiring.dsi)return 0; - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return -1; - DSIModeScope scope(fb);if(!scope.lock)return -1; - if(!dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb)))return 0; - if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return -1; - auto &c=display13FBPower;IOLockLock(c.lock); - const bool admitted=controller==c.controller && c.dsiNativeWriters!=UINT32_MAX; - if(admitted)++c.dsiNativeWriters; - IOLockUnlock(c.lock);return admitted ? 1 : -1; -} -void XE::endDSINativeWriter(int token) { - if(token!=1)return; - auto &c=display13FBPower;IOLockLock(c.lock); - if(c.dsiNativeWriters)--c.dsiNativeWriters; - else adlnFault(c.memoryFaulted,__LINE__); - IOLockUnlock(c.lock); -} -uint32_t XE::wrapDSICursorImage(IOService *controller,IOService *fb,void *info) { - const int token=beginDSINativeWriter(controller,fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSICursorImage,callbackXE->orgDSICursor[0])(controller,fb,info); - endDSINativeWriter(token);return result; -} -uint32_t XE::wrapDSICursorState(IOService *controller,IOService *fb,int32_t x,int32_t y,bool visible,bool commit) { - const int token=beginDSINativeWriter(controller,fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSICursorState,callbackXE->orgDSICursor[1])(controller,fb,x,y,visible,commit); - endDSINativeWriter(token);return result; -} -// Transaction bodies may wait and may call armUpdate, cursor or color hooks. -// Count the complete body without changing its native lock/wait intervals. -int XE::beginDSIFramebufferWriter(IOService *fb) { - if(!callbackXE->adlnOutputWiring.dsi)return 0; - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return -1; - return beginDSINativeWriter(Display13FBMemory::field(fb,0x1D0),fb); -} -uint32_t XE::wrapDSIFlipTransaction(IOService *fb,void *transaction,uint64_t options,void *params) { - const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSIFlipTransaction,callbackXE->orgDSITransaction[0])(fb,transaction,options,params); - endDSINativeWriter(token);return result; -} -uint32_t XE::wrapDSIGammaTransaction(IOService *fb,void *transaction,void *params) { - const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSIGammaTransaction,callbackXE->orgDSITransaction[1])(fb,transaction,params); - endDSINativeWriter(token);return result; -} -uint32_t XE::wrapDSICscTransaction(IOService *fb,void *transaction,void *matrix,uint64_t options,bool final) { - const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSICscTransaction,callbackXE->orgDSITransaction[2])(fb,transaction,matrix,options,final); - endDSINativeWriter(token);return result; -} -uint32_t XE::wrapDSIPlaneGammaTransaction(IOService *fb,void *transaction,bool post) { - const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSIPlaneGammaTransaction,callbackXE->orgDSITransaction[3])(fb,transaction,post); - endDSINativeWriter(token);return result; -} -uint32_t XE::wrapDSISetGammaTable(IOService *fb,uint32_t channels,uint32_t count,uint32_t bits,void *data,bool sync) { - const int token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - const auto result=FunctionCast(wrapDSISetGammaTable,callbackXE->orgDSITransaction[4])(fb,channels,count,bits,data,sync); - endDSINativeWriter(token);return result; -} -// Power/backlight use DSI owners. Block only the donor branches that enter -// unsupported native transport controls; let ordinary OS attributes reach -// their native implementations under the existing writer lifetime. -uint32_t XE::wrapDSISetAttribute(IOService *fb,uint32_t attribute,uintptr_t value) { - recordDSIClient(fb,7,attribute,UINT32_MAX); - const auto result=[&]() -> uint32_t { - int token=0; - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(attribute==0x706F7772 || attribute==0x70777273) { // powr / IOGraphics pwrs relay. - if(value>UINT32_MAX)return kIOReturnUnsupported; - return wrapDSISetPowerState(fb,static_cast(value)); - } - if(attribute==0x626B7376)return callbackXE->saveDSIBrightness(fb,value); - if(XELP::DSI::AppleAdapter::transportAttribute(attribute))return kIOReturnUnsupported; - token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - } - } - const auto result=FunctionCast(wrapDSISetAttribute,callbackXE->orgDSIAttributes[0])(fb,attribute,value); - endDSINativeWriter(token);return result; - }(); - recordDSIClient(fb,7,attribute,result); - recordDSIAttribute(fb,7,attribute,value,result); - return result; -} -// Bounded CPU-only history: the last attribute alone hides earlier rejected UI -// requests behind a successful gamma flush. No MMIO or object pointers are exposed. -void XE::recordDSIAttribute(IOService *fb,unsigned event,uint32_t attribute,uintptr_t value,uint32_t result) { - auto s=callbackXE;auto &c=display13FBPower; - if(!s->adlnOutputWiring.dsi || !dsiModeTarget(fb) || !c.lock)return; - DSIModeScope scope(fb);if(!scope.lock)return; - static uint64_t history[2+16*8] {1,0}; - IOLockLock(c.lock); - auto controller=Display13FBMemory::field(fb,0x1D0); - if(controller && controller==c.controller && controller==s->adlnTopologyController) { - const auto sequence=++history[1];auto row=history+2+((sequence-1)%16)*8; - row[0]=sequence;row[1]=event;row[2]=attribute;row[3]=value<=UINT32_MAX?value:UINT64_MAX;row[4]=result; - row[5]=Display13FBMemory::field(controller,0xE58); - row[6]=Display13FBMemory::field(controller,0xE5C); - row[7]=Display13FBMemory::field(fb,0x44E0); - } - IOLockUnlock(c.lock); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIAttributeHistory",history,sizeof(history)); -} -void XE::publishDSIBacklight() { - auto &c=display13FBPower;if(!c.lock)return; - IOLockLock(c.lock);const auto &b=dsiBacklight; - uint64_t receipt[]={1,b.initialized,b.faulted,b.requested,b.applied,b.pwm.period,b.minimum, - b.control,b.requests,b.writes,dsiRuntimePower.sleeping}; - IOLockUnlock(c.lock); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIBacklight",receipt,sizeof(receipt)); -} -// Exact donor setAttribute('bksv') stores its 32-bit value at controller+E5C. -// This is user preference bookkeeping, not a hardware brightness command. -uint32_t XE::saveDSIBrightness(IOService *fb,uintptr_t value) { - auto &c=display13FBPower; - if(value>UINT32_MAX)return kIOReturnBadArgument; - if(!c.lock || !dsiModeTarget(fb))return kIOReturnNotReady; - IOLockLock(c.lock); - const bool owned=dsiResourcesReady && c.controller && c.controller==adlnTopologyController && - Display13FBMemory::field(fb,0x1D0)==c.controller && c.pipe[0].memoryFramebuffer==fb; - if(owned)*reinterpret_cast(static_cast(c.controller)+0xE5C)=static_cast(value); - IOLockUnlock(c.lock);return owned?kIOReturnSuccess:kIOReturnNotReady; -} -// The caller holds the same framebuffer mutex as stop/resume. Deferred requests -// update only software; the panel-on sequence applies PWM before its enable GPIO. -uint32_t XE::setDSIBrightness(IOService *fb,uintptr_t value) { - auto &c=display13FBPower; - if(value>XELP::DSI::Backlight::Maximum)return kIOReturnBadArgument; - if(!c.lock || !dsiModeTarget(fb))return kIOReturnNotReady; - const bool awake=dsiNativeUpdateAllowed(fb); - IOLockLock(c.lock); - const bool owned=dsiResourcesReady && dsiBacklight.initialized && !dsiRuntimePower.failed && - !dsiBacklight.faulted && !c.memoryFaulted && !c.clockFaulted && !c.watermarkFaulted && - !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && - c.pipe[0].memoryFramebuffer==fb && !c.pipe[0].destroyingPath && - Display13FBMemory::field(fb,0x1D0)==c.controller && - c.controller==adlnTopologyController && adlnWriterMappingBound(); - const bool deferred=owned && dsiPowerTest && dsiRuntimePower.sleeping && dsiStopAttempted && - dsiStop.complete() && dsiPanelStop.complete && dsiIRQParked && !dsiMemoryReleased && !dsiDDIReleased; - struct IO:Display13FBBootIO { - bool owned;IO(IOMemoryMap *m,bool o):Display13FBBootIO{m},owned(o){} - bool ready(){return owned;} - void write(uint32_t reg,uint32_t value) { - if(owned && reg==XELP::DSI::Backlight::Duty && readable(reg)) - *reinterpret_cast(map->getVirtualAddress()+reg)=value; - } - } io{c.map,owned && awake && !dsiStopAttempted}; - const bool accepted=owned && (deferred || (io.ready() && c.bridge.outputHeld(io,0,0))) && dsiBacklight.request(value); - const bool complete=accepted && (deferred || dsiBacklight.apply(io)); - // The donor getter returns this 16-bit requested value with range 0..65535. - if(complete)*reinterpret_cast(static_cast(c.controller)+0xE58)=dsiBacklight.requested; - IOLockUnlock(c.lock);publishDSIBacklight(); - return complete?kIOReturnSuccess:kIOReturnNotReady; -} -uint32_t XE::wrapDSISetConnectionAttribute(IOService *fb,int32_t connection,uint32_t attribute,uintptr_t value) { - recordDSIClient(fb,8,attribute,UINT32_MAX); - const auto result=[&]() -> uint32_t { - int token=0; - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(connection!=0)return kIOReturnUnsupported; - if(attribute==0x626B6C74)return callbackXE->setDSIBrightness(fb,value); - if(XELP::DSI::AppleAdapter::transportConnectionAttribute(attribute))return kIOReturnUnsupported; - token=beginDSIFramebufferWriter(fb);if(token<0)return kIOReturnNotReady; - } - } - const auto result=FunctionCast(wrapDSISetConnectionAttribute,callbackXE->orgDSIAttributes[1])(fb,connection,attribute,value); - endDSINativeWriter(token);return result; - }(); - recordDSIClient(fb,8,attribute,result); - recordDSIAttribute(fb,8,attribute,value,result); - return result; -} -uint32_t XE::wrapDSISetPowerState(IOService *fb,uint32_t state) { - recordDSIClient(fb,9,state,UINT32_MAX); - const auto result=[&]() -> uint32_t { - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,Display13FBMemory::singlePath(fb))) { - if(callbackXE->dsiPowerTest && state<=2 && dsiModeTarget(fb)) { - auto controller=Display13FBMemory::field(fb,0x1D0); - auto path=Display13FBMemory::singlePath(fb); - auto &power=callbackXE->dsiRuntimePower; - const bool ready=!power.failed && (state==2 ? (power.sleeping ? callbackXE->resumeDSIPath(controller,fb,path) : dsiNativeUpdateAllowed(fb)) : - (power.sleeping ? true : (dsiNativeUpdateAllowed(fb) && callbackXE->quiesceDSIPath(controller,fb,path,true)))); - if(!ready)return kIOReturnNotReady; - *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; - return kIOReturnSuccess; - } - // One-way runtime stop experiment through the real power callback. - // Recovery is a reboot; state 2 never replays the donor's DDI path. - if(callbackXE->dsiStopTest && state<2 && dsiModeTarget(fb)) { - const bool stopped=callbackXE->dsiMemoryReleased && callbackXE->dsiDDIReleased && - callbackXE->dsiClockStop.complete() && callbackXE->dsiPanelStop.complete; - if(stopped)return kIOReturnSuccess; - if(!callbackXE->dsiNativeWriterHooksReady || !dsiNativeUpdateAllowed(fb))return kIOReturnNotReady; - auto controller=Display13FBMemory::field(fb,0x1D0); - auto path=Display13FBMemory::singlePath(fb); - if(!callbackXE->quiesceDSIPath(controller,fb,path))return kIOReturnNotReady; - *reinterpret_cast(reinterpret_cast(fb)+0x4284)=state; - return kIOReturnSuccess; - } - // Match only the donor's already-Wake no-op. Do not publish a new - // native power state without executing a DSI power transaction. - if(state!=2 || Display13FBMemory::field(fb,0x4284)!=2)return kIOReturnUnsupported; - return callbackXE->dsiNativeWriterHooksReady && dsiNativeUpdateAllowed(fb) ? kIOReturnSuccess : kIOReturnNotReady; - } - } - return FunctionCast(wrapDSISetPowerState,callbackXE->orgDSIAttributes[2])(fb,state); - }(); - recordDSIClient(fb,9,state,result); - return result; -} -uint32_t XE::wrapDSISetStream(IOService *controller,IOService *fb,OSObject *path,const void *timing) { - if(callbackXE->adlnOutputWiring.dsi) { - if(!fb || fb->getMetaClass()->getClassSize()<0x4B90)return kIOReturnNotReady; - DSIModeScope scope(fb);if(!scope.lock)return kIOReturnNotReady; - if(dsiLifecycleTarget(fb,path))return kIOReturnNotReady; - } - return FunctionCast(wrapDSISetStream,callbackXE->orgDSIAttributes[3])(controller,fb,path,timing); -} -bool XE::processDSINativeAttributes(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN21AppleIntelFramebuffer12setAttributeEjm", - "__ZN21AppleIntelFramebuffer25setAttributeForConnectionEijm", - "__ZN21AppleIntelFramebuffer24setFramebufferPowerStateEj", - "__ZN31AppleIntelFramebufferController11hwSetStreamEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"}; - const uint32_t offsets[]={0x3331E,0x352D4,0x24DE8,0x50DB0}; - const uint8_t entries[][20]={ - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18,0x48,0xFF,0x05}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x53,0x50,0x41,0x89,0xF6,0x49,0x89,0xFF,0x48,0xFF,0x05,0xF1}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x81,0xEC,0x68,0x01,0x00,0x00}}; - if(!display13FramebufferRequested || size<0x50DC4)return false; - for(unsigned i=0;i<4;++i) { - const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; - } - // Standard routes copy 17/17/16/20-byte prologues, before RIP counters. - KernelPatcher::RouteRequest routes[]={ - {symbols[0],wrapDSISetAttribute,orgDSIAttributes[0]}, - {symbols[1],wrapDSISetConnectionAttribute,orgDSIAttributes[1]}, - {symbols[2],wrapDSISetPowerState,orgDSIAttributes[2]}, - {symbols[3],wrapDSISetStream,orgDSIAttributes[3]}}; - const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; -} - -bool XE::processDSINativeTransactions(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN21AppleIntelFramebuffer22PerformFlipTransactionEP30IOAccelDisplayPipeTransaction2yP21FlipTransactionParams", - "__ZN21AppleIntelFramebuffer23PerformGammaTransactionEP30IOAccelDisplayPipeTransaction2P20GammaTransactionArgs", - "__ZN21AppleIntelFramebuffer21PerformCscTransactionEP30IOAccelDisplayPipeTransaction2P29IOAccelDisplayPipeColorMatrixyb", - "__ZN21AppleIntelFramebuffer28PerformPlaneGammaTransactionEP30IOAccelDisplayPipeTransaction2b", - "__ZN21AppleIntelFramebuffer13setGammaTableEjjjPvb"}; - const uint32_t offsets[]={0x2B602,0x2C800,0x2D03C,0x2D838,0x36E72}; - // Whole prologues; no relocated RIP-relative counter in these 17 bytes. - const uint8_t entries[][17]={ - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x48}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x68}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x58}}; - if(!display13FramebufferRequested || size<0x36E83)return false; - for(unsigned i=0;i<5;++i) { - const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],17))return false; - } - KernelPatcher::RouteRequest routes[]={ - {symbols[0],wrapDSIFlipTransaction,orgDSITransaction[0]}, - {symbols[1],wrapDSIGammaTransaction,orgDSITransaction[1]}, - {symbols[2],wrapDSICscTransaction,orgDSITransaction[2]}, - {symbols[3],wrapDSIPlaneGammaTransaction,orgDSITransaction[3]}, - {symbols[4],wrapDSISetGammaTable,orgDSITransaction[4]}}; - const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; -} - -bool XE::processDSINativeWriters(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN31AppleIntelFramebufferController16hwSetCursorImageEP21AppleIntelFramebufferP20IOHardwareCursorInfo", - "__ZN31AppleIntelFramebufferController16hwSetCursorStateEP21AppleIntelFramebufferiibb"}; - const uint32_t offsets[]={0x558F6,0x55DA8}; - const uint8_t entries[][20]={ - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28,0x49,0x89,0xD4}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38,0x45,0x89,0xCE}}; - if(!display13FramebufferRequested || size<=0x55DBC)return false; - for(unsigned i=0;i<2;++i) { - const auto fn=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entries[i],20))return false; - } - KernelPatcher::RouteRequest routes[]={ - {symbols[0],wrapDSICursorImage,orgDSICursor[0]}, - {symbols[1],wrapDSICursorState,orgDSICursor[1]}}; - const bool ready=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError();return ready; -} -bool XE::updateDSIBootCache(IOService *controller,IOService *fb,OSObject *path) { - auto &c=display13FBPower; - if(!c.lock || !controller || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || - controller->getMetaClass()->getClassSize()<0x1C20 || path->getMetaClass()->getClassSize()<0x3730)return false; - DSIModeScope scope(fb);if(!scope.lock)return false; - IOLockLock(c.lock);Display13FBBootIO io{c.map}; - const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring,false); - const bool bound=dsiCacheHooksReady && !display13Offline && dsiResourcesReady && c.map && - adlnOutputWiring.dsi==1 && controller==c.controller && controller==adlnTopologyController && - binding.status==TGLFBBinding::Status::Complete && binding.pipe==0 && binding.ddi==0 && - c.transcoders.dsi0Pipe==0 && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !c.bridge.state.suspended && - Display13FBMemory::field(path,0x3648)==c.dsiPort && c.dsiPort && - Display13FBMemory::field(c.dsiPort,0x550)==1 && - Display13FBMemory::field(path,0x34)==0x4A480 && - c.bootDSI.sameSource(dsiBoot.source) && c.bridge.outputHeld(io,0,0); - bool ready=false; - if(bound) { - auto plane=Display13FBMemory::field(path,0x32C8); - auto scaler=Display13FBMemory::field(path,0x32C0); - bool addresses=true; - for(unsigned i=0;i<6;++i)addresses=addresses && - Display13FBMemory::field(plane,XELP::DSI::NativeCache::PlaneAddressFields[i])==XELP::DSI::NativeCache::PlaneRegisters[i]; - uint32_t scale=0; - if(scaler) { - addresses=addresses && scaler->getMetaClass()->getClassSize()>=0x50; - if(addresses) { - scale=Display13FBMemory::field(scaler,0x30); - addresses=Display13FBMemory::field(scaler,0x28)==c.access && - Display13FBMemory::field(scaler,0x18)==0 && (scale==0x68180 || scale==0x68280) && - Display13FBMemory::field(scaler,0x34)==scale-0x10 && - Display13FBMemory::field(scaler,0x38)==scale-0xC; - } - } - XELP::DSI::NativeCache::View view; - ready=addresses && (dsiPublishedTarget(fb) ? XELP::DSI::NativeCache::readActive(io,dsiBoot,scale,view) : - XELP::DSI::NativeCache::read(io,dsiBoot,xelpMemory,scale,view)); - if(ready)XELP::DSI::NativeCache::publish(view,path,plane,scaler); - } - IOLockUnlock(c.lock);return ready; -} -void XE::wrapDSIUpdateCache(IOService *controller,IOService *fb,OSObject *path) { - if(callbackXE->adlnOutputWiring.dsi && dsiCachePath(path)) { - if(!callbackXE->updateDSIBootCache(controller,fb,path)) { - auto &c=display13FBPower; - if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} - } - return; - } - FunctionCast(wrapDSIUpdateCache,callbackXE->orgDSICache[0])(controller,fb,path); -} -void XE::wrapDSIGetCRTC(IOService *controller,IOService *fb,OSObject *path) { - if(callbackXE->adlnOutputWiring.dsi && dsiCachePath(path)) { - // DSI brightness belongs to its panel backend. The donor's internal - // FB branch divides by an ordinary PWM period that may be zero here. - wrapDSIUpdateCache(controller,fb,path);return; - } - FunctionCast(wrapDSIGetCRTC,callbackXE->orgDSICache[1])(controller,fb,path); -} -bool XE::processDisplay13DSIBoot(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbol="__ZN31AppleIntelFramebufferController13probeBootPipeEPbPN17AppleIntelPortHAL3DDIE"; - const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89}; - // setupBootDisplay consumes the returned pipe, builtin bool and DDI before - // allocating the real native path. Do not replace that allocation with a - // fabricated path. Its DSI cache/stop adapters remain separate prerequisites. - const uint8_t call[]={0xE8,0xA7,0x1D,0x00,0x00}; - if(!display13FramebufferRequested || size<=0x455E8)return false; - const auto fn=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); - if(fn!=address+0x453D6 || memcmp(reinterpret_cast(fn),entry,sizeof(entry)) || - memcmp(reinterpret_cast(address+0x4362A),call,sizeof(call)))return false; - KernelPatcher::RouteRequest route(symbol,wrapDSIBootProbe,orgDSIBootProbe); - const bool ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError();return ready; -} -uint32_t XE::wrapDSIBootProbe(IOService *controller,bool *builtin,uint32_t *ddi) { - auto self=callbackXE; - if(!self->adlnOutputWiring.dsi) - return FunctionCast(wrapDSIBootProbe,self->orgDSIBootProbe)(controller,builtin,ddi); - // Failure must not leak an old builtin/DDI result to setupBootDisplay. - if(builtin)*builtin=false;if(ddi)*ddi=UINT32_MAX; - auto &c=display13FBPower; - if(!self->dsiBootProbeReady || display13Offline || !c.lock || !controller || - controller->getMetaClass()->getClassSize()<0x1C20 || - !__atomic_load_n(&self->dsiResourcesReady,__ATOMIC_ACQUIRE))return 0xFFFF; - IOLockLock(c.lock);Display13FBBootIO io{c.map}; - auto port=c.dsiPort; - const auto config=port && port->getMetaClass()->getClassSize()>=0x6B0 ? - Display13FBMemory::field(port,0x548) : nullptr; - const bool valid=c.map && self->adlnOutputWiring.dsi==1 && controller==c.controller && - controller==self->adlnTopologyController && c.transcoders.dsi0Pipe==0 && - !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && - c.access && Display13FBTopology::bound(self->adlnPlatform,config,0) && - self->adlnOutputWiring.matches(config) && config[0]==0 && - Display13FBMemory::field(port,0x120)==0 && - Display13FBMemory::field(port,0x550)==1 && - Display13FBMemory::field(port,0x578)==c.access && - self->dsiBoot.complete() && c.bootDSI.sameSource(self->dsiBoot.source) && - c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,self->dsiBoot,xelpMemory); - if(valid){if(builtin)*builtin=true;if(ddi)*ddi=0;} - IOLockUnlock(c.lock);return valid?0U:0xFFFFU; -} + + + + + + + + + uint32_t XE::wrapDisplay13ProbeMode(OSObject *port) { auto self=callbackXE; @@ -8366,40 +6691,6 @@ unsigned XE::display13TCTarget(OSObject *port,bool cleanup) { return config[0]; } -// The Display13 native Mode hook has one owner for TC, Combo and DSI. -// Caller must have installed the source/mapping and native power references at -// the adoption boundary. A board record or firmware-on bits alone never admit. -uint32_t XE::setDSIPortMode(OSObject *port,uint32_t wanted) { - auto self=callbackXE;auto &c=display13FBPower; - if(!self->display13FBTCHooksReady || !self->display13TCBaseMode || !c.lock || - !port || port->getMetaClass()->getClassSize()<0x6B0)return kIOReturnNotReady; - // Mode zero must be connected to an actual DSI stop/cleanup transaction. - // Reject it for now, including when the native caller asks only for reset. - if(wanted!=1)return kIOReturnUnsupported; - IOLockLock(c.lock); - auto config=Display13FBMemory::field(port,0x548); - const auto previous=Display13FBMemory::field(port,0x550); - const bool bound=self->adlnOutputWiring.dsi==1 && self->adlnOutputWiring.matches(config) && - config[0]==0 && Display13FBTopology::bound(self->adlnPlatform,config,0) && - c.controller && c.controller==self->adlnTopologyController && c.access && - Display13FBMemory::field(port,0x578)==c.access && - Display13FBMemory::field(port,0x120)==0 && - c.transcoders.dsi(0) && c.bootDSI.complete() && c.bootDSI.identity.pipe==0 && - c.bootDSI.identity.phy==0 && c.bootDSI.identity.transcoder==0 && - (!c.dsiPort || c.dsiPort==port) && previous<=1 && - !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted; - Display13FBBootIO io{c.map}; - const bool ready=bound && c.bridge.outputHeld(io,0,0) && XELP::DSI::sourceUnchanged(io,c.bootDSI); - uint32_t result=kIOReturnNotReady; - if(ready) { - // Exact donor base setPortMode(1) only records port+0x550. The HAL - // setter and mode0's reset callback must never run on this path. - result=reinterpret_cast(self->display13TCBaseMode)(port,1); - if(result==kIOReturnSuccess && Display13FBMemory::field(port,0x550)!=1)result=kIOReturnNotReady; - if(result==kIOReturnSuccess && !c.dsiPort){port->retain();c.dsiPort=port;} - } - IOLockUnlock(c.lock);return result; -} uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { auto self=callbackXE;auto &c=display13FBPower; @@ -10041,58 +8332,6 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { return accepted; } -// Called only after the real PCI/workloop IRQ source and native memory have -// been qualified. Keep the FB mutex across mode publication; never call the -// native timing setter or registry code with the display-owner lock held. -bool XE::adoptDSIBootPath(IOService *fb,OSObject *path) { - auto &c=display13FBPower;auto &irq=display13FBIRQ; - if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || - path->getMetaClass()->getClassSize()<0x3730)return false; - DSIModeScope scope(fb);if(!scope.lock)return false; - const auto qualified=[&]() { - if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiCacheHooksReady || !dsiBootProbeReady || - display13Offline || !c.map || !c.power || !c.controller || c.controller!=adlnTopologyController || - c.pipe[0].memoryFramebuffer!=fb || c.memoryFaulted || c.clockFaulted || - c.bridge.state.faulted || c.bridge.state.suspended || c.transcoders.dsi0Pipe!=0 || !irq.lock || - !Display13Power::objectAssociations(c.power,0x80,c.controller,0x1C20))return false; - const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); - if(binding.status!=TGLFBBinding::Status::Complete || binding.pipe!=0 || binding.ddi!=0 || - Display13FBMemory::field(path,0x3648)!=c.dsiPort || !c.dsiPort || - Display13FBMemory::field(c.dsiPort,0x550)!=1 || - Display13FBMemory::field(path,0x3644)!=2 || - Display13FBMemory::field(fb,0x4348) || - !Display13FBMemory::field(fb,0x44DE) || !Display13FBMemory::field(fb,0x1E7))return false; - Display13FBBootIO io{c.map}; - if(!c.bridge.outputHeld(io,0,0) || !dsiBoot.complete() || - !c.bootDSI.sameSource(dsiBoot.source) || !XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory))return false; - const auto memory=display13FBInspectMemory(fb,true,0,false); - if(memory.status!=Display13FBMemory::Status::Complete || memory.control!=dsiBoot.control || - memory.stride!=dsiBoot.stride || memory.size!=dsiBoot.size || memory.surface!=dsiBoot.surface)return false; - const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const bool enabled=irq.controller==c.controller && (irq.admittedPipes&1) && !irq.quiescing && - irq.state.begun && irq.state.enabled && !irq.state.faulted && irq.state.transcoders.dsi0Pipe==0; - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return enabled; - }; - IOLockLock(c.lock);const bool before=qualified();IOLockUnlock(c.lock); - bool ready=before && bindDSIFixedMode(fb,dsiProfile); - IOLockLock(c.lock); - ready=ready && qualified(); - if(ready) { - const auto key=reinterpret_cast(path); - ready=c.bridge.bootAdopted ? c.bridge.bootPath==key : - c.bridge.adoptBootPath(key,0,0).status==Display13Power::Status::Complete; - ready=ready && Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0x1C20); - } - if(!ready) { - // The setter may already have published its raw array pointer. Retain - // all backing/objects until stop; only withdraw mode readiness here. - if(dsiModeTarget(fb))dsiFixedMode.ready=false; - adlnFault(c.memoryFaulted,__LINE__); - } - IOLockUnlock(c.lock); - if(!ready && dsiModeTarget(fb))fb->setProperty("WEXDSIFixedModeReady",false); - return ready; -} void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) { FunctionCast(wrapAlderLakeNSetupWorkLoop,callbackXE->orgAlderLakeNSetupWorkLoop)(controller,workLoop); @@ -10601,139 +8840,7 @@ bool XE::adlnFreeStoppedPipe(IOService *fb) { return freed; } -// The whole freeDisplayPathResources body also touches global audio and can -// reset a normal DDI port. Keep only its audited CPU leaves for the DSI path. -bool XE::processDSIPathDisposal(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - const char *symbols[]={ - "__ZN31AppleIntelFramebufferController9freePlaneEP15AppleIntelPlane", - "__ZN31AppleIntelFramebufferController10freeScalerEP16AppleIntelScaler", - "__ZN21AppleIntelFramebuffer17detachDisplayPathEP21AppleIntelDisplayPath", - "__ZN21AppleIntelFramebuffer17removeDisplayPathEP21AppleIntelDisplayPath"}; - const uint32_t offsets[]={0x5751C,0x5739A,0x67FB4,0x68EA4}; - const uint8_t entries[][16]={ - {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xC9,0x96,0x08,0x00,0x48,0x85,0xF6,0x74,0x50}, - {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0xEB,0x97,0x08,0x00,0x48,0x85,0xF6,0x74,0x20}, - {0x55,0x48,0x89,0xE5,0x48,0xFF,0x05,0x19,0xC5,0x07,0x00,0x48,0x8B,0x05,0x0A,0xC5}, - {0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x48,0xFF,0x05,0xEE,0xB8,0x07,0x00,0x48,0x8B}}; - if(size<=0x138BB8)return false; - mach_vm_address_t functions[4] {}; - for(unsigned i=0;i<4;++i) { - functions[i]=patcher.solveSymbol(index,symbols[i],address,size);patcher.clearError(); - if(functions[i]!=address+offsets[i] || memcmp(reinterpret_cast(functions[i]),entries[i],16))return false; - } - const auto map=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController12fPipeToFBMapE",address,size); - patcher.clearError();if(map!=address+0x138BB0)return false; - dsiFreePlane=functions[0];dsiFreeScaler=functions[1];dsiDetachPath=functions[2];dsiRemovePath=functions[3];dsiPipeMap=map; - return true; -} -// Caller retains FB/path, owns its recursive mutex, and has detached its DDB -// reservation. Gate ownership excludes queued native work across PG put and -// CPU disposal. A failed put leaves the native path arrays and objects intact. -bool XE::disposeDSIPath(IOService *fb,OSObject *path) { - auto &c=display13FBPower; - if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || - path->getMetaClass()->getClassSize()<0x3730 || !dsiFreePlane || !dsiFreeScaler || - !dsiDetachPath || !dsiRemovePath || !dsiPipeMap || - !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; - IOLockLock(c.lock);auto loop=c.workLoop;if(loop)loop->retain();IOLockUnlock(c.lock); - if(!loop)return false; - const bool inGate=reinterpret_cast(adlnWorkLoopInGate)(loop); - const bool acquired=!inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); - if(!inGate && !acquired){loop->release();return false;} - uint64_t record[14] {1};IOLockLock(c.lock); - auto controller=static_cast(c.controller); - auto port=c.dsiPort; - auto plane=Display13FBMemory::field(path,0x32C8); - auto scaler=Display13FBMemory::field(path,0x32C0); - auto planeScaler=plane && plane->getMetaClass()->getClassSize()>=0x1C8 ? Display13FBMemory::field(plane,0x88) : nullptr; - auto pipeMap=*reinterpret_cast(dsiPipeMap); - const auto scalerOwned=[&](OSObject *o) { - return !o || (o->getMetaClass()->getClassSize()>=0x58 && - Display13FBMemory::field(o,0x10)==fb && Display13FBMemory::field(o,0x20)==1 && - Display13FBMemory::field(o,0x18)==0); - }; - bool planeTailEmpty=true;for(unsigned i=1;i<5;++i)if(Display13FBMemory::field(path,0x32C8+8*i))planeTailEmpty=false; - const auto attached=Display13FBMemory::field(fb,0x4A00); - const auto irqIdle=[&]() { - auto &irq=display13FBIRQ; - if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || !adlnIRQOwner(controller) || !irq.lock)return false; - const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const bool idle=!irq.state.faulted && !irq.quiescing && !irq.blockedPipes && - !(irq.state.active&1) && !(irq.admittedPipes&1) && irq.scopes.total==irq.handlers && - !irq.scopes.display && !irq.softwareHandlers && - !(__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE08),__ATOMIC_ACQUIRE)&Display13FBIRQ::pipeEvents(0)) && - !__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE0C),__ATOMIC_ACQUIRE) && - !(__atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+0xE10),__ATOMIC_ACQUIRE)&3U); - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved);return idle; - }; - const bool irqBefore=irqIdle();record[12]=irqBefore; - const bool ready=irqBefore && !c.dsiNativeWriters && adlnOutputWiring.dsi==1 && controller && controller==adlnTopologyController && - controller->getMetaClass()->getClassSize()>=0x1C20 && c.workLoop==loop && - Display13FBMemory::field(controller,0xE00)==loop && - Display13FBMemory::field(fb,0x1D0)==controller && - Display13FBMemory::field(path,0x32B0)==fb && - Display13FBMemory::field(path,0x3640)==0 && Display13FBMemory::field(path,0x3644)==1 && - c.pipe[0].memoryFramebuffer==fb && c.pipe[0].destroyingPath && !c.pipe[0].publishedScanout && !c.pipe[0].scanout.record && - !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && !c.bridge.state.suspended && !c.dbuf.faulted && - !(c.dbuf.selected&1) && !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired && - dsiStop.complete() && dsiClockStop.complete() && dsiIRQParked && dsiDDIReleased && dsiMemoryReleased && !dsiPGReleased && - c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && c.bridge.bootPort==0 && - !c.bridge.bootPGProtected && c.bridge.pgRefs[0]==1 && !c.bridge.ddiRefs[0] && - Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && - port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(path,0x3648)==port && - Display13FBMemory::field(port,0x550)==1 && Display13FBMemory::field(port,0x554)<=3 && - !Display13FBMemory::field(path,0x32B8) && planeTailEmpty && - plane && plane->getMetaClass()->getClassSize()>=0x1C8 && Display13FBMemory::field(plane,0x68)==fb && - Display13FBMemory::field(plane,0x70)==path && Display13FBMemory::field(plane,0x78)==0 && - scalerOwned(scaler) && scalerOwned(planeScaler) && - Display13FBMemory::field(fb,0x49E8)==1 && Display13FBMemory::field(fb,0x49F0)==path && - attached<=1 && Display13FBMemory::field(fb,0x4A08)==(attached ? path : nullptr) && pipeMap && pipeMap[0]==fb; - record[1]=ready;record[2]=c.bridge.pgRefs[0];record[3]=c.dbuf.selected;record[4]=attached; - auto power=c.power;IOLockUnlock(c.lock); - const bool put=ready && power && adlnPowerReference(power,2,1); - IOLockLock(c.lock); - if(put)dsiPGReleased=c.power==power && c.controller==controller && !c.bridge.state.faulted && - !c.bridge.pgRefs[0] && !c.bridge.pgLease[0].owner; - record[5]=put;record[6]=dsiPGReleased;record[7]=c.bridge.pgRefs[0]; - bool removed=false;const bool irqAfter=put && irqIdle();record[13]=irqAfter; - if(irqAfter && dsiPGReleased && !c.memoryFaulted && !c.dbuf.faulted && !c.clockFaulted && - c.pipe[0].memoryFramebuffer==fb && *reinterpret_cast(dsiPipeMap)==pipeMap && pipeMap[0]==fb) { - // These leaves only update CPU objects; no power or MMIO callback. - reinterpret_cast(dsiFreePlane)(controller,plane); - reinterpret_cast(dsiFreeScaler)(controller,scaler); - if(Display13FBMemory::field(port,0x554)==3) - *reinterpret_cast(reinterpret_cast(port)+0x554)=2; - // The normal setPortMode(0) could reset DDI. Hardware is already stopped. - if(!Display13FBMemory::field(port,0x554)) - *reinterpret_cast(reinterpret_cast(port)+0x550)=0; - *reinterpret_cast(reinterpret_cast(path)+0x3648)=nullptr; - reinterpret_cast(dsiDetachPath)(fb,path); - reinterpret_cast(dsiRemovePath)(fb,path); - const auto freedScaler=[](OSObject *o) {return !o || (!Display13FBMemory::field(o,0x10) && !Display13FBMemory::field(o,0x20));}; - removed=!Display13FBMemory::field(fb,0x49E8) && !Display13FBMemory::field(fb,0x49F0) && - !Display13FBMemory::field(fb,0x4A00) && !Display13FBMemory::field(fb,0x4A08) && !pipeMap[0] && - !Display13FBMemory::field(path,0x3644) && !Display13FBMemory::field(path,0x32B0) && - !Display13FBMemory::field(path,0x3648) && !Display13FBMemory::field(plane,0x68) && - freedScaler(scaler) && freedScaler(planeScaler); - } - record[8]=removed;record[9]=Display13FBMemory::field(fb,0x49E8); - record[10]=Display13FBMemory::field(fb,0x4A00);record[11]=Display13FBMemory::field(path,0x3644); - IOLockUnlock(c.lock); - if(removed) { - removed=releaseDSIFixedMode(fb); - if(removed) { - IOLockLock(c.lock); - removed=c.dsiPort==port; - if(removed)c.dsiPort=nullptr; - IOLockUnlock(c.lock); - if(removed)port->release(); - } - } - record[8]=removed; - if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); - loop->release();adlnPublishMemory("N97FBDSIPathDispose",0,record,sizeof(record));return removed; -} uint32_t XE::releaseDisplay13Path(IOService *fb,OSObject *path) { if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && !callbackXE->adlnOutputWiring.dsi) { @@ -11337,394 +9444,7 @@ static bool adlnWriterMappingBound() { native->getPhysicalAddress(),native->getLength(),c.map->getPhysicalAddress(),c.map->getLength()); } -// Host/IRQ/PLL retirement; the native DDI reference follows the stopped clock. -// Keep the real stopped source separate from Session cleanup: it is not a full -// Session StopProof. The one-shot journal retains a partial stop for diagnosis. -bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool retain) { - auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; - if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || - !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; - auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; - IORecursiveLockLock(mutex); - IOLockLock(c.lock); - auto loop=controller==c.controller ? c.workLoop : nullptr; - if(loop)loop->retain(); - IOLockUnlock(c.lock); - const bool boundLoop=loop && Display13FBMemory::field(controller,0xE00)==loop; - const bool inGate=boundLoop && reinterpret_cast(adlnWorkLoopInGate)(loop); - const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); - if(!inGate && !acquired) {if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} - IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - uint64_t record[51] {6};record[38]=c.dsiNativeWriters;bool masked=false,maskOK=false,parked=false,restored=false,enabled=false,modeWithdrawn=false;uint32_t master=0; - const auto qualified=[&]() { - if(adlnOutputWiring.dsi!=1 || !dsiResourcesReady || !dsiNativeWriterHooksReady || c.dsiNativeWriters || (dsiPendingTarget(fb) && !dsiArmedBackingOwned(fb,path)) || !dsiProfile.valid() || - controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || - Display13FBMemory::field(controller,0xE00)!=loop || - c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || - c.transcoders.dsi0Pipe!=0 || c.bridge.state.faulted || c.bridge.state.suspended || c.clockFaulted || - c.pipe[0].destroyingPath || c.dbuf.updating || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || - Display13FBMemory::field(path,0x35F0) || !adlnWriterMappingBound() || - !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || - Display13FBMemory::field(c.dsiPort,0x550)!=1 || - Display13FBMemory::field(path,0x3644)!=2 || - clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || - clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || - !c.bootDSI.sameSource(dsiBoot.source))return false; - const auto binding=adlnInspectPathBinding(fb,path,adlnPlatform,adlnOutputWiring); - Display13FBBootIO io{c.map}; - return binding.status==TGLFBBinding::Status::Complete && binding.pipe==0 && binding.ddi==0 && - c.bridge.outputHeld(io,0,0); - }; - struct IO : Display13FBBootIO { - decltype(qualified) &owns;IOSimpleLock *irqLock;bool panel,retain; - IO(IOMemoryMap *m,decltype(qualified) &q,IOSimpleLock *l,bool p,bool r):Display13FBBootIO{m},owns(q),irqLock(l),panel(p),retain(r) {} - bool ready(){return owns();} - void write(uint32_t reg,uint32_t value) { - if(readable(reg) && (reg==0x70080 || reg==0x70084 || reg==0x70088 || reg==0x70180 || reg==0x7019C || - reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000 || - reg==0x46010+4*display13FBPower.bootDSI.identity.pll || - (retain && XELP::DSI::Restart::writable(reg,display13FBPower.bootDSI.identity.pll)) || - (panel && (reg==0x6B100 || reg==0x6B104 || reg==0xC4030 || reg==0xC7204)))) - *reinterpret_cast(map->getVirtualAddress()+reg)=value; - } - bool gpio(unsigned role,unsigned value) { - if(!panel || !irqLock)return false; - const auto saved=IOSimpleLockLockDisableInterrupt(irqLock); - const auto r=XELP::DSI::Panel::gpio(*this,role,value); - IOSimpleLockUnlockEnableInterrupt(irqLock,saved);return r.complete; - } - void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} - } io{c.map,qualified,irq.lock,dsiStopTest || dsiPowerTest || retain,retain}; - auto &power=dsiRuntimePower; - record[1]=qualified(); - bool snapshot=!retain; - if(retain && record[1] && !dsiStopAttempted && !power.sleeping && !c.memoryFaulted && !c.dbuf.faulted && !c.watermarkFaulted && - dsiPublishedTarget(fb) && dsiPendingBacking.modeReady && dsiArmedBackingOwned(fb,path)) { - auto &p=power.plane; - p.control=io.read(0x70180);p.surface=io.read(0x7019C);p.stride=io.read(0x70188);p.size=io.read(0x70190); - p.position=io.read(0x7018C);p.offset=io.read(0x701A4);p.color=io.read(0x701CC); - power.cursorControl=io.read(0x70080);power.cursorPosition=io.read(0x70088);power.cursorBase=io.read(0x70084); - // The published aperture is not the current native flip surface. - // As in NativeCache::readActive, retain the native mode/display pipe - // and its transaction ownership; do not free/rebind it or substitute - // the initial backing. Writers are excluded through wake. Restore only - // the latched native plane image, with the supported fixed geometry. - snapshot=XELP::DSI::Surface::shape(p,dsiProfile) && XELP::DSI::Surface::matches(io,p,true) && - power.cursorControl!=UINT32_MAX && power.cursorBase!=UINT32_MAX && power.cursorPosition!=UINT32_MAX && - Display13Cursor::controlWrite(0x70080,power.cursorControl)==power.cursorControl; - if(snapshot){power.host=XELP::DSI::Restart::State{};power.panel=XELP::DSI::Panel::Result{};dsiPanelStop={};dsiStop={};} - } - record[1]=record[1] && snapshot; - if(record[1] && !dsiStopAttempted && adlnIRQOwner(controller) && irq.lock && - irq.lock==Display13FBMemory::field(controller,0x90) && irq.base==c.map->getVirtualAddress()) { - Display13FBIRQIO irqIO{irq.base}; - auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const auto queued=[&](unsigned offset) { - return __atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+offset),__ATOMIC_ACQUIRE); - }; - master=irqIO.read(Display13FBIRQ::Master);enabled=irq.state.enabled; - record[2]=irq.handlers;record[3]=irq.scopes.display;record[4]=irq.softwareHandlers; - record[5]=queued(0xE08);record[6]=queued(0xE0C);record[7]=queued(0xE10); - const bool quiet=irq.scopes.total==irq.handlers && !irq.scopes.display && !irq.softwareHandlers && - !(record[5]&Display13FBIRQ::pipeEvents(0)) && !record[6] && !(record[7]&3); - const bool ready=quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && - irq.state.begun && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && - master!=UINT32_MAX && bool(master&Display13FBIRQ::Enable)==enabled; - if(ready) { - // DE permission is assigned on actual HW-handler entry. New shared - // GT-only handlers can still run but cannot consume display banks. - irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; - irqIO.write(Display13FBIRQ::Master,0); - const auto after=irqIO.read(Display13FBIRQ::Master); - maskOK=after!=UINT32_MAX && !(after&Display13FBIRQ::Enable); - if(!maskOK)irq.state.faulted=true; - } - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - if(maskOK) { - dsiStopAttempted=true; - if(!retain)dsiPanelStop={}; - if(dsiStopTest || dsiPowerTest || retain)dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile,dsiPanelStop); - else dsiStop=XELP::DSI::Quiesce::run(io,c.bootDSI,dsiProfile); - } - if(masked) { - saved=IOSimpleLockLockDisableInterrupt(irq.lock); - record[8]=irq.handlers;record[9]=irq.scopes.display;record[10]=irq.softwareHandlers; - record[11]=queued(0xE08);record[12]=queued(0xE0C);record[13]=queued(0xE10); - if(dsiStop.complete() && irq.scopes.total==irq.handlers && !irq.scopes.display && - !irq.softwareHandlers && !record[12] && !(record[13]&3)) { - // Pass the real DE-permitted scope count. A GT-only stack does - // not own a display consumer, even when it spans this stop. - const auto status=Display13FBIRQ::parkStoppedPipe(irqIO,irq.state,0,irq.scopes.display, - irq.softwareHandlers,static_cast(record[11])); - record[14]=static_cast(status);parked=status==Display13FBIRQ::Status::Complete; - } - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - if(parked) { - if(retain)XELP::DSI::Restart::stopQuiesced(io,c.bootDSI,dsiProfile,dsiStop,clock.allocation, - reinterpret_cast(controller),power.host); - else dsiClockStop=XELP::DSI::Clock::retire(io,c.bootDSI,dsiStop,true,clock.allocation, - reinterpret_cast(controller)); - } - saved=IOSimpleLockLockDisableInterrupt(irq.lock); - if(!retain && dsiClockStop.complete()) { - // The host and its PLL are now stopped. Withdraw both maps at - // the same masked IRQ boundary, so an external clock client - // no longer tries to re-adopt the obsolete running DSI source. - c.transcoders.dsi0Pipe=UINT32_MAX;irq.state.transcoders=c.transcoders;c.dbuf.transcoders=c.transcoders; - __atomic_fetch_and(&irq.admittedPipes,~1U,__ATOMIC_RELEASE); - __atomic_store_n(&dsiResourcesReady,false,__ATOMIC_RELEASE); - } - irq.quiescing=false;irq.blockedPipes=0; - const auto wells=irqIO.read(Display13FBIRQ::Driver); - if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; - if(!irq.state.faulted) { - irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); - const auto after=irqIO.read(Display13FBIRQ::Master); - restored=after!=UINT32_MAX && bool(after&Display13FBIRQ::Enable)==enabled; - irq.state.enabled=restored && enabled; - if(!restored) {irq.state.faulted=true;irqIO.write(Display13FBIRQ::Master,0);} - } - adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - } - } - if(masked)dsiIRQParked=parked && restored; - record[15]=masked;record[16]=parked;record[17]=restored;record[18]=dsiStopAttempted; - record[19]=static_cast(dsiStop.stage);record[20]=static_cast(dsiStop.error); - record[21]=dsiStop.writes;record[22]=dsiStop.waitUs;record[23]=dsiIRQParked; - record[24]=static_cast(dsiClockStop.status);record[25]=dsiClockStop.reg;record[26]=dsiClockStop.after; - record[27]=dsiClockStop.writes;record[28]=dsiClockStop.waitUs;record[29]=dsiClockStop.complete(); - // The donor's disableClocks(port,peer) puts this DDI reference at 6db04; - // PG1 belongs to freeDisplayPathResources and must remain until memory / - // DDB / path destruction. Match that ownership without running DDI/DP code. - const bool stopped=!retain && parked && restored && dsiClockStop.complete(); - record[30]=c.bridge.ddiRefs[0]; - if(stopped && c.bridge.bootAdopted && c.bridge.bootPath==reinterpret_cast(path) && - c.bridge.bootPort==0 && !c.bridge.bootDDIProtected && c.bridge.ddiRefs[0]==1 && c.power) { - auto power=c.power; - IOLockUnlock(c.lock);const bool put=adlnPowerReference(power,4,0);IOLockLock(c.lock); - dsiDDIReleased=put && c.power==power && !c.bridge.ddiRefs[0] && !c.bridge.ddiLease[0].owner; - } - record[31]=c.bridge.ddiRefs[0];record[32]=dsiDDIReleased; - const bool stoppedPath=stopped && dsiDDIReleased; - if(stoppedPath && !c.memoryFaulted && !c.dbuf.faulted) { - // Keep the real FB mutex and workloop gate while the shared native - // retirement path acquires its own allocator serializer. Neither the - // Combo mutex nor the owner lock may span native allocator callbacks. - // PG1 remains held for PTE/DDB validation; the stopped DSI PLL is free. - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - const bool released=retireDSINativeBacking(fb,path) && adlnFreeStoppedPipe(fb); - IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - Display13FBDBUFIO dbufIO{c.map}; - // A native no-resource return can precede path destruction. Retire - // that path's DDB consumers too, even if no allocator call was needed. - const auto dbufStatus=released && !c.memoryFaulted ? Display13DBUF::stopPipe(dbufIO,c.dbuf,0) : Display13DBUF::Status::Unavailable; - record[35]=released;record[36]=static_cast(dbufStatus); - const bool dbufStopped=dbufStatus==Display13DBUF::Status::Complete; - bool irqRestored=false; - if(released && irq.controller==controller && irq.lock && irq.base) { - Display13FBIRQIO irqIO{irq.base};const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const auto wells=irqIO.read(Display13FBIRQ::Driver); - const bool powered=wells!=UINT32_MAX && (wells&3)==3; - const auto after=powered ? irqIO.read(Display13FBIRQ::Master) : UINT32_MAX; - irqRestored=!irq.state.faulted && !irq.quiescing && !irq.blockedPipes && !(irq.state.active&1) && - after!=UINT32_MAX && bool(after&Display13FBIRQ::Enable)==irq.state.enabled; - if(!irqRestored){irq.state.faulted=true;irq.state.enabled=false;if(powered)irqIO.write(Display13FBIRQ::Master,0);} - adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - } - record[37]=irqRestored; - dsiMemoryReleased=dbufStopped && irqRestored && c.controller==controller && c.pipe[0].memoryFramebuffer==fb && - Display13FBMemory::singlePath(fb)==path && !c.memoryFaulted && - Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && - !c.pipe[0].scanout.record && !__atomic_load_n(&c.pipe[0].publishedScanout,__ATOMIC_ACQUIRE) && - !c.dbuf.pipe[0].consumers && !c.dbuf.pipe[0].unretired; - } - record[39]=dsiStop.reg;record[40]=dsiStop.value; - record[41]=dsiPanelStop.stage;record[42]=dsiPanelStop.complete; - record[43]=static_cast(dsiPanelStop.host.error);record[44]=dsiPanelStop.host.sequenceId; - record[45]=dsiPanelStop.host.operationOffset;record[46]=dsiPanelStop.host.headers; - record[47]=dsiPanelStop.host.payloadWords;record[48]=dsiPanelStop.host.completedOperations; - record[49]=dsiPanelStop.host.waitedUs;record[50]=dsiPanelStop.host.pending; - record[33]=dsiMemoryReleased;record[34]=c.memoryFaulted; - const bool complete=retain ? (parked && restored && power.host.stopped && !power.host.started && - power.host.error==XELP::DSI::Restart::Error::None && dsiPanelStop.complete && qualified() && - dsiArmedBackingOwned(fb,path) && !c.memoryFaulted && !c.dbuf.faulted) : (stoppedPath && dsiMemoryReleased); - if(retain && complete)power.sleeping=true; - if(complete && !retain) { - // Exact donor disableDisplay publishes path state 1 only after its - // clock-disable call (4dbba -> 4dbc6). Mirror that CPU completion now, - // never on a partial host/IRQ/PLL stop or by replaying the donor body. - *reinterpret_cast(reinterpret_cast(path)+0x3644)=1; - *reinterpret_cast(reinterpret_cast(path)+0x170)=0; - *reinterpret_cast(reinterpret_cast(fb)+0x4270)=0; - if(dsiModeTarget(fb)){dsiFixedMode.ready=false;modeWithdrawn=true;} - } - // Native backing and the GOP pool have been retired before a full stop - // leaves PG/path disposal to releaseDisplay13Path; only partial stops - // need the global retention guard. Never clear a pre-existing real fault. - if(masked && !complete){if(retain)power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - if(modeWithdrawn)fb->setProperty("WEXDSIFixedModeReady",false); - if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); - loop->release();IORecursiveLockUnlock(mutex); - adlnPublishMemory("N97FBDSIHostStop",0,record,sizeof(record)); - if(retain) { - const auto &h=power.host; - uint64_t receipt[] {1,0,complete,power.cycles,power.sleeping,snapshot, - power.plane.control,power.plane.surface,power.cursorControl,power.cursorBase, - static_cast(h.stage),static_cast(h.error),h.reg,h.value,h.writes,h.waitUs, - parked,restored,dsiPanelStop.complete,dsiMemoryReleased,dsiDDIReleased}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); - } - return complete; -} -// Runtime display wake keeps the native scanout allocation and every display -// lease from the matching off transaction. System power loss is not covered. -bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { - auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ;auto &power=dsiRuntimePower; - if(!c.lock || !clock.lock || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !fb || fb->getMetaClass()->getClassSize()<0x4B90 || !path || path->getMetaClass()->getClassSize()<0x3730 || - !adlnWorkLoopInGate || !adlnWorkLoopTryClose || !adlnWorkLoopOpen)return false; - auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; - IORecursiveLockLock(mutex);IOLockLock(c.lock); - auto loop=controller==c.controller ? c.workLoop : nullptr;if(loop)loop->retain();IOLockUnlock(c.lock); - const bool boundLoop=loop && Display13FBMemory::field(controller,0xE00)==loop; - const bool inGate=boundLoop && reinterpret_cast(adlnWorkLoopInGate)(loop); - const bool acquired=boundLoop && !inGate && reinterpret_cast(adlnWorkLoopTryClose)(loop); - if(!inGate && !acquired){if(loop)loop->release();IORecursiveLockUnlock(mutex);return false;} - IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - const auto owned=[&]() { - if(!power.sleeping || power.failed || !dsiStopAttempted || !dsiIRQParked || !dsiResourcesReady || - !dsiStop.complete() || !dsiPanelStop.complete || !dsiNativeWriterHooksReady || !dsiProfile.valid() || - controller!=c.controller || controller!=adlnTopologyController || c.workLoop!=loop || - Display13FBMemory::field(controller,0xE00)!=loop || adlnOutputWiring.dsi!=1 || - c.pipe[0].memoryFramebuffer!=fb || Display13FBMemory::singlePath(fb)!=path || - c.transcoders.dsi0Pipe!=0 || c.dsiNativeWriters || c.memoryFaulted || c.clockFaulted || c.watermarkFaulted || - c.bridge.state.faulted || c.bridge.state.suspended || c.dbuf.faulted || c.dbuf.updating || - c.pipe[0].destroyingPath || c.pipe[0].wmProgramming || c.pipe[0].wmCommitting || - !c.dsiPort || Display13FBMemory::field(path,0x3648)!=c.dsiPort || - Display13FBMemory::field(c.dsiPort,0x550)!=1 || Display13FBMemory::field(path,0x3644)!=2 || - Display13FBMemory::field(path,0x35F0) || !adlnWriterMappingBound() || - clock.thread || clock.allocation.external[0]!=reinterpret_cast(controller) || - clock.allocation.binding[0]!=dsiBoot.source.identity.pll+1 || !c.bootDSI.sameSource(dsiBoot.source) || - !dsiPublishedTarget(fb) || !dsiPendingBacking.modeReady || !dsiArmedBackingOwned(fb,path))return false; - Display13FBBootIO io{c.map};return c.bridge.outputHeld(io,0,0); - }; - struct IO : Display13FBBootIO { - decltype(owned) &owns;IOSimpleLock *lock;unsigned pll; - IO(IOMemoryMap *m,decltype(owned) &o,IOSimpleLock *l,unsigned p):Display13FBBootIO{m},owns(o),lock(l),pll(p) {} - bool ready(){return owns();} - void write(uint32_t r,uint32_t v) { - if(readable(r) && (XELP::DSI::Restart::writable(r,pll) || r==0x6B100 || r==0x6B104 || - r==0xC4030 || r==0xC7204 || r==0x70180 || r==0x7019C || r==0x7B008 || - r==0x70080 || r==0x70088 || r==0x70084 || r==XELP::DSI::Backlight::Duty)) - *reinterpret_cast(map->getVirtualAddress()+r)=v; - } - bool gpio(unsigned role,unsigned value) { - const auto saved=IOSimpleLockLockDisableInterrupt(lock);const auto r=XELP::DSI::Panel::gpio(*this,role,value); - IOSimpleLockUnlockEnableInterrupt(lock,saved);return r.complete; - } - void delayUs(unsigned us){if(us>=1000)IOSleep((us+999)/1000);else IODelay(us);} - } io{c.map,owned,irq.lock,dsiBoot.source.identity.pll}; - uint64_t receipt[24] {1,1,0,power.cycles,power.sleeping},irqReceipt[24] {1}; - bool masked=false,restored=false,rearmed=false,cursor=false,complete=false;uint32_t master=0; - auto stoppedPlane=power.plane;stoppedPlane.control&=~(1U<<31); - receipt[5]=owned() && power.host.stopped && !power.host.started && !power.panel.attempted && - power.host.error==XELP::DSI::Restart::Error::None && XELP::DSI::Surface::matches(io,stoppedPlane,false); - if(receipt[5] && adlnIRQOwner(controller) && irq.lock && irq.lock==Display13FBMemory::field(controller,0x90) && - irq.base==c.map->getVirtualAddress()) { - Display13FBIRQIO irqIO{irq.base};auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const auto queued=[&](unsigned offset) { - return __atomic_load_n(reinterpret_cast(reinterpret_cast(controller)+offset),__ATOMIC_ACQUIRE); - }; - master=irqIO.read(Display13FBIRQ::Master); - const bool quiet=irq.scopes.total==irq.handlers && !irq.scopes.display && !irq.softwareHandlers && - !(queued(0xE08)&Display13FBIRQ::pipeEvents(0)) && !queued(0xE0C) && !(queued(0xE10)&3); - if(quiet && !irq.quiescing && !irq.blockedPipes && !irq.state.faulted && irq.state.begun && irq.state.enabled && - !(irq.state.active&1) && (irq.state.configured&1) && (irq.admittedPipes&1) && irq.state.transcoders.dsi0Pipe==0 && - master!=UINT32_MAX && (master&Display13FBIRQ::Enable)) { - irq.quiescing=true;irq.blockedPipes=1;irq.state.enabled=false;masked=true; - irqIO.write(Display13FBIRQ::Master,0); - const auto after=irqIO.read(Display13FBIRQ::Master); - if(after==UINT32_MAX || (after&Display13FBIRQ::Enable))irq.state.faulted=true; - } - const bool canStart=masked && !irq.state.faulted; - IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - if(canStart) { - struct Lifecycle { - XELPComboClock::ClockAllocation &allocation;uintptr_t owner;XELP::DSI::Restart::State &state; - bool stop(IO &,const XELP::DSI::Readout &,const XELP::DSI::Profile &){return false;} - bool start(IO &io,XELP::DSI::Readout &source) { - if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; - source=XELP::DSI::Restart::expected(source,state);return source.complete(); - } - } lifecycle{clock.allocation,reinterpret_cast(controller),power.host}; - XELP::DSI::Panel::cycle(io,c.bootDSI,dsiProfile,power.plane.control,power.plane.surface,power.panel,lifecycle,&dsiPanelStop, - dsiBacklight.initialized?&dsiBacklight:nullptr); - if(power.panel.complete() && owned() && XELP::DSI::Surface::matches(io,power.plane,true)) { - io.write(0x70080,power.cursorControl);io.write(0x70088,power.cursorPosition);io.write(0x70084,power.cursorBase); - const auto before=io.read(0x70040); - for(unsigned us=0;us<=50000;us+=1000) { - const auto after=io.read(0x70040); - if(before!=UINT32_MAX && after!=UINT32_MAX && after!=before) { - cursor=XELP::DSI::Surface::word(io,0x70080,UINT32_MAX,power.cursorControl) && - XELP::DSI::Surface::word(io,0x70088,UINT32_MAX,power.cursorPosition) && - XELP::DSI::Surface::word(io,0x70084,UINT32_MAX,power.cursorBase);break; - } - if(us<50000)io.delayUs(1000); - } - if(cursor){dsiBoot.source=power.panel.source;c.bootDSI=power.panel.source;} - } - } - if(masked) { - saved=IOSimpleLockLockDisableInterrupt(irq.lock); - const auto wells=irqIO.read(Display13FBIRQ::Driver); - if(wells==UINT32_MAX || (wells&3)!=3)irq.state.faulted=true; - if(!irq.state.faulted) { - irqIO.write(Display13FBIRQ::Master,master&Display13FBIRQ::Enable); - const auto after=irqIO.read(Display13FBIRQ::Master); - restored=after!=UINT32_MAX && (after&Display13FBIRQ::Enable);irq.state.enabled=restored; - if(!restored)irq.state.faulted=true; - if(restored && cursor && owned()) { - const auto actual=Display13FBIRQ::readActivePipes(irqIO,irq.admittedPipes,irq.state.transcoders); - irqReceipt[1]=irq.admittedPipes;irqReceipt[2]=irq.state.active;irqReceipt[3]=actual.active; - irqReceipt[4]=static_cast(actual.status);irqReceipt[5]=wells; - for(unsigned p=0;p<3;++p)if(Display13FBIRQ::powered(wells,p)) { - irqReceipt[6+3*p]=Display13Transcoder::control(irqIO,p,irq.state.transcoders); - irqReceipt[7+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)+4); - irqReceipt[8+3*p]=irqIO.read(Display13FBIRQ::PipeIIR(p)-4); - } - const auto status=Display13FBIRQ::extendActivePipes(irqIO,irq.state,irq.admittedPipes); - irqReceipt[15]=static_cast(status);irqReceipt[16]=irq.state.active; - irqReceipt[17]=static_cast(irq.state.firstFailure); - irqReceipt[18]=irq.state.failureRegister;irqReceipt[19]=irq.state.failureBits; - irqReceipt[20]=irq.state.enabled;irqReceipt[21]=irq.state.faulted; - rearmed=status==Display13FBIRQ::Status::Complete && (irq.state.active&1); - } - } - irq.quiescing=false;irq.blockedPipes=0; - adlnIRQCache(controller,irqIO);IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); - complete=restored && rearmed && cursor && power.panel.complete() && owned(); - if(complete){power.sleeping=false;++power.cycles;dsiStopAttempted=false;dsiIRQParked=false;} - else{power.failed=true;adlnFault(c.memoryFaulted,__LINE__);} - } - } - const auto &h=power.host;const auto &p=power.panel; - receipt[2]=complete;receipt[3]=power.cycles;receipt[4]=power.sleeping; - receipt[6]=power.plane.control;receipt[7]=power.plane.surface;receipt[8]=power.cursorControl;receipt[9]=power.cursorBase; - receipt[10]=static_cast(h.stage);receipt[11]=static_cast(h.error); - receipt[12]=h.reg;receipt[13]=h.value;receipt[14]=h.writes;receipt[15]=h.waitUs; - receipt[16]=masked;receipt[17]=restored;receipt[18]=rearmed;receipt[19]=cursor; - receipt[20]=static_cast(p.stage);receipt[21]=static_cast(p.error);receipt[22]=p.reg;receipt[23]=p.value; - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); - loop->release();IORecursiveLockUnlock(mutex); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); - publishDSIBacklight(); - return complete; -} void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObject *path,uint8_t mode) { if(callbackXE->adlnOutputWiring.dsi && dsiLifecycleTarget(fb,path)) { @@ -12645,250 +10365,8 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo void XE::wrapAlderLakeNDefaultDBUF(IOService *controller) {adlnSetupDBUF(controller,false);} void XE::wrapAlderLakeNOptimizedDBUF(IOService *controller) {adlnSetupDBUF(controller,true);} -// One optional bootstrap NOP/drain trial before native engine and IRQ users. -// This exercises the same packet engine used by panel sequences, without -// changing panel power/reset, timing, brightness, or any framebuffer state. -bool XE::probeDSICommands(IOService *controller) { - auto &c=display13FBPower;auto &clock=comboClockTransaction; - if(!controller || !c.lock || !clock.lock)return false; - IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - const auto owned=[&]() { - return controller==c.controller && controller==adlnTopologyController && - dsiResourcesReady && !dsiStopAttempted && adlnOutputWiring.dsi==1 && - !c.engineEnabled && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !c.bridge.state.suspended && !clock.thread && - clock.allocation.external[0]==reinterpret_cast(controller) && - clock.allocation.binding[0]==dsiBoot.source.identity.pll+1; - }; - struct IO : Display13FBBootIO { - decltype(owned) &owns; - IO(IOMemoryMap *m,decltype(owned) &q):Display13FBBootIO{m},owns(q) {} - bool ready() { - if(!owns() || !readable(0x45404))return false; - const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; - } - void write(uint32_t reg,uint32_t value) { - if(reg==0x6B100 || reg==0x6B104) - *reinterpret_cast(map->getVirtualAddress()+reg)=value; - } - void delayUs(unsigned us){IODelay(us);} - } io{c.map,owned}; - XELP::DSI::CommandPort port{io};uint64_t record[20] {2}; - // Active video can defer an LP packet until its blanking interval. The - // cold panel-sequence engine keeps its Linux 100 us default. Bound this - // running-link trial to three frames per credit wait, rounded to 10 us. - const auto &mode=dsiProfile.mode; - const uint64_t period=mode.clockKHz ? (uint64_t(mode.htotal())*mode.vtotal()*1000+mode.clockKHz-1)/mode.clockKHz : 0; - record[16]=period ? ((3*period+9)/10)*10 : 0; - uint32_t credits=0,lp=0; - record[1]=io.ready() && !dsiCommandHost.enabled && record[16]>=100 && record[16]<=100000 && dsiProfile.valid() && - c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); - if(record[1]) { - const bool empty=port.credits(credits) && port.read(0x6B0D8,lp) && - (credits&0x1FFF)==0x1040 && !(lp&((1U<<17)|(1U<<16)|1U)); - record[2]=credits;record[3]=lp;record[4]=empty; - if(empty) { - record[17]=io.read(0x70040); - dsiCommandHost.enabled=true;const uint8_t nop=0; - record[5]=dsiCommandHost.send(port,0x05,{&nop,1}); - record[6]=record[5] && dsiCommandHost.drain(port,static_cast(record[16])); - record[18]=io.read(0x70040); - } - record[7]=port.credits(credits)?credits:UINT32_MAX; - record[8]=port.read(0x6B0D8,lp)?lp:UINT32_MAX; - record[9]=io.ready() && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); - } - record[10]=static_cast(dsiCommandHost.error);record[11]=dsiCommandHost.pending; - record[12]=dsiCommandHost.headers;record[13]=dsiCommandHost.payloadWords; - record[14]=dsiCommandHost.waitedUs;record[15]=port.writes;record[19]=dsiBoot.source.conf; - const bool complete=record[1] && record[4] && record[6] && record[9] && - !record[10] && !record[11] && !port.failed && (record[7]&0x1FFF)==0x1040 && - record[8]!=UINT32_MAX && !(record[8]&((1U<<17)|(1U<<16)|1U)); - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSICommandTrial",record,sizeof(record)); - return complete; -} -// Bootstrap-only panel sequence experiment. By default retain host/PHY/PLL; -// the optional host trial rebuilds them before panel INIT_OTP. Neither path -// releases shared resources or enables runtime power transitions. -bool XE::probeDSIPanel(IOService *controller) { - auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; - if(!controller || !c.lock || !clock.lock || !irq.lock)return false; - IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - const auto owned=[&]() { - return controller==c.controller && controller==adlnTopologyController && - dsiResourcesReady && !dsiStopAttempted && adlnOutputWiring.dsi==1 && - !c.engineEnabled && !c.memoryFaulted && !c.clockFaulted && - !c.bridge.state.faulted && !c.bridge.state.suspended && !clock.thread && - clock.allocation.external[0]==reinterpret_cast(controller) && - clock.allocation.binding[0]==dsiBoot.source.identity.pll+1 && - irq.controller==controller && !irq.state.begun && !irq.state.enabled && - !irq.quiescing && !irq.handlers && !irq.softwareHandlers; - }; - struct IO : Display13FBBootIO { - decltype(owned) &owns;IOSimpleLock *irqLock; - bool restart {};unsigned pll {};uint32_t writes {},gpios {};XELP::DSI::Panel::GPIOResult lastGPIO; - IO(IOMemoryMap *m,decltype(owned) &q,IOSimpleLock *l,bool cold,unsigned id):Display13FBBootIO{m},owns(q),irqLock(l),restart(cold),pll(id) {} - bool ready() { - if(!owns() || !readable(0x45404))return false; - const auto wells=read(0x45404);return wells!=UINT32_MAX && (wells&0x403)==0x403; - } - void write(uint32_t reg,uint32_t value) { - if(restart && XELP::DSI::Restart::writable(reg,pll)) { - *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;return; - } - switch(reg) { - case 0x6B100:case 0x6B104:case 0xC4030:case 0xC7204: - case 0x70180:case 0x7019C:case 0x7B008: - *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;break; - } - } - bool gpio(unsigned role,unsigned value) { - // Only this short RMW is under the IRQ lock. All VBT delays and - // FIFO/frame polling execute with interrupts enabled. - const auto saved=IOSimpleLockLockDisableInterrupt(irqLock); - lastGPIO=XELP::DSI::Panel::gpio(*this,role,value); - IOSimpleLockUnlockEnableInterrupt(irqLock,saved); - if(lastGPIO.complete)++gpios; - return lastGPIO.complete; - } - void delayUs(unsigned us){IODelay(us);} - } io{c.map,owned,irq.lock,dsiHostTest,dsiBoot.source.identity.pll}; - Display13Clock::Change bootCDClock; - struct Lifecycle { - bool enabled,prepareExternal;XELPComboClock::ClockAllocation &allocation;uintptr_t owner; - XELP::DSI::Restart::State &state;Display13Clock::Change &cdclk; - bool stop(IO &io,const XELP::DSI::Readout &source,const XELP::DSI::Profile &profile) { - return !enabled || XELP::DSI::Restart::stop(io,source,profile,allocation,owner,state); - } - bool start(IO &io,XELP::DSI::Readout &source) { - if(!enabled)return true; - // Native external modes request the maximum CDCLK. Prepare that - // shared clock while the bootstrap cycle has stopped DSI and all - // other consumers, before native mode setting/IRQ users start. - // The DSI link PLL remains a separate retained allocation. - if(prepareExternal) { - auto &c=display13FBPower; - Display13FBClockIO clockIO {c.map}; - const auto current=Display13Clock::read(clockIO); - cdclk.before=current;cdclk.requestedHz=current.nativeMaxHz(); - const bool stopped=io.ready() && state.stopped && !state.started && - !c.engineEnabled && Display13FBIRQ::consumersStopped(io,7,c.transcoders); - if(!stopped || current.status!=Display13Clock::Status::Complete)return false; - cdclk=Display13Clock::set(clockIO,current.nativeMaxHz(),{true,true,0}); - if(cdclk.status!=Display13Clock::ChangeStatus::Complete) { - adlnFault(c.clockFaulted,__LINE__);return false; - } - c.clock=cdclk.after;c.clockStopped=false;c.resumeClockHz=cdclk.after.nativeHz(); - auto controller=reinterpret_cast(owner); - *reinterpret_cast(reinterpret_cast(controller)+0xE98)=cdclk.after.nativeHz(); - *reinterpret_cast(reinterpret_cast(controller)+0xEA0)=cdclk.after.nativeHz(); - } - if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; - source=XELP::DSI::Restart::expected(source,state);return source.complete(); - } - } lifecycle{dsiHostTest,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; - uint64_t record[32] {1}; - record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && - !dsiCommandHost.faulted && !dsiCommandHost.pending && - c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); - if(record[1]) { - record[20]=io.read(0xC7204);record[22]=io.read(0xC4030); - record[24]=io.read(0xC7200);record[28]=io.read(0x70040); - XELP::DSI::Panel::cycle(io,dsiBoot.source,dsiProfile,dsiBoot.control,dsiBoot.surface,dsiPanelTrial,lifecycle); - record[21]=io.ready()?io.read(0xC7204):UINT32_MAX; - record[23]=io.ready()?io.read(0xC4030):UINT32_MAX; - record[25]=io.ready()?io.read(0xC7200):UINT32_MAX; - record[26]=io.ready()?io.read(0x6B0D0):UINT32_MAX; - record[27]=io.ready()?io.read(0x6B0D8):UINT32_MAX; - record[29]=io.ready()?io.read(0x70040):UINT32_MAX; - // Publish the new clock/transport witness only after the complete - // cycle and old backing/geometry have been revalidated together. - auto nextBoot=dsiBoot; - if(dsiPanelTrial.complete())nextBoot.source=dsiPanelTrial.source; - record[30]=io.ready() && dsiPanelTrial.complete() && XELP::DSI::bootUnchanged(io,nextBoot,xelpMemory); - if(record[30]){dsiBoot=nextBoot;c.bootDSI=nextBoot.source;} - } - const auto &r=dsiPanelTrial;const auto &h=r.host; - record[2]=static_cast(r.stage);record[3]=static_cast(r.error); - record[4]=r.reg;record[5]=r.value;record[6]=io.writes;record[7]=r.waitUs; - record[8]=static_cast(h.error);record[9]=h.pending;record[10]=h.sequenceId; - record[11]=h.operationOffset;record[12]=h.headers;record[13]=h.payloadWords; - record[14]=h.waitedUs;record[15]=h.completedOperations;record[16]=io.gpios; - record[17]=io.lastGPIO.role;record[18]=io.lastGPIO.value;record[19]=io.lastGPIO.complete; - const bool complete=record[1] && r.complete() && record[30];record[31]=complete; - const auto &host=dsiHostRestart;const auto &p=host.config; - uint64_t hostRecord[20] {1,dsiHostTest,static_cast(host.stage),static_cast(host.error), - host.reg,host.value,host.writes,host.waitUs,host.stopped,host.started,host.pll, - p.pll.cfg0(),p.pll.cfg1(),p.link.clockTiming,p.link.dataTiming,p.hsTimeout,p.lpTimeout,p.taTimeout, - record[30],complete}; - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); - if(dsiHostTest)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); - if(dsiHostTest && !dsiPanelOnly) - IORegistryEntry::getRegistryRoot()->setProperty("N97FBDSIBootCDClock",&bootCDClock,sizeof(bootCDClock)); - return complete; -} -// Called outside the owner lock, before native engine/probe/IRQ users start. -// The lock order matches clock clients: Combo clock -> display owner -> IRQ. -// Failure retains any already acquired boot power references for cleanup. -bool XE::bindDSIResources(IOService *controller) { - auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; - if(!controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !display13FBIRQReady || !dsiNativeWriterHooksReady || !clock.lock || !c.lock)return false; - IORecursiveLockLock(clock.lock);IOLockLock(c.lock); - struct IO : Display13FBBootIO { - explicit IO(IOMemoryMap *map):Display13FBBootIO{map} {} - void write(uint32_t reg,uint32_t value) { - if(reg==0x42084 && readable(reg)) - *reinterpret_cast(map->getVirtualAddress()+reg)=value; - } - } io{c.map}; - const bool owned=adlnOutputWiring.dsi==1 && dsiProfile.valid() && dsiBoot.complete() && - c.controller==controller && controller==adlnTopologyController && !clock.thread && - !dsiResourcesReady && c.transcoders.dsi0Pipe==UINT32_MAX && - !c.engineEnabled && !c.dbuf.begun && !c.memoryFaulted && !c.clockFaulted && - c.bridge.outputHeld(io,0,0) && XELP::DSI::bootUnchanged(io,dsiBoot,xelpMemory); - auto irqLock=Display13FBMemory::field(controller,0x90); - bool ready=false; - if(owned && irqLock) { - XELPComboClock::ClockSnapshot snapshot;snapshot.layout=XELPComboClock::Layout::Display13; - snapshot.mux[0]=io.read(0x164280); - for(unsigned p=0;p(controller),snapshot)) { - // Native X/Y tiled scanout and every external output use GGTT. - // The live DSI GOP is linear, so this shared mode can be prepared - // without disabling its plane. Retain PLL/power on any failure. - if(XELP::DSI::prepareGGTT(io,dsiBoot,xelpMemory,dsiGGTT)) { - c.bootDSI=dsiBoot.source;c.transcoders.dsi0Pipe=0; - irq.state.transcoders=c.transcoders;irq.base=c.map->getVirtualAddress();irq.lock=irqLock; - __atomic_store_n(&irq.admittedPipes,1U,__ATOMIC_RELEASE); - __atomic_store_n(&irq.controller,controller,__ATOMIC_RELEASE); - __atomic_store_n(&dsiResourcesReady,true,__ATOMIC_RELEASE);ready=true; - } else adlnFault(c.memoryFaulted,__LINE__); - } - IOSimpleLockUnlockEnableInterrupt(irqLock,saved); - } - IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); - if(ready)dsiPublishIRQLive(); - if(ready && dsiCommandTest)ready=probeDSICommands(controller); - if(ready && dsiPanelTest)ready=probeDSIPanel(controller); - if(ready) { - IOLockLock(c.lock); - using Light=XELP::DSI::Backlight; - if(dsiBacklight.capture(dsiProfile,io.read(Light::Control),io.read(Light::Period),io.read(Light::Duty))) { - *reinterpret_cast(reinterpret_cast(controller)+0xE58)=dsiBacklight.requested; - *reinterpret_cast(reinterpret_cast(controller)+0xE60)=dsiBacklight.pwm.period; - } - IOLockUnlock(c.lock);publishDSIBacklight(); - } - return ready; -} void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { auto &c=display13FBPower; if(!c.lock)return; @@ -22489,3 +19967,12 @@ void XE::wrapTGLWriteDekel(void *port, uint32_t index, uint32_t value) { IORegistryEntry::getRegistryRoot()->setProperty("TGLDekelRejectedWrite", index, 32); } } + +// DSI attachment implementations share this translation unit intentionally. +// Common eDP/DP/HDMI dispatch, private resource owners and hook call sites stay +// above; these fragments do not introduce another driver or a shared ABI. +#include "kern_dsi_adapter_boot.inc" +#include "kern_dsi_adapter_backing.inc" +#include "kern_dsi_adapter_modes.inc" +#include "kern_dsi_adapter_lifecycle.inc" +#include "kern_dsi_adapter_diagnostics.inc" diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index f0dd52d8..97a3120d 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """Replay production board selection and WEX/provider binding for firmware-selected fixed HDMI.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile -r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +r=Path(__file__).resolve().parents[1];s=read_source(r/'WhateverXe/kern_xe.cpp') f='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in ['Display13OutputWiring XE::display13BoardWiring(', 'bool XE::xelpBindProbe(']) code=r''' #include @@ -21,7 +22,7 @@ XELP::ProbeConfig seen;unsigned calls=0; bool configure(void *c,const XELP::ProbeConfig *p){assert(c==(void*)1);seen=*p;++calls;return p->valid();} struct XE { - bool dsiPanelOnly=false,dsiCommandTest=false,dsiPanelTest=false,dsiHostTest=false; + bool dsiPanelOnly=false,dsiCommandTest=false,dsiPanelTest=false,dsiHostTest=false,dsiStopTest=false,dsiPowerTest=false; XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; diff --git a/tests/adls_clock_runtime.py b/tests/adls_clock_runtime.py index d2f7e4c6..a9dac3da 100644 --- a/tests/adls_clock_runtime.py +++ b/tests/adls_clock_runtime.py @@ -8,6 +8,8 @@ functions='\n'.join(re.search(r'(?:bool|void) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['comboBeginClock','comboEndClock']) code=r''' #include "kern_adls_clock.hpp" +#include "kern_dsi_restart.hpp" +#include "kern_dsi_panel.hpp" #include "kern_display13_fb_memory.hpp" #include "kern_display13_transcoder.hpp" #include @@ -33,6 +35,9 @@ void putPG(void *p,uint32_t pg){assert(p==power && pg==1 && refs);--refs;++putCount;} struct XE { bool adlsDisplaySelected=true,display13FramebufferRequested=false; + bool dsiStopAttempted=false,dsiIRQParked=false,dsiResourcesReady=false; + XELP::DSI::Quiesce::Result dsiStop;XELP::DSI::Panel::Shutdown dsiPanelStop; + struct {bool sleeping=false,failed=false;XELP::DSI::Restart::State host;} dsiRuntimePower; struct Native { bool ready=true;bool complete(){return ready;} void (*getPG)(void*,uint32_t)=::getPG,(*putPG)(void*,uint32_t)=::putPG; diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index b5fe488d..d1c621ca 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -15,7 +15,8 @@ external = {'-igfxdvmt', 'gfxrst', '-allow3d', '-disablegfxfirmware'} used = set() for directory in ['WhateverXe', 'AppleIntelTGLCompat']: - for path in (root / directory).glob('*.cpp'): + for path in (root / directory).iterdir(): + if path.suffix not in {'.cpp', '.inc'}: continue used.update(re.findall(r'(?:checkKernelArgument|PE_parse_boot_argn)\("([^"]+)"', path.read_text())) for array, expected in [('Off', '-wexoff'), ('Debug', '-wexdbg'), ('Beta', '-wexbeta')]: body = re.search(r'bootarg' + array + r'\[\] \{(.*?)\}', start, re.S).group(1) diff --git a/tests/display13_dbuf_setup.py b/tests/display13_dbuf_setup.py index 1af29821..e6b554c6 100644 --- a/tests/display13_dbuf_setup.py +++ b/tests/display13_dbuf_setup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise the production allocation adapter with real DBUF ownership.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile r = Path(__file__).resolve().parents[1] -s = (r / 'WhateverXe/kern_xe.cpp').read_text() +s = read_source(r/'WhateverXe/kern_xe.cpp') guard = re.search(r'bool XE::dsiSharedWriterStopped\(.*?\n\}', s, re.S).group() body = re.search(r'void XE::adlnSetupDBUFPipe\(.*?\n\}', s, re.S).group() pipewm = re.search(r'void XE::wrapAlderLakeNPipeWMSetup\(.*?\n\}', s, re.S).group() diff --git a/tests/display13_offline.py b/tests/display13_offline.py index 858d396a..30d7cbb9 100644 --- a/tests/display13_offline.py +++ b/tests/display13_offline.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Replay the production port-state boundary, including the normal native path.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile r=Path(__file__).resolve().parents[1] -s=(r/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(r/'WhateverXe/kern_xe.cpp') body=re.search(r'uint32_t XE::wrapAlderLakeNPortState\(.*?\n\}',s,re.S).group() code=r''' #include diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index 48659cb5..5a01f451 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -5,7 +5,7 @@ r=Path(__file__).resolve().parents[1] s=(r/'WhateverXe/kern_xe.cpp').read_text() a=s.index('\t// Display dispatch is independent') -b=s.index('\nstatic bool adlnWriterMappingBound();',a) +b=s.index('\n}',a)+2 body=s[a:b].rstrip()[:-1] kernel_start=s.index('\tif (adlnDetected || display13FramebufferRequested) {') kernel_end=s.index('\n\tif (tglRequested) {',kernel_start) diff --git a/tests/dsi_aperture_adapter.py b/tests/dsi_aperture_adapter.py index 9ce0ef61..aabf1301 100644 --- a/tests/dsi_aperture_adapter.py +++ b/tests/dsi_aperture_adapter.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """An unpublished DSI aperture stays unavailable while private scanout is armed.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'IOMemoryDescriptor \*XE::wrapAlderLakeNAperture\(.*?\n\}',s,re.S)[0] code=r''' #include "kern_display13_fb_memory.hpp" diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 9945fda2..981dc7c3 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 """Replay production DSI fixed-mode ownership/dispatch with IOKit leaves mocked.""" from pathlib import Path +from production_source import read_source import hashlib, re, subprocess, sys, tempfile root = Path(__file__).resolve().parents[1] -source = (root/'WhateverXe/kern_xe.cpp').read_text() +source = read_source(root/'WhateverXe/kern_xe.cpp') owner = source[source.index('struct DSIFixedModeOwner {'):source.index('// AlderLakeN start bypasses')] names = ['recordDSIClient', 'releaseDSIFixedMode', 'bindDSIFixedMode', 'wrapDSISetDetailedTimings', 'wrapDSIValidateMode', 'wrapDSIGetModes', 'wrapDSIGetModeCount', 'wrapDSIGetModeInformation', 'wrapDSIGetCurrentMode', 'wrapDSIGetOnlineInfo', 'wrapDSIDDC'] diff --git a/tests/dsi_backing_adapter.py b/tests/dsi_backing_adapter.py index fb685eec..d0d75527 100644 --- a/tests/dsi_backing_adapter.py +++ b/tests/dsi_backing_adapter.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise the production pending-backing adapter with bounded native leaves.""" from pathlib import Path +from production_source import read_source import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') functions='\n'.join(re.search(r'(?:bool|uint32_t|void) XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in ['wrapDSIBackingAllocate','wrapDSIBackingFree','prepareDSINativeBacking','validateDSINativeBacking','paintDSINativeBacking','discardDSINativeBacking','probeDSINativeBacking']) state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] diff --git a/tests/dsi_backlight_adapter.py b/tests/dsi_backlight_adapter.py index 7de98ad3..c3f51024 100644 --- a/tests/dsi_backlight_adapter.py +++ b/tests/dsi_backlight_adapter.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Execute the production brightness adapter across retained display-off.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile r=Path(__file__).resolve().parents[1] -s=(r/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(r/'WhateverXe/kern_xe.cpp') body='\n'.join(re.search(r'uint32_t XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in ['setDSIBrightness','saveDSIBrightness']) code=r''' #include "kern_dsi_backlight.hpp" diff --git a/tests/dsi_boot_adapter.py b/tests/dsi_boot_adapter.py index 72900150..1d511168 100644 --- a/tests/dsi_boot_adapter.py +++ b/tests/dsi_boot_adapter.py @@ -1,18 +1,17 @@ #!/usr/bin/env python3 """Execute production firmware-copy and DSI pre-native handoff with mock IOKit.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile root=Path(__file__).resolve().parents[1] -source=(root/'WhateverXe/kern_xe.cpp').read_text() +source=read_source(root/'WhateverXe/kern_xe.cpp') # Observation must precede the destructive external-output trial and must # use scratch storage, not publish the certificate to the live DSI owner. -audit_start=source.index('void XE::auditDSIBoot(') -audit_end=source.index('void XE::adlnBootstrap(',audit_start) -audit_source=source[audit_start:audit_end] +audit_source=re.search(r'void XE::auditDSIBoot\([^;{]*\{.*?\n\}',source,re.S)[0] assert 'if(!checkKernelArgument("-wex-dsi-audit"))return;' in audit_source assert 'scratch->boot,scratch->audit' in audit_source and '"WEXDSIBootAudit"' in audit_source assert 'dsiBoot=' not in audit_source and 'write(' not in audit_source -bootstrap=source[audit_end:] +bootstrap=re.search(r'void XE::adlnBootstrap\([^;{]*\{.*?\n\}',source,re.S)[0] assert bootstrap.index('auditDSIBoot(pci,') < bootstrap.index('Display13DSITrial::transfer(') functions=audit_source+'\n'+'\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',source,re.S)[0] for name in ['prepareDSIFirmware','prepareDisplay13BootScanout']) diff --git a/tests/dsi_boot_adoption.py b/tests/dsi_boot_adoption.py index 28bced06..3fc5b1fc 100644 --- a/tests/dsi_boot_adoption.py +++ b/tests/dsi_boot_adoption.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise the production boot adoption transaction; mode setter is a fault-injectable leaf.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'bool XE::adoptDSIBootPath\(.*?\n\}',s,re.S)[0] binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S)[0] boot=(root/'tests/dsi_boot.cpp').read_text();io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] diff --git a/tests/dsi_boot_probe.py b/tests/dsi_boot_probe.py index 6f701413..b76f6cdb 100644 --- a/tests/dsi_boot_probe.py +++ b/tests/dsi_boot_probe.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Qualify DSI boot recognition; optionally replay the exact native consumer.""" from pathlib import Path +from production_source import read_source import hashlib, re, subprocess, sys, tempfile root=Path(__file__).resolve().parents[1] -source=(root/'WhateverXe/kern_xe.cpp').read_text() +source=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'uint32_t XE::wrapDSIBootProbe\(.*?\n\}',source,re.S)[0] boot=(root/'tests/dsi_boot.cpp').read_text() io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] diff --git a/tests/dsi_command_adapter.py b/tests/dsi_command_adapter.py index c7ab8126..c09711a8 100644 --- a/tests/dsi_command_adapter.py +++ b/tests/dsi_command_adapter.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Replay the bootstrap command trial's real ownership and MMIO boundary.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'bool XE::probeDSICommands\(.*?\n\}',s,re.S)[0] code=r''' #include "kern_dsi_commands.hpp" diff --git a/tests/dsi_irq_live.py b/tests/dsi_irq_live.py index 042ad61f..d26c0aef 100644 --- a/tests/dsi_irq_live.py +++ b/tests/dsi_irq_live.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise the production memory-only DSI IRQ snapshot and lock lifetime.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') parts=[re.search(pattern,s,re.S)[0] for pattern in [ r'struct Display13FBIRQContext \{.*?\n\};', r'static void adlnIRQRecord\(.*?\n\}', diff --git a/tests/dsi_lifecycle_dispatch.py b/tests/dsi_lifecycle_dispatch.py index 7a6ea98b..e499b98e 100644 --- a/tests/dsi_lifecycle_dispatch.py +++ b/tests/dsi_lifecycle_dispatch.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 """Run production lifecycle entry points; no DSI request may reach donor writes.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() -functions='\n'.join(re.search(r'(?:static bool|uint32_t|void) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] +s=read_source(root/'WhateverXe/kern_xe.cpp') +functions='\n'.join(re.search(r'(?:static bool|uint32_t|void) (?:XE::)?'+name+r'\([^;{]*\{.*?\n\}',s,re.S)[0] for name in ['dsiCachePath','dsiLifecycleTarget','wrapDisplay13TCSetMode','wrapAlderLakeNDisableDisplay']) code=r''' #include "kern_display13_fb_memory.hpp" diff --git a/tests/dsi_native_cache.py b/tests/dsi_native_cache.py index 9a55e763..48fe35f1 100644 --- a/tests/dsi_native_cache.py +++ b/tests/dsi_native_cache.py @@ -1,20 +1,21 @@ #!/usr/bin/env python3 """Execute DSI cache hooks with real binding/boot helpers and bounded fake objects.""" from pathlib import Path +from production_source import read_source import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') names=['updateDSIBootCache','wrapDSIUpdateCache','wrapDSIGetCRTC'] -functions='\n'.join(re.search(r'(?:bool|void) XE::'+n+r'\(.*?\n\}',s,re.S)[0] for n in names) -functions=re.search(r'static bool dsiCachePath\(.*?\n\}',s,re.S)[0]+'\n'+functions -binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S)[0] +functions='\n'.join(re.search(r'(?:bool|void) XE::'+n+r'\([^;{]*\{.*?\n\}',s,re.S)[0] for n in names) +functions=re.search(r'static bool dsiCachePath\([^;{]*\{.*?\n\}',s,re.S)[0]+'\n'+functions +binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\([^;{]*\{.*?\n\}',s,re.S)[0] boot=(root/'tests/dsi_boot.cpp').read_text();io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] # Non-source cache-word drift must be caught by the two snapshots as well. io=io.replace('bool driftOnWrite=false,strict=false;','bool driftOnWrite=false,strict=false,cacheDrift=false;unsigned cacheReads=0;') io=io.replace('reads.push_back(reg);return regs[reg];','reads.push_back(reg);if(cacheDrift && reg==0x701CC && ++cacheReads==2)++regs[reg];return regs[reg];') if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' - install=re.search(r'bool XE::processDisplay13DSICache\(.*?\n\}',s,re.S)[0] + install=re.search(r'bool XE::processDisplay13DSICache\([^;{]*\{.*?\n\}',s,re.S)[0] offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',install)[1].split(',')] entries=re.search(r'entries\[\]\[16\]=\{(.*?)\};',install,re.S)[1] for offset,row in zip(offsets,re.findall(r'\{([^}]+)\}',entries)): diff --git a/tests/dsi_path_disposal.py b/tests/dsi_path_disposal.py index b8905f23..de481246 100644 --- a/tests/dsi_path_disposal.py +++ b/tests/dsi_path_disposal.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise production DSI PG/path disposal, optionally replay exact CPU leaves.""" from pathlib import Path +from production_source import read_source import hashlib,re,subprocess,sys,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'bool XE::disposeDSIPath\(.*?\n\}',s,re.S)[0] code=r''' #include "kern_dsi_quiesce.hpp" diff --git a/tests/dsi_port_adapter.py b/tests/dsi_port_adapter.py index ffc91d9b..e4529712 100644 --- a/tests/dsi_port_adapter.py +++ b/tests/dsi_port_adapter.py @@ -5,9 +5,10 @@ The topology, power leases and register values are synthetic CPU fixtures. """ from pathlib import Path +from production_source import read_source import hashlib, re, subprocess, sys, tempfile root=Path(__file__).resolve().parents[1] -source=(root/'WhateverXe/kern_xe.cpp').read_text() +source=read_source(root/'WhateverXe/kern_xe.cpp') functions='\n'.join(re.search(r'uint32_t XE::'+name+r'\(.*?\n\}',source,re.S)[0] for name in ['setDSIPortMode','wrapAlderLakeNPortState','wrapDisplay13ProbeMode']) dispatch=re.search(r'uint32_t XE::wrapDisplay13TCMode\(.*?\n\}',source,re.S)[0] diff --git a/tests/dsi_presentation.py b/tests/dsi_presentation.py index 875aceae..6e31dd82 100644 --- a/tests/dsi_presentation.py +++ b/tests/dsi_presentation.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise the production presentation transaction with native allocation leaves mocked.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile root=Path(__file__).resolve().parents[1] -source=(root/'WhateverXe/kern_xe.cpp').read_text() +source=read_source(root/'WhateverXe/kern_xe.cpp') functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+name+r'\(.*?\n\}',source,re.S)[0] for name in ['releaseDSILogical','setDSIPresentation']) code=r''' #include "kern_dsi_native_mode.hpp" diff --git a/tests/dsi_quiesce_adapter.py b/tests/dsi_quiesce_adapter.py index 1978e8e3..6c3fff82 100644 --- a/tests/dsi_quiesce_adapter.py +++ b/tests/dsi_quiesce_adapter.py @@ -1,15 +1,19 @@ #!/usr/bin/env python3 """Exercise the production native DSI stop boundary with real host/IRQ cores.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'bool XE::quiesceDSIPath\(.*?\n\}',s,re.S)[0] b=(root/'tests/dsi_boot.cpp').read_text();fixture=b[b.index('struct IO {'):b.index('Boot capture(')] code=r''' #include "kern_dsi_quiesce.hpp" #include "kern_dsi_clock.hpp" +#include "kern_dsi_restart.hpp" +#include "kern_dsi_panel.hpp" #include "kern_dsi_boot.hpp" +#include "kern_dsi_native_backing.hpp" #include "kern_display13_fb_memory.hpp" #include "kern_display13_power_bridge.hpp" #include "kern_xelp_combo_clock.hpp" @@ -21,6 +25,10 @@ using namespace XELP::DSI; FIXTURE std::vector locks; +#ifndef DSI_SURFACE_REPLAY +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char*,const void*,unsigned){assert(locks.empty());}}; +#endif struct IORecursiveLock{unsigned rank;};struct IOLock{};struct IOSimpleLock{}; void IORecursiveLockLock(IORecursiveLock *p){assert(locks.empty() || locks.back()rank);locks.push_back(p->rank);} void IORecursiveLockUnlock(IORecursiveLock *p){assert(locks.back()==p->rank);locks.pop_back();} @@ -62,7 +70,7 @@ }; struct {IORecursiveLock *lock=nullptr;uintptr_t thread=0;XELPComboClock::ClockAllocation allocation;}comboClockTransaction; struct {IOLock *lock=nullptr;IOService *controller=nullptr;void *power=nullptr;IOMemoryMap *map=nullptr;IOWorkLoop *workLoop=nullptr;OSObject *dsiPort=nullptr; - Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge;bool clockFaulted=false,memoryFaulted=false; + Display13Transcoder::Bindings transcoders;Readout bootDSI;Display13Power::NativeBridge bridge;bool clockFaulted=false,memoryFaulted=false,watermarkFaulted=false; Display13DBUF::State dbuf;unsigned dsiNativeWriters=0; struct {IOService *memoryFramebuffer=nullptr;uintptr_t publishedScanout=0;struct {uintptr_t record=0;}scanout;bool destroyingPath=false,wmProgramming=false,wmCommitting=false;}pipe[3]; }display13FBPower; @@ -71,6 +79,10 @@ Display13FBIRQ::HandlerScopes scopes;}display13FBIRQ; void adlnFault(bool &fault,unsigned){assert(locks==std::vector({1,2,3}));fault=true;} struct {bool ready=true;}dsiFixedMode; + #ifndef DSI_SURFACE_REPLAY +struct {NativeBacking::Record *record=nullptr;bool modeReady=false;} dsiPendingBacking; +bool dsiPublishedTarget(IOService *){return false;} // This fixture retires backing; it does not retain it for wake. +#endif bool dsiModeTarget(IOService *fb){return fb==display13FBPower.pipe[0].memoryFramebuffer;} bool mappingBound=true,failDDIPut=false,failMemoryFree=false,lieMemoryFree=false,loseDBUF=false,loseIRQRestore=false,losePowerFree=false;unsigned memoryFrees=0; bool adlnWriterMappingBound(){return mappingBound;} @@ -97,6 +109,10 @@ uintptr_t adlnWorkLoopInGate=reinterpret_cast(inGate),adlnWorkLoopTryClose=reinterpret_cast(tryClose),adlnWorkLoopOpen=reinterpret_cast(open); struct {unsigned dsi=1;operator unsigned()const{return dsi;}}adlnOutputWiring;void *adlnPlatform=nullptr; bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false,dsiMemoryReleased=false; + bool dsiStopTest=false,dsiPowerTest=false; + Panel::Shutdown dsiPanelStop; + struct {bool sleeping=false,failed=false;unsigned cycles=0;Surface::Plane plane; + uint32_t cursorControl=0,cursorPosition=0,cursorBase=0;Restart::State host;Panel::Result panel;}dsiRuntimePower; IOService *adlnTopologyController=nullptr;Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop; bool dsiArmedBackingOwned(IOService *fb,OSObject*){return armedOwner && pendingFB==fb;} bool retireDSINativeBacking(IOService *fb,OSObject*){ @@ -128,7 +144,7 @@ const auto result=display13FBPower.bridge.putDDI(pio,0); IOLockUnlock(display13FBPower.lock);return result.status==Display13Power::Status::Complete; } - bool quiesceDSIPath(IOService*,IOService*,OSObject*); + bool quiesceDSIPath(IOService*,IOService*,OSObject*,bool retain=false); }; BODY templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index eb51cb3d..721da04c 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 """Replay the production DSI resource publication boundary and lock order.""" from pathlib import Path +from production_source import read_source import re, subprocess, tempfile root=Path(__file__).resolve().parents[1] -source=(root/'WhateverXe/kern_xe.cpp').read_text() +source=read_source(root/'WhateverXe/kern_xe.cpp') body=re.search(r'bool XE::bindDSIResources\(.*?\n\}',source,re.S)[0] boot=(root/'tests/dsi_boot.cpp').read_text() io=boot[boot.index('struct IO {'):boot.index('Boot capture(')] code=r''' #include "kern_dsi_ggtt.hpp" +#include "kern_dsi_backlight.hpp" #include "kern_display13_power_bridge.hpp" #include "kern_display13_fb_irq.hpp" #include "kern_xelp_combo_clock.hpp" @@ -22,8 +24,9 @@ struct IORecursiveLock{};struct IOLock{};struct IOSimpleLock{}; void IORecursiveLockLock(IORecursiveLock*){assert(lockLevel==0);lockLevel=1;} void IORecursiveLockUnlock(IORecursiveLock*){assert(lockLevel==1);lockLevel=0;} -void IOLockLock(IOLock*){assert(lockLevel==1);lockLevel=2;} -void IOLockUnlock(IOLock*){assert(lockLevel==2);lockLevel=1;} +unsigned ownerParent=0; +void IOLockLock(IOLock*){assert(lockLevel<=1);ownerParent=lockLevel;lockLevel=2;} +void IOLockUnlock(IOLock*){assert(lockLevel==2);lockLevel=ownerParent;} bool IOSimpleLockLockDisableInterrupt(IOSimpleLock*){assert(lockLevel==2);lockLevel=3;return true;} void IOSimpleLockUnlockEnableInterrupt(IOSimpleLock*,bool saved){assert(lockLevel==3 && saved);lockLevel=2;} struct IOService {alignas(8) uint8_t bytes[0x1C20]{};IOService *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);}}; @@ -53,7 +56,8 @@ bool dsiCommandTest=false;bool probeDSICommands(IOService*){assert(lockLevel==0);return true;} bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; struct {unsigned dsi=1;} adlnOutputWiring; - Profile dsiProfile;Boot dsiBoot;GGTTMode dsiGGTT; + Profile dsiProfile;Boot dsiBoot;GGTTMode dsiGGTT;Backlight dsiBacklight; + void publishDSIBacklight(){assert(lockLevel==0);} bool bindDSIResources(IOService *); }; BODY @@ -65,6 +69,8 @@ XE xe;xe.adlnTopologyController=&controller;xe.dsiProfile=profile();xelpMemory=map.memory; xe.dsiBoot=captureBoot(map,AlderLakeN::readDisplay(map,true),xe.dsiProfile,map.memory,true); assert(verifyBootPrefix(map,xe.dsiBoot,map.memory)); + xe.dsiProfile.precision=8;xe.dsiProfile.minimum=6; + map.regs[Backlight::Control]=1U<<31;map.regs[Backlight::Period]=480;map.regs[Backlight::Duty]=240; map.regs[0x45454]=3;map.regs[0x42000]=0x8E380000; auto &b=c.bridge;auto &s=b.state;b.begun=true;b.selectedPorts=3;s.ready=true;s.allowed=Display13Power::All; const auto pg=Display13Power::dependencies(Display13Power::nativeScope(1)); diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index 6fa0a789..ac87f469 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Exercise the production surface/retirement adapters on the stop fixture's BAR.""" from pathlib import Path +from production_source import read_source import re,subprocess,tempfile root=Path(__file__).resolve().parents[1] -s=(root/'WhateverXe/kern_xe.cpp').read_text() +s=read_source(root/'WhateverXe/kern_xe.cpp') functions='\n'.join(re.search(r'bool XE::'+name+r'\(.*?\n\}',s,re.S)[0] for name in ('switchDSINativeSurface','dsiArmedBackingOwned','retireDSINativeBacking','publishDSINativeBacking','publishDSINativeMode')) state=re.search(r'struct DSIPendingBackingOwner \{.*?\n\};',s,re.S)[0] @@ -12,7 +13,7 @@ # are rewritten. Allocation/PTE proof and CPU mapping have their own adapter test. q=(root/'tests/dsi_quiesce_adapter.py').read_text() q=q.split("code=r'''",1)[1].split("'''.replace",1)[0] -prelude=q[:q.index('struct XE {')] +prelude='#define DSI_SURFACE_REPLAY\n'+q[:q.index('struct XE {')] prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_test_image.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"') b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') diff --git a/tests/dsi_update_dispatch.py b/tests/dsi_update_dispatch.py index fb46e78f..3450874a 100644 --- a/tests/dsi_update_dispatch.py +++ b/tests/dsi_update_dispatch.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 """Execute production mode/update/cursor admission and stop serialization.""" from pathlib import Path +from production_source import read_source import hashlib,re,subprocess,sys,tempfile -root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() -functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\(.*?\n\}',s,re.S)[0] +root=Path(__file__).resolve().parents[1];s=read_source(root/'WhateverXe/kern_xe.cpp') +functions='\n'.join(re.search(r'(?:static bool|bool|uint32_t|void|int) (?:XE::)?'+name+r'\([^;{]*\{.*?\n\}',s,re.S)[0] for name in ['dsiCachePath','dsiLifecycleTarget','wrapDSISetDisplayMode','dsiNativeUpdateAllowed','dsiSharedWriterStopped','wrapDisplay13WMArmUpdate','beginDSINativeWriter','endDSINativeWriter','wrapDSICursorImage','wrapDSICursorState','beginDSIFramebufferWriter','wrapDSIFlipTransaction','wrapDSIGammaTransaction','wrapDSICscTransaction','wrapDSIPlaneGammaTransaction','wrapDSISetGammaTable','wrapDSISetAttribute','wrapDSISetConnectionAttribute','wrapDSISetPowerState','wrapDSISetStream']) functions=functions.replace('void XE::endDSINativeWriter(int token) {', 'void XE::endDSINativeWriter(int token) {\n struct EndScope {EndScope(){endingWriter=true;}~EndScope(){endingWriter=false;}} endScope;') scope=re.search(r'struct DSIModeScope \{.*?\n\};',s,re.S)[0] @@ -11,7 +12,7 @@ binary=Path(sys.argv[1]).read_bytes() assert hashlib.sha256(binary).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' for name,width,count in [('processDSINativeWriters',20,2),('processDSINativeTransactions',17,5),('processDSINativeAttributes',20,4)]: - installer=re.search(r'bool XE::'+name+r'\(.*?\n\}',s,re.S)[0] + installer=re.search(r'bool XE::'+name+r'\([^;{]*\{.*?\n\}',s,re.S)[0] offsets=[int(x,0) for x in re.search(r'offsets\[\]=\{([^}]+)',installer)[1].split(',')] entries=re.search(r'entries\[\]\['+str(width)+r'\]=\{(.*?)\};',installer,re.S)[1] entries=[bytes(int(x,0) for x in row.split(',')) for row in re.findall(r'\{([^}]+)\}',entries)] diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index 9bef982c..a29e95d4 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -147,5 +147,6 @@ "-wex-adlp-load-only": [], "-wex-display13-offline": [], "-wex-dsi-hdmi-test": [], - "-wex-dsi-power-test": [] + "-wex-dsi-power-test": [], + "-wex-shutdown-observe": [] } diff --git a/tests/host_publication.py b/tests/host_publication.py index a80017bd..46a62855 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -43,6 +43,8 @@ bool routeMultiple(size_t,RouteRequest*,unsigned,mach_vm_address_t,size_t){return true;} void clearError(){} }; +// The optional observer is inactive in this host-publication fixture. +namespace ShutdownObserver {void install(KernelPatcher &) {}} namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; struct {size_t loadIndex=5;}backlight; diff --git a/tests/production_source.py b/tests/production_source.py new file mode 100644 index 00000000..996d2f03 --- /dev/null +++ b/tests/production_source.py @@ -0,0 +1,14 @@ +"""Read production source, expanding same-translation-unit implementation parts. + +Replay tests must extract the compiled implementation rather than a stale copy. +Ordinary headers stay as includes and are resolved by the C++ fixture compiler. +""" +import re +from pathlib import Path + + +def read_source(path): + path = Path(path) + return re.sub(r'^#include "([^"\n]+\.inc)"[ \t]*$', + lambda m: read_source(path.parent / m[1]), + path.read_text(), flags=re.M) diff --git a/tools/build.py b/tools/build.py index 5c4e919b..cf99fa3e 100644 --- a/tools/build.py +++ b/tools/build.py @@ -63,7 +63,7 @@ def main(): assert not any(x in unresolved for x in ['__ZN2XE','__ZN6XeHost','__ZN11DVMTCalcFix']), 'unresolved local implementation' manifest[name]={'identifier':ident,'version':version,'configuration':'RELEASE' if a.release else 'DEBUG','sha256':hashlib.sha256(exe.read_bytes()).hexdigest(),'source_sha256':{str(s.relative_to(ROOT)) if s.is_relative_to(ROOT) else 'Lilu/'+str(s.relative_to(lilu)):hashlib.sha256(s.read_bytes()).hexdigest() for s in sources}} if name=='AppleIntelTGLCompat':manifest[name]['personality_sha256']=hashlib.sha256((ROOT/'AppleIntelTGLCompat/GraphicsPolicy.plist').read_bytes()).hexdigest() - manifest['headers']={str(p.relative_to(ROOT)):hashlib.sha256(p.read_bytes()).hexdigest() for d in ['WhateverXe','AppleIntelTGLCompat'] for p in sorted((ROOT/d).glob('*.hpp'))} + manifest['headers']={str(p.relative_to(ROOT)):hashlib.sha256(p.read_bytes()).hexdigest() for d in ['WhateverXe','AppleIntelTGLCompat'] for p in sorted((ROOT/d).iterdir()) if p.suffix in {'.hpp','.inc'}} (out/'build-manifest.json').write_text(json.dumps(manifest,indent=2)+'\n') print(json.dumps({n:v['sha256'] for n,v in manifest.items() if n!='headers'},indent=2)) if __name__=='__main__':main() From 5bf1478fd14ce8637172a67121cbae389ffd36c0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 31 Oct 2026 11:01:54 +0900 Subject: [PATCH 117/139] Remove DSI startup test patterns and initialize scanout to black --- WhateverXe/kern_dsi_adapter_backing.inc | 71 +++++++++---------------- WhateverXe/kern_dsi_adapter_state.inc | 2 +- WhateverXe/kern_dsi_test_image.hpp | 54 ------------------- WhateverXe/kern_xe.cpp | 1 - WhateverXe/kern_xe.hpp | 2 +- tests/dsi_backing_adapter.py | 16 +++--- tests/dsi_surface_adapter.py | 24 ++++----- tests/dsi_test_image.cpp | 34 ------------ 8 files changed, 47 insertions(+), 157 deletions(-) delete mode 100644 WhateverXe/kern_dsi_test_image.hpp delete mode 100644 tests/dsi_test_image.cpp diff --git a/WhateverXe/kern_dsi_adapter_backing.inc b/WhateverXe/kern_dsi_adapter_backing.inc index c4b30483..c0817c53 100644 --- a/WhateverXe/kern_dsi_adapter_backing.inc +++ b/WhateverXe/kern_dsi_adapter_backing.inc @@ -48,7 +48,7 @@ uint32_t XE::wrapDSIBackingAllocate(OSObject *pipe,void *record,uint32_t flags) o.pinnedMap->retain();o.pinnedMemory->retain(); } return o.allocated && callbackXE->validateDSINativeBacking() && - callbackXE->paintDSINativeBacking(0) && callbackXE->switchDSINativeSurface() && + callbackXE->initializeDSINativeBacking() && callbackXE->switchDSINativeSurface() && callbackXE->publishDSINativeBacking() ? result : kIOReturnNotReady; } @@ -208,45 +208,40 @@ bool XE::validateDSINativeBacking() { // Mapping and writes remain inside the same native GPU lock as the allocator // and full PTE proof. The GPU map is IGAccelMemoryMap, not IOMemoryMap: create // a separate CPU mapping from its validated SysMemory descriptor. -bool XE::paintDSINativeBacking(unsigned phase) { +bool XE::initializeDSINativeBacking() { auto &o=dsiPendingBacking; - o.image={}; - if(!o.allocating || !o.allocated || (o.armed && dsiSession.state!=XELP::DSI::SessionState::Streaming) || !o.record || !o.pinnedMemory || + if(!o.allocating || !o.allocated || o.armed || !o.record || !o.pinnedMemory || o.thread!=reinterpret_cast(current_thread()) || !XELP::DSI::NativeBacking::allocated(*o.record,dsiProfile,o.proof))return false; + o.initialized=false; auto descriptor=OSDynamicCast(IOMemoryDescriptor,Display13FBMemory::field(o.pinnedMemory,0xD0)); if(!descriptor || descriptor->getLength()(o.proof.pitch),o.proof.bytes); - if(!shape.valid())return false; auto map=descriptor->createMappingInTask(kernel_task,0,kIOMapAnywhere,0,o.proof.bytes); if(!map)return false; const auto address=map->getVirtualAddress(); if(address && map->getLength()==o.proof.bytes && !(address&4095)) { - o.image=XELP::DSI::TestImage::paint(reinterpret_cast(address),map->getLength(),shape,phase); - if(o.image.complete) { - for(uint64_t offset=0;offset(address),0,o.proof.bytes); + for(uint64_t offset=0;offsetrelease(); - uint64_t receipt[] {1,phase,o.image.complete,o.image.bytes,o.image.digest,o.proof.gpu}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIImage",receipt,sizeof(receipt)); - return o.image.complete; + return o.initialized; } // The allocator's native GPU lock and FB mutex span this transaction. The // owner lock protects shared clock/DDB/IRQ association; no IRQ spinlock spans -// the bounded frame wait. Keep backing private in P2: normal native flips and -// mode-success publication belong to the desktop adapter. +// the bounded frame wait. Publish the backing only after the plane has latched. bool XE::switchDSINativeSurface() { using namespace XELP::DSI; auto &o=dsiPendingBacking;auto &c=display13FBPower;auto &irq=display13FBIRQ; auto fb=o.framebuffer; - if(!fb || !o.image.complete || !o.allocating || !o.allocated || o.armed || !o.record || !c.lock || + if(!fb || !o.initialized || !o.allocating || !o.allocated || o.armed || !o.record || !c.lock || o.thread!=reinterpret_cast(current_thread()) || dsiSession.state!=SessionState::Unbound)return false; - uint64_t receipt[24] {1,1}; + uint64_t receipt[18] {2,1}; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); auto path=Display13FBMemory::singlePath(fb); IOLockLock(c.lock); @@ -314,35 +309,19 @@ bool XE::switchDSINativeSurface() { if(!o.armed)dsiSession={}; } receipt[3]=ddb;receipt[4]=cursorDDB;receipt[5]=hz;receipt[6]=wm.allocation; - receipt[7]=next.control;receipt[8]=next.stride;receipt[9]=next.surface;receipt[10]=o.image.digest; + receipt[7]=next.control;receipt[8]=next.stride;receipt[9]=next.surface; } - receipt[11]=static_cast(result.status);receipt[12]=result.writes; - receipt[13]=static_cast(result.wm.status);receipt[14]=static_cast(result.latch.status); - receipt[15]=static_cast(dsiSession.state);receipt[16]=o.armed; + receipt[10]=static_cast(result.status);receipt[11]=result.writes; + receipt[12]=static_cast(result.wm.status);receipt[13]=static_cast(result.latch.status); + receipt[14]=static_cast(dsiSession.state);receipt[15]=o.armed; + receipt[16]=result.latch.frameBefore;receipt[17]=result.latch.frameAfter; + // Surface::change already proves frame progress, the live surface and + // source/owner stability before completing the session transaction. const bool switched=result.status==Surface::Status::Complete; IOLockUnlock(c.lock); - // A second pattern changes the contents of the same owned live surface. - // Keep native GPU serialization, flush it, then prove scanout advances. - bool updated=false; - if(switched && paintDSINativeBacking(1)) { - IOLockLock(c.lock); - receipt[17]=o.image.digest;receipt[18]=io.read(0x70040); - for(unsigned us=0;us<=100000;us+=1000) { - receipt[19]=io.read(0x70040); - const uint32_t frames=static_cast(receipt[19]-receipt[18]); - if(frames && frames<0x80000000U) { - updated=owned() && Surface::source(io,c.bootDSI) && - Surface::word(io,0x701AC,UINT32_MAX,static_cast(o.proof.gpu));break; - } - if(!owned() || receipt[19]==UINT32_MAX)break; - io.delayUs(1000); - } - IOLockUnlock(c.lock); - } - if(switched && !updated)dsiSession.abort(dsiSession.ticket(),SessionError::Switch); - receipt[1]=2;receipt[20]=updated;receipt[21]=static_cast(dsiSession.state); + receipt[1]=2; IORegistryEntry::getRegistryRoot()->setProperty("WEXDSISurface",receipt,sizeof(receipt)); - return switched && updated; + return switched; } // Still inside allocateScanoutMemory's GPU lock and the FB mutex. Transfer @@ -577,7 +556,7 @@ bool XE::discardDSINativeBacking(IOService *fb) { owner=o.accelerator;pipe=o.pipe;record=o.record;mapping=o.pinnedMap;memory=o.pinnedMemory; __atomic_store_n(&o.framebuffer,static_cast(nullptr),__ATOMIC_RELEASE); __atomic_store_n(&o.pipe,static_cast(nullptr),__ATOMIC_RELEASE); - o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=o.published=o.modeReady=false;o.nativeResult=0;o.proof={};o.image={}; + o.accelerator=nullptr;o.record=nullptr;o.pinnedMap=o.pinnedMemory=nullptr;__atomic_store_n(&o.thread,uintptr_t(0),__ATOMIC_RELEASE);o.allocated=o.freed=o.published=o.modeReady=false;o.nativeResult=0;o.proof={};o.initialized=false; } IOFree(record,sizeof(XELP::DSI::NativeBacking::Record)); if(mapping)mapping->release();if(memory)memory->release();if(pipe)pipe->release();owner->release();fb->release();return true; diff --git a/WhateverXe/kern_dsi_adapter_state.inc b/WhateverXe/kern_dsi_adapter_state.inc index e2bb4191..5c6667be 100644 --- a/WhateverXe/kern_dsi_adapter_state.inc +++ b/WhateverXe/kern_dsi_adapter_state.inc @@ -14,7 +14,7 @@ struct DSIPendingBackingOwner { bool allocating {},freeing {},allocated {},freed {},armed {},published {},modeReady {}; uint32_t nativeResult {}; Display13FBMemory::SurfaceReadout proof; - XELP::DSI::TestImage::Result image; + bool initialized {}; }; static DSIPendingBackingOwner dsiPendingBacking; static bool dsiPendingTarget(IOService *fb) { diff --git a/WhateverXe/kern_dsi_test_image.hpp b/WhateverXe/kern_dsi_test_image.hpp deleted file mode 100644 index 1f8cae5b..00000000 --- a/WhateverXe/kern_dsi_test_image.hpp +++ /dev/null @@ -1,54 +0,0 @@ -// P2 identification image for an uncompressed Y-tiled RGB8888 allocation. -// Fill every 4 KiB tile with one color, avoiding assumptions about its internal -// pixel swizzle. The last tiles include the padding beyond the visible mode. -#ifndef KERN_DSI_TEST_IMAGE_HPP -#define KERN_DSI_TEST_IMAGE_HPP -#include -#include -namespace XELP { namespace DSI { namespace TestImage { -struct Layout { - uint32_t columns {},rows {};uint64_t bytes {}; - bool valid() const {return columns>=2 && columns<=256 && rows>=2 && rows<=256 && - bytes==uint64_t(columns)*rows*4096;} -}; -inline Layout layout(uint32_t width,uint32_t height,uint32_t pitch,uint64_t bytes) { - Layout result; - if(width<33 || height<33 || width>8192 || height>8192 || pitch!=((width*4+127)&~127U))return result; - result={(width+31)/32,(height+31)/32,bytes};return result.valid()?result:Layout{}; -} -// Phase changes all cells. Red/green vertical edges and blue/yellow horizontal -// edges identify the portrait orientation; four distinct corners identify crop. -inline uint32_t color(const Layout &l,uint32_t x,uint32_t y,unsigned phase) { - uint32_t rgb=(((x/3+y/3)&1)?0x204060U:0xC08020U); - if(y==0)rgb=0x2040FF; - if(y+1==l.rows)rgb=0xFFFF20; - if(x==0)rgb=0xFF2020; - if(x+1==l.columns)rgb=0x20FF20; - if(x==0 && y==0)rgb=0xFFFFFF; - if(x+1==l.columns && y==0)rgb=0xFF20FF; - if(x==0 && y+1==l.rows)rgb=0x20FFFF; - if(x+1==l.columns && y+1==l.rows)rgb=0x808080; - return 0xFF000000U|(phase ? rgb^0xFFFFFFU : rgb); -} -struct Result {bool complete {};uint64_t bytes {};uint32_t digest {2166136261U};}; -// The adapter maps the verified SysMemory descriptor, owns GPU serialization, -// and flushes CPU cache lines before scanout. This core never owns a mapping. -inline Result paint(volatile uint32_t *memory,uint64_t length,const Layout &l,unsigned phase) { - Result r;if(!memory || (reinterpret_cast(memory)&3) || !l.valid() || length1)return r; - for(uint32_t y=0;y1: b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' @@ -18,7 +18,6 @@ assert 'routeMultipleShort' in installer code=r''' #include "kern_dsi_native_backing.hpp" -#include "kern_dsi_test_image.hpp" #include "kern_dsi_quiesce.hpp" #include "kern_dsi_clock.hpp" #include @@ -59,7 +58,7 @@ #define OSDynamicCast(T,p) static_cast(p) uint64_t receipt[16]{}; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry root;return &root;} - void setProperty(const char *name,const void *p,unsigned n){if(!strcmp(name,"WEXDSIImage")){assert(n==48);return;}assert(!strcmp(name,"WEXDSIBackingTrial") && n==sizeof(receipt));memcpy(receipt,p,n);}}; + void setProperty(const char *name,const void *p,unsigned n){assert(!strcmp(name,"WEXDSIBackingTrial") && n==sizeof(receipt));memcpy(receipt,p,n);}}; OSObject nativeMapping,nativeMemory,nativeDescriptor; IOService *modeFB=nullptr;bool dsiModeTarget(IOService *fb){return fb && modeFB==fb;} struct DSIModeScope {IORecursiveLock *lock;explicit DSIModeScope(IOService *fb):lock(field(fb,0x4220)){if(lock)++fbDepth;} @@ -102,7 +101,7 @@ static bool dsiNativeUpdateAllowed(IOService *fb){assert(fbDepth);return available && !dsiPendingTarget(fb);} static uint32_t wrapDSIBackingAllocate(OSObject*,void*,uint32_t); static uint32_t wrapDSIBackingFree(OSObject*,void*,uint32_t); - bool paintDSINativeBacking(unsigned);Session dsiSession; + bool initializeDSINativeBacking();Session dsiSession; bool publishDSINativeBacking(){assert(gpuDepth==1 && fbDepth && !ownerDepth);return true;} bool switchDSINativeSurface(){assert(gpuDepth==1 && fbDepth && !ownerDepth);return true;} bool probeDSIModeGeometry(IOService*){assert(fbDepth && !ownerDepth && !gpuDepth);return true;} @@ -135,10 +134,11 @@ void discard(){assert(xe.discardDSINativeBacking(&fb));} }; int main(){ - {Case c;failCPUMap=true;assert(!c.xe.prepareDSINativeBacking(&c.fb));assert(!dsiPendingBacking.image.complete && !cpuMaps);c.discard();} - {Case c;assert(c.xe.prepareDSINativeBacking(&c.fb)); - assert(dsiPendingBacking.image.complete && dsiPendingBacking.image.bytes==sizeof(cpuImage) && dsiPendingBacking.image.digest==0xf89939c5 && !cpuMaps); - c.discard();assert(!dsiPendingBacking.image.complete);} + {Case c;failCPUMap=true;assert(!c.xe.prepareDSINativeBacking(&c.fb));assert(!dsiPendingBacking.initialized && !cpuMaps);c.discard();} + {Case c;memset(cpuImage,0xA5,sizeof(cpuImage));assert(c.xe.prepareDSINativeBacking(&c.fb)); + assert(dsiPendingBacking.initialized && !cpuMaps); + for(auto pixel:cpuImage)assert(pixel==0); + c.discard();assert(!dsiPendingBacking.initialized);} {Case c;const auto a=allocCalls,f=freeCalls;DSIModeScope scope(&c.fb); available=false;c.xe.probeDSINativeBacking(&c.fb);assert(!c.xe.dsiBackingAttempted && allocCalls==a && receipt[2]==1); diff --git a/tests/dsi_surface_adapter.py b/tests/dsi_surface_adapter.py index ac87f469..5f30f69b 100644 --- a/tests/dsi_surface_adapter.py +++ b/tests/dsi_surface_adapter.py @@ -14,7 +14,7 @@ q=(root/'tests/dsi_quiesce_adapter.py').read_text() q=q.split("code=r'''",1)[1].split("'''.replace",1)[0] prelude='#define DSI_SURFACE_REPLAY\n'+q[:q.index('struct XE {')] -prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_test_image.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"') +prelude=prelude.replace('#include "kern_dsi_boot.hpp"','#include "kern_dsi_boot.hpp"\n#include "kern_dsi_surface.hpp"\n#include "kern_dsi_native_backing.hpp"\n#include "kern_display13_clock.hpp"') b=(root/'tests/dsi_boot.cpp').read_text();prelude=prelude.replace('FIXTURE',b[b.index('struct IO {'):b.index('Boot capture(')]) prelude=prelude.replace('bool armedOwner=false,failNativeRetire=false;unsigned nativeRetires=0;IOService *pendingFB=nullptr;bool dsiPendingTarget(IOService *fb){return fb && pendingFB==fb;}',state+'\nDSIPendingBackingOwner dsiPendingBacking;\nbool dsiPendingTarget(IOService *fb){return fb && dsiPendingBacking.framebuffer==fb;}') prelude=prelude.replace('#include "kern_dsi_native_backing.hpp"','#include "kern_dsi_native_backing.hpp"\n#include "kern_dsi_native_mode.hpp"') @@ -24,7 +24,7 @@ prelude=prelude.replace('struct {bool ready=true;}dsiFixedMode;','struct {bool ready=true;AppleAdapter::Timing timing;}dsiFixedMode;') prelude=prelude.replace(' void retain(){',' uint32_t callPlatformFunction(const char*,bool,void*,void*,void*,void*);\n void retain(){') prelude=prelude[:prelude.index('void tick(unsigned us)')]+published+r''' -uint64_t elapsed=0;unsigned gpuDepth=1;bool failPaint=false,failDiscard=false;unsigned discards=0; +uint64_t elapsed=0;unsigned gpuDepth=1;bool failDiscard=false;unsigned discards=0; templatevoid put(void *p,unsigned o,T v){memcpy(static_cast(p)+o,&v,sizeof(v));} void *current_thread(){return reinterpret_cast(1);} void clock_get_uptime(uint64_t *v){*v=elapsed*1000;}void absolutetime_to_nanoseconds(uint64_t t,uint64_t *v){*v=t;} @@ -60,15 +60,13 @@ void initial(IOService *fb,uint32_t arg){assert(!gpuDepth && locks==std::vector{1} && !arg);++cpuInitial; put(fb,0x4000,0U);put(fb,0x4008,UINT32_MAX);put(fb,0x400C,UINT32_MAX);put(fb,0x4200,1U);} void caps(IOService *fb){assert(!gpuDepth && locks==std::vector{1});++cpuCaps;put(fb,0x402C,1U);} -uint64_t receipt[24]{}; +uint64_t receipt[18]{}; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char *name,const void *p,unsigned n){if(!strcmp(name,"WEXDSISurface")){assert(n==sizeof(receipt));memcpy(receipt,p,n);}else assert(n==96);}}; struct XE { bool dsiNativeWriterHooksReady=true,dsiResourcesReady=true,dsiCacheHooksReady=true,dsiBackingHooksReady=true; bool dsiStopAttempted=false,dsiIRQParked=false,dsiDDIReleased=false; Profile dsiProfile;Boot dsiBoot;Quiesce::Result dsiStop;Clock::Result dsiClockStop;Session dsiSession;Session::Cleanup dsiCleanup; - bool paintDSINativeBacking(unsigned phase){assert(phase==1 && locks==std::vector{1} && gpuDepth==1); - if(failPaint)return false;dsiPendingBacking.image.digest=0x686999c5;return true;} bool discardDSINativeBacking(IOService *fb){assert(locks==std::vector{1} && !gpuDepth && !dsiPendingBacking.armed && dsiPendingTarget(fb)); ++discards;if(failDiscard)return false;dsiPendingBacking={};return true;} uintptr_t dsiComputeGeometry=reinterpret_cast(compute),orgAlderLakeNScanoutFree=reinterpret_cast(freeSlot); @@ -114,7 +112,7 @@ assert(AppleAdapter::makeTiming(xe.dsiProfile,dsiFixedMode.timing)); put(&fb,0x4A00,1U);put(&fb,0x4A08,&path);put(&controller,0xDF5,uint8_t(3));put(&controller,0xDF4,uint8_t(1));put(&controller,0xDF0,1U); put(&controller,0x1A10,1U);put(&fb,0x4288,uint8_t(0));failSlotReturn=failCache=failLogical=failModeDBUF=false;slotReturns=cpuInitial=cpuCaps=0; - o.image.complete=true;o.image.digest=0xf89939c5;elapsed=0;gpuDepth=1;failPaint=failDiscard=false;discards=0;memset(receipt,0,sizeof(receipt)); + o.initialized=true;elapsed=0;gpuDepth=1;failDiscard=false;discards=0;memset(receipt,0,sizeof(receipt)); ''') code=prelude+functions+'\n'+case+r''' bool run(){locks={1};bool result=xe.switchDSINativeSurface();assert(locks==std::vector{1});locks.clear();dsiPendingBacking.allocating=false;gpuDepth=0;return result;} @@ -123,16 +121,18 @@ ~Case(){assert(locks.empty());dsiPendingBacking={};} }; int main(){ - {Case c;assert(c.run());assert(receipt[11]==unsigned(Surface::Status::Complete) && receipt[12]==17 && receipt[20]); - assert(receipt[10]==0xf89939c5 && receipt[17]==0x686999c5 && receipt[19]>receipt[18]); + {Case c;dsiPendingBacking.initialized=false;const auto before=c.map.bar;assert(!c.run()); + assert(c.map.bar==before && !dsiPendingBacking.armed);} + {Case c;assert(c.run());assert(receipt[0]==2 && receipt[10]==unsigned(Surface::Status::Complete) && receipt[11]==17); + assert(receipt[17]>receipt[16]); assert(c.map.reg(0x7019C)==c.record.gpu && c.map.reg(0x701AC)==c.record.gpu && c.map.reg(0x70188)==34); assert(c.xe.dsiSession.state==SessionState::Streaming && dsiPendingBacking.armed && c.xe.dsiArmedBackingOwned(&c.fb,&c.path)); assert(!c.retire() && !discards);c.stopped();assert(c.retire() && discards==1 && !dsiPendingTarget(&c.fb)); assert(c.xe.dsiSession.state==SessionState::Released && c.xe.dsiCleanup.retiredBacking==reinterpret_cast(&c.xe.dsiBoot));} - for(unsigned fail=0;fail<3;++fail){Case c; - if(fail==0)c.map.freeze=true;else if(fail==1)failPaint=true;else display13FBIRQ.state.enabled=false; + for(unsigned fail=0;fail<2;++fail){Case c; + if(fail==0)c.map.freeze=true;else display13FBIRQ.state.enabled=false; const auto before=c.map.bar;assert(!c.run()); - if(fail==2){assert(!dsiPendingBacking.armed && c.map.bar==before && c.xe.dsiSession.state==SessionState::Unbound);continue;} + if(fail==1){assert(!dsiPendingBacking.armed && c.map.bar==before && c.xe.dsiSession.state==SessionState::Unbound);continue;} assert(dsiPendingBacking.armed && c.xe.dsiSession.state==SessionState::Faulted && !c.retire() && !discards); c.stopped();failDiscard=true;assert(!c.retire() && dsiPendingBacking.armed && c.xe.dsiSession.state==SessionState::Released); assert(c.xe.dsiArmedBackingOwned(&c.fb,&c.path));failDiscard=false;assert(c.retire() && discards==2);} @@ -152,7 +152,7 @@ if(!failure)assert(dsiPublishedTarget(&c.fb) && dsiPendingBacking.modeReady && Display13FBMemory::field(&c.fb,0x4330)==c.record.gpu); locks.clear(); } - puts("PASS production DSI surface: actual MMIO allowlist/WM/latch, second image frame, GPU/owner/IRQ locking, retained partial switches and stopped cleanup retry"); + puts("PASS production DSI surface: actual MMIO allowlist/WM/latch, GPU/owner/IRQ locking, retained partial switches and stopped cleanup retry"); } ''' with tempfile.TemporaryDirectory(prefix='dsi-surface-adapter-') as directory: diff --git a/tests/dsi_test_image.cpp b/tests/dsi_test_image.cpp deleted file mode 100644 index f27a5375..00000000 --- a/tests/dsi_test_image.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "kern_dsi_test_image.hpp" -#include "kern_display13_fb_memory.hpp" -#include -#include -#include -using namespace XELP::DSI::TestImage; -int main() { - const auto l=layout(1080,1920,4352,8355840); - assert(l.valid() && l.columns==34 && l.rows==60); - std::vector memory(l.bytes/4+2,0x12345678); - uint32_t digests[2]{}; - for(unsigned phase=0;phase<2;++phase) { - const auto result=paint(memory.data()+1,l.bytes,l,phase); - assert(result.complete && result.bytes==l.bytes);digests[phase]=result.digest; - assert(memory.front()==0x12345678 && memory.back()==0x12345678); - // Cross-check tile addressing against the existing scanout sampler; - // inspect both tile extremes and every tile at the visible edges. - for(unsigned y=0;y<1920;y+=32)for(unsigned x=0;x<1080;x+=32) { - uint64_t offset=0; - assert(Display13FBMemory::scanoutSampleOffset(true,34,1080,1920,0,x,y,offset)); - const auto expected=color(l,x/32,y/32,phase); - for(unsigned i=0;i<1024;++i)assert(memory[1+offset/4+i]==expected); - } - } - assert(digests[0]!=digests[1]); - const auto unchanged=memory; - assert(!paint(memory.data()+1,l.bytes-4,l,0).complete && memory==unchanged); - assert(!paint(memory.data()+1,l.bytes,l,2).complete && memory==unchanged); - assert(!paint(nullptr,l.bytes,l,0).complete); - assert(!layout(1080,1920,4320,8355840).valid()); - assert(!layout(1080,1920,4352,8351744).valid()); - assert(!layout(UINT32_MAX,1920,4352,8355840).valid()); - printf("PASS DSI full allocation image: 2040 tiles, edges, two phases, padding and bounds; digests %08x/%08x\n",digests[0],digests[1]); -} From 82faacd97529d750b5734a090536b15c3fb8b218 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:32:52 +0900 Subject: [PATCH 118/139] Name the synthesized DSI display MIPI DSI --- WhateverXe/kern_dsi_apple_timing.hpp | 2 +- tests/dsi_apple_timing.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 328eb826..1290aeb1 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -93,7 +93,7 @@ inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128]) { d[11]=((m.hfront>>8)<<6)|((m.hsync>>8)<<4)|((m.vfront>>4)<<2)|(m.vsync>>4); d[12]=m.widthMM;d[13]=m.heightMM;d[14]=((m.widthMM>>8)<<4)|(m.heightMM>>8); d[17]=0x18; // separate negative sync, matching the fixed Apple timing - edid[75]=0xFC;memcpy(edid+77,"WEX DSI VBT\n ",13); + edid[75]=0xFC;memcpy(edid+77,"MIPI DSI\n ",13); edid[93]=0x10;edid[111]=0x10; // unused descriptor slots uint8_t sum=0;for(unsigned i=0;i<127;++i)sum+=edid[i];edid[127]=static_cast(0-sum); return true; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index fcf51f96..7b1130c5 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -62,7 +62,7 @@ int main() { assert(((d[10]>>4)|(((d[11]>>2)&3)<<4))==40 && ((d[10]&15)|((d[11]&3)<<4))==2); assert(d[12]==86 && d[13]==154 && !d[14] && d[17]==0x18); for(unsigned i=35;i<38;i++)assert(!edid[i]);for(unsigned i=38;i<54;i++)assert(edid[i]==1); - assert(edid[75]==0xFC && !memcmp(edid+77,"WEX DSI VBT",11)); + assert(edid[75]==0xFC && !memcmp(edid+77,"MIPI DSI\n ",13)); auto bad=p.mode;bad.width=4096;assert(!AppleAdapter::makePanelEDID(bad,edid)); bad=p.mode;bad.vfront=64;assert(!AppleAdapter::makePanelEDID(bad,edid)); bad=p.mode;bad.clockKHz=10001;assert(AppleAdapter::makePanelEDID(bad,edid)); From a2254a16ed23cc353da21ca60bf07fb37f223094 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:58:12 +0900 Subject: [PATCH 119/139] Observe Display13 TC state across framebuffer shutdown --- WhateverXe/kern_shutdown_observer.cpp | 43 +++++++++++++++++++++++++-- WhateverXe/kern_shutdown_observer.hpp | 6 +++- WhateverXe/kern_xe.cpp | 23 ++++++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/framebuffer_common.py | 8 +++++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp index 8c5c8d45..adfb0cd3 100644 --- a/WhateverXe/kern_shutdown_observer.cpp +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -16,7 +16,13 @@ namespace ShutdownObserver { static const char *traceKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-trace"; struct Event { uint32_t code, detail; uint64_t time, pc; char label[24]; }; -struct Journal { uint32_t magic, version; uint64_t run; uint32_t count, dropped, failed, reserved; Event events[32]; }; +struct Journal { + uint32_t magic, version; uint64_t run; uint32_t count, dropped, failed, reserved; Event events[32]; + // Latest hwShutdown entry/return. The port snapshot is memory-only; it + // distinguishes retained software ownership from a missing native return. + uint32_t display[2][48]; +}; +static_assert(sizeof(Journal)==1952,"bounded NVRAM journal"); static Journal journal {}; static IODTNVRAM *nvram; static OSData *snapshots[2]; @@ -31,6 +37,8 @@ static int (*orgSync)(proc_t,void *,int *); static void (*orgHaltLog)(const char *,const void *,uint64_t); static int (*orgPEHalt)(unsigned int,uint32_t); static void (*orgPlatform)(void *,unsigned int); +static uint32_t (*orgDisplayShutdown)(void *,void *); +static DisplaySnapshot displaySnapshot; // _sync starts with a RIP-relative LEA. Lilu copies prologues verbatim, // so replay that exact prefix with its original callback address explicitly. extern "C" { @@ -42,7 +50,8 @@ __attribute__((naked,noinline)) static int syncBridge(proc_t,void *,int *) { static void publish() { IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownObserver",ready,sizeof(ready)); } // All call sites precede the final platform power-off. Refuse an interrupt-off // context; never take a blocking diagnostic lock or allocate journal storage here. -static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,const void *pc=nullptr) { +static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,const void *pc=nullptr, + const uint32_t *display=nullptr) { if(!armed || !ml_get_interrupts_enabled())return; if(!IOLockTryLock(lock)){OSIncrementAtomic(reinterpret_cast(&journal.dropped));return;} if(journal.count>=128){IOLockUnlock(lock);return;} @@ -50,6 +59,10 @@ static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,con e.code=code;e.detail=detail;e.time=mach_absolute_time();e.pc=reinterpret_cast(pc); if(label)strlcpy(e.label,label,sizeof(e.label)); ++journal.count; + if(display && (code==70 || code==71)) { + auto dst=journal.display[code-70];memcpy(dst,display,sizeof(journal.display[0])); + dst[0]=journal.count;dst[3]=detail; + } auto data=snapshots[journal.count%2]; memcpy(const_cast(data->getBytesNoCopy()),&journal,sizeof(journal)); const bool written=nvram->setProperty(traceSymbol,data); @@ -80,6 +93,13 @@ static int wrapSync(proc_t p,void *a,int *r) { record(30);int v=orgSync(p,a,r);r static int wrapReboot(int howto,char *message) { record(10,howto);return orgReboot(howto,message); } static void wrapNotify(int stage) { record(20,stage);orgNotify(stage);record(21,stage); } static void wrapPlatform(void *self,unsigned int type) { record(60,type);orgPlatform(self,type);record(61,type); } +static uint32_t wrapDisplayShutdown(void *controller,void *fb) { + if(!armed || !displaySnapshot || !ml_get_interrupts_enabled())return orgDisplayShutdown(controller,fb); + uint32_t state[48] {};displaySnapshot(controller,fb,state);record(70,0,"hwShutdown",nullptr,state); + const auto result=orgDisplayShutdown(controller,fb); + bzero(state,sizeof(state));displaySnapshot(controller,fb,state);record(71,result,"hwShutdown",nullptr,state); + return result; +} static int wrapPEHalt(unsigned int type,uint32_t details) { if(type==0 || type==1 || type==3)record(50,type);int v=orgPEHalt(type,details);if(type==0 || type==1 || type==3)record(51,type);return v; } static void wrapHaltLog(const char *what,const void *pc,uint64_t time) { orgHaltLog(what,pc,time); @@ -112,7 +132,7 @@ void install(KernelPatcher &p) { syncNvram=reinterpret_cast(sync); // NVRAM may not be published when Lilu routes the kernel; resolve it on arm. lock=IOLockAlloc();traceSymbol=OSSymbol::withCString(traceKey); - journal.magic=0x57585344;journal.version=1;journal.run=mach_absolute_time()^base;ready[1]=journal.run; + journal.magic=0x57585344;journal.version=2;journal.run=mach_absolute_time()^base;ready[1]=journal.run; snapshots[0]=OSData::withBytes(&journal,sizeof(journal));snapshots[1]=OSData::withBytes(&journal,sizeof(journal)); if(!lock || !traceSymbol || !snapshots[0] || !snapshots[1]){ready[6]=1;publish();return;} KernelPatcher::RouteRequest routes[]={ @@ -141,4 +161,21 @@ void install(KernelPatcher &p) { else {ready[6]=4;ready[7]=static_cast(p.getError());p.clearError();} publish(); } +void installFramebuffer(KernelPatcher &p,size_t index,mach_vm_address_t address,size_t size,DisplaySnapshot snapshot) { + // Reuse the explicitly enabled, exact-kernel observer. This optional route + // never becomes a prerequisite for normal framebuffer operation. + if(ready[2]!=63 || !snapshot)return; + const char *symbol="__ZN31AppleIntelFramebufferController10hwShutdownEP21AppleIntelFramebuffer"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; + const auto target=p.solveSymbol(index,symbol,address,size);p.clearError(); + const auto delta=static_cast(reinterpret_cast(wrapDisplayShutdown)-(target+5)); + bool known=size>0x51d4b && target==address+0x5173a && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && delta==static_cast(delta); + if(known) { + KernelPatcher::RouteRequest route(symbol,wrapDisplayShutdown,orgDisplayShutdown); + known=p.routeMultipleShort(index,&route,1,address,size);p.clearError(); + if(known)displaySnapshot=snapshot; + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownDisplayObserver",known); +} } diff --git a/WhateverXe/kern_shutdown_observer.hpp b/WhateverXe/kern_shutdown_observer.hpp index 9e4b1207..bf8f9aef 100644 --- a/WhateverXe/kern_shutdown_observer.hpp +++ b/WhateverXe/kern_shutdown_observer.hpp @@ -1,3 +1,7 @@ #pragma once #include -namespace ShutdownObserver { void install(KernelPatcher &patcher); } +namespace ShutdownObserver { +void install(KernelPatcher &patcher); +using DisplaySnapshot = void (*)(void *controller, void *fb, uint32_t *words); +void installFramebuffer(KernelPatcher &, size_t, mach_vm_address_t, size_t, DisplaySnapshot); +} diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 8eacca41..14a6c61b 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11,6 +11,7 @@ #include "kern_dsi_native_backing.hpp" #include "kern_dsi_native_mode.hpp" #include "kern_xe.hpp" +#include "kern_shutdown_observer.hpp" #include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" #include "kern_alderlake_n.hpp" @@ -11890,6 +11891,26 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add // One entry owns donor ABI/lifecycle repairs for every hardware backend. // Backend installers cannot independently opt into or replace these hooks. +void XE::snapshotDisplay13Shutdown(void *controller,void *fb,uint32_t *out) { + // Diagnostics must neither enter a gated native call nor wait behind the + // shutdown path. No MMIO, power transitions, or register serializers here. + auto &c=display13FBPower; + out[1]=1;out[2]=UINT32_MAX; + if(!c.lock || !IOLockTryLock(c.lock))return; + if(controller!=c.controller || !fb || !c.tcSupported) {IOLockUnlock(c.lock);return;} + out[2]=Display13FBMemory::field(fb,0x1DC); + out[4]=1;out[5]=c.clockFaulted;out[6]=c.tcBankFaulted;out[7]=c.bridge.state.faulted; + for(unsigned tc=0;tc<4;++tc) { + auto &m=c.tcMode[tc];auto port=c.tcObject[tc];auto row=out+8+10*tc; + row[0]=port?Display13FBMemory::field(port,0x550):UINT32_MAX; + row[1]=static_cast(m.phase);row[2]=m.disconnectPending;row[3]=m.ownership.active; + row[4]=m.accessLease.domains;row[5]=m.auxLease.domains; + row[6]=c.bridge.nativeDDIRefs(tc+3);row[7]=c.bridge.nativeAUXRefs(tc+3); + row[8]=c.tcBusy[tc];row[9]=c.tcSignalReady[tc]; + } + IOLockUnlock(c.lock); +} + bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(framebufferDonorAttempted)return framebufferBackendInstalled; framebufferDonorAttempted=true; @@ -11901,6 +11922,8 @@ bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_addr KernelPatcher::RouteRequest gate(symbol,wrapFramebufferStart,orgFramebufferStart); const bool routed=patcher.routeMultiple(index,&gate,1,address,size);patcher.clearError(); if(!routed)return false; + if(display13FramebufferRequested) + ShutdownObserver::installFramebuffer(patcher,index,address,size,snapshotDisplay13Shutdown); framebufferDonorCommonReady=processFramebufferCommon(patcher,index,address,size); // Install hardware routes even after a common failure so their diagnostics // remain available. The common start gate never enters native startup then. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index d08ac000..8db52553 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -760,6 +760,7 @@ class XE { bool framebufferDonorAttempted {},framebufferDonorCommonReady {},framebufferBackendInstalled {}; mach_vm_address_t orgFramebufferStart {},orgFramebufferInit {},orgFramebufferComputeLanes {},framebufferLaneCaller {}; __attribute__((noinline)) bool processFramebufferKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static void snapshotDisplay13Shutdown(void *,void *,uint32_t *); __attribute__((noinline)) bool processFramebufferCommon(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool framebufferTrainingHooksReady {}; bool framebufferStartInProgress {}; diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index 6eff1f95..51eb184b 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -60,6 +60,10 @@ }; bool tglInstallSonomaSurfaceABI(KernelPatcher&,size_t,uintptr_t,size_t,bool){return surfaceOK;} bool xelpInstallNVRAMABI(KernelPatcher&,size_t,uintptr_t,size_t){return nvramOK;} +namespace ShutdownObserver { +unsigned calls=0; +void installFramebuffer(KernelPatcher&,size_t,uintptr_t,size_t,void(*)(void*,void*,uint32_t*)){++calls;} +} struct XE { bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; bool display13FramebufferRequested=false,tglPlatformNVRAMReady=false,tglProbeReady=false,tglFramebufferInitReady=false; @@ -69,6 +73,7 @@ uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0;bool stopDuringStart=false,nativeOK=true; bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_t); + static void snapshotDisplay13Shutdown(void*,void*,uint32_t*){} bool processFramebufferCommon(KernelPatcher&,size_t,uintptr_t,size_t); bool processFramebufferDonorRuntime(KernelPatcher&,size_t,uintptr_t,size_t){++commonRuntimeCalls;return runtimeOK;} bool processFramebufferAGDC(KernelPatcher&,size_t,uintptr_t,size_t){return agdcOK;} @@ -98,11 +103,13 @@ else memcpy(image.data()+span.first,span.second.data(),span.second.size()); } for(bool display13:{false,true})for(unsigned failure=0;failure<14;++failure){ + ShutdownObserver::calls=0; auto b=image;uintptr_t pure=0x1234;memcpy(b.data()+0xccad0+0x188,&pure,sizeof(pure)); XE x;callbackXE=&x;x.display13FramebufferRequested=display13;KernelPatcher p; surfaceOK=failure!=5;nvramOK=failure!=6;x.runtimeOK=failure!=7;x.agdcOK=failure!=8;x.backendOK=failure!=9;x.trainingOK=failure!=10;x.stopDuringStart=failure==11;x.nativeOK=failure!=12; if(failure>=1&&failure<=4)p.failAt=failure; bool handled=x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); + assert(ShutdownObserver::calls==(display13 && failure!=1?1U:0U)); assert(handled==(failure!=1)); IOService c; if(failure==13){XELPComboClock::ClockSnapshot clocks;clocks.layout=XELPComboClock::Layout::Display13; @@ -117,6 +124,7 @@ assert(XE::wrapFramebufferStart(&c,&c) && x.startCalls==1);} const auto calls=p.attempts,backends=x.backendCalls; x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); + assert(ShutdownObserver::calls==(display13 && failure!=1?1U:0U)); assert(p.attempts==calls&&x.backendCalls==backends); // no second trampoline } puts("PASS common-before-backend order, single ownership, partial failures and mandatory native-start gate"); From 4ffc21d5c6cd51cd270e8107133abfcb54af6d5e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:12:42 +0900 Subject: [PATCH 120/139] Release idle Display13 TC ownership during system shutdown --- WhateverXe/kern_shutdown_observer.cpp | 29 ++-------- WhateverXe/kern_shutdown_observer.hpp | 2 +- WhateverXe/kern_xe.cpp | 75 +++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 5 ++ tests/display13_shutdown.py | 79 +++++++++++++++++++++++++++ tests/framebuffer_common.py | 13 ++--- tools/test.py | 2 + 7 files changed, 166 insertions(+), 39 deletions(-) create mode 100644 tests/display13_shutdown.py diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp index adfb0cd3..2ff0cc3c 100644 --- a/WhateverXe/kern_shutdown_observer.cpp +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -37,8 +37,6 @@ static int (*orgSync)(proc_t,void *,int *); static void (*orgHaltLog)(const char *,const void *,uint64_t); static int (*orgPEHalt)(unsigned int,uint32_t); static void (*orgPlatform)(void *,unsigned int); -static uint32_t (*orgDisplayShutdown)(void *,void *); -static DisplaySnapshot displaySnapshot; // _sync starts with a RIP-relative LEA. Lilu copies prologues verbatim, // so replay that exact prefix with its original callback address explicitly. extern "C" { @@ -93,12 +91,10 @@ static int wrapSync(proc_t p,void *a,int *r) { record(30);int v=orgSync(p,a,r);r static int wrapReboot(int howto,char *message) { record(10,howto);return orgReboot(howto,message); } static void wrapNotify(int stage) { record(20,stage);orgNotify(stage);record(21,stage); } static void wrapPlatform(void *self,unsigned int type) { record(60,type);orgPlatform(self,type);record(61,type); } -static uint32_t wrapDisplayShutdown(void *controller,void *fb) { - if(!armed || !displaySnapshot || !ml_get_interrupts_enabled())return orgDisplayShutdown(controller,fb); - uint32_t state[48] {};displaySnapshot(controller,fb,state);record(70,0,"hwShutdown",nullptr,state); - const auto result=orgDisplayShutdown(controller,fb); - bzero(state,sizeof(state));displaySnapshot(controller,fb,state);record(71,result,"hwShutdown",nullptr,state); - return result; +void framebuffer(bool returned,void *controller,void *fb,uint32_t result,DisplaySnapshot snapshot) { + if(!armed || !snapshot || !ml_get_interrupts_enabled())return; + uint32_t state[48] {};snapshot(controller,fb,state); + record(returned?71:70,result,"hwShutdown",nullptr,state); } static int wrapPEHalt(unsigned int type,uint32_t details) { if(type==0 || type==1 || type==3)record(50,type);int v=orgPEHalt(type,details);if(type==0 || type==1 || type==3)record(51,type);return v; } static void wrapHaltLog(const char *what,const void *pc,uint64_t time) { @@ -161,21 +157,4 @@ void install(KernelPatcher &p) { else {ready[6]=4;ready[7]=static_cast(p.getError());p.clearError();} publish(); } -void installFramebuffer(KernelPatcher &p,size_t index,mach_vm_address_t address,size_t size,DisplaySnapshot snapshot) { - // Reuse the explicitly enabled, exact-kernel observer. This optional route - // never becomes a prerequisite for normal framebuffer operation. - if(ready[2]!=63 || !snapshot)return; - const char *symbol="__ZN31AppleIntelFramebufferController10hwShutdownEP21AppleIntelFramebuffer"; - const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; - const auto target=p.solveSymbol(index,symbol,address,size);p.clearError(); - const auto delta=static_cast(reinterpret_cast(wrapDisplayShutdown)-(target+5)); - bool known=size>0x51d4b && target==address+0x5173a && - !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && delta==static_cast(delta); - if(known) { - KernelPatcher::RouteRequest route(symbol,wrapDisplayShutdown,orgDisplayShutdown); - known=p.routeMultipleShort(index,&route,1,address,size);p.clearError(); - if(known)displaySnapshot=snapshot; - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownDisplayObserver",known); -} } diff --git a/WhateverXe/kern_shutdown_observer.hpp b/WhateverXe/kern_shutdown_observer.hpp index bf8f9aef..4e36224d 100644 --- a/WhateverXe/kern_shutdown_observer.hpp +++ b/WhateverXe/kern_shutdown_observer.hpp @@ -3,5 +3,5 @@ namespace ShutdownObserver { void install(KernelPatcher &patcher); using DisplaySnapshot = void (*)(void *controller, void *fb, uint32_t *words); -void installFramebuffer(KernelPatcher &, size_t, mach_vm_address_t, size_t, DisplaySnapshot); +void framebuffer(bool returned,void *controller,void *fb,uint32_t result,DisplaySnapshot); } diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 14a6c61b..32cfc088 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10082,6 +10082,12 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { void XE::wrapAlderLakeNPowerOverride(void *power,bool enable) { auto &c=display13FBPower;if(!c.lock || power!=c.power || !c.controller)return; + // The last hwShutdown can suspend the power banks before returning. + // Return idle TC PHY ownership while its access lease is still usable. + // Ordinary display/system sleep override calls keep their existing policy. + if(!enable && callbackXE->display13ShutdownPowerCaller && + reinterpret_cast(__builtin_return_address(0))==callbackXE->display13ShutdownPowerCaller) + finishDisplay13TCShutdown(c.controller); // override(true) is only reached by the donor's simulator backend; our // physical object has simulator=0. Do not force unrelated DDI/AUX domains. if(!enable)wrapAlderLakeNEngineDisable(power); @@ -11889,17 +11895,74 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add return true; } -// One entry owns donor ABI/lifecycle repairs for every hardware backend. -// Backend installers cannot independently opt into or replace these hooks. +bool XE::processDisplay13Shutdown(KernelPatcher &p,size_t index,mach_vm_address_t address,size_t size) { + const char *symbol="__ZN31AppleIntelFramebufferController10hwShutdownEP21AppleIntelFramebuffer"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; + const uint8_t powerCall[]={0xe8,0x3b,0x97,0,0}; + const auto target=p.solveSymbol(index,symbol,address,size);p.clearError(); + const auto delta=static_cast(reinterpret_cast(wrapDisplay13Shutdown)-(target+5)); + bool known=size>0x51d4b && target==address+0x5173a && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && + !memcmp(reinterpret_cast(address+0x51d24),powerCall,sizeof(powerCall)) && delta==static_cast(delta); + if(known) { + KernelPatcher::RouteRequest route(symbol,wrapDisplay13Shutdown,orgDisplay13Shutdown); + known=p.routeMultipleShort(index,&route,1,address,size);p.clearError(); + if(known)display13ShutdownPowerCaller=address+0x51d29; + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ShutdownReady",known); + return known; +} + +void XE::finishDisplay13TCShutdown(void *controller) { + auto self=callbackXE;auto &c=display13FBPower; + if(!c.lock || !self->display13FBTCHooksReady)return; + IOLockLock(c.lock); + if(controller!=c.controller || !c.tcSupported || !c.bridge.begun || c.bridge.state.suspended) {IOLockUnlock(c.lock);return;} + // Native mode0 resets the port outside the owner lock. Serialize against + // hotplug on the same workloop, using the existing nonblocking gate API. + auto gate=c.workLoop;bool acquired=false; + bool gated=gate && Display13FBMemory::field(controller,0xE00)==gate && + self->adlnWorkLoopInGate && self->adlnWorkLoopTryClose && self->adlnWorkLoopOpen; + if(gated && !reinterpret_cast(self->adlnWorkLoopInGate)(gate)) + gated=acquired=reinterpret_cast(self->adlnWorkLoopTryClose)(gate); + uint32_t record[8] {1,static_cast(gated),0,0,0,0,0,0}; + if(gated)for(unsigned tc=0;tc<4;++tc) { + auto port=c.tcObject[tc]; + // Other framebuffers may not have shut down yet. Their live references + // remain intact; a later hwShutdown handles them after link/PLL stop. + if(!port || c.tcBusy[tc] || c.tcSignalReady[tc] || + c.bridge.nativeDDIRefs(tc+3) || c.bridge.nativeAUXRefs(tc+3))continue; + port->retain();record[2]|=1U<release();IOLockLock(c.lock); + record[4+tc]=result;if(!result)record[3]|=1U<display13ShutdownTrace=(record[1]<<1)|(record[2]<<8)|(record[3]<<16); + IOLockUnlock(c.lock); + if(acquired)reinterpret_cast(self->adlnWorkLoopOpen)(gate); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCShutdown",record,sizeof(record)); +} + +uint32_t XE::wrapDisplay13Shutdown(void *controller,void *fb) { + ShutdownObserver::framebuffer(false,controller,fb,0,snapshotDisplay13Shutdown); + const auto result=FunctionCast(wrapDisplay13Shutdown,callbackXE->orgDisplay13Shutdown)(controller,fb); + // hwShutdown is called by the donor only for system power-off/restart. + // A disconnected sink's hotplug path already does this; a connected sink + // still needs mode0 after its scanout/link stop, even while another FB lives. + if(!result)finishDisplay13TCShutdown(controller); + ShutdownObserver::framebuffer(true,controller,fb,result,snapshotDisplay13Shutdown); + return result; +} + void XE::snapshotDisplay13Shutdown(void *controller,void *fb,uint32_t *out) { // Diagnostics must neither enter a gated native call nor wait behind the // shutdown path. No MMIO, power transitions, or register serializers here. auto &c=display13FBPower; - out[1]=1;out[2]=UINT32_MAX; + out[1]=2;out[2]=UINT32_MAX; if(!c.lock || !IOLockTryLock(c.lock))return; if(controller!=c.controller || !fb || !c.tcSupported) {IOLockUnlock(c.lock);return;} out[2]=Display13FBMemory::field(fb,0x1DC); - out[4]=1;out[5]=c.clockFaulted;out[6]=c.tcBankFaulted;out[7]=c.bridge.state.faulted; + out[4]=1|(c.bridge.state.suspended?4U:0U)|callbackXE->display13ShutdownTrace; + out[5]=c.clockFaulted;out[6]=c.tcBankFaulted;out[7]=c.bridge.state.faulted; for(unsigned tc=0;tc<4;++tc) { auto &m=c.tcMode[tc];auto port=c.tcObject[tc];auto row=out+8+10*tc; row[0]=port?Display13FBMemory::field(port,0x550):UINT32_MAX; @@ -11911,6 +11974,8 @@ void XE::snapshotDisplay13Shutdown(void *controller,void *fb,uint32_t *out) { IOLockUnlock(c.lock); } +// One entry owns donor ABI/lifecycle repairs for every hardware backend. +// Backend installers cannot independently opt into or replace these hooks. bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(framebufferDonorAttempted)return framebufferBackendInstalled; framebufferDonorAttempted=true; @@ -11923,7 +11988,7 @@ bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_addr const bool routed=patcher.routeMultiple(index,&gate,1,address,size);patcher.clearError(); if(!routed)return false; if(display13FramebufferRequested) - ShutdownObserver::installFramebuffer(patcher,index,address,size,snapshotDisplay13Shutdown); + processDisplay13Shutdown(patcher,index,address,size); framebufferDonorCommonReady=processFramebufferCommon(patcher,index,address,size); // Install hardware routes even after a common failure so their diagnostics // remain available. The common start gate never enters native startup then. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 8db52553..258a43bd 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -761,6 +761,11 @@ class XE { mach_vm_address_t orgFramebufferStart {},orgFramebufferInit {},orgFramebufferComputeLanes {},framebufferLaneCaller {}; __attribute__((noinline)) bool processFramebufferKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); static void snapshotDisplay13Shutdown(void *,void *,uint32_t *); + bool processDisplay13Shutdown(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static uint32_t wrapDisplay13Shutdown(void *,void *); + static void finishDisplay13TCShutdown(void *); + mach_vm_address_t orgDisplay13Shutdown {},display13ShutdownPowerCaller {}; + uint32_t display13ShutdownTrace {}; __attribute__((noinline)) bool processFramebufferCommon(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool framebufferTrainingHooksReady {}; bool framebufferStartInProgress {}; diff --git a/tests/display13_shutdown.py b/tests/display13_shutdown.py new file mode 100644 index 00000000..8ee0c5b8 --- /dev/null +++ b/tests/display13_shutdown.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Exercise the production shutdown adapter without powering hardware.""" +from pathlib import Path +import re,subprocess,tempfile,sys +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +body='\n'.join(re.search(r'(?:void|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S)[0] for n in ['finishDisplay13TCShutdown','wrapDisplay13Shutdown']) +code=r''' +#include +#include +#include +#include +struct Lock{bool held=false;}; +void IOLockLock(Lock*l){assert(!l->held);l->held=true;}void IOLockUnlock(Lock*l){assert(l->held);l->held=false;} +struct IOWorkLoop{bool held=false,available=true;}; +bool inGate(const IOWorkLoop*g){return g->held;} +bool tryGate(IOWorkLoop*g){if(!g->available)return false;g->held=true;return true;} +void openGate(IOWorkLoop*g){assert(g->held);g->held=false;} +struct Port{unsigned id,refs=1;void retain(){++refs;}void release(){assert(refs>1);--refs;}}; +struct Bridge{bool begun=true;struct{bool suspended=false;}state;unsigned ddi[7]{},aux[7]{}; + unsigned nativeDDIRefs(unsigned d){return ddi[d];}unsigned nativeAUXRefs(unsigned d){return aux[d];}}; +struct {Lock*lock;void*controller;IOWorkLoop*workLoop;bool tcSupported=true;Bridge bridge;Port*tcObject[4]{};unsigned tcBusy[4]{};bool tcSignalReady[4]{};}display13FBPower; +namespace Display13FBMemory{templateT field(void*p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}} +uint32_t published[8]{};struct IORegistryEntry{static IORegistryEntry*getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,const void*p,size_t n){assert(n==sizeof(published));memcpy(published,p,n);}}; +std::vector events;unsigned modeCalls=0,nativeCalls=0;uint32_t nativeResult=0,modeResult=0;bool shutdownSuspends=false; +namespace ShutdownObserver{void framebuffer(bool done,void*,void*,uint32_t,void(*)(void*,void*,uint32_t*)){events.push_back(done?4:1);}} +#define FunctionCast(fn,p) reinterpret_cast(p) +struct XE{bool display13FBTCHooksReady=true;uintptr_t orgDisplay13Shutdown;uint32_t display13ShutdownTrace=0; + uintptr_t adlnWorkLoopInGate=(uintptr_t)inGate,adlnWorkLoopTryClose=(uintptr_t)tryGate,adlnWorkLoopOpen=(uintptr_t)openGate; + static void finishDisplay13TCShutdown(void*);static uint32_t wrapDisplay13Shutdown(void*,void*); + static void snapshotDisplay13Shutdown(void*,void*,uint32_t*){} + static uint32_t wrapDisplay13TCMode(Port*p,uint32_t mode){auto &c=display13FBPower;assert(!c.lock->held && c.workLoop->held && mode==0 && p->refs==2 && !c.bridge.state.suspended);++modeCalls;events.push_back(3);if(!modeResult)c.tcObject[p->id]=nullptr;return modeResult;} +};XE*callbackXE; +FUNCTIONS +uint32_t nativeShutdown(void*c,void*){++nativeCalls;events.push_back(2); + if(shutdownSuspends){XE::finishDisplay13TCShutdown(c);display13FBPower.bridge.state.suspended=true;} + return nativeResult;} +int main(){ + for(unsigned test=0;test<12;++test){Lock lock;IOWorkLoop gate;char controller[0xe10]{};auto ptr=&gate;memcpy(controller+0xe00,&ptr,sizeof(ptr)); + display13FBPower={};auto &c=display13FBPower;c.lock=&lock;c.controller=controller;c.workLoop=&gate;c.tcSupported=true;c.bridge.begun=true; + Port idle{1},active{0};c.tcObject[0]=&active;c.tcObject[1]=&idle;c.tcSignalReady[0]=true;c.bridge.ddi[3]=1; + XE x;callbackXE=&x;x.orgDisplay13Shutdown=(uintptr_t)nativeShutdown; + modeCalls=nativeCalls=0;modeResult=nativeResult=0;events.clear();shutdownSuspends=false; + if(test==1)gate.available=false; + if(test==2)c.bridge.state.suspended=true; + if(test==3)c.tcBusy[1]=1; + if(test==4)c.bridge.ddi[4]=1; + if(test==5)c.bridge.aux[4]=1; + if(test==6)c.tcSignalReady[1]=true; + if(test==7)nativeResult=0x1234; + if(test==8)modeResult=0x4567; + if(test==9)gate.held=true; + if(test==10)shutdownSuspends=true; + assert(XE::wrapDisplay13Shutdown(test==11?nullptr:controller,nullptr)==nativeResult); + assert(nativeCalls==1 && events.front()==1 && events.back()==4); + const bool attempted=test==0||test==8||test==9||test==10; + assert(modeCalls==unsigned(attempted));assert(!lock.held && gate.held==(test==9));assert(idle.refs==1&&active.refs==1); + assert(c.tcObject[0]==&active && c.bridge.ddi[3]==1); + assert((c.tcObject[1]==nullptr)==(attempted&&test!=8)); + if(test==0){XE::wrapDisplay13Shutdown(controller,nullptr);assert(modeCalls==1);c.tcSignalReady[0]=false;c.bridge.ddi[3]=0;XE::wrapDisplay13Shutdown(controller,nullptr);assert(modeCalls==2&&!c.tcObject[0]);} + } +} +'''.replace('FUNCTIONS',body) +with tempfile.TemporaryDirectory(prefix='wex-shutdown-') as tmp: + p=Path(tmp);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) +override=re.search(r'void XE::wrapAlderLakeNPowerOverride\(.*?\n\}',s,re.S)[0] +assert override.index('display13ShutdownPowerCaller')1: + b=Path(sys.argv[1]).read_bytes() + install=re.search(r'bool XE::processDisplay13Shutdown\(.*?\n\}',s,re.S)[0] + for name,offset in [('entry',0x5173a),('powerCall',0x51d24)]: + raw=bytes(int(v,0) for v in re.search(r'const uint8_t '+name+r'\[\]=\{([^}]+)',install)[1].split(',')) + assert b[offset:offset+len(raw)]==raw + assert b[0x339b8:0x339d3]==bytes.fromhex('48ff05a96f0a00b8100300e04939c6740ab8500200e04939c67570') + assert b[0x339e4]==0xe8 and 0x339e9+int.from_bytes(b[0x339e5:0x339e9],'little',signed=True)==0x5173a + assert 0x51d29+int.from_bytes(b[0x51d25:0x51d29],'little',signed=True)==0x5b464 +print('PASS shutdown mode0: live-port exclusion, gate contention, native errors, retry/idempotence, cleanup before power suspend, donor power-off/restart callsites') diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index 51eb184b..c30a1bed 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -60,10 +60,7 @@ }; bool tglInstallSonomaSurfaceABI(KernelPatcher&,size_t,uintptr_t,size_t,bool){return surfaceOK;} bool xelpInstallNVRAMABI(KernelPatcher&,size_t,uintptr_t,size_t){return nvramOK;} -namespace ShutdownObserver { -unsigned calls=0; -void installFramebuffer(KernelPatcher&,size_t,uintptr_t,size_t,void(*)(void*,void*,uint32_t*)){++calls;} -} +unsigned shutdownCalls=0; struct XE { bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; bool display13FramebufferRequested=false,tglPlatformNVRAMReady=false,tglProbeReady=false,tglFramebufferInitReady=false; @@ -73,7 +70,7 @@ uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0;bool stopDuringStart=false,nativeOK=true; bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_t); - static void snapshotDisplay13Shutdown(void*,void*,uint32_t*){} + bool processDisplay13Shutdown(KernelPatcher&,size_t,uintptr_t,size_t){++shutdownCalls;return true;} bool processFramebufferCommon(KernelPatcher&,size_t,uintptr_t,size_t); bool processFramebufferDonorRuntime(KernelPatcher&,size_t,uintptr_t,size_t){++commonRuntimeCalls;return runtimeOK;} bool processFramebufferAGDC(KernelPatcher&,size_t,uintptr_t,size_t){return agdcOK;} @@ -103,13 +100,13 @@ else memcpy(image.data()+span.first,span.second.data(),span.second.size()); } for(bool display13:{false,true})for(unsigned failure=0;failure<14;++failure){ - ShutdownObserver::calls=0; + shutdownCalls=0; auto b=image;uintptr_t pure=0x1234;memcpy(b.data()+0xccad0+0x188,&pure,sizeof(pure)); XE x;callbackXE=&x;x.display13FramebufferRequested=display13;KernelPatcher p; surfaceOK=failure!=5;nvramOK=failure!=6;x.runtimeOK=failure!=7;x.agdcOK=failure!=8;x.backendOK=failure!=9;x.trainingOK=failure!=10;x.stopDuringStart=failure==11;x.nativeOK=failure!=12; if(failure>=1&&failure<=4)p.failAt=failure; bool handled=x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); - assert(ShutdownObserver::calls==(display13 && failure!=1?1U:0U)); + assert(shutdownCalls==(display13 && failure!=1?1U:0U)); assert(handled==(failure!=1)); IOService c; if(failure==13){XELPComboClock::ClockSnapshot clocks;clocks.layout=XELPComboClock::Layout::Display13; @@ -124,7 +121,7 @@ assert(XE::wrapFramebufferStart(&c,&c) && x.startCalls==1);} const auto calls=p.attempts,backends=x.backendCalls; x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); - assert(ShutdownObserver::calls==(display13 && failure!=1?1U:0U)); + assert(shutdownCalls==(display13 && failure!=1?1U:0U)); assert(p.attempts==calls&&x.backendCalls==backends); // no second trampoline } puts("PASS common-before-backend order, single ownership, partial failures and mandatory native-start gate"); diff --git a/tools/test.py b/tools/test.py index 47d9408e..21a4ffaf 100644 --- a/tools/test.py +++ b/tools/test.py @@ -10,8 +10,10 @@ python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'display13_panel_runtime', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_cursor_resize','dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] +python_tests += ['display13_shutdown'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] if a.donor:jobs += [('display13_agdc_native',[sys.executable,str(R/'tests/display13_agdc.py'),str(a.donor.resolve())],None)] +if a.donor:jobs += [('display13_shutdown_native',[sys.executable,str(R/'tests/display13_shutdown.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_update_dispatch_native',[sys.executable,str(R/'tests/dsi_update_dispatch.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_path_disposal_native',[sys.executable,str(R/'tests/dsi_path_disposal.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('dsi_surface_native',[sys.executable,str(R/'tests/dsi_surface.py'),str(a.donor.resolve())],None)] From f0a0fae346260472feaaee5ad6edaf7537dc7fbe Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:27:25 +0900 Subject: [PATCH 121/139] Document connected USB-C shutdown support --- CHANGELOG.md | 2 ++ README.md | 2 +- README_ja.md | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd5d629..6863038b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Fix system power-off hangs with a USB-C display connected on Alder Lake-class GPUs by releasing idle Type-C PHY ownership during shutdown. + - Fix display freezes during hardware cursor size changes by preparing watermarks for the requested size before the native control update. - Remove arbitrary MIPI DSI mode-list limits, forward ordinary OS attributes to the native driver, and accept firmware panel orientation metadata without changing physical link timing. diff --git a/README.md b/README.md index 0d562886..114917a7 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ not been verified with the fixed TC DP path. Experimental USB-C DP Alt Mode supports VBT-described external TC1–TC4 DP wiring with matching AUX. It reuses SST link training with Type-C ownership and the detected physical lane assignment. MST, Thunderbolt, cross-routed AUX and USB-C boot-display adoption remain unsupported. -Alder Lake-N USB-C output has been tested at 1080p60 through an HDMI adapter on Big Sur, including Metal and OpenGL acceleration. USB-C hotplug, sleep/wake and other adapter types have not been verified. +Alder Lake-N USB-C output has been tested at 1080p60 through an HDMI adapter on Big Sur, including Metal and OpenGL acceleration. System power-off with a USB-C display connected has also been tested on Big Sur. USB-C hotplug, sleep/wake and other adapter types have not been verified. ADL-P/RPL-P uses its physical ID and detected GPU resources automatically. No device-ID override or machine-specific AGDP kext is required. diff --git a/README_ja.md b/README_ja.md index 472120ba..ee54e81a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -98,7 +98,7 @@ Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPort Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uでは、Combo A/B・固定TC1〜TC4 HDMI配線をVBTから選択します。試験的な固定TC1〜TC4 SST DisplayPortには、VBTのAUX配線の一致が必要です。Alder Lake-NのHDMI明示propertyはCombo配線にのみ適用します。 -ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と、これらのファミリーの他のGPUは未検証です。試験的なUSB-C DP Alt Modeは、AUXが一致するVBTの外部TC1〜TC4 DP配線に対応します。既存SSTリンク訓練にType-C所有権と検出した物理レーン割当を組み合わせます。MST、Thunderbolt、交差AUX配線、USB-Cを起動用画面とする引継ぎは未対応です。Alder Lake-NではBig SurでHDMI変換器経由のUSB-C 1080p60表示とMetal/OpenGL加速を確認しています。USB-Cの抜き差し・スリープ復帰・他の変換器は未検証です。 +ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と、これらのファミリーの他のGPUは未検証です。試験的なUSB-C DP Alt Modeは、AUXが一致するVBTの外部TC1〜TC4 DP配線に対応します。既存SSTリンク訓練にType-C所有権と検出した物理レーン割当を組み合わせます。MST、Thunderbolt、交差AUX配線、USB-Cを起動用画面とする引継ぎは未対応です。Alder Lake-NではBig SurでHDMI変換器経由のUSB-C 1080p60表示とMetal/OpenGL加速を確認しています。USB-C映像を接続したままの通常シャットダウンによる電源断もBig Surで確認しています。USB-Cの抜き差し・スリープ復帰・他の変換器は未検証です。 ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。device-ID上書きや機種別AGDP kextは不要です。`-wex-noaccel`でFramebufferのみの動作を維持します。 From 69f7592a8293771f6db081f8ae9013f8313d4543 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:56:24 +0900 Subject: [PATCH 122/139] Observe Display12 TC state across native shutdown without altering teardown --- WhateverXe/kern_shutdown_observer.cpp | 34 +++++++++++++-- WhateverXe/kern_shutdown_observer.hpp | 1 + WhateverXe/kern_shutdown_tc_observation.hpp | 32 ++++++++++++++ WhateverXe/kern_xe.cpp | 43 ++++++++++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/display12_shutdown_observer.py | 48 +++++++++++++++++++++ tests/framebuffer_common.py | 2 + tests/tgl_mode_native_fixture.cpp | 1 + tests/tgl_mode_serial_fixture.cpp | 1 + tools/test.py | 2 +- 10 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 WhateverXe/kern_shutdown_tc_observation.hpp create mode 100644 tests/display12_shutdown_observer.py diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp index 2ff0cc3c..71f2a702 100644 --- a/WhateverXe/kern_shutdown_observer.cpp +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -1,6 +1,7 @@ // Optional, explicitly armed diagnostics for the qualified Big Sur kernel. // No timeout, panic, power-policy or shutdown-return-value changes. #include "kern_shutdown_observer.hpp" +#include "kern_shutdown_tc_observation.hpp" #include #include #include @@ -29,6 +30,8 @@ static OSData *snapshots[2]; static const OSSymbol *traceSymbol; static IOLock *lock; static bool armed; +static DisplaySnapshot display12Snapshot; +static uint32_t (*orgDisplay12Shutdown)(void *,void *); static uint64_t ready[8] {}; static void (*syncNvram)(IODTNVRAM *,bool); static int (*orgReboot)(int,char *); @@ -57,8 +60,8 @@ static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,con e.code=code;e.detail=detail;e.time=mach_absolute_time();e.pc=reinterpret_cast(pc); if(label)strlcpy(e.label,label,sizeof(e.label)); ++journal.count; - if(display && (code==70 || code==71)) { - auto dst=journal.display[code-70];memcpy(dst,display,sizeof(journal.display[0])); + if(display && (code==70 || code==71 || code==72)) { + auto dst=journal.display[code==71?1:0];memcpy(dst,display,sizeof(journal.display[0])); dst[0]=journal.count;dst[3]=detail; } auto data=snapshots[journal.count%2]; @@ -82,7 +85,9 @@ static int armControl SYSCTL_HANDLER_ARGS { auto entry=IORegistryEntry::fromPath("IODeviceTree:/options"); nvram=OSDynamicCast(IODTNVRAM,entry); if(!nvram){if(entry)entry->release();ready[6]=2;publish();return ENXIO;} - armed=true;record(1,0,"probe");publish(); + armed=true;record(1,0,"probe"); + if(display12Snapshot){uint32_t state[48] {};display12Snapshot(nullptr,nullptr,state);record(72,0,"display preflight",nullptr,state);} + publish(); return journal.failed ? EIO : 0; } SYSCTL_PROC(_debug, OID_AUTO, wex_shutdown_arm, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED, @@ -96,6 +101,29 @@ void framebuffer(bool returned,void *controller,void *fb,uint32_t result,Display uint32_t state[48] {};snapshot(controller,fb,state); record(returned?71:70,result,"hwShutdown",nullptr,state); } +static uint32_t wrapDisplay12Shutdown(void *controller,void *fb) { + framebuffer(false,controller,fb,0,display12Snapshot); + const auto result=orgDisplay12Shutdown(controller,fb); + framebuffer(true,controller,fb,result,display12Snapshot); + return result; +} +void installDisplay12(KernelPatcher &p,size_t index,mach_vm_address_t address,size_t size,DisplaySnapshot snapshot) { + // Only the explicitly requested, exact-kernel observer installs this hook. + if(ready[2]!=63 || !snapshot)return; + const char *symbol="__ZN31AppleIntelFramebufferController10hwShutdownEP21AppleIntelFramebuffer"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; + const auto target=p.solveSymbol(index,symbol,address,size);p.clearError(); + const auto delta=static_cast(reinterpret_cast(wrapDisplay12Shutdown)-(target+5)); + bool known=size>0x51d4b && target==address+0x5173a && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && delta==static_cast(delta); + if(known) { + mach_vm_address_t original=0; + KernelPatcher::RouteRequest route(symbol,wrapDisplay12Shutdown,original); + known=p.routeMultipleShort(index,&route,1,address,size);p.clearError(); + if(known){orgDisplay12Shutdown=reinterpret_cast(original);display12Snapshot=snapshot;ShutdownTCObservation::state().enabled=true;} + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownDisplay12Observer",known); +} static int wrapPEHalt(unsigned int type,uint32_t details) { if(type==0 || type==1 || type==3)record(50,type);int v=orgPEHalt(type,details);if(type==0 || type==1 || type==3)record(51,type);return v; } static void wrapHaltLog(const char *what,const void *pc,uint64_t time) { orgHaltLog(what,pc,time); diff --git a/WhateverXe/kern_shutdown_observer.hpp b/WhateverXe/kern_shutdown_observer.hpp index 4e36224d..3fd869a2 100644 --- a/WhateverXe/kern_shutdown_observer.hpp +++ b/WhateverXe/kern_shutdown_observer.hpp @@ -3,5 +3,6 @@ namespace ShutdownObserver { void install(KernelPatcher &patcher); using DisplaySnapshot = void (*)(void *controller, void *fb, uint32_t *words); +void installDisplay12(KernelPatcher &,size_t,mach_vm_address_t,size_t,DisplaySnapshot); void framebuffer(bool returned,void *controller,void *fb,uint32_t result,DisplaySnapshot); } diff --git a/WhateverXe/kern_shutdown_tc_observation.hpp b/WhateverXe/kern_shutdown_tc_observation.hpp new file mode 100644 index 00000000..d15e11a8 --- /dev/null +++ b/WhateverXe/kern_shutdown_tc_observation.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +// Passive copies of accesses the driver already performs. Never reads MMIO, +// changes a returned value, acquires a driver lock, or persists from an accessor. +namespace ShutdownTCObservation { +struct State { + bool enabled {}; + uint32_t sequence {}, mode0[4] {}, modeResult[4] {}; + uint64_t reads[4] {}, writes[4] {}; // FIA1..3 ownership, FIA1 topology +}; +inline State &state() {static State s;return s;} +inline unsigned index(uint32_t reg) { + if(reg==0x1638A0)return 3; + if(reg>=0x163894 && reg<=0x163A94 && !((reg-0x163894)%0x100))return (reg-0x163894)/0x100; + return 4; +} +inline void record(uint32_t reg,uint32_t value,bool write) { + auto &s=state();if(!s.enabled)return; + const auto i=index(reg);if(i==4)return; + const auto seq=__atomic_add_fetch(&s.sequence,1,__ATOMIC_RELAXED); + __atomic_store_n((write?s.writes:s.reads)+i,(static_cast(seq)<<32)|value,__ATOMIC_RELEASE); +} +inline uint64_t sample(uint32_t reg,bool write) { + const auto i=index(reg);if(i==4)return 0; + auto &s=state();return __atomic_load_n((write?s.writes:s.reads)+i,__ATOMIC_ACQUIRE); +} +inline void mode(uint32_t ddi,uint32_t wanted,uint32_t result) { + auto &s=state();if(!s.enabled || ddi<3 || ddi>6)return; + if(!wanted)__atomic_add_fetch(s.mode0+ddi-3,1,__ATOMIC_RELAXED); + __atomic_store_n(s.modeResult+ddi-3,result,__ATOMIC_RELEASE); +} +} diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 32cfc088..993b5856 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -12,6 +12,7 @@ #include "kern_dsi_native_mode.hpp" #include "kern_xe.hpp" #include "kern_shutdown_observer.hpp" +#include "kern_shutdown_tc_observation.hpp" #include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" #include "kern_alderlake_n.hpp" @@ -11989,6 +11990,8 @@ bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_addr if(!routed)return false; if(display13FramebufferRequested) processDisplay13Shutdown(patcher,index,address,size); + else + ShutdownObserver::installDisplay12(patcher,index,address,size,snapshotDisplay12Shutdown); framebufferDonorCommonReady=processFramebufferCommon(patcher,index,address,size); // Install hardware routes even after a common failure so their diagnostics // remain available. The common start gate never enters native startup then. @@ -16400,6 +16403,7 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { IORegistryEntry::getRegistryRoot()->setProperty("TGLModeResultCorrection",record,sizeof(record)); } + ShutdownTCObservation::mode(traceDDI,mode,result); tglAUXRecord(port, 1, true, result, mode, 0); tglConnectionRecord(port, 2, true, result, mode, caller); auto bytes = static_cast(port); @@ -16660,6 +16664,43 @@ void XE::tglBeginTCColdProtection(void *controller) { } } +void XE::snapshotDisplay12Shutdown(void *controller,void *fb,uint32_t *out) { + // The native hwShutdown call owns these live objects; the preflight uses + // the already-running controller. getPortByDDI is a qualified memory-only + // table lookup. No register reads, native power calls or blocking locks here. + auto self=callbackXE;auto &f=self->tglTCNativeFunctions;auto &cold=tglTCBootstrap; + out[1]=3;out[2]=UINT32_MAX; + if(!controller)controller=self->tglObservationController; + if(!controller || !f.complete() || *f.controllerSlot!=controller)return; + auto power=TGLTCNativeBinding::field(controller,0xC8); + if(!power || TGLTCNativeBinding::field(power,0x10)!=controller)return; + out[4]=1;out[2]=fb?TGLTCNativeBinding::field(fb,0x1DC):UINT32_MAX; + out[5]=out[6]=out[7]=UINT32_MAX; + if(cold.controller==controller && cold.lock && IOLockTryLock(cold.lock)) { + out[4]|=2;out[5]=static_cast(cold.cold.phase);out[6]=cold.cold.total;out[7]=cold.cold.bootstrapHeld; + IOLockUnlock(cold.lock); + } + const auto topology=ShutdownTCObservation::sample(0x1638A0,false); + for(unsigned tc=0;tc<4;++tc) { + auto row=out+8+10*tc;const unsigned ddi=tc+3; + for(unsigned i=0;i<10;++i)row[i]=UINT32_MAX; + auto port=f.portByDDI(ddi); + if(port)row[0]=TGLTCNativeBinding::field(port,0x550); + row[1]=TGLTCNativeBinding::field(power,0x54+4*ddi); + row[2]=self->tglAUXParentRefs[ddi];row[3]=UINT32_MAX; + TGLTypeCPort coords;TGLFiaPort fia; + if((topology>>32) && TGLTypeCPort::resolve(ddi,coords) && coords.resolveFia(static_cast(topology),fia)) { + const auto read=ShutdownTCObservation::sample(fia.owned,false),write=ShutdownTCObservation::sample(fia.owned,true); + row[3]=fia.readyMask; + row[4]=static_cast(read);row[5]=static_cast(write); + row[6]=read>>32;row[7]=write>>32; + } + auto &observation=ShutdownTCObservation::state(); + row[8]=__atomic_load_n(observation.mode0+tc,__ATOMIC_RELAXED); + row[9]=__atomic_load_n(observation.modeResult+tc,__ATOMIC_ACQUIRE); + } +} + // Runs synchronously inside native controller start, after native PowerWell // accounting init and before display-path allocation/boot-display setup. static TGLBootReadout::Result tglBootReadout; @@ -17069,6 +17110,7 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { const bool trace=tglTransitionRegister(reg); if(trace)tglTransitionRecord(35,false,access,reg,tglConnectionCaller(__builtin_return_address(0)),0); auto value = FunctionCast(wrapTGLRegisterRead, callbackXE->orgTGLRegisterRead)(access, reg); + ShutdownTCObservation::record(static_cast(reg),value,false); if(trace)tglTransitionRecord(35,true,access,reg,tglConnectionCaller(__builtin_return_address(0)),value); const auto caller = reinterpret_cast(__builtin_return_address(0)); auto s = callbackXE; @@ -18992,6 +19034,7 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { const bool trace=tglTransitionRegister(reg); if(trace)tglTransitionRecord(36,false,access,reg,tglConnectionCaller(__builtin_return_address(0)),value); FunctionCast(wrapTGLRegisterWrite, callbackXE->orgTGLRegisterWrite)(access, reg, value); + ShutdownTCObservation::record(static_cast(reg),value,true); // Sample PWM only at a native duty write while its caller owns power. // IORegistry reads consume this receipt; they never touch live MMIO. if(callbackXE->tglActive && reg==0xC8258 && callbackXE->orgTGLRegisterRead) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 258a43bd..15f8855f 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -760,6 +760,7 @@ class XE { bool framebufferDonorAttempted {},framebufferDonorCommonReady {},framebufferBackendInstalled {}; mach_vm_address_t orgFramebufferStart {},orgFramebufferInit {},orgFramebufferComputeLanes {},framebufferLaneCaller {}; __attribute__((noinline)) bool processFramebufferKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static void snapshotDisplay12Shutdown(void *,void *,uint32_t *); static void snapshotDisplay13Shutdown(void *,void *,uint32_t *); bool processDisplay13Shutdown(KernelPatcher &,size_t,mach_vm_address_t,size_t); static uint32_t wrapDisplay13Shutdown(void *,void *); diff --git a/tests/display12_shutdown_observer.py b/tests/display12_shutdown_observer.py new file mode 100644 index 00000000..c1def9f7 --- /dev/null +++ b/tests/display12_shutdown_observer.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text();o=(r/'WhateverXe/kern_shutdown_observer.cpp').read_text() +snapshot=re.search(r'void XE::snapshotDisplay12Shutdown\(.*?\n\}',s,re.S)[0] +wrapper=re.search(r'static uint32_t wrapDisplay12Shutdown\(.*?\n\}',o,re.S)[0] +code=r''' +#include +#include +#include "kern_shutdown_tc_observation.hpp" +#include "kern_tgl_port_map.hpp" +struct Lock{bool busy=false;};bool IOLockTryLock(Lock*l){if(l->busy)return false;l->busy=true;return true;}void IOLockUnlock(Lock*l){assert(l->busy);l->busy=false;} +struct TGLTCNativeBinding{templatestatic T field(void*p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}}; +struct {void*controller;Lock*lock;struct{unsigned phase=1,total=1;bool bootstrapHeld=true;}cold;}tglTCBootstrap; +struct Functions{bool valid=true;void**controllerSlot;void*(*portByDDI)(unsigned);bool complete(){return valid;}}; +struct XE{Functions tglTCNativeFunctions;void*tglObservationController;unsigned tglAUXParentRefs[9]{};static void snapshotDisplay12Shutdown(void*,void*,uint32_t*);};XE*callbackXE; +SNAPSHOT +using Snapshot=void(*)(void*,void*,uint32_t*);Snapshot display12Snapshot=XE::snapshotDisplay12Shutdown; +unsigned events[2]{},calls=0,nativeCalls=0;uint32_t result=0; +void framebuffer(bool done,void*c,void*f,uint32_t ret,Snapshot fn){events[calls++]=done?ret:0;uint32_t data[48]{};fn(c,f,data);} +uint32_t native(void*,void*){++nativeCalls;return result;}auto orgDisplay12Shutdown=native; +WRAPPER +char ports[4][0x560]{};void*get(unsigned d){return d>=3&&d<7?ports[d-3]:nullptr;} +templatevoid put(void*p,unsigned off,T v){memcpy((char*)p+off,&v,sizeof(v));} +int main(){ + using namespace ShutdownTCObservation;record(0x163894,3,false);mode(3,0,0);assert(sample(0x163894,false)==0&&state().mode0[0]==0); + state().enabled=true;record(0x1234,3,false);assert(state().sequence==0); + record(0x1638a0,0x10,false);record(0x163894,1,false);record(0x163894,0,true); + mode(3,0,0x1234);mode(7,0,0);assert(state().mode0[0]==1); + char c[0xc50]{},power[0x90]{},fb[0x200]{};void*cp=c;put(c,0xc8,(void*)power);put(power,0x10,cp);put(fb,0x1dc,1U); + for(unsigned i=0;i<4;++i){put(ports[i],0x550,3U);put(power,0x54+4*(i+3),i+1);} + XE x;callbackXE=&x;x.tglObservationController=c;x.tglTCNativeFunctions={true,&cp,get};Lock lock;tglTCBootstrap.controller=c;tglTCBootstrap.lock=&lock; + uint32_t out[48]{};XE::snapshotDisplay12Shutdown(c,fb,out);assert(out[1]==3&&out[2]==1&&out[4]==3&&out[6]==1&&out[7]==1); + assert(out[8]==3&&out[9]==1&&out[11]==1&&out[12]==1&&out[13]==0&&out[14]==2&&out[15]==3&&out[16]==1&&out[17]==0x1234); + // No hardware access exists in the sampler; a busy cold lock is not waited on. + lock.busy=true;memset(out,0,sizeof(out));XE::snapshotDisplay12Shutdown(nullptr,nullptr,out);assert(out[4]==1&&out[5]==UINT32_MAX&&lock.busy); + for(unsigned n=0;n<2;++n){calls=nativeCalls=0;result=n?0xdead:0;assert(wrapDisplay12Shutdown(c,fb)==result&&nativeCalls==1&&calls==2&&events[1]==result);} + cp=nullptr;memset(out,0,sizeof(out));XE::snapshotDisplay12Shutdown(c,fb,out);assert(out[4]==0); +} +'''.replace('SNAPSHOT',snapshot).replace('WRAPPER',wrapper) +with tempfile.TemporaryDirectory() as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++14','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) +assert 'ready[2]!=63' in o and 'record(72,0,"display preflight"' in o +assert 'finishDisplay13TCShutdown' not in wrapper and 'record(' not in snapshot +print('PASS Display12 passive cache, memory-only sampler, busy-lock bypass, original shutdown result/call count') diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index c30a1bed..84a55f3a 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -60,6 +60,7 @@ }; bool tglInstallSonomaSurfaceABI(KernelPatcher&,size_t,uintptr_t,size_t,bool){return surfaceOK;} bool xelpInstallNVRAMABI(KernelPatcher&,size_t,uintptr_t,size_t){return nvramOK;} +namespace ShutdownObserver {templatevoid installDisplay12(T...) {}} unsigned shutdownCalls=0; struct XE { bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; @@ -70,6 +71,7 @@ uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0;bool stopDuringStart=false,nativeOK=true; bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_t); + static void snapshotDisplay12Shutdown(void*,void*,uint32_t*){} bool processDisplay13Shutdown(KernelPatcher&,size_t,uintptr_t,size_t){++shutdownCalls;return true;} bool processFramebufferCommon(KernelPatcher&,size_t,uintptr_t,size_t); bool processFramebufferDonorRuntime(KernelPatcher&,size_t,uintptr_t,size_t){++commonRuntimeCalls;return runtimeOK;} diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index abfe1e03..fe319263 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_shutdown_tc_observation.hpp" #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index e365996d..824c63d8 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_shutdown_tc_observation.hpp" #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" diff --git a/tools/test.py b/tools/test.py index 21a4ffaf..ca86ed6c 100644 --- a/tools/test.py +++ b/tools/test.py @@ -10,7 +10,7 @@ python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'display13_panel_runtime', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_cursor_resize','dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] -python_tests += ['display13_shutdown'] +python_tests += ['display13_shutdown','display12_shutdown_observer'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] if a.donor:jobs += [('display13_agdc_native',[sys.executable,str(R/'tests/display13_agdc.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('display13_shutdown_native',[sys.executable,str(R/'tests/display13_shutdown.py'),str(a.donor.resolve())],None)] From 8bc0ce6a2c8db2b38e20e1f2d30f19d2457e7c9a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:01:30 +0900 Subject: [PATCH 123/139] Share passive shutdown observation storage across kernel translation units --- WhateverXe/kern_shutdown_observer.cpp | 1 + WhateverXe/kern_shutdown_tc_observation.hpp | 4 +++- tests/display12_shutdown_observer.py | 12 ++++++++++++ tests/tgl_mode_native_fixture.cpp | 1 + tests/tgl_mode_serial_fixture.cpp | 1 + 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp index 71f2a702..dee8f32e 100644 --- a/WhateverXe/kern_shutdown_observer.cpp +++ b/WhateverXe/kern_shutdown_observer.cpp @@ -14,6 +14,7 @@ #include #include #include +namespace ShutdownTCObservation { State storage; } namespace ShutdownObserver { static const char *traceKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-trace"; struct Event { uint32_t code, detail; uint64_t time, pc; char label[24]; }; diff --git a/WhateverXe/kern_shutdown_tc_observation.hpp b/WhateverXe/kern_shutdown_tc_observation.hpp index d15e11a8..633de68c 100644 --- a/WhateverXe/kern_shutdown_tc_observation.hpp +++ b/WhateverXe/kern_shutdown_tc_observation.hpp @@ -8,7 +8,9 @@ struct State { uint32_t sequence {}, mode0[4] {}, modeResult[4] {}; uint64_t reads[4] {}, writes[4] {}; // FIA1..3 ownership, FIA1 topology }; -inline State &state() {static State s;return s;} +// -fapple-kext does not coalesce inline-function local statics across TUs. +extern State storage; +inline State &state() {return storage;} inline unsigned index(uint32_t reg) { if(reg==0x1638A0)return 3; if(reg>=0x163894 && reg<=0x163A94 && !((reg-0x163894)%0x100))return (reg-0x163894)/0x100; diff --git a/tests/display12_shutdown_observer.py b/tests/display12_shutdown_observer.py index c1def9f7..13919f1d 100644 --- a/tests/display12_shutdown_observer.py +++ b/tests/display12_shutdown_observer.py @@ -9,6 +9,7 @@ #include #include #include "kern_shutdown_tc_observation.hpp" +namespace ShutdownTCObservation {State storage;} #include "kern_tgl_port_map.hpp" struct Lock{bool busy=false;};bool IOLockTryLock(Lock*l){if(l->busy)return false;l->busy=true;return true;}void IOLockUnlock(Lock*l){assert(l->busy);l->busy=false;} struct TGLTCNativeBinding{templatestatic T field(void*p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}}; @@ -43,6 +44,17 @@ d=Path(d);(d/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++14','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) subprocess.run([str(d/'test')],check=True) +# Match the kernel's separate-translation-unit compilation model. An inline +# local-static singleton is TU-local with -fapple-kext, unlike the userland test. +with tempfile.TemporaryDirectory() as tmp: + d=Path(tmp) + (d/'writer.cpp').write_text('#include "kern_shutdown_tc_observation.hpp"\nnamespace ShutdownTCObservation {State storage;}\nvoid enable(){ShutdownTCObservation::state().enabled=true;}') + (d/'reader.cpp').write_text('#include "kern_shutdown_tc_observation.hpp"\nvoid observe(){ShutdownTCObservation::record(0x163894,1,false);}') + (d/'main.cpp').write_text('#include \n#include "kern_shutdown_tc_observation.hpp"\nvoid enable();void observe();extern "C" int main(){enable();observe();assert(ShutdownTCObservation::sample(0x163894,false)==0x100000001ULL);return 0;}') + for name in ['writer','reader','main']: + subprocess.run(['clang++','-std=c++17','-O2','-fapple-kext','-fno-exceptions','-fno-rtti','-I'+str(r/'WhateverXe'),'-c',str(d/(name+'.cpp')),'-o',str(d/(name+'.o'))],check=True) + subprocess.run(['clang++',*[str(d/(n+'.o')) for n in ['writer','reader','main']],'-o',str(d/'cross-tu')],check=True) + subprocess.run([str(d/'cross-tu')],check=True) assert 'ready[2]!=63' in o and 'record(72,0,"display preflight"' in o assert 'finishDisplay13TCShutdown' not in wrapper and 'record(' not in snapshot print('PASS Display12 passive cache, memory-only sampler, busy-lock bypass, original shutdown result/call count') diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index fe319263..8dd6ab6e 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -1,4 +1,5 @@ #include "kern_shutdown_tc_observation.hpp" +namespace ShutdownTCObservation {State storage;} #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index 824c63d8..5565b14e 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,4 +1,5 @@ #include "kern_shutdown_tc_observation.hpp" +namespace ShutdownTCObservation {State storage;} #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" From 120b26bcdb67ed9b0180f86e212ebd4f23654fff Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 16:15:42 +0900 Subject: [PATCH 124/139] Remove temporary shutdown observers after TC validation --- README.md | 2 - README_ja.md | 2 - WhateverXe/kern_host.cpp | 2 - WhateverXe/kern_shutdown_observer.cpp | 189 -------------------- WhateverXe/kern_shutdown_observer.hpp | 8 - WhateverXe/kern_shutdown_tc_observation.hpp | 34 ---- WhateverXe/kern_xe.cpp | 69 +------ WhateverXe/kern_xe.hpp | 3 - tests/display12_shutdown_observer.py | 60 ------- tests/display13_shutdown.py | 6 +- tests/fixtures/boot_argument_names.json | 3 +- tests/framebuffer_common.py | 2 - tests/host_publication.py | 1 - tests/tgl_mode_native_fixture.cpp | 2 - tests/tgl_mode_serial_fixture.cpp | 2 - tools/test.py | 2 +- 16 files changed, 5 insertions(+), 382 deletions(-) delete mode 100644 WhateverXe/kern_shutdown_observer.cpp delete mode 100644 WhateverXe/kern_shutdown_observer.hpp delete mode 100644 WhateverXe/kern_shutdown_tc_observation.hpp delete mode 100644 tests/display12_shutdown_observer.py diff --git a/README.md b/README.md index 114917a7..20f5e0a4 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,6 @@ The experimental `-wex-dsi-hdmi-test` argument transfers a qualified Display13 D The optional `-wex-dsi-audit` argument checks the supported MIPI DSI firmware layout and pre-native GOP state, including peer engines and the stolen-memory GGTT prefix, then publishes diagnostic summaries. It is disabled by default; the audit does not program the display or enable DSI output. -The optional `-wex-shutdown-observe` argument installs explicitly armed shutdown diagnostics on the qualified Big Sur 20G1443 kernel. It records bounded shutdown milestones in dedicated NVRAM variables for recovery after power-off. It is disabled by default, requires a per-boot diagnostic arm token, and does not change shutdown policy or suppress waits. - The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GOP adoption on a supported panel profile. It preserves the firmware link and exposes a synthetic EDID derived from the panel profile for native path and fixed-mode accelerated desktop output. OS-requested 90°/270° rotation and scaled GPU source modes retain the physical panel timing; orientation and HiDPI mode selection are configured in macOS. OS-provided scaled modes are not restricted to a preferred resolution or a fixed list capacity; available HiDPI sizes depend on the macOS display configuration. Native PWM brightness requests and readback use the existing AppleBacklight interface on supported DSI profiles; brightness zero sets zero PWM duty. With display off/on enabled below, requests made while the panel is off are saved and applied before its backlight is enabled on wake. On macOS Big Sur, the internal HiDPI panel has been tested with up to two external HDMI/USB-C displays, including accelerated rendering and display off/on. System sleep and full lifecycle validation remain under development. Do not combine it with `-wex-dsi-hdmi-test`. The additional `-wex-dsi-panel-only` argument temporarily excludes external outputs from online detection and mode setting during internal-panel development; physical output wiring is unchanged. Omit it to test external coexistence. The optional `-wex-dsi-command-test` runs a single DCS NOP/drain trial during DSI adoption; it does not enable panel power cycling. The optional `-wex-dsi-panel-test` tests one panel power/reset/initialization cycle before native startup while retaining the host clock and PHY; complete host reinitialization remains under development. The additional `-wex-dsi-host-test` includes the panel cycle and restarts the exclusively retained PLL, PHY and DSI host from the panel profile. These bootstrap experiments do not enable runtime display/system sleep. The optional `-wex-dsi-stop-test` additionally permits a one-way panel/host shutdown and resource retirement from the normal display power callback. Reboot to recover; this experiment does not support display wake. The separate `-wex-dsi-power-test` selects experimental display off/on with the native scanout backing and shared leases retained. It takes priority over `-wex-dsi-stop-test`; system sleep remains unsupported. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index ee54e81a..d5075551 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,8 +106,6 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 任意の `-wex-dsi-audit` は、対応するMIPI DSI firmware形式とnative起動前のGOP状態(他の表示エンジンとstolen memoryのGGTT prefixを含む)を検査し、診断結果を公開します。既定では無効で、この検査はディスプレイへの設定書込みやDSI出力の有効化を行いません。 -任意の `-wex-shutdown-observe` 引数は、適合するBig Sur 20G1443 kernelに明示的な有効化が必要な終了診断を組み込みます。終了段階を専用NVRAM変数へ有限件記録し、電源断後の回収に使用します。既定では無効で、起動ごとの診断用arm tokenが必要です。終了方針や待機処理は変更しません。 - 任意の `-wex-dsi-adopt` は、対応パネルprofileで実験的なMIPI DSI firmware GOP採用を選択します。firmwareのリンクを保持し、パネルprofile由来の合成EDIDを公開してnative pathと固定modeのアクセラレーション付きデスクトップへ接続します。OSが要求する90°/270°回転とGPU描画元の拡縮では物理パネルのタイミングを保持し、向きとHiDPIモードはmacOS側で設定します。OSが提示する拡縮モードを特定の解像度や一覧の固定件数に制限せず、選択できるHiDPIサイズはmacOS側のディスプレイ設定に依存します。対応DSI profileでは既存AppleBacklight経由でnative PWM輝度の要求・読戻しを扱い、輝度0ではPWM dutyを0にします。後述の画面消灯・復帰を有効にした場合、消灯中の輝度要求は保存し、復帰時のbacklight有効化前に反映します。macOS Big Surでは、内蔵HiDPIパネルとHDMI/USB-Cの外部2画面までの同時表示・加速・画面消灯復帰を確認しています。システムsleepと全ライフサイクルの検証は開発中です。`-wex-dsi-hdmi-test` とは併用できません。追加の `-wex-dsi-panel-only` は、内蔵パネルの開発中に外部出力の接続公開とmode設定を一時的に抑止します。物理配線の定義は変更しません。外部共存の検証時はこの引数を省略してください。 任意の `-wex-dsi-command-test` は、DSI採用時にDCS NOP送信・drainを1回試験します。パネルの電源再投入を有効にする引数ではありません。任意の `-wex-dsi-panel-test` は、native起動前にhostのclock・PHYを保持したままパネルの電源再投入・reset・初期化を1回試験します。hostの完全再初期化は開発中です。追加の `-wex-dsi-host-test` はパネルcycleを含み、排他的に保持したPLL・PHY・DSI hostをpanel profileから再構築します。これらの起動時試験は通常の画面・システムsleepを有効にしません。任意の `-wex-dsi-stop-test` は通常の画面電源callbackから1回のpanel/host停止と資源退役を許可します。復帰には再起動が必要で、通常の画面wakeには未対応です。 別の `-wex-dsi-power-test` はnative画面メモリと共有資源の所有権を保持する実験的な画面消灯・復帰を選択し、`-wex-dsi-stop-test` より優先します。システムsleepは未対応です。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 98030b15..db79068d 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -3,7 +3,6 @@ #include "kern_xelp_device.hpp" #include "kern_xe_data_access.hpp" #include "kern_backlight_profile.hpp" -#include "kern_shutdown_observer.hpp" #include #include #include @@ -27,7 +26,6 @@ void XeHost::init() { XeDataAccess::init(); } void XeHost::processKernel(KernelPatcher &patcher) { - ShutdownObserver::install(patcher); auto info=DeviceInfo::create();if(!info)return; auto gpu=OSDynamicCast(IOPCIDevice,info->videoBuiltin); // Match WEG ordering: PCI config reads require a published IOPCIDevice. diff --git a/WhateverXe/kern_shutdown_observer.cpp b/WhateverXe/kern_shutdown_observer.cpp deleted file mode 100644 index dee8f32e..00000000 --- a/WhateverXe/kern_shutdown_observer.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// Optional, explicitly armed diagnostics for the qualified Big Sur kernel. -// No timeout, panic, power-policy or shutdown-return-value changes. -#include "kern_shutdown_observer.hpp" -#include "kern_shutdown_tc_observation.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -namespace ShutdownTCObservation { State storage; } -namespace ShutdownObserver { -static const char *traceKey="4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:wex-shutdown-trace"; -struct Event { uint32_t code, detail; uint64_t time, pc; char label[24]; }; -struct Journal { - uint32_t magic, version; uint64_t run; uint32_t count, dropped, failed, reserved; Event events[32]; - // Latest hwShutdown entry/return. The port snapshot is memory-only; it - // distinguishes retained software ownership from a missing native return. - uint32_t display[2][48]; -}; -static_assert(sizeof(Journal)==1952,"bounded NVRAM journal"); -static Journal journal {}; -static IODTNVRAM *nvram; -static OSData *snapshots[2]; -static const OSSymbol *traceSymbol; -static IOLock *lock; -static bool armed; -static DisplaySnapshot display12Snapshot; -static uint32_t (*orgDisplay12Shutdown)(void *,void *); -static uint64_t ready[8] {}; -static void (*syncNvram)(IODTNVRAM *,bool); -static int (*orgReboot)(int,char *); -static void (*orgNotify)(int); -static int (*orgSync)(proc_t,void *,int *); -static void (*orgHaltLog)(const char *,const void *,uint64_t); -static int (*orgPEHalt)(unsigned int,uint32_t); -static void (*orgPlatform)(void *,unsigned int); -// _sync starts with a RIP-relative LEA. Lilu copies prologues verbatim, -// so replay that exact prefix with its original callback address explicitly. -extern "C" { -uintptr_t wexShutdownSyncCallback=0, wexShutdownSyncContinue=0; -} -__attribute__((naked,noinline)) static int syncBridge(proc_t,void *,int *) { - __asm__("pushq %rbp\n\tmovq %rsp, %rbp\n\tmovq _wexShutdownSyncCallback(%rip), %rsi\n\tjmpq *_wexShutdownSyncContinue(%rip)"); -} -static void publish() { IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownObserver",ready,sizeof(ready)); } -// All call sites precede the final platform power-off. Refuse an interrupt-off -// context; never take a blocking diagnostic lock or allocate journal storage here. -static void record(uint32_t code,uint32_t detail=0,const char *label=nullptr,const void *pc=nullptr, - const uint32_t *display=nullptr) { - if(!armed || !ml_get_interrupts_enabled())return; - if(!IOLockTryLock(lock)){OSIncrementAtomic(reinterpret_cast(&journal.dropped));return;} - if(journal.count>=128){IOLockUnlock(lock);return;} - auto &e=journal.events[journal.count%32];bzero(&e,sizeof(e)); - e.code=code;e.detail=detail;e.time=mach_absolute_time();e.pc=reinterpret_cast(pc); - if(label)strlcpy(e.label,label,sizeof(e.label)); - ++journal.count; - if(display && (code==70 || code==71 || code==72)) { - auto dst=journal.display[code==71?1:0];memcpy(dst,display,sizeof(journal.display[0])); - dst[0]=journal.count;dst[3]=detail; - } - auto data=snapshots[journal.count%2]; - memcpy(const_cast(data->getBytesNoCopy()),&journal,sizeof(journal)); - const bool written=nvram->setProperty(traceSymbol,data); - if(written)syncNvram(nvram,false); // qualified syncInternal(false): no 15-minute throttle - auto read=OSDynamicCast(OSData,nvram->getProperty(traceSymbol)); - const bool same=read && read->getLength()==sizeof(journal) && !memcmp(read->getBytesNoCopy(),&journal,sizeof(journal)); - if(!written || !same)++journal.failed; - ready[3]=journal.count;ready[4]=journal.failed;ready[5]=journal.dropped; - IOLockUnlock(lock); -} -// No registry access from normal sync calls. Only this root-only control -// operation resolves NVRAM and arms the observer after userspace is online. -static int armControl SYSCTL_HANDLER_ARGS { - uint64_t token=0; - int error=sysctl_handle_quad(oidp,&token,0,req); - if(error || !req->newptr)return error; - if(ready[2]!=63 || token!=journal.run)return EINVAL; - if(armed)return 0; - auto entry=IORegistryEntry::fromPath("IODeviceTree:/options"); - nvram=OSDynamicCast(IODTNVRAM,entry); - if(!nvram){if(entry)entry->release();ready[6]=2;publish();return ENXIO;} - armed=true;record(1,0,"probe"); - if(display12Snapshot){uint32_t state[48] {};display12Snapshot(nullptr,nullptr,state);record(72,0,"display preflight",nullptr,state);} - publish(); - return journal.failed ? EIO : 0; -} -SYSCTL_PROC(_debug, OID_AUTO, wex_shutdown_arm, CTLTYPE_QUAD | CTLFLAG_RW | CTLFLAG_LOCKED, - nullptr, 0, armControl, "Q", "Arm bounded shutdown diagnostics for this boot"); -static int wrapSync(proc_t p,void *a,int *r) { record(30);int v=orgSync(p,a,r);record(31,static_cast(v));return v; } -static int wrapReboot(int howto,char *message) { record(10,howto);return orgReboot(howto,message); } -static void wrapNotify(int stage) { record(20,stage);orgNotify(stage);record(21,stage); } -static void wrapPlatform(void *self,unsigned int type) { record(60,type);orgPlatform(self,type);record(61,type); } -void framebuffer(bool returned,void *controller,void *fb,uint32_t result,DisplaySnapshot snapshot) { - if(!armed || !snapshot || !ml_get_interrupts_enabled())return; - uint32_t state[48] {};snapshot(controller,fb,state); - record(returned?71:70,result,"hwShutdown",nullptr,state); -} -static uint32_t wrapDisplay12Shutdown(void *controller,void *fb) { - framebuffer(false,controller,fb,0,display12Snapshot); - const auto result=orgDisplay12Shutdown(controller,fb); - framebuffer(true,controller,fb,result,display12Snapshot); - return result; -} -void installDisplay12(KernelPatcher &p,size_t index,mach_vm_address_t address,size_t size,DisplaySnapshot snapshot) { - // Only the explicitly requested, exact-kernel observer installs this hook. - if(ready[2]!=63 || !snapshot)return; - const char *symbol="__ZN31AppleIntelFramebufferController10hwShutdownEP21AppleIntelFramebuffer"; - const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; - const auto target=p.solveSymbol(index,symbol,address,size);p.clearError(); - const auto delta=static_cast(reinterpret_cast(wrapDisplay12Shutdown)-(target+5)); - bool known=size>0x51d4b && target==address+0x5173a && - !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && delta==static_cast(delta); - if(known) { - mach_vm_address_t original=0; - KernelPatcher::RouteRequest route(symbol,wrapDisplay12Shutdown,original); - known=p.routeMultipleShort(index,&route,1,address,size);p.clearError(); - if(known){orgDisplay12Shutdown=reinterpret_cast(original);display12Snapshot=snapshot;ShutdownTCObservation::state().enabled=true;} - } - IORegistryEntry::getRegistryRoot()->setProperty("WEXShutdownDisplay12Observer",known); -} -static int wrapPEHalt(unsigned int type,uint32_t details) { if(type==0 || type==1 || type==3)record(50,type);int v=orgPEHalt(type,details);if(type==0 || type==1 || type==3)record(51,type);return v; } -static void wrapHaltLog(const char *what,const void *pc,uint64_t time) { - orgHaltLog(what,pc,time); - if(!what)return; - // Per-driver successful callbacks are numerous; retain slow/outstanding ones. - if(!strcmp(what,"PowerOff/Restart handler completed"))return; - record(40,0,what,pc); -} -void install(KernelPatcher &p) { - if(!checkKernelArgument("-wex-shutdown-observe"))return; - ready[0]=1; - const auto base=p.solveSymbol(KernelPatcher::KernelID,"__mh_execute_header"); - if(!base){p.clearError();publish();return;} - auto h=reinterpret_cast(base); - const uint8_t expected[]={0x51,0x9a,0x08,0x98,0xed,0x4b,0x3e,0x72,0x8f,0x00,0x1b,0x49,0xf7,0x15,0xa4,0x74}; - bool match=false; - if(h->magic!=MH_MAGIC_64 || h->sizeofcmds>65536 || h->ncmds>128){publish();return;} - auto at=reinterpret_cast(h+1);auto end=at+h->sizeofcmds; - for(uint32_t i=0;incmds && at+sizeof(load_command)<=end;i++) { - auto c=reinterpret_cast(at); - if(c->cmdsizecmdsize>end)break; - if(c->cmd==LC_UUID && c->cmdsize==sizeof(uuid_command))match=!memcmp(reinterpret_cast(c)->uuid,expected,16); - at+=c->cmdsize; - } - if(!match){publish();return;} - const auto sync=p.solveSymbol(KernelPatcher::KernelID,"__ZN9IODTNVRAM12syncInternalEb"); - // Match the exact function entry as well as the kernel UUID. - const uint8_t syncBytes[]={0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x83,0xbf,0x88,0,0,0,0}; - if(!sync || memcmp(reinterpret_cast(sync),syncBytes,sizeof(syncBytes))){p.clearError();publish();return;} - syncNvram=reinterpret_cast(sync); - // NVRAM may not be published when Lilu routes the kernel; resolve it on arm. - lock=IOLockAlloc();traceSymbol=OSSymbol::withCString(traceKey); - journal.magic=0x57585344;journal.version=2;journal.run=mach_absolute_time()^base;ready[1]=journal.run; - snapshots[0]=OSData::withBytes(&journal,sizeof(journal));snapshots[1]=OSData::withBytes(&journal,sizeof(journal)); - if(!lock || !traceSymbol || !snapshots[0] || !snapshots[1]){ready[6]=1;publish();return;} - KernelPatcher::RouteRequest routes[]={ - {"_reboot_kernel",wrapReboot,orgReboot}, - {"_IOSystemShutdownNotification",wrapNotify,orgNotify}, - {"_sync",wrapSync}, - {"_halt_log_enter",wrapHaltLog,orgHaltLog}, - {"_PEHaltRestartInternal",wrapPEHalt,orgPEHalt}, - {"__ZN14IOPMrootDomain25handlePlatformHaltRestartEj",wrapPlatform,orgPlatform} - }; - // Every automatic trampoline copies exactly six position-independent - // bytes. Reject changed entries or out-of-range rel32 jumps before patching. - const uint8_t simple[]={0x55,0x48,0x89,0xe5,0x41,0x57}; - const uint8_t syncEntry[]={0x55,0x48,0x89,0xe5,0x48,0x8d,0x35,0x25,0,0,0,0xbf,1,0,0,0}; - for(auto &r:routes) { - auto address=p.solveSymbol(KernelPatcher::KernelID,r.symbol); - if(!address){ready[6]=3;p.clearError();publish();return;} - const auto delta=static_cast(r.to-(address+5)); - if(delta!=static_cast(delta)){ready[6]=5;publish();return;} - if(!strcmp(r.symbol,"_sync")) { - if(memcmp(reinterpret_cast(address),syncEntry,sizeof(syncEntry))){ready[6]=6;publish();return;} - wexShutdownSyncCallback=address+0x30;wexShutdownSyncContinue=address+11;orgSync=syncBridge; - } else if(memcmp(reinterpret_cast(address),simple,sizeof(simple))){ready[6]=7;publish();return;} - } - if(p.routeMultipleShort(KernelPatcher::KernelID,routes,arrsize(routes))){ready[2]=63;sysctl_register_oid(&sysctl__debug_wex_shutdown_arm);} - else {ready[6]=4;ready[7]=static_cast(p.getError());p.clearError();} - publish(); -} -} diff --git a/WhateverXe/kern_shutdown_observer.hpp b/WhateverXe/kern_shutdown_observer.hpp deleted file mode 100644 index 3fd869a2..00000000 --- a/WhateverXe/kern_shutdown_observer.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include -namespace ShutdownObserver { -void install(KernelPatcher &patcher); -using DisplaySnapshot = void (*)(void *controller, void *fb, uint32_t *words); -void installDisplay12(KernelPatcher &,size_t,mach_vm_address_t,size_t,DisplaySnapshot); -void framebuffer(bool returned,void *controller,void *fb,uint32_t result,DisplaySnapshot); -} diff --git a/WhateverXe/kern_shutdown_tc_observation.hpp b/WhateverXe/kern_shutdown_tc_observation.hpp deleted file mode 100644 index 633de68c..00000000 --- a/WhateverXe/kern_shutdown_tc_observation.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include -// Passive copies of accesses the driver already performs. Never reads MMIO, -// changes a returned value, acquires a driver lock, or persists from an accessor. -namespace ShutdownTCObservation { -struct State { - bool enabled {}; - uint32_t sequence {}, mode0[4] {}, modeResult[4] {}; - uint64_t reads[4] {}, writes[4] {}; // FIA1..3 ownership, FIA1 topology -}; -// -fapple-kext does not coalesce inline-function local statics across TUs. -extern State storage; -inline State &state() {return storage;} -inline unsigned index(uint32_t reg) { - if(reg==0x1638A0)return 3; - if(reg>=0x163894 && reg<=0x163A94 && !((reg-0x163894)%0x100))return (reg-0x163894)/0x100; - return 4; -} -inline void record(uint32_t reg,uint32_t value,bool write) { - auto &s=state();if(!s.enabled)return; - const auto i=index(reg);if(i==4)return; - const auto seq=__atomic_add_fetch(&s.sequence,1,__ATOMIC_RELAXED); - __atomic_store_n((write?s.writes:s.reads)+i,(static_cast(seq)<<32)|value,__ATOMIC_RELEASE); -} -inline uint64_t sample(uint32_t reg,bool write) { - const auto i=index(reg);if(i==4)return 0; - auto &s=state();return __atomic_load_n((write?s.writes:s.reads)+i,__ATOMIC_ACQUIRE); -} -inline void mode(uint32_t ddi,uint32_t wanted,uint32_t result) { - auto &s=state();if(!s.enabled || ddi<3 || ddi>6)return; - if(!wanted)__atomic_add_fetch(s.mode0+ddi-3,1,__ATOMIC_RELAXED); - __atomic_store_n(s.modeResult+ddi-3,result,__ATOMIC_RELEASE); -} -} diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 993b5856..0413ff29 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11,8 +11,6 @@ #include "kern_dsi_native_backing.hpp" #include "kern_dsi_native_mode.hpp" #include "kern_xe.hpp" -#include "kern_shutdown_observer.hpp" -#include "kern_shutdown_tc_observation.hpp" #include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" #include "kern_alderlake_n.hpp" @@ -11937,44 +11935,20 @@ void XE::finishDisplay13TCShutdown(void *controller) { const auto result=wrapDisplay13TCMode(port,0);port->release();IOLockLock(c.lock); record[4+tc]=result;if(!result)record[3]|=1U<display13ShutdownTrace=(record[1]<<1)|(record[2]<<8)|(record[3]<<16); IOLockUnlock(c.lock); if(acquired)reinterpret_cast(self->adlnWorkLoopOpen)(gate); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCShutdown",record,sizeof(record)); } uint32_t XE::wrapDisplay13Shutdown(void *controller,void *fb) { - ShutdownObserver::framebuffer(false,controller,fb,0,snapshotDisplay13Shutdown); const auto result=FunctionCast(wrapDisplay13Shutdown,callbackXE->orgDisplay13Shutdown)(controller,fb); // hwShutdown is called by the donor only for system power-off/restart. // A disconnected sink's hotplug path already does this; a connected sink // still needs mode0 after its scanout/link stop, even while another FB lives. if(!result)finishDisplay13TCShutdown(controller); - ShutdownObserver::framebuffer(true,controller,fb,result,snapshotDisplay13Shutdown); return result; } -void XE::snapshotDisplay13Shutdown(void *controller,void *fb,uint32_t *out) { - // Diagnostics must neither enter a gated native call nor wait behind the - // shutdown path. No MMIO, power transitions, or register serializers here. - auto &c=display13FBPower; - out[1]=2;out[2]=UINT32_MAX; - if(!c.lock || !IOLockTryLock(c.lock))return; - if(controller!=c.controller || !fb || !c.tcSupported) {IOLockUnlock(c.lock);return;} - out[2]=Display13FBMemory::field(fb,0x1DC); - out[4]=1|(c.bridge.state.suspended?4U:0U)|callbackXE->display13ShutdownTrace; - out[5]=c.clockFaulted;out[6]=c.tcBankFaulted;out[7]=c.bridge.state.faulted; - for(unsigned tc=0;tc<4;++tc) { - auto &m=c.tcMode[tc];auto port=c.tcObject[tc];auto row=out+8+10*tc; - row[0]=port?Display13FBMemory::field(port,0x550):UINT32_MAX; - row[1]=static_cast(m.phase);row[2]=m.disconnectPending;row[3]=m.ownership.active; - row[4]=m.accessLease.domains;row[5]=m.auxLease.domains; - row[6]=c.bridge.nativeDDIRefs(tc+3);row[7]=c.bridge.nativeAUXRefs(tc+3); - row[8]=c.tcBusy[tc];row[9]=c.tcSignalReady[tc]; - } - IOLockUnlock(c.lock); -} - // One entry owns donor ABI/lifecycle repairs for every hardware backend. // Backend installers cannot independently opt into or replace these hooks. bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { @@ -11990,8 +11964,7 @@ bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_addr if(!routed)return false; if(display13FramebufferRequested) processDisplay13Shutdown(patcher,index,address,size); - else - ShutdownObserver::installDisplay12(patcher,index,address,size,snapshotDisplay12Shutdown); + framebufferDonorCommonReady=processFramebufferCommon(patcher,index,address,size); // Install hardware routes even after a common failure so their diagnostics // remain available. The common start gate never enters native startup then. @@ -16403,7 +16376,6 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { IORegistryEntry::getRegistryRoot()->setProperty("TGLModeResultCorrection",record,sizeof(record)); } - ShutdownTCObservation::mode(traceDDI,mode,result); tglAUXRecord(port, 1, true, result, mode, 0); tglConnectionRecord(port, 2, true, result, mode, caller); auto bytes = static_cast(port); @@ -16664,43 +16636,6 @@ void XE::tglBeginTCColdProtection(void *controller) { } } -void XE::snapshotDisplay12Shutdown(void *controller,void *fb,uint32_t *out) { - // The native hwShutdown call owns these live objects; the preflight uses - // the already-running controller. getPortByDDI is a qualified memory-only - // table lookup. No register reads, native power calls or blocking locks here. - auto self=callbackXE;auto &f=self->tglTCNativeFunctions;auto &cold=tglTCBootstrap; - out[1]=3;out[2]=UINT32_MAX; - if(!controller)controller=self->tglObservationController; - if(!controller || !f.complete() || *f.controllerSlot!=controller)return; - auto power=TGLTCNativeBinding::field(controller,0xC8); - if(!power || TGLTCNativeBinding::field(power,0x10)!=controller)return; - out[4]=1;out[2]=fb?TGLTCNativeBinding::field(fb,0x1DC):UINT32_MAX; - out[5]=out[6]=out[7]=UINT32_MAX; - if(cold.controller==controller && cold.lock && IOLockTryLock(cold.lock)) { - out[4]|=2;out[5]=static_cast(cold.cold.phase);out[6]=cold.cold.total;out[7]=cold.cold.bootstrapHeld; - IOLockUnlock(cold.lock); - } - const auto topology=ShutdownTCObservation::sample(0x1638A0,false); - for(unsigned tc=0;tc<4;++tc) { - auto row=out+8+10*tc;const unsigned ddi=tc+3; - for(unsigned i=0;i<10;++i)row[i]=UINT32_MAX; - auto port=f.portByDDI(ddi); - if(port)row[0]=TGLTCNativeBinding::field(port,0x550); - row[1]=TGLTCNativeBinding::field(power,0x54+4*ddi); - row[2]=self->tglAUXParentRefs[ddi];row[3]=UINT32_MAX; - TGLTypeCPort coords;TGLFiaPort fia; - if((topology>>32) && TGLTypeCPort::resolve(ddi,coords) && coords.resolveFia(static_cast(topology),fia)) { - const auto read=ShutdownTCObservation::sample(fia.owned,false),write=ShutdownTCObservation::sample(fia.owned,true); - row[3]=fia.readyMask; - row[4]=static_cast(read);row[5]=static_cast(write); - row[6]=read>>32;row[7]=write>>32; - } - auto &observation=ShutdownTCObservation::state(); - row[8]=__atomic_load_n(observation.mode0+tc,__ATOMIC_RELAXED); - row[9]=__atomic_load_n(observation.modeResult+tc,__ATOMIC_ACQUIRE); - } -} - // Runs synchronously inside native controller start, after native PowerWell // accounting init and before display-path allocation/boot-display setup. static TGLBootReadout::Result tglBootReadout; @@ -17110,7 +17045,6 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { const bool trace=tglTransitionRegister(reg); if(trace)tglTransitionRecord(35,false,access,reg,tglConnectionCaller(__builtin_return_address(0)),0); auto value = FunctionCast(wrapTGLRegisterRead, callbackXE->orgTGLRegisterRead)(access, reg); - ShutdownTCObservation::record(static_cast(reg),value,false); if(trace)tglTransitionRecord(35,true,access,reg,tglConnectionCaller(__builtin_return_address(0)),value); const auto caller = reinterpret_cast(__builtin_return_address(0)); auto s = callbackXE; @@ -19034,7 +18968,6 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { const bool trace=tglTransitionRegister(reg); if(trace)tglTransitionRecord(36,false,access,reg,tglConnectionCaller(__builtin_return_address(0)),value); FunctionCast(wrapTGLRegisterWrite, callbackXE->orgTGLRegisterWrite)(access, reg, value); - ShutdownTCObservation::record(static_cast(reg),value,true); // Sample PWM only at a native duty write while its caller owns power. // IORegistry reads consume this receipt; they never touch live MMIO. if(callbackXE->tglActive && reg==0xC8258 && callbackXE->orgTGLRegisterRead) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 15f8855f..9238f390 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -760,13 +760,10 @@ class XE { bool framebufferDonorAttempted {},framebufferDonorCommonReady {},framebufferBackendInstalled {}; mach_vm_address_t orgFramebufferStart {},orgFramebufferInit {},orgFramebufferComputeLanes {},framebufferLaneCaller {}; __attribute__((noinline)) bool processFramebufferKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); - static void snapshotDisplay12Shutdown(void *,void *,uint32_t *); - static void snapshotDisplay13Shutdown(void *,void *,uint32_t *); bool processDisplay13Shutdown(KernelPatcher &,size_t,mach_vm_address_t,size_t); static uint32_t wrapDisplay13Shutdown(void *,void *); static void finishDisplay13TCShutdown(void *); mach_vm_address_t orgDisplay13Shutdown {},display13ShutdownPowerCaller {}; - uint32_t display13ShutdownTrace {}; __attribute__((noinline)) bool processFramebufferCommon(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool framebufferTrainingHooksReady {}; bool framebufferStartInProgress {}; diff --git a/tests/display12_shutdown_observer.py b/tests/display12_shutdown_observer.py deleted file mode 100644 index 13919f1d..00000000 --- a/tests/display12_shutdown_observer.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -from pathlib import Path -import re,subprocess,tempfile -r=Path(__file__).resolve().parents[1] -s=(r/'WhateverXe/kern_xe.cpp').read_text();o=(r/'WhateverXe/kern_shutdown_observer.cpp').read_text() -snapshot=re.search(r'void XE::snapshotDisplay12Shutdown\(.*?\n\}',s,re.S)[0] -wrapper=re.search(r'static uint32_t wrapDisplay12Shutdown\(.*?\n\}',o,re.S)[0] -code=r''' -#include -#include -#include "kern_shutdown_tc_observation.hpp" -namespace ShutdownTCObservation {State storage;} -#include "kern_tgl_port_map.hpp" -struct Lock{bool busy=false;};bool IOLockTryLock(Lock*l){if(l->busy)return false;l->busy=true;return true;}void IOLockUnlock(Lock*l){assert(l->busy);l->busy=false;} -struct TGLTCNativeBinding{templatestatic T field(void*p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}}; -struct {void*controller;Lock*lock;struct{unsigned phase=1,total=1;bool bootstrapHeld=true;}cold;}tglTCBootstrap; -struct Functions{bool valid=true;void**controllerSlot;void*(*portByDDI)(unsigned);bool complete(){return valid;}}; -struct XE{Functions tglTCNativeFunctions;void*tglObservationController;unsigned tglAUXParentRefs[9]{};static void snapshotDisplay12Shutdown(void*,void*,uint32_t*);};XE*callbackXE; -SNAPSHOT -using Snapshot=void(*)(void*,void*,uint32_t*);Snapshot display12Snapshot=XE::snapshotDisplay12Shutdown; -unsigned events[2]{},calls=0,nativeCalls=0;uint32_t result=0; -void framebuffer(bool done,void*c,void*f,uint32_t ret,Snapshot fn){events[calls++]=done?ret:0;uint32_t data[48]{};fn(c,f,data);} -uint32_t native(void*,void*){++nativeCalls;return result;}auto orgDisplay12Shutdown=native; -WRAPPER -char ports[4][0x560]{};void*get(unsigned d){return d>=3&&d<7?ports[d-3]:nullptr;} -templatevoid put(void*p,unsigned off,T v){memcpy((char*)p+off,&v,sizeof(v));} -int main(){ - using namespace ShutdownTCObservation;record(0x163894,3,false);mode(3,0,0);assert(sample(0x163894,false)==0&&state().mode0[0]==0); - state().enabled=true;record(0x1234,3,false);assert(state().sequence==0); - record(0x1638a0,0x10,false);record(0x163894,1,false);record(0x163894,0,true); - mode(3,0,0x1234);mode(7,0,0);assert(state().mode0[0]==1); - char c[0xc50]{},power[0x90]{},fb[0x200]{};void*cp=c;put(c,0xc8,(void*)power);put(power,0x10,cp);put(fb,0x1dc,1U); - for(unsigned i=0;i<4;++i){put(ports[i],0x550,3U);put(power,0x54+4*(i+3),i+1);} - XE x;callbackXE=&x;x.tglObservationController=c;x.tglTCNativeFunctions={true,&cp,get};Lock lock;tglTCBootstrap.controller=c;tglTCBootstrap.lock=&lock; - uint32_t out[48]{};XE::snapshotDisplay12Shutdown(c,fb,out);assert(out[1]==3&&out[2]==1&&out[4]==3&&out[6]==1&&out[7]==1); - assert(out[8]==3&&out[9]==1&&out[11]==1&&out[12]==1&&out[13]==0&&out[14]==2&&out[15]==3&&out[16]==1&&out[17]==0x1234); - // No hardware access exists in the sampler; a busy cold lock is not waited on. - lock.busy=true;memset(out,0,sizeof(out));XE::snapshotDisplay12Shutdown(nullptr,nullptr,out);assert(out[4]==1&&out[5]==UINT32_MAX&&lock.busy); - for(unsigned n=0;n<2;++n){calls=nativeCalls=0;result=n?0xdead:0;assert(wrapDisplay12Shutdown(c,fb)==result&&nativeCalls==1&&calls==2&&events[1]==result);} - cp=nullptr;memset(out,0,sizeof(out));XE::snapshotDisplay12Shutdown(c,fb,out);assert(out[4]==0); -} -'''.replace('SNAPSHOT',snapshot).replace('WRAPPER',wrapper) -with tempfile.TemporaryDirectory() as d: - d=Path(d);(d/'test.cpp').write_text(code) - subprocess.run(['clang++','-std=c++14','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) - subprocess.run([str(d/'test')],check=True) -# Match the kernel's separate-translation-unit compilation model. An inline -# local-static singleton is TU-local with -fapple-kext, unlike the userland test. -with tempfile.TemporaryDirectory() as tmp: - d=Path(tmp) - (d/'writer.cpp').write_text('#include "kern_shutdown_tc_observation.hpp"\nnamespace ShutdownTCObservation {State storage;}\nvoid enable(){ShutdownTCObservation::state().enabled=true;}') - (d/'reader.cpp').write_text('#include "kern_shutdown_tc_observation.hpp"\nvoid observe(){ShutdownTCObservation::record(0x163894,1,false);}') - (d/'main.cpp').write_text('#include \n#include "kern_shutdown_tc_observation.hpp"\nvoid enable();void observe();extern "C" int main(){enable();observe();assert(ShutdownTCObservation::sample(0x163894,false)==0x100000001ULL);return 0;}') - for name in ['writer','reader','main']: - subprocess.run(['clang++','-std=c++17','-O2','-fapple-kext','-fno-exceptions','-fno-rtti','-I'+str(r/'WhateverXe'),'-c',str(d/(name+'.cpp')),'-o',str(d/(name+'.o'))],check=True) - subprocess.run(['clang++',*[str(d/(n+'.o')) for n in ['writer','reader','main']],'-o',str(d/'cross-tu')],check=True) - subprocess.run([str(d/'cross-tu')],check=True) -assert 'ready[2]!=63' in o and 'record(72,0,"display preflight"' in o -assert 'finishDisplay13TCShutdown' not in wrapper and 'record(' not in snapshot -print('PASS Display12 passive cache, memory-only sampler, busy-lock bypass, original shutdown result/call count') diff --git a/tests/display13_shutdown.py b/tests/display13_shutdown.py index 8ee0c5b8..a52b9ccf 100644 --- a/tests/display13_shutdown.py +++ b/tests/display13_shutdown.py @@ -23,12 +23,10 @@ namespace Display13FBMemory{templateT field(void*p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}} uint32_t published[8]{};struct IORegistryEntry{static IORegistryEntry*getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,const void*p,size_t n){assert(n==sizeof(published));memcpy(published,p,n);}}; std::vector events;unsigned modeCalls=0,nativeCalls=0;uint32_t nativeResult=0,modeResult=0;bool shutdownSuspends=false; -namespace ShutdownObserver{void framebuffer(bool done,void*,void*,uint32_t,void(*)(void*,void*,uint32_t*)){events.push_back(done?4:1);}} #define FunctionCast(fn,p) reinterpret_cast(p) -struct XE{bool display13FBTCHooksReady=true;uintptr_t orgDisplay13Shutdown;uint32_t display13ShutdownTrace=0; +struct XE{bool display13FBTCHooksReady=true;uintptr_t orgDisplay13Shutdown; uintptr_t adlnWorkLoopInGate=(uintptr_t)inGate,adlnWorkLoopTryClose=(uintptr_t)tryGate,adlnWorkLoopOpen=(uintptr_t)openGate; static void finishDisplay13TCShutdown(void*);static uint32_t wrapDisplay13Shutdown(void*,void*); - static void snapshotDisplay13Shutdown(void*,void*,uint32_t*){} static uint32_t wrapDisplay13TCMode(Port*p,uint32_t mode){auto &c=display13FBPower;assert(!c.lock->held && c.workLoop->held && mode==0 && p->refs==2 && !c.bridge.state.suspended);++modeCalls;events.push_back(3);if(!modeResult)c.tcObject[p->id]=nullptr;return modeResult;} };XE*callbackXE; FUNCTIONS @@ -52,7 +50,7 @@ if(test==9)gate.held=true; if(test==10)shutdownSuspends=true; assert(XE::wrapDisplay13Shutdown(test==11?nullptr:controller,nullptr)==nativeResult); - assert(nativeCalls==1 && events.front()==1 && events.back()==4); + assert(nativeCalls==1 && events.front()==2 && events.size()==1+modeCalls); const bool attempted=test==0||test==8||test==9||test==10; assert(modeCalls==unsigned(attempted));assert(!lock.held && gate.held==(test==9));assert(idle.refs==1&&active.refs==1); assert(c.tcObject[0]==&active && c.bridge.ddi[3]==1); diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index a29e95d4..9bef982c 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -147,6 +147,5 @@ "-wex-adlp-load-only": [], "-wex-display13-offline": [], "-wex-dsi-hdmi-test": [], - "-wex-dsi-power-test": [], - "-wex-shutdown-observe": [] + "-wex-dsi-power-test": [] } diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index 84a55f3a..c30a1bed 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -60,7 +60,6 @@ }; bool tglInstallSonomaSurfaceABI(KernelPatcher&,size_t,uintptr_t,size_t,bool){return surfaceOK;} bool xelpInstallNVRAMABI(KernelPatcher&,size_t,uintptr_t,size_t){return nvramOK;} -namespace ShutdownObserver {templatevoid installDisplay12(T...) {}} unsigned shutdownCalls=0; struct XE { bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; @@ -71,7 +70,6 @@ uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0;bool stopDuringStart=false,nativeOK=true; bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_t); - static void snapshotDisplay12Shutdown(void*,void*,uint32_t*){} bool processDisplay13Shutdown(KernelPatcher&,size_t,uintptr_t,size_t){++shutdownCalls;return true;} bool processFramebufferCommon(KernelPatcher&,size_t,uintptr_t,size_t); bool processFramebufferDonorRuntime(KernelPatcher&,size_t,uintptr_t,size_t){++commonRuntimeCalls;return runtimeOK;} diff --git a/tests/host_publication.py b/tests/host_publication.py index 46a62855..a86433aa 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -44,7 +44,6 @@ void clearError(){} }; // The optional observer is inactive in this host-publication fixture. -namespace ShutdownObserver {void install(KernelPatcher &) {}} namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; struct {size_t loadIndex=5;}backlight; diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index 8dd6ab6e..abfe1e03 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -1,5 +1,3 @@ -#include "kern_shutdown_tc_observation.hpp" -namespace ShutdownTCObservation {State storage;} #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index 5565b14e..e365996d 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,5 +1,3 @@ -#include "kern_shutdown_tc_observation.hpp" -namespace ShutdownTCObservation {State storage;} #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" diff --git a/tools/test.py b/tools/test.py index ca86ed6c..21a4ffaf 100644 --- a/tools/test.py +++ b/tools/test.py @@ -10,7 +10,7 @@ python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'display13_panel_runtime', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_cursor_resize','dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] python_tests += ['dsi_backlight_adapter','dsi_restart','dsi_panel','dsi_command_adapter','dsi_irq_live','dsi_surface_adapter','dsi_aperture_adapter'] -python_tests += ['display13_shutdown','display12_shutdown_observer'] +python_tests += ['display13_shutdown'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] if a.donor:jobs += [('display13_agdc_native',[sys.executable,str(R/'tests/display13_agdc.py'),str(a.donor.resolve())],None)] if a.donor:jobs += [('display13_shutdown_native',[sys.executable,str(R/'tests/display13_shutdown.py'),str(a.donor.resolve())],None)] From 2fe2f41bd6548ef9f2ecea9d23672b7c491a7ee5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:17:48 +0900 Subject: [PATCH 125/139] Support compatible DSI panel profiles and extended timings --- CHANGELOG.md | 4 ++ README.md | 2 + README_ja.md | 2 + WhateverXe/kern_dsi_adapter_diagnostics.inc | 2 +- WhateverXe/kern_dsi_adapter_lifecycle.inc | 9 +++- WhateverXe/kern_dsi_adapter_modes.inc | 5 +- WhateverXe/kern_dsi_apple_timing.hpp | 53 ++++++++++++++++----- WhateverXe/kern_dsi_host.hpp | 20 ++++---- WhateverXe/kern_dsi_panel.hpp | 30 ++++++++---- WhateverXe/kern_dsi_profile.hpp | 7 ++- WhateverXe/kern_dsi_sequence.hpp | 5 +- tests/dsi_apple_adapter.py | 5 ++ tests/dsi_apple_timing.cpp | 18 ++++++- tests/dsi_host.cpp | 20 +++++++- tests/dsi_panel.py | 32 +++++++++++-- tests/dsi_profile.cpp | 12 +++-- tests/dsi_sequence.cpp | 6 +++ 17 files changed, 186 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6863038b..0831da30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Accept compatible MIPI DSI firmware layouts before BDB 256, inline panel-reset sequences, and the second native panel GPIO bank. +- Publish MIPI DSI timings that exceed base EDID limits through a DisplayID extension, preserving the firmware link timing. +- Allow frame-period-based command completion waits while MIPI DSI video is active, fixing display wake timeouts for panels with a separate display-on sequence. + - Fix system power-off hangs with a USB-C display connected on Alder Lake-class GPUs by releasing idle Type-C PHY ownership during shutdown. - Fix display freezes during hardware cursor size changes by preparing watermarks for the requested size before the native control update. diff --git a/README.md b/README.md index 20f5e0a4..8ed63f2f 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,8 @@ The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GO Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. +DSI profiles are read from firmware VBT (BDB 236–256), including native panel GPIO banks 1 and 2 and reset operations embedded in the initialization sequence. Timings that exceed the base EDID limits are described with a DisplayID extension; the physical panel timing is preserved. The existing single-link, four-lane RGB888 video-mode restrictions still apply. + ### DVMT Set **DVMT Pre-Allocated to 96MB** in the BIOS/UEFI settings where possible. diff --git a/README_ja.md b/README_ja.md index d5075551..6e163b79 100644 --- a/README_ja.md +++ b/README_ja.md @@ -112,6 +112,8 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 +DSI profileはfirmware VBT(BDB 236–256)から読み取り、native panel GPIOの1組目・2組目と、初期化列に組み込まれたreset操作に対応します。基本EDIDの表現範囲を超えるタイミングはDisplayID拡張で記述し、物理パネルのタイミングは保持します。single-link・4-lane・RGB888 video modeの制約は維持します。 + ### DVMT BIOS/UEFIで変更できる場合は、**DVMT Pre-Allocatedを96MBに設定することを推奨します**。 diff --git a/WhateverXe/kern_dsi_adapter_diagnostics.inc b/WhateverXe/kern_dsi_adapter_diagnostics.inc index b837e93f..e506b8c3 100644 --- a/WhateverXe/kern_dsi_adapter_diagnostics.inc +++ b/WhateverXe/kern_dsi_adapter_diagnostics.inc @@ -95,7 +95,7 @@ bool XE::probeDSIPanel(IOService *controller) { *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;return; } switch(reg) { - case 0x6B100:case 0x6B104:case 0xC4030:case 0xC7204: + case 0x6B100:case 0x6B104:case 0xC4030:case 0xC7204:case 0xC7304: case 0x70180:case 0x7019C:case 0x7B008: *reinterpret_cast(map->getVirtualAddress()+reg)=value;++writes;break; } diff --git a/WhateverXe/kern_dsi_adapter_lifecycle.inc b/WhateverXe/kern_dsi_adapter_lifecycle.inc index 573d0b7c..f258b1f4 100644 --- a/WhateverXe/kern_dsi_adapter_lifecycle.inc +++ b/WhateverXe/kern_dsi_adapter_lifecycle.inc @@ -534,7 +534,7 @@ bool XE::quiesceDSIPath(IOService *controller,IOService *fb,OSObject *path,bool reg==0x7B008 || reg==0x6B0D8 || reg==0x6B400 || reg==0x164280 || reg==0x64000 || reg==0x46010+4*display13FBPower.bootDSI.identity.pll || (retain && XELP::DSI::Restart::writable(reg,display13FBPower.bootDSI.identity.pll)) || - (panel && (reg==0x6B100 || reg==0x6B104 || reg==0xC4030 || reg==0xC7204)))) + (panel && (reg==0x6B100 || reg==0x6B104 || reg==0xC4030 || reg==0xC7204 || reg==0xC7304)))) *reinterpret_cast(map->getVirtualAddress()+reg)=value; } bool gpio(unsigned role,unsigned value) { @@ -766,7 +766,7 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { bool ready(){return owns();} void write(uint32_t r,uint32_t v) { if(readable(r) && (XELP::DSI::Restart::writable(r,pll) || r==0x6B100 || r==0x6B104 || - r==0xC4030 || r==0xC7204 || r==0x70180 || r==0x7019C || r==0x7B008 || + r==0xC4030 || r==0xC7204 || r==0xC7304 || r==0x70180 || r==0x7019C || r==0x7B008 || r==0x70080 || r==0x70088 || r==0x70084 || r==XELP::DSI::Backlight::Duty)) *reinterpret_cast(map->getVirtualAddress()+r)=v; } @@ -866,11 +866,16 @@ bool XE::resumeDSIPath(IOService *controller,IOService *fb,OSObject *path) { receipt[12]=h.reg;receipt[13]=h.value;receipt[14]=h.writes;receipt[15]=h.waitUs; receipt[16]=masked;receipt[17]=restored;receipt[18]=rearmed;receipt[19]=cursor; receipt[20]=static_cast(p.stage);receipt[21]=static_cast(p.error);receipt[22]=p.reg;receipt[23]=p.value; + const auto &commands=p.host; + uint64_t commandReceipt[] {1,static_cast(commands.error),commands.pending,commands.faulted, + commands.sequenceId,commands.operationOffset,commands.headers,commands.payloadWords, + commands.completedOperations,commands.waitedUs}; IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(acquired)reinterpret_cast(adlnWorkLoopOpen)(loop); loop->release();IORecursiveLockUnlock(mutex); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIRuntimePower",receipt,sizeof(receipt)); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeIRQ",irqReceipt,sizeof(irqReceipt)); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIResumeCommands",commandReceipt,sizeof(commandReceipt)); publishDSIBacklight(); return complete; } diff --git a/WhateverXe/kern_dsi_adapter_modes.inc b/WhateverXe/kern_dsi_adapter_modes.inc index d3a7c22a..6d600ab7 100644 --- a/WhateverXe/kern_dsi_adapter_modes.inc +++ b/WhateverXe/kern_dsi_adapter_modes.inc @@ -185,13 +185,14 @@ uint32_t XE::wrapDSIDDC(IOService *fb,uint32_t block,uint8_t *data,uint64_t *len const auto result=[&]() -> uint32_t { if(!length || !data)return kIOReturnBadArgument; const auto capacity=*length;*length=0; - if(block!=1 || flags)return kIOReturnUnsupported; + if(block<1 || block>2 || flags)return kIOReturnUnsupported; if(capacity<128)return kIOReturnBadArgument; DSIModeScope scope(fb); if(!scope.lock || !dsiModeTarget(fb) || !dsiModeCoherent(fb) || (path && path!=Display13FBMemory::singlePath(fb)))return kIOReturnNotReady; + if(block==2 && !XELP::DSI::AppleAdapter::panelNeedsDisplayID(dsiFixedMode.mode))return kIOReturnUnsupported; uint8_t edid[128]; - if(!XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,edid))return kIOReturnNotReady; + if(!XELP::DSI::AppleAdapter::makePanelEDID(dsiFixedMode.mode,edid,block))return kIOReturnNotReady; memcpy(data,edid,sizeof(edid));*length=sizeof(edid);return kIOReturnSuccess; }(); recordDSIClient(fb,12,block,result);return result; diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 1290aeb1..3e30cf73 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -65,16 +65,39 @@ inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigne // Synthetic Apple enumeration descriptor with timing from the panel profile. // EDID wire layout reference: Linux v6.12 include/drm/drm_edid.h. This is not // an EDID read from the panel and does not advertise DP/HDMI, HDR or DPMS. -inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128]) { +inline bool panelNeedsDisplayID(const Mode &m) { + return m.width>4095 || m.height>4095 || m.hblank>4095 || m.vblank>4095 || + m.hfront>1023 || m.hsync>1023 || m.vfront>63 || m.vsync>63; +} +inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128],unsigned block=1) { memset(edid,0,sizeof(edid)); const uint32_t clock=(m.clockKHz+5)/10; - if(!m.valid() || !clock || clock>65535 || m.width>4095 || m.height>4095 || - m.hblank>4095 || m.vblank>4095 || m.hfront>1023 || m.hsync>1023 || - m.vfront>63 || m.vsync>63 || m.widthMM>4095 || m.heightMM>4095 || + if(!m.valid() || !clock || clock>65535 || m.widthMM>2550 || m.heightMM>2550)return false; + const bool extended=panelNeedsDisplayID(m); + // DisplayID Type I has 15-bit sync offsets, stored minus one. Preserve + // the real panel timing instead of truncating the EDID DTD's six-bit VFP. + if(extended && (!m.hfront || !m.vfront || m.hfront>32768 || m.vfront>32768))return false; + if(block<1 || block>2 || (block==2 && !extended))return false; const uint32_t rounded=clock*10; const uint32_t error=rounded>m.clockKHz ? rounded-m.clockKHz : m.clockKHz-rounded; if(uint64_t(error)*10000>m.clockKHz)return false; // retained link: at most 100 ppm + if(block==2) { + // VESA DisplayID 1.2, Type I timing; Linux drm_displayid_internal.h + // and Big Sur CoreDisplay's InstallDisplayIDEXT use this wire layout. + edid[0]=0x70;edid[1]=0x12;edid[2]=23; // one 3+20 byte data block + edid[3]=0;edid[4]=0; // EDID extension, no further DisplayID sections + edid[5]=3;edid[6]=0;edid[7]=20; + auto d=edid+8;const uint32_t encodedClock=clock-1; + d[0]=encodedClock;d[1]=encodedClock>>8;d[2]=encodedClock>>16;d[3]=0x80; + const uint16_t fields[]={m.width,m.hblank,m.hfront,m.hsync,m.height,m.vblank,m.vfront,m.vsync}; + for(unsigned i=0;i<8;++i) { + const auto value=uint16_t(fields[i]-1);d[4+2*i]=value;d[5+2*i]=value>>8; + } + uint8_t section=0;for(unsigned i=1;i<28;++i)section+=edid[i];edid[28]=uint8_t(0-section); + uint8_t sum=0;for(unsigned i=0;i<127;++i)sum+=edid[i];edid[127]=uint8_t(0-sum); + return true; + } const uint8_t header[]={0,255,255,255,255,255,255,0};memcpy(edid,header,8); edid[8]=0x5C;edid[9]=0xB8; // synthetic WEX identity, no panel serial edid[10]=0x10;edid[11]=0xD5; @@ -84,15 +107,21 @@ inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128]) { edid[18]=1;edid[19]=4;edid[20]=0xA0; // digital RGB8, transport unspecified edid[21]=static_cast((m.widthMM+5)/10); edid[22]=static_cast((m.heightMM+5)/10); - edid[23]=0xFF;edid[24]=2; // gamma unspecified, preferred DTD, fixed frequency + edid[23]=0xFF;edid[24]=extended?0:2; // preferred timing lives in DTD or DisplayID for(unsigned i=38;i<54;++i)edid[i]=1; // no standard/established timings - auto d=edid+54;d[0]=clock;d[1]=clock>>8; - d[2]=m.width;d[3]=m.hblank;d[4]=((m.width>>8)<<4)|(m.hblank>>8); - d[5]=m.height;d[6]=m.vblank;d[7]=((m.height>>8)<<4)|(m.vblank>>8); - d[8]=m.hfront;d[9]=m.hsync;d[10]=((m.vfront&15)<<4)|(m.vsync&15); - d[11]=((m.hfront>>8)<<6)|((m.hsync>>8)<<4)|((m.vfront>>4)<<2)|(m.vsync>>4); - d[12]=m.widthMM;d[13]=m.heightMM;d[14]=((m.widthMM>>8)<<4)|(m.heightMM>>8); - d[17]=0x18; // separate negative sync, matching the fixed Apple timing + auto d=edid+54; + if(extended) { + d[3]=0x10; // unused base descriptor; exact timing is in extension 1 + edid[126]=1; + } else { + d[0]=clock;d[1]=clock>>8; + d[2]=m.width;d[3]=m.hblank;d[4]=((m.width>>8)<<4)|(m.hblank>>8); + d[5]=m.height;d[6]=m.vblank;d[7]=((m.height>>8)<<4)|(m.vblank>>8); + d[8]=m.hfront;d[9]=m.hsync;d[10]=((m.vfront&15)<<4)|(m.vsync&15); + d[11]=((m.hfront>>8)<<6)|((m.hsync>>8)<<4)|((m.vfront>>4)<<2)|(m.vsync>>4); + d[12]=m.widthMM;d[13]=m.heightMM;d[14]=((m.widthMM>>8)<<4)|(m.heightMM>>8); + d[17]=0x18; // separate negative sync, matching the fixed Apple timing + } edid[75]=0xFC;memcpy(edid+77,"MIPI DSI\n ",13); edid[93]=0x10;edid[111]=0x10; // unused descriptor slots uint8_t sum=0;for(unsigned i=0;i<127;++i)sum+=edid[i];edid[127]=static_cast(0-sum); diff --git a/WhateverXe/kern_dsi_host.hpp b/WhateverXe/kern_dsi_host.hpp index e81a242a..3082e5db 100644 --- a/WhateverXe/kern_dsi_host.hpp +++ b/WhateverXe/kern_dsi_host.hpp @@ -37,7 +37,7 @@ struct Host { return true; } template bool credits(IO &io,unsigned h,unsigned p,unsigned budgetUs=100) { - if(budgetUs>100000 || budgetUs%10)return fail(HostError::Invalid); + if(budgetUs>200000 || budgetUs%10)return fail(HostError::Invalid); for(unsigned us=0;;us+=10) { if(!ready(io))return false; uint32_t status=0; @@ -49,13 +49,13 @@ struct Host { io.delayUs(10);waitedUs+=10; } } - template bool send(IO &io,uint8_t type,Span data) { + template bool send(IO &io,uint8_t type,Span data,unsigned creditBudgetUs=100) { const auto p=packet(type,data); if(!p.valid)return fail(HostError::Invalid); if(!ready(io))return false; // Reserve sufficient credit before the first payload write. The caller // serializes the only producer; hardware can only return more credit. - if(!credits(io,1,unsigned((p.payload.size+3)/4)))return false; + if(!credits(io,1,unsigned((p.payload.size+3)/4),creditBudgetUs))return false; for(size_t at=0;at bool run(IO &io,const Sequences &sequences,unsigned id) { - if(!sequences.valid() || id>=12 || !sequences.sequence[id].data)return fail(HostError::Invalid); + template bool run(IO &io,const Sequences &sequences,unsigned id,unsigned creditBudgetUs=100) { + if(!sequences.valid() || id>=12 || creditBudgetUs>200000 || creditBudgetUs%10)return fail(HostError::Invalid); + if(!sequences.sequence[id].data) + return optionalResetSequence(id) ? drain(io,creditBudgetUs) : fail(HostError::Invalid); const auto bytes=sequences.sequence[id];Sequences check; if(validateSequence(bytes,check)!=SequenceStatus::Valid)return fail(HostError::Invalid); sequenceId=id; if(!ready(io))return false; for(size_t at=0;at GPIOResult gpio(IO &io,unsigned role,unsigned value) { GPIOResult r;r.role=role;r.value=value; - if(role>2 || value>1 || !io.ready())return r; - r.reg=role==0?0xC4030:0xC7204; + if(!nativePanelGPIO(role) || value>1 || !io.ready())return r; + const unsigned bank=role/5,function=role%5; + r.reg=function==0?0xC4030:0xC7204+0x100*bank; if(!io.readable(r.reg))return r; r.before=io.read(r.reg);if(r.before==UINT32_MAX)return r; - const uint32_t mask=role==0?12U:role==1?1U:4U; - const uint32_t bits=value?(role==1?1U:4U):0; + const uint32_t mask=function==0?(12U<<(4*bank)):function==1?1U:4U; + const uint32_t bits=value?(function==0?(4U<<(4*bank)):function==1?1U:4U):0; // HPD status is W1C. Reset must not acknowledge another port's event. - const auto next=(r.before&~(mask|(role==0?0x3333U:0U)))|bits; + const auto next=(r.before&~(mask|(function==0?0x3333U:0U)))|bits; if(!io.ready())return r; io.write(r.reg,next);++r.writes; r.after=io.read(r.reg); @@ -34,14 +35,24 @@ struct Result { Host host {};Readout source;bool attempted {}; bool complete() const {return stage==Stage::Complete && error==Error::None;} }; +// LP commands submitted during video can wait for a blanking opportunity. +// Bound each credit wait by two actual frame periods, not the stopped host's +// 100 us allowance. Keep completion/drain checks before delays and GPIOs. +inline unsigned videoCreditBudgetUs(const Mode &m) { + if(!m.valid())return 0; + const uint64_t period=(uint64_t(m.htotal())*m.vtotal()*1000+m.clockKHz-1)/m.clockKHz; + if(period<1000 || period>100000)return 0; + return unsigned((2*period+9)/10*10); +} // Panel half of a serialized runtime shutdown. Host/IRQ/clock/memory remain // owned by the caller. Reset/power-off must contain no packets after ULPS. struct Shutdown { Host host {true};unsigned stage {};bool complete {}; bool preflight(const Profile &p) { - if(!p.valid())return false; + if(!p.valid() || !videoCreditBudgetUs(p.mode))return false; constexpr unsigned ids[]={7,4,5,11}; for(auto id:ids) { + if(optionalResetSequence(id) && !p.sequences.sequence[id].data)continue; Sequences n; if(validateSequence(p.sequences.sequence[id],n)!=SequenceStatus::Valid || n.delayUs>5000000 || ((id==5 || id==11) && n.packets))return false; @@ -55,7 +66,7 @@ struct Shutdown { explicit Port(IO &q):CommandPort{q} {} bool gpio(unsigned role,unsigned value){return this->io.gpio(role,value);} } port{io}; - if(next==1)return host.run(port,p.sequences,7) && host.delay(port,p.ppsUs[2]); + if(next==1)return host.run(port,p.sequences,7,videoCreditBudgetUs(p.mode)) && host.delay(port,p.ppsUs[2]); if(next==2)return host.run(port,p.sequences,4); if(next==3) { complete=host.run(port,p.sequences,5) && host.delay(port,p.ppsUs[3]) && @@ -84,6 +95,7 @@ template void cycle(IO &io,const Readout &link,const P !(planeControl&(1U<<31)) || (surface&4095) || !profile.sequences.valid())return; constexpr unsigned required[]={1,2,4,5,6,7,10,11}; for(auto id:required) { + if(optionalResetSequence(id) && !profile.sequences.sequence[id].data)continue; Sequences count; if(validateSequence(profile.sequences.sequence[id],count)!=SequenceStatus::Valid || count.delayUs>5000000)return; } @@ -127,7 +139,9 @@ template void cycle(IO &io,const Readout &link,const P bool gpio(unsigned role,unsigned value){return this->io.gpio(role,value);} } port{io}; const auto sequence=[&](unsigned id) { - if(!r.host.run(port,profile.sequences,id)){r.error=Error::Sequence;return false;}return true; + const bool video=r.stage==Stage::BacklightOff || r.stage==Stage::TranscoderOn || r.stage==Stage::BacklightOn; + const auto budget=video?videoCreditBudgetUs(profile.mode):100; + if(!r.host.run(port,profile.sequences,id,budget)){r.error=Error::Sequence;return false;}return true; }; const auto delay=[&](unsigned index) { if(!r.host.delay(port,profile.ppsUs[index])){r.error=Error::Sequence;return false;}return true; diff --git a/WhateverXe/kern_dsi_profile.hpp b/WhateverXe/kern_dsi_profile.hpp index 45e007f2..6ca45e11 100644 --- a/WhateverXe/kern_dsi_profile.hpp +++ b/WhateverXe/kern_dsi_profile.hpp @@ -49,7 +49,10 @@ inline Profile parseProfile(Span input,bool allowFinalEndQuirk=false) { const auto b=p+bo;const size_t bh=profileU16(b+18),bs=profileU16(b+20); if(memcmp(b,"BIOS_DATA_BLOCK ",16) || bh<22 || bsvs-bo)return failure; r.version=profileU16(b+16); - if(r.version!=256){failure.status=ProfileStatus::Version;return failure;} + // The consumed generic DTD, MIPI and 8-bit backlight fields share + // this layout from BDB 236 through 256. Child records carry their own + // stride (39 before 256, 40 in 256); do not bind panels to one BIOS. + if(r.version<236 || r.version>256){failure.status=ProfileStatus::Version;return failure;} for(size_t i=0;i namespace XELP { namespace DSI { struct Span { const uint8_t *data {}; size_t size {}; }; +inline bool nativePanelGPIO(unsigned role) {return role<=2 || (role>=5 && role<=7);} +// VBT may place reset edges in INIT_OTP instead of separate reset sequences. +inline bool optionalResetSequence(unsigned id) {return id==1 || id==5;} enum class SequenceStatus : uint32_t { Valid, Bounds, Version, Panel, Duplicate, Sequence, Operation, Packet, GPIO }; @@ -46,7 +49,7 @@ inline SequenceStatus validateSequence(Span bytes, Sequences &counts) { case 3: // v3 native display GPIO: index, GPIO role, value. The index // is retained in the bytes; it is not a gpiochip identifier. - if(n!=3 || p[1]>2 || p[2]>1)return SequenceStatus::GPIO; + if(n!=3 || !nativePanelGPIO(p[1]) || p[2]>1)return SequenceStatus::GPIO; ++counts.gpios;break; default:return SequenceStatus::Operation; } diff --git a/tests/dsi_apple_adapter.py b/tests/dsi_apple_adapter.py index 981dc7c3..4b7566d1 100644 --- a/tests/dsi_apple_adapter.py +++ b/tests/dsi_apple_adapter.py @@ -151,6 +151,11 @@ uint8_t expected[128];assert(makePanelEDID(p.mode,expected) && !memcmp(edid,expected,128)); length=127;memset(edid,0xCC,128);assert(XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr)==kIOReturnBadArgument && !length && edid[0]==0xCC); length=128;assert(XE::wrapDSIDDC(fb,2,edid,&length,0,nullptr)==kIOReturnUnsupported && !length); + {const auto saved=dsiFixedMode.mode;dsiFixedMode.mode={136887,1200,68,40,12,1920,239,205,2,141,226}; + length=128;assert(!XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr) && edid[126]==1); + length=128;assert(!XE::wrapDSIDDC(fb,2,edid,&length,0,nullptr) && length==128 && edid[0]==0x70); + uint8_t block[128];assert(makePanelEDID(dsiFixedMode.mode,block,2) && !memcmp(block,edid,128)); + dsiFixedMode.mode=saved;length=128;assert(!XE::wrapDSIDDC(fb,1,edid,&length,0,nullptr));} length=128;assert(XE::wrapDSIDDC(fb,1,edid,&length,1,nullptr)==kIOReturnUnsupported && !length); assert(XE::wrapDSIDDC(peer,2,edid,&length,1,testPath)==9 && length==128 && nativeDDCCalls==1); xe.adlnOutputWiring.dsi=0;assert(XE::wrapDSIDDC(fb,2,edid,&length,1,testPath)==9 && nativeDDCCalls==2);xe.adlnOutputWiring.dsi=1; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 7b1130c5..3ca8e5d4 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -63,8 +63,22 @@ int main() { assert(d[12]==86 && d[13]==154 && !d[14] && d[17]==0x18); for(unsigned i=35;i<38;i++)assert(!edid[i]);for(unsigned i=38;i<54;i++)assert(edid[i]==1); assert(edid[75]==0xFC && !memcmp(edid+77,"MIPI DSI\n ",13)); - auto bad=p.mode;bad.width=4096;assert(!AppleAdapter::makePanelEDID(bad,edid)); - bad=p.mode;bad.vfront=64;assert(!AppleAdapter::makePanelEDID(bad,edid)); + auto wide=p.mode;wide.width=4096;assert(AppleAdapter::makePanelEDID(wide,edid) && edid[126]==1); + auto tall=p.mode;tall.vfront=64;assert(AppleAdapter::makePanelEDID(tall,edid) && edid[126]==1); + Mode extended{136887,1200,68,40,12,1920,239,205,2,141,226}; + assert(AppleAdapter::makePanelEDID(extended,edid) && edid[126]==1 && !edid[54] && edid[57]==0x10); + sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); + uint8_t ext[128];assert(AppleAdapter::makePanelEDID(extended,ext,2)); + sum=0;for(auto b:ext)sum+=b;assert(!(sum&255)); + sum=0;for(unsigned i=1;i<=28;++i)sum+=ext[i];assert(!(sum&255)); + assert(ext[0]==0x70 && ext[1]==0x12 && ext[2]==23 && ext[5]==3 && ext[7]==20); + auto u16=[](const uint8_t *b){return unsigned(b[0])|(unsigned(b[1])<<8);}; + const auto e=ext+8; + assert((1+e[0]+(unsigned(e[1])<<8)+(unsigned(e[2])<<16))*10==136890 && e[3]==0x80); + const unsigned expected[]={1200,68,40,12,1920,239,205,2}; + for(unsigned i=0;i<8;++i)assert(u16(e+4+2*i)+1==expected[i]); + assert(!AppleAdapter::makePanelEDID(p.mode,ext,2) && !AppleAdapter::makePanelEDID(extended,ext,3)); + auto bad=extended;bad.vfront=32769;bad.vblank=40000;assert(!AppleAdapter::makePanelEDID(bad,edid)); bad=p.mode;bad.clockKHz=10001;assert(AppleAdapter::makePanelEDID(bad,edid)); bad.clockKHz=10005;assert(!AppleAdapter::makePanelEDID(bad,edid)); p.mode.hsync=46;assert(!AppleAdapter::makeTiming(p,t)&&!t.pixelClock); diff --git a/tests/dsi_host.cpp b/tests/dsi_host.cpp index daf8d7df..c316f7f0 100644 --- a/tests/dsi_host.cpp +++ b/tests/dsi_host.cpp @@ -16,7 +16,12 @@ struct Mock { void header(uint32_t v){h.push_back(v);} void payload(uint32_t v){p.push_back(v);} void delayUs(uint32_t us){elapsed+=us;} - bool gpio(uint8_t role,uint8_t value){assert(role<=2&&value<=1);++gpios;return gpioOK;} + bool gpio(uint8_t role,uint8_t value){assert(nativePanelGPIO(role)&&value<=1);++gpios;return gpioOK;} +}; +struct VideoMock:Mock { + uint32_t releaseAt=0; + bool credits(uint32_t &v){v=elapsed packets,gpioTrace; bool ready(){return owned && (regs[0x45404]&0x403)==0x403;} uint32_t read(uint32_t reg){ if(reg==0x6B0D0 && stuckCommand && headers)return 0; + if(reg==0x6B0D0 && elapsed({14,2,3,15})); + assert(f.io.regs[0xC7304]==4 && f.io.regs[0xC4030]==0xA7); + } {Fixture f;stop(f);} for(bool drop:{false,true}){Fixture f;Backlight light;Panel::RetainedHost host; f.io.regs[Backlight::Control]=0x80000000;f.io.regs[Backlight::Period]=480;f.io.regs[Backlight::Duty]=154; diff --git a/tests/dsi_profile.cpp b/tests/dsi_profile.cpp index 8c79eeaa..c1e6e6a1 100644 --- a/tests/dsi_profile.cpp +++ b/tests/dsi_profile.cpp @@ -11,10 +11,10 @@ void add(Bytes &b,uint8_t id,const Bytes &data) { b.push_back(id);b.push_back(data.size());b.push_back(data.size()>>8); b.insert(b.end(),data.begin(),data.end()); } -Bytes fixture() { +Bytes fixture(unsigned version=256,bool inlineReset=false) { Bytes b(70);memcpy(b.data(),"$VBT",4);put(b,22,48);put(b,28,48,4); - memcpy(b.data()+48,"BIOS_DATA_BLOCK ",16);put(b,64,256);put(b,66,22); - Bytes child(45);child[4]=40;put(child,7,0x1400);child[21]=21;add(b,2,child); + memcpy(b.data()+48,"BIOS_DATA_BLOCK ",16);put(b,64,version);put(b,66,22); + Bytes child(version<256?44:45);child[4]=child.size()-5;put(child,7,0x1400);child[21]=21;add(b,2,child); add(b,40,Bytes{0}); Bytes config(792);put(config,2,0x11190,4);put(config,6,12); config[93]=8;config[94]=50;put(config,98,310);config[103]=110;config[104]=50;put(config,105,200); @@ -27,6 +27,7 @@ Bytes fixture() { Bytes seq(10);seq[0]=3;seq[5]=0; // Empty, terminated sequences prove structure without firmware commands. for(unsigned id:{1U,2U,4U,5U,6U,7U,10U,11U}) { + if(inlineReset && (id==1 || id==5))continue; seq.push_back(id);seq.push_back(1);seq.insert(seq.end(),4,0); } seq.push_back(0);put(seq,6,seq.size()-10,4);put(seq,1,seq.size()-5,4);add(b,53,seq); @@ -40,6 +41,11 @@ int main(int argc,char **argv) { assert(r.valid()&&r.mode.htotal()==1126&&r.mode.vtotal()==2002&&r.pwmHz==40000); assert(r.mode.hsyncPositive && r.mode.vsyncPositive); assert(r.clockPrepare==50&&r.clockPrepareZero==310&&r.hsPrepareZero==200&&r.ppsUs[4]==500000); + for(unsigned version=236;version<=256;++version) { + auto compatible=fixture(version,true);auto p=parse(compatible); + assert(p.valid() && p.version==version && !p.sequences.sequence[1].data && !p.sequences.sequence[5].data); + } + reject(fixture(235));reject(fixture(257)); const auto link=linkPlan(r);assert(link.valid&&link.afeKHz==811530&&link.portKHz==162306); assert(link.escapeDivider==56&&link.phyEscapeDivider==3); // Linux v7.0 readback, reserved bits omitted for the escape divider. diff --git a/tests/dsi_sequence.cpp b/tests/dsi_sequence.cpp index 9d2463dd..3e42d96d 100644 --- a/tests/dsi_sequence.cpp +++ b/tests/dsi_sequence.cpp @@ -27,6 +27,12 @@ int main(int argc,char **argv) { bad=ops;bad.pop_back();rejected(block(panel(bad))); // no operation END bad=ops;bad.push_back(0);rejected(block(panel(bad))); // bytes after END bad=ops;bad[16]=3;rejected(block(panel(bad))); // unimplemented GPIO role + for(unsigned role:{0U,1U,2U,5U,6U,7U}) { + auto gpio=ops;gpio[16]=role;assert(parse(block(panel(gpio))).valid()); + } + for(unsigned role:{3U,4U,8U,9U,255U}) { + auto gpio=ops;gpio[16]=role;rejected(block(panel(gpio))); + } auto data=panel(ops);data.pop_back();auto second=panel(ops);data.insert(data.end(),second.begin(),second.end()); rejected(block(data)); // duplicate sequence auto duplicate=b;duplicate.insert(duplicate.end(),b.begin()+5,b.end()); From 09825bf4a6031865833e8d4c70da066f19021b62 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:17:01 +0900 Subject: [PATCH 126/139] Prepare DSI external display clock without host trial dependency --- CHANGELOG.md | 2 ++ README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_adapter_boot.inc | 10 +++++++- WhateverXe/kern_dsi_adapter_diagnostics.inc | 13 +++++----- WhateverXe/kern_xe.hpp | 2 +- tests/dsi_resources.py | 28 ++++++++++++++++++--- 7 files changed, 46 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0831da30..162abb61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Prepare the shared display clock automatically for MIPI DSI with external outputs, avoiding startup and hotplug freezes when the optional host trial is disabled. + - Accept compatible MIPI DSI firmware layouts before BDB 256, inline panel-reset sequences, and the second native panel GPIO bank. - Publish MIPI DSI timings that exceed base EDID limits through a DisplayID extension, preserving the firmware link timing. - Allow frame-period-based command completion waits while MIPI DSI video is active, fixing display wake timeouts for panels with a separate display-on sequence. diff --git a/README.md b/README.md index 8ed63f2f..68b5b1cd 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GO Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. -DSI profiles are read from firmware VBT (BDB 236–256), including native panel GPIO banks 1 and 2 and reset operations embedded in the initialization sequence. Timings that exceed the base EDID limits are described with a DisplayID extension; the physical panel timing is preserved. The existing single-link, four-lane RGB888 video-mode restrictions still apply. +DSI profiles are read from firmware VBT (BDB 236–256), including native panel GPIO banks 1 and 2 and reset operations embedded in the initialization sequence. Timings that exceed the base EDID limits are described with a DisplayID extension; the physical panel timing is preserved. The existing single-link, four-lane RGB888 video-mode restrictions still apply. When external outputs are enabled, adoption prepares their shared display-clock requirement before native modes start; this does not require `-wex-dsi-host-test`. ### DVMT diff --git a/README_ja.md b/README_ja.md index 6e163b79..bac4f7ba 100644 --- a/README_ja.md +++ b/README_ja.md @@ -112,7 +112,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 -DSI profileはfirmware VBT(BDB 236–256)から読み取り、native panel GPIOの1組目・2組目と、初期化列に組み込まれたreset操作に対応します。基本EDIDの表現範囲を超えるタイミングはDisplayID拡張で記述し、物理パネルのタイミングは保持します。single-link・4-lane・RGB888 video modeの制約は維持します。 +DSI profileはfirmware VBT(BDB 236–256)から読み取り、native panel GPIOの1組目・2組目と、初期化列に組み込まれたreset操作に対応します。基本EDIDの表現範囲を超えるタイミングはDisplayID拡張で記述し、物理パネルのタイミングは保持します。single-link・4-lane・RGB888 video modeの制約は維持します。 外部出力が有効な場合は、native mode開始前に必要な共有表示clockを準備します。この準備に `-wex-dsi-host-test` は不要です。 ### DVMT diff --git a/WhateverXe/kern_dsi_adapter_boot.inc b/WhateverXe/kern_dsi_adapter_boot.inc index a2fe43ad..080ff502 100644 --- a/WhateverXe/kern_dsi_adapter_boot.inc +++ b/WhateverXe/kern_dsi_adapter_boot.inc @@ -289,10 +289,18 @@ bool XE::bindDSIResources(IOService *controller) { } IOSimpleLockUnlockEnableInterrupt(irqLock,saved); } + // External hotplug may request a different CDCLK divider. Prepare it + // before native display users start; this is not an opt-in host trial. + bool prepareExternalClock=false; + if(ready && !dsiPanelOnly && Display13FBTopology::multiOutputWiring(adlnOutputWiring)) { + const auto current=Display13Clock::read(io); + ready=current.status==Display13Clock::Status::Complete; + prepareExternalClock=ready && current.nativeHz()!=current.nativeMaxHz(); + } IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); if(ready)dsiPublishIRQLive(); if(ready && dsiCommandTest)ready=probeDSICommands(controller); - if(ready && dsiPanelTest)ready=probeDSIPanel(controller); + if(ready && (dsiPanelTest || prepareExternalClock))ready=probeDSIPanel(controller,prepareExternalClock); if(ready) { IOLockLock(c.lock); using Light=XELP::DSI::Backlight; diff --git a/WhateverXe/kern_dsi_adapter_diagnostics.inc b/WhateverXe/kern_dsi_adapter_diagnostics.inc index e506b8c3..af8e1ed5 100644 --- a/WhateverXe/kern_dsi_adapter_diagnostics.inc +++ b/WhateverXe/kern_dsi_adapter_diagnostics.inc @@ -68,7 +68,8 @@ bool XE::probeDSICommands(IOService *controller) { // Bootstrap-only panel sequence experiment. By default retain host/PHY/PLL; // the optional host trial rebuilds them before panel INIT_OTP. Neither path // releases shared resources or enables runtime power transitions. -bool XE::probeDSIPanel(IOService *controller) { +bool XE::probeDSIPanel(IOService *controller,bool prepareExternalClock) { + const bool restart=dsiHostTest || prepareExternalClock; auto &c=display13FBPower;auto &clock=comboClockTransaction;auto &irq=display13FBIRQ; if(!controller || !c.lock || !clock.lock || !irq.lock)return false; IORecursiveLockLock(clock.lock);IOLockLock(c.lock); @@ -110,7 +111,7 @@ bool XE::probeDSIPanel(IOService *controller) { return lastGPIO.complete; } void delayUs(unsigned us){IODelay(us);} - } io{c.map,owned,irq.lock,dsiHostTest,dsiBoot.source.identity.pll}; + } io{c.map,owned,irq.lock,restart,dsiBoot.source.identity.pll}; Display13Clock::Change bootCDClock; struct Lifecycle { bool enabled,prepareExternal;XELPComboClock::ClockAllocation &allocation;uintptr_t owner; @@ -144,7 +145,7 @@ bool XE::probeDSIPanel(IOService *controller) { if(!XELP::DSI::Restart::start(io,allocation,owner,state))return false; source=XELP::DSI::Restart::expected(source,state);return source.complete(); } - } lifecycle{dsiHostTest,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; + } lifecycle{restart,!dsiPanelOnly,clock.allocation,reinterpret_cast(controller),dsiHostRestart,bootCDClock}; uint64_t record[32] {1}; record[1]=io.ready() && !dsiPanelTrial.attempted && dsiProfile.valid() && !dsiCommandHost.faulted && !dsiCommandHost.pending && @@ -175,14 +176,14 @@ bool XE::probeDSIPanel(IOService *controller) { record[17]=io.lastGPIO.role;record[18]=io.lastGPIO.value;record[19]=io.lastGPIO.complete; const bool complete=record[1] && r.complete() && record[30];record[31]=complete; const auto &host=dsiHostRestart;const auto &p=host.config; - uint64_t hostRecord[20] {1,dsiHostTest,static_cast(host.stage),static_cast(host.error), + uint64_t hostRecord[20] {1,restart,static_cast(host.stage),static_cast(host.error), host.reg,host.value,host.writes,host.waitUs,host.stopped,host.started,host.pll, p.pll.cfg0(),p.pll.cfg1(),p.link.clockTiming,p.link.dataTiming,p.hsTimeout,p.lpTimeout,p.taTimeout, record[30],complete}; IOLockUnlock(c.lock);IORecursiveLockUnlock(clock.lock); IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIPanelTrial",record,sizeof(record)); - if(dsiHostTest)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); - if(dsiHostTest && !dsiPanelOnly) + if(restart)IORegistryEntry::getRegistryRoot()->setProperty("WEXDSIHostRestart",hostRecord,sizeof(hostRecord)); + if(restart && !dsiPanelOnly) IORegistryEntry::getRegistryRoot()->setProperty("N97FBDSIBootCDClock",&bootCDClock,sizeof(bootCDClock)); return complete; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 9238f390..bbf4849a 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -272,7 +272,7 @@ class XE { XELP::DSI::Panel::Shutdown dsiPanelStop; XELP::DSI::Restart::State dsiHostRestart; XELP::DSI::Panel::Result dsiPanelTrial; - bool probeDSIPanel(IOService *); + bool probeDSIPanel(IOService *,bool prepareExternalClock=false); XELP::DSI::Host dsiCommandHost; bool probeDSICommands(IOService *); mach_vm_address_t orgDSIBacking[2] {}; diff --git a/tests/dsi_resources.py b/tests/dsi_resources.py index 721da04c..489b45a2 100644 --- a/tests/dsi_resources.py +++ b/tests/dsi_resources.py @@ -13,6 +13,8 @@ #include "kern_dsi_backlight.hpp" #include "kern_display13_power_bridge.hpp" #include "kern_display13_fb_irq.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_display13_clock.hpp" #include "kern_xelp_combo_clock.hpp" #include #include @@ -52,10 +54,11 @@ unsigned published=0; void dsiPublishIRQLive(){assert(lockLevel==0 && display13FBIRQ.controller && display13FBIRQ.lock);++published;} struct XE { - bool dsiPanelTest=false;bool probeDSIPanel(IOService*){assert(lockLevel==0);return true;} + bool dsiPanelTest=false,dsiPanelOnly=false,cycleOK=true;unsigned cycles=0;bool externalPreparation=false; + bool probeDSIPanel(IOService*,bool prepare){assert(lockLevel==0);++cycles;externalPreparation=prepare;return cycleOK;} bool dsiCommandTest=false;bool probeDSICommands(IOService*){assert(lockLevel==0);return true;} bool display13FBIRQReady=true,dsiNativeWriterHooksReady=true,dsiResourcesReady=false;IOService *adlnTopologyController; - struct {unsigned dsi=1;} adlnOutputWiring; + Display13OutputWiring adlnOutputWiring; Profile dsiProfile;Boot dsiBoot;GGTTMode dsiGGTT;Backlight dsiBacklight; void publishDSIBacklight(){assert(lockLevel==0);} bool bindDSIResources(IOService *); @@ -66,7 +69,7 @@ auto ptr=&irqLock;memcpy(controller.bytes+0x90,&ptr,sizeof(ptr)); auto &c=display13FBPower;auto &irq=display13FBIRQ;auto &clock=comboClockTransaction; c.lock=&ownerLock;c.controller=&controller;c.map=↦clock.lock=&clockLock; - XE xe;xe.adlnTopologyController=&controller;xe.dsiProfile=profile();xelpMemory=map.memory; + XE xe;xe.adlnOutputWiring.dsi=1;xe.adlnOutputWiring.valid=true;xe.adlnTopologyController=&controller;xe.dsiProfile=profile();xelpMemory=map.memory; xe.dsiBoot=captureBoot(map,AlderLakeN::readDisplay(map,true),xe.dsiProfile,map.memory,true); assert(verifyBootPrefix(map,xe.dsiBoot,map.memory)); xe.dsiProfile.precision=8;xe.dsiProfile.minimum=6; @@ -101,6 +104,25 @@ assert(!xe.bindDSIResources(&controller) && !lockLevel && map.writes==std::vector{0x42084}); assert(xe.dsiGGTT.ready() && xe.dsiGGTT.writes==1 && xe.dsiBoot.dpt==(1U<<30)); assert(b.pgRefs[0]==1 && b.ddiRefs[0]==1); // publication did not acquire/release someone else's lease + // Actual production dispatch: future hotplug requires preparation even + // without a host-test argument; panel-only and an already-high clock do not. + for(unsigned scenario=0;scenario<6;++scenario) { + clock.allocation={};irq={};c.transcoders={};c.bootDSI={};c.memoryFaulted=false; + xe.dsiResourcesReady=false;xe.dsiGGTT={};xe.dsiBoot=initialBoot;xe.cycles=0;xe.externalPreparation=false; + xe.adlnOutputWiring.hdmi=scenario==0?2:0;xe.adlnOutputWiring.ddc[1]=scenario==0?2:0; + xe.adlnOutputWiring.dp=xe.adlnOutputWiring.typeC=scenario==0?0:8; + xe.dsiPanelOnly=scenario==2;xe.cycleOK=scenario!=4; + map=Map{};map.regs[0x45454]=3;map.regs[0x42000]=0x8E380000; + map.regs[Display13Clock::DSSM]=2U<<29;map.regs[Display13Clock::Squash]=0; + map.regs[Display13Clock::PLL]=scenario==3?0xC0000022:0xC000000E; + map.regs[Display13Clock::Control]=scenario==3?0x380518:0x780164; + if(scenario==5)map.regs[Display13Clock::PLL]=UINT32_MAX; + assert(xe.bindDSIResources(&controller)==(scenario!=4 && scenario!=5)); + const bool needsCycle=scenario==0 || scenario==1 || scenario==4; + assert(xe.cycles==unsigned(needsCycle) && xe.externalPreparation==needsCycle && !lockLevel); + } + xe.adlnOutputWiring.hdmi=xe.adlnOutputWiring.ddc[1]=xe.adlnOutputWiring.dp=xe.adlnOutputWiring.typeC=0; + xe.dsiPanelOnly=false;xe.cycleOK=true; for(bool lost:{false,true}){ clock.allocation={};irq={};c.transcoders={};c.bootDSI={};c.memoryFaulted=false; xe.dsiResourcesReady=false;xe.dsiGGTT={};xe.dsiBoot=initialBoot; From 47fbc27149560f97bd04710aee0752a773107984 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 1 Nov 2026 21:41:58 +0900 Subject: [PATCH 127/139] Fix DSI VBT strides and qualify compatible profiles by capabilities --- CHANGELOG.md | 4 ++ README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_dsi_apple_timing.hpp | 10 +++-- WhateverXe/kern_dsi_host.hpp | 8 ++-- WhateverXe/kern_dsi_math.hpp | 13 ++++-- WhateverXe/kern_dsi_panel.hpp | 14 +++---- WhateverXe/kern_dsi_profile.hpp | 31 ++++++++------- WhateverXe/kern_dsi_sequence.hpp | 5 ++- tests/dsi_apple_timing.cpp | 6 +++ tests/dsi_host.cpp | 11 +++++- tests/dsi_panel.py | 12 ++++++ tests/dsi_profile.cpp | 59 ++++++++++++++++++++++------ tests/dsi_restart.py | 8 ++++ 14 files changed, 132 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 162abb61..cac72561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Fix MIPI DSI brightness decoding for nonzero VBT panel indices and accept extended generic-DTD records using their declared stride. +- Accept compatible later BDB layouts and omitted panel lifecycle sequences, following Intel's Linux VBT interpretation while retaining command-drain and ownership checks. +- Replace fixed DSI pixel/port-clock ceilings with PLL and register feasibility checks; describe higher clocks with DisplayID when needed. + - Prepare the shared display clock automatically for MIPI DSI with external outputs, avoiding startup and hotplug freezes when the optional host trial is disabled. - Accept compatible MIPI DSI firmware layouts before BDB 256, inline panel-reset sequences, and the second native panel GPIO bank. diff --git a/README.md b/README.md index 68b5b1cd..79a28e3e 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ The optional `-wex-dsi-adopt` argument selects experimental MIPI DSI firmware-GO Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. -DSI profiles are read from firmware VBT (BDB 236–256), including native panel GPIO banks 1 and 2 and reset operations embedded in the initialization sequence. Timings that exceed the base EDID limits are described with a DisplayID extension; the physical panel timing is preserved. The existing single-link, four-lane RGB888 video-mode restrictions still apply. When external outputs are enabled, adoption prepares their shared display-clock requirement before native modes start; this does not require `-wex-dsi-host-test`. +DSI profiles are read from firmware VBT (BDB 236 or later with compatible field layouts), including native panel GPIO banks 1 and 2 and reset operations embedded in the initialization sequence. Timings that exceed the base EDID limits are described with a DisplayID extension; the physical panel timing is preserved. Declared record strides and optional lifecycle sequences are supported. Pixel clocks are qualified by PLL, register and scanout constraints rather than a fixed 300 MHz ceiling. The existing single-link, four-lane RGB888 video-mode restrictions still apply. When external outputs are enabled, adoption prepares their shared display-clock requirement before native modes start; this does not require `-wex-dsi-host-test`. ### DVMT diff --git a/README_ja.md b/README_ja.md index bac4f7ba..7bbebda9 100644 --- a/README_ja.md +++ b/README_ja.md @@ -112,7 +112,7 @@ ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。devi 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 -DSI profileはfirmware VBT(BDB 236–256)から読み取り、native panel GPIOの1組目・2組目と、初期化列に組み込まれたreset操作に対応します。基本EDIDの表現範囲を超えるタイミングはDisplayID拡張で記述し、物理パネルのタイミングは保持します。single-link・4-lane・RGB888 video modeの制約は維持します。 外部出力が有効な場合は、native mode開始前に必要な共有表示clockを準備します。この準備に `-wex-dsi-host-test` は不要です。 +DSI profileはfirmware VBT(BDB 236以降で互換性のあるfield layout)から読み取り、native panel GPIOの1組目・2組目と、初期化列に組み込まれたreset操作に対応します。基本EDIDの表現範囲を超えるタイミングはDisplayID拡張で記述し、物理パネルのタイミングは保持します。宣言されたrecord strideと個別lifecycle sequenceの省略に対応します。pixel clockは固定300MHz上限ではなく、PLL・register・scanoutの制約で判定します。single-link・4-lane・RGB888 video modeの制約は維持します。 外部出力が有効な場合は、native mode開始前に必要な共有表示clockを準備します。この準備に `-wex-dsi-host-test` は不要です。 ### DVMT diff --git a/WhateverXe/kern_dsi_apple_timing.hpp b/WhateverXe/kern_dsi_apple_timing.hpp index 3e30cf73..55c23b0e 100644 --- a/WhateverXe/kern_dsi_apple_timing.hpp +++ b/WhateverXe/kern_dsi_apple_timing.hpp @@ -27,8 +27,10 @@ inline bool annotateMode(const Mode &m,ModeInformation &info,bool rotated=false, const auto width=drawWidth?drawWidth:(rotated?m.height:m.width); const auto height=drawHeight?drawHeight:(rotated?m.width:m.height); if(!m.valid() || info.width!=width || info.height!=height)return false; - info.refresh=static_cast((uint64_t(m.clockKHz)*1000*65536+m.htotal()*uint64_t(m.vtotal())/2)/ + const auto refresh=((uint64_t(m.clockKHz)*1000*65536+m.htotal()*uint64_t(m.vtotal())/2)/ (m.htotal()*uint64_t(m.vtotal()))); + if(refresh>UINT32_MAX)return false; + info.refresh=uint32_t(refresh); info.depth=0; // the adopted DSI surface is RGB8 only info.widthMM=static_cast(rotated?m.heightMM:m.widthMM);info.heightMM=static_cast(rotated?m.widthMM:m.heightMM); // IOGraphics: valid/safe/default + built-in + native. Acceleration is a @@ -66,13 +68,13 @@ inline bool presentationTiming(const Timing &fixed,const Timing &offered,unsigne // EDID wire layout reference: Linux v6.12 include/drm/drm_edid.h. This is not // an EDID read from the panel and does not advertise DP/HDMI, HDR or DPMS. inline bool panelNeedsDisplayID(const Mode &m) { - return m.width>4095 || m.height>4095 || m.hblank>4095 || m.vblank>4095 || + return (uint64_t(m.clockKHz)+5)/10>65535 || m.width>4095 || m.height>4095 || m.hblank>4095 || m.vblank>4095 || m.hfront>1023 || m.hsync>1023 || m.vfront>63 || m.vsync>63; } inline bool makePanelEDID(const Mode &m,uint8_t (&edid)[128],unsigned block=1) { memset(edid,0,sizeof(edid)); - const uint32_t clock=(m.clockKHz+5)/10; - if(!m.valid() || !clock || clock>65535 || + const uint32_t clock=uint32_t((uint64_t(m.clockKHz)+5)/10); + if(!m.valid() || !clock || clock>0x1000000 || m.widthMM>2550 || m.heightMM>2550)return false; const bool extended=panelNeedsDisplayID(m); // DisplayID Type I has 15-bit sync offsets, stored minus one. Preserve diff --git a/WhateverXe/kern_dsi_host.hpp b/WhateverXe/kern_dsi_host.hpp index 3082e5db..6c2e84ae 100644 --- a/WhateverXe/kern_dsi_host.hpp +++ b/WhateverXe/kern_dsi_host.hpp @@ -89,12 +89,12 @@ struct Host { return ready(io); } template bool run(IO &io,const Sequences &sequences,unsigned id,unsigned creditBudgetUs=100) { - if(!sequences.valid() || id>=12 || creditBudgetUs>200000 || creditBudgetUs%10)return fail(HostError::Invalid); - if(!sequences.sequence[id].data) - return optionalResetSequence(id) ? drain(io,creditBudgetUs) : fail(HostError::Invalid); + if(faulted)return false; + if(!sequences.valid() || !id || id>=12 || creditBudgetUs>200000 || creditBudgetUs%10)return fail(HostError::Invalid); + sequenceId=id;operationOffset=0; + if(absentSequence(sequences.sequence[id]))return drain(io,creditBudgetUs); const auto bytes=sequences.sequence[id];Sequences check; if(validateSequence(bytes,check)!=SequenceStatus::Valid)return fail(HostError::Invalid); - sequenceId=id; if(!ready(io))return false; for(size_t at=0;atUINT32_MAX)return r; + r.afeKHz=uint32_t(afe);r.portKHz=r.afeKHz/5; // ADL-P/N escape word clock divider must be odd, >=3, <=20 MHz. - auto word=(r.afeKHz+159999)/160000;word|=1;if(word<3)word=3; - r.escapeDivider=word*8;r.phyEscapeDivider=(word-1)/2; + auto word=(afe+159999)/160000;word|=1;if(word<3)word=3; + // Both escape-divider registers have nine bits. PLL feasibility and + // scanout bandwidth are checked by their respective consumers. + if(word*8>0x1FF)return {}; + r.escapeDivider=uint32_t(word*8);r.phyEscapeDivider=uint32_t((word-1)/2); constexpr uint32_t tlpx=50; // byteClock=0: 20 MHz escape timing reference r.clockTiming=(1U<<31)|(cappedCeil(p.clockPrepare*4,tlpx,7)<<28)|(1U<<27)| (cappedCeil(p.clockPrepareZero-p.clockPrepare,tlpx,15)<<20)|(1U<<19)|(cappedCeil(p.clockPre,tlpx,3)<<16); @@ -58,7 +63,7 @@ inline uint32_t pllAFE(uint32_t cfg0,uint32_t cfg1,uint32_t referenceKHz) { } inline PLLPlan pllPlan(uint32_t portKHz,uint32_t referenceKHz) { PLLPlan r; - if(!portKHz || portKHz>600000 || (referenceKHz!=19200 && referenceKHz!=24000 && referenceKHz!=38400))return r; + if(!portKHz || (referenceKHz!=19200 && referenceKHz!=24000 && referenceKHz!=38400))return r; // Hardware divider choices, in the documented tie-breaking order. static const uint8_t dividers[]={2,4,6,8,10,12,14,16,18,20,24,28,30,32,36,40,42,44,48,50,52,54, 56,60,64,66,68,70,72,76,78,80,84,88,90,92,96,98,100,102,3,5,7,9,15,21}; diff --git a/WhateverXe/kern_dsi_panel.hpp b/WhateverXe/kern_dsi_panel.hpp index 7d776a74..42624d40 100644 --- a/WhateverXe/kern_dsi_panel.hpp +++ b/WhateverXe/kern_dsi_panel.hpp @@ -52,7 +52,7 @@ struct Shutdown { if(!p.valid() || !videoCreditBudgetUs(p.mode))return false; constexpr unsigned ids[]={7,4,5,11}; for(auto id:ids) { - if(optionalResetSequence(id) && !p.sequences.sequence[id].data)continue; + if(absentSequence(p.sequences.sequence[id]))continue; Sequences n; if(validateSequence(p.sequences.sequence[id],n)!=SequenceStatus::Valid || n.delayUs>5000000 || ((id==5 || id==11) && n.packets))return false; @@ -93,16 +93,12 @@ template void cycle(IO &io,const Readout &link,const P if((shutdown && (!shutdown->complete || shutdown->host.faulted || shutdown->host.pending)) || !profile.valid() || !link.matches(profile) || link.identity.pipe!=0 || !(planeControl&(1U<<31)) || (surface&4095) || !profile.sequences.valid())return; - constexpr unsigned required[]={1,2,4,5,6,7,10,11}; - for(auto id:required) { - if(optionalResetSequence(id) && !profile.sequences.sequence[id].data)continue; + constexpr unsigned lifecycleIds[]={1,2,3,4,5,6,7,10,11}; + for(auto id:lifecycleIds) { + if(absentSequence(profile.sequences.sequence[id]))continue; Sequences count; if(validateSequence(profile.sequences.sequence[id],count)!=SequenceStatus::Valid || count.delayUs>5000000)return; } - if(profile.sequences.sequence[3].data) { - Sequences count; - if(validateSequence(profile.sequences.sequence[3],count)!=SequenceStatus::Valid || count.delayUs>5000000)return; - } for(auto us:profile.ppsUs)if(us>5000000)return; const uint64_t period=(uint64_t(profile.mode.htotal())*profile.mode.vtotal()*1000+profile.mode.clockKHz-1)/profile.mode.clockKHz; if(period<1000 || period>100000)return; @@ -181,7 +177,7 @@ template void cycle(IO &io,const Readout &link,const P if(port.failed || r.host.pending || !held())return; r.stage=Stage::TranscoderOn; if(!write(0x7B008,link.conf) || !wait(0x7B008,0xC0000000U,0xC0000000U,10000))return; - if(profile.sequences.sequence[3].data && !sequence(3))return; + if(!sequence(3))return; r.stage=Stage::PlaneOn; if(!write(0x70180,planeControl) || !write(0x7019C,surface) || !wait(0x70180,UINT32_MAX,planeControl,0) || !frame() || diff --git a/WhateverXe/kern_dsi_profile.hpp b/WhateverXe/kern_dsi_profile.hpp index 6ca45e11..e2aa04d1 100644 --- a/WhateverXe/kern_dsi_profile.hpp +++ b/WhateverXe/kern_dsi_profile.hpp @@ -16,7 +16,7 @@ struct Mode { uint32_t htotal() const {return uint32_t(width)+hblank;} uint32_t vtotal() const {return uint32_t(height)+vblank;} bool valid() const { - return clockKHz && clockKHz<=300000 && width && height && hsync && vsync && + return clockKHz && width && height && hsync && vsync && uint32_t(hfront)+hsyncvs-bo)return failure; r.version=profileU16(b+16); // The consumed generic DTD, MIPI and 8-bit backlight fields share - // this layout from BDB 236 through 256. Child records carry their own - // stride (39 before 256, 40 in 256); do not bind panels to one BIOS. - if(r.version<236 || r.version>256){failure.status=ProfileStatus::Version;return failure;} + // this layout from BDB 236 onward. Validate known prefixes and declared + // record strides rather than rejecting later BIOS versions by number. + if(r.version<236){failure.status=ProfileStatus::Version;return failure;} for(size_t i=0;i64 || + // Intel's legacy child prefix is 33 bytes; later tails are optional here. + if(children.size<5 || children.data[4]<33 || (children.size-5)%children.data[4])return failure; unsigned mipi=0; for(size_t at=5;at255 || r.minimum>r.brightness)return failure; failure.status=ProfileStatus::Sequence; - r.sequences=parseSequences(seq,r.panel); + // An absent block specifies no panel commands; a present malformed block + // must not be treated as absence. Host/PLL/PPS sequencing still executes. + if(seq.data)r.sequences=parseSequences(seq,r.panel); + else r.sequences.status=SequenceStatus::Valid; if(!r.sequences.valid())return failure; - static const unsigned required[]={2,4,6,7,10,11}; - for(unsigned id:required) - if(!r.sequences.sequence[id].data)return failure; r.status=ProfileStatus::Valid;return r; } }} diff --git a/WhateverXe/kern_dsi_sequence.hpp b/WhateverXe/kern_dsi_sequence.hpp index 93d84e75..e450b31f 100644 --- a/WhateverXe/kern_dsi_sequence.hpp +++ b/WhateverXe/kern_dsi_sequence.hpp @@ -8,8 +8,9 @@ namespace XELP { namespace DSI { struct Span { const uint8_t *data {}; size_t size {}; }; inline bool nativePanelGPIO(unsigned role) {return role<=2 || (role>=5 && role<=7);} -// VBT may place reset edges in INIT_OTP instead of separate reset sequences. -inline bool optionalResetSequence(unsigned id) {return id==1 || id==5;} +// Firmware may combine operations in other lifecycle sequences or omit them. +// Only the fully empty span means absent; malformed spans still reject. +inline bool absentSequence(Span bytes) {return !bytes.data && !bytes.size;} enum class SequenceStatus : uint32_t { Valid, Bounds, Version, Panel, Duplicate, Sequence, Operation, Packet, GPIO }; diff --git a/tests/dsi_apple_timing.cpp b/tests/dsi_apple_timing.cpp index 3ca8e5d4..0931d659 100644 --- a/tests/dsi_apple_timing.cpp +++ b/tests/dsi_apple_timing.cpp @@ -50,6 +50,12 @@ int main() { portrait.scalerFlags=1;assert(AppleAdapter::presentationTiming(t,portrait,0)); portrait.scalerFlags=0x20;portrait.horizontalInset=4;assert(AppleAdapter::presentationTiming(t,portrait,0)); assert(!AppleAdapter::acceptsMode(0x80001002,0x80001001)); + {auto mode=p.mode;mode.clockKHz=750000;uint8_t base[128],ext[128]; + assert(AppleAdapter::makePanelEDID(mode,base) && base[126]==1); + assert(AppleAdapter::makePanelEDID(mode,ext,2)); + assert((uint32_t(ext[8])|(uint32_t(ext[9])<<8)|(uint32_t(ext[10])<<16))==74999); + for(auto bytes:{base,ext}){unsigned sum=0;for(unsigned i=0;i<128;++i)sum+=bytes[i];assert(!(sum&255));} + mode.clockKHz=UINT32_MAX;assert(!AppleAdapter::makePanelEDID(mode,base));} uint8_t edid[128];assert(AppleAdapter::makePanelEDID(p.mode,edid)); unsigned sum=0;for(auto b:edid)sum+=b;assert(!(sum&255)); assert(edid[16]==0xFF && edid[17]>=17); // Model year enables native linear brightness. diff --git a/tests/dsi_host.cpp b/tests/dsi_host.cpp index c316f7f0..b160a47e 100644 --- a/tests/dsi_host.cpp +++ b/tests/dsi_host.cpp @@ -55,17 +55,24 @@ int main(int argc,char **argv) { Mock io;Host host(true);assert(!host.run(io,s,2,budget)); assert(host.error==HostError::Invalid && io.h.empty() && !io.gpios); } - for(unsigned id:{1U,5U}) { + for(unsigned id:{1U,3U,4U,5U,6U,7U,8U,9U,10U,11U}) { Mock io;Host host(true);assert(host.run(io,s,id) && !host.pending && io.h.empty() && !io.gpios); Host disabled;assert(!disabled.run(io,s,id)); } - {Mock io;Host host(true);assert(!host.run(io,s,4));} + {Mock io;Host host(true);assert(host.send(io,5,{data,1})); + assert(host.run(io,s,4) && !host.pending && io.h.size()==2 && host.sequenceId==4);} + {Mock io;io.cancelAt=1;Host host(true);assert(!host.run(io,s,4) && host.error==HostError::Cancelled);} + for(unsigned id:{0U,12U}){Mock io;Host host(true);assert(!host.run(io,s,id));} + {Mock io;Host host(true);auto bad=s;bad.sequence[4]={nullptr,1};assert(!host.run(io,bad,4));} // Cancellation at every callback boundary must latch first failure and // never submit a later GPIO/header after returning an error. for(unsigned at=1;at<=complete.checks;++at) { Mock cancelled;cancelled.cancelAt=at;Host stopped(true); assert(!stopped.run(cancelled,s,2)&&stopped.error==HostError::Cancelled); const auto headers=cancelled.h.size(),words=cancelled.p.size();const auto gpios=cancelled.gpios; + const auto sequence=stopped.sequenceId,offset=stopped.operationOffset; + assert(!stopped.run(cancelled,s,4)); // an absent slot cannot erase the first fault + assert(stopped.sequenceId==sequence && stopped.operationOffset==offset); assert(!stopped.run(cancelled,s,2)); assert(cancelled.h.size()==headers&&cancelled.p.size()==words&&cancelled.gpios==gpios); } diff --git a/tests/dsi_panel.py b/tests/dsi_panel.py index d6e57122..e9a39b34 100644 --- a/tests/dsi_panel.py +++ b/tests/dsi_panel.py @@ -112,6 +112,18 @@ assert(f.io.gpioTrace==std::vector({14,2,3,15})); assert(f.io.regs[0xC7304]==4 && f.io.regs[0xC4030]==0xA7); } + // Every lifecycle slot can be absent, including a panel with all operations + // folded outside these slots. Hardware stop/start and frame checks still run. + for(unsigned mask=0;mask<512;++mask) { + Fixture f;const unsigned ids[]={1,2,3,4,5,6,7,10,11}; + for(unsigned i=0;i<9;++i)if(mask&(1U< #include #include @@ -11,23 +11,28 @@ void add(Bytes &b,uint8_t id,const Bytes &data) { b.push_back(id);b.push_back(data.size());b.push_back(data.size()>>8); b.insert(b.end(),data.begin(),data.end()); } -Bytes fixture(unsigned version=256,bool inlineReset=false) { +Bytes fixture(unsigned version=256,bool inlineReset=false,unsigned panel=0,unsigned stride=28,unsigned childStride=0,unsigned mask=0xCFE) { Bytes b(70);memcpy(b.data(),"$VBT",4);put(b,22,48);put(b,28,48,4); memcpy(b.data()+48,"BIOS_DATA_BLOCK ",16);put(b,64,version);put(b,66,22); - Bytes child(version<256?44:45);child[4]=child.size()-5;put(child,7,0x1400);child[21]=21;add(b,2,child); - add(b,40,Bytes{0}); + Bytes child(5+(childStride?childStride:version<256?39:40));child[4]=child.size()-5;put(child,7,0x1400);child[21]=21;add(b,2,child); + add(b,40,Bytes{uint8_t(panel)}); Bytes config(792);put(config,2,0x11190,4);put(config,6,12); config[93]=8;config[94]=50;put(config,98,310);config[103]=110;config[104]=50;put(config,105,200); for(unsigned i=0;i<5;++i)put(config,732+2*i,i==4?5000:500); + if(panel) { + for(unsigned i=0;i<122;++i)config[122*panel+i]=config[i]; + for(unsigned i=0;i<10;++i)config[732+10*panel+i]=config[732+i]; + } add(b,52,config); - Bytes dtd(30);put(dtd,0,28);put(dtd,2,135255,4); + Bytes dtd(2+stride*(panel+1),0xA5);put(dtd,0,stride);const auto t=2+stride*panel;put(dtd,t,135255,4); const unsigned fields[]={1080,46,22,2,1920,82,40,2,86,154}; - for(unsigned i=0;i<10;++i)put(dtd,6+2*i,fields[i]);dtd[26]=0xC0;add(b,58,dtd); - Bytes bl(273);bl[0]=6;bl[1]=2;put(bl,2,40000);bl[113]=2;put(bl,129,255);put(bl,193,6);bl[257]=8;add(b,43,bl); - Bytes seq(10);seq[0]=3;seq[5]=0; + for(unsigned i=0;i<10;++i)put(dtd,t+4+2*i,fields[i]);dtd[t+24]=0xC0;add(b,58,dtd); + Bytes bl(273,0xA5);bl[0]=6;bl[1+6*panel]=2;put(bl,2+6*panel,40000); + bl[113+panel]=2;put(bl,129+4*panel,255);put(bl,193+4*panel,6);bl[257+panel]=8;add(b,43,bl); + Bytes seq(10);seq[0]=3;seq[5]=panel; // Empty, terminated sequences prove structure without firmware commands. for(unsigned id:{1U,2U,4U,5U,6U,7U,10U,11U}) { - if(inlineReset && (id==1 || id==5))continue; + if(!(mask&(1U< Date: Mon, 2 Nov 2026 02:34:54 +0900 Subject: [PATCH 128/139] Accept omitted Alder and Raptor Lake mobile GPU identities --- CHANGELOG.md | 2 ++ README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_xelp_devices.hpp | 14 ++++++++++++++ docs/device-selection.md | 10 ++++++++-- tests/adlp_render.py | 9 +++++---- tests/device_selection.cpp | 2 +- tests/provider_binding.py | 4 ++++ tests/provider_binding_fixture.cpp | 4 +++- tests/xelp_platform.cpp | 2 +- 10 files changed, 40 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cac72561..77c01230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphi ### Fixed +- Recognize omitted Alder Lake-H/P/U and Raptor Lake-H/P/U PCI IDs in both WhateverXe and Compat, including the single-DIMM `4626` identity, on Big Sur through Tahoe. Reuse the existing family backends and hardware-resource checks; ID groupings follow Linux i915. + - Fix MIPI DSI brightness decoding for nonzero VBT panel indices and accept extended generic-DTD records using their declared stride. - Accept compatible later BDB layouts and omitted panel lifecycle sequences, following Intel's Linux VBT interpretation while retaining command-drain and ownership checks. - Replace fixed DSI pixel/port-clock ceilings with PLL and register feasibility checks; describe higher clocks with DisplayID when needed. diff --git a/README.md b/README.md index 79a28e3e..dc34b5cb 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ for OS coverage and remaining problems. - Other supported Tiger Lake-U/H35 configurations, including 96-EU graphics. - 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. +- Other Alder Lake-H/P/U and Raptor Lake-H/P/U configurations with supported fixed HDMI or fixed TC DisplayPort wiring. The [device table](docs/device-selection.md#automatic-selection) includes memory-configuration variants such as `4626`; both WhateverXe and WhateverXe Graphics must recognize the physical ID. - 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. diff --git a/README_ja.md b/README_ja.md index 7bbebda9..fbe8ca26 100644 --- a/README_ja.md +++ b/README_ja.md @@ -42,7 +42,7 @@ OS別の検証範囲と残る問題は[動作確認済みの機能と既知の - 96 EUを含む、対応IDの他のTiger Lake-U/H35構成。 - Tiger Lake-H45(GT1):`9A60`、`9A68`、`9A70`。 - 対応IDの他のAlder Lake-N構成とTwin Lake。 -- 対応する固定HDMIまたは固定TC DisplayPort配線の、他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。 +- 対応する固定HDMIまたは固定TC DisplayPort配線の、他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。[デバイス表](docs/device-selection.md#automatic-selection)は、メモリ構成で変わる`4626`などのIDも含みます。WhateverXeとWhateverXe Graphicsの両方が物理IDに対応している必要があります。 - 対応IDの他のAlder Lake-S構成、Alder Lake-HX、Raptor Lake-S、Raptor Lake-HX。 確認済みのファミリーと処理を共有する実装があり、IDは自動で受け付けます。 diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index f902a6e4..f04292bf 100644 --- a/WhateverXe/kern_xelp_devices.hpp +++ b/WhateverXe/kern_xelp_devices.hpp @@ -24,6 +24,18 @@ constexpr DeviceEntry DeviceTable[] { {0x4690, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, {0x4692, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 730"}, {0x4693, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 710"}, + {0x4626, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x4628, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x462A, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46A0, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46A1, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46A2, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46B0, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46B1, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46B2, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46C0, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46C1, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46C2, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, {0x46A3, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, {0x46A6, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Iris Xe Graphics"}, {0x46A8, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Iris Xe Graphics"}, @@ -51,6 +63,8 @@ constexpr DeviceEntry DeviceTable[] { {0xA789, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, {0xA78A, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, {0xA78B, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA720, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0xA721, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, {0xA7A0, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Iris Xe Graphics"}, {0xA7A1, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Iris Xe Graphics"}, {0xA7A8, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, diff --git a/docs/device-selection.md b/docs/device-selection.md index 6f96fbf3..b7079150 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -11,11 +11,17 @@ IDs are Tiger Lake `9A40`, `9A49`, `9A60`, `9A68`, `9A70`, `9A78` and Alder Lake | Family | Accepted IDs | | --- | --- | -| ADL-H / P / U | `46A3`, `46A6`, `46A8`, `46AA`, `46B3`, `46C3` | -| RPL-H / P / U | `A7A0`, `A7A1`, `A7A8`–`A7AD` | +| ADL-H / P / U | `4626`, `4628`, `462A`, `46A0`–`46A3`, `46A6`, `46A8`, `46AA`, `46B0`–`46B3`, `46C0`–`46C3` | +| RPL-H / P / U | `A720`, `A721`, `A7A0`, `A7A1`, `A7A8`–`A7AD` | | ADL-S / HX | `4680`, `4682`, `4688`, `468A`, `468B`, `4690`, `4692`, `4693` | | RPL-S / HX | `A780`, `A781`, `A782`, `A783`, `A788`, `A789`, `A78A`, `A78B` | +Memory configuration can change the physical PCI ID, for example `46A6` to +`4626` on an Alder Lake-H system with one DIMM. Both IDs select the same +family backend without a device-id override. Install matching updated +WhateverXe and WhateverXe Graphics components; the Compat provider also +validates the physical ID. Other added IDs have CPU coverage only. + All PCI revisions use the selected family backend. Hardware test coverage is recorded separately and does not gate selection. A stepping-specific branch requires a concrete documented difference or an observed failure. OS, driver diff --git a/tests/adlp_render.py b/tests/adlp_render.py index 2b13c8ce..cc37333e 100644 --- a/tests/adlp_render.py +++ b/tests/adlp_render.py @@ -29,10 +29,11 @@ }; FUNCTION int main(){ - const uint32_t mobile[]={0x46a3,0x46a6,0x46a8,0x46aa,0x46b3,0x46c3,0xa7a0,0xa7a1,0xa7a8,0xa7a9,0xa7aa,0xa7ab,0xa7ac,0xa7ad}; + const uint32_t mobile[]={0x4626,0x4628,0x462a,0x46a0,0x46a1,0x46a2,0x46b0,0x46b1,0x46b2,0x46c0,0x46c1,0x46c2,0xa720,0xa721,0x46a3,0x46a6,0x46a8,0x46aa,0x46b3,0x46c3,0xa7a0,0xa7a1,0xa7a8,0xa7a9,0xa7aa,0xa7ab,0xa7ac,0xa7ad}; + constexpr unsigned mobileSelectors=2*sizeof(mobile)/sizeof(mobile[0]); unsigned cases=0; - for(unsigned which=0;which<30;++which)for(unsigned rev=0;rev<256;++rev)for(unsigned off=0;off<2;++off) { - noaccel=off;XE x;const bool isMobile=which<28,isN=which==28; + for(unsigned which=0;which #include #include #include @@ -37,7 +38,7 @@ int main(int argc,char **argv) { assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP|XELP::ProbeDisplay13Panel|XELP::ProbeDisplay13DPAlt):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); + assert(argc==2 || argc==3);const bool adln=!strncmp(argv[1],"adln",4); const bool rpls=!strncmp(argv[1],"rpls",4); const bool adls=!strncmp(argv[1],"adls",4) || rpls; const bool adlp=!strncmp(argv[1],"adlp",4) || !strncmp(argv[1],"rplp",4); @@ -59,6 +60,7 @@ int main(int argc,char **argv) { if(fake){pci.identity=0x12348086;fakeID.value=adln?0x46D2:0x9A49;pci.property=&fakeID;} if(adlp){pci.identity=!strncmp(argv[1],"rplp",4)?0xA7A88086:0x46A68086;pci.revision=12; if(!strcmp(argv[1],"adlp-fb")){fakeID.value=0x9A49;pci.property=&fakeID;}} + if(adlp && argc==3)pci.identity=(uint32_t(strtoul(argv[2],nullptr,16))<<16)|0x8086; controller.provider=&pci;platform[0x12]=9; map.address=reinterpret_cast(mmio.data());map.bytes=mmio.size(); field(&controller,0xC40)=&access;field(&controller,0xD40)=platform; diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index d21c0f45..4b840e1e 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -49,7 +49,7 @@ int main() { const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78 || id==0x9A60 || id==0x9A68 || id==0x9A70,adln=id>=0x46D0 && id<=0x46D4; const bool adls=id==0x4680 || id==0x4682 || id==0x4688 || id==0x468A || id==0x468B || id==0x4690 || id==0x4692 || id==0x4693; const bool rpls=id==0xA780 || id==0xA781 || id==0xA782 || id==0xA783 || id==0xA788 || id==0xA789 || id==0xA78A || id==0xA78B; - const bool mobile=id==0x46a3 || id==0x46a6 || id==0x46a8 || id==0x46aa || id==0x46b3 || id==0x46c3 || id==0xa7a0 || id==0xa7a1 || (id>=0xa7a8 && id<=0xa7ad); + const bool mobile=id==0x4626 || id==0x4628 || id==0x462a || (id>=0x46a0 && id<=0x46a2) || (id>=0x46b0 && id<=0x46b2) || (id>=0x46c0 && id<=0x46c2) || id==0xa720 || id==0xa721 || id==0x46a3 || id==0x46a6 || id==0x46a8 || id==0x46aa || id==0x46b3 || id==0x46c3 || id==0xa7a0 || id==0xa7a1 || (id>=0xa7a8 && id<=0xa7ad); assert(p.admitted()==(tgl || adln || adls || rpls || mobile)); if(mobile){assert(p.mobileDisplay13());assert(!identify((id<<16)|0x8086,rev,0x46d18086).admitted());} if(adls||rpls)assert(!identify((id<<16)|0x8086,rev,0x9A498086).admitted()); From 4ec53b6ff506dc3c41604916250d49347f68c02e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 2 Nov 2026 02:58:11 +0900 Subject: [PATCH 129/139] Document tested single-DIMM Alder Lake acceleration --- README.md | 2 +- README_ja.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc34b5cb..52d86b98 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Graphics; GPU generation and connector routing are handled by WEX. | --- | --- | | Tiger Lake-U | 48- and 80-EU graphics; desktop-style and laptop display layouts | | Alder Lake-N | 16- and 24-EU graphics; two HDMI displays | -| Alder Lake-H | 96-EU graphics (`46A6`); fixed TC HDMI and DisplayPort through a DP-to-HDMI adapter | +| Alder Lake-H | 96-EU graphics (`46A6`); fixed TC HDMI and DisplayPort through a DP-to-HDMI adapter. Single-DIMM `4626` display, Metal/GL/OpenCL and video acceleration tested on Big Sur | | Alder Lake-S | UHD Graphics 770 (`4690`); HDMI, DisplayPort and a USB-C port wired as fixed DisplayPort | These are tested configurations within each family, not a guarantee for every diff --git a/README_ja.md b/README_ja.md index fbe8ca26..a988365f 100644 --- a/README_ja.md +++ b/README_ja.md @@ -31,7 +31,7 @@ WEXはOSのABIとGPUバックエンドを独立して選択します。OS別の | --- | --- | | Tiger Lake-U | 48 EU・80 EU。外部画面を使う構成とノートPCの画面構成 | | Alder Lake-N | 16 EU・24 EU。HDMI 2画面 | -| Alder Lake-H | 96 EU(`46A6`)。固定TC HDMIとDP→HDMI変換器経由のDisplayPort | +| Alder Lake-H | 96 EU(`46A6`)。固定TC HDMIとDP→HDMI変換器経由のDisplayPort。1枚挿し`4626`の表示・Metal/GL/OpenCL・動画加速はBig Surで確認 | | Alder Lake-S | UHD Graphics 770(`4690`)。HDMI、DisplayPort、固定DisplayPortとして配線されたUSB-C端子 | 各ファミリーの一部構成で確認した結果であり、全モデル・全端子の動作保証ではありません。 From 2a83119b7bc3e9f8f43f0038aa0fc9b9abd8bcc2 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 2 Nov 2026 03:16:04 +0900 Subject: [PATCH 130/139] Admit remaining Tiger Lake GT2 IDs with fuse-derived URB capacity --- README.md | 3 + README_ja.md | 2 + WhateverXe/kern_xe.cpp | 33 +++++++---- WhateverXe/kern_xe.hpp | 2 + WhateverXe/kern_xelp_devices.hpp | 9 +++ WhateverXe/kern_xelp_gt_resources.hpp | 13 +++++ docs/device-selection.md | 18 ++++++ tests/adlp_load_only.py | 2 +- tests/adlp_render.py | 1 + tests/adls_boot_runtime.py | 1 + tests/boot_arguments.py | 12 +++- tests/device_selection.cpp | 2 +- tests/provider_binding.py | 2 + tests/provider_binding_fixture.cpp | 2 +- tests/small_gt_urb.py | 1 + tests/tgl_gt2_urb.py | 82 +++++++++++++++++++++++++++ tests/xelp_platform.cpp | 2 +- tools/test.py | 2 +- 18 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 tests/tgl_gt2_urb.py diff --git a/README.md b/README.md index 52d86b98..1484ffbd 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,9 @@ for OS coverage and remaining problems. ### Expected to work, not yet tested on hardware - Other supported Tiger Lake-U/H35 configurations, including 96-EU graphics. + The complete Linux TGL GT2 ID set is accepted, including `9A59`, `9AC0`, + `9AC9`, `9AD9` and `9AF8`; these five IDs have not been tested on hardware. + Physical slice/DSS/EU and L3-bank fuses select the supported URB capacity. - 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. The [device table](docs/device-selection.md#automatic-selection) includes memory-configuration variants such as `4626`; both WhateverXe and WhateverXe Graphics must recognize the physical ID. diff --git a/README_ja.md b/README_ja.md index a988365f..11408c7d 100644 --- a/README_ja.md +++ b/README_ja.md @@ -40,6 +40,8 @@ OS別の検証範囲と残る問題は[動作確認済みの機能と既知の ### 動作を期待できるが実機未検証 - 96 EUを含む、対応IDの他のTiger Lake-U/H35構成。 + LinuxのTGL GT2 ID集合全体を受け付け、`9A59`・`9AC0`・`9AC9`・`9AD9`・`9AF8`も含みます。 + この5 IDは実機未検証です。物理slice/DSS/EUとL3 bankのfuseから対応するURB容量を選びます。 - Tiger Lake-H45(GT1):`9A60`、`9A68`、`9A70`。 - 対応IDの他のAlder Lake-N構成とTwin Lake。 - 対応する固定HDMIまたは固定TC DisplayPort配線の、他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。[デバイス表](docs/device-selection.md#automatic-selection)は、メモリ構成で変わる`4626`などのIDも含みます。WhateverXeとWhateverXe Graphicsの両方が物理IDに対応している必要があります。 diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 0413ff29..e62e0aa1 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3804,6 +3804,7 @@ void XE::init() { void XE::selectProfile(const XELP::Profile &profile) { xeProfile=profile; + tglGT2Detected=profile.admitted() && !profile.overridden() && XELP::tigerLakeGT2Device(profile.identity); display12SmallGTDetected=profile.admitted() && XELP::display12SmallGT(profile.gt); adlsDisplaySelected=profile.admitted() && XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLS; adlnNativeRCSProfileRequested=false; @@ -4363,11 +4364,11 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { if(map)map->release(); return; } - if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) && + if(((display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity)) || tglGT2Detected) && !xeProfile.overridden() && physicalGPU && - XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { + (XELP::hostOS(XELP::kernelOS(version)) || (tglGT2Detected && XELP::kernelOS(version)==XELP::OS::Donor)) && (physicalGPU->configRead16(4)&2)) { // Gen12 forcewake/fuse coordinates are shared with the existing reader. - // Its ADL-N profile verdict is not used to admit Display12 GT1. No engine, + // Its ADL-N profile verdict is not used to admit Display12 GT1/GT2. No engine, // GGTT, display, PLL or reset programming is performed here. auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { @@ -4380,12 +4381,17 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } io {reinterpret_cast(map->getVirtualAddress())}; uint32_t edram=UINT32_MAX; auto gt=AlderLakeN::readGT(io,true,&edram); - display12SmallGTReady=XELP::decodeSmallGT(gt.fuses,display12SmallGT) && - (gt.status==AlderLakeN::GTStatus::Complete || gt.status==AlderLakeN::GTStatus::ProfileMismatch) && + const bool readoutReady=(gt.status==AlderLakeN::GTStatus::Complete || gt.status==AlderLakeN::GTStatus::ProfileMismatch) && gt.requestedMask==3 && gt.releasedMask==3 && !gt.releaseFailedMask && edram==0; - physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTQualified":"XELPSmallGTQualified",display12SmallGTReady); - physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTBoot":"XELPSmallGTBoot",>,sizeof(gt)); - physicalGPU->setProperty(adlsDisplaySelected?"ADLSEDRAM":"XELPSmallGTEDRAM",edram,32); + display12SmallGTReady=display12SmallGTDetected && readoutReady && XELP::decodeSmallGT(gt.fuses,display12SmallGT); + tglGT2Ready=tglGT2Detected && readoutReady && XELP::decodeTGLGT2(gt.fuses,tglGT2Resources); + if(tglGT2Detected) { + physicalGPU->setProperty("TGLGT2Qualified",tglGT2Ready); + physicalGPU->setProperty("TGLGT2Boot",>,sizeof(gt)); + } + if(display12SmallGTDetected)physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTQualified":"XELPSmallGTQualified",display12SmallGTReady); + if(display12SmallGTDetected)physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTBoot":"XELPSmallGTBoot",>,sizeof(gt)); + if(display12SmallGTDetected)physicalGPU->setProperty(adlsDisplaySelected?"ADLSEDRAM":"XELPSmallGTEDRAM",edram,32); if(adlsDisplaySelected) { uint32_t registers[][2] { {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, @@ -4401,7 +4407,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { for(auto ®:registers)reg[1]=io.read(reg[0]); physicalGPU->setProperty("ADLSDisplayBoot",registers,sizeof(registers)); } - SYSLOG("igfx","Display12 small-GT boot readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x", + SYSLOG("igfx","Display12 GT boot readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x", static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], gt.releasedMask,gt.releaseFailedMask); } @@ -11128,7 +11134,8 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add uint8_t urbReplaceAlderLakeN[] {0x83,0xFE,0x01,0x75,0x59,0x83,0xFA,0x04,0x75,0x54,0x41,0xC7,0x87,0x64,0x11,0x00,0x00,0x04,0x00,0x00,0x00}; if (adlnDetected) { urbReplaceAlderLakeN[7]=adlnNativeSubslices;urbReplaceAlderLakeN[17]=adlnL3Banks; } if(display12SmallGTDetected) {urbReplaceAlderLakeN[7]=display12SmallGT.nativeSubslices;urbReplaceAlderLakeN[17]=display12SmallGT.l3Banks;} - const auto urbReplacement = (adlnDetected || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : + if(tglGT2Detected) {urbReplaceAlderLakeN[7]=tglGT2Resources.nativeSubslices;urbReplaceAlderLakeN[17]=tglGT2Resources.l3Banks;} + const auto urbReplacement = (adlnDetected || XELP::display12SmallGT(xeProfile.gt) || tglGT2Detected) ? urbReplaceAlderLakeN : (xeProfile.gt==XELP::GT::TGLG4 ? urbReplaceG4 : urbReplace); auto gpuInfo=patcher.solveSymbol(index, "__ZN16IntelAccelerator10getGPUInfoEv",address,size); @@ -13926,7 +13933,8 @@ uint32_t XE::wrapTGLLaneMask(void *port) { } bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { - if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || + if((callbackXE->tglGT2Detected && !callbackXE->tglGT2Ready) || + (callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && (!callbackXE->adlsDisplayHooksReady || __atomic_load_n(&callbackXE->adlsBootConsumersFailed,__ATOMIC_ACQUIRE))))return false; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { @@ -14325,7 +14333,8 @@ void XE::tglObserveAccelerator(IOService *that, uint32_t stage, bool done, uint6 } IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SInt32 *score) { - if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || + if((callbackXE->tglGT2Detected && !callbackXE->tglGT2Ready) || + (callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return nullptr; if (!callbackXE->tglAcceleratorRequested) return nullptr; auto pci = OSDynamicCast(IOPCIDevice, provider); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index bbf4849a..b35a3d45 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -102,6 +102,8 @@ class XE { // Render and Display selections are independent; shared PHY/port code // must not depend on which GT implementation the product uses. bool display12SmallGTDetected {},display12SmallGTReady {}; + bool tglGT2Detected {},tglGT2Ready {}; + XELP::GTResources tglGT2Resources; bool adlsDisplaySelected {},adlsDisplayHooksReady {}; bool adlsPortInitReady {},tglHPDHookReady {}; bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {},adlsPanelPWMReady {}; diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index f04292bf..b696dc51 100644 --- a/WhateverXe/kern_xelp_devices.hpp +++ b/WhateverXe/kern_xelp_devices.hpp @@ -51,6 +51,11 @@ constexpr DeviceEntry DeviceTable[] { {0x4C8B, Family::RocketLake, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, {0x9A40, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, {0x9A49, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, + {0x9A59, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel UHD Graphics"}, + {0x9AC0, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel UHD Graphics"}, + {0x9AC9, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel UHD Graphics"}, + {0x9AD9, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel UHD Graphics"}, + {0x9AF8, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel UHD Graphics"}, {0x9A60, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, {0x9A68, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, {0x9A70, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, @@ -79,6 +84,10 @@ inline const DeviceEntry *deviceEntry(uint32_t pci) { for(const auto &entry:DeviceTable)if(entry.device==(pci>>16))return &entry; return nullptr; } +inline bool tigerLakeGT2Device(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && e->family==Family::TigerLake && (e->backend==GT::TGLGT2 || e->backend==GT::TGLG4); +} inline bool mobileDisplay13Device(uint32_t pci) { const auto e=deviceEntry(pci); return e && e->backend==GT::ADLPMobile; diff --git a/WhateverXe/kern_xelp_gt_resources.hpp b/WhateverXe/kern_xelp_gt_resources.hpp index 156748b1..545028a3 100644 --- a/WhateverXe/kern_xelp_gt_resources.hpp +++ b/WhateverXe/kern_xelp_gt_resources.hpp @@ -54,5 +54,18 @@ inline bool decodeADLPGT(const uint32_t *fuses, GTResources &out) { return true; } +// TGL U/Y shares Gen12 SSEU and L3-disable fuses with the existing reader. +// Reuse only the established 48-EU/6-bank and 80/96-EU/8-bank URB shapes. +// A PCI ID (including Linux's GT2 classification) does not determine capacity. +inline bool decodeTGLGT2(const uint32_t *fuses, GTResources &out) { + GTResources decoded; + out={}; + if(!decodeADLPGT(fuses,decoded))return false; + if(!((decoded.dss==3 && decoded.eus==48 && decoded.l3Banks==6) || + ((decoded.dss==5 || decoded.dss==6) && decoded.eus==16*decoded.dss && decoded.l3Banks==8)))return false; + out=decoded; + return true; +} + } #endif diff --git a/docs/device-selection.md b/docs/device-selection.md index b7079150..4508e723 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -117,3 +117,21 @@ The Display12 small-GT implementation accepts one slice, up to two DSS, 16/24/32 EUs, four L3 banks and the supported media engines. Different resource layouts need additional implementation. These checks are independent of whether an individual PCI revision has been tested. + +## Tiger Lake GT2 resources + +All eight Linux TGL GT2 IDs use the Tiger Lake U/Y Display12 adapter, including +`9A59`, `9AC0`, `9AC9`, `9AD9` and `9AF8`. The last five are implemented but +have no hardware validation. They do not use the Rocket Lake adapter. +Physical selection reads slice, DSS, EU-pair and L3-disable fuses before native +acceleration. Supported URB shapes are one slice with three DSS/48 EUs/six banks, +or five/six DSS with 80/96 EUs/eight banks. Invalid/unavailable readout or a +shape outside these paths prevents accelerator probe/start; framebuffer-only +operation does not depend on the render capacity. The PCI ID does not choose +between the six- and eight-bank paths. Existing explicit donor-override policy +is unchanged; automatic physical selection needs no override. + +Linux v6.12's `INTEL_TGL_GT2_IDS` maps to `tgl_info`, the U/Y subplatform and +`tgl_desc`; Gen12 SSEU is decoded from fuses. Combo PHY signal policy already +uses this complete U/Y set. See [Linux PCI IDs](https://github.com/torvalds/linux/blob/v6.12/include/drm/intel/i915_pciids.h) +and [SSEU](https://github.com/torvalds/linux/blob/v6.12/drivers/gpu/drm/i915/gt/intel_sseu.c). diff --git a/tests/adlp_load_only.py b/tests/adlp_load_only.py index b335aa37..17262154 100644 --- a/tests/adlp_load_only.py +++ b/tests/adlp_load_only.py @@ -5,7 +5,7 @@ r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() a=s.index('// ADLP_LOAD_ONLY_HOOKS:');b=s.index('// ADLP_LOAD_ONLY_HOOKS_END',a);body=s[a:b] a=s.index(' // ADLP_LOAD_ONLY_KERNEL:');b=s.index(' // ADLP_LOAD_ONLY_KERNEL_END',a);selection=s[a:b] -a=s.index(' if(xeProfile.mobileDisplay13() && physicalGPU && checkKernelArgument("-wex-adlp-readout")');b=s.index(' if(display12SmallGTDetected',a);diagnostic=s[a:b] +a=s.index(' if(xeProfile.mobileDisplay13() && physicalGPU && checkKernelArgument("-wex-adlp-readout")');b=s.index(' if(((display12SmallGTDetected',a);diagnostic=s[a:b] dispatch=s[s.index('bool XE::processKext('):s.index(' if((tglRequested',s.index('bool XE::processKext('))] assert dispatch.index('if(adlpLoadOnly)') struct IOService {}; struct XE { + bool tglGT2Detected=false,tglGT2Ready=false; bool display12SmallGTDetected=false,display12SmallGTReady=true; bool adlsDisplaySelected=true,adlsDisplayHooksReady=true,adlsBootConsumersFailed=false; static bool wrapTGLAcceleratorStart(IOService*,IOService*); diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index d1c621ca..36702c6a 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -65,6 +65,7 @@ static struct { void onKextLoadForce(int *) {} } lilu; struct XE { XELP::Profile xeProfile; + bool tglGT2Detected=false,tglGT2Ready=false; bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpDisplaySelected=false, adlpRenderRequested=false; bool display12SmallGTReady=false, adlsDisplayHooksReady=false; @@ -100,6 +101,12 @@ assert(wrapTGLAcceleratorStartGate()==ready); assert(wrapTGLAcceleratorProbeGate()==ready); } + for(unsigned bits=0;bits<4;++bits) { + XE selected;selected.tglGT2Detected=bits&1;selected.tglGT2Ready=bits&2;callbackXE=&selected; + assert(wrapTGLFramebufferStartGate()); + assert(wrapTGLAcceleratorStartGate()==(!(bits&1) || (bits&2))); + assert(wrapTGLAcceleratorProbeGate()==(!(bits&1) || (bits&2))); + } // Every implemented ADL-N/Twin ID enables the qualified normal path without args. XE xe; for(uint32_t pci:{0x46D08086U,0x46D18086U,0x46D28086U,0x46D38086U,0x46D48086U}) { @@ -146,7 +153,7 @@ } // Exercise the complete production selection, including the shared Display // adapter on registered desktop products and the still-unimplemented ADL-P. - for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x4690U,0x4680U,0xA780U,0x4688U,0x468BU,0xA788U,0xA78BU,0x9A60U,0x9A68U,0x9A70U,0x46A6U,0x46D2U}) + for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x9A59U,0x9AC0U,0x9AC9U,0x9AD9U,0x9AF8U,0x4690U,0x4680U,0xA780U,0x4688U,0x468BU,0xA788U,0xA78BU,0x9A60U,0x9A68U,0x9A70U,0x46A6U,0x46D2U}) for(auto rev:{0U,4U,12U,255U})for(unsigned option=0;option<4;++option) { version=kernel;args={}; if(option&1)args.insert("-wex-tgl-off"); @@ -154,7 +161,8 @@ XE selected;selected.init();selected.selectProfile(XELP::identify((id<<16)|0x8086,rev)); const bool desktop=id==0x4690 || id==0x4680 || id==0xA780 || id==0x4688 || id==0x468B || id==0xA788 || id==0xA78B; const bool gt1=id==0x9A60 || id==0x9A68 || id==0x9A70; - const bool mobile=id==0x9A49 || id==0x9A78 || gt1; + const bool mobile=XELP::tigerLakeGT2Device((id<<16)|0x8086) || gt1; + assert(selected.tglGT2Detected==(mobile && !gt1)); const bool mobile13=id==0x46A6 || id==0xA7A0; const bool small13=id==0x46D2; const bool render13=small13 || (mobile13 && !(option&2)); diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index a70ba4bf..ced500d3 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -25,7 +25,7 @@ int main() { assert(!strcmp(model(f),entry.model)); auto nonIntel=select(0x123410DE,0,true,&value,4);assert(!nonIntel.admitted()); } - assert(implemented==55); // Recognition of other table rows adds no native backend. + assert(implemented==60); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); assert(select(0x12348086,1,true,&id,4).admitted()); diff --git a/tests/provider_binding.py b/tests/provider_binding.py index fb9b5f42..5ded7e6e 100644 --- a/tests/provider_binding.py +++ b/tests/provider_binding.py @@ -28,3 +28,5 @@ # pass the actual Compat binding, physical-owner and native probe paths. for pci in ["4626", "4628", "462a", "46a0", "46a1", "46a2", "46b0", "46b1", "46b2", "46c0", "46c1", "46c2", "a720", "a721"]: subprocess.run([str(exe), "adlp-auto", pci], check=True) + for pci in ["9a59", "9ac0", "9ac9", "9ad9", "9af8"]: + subprocess.run([str(exe), "tgl", pci], check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index b530bd00..54ff4102 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -60,7 +60,7 @@ int main(int argc,char **argv) { if(fake){pci.identity=0x12348086;fakeID.value=adln?0x46D2:0x9A49;pci.property=&fakeID;} if(adlp){pci.identity=!strncmp(argv[1],"rplp",4)?0xA7A88086:0x46A68086;pci.revision=12; if(!strcmp(argv[1],"adlp-fb")){fakeID.value=0x9A49;pci.property=&fakeID;}} - if(adlp && argc==3)pci.identity=(uint32_t(strtoul(argv[2],nullptr,16))<<16)|0x8086; + if(argc==3)pci.identity=(uint32_t(strtoul(argv[2],nullptr,16))<<16)|0x8086; controller.provider=&pci;platform[0x12]=9; map.address=reinterpret_cast(mmio.data());map.bytes=mmio.size(); field(&controller,0xC40)=&access;field(&controller,0xD40)=platform; diff --git a/tests/small_gt_urb.py b/tests/small_gt_urb.py index 88dd24d5..10000d12 100644 --- a/tests/small_gt_urb.py +++ b/tests/small_gt_urb.py @@ -24,6 +24,7 @@ XELP::SmallGTResources display12SmallGT; uint32_t fuses[]={1,dss,eu,0xf0,media}; assert(xeProfile.admitted() && display12SmallGTDetected && XELP::decodeSmallGT(fuses,display12SmallGT)); + bool tglGT2Detected=false;XELP::GTResources tglGT2Resources; bool adlnDetected=false;unsigned adlnNativeSubslices=0,adlnL3Banks=0; BODY assert(urbReplacement==urbReplaceAlderLakeN); diff --git a/tests/tgl_gt2_urb.py b/tests/tgl_gt2_urb.py new file mode 100644 index 00000000..cc164829 --- /dev/null +++ b/tests/tgl_gt2_urb.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Replay physical TGL GT2 admission and fuse-derived production URB stores.""" +import hashlib,re,subprocess,sys,tempfile +from pathlib import Path +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('static const uint8_t urbFind[]');b=s.index('auto gpuInfo=',a) +body=s[a:b] +qa=s.index(' const bool readoutReady=') +qb=s.index(' if(tglGT2Detected)',qa) +qualification=s[qa:qb] +if len(sys.argv)>1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' + old=bytes(int(x,0) for x in re.search(r'urbFind\[\] \{([^}]+)',body)[1].split(',')) + assert donor[0x28781:0x28781+len(old)]==old +code=r'''#include "kern_xelp_platform.hpp" +#include "kern_xelp_gt_resources.hpp" +#include "kern_alderlake_n.hpp" +#include +#include +#include +#include +#include +void check(unsigned id,unsigned rev,unsigned dss,unsigned banks,unsigned media) { + auto xeProfile=XELP::identify((id<<16)|0x8086,rev); + bool display12SmallGTDetected=false; + XELP::SmallGTResources display12SmallGT; + uint32_t fuses[]={1,dss,0,banks,media}; + assert(xeProfile.admitted() && XELP::tigerLakeGT2Device(xeProfile.identity)); + bool tglGT2Detected=true;XELP::GTResources tglGT2Resources; + assert(XELP::decodeTGLGT2(fuses,tglGT2Resources)); + bool adlnDetected=false;unsigned adlnNativeSubslices=0,adlnL3Banks=0; + BODY + assert(urbReplacement==urbReplaceAlderLakeN); + auto program=(unsigned char *)mmap(nullptr,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0); + assert(program!=MAP_FAILED);memset(program,0xcc,4096); + const unsigned char prefix[]={0x41,0x57,0x49,0x89,0xff}; // save R15, destination from RDI + const unsigned char done[]={0x41,0x5f,0xc3}; + memcpy(program,prefix,5);memcpy(program+5,urbReplacement,sizeof(urbFind)); + memcpy(program+5+sizeof(urbFind),done,3);memcpy(program+5+94,done,3); + unsigned char state[0x1200]; + for(unsigned slice:{0U,1U,2U})for(unsigned ss:{0U,2U,4U,6U,10U,12U}) { + memset(state,0xa5,sizeof(state)); + reinterpret_cast(program)(state,slice,ss); + unsigned result;memcpy(&result,state+0x1164,4); + assert(result==((slice==1 && ss==tglGT2Resources.nativeSubslices)?tglGT2Resources.l3Banks:0xa5a5a5a5U)); + for(unsigned i=0;i=0x1168)assert(state[i]==0xa5); + } + munmap(program,4096); +} +int main() { + for(auto status:{AlderLakeN::GTStatus::Complete,AlderLakeN::GTStatus::ProfileMismatch,AlderLakeN::GTStatus::Unavailable,AlderLakeN::GTStatus::ExistingOwner,AlderLakeN::GTStatus::AckTimeout}) + for(unsigned mask=0;mask<8;++mask)for(unsigned release=0;release<8;++release)for(unsigned failed=0;failed<4;++failed) { + AlderLakeN::GTReadout gt;gt.status=status;gt.requestedMask=mask;gt.releasedMask=release;gt.releaseFailedMask=failed; + const uint32_t fuses[]={1,63,0,0,0xe00fa};memcpy(gt.fuses,fuses,sizeof(fuses)); + uint32_t edram=0;bool display12SmallGTDetected=false,display12SmallGTReady=false,tglGT2Detected=true,tglGT2Ready=false; + XELP::GTResources display12SmallGT,tglGT2Resources; + QUALIFICATION + assert(tglGT2Ready==((status==AlderLakeN::GTStatus::Complete || status==AlderLakeN::GTStatus::ProfileMismatch) && mask==3 && release==3 && failed==0)); + } + for(auto id:{0x9a40U,0x9a49U,0x9a78U,0x9a59U,0x9ac0U,0x9ac9U,0x9ad9U,0x9af8U}) + for(auto rev:{0U,1U,2U,3U,4U,255U})for(auto video:{0xe00feU,0xe00faU}) { + check(id,rev,7,0xc0,video); // 48 EU, three DSS, six banks + check(id,rev,0x3e,0,video); // 80 EU, five DSS, eight banks + check(id,rev,0x3f,0,video); // 96 EU, six DSS, eight banks + } + XELP::GTResources resource; + assert(!XELP::decodeTGLGT2(nullptr,resource)); + for(unsigned slice=0;slice<4;++slice)for(unsigned dss=0;dss<64;++dss) + for(unsigned eu:{0U,0xc0U,0xf0U,0xffU})for(unsigned banks:{0U,0xc0U,0xf0U,0xffU}) { + uint32_t f[]={slice,dss,eu,banks,0xe00fa};unsigned count=__builtin_popcount(dss); + bool expected=slice==1 && eu==0 && ((count==3 && banks==0xc0) || ((count==5 || count==6) && banks==0)); + assert(XELP::decodeTGLGT2(f,resource)==expected); + } + for(unsigned i=0;i<5;++i){uint32_t f[]={1,63,0,0,0xe00fa};f[i]=~0U;assert(!XELP::decodeTGLGT2(f,resource));} + puts("PASS all eight TGL GT2 IDs, fuse-derived URB native stores, unsupported topology rejection"); +}'''.replace('BODY',body).replace('QUALIFICATION',qualification) +with tempfile.TemporaryDirectory(prefix='small-gt-urb-') as tmp: + p=Path(tmp);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 4b840e1e..be110124 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -46,7 +46,7 @@ int main() { for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,4U,8U,12U,255U}) { const auto p=identify((id<<16)|0x8086,rev); - const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78 || id==0x9A60 || id==0x9A68 || id==0x9A70,adln=id>=0x46D0 && id<=0x46D4; + const bool tgl=id==0x9A59 || id==0x9AC0 || id==0x9AC9 || id==0x9AD9 || id==0x9AF8 || id==0x9A40 || id==0x9A49 || id==0x9A78 || id==0x9A60 || id==0x9A68 || id==0x9A70,adln=id>=0x46D0 && id<=0x46D4; const bool adls=id==0x4680 || id==0x4682 || id==0x4688 || id==0x468A || id==0x468B || id==0x4690 || id==0x4692 || id==0x4693; const bool rpls=id==0xA780 || id==0xA781 || id==0xA782 || id==0xA783 || id==0xA788 || id==0xA789 || id==0xA78A || id==0xA78B; const bool mobile=id==0x4626 || id==0x4628 || id==0x462a || (id>=0x46a0 && id<=0x46a2) || (id>=0x46b0 && id<=0x46b2) || (id>=0x46c0 && id<=0x46c2) || id==0xa720 || id==0xa721 || id==0x46a3 || id==0x46a6 || id==0x46a8 || id==0x46aa || id==0x46b3 || id==0x46c3 || id==0xa7a0 || id==0xa7a1 || (id>=0xa7a8 && id<=0xa7ad); diff --git a/tools/test.py b/tools/test.py index 21a4ffaf..95524582 100644 --- a/tools/test.py +++ b/tools/test.py @@ -5,7 +5,7 @@ R=Path(__file__).resolve().parents[1] p=argparse.ArgumentParser(description=__doc__);p.add_argument('--donor',type=Path);p.add_argument('--accelerator',type=Path);a=p.parse_args() 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'] +python_tests=['tgl_gt2_urb','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', 'display13_panel_runtime', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_cursor_resize','dsi_presentation','dsi_native_mode','dsi_backing_adapter','dsi_update_dispatch','dsi_path_disposal','dsi_clock','dsi_quiesce_adapter','dsi_quiesce','dsi_surface','dsi_ggtt','dsi_lifecycle_dispatch','dsi_boot_adoption','dsi_native_cache','dsi_boot_probe','dsi_resources','dsi_boot_adapter','dsi_port_adapter','dsi_apple_adapter','fatal_log_storage','boot_stack_limits','xelp_combo_clock_guard','display13_tc_preparation','display13_tc_disconnect','display13_dbuf_setup','display13_retirement','display13_trial_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime','compat_bundle_version'] From 7e318bdd80f440ee0582d788011beae8059f935f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 2 Nov 2026 04:19:56 +0900 Subject: [PATCH 131/139] Validate servicing kernel call targets without layout-distance pins --- WhateverXe/kern_xe.cpp | 44 ++++++++++++------- WhateverXe/kern_xelp_call_target.hpp | 29 +++++++++++++ tests/kernel_os_updates.cpp | 9 ++++ tests/pmap_kernel_contract.py | 64 ++++++++++++++++++++++++++++ tests/sequoia_pmap.py | 29 ++----------- tests/tahoe_pmap.py | 29 ++----------- tests/xelp_call_target.cpp | 55 ++++++++++++++++++++++++ 7 files changed, 193 insertions(+), 66 deletions(-) create mode 100644 WhateverXe/kern_xelp_call_target.hpp create mode 100644 tests/pmap_kernel_contract.py create mode 100644 tests/xelp_call_target.cpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ee4568ff..14e213e9 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10,6 +10,7 @@ #include "kern_xe.hpp" #include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" +#include "kern_xelp_call_target.hpp" #include "kern_alderlake_n.hpp" #include "kern_xelp_device.hpp" #include "kern_xelp_memory_layout.hpp" @@ -2014,7 +2015,8 @@ static void tglPrepareTahoeMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x75,0xB4,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB4,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const uint8_t protectCall[] {0xE8,0x2F,0xF3,0x02,0x00}; + const bool protectCallReady=fn && protect && XELPCall::targets( + reinterpret_cast(protect+0xCC),5,protect+0xCC,fn); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && @@ -2027,14 +2029,15 @@ static void tglPrepareTahoeMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { !memcmp(reinterpret_cast(fn+0x10C1),create,sizeof(create)) && !memcmp(reinterpret_cast(fn+0x1197),update,sizeof(update)) && !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && - !memcmp(reinterpret_cast(protect+0xCC),protectCall,sizeof(protectCall)) && + protectCallReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; uint64_t guard[20] {1,tglVM.enabled,tglVM.pciBAR,bar ? bar->getLength() : 0,pat,bool(fn),bool(protect),bool(kernelMap)}; if(fn && protect) { - const uintptr_t sites[] {fn,fn+0x15A,fn+0x44,fn+0x61,fn+0xAA,fn+0x10C1,fn+0x1197,protect,protect+0xCC}; - const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame,protectCall}; - const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame),sizeof(protectCall)}; - for(unsigned i=0;i<9;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); + const uintptr_t sites[] {fn,fn+0x15A,fn+0x44,fn+0x61,fn+0xAA,fn+0x10C1,fn+0x1197,protect}; + const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame}; + const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame)}; + for(unsigned i=0;i<8;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); + guard[16]=protectCallReady; } guard[17]=kernelMap && tglVMRead(kernelMap,tglKernelMap);guard[18]=tglKernelMap;guard[19]=ok; IORegistryEntry::getRegistryRoot()->setProperty("TGLTahoeMMIOGuard",guard,sizeof(guard)); @@ -2070,7 +2073,8 @@ static void tglPrepareSequoiaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { const uint8_t create[] {0x8B,0x75,0xB4,0x89,0xF1,0x83,0xE1,0x01,0x48,0x09,0xD1,0x40,0xF6,0xC6,0x02}; const uint8_t update[] {0x8B,0x55,0xB4,0x89,0xD0,0x83,0xE0,0x01,0x48,0x09,0xC8,0xF6,0xC2,0x02}; const uint8_t protectFrame[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}; - const uint8_t protectCall[] {0xE8,0x71,0xCB,0x02,0x00}; + const bool protectCallReady=fn && protect && XELPCall::targets( + reinterpret_cast(protect+0xCA),5,protect+0xCA,fn); uint32_t lo,hi;asm volatile("rdmsr" : "=a"(lo),"=d"(hi) : "c"(0x277)); const uint64_t pat=static_cast(hi)<<32|lo; bool ok=tglVM.enabled && tglPrepareMMIOBAR(bar) && @@ -2083,14 +2087,15 @@ static void tglPrepareSequoiaMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { !memcmp(reinterpret_cast(fn+0x1390),create,sizeof(create)) && !memcmp(reinterpret_cast(fn+0x1030),update,sizeof(update)) && !memcmp(reinterpret_cast(protect),protectFrame,sizeof(protectFrame)) && - !memcmp(reinterpret_cast(protect+0xCA),protectCall,sizeof(protectCall)) && + protectCallReady && tglVMRead(kernelMap,tglKernelMap) && (tglKernelMap>>47)==0x1FFFF; uint64_t guard[20] {1,tglVM.enabled,tglVM.pciBAR,bar ? bar->getLength() : 0,pat,bool(fn),bool(protect),bool(kernelMap)}; if(fn && protect) { - const uintptr_t sites[] {fn,fn+0x159,fn+0x35,fn+0x52,fn+0xB8,fn+0x1390,fn+0x1030,protect,protect+0xCA}; - const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame,protectCall}; - const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame),sizeof(protectCall)}; - for(unsigned i=0;i<9;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); + const uintptr_t sites[] {fn,fn+0x159,fn+0x35,fn+0x52,fn+0xB8,fn+0x1390,fn+0x1030,protect}; + const uint8_t *bytes[] {frame,flags,args,protection,nonEPT,create,update,protectFrame}; + const size_t lengths[] {sizeof(frame),sizeof(flags),sizeof(args),sizeof(protection),sizeof(nonEPT),sizeof(create),sizeof(update),sizeof(protectFrame)}; + for(unsigned i=0;i<8;++i)guard[8+i]=!memcmp(reinterpret_cast(sites[i]),bytes[i],lengths[i]); + guard[16]=protectCallReady; } guard[17]=kernelMap && tglVMRead(kernelMap,tglKernelMap);guard[18]=tglKernelMap;guard[19]=ok; IORegistryEntry::getRegistryRoot()->setProperty("TGLSequoiaMMIOGuard",guard,sizeof(guard)); @@ -2229,7 +2234,7 @@ static void tglPrepareMMIOUC(KernelPatcher &patcher,IOPCIDevice *pci) { } void tglVMPrepare(KernelPatcher &patcher,IOPCIDevice *pci) { // The only private pmap field used is root-table VA at+50. Verify the - // matching native accessor at each exact admitted kernel, without calling + // native accessor contract, without calling // pmap_verify_noncacheable (it deliberately panics on a cacheable mapping). auto verify=patcher.solveSymbol(KernelPatcher::KernelID,"_pmap_verify_noncacheable"); auto kp=patcher.solveSymbol(KernelPatcher::KernelID,"_kernel_pmap"); @@ -2248,9 +2253,16 @@ void tglVMPrepare(KernelPatcher &patcher,IOPCIDevice *pci) { const bool sonoma=(XELP::kernelOS(version)==XELP::OS::Sonoma); const bool sequoia=(XELP::kernelOS(version)==XELP::OS::Sequoia); const bool tahoe=(XELP::kernelOS(version)==XELP::OS::Tahoe); - if (target!=verify-(dp1 ? 0x3930 : monterey ? 0x4190 : ventura ? 0x3D70 : sonoma ? 0x3EF0 : sequoia ? 0x3FE0 : tahoe ? 0x3D40 : 0x3AB0)) return; - const uint8_t rootLoad[] {0x48,0x8B,0x47,0x50}; - if (memcmp(reinterpret_cast(target+0x20),rootLoad,sizeof(rootLoad))) return; + if(sequoia || tahoe) { + uint8_t accessor[36] {}; + const auto copy=reinterpret_cast(nf); + if((target>>47)!=0x1FFFF || copy(target,reinterpret_cast(accessor),sizeof(accessor))!=sizeof(accessor) || + !XELPCall::rootAccessor(accessor,sizeof(accessor)))return; + } else { + if (target!=verify-(dp1 ? 0x3930 : monterey ? 0x4190 : ventura ? 0x3D70 : sonoma ? 0x3EF0 : 0x3AB0)) return; + const uint8_t rootLoad[] {0x48,0x8B,0x47,0x50}; + if (memcmp(reinterpret_cast(target+0x20),rootLoad,sizeof(rootLoad))) return; + } tglVM.nofault=reinterpret_cast(nf); if (!tglVMRead(kp,tglVM.kernelPmap) || !tglVMRead(pb,tglVM.directBase) || !tglVMRead(pe,tglVM.directEnd)) return; diff --git a/WhateverXe/kern_xelp_call_target.hpp b/WhateverXe/kern_xelp_call_target.hpp new file mode 100644 index 00000000..b858a744 --- /dev/null +++ b/WhateverXe/kern_xelp_call_target.hpp @@ -0,0 +1,29 @@ +// Validate a direct x86 call by its resolved target, retaining opcode checks. +#ifndef KERN_XELP_CALL_TARGET_HPP +#define KERN_XELP_CALL_TARGET_HPP +#include +#include +#include +namespace XELPCall { +inline bool targets(const uint8_t *bytes,size_t length,uintptr_t site,uintptr_t target) { + if(!bytes || length<5 || !site || !target || bytes[0]!=0xE8 || site>UINTPTR_MAX-5)return false; + const uint32_t displacement=static_cast(bytes[1]) | + static_cast(bytes[2])<<8 | static_cast(bytes[3])<<16 | + static_cast(bytes[4])<<24; + const uintptr_t next=site+5; + if(displacement&0x80000000U) { + const uint64_t distance=0x100000000ULL-displacement; + return next>=distance && next-distance==target; + } + return next<=UINTPTR_MAX-displacement && next+displacement==target; +} +// The private pmap accessor's field load is the ABI evidence. Its distance +// from the caller and its conditional branch displacement are not ABI. +inline bool rootAccessor(const uint8_t *bytes,size_t length) { + const uint8_t prefix[]={0x55,0x48,0x89,0xe5,0x48,0xb8,0,0,0,0,0,0x80,0xff,0xff,0x48,0x01,0xf0, + 0x48,0xc1,0xe8,0x30,0x3d,0xff,0xff,0,0,0x0f,0x82}; + const uint8_t field[]={0x48,0x8b,0x47,0x50}; + return bytes && length>=36 && !memcmp(bytes,prefix,sizeof(prefix)) && !memcmp(bytes+32,field,sizeof(field)); +} +} +#endif diff --git a/tests/kernel_os_updates.cpp b/tests/kernel_os_updates.cpp index faf7d820..de642ecf 100644 --- a/tests/kernel_os_updates.cpp +++ b/tests/kernel_os_updates.cpp @@ -1,5 +1,6 @@ #include "kern_xelp_os.hpp" #include +#include int main() { using namespace XELP; @@ -12,6 +13,14 @@ int main() { assert(kernelOS("Darwin Kernel Version 24.6.1: update/RELEASE_X86_64")==OS::Sequoia); assert(kernelOS("Darwin Kernel Version 25.6.1: update/RELEASE_X86_64")==OS::Tahoe); assert(kernelOS("Darwin Kernel Version 25.7.0: update/RELEASE_X86_64")==OS::Tahoe); + // Metamorphic family acceptance: vary servicing minor/patch and arbitrary + // build metadata, without adding known Apple builds to an allowlist. + for(unsigned major=24;major<=25;++major) for(unsigned minor=6;minor<(major==24 ? 7U : 9U);++minor) + for(unsigned patch=0;patch<100;patch+=7) { + char version[160]; + snprintf(version,sizeof(version),"Darwin Kernel Version %u.%u.%u: synthetic-%u/RELEASE_X86_64",major,minor,patch,minor*109+patch); + assert(kernelOS(version)==(major==24 ? OS::Sequoia : OS::Tahoe)); + } const char *rejected[] { "Darwin Kernel Version 23.6.0: update/RELEASE_X86_64", "Darwin Kernel Version 24.5.0: update/RELEASE_X86_64", diff --git a/tests/pmap_kernel_contract.py b/tests/pmap_kernel_contract.py new file mode 100644 index 00000000..55d0434d --- /dev/null +++ b/tests/pmap_kernel_contract.py @@ -0,0 +1,64 @@ +"""Check runtime guards in any supplied x86_64 kernel/KC, without a build list.""" +import re +import struct +import subprocess +import tempfile +from pathlib import Path + + +def check(kernel, source, family): + data = kernel.read_bytes() + assert data[:4] == bytes.fromhex('cffaedfe'), 'expected x86_64 Mach-O' + symbols = {} + for line in subprocess.check_output(['nm', str(kernel)], text=True).splitlines(): + parts = line.split() + if len(parts) == 3: + try: + symbols[parts[2]] = int(parts[0], 16) + except ValueError: + pass + segments, pos = [], 32 + for _ in range(struct.unpack_from('\((fn|protect)(\+0x[0-9A-Fa-f]+)?\),([A-Za-z]+),sizeof', block) + assert len(checks) == 8, checks + for base, offset, name in checks: + address = (fn if base == 'fn' else protect) + (int(offset[1:], 16) if offset else 0) + values = re.search(r'const uint8_t ' + name + r'\[\] \{([^}]+)\}', block)[1] + expected = bytes(int(v.strip(), 0) for v in values.split(',')) + assert read(address, len(expected)) == expected, name + offsets = re.findall(r'XELPCall::targets\([^;]+?protect\+(0x[0-9A-Fa-f]+),fn\)', block) + assert len(offsets) == 1, offsets + site = protect + int(offsets[0], 16) + call = read(site, 5) + verify = symbols['_pmap_verify_noncacheable'] + accessor_call = read(verify + 0x10, 8) + assert accessor_call[:4] == bytes.fromhex('4889dee8') + target = verify + 0x18 + struct.unpack_from('