From dbdb3c08a09f1923df5a0ba944816c3790957708 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:21:02 +0900 Subject: [PATCH 01/60] Add opt-in Alder Lake-P boot resource readout --- README.md | 3 ++ README_ja.md | 3 ++ WhateverXe/kern_host.cpp | 3 +- WhateverXe/kern_xe.cpp | 38 +++++++++++++++++++++++++ tests/fixtures/boot_argument_names.json | 3 +- tests/host_publication.py | 6 ++++ 6 files changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f720223b..8567e35e 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,9 @@ installation or update location. - USB-C and internal-display support on Display 13 GPUs. - Improve display reconnection reliability and investigate full system sleep/wake. + +Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. + ## Build and test Use macOS with Xcode command-line tools, Python 3.9 or newer, MacKernelSDK and diff --git a/README_ja.md b/README_ja.md index 338368e4..1a7babeb 100644 --- a/README_ja.md +++ b/README_ja.md @@ -163,6 +163,9 @@ Safariのインストール先や更新による配置変更に依存せずData - Display 13 GPUでのUSB-C・内蔵画面対応。 - 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 + +開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 + ## ビルドとテスト macOS、Xcode command-line tools、Python 3.9以降、MacKernelSDK、Liluの開発用ヘッダ/ライブラリを使用します。CIではTahoeのkernel定義を含むLiluの[1.7.2リリースタグ](https://github.com/acidanthera/Lilu/tree/1.7.2)を使用します。`MacKernelSDK/`と`Lilu.kext/`をリポジトリ直下に配置するか、`--sdk`と`--lilu`でパスを指定してください。 diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index f4eb7fff..684f7727 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -44,7 +44,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { if(!gpu->getProperty("model"))if(const auto name=XELP::model(profile))gpu->setProperty("model",const_cast(name),strlen(name)+1); } // Capture the unmodified firmware/GT state before admitting a new backend. - if(!active && profile.family==XELP::Family::AlderLakeS)xe.processKernel(patcher,info); + if(!active && (profile.family==XELP::Family::AlderLakeS || + profile.family==XELP::Family::AlderLakeP))xe.processKernel(patcher,info); if(active) { XeDataAccess::processKernel(patcher); // Xe admission reads physical PCI identity. Do not install WEG's ID spoof hooks. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index bfc0eb1c..7bd3e3ef 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4095,6 +4095,44 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // This must precede tglLoadHDMIWiring, tglPrepareMMIOUC and TGL FIA reads. auto physicalGPU = OSDynamicCast(IOPCIDevice, info->videoBuiltin); selectProfile(XELP::deviceProfile(physicalGPU)); + // Temporary opt-in bring-up evidence. ADL-P remains unadmitted; only + // the existing bounded Gen12 forcewake transaction writes registers. + if(!xeProfile.admitted() && xeProfile.family==XELP::Family::AlderLakeP && + !xeProfile.overridden() && physicalGPU && checkKernelArgument("-wex-adlp-readout") && + XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { + auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { + struct IO { + volatile uint8_t *base; + bool readable(uint32_t o) const {return o<=0xFFFFFC;} + uint32_t read(uint32_t o) {return *reinterpret_cast(base+o);} + void write(uint32_t o,uint32_t v) {*reinterpret_cast(base+o)=v;} + void delay() {IODelay(100);} + } io {reinterpret_cast(map->getVirtualAddress())}; + uint32_t edram=UINT32_MAX; + auto gt=AlderLakeN::readGT(io,true,&edram); + physicalGPU->setProperty("ADLPGTBoot",>,sizeof(gt)); + physicalGPU->setProperty("ADLPEDRAM",edram,32); + uint32_t registers[][2] { + {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, + {0x46000,0},{0x46004,0},{0x46070,0},{0x46078,0}, + {0x46038,0},{0x46040,0},{0x46048,0},{0x46050,0}, + {0x4610C,0},{0x46110,0},{0x46114,0},{0x46118,0}, + {0xC4000,0},{0xC4030,0},{0xC5014,0},{0x44470,0}, + {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0},{0x64500,0},{0x64600,0}, + {0x161500,0},{0x161504,0},{0x161508,0},{0x16150C,0}, + {0x1010A0,0},{0x1082C0,0},{0x1082C4,0} + }; + for(auto ®:registers)reg[1]=io.read(reg[0]); + physicalGPU->setProperty("ADLPDisplayBoot",registers,sizeof(registers)); + auto display=AlderLakeN::readDisplay(io,true); + physicalGPU->setProperty("ADLPScanoutBoot",&display,sizeof(display)); + SYSLOG("igfx","ADL-P readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x; donor blocked", + static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], + gt.releasedMask,gt.releaseFailedMask); + } + if(map)map->release(); + } if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) && !xeProfile.overridden() && physicalGPU && XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index d9d7603d..ef7e9e94 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -135,5 +135,6 @@ "-wxebeta" ], "-wex-noaccel": [], - "-wex-display-trace": [] + "-wex-display-trace": [], + "-wex-adlp-readout": [] } diff --git a/tests/host_publication.py b/tests/host_publication.py index 21050ccc..85eb5deb 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -70,6 +70,12 @@ XeHost host;host.processKernel(patcher);assert(host.active); assert(host.backlightEnabled==!apple);assert(host.backlightGPU==(!apple?&gpu:nullptr)); } +for(unsigned rev:{0U,12U,255U}) { + DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; + publish=true;published=false;deviceProperty=nullptr;identity=0x46A68086;revision=rev;events.clear();graphics.off=false; + XeHost host;host.processKernel(patcher);assert(!host.active && graphics.off && !host.backlightEnabled); + assert(events[events.size()-2]==8); // Diagnostic dispatch does not admit driver hooks. +} puts("PASS actual host admission: absent/unpublished/unsupported GPUs never enter Xe; published PCI reads precede selected-driver dispatch");} ''' with tempfile.TemporaryDirectory(prefix='wxe-host-') as d: From b419c1ce991587c1ecf90285216dda8c095858e7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 19:22:17 +0900 Subject: [PATCH 02/60] Add Alder Lake-P resource and fixed TC HDMI building blocks --- WhateverXe/kern_display13_fb_topology.hpp | 20 ++- WhateverXe/kern_display13_firmware.hpp | 23 +++ WhateverXe/kern_display13_output_wiring.hpp | 61 +++++--- WhateverXe/kern_display13_tc_clock.hpp | 159 ++++++++++++++++++++ WhateverXe/kern_display13_tc_mode.hpp | 71 +++++++++ WhateverXe/kern_vbt.hpp | 10 +- WhateverXe/kern_xelp_display_resources.hpp | 3 +- WhateverXe/kern_xelp_gt_resources.hpp | 21 ++- tests/adlp_resources.cpp | 57 +++++++ tests/display13_tc_clock.cpp | 100 ++++++++++++ tests/display13_tc_mode.cpp | 60 ++++++++ tests/firmware_ports.cpp | 2 +- 12 files changed, 552 insertions(+), 35 deletions(-) create mode 100644 WhateverXe/kern_display13_firmware.hpp create mode 100644 WhateverXe/kern_display13_tc_clock.hpp create mode 100644 WhateverXe/kern_display13_tc_mode.hpp create mode 100644 tests/adlp_resources.cpp create mode 100644 tests/display13_tc_clock.cpp create mode 100644 tests/display13_tc_mode.cpp diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index b27cb2de..727610a5 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -1,4 +1,4 @@ -// Explicit Combo A/B/C wiring. Board wiring and native backend +// Explicit Combo/TC wiring. Board wiring and native backend // qualification are separate: describing a port does not attach unsupported resources. #ifndef KERN_DISPLAY13_FB_TOPOLOGY_HPP #define KERN_DISPLAY13_FB_TOPOLOGY_HPP @@ -24,7 +24,7 @@ inline bool singleOutputWiring(const Display13OutputWiring &w) { } inline uint32_t singleDDI(const Display13OutputWiring &w) { if(!singleOutputWiring(w))return UINT32_MAX; - for(unsigned d=0;d<3;++d)if(w.selected()&(1U<>27)&15; - if(selector<1 || selector>3)return UINT32_MAX; + if(selector<1 || selector>7)return UINT32_MAX; const auto port=selector-1; const auto mode=(trans>>24)&7; // Explicit source inspection only; runtime adoption keeps the TMDS default. @@ -41,6 +41,10 @@ inline uint32_t bootDDI(const Display13OutputWiring &w,uint32_t trans,bool dpSou if(mode>1)return UINT32_MAX; return (w.hdmi&(1U<(config)))return true; return false; } @@ -82,7 +86,7 @@ inline bool selected(const void *platform,const void *config,const Display13Outp // core below consumes only validated port identities, independent of transport. inline XELP::NativePorts boundPorts(const void *platform,const Display13OutputWiring &w) { if(!platform || !selectedWiring(w))return {}; - for(unsigned d=0;d<3;++d)if((w.selected()&(1U<(platform)+0x20+24*d,w))return {}; return {platform,w.selected()}; } @@ -99,12 +103,12 @@ inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const Display1 if(!platform || bytes(platform); if(p[0x12]!=Ports)return 0; - for(unsigned d=0;d<3;++d)if((w.selected()&(1U<(p+0x20+24*d); - if(config[0]!=d || (d>=3 && (config[4]!=1 || config[5])))return 0; + if(config[0]!=d || (d>=3 && !(w.selected()&(1U<>3)<<24); } } #endif diff --git a/WhateverXe/kern_display13_firmware.hpp b/WhateverXe/kern_display13_firmware.hpp new file mode 100644 index 00000000..309bbef1 --- /dev/null +++ b/WhateverXe/kern_display13_firmware.hpp @@ -0,0 +1,23 @@ +#ifndef KERN_DISPLAY13_FIRMWARE_HPP +#define KERN_DISPLAY13_FIRMWARE_HPP +#include "kern_vbt.hpp" +#include "kern_display13_output_wiring.hpp" +namespace XELP { namespace VBT { + // ADLP/ADLN VBT DDC1/2 => Combo pins1/2, DDC3..6 => TC pins9..12. + // Capability bits are not live modes. HDMI-only records may advertise + // USB-C/TBT capability; the TC runtime must separately prove legacy mode. + inline Display13OutputWiring display13HDMI(const Ports &ports,uint32_t backend) { + Display13OutputWiring w;w.valid=true; + if(!ports.valid())return w; + uint32_t used=0; + for(unsigned d=0;d<7;++d) { + if(!(ports.present&backend&Display13OutputWiring::Physical&(1U<=3 && selector<=6 ? selector+6 : selector<=2 ? selector : 0; + if(!pin || (d<3 ? pin>2 : pin<9) || (used&(1U<3 || (used&(1U<3) : (pin<9 || pin>12))return false; + if(used&(1U<=3?1U:0U) && + c[4]==0x800 && (c[5]&0xB)==2; + } + bool usesDDC(uint32_t bus) const { + if(!described())return false; + for(unsigned d=0;d=3)return false; - if(hdmi&(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; + if(c[0]!=d || c[1]!=(d<3?d+1:d+6) || c[2]!=1 || c[3]!=(d<3?0U:1U) || + c[4]!=0x400 || c[5]!=0x201)return false; + } + for(unsigned d=0;d(p+0x20+24*d); + const bool panel=!d && !c[2]; + c[1]=ddc[d];c[2]=2;c[4]=0x800;c[5]=panel?0x202:(c[5]&~9U)|2U; + } return TGLComboDP::apply(dp,platform,bytes); } }; diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp new file mode 100644 index 00000000..1adbe014 --- /dev/null +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -0,0 +1,159 @@ +// Fixed TMDS on ADLP Dekel PHY. Equations/register fields checked against +// Intel display definitions and Linux v6.12; independent integer implementation. +#ifndef KERN_DISPLAY13_TC_CLOCK_HPP +#define KERN_DISPLAY13_TC_CLOCK_HPP +#include "kern_display13_tc.hpp" +namespace Display13TC { +struct TMDSClock { + struct Word {uint32_t reg,mask,value;}; + uint32_t rate {},reference {},dco {},hs {},ds {}; + Word words[8] {}; + bool valid() const {return rate && reference && dco && hs && ds;} + static TMDSClock calculate(uint32_t rate,uint32_t reference) { + TMDSClock p; + if(rate<25000 || rate>600000 || (reference!=19200 && reference!=24000 && reference!=38400))return p; + const uint32_t high[]={7,5,3,2}; + for(auto h:high) { + for(unsigned d=10;d;--d) { + const auto oscillator=uint64_t(rate)*5*h*d; + if(oscillator<7992000 || oscillator>10000000)continue; + const auto divider=reference*2,integer=uint32_t(oscillator/divider),remainder=uint32_t(oscillator%divider); + if(!integer || integer>255)return {}; + const auto fraction=uint32_t((uint64_t(remainder)<<22)/divider); + const uint32_t integral=oscillator>=9000000?10:8,proportional=integral/2; + const auto target=uint32_t((2000000000ULL/(132*reference)+5)/10); + const auto gain=remainder?uint32_t(200000000ULL/(oscillator*3/10)):0; + if(target>255 || gain>255)return {}; + const uint32_t ratio=h==7?3:h==5?2:h==3?1:0; + p.rate=rate;p.reference=reference;p.dco=uint32_t(oscillator);p.hs=h;p.ds=d; + p.words[0]={0x212c,0x700,0x100}; + p.words[1]={0x20d8,0xff00,0x500}; + p.words[2]={0x20d4,0x1ff00,0x10000|(d>=2?0x4000U:0U)|(ratio<<12)|(d<<8)}; + // Preserve firmware AFC startup unless VBT supplies an explicit policy. + p.words[3]={0x2200,0x1fffff,(integral<<16)|(proportional<<12)|0x200|integer}; + p.words[4]={0x2204,0x1f00ff,((reference==24000?25U:28U)<<16)|target}; + p.words[5]={0x2210,0xe0ff3a00,(reference==38400?0x40000000U:0x20000000U)|0x2000}; + p.words[6]={0x2214,0x7fffff00,(fraction?0x40000000U:0U)|(fraction<<8)}; + p.words[7]={0x2218,0xffff,gain}; + return p; + } + } + return {}; + } + // Exact donor ClockParams is DCO integer/fraction15, P/Q/K. Its + // calculatePllDivisors uses 19.2 MHz for both DSSM codes1 and2, and + // 24 MHz for code0. Recover TMDS rate before encoding the real Dekel PLL. + static uint32_t nativeRate(const uint32_t *params,uint32_t refCode) { + if(!params || refCode>2 || !params[0] || params[0]>1023 || params[1]>32767 || + !params[3] || params[3]>255 || !params[4] || params[4]>3)return 0; + const auto p=params[2];if(p!=2 && p!=3 && p!=5 && p!=7)return 0; + const uint64_t denominator=uint64_t(5)*p*params[3]*params[4]*32768; + const auto rate=(uint64_t(refCode?19200:24000)*(uint64_t(params[0])*32768+params[1])+denominator/2)/denominator; + return rate>=25000 && rate<=600000 ? uint32_t(rate) : 0; + } + static uint32_t frequency(uint32_t reference,uint32_t div0,uint32_t bias,uint32_t hsctl) { + if((reference!=19200 && reference!=24000 && reference!=38400) || + div0==UINT32_MAX || bias==UINT32_MAX || hsctl==UINT32_MAX)return 0; + const uint32_t high[]={2,3,5,7};const auto h=high[(hsctl>>12)&3],d=(hsctl>>8)&15; + const auto pre=(div0>>8)&15,integer=div0&255,fraction=(bias&0x40000000)?((bias>>8)&0x3fffff):0; + if(!d || !pre || !integer)return 0; + return uint32_t((uint64_t(reference)*pre*((uint64_t(integer)<<22)+fraction)+(uint64_t(5*h*d)<<21))/(uint64_t(5*h*d)<<22)); + } +}; +// One masked bank transaction. The caller holds the native port/lifecycle +// mutex and all leases; HIP has its own IRQ-safe lock shared across ports. +struct PHY { + template static bool access(IO &io,const Port &p,uint32_t internal,uint32_t mask,uint32_t &value,bool write) { + Port checked;if(!Port::resolve(p.ddi,checked) || p.phy!=checked.phy || p.hip!=checked.hip || p.shift!=checked.shift || + internal>0x2fff || (internal&3) || !io.powerHeld() || !io.readable(p.hip) || !io.readable(p.phyRegister(internal)))return false; + const auto irq=io.lock();const auto prior=io.read(p.hip);bool changed=false,ok=prior!=UINT32_MAX; + if(ok) { + const auto bank=p.bank(prior,internal);changed=bank!=prior; + if(changed)io.write(p.hip,bank); + ok=io.read(p.hip)==bank; + if(ok) { + const auto reg=p.phyRegister(internal),old=io.read(reg);ok=old!=UINT32_MAX; + if(ok && write) {const auto desired=(old&~mask)|(value&mask);io.write(reg,desired);ok=io.read(reg)==desired;value=desired;} + else if(ok)value=old; + } + if(changed) {io.write(p.hip,prior);if(io.read(p.hip)!=prior){ok=false;io.bankFault();}} + } + io.unlock(irq);return ok; + } +}; +struct TMDSSignal { + // Public Intel HDMI preset coefficients: vswing/preshoot/de-emphasis. + // ADLP lane0 uses LOADGENSELECT 0/2, lane1 3/3. This is independent of + // the electrical preset and must not inherit DP's 0/0 settings. + template static bool program(IO &io,const Port &p,uint32_t preset) { + if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false; + if(preset==UINT32_MAX)preset=9; + if(preset>=10)return false; + const uint32_t swing[]={7,6,4,2,0,0,0,0,0,0}; + const uint32_t emphasis[]={0,0,0,0,0,5,6,7,8,10}; + for(unsigned lane=0;lane<2;++lane) { + const auto bank=0x1000*lane; + // Fixed legacy HDMI occupies both lane pairs (X2 in each bank). + uint32_t mode=0x80; + if(!PHY::access(io,p,bank+0xa0,0xc0,mode,true))return false; + uint32_t wake=0; + if(!PHY::access(io,p,bank+0xd00,UINT32_MAX,wake,true))return false; + for(unsigned tx=0;tx<2;++tx) { + uint32_t value=swing[preset]|(emphasis[preset]<<8); + if(!PHY::access(io,p,bank+0x2c0+4*tx,0x3ff07,value,true))return false; + } + uint32_t load=lane?0x78:0x40; + if(!PHY::access(io,p,bank+0x2c8,0x7c,load,true))return false; + } + return true; + } +}; + +struct Clock { + static constexpr uint32_t Gate=0x164280,Enable=1U<<31,Locked=1U<<30,Power=1U<<27,Powered=1U<<26; + 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) { + 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}; + 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; + } + 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); + bool ok=old!=UINT32_MAX; + if(ok) {const auto desired=(old&~mask)|(value&mask);io.write(reg,desired);ok=(io.read(reg)&mask)==(desired&mask);} + io.unlock(irq);return ok; + } + template static bool wait(IO &io,uint32_t reg,uint32_t mask,uint32_t expected) { + for(unsigned elapsed=0;;elapsed+=10) { + const auto v=io.read(reg);if(v==UINT32_MAX)return false; + if((v&mask)==expected)return true; + if(elapsed==1000)return false;io.delayUs(10); + } + } + template static bool disable(IO &io,const Port &p) { + if(!preflight(io,p))return false; + 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); + } + 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 + // its native path has explicitly quiesced and disabled the clock. + if((io.read(p.pll)&(Enable|Locked)) || !(io.read(Gate)&gateBit(p.tc)) || (io.read(p.clock)&0xf0000000))return false; + if(!rmw(io,p.pll,Power,Power) || !wait(io,p.pll,Powered,Powered))return false; + for(const auto &word:plan.words) { + auto value=word.value;if(!PHY::access(io,p,word.reg,word.mask,value,true))return false; + } + return rmw(io,p.pll,Enable,Enable) && wait(io,p.pll,Locked,Locked) && + rmw(io,p.clock,0xf0000000,0x80000000) && rmw(io,Gate,gateBit(p.tc),0); + } +}; +} +#endif diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp new file mode 100644 index 00000000..1ac793c0 --- /dev/null +++ b/WhateverXe/kern_display13_tc_mode.hpp @@ -0,0 +1,71 @@ +// Fixed legacy 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 +#include "kern_display13_tc.hpp" +#include "kern_display13_power_bridge.hpp" +namespace Display13TC { +struct Mode { + enum class Phase : uint32_t {Idle,Active,Cleanup}; + struct Change { + Status status {Status::Invalid};Result phy; + Display13Power::Result power;uint32_t native {}; + }; + uintptr_t controller {},object {};unsigned ddi {}; + Phase phase {Phase::Idle};bool disconnectPending {}; + Ownership ownership;Display13Power::Lease lease; + Mode()=default;Mode(const Mode&)=delete;Mode& operator=(const Mode&)=delete; + static uint32_t domains(unsigned d) { + Port p;return Port::resolve(d,p) ? Display13Power::ddiDomain(d)| + Display13Power::auxDomain(d)|Display13Power::bit(Display13Power::Domain::PW2) : 0; + } + template Change connect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) { + Change r; + if(!owner || !identity || !domains(d))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); + 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; + r.power=io.acquireDomains(lease,domains(d)); + 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); + if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} + r.native=io.nativeSetMode(4); + if(r.native || io.nativeMode()!=4){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) { + Change r; + if(!owner || !identity || !domains(d))return r; + if(!controller){r.status=io.nativeMode()==0 ? Status::Complete : Status::NeedsAdoption;return r;} + if(owner!=controller || identity!=object || d!=ddi){r.status=Status::InUse;return r;} + disconnectPending=true; + if(!io.quiescent(owner,identity,d)){r.status=Status::InUse;return r;} + phase=Phase::Cleanup; + if(ownership.owner) { + r.phy=ownership.release(io,owner,identity,d); + if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} + } + if(io.nativeMode()!=0) { + r.native=io.nativeSetMode(0); + if(r.native || io.nativeMode()!=0){r.status=Status::Readback;return r;} + } + if(lease.owner) { + r.power=io.releaseDomains(lease); + 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; + } +}; +} +#endif diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp index 1294f998..df539be0 100644 --- a/WhateverXe/kern_vbt.hpp +++ b/WhateverXe/kern_vbt.hpp @@ -15,12 +15,14 @@ inline uint64_t u64(const uint8_t *p) {return uint64_t(u32(p)) | uint64_t(u32(p+ enum class Status : uint32_t { Missing, Valid, Header, Checksum, Blocks, Version, Children, Conflict, Platform }; struct Port { uint32_t type {},dvo {},ddc {},aux {},flags {},capabilities {}; + uint32_t hdmiPreset {UINT32_MAX}; // BDB child byte7, table index; absent firmware uses default. // flags preserves byte23 (lane reversal, LSPCON, HPD polarity, lane count). // capabilities preserves byte33 (USB-C/TBT capabilities, NOT current mode). bool dp() const {return type&4;} bool internal() const {return type&0x1000;} - bool fixedTMDS() const {return (type&0x12)==0x12 && !(type&(4|0x1000|0x400|0x20|1)) && - !(flags&0x17) && !(capabilities&3);} + bool tmdsOnly() const {return (type&0x12)==0x12 && !(type&(4|0x1000|0x400|0x20|1)) && + !(flags&0x17);} + bool fixedTMDS() const {return tmdsOnly() && !(capabilities&3);} }; struct Ports { Status status {Status::Missing}; @@ -135,7 +137,7 @@ inline uint32_t ddi(Family family,uint32_t dvo) { const uint8_t hd[]={0,1,2,3,12,14,16,18,20}; for(unsigned p=0;p<9;++p)if(dvo==dp[p] || dvo==hd[p]) { if(adapter==DisplayAdapter::TGL)return p==2 ? PortCount : p; - if(adapter==DisplayAdapter::ADLN)return p<2 ? p : p>=5 ? p-2 : PortCount; + if((adapter==DisplayAdapter::ADLN || adapter==DisplayAdapter::ADLP))return p<2 ? p : p>=5 ? p-2 : PortCount; if(adapter==DisplayAdapter::ADLS)return p==0 ? 0 : p<5 ? p+2 : PortCount; } return PortCount; @@ -184,7 +186,7 @@ inline Ports parse(const void *data,size_t length,Family family) { const auto d=ddi(family,c[16]); if(d==PortCount){++r.unmapped;continue;} if(r.present&(1U< namespace XELP { -struct SmallGTResources { +struct GTResources { uint32_t slices {}, dss {}, nativeSubslices {}, eus {}, l3Banks {}, urbKiB {}; }; +using SmallGTResources = GTResources; // Gen12 SSEU: one slice, up to two DSS in these small GT SKUs, one disable // bit per EU pair. Native subslices are twice the DSS population. Preserve @@ -35,5 +36,23 @@ inline bool decodeSmallGT(const uint32_t *fuses, SmallGTResources &out) { fuses[2]&0xFF,fuses[3]&0xFF,out); } +// ADL-P Gen12.3 retains the Xe-LP fuse encoding, with up to six DSS and +// eight L3 banks. Capacity comes from fuses rather than a CPU/board model. +// The donor URB backend handles 4/6/8 banks; reject unsupported capacities. +inline bool decodeADLPGT(const uint32_t *fuses, GTResources &out) { + out={}; + if(!fuses)return false; + for(unsigned i=0;i<5;++i)if(fuses[i]==UINT32_MAX)return false; + const auto slice=fuses[0]&0xFF,mask=fuses[1]&0x3F; + const auto video=(~fuses[4])&0xFF,enhance=((~fuses[4])>>16)&0xF; + if(slice!=1 || !mask || (video!=1 && video!=5) || enhance!=1)return false; + const auto dss=static_cast(__builtin_popcount(mask)); + const auto perDSS=2*(8-static_cast(__builtin_popcount(fuses[2]&0xFF))); + const auto banks=8-static_cast(__builtin_popcount(fuses[3]&0xFF)); + if((perDSS!=8 && perDSS!=12 && perDSS!=16) || (banks!=4 && banks!=6 && banks!=8))return false; + out={1,dss,2*dss,dss*perDSS,banks,64*banks}; + return true; +} + } #endif diff --git a/tests/adlp_resources.cpp b/tests/adlp_resources.cpp new file mode 100644 index 00000000..197a8c4f --- /dev/null +++ b/tests/adlp_resources.cpp @@ -0,0 +1,57 @@ +#include "kern_xelp_gt_resources.hpp" +#include "kern_display13_firmware.hpp" +#include "kern_display13_fb_topology.hpp" +#include +#include +#include +using namespace XELP; +int main() { + // Hardware captures: 46A6, six DSS, 96 EU, eight L3 banks, VCS0/2. + uint32_t f[]={1,0x3f,0,0,0xe00fa};GTResources r; + assert(decodeADLPGT(f,r) && r.dss==6 && r.nativeSubslices==12 && r.eus==96 && r.l3Banks==8 && r.urbKiB==512); + assert(!decodeSmallGT(f,r)); + for(unsigned mask=1;mask<64;++mask)for(unsigned eu=0;eu<256;++eu)for(unsigned l3=0;l3<256;++l3) { + uint32_t v[]={1,mask,eu,l3,0xe00fa}; + const auto per=2*(8-__builtin_popcount(eu)),banks=8-__builtin_popcount(l3); + const bool expected=(per==8 || per==12 || per==16) && (banks==4 || banks==6 || banks==8); + assert(decodeADLPGT(v,r)==expected); + if(expected)assert(r.eus==unsigned(per*__builtin_popcount(mask)) && r.l3Banks==unsigned(banks)); + else assert(!r.eus); + } + assert(!decodeADLPGT(nullptr,r)); + for(unsigned i=0;i<5;++i){auto save=f[i];f[i]=~0U;assert(!decodeADLPGT(f,r));f[i]=save;} + for(unsigned bad:{0U,2U,3U}){f[0]=bad;assert(!decodeADLPGT(f,r));}f[0]=1; + f[4]=0xfffff;assert(!decodeADLPGT(f,r)); + VBT::Ports ports;ports.status=VBT::Status::Valid;ports.present=0x3a; + ports.port[1]={0x68c6,7,0,0x10,0xc0,0}; + ports.port[3]={0x60d2,13,3,0,0xc0,3}; + ports.port[4]={0x60d2,15,4,0,0xc0,3}; + ports.port[5]={0x60d6,17,5,0x80,0xc0,3}; + auto w=VBT::display13HDMI(ports,0x7b); + assert(w.described() && w.hdmi==0x18 && w.ddc[3]==9 && w.ddc[4]==10); + assert(!ports.hdmi(3).hdmi); // Existing Combo policy is unchanged. + assert(Display13FBTopology::xeLPDWiring(w)); + assert(!Display13FBTopology::supportedWiring(w)); // Runtime stays closed until hooks are installed. + assert(!identify(0x46a68086,12).admitted()); + for(unsigned d=3;d<7;++d) { + assert(VBT::ddi(Family::AlderLakeP,13+2*(d-3))==d); + auto p=ports;p.present=1U<=3?1U:0U,0x400,0x201}; + const uint32_t panel[]={0,0,0,0,2,0x18}; + memcpy(native+0x20+24*k,k?rec:panel,24); + } + assert(Display13FBTopology::apply(out,sizeof(out),native,v)); + const auto c=reinterpret_cast(out+0x20+24*d); + assert(v.matchesHDMI(c) && c[3]==1 && c[1]==d+6); + assert(Display13FBTopology::boundPorts(out,v).selected==(1U< +#include +#include +#include +using namespace Display13TC; +struct IO { + Port p;std::map regs,bank; + std::vector> writes; + bool held {},power {true},owner {true},idle {true},stuckPower {},stuckLock {},fault {}; + unsigned time {},locks {},unlocks {};uint32_t reject {},absent {}; + IO(unsigned ddi) { + assert(Port::resolve(ddi,p)); + regs={{p.pll,0x20000000},{p.clock,0x1234},{p.ready,0xc},{p.buffer,0xc0}, + {Clock::Gate,0xdeadffff|Clock::gateBit(p.tc)},{p.hip,0x01010101}}; + for(auto w:TMDSClock::calculate(148500,38400).words)bank[w.reg]=0xa5a5a5a5; + } + bool powerHeld(){return power;} + bool owned(unsigned d){return owner && d==p.ddi;} + 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);} + uint32_t read(uint32_t r){assert(readable(r));return regs.count(r)?regs[r]:bank[key(r)];} + void write(uint32_t r,uint32_t v) { + assert(held && readable(r));writes.emplace_back(r,v);if(r==reject)return; + if(r==p.pll) { + auto status=regs[r]&(Clock::Locked|Clock::Powered); + if(!stuckPower)status=(status&~Clock::Powered)|((v&Clock::Power)?Clock::Powered:0); + if(!stuckLock)status=(status&~Clock::Locked)|((v&Clock::Enable)?Clock::Locked:0); + regs[r]=(v&~(Clock::Locked|Clock::Powered))|status; + } else if(regs.count(r))regs[r]=v;else bank[key(r)]=v; + } + unsigned lock(){assert(!held);held=true;++locks;return 7;} + void unlock(unsigned irq){assert(held && irq==7);held=false;++unlocks;} + void delayUs(unsigned n){assert(!held);time+=n;} + void bankFault(){fault=true;} +}; +int main() { + const auto full=TMDSClock::calculate(148500,38400),hd=TMDSClock::calculate(74250,38400); + // Independent live i915 shared-PLL captures: central FHD / outer 720p. + const uint32_t f[]={0x100,0x500,0x15400,0x84274,0x1c0027,0x40002000,0x41000000,0x4a}; + const uint32_t h[]={0x100,0x500,0x16500,0xa5278,0x1c0027,0x40002000,0x76600000,0x47}; + assert(full.valid() && hd.valid()); + for(unsigned i=0;i<8;++i){assert(full.words[i].value==f[i]);assert(hd.words[i].value==h[i]);} + for(unsigned ref:{19200U,24000U,38400U})for(unsigned rate=25000;rate<=600000;rate+=125) { + auto plan=TMDSClock::calculate(rate,ref); + // Finite PLL divider range has real clock holes; do not round those + // into a different display timing. Independently enumerate solutions. + unsigned oscillator=0;for(unsigned h:{7U,5U,3U,2U})for(unsigned d=10;d;--d) + if(!oscillator && 5*rate*h*d>=7992000 && 5*rate*h*d<=10000000)oscillator=5*rate*h*d; + assert(plan.valid()==(oscillator && oscillator/(2*ref)<=255)); + if(!plan.valid())continue; + const auto actual=TMDSClock::frequency(ref,plan.words[3].value,plan.words[6].value,plan.words[2].value); + assert(actual==rate); + for(auto w:plan.words)assert(!(w.value&~w.mask)); + } + for(unsigned rate:{0U,24999U,600001U,UINT32_MAX})assert(!TMDSClock::calculate(rate,38400).valid()); + for(unsigned ref:{0U,1U,25000U,UINT32_MAX})assert(!TMDSClock::calculate(148500,ref).valid()); + for(unsigned ddi=3;ddi<=6;++ddi) { + IO io(ddi);const auto before=io.regs;auto previous=io.bank; + assert(Clock::enable(io,io.p,full));assert(io.regs[io.p.hip]==before.at(io.p.hip)); + assert(io.regs[io.p.pll]==0xec000000 && io.regs[io.p.clock]==0x80001234); + assert(io.regs[Clock::Gate]==(before.at(Clock::Gate)&~Clock::gateBit(ddi-3))); + for(auto w:full.words)assert(io.bank[w.reg]==((previous[w.reg]&~w.mask)|w.value)); + const auto count=io.writes.size();assert(!Clock::enable(io,io.p,hd) && io.writes.size()==count); + assert(Clock::disable(io,io.p));assert(io.regs[io.p.pll]==0x20000000 && io.regs[io.p.clock]==0x1234); + assert(io.regs[Clock::Gate]==before.at(Clock::Gate) && io.locks==io.unlocks); + assert(Clock::enable(io,io.p,hd));assert(Clock::disable(io,io.p)); + } + const uint32_t native[]={464,2048,3,2,2}; + assert(TMDSClock::nativeRate(native,2)==148500); + assert(!TMDSClock::nativeRate(native,3) && !TMDSClock::nativeRate(nullptr,2)); + for(unsigned d=3;d<=6;++d)for(unsigned preset=0;preset<10;++preset) { + IO io(d);auto before=io.regs[io.p.hip]; + for(unsigned lane=0;lane<2;++lane)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[lane*0x1000+reg]=0xa5a5a5a5; + assert(TMDSSignal::program(io,io.p,preset)); + const unsigned swing[]={7,6,4,2,0,0,0,0,0,0},emphasis[]={0,0,0,0,0,5,6,7,8,10}; + for(unsigned lane=0;lane<2;++lane) { + assert(io.bank[lane*0x1000+0xa0]==((0xa5a5a5a5&~0xc0U)|0x80)); + assert(io.bank[lane*0x1000+0xd00]==0); + for(unsigned tx=0;tx<2;++tx)assert(io.bank[lane*0x1000+0x2c0+4*tx]==((0xa5a5a5a5&~0x3ff07U)|swing[preset]|(emphasis[preset]<<8))); + assert(io.bank[lane*0x1000+0x2c8]==((0xa5a5a5a5&~0x7cU)|(lane?0x78U:0x40U))); + } + assert(io.regs[io.p.hip]==before); + const auto count=io.writes.size();assert(!TMDSSignal::program(io,io.p,10) && io.writes.size()==count); + io.owner=false;assert(!TMDSSignal::program(io,io.p,7) && io.writes.size()==count); + } + for(unsigned failure=0;failure<8;++failure) { + IO io(3); + if(failure==0)io.power=false;if(failure==1)io.owner=false;if(failure==2)io.idle=false; + if(failure==3)io.regs[io.p.ready]=5;if(failure==4)io.regs[io.p.buffer]|=1U<<31; + if(failure==5)io.regs[io.p.buffer]&=~64U;if(failure==6)io.absent=io.p.pll;if(failure==7)io.regs[io.p.pll]=~0U; + assert(!Clock::enable(io,io.p,full) && io.writes.empty()); + assert(!Clock::disable(io,io.p) && io.writes.empty()); + } + {IO io(3);io.stuckPower=true;assert(!Clock::enable(io,io.p,full) && io.time==1000 && io.writes.size()==1);} + {IO io(3);io.stuckLock=true;assert(!Clock::enable(io,io.p,full) && io.time==1000);assert(!(io.regs[io.p.clock]&0xf0000000));} + {IO io(3);io.reject=io.p.phyRegister(0x2200);auto bank=io.regs[io.p.hip];assert(!Clock::enable(io,io.p,full));assert(io.regs[io.p.hip]==bank && !(io.regs[io.p.pll]&Clock::Enable));} + puts("PASS Display13 TC HDMI PLL live fixtures, clock sweep, reserved bits, bounded failures and lifecycle"); +} diff --git a/tests/display13_tc_mode.cpp b/tests/display13_tc_mode.cpp new file mode 100644 index 00000000..ab239821 --- /dev/null +++ b/tests/display13_tc_mode.cpp @@ -0,0 +1,60 @@ +#include "kern_display13_tc_mode.hpp" +#include +#include +#include +#include +using namespace Display13TC; +struct IO { + Port port;std::map regs; + Display13Power::State power;bool ready {true},adopt {},quiet {true},nativeFail {},auxOK {true}; + unsigned native {},time {};std::vector sequence; + IO(unsigned d){assert(Port::resolve(d,port));regs={{port.ready,4},{port.buffer,0},{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8e380000}};assert(Display13Power::initialize(*this,power,Display13Power::nativeDomains(0x7b)));} + bool readable(uint32_t r){return regs.count(r);} + uint32_t read(uint32_t r){assert(readable(r));return regs[r];} + void write(uint32_t r,uint32_t v) { + assert(readable(r));sequence.push_back(r); + if(r==0x45404 || r==0x45454 || r==0x45444){auto mask=r==0x45404?0xa80aU:0x2aaaU;regs[r]=(v&~(mask>>1))|((v&mask)>>1);} + else regs[r]=v; + } + void delayUs(unsigned t){time+=t;} + bool admissionReady(){return ready;} + unsigned nativeMode(){return native;} + unsigned nativeSetMode(unsigned v){sequence.push_back(0x550);if(nativeFail)return 1;native=v;return 0;} + bool tcAuxSupported(){return true;} + bool prepareTCAux(unsigned){return true;} + bool tcAuxHealthy(unsigned){return auxOK;} + Display13Power::Result acquireDomains(Display13Power::Lease &l,uint32_t d){return Display13Power::acquire(*this,power,l,d);} + Display13Power::Result releaseDomains(Display13Power::Lease &l){return Display13Power::release(*this,power,l);} + bool powerHeld(uintptr_t owner,uintptr_t object,unsigned d){return owner==1 && object==2 && d==port.ddi && Display13Power::held(*this,power,Mode::domains(d));} + bool inheritedVerified(uintptr_t,uintptr_t,unsigned){return adopt;} + bool quiescent(uintptr_t,uintptr_t,unsigned){return quiet;} +}; +int main() { + for(unsigned d=3;d<=6;++d) { + IO io(d);Mode m;assert(m.connect(io,1,2,d).status==Status::Complete); + assert(m.phase==Mode::Phase::Active && io.native==4 && m.ownership.active && m.lease.owner==&io.power); + assert(io.sequence.back()==0x550 && io.regs[io.port.buffer]&64); + auto count=io.sequence.size();assert(m.connect(io,1,2,d).status==Status::Complete && io.sequence.size()==count); + assert(m.connect(io,1,3,d).status==Status::InUse && io.sequence.size()==count); + io.quiet=false;assert(m.disconnect(io,1,2,d).status==Status::InUse && m.disconnectPending && m.lease.owner); + assert(m.connect(io,1,2,d).status==Status::Complete && !m.disconnectPending); + io.quiet=true;io.regs[io.port.buffer]|=1U<<31; + assert(m.disconnect(io,1,2,d).status==Status::InUse && m.lease.owner); + io.regs[io.port.buffer]&=~(1U<<31); + assert(m.disconnect(io,1,2,d).status==Status::Complete); + assert(m.phase==Mode::Phase::Idle && !m.ownership.owner && !m.lease.owner && !io.native); + assert(m.connect(io,1,2,d).status==Status::Complete);assert(m.disconnect(io,1,2,d).status==Status::Complete); + } + {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040;io.adopt=true; + assert(m.connect(io,1,2,3).status==Status::Complete);assert(io.regs[io.port.buffer]==0x80000040);} + {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040; + assert(m.connect(io,1,2,3).status==Status::NeedsAdoption && m.phase==Mode::Phase::Cleanup && m.lease.owner); + assert(!io.native && m.connect(io,1,2,3).status==Status::InUse);} + {IO io(3);Mode m;io.nativeFail=true; + assert(m.connect(io,1,2,3).status==Status::Readback && m.ownership.owner && m.lease.owner); + io.nativeFail=false;assert(m.disconnect(io,1,2,3).status==Status::Complete);} + {IO io(3);Mode m;io.auxOK=false; + assert(m.connect(io,1,2,3).status==Status::NoPower && !m.ownership.owner && !io.native);} + {IO io(3);Mode m;io.ready=false;assert(m.connect(io,1,2,3).status==Status::NotReady && io.sequence.empty());} + puts("PASS Display13 legacy TC mode lifecycle: power/ownership/native order, adoption, retries and pending cleanup"); +} diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index 206cab7e..60f3aa1a 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -85,7 +85,7 @@ int main(int argc,char **argv){ } for(unsigned dvo=0;dvo<256;++dvo) assert(ddi(Family::RaptorLakeS,dvo)==ddi(Family::AlderLakeS,dvo)); - for(auto family:{Family::Unknown,Family::RocketLake,Family::AlderLakeP,Family::RaptorLakeP}) + for(auto family:{Family::Unknown,Family::RocketLake,Family::RaptorLakeP}) assert(parse(desktop.data(),desktop.size(),family).status==Status::Platform); auto a=parse(adl.data(),adl.size(),Family::AlderLakeN);assert(a.valid()&&a.present==0xB&&a.hdmi(3).hdmi==3); From acb9512af6186df0a98418670c137b904edcdf84 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:09:38 +0900 Subject: [PATCH 03/60] Preserve explicit ADL-P load-only recovery boundaries --- README.md | 1 + README_ja.md | 1 + WhateverXe/kern_xe.cpp | 64 +++++++++++++++++++ WhateverXe/kern_xe.hpp | 5 ++ tests/adlp_load_only.py | 84 +++++++++++++++++++++++++ tests/fixtures/boot_argument_names.json | 3 +- tools/test.py | 2 +- 7 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 tests/adlp_load_only.py diff --git a/README.md b/README.md index 8567e35e..33e932d4 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ installation or update location. Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. +`-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on an ADL-P device using an explicit backend override, for load-only diagnostics without GPU initialization. ## Build and test diff --git a/README_ja.md b/README_ja.md index 1a7babeb..864f85f3 100644 --- a/README_ja.md +++ b/README_ja.md @@ -165,6 +165,7 @@ Safariのインストール先や更新による配置変更に依存せずData 開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 +`-wex-adlp-load-only` は既存バックエンドを明示選択したADL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 ## ビルドとテスト diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 7bd3e3ef..45ef64be 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4095,6 +4095,17 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // This must precede tglLoadHDMIWiring, tglPrepareMMIOUC and TGL FIA reads. auto physicalGPU = OSDynamicCast(IOPCIDevice, info->videoBuiltin); selectProfile(XELP::deviceProfile(physicalGPU)); + // ADLP_LOAD_ONLY_KERNEL: do not enter TGL MMIO/FIA, bootstrap or VM setup. + const auto physicalEntry=XELP::deviceEntry(xeProfile.identity); + adlpLoadOnly=physicalEntry && physicalEntry->family==XELP::Family::AlderLakeP && + xeProfile.admitted() && xeProfile.overridden() && + XELP::hostOS(XELP::kernelOS(version)) && checkKernelArgument("-wex-adlp-load-only"); + if(adlpLoadOnly) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyKernel",true); + SYSLOG("igfx","ADL-P load-only: native FB/accelerator probes will be rejected; no GPU initialization"); + return; + } + // ADLP_LOAD_ONLY_KERNEL_END // Temporary opt-in bring-up evidence. ADL-P remains unadmitted; only // the existing bounded Gen12 forcewake transaction writes registers. if(!xeProfile.admitted() && xeProfile.family==XELP::Family::AlderLakeP && @@ -4241,6 +4252,12 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { + // No full donor patch set or native display/GT dispatcher in this stage. + if(adlpLoadOnly) { + if(index==kextIntelTGL.loadIndex || index==kextIntelTGLFb.loadIndex) + return processADLPLoadOnly(patcher,index,address,size); + return false; + } if((tglRequested || display13FramebufferRequested) && kextAppleIntelTGLCompat.loadIndex==index) { const auto probe=patcher.solveSymbol(index,"__ZN17AppleIntelPortHAL13probePortModeEv",address,size); patcher.clearError(); @@ -9116,6 +9133,53 @@ bool XE::processAlderLakeNRejectedAccelerator(KernelPatcher &patcher,size_t inde patcher.clearError();return ready; } +// ADLP_LOAD_ONLY_HOOKS: these replacements never call the original methods. +IOService *XE::rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPFramebufferProbeRejected",true); + SYSLOG("igfx","ADL-P load-only: framebuffer probe rejected before native entry"); + return nullptr; +} +IOService *XE::rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPAcceleratorProbeRejected",true); + SYSLOG("igfx","ADL-P load-only: accelerator probe rejected before native entry"); + return nullptr; +} +bool XE::rejectADLPStart(IOService *,IOService *) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyUnexpectedStart",true); + return false; +} +bool XE::processADLPLoadOnly(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const bool fb=index==kextIntelTGLFb.loadIndex; + const char *probe=fb ? "__ZN31AppleIntelFramebufferController5probeEP9IOServicePi" : + "__ZN16IntelAccelerator5probeEP9IOServicePi"; + const char *start=fb ? "__ZN31AppleIntelFramebufferController5startEP9IOService" : + "__ZN16IntelAccelerator5startEP9IOService"; + const uint32_t probeOffset=fb?0x446BC:0x238E2,startOffset=fb?0x3F1FC:0x23ECC; + const uint8_t fbHead[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x83,0xec,0x10,0x48,0x89,0xf3,0x49,0x89,0xfe}; + const uint8_t gtHead[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; + const uint8_t startHead[]={0x55,0x48,0x89,0xe5}; + auto p=patcher.solveSymbol(index,probe,address,size); + auto q=patcher.solveSymbol(index,start,address,size);patcher.clearError(); + bool ready=adlpLoadOnly && (fb || index==kextIntelTGL.loadIndex) && size>probeOffset+32 && size>startOffset+32 && + p==address+probeOffset && q==address+startOffset && + !memcmp(reinterpret_cast(p),fb?fbHead:gtHead,sizeof(gtHead)) && + !memcmp(reinterpret_cast(q),startHead,sizeof(startHead)); + if(ready) { + // Also block start as an independent boundary. A rejected probe must + // not attach; an unexpected direct start still cannot initialize GPU. + KernelPatcher::RouteRequest routes[]={ + {start,rejectADLPStart}, + {probe,fb?rejectADLPFramebufferProbe:rejectADLPAcceleratorProbe} + }; + ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size); + } + patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty(fb?"ADLPFramebufferLoadOnlyReady":"ADLPAcceleratorLoadOnlyReady",ready); + SYSLOG("igfx","ADL-P load-only: %s probe/start barriers ready=%u",fb?"framebuffer":"accelerator",ready); + return ready; +} +// ADLP_LOAD_ONLY_HOOKS_END + bool XE::processAlderLakeNLoadOnly(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { KernelPatcher::RouteRequest load[] { {"__ZN16IntelAccelerator5probeEP9IOServicePi",wrapTGLAcceleratorProbe,orgTGLAcceleratorProbe}, diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 71bd7350..b0847c9e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -62,6 +62,11 @@ class XE { bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); private: + bool adlpLoadOnly {}; + bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); + static IOService *rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *); + static bool rejectADLPStart(IOService *,IOService *); XELP::DisplayContract::Mask display12Available() const; XELP::DisplayContract::Mask display13Available() const; // Do not inline either helper into the small kext-load dispatcher. diff --git a/tests/adlp_load_only.py b/tests/adlp_load_only.py new file mode 100644 index 00000000..829807d4 --- /dev/null +++ b/tests/adlp_load_only.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Exercise production ADL-P load-only selection and both driver boundaries.""" +from pathlib import Path +import subprocess,tempfile +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] +dispatch=s[s.index('bool XE::processKext('):s.index(' if((tglRequested',s.index('bool XE::processKext('))] +assert dispatch.index('if(adlpLoadOnly)') +#include +#include +#include +#include +#include +#include "kern_xelp_platform.hpp" +#include "kern_xelp_os.hpp" +using mach_vm_address_t=uintptr_t; +#define arrsize(a) (sizeof(a)/sizeof((a)[0])) +#define SYSLOG(...) do{}while(0) +struct IOService{};using SInt32=int32_t; +struct IORegistryEntry { + static std::map props; + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *k,bool v){props[k]=v;} +};std::map IORegistryEntry::props; +struct Kext {size_t loadIndex;};Kext kextIntelTGLFb{1},kextIntelTGL{2}; +bool arg=true;bool checkKernelArgument(const char *s){assert(!strcmp(s,"-wex-adlp-load-only"));return arg;} +const char *version="Darwin Kernel Version 20.6.0: Thu Jul 6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64"; +struct KernelPatcher { + struct RouteRequest {const char *symbol;uintptr_t replacement; + templateRouteRequest(const char *s,T t):symbol(s),replacement(reinterpret_cast(t)){} + }; + bool wrongSymbol=false;unsigned routed=0; + templateT solveSymbol(size_t index,const char *s,uintptr_t base,size_t) { + bool start=strstr(s,"5start")!=nullptr;return base+(index==1?(start?0x3f1fc:0x446bc):(start?0x23ecc:0x238e2))+(wrongSymbol?1:0); + } + void clearError(){} + bool routeMultipleShort(size_t,RouteRequest *routes,size_t count,uintptr_t,size_t) { + assert(count==2 && strstr(routes[0].symbol,"5start") && strstr(routes[1].symbol,"5probe")); + // Calling the actual replacements must reject independently of object data. + assert(!reinterpret_cast(routes[0].replacement)(nullptr,nullptr)); + assert(!reinterpret_cast(routes[1].replacement)(nullptr,nullptr,nullptr)); + routed+=2;return true; + } +}; +struct XE { + bool adlpLoadOnly=false;XELP::Profile xeProfile;unsigned initialized=0; + void select(){SELECTION ++initialized;} + bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); + static IOService *rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *); + static bool rejectADLPStart(IOService *,IOService *); +}; +BODY +int main(){ + unsigned cases=0; + for(unsigned physical:{0x46a6U,0x46a3U,0x9a49U,0x4680U})for(bool flag:{false,true})for(bool override:{false,true}){ + XE xe;arg=flag;xe.xeProfile=XELP::identify((physical<<16)|0x8086,12,((override?0x9a49:physical)<<16)|0x8086);xe.select(); + bool expected=(physical==0x46a6 || physical==0x46a3)&&flag&&override; + assert(xe.adlpLoadOnly==expected && xe.initialized==!expected);++cases; + } + for(unsigned index:{1U,2U})for(unsigned bad=0;bad<4;++bad){ + std::vector image(0x50000);auto base=reinterpret_cast(image.data()); + uint8_t fb[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x83,0xec,0x10,0x48,0x89,0xf3,0x49,0x89,0xfe}; + uint8_t gt[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; + unsigned p=index==1?0x446bc:0x238e2,q=index==1?0x3f1fc:0x23ecc; + memcpy(image.data()+p,index==1?fb:gt,17);memcpy(image.data()+q,gt,4); + XE xe;xe.adlpLoadOnly=bad!=1;KernelPatcher patcher;patcher.wrongSymbol=bad==2; + if(bad==3)image[p+8]^=1; + IORegistryEntry::props.clear(); + assert(xe.processADLPLoadOnly(patcher,index,base,image.size())==(bad==0)); + assert(patcher.routed==(bad==0?2U:0U)); + if(!bad){assert(IORegistryEntry::props.at(index==1?"ADLPFramebufferProbeRejected":"ADLPAcceleratorProbeRejected"));assert(IORegistryEntry::props.at("ADLPLoadOnlyUnexpectedStart"));} + ++cases; + } + printf("PASS %u production ADLP load-only selection/probe/start checks\n",cases); +} +'''.replace('SELECTION',selection).replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='adlp-load-only-') 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/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index ef7e9e94..afacd671 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -136,5 +136,6 @@ ], "-wex-noaccel": [], "-wex-display-trace": [], - "-wex-adlp-readout": [] + "-wex-adlp-readout": [], + "-wex-adlp-load-only": [] } diff --git a/tools/test.py b/tools/test.py index c4b43266..37300198 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] From fae299f349cf9063e5837b67a1057a397d7fe8b1 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:14:49 +0900 Subject: [PATCH 04/60] Separate Display13 framebuffer preflight from render admission --- README.md | 5 +++++ README_ja.md | 4 ++++ WhateverXe/kern_xe.cpp | 27 ++++++++++++++++----------- WhateverXe/kern_xe.hpp | 6 ++++-- tests/boot_arguments.py | 8 +++++--- tests/display_backend_dispatch.py | 29 ++++++++++++++++++----------- 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 33e932d4..62b92076 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,11 @@ The supported Display 13 backend selects Y-tiled scanout automatically; Do not use the Apple driver's `-yTileDisable` argument with this backend: its X-tiled scanout path is unsupported. Display 12 tiling selection is unchanged. +For framebuffer bring-up, the optional `-wex-noaccel` argument disables accelerator +startup. On Display 13 it also skips GT resource probing while preserving the +framebuffer's independent boot-memory and MMIO checks. It does not enable an +otherwise unsupported display backend. + ## Tested features and known issues | Hardware | Verified features | diff --git a/README_ja.md b/README_ja.md index 864f85f3..73cdd4f5 100644 --- a/README_ja.md +++ b/README_ja.md @@ -127,6 +127,10 @@ GPU backendの選択と実機検証範囲は独立しています。未検証の 対応済みのDisplay 13バックエンドではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 このバックエンドはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。Display 12のタイル選択は変更しません。 +FBの立上げ時は、任意の`-wex-noaccel`引数でAcceleratorの起動を止められます。 +Display 13ではGT資源のprobeも省略し、FB独自の起動メモリ・MMIOの確認は維持します。 +未対応のDisplayバックエンドを有効にする引数ではありません。 + ## 動作確認済みの機能と既知の問題 | ハードウェア | 確認済みの機能 | diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 45ef64be..4ad584be 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3754,7 +3754,8 @@ void XE::selectProfile(const XELP::Profile &profile) { adlnNativeRequested=adlnKernelReady; adlnNativeRCSProfileRequested=adlnNativeRequested && !checkKernelArgument("-wex-adln-kernel-ctx"); if(adlnNativeRequested) { - tglRequested=tglAcceleratorRequested=true;currentGraphics=&kextIntelTGL; + tglRequested=true;tglAcceleratorRequested=!checkKernelArgument("-wex-noaccel"); + currentGraphics=&kextIntelTGL; } return; } @@ -3778,7 +3779,8 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); const auto revision = pci->configRead8(8); - const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci); + const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci) && + !checkKernelArgument("-wex-noaccel"); const bool displayRequested = display13FramebufferRequested && xelpDisplay13Device(pci); const bool admitted = renderRequested || displayRequested; pci->setProperty("N97Admission", admitted); @@ -3990,9 +3992,11 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { physicalOptions && (pci->configRead16(4)&6)==6 && checkKernelArgument("-allow3d") && checkKernelArgument("-disablegfxfirmware") && !checkKernelArgument("-wex-adln-scanout") && !checkKernelArgument("-wex-adln-gpu"); - // This shared admission remains the conjunction of all three - // certificates; selecting a Display adapter never admits a GT. - adlnNativeReady=renderReady && prefix && memoryReady; + // FB can adopt its boot memory without initializing a render engine. + // Keep the stricter GT certificate for every accelerator consumer. + display13BootReady=prefix && memoryReady; + adlnNativeReady=renderReady && display13BootReady; + pci->setProperty("WEXDisplay13BootReady",display13BootReady); pci->setProperty("N97NativePreflight",adlnNativeReady); pci->setProperty("N97NativeReservedGGTT",prefix); } @@ -4196,7 +4200,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { modDVMTCalcFix.processKernel(patcher,info); xelpLoadDisplayPool(physicalGPU); adlnBootstrap(physicalGPU); - if (adlnNativeRequested && adlnNativeReady) { + if (display13BootReady) { tglVMPrepare(patcher,physicalGPU); tglPrepareMMIOUC(patcher,physicalGPU); auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10); @@ -6681,7 +6685,7 @@ void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { // trigger queue. Apply Display 13's slot WA only to our proven owner. if(reg==0x70080 || reg==0x71080 || reg==0x72080) { const auto cursorBase=__atomic_load_n(&display13FBPower.arbitrationWriterBase,__ATOMIC_ACQUIRE); - if(callbackXE->xeProfile.display==XELP::Display::XeLPD && callbackXE->adlnNativeReady && access && + if(callbackXE->xeProfile.display==XELP::Display::XeLPD && callbackXE->display13BootReady && access && access==display13FBPower.access && cursorBase && Display13FBMemory::field(access,0x50)==cursorBase) value=Display13Cursor::controlWrite(reg,value); } @@ -6725,7 +6729,7 @@ void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { const bool arbitrationRegister=reg==0x70008 || reg==0x71008 || reg==0x72008 || reg==0x70180 || reg==0x71180 || reg==0x72180; const auto writerBase=__atomic_load_n(&display.arbitrationWriterBase,__ATOMIC_ACQUIRE); - if(arbitrationRegister && callbackXE->display13FramebufferRequested && callbackXE->adlnNativeReady && access==display.access && + if(arbitrationRegister && callbackXE->display13FramebufferRequested && callbackXE->display13BootReady && access==display.access && display.engineEnabled && writerBase && Display13FBMemory::field(access,0x50)==writerBase) { for(unsigned pipe=0;pipe<3;++pipe) { const auto bank=0x1000*pipe; @@ -6928,16 +6932,17 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { wiring?wiring->getLength():0,s->firmwarePorts,3); if(!Display13FBTopology::supportedWiring(s->adlnOutputWiring))return false; const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); - const bool ready=s->display13FramebufferRequested && contract.ready() && pci && xelpDisplay13Device(pci); + const bool ready=s->display13FramebufferRequested && s->display13BootReady && s->tglActive && + contract.ready() && pci && xelpDisplay13Device(pci); uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; that->setProperty("WEXDisplayStartupContract",contractRecord,sizeof(contractRecord)); if(!contract.ready())for(uint64_t bit=1;bit;bit<<=1)if(contract.missing()&bit) SYSLOG("igfx","Display13 missing operation %s",XELP::DisplayContract::name(bit)); that->setProperty("N97FramebufferBackendReady",ready); if(!ready)return false; - const bool dualReady=s->adlnNativeReady && s->adlnScanoutDisableFB && s->orgAlderLakeNSysMemAllocate && s->orgAlderLakeNSysMemFree && + const bool dualReady=s->display13BootReady && s->adlnScanoutDisableFB && s->orgAlderLakeNSysMemAllocate && s->orgAlderLakeNSysMemFree && (!adlnFixedSysMemEnabled || display13FBSysMem.lock) && display13FBSysMem.retireLock && s->adlnWorkLoopInGate && s->adlnWorkLoopTryClose && s->adlnWorkLoopOpen; - uint64_t dualPolicy[] {1,s->adlnOutputWiring.selected(),dualReady,s->adlnNativeReady,s->adlnScanoutDisableFB!=0, + uint64_t dualPolicy[] {1,s->adlnOutputWiring.selected(),dualReady,s->display13BootReady,s->adlnScanoutDisableFB!=0, s->orgAlderLakeNSysMemAllocate!=0,s->orgAlderLakeNSysMemFree!=0,display13FBSysMem.lock!=nullptr,display13FBSysMem.retireLock!=nullptr,adlnFixedSysMemEnabled}; IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferDualPolicy",dualPolicy,sizeof(dualPolicy)); if(Display13FBTopology::multiOutputWiring(s->adlnOutputWiring) && !dualReady)return false; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index b0847c9e..c73a5d4e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -117,8 +117,10 @@ class XE { uint32_t adlnNativeSubslices {}, adlnL3Banks {}, adlnNativeEUs {}; bool adlnKernelReady {false}; bool adlnNativeRequested {false}; - // Shared platform preflight: GT fuses, reserved GGTT/GOP, mapper, - // aperture and native boot policy. Both Render and Display consume it. + // Display handoff needs GOP/GGTT, mapper and aperture qualification, + // independently of GT fuses or an accelerator being allowed to start. + bool display13BootReady {false}; + // Render additionally requires the native GT resource certificate. bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; bool display13FBPowerReferencesReady {false}; diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index a07e0be7..8438477d 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -153,15 +153,17 @@ assert(selected.adlnDetected==small13 && selected.display13FramebufferRequested==small13); const bool d12=(desktop || mobile) && !(option&1); assert(selected.tglRequested==(small13 || d12)); - assert(selected.tglAcceleratorRequested==(small13 || (d12 && !(option&2)))); + assert(selected.tglAcceleratorRequested==((small13 || d12) && !(option&2))); assert((selected.currentFramebuffer==&kextIntelTGLFb)==(small13 || d12)); } version="Darwin Kernel Version 21.6.0: Mon Jun 24 00:56:10 PDT 2024; root:xnu-8020.240.18.709.2~1/RELEASE_X86_64"; xe=boot({},0x9A498086);assert(xe.tglRequested && xe.tglAcceleratorRequested); xe=boot({"-wex-noaccel"},0x9A498086); assert(xe.tglRequested && !xe.tglAcceleratorRequested && xe.currentFramebuffer==&kextIntelTGLFb); - // The existing noaccel diagnostic is a Display12 option. - xe=boot({"-wex-noaccel"},0x46D28086);assert(xe.adlnNativeRequested); + // Display13 keeps FB and the graphics-kext rejection route selected, + // while noaccel removes permission to execute the accelerator. + xe=boot({"-wex-noaccel"},0x46D28086); + assert(xe.adlnNativeRequested && xe.display13FramebufferRequested && xe.tglRequested && !xe.tglAcceleratorRequested); version=qualified; xe=boot({},0x12348086,0x46D28086); diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index cad926a2..821fe01d 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -12,7 +12,7 @@ kernel=s[kernel_start:kernel_end] bootstrap=s[s.index('void XE::adlnBootstrap('):s.index('bool XE::xelpBindProbe(')] def expression(name): - return re.findall(r'\b'+name+r'=(.*?);',bootstrap,re.S)[-1] + return re.findall(r'\b'+name+r'\s*=\s*(.*?);',bootstrap,re.S)[-1] code=r''' #include #include @@ -26,7 +26,7 @@ def expression(name): bool checkKernelArgument(const char *arg) {assert(!strcmp(arg,"-wex-adln-load-only"));return loadOnly;} struct XE { bool display13FramebufferRequested=false,adlnRequested=false,adlnDetected=false; - bool adlnNativeRequested=false,adlnNativeReady=false,tglActive=false; + bool adlnNativeRequested=false,adlnNativeReady=false,display13BootReady=false,tglActive=false; Kext *currentFramebuffer=nullptr,*currentGraphics=nullptr; enum Call {None,Display13,Render,Rejected,LoadOnly,Display12};Call call=None; bool processDisplay13Framebuffer(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display13;return true;} @@ -61,13 +61,16 @@ def expression(name): if(!strcmp(name,"-allow3d"))return bool(args&1); if(!strcmp(name,"-disablegfxfirmware"))return bool(args&2); if(!strcmp(name,"-wex-adln-scanout"))return bool(args&4); - assert(!strcmp(name,"-wex-adln-gpu"));return bool(args&8); + if(!strcmp(name,"-wex-adln-gpu"))return bool(args&8); + assert(!strcmp(name,"-wex-noaccel"));return bool(args&16); }; struct PCI {unsigned command;unsigned configRead16(unsigned r){assert(r==4);return command;}}device; auto pci=&device; struct Aperture {uint64_t length;uint64_t getLength(){return length;}}bar; struct {AlderLakeN::GTStatus status;}gt; - for(unsigned mask=0;mask<4096;++mask) { + bool adlnRequested=true,adlnKernelReady=true; + const auto xelpAlderLakeNDevice=[](PCI*){return true;}; + for(unsigned mask=0;mask<8192;++mask) { gt.status=(mask&1)?AlderLakeN::GTStatus::Complete:AlderLakeN::GTStatus::Failed; unsigned edram=(mask&2)?0:1; bool prefix=mask&4; @@ -75,13 +78,17 @@ def expression(name): unsigned page_size=(mask&32)?4096:16384; bool physicalOptions=mask&64;device.command=(mask&128)?6:2; args=mask>>8; - const bool renderReady=RENDER; + const bool renderRequested=RENDER_REQUEST; + const bool renderReady=renderRequested && (RENDER); const bool memoryReady=MEMORY; + const bool display13BootReady=DISPLAY; const bool adlnNativeReady=COMBINED; - // All eight platform prerequisites + both required arguments, no diagnostics. + // Display needs every memory prerequisite, independently of GT or noaccel. + assert(display13BootReady==((mask&0xffc)==0x3fc)); + // Render still needs all prerequisites and must be disabled by noaccel. assert(adlnNativeReady==(mask==0x3ff)); } - puts("PASS 4096 shared preflight combinations retain every admission prerequisite"); + puts("PASS 8192 FB/render preflight combinations: noaccel preserves FB memory, never admits render"); } int main() { certificates(); @@ -100,11 +107,11 @@ def expression(name): else assert(result && x.call==(render==1?XE::Rejected:render==2?XE::Render:XE::LoadOnly)); ++cases; } - for(bool render:{false,true})for(bool display:{false,true})for(bool ready:{false,true}) { + for(bool render:{false,true})for(bool display:{false,true})for(bool ready:{false,true})for(bool gt:{false,true}) { XE x;x.adlnDetected=render;x.display13FramebufferRequested=display; - x.adlnNativeRequested=render;x.adlnNativeReady=ready;x.kernel(patcher); + x.adlnNativeRequested=render;x.adlnNativeReady=ready&>x.display13BootReady=ready;x.kernel(patcher); assert(x.firmware==int(render||display) && x.pool==x.firmware && x.bootstrap==x.firmware); - assert(x.vm==int(render&&ready) && x.mmio==x.vm); + assert(x.vm==int((render||display)&&ready) && x.mmio==x.vm); } // Missing graphics object and missing native request never bypass rejection. for(bool detected:{false,true}) { @@ -115,7 +122,7 @@ def expression(name): } printf("PASS %u independent Display/Render dispatch cases, native preflight rejection and load-only routing\n",cases); } -'''.replace('BODY',body).replace('KERNEL',kernel).replace('RENDER',expression('renderReady')).replace('MEMORY',expression('memoryReady')).replace('COMBINED',expression('adlnNativeReady')).replace('#include ','#include \n#include ') +'''.replace('BODY',body).replace('KERNEL',kernel).replace('RENDER_REQUEST',expression('renderRequested')).replace('RENDER',expression('renderReady')).replace('DISPLAY',expression('display13BootReady')).replace('MEMORY',expression('memoryReady')).replace('COMBINED',expression('adlnNativeReady')).replace('#include ','#include \n#include ') with tempfile.TemporaryDirectory(prefix='wex-display-dispatch-') as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) From 1f8b0547a0e99b17a4937d5146f0a83010dc759e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:31:03 +0900 Subject: [PATCH 05/60] Connect Display13 TC boot memory, lane power and DDC preparation --- WhateverXe/kern_alderlake_n.hpp | 18 +++-- WhateverXe/kern_display13_boot.hpp | 8 ++- WhateverXe/kern_display13_fb_memory.hpp | 11 ++- WhateverXe/kern_display13_power.hpp | 7 +- WhateverXe/kern_display13_tc.hpp | 42 +++++++++++- WhateverXe/kern_xe.cpp | 33 +++++++-- tests/display13_ddc.py | 46 +++++++++++++ tests/display13_fb_memory.cpp | 15 +++- tests/display13_tc.cpp | 16 +++-- tests/display13_tc_aux.cpp | 3 +- tests/display13_tc_boot.cpp | 91 +++++++++++++++++++++++++ tools/test.py | 4 +- 12 files changed, 263 insertions(+), 31 deletions(-) create mode 100644 tests/display13_ddc.py create mode 100644 tests/display13_tc_boot.cpp diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 2b0e5505..b547f06c 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -13,6 +13,7 @@ #include #include "kern_display13_power_bridge.hpp" #include "kern_display13_clock.hpp" +#include "kern_display13_tc.hpp" namespace AlderLakeN { constexpr uint32_t Identity = 0x46D18086; @@ -269,7 +270,7 @@ template Display13Power::Result adoptDPBootAUX(IO &io,const DisplayRea } enum class LinearStatus : uint32_t { NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive, - StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource + StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource, InvalidTCSource }; struct LinearPlan { uint32_t pipe {}, chicken {}, arb {}, control {}, stride {}, position {}, size {}, offset {}, surface {}; @@ -282,18 +283,22 @@ struct LinearResult { }; // First Display 13 operation: reinitialize an existing linear RGB primary -// plane on the explicitly selected Combo A/B/C. The default remains B for +// plane on the explicitly selected Combo, or fixed TC with a live link +// certificate. The default remains B for // the original standalone diagnostics. No new PLL/PHY/power-well cold // initialization, a new GGTT mapping, full modeset or accelerated scanout. -inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS) { +inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS, + const Display13TC::Boot *tc=nullptr) { p={}; - if (ddi>=3 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || + if (ddi>=7 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || (r.activePipes & (r.activePipes-1)) || (r.activePipes&~r.poweredPipes) || (protocol!=GOPProtocol::TMDS && protocol!=GOPProtocol::DP) || (protocol==GOPProtocol::TMDS && ddi==1 && r.activePipes!=r.hdmiBPipes)) return LinearStatus::NotSingleHDMI; while (!(r.activePipes & (1U<=3) return LinearStatus::NotSingleHDMI; + if(ddi>=3 && (protocol!=GOPProtocol::TMDS || !tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe)) + return LinearStatus::InvalidTCSource; const uint32_t delta=0x1000*p.pipe; uint32_t trans {}, cursor {}, surfLive {}, plane {}, pipeConf {}, htotal {}, vtotal {}; if (!value(r,0x42084,p.chicken) || !value(r,0x70028+delta,p.arb) || @@ -328,11 +333,11 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d } template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1, - GOPProtocol protocol=GOPProtocol::TMDS) { + GOPProtocol protocol=GOPProtocol::TMDS,const Display13TC::Boot *tc=nullptr) { LinearResult out; if (!admitted) return out; LinearPlan p; - out.status=linearPlan(r,p,ddi,protocol); + out.status=linearPlan(r,p,ddi,protocol,tc); if(out.status!=LinearStatus::Ready)return out; out.pipe=p.pipe; const auto d=0x1000*p.pipe; @@ -341,6 +346,7 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout bool linkChanged=false; const auto linkStable=[&] { if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi))linkChanged=true; + if(ddi>=3 && (!tc || !tc->unchanged(io)))linkChanged=true; return !linkChanged; }; // Check all captured words used by the transaction before the first write. diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index 07e28cd6..172467ab 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -11,13 +11,15 @@ namespace Display13Boot { // separate; callers must also qualify the physical owner, BAR and memory layout. // DP/other-pipe adoption requires a separate boot contract, not a wider default. template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout &display, - bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory) { + bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory, + const Display13TC::Boot *tc=nullptr) { AlderLakeN::LinearPlan plan; - if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi)!=AlderLakeN::LinearStatus::Ready || + if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi,AlderLakeN::GOPProtocol::TMDS,tc)!=AlderLakeN::LinearStatus::Ready || plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false; + if(ddi>=3 && (!tc || !tc->unchanged(io)))return false; for(unsigned i=0;iunchanged(io); } enum class Status : uint32_t {Unavailable,Complete,Topology,Readback,Timeout,PeerChanged}; struct Result { diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index f1b6dd34..5b6ce866 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -7,6 +7,7 @@ #include #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_geometry.hpp" +#include "kern_display13_tc.hpp" #include #include @@ -326,12 +327,15 @@ template SurfaceReadout inspectSurface(IO &io,const void *controller,s 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, - unsigned expectedDDI=1) { + unsigned expectedDDI=1,const Display13TC::Boot *tc=nullptr) { Readout r; if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) { r.status=Status::Association;return r; } - if(bootScanout && expectedDDI>=3) {r.status=Status::DisplayState;return r;} + if(bootScanout && (expectedDDI>=7 || (expectedDDI>=3 && + (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 || !tc->unchanged(io))))) { + r.status=Status::DisplayState;return r; + } r.pool=pool(controller);const auto &p=r.pool; if (field(framebuffer,0x4288)!=0 || !field(controller,0xDF5) || field(controller,0xDF5)>3 || !field(controller,0xDF4) || @@ -388,7 +392,8 @@ template Readout inspect(IO &io,const void *controller,size_t controll (bootScanout && (io.read(0x42084)!=dpt || io.read(0x60400)!=trans || io.read(0x70008)!=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))) { + io.read(0x70180)!=r.control || io.read(0x70188)!=r.stride || io.read(0x70190)!=r.size || + (expectedDDI>=3 && !tc->unchanged(io))))) { r.status=Status::StateChanged;return r; } r.status=Status::Complete;return r; diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index 2685b86a..e52a47ee 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -34,9 +34,10 @@ constexpr uint32_t fuse(unsigned d) { return 1U << (26 - index(d)); } // PW2 is not a prerequisite of PW_A in the XE_LPD power-domain map. inline uint32_t dependencies(uint32_t domains) { if (domains & ~All) return 0; - // XE_LPD TC legacy AUX uses PW2 even while its scanout is pipe A. - constexpr uint32_t tcAuxMask=bit(Domain::AUXTC1)|bit(Domain::AUXTC2)|bit(Domain::AUXTC3)|bit(Domain::AUXTC4); - if (domains & (bit(Domain::PipeB) | bit(Domain::PipeC) | tcAuxMask)) domains |= bit(Domain::PW2); + // XE_LPD PW2 includes both TC DDI lanes and AUX, even on pipe A. + // Releasing an AUX transaction must not gate an active TMDS main link. + constexpr uint32_t tcMask=All&~((1U<<11)-1); + if (domains & (bit(Domain::PipeB) | bit(Domain::PipeC) | tcMask)) domains |= bit(Domain::PW2); if (domains) domains |= bit(Domain::PW1); return domains; } diff --git a/WhateverXe/kern_display13_tc.hpp b/WhateverXe/kern_display13_tc.hpp index f8dd5a99..b557c7d8 100644 --- a/WhateverXe/kern_display13_tc.hpp +++ b/WhateverXe/kern_display13_tc.hpp @@ -1,4 +1,4 @@ -// ADL-N/Twin Lake fixed TC HDMI/DP coordinates and PHY ownership. +// ADL-P/N fixed TC HDMI/DP coordinates and PHY ownership. // Linux v6.12 intel_tc.c/i915_reg.h; T8 Pro TC1 Linux values are an // independent live fixture. These are not TGL FIA ownership registers. #ifndef KERN_DISPLAY13_TC_HPP @@ -24,6 +24,46 @@ struct Port { }; enum class Status : uint32_t {Invalid,NoPower,NeedsAdoption,InUse,Unavailable,UnsupportedMode,NotReady,Readback,Complete}; struct Result {Status status {Status::Invalid};uint32_t before {},after {},writes {},elapsedUs {};}; +// Read-only certificate for an inherited fixed-TMDS 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. +struct Boot { + Port port;unsigned pipe {3}; + uint32_t power {},ddiPower {},ready {},buffer {},clock {},pll {},transClock {}; + bool valid() const { + const auto powered=pipe<3 ? 5U|(1U<<(10+2*pipe)) : 0U; + return port.ddi>=3 && port.ddi<=6 && pipe<3 && + power!=UINT32_MAX && (power&powered)==powered && ddiPower!=UINT32_MAX && + (ddiPower&(1U<<(2*port.ddi))) && ready!=UINT32_MAX && (ready&7)==4 && + buffer!=UINT32_MAX && (buffer&0x800000C0U)==0x80000040U && + clock!=UINT32_MAX && (clock>>28)==8 && pll!=UINT32_MAX && + (pll&0xCC000000U)==0xCC000000U && transClock!=UINT32_MAX && (transClock>>28)==port.ddi+3; + } + template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe) { + Boot b; + if(!admitted || pipe>=3 || !Port::resolve(ddi,b.port))return b; + b.pipe=pipe; + if(!io.readable(0x45404) || !io.readable(0x45454))return {}; + b.power=io.read(0x45404);b.ddiPower=io.read(0x45454); + const auto powered=5U|(1U<<(10+2*pipe)); + if(b.power==UINT32_MAX || (b.power&powered)!=powered || b.ddiPower==UINT32_MAX || + !(b.ddiPower&(1U<<(2*ddi))))return {}; + const uint32_t regs[]={b.port.ready,b.port.buffer,b.port.clock,b.port.pll,0x46140+4*pipe}; + uint32_t *values[]={&b.ready,&b.buffer,&b.clock,&b.pll,&b.transClock}; + for(unsigned i=0;i<5;++i) { + if(!io.readable(regs[i]))return {}; + *values[i]=io.read(regs[i]); + } + if(io.read(0x45404)!=b.power || io.read(0x45454)!=b.ddiPower || !b.valid())return {}; + return b; + } + template bool unchanged(IO &io) const { + if(!valid())return false; + const auto now=capture(io,true,port.ddi,pipe); + return now.valid() && now.power==power && now.ddiPower==ddiPower && now.ready==ready && + now.buffer==buffer && now.clock==clock && now.pll==pll && now.transClock==transClock; + } +}; // Caller keeps lifecycle/port exclusion, live mapping, and separate DDI, // lane/PW2 and qualified legacy AUX leases for the entire transaction. // A matching owner identity is insufficient without that power witness. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4ad584be..59c1fff6 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3860,7 +3860,10 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { SYSLOG("igfx", "AlderLakeN Display13 readout status=%u powered=%x active=%x hdmiB=%x; donor blocked", static_cast(result.status), result.poweredPipes, result.activePipes, result.hdmiBPipes); if (linearTest) { - auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI); + auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); + if(bootDDI>=3)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); + auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI, + AlderLakeN::GOPProtocol::TMDS,bootDDI>=3 ? &tc : nullptr); pci->setProperty("N97Display13Linear",&linear,sizeof(linear)); auto after=AlderLakeN::readDisplay(io,barReady); pci->setProperty("N97Display13After",&after,sizeof(after)); @@ -3985,7 +3988,9 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { ((!optionProperty || opts) && !mapperOptions && !systemMapper); uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); - const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory); + const auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); + const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory, + bootDDI>=3 ? &tc : nullptr); auto aperture=pci->getDeviceMemoryWithRegister(0x18); const bool memoryReady= aperture && aperture->getLength()==0x10000000 && page_size==4096 && @@ -4831,8 +4836,10 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo uint32_t read(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} uint64_t readPTE(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} } io {c.map}; + const auto tc=Display13TC::Boot::capture(io,bootScanout && c.bridge.bootPort>=3,c.bridge.bootPort,0); return Display13FBMemory::inspect(io,controller,controller->getMetaClass()->getClassSize(), - fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort); + fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort, + c.bridge.bootPort>=3 ? &tc : nullptr); } } @@ -6087,6 +6094,8 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m 0x49,0x8B,0x7D,0x00,0x48,0x8B,0x07,0xFF,0x90,0x30,0x01,0x00,0x00, 0x90,0x90,0x90,0x90,0x90,0x90,0x90}; const uint8_t busEntry[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56}; + const uint8_t pinLimit[] {0x83,0x7D,0xCC,0x08,0x72,0x2A}; + const uint8_t tcPinLimit[] {0x83,0x7D,0xCC,0x0D,0x72,0x2A}; // Same nominal CFGCR0 write as the Display12 adapter. ADL-P/N also // requires WA 22010492432 when the physical reference is 38.4 MHz. const uint8_t fractionWrite[] {0x89,0xfa,0x49,0x8b,0xbd,0x40,0x0c,0x00,0x00,0x48,0x8b,0x07, @@ -6129,12 +6138,14 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m memcmp(reinterpret_cast(write),busEntry,sizeof(busEntry)) || memcmp(reinterpret_cast(address+0x9A0E8),before,sizeof(before)) || memcmp(reinterpret_cast(bus+0x335),busBefore,sizeof(busBefore)) || + memcmp(reinterpret_cast(bus+0xEA),pinLimit,sizeof(pinLimit)) || memcmp(reinterpret_cast(bus+0x2E0),resetBefore,sizeof(resetBefore)))return false; struct Change {uintptr_t offset;const uint8_t *find,*replace;size_t length;}; const Change changes[] { {0x9A0E8,reinterpret_cast(before),reinterpret_cast(after),sizeof(before)}, {0x53DB1,busBefore,busAfter,sizeof(busBefore)}, - {0x53D5C,resetBefore,resetAfter,sizeof(resetBefore)} + {0x53D5C,resetBefore,resetAfter,sizeof(resetBefore)}, + {0x53B66,pinLimit,tcPinLimit,sizeof(pinLimit)} }; for(auto &change:changes) { KernelPatcher::LookupPatch patch {currentFramebuffer,change.find,change.replace,change.length,1}; @@ -6415,6 +6426,11 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { return program(false)?0:kIOReturnNotReady; } uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request) { + // Extending the donor's pin bound is not permission to use an arbitrary + // Type-C DDC bus. Only the retained controller's selected TMDS wiring + // may issue requests to pins9..12; pin8 remains excluded. + if(bus>=8 && (controller!=callbackXE->adlnTopologyController || + !callbackXE->adlnOutputWiring.usesDDC(bus)))return kIOReturnBadArgument; static uint32_t count {}; const auto call=__sync_add_and_fetch(&count,1); uint64_t r[21] {3,call,bus,speed};auto req=static_cast(request); if(req) {memcpy(r+4,req,16);r[6]=*reinterpret_cast(req+0x28);r[7]=*reinterpret_cast(req+0x38);} @@ -8907,16 +8923,19 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); const bool dpBoot=bootPort<2 && (callbackXE->adlnOutputWiring.dp&(1U<=3) { + Display13FBBootIO bootIO{map};boot=Display13TC::Boot::capture(bootIO,true,bootPort,0).valid(); + } if(boot && dpBoot) { Display13FBBootIO bootIO{map};const auto source=AlderLakeN::readDisplay(bootIO,true);uint32_t aux=0; boot=AlderLakeN::dpSource(source,0,bootPort) && AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort) && diff --git a/tests/display13_ddc.py b/tests/display13_ddc.py new file mode 100644 index 00000000..00408803 --- /dev/null +++ b/tests/display13_ddc.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Check the production TC pin guard and optional exact-donor pin-bound patch.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('uint32_t XE::wrapDisplay13GMBus(') +guard=s[s.index(' if(bus>=8',a):s.index(' static uint32_t count',a)] +cpp=r''' +#include "kern_display13_output_wiring.hpp" +#include +#include +struct IOService{}; +constexpr unsigned kIOReturnBadArgument=0xe00002c2; +struct State {IOService *adlnTopologyController{};Display13OutputWiring adlnOutputWiring;}state,*callbackXE=&state; +unsigned request(IOService *controller,unsigned bus){GUARD return 0;} +int main(){ + IOService owner,other;state.adlnTopologyController=&owner; + for(unsigned ddi=3;ddi<=6;++ddi) { + auto &w=state.adlnOutputWiring;w={};w.valid=true;w.hdmi=1U<1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + def array(name): + match=re.search(r'const uint8_t '+name+r'\[\] \{([^}]+)\}',s) + return bytes(int(v.strip(),0) for v in match.group(1).split(',')) + before,after=array('pinLimit'),array('tcPinLimit') + assert donor[0x53b66:0x53b66+len(before)]==before==bytes.fromhex('837dcc08722a') + assert after==bytes.fromhex('837dcc0d722a') + assert '{0x53B66,pinLimit,tcPinLimit,sizeof(pinLimit)}' in s + print('PASS exact c004fc1a GMBUS pin-bound site: comparison 8 -> 13; branch and remaining bytes unchanged') diff --git a/tests/display13_fb_memory.cpp b/tests/display13_fb_memory.cpp index 23d9342a..3643fdde 100644 --- a/tests/display13_fb_memory.cpp +++ b/tests/display13_fb_memory.cpp @@ -37,7 +37,7 @@ struct Fixture { if(page==2 && deadPower)registers[0x45404]=UINT32_MAX; return ptes[page]; } - Readout inspect(unsigned ddi=1) {return Display13FBMemory::inspect(*this,controller,sizeof(controller),framebuffer,sizeof(framebuffer),0x10000000,memory,true,0,ddi);} + 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 testRelocatedPools() { for(auto ggc : {0xFEC1U,0x02C1U,0x03C1U,0x04C1U}) { @@ -75,6 +75,19 @@ static void testSlotClaims() { } int main() { + 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; + f.registers[0x64000+0x100*ddi]=0x80000040;f.registers[0x46100+4*ddi]=0x80000000; + f.registers[0x46038+8*tc]=0xCC000000;f.registers[0x46140]=(ddi+3)<<28; + f.registers[0x60400]=0x80030100|((ddi+1)<<27); + const auto boot=Display13TC::Boot::capture(f,true,ddi,0); + assert(boot.valid()); + assert(f.inspect(ddi).status==Status::DisplayState && !f.reads); + assert(f.inspect(ddi,&boot).status==Status::Complete); + f.changeRegister=0x46038+8*tc; + assert(f.inspect(ddi,&boot).status==Status::StateChanged); + } uint64_t platform[0x1C0/8],savedPlatform[0x1C0/8];uint8_t poolController[0x1C20] {}; memset(platform,0x5A,sizeof(platform));put(platform,0x14,uint32_t(0x4000000)); memcpy(savedPlatform,platform,sizeof(platform));put(poolController,0xDE8,uint32_t(0x4000000)); diff --git a/tests/display13_tc.cpp b/tests/display13_tc.cpp index f086131a..19509a03 100644 --- a/tests/display13_tc.cpp +++ b/tests/display13_tc.cpp @@ -62,18 +62,26 @@ int main(){ assert(b.begin(io,0,1U|(1U<(Domain::PW2)]); + assert(b.state.refs[static_cast(Domain::PW2)]==1); assert(b.getAUX(io,port).status==Status::Complete && io.prepares==1 && io.preparedTC==tc); assert((io.regs[0x45444]&(3U<<(2*(tc+3))))==(3U<<(2*(tc+3)))); - assert(b.state.refs[1]==1 && b.state.refs[3]==0 && b.state.refs[4]==0); // No phantom pipe B/C. + assert(b.state.refs[1]==2 && b.state.refs[3]==0 && b.state.refs[4]==0); // DDI + AUX, no phantom pipe B/C. assert(b.getAUX(io,port).status==Status::Complete && b.nativeAUXRefs(port)==2 && io.prepares==1); - assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==1); + assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==2); assert(suspend(io,b.state).status==Status::Complete); assert(restore(io,b.state).status==Status::Complete && io.prepares==2); - assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==0); + assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==1 && (io.regs[Driver]&0xC)==0xC); assert(b.putDDI(io,port).status==Status::Complete && !b.nativeDDIRefs(port)); assert((io.regs[0x45404]&0xA80A)==0x802 && (io.regs[0x45454]&0x2AAA)==2); assert(!b.nativeDDIRefs(7) && !b.nativeAUXRefs(8)); + // A TC boot GOP on pipe A must retain PW2 without any AUX client. + // Dropping another native PG2 user cannot turn its lanes off. + PowerIO bootIO;NativeBridge boot; + assert(boot.begin(bootIO,port,1U< +#include +#include +#include +using namespace AlderLakeN; +struct IO { + std::map regs; + std::vector writes; + XELPMemory::Layout memory=XELPMemory::decode(0x04C1,0x38000000,0); + unsigned ticks {},changeTick {},pteReads {}; + uint32_t changeReg {},badPTE {UINT32_MAX}; + bool noFrames {},failEnable {}; + bool readable(uint32_t reg) const {return reg<=0xFFFFFC;} + uint32_t read(uint32_t reg) {return reg==0x70040 ? ticks : regs[reg];} + uint64_t readPTE(uint32_t reg) { + ++pteReads; + return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); + } + void write(uint32_t reg,uint32_t value) { + writes.push_back({reg,value}); + if(failEnable && reg==0x70180 && value==0x94000008){failEnable=false;return;} + regs[reg]=value; + } + void delay() { + if(!noFrames)++ticks; + if(changeTick && ticks==changeTick)regs[changeReg]^=1; + } +}; +static IO firmware(unsigned ddi) { + IO io;const auto tc=ddi-3; + // G660i single TC1 GOP geometry and clock/ownership, translated only + // by the Display13 physical TC stride for the other register instances. + io.regs={{0x45404,0xFC0F},{0x45454,3U<<(2*ddi)}, + {0x161500+4*tc,0xC},{0x64000+0x100*ddi,0x80000040}, + {0x46038+8*tc,0xCC000000},{0x46100+4*ddi,0x80000000}, + {0x46140,(ddi+3)<<28},{0x60400,0x80030100|((ddi+1)<<27)}, + {0x60000,0x0897077F},{0x6000C,0x04640437},{0x70008,0xC0000000}, + {0x70028,0xA0F},{0x70180,0x84000008},{0x70188,120},{0x70190,0x0437077F}}; + return io; +} +int main() { + unsigned cases=0; + for(unsigned ddi=3;ddi<=6;++ddi) { + auto io=firmware(ddi);const auto original=io.regs; + auto tc=Display13TC::Boot::capture(io,true,ddi,0); + auto display=readDisplay(io,true);LinearPlan plan; + assert(tc.valid() && tc.unchanged(io)); + assert(linearPlan(display,plan,ddi)==LinearStatus::InvalidTCSource); + auto result=reinitializeLinear(io,display,true,ddi,GOPProtocol::TMDS,&tc); + assert(result.status==LinearStatus::Success && result.writes==10 && tc.unchanged(io)); + for(auto w:io.writes)assert(w.offset==0x42084 || w.offset==0x70028 || w.offset==0x70180 || + w.offset==0x70188 || w.offset==0x7018C || w.offset==0x70190 || w.offset==0x701A4 || w.offset==0x7019C); + for(auto r:original)if(r.first!=0x70028 && r.first!=0x70180)assert(io.regs[r.first]==r.second); + assert(Display13Boot::reservedPrefix(io,readDisplay(io,true),true,ddi,io.memory,&tc)); + assert(io.pteReads==io.memory.bytes/4096); + auto bad=firmware(ddi);bad.badPTE=0x800010; + assert(!Display13Boot::reservedPrefix(bad,readDisplay(bad,true),true,ddi,bad.memory,&tc) && bad.pteReads==3); + for(unsigned wrong:{0U,1U,2U,7U,8U,UINT32_MAX})assert(!Display13TC::Boot::capture(io,true,wrong,0).valid()); + assert(!Display13TC::Boot::capture(io,false,ddi,0).valid()); + assert(!Display13TC::Boot::capture(io,true,ddi,3).valid()); + const auto p=tc.port; + const uint32_t link[]={0x45404,0x45454,p.ready,p.buffer,p.clock,p.pll,0x46140}; + for(auto reg:link) { + auto changed=firmware(ddi);auto source=readDisplay(changed,true);changed.regs[reg]^=1; + auto r=reinitializeLinear(changed,source,true,ddi,GOPProtocol::TMDS,&tc); + assert(r.status==LinearStatus::StateChanged && changed.writes.empty()); + for(unsigned tick:{1U,3U,5U}) { + auto drift=firmware(ddi);drift.changeReg=reg;drift.changeTick=tick; + r=reinitializeLinear(drift,readDisplay(drift,true),true,ddi,GOPProtocol::TMDS,&tc); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); + } + auto unavailable=firmware(ddi);unavailable.regs[reg]=UINT32_MAX; + assert(!Display13TC::Boot::capture(unavailable,true,ddi,0).valid()); + } + for(unsigned state:{0U,1U,2U,5U,6U,7U}) { + auto alt=firmware(ddi);alt.regs[p.ready]=state; + assert(!Display13TC::Boot::capture(alt,true,ddi,0).valid()); + } + auto wrongClock=firmware(ddi);wrongClock.regs[0x46140]=(ddi+1)<<28; + assert(!Display13TC::Boot::capture(wrongClock,true,ddi,0).valid()); + auto rollback=firmware(ddi);rollback.failEnable=true; + result=reinitializeLinear(rollback,readDisplay(rollback,true),true,ddi,GOPProtocol::TMDS,&tc); + assert(result.status==LinearStatus::EnableTimeout && result.restored && tc.unchanged(rollback)); + auto stopped=firmware(ddi);stopped.noFrames=true; + result=reinitializeLinear(stopped,readDisplay(stopped,true),true,ddi,GOPProtocol::TMDS,&tc); + assert(result.status==LinearStatus::NoVblank && stopped.writes.empty()); + ++cases; + } + printf("PASS %u TC GOP handoffs: power/ownership/clock preservation, plane rollback, GGTT prefix and link drift\n",cases); +} diff --git a/tools/test.py b/tools/test.py index 37300198..bf6fd926 100644 --- a/tools/test.py +++ b/tools/test.py @@ -5,11 +5,11 @@ 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] -if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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,[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 d32edc165432d1a76ab75748951c5b4e6fd8eae3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:48:50 +0900 Subject: [PATCH 06/60] Connect Display13 TC mode and guarded native TMDS startup --- AppleIntelTGLCompat/AppleIntelTGLCompat.cpp | 2 +- WhateverXe/kern_display13_tc_lightup.hpp | 33 ++ WhateverXe/kern_display13_tc_native.hpp | 46 +++ WhateverXe/kern_xe.cpp | 315 +++++++++++++++++++- WhateverXe/kern_xe.hpp | 12 + WhateverXe/kern_xelp_platform.hpp | 12 +- WhateverXe/kern_xelp_probe.hpp | 9 + tests/display13_boot_aux.py | 6 +- tests/display13_sst_fixture.cpp | 3 +- tests/display13_tc_lightup.cpp | 67 +++++ tests/display13_tc_native.py | 40 +++ tests/provider_binding_fixture.cpp | 6 +- tests/xelp_platform.cpp | 17 ++ tools/test.py | 4 +- 14 files changed, 560 insertions(+), 12 deletions(-) create mode 100644 WhateverXe/kern_display13_tc_lightup.hpp create mode 100644 WhateverXe/kern_display13_tc_native.hpp create mode 100644 tests/display13_tc_lightup.cpp create mode 100644 tests/display13_tc_native.py diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index d34c89b2..3570bef3 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 : 0; + return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC : 0; } extern "C" bool xelpConfigureDisplay(void *controller,const XELP::ProbeConfig *config) { if(!controller || !config || !config->valid())return false; diff --git a/WhateverXe/kern_display13_tc_lightup.hpp b/WhateverXe/kern_display13_tc_lightup.hpp new file mode 100644 index 00000000..24f38853 --- /dev/null +++ b/WhateverXe/kern_display13_tc_lightup.hpp @@ -0,0 +1,33 @@ +// Fixed-TMDS ordering for the exact Display13 native LightUpTMDS adapter. +// The caller's hwSetMode performs disableDisplay on any returned error. +#ifndef KERN_DISPLAY13_TC_LIGHTUP_HPP +#define KERN_DISPLAY13_TC_LIGHTUP_HPP +#include +namespace Display13TC { +enum class LightStage : uint32_t {Binding,Clocks,TransClock,Plane,Scaler,Pipe,Signal,Port,Packets,Complete}; +struct LightResult {LightStage stage {LightStage::Binding};uint32_t status {0xe00002d8U};}; +template LightResult lightUpTMDS(IO &io,unsigned ddi,unsigned pipe) { + LightResult r; + if(ddi<3 || ddi>6 || pipe>=3 || !io.ready(false))return r; + r.stage=LightStage::Clocks;io.clocks(); + if(!io.ready(true))return r; + r.stage=LightStage::TransClock; + if(!io.transClock(0x46140+4*pipe,(ddi+3)<<28))return r; + r.stage=LightStage::Plane;io.plane();if(!io.ready(true))return r; + r.stage=LightStage::Scaler;io.scaler();if(!io.ready(true))return r; + r.stage=LightStage::Pipe;io.pipe();if(!io.ready(true))return r; + r.stage=LightStage::Signal;io.signal();if(!io.ready(true))return r; + r.stage=LightStage::Port; + const auto enabled=io.port();if(enabled){r.status=enabled;return r;} + if(!io.ready(true))return r; + r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r; + r.stage=LightStage::Complete;r.status=0;return r; +} +// Native hwSetMode sends the begin notification before the path call. Its +// error branch skips post-mode writes and the normal end notification; +// balance that notification without replacing the returned error. +template uint32_t finishMode(Run run,Notify notify) { + const auto result=run();if(result)notify();return result; +} +} +#endif diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp new file mode 100644 index 00000000..25b90209 --- /dev/null +++ b/WhateverXe/kern_display13_tc_native.hpp @@ -0,0 +1,46 @@ +// Exact c004fc1a entry/ABI and failure branches for fixed-TMDS only. +#ifndef KERN_DISPLAY13_TC_NATIVE_HPP +#define KERN_DISPLAY13_TC_NATIVE_HPP +#include +namespace Display13TC { namespace Native { +enum EntryIndex {Mode,BaseMode,Clocks,DisableClocks,LightUp,SetMode,Notify,FullClocks,Plane,Scaler,Pipe,Packets}; +struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[20];}; +static constexpr Entry entries[]={ + {"__ZN17AppleIntelPortHAL11setPortModeEN14AppleIntelPort8PortModeE",0x5fcb8,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x41,0x89,0xf7}}, + {"__ZN14AppleIntelPort11setPortModeENS_8PortModeE",0x1b4c8,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x89,0xf3,0x49,0x89,0xfe,0x48,0xff,0x05,0x45,0xae,0x0b,0x00,0x85}}, + {"__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams",0x6ce10,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x49,0x89,0xcf}}, + {"__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_",0x6dae6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xd7,0x48,0x89,0xf3,0x49,0x89,0xfe}}, + {"__ZN31AppleIntelFramebufferController11LightUpTMDSEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x50b22,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6}}, + {"__ZN31AppleIntelFramebufferController9hwSetModeEP21AppleIntelFramebufferPK29IODetailedTimingInformationV2",0x693ec,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0x89,0x55}}, + {"__ZN31AppleIntelFramebufferController16sendNotificationEP21AppleIntelFramebufferNS_17ControllerEvent_tE",0x52f60,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x45,0xd0,0x08,0x00,0x83,0xfa,0x01,0x74,0x0d,0x85,0xd2,0x75,0x1c}}, + {"__ZN31AppleIntelFramebufferController12enableClocksEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x6ccb4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x12,0x87,0x07,0x00,0x48,0xff}}, + {"__ZN15AppleIntelPlane11enablePlaneEb",0x1108,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x5a,0xf8,0x0c,0x00,0x48,0x8b,0x47,0x68,0x83,0xb8}}, + {"__ZN16AppleIntelScaler17programPipeScalerEP21AppleIntelDisplayPath",0x65fd4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05}}, + {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48bb6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x48,0x8b,0x05}}, + {"__ZN31AppleIntelFramebufferController23enableDataIslandPacketsEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52106,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0xff,0x05,0x11,0xdd,0x08,0x00,0x49,0x89,0xd6}}, +}; +static constexpr uint32_t lightBodyOffset=0x50b22; +static constexpr uint8_t lightBody[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6,0x49,0x89,0xff,0x48,0xff,0x05,0xd8,0xef,0x08,0x00,0xe8,0x6f,0xc1,0x01,0x00,0x49,0x8b,0xbf,0x40,0x0c,0x00,0x00,0x48,0xff,0x05,0x65,0xdb,0x08,0x00,0x8b,0x83,0x40,0x36,0x00,0x00,0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x8b,0x93,0x28,0x01,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00,0x48,0x8b,0xbb,0xc8,0x32,0x00,0x00,0x31,0xf6,0xe8,0x8b,0x05,0xfb,0xff,0x48,0x8b,0xbb,0xc0,0x32,0x00,0x00,0x48,0x89,0xde,0xe8,0x48,0x54,0x01,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0x4c,0x89,0xe1,0xe8,0x19,0x80,0xff,0xff,0x48,0xff,0x05,0x94,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0x31,0xf6,0x31,0xd2,0x31,0xc9,0xff,0x90,0x40,0x01,0x00,0x00,0x48,0xff,0x05,0x77,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x28,0x01,0x00,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0xe8,0x27,0x15,0x00,0x00,0x31,0xc0,0x5b,0x41,0x5c,0x41,0x5e,0x41,0x5f,0x5d,0xc3}; +static constexpr uint32_t innerBeforeOffset=0x4ffd9; +static constexpr uint8_t innerBefore[]={0x45,0x31,0xed}; +static constexpr uint32_t outerBeforeOffset=0x695ac; +static constexpr uint8_t outerBefore[]={0x41,0xf6,0x86,0x5c,0x0c,0x00,0x00,0x01,0x74,0x46,0x48,0xff,0x05,0x73,0xb3,0x07,0x00,0x48,0xff,0x05,0xec,0xae,0x07,0x00}; +static constexpr uint32_t modeExitOffset=0x698ed; +static constexpr uint8_t modeExit[]={0x48,0x83,0xc4,0x28,0x5b,0x41,0x5c,0x41,0x5d,0x41,0x5e,0x41,0x5f,0x5d,0xc3}; +static constexpr uint32_t innerCallOffset=0x4ffb6; +static constexpr uint8_t innerCall[]={0xe8,0x67,0x0b,0x00,0x00,0xeb,0x1c}; +static constexpr uint32_t outerCallOffset=0x695a7; +static constexpr uint8_t outerCall[]={0xe8,0x28,0x64,0xfe,0xff}; +static constexpr uint32_t modeBeginOffset=0x6940e; +static constexpr uint8_t modeBegin[]={0x45,0x31,0xff,0x31,0xd2,0xe8,0x48,0x9b,0xfe,0xff}; +static constexpr uint32_t modeEndOffset=0x698db; +static constexpr uint8_t modeEnd[]={0x4c,0x89,0xf7,0x4c,0x89,0xe6,0xba,0x01,0x00,0x00,0x00,0xe8,0x75,0x96,0xfe,0xff,0x31,0xc0}; +// Preserve the returned LightUp status so native hwSetMode disables the path. +static constexpr uint8_t innerAfter[]={0x41,0x89,0xc5}; +// Replace two profiling counters with a returned-status branch. On success, +// retain the original configuration-bit test and both of its destinations. +// Error jumps to the stack epilogue (preserving eax); the C++ outer adapter +// balances the begin notification. It does not execute post-mode MMIO. +static constexpr uint8_t outerAfter[]={0x85,0xc0,0x0f,0x85,0x39,0x03,0x00,0x00,0x41,0xf6,0x86,0x5c,0x0c,0x00,0x00,0x01,0x74,0x3e,0x90,0x90,0x90,0x90,0x90,0x90}; +}} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 59c1fff6..de0ef8e3 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -14,6 +14,12 @@ #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_forcewake_irq.hpp" #include "kern_display13_boot.hpp" +#include "kern_display13_firmware.hpp" +#include "kern_display13_tc_aux.hpp" +#include "kern_display13_tc_mode.hpp" +#include "kern_display13_tc_lightup.hpp" +#include "kern_display13_tc_native.hpp" +#include "kern_display13_tc_clock.hpp" #include "kern_tgl_gmbus.hpp" #include "kern_alderlake_n_gpu.hpp" #include "kern_display13_fb_memory.hpp" @@ -4342,6 +4348,12 @@ struct Display13FBPipeContext { uint64_t watermarkClockHz {}; }; struct Display13FBPowerContext { + IOSimpleLock *tcBankLock {}; + bool tcSupported {},tcBankFaulted {}; + Display13TC::Mode tcMode[4]; + OSObject *tcObject[4] {}; + uint32_t tcBusy[4] {}; + bool tcSignalReady[4] {}; Display13FBPipeContext pipe[3]; Display13Power::NativeBridge bridge; Display13Power::MainLinkAUX mainLink[2]; @@ -4599,7 +4611,45 @@ 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); } +// 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 { + IOMemoryMap *map;unsigned tc; + bool admitted(unsigned candidate) const { + auto &c=display13FBPower; + return candidate==tc && tc<4 && c.tcSupported && c.tcBankLock && !c.tcBankFaulted && + c.bridge.begun && (c.bridge.selectedPorts&(1U<<(tc+3))) && map==c.map && + map && map->getLength()==0x1000000 && map->getVirtualAddress(); + } + bool readable(uint32_t r) const {return admitted(tc) && !(r&3) && r<=0xfffffc;} + uint32_t read(uint32_t r) const {return *reinterpret_cast(map->getVirtualAddress()+r);} + void write(uint32_t r,uint32_t v) {*reinterpret_cast(map->getVirtualAddress()+r)=v;} + bool lanesPowered() const { + if(!admitted(tc))return false; + const auto pw=read(Display13Power::Driver),ddi=read(Display13Power::DDI),mode=read(0x161500+4*tc); + return pw!=UINT32_MAX && ddi!=UINT32_MAX && mode!=UINT32_MAX && (pw&5)==5 && + (ddi&(1U<<(2*(tc+3)))) && (mode&7)==4; + } + bool auxPowered(unsigned candidate) const { + if(!admitted(candidate))return false;const auto value=read(Display13Power::AUX); + return value!=UINT32_MAX && (value&(1U<<(2*(tc+3)))); + } + IOInterruptState lock(){return IOSimpleLockLockDisableInterrupt(display13FBPower.tcBankLock);} + void unlock(IOInterruptState irq){IOSimpleLockUnlockEnableInterrupt(display13FBPower.tcBankLock,irq);} + void bankFault(){display13FBPower.tcBankFaulted=true;} +}; +static bool display13TCAuxSupported() {return display13FBPower.tcSupported && display13FBPower.tcBankLock && !display13FBPower.tcBankFaulted;} +static bool display13TCPrepareAUX(IOMemoryMap *map,unsigned tc) { + Display13FBTCAuxIO io{map,tc};return Display13TC::Aux::prepare(io,tc).status==Display13TC::Status::Complete; +} +static bool display13TCHealthyAUX(IOMemoryMap *map,unsigned tc) { + Display13FBTCAuxIO io{map,tc};bool healthy=false; + return Display13TC::Aux::health(io,tc,healthy).status==Display13TC::Status::Complete && healthy; +} struct Display13FBPowerIO { + bool tcAuxSupported(){return display13TCAuxSupported();} + bool prepareTCAux(unsigned tc){return display13TCPrepareAUX(map,tc);} + bool tcAuxHealthy(unsigned tc){return display13TCHealthyAUX(map,tc);} IOMemoryMap *map; bool readable(uint32_t r) const { return map && map->getVirtualAddress() && map->getLength()==0x1000000 && @@ -4625,6 +4675,36 @@ struct Display13FBBootIO : Display13FBPowerIO { return map && map->getVirtualAddress() && map->getLength()==0x1000000 && r<=0x1000000-4; } }; +struct Display13FBTCIO : Display13FBTCAuxIO { + OSObject *object; + Display13FBTCIO(OSObject *p,unsigned t):Display13FBTCAuxIO{display13FBPower.map,t},object(p) {} + bool powerHeld() { + if(!admitted(tc))return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; + if(!object || c.tcObject[tc]!=object || mode.controller!=reinterpret_cast(c.controller) || + mode.object!=reinterpret_cast(object) || mode.lease.owner!=&c.bridge.state || + mode.lease.domains!=Display13Power::dependencies(Display13TC::Mode::domains(tc+3)))return false; + Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.lease.domains); + } + bool owned(unsigned ddi) { + 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; + } + bool quiescent(unsigned ddi) { + if(ddi!=tc+3 || !powerHeld())return false; + const auto buffer=read(0x64000+0x100*ddi),power=read(Display13Power::Driver); + if(buffer==UINT32_MAX || power==UINT32_MAX || (buffer&0x80000000))return false; + for(unsigned pipe=0;pipe<3;++pipe)if(power&(1U<<(10+2*pipe))) { + const auto trans=read(0x60400+0x1000*pipe),conf=read(0x70008+0x1000*pipe),clock=read(0x46140+4*pipe); + if(trans==UINT32_MAX || conf==UINT32_MAX || clock==UINT32_MAX)return false; + if(((trans>>27)&15)==ddi+1 && (trans&0x80000000))return false; + if((clock>>28)==ddi+3 && (conf&0xc0000000))return false; + } + return true; + } + void delayUs(unsigned us){IODelay(us);} +}; // Only used after native object/BAR binding and the power lease proof. The // common SST/signal sequencers choose all addresses; no caller register input. struct Display13FBSSTIO { @@ -5050,6 +5130,8 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBLifecycleHooksReady=processDisplay13FramebufferLifecycle(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBLifecycleHooksReady",display13FBLifecycleHooksReady); display13FBOutputHooksReady=processDisplay13FramebufferOutput(patcher,index,address,size); + display13FBTCHooksReady=processDisplay13FramebufferTC(patcher,index,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCHooksReady",display13FBTCHooksReady); IORegistryEntry::getRegistryRoot()->setProperty("N97FBOutputHooksReady",display13FBOutputHooksReady); display13FBGTTHooksReady=processDisplay13FramebufferGTT(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); @@ -6078,6 +6160,232 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { return 0; } +unsigned XE::display13TCTarget(OSObject *port,bool cleanup) { + auto &c=display13FBPower;auto self=callbackXE; + if(!self->display13FBTCHooksReady || !c.tcSupported || !c.controller || !c.bridge.begun || + c.tcBankFaulted || c.bridge.state.faulted || (!cleanup && c.clockFaulted) || !port || port->getMetaClass()->getClassSize()<0x6b0)return UINT32_MAX; + auto config=Display13FBMemory::field(port,0x548); + if(!config || !self->adlnOutputWiring.matchesHDMI(config) || config[0]<3 || config[0]>6 || + !Display13FBTopology::bound(self->adlnPlatform,config,config[0]) || + Display13FBMemory::field(port,0x578)!=c.access || + Display13FBMemory::field(port,0x120)!=config[0])return UINT32_MAX; + return config[0]; +} + +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(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] || + (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();} + struct IO : Display13FBTCIO { + uint32_t (*base)(OSObject *,uint32_t);bool firmware; + IO(OSObject *p,unsigned t,uint32_t (*f)(OSObject *,uint32_t),bool inherited):Display13FBTCIO(p,t),base(f),firmware(inherited) {} + bool admissionReady(){return base && admitted(tc);} + uint32_t nativeMode(){return Display13FBMemory::field(object,0x550);} + uint32_t nativeSetMode(uint32_t value) { + // mode0 invokes the native reset callback. Retain this exact port + // transaction, but drop the owner lock across possible callbacks. + auto &c=display13FBPower;++c.tcBusy[tc];IOLockUnlock(c.lock); + auto result=base(object,value);IOLockLock(c.lock);--c.tcBusy[tc];return result; + } + Display13Power::Result acquireDomains(Display13Power::Lease &lease,uint32_t domains) { + Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,lease,domains); + } + Display13Power::Result releaseDomains(Display13Power::Lease &lease) { + Display13FBPowerIO io{map};return Display13Power::release(io,display13FBPower.bridge.state,lease); + } + bool powerHeld(uintptr_t controller,uintptr_t port,unsigned d) { + return controller==reinterpret_cast(display13FBPower.controller) && + port==reinterpret_cast(object) && d==tc+3 && Display13FBTCIO::powerHeld(); + } + bool inheritedVerified(uintptr_t controller,uintptr_t port,unsigned d) { + return firmware && controller==reinterpret_cast(display13FBPower.controller) && + 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); + } + }; + // Adopt only the measured, protected firmware boot route. Merely finding + // a set ownership bit on another connector is not a handoff certificate. + Display13FBBootIO bootIO{c.map}; + const auto boot=Display13TC::Boot::capture(bootIO,true,ddi,0); + const bool inherited=ddi==c.bridge.bootPort && c.bridge.retainsDDI(ddi) && boot.valid() && + Display13FBTopology::bootDDI(self->adlnOutputWiring,bootIO.read(0x60400))==ddi && + (bootIO.read(0x70008)&0xc0000000)==0xc0000000 && boot.unchanged(bootIO); + IO io{port,tc,reinterpret_cast(self->display13TCBaseMode),inherited}; + 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 bool done=result.status==Display13TC::Status::Complete; + if(!wanted && done){c.tcSignalReady[tc]=false;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}; + IOLockUnlock(c.lock);auto root=IORegistryEntry::getRegistryRoot(); + root->setProperty("WEXDisplay13TCMode",record,sizeof(record)); + if(!done && !root->getProperty("WEXDisplay13TCModeFailure"))root->setProperty("WEXDisplay13TCModeFailure",record,sizeof(record)); + return done ? kIOReturnSuccess : kIOReturnNotReady; +} + +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; + if(config && config[0]<3){FunctionCast(wrapDisplay13TCEnableClocks,self->orgDisplay13TCEnableClocks)(controller,raw,other,params);return;} + if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); + bool ok=false;uint32_t rate=0; + if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { + const auto tc=ddi-3;Display13FBTCIO io{port,tc};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); + rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex); + const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); + // Native modeset must first stop the old scanout; this transaction + // may then retire its PLL before programming a different timing. + ok=plan.valid() && Display13TC::Clock::disable(io,p) && Display13TC::Clock::enable(io,p,plan) && + Display13TC::TMDSSignal::program(io,p,self->firmwarePorts.port[ddi].hdmiPreset); + c.tcSignalReady[tc]=ok; + } + if(!ok)adlnFault(c.clockFaulted,__LINE__); + uint32_t record[]={1,ddi,rate,ok};IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockEnable",record,sizeof(record)); +} +void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { + auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; + auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; + if(config && config[0]<3){FunctionCast(wrapDisplay13TCDisableClocks,self->orgDisplay13TCDisableClocks)(controller,raw,other);return;} + if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port,true);bool ok=false; + if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { + Display13FBTCIO io{port,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); + ok=Display13TC::Clock::disable(io,p);if(ok)c.tcSignalReady[ddi-3]=false; + } + if(!ok)adlnFault(c.clockFaulted,__LINE__); + uint32_t record[]={1,ddi,ok};IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockDisable",record,sizeof(record)); +} +uint32_t XE::wrapDisplay13TCSignal(void *raw,uint32_t swing,uint32_t emphasis,void *drive) { + auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; + auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; + if(config && config[0]<3)return FunctionCast(wrapDisplay13TCSignal,self->orgDisplay13SignalLevels)(raw,swing,emphasis,drive); + if(!c.lock)return kIOReturnNotReady;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); + // TMDS signal parameters were programmed while the path was stopped, + // before native enablePipe. Do not run TGL DP's late voltage sequence. + bool ok=false;if(ddi<7){Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && c.tcSignalReady[ddi-3];} + if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); + return ok ? 0 : kIOReturnNotReady; +} + +uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,const void *timing) { + auto self=callbackXE;auto &c=display13FBPower; + auto fb=static_cast(rawFB);auto path=static_cast(rawPath); + if(!c.lock || !path || path->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; + auto port=Display13FBMemory::field(path,0x3648); + if(port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(port,0x120)<3) + return FunctionCast(wrapDisplay13TCLightUp,self->orgDisplay13TCLightUp)(controller,rawFB,rawPath,timing); + struct IO { + XE *self;void *controller;IOService *fb;OSObject *path,*object;const void *timing; + unsigned ddi {UINT32_MAX},pipeIndex {UINT32_MAX}; + bool ready(bool clock) { + auto &c=display13FBPower;IOLockLock(c.lock); + const auto current=display13TCTarget(object); + const auto binding=adlnInspectPathBinding(fb,path,self->adlnPlatform,self->adlnOutputWiring,false); + auto scaler=Display13FBMemory::field(path,0x32C0); + Display13FBPowerIO power{c.map}; + bool ok=controller==c.controller && timing && current<7 && current==ddi && + binding.status==TGLFBBinding::Status::Complete && binding.ddi==ddi && binding.pipe==pipeIndex && + scaler && scaler->getMetaClass()->getClassSize()>=0x58 && + Display13FBMemory::field(scaler,0x10)==fb && Display13FBMemory::field(scaler,0x28)==c.access && + !c.memoryFaulted && !c.watermarkFaulted && !c.clockStopped && !c.tcBusy[ddi-3] && + c.bridge.outputHeld(power,ddi,pipeIndex); + if(ok){Display13FBTCIO tc{object,ddi-3};ok=tc.owned(ddi) && (!clock || c.tcSignalReady[ddi-3]);} + IOLockUnlock(c.lock);return ok; + } + void clocks() { + auto &c=display13FBPower;IOLockLock(c.lock);c.tcSignalReady[ddi-3]=false;IOLockUnlock(c.lock); + reinterpret_cast(self->display13TCFullClocks)(controller,fb,path); + } + bool transClock(uint32_t reg,uint32_t value) { + auto &c=display13FBPower;IOLockLock(c.lock);Display13FBTCIO tc{object,ddi-3}; + const bool held=tc.owned(ddi) && c.tcSignalReady[ddi-3]; + bool ok=false;if(held){tc.write(reg,value);ok=tc.read(reg)==value;} + if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return ok; + } + void plane(){reinterpret_cast(self->display13TCPlane)(Display13FBMemory::field(path,0x32C8),false);} + void scaler(){reinterpret_cast(self->display13TCScaler)(Display13FBMemory::field(path,0x32C0),path);} + void pipe(){reinterpret_cast(self->display13TCPipe)(controller,fb,path,timing);} + void signal(){wrapDisplay13TCSignal(object,0,0,nullptr);} + uint32_t port(){return wrapDisplay13EnablePort(object);} + void packets(){reinterpret_cast(self->display13TCPackets)(controller,fb,path);} + } io{self,controller,fb,path,port,timing}; + IOLockLock(c.lock);io.ddi=display13TCTarget(port);io.pipeIndex=Display13FBMemory::field(path,0x3640);IOLockUnlock(c.lock); + const auto result=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)); + return result.status; +} + +uint32_t XE::wrapDisplay13TCSetMode(void *controller,void *fb,const void *timing) { + auto self=callbackXE; + return Display13TC::finishMode( + [&](){return FunctionCast(wrapDisplay13TCSetMode,self->orgDisplay13TCSetMode)(controller,fb,timing);}, + [&](){reinterpret_cast(self->display13TCNotify)(controller,fb,1);}); +} + +bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace Display13TC::Native; + // Do not alter the successful Combo machines' native mode-return behavior. + if(!(adlnOutputWiring.hdmi&0x78) || !display13FBOutputHooksReady || !display13FBSSTHooksReady || + !display13FBABIReady || size<=0x6e000)return false; + for(const auto &e:entries) { + const auto f=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); + if(f!=address+e.offset || memcmp(reinterpret_cast(f),e.prefix,sizeof(e.prefix)))return false; + } + struct Span {uint32_t offset;const uint8_t *bytes;size_t size;}; + const Span spans[]={ + {lightBodyOffset,lightBody,sizeof(lightBody)},{innerBeforeOffset,innerBefore,sizeof(innerBefore)}, + {outerBeforeOffset,outerBefore,sizeof(outerBefore)},{modeExitOffset,modeExit,sizeof(modeExit)}, + {innerCallOffset,innerCall,sizeof(innerCall)},{outerCallOffset,outerCall,sizeof(outerCall)}, + {modeBeginOffset,modeBegin,sizeof(modeBegin)},{modeEndOffset,modeEnd,sizeof(modeEnd)} + }; + for(const auto &span:spans)if(memcmp(reinterpret_cast(address+span.offset),span.bytes,span.size))return false; + if(!display13FBPower.tcBankLock)display13FBPower.tcBankLock=IOSimpleLockAlloc(); + if(!display13FBPower.tcBankLock)return false; + display13TCBaseMode=address+entries[BaseMode].offset; + display13TCNotify=address+entries[Notify].offset;display13TCFullClocks=address+entries[FullClocks].offset; + display13TCPlane=address+entries[Plane].offset;display13TCScaler=address+entries[Scaler].offset; + display13TCPipe=address+entries[Pipe].offset;display13TCPackets=address+entries[Packets].offset; + KernelPatcher::RouteRequest routes[]={ + {entries[Mode].symbol,wrapDisplay13TCMode,orgDisplay13TCMode}, + {entries[Clocks].symbol,wrapDisplay13TCEnableClocks,orgDisplay13TCEnableClocks}, + {entries[DisableClocks].symbol,wrapDisplay13TCDisableClocks,orgDisplay13TCDisableClocks}, + {entries[LightUp].symbol,wrapDisplay13TCLightUp,orgDisplay13TCLightUp}, + {entries[SetMode].symbol,wrapDisplay13TCSetMode,orgDisplay13TCSetMode} + }; + // Signal is already routed by the common SST frontend; its wrapper + // dispatches TC here. Never install a second hook over the same entry. + if(!patcher.routeMultipleShort(index,routes,arrsize(routes),address,size)){patcher.clearError();return false;} + patcher.clearError(); + struct Change {uint32_t offset;const uint8_t *before,*after;size_t size;}; + const Change changes[]={ + {innerBeforeOffset,innerBefore,innerAfter,sizeof(innerAfter)}, + {outerBeforeOffset,outerBefore,outerAfter,sizeof(outerAfter)} + }; + for(const auto &change:changes) { + KernelPatcher::LookupPatch patch{currentFramebuffer,change.before,change.after,change.size,1}; + patcher.applyLookupPatch(&patch,reinterpret_cast(address+change.offset),change.size+1); + const bool applied=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+change.offset),change.after,change.size); + patcher.clearError();if(!applied)return false; + } + display13FBPower.tcSupported=true;return true; +} + bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // S1's Combo A/B share the Display>=12 CFGCR address branch with TGL. // Reuse the proven r319 table correction, without entering the Display12 @@ -6365,7 +6673,12 @@ uint32_t XE::wrapDisplay13DisablePort(void *port) { uint32_t XE::wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig) { auto s=callbackXE;auto &c=display13FBPower; - // No protocol replacement for HDMI; preserve the donor's existing behavior. + // The one installed signal hook dispatches TC TMDS before Combo handling. + auto object=static_cast(port); + if(s->display13FBTCHooksReady && object && object->getMetaClass()->getClassSize()>=0x580 && + Display13FBMemory::field(port,0x120)>=3) + return wrapDisplay13TCSignal(port,swing,emphasis,overrideConfig); + // No protocol replacement for Combo HDMI; preserve the donor's behavior. if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) return FunctionCast(wrapDisplay13SignalLevels,s->orgDisplay13SignalLevels)(port,swing,emphasis,overrideConfig); if(!c.lock)return kIOReturnNotReady; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index c73a5d4e..3cd03365 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -194,6 +194,18 @@ class XE { mach_vm_address_t orgDisplay13FramebufferInit {}; __attribute__((noinline)) bool processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); __attribute__((noinline)) bool processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); + bool display13FBTCHooksReady {}; + mach_vm_address_t orgDisplay13TCMode {},display13TCBaseMode {},orgDisplay13TCEnableClocks {},orgDisplay13TCDisableClocks {}; + mach_vm_address_t orgDisplay13TCLightUp {},orgDisplay13TCSetMode {},display13TCNotify {}; + mach_vm_address_t display13TCFullClocks {},display13TCPlane {},display13TCScaler {},display13TCPipe {},display13TCPackets {}; + static uint32_t wrapDisplay13TCLightUp(void *,void *,void *,const void *); + static uint32_t wrapDisplay13TCSetMode(void *,void *,const void *); + 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 wrapDisplay13TCEnableClocks(void *,void *,void *,uint32_t *); + static void wrapDisplay13TCDisableClocks(void *,void *,void *); + static uint32_t wrapDisplay13TCSignal(void *,uint32_t,uint32_t,void *); __attribute__((noinline)) bool processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); static bool wrapAlderLakeNGetOSInformation(IOService *controller); static uint32_t wrapAlderLakeNPortState(OSObject *port); diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index d2276056..41cb11f7 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -65,15 +65,18 @@ struct NativePorts { }; // ABI3 includes physical/effective identity and binds a profile and immutable probe policy, not mutable connection state. // legacy permits probing for mode4; it does not force mode4 or classify a -// USB-C-capable connector as physically fixed DP. The provider still checks FIA. +// USB-C-capable connector as physically fixed DP. The provider checks generation-specific live state. // Type-C intent is represented but not admitted by either current backend. -constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U; +constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U,ProbeDisplay13FixedTC=2U; using QueryProbeCapabilities=uint32_t (*)(uint32_t,uint32_t); // Optional export: old ABI3 providers remain valid for their existing routes, // but their lack of a capability reply cannot authorize a new DP route. inline bool display13ProbeCapable(QueryProbeCapabilities query) { return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13ComboDP); } +inline bool display13TCProbeCapable(QueryProbeCapabilities query) { + return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13FixedTC); +} struct ProbeConfig { uint32_t abi {ProbeABI},bytes {sizeof(ProbeConfig)}; Profile profile; @@ -91,8 +94,9 @@ struct ProbeConfig { if((combo&~7U) || ((legacy|typeC|excluded|legacyBackend)&~0x1F8U) || (comboBackend&~7U) || (legacy&typeC) || (legacy&excluded) || (typeC&excluded))return false; if(profile.display==Display::XeD)return comboBackend==7; - return profile.display==Display::XeLPD && combo && !(combo&~3U) && - comboBackend && !(comboBackend&~3U) && !legacy && !typeC && !legacyBackend; + return profile.display==Display::XeLPD && (combo|legacy) && !(combo&~3U) && + comboBackend && !(comboBackend&~3U) && !typeC && + !(legacy&~0x78U) && !(legacyBackend&~0x78U) && !(legacy&~legacyBackend); } }; inline bool same(const ProbeConfig &a,const ProbeConfig &b) { diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index ace6f8bf..81317ba8 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -4,6 +4,7 @@ #define KERN_XELP_PROBE_HPP #include "kern_xelp_platform.hpp" #include "kern_tgl_port_map.hpp" +#include "kern_display13_tc.hpp" namespace XELP { struct ProbeBinding { uintptr_t controller {},access {},platform {}; @@ -38,6 +39,14 @@ template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man } if(b.config.profile.display==Display::XeD) return legacyTGLMode(io,c[0],b.config.legacy&b.config.legacyBackend); + if(c[0]>=3) { + Display13TC::Port p; + if(!(b.config.legacy&b.config.legacyBackend&(1U<12 || c[2]!=2 || c[3]!=1 || + c[4]!=0x800 || (c[5]&0xB)!=2 || !io.readable(0xC4000) || !io.readable(p.ready))return 0; + const auto ready=io.read(p.ready); + return ready!=UINT32_MAX && (ready&7)==4 && p.legacyHPD(io.read(0xC4000)) ? 4 : 0; + } // Fixed Combo uses the same native mode for HDMI and SST DP. This is // classification only: startup separately proves AUX/PHY/boot operations. if(!(b.config.combo&b.config.comboBackend&(1U< +#include +#include +#include +#include +using namespace Display13TC; +struct IO { + unsigned count {},fail {},last {},reg {},value {}; + bool admission=true,posting=true; + uint32_t portError {}; + bool ready(bool clock){return admission && (!clock || count>0) && (!fail || last!=fail);} + void step(unsigned n){assert(n==++count);last=n;} + void clocks(){step(1);} + bool transClock(unsigned r,unsigned v){step(2);reg=r;value=v;return posting;} + void plane(){step(3);}void scaler(){step(4);}void pipe(){step(5);} + void signal(){step(6);}uint32_t port(){step(7);return portError;} + void packets(){step(8);} +}; +int main(){ + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ + IO io;const auto r=lightUpTMDS(io,ddi,pipe); + assert(!r.status && r.stage==LightStage::Complete && io.count==8); + assert(io.reg==0x46140+4*pipe && io.value==(ddi+3)<<28); + } + for(unsigned fail=1;fail<=8;++fail){ + IO io;io.fail=fail;if(fail==2)io.posting=false; + const auto r=lightUpTMDS(io,3,0); + assert(r.status && io.count==fail && unsigned(r.stage)==fail); + } + for(unsigned ddi: {0U,2U,7U,~0U}){IO io;assert(lightUpTMDS(io,ddi,0).status && !io.count);} + IO unbound;unbound.admission=false;assert(lightUpTMDS(unbound,3,0).status && !unbound.count); + IO pipe;assert(lightUpTMDS(pipe,3,3).status && !pipe.count); + IO port;port.portError=0xe00002d6;const auto r=lightUpTMDS(port,3,0); + assert(r.status==port.portError && r.stage==LightStage::Port && port.count==7); + unsigned notify=0;assert(finishMode([](){return 0U;},[&](){++notify;})==0 && notify==0); + assert(finishMode([](){return 0xe00002d8U;},[&](){++notify;})==0xe00002d8U && notify==1); + + // Execute the actual x86 patch bytes with both native flag outcomes. + // Error keeps eax and skips all post-mode work. Success reaches the same + // flag-dependent destinations as the donor. No real driver code executes. + auto mem=static_cast(mmap(nullptr,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0)); + assert(mem!=MAP_FAILED);memset(mem,0xcc,4096); + const unsigned char prefix[]={0x41,0x56,0x49,0x89,0xfe,0x89,0xf0}; // save r14; r14=rdi; eax=esi + memcpy(mem,prefix,sizeof(prefix));memcpy(mem+sizeof(prefix),Native::outerAfter,sizeof(Native::outerAfter)); + const unsigned char yes[]={0xb8,1,0,0,0,0x41,0x5e,0xc3},no[]={0xb8,2,0,0,0,0x41,0x5e,0xc3}; + const unsigned char error[]={0x41,0x5e,0xc3}; + memcpy(mem+sizeof(prefix)+sizeof(Native::outerAfter),yes,sizeof(yes)); + memcpy(mem+sizeof(prefix)+0x50,no,sizeof(no)); + memcpy(mem+sizeof(prefix)+Native::modeExitOffset-Native::outerBeforeOffset,error,sizeof(error)); + // Inner adapter: eax input -> r13d -> eax, with the callee-saved register restored. + const unsigned char innerPrefix[]={0x41,0x55,0x89,0xf8}; + const unsigned char innerExit[]={0x44,0x89,0xe8,0x41,0x5d,0xc3}; + memcpy(mem+1024,innerPrefix,sizeof(innerPrefix));memcpy(mem+1028,Native::innerAfter,sizeof(Native::innerAfter)); + memcpy(mem+1031,innerExit,sizeof(innerExit)); + assert(!mprotect(mem,4096,PROT_READ|PROT_EXEC)); + auto outer=reinterpret_cast(mem); + auto inner=reinterpret_cast(mem+1024); + unsigned char controller[0xc60]{}; + for(unsigned flag=0;flag<2;++flag){controller[0xc5c]=flag; + assert(outer(controller,0)==(flag?1U:2U)); + for(auto error: {1U,0xe00002d8U,0xe00002d6U})assert(outer(controller,error)==error && inner(error)==error); + } + assert(inner(0)==0);munmap(mem,4096); + puts("PASS TC LightUp: clock/each-stage failure stop, 12 selectors, native error branches, balanced notification"); +} diff --git a/tests/display13_tc_native.py b/tests/display13_tc_native.py new file mode 100644 index 00000000..9f8eaafd --- /dev/null +++ b/tests/display13_tc_native.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Qualify the TC native hook entries, mode-return patches and caller ABI.""" +from pathlib import Path +import hashlib,re,subprocess,sys +r=Path(__file__).resolve().parents[1] +h=(r/'WhateverXe/kern_display13_tc_native.hpp').read_text() +s=(r/'WhateverXe/kern_xe.cpp').read_text() +arrays={name:bytes(int(v,0) for v in body.split(',')) for name,body in + re.findall(r'uint8_t (\w+)\[\]=\{([^}]+)\}',h)} +offsets={name:int(value,0) for name,value in re.findall(r'uint32_t (\w+)Offset=(0x[0-9a-f]+);',h)} +assert len(arrays['innerBefore'])==len(arrays['innerAfter'])==3 +assert len(arrays['outerBefore'])==len(arrays['outerAfter'])==24 +assert int.from_bytes(arrays['outerAfter'][4:8],'little',signed=True)+0x695ac+8==0x698ed +assert 0x695ac+18+arrays['outerAfter'][17]==0x695fc +install=s[s.index('bool XE::processDisplay13FramebufferTC('):s.index('bool XE::processDisplay13FramebufferOutput(')] +assert '!(adlnOutputWiring.hdmi&0x78)' in install +assert 'entries[LightUp].symbol,wrapDisplay13TCLightUp' in install +assert 'entries[SetMode].symbol,wrapDisplay13TCSetMode' in install +assert 'wrapDisplay13TCSignal,' not in install # one common SST signal hook +assert '{innerBeforeOffset,innerBefore,innerAfter,sizeof(innerAfter)}' in install +assert '{outerBeforeOffset,outerBefore,outerAfter,sizeof(outerAfter)}' in install +print('PASS TC native mode-patch destinations, installation scope and single signal dispatcher') +if len(sys.argv)>1: + path=Path(sys.argv[1]);donor=path.read_bytes() + assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + symbols={line.split()[-1]:int(line.split()[0],16) for line in + subprocess.check_output(['nm','-n',str(path)],text=True).splitlines() + if len(line.split())==3 and re.fullmatch('[0-9a-fA-F]+',line.split()[0])} + entries=re.findall(r'\{"([^"\n]+)",(0x[0-9a-f]+),\{([^}]+)\}\}',h) + assert len(entries)==12 + for symbol,value,body in entries: + offset=int(value,0);prefix=bytes(int(v,0) for v in body.split(',')) + assert symbols[symbol]==offset and donor[offset:offset+len(prefix)]==prefix + for name,offset in offsets.items(): + assert donor[offset:offset+len(arrays[name])]==arrays[name] + # The path error branch calls disableDisplay before returning through its + # normal unlock path. These are independent of the new outer early return. + assert donor[0x50004:0x50009]==bytes.fromhex('4585ed7437') + assert donor[0x5001b:0x50020]==bytes.fromhex('e82cd3ffff') + print('PASS exact c004fc1a: 12 entries, full TMDS sequence, native cleanup, both mode-return boundaries') diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 64720428..a65aa112 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -27,8 +27,12 @@ struct IOMemoryMap:OSObject {uintptr_t address {};uint64_t bytes {}; int main(int argc,char **argv) { assert(XELP::display13ProbeCapable(xelpProbeCapabilities)); assert(!XELP::display13ProbeCapable(nullptr)); + assert(XELP::display13TCProbeCapable(xelpProbeCapabilities)); + assert(!XELP::display13TCProbeCapable(nullptr)); + assert(!XELP::display13TCProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return XELP::ProbeDisplay13ComboDP;})); + assert(XELP::display13TCProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return XELP::ProbeDisplay13FixedTC;})); for(unsigned schema=0;schema<5;++schema)for(unsigned abi=0;abi<6;++abi) - assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?XELP::ProbeDisplay13ComboDP:0)); + assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC):0)); assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 0;})); assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 2;})); assert(argc==2);const bool adln=!strncmp(argv[1],"adln",4); diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 8fd2fa17..9039d2e5 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -12,6 +12,23 @@ struct IO { uint32_t read(uint32_t r){reads.push_back(r);return regs.at(r);} }; int main() { + // ABI3 explicitly describes fixed TC HDMI; capability query prevents an + // old provider from accepting a configuration whose implementation it lacks. + for(unsigned d=3;d<=6;++d)for(unsigned ready=0;ready<16;++ready)for(unsigned h=0;h<2;++h) { + alignas(8) uint8_t platform[0x1c0] {}; + ProbeConfig c;c.profile=identify(0x46d18086,0);c.comboBackend=3;c.legacy=1U<(platform),c};assert(b.valid()); + auto rec=reinterpret_cast(platform+0x20+24*d); + const uint32_t native[]={d,d+6,2,1,0x800,0x202};memcpy(rec,native,sizeof(native)); + IO io;io.regs={{0xc4000,h<<(21+d)},{0x161500+4*(d-3),ready}}; + assert(probeMode(io,b,2,reinterpret_cast(rec),rec)==(h && (ready&7)==4?4U:0U)); + assert(io.reads[0]==0x161500+4*(d-3)); + for(unsigned f:{0U,1U,2U,3U,4U,5U}) { + auto old=rec[f];rec[f]=~0U;io.reads.clear(); + assert(!probeMode(io,b,2,reinterpret_cast(rec),rec) && io.reads.empty());rec[f]=old; + } + auto bad=c;bad.legacyBackend=0;assert(!bad.valid());bad=c;bad.typeC=c.legacy;assert(!bad.valid()); + } static_assert(sizeof(Profile)==28 && sizeof(ProbeConfig)==60,"Keep existing provider ABI3 layout"); for(auto id:{0xA7808086U,0xA7828086U,0xA7888086U,0xA78B8086U}) { auto p=identify(id,12); diff --git a/tools/test.py b/tools/test.py index bf6fd926..19851ee1 100644 --- a/tools/test.py +++ b/tools/test.py @@ -5,11 +5,11 @@ 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] -if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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,[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 1ad6fb0c8c390296edd5e038f7e7dac1c4dd2f75 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:00:07 +0900 Subject: [PATCH 07/60] Assemble physical ADL-P TC1 framebuffer-only candidate --- README.md | 2 + README_ja.md | 2 + WhateverXe/kern_display13_fb_topology.hpp | 6 +- WhateverXe/kern_xe.cpp | 74 +++++++++++++++-------- WhateverXe/kern_xe.hpp | 4 +- WhateverXe/kern_xelp_platform.hpp | 15 ++++- tests/adlp_fb_candidate.py | 57 +++++++++++++++++ tests/adlp_load_only.py | 10 ++- tests/boot_arguments.py | 5 +- tests/device_selection.cpp | 9 ++- tests/display_backend_dispatch.py | 10 ++- tests/provider_binding.py | 2 +- tests/provider_binding_fixture.cpp | 20 ++++-- tests/xelp_platform.cpp | 6 ++ tools/test.py | 2 +- 15 files changed, 184 insertions(+), 40 deletions(-) create mode 100644 tests/adlp_fb_candidate.py diff --git a/README.md b/README.md index 62b92076..b53dcf31 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,8 @@ installation or update location. Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. `-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on an ADL-P device using an explicit backend override, for load-only diagnostics without GPU initialization. +The experimental 46A6 ADL-P configuration with a 9A49 donor selector retains its physical Display13 identity and currently enables only the central TC1 framebuffer path; both accelerator probe and start remain blocked. This is a bring-up candidate, not a validated hardware configuration. + ## Build and test diff --git a/README_ja.md b/README_ja.md index 73cdd4f5..56e4ed25 100644 --- a/README_ja.md +++ b/README_ja.md @@ -170,6 +170,8 @@ Safariのインストール先や更新による配置変更に依存せずData 開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 `-wex-adlp-load-only` は既存バックエンドを明示選択したADL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 +46A6 ADL-Pで9A49をdonor選択に使う実験構成は、物理Display13の識別を維持し、現時点では中央TC1のFramebuffer経路のみを選択します。Acceleratorはprobe/startの両方を拒否します。実機成立済みの対応構成ではなく、立上げ候補です。 + ## ビルドとテスト diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 727610a5..3584c66d 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -15,7 +15,11 @@ 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) {return adlnWiring(w) && !w.dp;} +inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false) { + // First ADL-P candidate selects the measured central TC1 only. Extend + // this board policy after native FB handoff is observed on hardware. + return !w.dp && (adlnWiring(w) || (fixedTC && w.described() && w.hdmi==8 && w.ddc[3]==9)); +} inline bool multiOutputWiring(const Display13OutputWiring &w) { return selectedWiring(w) && (w.selected()&(w.selected()-1)); } diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index de0ef8e3..dde4a43d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -210,14 +210,14 @@ XE *XE::callbackXE; static IOPCIDevice *xelpSelectedGPU; static XELP::Profile xelpSelectedProfile; static bool xelpSelectedDevice(IOPCIDevice *pci) { - return pci && pci==xelpSelectedGPU && xelpSelectedProfile.admitted() && + return pci && pci==xelpSelectedGPU && xelpSelectedProfile.displayAdmitted() && XELP::same(xelpSelectedProfile,XELP::deviceProfile(pci)); } static bool xelpAlderLakeNDevice(IOPCIDevice *pci) { return xelpSelectedProfile.family==XELP::Family::AlderLakeN && xelpSelectedDevice(pci); } static bool xelpDisplay13Device(IOPCIDevice *pci) { - return XELP::displayAdapter(xelpSelectedProfile.family)==XELP::DisplayAdapter::ADLN && + return (XELP::displayAdapter(xelpSelectedProfile.family)==XELP::DisplayAdapter::ADLN || xelpSelectedProfile.framebufferOnlyADLP()) && xelpSelectedProfile.display==XELP::Display::XeLPD && xelpSelectedDevice(pci); } // Detailed read-only display evidence is opt-in; control and first-fault @@ -3749,12 +3749,16 @@ void XE::selectProfile(const XELP::Profile &profile) { adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN && profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; adlnDetected=adlnRequested; - modDVMTCalcFix.available=profile.admitted(); + modDVMTCalcFix.available=profile.displayAdmitted(); const bool nativeOS=XELP::kernelOS(version)!=XELP::OS::Unknown && XELP::kernelOS(version)!=XELP::OS::Donor; - display13FramebufferRequested=profile.admitted() && profile.display==XELP::Display::XeLPD && - XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN && nativeOS; + adlpFramebufferOnly=profile.framebufferOnlyADLP() && nativeOS; + display13FramebufferRequested=profile.displayAdmitted() && profile.display==XELP::Display::XeLPD && + (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpFramebufferOnly) && nativeOS; display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb; + if(adlpFramebufferOnly) { + tglRequested=tglAcceleratorRequested=false;currentGraphics=&kextIntelTGL;return; + } if(adlnRequested) { adlnKernelReady=nativeOS; adlnNativeRequested=adlnKernelReady; @@ -3781,6 +3785,20 @@ void XE::selectProfile(const XELP::Profile &profile) { void XE::deinit() {} +Display13OutputWiring XE::display13BoardWiring(IOService *provider) { + auto property=provider->getProperty("xelp-hdmi-wiring"); + if(!property)property=provider->getProperty("tgl-hdmi-wiring"); + if(adlpFramebufferOnly) { + // VBT remains the authority for pin/transport. No Combo property is + // silently interpreted as a TC policy for this single-TC1 candidate. + if(property)return {}; + return XELP::VBT::display13HDMI(firmwarePorts,8); + } + auto wiring=OSDynamicCast(OSData,property); + return XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, + wiring?wiring->getLength():0,firmwarePorts,3); +} + void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); @@ -3798,16 +3816,12 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97PhysicalMemoryLayout",memory,sizeof(memory)); // Bootstrap runs before framebuffer start loads its board policy. Native // GOP qualification must already use that physical port, not assume B. - auto property=pci->getProperty("xelp-hdmi-wiring"); - if(!property)property=pci->getProperty("tgl-hdmi-wiring"); - auto wiring=OSDynamicCast(OSData,property); - const auto board=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, - wiring?wiring->getLength():0,firmwarePorts,3); - if(displayRequested && !Display13FBTopology::supportedWiring(board)) { + const auto board=display13BoardWiring(pci);adlnOutputWiring=board; + if(displayRequested && !Display13FBTopology::supportedWiring(board,adlpFramebufferOnly)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } auto bootDDI=displayRequested ? UINT32_MAX : 1U; - uint64_t connector[] {1,displayRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; + uint64_t connector[] {1,displayRequested,pci->getProperty("xelp-hdmi-wiring")!=nullptr,board.valid,board.hdmi,bootDDI}; pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); pci->setProperty("N97DisplayVersion", AlderLakeN::DisplayVersion, 32); pci->setProperty("N97GTStep", revision==0 ? "A0" : "unknown"); @@ -4062,7 +4076,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { } bool XE::xelpBindProbe(void *controller) { - if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false; + if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.displayAdmitted())return false; XELP::ProbeConfig config;config.profile=xeProfile; if(adlsDisplaySelected) { if(!tglHDMIWiringApplied || !adlsWiring.valid || !adlsWiring.selected())return false; @@ -4075,9 +4089,11 @@ bool XE::xelpBindProbe(void *controller) { config.legacyBackend=tglAUXParentMask; } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || - !Display13FBTopology::adlnWiring(adlnOutputWiring) || + !Display13FBTopology::supportedWiring(adlnOutputWiring,adlpFramebufferOnly) || + ((adlnOutputWiring.hdmi&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; - config.combo=adlnOutputWiring.selected();config.comboBackend=3; + config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3; + config.legacy=adlnOutputWiring.hdmi&0x78;config.legacyBackend=config.legacy?0x78:0; } const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound); @@ -4113,7 +4129,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // ADLP_LOAD_ONLY_KERNEL: do not enter TGL MMIO/FIA, bootstrap or VM setup. const auto physicalEntry=XELP::deviceEntry(xeProfile.identity); adlpLoadOnly=physicalEntry && physicalEntry->family==XELP::Family::AlderLakeP && - xeProfile.admitted() && xeProfile.overridden() && + xeProfile.donor==XELP::Donor::C004FC1A && xeProfile.overridden() && XELP::hostOS(XELP::kernelOS(version)) && checkKernelArgument("-wex-adlp-load-only"); if(adlpLoadOnly) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyKernel",true); @@ -4203,7 +4219,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } if(map)map->release(); } - if(!xeProfile.admitted())return; + if(!xeProfile.displayAdmitted())return; xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; if (adlnDetected || display13FramebufferRequested) { xelpReadFirmware(physicalGPU); @@ -4286,8 +4302,11 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add patcher.clearError(); display13DPProbeReady=query>=address && query-address(query)); + display13TCProbeReady=query>=address && query-address(query)); } IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13DPProbeAvailable",display13DPProbeReady); + IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13TCProbeAvailable",display13TCProbeReady); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); if(adlnRequested)IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); return true; @@ -4296,6 +4315,10 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add // Keep both adapters outside the large Display12 framebuffer patch frame. if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) return processDisplay13Framebuffer(patcher,index,address,size); + if(adlpFramebufferOnly) { + if(index==kextIntelTGL.loadIndex)return processADLPLoadOnly(patcher,index,address,size); + return false; + } if (adlnRequested || adlnDetected) { if (!adlnDetected || !adlnNativeRequested || !currentGraphics || currentGraphics->loadIndex != index) return false; // Matching is persistent. A rejected bootstrap must not leave the @@ -6104,7 +6127,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) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->adlpFramebufferOnly) || !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; @@ -7254,12 +7277,12 @@ bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32 bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); if(!pci)return false; - auto property=provider->getProperty("xelp-hdmi-wiring"); - if(!property)property=provider->getProperty("tgl-hdmi-wiring"); - auto wiring=OSDynamicCast(OSData,property); - s->adlnOutputWiring=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, - wiring?wiring->getLength():0,s->firmwarePorts,3); - if(!Display13FBTopology::supportedWiring(s->adlnOutputWiring))return false; + const auto board=s->display13BoardWiring(provider); + if(!Display13FBTopology::supportedWiring(board,s->adlpFramebufferOnly) || + board.selected()!=s->adlnOutputWiring.selected())return false; + if(s->adlpFramebufferOnly && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBTCUnavailable",true);return false; + } const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); const bool ready=s->display13FramebufferRequested && s->display13BootReady && s->tglActive && contract.ready() && pci && xelpDisplay13Device(pci); @@ -9497,7 +9520,7 @@ bool XE::processADLPLoadOnly(KernelPatcher &patcher,size_t index,mach_vm_address const uint8_t startHead[]={0x55,0x48,0x89,0xe5}; auto p=patcher.solveSymbol(index,probe,address,size); auto q=patcher.solveSymbol(index,start,address,size);patcher.clearError(); - bool ready=adlpLoadOnly && (fb || index==kextIntelTGL.loadIndex) && size>probeOffset+32 && size>startOffset+32 && + bool ready=(adlpLoadOnly || (adlpFramebufferOnly && !fb)) && (fb || index==kextIntelTGL.loadIndex) && size>probeOffset+32 && size>startOffset+32 && p==address+probeOffset && q==address+startOffset && !memcmp(reinterpret_cast(p),fb?fbHead:gtHead,sizeof(gtHead)) && !memcmp(reinterpret_cast(q),startHead,sizeof(startHead)); @@ -9512,6 +9535,7 @@ bool XE::processADLPLoadOnly(KernelPatcher &patcher,size_t index,mach_vm_address } patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty(fb?"ADLPFramebufferLoadOnlyReady":"ADLPAcceleratorLoadOnlyReady",ready); + if(adlpFramebufferOnly && !fb)IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBOnlyAcceleratorBlocked",ready); SYSLOG("igfx","ADL-P load-only: %s probe/start barriers ready=%u",fb?"framebuffer":"accelerator",ready); return ready; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 3cd03365..993611b5 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -153,7 +153,9 @@ class XE { static uint32_t wrapDisplay13TrainingPattern(void *path,uint32_t pattern); static uint32_t wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig); mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; - bool display13DPProbeReady {}; + bool display13DPProbeReady {},display13TCProbeReady {}; + bool adlpFramebufferOnly {}; + Display13OutputWiring display13BoardWiring(IOService *); bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; Display13OutputWiring adlnOutputWiring; diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 41cb11f7..00acde9c 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -14,6 +14,13 @@ struct Profile { GT gt {GT::Unknown}; Donor donor {Donor::Unknown}; uint32_t effectiveIdentity {}; // Table selector; may come from device-id. + // Initial ADL-P FB candidate: donor selector does not change physical + // Display13 identity and grants no Render/Accelerator qualification. + bool framebufferOnlyADLP() const { + return identity==0x46A68086 && effectiveIdentity==0x9A498086 && revision<=255 && + family==Family::AlderLakeP && display==Display::XeLPD && gt==GT::Unknown && donor==Donor::C004FC1A; + } + bool displayAdmitted() const {return admitted() || framebufferOnlyADLP();} bool overridden() const {return identity!=effectiveIdentity;} // Select the implemented family backend for every PCI revision. Hardware // validation is independent; a stepping split needs a concrete behavior delta. @@ -26,6 +33,12 @@ inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; if((physical&0xFFFF)!=0x8086 || revision>255)return p; const auto entry=deviceEntry(effective);if(!entry)return p; + const auto physicalEntry=deviceEntry(physical); + if(physicalEntry && physicalEntry->family==Family::AlderLakeP) { + p.family=physicalEntry->family;p.display=physicalEntry->display; + if(entry->backend!=GT::Unknown)p.donor=Donor::C004FC1A; + return p; + } p.family=entry->family;p.display=entry->display;p.gt=entry->backend; if(p.gt!=GT::Unknown)p.donor=Donor::C004FC1A; return p; @@ -82,7 +95,7 @@ struct ProbeConfig { Profile profile; uint32_t combo {},legacy {},typeC {},excluded {},comboBackend {},legacyBackend {}; bool valid() const { - if(abi!=ProbeABI || bytes!=sizeof(ProbeConfig) || !profile.admitted() || + if(abi!=ProbeABI || bytes!=sizeof(ProbeConfig) || !profile.displayAdmitted() || !same(profile,identify(profile.identity,profile.revision,profile.effectiveIdentity)))return false; // ADL-S firmware routes use the donor's three Combo PHY slots. // Its TC label grants neither Type-C nor Dekel register access. diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py new file mode 100644 index 00000000..ffb0e183 --- /dev/null +++ b/tests/adlp_fb_candidate.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Replay production board selection and WEX/provider binding for the first TC1 candidate.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +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 +#include +#include +#include "kern_display13_firmware.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_alderlake_s.hpp" +struct OSData {void *getBytesNoCopy(){return nullptr;}unsigned getLength(){return 0;}}; +#define OSDynamicCast(T,p) static_cast(p) +struct IOService {OSData *property=nullptr;OSData *getProperty(const char *){return property;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}}; +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 { + XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); + XELP::VBT::Ports firmwarePorts; + bool adlpFramebufferOnly=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; + bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; + bool display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; + uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); + void *adlnTopologyController=(void*)1;Display13OutputWiring adlnOutputWiring,tglHDMIWiring; + struct {bool valid=false;unsigned excluded=0;unsigned selected(){return 0;}}adlsWiring; + struct {bool valid=false;unsigned typeC=0,excluded=0;}tglTCWiring; + unsigned tglAUXParentMask=0;unsigned tglTCProbeMask(){return 0;} + Display13OutputWiring display13BoardWiring(IOService *); + bool xelpBindProbe(void *); +}; +FUNCTIONS +int main(){ + 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==8 && w.ddc[3]==9 && !w.dp); + assert(!Display13FBTopology::supportedWiring(w) && Display13FBTopology::supportedWiring(w,true)); + x.adlnOutputWiring=w;assert(x.xelpBindProbe((void*)1)); + assert(seen.valid() && !seen.combo && seen.comboBackend==3 && seen.legacy==8 && seen.legacyBackend==0x78); + assert(seen.profile.family==XELP::Family::AlderLakeP && !seen.profile.admitted()); + for(unsigned f=0;f<5;++f){XE bad=x; + if(f==0)bad.display13TCProbeReady=false;if(f==1)bad.display13FBTCHooksReady=false; + if(f==2)bad.display13FBTopologyHooksReady=false;if(f==3)bad.xelpProviderConfigureProbe=0; + if(f==4)bad.adlnTopologyController=(void*)2; + const auto before=calls;assert(!bad.xelpBindProbe((void*)1) && calls==before); + } + OSData property;pci.property=&property;assert(!x.display13BoardWiring(&pci).described());pci.property=nullptr; + x.firmwarePorts.port[3].type=4;assert(!x.display13BoardWiring(&pci).described()); + puts("PASS production ADL-P candidate: VBT TC1/pin9 -> shared board policy -> capability/hook-gated provider binding; GT closed"); +} +'''.replace('FUNCTIONS',f) +with tempfile.TemporaryDirectory(prefix='adlp-fb-candidate-') as tmp: + p=Path(tmp);(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/adlp_load_only.py b/tests/adlp_load_only.py index 829807d4..7e9812cd 100644 --- a/tests/adlp_load_only.py +++ b/tests/adlp_load_only.py @@ -46,7 +46,7 @@ } }; struct XE { - bool adlpLoadOnly=false;XELP::Profile xeProfile;unsigned initialized=0; + bool adlpLoadOnly=false,adlpFramebufferOnly=false;XELP::Profile xeProfile;unsigned initialized=0; void select(){SELECTION ++initialized;} bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); @@ -75,6 +75,14 @@ if(!bad){assert(IORegistryEntry::props.at(index==1?"ADLPFramebufferProbeRejected":"ADLPAcceleratorProbeRejected"));assert(IORegistryEntry::props.at("ADLPLoadOnlyUnexpectedStart"));} ++cases; } + for(unsigned module:{1U,2U}){ + std::vector image(0x50000);auto base=reinterpret_cast(image.data()); + const uint8_t gt[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; + memcpy(image.data()+0x238e2,gt,17);memcpy(image.data()+0x23ecc,gt,4); + XE xe;xe.adlpFramebufferOnly=true;KernelPatcher p; + assert(xe.processADLPLoadOnly(p,module,base,image.size())==(module==2));assert(p.routed==(module==2?2U:0U)); + if(module==2)assert(IORegistryEntry::props.at("ADLPFBOnlyAcceleratorBlocked"));++cases; + } printf("PASS %u production ADLP load-only selection/probe/start checks\n",cases); } '''.replace('SELECTION',selection).replace('BODY',body) diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 8438477d..66a0c757 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -64,7 +64,7 @@ struct XE { XELP::Profile xeProfile; bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; - bool display13FramebufferRequested=false, display13TraceEnabled=false; + bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpFramebufferOnly=false; bool display12SmallGTReady=false, adlsDisplayHooksReady=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; @@ -169,6 +169,9 @@ xe=boot({},0x12348086,0x46D28086); assert(xe.adlnRequested && xe.adlnNativeRequested && xe.xeProfile.identity==0x12348086); xe=boot({},0x12348086,0x9A498086);assert(xe.tglRequested); + xe=XE{};xe.selectProfile(XELP::identify(0x46A68086,12,0x9A498086)); + assert(xe.adlpFramebufferOnly && xe.display13FramebufferRequested && !xe.tglAcceleratorRequested && !xe.adlnRequested && !xe.tglRequested); + assert(xe.xeProfile.family==XELP::Family::AlderLakeP && xe.xeProfile.gt==XELP::GT::Unknown); xe=boot({},0x46A68086);assert(!xe.tglRequested && !xe.adlnRequested); for(uint32_t pci:{0x46A68086U,0x46808086U,0xA7A08086U}) { xe=boot({},pci);assert(!xe.adlnNativeRequested && !xe.display13FramebufferRequested && !adlnNativeRCSProfileRequested); diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 56fb2e1d..4687b929 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -36,10 +36,13 @@ int main() { assert(!select(0x9A498086,0,true,nullptr,4).admitted()); for(uint32_t bad:{0U,0xFFFFU,0x808646D2U,0x1234U}) assert(!select(0x9A498086,0,true,&bad,4).admitted()); - auto p=select(0x46A68086,0,true,&id,4);assert(p.admitted() && p.family==Family::AlderLakeN); - assert(!strcmp(model(p),"Intel Iris Xe Graphics")); // Name is physical, backend is overridden. + auto p=select(0x46A68086,0,true,&id,4);assert(!p.admitted() && !p.displayAdmitted() && p.family==Family::AlderLakeP); + assert(!strcmp(model(p),"Intel Iris Xe Graphics")); // Physical ADL-P never becomes an ADL-N render backend. auto q=p;q.effectiveIdentity=0x46D18086;assert(!same(p,q)); - ProbeConfig c;c.profile=p;c.combo=c.comboBackend=3;assert(c.valid()); + ProbeConfig c;c.profile=p;c.combo=c.comboBackend=3;assert(!c.valid()); + id=0x9A49;p=select(0x46A68086,12,true,&id,4); + assert(p.displayAdmitted() && !p.admitted() && p.framebufferOnlyADLP()); + c={};c.profile=p;c.comboBackend=3;c.legacy=8;c.legacyBackend=0x78;assert(c.valid()); c.abi=2;assert(!c.valid());c.abi=3;c.profile.gt=GT::TGLGT2;assert(!c.valid()); puts("PASS device table, physical/effective identity, strict override bytes/vendor/revision, model choice and ABI3"); } diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index 821fe01d..7453bee7 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -21,15 +21,17 @@ def expression(name): #include using mach_vm_address_t=uintptr_t; struct KernelPatcher {}; -struct Kext {size_t loadIndex;}; +struct Kext {size_t loadIndex;};Kext kextIntelTGL{2}; bool loadOnly=false; bool checkKernelArgument(const char *arg) {assert(!strcmp(arg,"-wex-adln-load-only"));return loadOnly;} struct XE { + bool adlpFramebufferOnly=false; bool display13FramebufferRequested=false,adlnRequested=false,adlnDetected=false; bool adlnNativeRequested=false,adlnNativeReady=false,display13BootReady=false,tglActive=false; Kext *currentFramebuffer=nullptr,*currentGraphics=nullptr; enum Call {None,Display13,Render,Rejected,LoadOnly,Display12};Call call=None; bool processDisplay13Framebuffer(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display13;return true;} + bool processADLPLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} bool processTGLAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Render;return true;} bool processAlderLakeNRejectedAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} bool processAlderLakeNLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=LoadOnly;return true;} @@ -91,6 +93,12 @@ def expression(name): puts("PASS 8192 FB/render preflight combinations: noaccel preserves FB memory, never admits render"); } int main() { + for(unsigned module=1;module<=3;++module){ + XE x;KernelPatcher p;Kext fb{1};x.adlpFramebufferOnly=true;x.display13FramebufferRequested=true;x.currentFramebuffer=&fb; + const bool result=x.dispatch(p,module,0,0); + assert(x.call==(module==1?XE::Display13:module==2?XE::Rejected:XE::None));assert(result==(module!=3)); + } + certificates(); Kext fb{1},graphics{2};KernelPatcher patcher;unsigned cases=0; // Removing/rejecting Render must not redirect a selected Display13 kext diff --git a/tests/provider_binding.py b/tests/provider_binding.py index f67ba41d..4e90ff1e 100644 --- a/tests/provider_binding.py +++ b/tests/provider_binding.py @@ -22,5 +22,5 @@ "-fno-omit-frame-pointer", "-I" + str(root.parent / "AppleIntelTGLCompat"), "-I" + str(root.parent / "WhateverXe"), str(cpp), "-o", str(exe)], check=True) - for mode in ["tgl", "tgl-9a40", "tgl-g4", "adln", "adln-16eu", "adln-abi1", "unknown", "adln-revision", "adln-fake", "tgl-fake", "adln-abi1-fake", "adls", "adls-revision", "rpls", "rpls-revision"]: + for mode in ["adlp-fb", "tgl", "tgl-9a40", "tgl-g4", "adln", "adln-16eu", "adln-abi1", "unknown", "adln-revision", "adln-fake", "tgl-fake", "adln-abi1-fake", "adls", "adls-revision", "rpls", "rpls-revision"]: subprocess.run([str(exe), mode], check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index a65aa112..9b488cad 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -38,6 +38,7 @@ int main(int argc,char **argv) { assert(argc==2);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=!strcmp(argv[1],"adlp-fb"); const bool legacy=!strncmp(argv[1],"adln-abi1",9); const bool fake=strstr(argv[1],"fake")!=nullptr; const bool rejected=!strcmp(argv[1],"unknown"); @@ -54,6 +55,7 @@ int main(int argc,char **argv) { if(!strcmp(argv[1],"adln-revision"))pci.revision=1; OSData fakeID; if(fake){pci.identity=0x12348086;fakeID.value=adln?0x46D2:0x9A49;pci.property=&fakeID;} + if(adlp){pci.identity=0x46A68086;pci.revision=12;fakeID.value=0x9A49;pci.property=&fakeID;} controller.provider=&pci;platform[0x12]=9; map.address=reinterpret_cast(mmio.data());map.bytes=mmio.size(); field(&controller,0xC40)=&access;field(&controller,0xD40)=platform; @@ -66,21 +68,22 @@ int main(int argc,char **argv) { field(&port,0x548)=initial; assert(!tglProbePort(&port)); assert(!hardwareAllowed(nullptr));IOService orphan;assert(!hardwareAllowed(&orphan)); - const bool physicalTGL=!adln && !adls && !rejected; - assert(hardwareAllowed(&controller)==(!adln && !rejected)); + const bool physicalTGL=!adln && !adls && !adlp && !rejected; + assert(hardwareAllowed(&controller)==(!adln && !adlp && !rejected)); assert(tglProbePort(&port)==(physicalTGL?1U:0U)); field(&port,0x578)=&foreign;assert(!tglProbePort(&port));field(&port,0x578)=&access; XELP::ProbeConfig config;config.profile=XELP::deviceProfile(&pci); xelpSelectedGPU=&pci;xelpSelectedProfile=config.profile; - assert(xelpSelectedDevice(&pci)==config.profile.admitted()); + assert(xelpSelectedDevice(&pci)==config.profile.displayAdmitted()); assert(xelpAlderLakeNDevice(&pci)==(adln && !rejected)); - assert(xelpDisplay13Device(&pci)==(adln && !rejected)); + assert(xelpDisplay13Device(&pci)==((adln || adlp) && !rejected)); IOPCIDevice foreignPCI;foreignPCI.identity=pci.identity;foreignPCI.revision=pci.revision;foreignPCI.property=pci.property; assert(!xelpSelectedDevice(&foreignPCI)); assert(!xelpDisplay13Device(&foreignPCI)); config.combo=3;config.comboBackend=adln?3:7;config.legacy=adln?0:0x1F8;config.legacyBackend=adln?0:0x1F8; if(adls){config.combo=8;config.comboBackend=0x19;config.legacy=config.legacyBackend=0;} if(adls && !rejected){assert(hardwareAllowed(&controller));assert(!tglProbePort(&port));} + if(adlp){config.combo=0;config.comboBackend=3;config.legacy=8;config.legacyBackend=0x78;} // The contract derives generation from physical identity, not boot args. assert(!xelpConfigurePortProbe(&controller,2,3,3)); if(!adln)assert(!xelpConfigurePortProbe(&controller,1,3,3)); @@ -110,6 +113,15 @@ int main(int argc,char **argv) { assert(xelpConfigureDisplay(&controller,&config));assert(controller.retains==1); malformed=config;malformed.combo=1;assert(!xelpConfigureDisplay(&controller,&malformed)); if(adln){assert(xelpConfigurePortProbe(&controller,1,3,3));assert(!xelpConfigurePortProbe(&controller,2,3,3));} + if(adlp){ + auto rec=reinterpret_cast(platform+0x20+24*3); + const uint32_t values[]={3,9,2,1,0x800,0x202};memcpy(rec,values,sizeof(values));field(&port,0x548)=rec; + for(unsigned ready=0;ready<16;++ready)for(unsigned hpd=0;hpd<2;++hpd){ + field(mmio.data(),0x161500)=ready;field(mmio.data(),0xc4000)=hpd<<24; + assert(tglProbePort(&port)==(hpd && (ready&7)==4?4U:0U)); + } + assert(!hardwareAllowed(&controller));puts("PASS actual ADL-P Display13 provider: physical identity, legacy TC probe, no Display12/GT fallback");return 0; + } for(unsigned d=0;d<9;++d){auto rec=reinterpret_cast(platform+0x20+24*d); const uint32_t values[]={d,adls?9:d+1,2,0,0x800,0x202};memcpy(rec,values,sizeof(values)); field(&port,0x548)=rec; diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 9039d2e5..016ba080 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -12,6 +12,12 @@ struct IO { uint32_t read(uint32_t r){reads.push_back(r);return regs.at(r);} }; int main() { + auto candidate=identify(0x46a68086,12,0x9a498086); + assert(candidate.framebufferOnlyADLP() && candidate.displayAdmitted() && !candidate.admitted()); + assert(candidate.family==Family::AlderLakeP && candidate.display==Display::XeLPD && candidate.gt==GT::Unknown); + assert(!identify(0x46a68086,12).displayAdmitted()); + assert(!identify(0x46a38086,12,0x9a498086).displayAdmitted()); + // ABI3 explicitly describes fixed TC HDMI; capability query prevents an // old provider from accepting a configuration whose implementation it lacks. for(unsigned d=3;d<=6;++d)for(unsigned ready=0;ready<16;++ready)for(unsigned h=0;h<2;++h) { diff --git a/tools/test.py b/tools/test.py index 19851ee1..f63bbfe6 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] From 66fcab83d88dbe208fe38c3c7e2c87e69e1aefd8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:38:37 +0900 Subject: [PATCH 08/60] Isolate Display13 port probing during framebuffer bring-up --- README.md | 1 + README_ja.md | 1 + WhateverXe/kern_xe.cpp | 17 ++++++++++++- tests/boot_arguments.py | 5 ++++ tests/display13_offline.py | 33 +++++++++++++++++++++++++ tests/display13_tc_boot.cpp | 4 +++ tests/display13_tiling.py | 1 + tests/display_backend_dispatch.py | 1 + tests/fixtures/boot_argument_names.json | 3 ++- tools/test.py | 2 +- 10 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/display13_offline.py diff --git a/README.md b/README.md index b53dcf31..681c8117 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ installation or update location. Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. `-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on an ADL-P device using an explicit backend override, for load-only diagnostics without GPU initialization. +`-wex-display13-offline` keeps Display 13 native controller initialization enabled but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. The experimental 46A6 ADL-P configuration with a 9A49 donor selector retains its physical Display13 identity and currently enables only the central TC1 framebuffer path; both accelerator probe and start remain blocked. This is a bring-up candidate, not a validated hardware configuration. diff --git a/README_ja.md b/README_ja.md index 56e4ed25..c757b7cf 100644 --- a/README_ja.md +++ b/README_ja.md @@ -170,6 +170,7 @@ Safariのインストール先や更新による配置変更に依存せずData 開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 `-wex-adlp-load-only` は既存バックエンドを明示選択したADL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 +`-wex-display13-offline` はDisplay 13のnative controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 46A6 ADL-Pで9A49をdonor選択に使う実験構成は、物理Display13の識別を維持し、現時点では中央TC1のFramebuffer経路のみを選択します。Acceleratorはprobe/startの両方を拒否します。実機成立済みの対応構成ではなく、立上げ候補です。 diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index dde4a43d..2d540927 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -223,6 +223,7 @@ static bool xelpDisplay13Device(IOPCIDevice *pci) { // Detailed read-only display evidence is opt-in; control and first-fault // receipts remain active in the ordinary AlderLakeN profile. static bool display13TraceEnabled; +static bool display13Offline; static XELPMemory::Layout xelpMemory; static uint16_t xelpMemoryGGC; static XELPMemory::Layout xelpPhysicalMemory(IOPCIDevice *pci) { @@ -3755,6 +3756,7 @@ void XE::selectProfile(const XELP::Profile &profile) { display13FramebufferRequested=profile.displayAdmitted() && profile.display==XELP::Display::XeLPD && (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpFramebufferOnly) && nativeOS; display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); + display13Offline=display13FramebufferRequested && checkKernelArgument("-wex-display13-offline"); if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb; if(adlpFramebufferOnly) { tglRequested=tglAcceleratorRequested=false;currentGraphics=&kextIntelTGL;return; @@ -3879,7 +3881,9 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97DisplayReadoutStatus", static_cast(result.status), 32); SYSLOG("igfx", "AlderLakeN Display13 readout status=%u powered=%x active=%x hdmiB=%x; donor blocked", static_cast(result.status), result.poweredPipes, result.activePipes, result.hdmiBPipes); - if (linearTest) { + // ADL-P adopts a validated firmware surface below. A destructive plane + // exercise before native controller start is not a startup prerequisite. + if (linearTest && !adlpFramebufferOnly) { auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); if(bootDDI>=3)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI, @@ -4220,6 +4224,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { if(map)map->release(); } if(!xeProfile.displayAdmitted())return; + SYSLOG("igfx","Xe display bootstrap physical=%x FB-only=%u",xeProfile.identity,adlpFramebufferOnly); xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; if (adlnDetected || display13FramebufferRequested) { xelpReadFirmware(physicalGPU); @@ -4227,6 +4232,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { modDVMTCalcFix.processKernel(patcher,info); xelpLoadDisplayPool(physicalGPU); adlnBootstrap(physicalGPU); + SYSLOG("igfx","Display13 bootstrap ready=%u",display13BootReady); if (display13BootReady) { tglVMPrepare(patcher,physicalGPU); tglPrepareMMIOUC(patcher,physicalGPU); @@ -6158,6 +6164,13 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { if(!s->display13FBTopologyHooksReady || !s->adlnTopologyController || !port || port->getMetaClass()->getClassSize()<0x580)return 0; auto config=Display13FBMemory::field(port,0x548); if(Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring)) { + // Keep controller/memory/IRQ initialization reachable while isolating + // HPD, EDID and mode programming during framebuffer bring-up. + if(display13Offline) { + *reinterpret_cast(reinterpret_cast(port)+0x554)=0; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13OfflineProbe",true); + return 0; + } // 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)) { @@ -7315,7 +7328,9 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { (provider->getProperty("AAPL,GfxYTile") || provider->setProperty("AAPL,GfxYTile",&enabled,sizeof(enabled))); OSSafeReleaseNULL(yTiling);OSSafeReleaseNULL(mode);OSSafeReleaseNULL(features); if(!featuresSelected)return false; + SYSLOG("igfx","Display13 native controller start enter offline=%u",display13Offline); const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgDisplay13FramebufferStart)(that,provider); + SYSLOG("igfx","Display13 native controller start return=%u",started); that->setProperty("N97FramebufferOriginalStart",started); IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferOriginalStart",started); bool irqEnabled=false; diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 66a0c757..34a46cd4 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -59,6 +59,7 @@ bool checkKernelArgument(const char *name) { return args.count(name); } #define SYSLOG(...) ((void)0) #define IOLog(...) ((void)0) +static bool display13Offline; static int kextIntelTGLFb, kextAppleIntelTGLCompat, kextIntelTGL; static struct { void onKextLoadForce(int *) {} } lilu; struct XE { @@ -105,6 +106,10 @@ assert(adlnNativeRCSProfileRequested && xe.tglAcceleratorRequested); assert(xe.currentGraphics==&kextIntelTGL && xe.currentFramebuffer==&kextIntelTGLFb); } + xe=boot({"-wex-display13-offline"},0x46A68086U,0x9A498086U); + assert(display13Offline && xe.adlpFramebufferOnly && !xe.tglAcceleratorRequested); + xe=boot({"-wex-display13-offline"},0x9A498086U); + assert(!display13Offline); xe=boot({"-wex-display13-trace"}); assert(xe.display13TraceEnabled && xe.adlnNativeRequested && adlnNativeRCSProfileRequested); xe=boot({"-wex-adln-kernel-ctx"}); diff --git a/tests/display13_offline.py b/tests/display13_offline.py new file mode 100644 index 00000000..b42f1ca0 --- /dev/null +++ b/tests/display13_offline.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Replay the production port-state boundary, including the normal native path.""" +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::wrapAlderLakeNPortState\(.*?\n\}',s,re.S).group() +code=r''' +#include +#include +#include +#include +struct OSObject { 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 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*);}; +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); + display13Offline=true;assert(XE::wrapAlderLakeNPortState(&port)==0);assert(!calls && !binds);assert(Display13FBMemory::field(&port,0x554)==0); + display13Offline=false;assert(XE::wrapAlderLakeNPortState(&port)==1);assert(calls==1 && binds==1); + display13Offline=true;x.display13FBTopologyHooksReady=false;assert(XE::wrapAlderLakeNPortState(&port)==0);assert(calls==1 && binds==1); + puts("PASS production offline boundary: disconnected state, no provider/native probe, normal path preserved");} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='display13-offline-') as d: + p=Path(d);(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) diff --git a/tests/display13_tc_boot.cpp b/tests/display13_tc_boot.cpp index 4eb93c73..5d7b3daf 100644 --- a/tests/display13_tc_boot.cpp +++ b/tests/display13_tc_boot.cpp @@ -47,6 +47,10 @@ int main() { auto display=readDisplay(io,true);LinearPlan plan; assert(tc.valid() && tc.unchanged(io)); assert(linearPlan(display,plan,ddi)==LinearStatus::InvalidTCSource); + assert(Display13Boot::reservedPrefix(io,display,true,ddi,io.memory,&tc)); + assert(io.writes.empty()); + for(auto reg:original)assert(io.regs[reg.first]==reg.second); + io.pteReads=0; auto result=reinitializeLinear(io,display,true,ddi,GOPProtocol::TMDS,&tc); assert(result.status==LinearStatus::Success && result.writes==10 && tc.unchanged(io)); for(auto w:io.writes)assert(w.offset==0x42084 || w.offset==0x70028 || w.offset==0x70180 || diff --git a/tests/display13_tiling.py b/tests/display13_tiling.py index eb25a818..4a5a9b36 100644 --- a/tests/display13_tiling.py +++ b/tests/display13_tiling.py @@ -10,6 +10,7 @@ block=source[source.index('\tauto old=OSDynamicCast(OSDictionary,',start):end] code=r''' #include +#define SYSLOG(...) ((void)0) #include #include #include diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index 7453bee7..1d032a7a 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -15,6 +15,7 @@ def expression(name): return re.findall(r'\b'+name+r'\s*=\s*(.*?);',bootstrap,re.S)[-1] code=r''' #include +#define SYSLOG(...) ((void)0) #include #include #include diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index afacd671..afbfff46 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -137,5 +137,6 @@ "-wex-noaccel": [], "-wex-display-trace": [], "-wex-adlp-readout": [], - "-wex-adlp-load-only": [] + "-wex-adlp-load-only": [], + "-wex-display13-offline": [] } diff --git a/tools/test.py b/tools/test.py index f63bbfe6..6a227913 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] From 3537f0f1713ec22ba0efe01baee09427cfee7117 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:41:32 +0900 Subject: [PATCH 09/60] Dispatch framebuffer-only profiles through host and firmware setup --- WhateverXe/kern_firmware.cpp | 3 ++- WhateverXe/kern_host.cpp | 4 +++- tests/firmware_readout.py | 37 ++++++++++++++++++++++++++++++++++++ tests/host_publication.py | 9 +++++++++ tools/test.py | 2 +- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/firmware_readout.py diff --git a/WhateverXe/kern_firmware.cpp b/WhateverXe/kern_firmware.cpp index 60c553ba..751eb82f 100644 --- a/WhateverXe/kern_firmware.cpp +++ b/WhateverXe/kern_firmware.cpp @@ -10,7 +10,8 @@ static void publishBytes(IOPCIDevice *pci,const char *key,const void *bytes,unsi // Firmware is read once, before topology/hook selection. No PCI BAR/ROM scan, // power acquisition, or MMIO probing occurs here. Explicit properties win later. void XE::xelpReadFirmware(IOPCIDevice *pci) { - if(!pci || !xeProfile.admitted())return; + // Wiring is needed by framebuffer-only profiles before Render is admitted. + if(!pci || !xeProfile.displayAdmitted())return; const uint32_t asls=pci->configRead32(0xFC); uint32_t source=0; if(asls && asls<=0xFFFFE000U) { diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 684f7727..a0b5de2c 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -31,7 +31,9 @@ void XeHost::processKernel(KernelPatcher &patcher) { // Match WEG ordering: PCI config reads require a published IOPCIDevice. const bool published=gpu && WIOKit::awaitPublishing(gpu); const auto profile=published ? XELP::deviceProfile(gpu) : XELP::Profile {}; - active=profile.admitted(); + // The host must dispatch framebuffer patches and accelerator rejection + // hooks even when the selected profile deliberately has no Render backend. + active=profile.displayAdmitted(); const auto display=XELP::displayAdapter(profile.family); backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS) && info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple; diff --git a/tests/firmware_readout.py b/tests/firmware_readout.py new file mode 100644 index 00000000..c9bd9e11 --- /dev/null +++ b/tests/firmware_readout.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Execute production firmware acquisition with independent Display/Render profiles.""" +from pathlib import Path +import subprocess,tempfile +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 "kern_xelp_platform.hpp" +#include "kern_vbt.hpp" +constexpr unsigned kIODirectionIn=1,kIOMapReadOnly=2; +struct OSData {static OSData *withBytes(const void*,unsigned){static OSData d;return &d;}void release(){}}; +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;} +}; +struct XE {XELP::Profile xeProfile;XELP::VBT::Ports firmwarePorts;void xelpReadFirmware(IOPCIDevice*);}; +BODY +int main(){ + for(unsigned kind=0;kind<4;++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):XELP::identify(0x46d18086,0); + const bool expected=kind!=1; + if(!kind)assert(!x.xeProfile.admitted() && x.xeProfile.displayAdmitted()); + x.xelpReadFirmware(&pci);assert(pci.reads==unsigned(expected));assert(bool(pci.publications)==expected); + x.xelpReadFirmware(nullptr); + } + puts("PASS production VBT acquisition: FB-only ADL-P and admitted TGL/ADL-N read firmware; unselected ADL-P 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) diff --git a/tests/host_publication.py b/tests/host_publication.py index 85eb5deb..17a0cfd9 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -76,6 +76,15 @@ XeHost host;host.processKernel(patcher);assert(!host.active && graphics.off && !host.backlightEnabled); assert(events[events.size()-2]==8); // Diagnostic dispatch does not admit driver hooks. } +for(unsigned rev:{0U,12U,255U}) { + DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; + publish=true;published=false;deviceProperty=&fake;fake.value=0x9A49;identity=0x46A68086;revision=rev;events.clear();graphics.off=false; + XeHost host;host.processKernel(patcher); + assert(host.active && !graphics.off && !host.backlightEnabled); + assert(events[events.size()-2]==8); + const auto profile=XELP::identify(identity,revision,0x9A498086); + assert(profile.displayAdmitted() && !profile.admitted()); +} puts("PASS actual host admission: absent/unpublished/unsupported GPUs never enter Xe; published PCI reads precede selected-driver dispatch");} ''' with tempfile.TemporaryDirectory(prefix='wxe-host-') as d: diff --git a/tools/test.py b/tools/test.py index 6a227913..06a7d4cc 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py'))],None) for n in python_tests] From aaa180321b442fb475e5dc720f13fdfcebbf671b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:43:57 +0900 Subject: [PATCH 10/60] Exercise outer kext dispatch for framebuffer-only profiles --- tests/host_publication.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/host_publication.py b/tests/host_publication.py index 17a0cfd9..0090e495 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -4,7 +4,7 @@ import subprocess,tempfile r=Path(__file__).resolve().parents[1] adapter=(r/'WhateverXe/kern_xelp_device.hpp').read_text();adapter=adapter[adapter.index('namespace XELP {'):adapter.rindex('#endif')] -s=(r/'WhateverXe/kern_host.cpp').read_text();body=s[s.index('void XeHost::processKernel'):s.index('void XeHost::processKext')] +s=(r/'WhateverXe/kern_host.cpp').read_text();body=s[s.index('void XeHost::processKernel'):s.index('bool XeHost::wrapPanelSetDisplay')] code=r''' #include #include @@ -13,6 +13,8 @@ #include #include #include "kern_xelp_platform.hpp" +using mach_vm_address_t=uintptr_t; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}}; std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086;uint8_t revision=0; struct Object {virtual ~Object()=default;}; struct OSData:Object {uint32_t value=0;void *getBytesNoCopy(){return &value;}size_t getLength(){return 4;}}; @@ -33,15 +35,29 @@ };DeviceInfo DeviceInfo::value; namespace WIOKit {bool awaitPublishing(IOPCIDevice *){events.push_back(1);published=publish;return publish;}void renameDevice(IOPCIDevice *device,const char *name){device->name=name;++device->renames;events.push_back(5);}} // DEVICE_ADAPTER -struct KernelPatcher {static constexpr int KernelID=0;uintptr_t solveSymbol(int,const char *){return 0;}void clearError(){}}; -namespace XeDataAccess {void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} -struct Graphics {bool off=false;void switchOff(){off=true;}} graphics; +struct KernelPatcher { + static constexpr int KernelID=0; + uintptr_t solveSymbol(int,const char *){return 0;} + templateT solveSymbol(size_t,const char*,mach_vm_address_t,size_t){return nullptr;} + struct RouteRequest {templateRouteRequest(const char*,T,uintptr_t&) {}}; + bool routeMultiple(size_t,RouteRequest*,unsigned,mach_vm_address_t,size_t){return true;} + void clearError(){} +}; +namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} +struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; +struct {size_t loadIndex=5;}backlight; struct XeHost { bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; - struct XE {void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);}} xe; + struct XE { + void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} + void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} + } xe; void processKernel(KernelPatcher &); + void processKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static void wrapPanelSetDisplay(){}static void wrapFramebufferInit(){} + uintptr_t orgPanelSetDisplay=0,orgFramebufferInit=0;uint8_t *gIOFBVerboseBootPtr=nullptr; }; '''.replace('// DEVICE_ADAPTER',adapter)+body+r''' int main(){KernelPatcher patcher;IOPCIDevice gpu,mei; @@ -84,6 +100,10 @@ assert(events[events.size()-2]==8); const auto profile=XELP::identify(identity,revision,0x9A498086); assert(profile.displayAdmitted() && !profile.admitted()); + // Execute the real outer dispatcher; neither FB hooks nor Accelerator + // rejection may disappear merely because Render admission is closed. + host.processKext(patcher,1,0,0);assert(events.back()==101); + host.processKext(patcher,2,0,0);assert(events.back()==102); } puts("PASS actual host admission: absent/unpublished/unsupported GPUs never enter Xe; published PCI reads precede selected-driver dispatch");} ''' From 3ed8c7aac5f7ad7d2b5ce3a403319a09d2d6dce0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:55:01 +0900 Subject: [PATCH 11/60] Prepare Display13 scanout at the native controller boundary --- WhateverXe/kern_display13_boot.hpp | 11 +++++++++++ WhateverXe/kern_xe.cpp | 28 ++++++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 1 + tests/display13_tc_boot.cpp | 23 ++++++++++++++++++++++- tests/display13_tiling.py | 12 ++++++++++-- 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index 172467ab..b1cca2f9 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -21,6 +21,17 @@ template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout & if(io.readPTE(0x800000+8*i)!=memory.pte(uint64_t(i)*4096))return false; return ddi<3 || tc->unchanged(io); } +// Establish the native linear-GGTT prerequisites only after the controller +// startup contract is ready. +// Ownership/power/link drift and the stolen prefix are checked before mutation. +template AlderLakeN::LinearResult prepareLinear(IO &io,const AlderLakeN::DisplayReadout &boot, + bool admitted,unsigned ddi,const XELPMemory::Layout &memory,const Display13TC::Boot *tc=nullptr) { + using namespace AlderLakeN; + LinearResult r; + if(!admitted)return r; + if(!reservedPrefix(io,boot,true,ddi,memory,tc)){r.status=LinearStatus::StateChanged;return r;} + return reinitializeLinear(io,boot,true,ddi,GOPProtocol::TMDS,tc); +} enum class Status : uint32_t {Unavailable,Complete,Topology,Readback,Timeout,PeerChanged}; struct Result { uint32_t version {1};Status status {Status::Unavailable}; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2d540927..75239b15 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7287,6 +7287,33 @@ bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32 return true; } +bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { + if(!display13BootReady || !xelpDisplay13Device(pci) || !xelpMemoryMatches(pci) || + !(pci->configRead16(4)&2))return false; + auto map=pci->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(!map)return false; + struct IO { + volatile uint8_t *base;uint64_t bytes; + bool readable(uint32_t reg) const {return base && bytes>=4 && reg<=bytes-4;} + uint32_t read(uint32_t reg){return *reinterpret_cast(base+reg);} + uint64_t readPTE(uint32_t reg){return *reinterpret_cast(base+reg);} + void write(uint32_t reg,uint32_t value){*reinterpret_cast(base+reg)=value;} + void delay(){IODelay(100);} + } io{reinterpret_cast(map->getVirtualAddress()),map->getLength()}; + const bool mapped=io.base && io.bytes==0x1000000; + const auto boot=AlderLakeN::readDisplay(io,mapped); + uint32_t trans=UINT32_MAX;AlderLakeN::value(boot,0x60400,trans); + const auto ddi=Display13FBTopology::bootstrapDDI(adlnOutputWiring, + boot.status==AlderLakeN::ReadoutStatus::Complete,boot.poweredPipes,boot.activePipes,trans); + const auto tc=Display13TC::Boot::capture(io,mapped && ddi>=3,ddi,0); + auto result=Display13Boot::prepareLinear(io,boot,mapped,ddi,xelpMemory,ddi>=3?&tc:nullptr); + map->release(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13BootHandoff",&result,sizeof(result)); + SYSLOG("igfx","Display13 native handoff status=%u writes=%u restored=%u", + static_cast(result.status),result.writes,result.restored); + return result.status==AlderLakeN::LinearStatus::Success; +} + bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); if(!pci)return false; @@ -7328,6 +7355,7 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { (provider->getProperty("AAPL,GfxYTile") || provider->setProperty("AAPL,GfxYTile",&enabled,sizeof(enabled))); OSSafeReleaseNULL(yTiling);OSSafeReleaseNULL(mode);OSSafeReleaseNULL(features); if(!featuresSelected)return false; + if(s->adlpFramebufferOnly && !s->prepareDisplay13BootScanout(pci))return false; SYSLOG("igfx","Display13 native controller start enter offline=%u",display13Offline); const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgDisplay13FramebufferStart)(that,provider); SYSLOG("igfx","Display13 native controller start return=%u",started); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 993611b5..946529ca 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -156,6 +156,7 @@ class XE { bool display13DPProbeReady {},display13TCProbeReady {}; bool adlpFramebufferOnly {}; Display13OutputWiring display13BoardWiring(IOService *); + bool prepareDisplay13BootScanout(IOPCIDevice *); bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; Display13OutputWiring adlnOutputWiring; diff --git a/tests/display13_tc_boot.cpp b/tests/display13_tc_boot.cpp index 5d7b3daf..4aefb7aa 100644 --- a/tests/display13_tc_boot.cpp +++ b/tests/display13_tc_boot.cpp @@ -1,4 +1,5 @@ #include "kern_display13_boot.hpp" +#include "kern_display13_fb_memory.hpp" #include #include #include @@ -39,6 +40,16 @@ static IO firmware(unsigned ddi) { {0x70028,0xA0F},{0x70180,0x84000008},{0x70188,120},{0x70190,0x0437077F}}; return io; } +template static void put(void *p,size_t offset,T value) {memcpy(static_cast(p)+offset,&value,sizeof(value));} +static Display13FBMemory::Readout nativeMemory(IO &io,unsigned ddi,const Display13TC::Boot &tc) { + uint8_t controller[0x1C20] {},fb[0x4B90] {}; + put(fb,0x1D0,reinterpret_cast(controller)); + put(controller,0xD5C,uint32_t(1));put(controller,0xD60,reinterpret_cast(fb)); + put(controller,0xDF5,uint8_t(1));put(controller,0xDF4,uint8_t(1));put(controller,0xDF0,uint32_t(1)); + put(controller,0x1A10,uint32_t(1));put(controller,0x1A28,io.memory.base); + put(controller,0x1A30,uint32_t(0x4000000));put(controller,0x1A34,uint32_t(0x4000)); + return Display13FBMemory::inspect(io,controller,sizeof(controller),fb,sizeof(fb),0x10000000,io.memory,true,0,ddi,&tc); +} int main() { unsigned cases=0; for(unsigned ddi=3;ddi<=6;++ddi) { @@ -50,14 +61,24 @@ int main() { assert(Display13Boot::reservedPrefix(io,display,true,ddi,io.memory,&tc)); assert(io.writes.empty()); for(auto reg:original)assert(io.regs[reg.first]==reg.second); + assert(nativeMemory(io,ddi,tc).status==Display13FBMemory::Status::DisplayState); + io.pteReads=0; + auto result=Display13Boot::prepareLinear(io,display,true,ddi,io.memory,&tc); io.pteReads=0; - auto result=reinitializeLinear(io,display,true,ddi,GOPProtocol::TMDS,&tc); assert(result.status==LinearStatus::Success && result.writes==10 && tc.unchanged(io)); for(auto w:io.writes)assert(w.offset==0x42084 || w.offset==0x70028 || w.offset==0x70180 || w.offset==0x70188 || w.offset==0x7018C || w.offset==0x70190 || w.offset==0x701A4 || w.offset==0x7019C); for(auto r:original)if(r.first!=0x70028 && r.first!=0x70180)assert(io.regs[r.first]==r.second); assert(Display13Boot::reservedPrefix(io,readDisplay(io,true),true,ddi,io.memory,&tc)); assert(io.pteReads==io.memory.bytes/4096); + const auto native=nativeMemory(io,ddi,tc); + assert(native.status==Display13FBMemory::Status::Complete && native.verifiedPages==0x4000); + auto rejected=firmware(ddi);rejected.badPTE=0x800010; + auto preparation=Display13Boot::prepareLinear(rejected,readDisplay(rejected,true),true,ddi,rejected.memory,&tc); + assert(preparation.status==LinearStatus::StateChanged && rejected.writes.empty()); + auto denied=firmware(ddi); + preparation=Display13Boot::prepareLinear(denied,readDisplay(denied,true),false,ddi,denied.memory,&tc); + assert(preparation.status==LinearStatus::NotAdmitted && denied.writes.empty() && !denied.pteReads); auto bad=firmware(ddi);bad.badPTE=0x800010; assert(!Display13Boot::reservedPrefix(bad,readDisplay(bad,true),true,ddi,bad.memory,&tc) && bad.pteReads==3); for(unsigned wrong:{0U,1U,2U,7U,8U,UINT32_MAX})assert(!Display13TC::Boot::capture(io,true,wrong,0).valid()); diff --git a/tests/display13_tiling.py b/tests/display13_tiling.py index 4a5a9b36..cda40685 100644 --- a/tests/display13_tiling.py +++ b/tests/display13_tiling.py @@ -44,7 +44,12 @@ }; #define OSDynamicCast(T,p) dynamic_cast(p) #define OSSafeReleaseNULL(p) do{p=nullptr;}while(0) +struct Handoff { + bool adlpFramebufferOnly=true,ready=true;unsigned calls=0; + bool prepareDisplay13BootScanout(IOService *provider){assert(provider);++calls;return ready;} +} handoff; bool prepare(IOService *that,IOService *provider){ + auto s=&handoff;auto pci=provider; PRODUCTION return true; } @@ -54,8 +59,9 @@ prior.items={{"UnrelatedFeature",&untouched},{"YTiling",&disabled}}; if(oldFeatures)that.items["FeatureControl"]=&prior; if(oldKey)provider.items["AAPL,GfxYTile"]=&key; - step=0;failAt=failure;bool ok=prepare(&that,&provider); - if(failure || !workLoop){assert(!ok);return;} + step=0;failAt=failure;handoff.calls=0;bool ok=prepare(&that,&provider); + if(failure || !workLoop){assert(!ok && !handoff.calls);return;} + assert(handoff.calls==1); assert(ok);auto f=dynamic_cast(that.getProperty("FeatureControl"));assert(f); assert(dynamic_cast(f->items.at("YTiling"))->value==1); assert(dynamic_cast(f->items.at("ThreadingMode"))->value==0); @@ -64,6 +70,8 @@ assert(oldKey ? data==&key && data->value==keyValue : data->value==1); } int main(){ + {IOService that,provider;handoff.ready=false;assert(!prepare(&that,&provider));assert(handoff.calls==1);handoff.ready=true;} + for(bool features:{false,true})for(bool key:{false,true})for(uint32_t value:{0U,1U}){ check(features,key,value,0);unsigned operations=step; for(unsigned failure=1;failure<=operations;++failure)check(features,key,value,failure); From e98d3f7e1eba806ce0dbd9e99a2b22458d3efbf5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:15:55 +0900 Subject: [PATCH 12/60] Defer Display13 framebuffer opens until AGDC registration --- WhateverXe/kern_xe.cpp | 62 +++++++++++++++++++ WhateverXe/kern_xe.hpp | 4 ++ tests/display13_agdc.py | 133 ++++++++++++++++++++++++++++++++++++++++ tools/test.py | 2 + 4 files changed, 201 insertions(+) create mode 100644 tests/display13_agdc.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 75239b15..68ca6dd7 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6484,6 +6484,7 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m memcmp(reinterpret_cast(bus+0x335),busBefore,sizeof(busBefore)) || memcmp(reinterpret_cast(bus+0xEA),pinLimit,sizeof(pinLimit)) || memcmp(reinterpret_cast(bus+0x2E0),resetBefore,sizeof(resetBefore)))return false; + if(!processDisplay13FramebufferAGDC(patcher,index,address,size))return false; struct Change {uintptr_t offset;const uint8_t *find,*replace;size_t length;}; const Change changes[] { {0x9A0E8,reinterpret_cast(before),reinterpret_cast(after),sizeof(before)}, @@ -7172,6 +7173,66 @@ static void adlnPublishFrontend(const char *name,const char *phase,uint64_t (&r) char key[64];snprintf(key,sizeof(key),"N97FB%s%u%s",name,static_cast(r[2]),phase); IORegistryEntry::getRegistryRoot()->setProperty(key,r,sizeof(r)); } +bool XE::processDisplay13FramebufferAGDC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + // Both short trampolines end before the donor's RIP-relative counters. + const char *client="__ZN21AppleIntelFramebuffer13newUserClientEP4taskPvjPP12IOUserClient"; + const char *callback="__ZN31AppleIntelFramebufferController12CallBackAGDCE31kAGDCRegisterLinkControlEvent_tmj"; + const uint8_t clientEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56}; + const uint8_t callbackEntry[]={0x55,0x48,0x89,0xe5,0x89,0xf0}; + const uint8_t dispatch[]={0x4c,0x8b,0x87,0xf8,0x1b,0,0,0x48,0x8b,0xbf,0,0x1c,0,0, + 0x48,0x89,0xd6,0x89,0xca,0x89,0xc1,0x5d,0x41,0xff,0xe0}; + if(!display13FramebufferRequested || !display13FBABIReady || size<0x69E60)return false; + const auto c=patcher.solveSymbol(index,client,address,size);patcher.clearError(); + const auto a=patcher.solveSymbol(index,callback,address,size);patcher.clearError(); + if(c!=address+0x273B4 || a!=address+0x69DF2 || + memcmp(reinterpret_cast(c),clientEntry,sizeof(clientEntry)) || + memcmp(reinterpret_cast(a),callbackEntry,sizeof(callbackEntry)) || + memcmp(reinterpret_cast(address+0x69E1E),dispatch,sizeof(dispatch)))return false; + KernelPatcher::RouteRequest routes[] { + {client,wrapDisplay13FBUserClient,orgDisplay13FBUserClient}, + {callback,wrapDisplay13AGDCCallback,orgDisplay13AGDCCallback} + }; + const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13AGDCGuardReady",ready); + return ready; +} + +uint32_t XE::wrapDisplay13FBUserClient(IOService *fb,void *task,void *security,uint32_t type,void **client) { + // Only type0 enters IOFramebuffer::open. Refuse before entering IOG: an + // error from enableController permanently sets IOG's dead flag instead. + // Native controller start publishes FB children before its AGDC nub. + // A later open can retry after both the controller and callback are ready. + if(type==0) { + auto s=callbackXE; + const bool framebuffer=fb && fb->getMetaClass()->getClassSize()>=0x4B90; + auto controller=framebuffer ? + Display13FBMemory::field(fb,0x1D0) : nullptr; + const bool bound=controller && controller==s->adlnTopologyController && + controller->getMetaClass()->getClassSize()>=0x1C20; + const bool started=bound && controller==s->tglObservationController; + const bool registered=bound && Display13FBMemory::field(controller,0x1BF8); + uint64_t record[]={1,bound,started,registered, + bound && Display13FBMemory::field(controller,0x1C00), + framebuffer ? Display13FBMemory::field(fb,0x1DC) : UINT32_MAX}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ClientReadiness",record,sizeof(record)); + if(!started || !registered) { + if(client)*client=nullptr; + return kIOReturnNotReady; + } + } + return FunctionCast(wrapDisplay13FBUserClient,callbackXE->orgDisplay13FBUserClient)(fb,task,security,type,client); +} + +uint32_t XE::wrapDisplay13AGDCCallback(IOService *controller,uint32_t event,unsigned long argument,uint32_t value) { + // Other native notifications can also precede policy registration. Return + // failure for a real notification with no receiver; never synthesize ACK. + // Keep the native event filter, argument conversion and registered path. + const bool notification=event==4 || event==0x80 || (event<=11 && (0x90FU&(1U<getMetaClass()->getClassSize()<0x1C20 || + !Display13FBMemory::field(controller,0x1BF8)))return kIOReturnNotReady; + return FunctionCast(wrapDisplay13AGDCCallback,callbackXE->orgDisplay13AGDCCallback)(controller,event,argument,value); +} + uint32_t XE::wrapDisplay13FBEnable(IOService *fb) { uint64_t r[16] {1,1};adlnFrontendSnapshot(fb,r);adlnPublishFrontend("Enable","Entry",r); const auto result=FunctionCast(wrapDisplay13FBEnable,callbackXE->orgDisplay13FBEnable)(fb); @@ -7316,6 +7377,7 @@ bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); + if(s->tglObservationController==that)s->tglObservationController=nullptr; if(!pci)return false; const auto board=s->display13BoardWiring(provider); if(!Display13FBTopology::supportedWiring(board,s->adlpFramebufferOnly) || diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 946529ca..1df5b5d2 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -215,6 +215,10 @@ class XE { mach_vm_address_t orgDisplay13GMBus {},orgDisplay13GMBusRead {},orgDisplay13GMBusWrite {},adlnGMBusFunction {}; mach_vm_address_t orgDisplay13FBEnable {},orgAlderLakeNConnectionAttr {},orgAlderLakeNPortDDC {},orgDisplay13FBDDC {},orgAlderLakeNComputeLanes {},adlnHDMILaneCaller {}; static uint32_t wrapDisplay13FBEnable(IOService *fb); + bool processDisplay13FramebufferAGDC(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgDisplay13FBUserClient {},orgDisplay13AGDCCallback {}; + static uint32_t wrapDisplay13FBUserClient(IOService *,void *,void *,uint32_t,void **); + static uint32_t wrapDisplay13AGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); static uint32_t wrapAlderLakeNConnectionAttr(IOService *fb,int connection,uint32_t attribute,unsigned long *value); static uint32_t wrapDisplay13FBDDC(IOService *fb,int connection,uint32_t block,uint32_t segment,uint32_t type,uint8_t *data,uint64_t *length); static uint32_t wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,bool force); diff --git a/tests/display13_agdc.py b/tests/display13_agdc.py new file mode 100644 index 00000000..4c097973 --- /dev/null +++ b/tests/display13_agdc.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Production client admission and optional exact native AGDC dispatch replay.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['processDisplay13FramebufferAGDC','wrapDisplay13FBUserClient','wrapDisplay13AGDCCallback'] +functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) +arrays={n:re.search(n+r'\[\]=\{([^}]+)',functions).group(1) for n in ['clientEntry','callbackEntry','dispatch']} +assert 'if(!processDisplay13FramebufferAGDC(patcher,index,address,size))return false;' in s +start=s[s.index('bool XE::wrapDisplay13FramebufferStart('):s.index('void XE::wrapAlderLakeNSetupWorkLoop(')] +invalidate='if(s->tglObservationController==that)s->tglObservationController=nullptr;' +assert start.index(invalidate)tglObservationController=that;') +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(b).hexdigest() in ['c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731','8f83afbe21f216ed18ea798c10d91b7b4fd990b62a5740832c1f90137722c32f'] + for key,offset in [('clientEntry',0x273b4),('callbackEntry',0x69df2),('dispatch',0x69e1e)]: + expected=bytes(int(x,0) for x in arrays[key].split(','));assert b[offset:offset+len(expected)]==expected +code=r''' +#include +#include +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +#define FunctionCast(fn,addr) reinterpret_cast(addr) +#define arrsize(a) (sizeof(a)/sizeof(a[0])) +struct Meta {unsigned size=0x4b90;unsigned getClassSize(){return size;}}; +struct IOService {alignas(16) uint8_t bytes[0x4b90]{};Meta meta;Meta *getMetaClass(){return &meta;}}; +namespace Display13FBMemory {template T field(void *p,unsigned n){T v;memcpy(&v,(uint8_t*)p+n,sizeof(v));return v;}} +template void put(void *p,unsigned n,T v){memcpy((uint8_t*)p+n,&v,sizeof(v));} +struct IORegistryEntry { + void setProperty(const char *,bool){} void setProperty(const char *,void *,size_t){} + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} +}; +struct KernelPatcher { + unsigned routes=0;bool fail=false;uintptr_t delta=0; + struct RouteRequest {template RouteRequest(const char *,F,uintptr_t &) {}}; + template T solveSymbol(unsigned,const char *s,uintptr_t a,size_t){return a+(strstr(s,"newUserClient")?0x273b4:0x69df2)+delta;} + void clearError(){} + bool routeMultipleShort(unsigned,RouteRequest*,unsigned count,uintptr_t,size_t){assert(count==2);++routes;return !fail;} +}; +struct XE { + static XE *callbackXE;bool display13FramebufferRequested=true,display13FBABIReady=true; + IOService *adlnTopologyController=nullptr,*tglObservationController=nullptr; + uintptr_t orgDisplay13FBUserClient=0,orgDisplay13AGDCCallback=0; + bool processDisplay13FramebufferAGDC(KernelPatcher &,size_t,uintptr_t,size_t); + static uint32_t wrapDisplay13FBUserClient(IOService *,void *,void *,uint32_t,void **); + static uint32_t wrapDisplay13AGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); + void restart(IOService *that){auto s=this;INVALIDATE} +}; +XE *XE::callbackXE; +FUNCTIONS +IOService fb,controller,other;unsigned clientCalls=0,callbackCalls=0; +uintptr_t expectedArgument=0;uint32_t expectedEvent=0,expectedValue=0,result=0xe00002bc; +uint32_t receiver(void *context,uintptr_t arg,uint32_t value,uint32_t event){ + assert(!context&&arg==expectedArgument&&value==expectedValue&&event==expectedEvent);++callbackCalls;return result; +} +uint32_t originalClient(IOService *f,void *task,void *security,uint32_t type,void **client){ + assert(f==&fb&&task==(void*)0x1234&&security==(void*)0x5678);++clientCalls; + if(client)*client=&controller;return result+type; +} +uint32_t originalCallback(IOService *c,uint32_t e,unsigned long a,uint32_t v){ + const bool inc=e<=11&&(0x90fU&(1U<(c,0x1bf8); + return fn(Display13FBMemory::field(c,0x1c00),inc?uint32_t(a+1):a,v,e); +} +void reset(XE &s){fb={};controller={};controller.meta.size=0x1c20;clientCalls=0; + s.adlnTopologyController=s.tglObservationController=&controller;put(&fb,0x1d0,&controller);put(&controller,0x1bf8,(uintptr_t)receiver);} +uint32_t open(void **client,unsigned type=0){return XE::wrapDisplay13FBUserClient(&fb,(void*)0x1234,(void*)0x5678,type,client);} +void clients(XE &s){ + for(unsigned bad=0;bad<7;++bad){reset(s); + switch(bad){case 0:put(&controller,0x1bf8,uintptr_t(0));break;case 1:s.tglObservationController=nullptr;break; + case 2:s.adlnTopologyController=&other;break;case 3:fb.meta.size=0x100;break;case 4:controller.meta.size=0x100;break; + case 5:put(&fb,0x1d0,(IOService*)nullptr);break;case 6:s.restart(&controller);break;} + auto before=fb;void *client=(void*)1;assert(open(&client)==kIOReturnNotReady&&!client&&!clientCalls); + assert(!memcmp(&before,&fb,sizeof(fb))); + } + reset(s);put(&controller,0x1bf8,uintptr_t(0));void *client=(void*)1; + assert(open(&client)==kIOReturnNotReady&&!client&&!clientCalls); + put(&controller,0x1bf8,(uintptr_t)receiver); + assert(open(&client)==result&&client==&controller&&clientCalls==1); + put(&controller,0x1bf8,uintptr_t(0));s.tglObservationController=nullptr; + for(unsigned type:{1U,2U,~0U})assert(open(&client,type)==uint32_t(result+type)); +} +void events(){ + for(bool registered:{false,true})for(unsigned event=0;event<256;++event){ + put(&controller,0x1bf8,registered?(uintptr_t)receiver:0);put(&controller,0x1c00,(void*)nullptr); + const uintptr_t arg=0x12345678fffffff0;const bool inc=event<=11&&(0x90fU&(1U<b(0x150000); + memcpy(b.data()+0x273b4,clientEntry,sizeof(clientEntry));memcpy(b.data()+0x69df2,callbackEntry,sizeof(callbackEntry));memcpy(b.data()+0x69e1e,dispatch,sizeof(dispatch)); + for(unsigned bad=0;bad<8;++bad){KernelPatcher p;auto copy=b;s.display13FramebufferRequested=s.display13FBABIReady=true;size_t size=b.size(); + switch(bad){case 0:s.display13FramebufferRequested=false;break;case 1:s.display13FBABIReady=false;break;case 2:size=0x69e5f;break; + case 3:p.delta=1;break;case 4:copy[0x273b4]^=1;break;case 5:copy[0x69df2]^=1;break;case 6:copy[0x69e21]^=1;break;case 7:p.fail=true;break;} + assert(!s.processDisplay13FramebufferAGDC(p,1,(uintptr_t)copy.data(),size));assert(p.routes==(bad==7?1U:0U)); + } + s.display13FramebufferRequested=s.display13FBABIReady=true;KernelPatcher p; + assert(s.processDisplay13FramebufferAGDC(p,1,(uintptr_t)b.data(),b.size())&&p.routes==1); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);b.assign(std::istreambuf_iterator(f),{}); + size_t length=((b.size()>0x150000?b.size():0x150000)+4095)&~size_t(4095); + auto mem=(uint8_t*)mmap(nullptr,length,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);assert(mem!=MAP_FAILED);memcpy(mem,b.data(),b.size()); + uintptr_t baseVtable[256]{};baseVtable[0x788/8]=(uintptr_t)originalClient;put(mem,0xc8088,(uintptr_t)baseVtable); + assert(!mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)); + s.orgDisplay13FBUserClient=(uintptr_t)(mem+0x273b4);s.orgDisplay13AGDCCallback=(uintptr_t)(mem+0x69df2); + clients(s);events(); + reset(s);put(&controller,0x1bf8,uintptr_t(0));assert(open(&client)==kIOReturnNotReady); + using Register=uint32_t(*)(void*,void*);void *request[]={nullptr,(void*)receiver}; + assert(!reinterpret_cast(mem+0x69dce)(&controller,request)); + assert(open(&client)==result&&client==&controller);munmap(mem,length); + puts("PASS exact donor client tail call, registration and 512 guarded/native event cases"); + } + puts("PASS production AGDC admission, retry without FB mutation, passthrough, restart invalidation and installer refusals"); +} +'''.replace('FUNCTIONS',functions).replace('INVALIDATE',invalidate) +for old,key in [('CLIENT_ENTRY','clientEntry'),('CALLBACK_ENTRY','callbackEntry'),('DISPATCH','dispatch')]:code=code.replace(old,arrays[key]) +with tempfile.TemporaryDirectory(prefix='display13-agdc-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tools/test.py b/tools/test.py index 06a7d4cc..3facf7d3 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,7 +8,9 @@ python_tests=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] +python_tests += ['display13_agdc'] 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 += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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,[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 ec80f87b31a8baa7f51ee8859fa37876957436a3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:22:06 +0900 Subject: [PATCH 13/60] Mask excluded Display 13 north HPD sources before IRQ admission --- WhateverXe/kern_display13_fb_irq.hpp | 18 ++++++++++++++++-- tests/display13_fb_irq.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_display13_fb_irq.hpp b/WhateverXe/kern_display13_fb_irq.hpp index 9c1065ee..9155d317 100644 --- a/WhateverXe/kern_display13_fb_irq.hpp +++ b/WhateverXe/kern_display13_fb_irq.hpp @@ -82,6 +82,13 @@ inline uint32_t nativePipe(uint32_t raw,unsigned pipe) { return result; } inline uint32_t pchOwned(const State &s) {return ((s.ports&7)<<16)|((s.ports>>3)<<24);} +// Linux GEN11_DE_HPD: TC1..6 at bits16..21, TBT1..6 at bits0..5. +// Apply the same connector exclusion to both PCH and north HPD banks. +// Reference: samranahmad09/WhateverXe-RaptorLakeP, 2195924, this header. +constexpr uint32_t HPDBank=0x44478; +constexpr uint32_t hpdExcluded(uint32_t excludedPCH) { + const uint32_t ports=(excludedPCH>>24)&0x3FU;return ports|(ports<<16); +} inline uint32_t ddiMask(const State &s,uint32_t value) { uint32_t m=0;for(unsigned p=0;p<3;++p)if(s.ports&(1U< 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 io.write(0xC400C,pchIER&~excludedPCH); if((io.read(0xC400C)&excludedPCH) || (~io.read(0xC4004)&excludedPCH)) return fail(io,s,Status::Readback); + // Do not ACK pending status or alter selected/unrelated bits. + io.write(HPDBank-4,io.read(HPDBank-4)|excludedHPD); + io.write(HPDBank+4,io.read(HPDBank+4)&~excludedHPD); + if((io.read(HPDBank+4)&excludedHPD) || (~io.read(HPDBank-4)&excludedHPD)) + return fail(io,s,Status::Readback); } s.excludedPCH=excludedPCH; for(unsigned p=0;p<3;++p)if(configured&(1U<{Master});} + for(uint32_t bits:{1U,1U<<16,1U<<30}) { + IO io;State state;io.r[HPDBank+4]=0x40004|bits;io.r[HPDBank-4]=0; + assert(enable(io,state,0x3e000000,NativeGT,1,8)==Status::ForeignIRQ && io.writes==std::vector{Master}); + assert(state.failureRegister==HPDBank+4 && state.failureBits==bits); + } + for(auto reg:{HPDBank-4,HPDBank+4}) { + IO io;State state;io.r[HPDBank+4]=0x40004;io.r[HPDBank-4]=0;io.rejectRegister=reg; + assert(enable(io,state,0x3e000000,NativeGT,1,8)==Status::Readback && !(io.r[Master]&Enable)); + } // ADL-P/N TC legacy HPD is PCH bit24+tc and a separate control bank. // Foreign masked statuses must survive both enable and collect. for(unsigned tc=0;tc<4;++tc)for(unsigned pulse=0;pulse<4;++pulse) { From 88a77d02fc1a56d72b0ece4e1663f530ba6087a1 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:25:55 +0900 Subject: [PATCH 14/60] Derive Combo HPD exclusions from validated native connector policy --- WhateverXe/kern_display13_fb_irq.hpp | 4 ++-- WhateverXe/kern_display13_fb_topology.hpp | 12 +++++++----- WhateverXe/kern_xe.cpp | 4 ++-- tests/adlp_resources.cpp | 2 +- tests/display13_fb_irq.cpp | 16 ++++++++++++++++ tests/display13_fb_topology.cpp | 19 ++++++++++++------- 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/WhateverXe/kern_display13_fb_irq.hpp b/WhateverXe/kern_display13_fb_irq.hpp index 9155d317..4ab13de4 100644 --- a/WhateverXe/kern_display13_fb_irq.hpp +++ b/WhateverXe/kern_display13_fb_irq.hpp @@ -36,7 +36,7 @@ struct HandlerScopes { }; constexpr uint32_t Driver=0x45404, Master=0x44200, GfxMaster=0x190010; constexpr uint32_t Enable=1U<<31, PipeOwned=0x8060E00D, PCHOwned=3U<<16; -constexpr uint32_t PCHTypeC=0x3F000000; +constexpr uint32_t PCHTypeC=0x3F000000, PCHCombo=7U<<16; constexpr uint32_t TCControl=0xC4034; constexpr uint32_t DDIControl=0xC4030, DDIStatus=0x33, DDIEnable=0x88; constexpr uint32_t PipeIIR(unsigned pipe) {return 0x44408+16*pipe;} @@ -128,7 +128,7 @@ template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t 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((excludedPCH&pchOwned(s)) || (excludedPCH&~PCHTypeC) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology); + 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; // Native repeated enable is idempotent. Do not drop a pending flip/DSB // completion or undo hwSetInterruptForTransaction's VBL mask. diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 3584c66d..e9649474 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -101,18 +101,20 @@ inline bool skipDPLanes(const void *platform,const void *config,const Display13O return XELPDonor::skipHDMIDPLanes(selected(platform,config,w) && w.matchesHDMI(static_cast(config)),exactCaller,native,available,lanes); } // ICP/TGP SDE bits24..29 are TC1..6 hotplug, not legacy GMBUS bit24. -// Authorize masking only from the stable native connector policy: no TC -// record can be enabled or have panel/connection flags on a Combo route. -inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const Display13OutputWiring &w) { +// Combo A..C hotplug uses bits16..18. Authorize exclusions only after +// every unselected native record is disabled and has no connection flags. +// Other PCH sources (including DDI D) remain outside this policy. +inline uint32_t excludedHotplug(const void *platform,uint32_t bytes,const Display13OutputWiring &w) { if(!platform || bytes(platform); if(p[0x12]!=Ports)return 0; for(unsigned d=0;d(p+0x20+24*d); - if(config[0]!=d || (d>=3 && !(w.selected()&(1U<>3)<<24); + return (0x3F000000 & ~((w.selected()>>3)<<24)) | + ((7U & ~w.selected())<<16); } } #endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 68ca6dd7..b52807ab 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5555,7 +5555,7 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { if(!callbackXE->display13FBIRQReady || !adlnIRQOwner(controller))return kIOReturnNotReady; auto &c=display13FBIRQ;uint64_t record[32] {},check[42] {3};Display13FBIRQIO io {c.base}; const auto excluded=controller==callbackXE->adlnTopologyController ? - Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; + Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; const auto saved=IOSimpleLockLockDisableInterrupt(c.lock); // Preserve enable evidence even when start rejects its unpublished // controller. Inspect only pipe banks already qualified as powered. @@ -5627,7 +5627,7 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec 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)) { const auto excluded=controller==callbackXE->adlnTopologyController ? - Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; + Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); } if(!c.state.faulted)c.status=Display13FBIRQ::vblank(io,c.state,pipe,enabled); diff --git a/tests/adlp_resources.cpp b/tests/adlp_resources.cpp index 197a8c4f..c3873899 100644 --- a/tests/adlp_resources.cpp +++ b/tests/adlp_resources.cpp @@ -47,7 +47,7 @@ int main() { const auto c=reinterpret_cast(out+0x20+24*d); assert(v.matchesHDMI(c) && c[3]==1 && c[1]==d+6); assert(Display13FBTopology::boundPorts(out,v).selected==(1U<(1),w)); assert(!memcmp(out.data(),in.data(),0x20) && !memcmp(out.data()+0xF8,in.data()+0xF8,Bytes-0xF8)); auto malformed=w;malformed.ddc[0]=4;auto keep=out; assert(!apply(out.data(),Bytes,in.data(),malformed) && !memcmp(out.data(),keep.data(),Bytes)); - if(mask==3){auto broken=out;broken.config(0)[5]=0x18;assert(!excludedTypeC(broken.data(),Bytes,w));} + if(mask==3){auto broken=out;broken.config(0)[5]=0x18;assert(!excludedHotplug(broken.data(),Bytes,w));} } // All fixed Combo HDMI/DP assignments compose CPU topology/resources. // DP runtime/boot admission stays closed until the hardware hooks exist. @@ -108,7 +113,7 @@ int main() { for(unsigned mode=0;mode<8;++mode) assert(bootDDI(w,0x80000000|((d+1)<<27)|(mode<<24))==(hdmi&&mode<=1?d:~0U)); } - assert(excludedTypeC(out.data(),Bytes,w)==0x3f000000); + assert(excludedHotplug(out.data(),Bytes,w)==(0x3f000000|((7U&~w.selected())<<16))); auto reloaded=w;reloaded=wiring;assert(!reloaded.dp && reloaded.hdmi==2); auto bad=w;bad.dp|=bad.hdmi; // Empty HDMI in DP-only: inject explicit overlap. if(!bad.hdmi){bad.hdmi=bad.dp;for(unsigned d=0;d<2;++d)if(bad.hdmi&(1U< Date: Sat, 24 Oct 2026 22:58:18 +0900 Subject: [PATCH 15/60] Connect ADL-P fuse resources to the shared Display13 render path --- WhateverXe/kern_alderlake_n.hpp | 8 +++-- WhateverXe/kern_xe.cpp | 31 ++++++++++------- WhateverXe/kern_xe.hpp | 1 + tests/adlp_render.py | 54 ++++++++++++++++++++++++++++++ tests/alderlake_n.cpp | 14 ++++++++ tests/boot_arguments.py | 6 ++-- tests/display_backend_dispatch.py | 14 ++++++-- tests/provider_binding_fixture.cpp | 2 +- tools/test.py | 2 +- 9 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 tests/adlp_render.py diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index b547f06c..fbf83a5c 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -56,7 +56,8 @@ static_assert(sizeof(GTReadout)==60*4,"AlderLakeN GT readout layout"); // Linux v6.12 gen12 forcewake domains: fuses and BCS are GT, RCS is render. // Only acquire an unowned kernel bit0. Never reset domains or alter engines. -template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullptr) { +template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullptr, + bool (*decode)(const uint32_t *,Resources &)=nullptr) { GTReadout out; if(!admitted)return out; constexpr uint32_t request[] {0xA188,0xA278},ack[] {0x130044,0xD84}; @@ -96,8 +97,9 @@ template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullp if(out.fuses[i]==UINT32_MAX)out.status=GTStatus::Unavailable; } if(out.status==GTStatus::Complete && - (!resources(out.fuses[0],out.fuses[1],out.fuses[2],out.fuses[3],out.resources) || - out.fuses[4]!=0x000E00FE))out.status=GTStatus::ProfileMismatch; + !(decode ? decode(out.fuses,out.resources) : + (resources(out.fuses[0],out.fuses[1],out.fuses[2],out.fuses[3],out.resources) && + out.fuses[4]==0x000E00FE)))out.status=GTStatus::ProfileMismatch; if(edram) { *edram=io.readable(0x120010) ? io.read(0x120010) : UINT32_MAX; if(*edram==UINT32_MAX)out.status=GTStatus::Unavailable; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index b52807ab..e2128942 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -213,8 +213,9 @@ static bool xelpSelectedDevice(IOPCIDevice *pci) { return pci && pci==xelpSelectedGPU && xelpSelectedProfile.displayAdmitted() && XELP::same(xelpSelectedProfile,XELP::deviceProfile(pci)); } -static bool xelpAlderLakeNDevice(IOPCIDevice *pci) { - return xelpSelectedProfile.family==XELP::Family::AlderLakeN && xelpSelectedDevice(pci); +static bool xelpDisplay13RenderDevice(IOPCIDevice *pci) { + return (xelpSelectedProfile.family==XELP::Family::AlderLakeN || + xelpSelectedProfile.framebufferOnlyADLP()) && xelpSelectedDevice(pci); } static bool xelpDisplay13Device(IOPCIDevice *pci) { return (XELP::displayAdapter(xelpSelectedProfile.family)==XELP::DisplayAdapter::ADLN || xelpSelectedProfile.framebufferOnlyADLP()) && @@ -3749,16 +3750,20 @@ void XE::selectProfile(const XELP::Profile &profile) { adlnNativeRCSProfileRequested=false; adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN && profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; - adlnDetected=adlnRequested; modDVMTCalcFix.available=profile.displayAdmitted(); const bool nativeOS=XELP::kernelOS(version)!=XELP::OS::Unknown && XELP::kernelOS(version)!=XELP::OS::Donor; adlpFramebufferOnly=profile.framebufferOnlyADLP() && nativeOS; + // Preserve the accepted Display13/TC handoff when enabling Render. + // noaccel still selects the exact FB-only rejection path. + adlpRenderRequested=adlpFramebufferOnly && !checkKernelArgument("-wex-noaccel"); + adlnRequested=adlnRequested || adlpRenderRequested; + adlnDetected=adlnRequested; display13FramebufferRequested=profile.displayAdmitted() && profile.display==XELP::Display::XeLPD && (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpFramebufferOnly) && nativeOS; display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); display13Offline=display13FramebufferRequested && checkKernelArgument("-wex-display13-offline"); if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb; - if(adlpFramebufferOnly) { + if(adlpFramebufferOnly && !adlpRenderRequested) { tglRequested=tglAcceleratorRequested=false;currentGraphics=&kextIntelTGL;return; } if(adlnRequested) { @@ -3805,7 +3810,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); const auto revision = pci->configRead8(8); - const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci) && + const bool renderRequested = adlnRequested && adlnKernelReady && xelpDisplay13RenderDevice(pci) && !checkKernelArgument("-wex-noaccel"); const bool displayRequested = display13FramebufferRequested && xelpDisplay13Device(pci); const bool admitted = renderRequested || displayRequested; @@ -3990,7 +3995,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if (gtTest) { uint32_t edram=UINT32_MAX; auto gt=AlderLakeN::readGT(io,barReady && xelpMemoryMatches(pci), - adlnNativeRequested ? &edram : nullptr); + adlnNativeRequested ? &edram : nullptr,adlpRenderRequested ? XELP::decodeADLPGT : nullptr); if (adlnNativeRequested) { adlnNativeSubslices=gt.resources.nativeSubslices; adlnL3Banks=gt.resources.l3Banks; adlnNativeEUs=gt.resources.eus; renderReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1); @@ -4321,7 +4326,7 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add // Keep both adapters outside the large Display12 framebuffer patch frame. if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) return processDisplay13Framebuffer(patcher,index,address,size); - if(adlpFramebufferOnly) { + if(adlpFramebufferOnly && !adlpRenderRequested) { if(index==kextIntelTGL.loadIndex)return processADLPLoadOnly(patcher,index,address,size); return false; } @@ -10019,7 +10024,7 @@ 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 = (xeProfile.gt==XELP::GT::ADLNSmall || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : + const auto urbReplacement = (adlnDetected || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : (xeProfile.gt==XELP::GT::TGLG4 ? urbReplaceG4 : urbReplace); auto gpuInfo=patcher.solveSymbol(index, "__ZN16IntelAccelerator10getGPUInfoEv",address,size); @@ -12424,7 +12429,7 @@ bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { // No parent/native start, private-object reads, engine work or fake success. auto pci=OSDynamicCast(IOPCIDevice,provider); const bool admitted=callbackXE->tglAcceleratorReady && pci && - xelpAlderLakeNDevice(pci); + xelpDisplay13RenderDevice(pci); IORegistryEntry::getRegistryRoot()->setProperty("N97LoadOnlyStartBlocked",true); IORegistryEntry::getRegistryRoot()->setProperty("N97LoadOnlyProviderReady",admitted); if (admitted && callbackXE->adlnParentBoundaryReady && checkKernelArgument("-wex-adln-parent-entry")) @@ -12435,7 +12440,7 @@ bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { if (!callbackXE->tglAcceleratorReady) return false; if (callbackXE->adlnDetected) { auto pci=OSDynamicCast(IOPCIDevice,provider); - if (!callbackXE->adlnNativeReady || !pci || !xelpAlderLakeNDevice(pci)) return false; + if (!callbackXE->adlnNativeReady || !pci || !xelpDisplay13RenderDevice(pci)) return false; // Standalone startup never calls framebuffer start, which normally // exposes these retained IRQ/submission records. Publish the same // passive serializer here; it makes no driver calls or GPU reads. @@ -12547,7 +12552,7 @@ bool XE::startAlderLakeNParentBoundary(IOService *that, IOService *provider) { bool XE::wrapAlderLakeNConfigureDevice(IOService *that, IOPCIDevice *provider) { auto root=IORegistryEntry::getRegistryRoot(); const bool admitted=callbackXE->adlnParentBoundaryReady && provider && - xelpAlderLakeNDevice(provider); + xelpDisplay13RenderDevice(provider); root->setProperty("N97ConfigureDeviceEntered",admitted); if (admitted) { const auto bytes=reinterpret_cast(that); @@ -12820,7 +12825,7 @@ IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SIn if (callbackXE->adlnDetected && checkKernelArgument("-wex-adln-load-only")) { auto root=IORegistryEntry::getRegistryRoot(); const bool admitted=callbackXE->tglAcceleratorReady && pci && - xelpAlderLakeNDevice(pci); + xelpDisplay13RenderDevice(pci); root->setProperty("N97LoadOnlyProbeEntered",admitted); if (!admitted) return nullptr; // Qualify load/routing before executing the donor probe itself. @@ -12930,7 +12935,7 @@ bool XE::wrapAlderLakeNNativeConfigure(IOService *that, IOPCIDevice *provider) { // retain, factory replacement, or deliberate failure on the normal path. auto root=IORegistryEntry::getRegistryRoot(); root->setProperty("N97NativeConfigureEntered",true); - if (!provider || !xelpAlderLakeNDevice(provider)) return false; + if (!provider || !xelpDisplay13RenderDevice(provider)) return false; auto bar=provider->getDeviceMemoryWithRegister(0x10); auto map=bar && bar->getLength()==0x1000000 ? bar->map() : nullptr; if (!map) return false; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 1df5b5d2..66c86d28 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -155,6 +155,7 @@ class XE { mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; bool display13DPProbeReady {},display13TCProbeReady {}; bool adlpFramebufferOnly {}; + bool adlpRenderRequested {}; Display13OutputWiring display13BoardWiring(IOService *); bool prepareDisplay13BootScanout(IOPCIDevice *); bool xelpBindProbe(void *controller); diff --git a/tests/adlp_render.py b/tests/adlp_render.py new file mode 100644 index 00000000..02d71e32 --- /dev/null +++ b/tests/adlp_render.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Run production profile selection: ADL-P Render must preserve Display13.""" +from pathlib import Path +import re, subprocess, tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +f=re.search(r'void XE::selectProfile\(.*?\n\}',s,re.S).group() +code=r''' +#include "kern_xelp_platform.hpp" +#include "kern_xelp_os.hpp" +#include +#include +#include +const char *version="Darwin Kernel Version 20.6.0: Thu Jul 6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64"; +bool noaccel=false; +bool checkKernelArgument(const char *s){return noaccel && !strcmp(s,"-wex-noaccel");} +int kextIntelTGL,kextIntelTGLFb; +bool adlnNativeRCSProfileRequested=false,display13TraceEnabled=false,display13Offline=false; +struct XE { + XELP::Profile xeProfile; + bool display12SmallGTDetected=false,adlsDisplaySelected=false; + bool adlnRequested=false,adlnDetected=false,adlnKernelReady=false,adlnNativeRequested=false; + bool adlpFramebufferOnly=false,adlpRenderRequested=false,display13FramebufferRequested=false; + bool tglRequested=false,tglAcceleratorRequested=false,tglInactivePorts=false,tglPG1Poll=false; + bool tglClockOrderRequested=false,tglTrainingLeaseRequested=false,tglProgramEnabled=false; + int *currentFramebuffer=nullptr,*currentGraphics=nullptr; + struct {bool available=false;}modDVMTCalcFix; + void selectProfile(const XELP::Profile &); +}; +FUNCTION +int main(){ + for(unsigned which=0;which<3;++which)for(unsigned rev=0;rev<256;++rev)for(unsigned off=0;off<2;++off) { + noaccel=off;XE x; + const auto p=which==0?XELP::identify(0x46A68086,rev,0x9A498086): + which==1?XELP::identify(0x46D18086,rev):XELP::identify(0x9A498086,rev); + x.selectProfile(p); + assert(XELP::same(x.xeProfile,p)); + assert(x.currentFramebuffer==&kextIntelTGLFb && x.currentGraphics==&kextIntelTGL); + assert(x.display13FramebufferRequested==(which!=2)); + assert(x.adlpFramebufferOnly==(which==0)); + assert(x.adlpRenderRequested==(which==0&&!off)); + assert(x.adlnDetected==(which==1 || (which==0&&!off))); + assert(x.tglAcceleratorRequested==!off); + assert(adlnNativeRCSProfileRequested==(which==1 || (which==0&&!off))); + } + noaccel=false;XE unknown;unknown.selectProfile(XELP::identify(0x46A68086,12)); + assert(!unknown.adlpRenderRequested && !unknown.adlnDetected && !unknown.tglAcceleratorRequested); + puts("PASS 1536 production profile selections: ADL-P Render/noaccel, ADL-N and TGL; physical/display identities preserved"); +} +'''.replace('FUNCTION',f) +with tempfile.TemporaryDirectory(prefix='wex-adlp-render-') 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/alderlake_n.cpp b/tests/alderlake_n.cpp index 763a7fa2..1942776f 100644 --- a/tests/alderlake_n.cpp +++ b/tests/alderlake_n.cpp @@ -176,6 +176,20 @@ int main() { assert(t8Ready.resources.slices==1 && t8Ready.resources.dss==1 && t8Ready.resources.nativeSubslices==2); assert(t8Ready.resources.eus==16 && t8Ready.resources.l3Banks==4 && t8Ready.resources.urbKiB==256); assert(t8Ready.fuses[4]==0xe00fe && !(t8.regs[0x130044]&1) && !(t8.regs[0xD84]&1)); + // ADL-P shares forcewake/readout, not the small-GT/media capacity rule. + for(unsigned failure=0;failure<5;++failure) { + auto p=firmwareGT();p.regs[0x913C]=0x3f;p.regs[0x9134]=0;p.regs[0x9118]=0;p.regs[0x9140]=0xe00fa; + if(failure==1)p.failGT=true; + if(failure==2)p.failReleaseGT=true; + if(failure==3)p.regs[0x913C]=UINT32_MAX; + if(failure==4)p.regs[0x9140]=0xe00ff; + auto a=AlderLakeN::readGT(p,true,nullptr,XELP::decodeADLPGT); + using S=AlderLakeN::GTStatus; + const S expected[]={S::Complete,S::AckTimeout,S::ReleaseFailed,S::Unavailable,S::ProfileMismatch}; + assert(a.status==expected[failure]); + if(!failure)assert(a.resources.eus==96 && a.resources.nativeSubslices==12 && + a.resources.l3Banks==8 && a.resources.urbKiB==512 && a.releasedMask==3); + } auto excludedGT=firmwareGT();assert(AlderLakeN::readGT(excludedGT,false).status==AlderLakeN::GTStatus::NotAdmitted && excludedGT.reads.empty()); auto busyGT=firmwareGT();busyGT.regs[0xA188]=1; assert(AlderLakeN::readGT(busyGT,true).status==AlderLakeN::GTStatus::ExistingOwner && busyGT.writes.empty()); diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 34a46cd4..9d2dbf2f 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -65,7 +65,7 @@ struct XE { XELP::Profile xeProfile; bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; - bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpFramebufferOnly=false; + bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpFramebufferOnly=false, adlpRenderRequested=false; bool display12SmallGTReady=false, adlsDisplayHooksReady=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; @@ -106,7 +106,7 @@ assert(adlnNativeRCSProfileRequested && xe.tglAcceleratorRequested); assert(xe.currentGraphics==&kextIntelTGL && xe.currentFramebuffer==&kextIntelTGLFb); } - xe=boot({"-wex-display13-offline"},0x46A68086U,0x9A498086U); + xe=boot({"-wex-display13-offline","-wex-noaccel"},0x46A68086U,0x9A498086U); assert(display13Offline && xe.adlpFramebufferOnly && !xe.tglAcceleratorRequested); xe=boot({"-wex-display13-offline"},0x9A498086U); assert(!display13Offline); @@ -175,7 +175,7 @@ assert(xe.adlnRequested && xe.adlnNativeRequested && xe.xeProfile.identity==0x12348086); xe=boot({},0x12348086,0x9A498086);assert(xe.tglRequested); xe=XE{};xe.selectProfile(XELP::identify(0x46A68086,12,0x9A498086)); - assert(xe.adlpFramebufferOnly && xe.display13FramebufferRequested && !xe.tglAcceleratorRequested && !xe.adlnRequested && !xe.tglRequested); + assert(xe.adlpFramebufferOnly && xe.adlpRenderRequested && xe.display13FramebufferRequested && xe.tglAcceleratorRequested && xe.adlnRequested && xe.tglRequested); assert(xe.xeProfile.family==XELP::Family::AlderLakeP && xe.xeProfile.gt==XELP::GT::Unknown); xe=boot({},0x46A68086);assert(!xe.tglRequested && !xe.adlnRequested); for(uint32_t pci:{0x46A68086U,0x46808086U,0xA7A08086U}) { diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index 1d032a7a..70eecb33 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -26,7 +26,7 @@ def expression(name): bool loadOnly=false; bool checkKernelArgument(const char *arg) {assert(!strcmp(arg,"-wex-adln-load-only"));return loadOnly;} struct XE { - bool adlpFramebufferOnly=false; + bool adlpFramebufferOnly=false,adlpRenderRequested=false; bool display13FramebufferRequested=false,adlnRequested=false,adlnDetected=false; bool adlnNativeRequested=false,adlnNativeReady=false,display13BootReady=false,tglActive=false; Kext *currentFramebuffer=nullptr,*currentGraphics=nullptr; @@ -72,7 +72,7 @@ def expression(name): struct Aperture {uint64_t length;uint64_t getLength(){return length;}}bar; struct {AlderLakeN::GTStatus status;}gt; bool adlnRequested=true,adlnKernelReady=true; - const auto xelpAlderLakeNDevice=[](PCI*){return true;}; + const auto xelpDisplay13RenderDevice=[](PCI*){return true;}; for(unsigned mask=0;mask<8192;++mask) { gt.status=(mask&1)?AlderLakeN::GTStatus::Complete:AlderLakeN::GTStatus::Failed; unsigned edram=(mask&2)?0:1; @@ -100,6 +100,16 @@ def expression(name): assert(x.call==(module==1?XE::Display13:module==2?XE::Rejected:XE::None));assert(result==(module!=3)); } + for(bool ready:{false,true})for(unsigned module=1;module<=3;++module) { + XE x;KernelPatcher p;Kext fb{1},graphics{2}; + x.adlpFramebufferOnly=x.adlpRenderRequested=x.display13FramebufferRequested=true; + x.adlnRequested=x.adlnDetected=x.adlnNativeRequested=true;x.adlnNativeReady=x.tglActive=ready; + x.currentFramebuffer=&fb;x.currentGraphics=&graphics; + const bool result=x.dispatch(p,module,0,0); + assert(x.call==(module==1?XE::Display13:module==2?(ready?XE::Render:XE::Rejected):XE::None)); + assert(result==(module!=3)); + } + certificates(); Kext fb{1},graphics{2};KernelPatcher patcher;unsigned cases=0; // Removing/rejecting Render must not redirect a selected Display13 kext diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 9b488cad..301073af 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -75,7 +75,7 @@ int main(int argc,char **argv) { XELP::ProbeConfig config;config.profile=XELP::deviceProfile(&pci); xelpSelectedGPU=&pci;xelpSelectedProfile=config.profile; assert(xelpSelectedDevice(&pci)==config.profile.displayAdmitted()); - assert(xelpAlderLakeNDevice(&pci)==(adln && !rejected)); + assert(xelpDisplay13RenderDevice(&pci)==((adln || adlp) && !rejected)); assert(xelpDisplay13Device(&pci)==((adln || adlp) && !rejected)); IOPCIDevice foreignPCI;foreignPCI.identity=pci.identity;foreignPCI.revision=pci.revision;foreignPCI.property=pci.property; assert(!xelpSelectedDevice(&foreignPCI)); diff --git a/tools/test.py b/tools/test.py index 3facf7d3..14a25b2f 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_agdc'] From d24059919e5297151db78a5455879c65d3312825 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:18:02 +0900 Subject: [PATCH 16/60] Select Display13 fixed HDMI routes from firmware topology --- WhateverXe/kern_display13_fb_topology.hpp | 6 ++--- WhateverXe/kern_xe.cpp | 5 ++-- tests/adlp_fb_candidate.py | 31 +++++++++++++++++++---- tests/display13_fb_topology.cpp | 17 ++++++++++++- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index e9649474..7b49ed26 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -16,9 +16,9 @@ inline bool adlnWiring(const Display13OutputWiring &w) {return selectedWiring(w) // 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) { - // First ADL-P candidate selects the measured central TC1 only. Extend - // this board policy after native FB handoff is observed on hardware. - return !w.dp && (adlnWiring(w) || (fixedTC && w.described() && w.hdmi==8 && w.ddc[3]==9)); + // Fixed HDMI is composable across the physical Combo A/B and TC1..4. + // TC provider capability, legacy mode and clock hooks are checked separately. + return !w.dp && w.described() && !(w.selected()&~(fixedTC ? Display13OutputWiring::Physical : 3U)); } inline bool multiOutputWiring(const Display13OutputWiring &w) { return selectedWiring(w) && (w.selected()&(w.selected()-1)); diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e2128942..9343d4c5 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3797,9 +3797,10 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { if(!property)property=provider->getProperty("tgl-hdmi-wiring"); if(adlpFramebufferOnly) { // VBT remains the authority for pin/transport. No Combo property is - // silently interpreted as a TC policy for this single-TC1 candidate. + // silently interpreted as a TC policy. Select every supported fixed HDMI + // route from firmware; connection state is checked by the provider. if(property)return {}; - return XELP::VBT::display13HDMI(firmwarePorts,8); + return XELP::VBT::display13HDMI(firmwarePorts,Display13OutputWiring::Physical); } auto wiring=OSDynamicCast(OSData,property); return XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index ffb0e183..37336062 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Replay production board selection and WEX/provider binding for the first TC1 candidate.""" +"""Replay production board selection and WEX/provider binding for firmware-selected fixed HDMI.""" from pathlib import Path import re,subprocess,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() @@ -35,10 +35,10 @@ int main(){ 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==8 && w.ddc[3]==9 && !w.dp); + 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)); x.adlnOutputWiring=w;assert(x.xelpBindProbe((void*)1)); - assert(seen.valid() && !seen.combo && seen.comboBackend==3 && seen.legacy==8 && seen.legacyBackend==0x78); + assert(seen.valid() && !seen.combo && seen.comboBackend==3 && seen.legacy==0x18 && seen.legacyBackend==0x78); assert(seen.profile.family==XELP::Family::AlderLakeP && !seen.profile.admitted()); for(unsigned f=0;f<5;++f){XE bad=x; if(f==0)bad.display13TCProbeReady=false;if(f==1)bad.display13FBTCHooksReady=false; @@ -47,8 +47,29 @@ const auto before=calls;assert(!bad.xelpBindProbe((void*)1) && calls==before); } OSData property;pci.property=&property;assert(!x.display13BoardWiring(&pci).described());pci.property=nullptr; - x.firmwarePorts.port[3].type=4;assert(!x.display13BoardWiring(&pci).described()); - puts("PASS production ADL-P candidate: VBT TC1/pin9 -> shared board policy -> capability/hook-gated provider binding; GT closed"); + x.firmwarePorts.port[3].type=4; + assert(x.display13BoardWiring(&pci).hdmi==0x10); // Exclude DP without dropping unrelated HDMI. + x.firmwarePorts.port[4].type=4;assert(!x.display13BoardWiring(&pci).described()); + unsigned cases=0; + for(unsigned mask=1;mask<128;++mask)if(!(mask&~Display13OutputWiring::Physical)) { + XE each;each.firmwarePorts.status=XELP::VBT::Status::Valid;each.firmwarePorts.present=mask; + for(unsigned d=0;d<7;++d)if(mask&(1U<>3)<<24))|((7U&~mask)<<16))); + for(unsigned d=0;d<9;++d)assert(selected(out.data(),out.config(d),w)==bool(mask&(1U< Date: Sat, 24 Oct 2026 23:26:42 +0900 Subject: [PATCH 17/60] Share Display13 accelerator reservations across fixed TC outputs --- WhateverXe/kern_xe.cpp | 11 ++- tests/display13_shared_registration.py | 95 ++++++++++++++++++++++++++ tools/test.py | 2 +- 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 tests/display13_shared_registration.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 9343d4c5..93d490d1 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5960,7 +5960,7 @@ uint32_t XE::adlnRequestedSharedReservation(IOService *controller) { const auto prefix=(uint64_t(Display13FBMemory::field(controller,0xDF8))+0xFFF)&~0xFFFULL; return prefix<0x10000000 ? static_cast(prefix) : 0; } - if(!Display13FBTopology::adlnWiring(callbackXE->adlnOutputWiring))return 0; + if(!Display13FBTopology::xeLPDWiring(callbackXE->adlnOutputWiring))return 0; // Allocation/free may change descriptor/state fields later. The validated // fixed layout's registration promise is stable across those transitions. const auto prefix=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); @@ -5974,9 +5974,14 @@ IOReturn XE::wrapDisplay13FramebufferPlatformCall(IOService *controller,const OS controller->getMetaClass()->getClassSize()>=0x1C20 && name && name==Display13FBMemory::field(controller,0x1560); if(!fixed)return FunctionCast(wrapDisplay13FramebufferPlatformCall,self->orgDisplay13FramebufferPlatformCall)(controller,name,wait,arg1,arg2,arg3,arg4); - const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(),0x10000000); + // Disabled SysMem windows are native descriptions, not aperture allocations. + // A large stolen prefix can place their unused tail beyond the aperture. + // sharedReservation rejects every occupied secondary window and bounds the + // actual promised prefix to the aperture before any native registration. + const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(), + adlnFixedSysMemEnabled ? 0x10000000 : UINT32_MAX); const auto plan=Display13FBMemory::sharedReservation(layout,0x10000000,adlnFixedSysMemEnabled); - const bool ready=Display13FBTopology::adlnWiring(self->adlnOutputWiring) && plan.valid && + const bool ready=Display13FBTopology::xeLPDWiring(self->adlnOutputWiring) && plan.valid && display13FBGTT.initialized && !display13FBGTT.faulted && !display13FBPower.memoryFaulted && arg1 && arg1==__atomic_load_n(&adlnSharedOwner,__ATOMIC_ACQUIRE) && arg2 && arg3; if(!ready)return kIOReturnNotReady; diff --git a/tests/display13_shared_registration.py b/tests/display13_shared_registration.py new file mode 100644 index 00000000..23b33228 --- /dev/null +++ b/tests/display13_shared_registration.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Replay production shared registration across physical Combo/TC topologies.""" +from pathlib import Path +import re, subprocess, tempfile, sys +r=Path(__file__).resolve().parents[1] +s=(Path(sys.argv[1]) if len(sys.argv)>1 else r/'WhateverXe/kern_xe.cpp').read_text() +f='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in [ + 'uint32_t XE::adlnRequestedSharedReservation(', 'IOReturn XE::wrapDisplay13FramebufferPlatformCall(']) +code=r''' +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_fb_topology.hpp" +#include +#include +#include +struct OSSymbol {}; +struct Meta {unsigned getClassSize(){return 0x1C20;}}; +struct IOService {alignas(8) unsigned char data[0x1C20] {};Meta *getMetaClass(){static Meta m;return &m;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,const void*,unsigned){}}; +using IOReturn=int;constexpr int kIOReturnSuccess=0,kIOReturnNotReady=1; +using Call=IOReturn(*)(IOService*,const OSSymbol*,bool,void*,void*,void*,void*); +#define FunctionCast(name,addr) reinterpret_cast(addr) +template void put(void *p,unsigned off,T value){memcpy(static_cast(p)+off,&value,sizeof(value));} +struct {IOService *controller{};bool initialized=true,faulted=false;uint32_t sharedNativePrefix{},sharedPoolPrefix{};}display13FBGTT; +struct {bool memoryFaulted=false;}display13FBPower; +IOService *adlnSharedOwner;uintptr_t adlnSharedCallback=0x12345678; +bool adlnFixedSysMemEnabled=false;unsigned calls=0;int originalResult=0;bool badBinding=false,badInfo=false; +IOReturn original(IOService *c,const OSSymbol*,bool,void *owner,void*,void *info,void*) { + ++calls;if(originalResult)return originalResult; + put(c,0xC0,owner);put(c,0xD38,badBinding?uintptr_t(0):adlnSharedCallback); + put(info,0,badInfo?0U:Display13FBMemory::field(c,0xDF8));return 0; +} +struct XE { + Display13OutputWiring adlnOutputWiring;uintptr_t orgDisplay13FramebufferPlatformCall=reinterpret_cast(original); + static uint32_t adlnRequestedSharedReservation(IOService*); + static IOReturn wrapDisplay13FramebufferPlatformCall(IOService*,const OSSymbol*,bool,void*,void*,void*,void*); +}; +XE *callbackXE; +FUNCTIONS +void initialize(IOService &c,unsigned stolen) { + c={};put(&c,0xD5C,3U);put(&c,0xDF5,uint8_t(3));put(&c,0xDE4,stolen); + put(&c,0xDC8,0x180000U);put(&c,0xDCC,0x12000U);put(&c,0xDD0,uint64_t(stolen+0x192000)); + put(&c,0xDD8,0x3000U);put(&c,0xDF8,stolen+0x195000U);put(&c,0xDE8,0x4000000U); + put(&c,0x1A10,1U);put(&c,0x1A28,uint64_t(0x37800000)); + for(unsigned slot=0;slot<3;++slot) { + const auto o=0x1A10+40*slot,base=slot?stolen+0x195000U+(slot-1)*0x4000000U:0; + put(&c,o+4,base);put(&c,o+8,base/4096);put(&c,o+0x20,0x4000000U);put(&c,o+0x24,0x4000U); + } +} +int main() { + XE x;callbackXE=&x;IOService c,owner;OSSymbol symbol;unsigned cases=0; + for(unsigned mask=1;mask<128;++mask)if(!(mask&~Display13OutputWiring::Physical) && (mask&(mask-1))) + for(unsigned stolen:{0x04000000U,0x06000000U,0x08000000U,0x0c000000U}) + for(unsigned failure=0;failure<10;++failure) { + initialize(c,stolen);adlnFixedSysMemEnabled=false;x.adlnOutputWiring={};x.adlnOutputWiring.valid=true;x.adlnOutputWiring.hdmi=mask; + for(unsigned d=0;d<7;++d)if(mask&(1U<=5 && failure<=7))); + if(!failure) { + assert(info[0]==prefix && info[1]==0x10000000 && info[2]==0x9a490000); + assert(XE::adlnRequestedSharedReservation(&c)==prefix); + put(&c,0xDF8,prefix+4096);assert(!XE::adlnRequestedSharedReservation(&c)); + } else assert(!display13FBGTT.sharedPoolPrefix); + ++cases; + } + // Fixed backing enabled still requires its entire extent inside the aperture. + for(unsigned stolen:{0x04000000U,0x06000000U,0x08000000U,0x0c000000U}) { + initialize(c,stolen);put(&c,0x1560,&symbol);display13FBGTT={&c,true,false,0,0}; + adlnSharedOwner=&owner;adlnFixedSysMemEnabled=true;originalResult=0;badBinding=badInfo=false; + uint32_t info[3]{};const auto before=calls; + const auto result=XE::wrapDisplay13FramebufferPlatformCall(&c,&symbol,false,&owner,&symbol,info,nullptr); + const bool fits=stolen<0x08000000U; + assert((result==0)==fits && calls==before+fits); + if(fits)assert(info[0]==stolen+0x8195000U && XE::adlnRequestedSharedReservation(&c)==info[0]); + else assert(!display13FBGTT.sharedPoolPrefix); + } + assert(cases==2280);printf("PASS %u production shared registration cases: Combo/TC, native prefix, owner, callback, stale layout and failure propagation\n",cases); +} +'''.replace('FUNCTIONS',f) +with tempfile.TemporaryDirectory(prefix='wex-shared-registration-') 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/tools/test.py b/tools/test.py index 14a25b2f..48e84200 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_agdc'] From db3aa6dca57da3e06adc0829150c903c58fc73c0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:30:13 +0900 Subject: [PATCH 18/60] Name mobile Display13 selection independently of Render enablement --- WhateverXe/kern_xe.cpp | 36 +++++++++++++++---------------- WhateverXe/kern_xe.hpp | 2 +- WhateverXe/kern_xelp_platform.hpp | 8 +++---- tests/adlp_fb_candidate.py | 2 +- tests/adlp_load_only.py | 4 ++-- tests/adlp_render.py | 4 ++-- tests/boot_arguments.py | 6 +++--- tests/device_selection.cpp | 2 +- tests/display13_tiling.py | 2 +- tests/display_backend_dispatch.py | 6 +++--- tests/xelp_platform.cpp | 2 +- 11 files changed, 37 insertions(+), 37 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 93d490d1..9c21b9ab 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -215,10 +215,10 @@ static bool xelpSelectedDevice(IOPCIDevice *pci) { } static bool xelpDisplay13RenderDevice(IOPCIDevice *pci) { return (xelpSelectedProfile.family==XELP::Family::AlderLakeN || - xelpSelectedProfile.framebufferOnlyADLP()) && xelpSelectedDevice(pci); + xelpSelectedProfile.mobileDisplay13()) && xelpSelectedDevice(pci); } static bool xelpDisplay13Device(IOPCIDevice *pci) { - return (XELP::displayAdapter(xelpSelectedProfile.family)==XELP::DisplayAdapter::ADLN || xelpSelectedProfile.framebufferOnlyADLP()) && + return (XELP::displayAdapter(xelpSelectedProfile.family)==XELP::DisplayAdapter::ADLN || xelpSelectedProfile.mobileDisplay13()) && xelpSelectedProfile.display==XELP::Display::XeLPD && xelpSelectedDevice(pci); } // Detailed read-only display evidence is opt-in; control and first-fault @@ -3752,18 +3752,18 @@ void XE::selectProfile(const XELP::Profile &profile) { profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; modDVMTCalcFix.available=profile.displayAdmitted(); const bool nativeOS=XELP::kernelOS(version)!=XELP::OS::Unknown && XELP::kernelOS(version)!=XELP::OS::Donor; - adlpFramebufferOnly=profile.framebufferOnlyADLP() && nativeOS; + adlpDisplaySelected=profile.mobileDisplay13() && nativeOS; // Preserve the accepted Display13/TC handoff when enabling Render. // noaccel still selects the exact FB-only rejection path. - adlpRenderRequested=adlpFramebufferOnly && !checkKernelArgument("-wex-noaccel"); + adlpRenderRequested=adlpDisplaySelected && !checkKernelArgument("-wex-noaccel"); adlnRequested=adlnRequested || adlpRenderRequested; adlnDetected=adlnRequested; display13FramebufferRequested=profile.displayAdmitted() && profile.display==XELP::Display::XeLPD && - (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpFramebufferOnly) && nativeOS; + (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpDisplaySelected) && nativeOS; display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); display13Offline=display13FramebufferRequested && checkKernelArgument("-wex-display13-offline"); if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb; - if(adlpFramebufferOnly && !adlpRenderRequested) { + if(adlpDisplaySelected && !adlpRenderRequested) { tglRequested=tglAcceleratorRequested=false;currentGraphics=&kextIntelTGL;return; } if(adlnRequested) { @@ -3795,7 +3795,7 @@ void XE::deinit() {} Display13OutputWiring XE::display13BoardWiring(IOService *provider) { auto property=provider->getProperty("xelp-hdmi-wiring"); if(!property)property=provider->getProperty("tgl-hdmi-wiring"); - if(adlpFramebufferOnly) { + if(adlpDisplaySelected) { // VBT remains the authority for pin/transport. No Combo property is // silently interpreted as a TC policy. Select every supported fixed HDMI // route from firmware; connection state is checked by the provider. @@ -3825,7 +3825,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,adlpFramebufferOnly)) { + if(displayRequested && !Display13FBTopology::supportedWiring(board,adlpDisplaySelected)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } auto bootDDI=displayRequested ? UINT32_MAX : 1U; @@ -3889,7 +3889,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { static_cast(result.status), result.poweredPipes, result.activePipes, result.hdmiBPipes); // ADL-P adopts a validated firmware surface below. A destructive plane // exercise before native controller start is not a startup prerequisite. - if (linearTest && !adlpFramebufferOnly) { + if (linearTest && !adlpDisplaySelected) { auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); if(bootDDI>=3)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI, @@ -4099,7 +4099,7 @@ bool XE::xelpBindProbe(void *controller) { config.legacyBackend=tglAUXParentMask; } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || - !Display13FBTopology::supportedWiring(adlnOutputWiring,adlpFramebufferOnly) || + !Display13FBTopology::supportedWiring(adlnOutputWiring,adlpDisplaySelected) || ((adlnOutputWiring.hdmi&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3; @@ -4230,7 +4230,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { if(map)map->release(); } if(!xeProfile.displayAdmitted())return; - SYSLOG("igfx","Xe display bootstrap physical=%x FB-only=%u",xeProfile.identity,adlpFramebufferOnly); + SYSLOG("igfx","Xe display bootstrap physical=%x FB-only=%u",xeProfile.identity,adlpDisplaySelected); xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; if (adlnDetected || display13FramebufferRequested) { xelpReadFirmware(physicalGPU); @@ -4327,7 +4327,7 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add // Keep both adapters outside the large Display12 framebuffer patch frame. if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) return processDisplay13Framebuffer(patcher,index,address,size); - if(adlpFramebufferOnly && !adlpRenderRequested) { + if(adlpDisplaySelected && !adlpRenderRequested) { if(index==kextIntelTGL.loadIndex)return processADLPLoadOnly(patcher,index,address,size); return false; } @@ -6144,7 +6144,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->adlpFramebufferOnly) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->adlpDisplaySelected) || !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; @@ -7391,9 +7391,9 @@ 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->adlpFramebufferOnly) || + if(!Display13FBTopology::supportedWiring(board,s->adlpDisplaySelected) || board.selected()!=s->adlnOutputWiring.selected())return false; - if(s->adlpFramebufferOnly && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { + if(s->adlpDisplaySelected && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBTCUnavailable",true);return false; } const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); @@ -7428,7 +7428,7 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { (provider->getProperty("AAPL,GfxYTile") || provider->setProperty("AAPL,GfxYTile",&enabled,sizeof(enabled))); OSSafeReleaseNULL(yTiling);OSSafeReleaseNULL(mode);OSSafeReleaseNULL(features); if(!featuresSelected)return false; - if(s->adlpFramebufferOnly && !s->prepareDisplay13BootScanout(pci))return false; + if(s->adlpDisplaySelected && !s->prepareDisplay13BootScanout(pci))return false; SYSLOG("igfx","Display13 native controller start enter offline=%u",display13Offline); const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgDisplay13FramebufferStart)(that,provider); SYSLOG("igfx","Display13 native controller start return=%u",started); @@ -9636,7 +9636,7 @@ bool XE::processADLPLoadOnly(KernelPatcher &patcher,size_t index,mach_vm_address const uint8_t startHead[]={0x55,0x48,0x89,0xe5}; auto p=patcher.solveSymbol(index,probe,address,size); auto q=patcher.solveSymbol(index,start,address,size);patcher.clearError(); - bool ready=(adlpLoadOnly || (adlpFramebufferOnly && !fb)) && (fb || index==kextIntelTGL.loadIndex) && size>probeOffset+32 && size>startOffset+32 && + bool ready=(adlpLoadOnly || (adlpDisplaySelected && !fb)) && (fb || index==kextIntelTGL.loadIndex) && size>probeOffset+32 && size>startOffset+32 && p==address+probeOffset && q==address+startOffset && !memcmp(reinterpret_cast(p),fb?fbHead:gtHead,sizeof(gtHead)) && !memcmp(reinterpret_cast(q),startHead,sizeof(startHead)); @@ -9651,7 +9651,7 @@ bool XE::processADLPLoadOnly(KernelPatcher &patcher,size_t index,mach_vm_address } patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty(fb?"ADLPFramebufferLoadOnlyReady":"ADLPAcceleratorLoadOnlyReady",ready); - if(adlpFramebufferOnly && !fb)IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBOnlyAcceleratorBlocked",ready); + if(adlpDisplaySelected && !fb)IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBOnlyAcceleratorBlocked",ready); SYSLOG("igfx","ADL-P load-only: %s probe/start barriers ready=%u",fb?"framebuffer":"accelerator",ready); return ready; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 66c86d28..8ecfc7e7 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -154,7 +154,7 @@ class XE { static uint32_t wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig); mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; bool display13DPProbeReady {},display13TCProbeReady {}; - bool adlpFramebufferOnly {}; + bool adlpDisplaySelected {}; bool adlpRenderRequested {}; Display13OutputWiring display13BoardWiring(IOService *); bool prepareDisplay13BootScanout(IOPCIDevice *); diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 00acde9c..ca5292d6 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -14,13 +14,13 @@ struct Profile { GT gt {GT::Unknown}; Donor donor {Donor::Unknown}; uint32_t effectiveIdentity {}; // Table selector; may come from device-id. - // Initial ADL-P FB candidate: donor selector does not change physical - // Display13 identity and grants no Render/Accelerator qualification. - bool framebufferOnlyADLP() const { + // Mobile Display13 selection is distinct from whether Render is requested. + // The donor selector never changes the physical Display13 identity. + bool mobileDisplay13() const { return identity==0x46A68086 && effectiveIdentity==0x9A498086 && revision<=255 && family==Family::AlderLakeP && display==Display::XeLPD && gt==GT::Unknown && donor==Donor::C004FC1A; } - bool displayAdmitted() const {return admitted() || framebufferOnlyADLP();} + bool displayAdmitted() const {return admitted() || mobileDisplay13();} bool overridden() const {return identity!=effectiveIdentity;} // Select the implemented family backend for every PCI revision. Hardware // validation is independent; a stepping split needs a concrete behavior delta. diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 37336062..726a8315 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -20,7 +20,7 @@ struct XE { XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); XELP::VBT::Ports firmwarePorts; - bool adlpFramebufferOnly=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; + bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; bool display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); diff --git a/tests/adlp_load_only.py b/tests/adlp_load_only.py index 7e9812cd..6ebb0f9d 100644 --- a/tests/adlp_load_only.py +++ b/tests/adlp_load_only.py @@ -46,7 +46,7 @@ } }; struct XE { - bool adlpLoadOnly=false,adlpFramebufferOnly=false;XELP::Profile xeProfile;unsigned initialized=0; + bool adlpLoadOnly=false,adlpDisplaySelected=false;XELP::Profile xeProfile;unsigned initialized=0; void select(){SELECTION ++initialized;} bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); @@ -79,7 +79,7 @@ std::vector image(0x50000);auto base=reinterpret_cast(image.data()); const uint8_t gt[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; memcpy(image.data()+0x238e2,gt,17);memcpy(image.data()+0x23ecc,gt,4); - XE xe;xe.adlpFramebufferOnly=true;KernelPatcher p; + XE xe;xe.adlpDisplaySelected=true;KernelPatcher p; assert(xe.processADLPLoadOnly(p,module,base,image.size())==(module==2));assert(p.routed==(module==2?2U:0U)); if(module==2)assert(IORegistryEntry::props.at("ADLPFBOnlyAcceleratorBlocked"));++cases; } diff --git a/tests/adlp_render.py b/tests/adlp_render.py index 02d71e32..d56b3e84 100644 --- a/tests/adlp_render.py +++ b/tests/adlp_render.py @@ -20,7 +20,7 @@ XELP::Profile xeProfile; bool display12SmallGTDetected=false,adlsDisplaySelected=false; bool adlnRequested=false,adlnDetected=false,adlnKernelReady=false,adlnNativeRequested=false; - bool adlpFramebufferOnly=false,adlpRenderRequested=false,display13FramebufferRequested=false; + bool adlpDisplaySelected=false,adlpRenderRequested=false,display13FramebufferRequested=false; bool tglRequested=false,tglAcceleratorRequested=false,tglInactivePorts=false,tglPG1Poll=false; bool tglClockOrderRequested=false,tglTrainingLeaseRequested=false,tglProgramEnabled=false; int *currentFramebuffer=nullptr,*currentGraphics=nullptr; @@ -37,7 +37,7 @@ assert(XELP::same(x.xeProfile,p)); assert(x.currentFramebuffer==&kextIntelTGLFb && x.currentGraphics==&kextIntelTGL); assert(x.display13FramebufferRequested==(which!=2)); - assert(x.adlpFramebufferOnly==(which==0)); + assert(x.adlpDisplaySelected==(which==0)); assert(x.adlpRenderRequested==(which==0&&!off)); assert(x.adlnDetected==(which==1 || (which==0&&!off))); assert(x.tglAcceleratorRequested==!off); diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 9d2dbf2f..71d762df 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -65,7 +65,7 @@ struct XE { XELP::Profile xeProfile; bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; - bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpFramebufferOnly=false, adlpRenderRequested=false; + bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpDisplaySelected=false, adlpRenderRequested=false; bool display12SmallGTReady=false, adlsDisplayHooksReady=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; @@ -107,7 +107,7 @@ assert(xe.currentGraphics==&kextIntelTGL && xe.currentFramebuffer==&kextIntelTGLFb); } xe=boot({"-wex-display13-offline","-wex-noaccel"},0x46A68086U,0x9A498086U); - assert(display13Offline && xe.adlpFramebufferOnly && !xe.tglAcceleratorRequested); + assert(display13Offline && xe.adlpDisplaySelected && !xe.tglAcceleratorRequested); xe=boot({"-wex-display13-offline"},0x9A498086U); assert(!display13Offline); xe=boot({"-wex-display13-trace"}); @@ -175,7 +175,7 @@ assert(xe.adlnRequested && xe.adlnNativeRequested && xe.xeProfile.identity==0x12348086); xe=boot({},0x12348086,0x9A498086);assert(xe.tglRequested); xe=XE{};xe.selectProfile(XELP::identify(0x46A68086,12,0x9A498086)); - assert(xe.adlpFramebufferOnly && xe.adlpRenderRequested && xe.display13FramebufferRequested && xe.tglAcceleratorRequested && xe.adlnRequested && xe.tglRequested); + assert(xe.adlpDisplaySelected && xe.adlpRenderRequested && xe.display13FramebufferRequested && xe.tglAcceleratorRequested && xe.adlnRequested && xe.tglRequested); assert(xe.xeProfile.family==XELP::Family::AlderLakeP && xe.xeProfile.gt==XELP::GT::Unknown); xe=boot({},0x46A68086);assert(!xe.tglRequested && !xe.adlnRequested); for(uint32_t pci:{0x46A68086U,0x46808086U,0xA7A08086U}) { diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 4687b929..321450a2 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -41,7 +41,7 @@ int main() { auto q=p;q.effectiveIdentity=0x46D18086;assert(!same(p,q)); ProbeConfig c;c.profile=p;c.combo=c.comboBackend=3;assert(!c.valid()); id=0x9A49;p=select(0x46A68086,12,true,&id,4); - assert(p.displayAdmitted() && !p.admitted() && p.framebufferOnlyADLP()); + assert(p.displayAdmitted() && !p.admitted() && p.mobileDisplay13()); c={};c.profile=p;c.comboBackend=3;c.legacy=8;c.legacyBackend=0x78;assert(c.valid()); c.abi=2;assert(!c.valid());c.abi=3;c.profile.gt=GT::TGLGT2;assert(!c.valid()); puts("PASS device table, physical/effective identity, strict override bytes/vendor/revision, model choice and ABI3"); diff --git a/tests/display13_tiling.py b/tests/display13_tiling.py index cda40685..e762c6ed 100644 --- a/tests/display13_tiling.py +++ b/tests/display13_tiling.py @@ -45,7 +45,7 @@ #define OSDynamicCast(T,p) dynamic_cast(p) #define OSSafeReleaseNULL(p) do{p=nullptr;}while(0) struct Handoff { - bool adlpFramebufferOnly=true,ready=true;unsigned calls=0; + bool adlpDisplaySelected=true,ready=true;unsigned calls=0; bool prepareDisplay13BootScanout(IOService *provider){assert(provider);++calls;return ready;} } handoff; bool prepare(IOService *that,IOService *provider){ diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index 70eecb33..9201c97d 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -26,7 +26,7 @@ def expression(name): bool loadOnly=false; bool checkKernelArgument(const char *arg) {assert(!strcmp(arg,"-wex-adln-load-only"));return loadOnly;} struct XE { - bool adlpFramebufferOnly=false,adlpRenderRequested=false; + bool adlpDisplaySelected=false,adlpRenderRequested=false; bool display13FramebufferRequested=false,adlnRequested=false,adlnDetected=false; bool adlnNativeRequested=false,adlnNativeReady=false,display13BootReady=false,tglActive=false; Kext *currentFramebuffer=nullptr,*currentGraphics=nullptr; @@ -95,14 +95,14 @@ def expression(name): } int main() { for(unsigned module=1;module<=3;++module){ - XE x;KernelPatcher p;Kext fb{1};x.adlpFramebufferOnly=true;x.display13FramebufferRequested=true;x.currentFramebuffer=&fb; + XE x;KernelPatcher p;Kext fb{1};x.adlpDisplaySelected=true;x.display13FramebufferRequested=true;x.currentFramebuffer=&fb; const bool result=x.dispatch(p,module,0,0); assert(x.call==(module==1?XE::Display13:module==2?XE::Rejected:XE::None));assert(result==(module!=3)); } for(bool ready:{false,true})for(unsigned module=1;module<=3;++module) { XE x;KernelPatcher p;Kext fb{1},graphics{2}; - x.adlpFramebufferOnly=x.adlpRenderRequested=x.display13FramebufferRequested=true; + x.adlpDisplaySelected=x.adlpRenderRequested=x.display13FramebufferRequested=true; x.adlnRequested=x.adlnDetected=x.adlnNativeRequested=true;x.adlnNativeReady=x.tglActive=ready; x.currentFramebuffer=&fb;x.currentGraphics=&graphics; const bool result=x.dispatch(p,module,0,0); diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 016ba080..15479221 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -13,7 +13,7 @@ struct IO { }; int main() { auto candidate=identify(0x46a68086,12,0x9a498086); - assert(candidate.framebufferOnlyADLP() && candidate.displayAdmitted() && !candidate.admitted()); + assert(candidate.mobileDisplay13() && candidate.displayAdmitted() && !candidate.admitted()); assert(candidate.family==Family::AlderLakeP && candidate.display==Display::XeLPD && candidate.gt==GT::Unknown); assert(!identify(0x46a68086,12).displayAdmitted()); assert(!identify(0x46a38086,12,0x9a498086).displayAdmitted()); From 9bb5b6389bd592b5a95842f619aa2bcda4e36ce0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:39:30 +0900 Subject: [PATCH 19/60] Select mobile Display13 backends from physical ADL-P and RPL-P identities --- README.md | 10 ++++--- README_ja.md | 10 ++++--- WhateverXe/kern_xe.cpp | 17 ++++++----- WhateverXe/kern_xelp_devices.hpp | 34 ++++++++++++---------- WhateverXe/kern_xelp_display_resources.hpp | 3 +- WhateverXe/kern_xelp_platform.hpp | 15 ++++++---- docs/device-selection.md | 15 ++++++++-- tests/adlp_fb_candidate.py | 2 +- tests/adlp_load_only.py | 31 +++++++++++++++++--- tests/adlp_render.py | 28 +++++++++--------- tests/adlp_resources.cpp | 4 +-- tests/boot_arguments.py | 16 +++++----- tests/device_selection.cpp | 6 ++-- tests/firmware_ports.cpp | 3 +- tests/firmware_readout.py | 10 +++---- tests/host_publication.py | 12 ++++---- tests/provider_binding.py | 2 +- tests/provider_binding_fixture.cpp | 7 +++-- tests/xelp_platform.cpp | 12 ++++---- 19 files changed, 146 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 681c8117..1e7ed3c3 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,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 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 @@ -53,6 +54,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. +- Alder Lake-H/P/U and Raptor Lake-H/P/U with supported fixed HDMI wiring. - Other supported Alder Lake-S configurations, Alder Lake-HX, Raptor Lake-S and Raptor Lake-HX. These devices use implemented support shared with the tested families. Their @@ -62,8 +64,8 @@ limits are listed in [device selection](docs/device-selection.md). ### Further implementation required -- Alder Lake-H/P/U and Raptor Lake-H/P/U: additional graphics and display support - is needed. These are distinct from the HX products listed above. +- Display13 eDP, fixed DP and Type-C-managed output need complete transport + integration. Mobile-family ID admission does not enable those output paths. - Rocket Lake-S: its display power and clock handling differ from Tiger Lake. It is not supported by this release, and no hardware validation is scheduled. @@ -221,9 +223,9 @@ installation or update location. Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. -`-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on an ADL-P device using an explicit backend override, for load-only diagnostics without GPU initialization. +`-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on a selected ADL-P/RPL-P device, for load-only diagnostics without GPU initialization. `-wex-display13-offline` keeps Display 13 native controller initialization enabled but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. -The experimental 46A6 ADL-P configuration with a 9A49 donor selector retains its physical Display13 identity and currently enables only the central TC1 framebuffer path; both accelerator probe and start remain blocked. This is a bring-up candidate, not a validated hardware configuration. +ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. AGDP policies that unload the integrated GPU still require configuration before display registration. ## Build and test diff --git a/README_ja.md b/README_ja.md index c757b7cf..ef0eb63c 100644 --- a/README_ja.md +++ b/README_ja.md @@ -31,6 +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`)。Big Surの固定TC HDMI | | Alder Lake-S | UHD Graphics 770(`4690`)。HDMI、DisplayPort、固定DisplayPortとして配線されたUSB-C端子 | 各ファミリーの一部構成で確認した結果であり、全モデル・全端子の動作保証ではありません。 @@ -41,6 +42,7 @@ OS別の検証範囲と残る問題は[動作確認済みの機能と既知の - 96 EUを含む、対応IDの他のTiger Lake-U/H35構成。 - Tiger Lake-H45(GT1):`9A60`、`9A68`、`9A70`。 - 対応IDの他のAlder Lake-N構成とTwin Lake。 +- 対応する固定HDMI配線のAlder Lake-H/P/U、Raptor Lake-H/P/U。 - 対応IDの他のAlder Lake-S構成、Alder Lake-HX、Raptor Lake-S、Raptor Lake-HX。 確認済みのファミリーと処理を共有する実装があり、IDは自動で受け付けます。 @@ -49,8 +51,8 @@ CPUモデル名や機種固有の設定は不要ですが、実機での動作 ### 対応に追加改修が必要 -- Alder Lake-H/P/U、Raptor Lake-H/P/U:描画・表示処理の追加対応が必要です。 - 上記のHX製品とは別の系統です。 +- Display13のeDP・固定DP・Type-C管理下の出力は、transportの接続に追加改修が必要です。 + モバイル系IDの受付だけでこれらの出力経路が有効になるわけではありません。 - Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 このリリースでは未対応で、実機検証の予定もありません。 @@ -169,9 +171,9 @@ Safariのインストール先や更新による配置変更に依存せずData 開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 -`-wex-adlp-load-only` は既存バックエンドを明示選択したADL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 +`-wex-adlp-load-only` は選択済みADL-P/RPL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 `-wex-display13-offline` はDisplay 13のnative controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 -46A6 ADL-Pで9A49をdonor選択に使う実験構成は、物理Display13の識別を維持し、現時点では中央TC1のFramebuffer経路のみを選択します。Acceleratorはprobe/startの両方を拒否します。実機成立済みの対応構成ではなく、立上げ候補です。 +ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。IGPUをunloadするAGDP policyでは、表示登録前の設定が引き続き必要です。 ## ビルドとテスト diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 9c21b9ab..2d6ec2ba 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4138,8 +4138,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { selectProfile(XELP::deviceProfile(physicalGPU)); // ADLP_LOAD_ONLY_KERNEL: do not enter TGL MMIO/FIA, bootstrap or VM setup. const auto physicalEntry=XELP::deviceEntry(xeProfile.identity); - adlpLoadOnly=physicalEntry && physicalEntry->family==XELP::Family::AlderLakeP && - xeProfile.donor==XELP::Donor::C004FC1A && xeProfile.overridden() && + adlpLoadOnly=physicalEntry && xeProfile.mobileDisplay13() && XELP::hostOS(XELP::kernelOS(version)) && checkKernelArgument("-wex-adlp-load-only"); if(adlpLoadOnly) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyKernel",true); @@ -4147,11 +4146,12 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { return; } // ADLP_LOAD_ONLY_KERNEL_END - // Temporary opt-in bring-up evidence. ADL-P remains unadmitted; only - // the existing bounded Gen12 forcewake transaction writes registers. - if(!xeProfile.admitted() && xeProfile.family==XELP::Family::AlderLakeP && - !xeProfile.overridden() && physicalGPU && checkKernelArgument("-wex-adlp-readout") && - XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { + // Explicit diagnostic keeps both native drivers blocked even after the + // mobile family is admitted automatically. Only the bounded readout runs. + if(xeProfile.mobileDisplay13() && physicalGPU && checkKernelArgument("-wex-adlp-readout") && + XELP::hostOS(XELP::kernelOS(version))) { + adlpLoadOnly=true; + if(!(physicalGPU->configRead16(4)&2))return; auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { struct IO { @@ -4162,7 +4162,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { void delay() {IODelay(100);} } io {reinterpret_cast(map->getVirtualAddress())}; uint32_t edram=UINT32_MAX; - auto gt=AlderLakeN::readGT(io,true,&edram); + auto gt=AlderLakeN::readGT(io,true,&edram,XELP::decodeADLPGT); physicalGPU->setProperty("ADLPGTBoot",>,sizeof(gt)); physicalGPU->setProperty("ADLPEDRAM",edram,32); uint32_t registers[][2] { @@ -4184,6 +4184,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { gt.releasedMask,gt.releaseFailedMask); } if(map)map->release(); + return; } if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) && !xeProfile.overridden() && physicalGPU && diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index 4abc239c..f902a6e4 100644 --- a/WhateverXe/kern_xelp_devices.hpp +++ b/WhateverXe/kern_xelp_devices.hpp @@ -8,7 +8,7 @@ namespace XELP { enum class Family : uint32_t { Unknown, TigerLake, AlderLakeN, RocketLake, AlderLakeS, AlderLakeP, RaptorLakeS, RaptorLakeP }; enum class Display : uint32_t { Unknown, XeD = 12, XeLPD = 13 }; -enum class GT : uint32_t { Unknown, TGLGT2, TGLG4, ADLNSmall, ADLSSmall, TGLGT1 }; +enum class GT : uint32_t { Unknown, TGLGT2, TGLG4, ADLNSmall, ADLSSmall, TGLGT1, ADLPMobile }; enum class Donor : uint32_t { Unknown, C004FC1A = 1 }; struct DeviceEntry { uint16_t device; Family family; Display display; GT backend; @@ -24,12 +24,12 @@ 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"}, - {0x46A3, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, - {0x46A6, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, - {0x46A8, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, - {0x46AA, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, - {0x46B3, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, - {0x46C3, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "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"}, + {0x46AA, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Iris Xe Graphics"}, + {0x46B3, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0x46C3, Family::AlderLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, {0x46D0, Family::AlderLakeN, Display::XeLPD, GT::ADLNSmall, "Intel UHD Graphics"}, {0x46D1, Family::AlderLakeN, Display::XeLPD, GT::ADLNSmall, "Intel UHD Graphics"}, {0x46D2, Family::AlderLakeN, Display::XeLPD, GT::ADLNSmall, "Intel UHD Graphics"}, @@ -51,20 +51,24 @@ 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"}, - {0xA7A0, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, - {0xA7A1, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, - {0xA7A8, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, - {0xA7A9, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, - {0xA7AA, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Graphics"}, - {0xA7AB, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Graphics"}, - {0xA7AC, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Graphics"}, - {0xA7AD, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel 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"}, + {0xA7A9, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel UHD Graphics"}, + {0xA7AA, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Graphics"}, + {0xA7AB, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Graphics"}, + {0xA7AC, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Graphics"}, + {0xA7AD, Family::RaptorLakeP, Display::XeLPD, GT::ADLPMobile, "Intel Graphics"}, }; inline const DeviceEntry *deviceEntry(uint32_t pci) { if((pci&0xFFFF)!=0x8086)return nullptr; for(const auto &entry:DeviceTable)if(entry.device==(pci>>16))return &entry; return nullptr; } +inline bool mobileDisplay13Device(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && e->backend==GT::ADLPMobile; +} inline bool desktopGTDevice(uint32_t pci) { const auto e=deviceEntry(pci); return e && (e->family==Family::AlderLakeS || e->family==Family::RaptorLakeS); diff --git a/WhateverXe/kern_xelp_display_resources.hpp b/WhateverXe/kern_xelp_display_resources.hpp index 711d2731..81570f84 100644 --- a/WhateverXe/kern_xelp_display_resources.hpp +++ b/WhateverXe/kern_xelp_display_resources.hpp @@ -14,7 +14,8 @@ constexpr DisplayAdapter displayAdapter(Family family) { case Family::TigerLake:return DisplayAdapter::TGL; case Family::AlderLakeS: case Family::RaptorLakeS:return DisplayAdapter::ADLS; - case Family::AlderLakeP:return DisplayAdapter::ADLP; + case Family::AlderLakeP: + case Family::RaptorLakeP:return DisplayAdapter::ADLP; case Family::AlderLakeN:return DisplayAdapter::ADLN; default:return DisplayAdapter::Unknown; } diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index ca5292d6..0a1009f2 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -17,15 +17,18 @@ struct Profile { // Mobile Display13 selection is distinct from whether Render is requested. // The donor selector never changes the physical Display13 identity. bool mobileDisplay13() const { - return identity==0x46A68086 && effectiveIdentity==0x9A498086 && revision<=255 && - family==Family::AlderLakeP && display==Display::XeLPD && gt==GT::Unknown && donor==Donor::C004FC1A; + return mobileDisplay13Device(identity) && revision<=255 && + (effectiveIdentity==identity || effectiveIdentity==0x9A498086) && + display==Display::XeLPD && gt==GT::ADLPMobile && donor==Donor::C004FC1A && + family==deviceEntry(identity)->family; } - bool displayAdmitted() const {return admitted() || mobileDisplay13();} + bool displayAdmitted() const {return admitted();} bool overridden() const {return identity!=effectiveIdentity;} // Select the implemented family backend for every PCI revision. Hardware // validation is independent; a stepping split needs a concrete behavior delta. bool admitted() const {return (identity&0xFFFF)==0x8086 && revision<=255 && donor==Donor::C004FC1A && gt!=GT::Unknown && + ((gt!=GT::ADLPMobile && !mobileDisplay13Device(identity)) || mobileDisplay13()) && ((!display12SmallGT(gt) && !display12SmallGTDevice(identity)) || (display12SmallGT(gt) && display12SmallGTDevice(identity) && !overridden()));} }; @@ -34,9 +37,9 @@ inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) if((physical&0xFFFF)!=0x8086 || revision>255)return p; const auto entry=deviceEntry(effective);if(!entry)return p; const auto physicalEntry=deviceEntry(physical); - if(physicalEntry && physicalEntry->family==Family::AlderLakeP) { - p.family=physicalEntry->family;p.display=physicalEntry->display; - if(entry->backend!=GT::Unknown)p.donor=Donor::C004FC1A; + if(physicalEntry && physicalEntry->backend==GT::ADLPMobile) { + p.family=physicalEntry->family;p.display=physicalEntry->display;p.gt=physicalEntry->backend; + if(effective==physical || effective==0x9A498086)p.donor=Donor::C004FC1A; return p; } p.family=entry->family;p.display=entry->display;p.gt=entry->backend; diff --git a/docs/device-selection.md b/docs/device-selection.md index 8ec87e47..b6fa1a45 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -7,10 +7,12 @@ machine-name setting is required. By default, the physical Intel GPU device ID selects the backend. The implemented IDs are Tiger Lake `9A40`, `9A49`, `9A60`, `9A68`, `9A70`, `9A78` and Alder Lake-N / Twin Lake -`46D0`–`46D4`, plus the ADL-S/HX and RPL-S/HX IDs below. +`46D0`–`46D4`, plus the S/HX and mobile Display13 IDs below. | Family | Accepted IDs | | --- | --- | +| ADL-H / P / U | `46A3`, `46A6`, `46A8`, `46AA`, `46B3`, `46C3` | +| RPL-H / P / U | `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` | @@ -47,6 +49,12 @@ HX IDs `4688/468B/A788/A78B` select the S-family backend, independent of CPU branding. RKL remains identification-only: its absent PW_2, PLL/PHY mapping and HTI resource reservations require a separate Display adapter. +Mobile Display13 keeps the physical family and accepts its physical ID or the +legacy 9A49 donor selector. Other-family overrides are rejected. GT resources +come from slice/DSS/EU-pair/L3 fuses: one slice, up to six DSS, 8/12/16 EUs per +DSS and four/six/eight L3 banks, with supported media-engine fuses. A shared +backend does not imply every resource combination has been tested. + Do not use an override merely to change the displayed GPU name. ## Display name @@ -76,7 +84,10 @@ must match the implemented path; ambiguous or cross-routed AUX layouts are exclu has CPU coverage; not every panel or link rate has been tested on hardware. - TGL panels requesting HOBL use standard signal settings; the HOBL power-saving optimization is disabled until its retry behavior is supported. -- Display 13: implemented Combo HDMI/DP paths do not imply USB-C/eDP support. +- Display 13: fixed HDMI uses firmware Combo A/B and TC1–4 routes; TC requires + verified legacy mode. DP, USB-C and eDP need further integration. A single + TMDS firmware scanout on pipe A is required for TC boot adoption; simultaneous + firmware TC scanouts are not adopted by the Combo-only multi-GOP normalizer. 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 diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 726a8315..11a18af9 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -39,7 +39,7 @@ assert(!Display13FBTopology::supportedWiring(w) && Display13FBTopology::supportedWiring(w,true)); 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()); + assert(seen.profile.family==XELP::Family::AlderLakeP && seen.profile.admitted()); for(unsigned f=0;f<5;++f){XE bad=x; if(f==0)bad.display13TCProbeReady=false;if(f==1)bad.display13FBTCHooksReady=false; if(f==2)bad.display13FBTopologyHooksReady=false;if(f==3)bad.xelpProviderConfigureProbe=0; diff --git a/tests/adlp_load_only.py b/tests/adlp_load_only.py index 6ebb0f9d..f9c61bbe 100644 --- a/tests/adlp_load_only.py +++ b/tests/adlp_load_only.py @@ -5,6 +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] dispatch=s[s.index('bool XE::processKext('):s.index(' if((tglRequested',s.index('bool XE::processKext('))] assert dispatch.index('if(adlpLoadOnly)') #include "kern_xelp_platform.hpp" #include "kern_xelp_os.hpp" +#include "kern_alderlake_n.hpp" +#include "kern_xelp_gt_resources.hpp" using mach_vm_address_t=uintptr_t; #define arrsize(a) (sizeof(a)/sizeof((a)[0])) #define SYSLOG(...) do{}while(0) @@ -26,7 +29,7 @@ void setProperty(const char *k,bool v){props[k]=v;} };std::map IORegistryEntry::props; struct Kext {size_t loadIndex;};Kext kextIntelTGLFb{1},kextIntelTGL{2}; -bool arg=true;bool checkKernelArgument(const char *s){assert(!strcmp(s,"-wex-adlp-load-only"));return arg;} +bool arg=true,readout=false;bool checkKernelArgument(const char *s){if(!strcmp(s,"-wex-adlp-readout"))return readout;assert(!strcmp(s,"-wex-adlp-load-only"));return arg;} const char *version="Darwin Kernel Version 20.6.0: Thu Jul 6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64"; struct KernelPatcher { struct RouteRequest {const char *symbol;uintptr_t replacement; @@ -45,9 +48,21 @@ routed+=2;return true; } }; +constexpr unsigned kIOMapInhibitCache=0; +void IODelay(unsigned){assert(false);} +struct DiagnosticGPU { + unsigned command=2,maps=0; + unsigned configRead16(unsigned r){assert(r==4);return command;} + struct Map {uintptr_t getVirtualAddress(){return 0;}unsigned getLength(){return 0;}void release(){}}map; + Map *mapDeviceMemoryWithRegister(unsigned r,unsigned){assert(r==0x10);++maps;return ↦} + void setProperty(const char*,const void*,unsigned){} + void setProperty(const char*,uint32_t,unsigned){} +}; struct XE { bool adlpLoadOnly=false,adlpDisplaySelected=false;XELP::Profile xeProfile;unsigned initialized=0; + DiagnosticGPU *physicalGPU=nullptr; void select(){SELECTION ++initialized;} + void diagnose(){DIAGNOSTIC ++initialized;} bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); static IOService *rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *); @@ -56,11 +71,19 @@ BODY int main(){ unsigned cases=0; - for(unsigned physical:{0x46a6U,0x46a3U,0x9a49U,0x4680U})for(bool flag:{false,true})for(bool override:{false,true}){ + for(unsigned physical:{0x46a6U,0x46a3U,0xa7a8U,0x9a49U,0x4680U})for(bool flag:{false,true})for(bool override:{false,true}){ XE xe;arg=flag;xe.xeProfile=XELP::identify((physical<<16)|0x8086,12,((override?0x9a49:physical)<<16)|0x8086);xe.select(); - bool expected=(physical==0x46a6 || physical==0x46a3)&&flag&&override; + bool expected=(physical==0x46a6 || physical==0x46a3 || physical==0xa7a8)&&flag; assert(xe.adlpLoadOnly==expected && xe.initialized==!expected);++cases; } + // Failed/unavailable diagnostic mappings must still stop both native drivers. + for(unsigned physical:{0x46a6U,0xa7a8U,0x4c8aU})for(bool enabled:{false,true})for(bool powered:{false,true}) { + XE xe;DiagnosticGPU gpu;gpu.command=powered?2:0;xe.physicalGPU=&gpu;readout=enabled; + xe.xeProfile=XELP::identify((physical<<16)|0x8086,12);xe.diagnose(); + const bool blocked=enabled && physical!=0x4c8a; + assert(xe.adlpLoadOnly==blocked && xe.initialized==!blocked && gpu.maps==unsigned(blocked&&powered));++cases; + } + readout=false; for(unsigned index:{1U,2U})for(unsigned bad=0;bad<4;++bad){ std::vector image(0x50000);auto base=reinterpret_cast(image.data()); uint8_t fb[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x83,0xec,0x10,0x48,0x89,0xf3,0x49,0x89,0xfe}; @@ -85,7 +108,7 @@ } printf("PASS %u production ADLP load-only selection/probe/start checks\n",cases); } -'''.replace('SELECTION',selection).replace('BODY',body) +'''.replace('SELECTION',selection).replace('BODY',body).replace('DIAGNOSTIC',diagnostic) with tempfile.TemporaryDirectory(prefix='adlp-load-only-') 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/adlp_render.py b/tests/adlp_render.py index d56b3e84..2b13c8ce 100644 --- a/tests/adlp_render.py +++ b/tests/adlp_render.py @@ -29,23 +29,25 @@ }; FUNCTION int main(){ - for(unsigned which=0;which<3;++which)for(unsigned rev=0;rev<256;++rev)for(unsigned off=0;off<2;++off) { - noaccel=off;XE x; - const auto p=which==0?XELP::identify(0x46A68086,rev,0x9A498086): - which==1?XELP::identify(0x46D18086,rev):XELP::identify(0x9A498086,rev); - x.selectProfile(p); - assert(XELP::same(x.xeProfile,p)); + const uint32_t mobile[]={0x46a3,0x46a6,0x46a8,0x46aa,0x46b3,0x46c3,0xa7a0,0xa7a1,0xa7a8,0xa7a9,0xa7aa,0xa7ab,0xa7ac,0xa7ad}; + 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; + const auto pci=(isMobile?mobile[which/2]:isN?0x46d1:0x9a49)<<16|0x8086; + const auto effective=isMobile && (which&1)?0x9a498086:pci; + const auto p=XELP::identify(pci,rev,effective);x.selectProfile(p); + assert(XELP::same(x.xeProfile,p) && p.admitted()); assert(x.currentFramebuffer==&kextIntelTGLFb && x.currentGraphics==&kextIntelTGL); - assert(x.display13FramebufferRequested==(which!=2)); - assert(x.adlpDisplaySelected==(which==0)); - assert(x.adlpRenderRequested==(which==0&&!off)); - assert(x.adlnDetected==(which==1 || (which==0&&!off))); + assert(x.display13FramebufferRequested==(isMobile || isN)); + assert(x.adlpDisplaySelected==isMobile); + assert(x.adlpRenderRequested==(isMobile&&!off)); + assert(x.adlnDetected==(isN || (isMobile&&!off))); assert(x.tglAcceleratorRequested==!off); - assert(adlnNativeRCSProfileRequested==(which==1 || (which==0&&!off))); + assert(adlnNativeRCSProfileRequested==(isN || (isMobile&&!off)));++cases; } - noaccel=false;XE unknown;unknown.selectProfile(XELP::identify(0x46A68086,12)); + noaccel=false;XE unknown;unknown.selectProfile(XELP::identify(0x4c8a8086,12)); assert(!unknown.adlpRenderRequested && !unknown.adlnDetected && !unknown.tglAcceleratorRequested); - puts("PASS 1536 production profile selections: ADL-P Render/noaccel, ADL-N and TGL; physical/display identities preserved"); + assert(cases==15360);puts("PASS 15360 production profile selections: all mobile Display13 IDs/revisions, physical and legacy donor selectors, noaccel, ADL-N and TGL"); } '''.replace('FUNCTION',f) with tempfile.TemporaryDirectory(prefix='wex-adlp-render-') as d: diff --git a/tests/adlp_resources.cpp b/tests/adlp_resources.cpp index c3873899..971f40ac 100644 --- a/tests/adlp_resources.cpp +++ b/tests/adlp_resources.cpp @@ -32,7 +32,7 @@ int main() { assert(!ports.hdmi(3).hdmi); // Existing Combo policy is unchanged. assert(Display13FBTopology::xeLPDWiring(w)); assert(!Display13FBTopology::supportedWiring(w)); // Runtime stays closed until hooks are installed. - assert(!identify(0x46a68086,12).admitted()); + assert(identify(0x46a68086,12).admitted()); for(unsigned d=3;d<7;++d) { assert(VBT::ddi(Family::AlderLakeP,13+2*(d-3))==d); auto p=ports;p.present=1U<(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 15479221..e7cdbbe8 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -13,10 +13,10 @@ struct IO { }; int main() { auto candidate=identify(0x46a68086,12,0x9a498086); - assert(candidate.mobileDisplay13() && candidate.displayAdmitted() && !candidate.admitted()); - assert(candidate.family==Family::AlderLakeP && candidate.display==Display::XeLPD && candidate.gt==GT::Unknown); - assert(!identify(0x46a68086,12).displayAdmitted()); - assert(!identify(0x46a38086,12,0x9a498086).displayAdmitted()); + assert(candidate.mobileDisplay13() && candidate.displayAdmitted() && candidate.admitted()); + assert(candidate.family==Family::AlderLakeP && candidate.display==Display::XeLPD && candidate.gt==GT::ADLPMobile); + assert(identify(0x46a68086,12).displayAdmitted()); + assert(identify(0x46a38086,12,0x9a498086).displayAdmitted()); // ABI3 explicitly describes fixed TC HDMI; capability query prevents an // old provider from accepting a configuration whose implementation it lacks. @@ -49,7 +49,9 @@ 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; - assert(p.admitted()==(tgl || adln || adls || rpls)); + const bool mobile=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()); assert(!adln || p.display==Display::XeLPD); assert(!identify((id<<16)|0x1234,rev).admitted()); From 69bfa6d399aea0f25be5377f54afb586e07dd2fd Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 23:58:16 +0900 Subject: [PATCH 20/60] Scope native graphics policy registration to admitted Display13 clients --- AppleIntelTGLCompat/GraphicsPolicy.plist | 38 ++++++++++++++++++++++++ README.md | 2 +- README_ja.md | 2 +- tools/build.py | 5 ++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 AppleIntelTGLCompat/GraphicsPolicy.plist diff --git a/AppleIntelTGLCompat/GraphicsPolicy.plist b/AppleIntelTGLCompat/GraphicsPolicy.plist new file mode 100644 index 00000000..e8cf2fe7 --- /dev/null +++ b/AppleIntelTGLCompat/GraphicsPolicy.plist @@ -0,0 +1,38 @@ + + + + + WEX Display13 graphics policy + + CFBundleIdentifier + com.apple.driver.AppleGraphicsDevicePolicy + Default + + unload + + + IOClass + AppleGraphicsDevicePolicy + IOMatchCategory + AppleGraphicsDevicePolicy + IONameMatch + IntelFBClientControl + IOParentMatch + + IOPropertyMatch + + N97FramebufferBackendReady + + + IOProviderClass + AppleIntelFramebufferController + + IOProbeScore + 1000 + IOProviderClass + IntelFBClientControl + IOResourceMatch + IOKit + + + diff --git a/README.md b/README.md index 1e7ed3c3..407ed4be 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ installation or update location. Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. `-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on a selected ADL-P/RPL-P device, for load-only diagnostics without GPU initialization. `-wex-display13-offline` keeps Display 13 native controller initialization enabled but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. -ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. AGDP policies that unload the integrated GPU still require configuration before display registration. +ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. ## Build and test diff --git a/README_ja.md b/README_ja.md index ef0eb63c..bde4c515 100644 --- a/README_ja.md +++ b/README_ja.md @@ -173,7 +173,7 @@ Safariのインストール先や更新による配置変更に依存せずData 開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 `-wex-adlp-load-only` は選択済みADL-P/RPL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 `-wex-display13-offline` はDisplay 13のnative controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 -ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。IGPUをunloadするAGDP policyでは、表示登録前の設定が引き続き必要です。 +ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 ## ビルドとテスト diff --git a/tools/build.py b/tools/build.py index 75e9049f..b2eb8045 100644 --- a/tools/build.py +++ b/tools/build.py @@ -37,6 +37,10 @@ def main(): if name=='WhateverXe': libs['as.vit9696.Lilu']='1.2.0' info['IOKitPersonalities']={ident:dict(CFBundleIdentifier=ident,IOClass=name,IOMatchCategory=name,IOProviderClass='IOResources',IOResourceMatch='IOKit')} + else: + # The parent readiness property is published only by the admitted Display13 backend. + # Keep the native policy class, scoped to its Intel client and independent of SMBIOS. + info['IOKitPersonalities']=plistlib.loads((ROOT/'AppleIntelTGLCompat/GraphicsPolicy.plist').read_bytes()) (bundle/'Info.plist').write_bytes(plistlib.dumps(info)) # Preserve DWARF before removing the linker's object-path records. if name=='WhateverXe':run(['xcrun','dsymutil',exe,'-o',out/(name+'.kext.dSYM')]) @@ -49,6 +53,7 @@ def main(): assert not any(x in unresolved for x in ['chkstk_darwin','___memcpy_chk','___memmove_chk','DispatchE5IORPC']), 'userspace/DriverKit ABI imported into kernel plugin' 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'))} (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)) From 75404d1a3b2e7e98071a3faa685843c8ff054bd5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:10:44 +0900 Subject: [PATCH 21/60] Align mobile display configuration and remaining roadmap --- README.md | 16 +++++++--------- README_ja.md | 16 +++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 407ed4be..f55d6c33 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ On supported GPUs, WhateverXe also normalizes the detected Intel Management Engine device name to `IMEI`, as WhateverGreen does. A separate ACPI rename from `HECI` or `MEI` to `IMEI` is unnecessary. +ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. + ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. @@ -179,6 +181,10 @@ startup. On Display 13 it also skips GT resource probing while preserving the framebuffer's independent boot-memory and MMIO checks. It does not enable an otherwise unsupported display backend. +Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. +`-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on a selected ADL-P/RPL-P device, for load-only diagnostics without GPU initialization. +`-wex-display13-offline` keeps Display 13 native controller initialization enabled but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. + ## Tested features and known issues | Hardware | Verified features | @@ -217,17 +223,9 @@ installation or update location. ## Roadmap -- Alder Lake-H/P/U and Raptor Lake-H/P/U support. -- USB-C and internal-display support on Display 13 GPUs. +- Complete Display 13 DP, USB-C and internal-display support. - Improve display reconnection reliability and investigate full system sleep/wake. - -Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. -`-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on a selected ADL-P/RPL-P device, for load-only diagnostics without GPU initialization. -`-wex-display13-offline` keeps Display 13 native controller initialization enabled but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. -ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. - - ## Build and test Use macOS with Xcode command-line tools, Python 3.9 or newer, MacKernelSDK and diff --git a/README_ja.md b/README_ja.md index bde4c515..edae302b 100644 --- a/README_ja.md +++ b/README_ja.md @@ -90,6 +90,8 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー 対応GPUでは、WhateverGreenと同様に、検出されたIntel Management Engineのデバイス名も`IMEI`へ統一します。`HECI`や`MEI`から`IMEI`へのACPI renameを別途追加する必要はありません。 +ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 + ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 @@ -133,6 +135,10 @@ FBの立上げ時は、任意の`-wex-noaccel`引数でAcceleratorの起動を Display 13ではGT資源のprobeも省略し、FB独自の起動メモリ・MMIOの確認は維持します。 未対応のDisplayバックエンドを有効にする引数ではありません。 +開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 +`-wex-adlp-load-only` は選択済みADL-P/RPL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 +`-wex-display13-offline` はDisplay 13のnative controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 + ## 動作確認済みの機能と既知の問題 | ハードウェア | 確認済みの機能 | @@ -165,17 +171,9 @@ Safariのインストール先や更新による配置変更に依存せずData ## ロードマップ -- Alder Lake-H/P/UとRaptor Lake-H/P/Uへの対応。 -- Display 13 GPUでのUSB-C・内蔵画面対応。 +- Display 13 GPUでのDP・USB-C・内蔵画面対応の拡充。 - 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 - -開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 -`-wex-adlp-load-only` は選択済みADL-P/RPL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 -`-wex-display13-offline` はDisplay 13のnative controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 -ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 - - ## ビルドとテスト macOS、Xcode command-line tools、Python 3.9以降、MacKernelSDK、Liluの開発用ヘッダ/ライブラリを使用します。CIではTahoeのkernel定義を含むLiluの[1.7.2リリースタグ](https://github.com/acidanthera/Lilu/tree/1.7.2)を使用します。`MacKernelSDK/`と`Lilu.kext/`をリポジトリ直下に配置するか、`--sdk`と`--lilu`でパスを指定してください。 From 635bae428c00c46b97b96b9534337f5815abe9e7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 00:54:51 +0900 Subject: [PATCH 22/60] Fix Display13 cold TC ownership and isolate reversible leaf failures --- WhateverXe/kern_display13_power.hpp | 22 +++++++--- WhateverXe/kern_display13_tc.hpp | 26 +++++++----- WhateverXe/kern_display13_tc_mode.hpp | 29 ++++++++++--- WhateverXe/kern_xe.cpp | 34 ++++++++++----- tests/display13_tc.cpp | 5 ++- tests/display13_tc_mode.cpp | 59 +++++++++++++++++++++++---- 6 files changed, 134 insertions(+), 41 deletions(-) diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index e52a47ee..d036f4d9 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -49,6 +49,7 @@ enum class Status : uint32_t { struct Result { Status status {Status::NotReady}; uint32_t domain {Count}, writes {}, rollbackFailed {}, forcedOn {}; + bool isolated {}; // Failed new TC leaf, rollback and retained scopes proven. }; struct State { bool ready {}, faulted {}, suspended {}; @@ -156,7 +157,9 @@ template Status waitTCAuxHealth(IO &io,unsigned d) { } } -template Result acquire(IO &io, State &s, Lease &lease, uint32_t domains) { +// Isolation is opt-in for a caller that can propagate a failed acquisition. +// Native void getPower wrappers must retain controller-wide failure behavior. +template Result acquire(IO &io, State &s, Lease &lease, uint32_t domains,bool isolateTC=false) { Result r; if (!s.ready || s.faulted || s.suspended) return r; if (lease.owner || !domains || (domains & ~s.allowed)) { r.status = Status::InvalidDomains; return r; } @@ -173,6 +176,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai if ((domains & (1U << d)) && s.refs[d] == UINT32_MAX) { r.status = Status::Overflow; r.domain = d; return r; } + const uint32_t previousOwned=s.owned; uint32_t added = 0; r.status = Status::Complete; for (unsigned d = 0; d < Count; ++d) { @@ -211,10 +215,13 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai r.domain = Count; return r; } - // Failure is latched; no caller may continue using partially acquired - // domains. Roll back only requests made by this transaction, in reverse - // dependency order. Stop at a failed release to retain its prerequisites. - s.faulted = true; + // A newly requested, unshared TC leaf may fail without invalidating other + // outputs. Only isolate bounded ack/health timeouts after proving rollback + // and every previously held scope. Lost/borrowed/shared resources, unknown + // MMIO and failed rollback still poison the controller. + const bool local=isolateTC && (tcDDI(r.domain)||tcAux(r.domain)) && !s.refs[r.domain] && + (added&(1U< 0;) { if (!(added & (1U << d))) continue; if (clear(io, s, d, r) != Status::Complete) { @@ -222,6 +229,11 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai break; } } + uint32_t retained=0; + for(unsigned d=0;d Result release(IO &io,uintptr_t who,uintptr_t object,unsigned ddi) { diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp index 1ac793c0..af3de5ee 100644 --- a/WhateverXe/kern_display13_tc_mode.hpp +++ b/WhateverXe/kern_display13_tc_mode.hpp @@ -13,7 +13,12 @@ struct Mode { }; uintptr_t controller {},object {};unsigned ddi {}; Phase phase {Phase::Idle};bool disconnectPending {}; - Ownership ownership;Display13Power::Lease lease; + Ownership ownership; + // ADLP: register/lane access -> ownership -> AUX cold block -> DDI IO. + // An already-powered firmware link is not evidence that these scopes can + // be requested together on an unowned, cold PHY. + Display13Power::Lease accessLease,auxLease,lease; + static uint32_t accessDomains() {return Display13Power::bit(Display13Power::Domain::PW2);} Mode()=default;Mode(const Mode&)=delete;Mode& operator=(const Mode&)=delete; static uint32_t domains(unsigned d) { Port p;return Port::resolve(d,p) ? Display13Power::ddiDomain(d)| @@ -35,11 +40,15 @@ struct Mode { // 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; - r.power=io.acquireDomains(lease,domains(d)); + 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); 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.power=io.acquireDomains(lease,Display13Power::ddiDomain(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;} phase=Phase::Active;disconnectPending=false;r.status=Status::Complete;return r; @@ -52,10 +61,6 @@ struct Mode { disconnectPending=true; if(!io.quiescent(owner,identity,d)){r.status=Status::InUse;return r;} phase=Phase::Cleanup; - if(ownership.owner) { - r.phy=ownership.release(io,owner,identity,d); - if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} - } if(io.nativeMode()!=0) { r.native=io.nativeSetMode(0); if(r.native || io.nativeMode()!=0){r.status=Status::Readback;return r;} @@ -64,6 +69,18 @@ struct Mode { r.power=io.releaseDomains(lease); if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} } + if(auxLease.owner) { + r.power=io.releaseDomains(auxLease); + if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} + } + if(ownership.owner) { + r.phy=ownership.release(io,owner,identity,d); + if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} + } + if(accessLease.owner) { + 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; } }; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2d6ec2ba..e2544d94 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4662,9 +4662,9 @@ struct Display13FBTCAuxIO { void write(uint32_t r,uint32_t v) {*reinterpret_cast(map->getVirtualAddress()+r)=v;} bool lanesPowered() const { if(!admitted(tc))return false; - const auto pw=read(Display13Power::Driver),ddi=read(Display13Power::DDI),mode=read(0x161500+4*tc); - return pw!=UINT32_MAX && ddi!=UINT32_MAX && mode!=UINT32_MAX && (pw&5)==5 && - (ddi&(1U<<(2*(tc+3)))) && (mode&7)==4; + 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; } bool auxPowered(unsigned candidate) const { if(!admitted(candidate))return false;const auto value=read(Display13Power::AUX); @@ -4714,12 +4714,19 @@ struct Display13FBBootIO : Display13FBPowerIO { struct Display13FBTCIO : Display13FBTCAuxIO { OSObject *object; Display13FBTCIO(OSObject *p,unsigned t):Display13FBTCAuxIO{display13FBPower.map,t},object(p) {} - bool powerHeld() { + bool accessHeld() { if(!admitted(tc))return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; if(!object || c.tcObject[tc]!=object || mode.controller!=reinterpret_cast(c.controller) || - mode.object!=reinterpret_cast(object) || mode.lease.owner!=&c.bridge.state || - mode.lease.domains!=Display13Power::dependencies(Display13TC::Mode::domains(tc+3)))return false; - Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.lease.domains); + mode.object!=reinterpret_cast(object) || mode.accessLease.owner!=&c.bridge.state || + mode.accessLease.domains!=Display13Power::dependencies(Display13TC::Mode::accessDomains()))return false; + Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.accessLease.domains); + } + bool powerHeld() { + if(!accessHeld())return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; + if(mode.lease.owner!=&c.bridge.state || mode.auxLease.owner!=&c.bridge.state || + mode.lease.domains!=Display13Power::dependencies(Display13Power::ddiDomain(tc+3)) || + mode.auxLease.domains!=Display13Power::dependencies(Display13Power::auxDomain(tc+3)))return false; + Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,Display13TC::Mode::domains(tc+3)); } bool owned(unsigned ddi) { const auto &m=display13FBPower.tcMode[tc]; @@ -4728,7 +4735,7 @@ struct Display13FBTCIO : Display13FBTCAuxIO { Display13FBMemory::field(object,0x550)==4; } bool quiescent(unsigned ddi) { - if(ddi!=tc+3 || !powerHeld())return false; + if(ddi!=tc+3 || !accessHeld())return false; const auto buffer=read(0x64000+0x100*ddi),power=read(Display13Power::Driver); if(buffer==UINT32_MAX || power==UINT32_MAX || (buffer&0x80000000))return false; for(unsigned pipe=0;pipe<3;++pipe)if(power&(1U<<(10+2*pipe))) { @@ -6242,14 +6249,14 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { auto result=base(object,value);IOLockLock(c.lock);--c.tcBusy[tc];return result; } Display13Power::Result acquireDomains(Display13Power::Lease &lease,uint32_t domains) { - Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,lease,domains); + Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,lease,domains,true); } Display13Power::Result releaseDomains(Display13Power::Lease &lease) { Display13FBPowerIO io{map};return Display13Power::release(io,display13FBPower.bridge.state,lease); } bool powerHeld(uintptr_t controller,uintptr_t port,unsigned d) { return controller==reinterpret_cast(display13FBPower.controller) && - port==reinterpret_cast(object) && d==tc+3 && Display13FBTCIO::powerHeld(); + port==reinterpret_cast(object) && d==tc+3 && Display13FBTCIO::accessHeld(); } bool inheritedVerified(uintptr_t controller,uintptr_t port,unsigned d) { return firmware && controller==reinterpret_cast(display13FBPower.controller) && @@ -6280,6 +6287,13 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { 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}; IOLockUnlock(c.lock);auto root=IORegistryEntry::getRegistryRoot(); root->setProperty("WEXDisplay13TCMode",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) { + uint32_t power[]={1,ddi,wanted,uint32_t(result.power.status),result.power.domain, + result.power.writes,result.power.rollbackFailed,result.power.forcedOn,result.power.isolated}; + if(!root->getProperty("WEXDisplay13TCFirstPowerError"))root->setProperty("WEXDisplay13TCFirstPowerError",power,sizeof(power)); + } if(!done && !root->getProperty("WEXDisplay13TCModeFailure"))root->setProperty("WEXDisplay13TCModeFailure",record,sizeof(record)); return done ? kIOReturnSuccess : kIOReturnNotReady; } diff --git a/tests/display13_tc.cpp b/tests/display13_tc.cpp index 19509a03..c3335b68 100644 --- a/tests/display13_tc.cpp +++ b/tests/display13_tc.cpp @@ -133,7 +133,10 @@ int main(){ PHYIO io;io.regs={{0x161500,ready},{0x64300,0}};Display13TC::Ownership s; const auto r=s.acquire(io,0x100,0x200,3); assert(r.status==(ready==0?Display13TC::Status::NotReady:ready==UINT32_MAX?Display13TC::Status::Unavailable:Display13TC::Status::UnsupportedMode)); - assert(!s.owner && io.writes.empty() && (ready!=0 || io.time==500000)); + if(ready==0) { + assert(s.owner && io.writes.size()==1 && io.time==500000); + assert(s.release(io,0x100,0x200,3).status==Display13TC::Status::Complete); + } else assert(!s.owner && io.writes.empty()); } {PHYIO io;io.regs={{0x161500,4},{0x64300,0}};io.rejectWrite=true;Display13TC::Ownership s; assert(s.acquire(io,0x100,0x200,3).status==Display13TC::Status::Readback && s.owner && !s.active); diff --git a/tests/display13_tc_mode.cpp b/tests/display13_tc_mode.cpp index ab239821..782dfdc3 100644 --- a/tests/display13_tc_mode.cpp +++ b/tests/display13_tc_mode.cpp @@ -7,13 +7,26 @@ using namespace Display13TC; struct IO { Port port;std::map regs; Display13Power::State power;bool ready {true},adopt {},quiet {true},nativeFail {},auxOK {true}; - unsigned native {},time {};std::vector sequence; - IO(unsigned d){assert(Port::resolve(d,port));regs={{port.ready,4},{port.buffer,0},{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8e380000}};assert(Display13Power::initialize(*this,power,Display13Power::nativeDomains(0x7b)));} + unsigned native {},time {},failedTC {99};bool rollbackStuck {};std::vector sequence; + IO(unsigned d){assert(Port::resolve(d,port));regs={{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8e380000}}; + for(unsigned t=0;t<4;++t){regs[0x161500+4*t]=4;regs[0x64300+0x100*t]=0;} + assert(Display13Power::initialize(*this,power,Display13Power::nativeDomains(0x7b)));} bool readable(uint32_t r){return regs.count(r);} uint32_t read(uint32_t r){assert(readable(r));return regs[r];} void write(uint32_t r,uint32_t v) { assert(readable(r));sequence.push_back(r); - if(r==0x45404 || r==0x45454 || r==0x45444){auto mask=r==0x45404?0xa80aU:0x2aaaU;regs[r]=(v&~(mask>>1))|((v&mask)>>1);} + if(r==0x45404 || r==0x45454 || r==0x45444){ + const auto mask=r==0x45404?0xa80aU:0x2aaaU;auto ack=(v&mask)>>1; + // Model ADLP dependencies, not unconditional request -> state. + // AUX needs ownership; DDI IO needs the cold block already held. + if(r!=0x45404)for(unsigned t=0;t<4;++t) { + const auto state=1U<<(6+2*t),request=state<<1; + const bool phy=(regs[0x64300+0x100*t]&64) && (regs[0x161500+4*t]&4); + if(!phy || (r==0x45454 && !(regs[0x45444]&state)) || t==failedTC)ack&=~state; + if(rollbackStuck && t==failedTC && (regs[r]&request) && !(v&request))v|=request; + } + regs[r]=(v&~(mask>>1))|ack; + } else regs[r]=v; } void delayUs(unsigned t){time+=t;} @@ -21,13 +34,13 @@ struct IO { unsigned nativeMode(){return native;} unsigned nativeSetMode(unsigned v){sequence.push_back(0x550);if(nativeFail)return 1;native=v;return 0;} bool tcAuxSupported(){return true;} - bool prepareTCAux(unsigned){return true;} + bool prepareTCAux(unsigned t){return (regs[0x45404]&5)==5 && (regs[0x64300+0x100*t]&64);} bool tcAuxHealthy(unsigned){return auxOK;} - Display13Power::Result acquireDomains(Display13Power::Lease &l,uint32_t d){return Display13Power::acquire(*this,power,l,d);} + Display13Power::Result acquireDomains(Display13Power::Lease &l,uint32_t d){return Display13Power::acquire(*this,power,l,d,true);} Display13Power::Result releaseDomains(Display13Power::Lease &l){return Display13Power::release(*this,power,l);} - bool powerHeld(uintptr_t owner,uintptr_t object,unsigned d){return owner==1 && object==2 && d==port.ddi && Display13Power::held(*this,power,Mode::domains(d));} + bool powerHeld(uintptr_t owner,uintptr_t object,unsigned d){return owner==1 && object==2 && d==port.ddi && Display13Power::held(*this,power,Mode::accessDomains());} bool inheritedVerified(uintptr_t,uintptr_t,unsigned){return adopt;} - bool quiescent(uintptr_t,uintptr_t,unsigned){return quiet;} + bool quiescent(uintptr_t,uintptr_t,unsigned d){return quiet && !(regs[0x64000+0x100*d]&0x80000000U);} }; int main() { for(unsigned d=3;d<=6;++d) { @@ -48,13 +61,41 @@ int main() { {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040;io.adopt=true; assert(m.connect(io,1,2,3).status==Status::Complete);assert(io.regs[io.port.buffer]==0x80000040);} {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040; - assert(m.connect(io,1,2,3).status==Status::NeedsAdoption && m.phase==Mode::Phase::Cleanup && m.lease.owner); + assert(m.connect(io,1,2,3).status==Status::NeedsAdoption && m.phase==Mode::Phase::Cleanup && m.accessLease.owner && !m.lease.owner); assert(!io.native && m.connect(io,1,2,3).status==Status::InUse);} {IO io(3);Mode m;io.nativeFail=true; assert(m.connect(io,1,2,3).status==Status::Readback && m.ownership.owner && m.lease.owner); io.nativeFail=false;assert(m.disconnect(io,1,2,3).status==Status::Complete);} {IO io(3);Mode m;io.auxOK=false; - assert(m.connect(io,1,2,3).status==Status::NoPower && !m.ownership.owner && !io.native);} + assert(m.connect(io,1,2,3).status==Status::NoPower && m.ownership.owner && !io.native && !io.power.faulted); + assert(m.disconnect(io,1,2,3).status==Status::Complete && !m.accessLease.owner && !m.auxLease.owner); + io.auxOK=true;assert(m.connect(io,1,2,3).status==Status::Complete);} + {IO io(3);Mode m;io.regs[0x45404]=15;io.regs[0x45454]=io.regs[0x45444]=0xc0; + io.regs[io.port.buffer]=64;io.adopt=true; + assert(m.connect(io,1,2,3).status==Status::Complete && !io.power.owned); + assert(m.disconnect(io,1,2,3).status==Status::Complete); + assert(io.regs[0x45404]==15 && io.regs[0x45454]==0xc0 && io.regs[0x45444]==0xc0);} {IO io(3);Mode m;io.ready=false;assert(m.connect(io,1,2,3).status==Status::NotReady && io.sequence.empty());} + // Cold second instances share PW1/PW2, not the first port's ownership. + for(unsigned second=4;second<=6;++second) { + IO io(3);Mode first,other;assert(first.connect(io,1,2,3).status==Status::Complete); + io.regs[0x64300]|=1U<<31;const auto neighbor=io.regs[0x64300]; + assert(Port::resolve(second,io.port));io.native=0; + assert(other.connect(io,1,2,second).status==Status::Complete); + assert(other.disconnect(io,1,2,second).status==Status::Complete && !other.accessLease.owner); + assert(io.regs[0x64300]==neighbor && (io.regs[0x45444]&0xc0)==0xc0 && (io.regs[0x45454]&0xc0)==0xc0); + assert(Display13Power::held(io,io.power,Mode::domains(3))); + io.failedTC=second-3; + const auto failure=other.connect(io,1,2,second); + assert(failure.status==Status::NoPower && failure.power.status==Display13Power::Status::StateTimeout); + assert(failure.power.domain==12+2*(second-3) && failure.power.isolated && !io.power.faulted); + assert(Display13Power::held(io,io.power,Mode::domains(3))); + assert(other.disconnect(io,1,2,second).status==Status::Complete); + io.failedTC=99;assert(other.connect(io,1,2,second).status==Status::Complete); + } + {IO io(4);Mode m;io.failedTC=1;io.rollbackStuck=true; + const auto r=m.connect(io,1,2,4); + assert(r.status==Status::NoPower && r.power.rollbackFailed && io.power.faulted && m.accessLease.owner); + assert(m.disconnect(io,1,2,4).status!=Status::Complete);} puts("PASS Display13 legacy TC mode lifecycle: power/ownership/native order, adoption, retries and pending cleanup"); } From 4ed65c4134cfb0a6d234c8418bd77a745aaa88f6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 01:04:37 +0900 Subject: [PATCH 23/60] Select fixed Display13 TC routing by physical capabilities --- README.md | 2 ++ README_ja.md | 2 ++ WhateverXe/kern_display13_fb_topology.hpp | 5 ++++ WhateverXe/kern_display13_hdmi_wiring.hpp | 4 +-- WhateverXe/kern_xe.cpp | 33 +++++++++++------------ tests/adlp_fb_candidate.py | 17 +++++++++--- tests/display13_tiling.py | 8 ++++-- 7 files changed, 46 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f55d6c33..7e6cc5ce 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ On supported GPUs, WhateverXe also normalizes the detected Intel Management Engine device name to `IMEI`, as WhateverGreen does. A separate ACPI rename from `HECI` or `MEI` to `IMEI` is unnecessary. +Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Display13 DP runtime admission remains disabled pending completion of AUX, link training and boot handoff integration. + ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. ### Internal-panel brightness diff --git a/README_ja.md b/README_ja.md index edae302b..5678580a 100644 --- a/README_ja.md +++ b/README_ja.md @@ -90,6 +90,8 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー 対応GPUでは、WhateverGreenと同様に、検出されたIntel Management Engineのデバイス名も`IMEI`へ統一します。`HECI`や`MEI`から`IMEI`へのACPI renameを別途追加する必要はありません。 +Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。Display13のDPはAUX・link training・起動時引継ぎの統合が未完のため、実行時の受付を閉じています。 + ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 ### 内蔵パネルの輝度 diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 7b49ed26..e7887e9b 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -20,6 +20,11 @@ inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false) { // TC provider capability, legacy mode and clock hooks are checked separately. return !w.dp && 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. +inline bool deferredBootPrepare(const Display13OutputWiring &w,bool mobile) { + return mobile || (w.hdmi&0x78); +} inline bool multiOutputWiring(const Display13OutputWiring &w) { return selectedWiring(w) && (w.selected()&(w.selected()-1)); } diff --git a/WhateverXe/kern_display13_hdmi_wiring.hpp b/WhateverXe/kern_display13_hdmi_wiring.hpp index 158cc8ce..890d0343 100644 --- a/WhateverXe/kern_display13_hdmi_wiring.hpp +++ b/WhateverXe/kern_display13_hdmi_wiring.hpp @@ -1,6 +1,6 @@ -// ADL-N fixed HDMI uses the shared Combo-only board property. +// The legacy explicit HDMI property selects Combo wiring only. // Native nine-port identity and three-pipe resources are managed separately. -// TC HDMI has been deferred; fixed DP requires its own transport policy. +// Firmware-selected Display13 routes also support fixed TC HDMI. #ifndef KERN_DISPLAY13_HDMI_WIRING_HPP #define KERN_DISPLAY13_HDMI_WIRING_HPP // Compatibility name for the version1 HDMI property and older callers. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e2544d94..34b93e1d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3795,15 +3795,14 @@ void XE::deinit() {} Display13OutputWiring XE::display13BoardWiring(IOService *provider) { auto property=provider->getProperty("xelp-hdmi-wiring"); if(!property)property=provider->getProperty("tgl-hdmi-wiring"); - if(adlpDisplaySelected) { - // VBT remains the authority for pin/transport. No Combo property is - // silently interpreted as a TC policy. Select every supported fixed HDMI - // route from firmware; connection state is checked by the provider. - if(property)return {}; - return XELP::VBT::display13HDMI(firmwarePorts,Display13OutputWiring::Physical); - } + 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::display13HDMI(firmwarePorts,Display13OutputWiring::Physical); + // Preserve the older ADL-N Combo property contract. It cannot select TC. + if(adlpDisplaySelected)return {}; auto wiring=OSDynamicCast(OSData,property); - return XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, + return XELP::VBT::selectHDMI(true,wiring?wiring->getBytesNoCopy():nullptr, wiring?wiring->getLength():0,firmwarePorts,3); } @@ -3825,7 +3824,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,adlpDisplaySelected)) { + if(displayRequested && !Display13FBTopology::supportedWiring(board,xeProfile.display==XELP::Display::XeLPD)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } auto bootDDI=displayRequested ? UINT32_MAX : 1U; @@ -3887,9 +3886,9 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97DisplayReadoutStatus", static_cast(result.status), 32); SYSLOG("igfx", "AlderLakeN Display13 readout status=%u powered=%x active=%x hdmiB=%x; donor blocked", static_cast(result.status), result.poweredPipes, result.activePipes, result.hdmiBPipes); - // ADL-P adopts a validated firmware surface below. A destructive plane - // exercise before native controller start is not a startup prerequisite. - if (linearTest && !adlpDisplaySelected) { + // Mobile and TC configurations adopt a validated firmware surface at + // controller start. Preserve the established ADL-N Combo bootstrap. + if (linearTest && !Display13FBTopology::deferredBootPrepare(board,adlpDisplaySelected)) { auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); if(bootDDI>=3)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI, @@ -4099,7 +4098,7 @@ bool XE::xelpBindProbe(void *controller) { config.legacyBackend=tglAUXParentMask; } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || - !Display13FBTopology::supportedWiring(adlnOutputWiring,adlpDisplaySelected) || + !Display13FBTopology::supportedWiring(adlnOutputWiring,xeProfile.display==XELP::Display::XeLPD) || ((adlnOutputWiring.hdmi&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3; @@ -6152,7 +6151,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->adlpDisplaySelected) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->xeProfile.display==XELP::Display::XeLPD) || !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; @@ -7406,9 +7405,9 @@ 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->adlpDisplaySelected) || + if(!Display13FBTopology::supportedWiring(board,s->xeProfile.display==XELP::Display::XeLPD) || board.selected()!=s->adlnOutputWiring.selected())return false; - if(s->adlpDisplaySelected && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { + if((board.hdmi&0x78) && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBTCUnavailable",true);return false; } const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); @@ -7443,7 +7442,7 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { (provider->getProperty("AAPL,GfxYTile") || provider->setProperty("AAPL,GfxYTile",&enabled,sizeof(enabled))); OSSafeReleaseNULL(yTiling);OSSafeReleaseNULL(mode);OSSafeReleaseNULL(features); if(!featuresSelected)return false; - if(s->adlpDisplaySelected && !s->prepareDisplay13BootScanout(pci))return false; + if(Display13FBTopology::deferredBootPrepare(board,s->adlpDisplaySelected) && !s->prepareDisplay13BootScanout(pci))return false; SYSLOG("igfx","Display13 native controller start enter offline=%u",display13Offline); const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgDisplay13FramebufferStart)(that,provider); SYSLOG("igfx","Display13 native controller start return=%u",started); diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 11a18af9..03de9d3e 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -8,10 +8,11 @@ #include #include #include +#include #include "kern_display13_firmware.hpp" #include "kern_display13_fb_topology.hpp" #include "kern_alderlake_s.hpp" -struct OSData {void *getBytesNoCopy(){return nullptr;}unsigned getLength(){return 0;}}; +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;}}; struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}}; @@ -51,14 +52,17 @@ assert(x.display13BoardWiring(&pci).hdmi==0x10); // Exclude DP without dropping unrelated HDMI. x.firmwarePorts.port[4].type=4;assert(!x.display13BoardWiring(&pci).described()); unsigned cases=0; + for(unsigned identity : {0x46A68086U,0x46D18086U}) for(unsigned mask=1;mask<128;++mask)if(!(mask&~Display13OutputWiring::Physical)) { - XE each;each.firmwarePorts.status=XELP::VBT::Status::Valid;each.firmwarePorts.present=mask; + XE each;each.xeProfile=XELP::identify(identity,12);each.adlpDisplaySelected=each.xeProfile.mobileDisplay13(); + each.firmwarePorts.status=XELP::VBT::Status::Valid;each.firmwarePorts.present=mask; for(unsigned d=0;d<7;++d)if(mask&(1U< #include +#include "kern_display13_fb_topology.hpp" +Display13OutputWiring board; #include #include #include @@ -61,7 +63,7 @@ if(oldKey)provider.items["AAPL,GfxYTile"]=&key; step=0;failAt=failure;handoff.calls=0;bool ok=prepare(&that,&provider); if(failure || !workLoop){assert(!ok && !handoff.calls);return;} - assert(handoff.calls==1); + assert(handoff.calls==unsigned(handoff.adlpDisplaySelected || (board.hdmi&0x78))); assert(ok);auto f=dynamic_cast(that.getProperty("FeatureControl"));assert(f); assert(dynamic_cast(f->items.at("YTiling"))->value==1); assert(dynamic_cast(f->items.at("ThreadingMode"))->value==0); @@ -72,7 +74,9 @@ int main(){ {IOService that,provider;handoff.ready=false;assert(!prepare(&that,&provider));assert(handoff.calls==1);handoff.ready=true;} + for(bool mobile:{false,true})for(unsigned mask:{2U,8U}) for(bool features:{false,true})for(bool key:{false,true})for(uint32_t value:{0U,1U}){ + handoff.adlpDisplaySelected=mobile;board.hdmi=mask; check(features,key,value,0);unsigned operations=step; for(unsigned failure=1;failure<=operations;++failure)check(features,key,value,failure); check(features,key,value,0,false); @@ -81,6 +85,6 @@ '''.replace('PRODUCTION',block) with tempfile.TemporaryDirectory() as tmp: p=Path(tmp);(p/'test.cpp').write_text(code) - subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) subprocess.run([str(p/'test')],check=True) print('PASS: pre-start Y selection, existing-property preservation and setup failure propagation') From 8aedae7a775ebb49474d34622909c9e48b7e61ac Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 01:17:22 +0900 Subject: [PATCH 24/60] Acquire fixed TC DDI power after enabling its PLL --- WhateverXe/kern_display13_power_bridge.hpp | 9 +++-- WhateverXe/kern_display13_tc_clock.hpp | 12 ++++++ WhateverXe/kern_display13_tc_mode.hpp | 11 ++---- WhateverXe/kern_display13_tc_native.hpp | 5 +++ WhateverXe/kern_xe.cpp | 45 ++++++++++++++++++---- tests/display13_power_bridge.cpp | 5 +++ tests/display13_tc_clock.cpp | 20 ++++++++++ tests/display13_tc_mode.cpp | 19 ++++----- 8 files changed, 98 insertions(+), 28 deletions(-) diff --git a/WhateverXe/kern_display13_power_bridge.hpp b/WhateverXe/kern_display13_power_bridge.hpp index 39ec311a..a14d8bf4 100644 --- a/WhateverXe/kern_display13_power_bridge.hpp +++ b/WhateverXe/kern_display13_power_bridge.hpp @@ -93,13 +93,16 @@ struct NativeBridge { template bool ddiHeld(IO &io,unsigned port) const { return retainsDDI(port) && held(io,state,ddiDomain(port)); } - template bool outputHeld(IO &io,unsigned port,unsigned pipe) const { - if(pipe>=3 || !retainsDDI(port))return false; + template bool pipeHeld(IO &io,unsigned pipe) const { + if(pipe>=3)return false; const auto domain=bit(static_cast(static_cast(Domain::PipeA)+pipe)); bool retained=false; for(unsigned pg=0;pg<4;++pg)if(pgRefs[pg] && pgLease[pg].owner==&state && pgLease[pg].domains==dependencies(nativeScope(pg+1)) && (pgLease[pg].domains&domain))retained=true; - return retained && held(io,state,domain|ddiDomain(port)); + return retained && held(io,state,domain); + } + template bool outputHeld(IO &io,unsigned port,unsigned pipe) const { + return pipeHeld(io,pipe) && ddiHeld(io,port); } template Result begin(IO &io, unsigned port=1, uint32_t ports=2) { diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp index 1adbe014..fc4e3a89 100644 --- a/WhateverXe/kern_display13_tc_clock.hpp +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -154,6 +154,18 @@ struct Clock { return rmw(io,p.pll,Enable,Enable) && wait(io,p.pll,Locked,Locked) && rmw(io,p.clock,0xf0000000,0x80000000) && rmw(io,Gate,gateBit(p.tc),0); } + // Both Linux and the exact donor acquire DDI IO after enabling the PLL. + // Stop the link in reverse order. A control/AUX lease alone is sufficient + // to access the PLL; it must never require the output it is powering up. + template static bool startLink(IO &io,const Port &p,const TMDSClock &plan,uint32_t preset) { + return plan.valid() && preflight(io,p) && io.linkPower(false) && + disable(io,p) && enable(io,p,plan) && io.linkPower(true) && + TMDSSignal::program(io,p,preset); + } + template static bool stopLink(IO &io,const Port &p) { + return io.unstarted() || (preflight(io,p) && io.linkPower(false) && disable(io,p)); + } + }; } #endif diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp index af3de5ee..1b181f88 100644 --- a/WhateverXe/kern_display13_tc_mode.hpp +++ b/WhateverXe/kern_display13_tc_mode.hpp @@ -14,10 +14,11 @@ struct Mode { uintptr_t controller {},object {};unsigned ddi {}; Phase phase {Phase::Idle};bool disconnectPending {}; Ownership ownership; - // ADLP: register/lane access -> ownership -> AUX cold block -> DDI IO. + // 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 // be requested together on an unowned, cold PHY. - Display13Power::Lease accessLease,auxLease,lease; + Display13Power::Lease accessLease,auxLease; static uint32_t accessDomains() {return Display13Power::bit(Display13Power::Domain::PW2);} Mode()=default;Mode(const Mode&)=delete;Mode& operator=(const Mode&)=delete; static uint32_t domains(unsigned d) { @@ -47,8 +48,6 @@ struct Mode { 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.power=io.acquireDomains(lease,Display13Power::ddiDomain(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;} phase=Phase::Active;disconnectPending=false;r.status=Status::Complete;return r; @@ -65,10 +64,6 @@ struct Mode { r.native=io.nativeSetMode(0); if(r.native || io.nativeMode()!=0){r.status=Status::Readback;return r;} } - if(lease.owner) { - r.power=io.releaseDomains(lease); - if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} - } if(auxLease.owner) { r.power=io.releaseDomains(auxLease); if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp index 25b90209..2de6ecd4 100644 --- a/WhateverXe/kern_display13_tc_native.hpp +++ b/WhateverXe/kern_display13_tc_native.hpp @@ -19,6 +19,11 @@ static constexpr Entry entries[]={ {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48bb6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x48,0x8b,0x05}}, {"__ZN31AppleIntelFramebufferController23enableDataIslandPacketsEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52106,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0xff,0x05,0x11,0xdd,0x08,0x00,0x49,0x89,0xd6}}, }; +// Native clock replacements retain these DDI reference boundaries. +static constexpr uint32_t ddiEnableTailOffset=0x6d7a8; +static constexpr uint8_t ddiEnableTail[]={0xe9,0x51,0x27,0xfb,0xff}; +static constexpr uint32_t ddiDisableCallOffset=0x6db04; +static constexpr uint8_t ddiDisableCall[]={0xe8,0x4b,0x24,0xfb,0xff}; static constexpr uint32_t lightBodyOffset=0x50b22; static constexpr uint8_t lightBody[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6,0x49,0x89,0xff,0x48,0xff,0x05,0xd8,0xef,0x08,0x00,0xe8,0x6f,0xc1,0x01,0x00,0x49,0x8b,0xbf,0x40,0x0c,0x00,0x00,0x48,0xff,0x05,0x65,0xdb,0x08,0x00,0x8b,0x83,0x40,0x36,0x00,0x00,0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x8b,0x93,0x28,0x01,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00,0x48,0x8b,0xbb,0xc8,0x32,0x00,0x00,0x31,0xf6,0xe8,0x8b,0x05,0xfb,0xff,0x48,0x8b,0xbb,0xc0,0x32,0x00,0x00,0x48,0x89,0xde,0xe8,0x48,0x54,0x01,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0x4c,0x89,0xe1,0xe8,0x19,0x80,0xff,0xff,0x48,0xff,0x05,0x94,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0x31,0xf6,0x31,0xd2,0x31,0xc9,0xff,0x90,0x40,0x01,0x00,0x00,0x48,0xff,0x05,0x77,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x28,0x01,0x00,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0xe8,0x27,0x15,0x00,0x00,0x31,0xc0,0x5b,0x41,0x5c,0x41,0x5e,0x41,0x5f,0x5d,0xc3}; static constexpr uint32_t innerBeforeOffset=0x4ffd9; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 34b93e1d..dd1e06dd 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4722,10 +4722,37 @@ struct Display13FBTCIO : Display13FBTCAuxIO { } bool powerHeld() { if(!accessHeld())return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; - if(mode.lease.owner!=&c.bridge.state || mode.auxLease.owner!=&c.bridge.state || - mode.lease.domains!=Display13Power::dependencies(Display13Power::ddiDomain(tc+3)) || + if(mode.auxLease.owner!=&c.bridge.state || mode.auxLease.domains!=Display13Power::dependencies(Display13Power::auxDomain(tc+3)))return false; - Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,Display13TC::Mode::domains(tc+3)); + Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.auxLease.domains); + } + bool linkHeld() { + Display13FBPowerIO io{map};return powerHeld() && display13FBPower.bridge.ddiHeld(io,tc+3); + } + // Native enableClocks/disableClocks also own the DDI reference. Our + // replacement must reproduce that contract after/before the PLL change. + bool linkPower(bool enable) { + auto &c=display13FBPower;Display13FBPowerIO io{map};const auto ddi=tc+3; + if(!Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60))return false; + Display13Power::Result r;r.status=Display13Power::Status::Complete; + if(enable) { + if(!c.bridge.retainsDDI(ddi))r=c.bridge.getDDI(io,ddi); + } else if(c.bridge.nativeDDIRefs(ddi)) { + if(c.bridge.nativeDDIRefs(ddi)!=1)return false; + // The protected GOP ref is transferred by adoptBootPath, not us. + if(!(c.bridge.bootPort==ddi && c.bridge.bootDDIProtected))r=c.bridge.putDDI(io,ddi); + } + return r.status==Display13Power::Status::Complete && + Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0xC60); + } + bool unstarted() { + // A failed mode acquisition never started a PLL. Cleanup must not + // demand full output power or turn that known local failure global. + auto &c=display13FBPower;Display13TC::Port p; + if(!accessHeld() || !Display13TC::Port::resolve(tc+3,p) || c.bridge.nativeDDIRefs(tc+3) || + !quiescent(tc+3))return false; + const auto pll=read(p.pll); + return pll!=UINT32_MAX && !(pll&0xcc000000U) && !c.tcSignalReady[tc]; } bool owned(unsigned ddi) { const auto &m=display13FBPower.tcMode[tc]; @@ -6309,8 +6336,7 @@ void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); // Native modeset must first stop the old scanout; this transaction // may then retire its PLL before programming a different timing. - ok=plan.valid() && Display13TC::Clock::disable(io,p) && Display13TC::Clock::enable(io,p,plan) && - Display13TC::TMDSSignal::program(io,p,self->firmwarePorts.port[ddi].hdmiPreset); + ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset); c.tcSignalReady[tc]=ok; } if(!ok)adlnFault(c.clockFaulted,__LINE__); @@ -6324,7 +6350,8 @@ void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port,true);bool ok=false; if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { Display13FBTCIO io{port,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); - ok=Display13TC::Clock::disable(io,p);if(ok)c.tcSignalReady[ddi-3]=false; + ok=Display13TC::Clock::stopLink(io,p); + if(ok)c.tcSignalReady[ddi-3]=false; } if(!ok)adlnFault(c.clockFaulted,__LINE__); uint32_t record[]={1,ddi,ok};IOLockUnlock(c.lock); @@ -6337,7 +6364,7 @@ uint32_t XE::wrapDisplay13TCSignal(void *raw,uint32_t swing,uint32_t emphasis,vo if(!c.lock)return kIOReturnNotReady;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); // TMDS signal parameters were programmed while the path was stopped, // before native enablePipe. Do not run TGL DP's late voltage sequence. - bool ok=false;if(ddi<7){Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && c.tcSignalReady[ddi-3];} + bool ok=false;if(ddi<7){Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && io.linkHeld() && c.tcSignalReady[ddi-3];} if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); return ok ? 0 : kIOReturnNotReady; } @@ -6363,7 +6390,7 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c scaler && scaler->getMetaClass()->getClassSize()>=0x58 && Display13FBMemory::field(scaler,0x10)==fb && Display13FBMemory::field(scaler,0x28)==c.access && !c.memoryFaulted && !c.watermarkFaulted && !c.clockStopped && !c.tcBusy[ddi-3] && - c.bridge.outputHeld(power,ddi,pipeIndex); + (clock ? c.bridge.outputHeld(power,ddi,pipeIndex) : c.bridge.pipeHeld(power,pipeIndex)); if(ok){Display13FBTCIO tc{object,ddi-3};ok=tc.owned(ddi) && (!clock || c.tcSignalReady[ddi-3]);} IOLockUnlock(c.lock);return ok; } @@ -6409,6 +6436,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[]={ + {ddiEnableTailOffset,ddiEnableTail,sizeof(ddiEnableTail)}, + {ddiDisableCallOffset,ddiDisableCall,sizeof(ddiDisableCall)}, {lightBodyOffset,lightBody,sizeof(lightBody)},{innerBeforeOffset,innerBefore,sizeof(innerBefore)}, {outerBeforeOffset,outerBefore,sizeof(outerBefore)},{modeExitOffset,modeExit,sizeof(modeExit)}, {innerCallOffset,innerCall,sizeof(innerCall)},{outerCallOffset,outerCall,sizeof(outerCall)}, diff --git a/tests/display13_power_bridge.cpp b/tests/display13_power_bridge.cpp index 4ca59fad..8cee27bc 100644 --- a/tests/display13_power_bridge.cpp +++ b/tests/display13_power_bridge.cpp @@ -30,6 +30,11 @@ static void slots(const NativeBridge &b, uint32_t a,uint32_t two,uint32_t three, b.nativeRefs(3)==three && b.nativeRefs(4)==four); } int main() { + { IO io;NativeBridge b;assert(ok(b.begin(io)) && ok(b.adoptBootPath(1,0,1))); + assert(b.pipeHeld(io,0) && b.outputHeld(io,1,0)); + assert(ok(b.putDDI(io,1)) && b.pipeHeld(io,0) && !b.outputHeld(io,1,0)); + assert(!b.pipeHeld(io,1) && !b.pipeHeld(io,3)); + assert(ok(b.getDDI(io,1)) && b.outputHeld(io,1,0)); } // Resource leases depend on selected physical ports, not HDMI labels. // Start cold, drop the peer before the boot route, and require all counts // to return to zero for each fixed HDMI/DP assignment and boot choice. diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index 94f752d8..a18dc3bb 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -9,6 +9,13 @@ struct IO { std::vector> writes; bool held {},power {true},owner {true},idle {true},stuckPower {},stuckLock {},fault {}; unsigned time {},locks {},unlocks {};uint32_t reject {},absent {}; + bool link {},failLink {},coldCleanup {};unsigned linkGets {},linkPuts {}; + bool linkPower(bool enable) { + if(enable){assert((regs[p.pll]&0xcc000000U)==0xcc000000U && !(regs[Clock::Gate]&Clock::gateBit(p.tc)));++linkGets;if(failLink)return false;link=true;} + else {if(link){assert(regs[p.pll]&Clock::Enable);++linkPuts;}link=false;} + return true; + } + bool unstarted(){return coldCleanup && !link && !(regs[p.pll]&0xcc000000U);} IO(unsigned ddi) { assert(Port::resolve(ddi,p)); regs={{p.pll,0x20000000},{p.clock,0x1234},{p.ready,0xc},{p.buffer,0xc0}, @@ -96,5 +103,18 @@ int main() { {IO io(3);io.stuckPower=true;assert(!Clock::enable(io,io.p,full) && io.time==1000 && io.writes.size()==1);} {IO io(3);io.stuckLock=true;assert(!Clock::enable(io,io.p,full) && io.time==1000);assert(!(io.regs[io.p.clock]&0xf0000000));} {IO io(3);io.reject=io.p.phyRegister(0x2200);auto bank=io.regs[io.p.hip];assert(!Clock::enable(io,io.p,full));assert(io.regs[io.p.hip]==bank && !(io.regs[io.p.pll]&Clock::Enable));} + for(unsigned d=3;d<=6;++d) { + IO io(d);for(unsigned lane=0;lane<2;++lane)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[lane*0x1000+reg]=0; + // Cold control/AUX is held, but DDI IO is off until the PLL is locked. + assert(Clock::startLink(io,io.p,full,7) && io.link && io.linkGets==1); + assert(Clock::stopLink(io,io.p) && !io.link && io.linkPuts==1); + assert(Clock::startLink(io,io.p,hd,7) && io.linkGets==2); + assert(Clock::stopLink(io,io.p) && io.linkPuts==2); + } + {IO io(4);io.stuckLock=true;assert(!Clock::startLink(io,io.p,full,7) && !io.linkGets);} + {IO io(4);io.failLink=true;assert(!Clock::startLink(io,io.p,full,7) && io.linkGets==1 && !io.link);} + {IO io(4);io.power=io.owner=false;io.coldCleanup=true;const auto count=io.writes.size(); + assert(Clock::stopLink(io,io.p) && io.writes.size()==count); // local acquisition failure, PLL never started + io.regs[io.p.pll]|=Clock::Enable;assert(!Clock::stopLink(io,io.p));} puts("PASS Display13 TC HDMI PLL live fixtures, clock sweep, reserved bits, bounded failures and lifecycle"); } diff --git a/tests/display13_tc_mode.cpp b/tests/display13_tc_mode.cpp index 782dfdc3..717b5200 100644 --- a/tests/display13_tc_mode.cpp +++ b/tests/display13_tc_mode.cpp @@ -45,26 +45,27 @@ struct IO { int main() { for(unsigned d=3;d<=6;++d) { IO io(d);Mode m;assert(m.connect(io,1,2,d).status==Status::Complete); - assert(m.phase==Mode::Phase::Active && io.native==4 && m.ownership.active && m.lease.owner==&io.power); + assert(m.phase==Mode::Phase::Active && io.native==4 && m.ownership.active && m.auxLease.owner==&io.power); assert(io.sequence.back()==0x550 && io.regs[io.port.buffer]&64); + assert(!(io.power.owned & Display13Power::ddiDomain(d))); // DDI waits for PLL enable. auto count=io.sequence.size();assert(m.connect(io,1,2,d).status==Status::Complete && io.sequence.size()==count); assert(m.connect(io,1,3,d).status==Status::InUse && io.sequence.size()==count); - io.quiet=false;assert(m.disconnect(io,1,2,d).status==Status::InUse && m.disconnectPending && m.lease.owner); + io.quiet=false;assert(m.disconnect(io,1,2,d).status==Status::InUse && m.disconnectPending && m.auxLease.owner); assert(m.connect(io,1,2,d).status==Status::Complete && !m.disconnectPending); io.quiet=true;io.regs[io.port.buffer]|=1U<<31; - assert(m.disconnect(io,1,2,d).status==Status::InUse && m.lease.owner); + assert(m.disconnect(io,1,2,d).status==Status::InUse && m.auxLease.owner); io.regs[io.port.buffer]&=~(1U<<31); assert(m.disconnect(io,1,2,d).status==Status::Complete); - assert(m.phase==Mode::Phase::Idle && !m.ownership.owner && !m.lease.owner && !io.native); + assert(m.phase==Mode::Phase::Idle && !m.ownership.owner && !m.auxLease.owner && !io.native); assert(m.connect(io,1,2,d).status==Status::Complete);assert(m.disconnect(io,1,2,d).status==Status::Complete); } {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040;io.adopt=true; assert(m.connect(io,1,2,3).status==Status::Complete);assert(io.regs[io.port.buffer]==0x80000040);} {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040; - assert(m.connect(io,1,2,3).status==Status::NeedsAdoption && m.phase==Mode::Phase::Cleanup && m.accessLease.owner && !m.lease.owner); + assert(m.connect(io,1,2,3).status==Status::NeedsAdoption && m.phase==Mode::Phase::Cleanup && m.accessLease.owner && !m.auxLease.owner); assert(!io.native && m.connect(io,1,2,3).status==Status::InUse);} {IO io(3);Mode m;io.nativeFail=true; - assert(m.connect(io,1,2,3).status==Status::Readback && m.ownership.owner && m.lease.owner); + assert(m.connect(io,1,2,3).status==Status::Readback && m.ownership.owner && m.auxLease.owner); io.nativeFail=false;assert(m.disconnect(io,1,2,3).status==Status::Complete);} {IO io(3);Mode m;io.auxOK=false; assert(m.connect(io,1,2,3).status==Status::NoPower && m.ownership.owner && !io.native && !io.power.faulted); @@ -83,13 +84,13 @@ int main() { assert(Port::resolve(second,io.port));io.native=0; assert(other.connect(io,1,2,second).status==Status::Complete); assert(other.disconnect(io,1,2,second).status==Status::Complete && !other.accessLease.owner); - assert(io.regs[0x64300]==neighbor && (io.regs[0x45444]&0xc0)==0xc0 && (io.regs[0x45454]&0xc0)==0xc0); - assert(Display13Power::held(io,io.power,Mode::domains(3))); + assert(io.regs[0x64300]==neighbor && (io.regs[0x45444]&0xc0)==0xc0 && !(io.regs[0x45454]&0xc0)); + assert(Display13Power::held(io,io.power,Display13Power::auxDomain(3))); io.failedTC=second-3; const auto failure=other.connect(io,1,2,second); assert(failure.status==Status::NoPower && failure.power.status==Display13Power::Status::StateTimeout); assert(failure.power.domain==12+2*(second-3) && failure.power.isolated && !io.power.faulted); - assert(Display13Power::held(io,io.power,Mode::domains(3))); + assert(Display13Power::held(io,io.power,Display13Power::auxDomain(3))); assert(other.disconnect(io,1,2,second).status==Status::Complete); io.failedTC=99;assert(other.connect(io,1,2,second).status==Status::Complete); } From 7ede901cc4c034b31e3248a10b63a6764a61e35d Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 01:29:33 +0900 Subject: [PATCH 25/60] Initialize modular FIA lanes before Display 13 TC HDMI clocks --- WhateverXe/kern_display13_tc_clock.hpp | 27 ++++++++++++++++++++------ WhateverXe/kern_xe.cpp | 12 +++++++----- tests/display13_tc_clock.cpp | 12 ++++++++++-- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp index fc4e3a89..13a01c6e 100644 --- a/WhateverXe/kern_display13_tc_clock.hpp +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -154,13 +154,28 @@ struct Clock { return rmw(io,p.pll,Enable,Enable) && wait(io,p.pll,Locked,Locked) && rmw(io,p.clock,0xf0000000,0x80000000) && rmw(io,Gate,gateBit(p.tc),0); } + // ADLP uses modular FIA, two TC ports per instance (intel_tc.c). + // Fixed HDMI uses all four main-link lanes. PHY ownership registers are + // generation-specific, but the FIA lane-enable register is still required. + static uint32_t laneRegister(unsigned tc) {return tc<2?0x1638c0:0x16e8c0;} + static uint32_t laneMask(unsigned tc) {return 15U<<(4*(tc&1));} + template static bool lanes(IO &io,const Port &p) { + return preflight(io,p) && io.readable(laneRegister(p.tc)) && + rmw(io,laneRegister(p.tc),laneMask(p.tc),laneMask(p.tc)); + } + enum class LinkStage : uint32_t {Preflight,ReleaseDDI,StopPLL,Lanes,PLL,DDI,Signal,Complete}; // Both Linux and the exact donor acquire DDI IO after enabling the PLL. - // Stop the link in reverse order. A control/AUX lease alone is sufficient - // to access the PLL; it must never require the output it is powering up. - template static bool startLink(IO &io,const Port &p,const TMDSClock &plan,uint32_t preset) { - return plan.valid() && preflight(io,p) && io.linkPower(false) && - disable(io,p) && enable(io,p,plan) && io.linkPower(true) && - TMDSSignal::program(io,p,preset); + // Control/AUX power permits PLL access; output power cannot be its prerequisite. + template static bool startLink(IO &io,const Port &p,const TMDSClock &plan,uint32_t preset,LinkStage *stage=nullptr) { + auto step=[&](LinkStage s){if(stage)*stage=s;}; + step(LinkStage::Preflight);if(!plan.valid() || !preflight(io,p))return false; + step(LinkStage::ReleaseDDI);if(!io.linkPower(false))return false; + step(LinkStage::StopPLL);if(!disable(io,p))return false; + step(LinkStage::Lanes);if(!lanes(io,p))return false; + step(LinkStage::PLL);if(!enable(io,p,plan))return false; + step(LinkStage::DDI);if(!io.linkPower(true))return false; + step(LinkStage::Signal);if(!TMDSSignal::program(io,p,preset))return false; + 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)); diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index dd1e06dd..2a2cabe9 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4711,7 +4711,7 @@ struct Display13FBBootIO : Display13FBPowerIO { } }; struct Display13FBTCIO : Display13FBTCAuxIO { - OSObject *object; + OSObject *object;Display13Power::Result linkResult; Display13FBTCIO(OSObject *p,unsigned t):Display13FBTCAuxIO{display13FBPower.map,t},object(p) {} bool accessHeld() { if(!admitted(tc))return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; @@ -4734,7 +4734,7 @@ struct Display13FBTCIO : Display13FBTCAuxIO { bool linkPower(bool enable) { auto &c=display13FBPower;Display13FBPowerIO io{map};const auto ddi=tc+3; if(!Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60))return false; - Display13Power::Result r;r.status=Display13Power::Status::Complete; + auto &r=linkResult;r.status=Display13Power::Status::Complete; if(enable) { if(!c.bridge.retainsDDI(ddi))r=c.bridge.getDDI(io,ddi); } else if(c.bridge.nativeDDIRefs(ddi)) { @@ -6329,18 +6329,20 @@ void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; if(config && config[0]<3){FunctionCast(wrapDisplay13TCEnableClocks,self->orgDisplay13TCEnableClocks)(controller,raw,other,params);return;} if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); - bool ok=false;uint32_t rate=0; + bool ok=false;uint32_t rate=0;Display13Power::Result power; + auto stage=Display13TC::Clock::LinkStage::Preflight; if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { const auto tc=ddi-3;Display13FBTCIO io{port,tc};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex); const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); // Native modeset must first stop the old scanout; this transaction // may then retire its PLL before programming a different timing. - ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset); + ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset,&stage); + power=io.linkResult; c.tcSignalReady[tc]=ok; } if(!ok)adlnFault(c.clockFaulted,__LINE__); - uint32_t record[]={1,ddi,rate,ok};IOLockUnlock(c.lock); + uint32_t record[]={2,ddi,rate,ok,uint32_t(stage),uint32_t(power.status),power.domain,power.writes,power.rollbackFailed};IOLockUnlock(c.lock); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockEnable",record,sizeof(record)); } void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index a18dc3bb..7edd254d 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -11,7 +11,7 @@ struct IO { unsigned time {},locks {},unlocks {};uint32_t reject {},absent {}; bool link {},failLink {},coldCleanup {};unsigned linkGets {},linkPuts {}; bool linkPower(bool enable) { - if(enable){assert((regs[p.pll]&0xcc000000U)==0xcc000000U && !(regs[Clock::Gate]&Clock::gateBit(p.tc)));++linkGets;if(failLink)return false;link=true;} + if(enable){assert((regs[Clock::laneRegister(p.tc)]&Clock::laneMask(p.tc))==Clock::laneMask(p.tc));assert((regs[p.pll]&0xcc000000U)==0xcc000000U && !(regs[Clock::Gate]&Clock::gateBit(p.tc)));++linkGets;if(failLink)return false;link=true;} else {if(link){assert(regs[p.pll]&Clock::Enable);++linkPuts;}link=false;} return true; } @@ -19,7 +19,7 @@ struct IO { IO(unsigned ddi) { assert(Port::resolve(ddi,p)); regs={{p.pll,0x20000000},{p.clock,0x1234},{p.ready,0xc},{p.buffer,0xc0}, - {Clock::Gate,0xdeadffff|Clock::gateBit(p.tc)},{p.hip,0x01010101}}; + {Clock::Gate,0xdeadffff|Clock::gateBit(p.tc)},{p.hip,0x01010101},{Clock::laneRegister(p.tc),0xa5a50000}}; for(auto w:TMDSClock::calculate(148500,38400).words)bank[w.reg]=0xa5a5a5a5; } bool powerHeld(){return power;} @@ -106,11 +106,19 @@ int main() { for(unsigned d=3;d<=6;++d) { IO io(d);for(unsigned lane=0;lane<2;++lane)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[lane*0x1000+reg]=0; // Cold control/AUX is held, but DDI IO is off until the PLL is locked. + const auto prior=io.regs[Clock::laneRegister(io.p.tc)]; assert(Clock::startLink(io,io.p,full,7) && io.link && io.linkGets==1); + assert(io.regs[Clock::laneRegister(io.p.tc)]==(prior|Clock::laneMask(io.p.tc))); assert(Clock::stopLink(io,io.p) && !io.link && io.linkPuts==1); assert(Clock::startLink(io,io.p,hd,7) && io.linkGets==2); assert(Clock::stopLink(io,io.p) && io.linkPuts==2); } + for(unsigned d=3;d<=6;++d) { + IO io(d);auto reg=Clock::laneRegister(io.p.tc);io.regs[reg]=0xa5a500a5; + auto prior=io.regs[reg];assert(Clock::lanes(io,io.p)); + assert(io.regs[reg]==(prior|Clock::laneMask(io.p.tc))); + io.regs[reg]=UINT32_MAX;auto n=io.writes.size();assert(!Clock::lanes(io,io.p) && io.writes.size()==n); + } {IO io(4);io.stuckLock=true;assert(!Clock::startLink(io,io.p,full,7) && !io.linkGets);} {IO io(4);io.failLink=true;assert(!Clock::startLink(io,io.p,full,7) && io.linkGets==1 && !io.link);} {IO io(4);io.power=io.owner=false;io.coldCleanup=true;const auto count=io.writes.size(); From 437603e81abffa5796c609d26b12be1d94d1c329 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 02:07:16 +0900 Subject: [PATCH 26/60] Generalize Tiger Lake fixed DP routing and FIA lifecycle --- README.md | 2 + README_ja.md | 2 + WhateverXe/kern_tgl_native_fia.hpp | 14 +-- WhateverXe/kern_vbt.hpp | 6 +- WhateverXe/kern_xe.cpp | 112 ++++++++++++++-------- WhateverXe/kern_xe.hpp | 4 +- WhateverXe/kern_xelp_display_contract.hpp | 2 +- WhateverXe/kern_xelp_probe.hpp | 2 +- tests/firmware_ports.cpp | 8 +- tests/tgl_dp_alt_fia.py | 2 +- tests/tgl_dp_alt_fia_fixture.cpp | 22 +++-- tests/tgl_mode_native.py | 16 +++- tests/tgl_mode_native_fixture.cpp | 36 +++++-- tests/tgl_mode_serial_fixture.cpp | 1 + tests/tgl_probe_fixture.cpp | 2 +- tests/tgl_wiring_fixture.cpp | 11 ++- tests/xelp_display_contract.cpp | 2 +- tests/xelp_platform.cpp | 8 +- 18 files changed, 175 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 7e6cc5ce..f3a5ed26 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ On supported GPUs, WhateverXe also normalizes the detected Intel Management Engine device name to `IMEI`, as WhateverGreen does. A separate ACPI rename from `HECI` or `MEI` to `IMEI` is unnecessary. +Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. + Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Display13 DP runtime admission remains disabled pending completion of AUX, link training and boot handoff integration. ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. diff --git a/README_ja.md b/README_ja.md index 5678580a..83060adb 100644 --- a/README_ja.md +++ b/README_ja.md @@ -90,6 +90,8 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー 対応GPUでは、WhateverGreenと同様に、検出されたIntel Management Engineのデバイス名も`IMEI`へ統一します。`HECI`や`MEI`から`IMEI`へのACPI renameを別途追加する必要はありません。 +Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 + Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。Display13のDPはAUX・link training・起動時引継ぎの統合が未完のため、実行時の受付を閉じています。 ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 diff --git a/WhateverXe/kern_tgl_native_fia.hpp b/WhateverXe/kern_tgl_native_fia.hpp index 487142f4..3b03a38f 100644 --- a/WhateverXe/kern_tgl_native_fia.hpp +++ b/WhateverXe/kern_tgl_native_fia.hpp @@ -5,7 +5,7 @@ #include struct TGLNativeFiaContext { - uintptr_t owner {}; void *port {}; uint32_t ddi {}; + uintptr_t owner {}; void *port {}; uint32_t ddi {}; bool failed {}; bool matches(uintptr_t thread,void *p) const { return thread && __atomic_load_n(&owner,__ATOMIC_ACQUIRE)==thread && port==p; } @@ -13,21 +13,23 @@ struct TGLNativeFiaContext { // The existing recursive mode mutex serializes writers. Only the owning // thread reads port/ddi; other threads stop at the atomic owner check. struct TGLNativeFiaScope { - TGLNativeFiaContext &ctx; uintptr_t previousOwner; void *previousPort; uint32_t previousDDI; bool active; + TGLNativeFiaContext &ctx; uintptr_t previousOwner; void *previousPort; uint32_t previousDDI; bool previousFailed,active; TGLNativeFiaScope(TGLNativeFiaContext &c,uintptr_t thread,void *p,uint32_t d,bool enable): - ctx(c),previousOwner(0),previousPort(nullptr),previousDDI(0),active(enable) { + ctx(c),previousOwner(0),previousPort(nullptr),previousDDI(0),previousFailed(false),active(enable) { if(!active)return; - previousOwner=__atomic_load_n(&ctx.owner,__ATOMIC_RELAXED);previousPort=ctx.port;previousDDI=ctx.ddi; + previousOwner=__atomic_load_n(&ctx.owner,__ATOMIC_RELAXED);previousPort=ctx.port;previousDDI=ctx.ddi;previousFailed=ctx.failed;ctx.failed=false; ctx.port=p;ctx.ddi=d;__atomic_store_n(&ctx.owner,thread,__ATOMIC_RELEASE); } + bool failed()const{return active && ctx.failed;} void finish(){if(!active)return;__atomic_store_n(&ctx.owner,0,__ATOMIC_RELEASE); - ctx.port=previousPort;ctx.ddi=previousDDI;__atomic_store_n(&ctx.owner,previousOwner,__ATOMIC_RELEASE);active=false;} + ctx.port=previousPort;ctx.ddi=previousDDI;ctx.failed=previousFailed;__atomic_store_n(&ctx.owner,previousOwner,__ATOMIC_RELEASE);active=false;} ~TGLNativeFiaScope(){finish();} }; struct TGLNativeFia { TGLFiaPort fia {}; uint32_t donorMask {}; - // The caller has already observed modular FIA in the powered mode4 probe. + // Initial modular coordinates; the host resolves the live layout at the + // donor's powered access before using them. No boot-probe prerequisite. // TC5/6 remain excluded: the native ready switch has no case for them. bool resolve(uint32_t ddi){TGLTypeCPort tc; if(ddi<3 || ddi>6 || !TGLTypeCPort::resolve(ddi,tc) || !tc.resolveFia(0x10,fia))return false; diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp index df539be0..a8022a21 100644 --- a/WhateverXe/kern_vbt.hpp +++ b/WhateverXe/kern_vbt.hpp @@ -39,6 +39,8 @@ struct Ports { } panel; Port port[PortCount]; bool valid() const {return status==Status::Valid;} + // Linux direct_aux_ch_map: A=0x40, B/C/D=0x10/0x20/0x30, E..I=0x50..0x90. + static uint32_t tglTCAux(unsigned ddi) {return ddi>=3 && dditglTCWiring=property ? (data?TGLTCWiring::fromProperty(data->getBytesNoCopy(),data->getLength()):TGLTCWiring{}) : TGLTCWiring::fromMasks(0,0,0); if(!property && XELP::displayAdapter(self->xeProfile.family)==XELP::DisplayAdapter::TGL) - self->tglTCAutoCandidates=self->firmwarePorts.tglDPCandidates(self->tglAUXParentMask&0x18); + self->tglTCAutoCandidates=self->firmwarePorts.tglDPCandidates(self->tglAUXParentMask&0x78); IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTCDPCandidates",self->tglTCAutoCandidates,32); // The qualified donor ready switch covers TC1..4. Resolve modular FIA // coordinates under TC-cold before admitting their mode3 transaction. @@ -14928,19 +14928,18 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { return kIOReturnNotReady; } const bool fiaEligible=dpAltTransaction || ((mode==0 || mode==4) && traceDDI<9 && - (previousMode==0 || previousMode==4) && self->tglLegacyTCEligible(traceDDI) && - (__atomic_load_n(&self->tglModularProbed,__ATOMIC_ACQUIRE)&(1U<tglLegacyTCEligible(traceDDI)); TGLNativeFiaScope fiaScope(self->tglNativeFiaContext,reinterpret_cast(current_thread()),fiaEligible?port:nullptr, traceDDI,transitionLock && self->tglNativeFiaReady); auto result = FunctionCast(wrapTGLSetPortMode, callbackXE->orgTGLSetPortMode)(port, mode); - fiaScope.finish(); + const bool fiaFailed=fiaScope.failed();fiaScope.finish(); + if(fiaFailed && !result)result=kIOReturnNotReady; if(fiaEligible && self->tglNativeFiaReady && transitionLock){ - TGLNativeFia mapping; - if(mapping.resolve(traceDDI)){ - uint32_t record[]={1,traceDDI,mapping.fia.instance,mapping.fia.readyMask, + // No topology MMIO after native release; do not report assumed modular + // coordinates for a transaction that may have used non-modular FIA. + uint32_t record[]={2,traceDDI,static_cast(fiaFailed), __atomic_load_n(&self->tglNativeFiaReads,__ATOMIC_RELAXED),__atomic_load_n(&self->tglNativeFiaWrites,__ATOMIC_RELAXED)}; IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeNativeFiaAccess",record,sizeof(record)); - } } // A rejected mode must not look like success to training/lifetime callers. // Read software state only; do not add FIA MMIO outside a power reference. @@ -17828,36 +17827,65 @@ bool XE::tglDPAltFia(void *port,TGLFiaPort &fia) { FunctionCast(wrapTGLPortReadReg,self->orgTGLPortReadReg)(port,fia.owned)!=~0U; } +// Fixed DP uses the same FIA topology as DP-alt. Called only under the +// mode mutex at native powered mode/lanes accesses: mode4's AUX acquisition +// precedes the ready/owner accesses and its release follows owner clear. +// Do not substitute a DP-alt bootstrap hold or a boot probe for this lifetime. +bool XE::tglLegacyFia(void *port,TGLFiaPort &fia) { + auto self=callbackXE;auto p=static_cast(port);fia={}; + if(!p || !self->tglActive || !self->tglNativeFiaReady || !self->tglDPAltFiaReady)return false; + auto c=*reinterpret_cast(p+0x548); + auto access=*reinterpret_cast(p+0x578); + if(!c || c[0]<3 || c[0]>6 || c[2]!=1 || c[3]!=1 || + !self->tglLegacyTCEligible(c[0]) || *reinterpret_cast(p+0x550)!=4 || + !access || !*reinterpret_cast(access+0x50) || + *reinterpret_cast(access+0x60)<0x1638C4)return false; + TGLTypeCPort tc;TGLFiaPort actual; + const auto first=FunctionCast(wrapTGLPortReadReg,self->orgTGLPortReadReg)(port,0x1638A0); + if(!TGLTypeCPort::resolve(c[0],tc) || !tc.resolveFia(first,actual) || + *reinterpret_cast(access+0x60)(port);auto c=*reinterpret_cast(p+0x548); - if(c && c[0]<9 && (callbackXE->tglDPAltCandidates&(1U<(p+0x550)==3) { - TGLFiaPort fia;if(!tglDPAltFia(port,fia))return 0; - const auto value=wrapTGLPortReadReg(port,fia.laneSelect); - return value==~0U?0:(value>>fia.laneSelectShift)&15; + auto self=callbackXE;auto p=static_cast(port);auto c=*reinterpret_cast(p+0x548); + const auto mode=*reinterpret_cast(p+0x550); + const bool alt=c && c[0]<9 && (self->tglDPAltCandidates&(1U<=3 && c[0]<=6 && self->tglLegacyTCEligible(c[0]) && mode==4; + if(alt || legacy) { + auto lock=self->tglModeTransitionLock;if(!lock)return 0;IORecursiveLockLock(lock); + TGLFiaPort fia;uint32_t value=0; + if(alt?tglDPAltFia(port,fia):tglLegacyFia(port,fia)) { + const auto raw=wrapTGLPortReadReg(port,fia.laneSelect); + if(raw!=UINT32_MAX)value=(raw>>fia.laneSelectShift)&15; + } + IORecursiveLockUnlock(lock);return value; } - return FunctionCast(wrapTGLConfiguredLanes,callbackXE->orgTGLConfiguredLanes)(port); + return FunctionCast(wrapTGLConfiguredLanes,self->orgTGLConfiguredLanes)(port); } void XE::wrapTGLFIALaneCount(void *port,uint32_t lanes) { - auto p=static_cast(port);auto c=*reinterpret_cast(p+0x548); - if(c && c[0]<9 && (callbackXE->tglDPAltCandidates&(1U<(p+0x550)==3) { - TGLFiaPort fia;if(!tglDPAltFia(port,fia) || (lanes!=1 && lanes!=2 && lanes!=4))return; - // Logical main-link lanes, independent of physical USB-C orientation. - // Use the same shared mutex as ownership transactions for sibling RMWs. - auto lock=callbackXE->tglModeTransitionLock;if(!lock)return; - IORecursiveLockLock(lock); - const auto owned=wrapTGLPortReadReg(port,fia.owned); - auto value=wrapTGLPortReadReg(port,fia.laneSelect); - if(owned!=~0U && (owned&fia.readyMask) && value!=~0U) { - value=(value&~fia.laneSelectMask)|(((1U<(port);auto c=*reinterpret_cast(p+0x548); + const auto mode=*reinterpret_cast(p+0x550); + const bool alt=c && c[0]<9 && (self->tglDPAltCandidates&(1U<=3 && c[0]<=6 && self->tglLegacyTCEligible(c[0]) && mode==4; + if(alt || legacy) { + if(lanes!=1 && lanes!=2 && lanes!=4)return; + // Admitted fixed routes have no VBT lane reversal; both transports use + // logical ML0/ML1_0/ML3_0, not the physical DP-alt lane assignment. + auto lock=self->tglModeTransitionLock;if(!lock)return;IORecursiveLockLock(lock); + TGLFiaPort fia; + if(alt?tglDPAltFia(port,fia):tglLegacyFia(port,fia)) { + const auto owned=wrapTGLPortReadReg(port,fia.owned); + auto value=wrapTGLPortReadReg(port,fia.laneSelect); + if(owned!=UINT32_MAX && (owned&fia.readyMask) && value!=UINT32_MAX) { + value=(value&~fia.laneSelectMask)|(((1U<orgTGLFIALaneCount)(port,lanes); + FunctionCast(wrapTGLFIALaneCount,self->orgTGLFIALaneCount)(port,lanes); } // r323: the donor DKL table has four HBR2 coefficient mismatches against @@ -18049,16 +18077,24 @@ bool XE::tglNativeFiaAccess(void *port,uint32_t reg,uintptr_t caller,bool write, const auto pc=caller-self->tglNativeFiaBase; const auto ddi=self->tglNativeFiaContext.ddi; if(!(write?TGLNativeFia::writeSite(pc,reg):TGLNativeFia::readSite(pc,reg)) || !mapping.resolve(ddi))return false; - if(ddi<9 && (self->tglDPAltCandidates&(1U<(static_cast(port)+0x550); + TGLFiaPort actual; + const bool ok=mode==3 ? tglDPAltFia(port,actual) : tglLegacyFia(port,actual); + // Keep an intercepted failure intercepted: never fall back to FIA1 when + // the selected port's topology is unavailable. Read zero / skip writes. + mapping.fia=ok?actual:TGLFiaPort{}; + if(!ok)self->tglNativeFiaContext.failed=true; return true; } uint32_t XE::wrapTGLPortReadReg(void *port,uint32_t reg) { TGLNativeFia mapping; const bool mapped=tglNativeFiaAccess(port,reg,reinterpret_cast(__builtin_return_address(0)),false,mapping); + if(mapped && !mapping.fia.readyMask)return 0; const auto physical=mapped?(reg==0x163890?mapping.fia.ready:mapping.fia.owned):reg; auto value=FunctionCast(wrapTGLPortReadReg,callbackXE->orgTGLPortReadReg)(port,physical); if(mapped){ + if(value==UINT32_MAX){callbackXE->tglNativeFiaContext.failed=true;return 0;} value=mapping.logical(value); __atomic_add_fetch(&callbackXE->tglNativeFiaReads,1,__ATOMIC_RELAXED); } @@ -18068,12 +18104,12 @@ uint32_t XE::wrapTGLPortReadReg(void *port,uint32_t reg) { void XE::wrapTGLPortWriteReg(void *port, uint32_t reg, uint32_t value) { TGLNativeFia mapping; if(tglNativeFiaAccess(port,reg,reinterpret_cast(__builtin_return_address(0)),true,mapping)) { - if(!mapping.identity()){ - // The complete native transaction holds the shared mode lock. Preserve - // every other physical owner bit, including the sibling modular port. - const auto old=FunctionCast(wrapTGLPortReadReg,callbackXE->orgTGLPortReadReg)(port,mapping.fia.owned); - value=mapping.physical(old,value); - } + if(!mapping.fia.readyMask)return; + // Serialize the whole transaction, and preserve peer owner bits even + // for FIA1 identity mapping. An unavailable owner read cannot be written. + const auto old=FunctionCast(wrapTGLPortReadReg,callbackXE->orgTGLPortReadReg)(port,mapping.fia.owned); + if(old==UINT32_MAX){callbackXE->tglNativeFiaContext.failed=true;return;} + value=mapping.physical(old,value); reg=mapping.fia.owned; __atomic_add_fetch(&callbackXE->tglNativeFiaWrites,1,__ATOMIC_RELAXED); } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 8ecfc7e7..e23f235b 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -504,7 +504,8 @@ class XE { (tglTCWiring.legacy|tglTCAutoCandidates)&tglAUXParentMask : 0;} bool tglTCModeAllowed(uint32_t ddi,uint32_t mode) const { if(ddi>=9 || !(tglTCAutoCandidates&(1U< uint32_t legacyTGLMode(IO &io,uint32_t ddi,uint32_t selected) if(!TGLTypeCPort::resolve(ddi,port) || !io.readable(0xC4000) || !port.legacyHPD(io.read(0xC4000)) || !io.readable(0x1638A0))return 0; const auto first=io.read(0x1638A0); - if(!(first&0x10) || !port.resolveFia(first,fia) || !io.readable(fia.live) || !io.readable(fia.ready))return 0; + if(!port.resolveFia(first,fia) || !io.readable(fia.live) || !io.readable(fia.ready))return 0; const auto live=fia.live==0x1638A0 ? first : io.read(fia.live); return fia.readyForLegacy(live,io.read(fia.ready)) ? 4 : 0; } diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index 63a610dd..be3f2d0b 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -51,14 +51,16 @@ int main(int argc,char **argv){ // Type-C capability never becomes an automatic fixed-DP mode decision. assert(r.port[4].capabilities==3&&r.port[4].dp()); // All six port coordinates and capability combinations, independent of - // CPU model. Current runtime auto backend is TC1/2 only (native FIA1). + // CPU model. Runtime automatic fixed-DP backend covers native TC1..4. + const unsigned directAUX[]={0x30,0x50,0x60,0x70,0x80,0x90}; for(unsigned d=3;d<9;++d)for(unsigned caps=0;caps<4;++caps){ const unsigned dvo[]={9,11,13,15,17,19}; - auto b=table({{0x60d6,dvo[d-3],d+1,(d+1)*0x10,0xc0,caps}}); + auto b=table({{0x60d6,dvo[d-3],d+1,directAUX[d-3],0xc0,caps}}); auto q=parse(b.data(),b.size(),Family::TigerLake); assert(q.tglDPCandidates(0x1f8)==(1U<(XE::wrapTGLPortReadReg); instance.orgTGLLaneMask=instance.orgTGLConfiguredLanes=reinterpret_cast(fallback); instance.orgTGLFIALaneCount=reinterpret_cast(fallbackSet); - mode=3;config[3]=1; - for(bool modular:{false,true})for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned physical:{3u,12u,15u}) { - config[0]=ddi;TGLTypeCPort tc;TGLFiaPort fia; + mode=3;config[2]=1;config[3]=1; + for(unsigned transport:{3u,4u})for(bool modular:{false,true})for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned physical:{3u,12u,15u}) { + mode=transport;config[0]=ddi;TGLTypeCPort tc;TGLFiaPort fia; assert(TGLTypeCPort::resolve(ddi,tc)&&tc.resolveFia(modular?16:0,fia)); mmio.clear();mmio[0x1638a0]=modular?16:0; mmio[fia.live]|=(physical|32u)<<(8*fia.index); mmio[fia.ready]=fia.readyMask;mmio[fia.owned]=fia.readyMask;mmio[fia.laneSelect]=0xabcdefff; TGLFiaPort actual;assert(XE::tglDPAltFia(port,actual)&&actual.live==fia.live&&actual.index==fia.index); - assert(XE::wrapTGLLaneMask(port)==physical); + assert(XE::wrapTGLLaneMask(port)==(transport==4?15u:physical)); + // Independent register coordinates, not an expectation generated by resolveFia. + const uint32_t laneReg[]={0x1638c0,0x1638c0,0x16e8c0,0x16e8c0}; + assert(fia.laneSelect==(modular?laneReg[ddi-3]:0x1638c0)); + assert(fia.laneSelectShift==(modular?4*((ddi-3)%2):4*(ddi-3))); + if(transport==4){TGLFiaPort fixed;assert(XE::tglLegacyFia(port,fixed)&&fixed.laneSelect==fia.laneSelect);} for(unsigned lanes:{1u,2u,4u}) { const auto before=mmio[fia.laneSelect]; XE::wrapTGLFIALaneCount(port,lanes); @@ -74,7 +80,7 @@ int main(){ config[3]=1;config[0]=5;size=0x170000;instance.tglDPAltCandidates=0x78; mmio[0x1638a0]=~0u;assert(!XE::tglDPAltFia(port,fia)); mmio[0x1638a0]=16;size=0x16e8c0;assert(!XE::tglDPAltFia(port,fia)); - mode=4;assert(XE::wrapTGLLaneMask(port)==0xbeef&&XE::wrapTGLConfiguredLanes(port)==0xbeef); + instance.legacyMask=0;mode=4;assert(XE::wrapTGLLaneMask(port)==0xbeef&&XE::wrapTGLConfiguredLanes(port)==0xbeef); XE::wrapTGLFIALaneCount(port,2);assert(fallbacks==3); - puts("PASS production DP-alt FIA: TC1..4 modular/non-modular, both orientations, logical lanes, sibling preservation, power/map/backend/all-ones guards and native fallback"); + puts("PASS production fixed/DP-alt FIA: TC1..4 modular/non-modular, both orientations, logical lanes, sibling preservation, power/map/backend/all-ones guards and native fallback"); } diff --git a/tests/tgl_mode_native.py b/tests/tgl_mode_native.py index 6d3a39d0..013f72ba 100644 --- a/tests/tgl_mode_native.py +++ b/tests/tgl_mode_native.py @@ -1,12 +1,23 @@ #!/usr/bin/env python3 """Replay qualified native TGL setPortMode with the production result repair.""" from pathlib import Path -import argparse,hashlib,re,subprocess,tempfile +import argparse,hashlib,re,struct,subprocess,tempfile p=argparse.ArgumentParser(description=__doc__);p.add_argument('donor',type=Path);p.add_argument('--serialization',action='store_true');p.add_argument('--source',type=Path);a=p.parse_args() if hashlib.sha256(a.donor.read_bytes()).hexdigest()!='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731':p.error('unqualified donor SHA256') r=Path(__file__).resolve().parents[1];s=(a.source or r/'WhateverXe/kern_xe.cpp').read_text() # Verify the production admission bytes, not just the fixture's ready flag. donor=a.donor.read_bytes() +# Verify reachability of the lane wrapper in the actual fixed-DP clock path, +# independently of direct wrapper tests. TC mode != TBT selects this branch; +# the port's negotiated/default lanes feed setFIALaneCount before clocks and +# DP_MODE. These are static donor assertions, not a full clock/MMIO replay. +for slot,target in [(0x198,0x603e8),(0x150,0x61d5c),(0x180,0x60930),(0x1a0,0x602cc)]: + assert struct.unpack_from('(static_cast(port)+0x548); TGLTypeCPort tc;return coldReady && TGLTypeCPort::resolve(config[0],tc) && tc.resolveFia(0x10,fia); } + static bool tglLegacyFia(void*,TGLFiaPort&); static bool tglNativeFiaAccess(void*,uint32_t,uintptr_t,bool,TGLNativeFia&); static uint32_t wrapTGLPortReadReg(void*,uint32_t); static void wrapTGLPortWriteReg(void*,uint32_t,uint32_t); @@ -56,15 +58,16 @@ XE instance,*callbackXE=&instance; bool tglQuietTraining(){return true;} // PRODUCTION_WRAPPER #undef __builtin_return_address +// LEGACY_FIA_WRAPPER // FIA_WRAPPERS -alignas(8) uint8_t port[0x600],controller[0x1000]; +alignas(8) uint8_t port[0x600],controller[0x1000],access[0x80]; uint32_t config[8]; unsigned powerGets,powerPuts,sleeps,reads,writes; uint32_t ready,owned,baseError; -uint32_t ready2=0,owned2=0; +uint32_t ready2=0,owned2=0,topology=0x10; uint32_t &mode(){return *reinterpret_cast(port+0x550);} uint32_t readReg(void *p,uint32_t r){ - assert(p==port);++reads;assert(r==0x163890 || r==0x163894 || r==0x16e890 || r==0x16e894); + assert(p==port);++reads;if(r==0x1638a0){assert(powerGets>powerPuts);return topology;}assert(r==0x163890 || r==0x163894 || r==0x16e890 || r==0x16e894); if(r==0x16e890)return ready2;if(r==0x16e894)return owned2; return r==0x163890?ready:owned; } @@ -81,7 +84,7 @@ void jump(uint8_t *p,uintptr_t t){p[0]=0x48;p[1]=0xb8;std::memcpy(p+2,&t,8);p[10 void callTo(uint8_t *p,unsigned site,unsigned target){assert(p[site]==0xe8);int32_t rel=target-site-5;std::memcpy(p+site+1,&rel,4);} void reset(unsigned ddi,uint32_t value=0){ powerGets=powerPuts=sleeps=reads=writes=0;ready=value;owned=0x40000000;mode()=0;baseError=0; - config[0]=ddi;config[5]=8;instance.tglModeResultCorrections=0;instance.tglModeResultReady=true; + config[0]=ddi;config[2]=1;config[3]=1;config[5]=8;instance.tglModeResultCorrections=0;instance.tglModeResultReady=true; } int main(int argc,char **argv){ assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{}); @@ -193,6 +196,9 @@ int main(int argc,char **argv){ mapped.bank=1;mapped.ready=1; assert(TGLFia::waitLegacyReady(mapped,5).ok() && TGLFia::setOwned(mapped,5,true).ok()); assert(mapped.ownership[0]==0x40000000 && mapped.ownership[1]==0x40000001 && mapped.ownership[2]==0x40000000); + *reinterpret_cast(port+0x578)=access; + *reinterpret_cast(access+0x50)=controller; + *reinterpret_cast(access+0x60)=0x170000; // Replay the coordinate adapter through the actual native call sites. IORecursiveLock lock;instance.tglModeTransitionLock=&lock;instance.tglNativeFiaReady=true; instance.tglNativeFiaBase=reinterpret_cast(m);instance.tglModularProbed=0x78; @@ -226,9 +232,25 @@ int main(int argc,char **argv){ assert(instance.tglNativeFiaContext.matches(reinterpret_cast(current_thread()),port)); } assert(!instance.tglNativeFiaContext.owner && instance.tglNativeFiaReads && instance.tglNativeFiaWrites); - // Missing probe qualification or mutex must leave the native FIA1 failure. - instance.tglModularProbed=0;reset(5,0);ready2=1; - assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && reads==100 && !writes); + // Hotplug needs no previous powered probe or DP-alt bootstrap. Native + // acquisition must precede topology reads; the read mock enforces AUX debt. + instance.tglDPAltCandidates=0;instance.tglModularProbed=0;coldReady=false;reset(5,0);ready2=1; + assert(XE::wrapTGLSetPortMode(port,4)==0 && mode()==4 && owned2&1); + assert(XE::wrapTGLSetPortMode(port,0)==0 && powerGets==powerPuts);coldReady=true; + for(unsigned d=3;d<=6;++d) { + topology=0;reset(d,1U<<(d-3)); + assert(XE::wrapTGLSetPortMode(port,4)==0 && (owned&(1U<<(d-3)))); + assert(XE::wrapTGLSetPortMode(port,0)==0 && owned==0x40000000 && powerGets==powerPuts); + } + topology=UINT32_MAX;reset(5,0);ready2=1; + assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && !writes && powerGets==powerPuts); + topology=16;reset(5,0);ready2=UINT32_MAX; + assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && !writes && powerGets==powerPuts); + // Even when native returns zero after an unavailable ownership access, + // the wrapper reports failure and never overwrites the peer owner word. + topology=16;reset(5,0);ready2=1;owned2=UINT32_MAX; + assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && !writes && owned2==UINT32_MAX); + // Missing mutex keeps this unqualified path outside the adapter. instance.tglModularProbed=0x78;instance.tglModeTransitionLock=nullptr;reset(5,0); assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && reads==100 && !writes); puts("PASS qualified native FIA adapter: TC1/2 identity and TC3/4 modular bank/bit, sibling preservation, paired AUX, scoped caller guards"); diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index 91a53551..1828f11f 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -42,6 +42,7 @@ struct XE { auto config=*reinterpret_cast(static_cast(port)+0x548); TGLTypeCPort tc;return coldReady && TGLTypeCPort::resolve(config[0],tc) && tc.resolveFia(0x10,fia); } + static bool tglLegacyFia(void *port,TGLFiaPort &fia) {return tglDPAltFia(port,fia);} static bool tglNativeFiaAccess(void*,uint32_t,uintptr_t,bool,TGLNativeFia&); static uint32_t wrapTGLPortReadReg(void*,uint32_t); static void wrapTGLPortWriteReg(void*,uint32_t,uint32_t); diff --git a/tests/tgl_probe_fixture.cpp b/tests/tgl_probe_fixture.cpp index 0888b8d6..c40417d8 100644 --- a/tests/tgl_probe_fixture.cpp +++ b/tests/tgl_probe_fixture.cpp @@ -111,7 +111,7 @@ int main(int argc,char **) { unsigned cases=0; for(unsigned hp:{0u,1u})for(unsigned modular:{0u,1u})for(unsigned rd:{0u,1u})for(unsigned alt=0;alt<4;++alt) { hpd=hp<<25;live=(modular<<4)|(alt<<13);ready=rd<<1; - const auto expected=hp&&modular&&rd&&!alt?4u:0u; + const auto expected=hp&&rd&&!alt?4u:0u; assert(XE::wrapTGLProbePort(port)==expected);++cases; } hpd=1u<<25;live=0x10;ready=2;length=0x1638A4; diff --git a/tests/tgl_wiring_fixture.cpp b/tests/tgl_wiring_fixture.cpp index 7d6461e3..f8a72061 100644 --- a/tests/tgl_wiring_fixture.cpp +++ b/tests/tgl_wiring_fixture.cpp @@ -61,7 +61,16 @@ int main(){ assert(!object.tglRegisterLegacyHPD(4,4,0x1234));object.tglAUXParentMask=0x18; assert(object.tglRegisterLegacyHPD(4,4,0x1234)); assert(object.tglLegacyTCEligible(4)&&object.tglTCModeAllowed(4,4)); - assert(!object.tglTCModeAllowed(4,2)&&!object.tglTCModeAllowed(4,3)); + assert(!object.tglTCModeAllowed(4,2)&&object.tglTCModeAllowed(4,3)); + // Fixed DP and DP-alt share the exact AUX mapping and native TC1..4 limit. + for(unsigned d=3;d<=8;++d) { + reset();const uint32_t aux[]={0x30,0x50,0x60,0x70,0x80,0x90}; + object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=1U<(platform),c};assert(b.valid()); unsigned cases=0; - // Independent old WEG equations, plus exact MMIO read order for every TC. + // Independent Linux FIA layouts and exact MMIO read order for every TC. for(unsigned d=3;d<=8;++d)for(unsigned h=0;h<2;++h)for(unsigned mod=0;mod<2;++mod) for(unsigned ready=0;ready<2;++ready)for(unsigned alt=0;alt<4;++alt) { - const unsigned tc=d-3,instance=tc/2,index=tc%2;const uint32_t bases[]={0x163000,0x16E000,0x16F000}; + const unsigned tc=d-3,instance=mod?tc/2:0,index=mod?tc%2:tc<4?tc:0;const uint32_t bases[]={0x163000,0x16E000,0x16F000}; const auto liveReg=bases[instance]+0x8A0,readyReg=bases[instance]+0x890; IO io;io.regs={{0xC4000,h<<(24+tc)},{0x1638A0,mod<<4},{liveReg,(mod<<4)|(alt<<(5+8*index))},{readyReg,ready<(platform+0x20+24*d);record[0]=d; const auto mode=probeMode(io,b,2,reinterpret_cast(record),record); - assert(mode==(h && mod && ready && !alt?4U:0U)); + assert(mode==(h && (mod || tc<4) && ready && !alt?4U:0U)); std::vector expected {0xC4000}; - if(h){expected.push_back(0x1638A0);if(mod){if(instance)expected.push_back(liveReg);expected.push_back(readyReg);}} + if(h){expected.push_back(0x1638A0);if(mod || tc<4){if(instance)expected.push_back(liveReg);expected.push_back(readyReg);}} assert(io.reads==expected);++cases; } // Selection never grants an unknown, Type-C or excluded port access. From 13f798d4e6849f66bb0d66ca3aa5244c47ad9a2a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 02:37:56 +0900 Subject: [PATCH 27/60] Integrate fixed Display13 TC SST DP after HDMI boot --- AppleIntelTGLCompat/AppleIntelTGLCompat.cpp | 2 +- README.md | 4 +- README_ja.md | 4 +- WhateverXe/kern_display13_fb_irq.hpp | 10 +- WhateverXe/kern_display13_fb_topology.hpp | 4 +- WhateverXe/kern_display13_firmware.hpp | 17 +++ WhateverXe/kern_display13_output_wiring.hpp | 10 +- WhateverXe/kern_display13_tc_clock.hpp | 6 +- WhateverXe/kern_display13_tc_dp.hpp | 81 +++++++++++ WhateverXe/kern_display13_tc_lightup.hpp | 18 ++- WhateverXe/kern_display13_tc_native.hpp | 9 +- WhateverXe/kern_xe.cpp | 141 +++++++++++++++++--- WhateverXe/kern_xe.hpp | 10 +- WhateverXe/kern_xelp_platform.hpp | 6 +- WhateverXe/kern_xelp_probe.hpp | 10 +- WhateverXe/kern_xelp_sst_program.hpp | 4 +- tests/adlp_fb_candidate.py | 15 ++- tests/display13_fb_irq.cpp | 12 +- tests/display13_sst_fixture.cpp | 49 +++++-- tests/display13_tc_clock.cpp | 40 +++++- tests/display13_tc_lightup.cpp | 20 +++ tests/display13_tc_native.py | 16 ++- tests/display_contract_adapter.py | 4 + tests/provider_binding_fixture.cpp | 10 +- 24 files changed, 435 insertions(+), 67 deletions(-) create mode 100644 WhateverXe/kern_display13_tc_dp.hpp diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index 3570bef3..8c31560c 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 : 0; + return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP : 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 f3a5ed26..9a03b357 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ limits are listed in [device selection](docs/device-selection.md). ### Further implementation required -- Display13 eDP, fixed DP and Type-C-managed output need complete transport +- Display13 eDP, Combo DP and Type-C-managed output need complete transport integration. Mobile-family ID admission does not enable those output paths. - Rocket Lake-S: its display power and clock handling differ from Tiger Lake. It is not supported by this release, and no hardware validation is scheduled. @@ -127,7 +127,7 @@ from `HECI` or `MEI` to `IMEI` is unnecessary. Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Display13 DP runtime admission remains disabled pending completion of AUX, link training and boot handoff integration. +Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support is implemented for HDMI-booted Display13 controllers, with matching VBT AUX wiring and a matching Compat build. Hardware validation is pending. Booting from DP, Combo DP, MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. diff --git a/README_ja.md b/README_ja.md index 83060adb..39e68173 100644 --- a/README_ja.md +++ b/README_ja.md @@ -51,7 +51,7 @@ CPUモデル名や機種固有の設定は不要ですが、実機での動作 ### 対応に追加改修が必要 -- Display13のeDP・固定DP・Type-C管理下の出力は、transportの接続に追加改修が必要です。 +- Display13のeDP・Combo DP・Type-C管理下の出力は、transportの接続に追加改修が必要です。 モバイル系IDの受付だけでこれらの出力経路が有効になるわけではありません。 - Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 このリリースでは未対応で、実機検証の予定もありません。 @@ -92,7 +92,7 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。Display13のDPはAUX・link training・起動時引継ぎの統合が未完のため、実行時の受付を閉じています。 +Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。HDMIから起動したDisplay13 controllerに対して、固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。実機検証は未完了です。DPからの起動、Combo DP、MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 diff --git a/WhateverXe/kern_display13_fb_irq.hpp b/WhateverXe/kern_display13_fb_irq.hpp index 4ab13de4..198a408a 100644 --- a/WhateverXe/kern_display13_fb_irq.hpp +++ b/WhateverXe/kern_display13_fb_irq.hpp @@ -149,7 +149,11 @@ 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 io.write(0xC400C,pchIER&~excludedPCH); if((io.read(0xC400C)&excludedPCH) || (~io.read(0xC4004)&excludedPCH)) return fail(io,s,Status::Readback); - // Do not ACK pending status or alter selected/unrelated bits. + } + if(excludedHPD) { + // Keep pending north status and every unrelated source intact. io.write(HPDBank-4,io.read(HPDBank-4)|excludedHPD); io.write(HPDBank+4,io.read(HPDBank+4)&~excludedHPD); if((io.read(HPDBank+4)&excludedHPD) || (~io.read(HPDBank-4)&excludedHPD)) diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index e7887e9b..995ec791 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -18,12 +18,12 @@ inline bool adlnWiring(const Display13OutputWiring &w) {return selectedWiring(w) inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=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. - return !w.dp && w.described() && !(w.selected()&~(fixedTC ? Display13OutputWiring::Physical : 3U)); + return !(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. inline bool deferredBootPrepare(const Display13OutputWiring &w,bool mobile) { - return mobile || (w.hdmi&0x78); + return mobile || (w.selected()&0x78); } inline bool multiOutputWiring(const Display13OutputWiring &w) { return selectedWiring(w) && (w.selected()&(w.selected()-1)); diff --git a/WhateverXe/kern_display13_firmware.hpp b/WhateverXe/kern_display13_firmware.hpp index 309bbef1..abbb98f1 100644 --- a/WhateverXe/kern_display13_firmware.hpp +++ b/WhateverXe/kern_display13_firmware.hpp @@ -19,5 +19,22 @@ 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. + inline Display13OutputWiring display13Fixed(const Ports &ports,uint32_t backend) { + auto w=display13HDMI(ports,backend); + if(!w.valid || !ports.valid())return w; + for(unsigned d=3;d<7;++d) { + const auto &p=ports.port[d];const auto aux=(d+3)*0x10U; + if(!(ports.present&backend&(1U<=Ports)return false; if(hdmi&(1U<(platform);if(p[0x12]!=Ports)return false; - for(unsigned d=0;d(p+0x20+24*d); const uint32_t panel[]={0,0,0,0,2,0x18}; if(!d && !memcmp(c,panel,24))continue; @@ -63,7 +63,7 @@ struct Display13OutputWiring { const bool panel=!d && !c[2]; c[1]=ddc[d];c[2]=2;c[4]=0x800;c[5]=panel?0x202:(c[5]&~9U)|2U; } - return TGLComboDP::apply(dp,platform,bytes); + return TGLComboDP::apply(dp&3,platform,bytes); } }; #endif diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp index 13a01c6e..d97c8e04 100644 --- a/WhateverXe/kern_display13_tc_clock.hpp +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -159,9 +159,9 @@ struct Clock { // generation-specific, but the FIA lane-enable register is still required. static uint32_t laneRegister(unsigned tc) {return tc<2?0x1638c0:0x16e8c0;} static uint32_t laneMask(unsigned tc) {return 15U<<(4*(tc&1));} - template static bool lanes(IO &io,const Port &p) { - return preflight(io,p) && io.readable(laneRegister(p.tc)) && - rmw(io,laneRegister(p.tc),laneMask(p.tc),laneMask(p.tc)); + template static bool lanes(IO &io,const Port &p,unsigned count=4) { + return (count==1 || count==2 || count==4) && preflight(io,p) && io.readable(laneRegister(p.tc)) && + rmw(io,laneRegister(p.tc),laneMask(p.tc),((1U<=2?10U:5U)<<8}; + p.words[2]={0x20d4,0x1ff00,(d>=2?0x4000U:0U)|(ratio<<12)|(d<<8)}; + p.words[3]={0x2200,0x1fffff,0x84200|integer}; + p.words[4]={0x2204,0x1f00ff,((ref==24000?25U:28U)<<16)|target}; + p.words[5]={0x2210,0xe0ff3a00,(ref==38400?0x40000000U:0x20000000U)|0x2000}; + p.words[6]={0x2214,0x7fffff00,(fraction?0x40000000U:0U)|(fraction<<8)}; + p.words[7]={0x2218,0xffff,rem?82U:0U}; // 200000000 / (8100000 * 3/10) + return p; + } + return {}; + } +}; +struct DPSignal { + 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}; + constexpr uint8_t low[]={1,6,11,23,0,8,20,0,11,0},high[]={0,4,10,24,0,6,20,0,9,0}; + 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) { + if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi) || (lanes!=1 && lanes!=2 && lanes!=4))return false; + for(unsigned group=0;group<2;++group) { + uint32_t value=lanes==1?(group?0x40U:0U):0x80U; + 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) { + uint32_t selected; + if(!io.powerHeld() || !io.owned(p.ddi) || !coefficients(rate,swing,emphasis,selected))return false; + for(unsigned group=0;group<2;++group) { + 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) { + 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; + } + return true; + } + template static bool startLink(IO &io,const Port &p,const DPClock &plan,unsigned lanes,Clock::LinkStage *stage=nullptr) { + 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; + 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; + step(Clock::LinkStage::Complete);return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_display13_tc_lightup.hpp b/WhateverXe/kern_display13_tc_lightup.hpp index 24f38853..d2438a2c 100644 --- a/WhateverXe/kern_display13_tc_lightup.hpp +++ b/WhateverXe/kern_display13_tc_lightup.hpp @@ -4,7 +4,7 @@ #define KERN_DISPLAY13_TC_LIGHTUP_HPP #include namespace Display13TC { -enum class LightStage : uint32_t {Binding,Clocks,TransClock,Plane,Scaler,Pipe,Signal,Port,Packets,Complete}; +enum class LightStage : uint32_t {Binding,Clocks,TransClock,Plane,Scaler,Pipe,Signal,Port,Packets,Complete,Training}; struct LightResult {LightStage stage {LightStage::Binding};uint32_t status {0xe00002d8U};}; template LightResult lightUpTMDS(IO &io,unsigned ddi,unsigned pipe) { LightResult r; @@ -23,6 +23,22 @@ template LightResult lightUpTMDS(IO &io,unsigned ddi,unsigned pipe) { r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r; r.stage=LightStage::Complete;r.status=0;return r; } +// Fixed SST retains native training and SDP setup, but stops on failure +// instead of the donor's unconditional success/pipe programming afterward. +template LightResult lightUpDP(IO &io,unsigned ddi,unsigned pipe) { + LightResult r; + if(ddi<3 || ddi>6 || pipe>=3 || !io.ready(false))return r; + r.stage=LightStage::Clocks;io.clocks();if(!io.ready(true))return r; + r.stage=LightStage::Training; + const auto trained=io.train();if(trained){r.status=trained;return r;} + if(!io.ready(true))return r; + r.stage=LightStage::TransClock;if(!io.transClock(0x46140+4*pipe,(ddi+3)<<28))return r; + r.stage=LightStage::Plane;io.plane();if(!io.ready(true))return r; + r.stage=LightStage::Scaler;io.scaler();if(!io.ready(true))return r; + r.stage=LightStage::Pipe;io.pipe();if(!io.ready(true))return r; + r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r; + r.stage=LightStage::Complete;r.status=0;return r; +} // Native hwSetMode sends the begin notification before the path call. Its // error branch skips post-mode writes and the normal end notification; // balance that notification without replacing the returned error. diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp index 2de6ecd4..85f5d8e6 100644 --- a/WhateverXe/kern_display13_tc_native.hpp +++ b/WhateverXe/kern_display13_tc_native.hpp @@ -3,7 +3,7 @@ #define KERN_DISPLAY13_TC_NATIVE_HPP #include namespace Display13TC { namespace Native { -enum EntryIndex {Mode,BaseMode,Clocks,DisableClocks,LightUp,SetMode,Notify,FullClocks,Plane,Scaler,Pipe,Packets}; +enum EntryIndex {Mode,BaseMode,Clocks,DisableClocks,LightUp,SetMode,Notify,FullClocks,Plane,Scaler,Pipe,Packets,DPLightUp,Training,SDPs,PhyGating,LaneMask,PortWrite}; struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[20];}; static constexpr Entry entries[]={ {"__ZN17AppleIntelPortHAL11setPortModeEN14AppleIntelPort8PortModeE",0x5fcb8,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x41,0x89,0xf7}}, @@ -18,6 +18,13 @@ static constexpr Entry entries[]={ {"__ZN16AppleIntelScaler17programPipeScalerEP21AppleIntelDisplayPath",0x65fd4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05}}, {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48bb6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x48,0x8b,0x05}}, {"__ZN31AppleIntelFramebufferController23enableDataIslandPacketsEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52106,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0xff,0x05,0x11,0xdd,0x08,0x00,0x49,0x89,0xd6}}, + {"__ZN31AppleIntelFramebufferController9LightUpDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x508c4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89,0xce,0x48,0x89,0xd3}}, + {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t",0x1ba02,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x48,0x48,0xff,0x5}}, + {"__ZN31AppleIntelFramebufferController20configureSDPsForHDMIEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x51f56,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x5,0x45,0xde,0x8}}, + {"__ZN17AppleIntelPortHAL17setPhyClockGatingEb",0x606b4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x5,0x9f,0x25,0x8}}, + {"__ZN17AppleIntelPortHAL26getDklPhyLaneConfigBitMaskEv",0x60160,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x5,0x6a,0x29,0x8,0x0,0x48,0xff,0x5,0xb3,0x29,0x8}}, + {"__ZN14AppleIntelPort15writeRegister32Ejj",0x1fea8,{0x55,0x48,0x89,0xe5,0x48,0xff,0x5,0x9d,0x73,0xb,0x0,0x48,0x8b,0xbf,0x78,0x5,0x0,0x0,0x89,0xf6}}, + }; // Native clock replacements retain these DDI reference boundaries. static constexpr uint32_t ddiEnableTailOffset=0x6d7a8; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 80946424..5963194f 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -20,6 +20,7 @@ #include "kern_display13_tc_lightup.hpp" #include "kern_display13_tc_native.hpp" #include "kern_display13_tc_clock.hpp" +#include "kern_display13_tc_dp.hpp" #include "kern_tgl_gmbus.hpp" #include "kern_alderlake_n_gpu.hpp" #include "kern_display13_fb_memory.hpp" @@ -3798,7 +3799,7 @@ Display13OutputWiring XE::display13BoardWiring(IOService *provider) { if(!xeProfile.displayAdmitted() || xeProfile.display!=XELP::Display::XeLPD)return {}; // Display13 physical capacity is shared by ADL-N and mobile ADL/RPL. // VBT chooses board routing; a family name must not exclude TC PHYs. - if(!property)return XELP::VBT::display13HDMI(firmwarePorts,Display13OutputWiring::Physical); + if(!property)return XELP::VBT::display13Fixed(firmwarePorts,Display13OutputWiring::Physical); // Preserve the older ADL-N Combo property contract. It cannot select TC. if(adlpDisplaySelected)return {}; auto wiring=OSDynamicCast(OSData,property); @@ -4099,10 +4100,10 @@ bool XE::xelpBindProbe(void *controller) { } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || !Display13FBTopology::supportedWiring(adlnOutputWiring,xeProfile.display==XELP::Display::XeLPD) || - ((adlnOutputWiring.hdmi&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || - (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; + ((adlnOutputWiring.selected()&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || + (adlnOutputWiring.dp && (!display13TCDPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3; - config.legacy=adlnOutputWiring.hdmi&0x78;config.legacyBackend=config.legacy?0x78:0; + config.legacy=adlnOutputWiring.selected()&0x78;config.legacyBackend=config.legacy?0x78:0; } const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound); @@ -4316,6 +4317,8 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add XELP::display13ProbeCapable(reinterpret_cast(query)); display13TCProbeReady=query>=address && query-address(query)); + display13TCDPProbeReady=query>=address && query-address(query)); } IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13DPProbeAvailable",display13DPProbeReady); IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13TCProbeAvailable",display13TCProbeReady); @@ -6246,7 +6249,7 @@ unsigned XE::display13TCTarget(OSObject *port,bool cleanup) { if(!self->display13FBTCHooksReady || !c.tcSupported || !c.controller || !c.bridge.begun || c.tcBankFaulted || c.bridge.state.faulted || (!cleanup && c.clockFaulted) || !port || port->getMetaClass()->getClassSize()<0x6b0)return UINT32_MAX; auto config=Display13FBMemory::field(port,0x548); - if(!config || !self->adlnOutputWiring.matchesHDMI(config) || config[0]<3 || config[0]>6 || + if(!config || !self->adlnOutputWiring.matches(config) || config[0]<3 || config[0]>6 || !Display13FBTopology::bound(self->adlnPlatform,config,config[0]) || Display13FBMemory::field(port,0x578)!=c.access || Display13FBMemory::field(port,0x120)!=config[0])return UINT32_MAX; @@ -6333,11 +6336,16 @@ void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint auto stage=Display13TC::Clock::LinkStage::Preflight; if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { const auto tc=ddi-3;Display13FBTCIO io{port,tc};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); - rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex); - const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); - // Native modeset must first stop the old scanout; this transaction - // may then retire its PLL before programming a different timing. - ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset,&stage); + if(self->adlnOutputWiring.dp&(1U<(port,0x128); + const auto lanes=Display13FBMemory::field(port,0x124); + const auto plan=Display13TC::DPClock::calculate(linkRate,c.clock.refKHz);rate=plan.rate; + ok=Display13TC::DPSignal::startLink(io,p,plan,lanes,&stage); + } else { + rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex); + const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); + ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset,&stage); + } power=io.linkResult; c.tcSignalReady[tc]=ok; } @@ -6366,11 +6374,72 @@ uint32_t XE::wrapDisplay13TCSignal(void *raw,uint32_t swing,uint32_t emphasis,vo if(!c.lock)return kIOReturnNotReady;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); // TMDS signal parameters were programmed while the path was stopped, // before native enablePipe. Do not run TGL DP's late voltage sequence. - bool ok=false;if(ddi<7){Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && io.linkHeld() && c.tcSignalReady[ddi-3];} + bool ok=false;if(ddi<7){ + 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); + } + } if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); return ok ? 0 : kIOReturnNotReady; } +uint32_t XE::wrapDisplay13DPLightUp(void *controller,void *fb,void *path,const void *timing) { + auto object=static_cast(path); + if(!object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; + auto port=Display13FBMemory::field(path,0x3648); + if(port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(port,0x120)>=3) + return wrapDisplay13TCLightUp(controller,fb,path,timing); + return FunctionCast(wrapDisplay13DPLightUp,callbackXE->orgDisplay13DPLightUp)(controller,fb,path,timing); +} +uint32_t XE::wrapDisplay13DPLaneMask(void *port) { + auto s=callbackXE;auto object=static_cast(port); + auto config=object && object->getMetaClass()->getClassSize()>=0x580?Display13FBMemory::field(port,0x548):nullptr; + if(!config || config[0]>=7 || !(s->adlnOutputWiring.dp&(1U<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; +} +void XE::wrapDisplay13DPPhyGating(void *port,bool gate) { + auto s=callbackXE;auto object=static_cast(port); + auto config=object && object->getMetaClass()->getClassSize()>=0x580?Display13FBMemory::field(port,0x548):nullptr; + if(!config || config[0]>=7 || !(s->adlnOutputWiring.dp&(1U<orgDisplay13DPPhyGating)(port,gate);return; + } + auto &c=display13FBPower;if(!c.lock)return;IOLockLock(c.lock); + 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(); + // 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) { + uint32_t value=0x3e;ok=Display13TC::PHY::access(io,p,0x1000*group+0xa0,0x3e,value,true) && ok; + } + } + if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); +} +void XE::wrapDisplay13DPPortWrite(void *port,uint32_t reg,uint32_t value) { + auto s=callbackXE; + if(reinterpret_cast(__builtin_return_address(0))==s->display13DPImage+0x5fba0) { + auto object=static_cast(port);auto &c=display13FBPower; + if(c.lock && object && object->getMetaClass()->getClassSize()>=0x580) { + 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); + else {adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return;} + } + IOLockUnlock(c.lock); + } + } + FunctionCast(wrapDisplay13DPPortWrite,s->orgDisplay13DPPortWrite)(port,reg,value); +} + uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,const void *timing) { auto self=callbackXE;auto &c=display13FBPower; auto fb=static_cast(rawFB);auto path=static_cast(rawPath); @@ -6381,6 +6450,7 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c struct IO { XE *self;void *controller;IOService *fb;OSObject *path,*object;const void *timing; unsigned ddi {UINT32_MAX},pipeIndex {UINT32_MAX}; + bool dp {}; bool ready(bool clock) { auto &c=display13FBPower;IOLockLock(c.lock); const auto current=display13TCTarget(object); @@ -6389,6 +6459,7 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c Display13FBPowerIO power{c.map}; bool ok=controller==c.controller && timing && current<7 && current==ddi && binding.status==TGLFBBinding::Status::Complete && binding.ddi==ddi && binding.pipe==pipeIndex && + (!dp || !Display13FBMemory::field(object,0x117)) && scaler && scaler->getMetaClass()->getClassSize()>=0x58 && Display13FBMemory::field(scaler,0x10)==fb && Display13FBMemory::field(scaler,0x28)==c.access && !c.memoryFaulted && !c.watermarkFaulted && !c.clockStopped && !c.tcBusy[ddi-3] && @@ -6411,10 +6482,18 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c void pipe(){reinterpret_cast(self->display13TCPipe)(controller,fb,path,timing);} void signal(){wrapDisplay13TCSignal(object,0,0,nullptr);} uint32_t port(){return wrapDisplay13EnablePort(object);} - void packets(){reinterpret_cast(self->display13TCPackets)(controller,fb,path);} + uint32_t train(){ + const auto r=reinterpret_cast(self->display13DPTraining)(object,nullptr); + *(reinterpret_cast(fb)+0x4b84)=r!=0;return r; + } + void packets(){ + if(dp)reinterpret_cast(self->display13DPSDPs)(controller,fb,path,timing); + else reinterpret_cast(self->display13TCPackets)(controller,fb,path); + } } io{self,controller,fb,path,port,timing}; IOLockLock(c.lock);io.ddi=display13TCTarget(port);io.pipeIndex=Display13FBMemory::field(path,0x3640);IOLockUnlock(c.lock); - const auto result=Display13TC::lightUpTMDS(io,io.ddi,io.pipeIndex); + io.dp=io.ddi<7 && (self->adlnOutputWiring.dp&(1U<setProperty("WEXDisplay13TCLightUp",record,sizeof(record)); return result.status; @@ -6430,7 +6509,7 @@ uint32_t XE::wrapDisplay13TCSetMode(void *controller,void *fb,const void *timing bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { using namespace Display13TC::Native; // Do not alter the successful Combo machines' native mode-return behavior. - if(!(adlnOutputWiring.hdmi&0x78) || !display13FBOutputHooksReady || !display13FBSSTHooksReady || + if(!(adlnOutputWiring.selected()&0x78) || !display13FBOutputHooksReady || !display13FBSSTHooksReady || !display13FBABIReady || size<=0x6e000)return false; for(const auto &e:entries) { const auto f=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); @@ -6452,12 +6531,18 @@ bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_ display13TCNotify=address+entries[Notify].offset;display13TCFullClocks=address+entries[FullClocks].offset; display13TCPlane=address+entries[Plane].offset;display13TCScaler=address+entries[Scaler].offset; display13TCPipe=address+entries[Pipe].offset;display13TCPackets=address+entries[Packets].offset; + display13DPTraining=address+entries[Training].offset;display13DPSDPs=address+entries[SDPs].offset; + display13DPImage=address; KernelPatcher::RouteRequest routes[]={ {entries[Mode].symbol,wrapDisplay13TCMode,orgDisplay13TCMode}, {entries[Clocks].symbol,wrapDisplay13TCEnableClocks,orgDisplay13TCEnableClocks}, {entries[DisableClocks].symbol,wrapDisplay13TCDisableClocks,orgDisplay13TCDisableClocks}, {entries[LightUp].symbol,wrapDisplay13TCLightUp,orgDisplay13TCLightUp}, - {entries[SetMode].symbol,wrapDisplay13TCSetMode,orgDisplay13TCSetMode} + {entries[SetMode].symbol,wrapDisplay13TCSetMode,orgDisplay13TCSetMode}, + {entries[DPLightUp].symbol,wrapDisplay13DPLightUp,orgDisplay13DPLightUp}, + {entries[PhyGating].symbol,wrapDisplay13DPPhyGating,orgDisplay13DPPhyGating}, + {entries[LaneMask].symbol,wrapDisplay13DPLaneMask,orgDisplay13DPLaneMask}, + {entries[PortWrite].symbol,wrapDisplay13DPPortWrite,orgDisplay13DPPortWrite} }; // Signal is already routed by the common SST frontend; its wrapper // dispatches TC here. Never install a second hook over the same entry. @@ -6475,7 +6560,7 @@ bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_ !memcmp(reinterpret_cast(address+change.offset),change.after,change.size); patcher.clearError();if(!applied)return false; } - display13FBPower.tcSupported=true;return true; + display13FBDPHooksReady=true;display13FBPower.tcSupported=true;return true; } bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { @@ -6616,11 +6701,16 @@ bool XE::display13AUXReady(void *port,bool command) { Display13FBMemory::field(c.controller,0x1BD8) || !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]>=2 || - Display13FBMemory::field(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)>1 || + if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || config[0]>=7 || config[0]==2 || + Display13FBMemory::field(p,0x120)!=config[0] || + (config[0]<3 && Display13FBMemory::field(p,0x550)>1) || Display13FBMemory::field(p,0x578)!=c.access || Display13FBMemory::field(c.power,0x10)!=c.controller || Display13FBMemory::field(c.power,0x78)!=c.access)return false; + if(config[0]>=3) { + Display13FBTCIO tc{object,config[0]-3}; + if(!(s->adlnOutputWiring.dp&(1U<(p,0x594+4*n)!=ctl+4*n)return false; @@ -6710,7 +6800,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.dp&(1U<(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=1 || + Display13FBMemory::field(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=(config[0]>=3?4U:1U) || Display13FBMemory::field(p,0x578)!=c.access)return nullptr; return p; } @@ -6730,6 +6820,11 @@ bool XE::display13MainLinkAUX(void *port,bool acquire) { Display13FBMemory::field(c.power,0x10)==c.controller && Display13FBMemory::field(c.power,0x78)==c.access) { const auto ddi=Display13FBMemory::field(p,0x120); + 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(); + } if(ddi<2 && Display13FBMemory::field(p,0x590)==0x64000+0x100*ddi) { auto &link=c.mainLink[ddi];const auto previous=link.owner;Display13FBPowerIO io{c.map}; const auto result=acquire ? link.begin(io,c.bridge,reinterpret_cast(port),ddi) : @@ -6813,7 +6908,7 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { if(before) { ok=true; if(pattern==1) { - const auto r=XELP::SSTProgram::prepare(read,write,binding.ddi,binding.pipe,Display13FBMemory::field(p,0x124),true); + const auto r=XELP::SSTProgram::prepare(read,write,binding.ddi,binding.pipe,Display13FBMemory::field(p,0x124),true,binding.ddi>=3?binding.ddi+3:binding.ddi+1); // An already-active, identical route needs no rewrite. Never repair an // active neighbor or a failed posting read by falling through to native. ok=r.bufferBefore!=UINT32_MAX && r.clockBefore!=UINT32_MAX && r.funcBefore!=UINT32_MAX && @@ -7438,7 +7533,7 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { const auto board=s->display13BoardWiring(provider); if(!Display13FBTopology::supportedWiring(board,s->xeProfile.display==XELP::Display::XeLPD) || board.selected()!=s->adlnOutputWiring.selected())return false; - if((board.hdmi&0x78) && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { + if((board.selected()&0x78) && (!s->display13FBTCHooksReady || !s->display13TCProbeReady)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBTCUnavailable",true);return false; } const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); @@ -12333,7 +12428,9 @@ XELP::DisplayContract::Mask XE::display13Available() const { if(display13FBSSTHooksReady)a|=C::SST13; if(display13FBAUXHooksReady)a|=C::AUX13; if(display13DPProbeReady)a|=C::DPProbe13; - // No DPBoot13 until GOP handoff and cold-start have a proven backend. + // Startup still requires the existing TMDS GOP certificate. Fixed TC DP + // is a cold link on that admitted controller; DP GOP adoption stays closed. + if(display13FBDPHooksReady && display13TCDPProbeReady)a|=C::DPBoot13; return a; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e23f235b..4ac3d9ce 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -153,7 +153,7 @@ class XE { static uint32_t wrapDisplay13TrainingPattern(void *path,uint32_t pattern); static uint32_t wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig); mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; - bool display13DPProbeReady {},display13TCProbeReady {}; + bool display13DPProbeReady {},display13TCProbeReady {},display13TCDPProbeReady {},display13FBDPHooksReady {}; bool adlpDisplaySelected {}; bool adlpRenderRequested {}; Display13OutputWiring display13BoardWiring(IOService *); @@ -202,7 +202,13 @@ class XE { mach_vm_address_t orgDisplay13TCMode {},display13TCBaseMode {},orgDisplay13TCEnableClocks {},orgDisplay13TCDisableClocks {}; mach_vm_address_t orgDisplay13TCLightUp {},orgDisplay13TCSetMode {},display13TCNotify {}; mach_vm_address_t display13TCFullClocks {},display13TCPlane {},display13TCScaler {},display13TCPipe {},display13TCPackets {}; - static uint32_t wrapDisplay13TCLightUp(void *,void *,void *,const void *); + static uint32_t wrapDisplay13DPLightUp(void *,void *,void *,const void *); + static void wrapDisplay13DPPhyGating(void *,bool); + static uint32_t wrapDisplay13DPLaneMask(void *); + static void wrapDisplay13DPPortWrite(void *,uint32_t,uint32_t); + mach_vm_address_t orgDisplay13DPLightUp {},display13DPTraining {},display13DPSDPs {}; + mach_vm_address_t orgDisplay13DPPhyGating {},orgDisplay13DPLaneMask {},orgDisplay13DPPortWrite {},display13DPImage {}; + static uint32_t wrapDisplay13TCLightUp(void *,void *,void *,const void *); static uint32_t wrapDisplay13TCSetMode(void *,void *,const void *); bool processDisplay13FramebufferTC(KernelPatcher &,size_t,mach_vm_address_t,size_t); static unsigned display13TCTarget(OSObject *,bool cleanup=false); diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 0a1009f2..0b5ef729 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -83,7 +83,7 @@ struct NativePorts { // legacy permits probing for mode4; it does not force mode4 or classify a // USB-C-capable connector as physically fixed DP. The provider checks generation-specific live state. // Type-C intent is represented but not admitted by either current backend. -constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U,ProbeDisplay13FixedTC=2U; +constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U,ProbeDisplay13FixedTC=2U,ProbeDisplay13FixedDP=4U; using QueryProbeCapabilities=uint32_t (*)(uint32_t,uint32_t); // Optional export: old ABI3 providers remain valid for their existing routes, // but their lack of a capability reply cannot authorize a new DP route. @@ -93,6 +93,10 @@ inline bool display13ProbeCapable(QueryProbeCapabilities query) { inline bool display13TCProbeCapable(QueryProbeCapabilities query) { return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13FixedTC); } +inline bool display13TCDPProbeCapable(QueryProbeCapabilities query) { + return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13FixedDP); +} + struct ProbeConfig { uint32_t abi {ProbeABI},bytes {sizeof(ProbeConfig)}; Profile profile; diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index efde2e22..4e6981b4 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -41,11 +41,15 @@ template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man return legacyTGLMode(io,c[0],b.config.legacy&b.config.legacyBackend); if(c[0]>=3) { 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(!(b.config.legacy&b.config.legacyBackend&(1U<12 || c[2]!=2 || c[3]!=1 || - c[4]!=0x800 || (c[5]&0xB)!=2 || !io.readable(0xC4000) || !io.readable(p.ready))return 0; + !Display13TC::Port::resolve(c[0],p) || (!hdmi && !dp) || + !io.readable(0xC4000) || !io.readable(p.ready))return 0; const auto ready=io.read(p.ready); - return ready!=UINT32_MAX && (ready&7)==4 && p.legacyHPD(io.read(0xC4000)) ? 4 : 0; + // Fixed DP classification precedes setPortMode and AUX. A cold PHY + // can assert ready only after ownership; mode acquisition polls it. + return ready!=UINT32_MAX && (dp ? !(ready&3) : (ready&7)==4) && p.legacyHPD(io.read(0xC4000)) ? 4 : 0; } // Fixed Combo uses the same native mode for HDMI and SST DP. This is // classification only: startup separately proves AUX/PHY/boot operations. diff --git a/WhateverXe/kern_xelp_sst_program.hpp b/WhateverXe/kern_xelp_sst_program.hpp index 8e71c5f2..7aad89fe 100644 --- a/WhateverXe/kern_xelp_sst_program.hpp +++ b/WhateverXe/kern_xelp_sst_program.hpp @@ -8,12 +8,12 @@ namespace XELP { namespace SSTProgram { struct Prepare { uint32_t reason {},clockBefore {},clockWanted {},clockAfter {},funcBefore {},funcWanted {},funcAfter {},bufferBefore {}; }; -template Prepare prepare(Read read,Write write,uint32_t ddi,uint32_t pipe,uint32_t lanes,bool powered) { +template Prepare prepare(Read read,Write write,uint32_t ddi,uint32_t pipe,uint32_t lanes,bool powered,uint32_t clockSelector=0) { Prepare r; if(ddi>=9 || pipe>=3){r.reason=8;return r;} if(!powered){r.reason=5;return r;} const auto clock=0x46140+4*pipe,func=0x60400+0x1000*pipe,buffer=0x64000+0x100*ddi; - const auto routeClock=(ddi+1)<<28,routePort=(ddi+1)<<27; + const auto routeClock=(clockSelector?clockSelector:ddi+1)<<28,routePort=(ddi+1)<<27; r.clockBefore=r.clockAfter=read(clock);r.funcBefore=r.funcAfter=read(func);r.bufferBefore=read(buffer); if(lanes!=1 && lanes!=2 && lanes!=4)r.reason=1; else if((r.funcBefore|r.bufferBefore)&0x80000000U)r.reason=2; diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py index 03de9d3e..e3534208 100644 --- a/tests/adlp_fb_candidate.py +++ b/tests/adlp_fb_candidate.py @@ -23,7 +23,7 @@ XELP::VBT::Ports firmwarePorts; bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; - bool display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; + bool display13TCDPProbeReady=true,display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); void *adlnTopologyController=(void*)1;Display13OutputWiring adlnOutputWiring,tglHDMIWiring; struct {bool valid=false;unsigned excluded=0;unsigned selected(){return 0;}}adlsWiring; @@ -51,6 +51,19 @@ x.firmwarePorts.port[3].type=4; assert(x.display13BoardWiring(&pci).hdmi==0x10); // Exclude DP without dropping unrelated HDMI. x.firmwarePorts.port[4].type=4;assert(!x.display13BoardWiring(&pci).described()); + // DP-capable records with TC AUX F..I compose with the existing HDMI. + for(unsigned ddi=3;ddi<=6;++ddi) { + XE dp;dp.firmwarePorts.status=XELP::VBT::Status::Valid;dp.firmwarePorts.present=1|(1U<{Master});} - for(uint32_t bits:{1U,1U<<16,1U<<30}) { + for(uint32_t bits:{1U<<15,1U<<22,1U<<30}) { IO io;State state;io.r[HPDBank+4]=0x40004|bits;io.r[HPDBank-4]=0; assert(enable(io,state,0x3e000000,NativeGT,1,8)==Status::ForeignIRQ && io.writes==std::vector{Master}); assert(state.failureRegister==HPDBank+4 && state.failureBits==bits); @@ -89,6 +89,12 @@ int main() { IO io;State state;io.r[HPDBank+4]=0x40004;io.r[HPDBank-4]=0;io.rejectRegister=reg; assert(enable(io,state,0x3e000000,NativeGT,1,8)==Status::Readback && !(io.r[Master]&Enable)); } + // HDMI TC1/2 + cold DP TC3: GOP left north TC3/TBT3 enabled. + {IO io;State state;io.r[HPDBank+4]=0x40004;io.r[HPDBank-4]=0; + io.r[HPDBank]=0x40004; + assert(enable(io,state,0x38070000,NativeGT,1,0x38)==Status::Complete); + assert(!(io.r[HPDBank+4]&0x40004) && (io.r[HPDBank-4]&0x40004)==0x40004); + assert(io.r[HPDBank]==0x40004 && (io.r[0xC400C]&0x07000000)==0x07000000);} // ADL-P/N TC legacy HPD is PCH bit24+tc and a separate control bank. // Foreign masked statuses must survive both enable and collect. for(unsigned tc=0;tc<4;++tc)for(unsigned pulse=0;pulse<4;++pulse) { diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index e6bc74ee..9fe303c2 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -25,7 +25,7 @@ struct Map { uint32_t read(uint32_t reg){reads.push_back(reg);return regs.at(reg);} void write(uint32_t reg,uint32_t v){writes.push_back(reg);if(reg==drop)return; if(reg==0x45404)v=(v&~0x5405U)|((v&0xA80A)>>1); - if(reg==0x45454||reg==0x45444)v=(v&~21U)|((v&42)>>1); + if(reg==0x45454||reg==0x45444)v=(v&~0x1555U)|((v&0x2aaaU)>>1); if(reg==base+0x694)for(unsigned n=0;n<4;++n)regs.at(base+0x894+0x100*n)=v; regs.at(reg)=v; } @@ -36,9 +36,19 @@ struct Context { Map *map=nullptr;bool clockFaulted=false,memoryFaulted=false,clockStopped=false; }display13FBPower; struct Display13FBPowerIO { - Map *map;bool readable(uint32_t r){return map&&map->regs.count(r);}uint32_t read(uint32_t r){return map->read(r);} + Map *map;bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned tc){return tc<4;}bool tcAuxHealthy(unsigned tc){return tc<4;} + bool readable(uint32_t r){return map&&map->regs.count(r);}uint32_t read(uint32_t r){return map->read(r);} void write(uint32_t r,uint32_t v){map->write(r,v);}void delayUs(unsigned us){map->delay+=us;if(map->idleReg&&map->delay>=map->idleAfter)map->regs.at(map->idleReg)|=0x80;} }; +// 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; +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);} +}; struct Display13FBSSTIO { Map *map;uint32_t read(uint32_t r){assert(locked);return map->read(r);} void write(uint32_t r,uint32_t v){assert(locked);map->write(r,v);} @@ -104,11 +114,13 @@ unsigned nativePattern(void *path,unsigned pattern){ } void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigned rate){ display13FBPower.~Context();new(&display13FBPower)Context;state={};calls=nativeResult=0;bound=true;loseAfterNative=false; - assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; + tcOwned=tcLinked=false;assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; auto &c=display13FBPower;c.controller=f.ctl;c.access=f.access;c.map=↦ 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)); - f.wiring.hdmi=1U<<(1-ddi);f.wiring.dp=1U<=3?0:1-ddi; + f.wiring.hdmi=1U<=3){put(f.donor,0x20+24*ddi+4,ddi+6);put(f.donor,0x20+24*ddi+12,1U);} assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,f.wiring)); state.adlnOutputWiring=f.wiring;state.adlnPlatform=f.platform;state.adlnTopologyController=f.ctl;state.xeProfile.display=XELP::Display::XeLPD; state.orgDisplay13TrainingPattern=reinterpret_cast(nativePattern);state.orgDisplay13SignalLevels=reinterpret_cast(nativeSignal); @@ -116,7 +128,7 @@ void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigne static uintptr_t vtable[64]{};vtable[0x130/8]=reinterpret_cast(nativeWrite);put(f.access,0,static_cast(vtable)); put(f.ctl,0,unsigned(sizeof(f.ctl)));put(f.fb,0,unsigned(sizeof(f.fb)));put(f.path,0,unsigned(sizeof(f.path))); put(f.plane,0,unsigned(sizeof(f.plane)));put(f.port,0,unsigned(sizeof(f.port))); - put(f.port,0x578,static_cast(f.access));put(f.port,0x550,1U);put(f.port,0x124,lanes);put(f.port,0x128,rate);f.port[0x11A]=1; + put(f.port,0x578,static_cast(f.access));put(f.port,0x550,ddi>=3?4U:1U);put(f.port,0x124,lanes);put(f.port,0x128,rate);f.port[0x11A]=1; map.base=ddi?0x6C000:0x162000; put(f.port,0x620,map.base+0x804);put(f.port,0x5C8,map.base+0x28);put(f.access,0x60,0x200000U); put(f.port,0x590,0x64000+0x100*ddi); @@ -124,13 +136,13 @@ void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigne for(unsigned n=0;n<4;++n)for(auto reg:{0x888U,0x890U,0x894U,0x89CU})map.regs[map.base+0x100*n+reg]=0; map.regs[0x46140+4*pipe]=0;map.regs[0x60400+0x1000*pipe]=0;map.regs[0x64000+0x100*ddi]=0;map.regs[0x60540+0x1000*pipe]=0; Display13FBPowerIO io{&map};using Display13Power::Status; - assert(c.bridge.begin(io,1-ddi,3).status==Status::Complete); + assert(c.bridge.begin(io,peer,f.wiring.selected()).status==Status::Complete); const auto pg=pipe?pipe+2:1; assert(c.bridge.getPG(io,pg).status==Status::Complete&&c.bridge.getDDI(io,ddi).status==Status::Complete); assert(c.bridge.getAUX(io,ddi).status==Status::Complete&&c.bridge.putAUX(io,ddi).status==Status::Complete); // Remove boot peer first: training must depend only on its actual resources. - assert(c.bridge.adoptBootPath(1,0,1-ddi).status==Status::Complete); - assert(c.bridge.putDDI(io,1-ddi).status==Status::Complete&&c.bridge.putPG(io,1).status==Status::Complete); + assert(c.bridge.adoptBootPath(1,0,peer).status==Status::Complete); + assert(c.bridge.putDDI(io,peer).status==Status::Complete&&c.bridge.putPG(io,1).status==Status::Complete); assert(c.bridge.outputHeld(io,ddi,pipe));map.reads.clear();map.writes.clear(); } int main(int argc,char **argv){ @@ -194,6 +206,27 @@ int main(int argc,char **argv){ assert(XE::wrapDisplay13TrainingPattern(f.path,1)==kIOReturnNotReady&&calls==completedCalls&&map.writes.empty()&&map.reads.empty()); ++count; } + // 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}){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,lanes,rate); + Display13FBPowerIO power{&map};auto &bridge=display13FBPower.bridge; + Display13Power::Lease modeAUX; + assert(Display13Power::acquire(power,bridge.state,modeAUX,Display13Power::auxDomain(ddi)).status==Display13Power::Status::Complete); + assert(!XE::display13MainLinkAUX(f.port,true));tcOwned=true;tcLinked=true; + assert(XE::display13MainLinkAUX(f.port,true)); + assert(!XE::wrapDisplay13TrainingPattern(f.path,1)); + assert(map.regs[0x46140+4*pipe]==((ddi+3)<<28)); + assert(map.regs[0x60400+0x1000*pipe]==(((ddi+1)<<27)|0x02000000|((lanes-1)<<1))); + assert(!XE::wrapDisplay13EnablePort(f.port)); + assert(!XE::wrapDisplay13TrainingPattern(f.path,0)); + assert(!XE::wrapDisplay13DisablePort(f.port)); + assert(objectRefs.empty() && Display13Power::held(power,bridge.state,Display13Power::auxDomain(ddi))); + assert(!bridge.nativeAUXRefs(ddi)); + tcOwned=false;assert(!XE::display13MainLinkAUX(f.port,true)); + assert(Display13Power::release(power,bridge.state,modeAUX).status==Display13Power::Status::Complete); + } for(unsigned fault=0;fault<25;++fault){ Fixture f(2,1,1);Map map;setup(f,map,1,1,4,20);auto &c=display13FBPower; if(fault==0)c.bridge.state.suspended=true;if(fault==1)c.bridge.state.faulted=true; diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index 7edd254d..9ed6ff63 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -1,4 +1,4 @@ -#include "kern_display13_tc_clock.hpp" +#include "kern_display13_tc_dp.hpp" #include #include #include @@ -9,9 +9,10 @@ struct IO { std::vector> writes; bool held {},power {true},owner {true},idle {true},stuckPower {},stuckLock {},fault {}; unsigned time {},locks {},unlocks {};uint32_t reject {},absent {}; + unsigned expectedLanes {4}; bool link {},failLink {},coldCleanup {};unsigned linkGets {},linkPuts {}; bool linkPower(bool enable) { - if(enable){assert((regs[Clock::laneRegister(p.tc)]&Clock::laneMask(p.tc))==Clock::laneMask(p.tc));assert((regs[p.pll]&0xcc000000U)==0xcc000000U && !(regs[Clock::Gate]&Clock::gateBit(p.tc)));++linkGets;if(failLink)return false;link=true;} + if(enable){assert((regs[Clock::laneRegister(p.tc)]&Clock::laneMask(p.tc))==((1U<0) && count!=fail;} + void step(unsigned n){assert(n==++count);} + void clocks(){step(1);}uint32_t train(){step(2);return trainingError;} + bool transClock(unsigned r,unsigned v){step(3);assert(r>=0x46140 && r<=0x46148 && v>=0x60000000);return count!=fail;} + void plane(){step(4);}void scaler(){step(5);}void pipe(){step(6);}void packets(){step(7);} +}; int main(){ for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ IO io;const auto r=lightUpTMDS(io,ddi,pipe); @@ -37,6 +45,18 @@ int main(){ unsigned notify=0;assert(finishMode([](){return 0U;},[&](){++notify;})==0 && notify==0); assert(finishMode([](){return 0xe00002d8U;},[&](){++notify;})==0xe00002d8U && notify==1); + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ + DPIO io;io.fail=99;const auto r=lightUpDP(io,ddi,pipe); + assert(!r.status && r.stage==LightStage::Complete && io.count==7); + } + const LightStage dpStages[]={LightStage::Binding,LightStage::Clocks,LightStage::Training,LightStage::TransClock, + LightStage::Plane,LightStage::Scaler,LightStage::Pipe,LightStage::Packets}; + for(unsigned fail=1;fail<=7;++fail){DPIO io;io.fail=fail;const auto r=lightUpDP(io,5,1); + assert(r.status && r.stage==dpStages[fail] && io.count==fail);} + for(auto error:{1U,0xe00002d6U,0xe00002d8U}){DPIO io;io.fail=99;io.trainingError=error; + const auto r=lightUpDP(io,5,1);assert(r.status==error && io.count==2 && r.stage==LightStage::Training);} + DPIO rejected;assert(lightUpDP(rejected,5,0).status && !rejected.count); + // Execute the actual x86 patch bytes with both native flag outcomes. // Error keeps eax and skips all post-mode work. Success reaches the same // flag-dependent destinations as the donor. No real driver code executes. diff --git a/tests/display13_tc_native.py b/tests/display13_tc_native.py index 9f8eaafd..da7b11e5 100644 --- a/tests/display13_tc_native.py +++ b/tests/display13_tc_native.py @@ -13,7 +13,7 @@ assert int.from_bytes(arrays['outerAfter'][4:8],'little',signed=True)+0x695ac+8==0x698ed assert 0x695ac+18+arrays['outerAfter'][17]==0x695fc install=s[s.index('bool XE::processDisplay13FramebufferTC('):s.index('bool XE::processDisplay13FramebufferOutput(')] -assert '!(adlnOutputWiring.hdmi&0x78)' in install +assert '!(adlnOutputWiring.selected()&0x78)' in install assert 'entries[LightUp].symbol,wrapDisplay13TCLightUp' in install assert 'entries[SetMode].symbol,wrapDisplay13TCSetMode' in install assert 'wrapDisplay13TCSignal,' not in install # one common SST signal hook @@ -27,7 +27,7 @@ subprocess.check_output(['nm','-n',str(path)],text=True).splitlines() if len(line.split())==3 and re.fullmatch('[0-9a-fA-F]+',line.split()[0])} entries=re.findall(r'\{"([^"\n]+)",(0x[0-9a-f]+),\{([^}]+)\}\}',h) - assert len(entries)==12 + assert len(entries)==18 for symbol,value,body in entries: offset=int(value,0);prefix=bytes(int(v,0) for v in body.split(',')) assert symbols[symbol]==offset and donor[offset:offset+len(prefix)]==prefix @@ -37,4 +37,14 @@ # normal unlock path. These are independent of the new outer early return. assert donor[0x50004:0x50009]==bytes.fromhex('4585ed7437') assert donor[0x5001b:0x50020]==bytes.fromhex('e82cd3ffff') - print('PASS exact c004fc1a: 12 entries, full TMDS sequence, native cleanup, both mode-return boundaries') + print('PASS exact c004fc1a: 18 entries, full TMDS sequence, native cleanup, both mode-return boundaries') + + # Ordinary probe classifies and acquires mode before any DP AUX access. + # Qualify the virtual slots and direct call targets independently of wrappers. + for at,expected in [(0x1faad,'ff9078010000'),(0x1fabb,'ff9138010000'), + (0x1fb38,'ff9038010000'),(0x1bdbe,'ff9088010000'), + (0x1be04,'ff9040010000'),(0x1be1e,'ff9028010000')]: + assert donor[at:at+6]==bytes.fromhex(expected),hex(at) + for at,target in [(0x50979,0x1ba02),(0x50b0c,0x51f56),(0x5fb9b,0x1fea8)]: + assert donor[at]==0xe8 and at+5+int.from_bytes(donor[at+1:at+5],'little',signed=True)==target + print('PASS native DP mode-before-AUX, training signal/gating/enable slots and SDP/stagger caller ABI') diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index a1800d1b..761937ef 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -16,6 +16,7 @@ using C=XELP::DisplayContract; struct State { FIELDS + bool display13FBDPHooksReady=false,display13TCDPProbeReady=false; struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; bool tglHDMIB=false,adlsDisplaySelected=false,tglTrainingLeaseRequested=true; struct {struct {bool valid=false;}panel;}firmwarePorts; @@ -38,6 +39,9 @@ s.display13FBAUXHooksReady=true;s.display13FBSSTHooksReady=false; assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::SST13)); s.display13FBSSTHooksReady=true; + s.display13FBDPHooksReady=true;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); + s.display13TCDPProbeReady=true;assert(C::display13(s.display13Available(),true).ready()); + s.display13FBDPHooksReady=false;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); // TGL: candidate not yet registered, native donor setup is complete. // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. s.tglTCAutoCandidates=8;assert(s.start().ready()); diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 9c0c5ff6..00a00bad 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -31,8 +31,10 @@ int main(int argc,char **argv) { assert(!XELP::display13TCProbeCapable(nullptr)); assert(!XELP::display13TCProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return XELP::ProbeDisplay13ComboDP;})); assert(XELP::display13TCProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return XELP::ProbeDisplay13FixedTC;})); + 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):0)); + assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP):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); @@ -121,6 +123,12 @@ int main(int argc,char **argv) { field(mmio.data(),0x161500)=ready;field(mmio.data(),0xc4000)=hpd<<24; assert(tglProbePort(&port)==(hpd && (ready&7)==4?4U:0U)); } + rec[2]=1;rec[4]=0x400;rec[5]=0x201; + for(unsigned ready=0;ready<16;++ready)for(unsigned hpd=0;hpd<2;++hpd){ + field(mmio.data(),0x161500)=ready;field(mmio.data(),0xc4000)=hpd<<24; + assert(tglProbePort(&port)==(hpd && !(ready&3)?4U:0U)); + } + rec[1]=10;assert(!tglProbePort(&port)); assert(!hardwareAllowed(&controller));puts("PASS actual ADL-P Display13 provider: physical identity, legacy TC probe, no Display12/GT fallback");return 0; } for(unsigned d=0;d<9;++d){auto rec=reinterpret_cast(platform+0x20+24*d); From 47666de33fb1156947326f47f3cb653f9704df24 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:19:02 +0900 Subject: [PATCH 28/60] Connect Display13 DP training reservations and link lifecycle --- WhateverXe/kern_display13_tc_dp.hpp | 18 ++ WhateverXe/kern_display13_training_native.hpp | 35 +++ WhateverXe/kern_xe.cpp | 290 ++++++++++++++++-- WhateverXe/kern_xe.hpp | 26 +- WhateverXe/kern_xelp_display_contract.hpp | 2 +- tests/display13_sst_fixture.cpp | 3 + tests/display13_tc_clock.cpp | 9 +- tests/display13_training_frontend.py | 70 +++++ tests/display13_training_lease.py | 128 ++++++++ tests/display_contract_adapter.py | 9 +- tests/xelp_display_contract.cpp | 2 +- tools/test.py | 4 +- 12 files changed, 562 insertions(+), 34 deletions(-) create mode 100644 WhateverXe/kern_display13_training_native.hpp create mode 100644 tests/display13_training_frontend.py create mode 100644 tests/display13_training_lease.py diff --git a/WhateverXe/kern_display13_tc_dp.hpp b/WhateverXe/kern_display13_tc_dp.hpp index a1836c8e..228fecad 100644 --- a/WhateverXe/kern_display13_tc_dp.hpp +++ b/WhateverXe/kern_display13_tc_dp.hpp @@ -31,6 +31,24 @@ struct DPClock : TMDSClock { return {}; } }; +// Retain only a link programmed by this owner, with matching live PLL/FIA. +// AGDC success leaves it enabled before native adoption and LightUp. +struct DPRetainedLink { + uint32_t rate {},reference {},lanes {}; + template bool matches(IO &io,const Port &p,const TMDSClock &plan,unsigned count) const { + if(!plan.valid() || !rate || rate!=plan.rate || reference!=plan.reference || lanes!=count || + (count!=1 && count!=2 && count!=4) || !io.owned(p.ddi) || !io.linkHeld())return false; + const uint32_t registers[]={p.pll,p.clock,Clock::Gate,Clock::laneRegister(p.tc)}; + for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false; + if((io.read(p.pll)&0xcc000000U)!=0xcc000000U || (io.read(p.clock)&0xf0000000U)!=0x80000000U || + (io.read(Clock::Gate)&Clock::gateBit(p.tc)) || + (io.read(Clock::laneRegister(p.tc))&Clock::laneMask(p.tc))!=((1U<3 || emphasis>3 || swing+emphasis>3)return false; diff --git a/WhateverXe/kern_display13_training_native.hpp b/WhateverXe/kern_display13_training_native.hpp new file mode 100644 index 00000000..c4f360a4 --- /dev/null +++ b/WhateverXe/kern_display13_training_native.hpp @@ -0,0 +1,35 @@ +// Exact c004fc1a donor interfaces for pre-modeset SST training and adoption. +#ifndef KERN_DISPLAY13_TRAINING_NATIVE_HPP +#define KERN_DISPLAY13_TRAINING_NATIVE_HPP +#include +namespace Display13TrainingNative { +enum Index {Begin,Train,End,AllocateAny,AllocateFixed,Free,Add,Resources,Stop,PortTrain,Lookup,Port}; +struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[16];}; +static constexpr Entry entries[]={ + {"__ZN31AppleIntelFramebufferController19doLinkTrainingBeginEP25AGDCDPLinkTrainingBegin_t",0x6aae0,{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0xff,0x05,0xd3,0xa1,0x07,0x00,0x48,0x85,0xf6}}, + {"__ZN31AppleIntelFramebufferController14doLinkTrainingEP18AGDCDPPortConfig_tS1_",0x6ab7e,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x68,0xa1}}, + {"__ZN31AppleIntelFramebufferController17doLinkTrainingEndEP23AGDCDPLinkTrainingEnd_t",0x6ad06,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x28,0xa0}}, + {"__ZN21AppleIntelDisplayPath12allocatePathEv",0x9188a,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x23,0xa7,0x05,0x00,0xe8,0x4e,0x9a,0xf8,0xff}}, + {"__ZN21AppleIntelDisplayPath12allocatePathEj",0x91950,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x8d,0xa6,0x05,0x00,0xe8,0x64,0xfe,0xff,0xff}}, + {"__ZN21AppleIntelDisplayPath8freePathEv",0x91d88,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x85,0xa3,0x05,0x00,0x5d,0xe9,0xb3,0xf7,0xff}}, + {"__ZN21AppleIntelFramebuffer14addDisplayPathEj",0x68d3e,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0xd4,0xb9,0x07,0x00,0x48,0x8b}}, + {"__ZN21AppleIntelFramebuffer28allocateDisplayPathResourcesEjj",0x679a6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x38,0xca}}, + {"__ZN31AppleIntelFramebufferController4stopEP9IOService",0x44982,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xf6,0x49,0x89}}, + {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t",0x1ba02,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec}}, + {"__ZN21AppleIntelDisplayPath18getFirstPathByPortEP14AppleIntelPort",0x91bca,{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0xff,0x05,0xc1,0xa4,0x05,0x00,0x48,0x85,0xff}}, + {"__ZN14AppleIntelPort12getPortByDDIEj",0x1b1ea,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x53,0xb0,0x0b,0x00,0x44,0x8b,0x05,0xbc,0xd8}}, +}; +struct Span {uint32_t offset;uint8_t length;uint8_t bytes[40];}; +static constexpr Span spans[]={ + {0x6ac66,12,{0x48,0x8d,0x73,0x08,0x4c,0x89,0xff,0xe8,0xaa,0x08,0xfb,0xff}}, + {0x6ac7d,15,{0x4c,0x89,0xe7,0x4c,0x89,0xfe,0x31,0xd2,0x31,0xc9,0xe8,0x84,0x21,0x00,0x00}}, + {0x6acc1,11,{0x4c,0x89,0xff,0x4c,0x89,0xf6,0xe8,0x36,0x0d,0xfb,0xff}}, + {0x6ad44,28,{0x48,0x8b,0x00,0x4c,0x89,0xff,0xff,0x90,0x30,0x01,0x00,0x00,0x45,0x31,0xe4,0x4c,0x89,0xf7,0x4c,0x89,0xfe,0x31,0xd2,0xe8,0x86,0x2d,0x00,0x00}}, + {0x67a2d,8,{0x48,0x89,0xdf,0xe8,0x09,0x13,0x00,0x00}}, + {0x67a75,22,{0x44,0x89,0xe7,0xe8,0x1f,0x37,0xfb,0xff,0x48,0xff,0x05,0xec,0xcc,0x07,0x00,0x49,0x89,0x86,0x48,0x36,0x00,0x00}}, + {0x1e328,39,{0x41,0xc6,0x47,0x10,0x00,0x41,0x38,0x57,0x0e,0x75,0x0e,0x48,0xff,0x05,0xb6,0x89,0x0b,0x00,0x41,0x80,0x7f,0x0d,0x00,0x74,0x0e,0x48,0xff,0x05,0xa0,0x89,0x0b,0x00,0xc0,0xe8,0x07,0x41,0x88,0x47,0x10}}, + {0x1b607,20,{0x0f,0xb6,0x43,0x1e,0x48,0x01,0x05,0x66,0xad,0x0b,0x00,0x34,0x01,0x88,0x83,0x1a,0x01,0x00,0x00,0x0f}}, + {0x4d980,16,{0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00}}, +}; +} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 5963194f..0214c299 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -21,6 +21,7 @@ #include "kern_display13_tc_native.hpp" #include "kern_display13_tc_clock.hpp" #include "kern_display13_tc_dp.hpp" +#include "kern_display13_training_native.hpp" #include "kern_tgl_gmbus.hpp" #include "kern_alderlake_n_gpu.hpp" #include "kern_display13_fb_memory.hpp" @@ -4392,6 +4393,7 @@ struct Display13FBPowerContext { OSObject *tcObject[4] {}; uint32_t tcBusy[4] {}; bool tcSignalReady[4] {}; + Display13TC::DPRetainedLink tcDPLink[4]; Display13FBPipeContext pipe[3]; Display13Power::NativeBridge bridge; Display13Power::MainLinkAUX mainLink[2]; @@ -6312,7 +6314,7 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { 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 bool done=result.status==Display13TC::Status::Complete; - if(!wanted && done){c.tcSignalReady[tc]=false;OSSafeReleaseNULL(c.tcObject[tc]);} + 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}; IOLockUnlock(c.lock);auto root=IORegistryEntry::getRegistryRoot(); root->setProperty("WEXDisplay13TCMode",record,sizeof(record)); @@ -6340,7 +6342,12 @@ void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint const auto linkRate=Display13FBMemory::field(port,0x128); const auto lanes=Display13FBMemory::field(port,0x124); const auto plan=Display13TC::DPClock::calculate(linkRate,c.clock.refKHz);rate=plan.rate; - ok=Display13TC::DPSignal::startLink(io,p,plan,lanes,&stage); + 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); + if(ok)c.tcDPLink[tc]={plan.rate,plan.reference,lanes}; + } } else { rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex); const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); @@ -6357,11 +6364,17 @@ void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; if(config && config[0]<3){FunctionCast(wrapDisplay13TCDisableClocks,self->orgDisplay13TCDisableClocks)(controller,raw,other);return;} + // Native AGDC End ignores the port-disable return. Independently require + // transcoder stop before dropping DDI/PLL, including retry and abort paths. + if(config && config[0]<7 && (self->adlnOutputWiring.dp&(1U<display13ClockOrderReady && + !display13StopTranscoder(raw)) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ClockStopRejected",true);return; + } if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port,true);bool ok=false; if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { Display13FBTCIO io{port,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); ok=Display13TC::Clock::stopLink(io,p); - if(ok)c.tcSignalReady[ddi-3]=false; + if(ok){c.tcSignalReady[ddi-3]=false;c.tcDPLink[ddi-3]={};} } if(!ok)adlnFault(c.clockFaulted,__LINE__); uint32_t record[]={1,ddi,ok};IOLockUnlock(c.lock); @@ -6468,7 +6481,7 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c IOLockUnlock(c.lock);return ok; } void clocks() { - auto &c=display13FBPower;IOLockLock(c.lock);c.tcSignalReady[ddi-3]=false;IOLockUnlock(c.lock); + auto &c=display13FBPower;IOLockLock(c.lock);if(!dp)c.tcSignalReady[ddi-3]=false;IOLockUnlock(c.lock); reinterpret_cast(self->display13TCFullClocks)(controller,fb,path); } bool transClock(uint32_t reg,uint32_t value) { @@ -6483,7 +6496,7 @@ uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,c void signal(){wrapDisplay13TCSignal(object,0,0,nullptr);} uint32_t port(){return wrapDisplay13EnablePort(object);} uint32_t train(){ - const auto r=reinterpret_cast(self->display13DPTraining)(object,nullptr); + const auto r=wrapDisplay13PortTraining(object,nullptr); *(reinterpret_cast(fb)+0x4b84)=r!=0;return r; } void packets(){ @@ -6560,7 +6573,7 @@ bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_ !memcmp(reinterpret_cast(address+change.offset),change.after,change.size); patcher.clearError();if(!applied)return false; } - display13FBDPHooksReady=true;display13FBPower.tcSupported=true;return true; + display13FBDPHooksReady=true;display13FBPower.tcSupported=true;return processDisplay13Training(patcher,index,address,size); } bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { @@ -6754,6 +6767,166 @@ uint32_t XE::wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,ui IOLockUnlock(c.lock);return r ? r : complete ? 0 : kIOReturnNotReady; } +bool XE::processDisplay13Training(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(!adlnOutputWiring.dp)return true; + using namespace Display13TrainingNative; + if(!display13FBDPHooksReady || !display13FBSSTHooksReady || !display13FBPowerReferencesReady || + !orgDisplay13GMBusWrite || size<=0x91DA0)return false; + for(const auto &e:entries) { + const auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); + if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.prefix,sizeof(e.prefix)))return false; + } + for(const auto &span:spans)if(memcmp(reinterpret_cast(address+span.offset),span.bytes,span.length))return false; + tglPathPoolLock=IORecursiveLockAlloc();if(!tglPathPoolLock)return false; + for(unsigned ddi=0;ddi(wrapDisplay13DisablePort); + tglDisableClocks=reinterpret_cast(wrapDisplay13TCDisableClocks); + display13TrainingReady=tglTrainingLeaseReady=true; + display13FramingReady=display13ClockOrderReady=true; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TrainingReady",true); + return true; +} + +// The native reservation/adoption algorithm is shared with Display12. This +// adapter proves Display13 ownership and uses its IRQ-aware PG bridge. +bool XE::display13LeaseValidate(void *controller,void *port,uint32_t pipe) { + auto s=callbackXE;auto &c=display13FBPower;if(!c.lock)return false; + IOLockLock(c.lock);auto p=display13SSTPort(port); + const bool bound=p && controller==c.controller && controller==s->adlnTopologyController && + Display13FBMemory::field(controller,0xD40)==s->adlnPlatform && + Display13FBMemory::field(controller,0xC8)==c.power && + !c.bridge.state.faulted && !c.bridge.state.suspended; + Display13FBPowerIO io{c.map}; + const bool ok=bound && (pipe==UINT32_MAX || c.bridge.pipeHeld(io,pipe)); + if(ok)s->tglObservationController=controller; + IOLockUnlock(c.lock);return ok; +} +bool XE::display13LeasePower(void *power,uint32_t pg,bool acquire) { + return adlnPowerReference(power,acquire?1:2,pg); +} +bool XE::display13LeaseStop(void *controller,void *port) { + auto &c=display13FBPower;if(!c.lock || controller!=c.controller)return false; + const auto stopped=[&]() { + IOLockLock(c.lock);auto p=display13SSTPort(port);bool ok=false; + if(p) { + const auto ddi=Display13FBMemory::field(p,0x120);Display13TC::Port phy; + if(Display13TC::Port::resolve(ddi,phy)) { + Display13FBTCIO io{static_cast(port),ddi-3}; + if(io.powerHeld()) { + const auto pll=io.read(phy.pll); + ok=pll!=UINT32_MAX && !(pll&0xcc000000U) && !c.bridge.nativeDDIRefs(ddi) && !c.tcSignalReady[ddi-3]; + } + } + } + IOLockUnlock(c.lock);return ok; + }; + if(stopped())return true; + // Native abort is void and can have left a retained clock. The TC teardown + // checks ownership/idle and never puts an absent DDI reference on a retry. + wrapDisplay13TCDisableClocks(controller,port,nullptr);return stopped(); +} +bool XE::display13TrainingPath(void *path,uint32_t &ddi,uint32_t &pipe) { + auto s=callbackXE; + if(!s->display13TrainingReady || !s->tglPathPoolLock || !path)return false; + bool found=false;IORecursiveLockLock(s->tglPathPoolLock); + for(const auto &lease:s->tglTrainingLeases)if(lease.path==path && lease.busy && + lease.trainingThread==current_thread() && lease.powerOwned && lease.prepared && !lease.adopted && !lease.endCleanupPending) { + auto fb=static_cast(lease.fb);auto object=static_cast(path); + found=fb && fb->getMetaClass()->getClassSize()>=0x4B90 && object->getMetaClass()->getClassSize()>=0x3730 && + lease.pipe<3 && lease.ddi<7 && (s->trainingLeaseDP13Mask&(1U<(path,0x32B0)==fb && + Display13FBMemory::field(path,0x3648)==lease.port && + Display13FBMemory::field(path,0x3640)==lease.pipe && + Display13FBMemory::field(path,0x3644)==1 && + !Display13FBMemory::field(fb,0x49E8) && !Display13FBMemory::field(fb,0x4A00) && + Display13FBMemory::associations(s->adlnTopologyController,0x1C20,fb,fb->getMetaClass()->getClassSize(), + display13FBExpectedOwner(s->adlnTopologyController),true); + if(found){ddi=lease.ddi;pipe=lease.pipe;}break; + } + IORecursiveLockUnlock(s->tglPathPoolLock);return found; +} +uint32_t XE::wrapDisplay13PortTraining(void *port,void *request) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->display13FramingReady || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13PortTraining,s->orgDisplay13PortTraining)(port,request); + if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);auto p=display13SSTPort(port); + const bool valid=p && p[0x115]==1 && !p[0x117] && p[0x10]<=1 && p[0x11A]<=1; + if(valid)p[0x11A]&=p[0x10]; + IOLockUnlock(c.lock);if(!valid)return kIOReturnNotReady; + return FunctionCast(wrapDisplay13PortTraining,s->orgDisplay13PortTraining)(port,request); +} +// Deferred native clock writes are per pipe and protected by the display +// owner lock. The native port disable finishes them before PLL/DDI teardown. +struct Display13DeferredClock {void *port {};uint32_t value {};bool pending {};}; +static Display13DeferredClock display13DeferredClocks[3]; +bool XE::display13DeferClock(void *access,uint64_t reg,uint32_t value,uintptr_t caller) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->display13ClockOrderReady || caller!=s->display13DPImage+0x4D990 || !c.lock || + reg<0x46140 || reg>0x46148 || (reg&3))return false; + const auto pipe=unsigned((reg-0x46140)/4);void *port=nullptr; + // The native caller owns the path/FB mutex. Look up the real pool entry, + // including partial modesets not yet published to the scanout allocator. + for(unsigned ddi=3;ddi<7;++ddi)if(s->adlnOutputWiring.dp&(1U<(s->tglPortByDDI)(ddi); + auto path=candidate?reinterpret_cast(s->orgTGLPathLookup)(candidate):nullptr; + if(path && Display13FBMemory::field(path,0x3640)==pipe && + Display13FBMemory::field(path,0x3648)==candidate) {port=candidate;break;} + } + IOLockLock(c.lock);Display13FBPowerIO power{c.map}; + const bool selected=port && access==c.access; + if(selected) { + auto &deferred=display13DeferredClocks[pipe]; + if(display13SSTPort(port) && c.bridge.pipeHeld(power,pipe) && !deferred.pending && !(value&0xf0000000U))deferred={port,value,true}; + else adlnFault(c.clockFaulted,__LINE__); + } + IOLockUnlock(c.lock);return selected; +} +bool XE::display13StopTranscoder(void *port) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->display13ClockOrderReady || !s->orgTGLPathLookup || !c.lock)return false; + auto path=reinterpret_cast(s->orgTGLPathLookup)(port); + if(!path)return true; // A failed early probe may have no pipe at all. + auto object=static_cast(path); + if(object->getMetaClass()->getClassSize()<0x3730)return false; + const auto pipe=Display13FBMemory::field(path,0x3640); + if(pipe>=3 || Display13FBMemory::field(path,0x3648)!=port)return false; + IOLockLock(c.lock);auto p=display13SSTPort(port);Display13FBPowerIO power{c.map};bool ok=false; + if(p && c.bridge.pipeHeld(power,pipe)) { + const auto ddi=Display13FBMemory::field(port,0x120);Display13FBSSTIO io{c.map}; + const auto buffer=io.read(0x64000+0x100*ddi),conf=io.read(0x70008+0x1000*pipe), + func=io.read(0x60400+0x1000*pipe),tp=io.read(0x60540+0x1000*pipe),clock=io.read(0x46140+4*pipe); + auto &deferred=display13DeferredClocks[pipe]; + ok=buffer!=UINT32_MAX && conf!=UINT32_MAX && func!=UINT32_MAX && tp!=UINT32_MAX && clock!=UINT32_MAX && + !(buffer&0x80000000U) && !(conf&0xc0000000U) && !((func|tp)&0x80000000U) && + (!(clock&0xf0000000U) || (clock>>28)==ddi+3) && (!deferred.pending || deferred.port==port); + if(ok) { + if(!(buffer&0x80U))IODelay(8); // DDI disable: idle or >=8us before clock removal. + const auto value=deferred.pending?deferred.value:(clock&0x0fffffffU); + io.write(0x46140+4*pipe,value);ok=io.read(0x46140+4*pipe)==value; + if(ok)deferred={}; + } + } + IOLockUnlock(c.lock);return ok; +} + bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // Install the bounded Combo backend independently of selecting DP at startup. // Startup still requires AUX/probe and DP boot-handoff operation closure. @@ -6855,6 +7028,7 @@ uint32_t XE::wrapDisplay13DisablePort(void *port) { return FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); if(!display13MainLinkAUX(port,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); return result ? result : released ? 0 : kIOReturnNotReady; } @@ -6893,13 +7067,19 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { if(!c.lock || !object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; if(display13HDMIPort(Display13FBMemory::field(path,0x3648))) return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + uint32_t reservedDDI=UINT32_MAX,reservedPipe=UINT32_MAX; + const bool reserved=display13TrainingPath(path,reservedDDI,reservedPipe); // Never hold the nonrecursive power lock over donor training or AUX calls. // Both register stages revalidate the graph, logical leases and live power. const auto program=[&](bool before) { IOLockLock(c.lock);bool ok=false; auto p=display13SSTPort(Display13FBMemory::field(path,0x3648)); auto fb=Display13FBMemory::field(path,0x32B0); - const auto binding=p ? adlnInspectPathBinding(fb,object,s->adlnPlatform,s->adlnOutputWiring,false) : TGLFBBinding::Readout{}; + auto binding=p ? adlnInspectPathBinding(fb,object,s->adlnPlatform,s->adlnOutputWiring,false) : TGLFBBinding::Readout{}; + if(p && reserved && reservedDDI==Display13FBMemory::field(p,0x120) && + reservedPipe==Display13FBMemory::field(path,0x3640)) { + binding.status=TGLFBBinding::Status::Complete;binding.ddi=reservedDDI;binding.pipe=reservedPipe; + } Display13FBPowerIO io{c.map}; if(binding.status==TGLFBBinding::Status::Complete && c.bridge.outputHeld(io,binding.ddi,binding.pipe)) { Display13FBSSTIO registers{c.map}; @@ -7189,6 +7369,7 @@ void XE::wrapAlderLakeNGamma10(void *path,void *transaction,uint32_t control) { } void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { + if(display13DeferClock(access,reg,value,reinterpret_cast(__builtin_return_address(0))))return; // DISPLAY13_COMBO_FRACTION: qualified nominal writer, physical reference, // retained controller/manager. No MMIO or other port's PLL inference. if(callbackXE->display13FramebufferRequested && callbackXE->xeProfile.display==XELP::Display::XeLPD && @@ -9571,8 +9752,8 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { if(result.status==Display13Power::Status::Complete)wrapAlderLakeNEngineEnable(power); } -void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { - auto &c=display13FBPower; if(!c.lock)return; +bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { + auto &c=display13FBPower; if(!c.lock)return false; if(operation==1 && domain>=1 && domain<=4 && power==c.power) { IOLockLock(c.lock);const bool enable=!c.engineEnabled && !c.bridge.state.suspended && !c.bridge.state.faulted;IOLockUnlock(c.lock); if(enable)wrapAlderLakeNEngineEnable(power); @@ -9723,6 +9904,7 @@ void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { // the gate this invocation acquired without waiting, including failure. // No ACK, queue rewrite, source replacement or refcount substitution. if(acquiredGate)reinterpret_cast(callbackXE->adlnWorkLoopOpen)(acquiredGate); + return result.status==Display13Power::Status::Complete; } void XE::wrapAlderLakeNPowerGetPG(void *p,uint32_t d) { adlnPowerReference(p,1,d); } void XE::wrapAlderLakeNPowerPutPG(void *p,uint32_t d) { adlnPowerReference(p,2,d); } @@ -12431,6 +12613,9 @@ XELP::DisplayContract::Mask XE::display13Available() const { // Startup still requires the existing TMDS GOP certificate. Fixed TC DP // is a cold link on that admitted controller; DP GOP adoption stays closed. if(display13FBDPHooksReady && display13TCDPProbeReady)a|=C::DPBoot13; + if(display13TrainingReady)a|=C::Training12; + if(display13FramingReady)a|=C::Framing; + if(!display13ClockOrderReady)a&=~C::ClockOrder; return a; } @@ -16186,7 +16371,8 @@ uint32_t XE::wrapTGLAGDCPortStatus(void *controller, void *request, void *output // the resource pool multi-port must not silently enable an unimplemented PHY. XE::TGLTrainingLease *XE::tglTrainingLeaseForDDI(uint32_t ddi) { auto s = callbackXE; - if (!s->tglTrainingLeaseReady || ddi >= TGLPortCount || !s->tglDPSSTEligible(ddi)) return nullptr; + if (!s->tglTrainingLeaseReady || ddi >= TGLPortCount || + !(s->trainingLeaseValidate ? (s->trainingLeaseDP13Mask & (1U<tglDPSSTEligible(ddi))) return nullptr; auto &lease = s->tglTrainingLeases[ddi]; return &lease; } @@ -16260,8 +16446,10 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) auto port = static_cast(reinterpret_cast(s->tglPortByDDI)(lease.ddi)); if (!ctl || !port) return 1; const auto route=s->tglSSTRoutes().external(lease.ddi,*reinterpret_cast(port + 0x550)); - if (*reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || - !route.needsLease() || port[0x117]) return 1; + if (s->trainingLeaseValidate) { + if(!s->trainingLeaseValidate(controller,port,UINT32_MAX))return 1; + } else if (*reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || + !route.needsLease() || port[0x117]) return 1; auto access = *reinterpret_cast(ctl + 0xC40); auto power = *reinterpret_cast(ctl + 0xC8); if (!access || !power || *reinterpret_cast(access + 0x60) < 0x16A000 || @@ -16274,6 +16462,9 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) } lease.busy = true; if (lease.path) { + if(s->trainingLeaseValidate && (!lease.powerOwned || !lease.prepared || lease.adopted)) { + lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock);return 8; + } IORecursiveLockUnlock(s->tglPathPoolLock); tglObserveTrainingLease(lease, 1, 0); return 0; @@ -16305,11 +16496,11 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) lease.busy = false; IORecursiveLockUnlock(s->tglPathPoolLock); return 7; } lease.path = path; lease.port = port; lease.fb = fb; lease.power = power; - lease.busy = true; lease.adopted = false; ++lease.generation; + lease.busy = true; lease.adopted = false; lease.prepared=false; ++lease.generation; lease.clocksActive = false; lease.endCleanupPending = false; lease.pipe = pipe; // Own both consumers. Pipe A alone needs PW1, but a TC-numbered lane // still needs PW1..3 even when firmware/AUX/other displays hold no refs. - lease.pg = route.powerGroup(pipe); + lease.pg = s->trainingLeaseValidate ? (pipe ? pipe+2 : 1) : route.powerGroup(pipe); static_cast(lease.fb)->retain(); static_cast(lease.port)->retain(); static_cast(lease.power)->retain(); @@ -16319,9 +16510,17 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) // Keep the selected domain powered while checking, training and restoring // its registers. No pool lock is held across native power waits. SYSLOG("igfx", "TGL330 prepare power-enter ddi=%u pipe=%u pg=%u", lease.ddi, pipe, lease.pg); - reinterpret_cast(s->tglEnablePG)(power, lease.pg); + if(s->trainingLeasePower) { + if(!s->trainingLeasePower(power,lease.pg,true)) { + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock); + return 8; // Keep the native reservation on an unproven power transition. + } + } else reinterpret_cast(s->tglEnablePG)(power, lease.pg); SYSLOG("igfx", "TGL330 prepare power-return ddi=%u", lease.ddi); lease.powerOwned = true; + if(s->trainingLeaseValidate && !s->trainingLeaseValidate(controller,port,pipe)) { + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock);return 8; + } const uint32_t clock = *reinterpret_cast(mmio + 0x46140 + 4*pipe); const uint32_t func = *reinterpret_cast(mmio + 0x60400 + 0x1000*pipe); const uint32_t tp = *reinterpret_cast(mmio + 0x60540 + 0x1000*pipe); @@ -16331,7 +16530,11 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) // No training register was changed. Drop only this reservation and its // power reference; never restore over unexpected live hardware. tglObserveTrainingLease(lease, 0, 5); - reinterpret_cast(s->tglDisablePG)(power, lease.pg); + if(s->trainingLeasePower) { + if(!s->trainingLeasePower(power,lease.pg,false)) { + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock);return 8; + } + } else reinterpret_cast(s->tglDisablePG)(power, lease.pg); IORecursiveLockLock(s->tglPathPoolLock); reinterpret_cast(s->orgTGLFreePath)(path); lease.path = lease.port = lease.fb = lease.power = nullptr; @@ -16342,7 +16545,7 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) return 5; } SYSLOG("igfx", "TGL330 prepare registers-valid ddi=%u", lease.ddi); - lease.savedClock = clock; lease.savedFunc = func; lease.savedTP = tp; + lease.savedClock = clock; lease.savedFunc = func; lease.savedTP = tp;lease.prepared=true; tglObserveTrainingLease(lease, 0, 0); tglObserveTrainingLease(lease, 1, 0); return 0; @@ -16353,9 +16556,11 @@ bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { auto s = callbackXE; const auto pipe = lease.pipe; if (!lease.path) return true; - if (!lease.powerOwned || pipe >= 3 || lease.ddi >= TGLPortCount) return false; + if (!lease.powerOwned || pipe >= 3 || lease.ddi >= TGLPortCount || + (s->trainingLeaseValidate && (!lease.prepared || lease.adopted))) return false; auto fb = static_cast(lease.fb); auto ctl = *reinterpret_cast(fb + 0x1D0); + if(s->trainingLeaseValidate && !s->trainingLeaseValidate(ctl,lease.port,pipe))return false; auto access = *reinterpret_cast(fb + 0x4A40); auto mmio = *reinterpret_cast(access + 0x50); tglObserveTrainingLease(lease, 4, 0, disableLink); @@ -16385,6 +16590,9 @@ bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { if (!hardwareIdle()) { tglObserveTrainingLease(lease, 4, 1); return false; // never free active or unreadable hardware } + if(s->trainingLeaseStop && !s->trainingLeaseStop(ctl,lease.port)) { + tglObserveTrainingLease(lease,4,6);return false; + } *reinterpret_cast(mmio + 0x46140 + 4*pipe) = lease.savedClock; (void)*reinterpret_cast(mmio + 0x46140 + 4*pipe); *reinterpret_cast(mmio + 0x60400 + 0x1000*pipe) = lease.savedFunc; @@ -16398,7 +16606,8 @@ bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { } if (lease.powerOwned) { tglTransitionRecord(37,false,lease.power,lease.pg,lease.ddi,0); - reinterpret_cast(s->tglDisablePG)(lease.power, lease.pg); + if(s->trainingLeasePower) {if(!s->trainingLeasePower(lease.power,lease.pg,false))return false;} + else reinterpret_cast(s->tglDisablePG)(lease.power, lease.pg); tglTransitionRecord(37,true,lease.power,lease.pg,lease.ddi,0); } lease.powerOwned = false; @@ -16450,7 +16659,13 @@ void XE::wrapTGLControllerStop(void *controller, IOService *provider) { IORecursiveLockUnlock(s->tglPathPoolLock); // Native resource allocation runs outside the pool lock too. Drain those // FB claims before touching their leases or tearing down the native pool. - for (;;) { + for (unsigned waited=0;;++waited) { + if(s->trainingLeaseValidate && waited==500) { + // A void stop cannot cancel an in-flight native call. Keep the + // controller/pool alive when refusing teardown; admission is closed. + if(__sync_bool_compare_and_swap(&s->tglStopRetained,false,true))static_cast(controller)->retain(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13StopBusy",true);return; + } IORecursiveLockLock(s->tglPathPoolLock); bool allocating = false; for (auto fb : s->tglAllocatingFB) allocating |= fb != nullptr; @@ -16461,7 +16676,13 @@ void XE::wrapTGLControllerStop(void *controller, IOService *provider) { // Block new reservations first, then drain every existing port. Do not hold // the shared pool lock across native power waits or port shutdown. for (auto &lease : s->tglTrainingLeases) { - for (;;) { + for (unsigned waited=0;;++waited) { + if(s->trainingLeaseValidate && waited==500) { + // A void stop cannot cancel an in-flight native call. Keep the + // controller/pool alive when refusing teardown; admission is closed. + if(__sync_bool_compare_and_swap(&s->tglStopRetained,false,true))static_cast(controller)->retain(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13StopBusy",true);return; + } IORecursiveLockLock(s->tglPathPoolLock); if (!lease.busy) { lease.busy = true; IORecursiveLockUnlock(s->tglPathPoolLock); break; } IORecursiveLockUnlock(s->tglPathPoolLock); @@ -16479,9 +16700,20 @@ void XE::wrapTGLControllerStop(void *controller, IOService *provider) { IORecursiveLockUnlock(s->tglPathPoolLock); fb->release(); port->release(); power->release(); } + if (s->trainingLeaseValidate) { + // This stop owns the claim. Admission remains closed; release it so + // a later stop can retry if another lease is still in flight. + IORecursiveLockLock(s->tglPathPoolLock); + lease.busy = false; + IORecursiveLockUnlock(s->tglPathPoolLock); + } } FunctionCast(wrapTGLControllerStop, s->orgTGLControllerStop)(controller, provider); tglRecordStartOrder(8,controller); + if(__sync_bool_compare_and_swap(&s->tglStopRetained,true,false)) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13StopBusy",false); + static_cast(controller)->release(); + } } uint32_t XE::wrapTGLTrainingBegin(void *controller, void *request) { @@ -16548,6 +16780,7 @@ uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { r = kIOReturnBusy; } else { SYSLOG("igfx", "TGL330 training native-enter id=%u", id); + if(selected) {IORecursiveLockLock(callbackXE->tglPathPoolLock);selected->trainingThread=current_thread();IORecursiveLockUnlock(callbackXE->tglPathPoolLock);} r = FunctionCast(wrapTGLTraining, callbackXE->orgTGLTraining)(controller, request, output); SYSLOG("igfx", "TGL330 training native-return id=%u result=%x", id, r); } @@ -16558,7 +16791,7 @@ uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { lease.clocksActive = true; tglObserveTrainingLease(lease, 2, r, output ? *reinterpret_cast(static_cast(output) + 0x38) : 0xFFFFFFFFU); IORecursiveLockLock(callbackXE->tglPathPoolLock); - lease.busy = false; + lease.busy = false;lease.trainingThread=nullptr; IORecursiveLockUnlock(callbackXE->tglPathPoolLock); } tglObservePath(observedPort, nullptr, 6, true, r, output ? *reinterpret_cast(static_cast(output) + 0x38) : 0, __builtin_return_address(0)); @@ -16865,7 +17098,18 @@ bool XE::wrapTGLAllocatePathResources(void *fb, uint32_t portIndex, uint32_t str if (selected->adopted) { // Native addDisplayPath populated the map/FB arrays and the caller // acquired its own PG reference. Drop only our extra reservation ref. - if (selected->powerOwned) reinterpret_cast(s->tglDisablePG)(selected->power, selected->pg); + if (selected->powerOwned) { + if(s->trainingLeasePower) { + if(!s->trainingLeasePower(selected->power,selected->pg,false)) { + // Native owns the adopted path; retain only our extra power/object + // references. Never hand that path out for a second adoption. + IORecursiveLockLock(s->tglPathPoolLock); + selected->endCleanupPending=true;selected->adoptThread=nullptr;selected->busy=false; + if(claimed)s->tglAllocatingFB[portIndex]=nullptr; + IORecursiveLockUnlock(s->tglPathPoolLock);return false; + } + } else reinterpret_cast(s->tglDisablePG)(selected->power,selected->pg); + } selected->powerOwned = false; tglObserveTrainingLease(*selected, 8, r ? 0 : 1); auto oldFB = static_cast(selected->fb); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 4ac3d9ce..8dd2a733 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -148,6 +148,17 @@ class XE { static uint32_t wrapDisplay13EnablePort(void *port); static uint32_t wrapDisplay13DisablePort(void *port); bool processDisplay13FramebufferSST(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool display13TrainingReady {},display13FramingReady {},display13ClockOrderReady {}; + mach_vm_address_t orgDisplay13PortTraining {}; + bool processDisplay13Training(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static bool display13LeaseValidate(void *controller,void *port,uint32_t pipe); + static bool display13LeasePower(void *power,uint32_t pg,bool acquire); + static bool display13LeaseStop(void *controller,void *port); + static bool display13TrainingPath(void *path,uint32_t &ddi,uint32_t &pipe); + static uint32_t wrapDisplay13PortTraining(void *port,void *request); + static bool display13StopTranscoder(void *port); + static bool display13DeferClock(void *access,uint64_t reg,uint32_t value,uintptr_t caller); + static uint8_t *display13SSTPort(void *port); static bool display13HDMIPort(void *port); static uint32_t wrapDisplay13TrainingPattern(void *path,uint32_t pattern); @@ -284,7 +295,7 @@ class XE { static void wrapAlderLakeNPowerPutDDI(void *power, uint32_t ddi); static void wrapAlderLakeNPowerGetAUX(void *power, uint32_t aux); static void wrapAlderLakeNPowerPutAUX(void *power, uint32_t aux); - static void adlnPowerReference(void *power, uint32_t operation, uint32_t domain); + static bool adlnPowerReference(void *power, uint32_t operation, uint32_t domain); static Display13Clock::Readout adlnClockReadout(IOService *controller); static void wrapAlderLakeNClockInit(IOService *controller); static uint64_t wrapAlderLakeNClockProbe(IOService *controller); @@ -393,16 +404,23 @@ class XE { // One reservation per physical port; the native pool limits simultaneous pipes. struct TGLTrainingLease { void *path {}, *port {}, *fb {}, *power {}, *adoptThread {}; - bool busy {}, powerOwned {}, adopted {}, clocksActive {}; + bool busy {}, powerOwned {}, adopted {}, clocksActive {}, prepared {}; + void *trainingThread {}; // Native abort-End already ran; retry only our retained cleanup. bool endCleanupPending {}; uint32_t generation {}, savedClock {}, savedFunc {}, savedTP {}; uint32_t pipe {}, pg {1}, ddi {}; }; - static constexpr uint32_t TGLPortCount = 9; + // Optional Display13 adapter. The native pool/adoption protocol is shared; + // register domains, identity and power results remain backend-specific. + uint32_t trainingLeaseDP13Mask {}; + bool (*trainingLeaseValidate)(void *,void *,uint32_t) {}; + bool (*trainingLeasePower)(void *,uint32_t,bool) {}; + bool (*trainingLeaseStop)(void *,void *) {}; + static constexpr uint32_t TGLPortCount = 9; TGLTrainingLease tglTrainingLeases[TGLPortCount] {}; void *tglAllocatingFB[TGLPortCount] {}; - bool tglLeasesStopping {}; + bool tglLeasesStopping {},tglStopRetained {}; static TGLTrainingLease *tglTrainingLeaseForDDI(uint32_t ddi); static TGLTrainingLease *tglAdoptingLease(); // caller holds the pool lock static bool tglLeaseFBReserved(void *fb, const TGLTrainingLease *except = nullptr); diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 860ff4c8..709a79d6 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -105,7 +105,7 @@ struct DisplayContract { return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0)| (panel?Combo12|PanelDepth|PanelTiming|PanelPWM|AUXParent|PowerGet|PowerPut:0),installed}; } - static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13; + static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13|Training12|Framing; static DisplayContract display13(Mask installed,bool dp=false) {return {Backend13|(dp?DP13:0),installed};} static const char *name(Mask bit) { switch(bit) { diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index 9fe303c2..0d828d80 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -56,6 +56,9 @@ struct Display13FBSSTIO { bool adlnWriterMappingBound(){return bound;} uintptr_t display13FBExpectedOwner(void*){return 0;} struct XE { + bool display13ClockOrderReady=false; + static bool display13StopTranscoder(void*){assert(false);return false;} + static bool display13TrainingPath(void*,uint32_t&,uint32_t&){return false;} bool display13FramebufferRequested=true,display13FBSSTHooksReady=true,display13FBTCHooksReady=false;XELP::Profile xeProfile; Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp index 9ed6ff63..22cdf99f 100644 --- a/tests/display13_tc_clock.cpp +++ b/tests/display13_tc_clock.cpp @@ -23,6 +23,7 @@ struct IO { {Clock::Gate,0xdeadffff|Clock::gateBit(p.tc)},{p.hip,0x01010101},{Clock::laneRegister(p.tc),0xa5a50000}}; for(auto w:TMDSClock::calculate(148500,38400).words)bank[w.reg]=0xa5a5a5a5; } + bool linkHeld(){return power&&link;} bool powerHeld(){return power;} bool owned(unsigned d){return owner && d==p.ddi;} bool quiescent(unsigned d){return idle && d==p.ddi;} @@ -65,7 +66,13 @@ int main() { assert((io.bank[group*0x1000+0xa0]&0xc0)==(lanes==1?(group?0x40U:0U):0x80U)); assert(!(io.bank[group*0x1000+0x2c8]&0x7c)); } - io.idle=false; // Training signal changes are valid with a live buffer. + DPRetainedLink retained{plan.rate,plan.reference,lanes}; + assert(retained.matches(io,io.p,plan,lanes)); + auto mismatch=retained;mismatch.lanes=lanes==4?2:4;assert(!mismatch.matches(io,io.p,plan,lanes)); + const auto pll=io.regs[io.p.pll];io.regs[io.p.pll]&=~Clock::Locked; + assert(!retained.matches(io,io.p,plan,lanes));io.regs[io.p.pll]=pll; + io.idle=false;assert(retained.matches(io,io.p,plan,lanes)); // Active training link is retained. + // Training signal changes are valid with a live buffer. for(unsigned swing=0;swing<4;++swing)for(unsigned pre=0;pre+swing<4;++pre)assert(DPSignal::program(io,io.p,rates[i],swing,pre)); assert(!DPSignal::program(io,io.p,rates[i],3,1)); io.idle=true;assert(Clock::stopLink(io,io.p) && io.linkPuts==1); diff --git a/tests/display13_training_frontend.py b/tests/display13_training_frontend.py new file mode 100644 index 00000000..28ebce3e --- /dev/null +++ b/tests/display13_training_frontend.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Production Display13 temporary binding, framing and deferred clock frontend. + +Uses real native object contracts and the power bridge with mock MMIO; no claim +about physical output or completeness follows from these regression cases. +""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() +def fn(sig):return re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() +functions='\n'.join(fn(x) for x in ['uint8_t *XE::display13SSTPort(', 'bool XE::display13HDMIPort(', 'bool XE::display13MainLinkAUX(', 'uint32_t XE::wrapDisplay13EnablePort(', 'uint32_t XE::wrapDisplay13DisablePort(', 'uint32_t XE::wrapDisplay13SignalLevels(', 'uint32_t XE::wrapDisplay13TrainingPattern(', 'bool XE::display13TrainingPath(', 'uint32_t XE::wrapDisplay13PortTraining(', 'bool XE::display13DeferClock(', 'bool XE::display13StopTranscoder(']) +binding=fn('static TGLFBBinding::Readout adlnInspectPathBinding(') +native=(r/'tests/tgl_fb_binding.cpp').read_text().split('int main()')[0].replace('port[0x580]','port[0x700]') +code=(r/'tests/display13_sst_fixture.cpp').read_text().split('int main(')[0] +code=code.replace('// NATIVE_OBJECTS',native).replace('// BINDING',binding) +lease=re.search(r'\tstruct TGLTrainingLease \{.*?\n\t\};',h,re.S).group() +code=code.replace('struct XE {','''struct XE { + LEASE + TGLTrainingLease tglTrainingLeases[9]; + bool display13TrainingReady=false,display13FramingReady=false; + unsigned trainingLeaseDP13Mask=0;IORecursiveLock *tglPathPoolLock=nullptr; + uintptr_t orgDisplay13PortTraining=0,orgTGLPathLookup=0,tglPortByDDI=0,display13DPImage=0; + static uint32_t wrapDisplay13PortTraining(void*,void*); + static bool display13DeferClock(void*,uint64_t,uint32_t,uintptr_t); +'''.replace('LEASE',lease)) +code=code.replace('static bool display13StopTranscoder(void*){assert(false);return false;}','static bool display13StopTranscoder(void*);') +code=code.replace('static bool display13TrainingPath(void*,uint32_t&,uint32_t&){return false;}','static bool display13TrainingPath(void*,uint32_t&,uint32_t&);') +code=code.replace('// FUNCTIONS',re.search(r'struct Display13DeferredClock .*?static Display13DeferredClock display13DeferredClocks\[3\];',s,re.S).group()+'\n'+functions) +prefix='''#include +#include +#include +using IORecursiveLock=std::recursive_mutex; +void IORecursiveLockLock(IORecursiveLock*p){p->lock();}void IORecursiveLockUnlock(IORecursiveLock*p){p->unlock();} +void *current_thread(){return (void*)1;}unsigned delayUs=0;void IODelay(unsigned n){delayUs+=n;} +void adlnFault(bool &v,unsigned){v=true;} +''' +extra=r''' +void *testPort;void *testLookup(void*p){return p==testPort?currentPath:nullptr;} +void *testByDDI(unsigned ddi){return Display13FBMemory::field(testPort,0x120)==ddi?testPort:nullptr;} +unsigned nativeTrain(void*p,void*){assert(p==testPort&&!locked);return XE::wrapDisplay13TrainingPattern(currentPath,1);} +int main(){IORecursiveLock pool; + for(unsigned ddi=3;ddi<7;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned cap=0;cap<2;++cap)for(unsigned wanted=0;wanted<2;++wanted){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,4,6);testPort=f.port; + state.display13TrainingReady=state.display13FramingReady=state.display13ClockOrderReady=true; + state.trainingLeaseDP13Mask=0x78;state.tglPathPoolLock=&pool;state.display13DPImage=0x100000; + state.orgTGLPathLookup=(uintptr_t)testLookup;state.tglPortByDDI=(uintptr_t)testByDDI;state.orgDisplay13PortTraining=(uintptr_t)nativeTrain; + put(f.path,0x32c8,static_cast(nullptr));put(f.path,0x3644,1U);f.port[0x115]=1;f.port[0x10]=cap;f.port[0x11a]=wanted; + auto &lease=state.tglTrainingLeases[ddi];lease.path=f.path;lease.port=f.port;lease.fb=f.fb;lease.power=display13FBPower.power; + lease.busy=lease.powerOwned=lease.prepared=true;lease.trainingThread=current_thread();lease.ddi=ddi;lease.pipe=pipe; + unsigned d=99,p=99;assert(XE::display13TrainingPath(f.path,d,p)&&d==ddi&&p==pipe); + lease.trainingThread=nullptr;assert(!XE::display13TrainingPath(f.path,d,p));lease.trainingThread=current_thread(); + lease.adopted=true;assert(!XE::display13TrainingPath(f.path,d,p));lease.adopted=false; + put(f.fb,0x49e8,1U);assert(!XE::display13TrainingPath(f.path,d,p));put(f.fb,0x49e8,0U); + assert(!XE::wrapDisplay13PortTraining(f.port,nullptr));assert(f.port[0x11a]==(cap&wanted)); + assert((map.regs[0x60540+0x1000*pipe]&0x40000U)==((cap&wanted)<<18)); + const auto clock=0x46140+4*pipe;assert(map.regs[clock]==((ddi+3)<<28)); + assert(!XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d991)); + assert(XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d990));assert(map.regs[clock]==((ddi+3)<<28)); + map.regs[0x70008+0x1000*pipe]=0;map.regs[0x64000+0x100*ddi]=0x80000000; + assert(!XE::display13StopTranscoder(f.port)&&map.regs[clock]==((ddi+3)<<28)); + map.regs[0x64000+0x100*ddi]=0x80;map.regs[0x60540+0x1000*pipe]&=~0x80000000U; + assert(XE::display13StopTranscoder(f.port)&&map.regs[clock]==0&&!display13DeferredClocks[pipe].pending); + } + puts("PASS production Display13 pre-plane reservation binding, sink framing and DDI-before-transcoder-clock stop; mock MMIO only"); +} +''' +with tempfile.TemporaryDirectory(prefix='display13-training-front-') as d: + p=Path(d);(p/'test.cpp').write_text(prefix+code+extra) + 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_training_lease.py b/tests/display13_training_lease.py new file mode 100644 index 00000000..ea46c4d5 --- /dev/null +++ b/tests/display13_training_lease.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Shared production lease/adoption with exact donor AGDC and native path lookup. + +Power/PHY/sink boundaries are mocks. This detects integration and ownership +regressions; it does not establish hardware readiness or requirement coverage. +""" +from pathlib import Path +import hashlib,re,struct,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() +fields=h[h.index('\tstruct TGLTrainingLease {'):h.index('\tmach_vm_address_t orgTGLGetOSInformation')] +body=s[s.index('XE::TGLTrainingLease *XE::tglTrainingLeaseForDDI'):s.index('void XE::tglObserveActivation(')] +adopt=s[s.index('bool XE::wrapTGLAllocatePathResources'):s.index('void *XE::wrapTGLAddPath')] +f=(r/'tests/tgl_training_leases_fixture.cpp').read_text().split('int main(')[0] +f=f.replace('// TC_WIRING_FIELDS',h[h.index('\tbool tglAUXParentReady'):h.index('\tstatic void wrapTGLEnableAUXPower')]) +f=f.replace('// LEASE_FIELDS',fields).replace('// LEASE_FUNCTIONS',body+adopt) +b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +# Symbols resolve pool globals and native boundary entries from the accepted donor. +pos=32;symbols={} +for _ in range(struct.unpack_from(' +#include +#include +uint8_t *image;void *nativePaths[4];unsigned linkCalls,clockStarts,clockStops;bool rejectPowerGet,rejectPowerPut,stoppedProof; +bool clockProof(void*,void*){return stoppedProof;} +void jump(unsigned off,uintptr_t target){image[off]=0x48;image[off+1]=0xb8;memcpy(image+off+2,&target,8);image[off+10]=0xff;image[off+11]=0xe0;} +void *builtIn(){return nullptr;}bool hpd(void*){return false;} +uint32_t dpcd(void*){return 0;} +void setLink(void *p,void*){assert(p);} +void setupClock(void*,void*){} +void enableClock(void*,void*,void*,void*){++clockStarts;} +void stopClock(void*c,void*p,void*o){++clockStops;disableClocks(c,p,o);} +void *poolAllocate(){auto p=reinterpret_cast(image+0x9188a)(); + if(p)for(unsigned i=0;i<3;++i)if(p==paths[i].data())used[i]=true;return p;} +void *poolLookup(void*p){return reinterpret_cast(image+0x91bca)(p);} +uint32_t sinkTraining(void*p,void*out){ + ++linkCalls;auto path=poolLookup(p);assert(path); // r23 failed here, before sink/PHY training. + auto &lease=obj.tglTrainingLeases[reinterpret_cast(p)==ports[3].data()?3: + reinterpret_cast(p)==ports[4].data()?4:reinterpret_cast(p)==ports[5].data()?5:6]; + assert(path==lease.path && lease.powerOwned && lease.trainingThread==current_thread()); + assert(pgRefs[lease.pipe?lease.pipe+2:1]==1); + assert(!*reinterpret_cast(static_cast(path)+0x32c8)); // No primary plane yet. + static_cast(out)[0x38/4]=0;return 0; +} +bool adapterValidate(void*c,void*p,uint32_t pipe){ + if(c!=ctl.data() || !p)return false; + return pipe==UINT32_MAX || (pipe<3 && pgRefs[pipe?pipe+2:1]>0); +} +bool adapterPower(void*p,uint32_t pg,bool get){ + if((get&&rejectPowerGet)||(!get&&rejectPowerPut))return false; + if(get)enablePG(p,pg);else disablePG(p,pg);return true; +} +void init(unsigned ddi,unsigned pipe){ + setup();rejectPowerGet=rejectPowerPut=false;linkCalls=clockStarts=clockStops=0; + obj.trainingLeaseDP13Mask=0x78;obj.trainingLeaseValidate=adapterValidate;obj.trainingLeasePower=adapterPower; + obj.orgTGLPathLookup=(uintptr_t)poolLookup;obj.orgTGLAllocatePathAny=(uintptr_t)poolAllocate; + obj.orgTGLTrainingBegin=(uintptr_t)(image+0x6aae0);obj.orgTGLTraining=(uintptr_t)(image+0x6ab7e);obj.orgTGLTrainingEnd=(uintptr_t)(image+0x6ad06); + for(unsigned i=0;i<3;++i){nativePaths[i]=paths[i].data();paths[i].at(0x3644)=i==pipe?0:2;} + // Other native pipes are reserved; free pipe is selected by the actual allocator. + static uint32_t config[6];config[0]=ddi;config[2]=1;ports[ddi].at(0x548)=config;ports[ddi].at(0x11)=4; + static uintptr_t vt[64];vt[0x130/8]=(uintptr_t)disablePort;ports[ddi].at(0)=vt; +} +int main(int argc,char**argv){ + std::ifstream input(argv[1],std::ios::binary);std::vector b((std::istreambuf_iterator(input)),{}); + image=(uint8_t*)mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0);assert(image!=MAP_FAILED);memcpy(image,b.data(),b.size()); + INSTALL + assert(image[0x6aba6]==0xe8);jump(0x300000,(uintptr_t)memcpy); + int32_t copyCall=0x300000-0x6aba6-5;memcpy(image+0x6aba7,©Call,4); + *reinterpret_cast(image+PATHS)=nativePaths;*reinterpret_cast(image+COUNT)=3; + for(unsigned ddi=3;ddi<7;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ + init(ddi,pipe);uint32_t req[20]={ddi+1},out[20]={};req[4]=4U<<24; + assert(!XE::wrapTGLTrainingBegin(ctl.data(),req));assert(!poolLookup(ports[ddi].data())); + assert(!XE::wrapTGLTraining(ctl.data(),req,out));auto &lease=obj.tglTrainingLeases[ddi];auto path=lease.path; + assert(path==paths[pipe].data()&&linkCalls==1&&clockStarts==1); + assert(!XE::wrapTGLTraining(ctl.data(),req,out));assert(lease.path==path&&clockStops==1&&clockStarts==2); + req[2]=1;assert(!XE::wrapTGLTrainingEnd(ctl.data(),req));assert(lease.path==path); + // Native resource allocation acquires its own PG before returning. The + // production wrapper must return only the reservation's extra reference. + nativeBoundary=[&]{enablePG(power.data(),lease.pg);}; + assert(XE::wrapTGLAllocatePathResources(fbs[0].data(),ddi,0));nativeBoundary={}; + assert(!lease.path&&pgRefs[pipe?pipe+2:1]==1);disablePG(power.data(),pipe?pipe+2:1);freePath(path);clean(); + // Abort End consumes native stop exactly once and releases the reservation. + init(ddi,pipe);req[2]=0;assert(!XE::wrapTGLTraining(ctl.data(),req,out)); + assert(!XE::wrapTGLTrainingEnd(ctl.data(),req));assert(!obj.tglTrainingLeases[ddi].path&&clockStops==1);clean(); + } + // A void native clock-stop return is not a completed cleanup. + init(5,1);uint32_t endReq[20]={6},endOut[20]={}; + assert(!XE::wrapTGLTraining(ctl.data(),endReq,endOut));obj.trainingLeaseStop=clockProof;stoppedProof=false; + assert(XE::wrapTGLTrainingEnd(ctl.data(),endReq)==kIOReturnBusy&&clockStops==1); + assert(obj.tglTrainingLeases[5].path&&obj.tglTrainingLeases[5].powerOwned); + stoppedProof=true;assert(!XE::wrapTGLTrainingEnd(ctl.data(),endReq)&&clockStops==1);clean(); + // An in-flight call blocks native teardown. The refusal is bounded and the + // controller stays retained until a later real stop can drain the pool. + init(5,1);assert(!XE::wrapTGLTraining(ctl.data(),endReq,endOut)); + obj.tglTrainingLeases[5].busy=true;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(!stopCalled&&obj.tglStopRetained&&retained[ctl.data()]==1); + obj.tglTrainingLeases[5].busy=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(stopCalled&&!obj.tglStopRetained);clean(); + // Failed power acquisition retains the reservation and cannot train on retry. + init(5,1);uint32_t req[20]={6},out[20]={};rejectPowerGet=true; + assert(XE::wrapTGLTraining(ctl.data(),req,out)==kIOReturnBusy&&linkCalls==0); + assert(XE::wrapTGLTraining(ctl.data(),req,out)==kIOReturnBusy&&linkCalls==0); + XE::wrapTGLControllerStop(ctl.data(),nullptr);used[1]=false;clean(); + munmap(image,0x400000); + puts("PASS production Display13 lease dispatch with native AGDC Begin/Train/retry/End and pool lookup; mock power/sink/adoption boundaries"); +} +''' +install=[] +for symbol,fn in [('__ZN14AppleIntelPort14getPortByIndexEj','getPort'),('__ZN14AppleIntelPort14getBuiltInPortEv','builtIn'),('__ZN14AppleIntelPort8isHPDLowEv','hpd'),('__ZN14AppleIntelPort11getDPCDInfoEv','dpcd'),('__ZN14AppleIntelPort13setLinkConfigEP18AGDCDPLinkConfig_t','setLink'),('__ZN31AppleIntelFramebufferController11setupClocksEP14AppleIntelPort','setupClock'),('__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams','enableClock'),('__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_','stopClock'),('__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t','sinkTraining')]: + install.append(f'jump(0x{symbols[symbol]:x},(uintptr_t){fn});') +extra=extra.replace('INSTALL','\n '.join(install)).replace('PATHS',str(symbols['__ZN21AppleIntelDisplayPath6fPathsE'])).replace('COUNT',str(symbols['__ZN21AppleIntelDisplayPath10fPathCountE'])) +with tempfile.TemporaryDirectory(prefix='display13-training-') as d: + p=Path(d);(p/'check.cpp').write_text(f+extra) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'check.cpp'),'-o',str(p/'check')],check=True) + subprocess.run([str(p/'check'),sys.argv[1]],check=True) diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index 761937ef..3ee64731 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -16,7 +16,7 @@ using C=XELP::DisplayContract; struct State { FIELDS - bool display13FBDPHooksReady=false,display13TCDPProbeReady=false; + bool display13FBDPHooksReady=false,display13TCDPProbeReady=false,display13ClockOrderReady=true; struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; bool tglHDMIB=false,adlsDisplaySelected=false,tglTrainingLeaseRequested=true; struct {struct {bool valid=false;}panel;}firmwarePorts; @@ -29,7 +29,7 @@ int main() { State s; assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM)); - assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13)); + assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13|C::Training12|C::Framing)); assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); s.display13DPProbeReady=false; assert(C::display13(s.display13Available()).ready()); // Existing provider/HDMI stays valid. @@ -41,6 +41,11 @@ s.display13FBSSTHooksReady=true; s.display13FBDPHooksReady=true;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); s.display13TCDPProbeReady=true;assert(C::display13(s.display13Available(),true).ready()); + s.display13TrainingReady=false;assert(C::display13(s.display13Available(),true).missing()==C::Training12); + s.display13TrainingReady=true;s.display13ClockOrderReady=false; + assert(C::display13(s.display13Available(),true).missing()==C::ClockOrder); + s.display13ClockOrderReady=true;s.display13FramingReady=false; + assert(C::display13(s.display13Available(),true).missing()==C::Framing);s.display13FramingReady=true; s.display13FBDPHooksReady=false;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); // TGL: candidate not yet registered, native donor setup is complete. // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index f8142383..9a50cd4d 100644 --- a/tests/xelp_display_contract.cpp +++ b/tests/xelp_display_contract.cpp @@ -51,7 +51,7 @@ int main() { auto d13=C::display13(~0ULL);faults(d13); auto dp13=C::display13(~0ULL,true);faults(dp13); assert(dp13.required==(d13.required|C::DP13)); - assert(C::display13(C::Backend13|C::SST13|C::AUX13|C::DPProbe13,true).missing()==C::DPBoot13); + assert(C::display13(C::Backend13|C::SST13|C::AUX13|C::DPProbe13,true).missing()==(C::DPBoot13|C::Training12|C::Framing)); assert(d13.required==(C::Power13|C::Clock13|C::MemoryGroup13|C::IRQ13|C::WMLayout13|C::Output13|C::Topology13)); printf("PASS %u route combinations, union composition and all single-operation failures/restores\n",cases); } diff --git a/tools/test.py b/tools/test.py index 48e84200..111a1f4c 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,10 +8,10 @@ python_tests=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] -python_tests += ['display13_agdc'] +python_tests += ['display13_agdc','display13_training_frontend'] 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 += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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,[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 4e7606ce07ba02edf5ee5f68b9ea7db87edf3219 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:41:30 +0900 Subject: [PATCH 29/60] Share required framebuffer donor runtime corrections across display backends --- WhateverXe/kern_xe.cpp | 142 ++++++++++++++-------- WhateverXe/kern_xe.hpp | 2 + WhateverXe/kern_xelp_display_contract.hpp | 8 +- tests/display_contract_adapter.py | 8 +- tests/framebuffer_donor_runtime.py | 78 ++++++++++++ tests/tgl_panel_power.py | 2 +- tests/xelp_display_contract.cpp | 6 +- tools/test.py | 4 +- 8 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 tests/framebuffer_donor_runtime.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 0214c299..2bf9f8c7 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5211,6 +5211,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); + processFramebufferDonorRuntime(patcher,index,address,size); using C=XELP::DisplayContract;const auto installed=display13Available(); display13FBPowerBackendReady=C{C::Power13,installed}.ready(); display13FBClockReady=C{C::Clock13,installed}.ready(); @@ -11118,6 +11119,87 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add return true; } +// CPU object initialization and posted FIFO writes belong to this donor, +// not to a Display generation. Both framebuffer dispatchers install them. +bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(framebufferDonorRuntimeReady)return true; + bool hdcpReady=false,meiReady=false; + { + const char *symbol="__ZN15AppleIntelHDCP24initEP21AppleIntelDisplayPath"; + const uint8_t init[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x96,0x02,0x06,0x00,0x49,0x89}; + const uint8_t associations[]={0x4c,0x89,0x73,0x20,0x49,0x8b,0xbe,0xb0,0x32,0x00,0x00,0x48,0x89,0x7b,0x18,0x48,0x8b,0x87,0xd0,0x01,0x00,0x00,0x48,0x89,0x43,0x10,0x48,0xff,0x05,0x00,0xf5,0x05,0x00,0x49,0x8b,0x8e,0x48,0x36,0x00,0x00}; + const uint8_t use[]={0x48,0x8b,0x7f,0x38,0xbe,0xc0,0x50,0x06,0x00,0xe8,0x10,0x2c,0xfd,0xff}; + const auto fn=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + const bool known=fn==address+0x8AB94 && size>=0x8B734 && + !memcmp(reinterpret_cast(fn),init,sizeof(init)) && + !memcmp(reinterpret_cast(address+0x8AC17),associations,sizeof(associations)) && + !memcmp(reinterpret_cast(address+0x8B726),use,sizeof(use)); + KernelPatcher::RouteRequest route(symbol,wrapTGLHDCP2Init,orgTGLHDCP2Init); + hdcpReady=known && patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGHDCP2InitRouted",hdcpReady); + } + // DONOR_PANEL_PROPERTY_BEGIN + { + // Both power-on and power-off converge on this property update. + // Native getBuiltInFramebuffer returns null when there is no + // built-in port; the donor nevertheless dereferences its vtable. + // Replace only the metadata call, preserving the PPS sequence. + const auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj", address, size); + static const uint8_t expected[] {0x48,0x8B,0x07,0x48,0x8D,0x35,0x1D,0xDB,0x05,0x00,0xFF,0x90,0x68,0x02,0x00,0x00}; + uint8_t replacement[] {0x48,0xB8,0,0,0,0,0,0,0,0,0xFF,0xD0,0x90,0x90,0x90,0x90}; + const auto handler = reinterpret_cast(wrapTGLPanelPowerProperty); + memcpy(replacement+2,&handler,sizeof(handler)); + auto site = reinterpret_cast(fn+0x45A); + if (fn == address+0x48652 && size >= 0x48ABC && !memcmp(site,expected,sizeof(expected))) { + patcher.routeBlock(fn+0x45A,replacement,sizeof(replacement)); + tglPanelPowerPropertyReady = patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(site,replacement,sizeof(replacement)); + } + IORegistryEntry::getRegistryRoot()->setProperty("TGLPanelPowerPropertyGuard",tglPanelPowerPropertyReady); + patcher.clearError(); + } + // DONOR_PANEL_PROPERTY_END + + // Native HDCP TX reported 21 bytes sent but H_CBWP advanced only + // three of seven required FIFO slots (r363). Flush the previous + // FIFO store before each payload store; the native trailing CSR + // read flushes the final word before H_IG. Replace only a profiling + // increment. ESI is reloaded immediately; framing/fragmentation, + // secure flags, queues and completion semantics remain native. + { + static const uint8_t loop[] { + 0x44,0x89,0x38,0x85,0xd2,0x74,0x3d,0x48,0xff,0x05,0xd1,0x4b,0x07,0x00, + 0x89,0xd1,0x31,0xd2,0x48,0xff,0x05,0xce,0x4b,0x07,0x00, + 0x41,0x8b,0x34,0x96,0x48,0xff,0x05,0xd3,0x4b,0x07,0x00, + 0x89,0x30,0x48,0xff,0xc2,0x48,0x39,0xd1,0x75,0xe4}; + static const uint8_t readback[] {0x8b,0x70,0x04,0x90,0x90,0x90,0x90}; + static const uint8_t notify[] {0x8b,0x48,0x04,0x83,0xe1,0xf9,0x83,0xc9,0x04,0x89,0x48,0x04}; + const auto fn = patcher.solveSymbol(index, + "__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", address, size); + bool known = size >= 0x716f7 && fn == address + 0x714b6 && + !memcmp(reinterpret_cast(address + 0x71699), loop, sizeof(loop)) && + !memcmp(reinterpret_cast(address + 0x716eb), notify, sizeof(notify)); + if (known) { + patcher.clearError(); + patcher.routeBlock(address + 0x716ab, readback, sizeof(readback)); + known = patcher.getError() == KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address + 0x716ab), readback, sizeof(readback)); + } + // The four-byte HBM path also checks each accepted FIFO word. + // Keep this functional correction independent of trace allocation. + KernelPatcher::RouteRequest control( + "__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", + wrapTGLMEIWrite,orgTGLMEIWrite); + meiReady=known && patcher.routeMultiple(index,&control,1,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXMEIControlWriteReady",meiReady); + IORegistryEntry::getRegistryRoot()->setProperty("TGLMEITXFIFOReadbackR364", known); + patcher.clearError(); + } + framebufferDonorRuntimeReady=hdcpReady && tglPanelPowerPropertyReady && meiReady; + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferDonorRuntimeReady",framebufferDonorRuntimeReady); + return framebufferDonorRuntimeReady; +} + bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { if (adlnRequested || adlnDetected) { if (!adlnDetected || !adlnNativeRequested || !adlnNativeReady || !tglActive || @@ -11126,6 +11208,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (tglRequested) { if (!tglActive) return false; if (currentFramebuffer->loadIndex == index) { + processFramebufferDonorRuntime(patcher,index,address,size); tglSonomaSurfaceFramebufferReady=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); IORegistryEntry::getRegistryRoot()->setProperty("TGLSonomaSurfaceFramebufferReady",tglSonomaSurfaceFramebufferReady); if (modDVMTCalcFix.enabled && modDVMTCalcFix.available) @@ -11447,23 +11530,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } if (tglActive) { - // Both power-on and power-off converge on this property update. - // Native getBuiltInFramebuffer returns null when there is no - // built-in port; the donor nevertheless dereferences its vtable. - // Replace only the metadata call, preserving the PPS sequence. - const auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj", address, size); - static const uint8_t expected[] {0x48,0x8B,0x07,0x48,0x8D,0x35,0x1D,0xDB,0x05,0x00,0xFF,0x90,0x68,0x02,0x00,0x00}; - uint8_t replacement[] {0x48,0xB8,0,0,0,0,0,0,0,0,0xFF,0xD0,0x90,0x90,0x90,0x90}; - const auto handler = reinterpret_cast(wrapTGLPanelPowerProperty); - memcpy(replacement+2,&handler,sizeof(handler)); - auto site = reinterpret_cast(fn+0x45A); - if (fn == address+0x48652 && size >= 0x48ABC && !memcmp(site,expected,sizeof(expected))) { - patcher.routeBlock(fn+0x45A,replacement,sizeof(replacement)); - tglPanelPowerPropertyReady = patcher.getError()==KernelPatcher::Error::NoError && - !memcmp(site,replacement,sizeof(replacement)); - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLPanelPowerPropertyGuard",tglPanelPowerPropertyReady); - patcher.clearError(); + if (framebufferPatchFlags.bits.FPFStolenMemorySize) { const auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController16getOSInformationEv", address, size); // Selected platform +0x14 is copied to controller +0xDE8 @@ -11500,12 +11567,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t tglFramebufferInitReady = patcher.routeMultiple(index, &initRequest, 1, address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferInitRouted", tglFramebufferInitReady); patcher.clearError(); - { - KernelPatcher::RouteRequest init("__ZN15AppleIntelHDCP24initEP21AppleIntelDisplayPath", wrapTGLHDCP2Init, orgTGLHDCP2Init); - const bool routed = patcher.routeMultiple(index, &init, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGHDCP2InitRouted", routed); - patcher.clearError(); - } + { KernelPatcher::RouteRequest hdcp[] { {"__ZN15AppleIntelHDCP113dispatchEventENS_10HDCP1EventE", wrapTGLHDCP1Dispatch, orgTGLHDCP1Dispatch}, @@ -11923,34 +11985,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteMEConnectTiming", routed); patcher.clearError(); } - // Native HDCP TX reported 21 bytes sent but H_CBWP advanced only - // three of seven required FIFO slots (r363). Flush the previous - // FIFO store before each payload store; the native trailing CSR - // read flushes the final word before H_IG. Replace only a profiling - // increment. ESI is reloaded immediately; framing/fragmentation, - // secure flags, queues and completion semantics remain native. - { - static const uint8_t loop[] { - 0x44,0x89,0x38,0x85,0xd2,0x74,0x3d,0x48,0xff,0x05,0xd1,0x4b,0x07,0x00, - 0x89,0xd1,0x31,0xd2,0x48,0xff,0x05,0xce,0x4b,0x07,0x00, - 0x41,0x8b,0x34,0x96,0x48,0xff,0x05,0xd3,0x4b,0x07,0x00, - 0x89,0x30,0x48,0xff,0xc2,0x48,0x39,0xd1,0x75,0xe4}; - static const uint8_t readback[] {0x8b,0x70,0x04,0x90,0x90,0x90,0x90}; - static const uint8_t notify[] {0x8b,0x48,0x04,0x83,0xe1,0xf9,0x83,0xc9,0x04,0x89,0x48,0x04}; - const auto fn = patcher.solveSymbol(index, - "__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", address, size); - bool known = size >= 0x716f7 && fn == address + 0x714b6 && - !memcmp(reinterpret_cast(address + 0x71699), loop, sizeof(loop)) && - !memcmp(reinterpret_cast(address + 0x716eb), notify, sizeof(notify)); - if (known) { - patcher.clearError(); - patcher.routeBlock(address + 0x716ab, readback, sizeof(readback)); - known = patcher.getError() == KernelPatcher::Error::NoError && - !memcmp(reinterpret_cast(address + 0x716ab), readback, sizeof(readback)); - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLMEITXFIFOReadbackR364", known); - patcher.clearError(); - } + tglMEILock = IOSimpleLockAlloc(); if (tglMEILock) { KernelPatcher::RouteRequest observations[] { @@ -11958,8 +11993,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t {"__ZN19AppleIntelMEIDriver15handleInterruptEP22IOInterruptEventSourcei", wrapTGLMEIInterrupt, orgTGLMEIInterrupt}, {"__ZN19AppleIntelMEIDriver15filterInterruptEP28IOFilterInterruptEventSource", wrapTGLMEIFilter, orgTGLMEIFilter}, {"__ZN19AppleIntelMEIDriver16readHeaderFromMEEv", wrapTGLMEIHeader, orgTGLMEIHeader}, - {"__ZN19AppleIntelMEIDriver17readMessageFromMEEPvPjPh", wrapTGLMEIRead, orgTGLMEIRead}, - {"__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", wrapTGLMEIWrite, orgTGLMEIWrite} + {"__ZN19AppleIntelMEIDriver17readMessageFromMEEPvPjPh", wrapTGLMEIRead, orgTGLMEIRead} }; const bool routed = patcher.routeMultiple(index, observations, arrsize(observations), address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteMEIProtocol", routed); @@ -12543,6 +12577,7 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { XELP::DisplayContract::Mask XE::display12Available() const { using C=XELP::DisplayContract;C::Mask a=0; + if(framebufferDonorRuntimeReady)a|=C::DonorRuntime; if(tglSonomaSurfaceFramebufferReady)a|=C::Surface; if(tglPlatformNVRAMReady)a|=C::NVRAM; if(tglFBIRQReady)a|=C::IRQ; @@ -12592,6 +12627,7 @@ XELP::DisplayContract::Mask XE::display12Available() const { XELP::DisplayContract::Mask XE::display13Available() const { using C=XELP::DisplayContract;C::Mask a=0; + if(framebufferDonorRuntimeReady)a|=C::DonorRuntime; if(display13FBPowerReferencesReady)a|=C::PowerRefs13; if(display13FBEngineEnableHookReady)a|=C::Engine13; if(display13FBLifecycleHooksReady)a|=C::Lifecycle13; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 8dd2a733..29cf7d4a 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -589,6 +589,8 @@ class XE { bool tglPlatformNVRAMReady {false}; bool tglFramebufferInitReady {false}; mach_vm_address_t orgTGLFramebufferInit {}; + bool framebufferDonorRuntimeReady {}; + bool processFramebufferDonorRuntime(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgTGLHDCP2Init {}; static bool wrapTGLHDCP2Init(void *hdcp, void *path); mach_vm_address_t orgTGLHDCP1Dispatch {}, orgTGLHDCP1Timer {}; diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 709a79d6..13807bfa 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -70,20 +70,21 @@ struct DisplayContract { AUX13=1ULL<<60, DPProbe13=1ULL<<61, DPBoot13=1ULL<<62, + DonorRuntime=1ULL<<63, }; Mask required {},available {}; Mask missing() const {return required&~available;} bool ready() const {return !missing();} - static constexpr Mask Base12=Surface|NVRAM|IRQ|Probe|Framebuffer|Topology|PanelPower|PanelTranscoder|Dekel; + static constexpr Mask Base12=DonorRuntime|Surface|NVRAM|IRQ|Probe|Framebuffer|Topology|PanelPower|PanelTranscoder|Dekel; static constexpr Mask Training12=Lease|PowerGet|PowerPut|PortLookup|PathLookup|PathAdd|PathFree|TrainingEnd|Training|TrainingBegin|PathAllocate|ClockOrder; static constexpr Mask Combo12=Framing|ComboSignal|ComboAUX|ComboAUXGate|TCNative|MMIORead|MMIOWrite; static constexpr Mask Fixed12=AUXParent|TCNative|Mode|HPDCaller; static constexpr Mask Alt12=TCNative|Mode|ModeLock|AltFIA|NativeFIA; - static constexpr Mask PlatformADLS=ClockAdapter|PowerAdapter|PortInit|EnableCombo|PortLookup|OSInformation|MMIORead|MMIOWrite|HPD; + static constexpr Mask PlatformADLS=DonorRuntime|ClockAdapter|PowerAdapter|PortInit|EnableCombo|PortLookup|OSInformation|MMIORead|MMIOWrite|HPD; static constexpr Mask Power13=PowerRefs13|Engine13|Lifecycle13|DBUF13|WM13; static constexpr Mask Clock13=ClockRead13|ClockSet13|Lifecycle13; static constexpr Mask MemoryGroup13=Memory13|GTT13|ABI13|Handoff13; - static constexpr Mask Backend13=Power13|Clock13|MemoryGroup13|IRQ13|WMLayout13|Output13|Topology13; + static constexpr Mask Backend13=DonorRuntime|Power13|Clock13|MemoryGroup13|IRQ13|WMLayout13|Output13|Topology13; struct Routes { bool hdmi {},panel {},combo {},legacy {},alt {},leaseRequested {}; }; @@ -109,6 +110,7 @@ struct DisplayContract { static DisplayContract display13(Mask installed,bool dp=false) {return {Backend13|(dp?DP13:0),installed};} static const char *name(Mask bit) { switch(bit) { + case DonorRuntime:return "DonorRuntime"; case Surface:return "Surface"; case NVRAM:return "NVRAM"; case IRQ:return "IRQ"; diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index 3ee64731..7f3674fd 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -28,7 +28,11 @@ }; int main() { State s; - assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM)); + s.framebufferDonorRuntimeReady=false; + assert(s.start().missing()==C::DonorRuntime); + assert(C::display13(s.display13Available()).missing()==C::DonorRuntime); + s.framebufferDonorRuntimeReady=true; + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM|C::DonorRuntime)); assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13|C::Training12|C::Framing)); assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); s.display13DPProbeReady=false; @@ -69,7 +73,7 @@ return 0; } ''' -code=code.replace('FIELDS','\n'.join(' bool '+f+'=true;' for f in fields)).replace('METHODS','\n'.join(methods)).replace('ADAPTER',adapter) +code=code.replace('FIELDS','\n'.join(' bool '+f+'=true;' for f in dict.fromkeys(fields))).replace('METHODS','\n'.join(methods)).replace('ADAPTER',adapter) with tempfile.TemporaryDirectory(prefix='display-contract-') 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) diff --git a/tests/framebuffer_donor_runtime.py b/tests/framebuffer_donor_runtime.py new file mode 100644 index 00000000..e6fc99bd --- /dev/null +++ b/tests/framebuffer_donor_runtime.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Exercise the borrowed HDCP2 manager binding; qualify its native consumer.""" +from pathlib import Path +import re, subprocess, tempfile, sys + +r = Path(__file__).resolve().parents[1] +s = (r / 'WhateverXe/kern_xe.cpp').read_text() +install = s[s.index('bool XE::processFramebufferDonorRuntime('):s.index('bool XE::processKextFull(')] +wrapper = re.search(r'bool XE::wrapTGLHDCP2Init\(.*?\n\}', s, re.S).group() +assert s.count('processFramebufferDonorRuntime(patcher,index,address,size);') == 2 +assert s.count('wrapTGLMEIWrite,orgTGLMEIWrite') == 1 +assert 'wrapTGLMEIWrite, orgTGLMEIWrite' not in s +assert 'hdcpReady && tglPanelPowerPropertyReady && meiReady' in install +if len(sys.argv) > 1: + b = Path(sys.argv[1]).read_bytes() + for name, offset in [('init',0x8ab94), ('associations',0x8ac17), ('use',0x8b726), + ('loop',0x71699), ('notify',0x716eb)]: + raw = re.search(r'\b'+name+r'\[\]\s*(?:=)?\s*\{([^}]+)\}',install).group(1) + expected = bytes(int(x.strip(),0) for x in raw.split(',') if x.strip()) + assert b[offset:offset+len(expected)] == expected, name + # HDCP1's reference binding and HDCP2's null-manager consumer are independent + # donor evidence, not values generated by the production adapter. + assert b[0x8674b:0x86756] == bytes.fromhex('48 8b 80 40 0c 00 00 48 89 43 38') + +code = r''' +#include +#include +#include +#include +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *,const void *,size_t){} +}; +#define SYSLOG(...) do {} while (0) +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE {uintptr_t orgTGLHDCP2Init;static bool wrapTGLHDCP2Init(void *,void *);}; +XE state{},*callbackXE=&state; +bool result=true;unsigned calls=0; +bool native(void *,void *) {++calls;return result;} +BODY +alignas(8) uint8_t hdcp[0x100],path[0x3700],fb[0x5000],ctl[0xD00],manager[0x80]; +templatevoid put(void *b,unsigned o,T v){memcpy(static_cast(b)+o,&v,sizeof(v));} +void setup(){ + memset(hdcp,0,sizeof(hdcp));memset(path,0,sizeof(path));memset(fb,0,sizeof(fb)); + memset(ctl,0,sizeof(ctl));memset(manager,0,sizeof(manager)); + result=true;calls=0;state.orgTGLHDCP2Init=reinterpret_cast(native); + put(hdcp,0x10,ctl+0);put(hdcp,0x18,fb+0);put(hdcp,0x20,path+0); + put(path,0x32b0,fb+0);put(fb,0x1d0,ctl+0);put(ctl,0xc40,manager+0); + put(manager,0x50,reinterpret_cast(0x100000));put(manager,0x60,0x200000u); +} +int main(){ + setup();assert(XE::wrapTGLHDCP2Init(hdcp,path));assert(calls==1); + assert(*reinterpret_cast(hdcp+0x38)==manager); + assert(XE::wrapTGLHDCP2Init(hdcp,path)); // already bound is preserved. + for(unsigned bad=0;bad<9;++bad){ + setup();void *p=path; + if(bad==0)result=false; + if(bad==1)p=nullptr; + if(bad==2)put(hdcp,0x18,static_cast(nullptr)); + if(bad==3)put(hdcp,0x10,static_cast(nullptr)); + if(bad==4)put(path,0x32b0,static_cast(nullptr)); + if(bad==5)put(fb,0x1d0,static_cast(nullptr)); + if(bad==6)put(ctl,0xc40,static_cast(nullptr)); + if(bad==7)put(manager,0x50,static_cast(nullptr)); + if(bad==8)put(manager,0x60,0x650c4u); + assert(!XE::wrapTGLHDCP2Init(hdcp,p));assert(calls==1); + assert(!*reinterpret_cast(hdcp+0x38)); + } + setup();put(hdcp,0x38,ctl+0);assert(!XE::wrapTGLHDCP2Init(hdcp,path)); + assert(*reinterpret_cast(hdcp+0x38)==ctl); // never overwrite another owner. +} +'''.replace('BODY',wrapper) +with tempfile.TemporaryDirectory(prefix='donor-runtime-') as d: + p=Path(d);(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) +print('PASS: borrowed HDCP2 binding, rejected associations and conflicting owner; common installation') diff --git a/tests/tgl_panel_power.py b/tests/tgl_panel_power.py index 3f076fbe..87f53a76 100644 --- a/tests/tgl_panel_power.py +++ b/tests/tgl_panel_power.py @@ -5,7 +5,7 @@ r=Path(__file__).resolve().parents[1] s=(r/'WhateverXe/kern_xe.cpp').read_text() begin=s.index('\t\t\t\t// Both power-on and power-off converge') -end=s.index('\n\t\t\t\tif (framebufferPatchFlags.bits.FPFStolenMemorySize)',begin) +end=s.index('\n }\n // DONOR_PANEL_PROPERTY_END',begin) install=s[begin:end] wrapper=re.search(r'bool XE::wrapTGLPanelPowerProperty\(.*?\n\}',s,re.S).group() if len(sys.argv)>1: diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index 9a50cd4d..562603ac 100644 --- a/tests/xelp_display_contract.cpp +++ b/tests/xelp_display_contract.cpp @@ -7,7 +7,7 @@ static void faults(C c) { assert(c.required && c.ready()); // Every dependency must independently close admission. An unrelated failed // operation must not disable this route. Reinstalling restores admission. - for(unsigned i=0;i<63;++i) { + for(unsigned i=0;i<64;++i) { auto broken=c;auto bit=1ULL< Date: Sun, 25 Oct 2026 13:44:07 +0900 Subject: [PATCH 30/60] Clarify validated fixed DisplayPort scope on Display13 --- README.md | 2 +- README_ja.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a03b357..6c65aa43 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ from `HECI` or `MEI` to `IMEI` is unnecessary. Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support is implemented for HDMI-booted Display13 controllers, with matching VBT AUX wiring and a matching Compat build. Hardware validation is pending. Booting from DP, Combo DP, MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. +Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support is implemented for HDMI-booted Display13 controllers, with matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Booting from DP, Combo DP, MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. diff --git a/README_ja.md b/README_ja.md index 39e68173..fb87e092 100644 --- a/README_ja.md +++ b/README_ja.md @@ -92,7 +92,7 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。HDMIから起動したDisplay13 controllerに対して、固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。実機検証は未完了です。DPからの起動、Combo DP、MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。HDMIから起動したDisplay13 controllerに対して、固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。DPからの起動、Combo DP、MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 From ef2e58d703217917b0dff7bd2c7437ab46afe234 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 13:56:06 +0900 Subject: [PATCH 31/60] Validate Display13 TC SST sources through boot scanout memory handoff --- WhateverXe/kern_alderlake_n.hpp | 43 ++++++++++---- WhateverXe/kern_display13_boot.hpp | 25 +++++--- WhateverXe/kern_display13_fb_memory.hpp | 13 ++++- WhateverXe/kern_display13_tc.hpp | 4 +- tests/display13_tc_boot.cpp | 78 ++++++++++++++++++++++++- 5 files changed, 135 insertions(+), 28 deletions(-) diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index fbf83a5c..129df702 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -194,13 +194,23 @@ inline bool value(const DisplayReadout &r, uint32_t offset, uint32_t &out) { // Source-side certificate for an already-running, uncompressed external SST // GOP. This does not prove sink DPCD state, PLL rate or native AUX ownership. -inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) { - if(pipe>=3 || ddi>=2 || boot.status!=ReadoutStatus::Complete || +inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const Display13TC::Boot *tc=nullptr) { + const bool typeC=ddi>=3 && ddi<=6; + if(pipe>=3 || (ddi>=2 && !typeC) || boot.status!=ReadoutStatus::Complete || !(boot.activePipes&boot.poweredPipes&(1U<valid() || tc->port.ddi!=ddi || tc->pipe!=pipe || + !value(boot,0x45404,power) || power!=tc->power || + !value(boot,0x45454,ddiPower) || ddiPower!=tc->ddiPower)return false; + buf=tc->buffer; + } else if(!value(boot,0x64000+ddi*0x100,buf))return false; if(!value(boot,0x60400+bank,trans) || !value(boot,0x60540+bank,tp) || - !value(boot,0x64000+ddi*0x100,buf) || !value(boot,0x46140+4*pipe,clock) || + !value(boot,0x46140+4*pipe,clock) || !value(boot,0x60404+bank,ctl2) || !value(boot,0x78000+pipe*0x200,dss1) || !value(boot,0x78004+pipe*0x200,dss2) || !value(boot,0x60030+bank,dm) || !value(boot,0x60034+bank,dn) || !value(boot,0x60040+bank,lm) || @@ -211,7 +221,8 @@ inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) { return (trans&0xFF700000U)==(0x82000000U|((ddi+1)<<27)) && !(trans&((1U<<15)|(1U<<9)|(1U<<8))) && !ctl2 && (lanes==1 || lanes==2 || lanes==4) && ((buf>>1)&7)==lanes-1 && - (buf&0x800000C0U)==0x80000000U && (clock>>28)==ddi+1 && + (buf&0x800000C0U)==(typeC?0x80000040U:0x80000000U) && + (clock>>28)==ddi+(typeC?3:1) && (!typeC || clock==tc->transClock) && (tp&~(1U<<18))==0x80000300U && !(dss1&0xF0300000U) && !(dss2&0x80008000U) && !(dm&0x81000000U) && (dm&0xFFFFFF) && @@ -223,15 +234,23 @@ enum class GOPProtocol : uint32_t {TMDS,DP}; // This is an early, serialized bootstrap transaction, not a modeset or link // adoption certificate. A changed link stops further writes, including rollback. template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi, - uint32_t addedAUXRequest=0) { - if(pipe>=3 || ddi>=2 || (addedAUXRequest && addedAUXRequest!=(2U<<(2*ddi))))return false; + uint32_t addedAUXRequest=0,const Display13TC::Boot *tc=nullptr) { + // addedAUXRequest is the existing Combo boot-lease transition only. + // TC lifetime uses Mode's own AUX lease, not the Combo MainLinkAUX slot. + if(!dpSource(r,pipe,ddi,tc) || (addedAUXRequest && (ddi>=2 || addedAUXRequest!=(2U<<(2*ddi)))))return false; + if(ddi>=3 && !tc->unchanged(io))return false; const uint32_t bank=pipe*0x1000; - const uint32_t regs[] {0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank, + // Recheck power before touching pipe-local registers. A captured snapshot + // is not permission to read a bank whose well has since disappeared. + const uint32_t regs[] {0x45404,0x45454,0x45444,0x42000, + 0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank, 0x60040+bank,0x60044+bank,0x70008+bank,0x46140+4*pipe,0x64000+0x100*ddi, - 0x78000+0x200*pipe,0x78004+0x200*pipe,0x45404,0x45454,0x45444,0x42000}; + 0x78000+0x200*pipe,0x78004+0x200*pipe}; for(auto reg:regs) { uint32_t expected=0; - if(!value(r,reg,expected) || !io.readable(reg))return false; + if(ddi>=3 && reg==0x64000+0x100*ddi)expected=tc->buffer; + else if(!value(r,reg,expected))return false; + if(!io.readable(reg))return false; if(reg==Display13Power::AUX)expected|=addedAUXRequest; if(io.read(reg)!=expected)return false; } @@ -299,7 +318,7 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d return LinearStatus::NotSingleHDMI; while (!(r.activePipes & (1U<=3) return LinearStatus::NotSingleHDMI; - if(ddi>=3 && (protocol!=GOPProtocol::TMDS || !tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe)) + if(ddi>=3 && (!tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe)) return LinearStatus::InvalidTCSource; const uint32_t delta=0x1000*p.pipe; uint32_t trans {}, cursor {}, surfLive {}, plane {}, pipeConf {}, htotal {}, vtotal {}; @@ -313,7 +332,7 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d if(!(trans&(1U<<31)) || ((trans>>27)&15)!=ddi+1 || (protocol==GOPProtocol::TMDS && ((trans>>24)&7)>1)) return LinearStatus::NotSingleHDMI; - if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi))return LinearStatus::InvalidDPSource; + if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi,tc))return LinearStatus::InvalidDPSource; p.geometry=XELPGeometry::linear(p.control,p.stride,p.size,p.position,p.offset); if(!p.geometry.valid || (p.surface&4095) || p.surface!=surfLive || ((trans>>20)&7)!=0 || (pipeConf&0xC0000000)!=0xC0000000 || @@ -347,7 +366,7 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout const auto write=[&](uint32_t reg,uint32_t v){io.write(reg,v);++out.writes;io.read(reg);}; bool linkChanged=false; const auto linkStable=[&] { - if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi))linkChanged=true; + if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi,0,tc))linkChanged=true; if(ddi>=3 && (!tc || !tc->unchanged(io)))linkChanged=true; return !linkChanged; }; diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index b1cca2f9..d1f81379 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -7,30 +7,37 @@ #include "kern_display13_fb_topology.hpp" #include "kern_xelp_memory_layout.hpp" namespace Display13Boot { -// Certificate for the existing linear TMDS boot path. Render qualification is +// Certificate for the linear boot memory. Render qualification is // separate; callers must also qualify the physical owner, BAR and memory layout. -// DP/other-pipe adoption requires a separate boot contract, not a wider default. +// DP requires an explicit source certificate; native link adoption remains a +// separate contract. Existing runtime callers retain their TMDS default. template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout &display, bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory, - const Display13TC::Boot *tc=nullptr) { + const Display13TC::Boot *tc=nullptr, + AlderLakeN::GOPProtocol protocol=AlderLakeN::GOPProtocol::TMDS) { AlderLakeN::LinearPlan plan; - if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi,AlderLakeN::GOPProtocol::TMDS,tc)!=AlderLakeN::LinearStatus::Ready || + if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi,protocol,tc)!=AlderLakeN::LinearStatus::Ready || plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false; - if(ddi>=3 && (!tc || !tc->unchanged(io)))return false; + const auto stable=[&] { + return (ddi<3 || (tc && tc->unchanged(io))) && + (protocol!=AlderLakeN::GOPProtocol::DP || AlderLakeN::dpSourceUnchanged(io,display,plan.pipe,ddi,0,tc)); + }; + if(!stable())return false; for(unsigned i=0;iunchanged(io); + return stable(); } // Establish the native linear-GGTT prerequisites only after the controller // startup contract is ready. // Ownership/power/link drift and the stolen prefix are checked before mutation. template AlderLakeN::LinearResult prepareLinear(IO &io,const AlderLakeN::DisplayReadout &boot, - bool admitted,unsigned ddi,const XELPMemory::Layout &memory,const Display13TC::Boot *tc=nullptr) { + bool admitted,unsigned ddi,const XELPMemory::Layout &memory,const Display13TC::Boot *tc=nullptr, + AlderLakeN::GOPProtocol protocol=AlderLakeN::GOPProtocol::TMDS) { using namespace AlderLakeN; LinearResult r; if(!admitted)return r; - if(!reservedPrefix(io,boot,true,ddi,memory,tc)){r.status=LinearStatus::StateChanged;return r;} - return reinitializeLinear(io,boot,true,ddi,GOPProtocol::TMDS,tc); + if(!reservedPrefix(io,boot,true,ddi,memory,tc,protocol)){r.status=LinearStatus::StateChanged;return r;} + return reinitializeLinear(io,boot,true,ddi,protocol,tc); } enum class Status : uint32_t {Unavailable,Complete,Topology,Readback,Timeout,PeerChanged}; struct Result { diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index 5b6ce866..f413f6ab 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -8,6 +8,7 @@ #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_geometry.hpp" #include "kern_display13_tc.hpp" +#include "kern_alderlake_n.hpp" #include #include @@ -327,7 +328,8 @@ template SurfaceReadout inspectSurface(IO &io,const void *controller,s 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, - unsigned expectedDDI=1,const Display13TC::Boot *tc=nullptr) { + unsigned expectedDDI=1,const Display13TC::Boot *tc=nullptr, + const AlderLakeN::DisplayReadout *dp=nullptr) { Readout r; if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) { r.status=Status::Association;return r; @@ -336,6 +338,10 @@ template Readout inspect(IO &io,const void *controller,size_t controll (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 || !tc->unchanged(io))))) { r.status=Status::DisplayState;return r; } + if(bootScanout && dp && (dp->activePipes!=1 || + !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))) { + r.status=Status::DisplayState;return r; + } r.pool=pool(controller);const auto &p=r.pool; if (field(framebuffer,0x4288)!=0 || !field(controller,0xDF5) || field(controller,0xDF5)>3 || !field(controller,0xDF4) || @@ -366,7 +372,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll 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 || - ((trans>>24)&7)>1 || ((trans>>20)&7) || (conf&0xC0000000)!=0xC0000000 || + (dp ? ((trans>>24)&7)!=2 : ((trans>>24)&7)>1) || ((trans>>20)&7) || (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 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))))) { + (expectedDDI>=3 && !tc->unchanged(io)) || + (dp && !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))))) { r.status=Status::StateChanged;return r; } r.status=Status::Complete;return r; diff --git a/WhateverXe/kern_display13_tc.hpp b/WhateverXe/kern_display13_tc.hpp index 99024284..1e04796a 100644 --- a/WhateverXe/kern_display13_tc.hpp +++ b/WhateverXe/kern_display13_tc.hpp @@ -24,9 +24,11 @@ struct Port { }; enum class Status : uint32_t {Invalid,NoPower,NeedsAdoption,InUse,Unavailable,UnsupportedMode,NotReady,Readback,Complete}; struct Result {Status status {Status::Invalid};uint32_t before {},after {},writes {},elapsedUs {};}; -// Read-only certificate for an inherited fixed-TMDS link. This never takes +// 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. +// Protocol/source, sink and PLL frequency are separate certificates. An +// enabled/locked PLL alone does not prove a valid DP link or authorize adoption. struct Boot { Port port;unsigned pipe {3}; uint32_t power {},ddiPower {},ready {},buffer {},clock {},pll {},transClock {}; diff --git a/tests/display13_tc_boot.cpp b/tests/display13_tc_boot.cpp index 4aefb7aa..4f8eda37 100644 --- a/tests/display13_tc_boot.cpp +++ b/tests/display13_tc_boot.cpp @@ -10,12 +10,13 @@ struct IO { std::vector writes; XELPMemory::Layout memory=XELPMemory::decode(0x04C1,0x38000000,0); unsigned ticks {},changeTick {},pteReads {}; - uint32_t changeReg {},badPTE {UINT32_MAX}; + uint32_t changeReg {},changePTEReg {},badPTE {UINT32_MAX}; bool noFrames {},failEnable {}; bool readable(uint32_t reg) const {return reg<=0xFFFFFC;} uint32_t read(uint32_t reg) {return reg==0x70040 ? ticks : regs[reg];} uint64_t readPTE(uint32_t reg) { ++pteReads; + if(pteReads==3 && changePTEReg)regs[changePTEReg]^=1; return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); } void write(uint32_t reg,uint32_t value) { @@ -41,14 +42,84 @@ static IO firmware(unsigned ddi) { return io; } template static void put(void *p,size_t offset,T value) {memcpy(static_cast(p)+offset,&value,sizeof(value));} -static Display13FBMemory::Readout nativeMemory(IO &io,unsigned ddi,const Display13TC::Boot &tc) { +static Display13FBMemory::Readout nativeMemory(IO &io,unsigned ddi,const Display13TC::Boot &tc, + const DisplayReadout *dp=nullptr) { uint8_t controller[0x1C20] {},fb[0x4B90] {}; put(fb,0x1D0,reinterpret_cast(controller)); put(controller,0xD5C,uint32_t(1));put(controller,0xD60,reinterpret_cast(fb)); put(controller,0xDF5,uint8_t(1));put(controller,0xDF4,uint8_t(1));put(controller,0xDF0,uint32_t(1)); put(controller,0x1A10,uint32_t(1));put(controller,0x1A28,io.memory.base); put(controller,0x1A30,uint32_t(0x4000000));put(controller,0x1A34,uint32_t(0x4000)); - return Display13FBMemory::inspect(io,controller,sizeof(controller),fb,sizeof(fb),0x10000000,io.memory,true,0,ddi,&tc); + return Display13FBMemory::inspect(io,controller,sizeof(controller),fb,sizeof(fb),0x10000000,io.memory,true,0,ddi,&tc,dp); +} +static IO dpFirmware(unsigned ddi,unsigned lanes,bool enhanced) { + auto io=firmware(ddi); + // Synthetic SST source from Linux's Display13 register fields. The + // measured HDMI fixture supplies only geometry/physical TC coordinates; + // this is not a claim about a particular firmware's DP startup values. + io.regs[0x60400]=0x82030000|((ddi+1)<<27)|((lanes-1)<<1); + io.regs[0x64000+0x100*ddi]=0x80000040|((lanes-1)<<1); + io.regs[0x60540]=0x80000300|(enhanced?1U<<18:0); + io.regs[0x60030]=0x7E100000;io.regs[0x60034]=0x800000; + io.regs[0x60040]=0x100000;io.regs[0x60044]=0x800000; + return io; +} +static void dpHandoff() { + unsigned cases=0; + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned lanes:{1U,2U,4U})for(bool enhanced:{false,true}) { + auto io=dpFirmware(ddi,lanes,enhanced);const auto original=io.regs; + const auto tc=Display13TC::Boot::capture(io,true,ddi,0); + const auto source=readDisplay(io,true);LinearPlan plan; + assert(dpSource(source,0,ddi,&tc) && dpSourceUnchanged(io,source,0,ddi,0,&tc)); + // No unpowered TC register is pulled into the shared reader, and + // no DP acceptance is obtained by omitting either source/TC proof. + uint32_t ignored=0;assert(!value(source,tc.port.buffer,ignored)); + assert(!dpSource(source,0,ddi)); + assert(!Display13Boot::reservedPrefix(io,source,true,ddi,io.memory,&tc)); + assert(linearPlan(source,plan,ddi,GOPProtocol::DP)==LinearStatus::InvalidTCSource); + auto foreign=tc;foreign.port.ddi=ddi==6?3:ddi+1; + assert(!dpSource(source,0,ddi,&foreign)); + assert(!dpSourceUnchanged(io,source,0,ddi,2U<<(2*ddi),&tc)); + assert(Display13Boot::reservedPrefix(io,source,true,ddi,io.memory,&tc,GOPProtocol::DP)); + assert(io.writes.empty()); + const auto result=Display13Boot::prepareLinear(io,source,true,ddi,io.memory,&tc,GOPProtocol::DP); + assert(result.status==LinearStatus::Success && result.writes==10 && tc.unchanged(io)); + for(auto pair:original)if(pair.first!=0x70028 && pair.first!=0x70180)assert(io.regs[pair.first]==pair.second); + auto after=readDisplay(io,true); + assert(nativeMemory(io,ddi,tc).status==Display13FBMemory::Status::DisplayState); + assert(nativeMemory(io,ddi,tc,&after).status==Display13FBMemory::Status::Complete); + // Reject loss/drift at entry, while waiting for a frame, and during + // the PTE walk. A changed active link forbids speculative rollback. + for(auto reg:{0x45404U,0x45454U,0x45444U,0x60400U,0x60540U,0x60030U, + 0x60034U,0x60040U,0x60044U,0x46140U,0x78000U,0x78004U,tc.port.buffer}) { + auto changed=dpFirmware(ddi,lanes,enhanced);changed.regs[reg]^=1; + auto r=reinitializeLinear(changed,source,true,ddi,GOPProtocol::DP,&tc); + assert(r.status==LinearStatus::StateChanged && changed.writes.empty()); + for(unsigned tick:{1U,3U,5U}) { + auto drift=dpFirmware(ddi,lanes,enhanced);drift.changeReg=reg;drift.changeTick=tick; + r=reinitializeLinear(drift,source,true,ddi,GOPProtocol::DP,&tc); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); + } + auto prefix=dpFirmware(ddi,lanes,enhanced);prefix.changePTEReg=reg; + assert(!Display13Boot::reservedPrefix(prefix,source,true,ddi,prefix.memory,&tc,GOPProtocol::DP)); + assert(prefix.writes.empty()); + auto pool=io;pool.changePTEReg=reg;pool.pteReads=0; + assert(nativeMemory(pool,ddi,tc,&after).status==Display13FBMemory::Status::StateChanged); + } + for(auto invalid:std::vector{{0x60400,0x83030006|((ddi+1)<<27)}, + {0x60540,0x80000000},{0x60540,0x80000380},{0x60034,0},{0x60040,0}, + {0x78000,0x80000000},{0x78004,0x80000000},{0x60404,1}, + {tc.port.buffer,0x80000040|((lanes==4?0:3)<<1)}}) { + auto bad=dpFirmware(ddi,lanes,enhanced);bad.regs[invalid.offset]=invalid.value; + const auto witness=Display13TC::Boot::capture(bad,true,ddi,0); + assert(!dpSource(readDisplay(bad,true),0,ddi,&witness)); + } + auto rollback=dpFirmware(ddi,lanes,enhanced);rollback.failEnable=true; + auto r=reinitializeLinear(rollback,source,true,ddi,GOPProtocol::DP,&tc); + assert(r.status==LinearStatus::EnableTimeout && r.restored && tc.unchanged(rollback)); + ++cases; + } + printf("PASS %u TC SST source/plane/pool handoffs, proof mismatch, drift and rollback\n",cases); } int main() { unsigned cases=0; @@ -113,4 +184,5 @@ int main() { ++cases; } printf("PASS %u TC GOP handoffs: power/ownership/clock preservation, plane rollback, GGTT prefix and link drift\n",cases); + dpHandoff(); } From c0b6e6d43b8e600d3961931b14b7e93a965c4b90 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:08:38 +0900 Subject: [PATCH 32/60] Connect fixed TC DP boot handoff to native power and path ownership --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_alderlake_n.hpp | 6 +- WhateverXe/kern_display13_fb_memory.hpp | 3 +- WhateverXe/kern_display13_fb_topology.hpp | 9 +- WhateverXe/kern_display13_tc.hpp | 12 ++- WhateverXe/kern_display13_tc_native.hpp | 14 ++- WhateverXe/kern_xe.cpp | 103 +++++++++++++++++----- tests/display13_fb_topology.cpp | 13 +++ tests/display13_tc_boot.cpp | 25 ++++++ tests/display13_tc_mode.cpp | 14 ++- tests/display13_tc_native.py | 12 +++ 12 files changed, 176 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 6c65aa43..9d58dd5e 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ from `HECI` or `MEI` to `IMEI` is unnecessary. Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support is implemented for HDMI-booted Display13 controllers, with matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Booting from DP, Combo DP, MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. +Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support requires matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Fixed TC DP boot handoff is implemented but unverified on hardware; it requires one firmware SST scanout on pipe A, a supported linear RGB surface and the validated stolen-memory mapping. Multiple firmware DP scanouts, Combo DP, MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. diff --git a/README_ja.md b/README_ja.md index fb87e092..444be608 100644 --- a/README_ja.md +++ b/README_ja.md @@ -92,7 +92,7 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。HDMIから起動したDisplay13 controllerに対して、固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。DPからの起動、Combo DP、MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。固定TC DPからの起動引継ぎは実装済み・実機未検証です。firmwareがpipe Aで1つのSST表示を出力し、対応するlinear RGB surfaceと検証可能なstolen memory mappingを使っていることが条件です。複数のfirmware DP表示、Combo DP、MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 129df702..e84b126b 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -221,7 +221,8 @@ inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const return (trans&0xFF700000U)==(0x82000000U|((ddi+1)<<27)) && !(trans&((1U<<15)|(1U<<9)|(1U<<8))) && !ctl2 && (lanes==1 || lanes==2 || lanes==4) && ((buf>>1)&7)==lanes-1 && - (buf&0x800000C0U)==(typeC?0x80000040U:0x80000000U) && + (buf&(typeC && tc->ownershipOptional?0x80000080U:0x800000C0U))== + (typeC && !tc->ownershipOptional?0x80000040U:0x80000000U) && (clock>>28)==ddi+(typeC?3:1) && (!typeC || clock==tc->transClock) && (tp&~(1U<<18))==0x80000300U && !(dss1&0xF0300000U) && !(dss2&0x80008000U) && @@ -318,7 +319,8 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d return LinearStatus::NotSingleHDMI; while (!(r.activePipes & (1U<=3) return LinearStatus::NotSingleHDMI; - if(ddi>=3 && (!tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe)) + if(ddi>=3 && (!tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe || + (protocol!=GOPProtocol::DP && tc->ownershipOptional))) return LinearStatus::InvalidTCSource; const uint32_t delta=0x1000*p.pipe; uint32_t trans {}, cursor {}, surfLive {}, plane {}, pipeConf {}, htotal {}, vtotal {}; diff --git a/WhateverXe/kern_display13_fb_memory.hpp b/WhateverXe/kern_display13_fb_memory.hpp index f413f6ab..96ae4a0e 100644 --- a/WhateverXe/kern_display13_fb_memory.hpp +++ b/WhateverXe/kern_display13_fb_memory.hpp @@ -335,7 +335,8 @@ template Readout inspect(IO &io,const void *controller,size_t controll r.status=Status::Association;return r; } if(bootScanout && (expectedDDI>=7 || (expectedDDI>=3 && - (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 || !tc->unchanged(io))))) { + (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 || + !(tc->buffer&64) || (!dp && tc->ownershipOptional) || !tc->unchanged(io))))) { r.status=Status::DisplayState;return r; } if(bootScanout && dp && (dp->activePipes!=1 || diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 995ec791..96455bc4 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -45,8 +45,9 @@ inline uint32_t bootDDI(const Display13OutputWiring &w,uint32_t trans,bool dpSou if(selector<1 || selector>7)return UINT32_MAX; const auto port=selector-1; const auto mode=(trans>>24)&7; - // Explicit source inspection only; runtime adoption keeps the TMDS default. - if(dpSource && mode==2 && port<2 && (w.dp&(1U<=3) && (w.dp&(1U<1)return UINT32_MAX; return (w.hdmi&(1U<=3 && port.ddi<=6 && pipe<3 && power!=UINT32_MAX && (power&powered)==powered && ddiPower!=UINT32_MAX && (ddiPower&(1U<<(2*port.ddi))) && ready!=UINT32_MAX && (ready&7)==4 && - buffer!=UINT32_MAX && (buffer&0x800000C0U)==0x80000040U && + buffer!=UINT32_MAX && (buffer&0x80000080U)==0x80000000U && (ownershipOptional || (buffer&64)) && clock!=UINT32_MAX && (clock>>28)==8 && pll!=UINT32_MAX && (pll&0xCC000000U)==0xCC000000U && transClock!=UINT32_MAX && (transClock>>28)==port.ddi+3; } - template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe) { + template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe,bool ownershipOptional=false) { Boot b; if(!admitted || pipe>=3 || !Port::resolve(ddi,b.port))return b; - b.pipe=pipe; + b.pipe=pipe;b.ownershipOptional=ownershipOptional; if(!io.readable(0x45404) || !io.readable(0x45454))return {}; b.power=io.read(0x45404);b.ddiPower=io.read(0x45454); const auto powered=5U|(1U<<(10+2*pipe)); @@ -61,7 +65,7 @@ struct Boot { } template bool unchanged(IO &io) const { if(!valid())return false; - const auto now=capture(io,true,port.ddi,pipe); + const auto now=capture(io,true,port.ddi,pipe,ownershipOptional); return now.valid() && now.power==power && now.ddiPower==ddiPower && now.ready==ready && now.buffer==buffer && now.clock==clock && now.pll==pll && now.transClock==transClock; } diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp index 85f5d8e6..2fad5123 100644 --- a/WhateverXe/kern_display13_tc_native.hpp +++ b/WhateverXe/kern_display13_tc_native.hpp @@ -1,4 +1,4 @@ -// Exact c004fc1a entry/ABI and failure branches for fixed-TMDS only. +// Exact c004fc1a entry/ABI and failure branches for fixed TC outputs. #ifndef KERN_DISPLAY13_TC_NATIVE_HPP #define KERN_DISPLAY13_TC_NATIVE_HPP #include @@ -27,6 +27,18 @@ static constexpr Entry entries[]={ }; // 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. +static constexpr uint32_t bootPortInitOffset=0x3fdd0; +static constexpr uint8_t bootPortInit[]={0x89,0xdf,0xe8,0xc5,0xb3,0xfd,0xff,0x49,0x8b,0xb5,0x40,0x0d,0x00,0x00,0x4c,0x01,0xe6,0x48,0x8b,0x08,0x48,0x89,0xc7,0xff,0x91,0x18,0x01,0x00,0x00,0x84,0xc0,0x0f,0x84,0x9a,0x01,0x00,0x00,0x48,0xff,0x05,0x8c,0xd6,0x09,0x00,0x48,0xff,0xc3,0x48,0x8d,0x05,0xb2,0x8c,0x0f,0x00,0x8b,0x00,0x49,0x83,0xc4,0x18,0x48,0x39,0xc3,0x72,0xb8}; +static constexpr uint32_t bootPowerCallOffset=0x3fe36; +static constexpr uint8_t bootPowerCall[]={0x48,0x89,0xdf,0x4c,0x89,0xee,0xe8,0x47,0xa1,0x01,0x00}; +static constexpr uint32_t bootSetupCallOffset=0x3ff41; +static constexpr uint8_t bootSetupCall[]={0x4c,0x89,0xef,0xe8,0xc1,0x36,0x00,0x00}; +static constexpr uint32_t modeStopCallOffset=0x4febb; +static constexpr uint8_t modeStopCall[]={0x0f,0xb6,0xcb,0x4c,0x89,0xe7,0x4c,0x89,0xf6,0x4c,0x89,0xfa,0xe8,0x80,0xd4,0xff,0xff}; +static constexpr uint32_t dpLightCallOffset=0x4ffc4; +static constexpr uint8_t dpLightCall[]={0x48,0x8d,0x8d,0xf8,0xfe,0xff,0xff,0x4c,0x89,0xe7,0x4c,0x89,0xf6,0x4c,0x89,0xfa,0xe8,0xeb,0x08,0x00,0x00}; static constexpr uint32_t ddiEnableTailOffset=0x6d7a8; static constexpr uint8_t ddiEnableTail[]={0xe9,0x51,0x27,0xfb,0xff}; static constexpr uint32_t ddiDisableCallOffset=0x6db04; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2bf9f8c7..4a2e18ad 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3882,7 +3882,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if(displayRequested && bootstrapReady) { uint32_t trans=UINT32_MAX; if(AlderLakeN::value(result,0x60400,trans))bootDDI=Display13FBTopology::bootstrapDDI(board, - result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans); + result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans,true); connector[5]=bootDDI;pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); } pci->setProperty("N97DisplayReadoutStatus", static_cast(result.status), 32); @@ -4019,9 +4019,11 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { ((!optionProperty || opts) && !mapperOptions && !systemMapper); uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); - const auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); + const bool dp=bootDDI<7 && (board.dp&(1U<=3 && xelpMemoryMatches(pci),bootDDI,0,dp); const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory, - bootDDI>=3 ? &tc : nullptr); + bootDDI>=3 ? &tc : nullptr,protocol); auto aperture=pci->getDeviceMemoryWithRegister(0x18); const bool memoryReady= aperture && aperture->getLength()==0x10000000 && page_size==4096 && @@ -4393,6 +4395,7 @@ struct Display13FBPowerContext { OSObject *tcObject[4] {}; uint32_t tcBusy[4] {}; bool tcSignalReady[4] {}; + bool bootDP {}; // Board protocol captured with the protected boot reference. Display13TC::DPRetainedLink tcDPLink[4]; Display13FBPipeContext pipe[3]; Display13Power::NativeBridge bridge; @@ -4991,9 +4994,11 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo uint64_t readPTE(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} } io {c.map}; const auto tc=Display13TC::Boot::capture(io,bootScanout && c.bridge.bootPort>=3,c.bridge.bootPort,0); + const bool dp=bootScanout && c.bootDP; + const auto source=AlderLakeN::readDisplay(io,dp); 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); + c.bridge.bootPort>=3 ? &tc : nullptr,dp?&source:nullptr); } } @@ -6304,13 +6309,18 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { return pll!=UINT32_MAX && aux!=UINT32_MAX && !(pll&0xc0000000) && !(aux&0x80000000); } }; - // Adopt only the measured, protected firmware boot route. Merely finding + // Adopt only the validated, protected firmware boot route. Merely finding // a set ownership bit on another connector is not a handoff certificate. Display13FBBootIO bootIO{c.map}; - const auto boot=Display13TC::Boot::capture(bootIO,true,ddi,0); - const bool inherited=ddi==c.bridge.bootPort && c.bridge.retainsDDI(ddi) && boot.valid() && - Display13FBTopology::bootDDI(self->adlnOutputWiring,bootIO.read(0x60400))==ddi && + const bool dpBoot=ddi==c.bridge.bootPort && (self->adlnOutputWiring.dp&(1U<adlnOutputWiring,bootIO.read(0x60400),true)==ddi && (bootIO.read(0x70008)&0xc0000000)==0xc0000000 && boot.unchanged(bootIO); + if(inherited && dpBoot) { + const auto source=AlderLakeN::readDisplay(bootIO,true); + inherited=source.activePipes==1 && AlderLakeN::dpSourceUnchanged(bootIO,source,0,ddi,0,&boot); + } IO io{port,tc,reinterpret_cast(self->display13TCBaseMode),inherited}; 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); @@ -6531,6 +6541,11 @@ bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_ } struct Span {uint32_t offset;const uint8_t *bytes;size_t size;}; const Span spans[]={ + {bootPortInitOffset,bootPortInit,sizeof(bootPortInit)}, + {bootPowerCallOffset,bootPowerCall,sizeof(bootPowerCall)}, + {bootSetupCallOffset,bootSetupCall,sizeof(bootSetupCall)}, + {modeStopCallOffset,modeStopCall,sizeof(modeStopCall)}, + {dpLightCallOffset,dpLightCall,sizeof(dpLightCall)}, {ddiEnableTailOffset,ddiEnableTail,sizeof(ddiEnableTail)}, {ddiDisableCallOffset,ddiDisableCall,sizeof(ddiDisableCall)}, {lightBodyOffset,lightBody,sizeof(lightBody)},{innerBeforeOffset,innerBefore,sizeof(innerBefore)}, @@ -7698,9 +7713,12 @@ bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { const auto boot=AlderLakeN::readDisplay(io,mapped); uint32_t trans=UINT32_MAX;AlderLakeN::value(boot,0x60400,trans); const auto ddi=Display13FBTopology::bootstrapDDI(adlnOutputWiring, - boot.status==AlderLakeN::ReadoutStatus::Complete,boot.poweredPipes,boot.activePipes,trans); - const auto tc=Display13TC::Boot::capture(io,mapped && ddi>=3,ddi,0); - auto result=Display13Boot::prepareLinear(io,boot,mapped,ddi,xelpMemory,ddi>=3?&tc:nullptr); + boot.status==AlderLakeN::ReadoutStatus::Complete,boot.poweredPipes,boot.activePipes,trans,true); + const bool dp=ddi<7 && (adlnOutputWiring.dp&(1U<=3,ddi,0,dp); + if(ddi>=3 && ddi<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); + auto result=Display13Boot::prepareLinear(io,boot,mapped,ddi,xelpMemory,ddi>=3?&tc:nullptr, + dp?AlderLakeN::GOPProtocol::DP:AlderLakeN::GOPProtocol::TMDS); map->release(); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13BootHandoff",&result,sizeof(result)); SYSLOG("igfx","Display13 native handoff status=%u writes=%u restored=%u", @@ -7885,15 +7903,23 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) if(valid) { if(!c.bridge.bootAdopted) { const auto ddi=c.bridge.bootPort; - const bool dp=ddi<2 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.dp&(1U<(port),ddi); - linked=result.status==Display13Power::Status::Complete; - if(linked)port->retain(); // Released only after real buffer stop/idle. + if(ddi>=3) { + const auto source=Display13TC::Boot::capture(io,true,ddi,0); + Display13FBTCIO tc{port,ddi-3}; + linked=tc.owned(ddi) && tc.linkHeld() && boot.activePipes==1 && + AlderLakeN::dpSourceUnchanged(io,boot,0,ddi,0,&source); + } else if(ddi<2) { + result=AlderLakeN::adoptDPBootAUX(io,boot,c.bridge,c.mainLink[ddi],reinterpret_cast(port),ddi); + linked=result.status==Display13Power::Status::Complete; + if(linked)port->retain(); // Released only after real buffer stop/idle. + } } if(linked)result=c.bridge.adoptBootPath(reinterpret_cast(path),0,ddi); + else result.status=Display13Power::Status::NotReady; } else if(c.bridge.bootPath==reinterpret_cast(path))result.status=Display13Power::Status::Complete; if(result.status==Display13Power::Status::Complete && @@ -9676,6 +9702,7 @@ void XE::wrapAlderLakeNOptimizedDBUF(IOService *controller) {adlnSetupDBUF(contr void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { auto &c=display13FBPower; if(!c.lock)return; uint64_t record[32] {}; Display13Power::Result result; + unsigned tcBootDDI=UINT32_MAX; IOLockLock(c.lock); auto service=static_cast(controller); auto pci=service ? OSDynamicCast(IOPCIDevice,service->getProvider()) : nullptr; @@ -9696,7 +9723,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { Display13FBPowerIO io {map}; const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); - const bool dpBoot=bootPort<2 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.dp&(1U<=3) { - Display13FBBootIO bootIO{map};boot=Display13TC::Boot::capture(bootIO,true,bootPort,0).valid(); + Display13FBBootIO bootIO{map};boot=Display13TC::Boot::capture(bootIO,true,bootPort,0,dpBoot).valid(); } if(boot && dpBoot) { Display13FBBootIO bootIO{map};const auto source=AlderLakeN::readDisplay(bootIO,true);uint32_t aux=0; - boot=AlderLakeN::dpSource(source,0,bootPort) && AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort) && - AlderLakeN::value(source,Display13Power::AUX,aux) && (aux&(1U<<(2*bootPort))); + const auto tc=Display13TC::Boot::capture(bootIO,bootPort>=3,bootPort,0,true); + boot=AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort,0,bootPort>=3?&tc:nullptr); + if(bootPort<2)boot=boot && AlderLakeN::value(source,Display13Power::AUX,aux) && (aux&(1U<<(2*bootPort))); + } + if(boot) { + result=c.bridge.begin(io,bootPort,callbackXE->adlnOutputWiring.selected()); + if(result.status==Display13Power::Status::Complete)c.bootDP=dpBoot; } - if(boot)result=c.bridge.begin(io,bootPort,callbackXE->adlnOutputWiring.selected()); else {result.status=Display13Power::Status::InvalidDomains;adlnFault(c.bridge.state.faulted,__LINE__);} - if(result.status==Display13Power::Status::Complete && dpBoot) { + if(result.status==Display13Power::Status::Complete && dpBoot && bootPort<2) { Display13FBBootIO bootIO{map};const auto source=AlderLakeN::readDisplay(bootIO,true); result=AlderLakeN::holdDPBootAUX(bootIO,source,c.bridge,c.mainLink[bootPort],bootPort); if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); @@ -9743,8 +9774,34 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { __atomic_store_n(&display13FBIRQ.controller,service,__ATOMIC_RELEASE); } else {result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} } + if(result.status==Display13Power::Status::Complete && dpBoot && bootPort>=3)tcBootDDI=bootPort; } else OSSafeReleaseNULL(map); } + if(tcBootDDI<7) { + // Exact donor start initializes every port before PowerWell::init. + // Establish the same durable Mode used by probing/hotplug before any + // native engine/client can issue AUX or stop the inherited DP link. + auto getPort=reinterpret_cast(callbackXE->tglPortByDDI); + auto port=getPort?getPort(tcBootDDI):nullptr; + bool adopted=port && display13TCTarget(port)==tcBootDDI; + if(adopted) { + port->retain();IOLockUnlock(c.lock); + adopted=wrapDisplay13TCMode(port,4)==kIOReturnSuccess; + IOLockLock(c.lock); + if(adopted) { + Display13FBBootIO io{c.map};const auto view=AlderLakeN::readDisplay(io,true); + const auto tc=Display13TC::Boot::capture(io,true,tcBootDDI,0); + Display13FBTCIO access{port,tcBootDDI-3}; + adopted=access.owned(tcBootDDI) && access.linkHeld() && view.activePipes==1 && + AlderLakeN::dpSourceUnchanged(io,view,0,tcBootDDI,0,&tc); + } + port->release(); + } + if(!adopted) {result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} + // Do not set tcSignalReady/tcDPLink from an enabled GOP. They certify + // our own programmed PLL; the firmware link is preserved until the + // native disable path stops it, then the ordinary cold path owns it. + } display13FBPowerRecord(record,0,0,result); IOLockUnlock(c.lock); adlnPublishPowerRecord("N97FBPower",record); @@ -12646,8 +12703,8 @@ XELP::DisplayContract::Mask XE::display13Available() const { if(display13FBSSTHooksReady)a|=C::SST13; if(display13FBAUXHooksReady)a|=C::AUX13; if(display13DPProbeReady)a|=C::DPProbe13; - // Startup still requires the existing TMDS GOP certificate. Fixed TC DP - // is a cold link on that admitted controller; DP GOP adoption stays closed. + // Fixed TC DP supports cold training and an explicitly validated SST GOP. + // Boot source, memory, Mode/AUX and native path adoption are all required. if(display13FBDPHooksReady && display13TCDPProbeReady)a|=C::DPBoot13; if(display13TrainingReady)a|=C::Training12; if(display13FramingReady)a|=C::Framing; diff --git a/tests/display13_fb_topology.cpp b/tests/display13_fb_topology.cpp index 5b38a4cd..4bb721d6 100644 --- a/tests/display13_fb_topology.cpp +++ b/tests/display13_fb_topology.cpp @@ -139,6 +139,19 @@ int main() { bad=w;bad.dp=1U< Date: Sun, 25 Oct 2026 14:19:59 +0900 Subject: [PATCH 33/60] Classify firmware DP SST by transport mode while preserving payload state --- WhateverXe/kern_alderlake_n.hpp | 6 +++++- WhateverXe/kern_xe.cpp | 7 ++++++- tests/display13_tc_boot.cpp | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index e84b126b..0186bc94 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -218,8 +218,12 @@ inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const const unsigned lanes=((trans>>1)&7)+1; // Linux v6.12 i915_reg.h: SST, normal training, scrambling, no FEC/MST, // port sync, HDCP, DSC or joined pipes. Enhanced framing may be on or off. + // intel_ddi_read_func_ctl classifies SST from MODE_SELECT, not the MST + // payload-allocation bit. Firmware may leave bit8 set on an SST GOP; + // preserve it and include it in the unchanged-source check, never clear it + // on an enabled transcoder or infer an MST transport from that bit alone. return (trans&0xFF700000U)==(0x82000000U|((ddi+1)<<27)) && - !(trans&((1U<<15)|(1U<<9)|(1U<<8))) && !ctl2 && + !(trans&((1U<<15)|(1U<<9))) && !ctl2 && (lanes==1 || lanes==2 || lanes==4) && ((buf>>1)&7)==lanes-1 && (buf&(typeC && tc->ownershipOptional?0x80000080U:0x800000C0U))== (typeC && !tc->ownershipOptional?0x80000040U:0x80000000U) && diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 4a2e18ad..03d12aeb 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4021,7 +4021,12 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); const bool dp=bootDDI<7 && (board.dp&(1U<=3 && xelpMemoryMatches(pci),bootDDI,0,dp); + auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0,dp); + if(bootDDI>=3 && bootDDI<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); + AlderLakeN::LinearPlan bootPlan; + uint32_t sourceProof[]={1,bootDDI,uint32_t(protocol),uint32_t(AlderLakeN::linearPlan( + display,bootPlan,bootDDI,protocol,bootDDI>=3?&tc:nullptr)),tc.valid()}; + pci->setProperty("N97NativeBootSource",sourceProof,sizeof(sourceProof)); const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory, bootDDI>=3 ? &tc : nullptr,protocol); auto aperture=pci->getDeviceMemoryWithRegister(0x18); diff --git a/tests/display13_tc_boot.cpp b/tests/display13_tc_boot.cpp index dd77cb08..815ce115 100644 --- a/tests/display13_tc_boot.cpp +++ b/tests/display13_tc_boot.cpp @@ -145,7 +145,27 @@ static void unownedDP() { assert(nativeMemory(hdmi,ddi,optional).status==Display13FBMemory::Status::DisplayState); } } +static void sstPayloadReadout() { + for(unsigned ddi=3;ddi<=6;++ddi) { + auto io=dpFirmware(ddi,4,true); + // Observed firmware SST source: bit8 set, enhanced framing, four lanes. + // The coordinate translation is synthetic for the other TC slots. + io.regs[0x60400]|=1U<<8; + io.regs[0x60030]=0x7e1a6666;io.regs[0x60040]=0x23333;io.regs[0x60044]=0x80000; + const auto tc=Display13TC::Boot::capture(io,true,ddi,0); + const auto source=readDisplay(io,true); + assert(Display13Boot::prepareLinear(io,source,true,ddi,io.memory,&tc,GOPProtocol::DP).status==LinearStatus::Success); + assert(io.regs[0x60400]==(0x82030106U|((ddi+1)<<27))); + const auto after=readDisplay(io,true); + assert(nativeMemory(io,ddi,tc,&after).status==Display13FBMemory::Status::Complete); + io.regs[0x60400]^=1U<<8; + assert(!dpSourceUnchanged(io,source,0,ddi,0,&tc)); + io.regs[0x60400]|=1U<<24; // Real MST mode still rejected. + assert(!dpSource(readDisplay(io,true),0,ddi,&tc)); + } +} int main() { + sstPayloadReadout(); unownedDP(); unsigned cases=0; for(unsigned ddi=3;ddi<=6;++ddi) { From f17e6e5d83991e0801a28b27e87de1e29e896a61 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:32:07 +0900 Subject: [PATCH 34/60] Guard shared lane hook argument reads by the exact donor caller --- WhateverXe/kern_xe.cpp | 13 ++++----- WhateverXe/kern_xe.hpp | 2 +- WhateverXe/kern_xelp_donor.hpp | 10 +++++++ tests/tgl_hdmi_wiring.py | 18 +++++++++++-- tests/tgl_hdmi_wiring_fixture.cpp | 45 ++++++++++++++++++++++++------- tools/test.py | 2 +- 6 files changed, 69 insertions(+), 21 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 03d12aeb..3bb39174 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7622,10 +7622,11 @@ uint32_t XE::wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,b } return result; } -bool XE::wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes) { +bool XE::wrapAlderLakeNComputeLanes(void *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes) { const auto caller=reinterpret_cast(__builtin_return_address(0)); const auto native=FunctionCast(wrapAlderLakeNComputeLanes,callbackXE->orgAlderLakeNComputeLanes)(port,timing,bpp,available,lanes); - auto config=Display13FBMemory::field(port,0x548); + auto config=XELPDonor::hdmiLaneConfig(port,caller,callbackXE->adlnHDMILaneCaller); + if(!config)return native; const bool corrected=lanes && Display13FBTopology::skipDPLanes(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring, caller==callbackXE->adlnHDMILaneCaller,native,available,*lanes); static uint32_t count {};uint64_t r[] {1,__sync_add_and_fetch(&count,1),config ? config[0] : 9,config ? config[2] : 0, @@ -16241,12 +16242,8 @@ uint32_t XE::wrapTGLSetEDID(void *controller, void *request) { bool XE::wrapTGLComputeLanes(void *port, const void *timing, uint32_t bpp, uint32_t available, uint32_t *lanes) { const auto result = FunctionCast(wrapTGLComputeLanes, callbackXE->orgTGLComputeLanes)(port, timing, bpp, available, lanes); const auto caller = reinterpret_cast(__builtin_return_address(0)); - // The donor does not consume its first argument. setupOptimalLaneCount - // passes port+0x124 there, whereas the guarded validator passes the port. - // Establish that caller contract before interpreting any object fields. - if (!callbackXE->tglHDMILaneCaller || caller != callbackXE->tglHDMILaneCaller) - return result; - auto config = *reinterpret_cast(static_cast(port)+0x548); + auto config = XELPDonor::hdmiLaneConfig(port,caller,callbackXE->tglHDMILaneCaller); + if (!config) return result; // r316: this exact validator incorrectly asks a TMDS connector for DP link lanes. // Leave lane count zero and continue the remaining native HDMI/scaler checks. // Preserve every DP call, other caller, positive result and nonzero lane result. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 29cf7d4a..3432cd71 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -240,7 +240,7 @@ class XE { static uint32_t wrapAlderLakeNConnectionAttr(IOService *fb,int connection,uint32_t attribute,unsigned long *value); static uint32_t wrapDisplay13FBDDC(IOService *fb,int connection,uint32_t block,uint32_t segment,uint32_t type,uint8_t *data,uint64_t *length); static uint32_t wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,bool force); - static bool wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes); + static bool wrapAlderLakeNComputeLanes(void *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes); static uint32_t wrapDisplay13GMBusRead(void *access,uint64_t reg); static void wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value); mach_vm_address_t orgAlderLakeNGamma10 {},adlnGammaTableUpdate {},orgAlderLakeNPreGamma {}; diff --git a/WhateverXe/kern_xelp_donor.hpp b/WhateverXe/kern_xelp_donor.hpp index f81d366a..820dfbf1 100644 --- a/WhateverXe/kern_xelp_donor.hpp +++ b/WhateverXe/kern_xelp_donor.hpp @@ -3,6 +3,7 @@ #define KERN_XELP_DONOR_HPP #include #include +#include namespace XELPDonor { inline bool displayPoolRequest(uint32_t enabled,uint32_t bytes) { return enabled && bytes && !(bytes&4095) && bytes<=0x4000000; @@ -42,6 +43,15 @@ template Poll waitEDIDReady(IO &io,uint32_t initial) { } return r; } +// computeLaneCount ignores arg0: setupOptimalLaneCount passes port+0x124, +// while the HDMI validator passes the actual port. Qualify the caller before +// even reading config; correcting only the result does not protect this ABI. +inline const uint32_t *hdmiLaneConfig(const void *argument,uintptr_t caller,uintptr_t validator) { + if(!argument || !validator || caller!=validator)return nullptr; + const uint32_t *config {}; + memcpy(&config,static_cast(argument)+0x548,sizeof(config)); + return config; +} inline bool skipHDMIDPLanes(bool selected,bool exactCaller,bool native,uint32_t available,uint32_t lanes) { return selected && exactCaller && !native && !available && !lanes; } diff --git a/tests/tgl_hdmi_wiring.py b/tests/tgl_hdmi_wiring.py index c7e130d0..155305df 100644 --- a/tests/tgl_hdmi_wiring.py +++ b/tests/tgl_hdmi_wiring.py @@ -1,13 +1,27 @@ #!/usr/bin/env python3 """Test the actual HDMI loader/topology/GMBUS/lane entry points with IOKit mocks.""" from pathlib import Path -import re,subprocess,tempfile +import hashlib,re,subprocess,sys,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() fields=h[h.index(' TGLHDMIWiring tglHDMIWiring;'):h.index('\tbool tglGMBusReady')] -functions='\n'.join(re.search(p,s,re.S).group() for p in [r'bool XE::tglLoadHDMIWiring\(.*?\n\}',r'bool XE::wrapTGLGetOSInformation\(.*?\n\}',r'uint32_t XE::wrapTGLGMBus\(.*?\n\}',r'bool XE::wrapTGLComputeLanes\(.*?\n\}']) +functions='\n'.join(re.search(p,s,re.S).group() for p in [r'bool XE::tglLoadHDMIWiring\(.*?\n\}',r'bool XE::wrapTGLGetOSInformation\(.*?\n\}',r'uint32_t XE::wrapTGLGMBus\(.*?\n\}',r'bool XE::wrapTGLComputeLanes\(.*?\n\}',r'bool XE::wrapAlderLakeNComputeLanes\(.*?\n\}']) observations=s[s.index('struct TGLGMBusObservation {'):s.index('struct TGLMEIRecord {')] code=(r/'tests/tgl_hdmi_wiring_fixture.cpp').read_text().replace('// FIELDS',fields).replace('// OBSERVATIONS',observations).replace('// FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='tgl-hdmi-wiring-') as d: d=Path(d);(d/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++14','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I',str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) subprocess.run([str(d/'test')],check=True) + +if len(sys.argv)>1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + # All three direct callers found in exact-donor disassembly. Only the + # 0x304f8 validator is admitted to the HDMI correction/config reader. + for at in [0x1fc07,0x304f3,0x3c4cf]: + assert donor[at]==0xe8 + assert at+5+int.from_bytes(donor[at+1:at+5],'little',signed=True)==0x1fc22 + # At setupOptimalLaneCount, arg0 is the output pointer, not the object. + assert donor[0x1fbfd:0x1fc07]==bytes.fromhex('4881c7240100004989f8') + # The callee overwrites edi without reading arg0; it writes the output r8. + assert donor[0x1fc2f:0x1fc38]==bytes.fromhex('89d741c70000000000') + print('PASS exact donor lane callers: interior output pointer vs HDMI validator') diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index a173b4cc..4f24d054 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -12,6 +12,7 @@ #include "kern_alderlake_s.hpp" #include "kern_tgl_combo_dp.hpp" #include "kern_xelp_donor.hpp" +#include "kern_display13_fb_topology.hpp" struct OSObject {virtual ~OSObject()=default;}; struct OSData:OSObject {std::vector data;void *getBytesNoCopy(){return data.data();}uint32_t getLength(){return data.size();}}; struct IOService {OSObject *property {};unsigned reads {};OSObject *getProperty(const char *name){assert(!strcmp(name,"tgl-hdmi-wiring"));++reads;return property;}}; @@ -38,6 +39,9 @@ struct XE { static bool wrapTGLGetOSInformation(void *); static uint32_t wrapTGLGMBus(void *,uint32_t,uint32_t,void *); static bool wrapTGLComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); + static bool wrapAlderLakeNComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); + uintptr_t orgAlderLakeNComputeLanes {},adlnHDMILaneCaller=0x100; + void *adlnPlatform {};Display13OutputWiring adlnOutputWiring; }; XE object,*callbackXE=&object; // OBSERVATIONS @@ -50,7 +54,7 @@ uint32_t nativeGMBus(void*,uint32_t,uint32_t,void*){++nativeGMBusCalls;edidSelec for(auto &o:tglGMBusObservations)if(o.thread==1)edidSelected=o.edid;return 0x91;} uint8_t *&platform(){return *reinterpret_cast(controller+0xD40);} void setup(){ - object=XE{};object.orgTGLGetOSInformation=(uintptr_t)nativeOS;object.orgTGLComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGMBus=(uintptr_t)nativeGMBus; + object=XE{};object.orgAlderLakeNComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGetOSInformation=(uintptr_t)nativeOS;object.orgTGLComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGMBus=(uintptr_t)nativeGMBus; memset(controller,0,sizeof(controller));memset(original,0x5a,sizeof(original));platform()=original;original[0x12]=9; for(unsigned d=0;d<9;++d){uint32_t cfg[]={d,d+1,1,d>=3?1u:0u,0x400,0x201};memcpy(original+0x20+24*d,cfg,24);} const uint32_t panel[]={0,0,0,0,2,0x18};memcpy(original+0x20,panel,24); @@ -104,15 +108,38 @@ int main(){ const auto page=static_cast(sysconf(_SC_PAGESIZE)); void *inaccessible=mmap(nullptr,page,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0); assert(inaccessible!=MAP_FAILED); - testCaller=0x101; - for(bool native:{false,true}){ - nativeLanesResult=native;uint32_t lanes=4; - assert(XE::wrapTGLComputeLanes(inaccessible,nullptr,24,20,&lanes)==native); - assert(lanes==4); + for(auto wrapper:{XE::wrapTGLComputeLanes,XE::wrapAlderLakeNComputeLanes}) { + testCaller=0x101;nativeLanesCalls=0; + for(bool native:{false,true}){ + nativeLanesResult=native;uint32_t lanes=4; + assert(wrapper(inaccessible,nullptr,24,20,&lanes)==native); + assert(lanes==4); + } + object.tglHDMILaneCaller=object.adlnHDMILaneCaller=0;testCaller=0; + assert(wrapper(inaccessible,nullptr,24,20,nullptr)); + assert(nativeLanesCalls==3); + object.tglHDMILaneCaller=object.adlnHDMILaneCaller=0x100; + } + assert(munmap(inaccessible,page)==0); + // The production Display13 wrapper keeps HDMI correction and DP pass-through + // under the exact validator, across both Combo and TC topology records. + for(unsigned d:{0U,1U,3U,4U,5U,6U})for(bool hdmi:{false,true}) { + setup();Display13OutputWiring w;w.valid=true; + original[0x13]=3; + for(unsigned i=3;i<9;++i)reinterpret_cast(original+0x20+24*i)[1]=i+6; + if(hdmi){w.hdmi=1U<(out+0x20+24*d); + memcpy(port+0x548,&config,sizeof(config)); + for(bool native:{false,true})for(unsigned available:{0U,20U})for(unsigned lanes:{0U,4U}) { + nativeLanesResult=native;auto before=lanes; + assert(XE::wrapAlderLakeNComputeLanes(port,nullptr,24,available,&lanes)== + (native || (hdmi && !available && !lanes))); + assert(lanes==before); + } } - object.tglHDMILaneCaller=0;testCaller=0; - assert(XE::wrapTGLComputeLanes(inaccessible,nullptr,24,20,nullptr)); - assert(nativeLanesCalls==3);assert(munmap(inaccessible,page)==0); setup();auto current=property(2,0,2,0);IOService absent{¤t};assert(XE::tglLoadHDMIWiring(&absent));assert(XE::wrapTGLGetOSInformation(controller)); const auto p=platform();assert(p!=original&&object.tglHDMIWiringApplied&&object.tglHDMIWiring.ddc[1]==2); expectUnchangedExcept(p,{0x40,0x48,0x4C}); // HDMI protocol/type/flags; panel unchanged diff --git a/tools/test.py b/tools/test.py index c8d0c2bb..cce284dc 100644 --- a/tools/test.py +++ b/tools/test.py @@ -11,7 +11,7 @@ python_tests += ['display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] 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 += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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,[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 646a8cae852ee0c33bff1d4a9451d7533eaa064c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:58:52 +0900 Subject: [PATCH 35/60] Own framebuffer donor contracts before hardware backend dispatch --- WhateverXe/kern_display13_fb_topology.hpp | 6 - WhateverXe/kern_framebuffer_donor.hpp | 35 ++ ...p => kern_framebuffer_training_native.hpp} | 6 +- WhateverXe/kern_xe.cpp | 487 +++++++----------- WhateverXe/kern_xe.hpp | 39 +- tests/display13_agdc.py | 35 +- tests/display13_fb_topology.cpp | 10 - tests/display13_training_lease.py | 2 +- tests/display_backend_dispatch.py | 4 +- tests/framebuffer_common.py | 117 +++++ tests/framebuffer_donor.cpp | 53 ++ tests/framebuffer_donor_runtime.py | 2 +- tests/framebuffer_training_registration.py | 83 +++ tests/tgl_hdmi_wiring.py | 2 +- tests/tgl_hdmi_wiring_fixture.cpp | 31 +- tools/test.py | 6 +- 16 files changed, 544 insertions(+), 374 deletions(-) create mode 100644 WhateverXe/kern_framebuffer_donor.hpp rename WhateverXe/{kern_display13_training_native.hpp => kern_framebuffer_training_native.hpp} (96%) create mode 100644 tests/framebuffer_common.py create mode 100644 tests/framebuffer_donor.cpp create mode 100644 tests/framebuffer_training_registration.py diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 96455bc4..19157990 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -100,12 +100,6 @@ inline XELP::NativePorts boundPorts(const void *platform,const Display13OutputWi !selected(platform,static_cast(platform)+0x20+24*d,w))return {}; return {platform,w.selected()}; } -// Same exact donor CPU defect corrected for MI646: the mode validator -// asks a TMDS connector for DP lanes. Keep its remaining HDMI constraints. -inline bool skipDPLanes(const void *platform,const void *config,const Display13OutputWiring &w, - bool exactCaller,bool native,uint32_t available,uint32_t lanes) { - return XELPDonor::skipHDMIDPLanes(selected(platform,config,w) && w.matchesHDMI(static_cast(config)),exactCaller,native,available,lanes); -} // ICP/TGP SDE bits24..29 are TC1..6 hotplug, not legacy GMBUS bit24. // Combo A..C hotplug uses bits16..18. Authorize exclusions only after // every unselected native record is disabled and has no connection flags. diff --git a/WhateverXe/kern_framebuffer_donor.hpp b/WhateverXe/kern_framebuffer_donor.hpp new file mode 100644 index 00000000..7a6e2538 --- /dev/null +++ b/WhateverXe/kern_framebuffer_donor.hpp @@ -0,0 +1,35 @@ +// Object contracts of the c004fc1a framebuffer donor, independent of Display IP. +#ifndef KERN_FRAMEBUFFER_DONOR_HPP +#define KERN_FRAMEBUFFER_DONOR_HPP +#include +#include +#include +namespace FramebufferDonor { +template T field(const void *object,size_t offset) { + T value {};if(object)memcpy(&value,static_cast(object)+offset,sizeof(value));return value; +} +// Native FB init leaves borrowed MMIO-manager fields empty. Validate the whole +// controller-owned graph before changing any reference. Never retain/release a +// borrowed manager, overwrite a foreign owner, or partly bind a rejected graph. +template bool bindManagers(Objects &objects,void *fb,void *controller,uint32_t index) { + if(index>=3 || objects.size(fb)<0x4B90 || objects.size(controller)<0x1C20 || + field(fb,0x1D0)!=controller || field(fb,0x1DC)!=index)return false; + auto manager=field(controller,0xC40); + if(objects.size(manager)<0x68 || !field(manager,0x50) || field(manager,0x60)<=0x60404)return false; + const size_t arrays[] {0xF58,0xF28},identities[] {0x7C,0x1C},slots[] {0x90,0x28}; + const unsigned counts[] {15,6},minimum[] {0x1C8,0x58}; + for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+sizeof(void *)*i); + if(objects.size(object)(object,identities[group])!=i)return false; + auto stored=field(object,slots[group]); + if(stored && stored!=manager)return false; + } + auto stored=field(fb,0x4A40);if(stored && stored!=manager)return false; + for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+sizeof(void *)*i); + memcpy(object+slots[group],&manager,sizeof(manager)); + } + memcpy(static_cast(fb)+0x4A40,&manager,sizeof(manager));return true; +} +} +#endif diff --git a/WhateverXe/kern_display13_training_native.hpp b/WhateverXe/kern_framebuffer_training_native.hpp similarity index 96% rename from WhateverXe/kern_display13_training_native.hpp rename to WhateverXe/kern_framebuffer_training_native.hpp index c4f360a4..b0b7c726 100644 --- a/WhateverXe/kern_display13_training_native.hpp +++ b/WhateverXe/kern_framebuffer_training_native.hpp @@ -1,8 +1,8 @@ // Exact c004fc1a donor interfaces for pre-modeset SST training and adoption. -#ifndef KERN_DISPLAY13_TRAINING_NATIVE_HPP -#define KERN_DISPLAY13_TRAINING_NATIVE_HPP +#ifndef KERN_FRAMEBUFFER_TRAINING_NATIVE_HPP +#define KERN_FRAMEBUFFER_TRAINING_NATIVE_HPP #include -namespace Display13TrainingNative { +namespace FramebufferTrainingNative { enum Index {Begin,Train,End,AllocateAny,AllocateFixed,Free,Add,Resources,Stop,PortTrain,Lookup,Port}; struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[16];}; static constexpr Entry entries[]={ diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3bb39174..e93f104a 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -8,6 +8,7 @@ // #include "kern_xe.hpp" +#include "kern_framebuffer_donor.hpp" #include "kern_xelp_os.hpp" #include "kern_alderlake_n.hpp" #include "kern_xelp_device.hpp" @@ -21,7 +22,7 @@ #include "kern_display13_tc_native.hpp" #include "kern_display13_tc_clock.hpp" #include "kern_display13_tc_dp.hpp" -#include "kern_display13_training_native.hpp" +#include "kern_framebuffer_training_native.hpp" #include "kern_tgl_gmbus.hpp" #include "kern_alderlake_n_gpu.hpp" #include "kern_display13_fb_memory.hpp" @@ -4336,8 +4337,9 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add } // Display dispatch is independent of the selected Render implementation. // Keep both adapters outside the large Display12 framebuffer patch frame. - if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) - return processDisplay13Framebuffer(patcher,index,address,size); + if(currentFramebuffer && currentFramebuffer->loadIndex==index && + (display13FramebufferRequested || (tglRequested && tglActive))) + return processFramebufferKext(patcher,index,address,size); if(adlpDisplaySelected && !adlpRenderRequested) { if(index==kextIntelTGL.loadIndex)return processADLPLoadOnly(patcher,index,address,size); return false; @@ -5017,19 +5019,17 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm "__ZN19AppleIntelPowerWell18enablePowerWellDDIEj", "__ZN19AppleIntelPowerWell19disablePowerWellDDIEj", "__ZN19AppleIntelPowerWell18enablePowerWellAuxEj", - "__ZN19AppleIntelPowerWell19disablePowerWellAuxEj", - "__ZN31AppleIntelFramebufferController5startEP9IOService" + "__ZN19AppleIntelPowerWell19disablePowerWellAuxEj" }; - static const uintptr_t offsets[] {0x59F88,0x5BEDC,0x5B366,0x5BFA8,0x5AA86,0x5C4BA,0x5AE0A,0x3F1FC}; - static const uint8_t prefixes[8][24] { + static const uintptr_t offsets[] {0x59F88,0x5BEDC,0x5B366,0x5BFA8,0x5AA86,0x5C4BA,0x5AE0A}; + static const uint8_t prefixes[7][24] { {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05,0xe8,0x76,0x08,0x00}, {0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0xac,0x5e,0x08,0x00,0x85,0xf6,0x74,0x18,0xb8,0x01,0x00,0x00}, {0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x5a,0x67,0x08,0x00,0x85,0xf6,0x74,0x23,0xb8,0x01,0x00,0x00}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x05,0x23,0x5e,0x08,0x00,0x89,0xf0,0x8b}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05,0x92,0x6e,0x08,0x00}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x48,0x48,0xff,0x05,0x26,0x5a,0x08,0x00}, - {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00}, - {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x49,0x89,0xf6,0x49,0x89,0xfd,0x48} + {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00} }; bool known=display13FramebufferRequested && size>0x5C4D2; for(unsigned i=0;i(address+0x3FE26),ownerStore,sizeof(ownerStore)); if(!known)return false; if(!display13FBPower.lock)display13FBPower.lock=IOLockAlloc(); - KernelPatcher::RouteRequest start(symbols[7],wrapDisplay13FramebufferStart,orgDisplay13FramebufferStart); - const bool startRouted=patcher.routeMultiple(index,&start,1,address,size);patcher.clearError(); - if(!startRouted)return false; // Apply DVMT calculation before routing FBMemMgr_Init: the common // module scans its native entry, not our later trampoline. Startup is // already guarded, so missing opt-in/failed patch cannot run the bad formula. @@ -5209,7 +5206,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBIRQReady=processDisplay13FramebufferIRQ(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBIRQHooksReady",display13FBIRQReady); - display13FBABIReady=processDisplay13FramebufferABI(patcher,index,address,size); + display13FBABIReady=framebufferDonorCommonReady; IORegistryEntry::getRegistryRoot()->setProperty("N97FBCommonABIReady",display13FBABIReady); display13FBLifecycleHooksReady=processDisplay13FramebufferLifecycle(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBLifecycleHooksReady",display13FBLifecycleHooksReady); @@ -5221,7 +5218,6 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); - processFramebufferDonorRuntime(patcher,index,address,size); using C=XELP::DisplayContract;const auto installed=display13Available(); display13FBPowerBackendReady=C{C::Power13,installed}.ready(); display13FBClockReady=C{C::Clock13,installed}.ready(); @@ -6634,16 +6630,12 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m {"__ZN21AppleIntelFramebuffer16enableControllerEv",0x3A6F4,false}, {"__ZN21AppleIntelFramebuffer25getAttributeForConnectionEijPm",0x34692,false}, {"__ZN14AppleIntelPort11getDDCBlockEjPhb",0x1F5B2,false}, - {"__ZN21AppleIntelFramebuffer11getDDCBlockEijjjPhPy",0x2FEE8,false}, - {"__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj",0x1FC22,true} + {"__ZN21AppleIntelFramebuffer11getDDCBlockEijjjPhPy",0x2FEE8,false} }; for(auto &e:frontend) { auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.shortBase ? readEntry : busEntry,e.shortBase ? sizeof(readEntry) : sizeof(busEntry)))return false; } - const uint8_t laneCall[] {0xE8,0x2A,0xF7,0xFE,0xFF,0x84,0xC0,0x74,0x1A}; - if(memcmp(reinterpret_cast(address+0x304F3),laneCall,sizeof(laneCall)))return false; - adlnHDMILaneCaller=address+0x304F8; auto read=patcher.solveSymbol(index, "__ZN31AppleIntelRegisterAccessManager14ReadRegister32Em",address,size);patcher.clearError(); auto write=patcher.solveSymbol(index, @@ -6659,7 +6651,6 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m memcmp(reinterpret_cast(bus+0x335),busBefore,sizeof(busBefore)) || memcmp(reinterpret_cast(bus+0xEA),pinLimit,sizeof(pinLimit)) || memcmp(reinterpret_cast(bus+0x2E0),resetBefore,sizeof(resetBefore)))return false; - if(!processDisplay13FramebufferAGDC(patcher,index,address,size))return false; struct Change {uintptr_t offset;const uint8_t *find,*replace;size_t length;}; const Change changes[] { {0x9A0E8,reinterpret_cast(before),reinterpret_cast(after),sizeof(before)}, @@ -6684,8 +6675,7 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m {frontend[0].symbol,wrapDisplay13FBEnable,orgDisplay13FBEnable}, {frontend[1].symbol,wrapAlderLakeNConnectionAttr,orgAlderLakeNConnectionAttr}, {frontend[2].symbol,wrapAlderLakeNPortDDC,orgAlderLakeNPortDDC}, - {frontend[3].symbol,wrapDisplay13FBDDC,orgDisplay13FBDDC}, - {frontend[4].symbol,wrapAlderLakeNComputeLanes,orgAlderLakeNComputeLanes} + {frontend[3].symbol,wrapDisplay13FBDDC,orgDisplay13FBDDC} }; const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGMBusObservationReady",ready); @@ -6788,11 +6778,11 @@ uint32_t XE::wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,ui IOLockUnlock(c.lock);return r ? r : complete ? 0 : kIOReturnNotReady; } -bool XE::processDisplay13Training(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - if(!adlnOutputWiring.dp)return true; - using namespace Display13TrainingNative; - if(!display13FBDPHooksReady || !display13FBSSTHooksReady || !display13FBPowerReferencesReady || - !orgDisplay13GMBusWrite || size<=0x91DA0)return false; +// The donor protocol owns reservation, adoption and pool serialization once. +// Installing these hooks grants no port/power eligibility; backend adapters do. +bool XE::processFramebufferTraining(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace FramebufferTrainingNative; + if(size<=0x91DA0)return false; for(const auto &e:entries) { const auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.prefix,sizeof(e.prefix)))return false; @@ -6811,10 +6801,29 @@ bool XE::processDisplay13Training(KernelPatcher &patcher,size_t index,mach_vm_ad {entries[Add].symbol,wrapTGLAddPath,orgTGLAddPath}, {entries[Resources].symbol,wrapTGLAllocatePathResources,orgTGLAllocatePathResources}, {entries[Stop].symbol,wrapTGLControllerStop,orgTGLControllerStop}, - {entries[PortTrain].symbol,wrapDisplay13PortTraining,orgDisplay13PortTraining}, }; const bool routed=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); if(!routed)return false; + // Lookup tracing is backend-specific, but the original has one owner. + if(!display13FramebufferRequested) { + KernelPatcher::RouteRequest trace(entries[Lookup].symbol,wrapTGLPathLookup,orgTGLPathLookup); + const bool traced=patcher.routeMultipleShort(index,&trace,1,address,size);patcher.clearError(); + if(!traced)return false; + } + return true; +} + +bool XE::processDisplay13Training(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(!adlnOutputWiring.dp)return true; + using namespace FramebufferTrainingNative; + if(!framebufferTrainingHooksReady || !display13FBDPHooksReady || !display13FBSSTHooksReady || + !display13FBPowerReferencesReady || !orgDisplay13GMBusWrite)return false; + const auto &e=entries[PortTrain]; + const auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); + if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.prefix,sizeof(e.prefix)))return false; + KernelPatcher::RouteRequest route(e.symbol,wrapDisplay13PortTraining,orgDisplay13PortTraining); + const bool routed=patcher.routeMultipleShort(index,&route,1,address,size);patcher.clearError(); + if(!routed)return false; trainingLeaseDP13Mask=adlnOutputWiring.dp; trainingLeaseValidate=display13LeaseValidate;trainingLeasePower=display13LeasePower;trainingLeaseStop=display13LeaseStop; tglDisablePort=reinterpret_cast(wrapDisplay13DisablePort); @@ -7526,7 +7535,7 @@ static void adlnPublishFrontend(const char *name,const char *phase,uint64_t (&r) char key[64];snprintf(key,sizeof(key),"N97FB%s%u%s",name,static_cast(r[2]),phase); IORegistryEntry::getRegistryRoot()->setProperty(key,r,sizeof(r)); } -bool XE::processDisplay13FramebufferAGDC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { +bool XE::processFramebufferAGDC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // Both short trampolines end before the donor's RIP-relative counters. const char *client="__ZN21AppleIntelFramebuffer13newUserClientEP4taskPvjPP12IOUserClient"; const char *callback="__ZN31AppleIntelFramebufferController12CallBackAGDCE31kAGDCRegisterLinkControlEvent_tmj"; @@ -7534,7 +7543,7 @@ bool XE::processDisplay13FramebufferAGDC(KernelPatcher &patcher,size_t index,mac const uint8_t callbackEntry[]={0x55,0x48,0x89,0xe5,0x89,0xf0}; const uint8_t dispatch[]={0x4c,0x8b,0x87,0xf8,0x1b,0,0,0x48,0x8b,0xbf,0,0x1c,0,0, 0x48,0x89,0xd6,0x89,0xca,0x89,0xc1,0x5d,0x41,0xff,0xe0}; - if(!display13FramebufferRequested || !display13FBABIReady || size<0x69E60)return false; + if(size<0x69E60)return false; const auto c=patcher.solveSymbol(index,client,address,size);patcher.clearError(); const auto a=patcher.solveSymbol(index,callback,address,size);patcher.clearError(); if(c!=address+0x273B4 || a!=address+0x69DF2 || @@ -7542,15 +7551,15 @@ bool XE::processDisplay13FramebufferAGDC(KernelPatcher &patcher,size_t index,mac memcmp(reinterpret_cast(a),callbackEntry,sizeof(callbackEntry)) || memcmp(reinterpret_cast(address+0x69E1E),dispatch,sizeof(dispatch)))return false; KernelPatcher::RouteRequest routes[] { - {client,wrapDisplay13FBUserClient,orgDisplay13FBUserClient}, - {callback,wrapDisplay13AGDCCallback,orgDisplay13AGDCCallback} + {client,wrapFramebufferUserClient,orgFramebufferUserClient}, + {callback,wrapFramebufferAGDCCallback,orgFramebufferAGDCCallback} }; const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13AGDCGuardReady",ready); return ready; } -uint32_t XE::wrapDisplay13FBUserClient(IOService *fb,void *task,void *security,uint32_t type,void **client) { +uint32_t XE::wrapFramebufferUserClient(IOService *fb,void *task,void *security,uint32_t type,void **client) { // Only type0 enters IOFramebuffer::open. Refuse before entering IOG: an // error from enableController permanently sets IOG's dead flag instead. // Native controller start publishes FB children before its AGDC nub. @@ -7560,9 +7569,11 @@ uint32_t XE::wrapDisplay13FBUserClient(IOService *fb,void *task,void *security,u const bool framebuffer=fb && fb->getMetaClass()->getClassSize()>=0x4B90; auto controller=framebuffer ? Display13FBMemory::field(fb,0x1D0) : nullptr; - const bool bound=controller && controller==s->adlnTopologyController && - controller->getMetaClass()->getClassSize()>=0x1C20; - const bool started=bound && controller==s->tglObservationController; + const bool bound=controller && controller->getMetaClass()->getClassSize()>=0x1C20 && + (!s->display13FramebufferRequested || controller==s->adlnTopologyController); + // Display13 additionally needs the hardware handoff complete before + // IOFramebuffer::open; callback safety itself is donor-wide. + const bool started=bound && (!s->display13FramebufferRequested || controller==s->tglObservationController); const bool registered=bound && Display13FBMemory::field(controller,0x1BF8); uint64_t record[]={1,bound,started,registered, bound && Display13FBMemory::field(controller,0x1C00), @@ -7573,17 +7584,21 @@ uint32_t XE::wrapDisplay13FBUserClient(IOService *fb,void *task,void *security,u return kIOReturnNotReady; } } - return FunctionCast(wrapDisplay13FBUserClient,callbackXE->orgDisplay13FBUserClient)(fb,task,security,type,client); + const auto result=FunctionCast(wrapFramebufferUserClient,callbackXE->orgFramebufferUserClient)(fb,task,security,type,client); + if(!callbackXE->display13FramebufferRequested)observeFramebufferUserClient(fb,type,client,result); + return result; } -uint32_t XE::wrapDisplay13AGDCCallback(IOService *controller,uint32_t event,unsigned long argument,uint32_t value) { +uint32_t XE::wrapFramebufferAGDCCallback(IOService *controller,uint32_t event,unsigned long argument,uint32_t value) { // Other native notifications can also precede policy registration. Return // failure for a real notification with no receiver; never synthesize ACK. // Keep the native event filter, argument conversion and registered path. const bool notification=event==4 || event==0x80 || (event<=11 && (0x90FU&(1U<getMetaClass()->getClassSize()<0x1C20 || !Display13FBMemory::field(controller,0x1BF8)))return kIOReturnNotReady; - return FunctionCast(wrapDisplay13AGDCCallback,callbackXE->orgDisplay13AGDCCallback)(controller,event,argument,value); + const auto result=FunctionCast(wrapFramebufferAGDCCallback,callbackXE->orgFramebufferAGDCCallback)(controller,event,argument,value); + if(!callbackXE->display13FramebufferRequested)tglTrace(7,event,true,event,value,result,argument); + return result; } uint32_t XE::wrapDisplay13FBEnable(IOService *fb) { @@ -7622,85 +7637,6 @@ uint32_t XE::wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,b } return result; } -bool XE::wrapAlderLakeNComputeLanes(void *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes) { - const auto caller=reinterpret_cast(__builtin_return_address(0)); - const auto native=FunctionCast(wrapAlderLakeNComputeLanes,callbackXE->orgAlderLakeNComputeLanes)(port,timing,bpp,available,lanes); - auto config=XELPDonor::hdmiLaneConfig(port,caller,callbackXE->adlnHDMILaneCaller); - if(!config)return native; - const bool corrected=lanes && Display13FBTopology::skipDPLanes(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring, - caller==callbackXE->adlnHDMILaneCaller,native,available,*lanes); - static uint32_t count {};uint64_t r[] {1,__sync_add_and_fetch(&count,1),config ? config[0] : 9,config ? config[2] : 0, - bpp,available,native,lanes ? *lanes : UINT32_MAX,caller==callbackXE->adlnHDMILaneCaller,corrected}; - IORegistryEntry::getRegistryRoot()->setProperty("N97FBHDMILaneLast",r,sizeof(r)); - return native || corrected; -} - -bool XE::processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - // OS/donor ABI corrections are shared with Display12. Keep hardware - // routing and topology in the selected Display13 backend. - if(!display13FramebufferRequested || size<=0xCCCD8)return false; - const bool surface=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); - const bool nvram=xelpInstallNVRAMABI(patcher,index,address,size); - auto table=patcher.solveSymbol(index,"__ZTV17AppleIntelPortHAL",address,size); - auto pure=patcher.solveSymbol(KernelPatcher::KernelID,"___cxa_pure_virtual"); - const uint8_t halSize[] {0xB9,0xB0,0x06,0,0}; - bool probe=table==address+0xCCAD0 && pure && xelpProviderProbe && xelpProviderConfigureProbe && - !memcmp(reinterpret_cast(address+0x5EE5E),halSize,sizeof(halSize)) && - *reinterpret_cast(table+0x188)==pure; - patcher.clearError(); - if(probe) { - auto replacement=xelpProviderProbe; - patcher.routeBlock(table+0x188,reinterpret_cast(&replacement),sizeof(replacement)); - probe=patcher.getError()==KernelPatcher::Error::NoError && - *reinterpret_cast(table+0x188)==replacement; - patcher.clearError(); - } - const char *initSymbol="__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj"; - auto init=patcher.solveSymbol(index,initSymbol,address,size); - const uint8_t initEntry[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x10, - 0x41,0x89,0xD6,0x49,0x89,0xF7,0x48,0x89,0xFB}; - bool managers=init==address+0x29EC8 && !memcmp(reinterpret_cast(init),initEntry,sizeof(initEntry)); - patcher.clearError(); - KernelPatcher::RouteRequest binding(initSymbol,wrapDisplay13FramebufferInit,orgDisplay13FramebufferInit); - managers=managers && patcher.routeMultiple(index,&binding,1,address,size); - patcher.clearError(); - uint32_t record[] {2,nvram,probe,managers,surface}; - IORegistryEntry::getRegistryRoot()->setProperty("N97FBCommonABIHooks",record,sizeof(record)); - return surface && nvram && probe && managers; -} - -bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32_t index) { - auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; - if(!callbackXE->display13FBABIReady || !fb || !pci || index>=3 || - !xelpDisplay13Device(pci) || - controller->getMetaClass()->getClassSize()<0x1C20 || fb->getMetaClass()->getClassSize()<0x4B90)return false; - if(!FunctionCast(wrapDisplay13FramebufferInit,callbackXE->orgDisplay13FramebufferInit)(fb,controller,index))return false; - auto manager=Display13FBMemory::field(controller,0xC40); - if(Display13FBMemory::field(fb,0x1D0)!=controller || - Display13FBMemory::field(fb,0x1DC)!=index || !manager || - manager->getMetaClass()->getClassSize()<0x68 || !Display13FBMemory::field(manager,0x50) || - Display13FBMemory::field(manager,0x60)!=0x1000000)return false; - // Validate every donor-owned CPU object before filling any borrowed manager - // field. A conflicting non-null reference is failure, not a successful bind. - const size_t arrays[] {0xF58,0xF28},identities[] {0x7C,0x1C},slots[] {0x90,0x28}; - const unsigned counts[] {15,6},minimum[] {0x1C8,0x58}; - for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+8*i); - if(!object || object->getMetaClass()->getClassSize()(object,identities[group])!=i)return false; - auto stored=Display13FBMemory::field(object,slots[group]); - if(stored && stored!=manager)return false; - } - auto stored=Display13FBMemory::field(fb,0x4A40); - if(stored && stored!=manager)return false; - for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+8*i); - memcpy(object+slots[group],&manager,sizeof(manager)); - } - memcpy(reinterpret_cast(fb)+0x4A40,&manager,sizeof(manager)); - fb->setProperty("N97FBManagersBound",true); - return true; -} bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { if(!display13BootReady || !xelpDisplay13Device(pci) || !xelpMemoryMatches(pci) || @@ -7776,7 +7712,7 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { if(!featuresSelected)return false; if(Display13FBTopology::deferredBootPrepare(board,s->adlpDisplaySelected) && !s->prepareDisplay13BootScanout(pci))return false; SYSLOG("igfx","Display13 native controller start enter offline=%u",display13Offline); - const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgDisplay13FramebufferStart)(that,provider); + const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgFramebufferStart)(that,provider); SYSLOG("igfx","Display13 native controller start return=%u",started); that->setProperty("N97FramebufferOriginalStart",started); IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferOriginalStart",started); @@ -11182,8 +11118,124 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add return true; } -// CPU object initialization and posted FIFO writes belong to this donor, -// not to a Display generation. Both framebuffer dispatchers install them. +// 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; + const auto symbol="__ZN31AppleIntelFramebufferController5startEP9IOService"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x49,0x89,0xf6,0x49,0x89,0xfd}; + const auto start=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(size<=0x3F1FC+sizeof(entry) || start!=address+0x3F1FC || + memcmp(reinterpret_cast(start),entry,sizeof(entry)))return false; + KernelPatcher::RouteRequest gate(symbol,wrapFramebufferStart,orgFramebufferStart); + const bool routed=patcher.routeMultiple(index,&gate,1,address,size);patcher.clearError(); + if(!routed)return false; + 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. + framebufferBackendInstalled=display13FramebufferRequested ? + processDisplay13Framebuffer(patcher,index,address,size) : processKextFull(patcher,index,address,size); + uint32_t record[]={1,routed,framebufferDonorCommonReady,framebufferBackendInstalled}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferInstallation",record,sizeof(record)); + return true; +} + +bool XE::processFramebufferCommon(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + tglSonomaSurfaceFramebufferReady=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); + tglPlatformNVRAMReady=xelpInstallNVRAMABI(patcher,index,address,size); + auto table=patcher.solveSymbol(index,"__ZTV17AppleIntelPortHAL",address,size); + auto pure=patcher.solveSymbol(KernelPatcher::KernelID,"___cxa_pure_virtual"); + const uint8_t halSize[]={0xB9,0xB0,0x06,0,0}; + bool probe=size>0xCCCD8 && table==address+0xCCAD0 && pure && xelpProviderProbe && xelpProviderConfigureProbe && + !memcmp(reinterpret_cast(address+0x5EE5E),halSize,sizeof(halSize)) && + *reinterpret_cast(table+0x188)==pure; + patcher.clearError(); + if(probe) { + // Slot repair is common; the target retains the existing hardware adapter. + auto replacement=display13FramebufferRequested ? xelpProviderProbe : 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(); + } + tglProbeReady=probe; + const auto initSymbol="__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj"; + const auto init=patcher.solveSymbol(index,initSymbol,address,size);patcher.clearError(); + const uint8_t initEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x10,0x41,0x89,0xD6,0x49,0x89,0xF7,0x48,0x89,0xFB}; + KernelPatcher::RouteRequest binding(initSymbol,wrapFramebufferInit,orgFramebufferInit); + tglFramebufferInitReady=size>0x29EC8+sizeof(initEntry) && init==address+0x29EC8 && + !memcmp(reinterpret_cast(init),initEntry,sizeof(initEntry)) && patcher.routeMultiple(index,&binding,1,address,size); + patcher.clearError(); + const auto laneSymbol="__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj"; + const auto lanes=patcher.solveSymbol(index,laneSymbol,address,size);patcher.clearError(); + const uint8_t laneEntry[]={0x55,0x48,0x89,0xE5,0x53,0x50}; + const uint8_t laneCall[]={0xE8,0x2A,0xF7,0xFE,0xFF,0x84,0xC0,0x74,0x1A}; + bool laneReady=size>0x304FC && lanes==address+0x1FC22 && + !memcmp(reinterpret_cast(lanes),laneEntry,sizeof(laneEntry)) && + !memcmp(reinterpret_cast(address+0x304F3),laneCall,sizeof(laneCall)); + if(laneReady) { + framebufferLaneCaller=address+0x304F8; + KernelPatcher::RouteRequest lane(laneSymbol,wrapFramebufferComputeLanes,orgFramebufferComputeLanes); + laneReady=patcher.routeMultipleShort(index,&lane,1,address,size);patcher.clearError(); + } + const bool agdc=processFramebufferAGDC(patcher,index,address,size); + const bool runtime=processFramebufferDonorRuntime(patcher,index,address,size); + framebufferTrainingHooksReady=processFramebufferTraining(patcher,index,address,size); + const bool ready=tglSonomaSurfaceFramebufferReady && tglPlatformNVRAMReady && probe && tglFramebufferInitReady && laneReady && agdc && runtime && framebufferTrainingHooksReady; + uint32_t record[]={4,tglPlatformNVRAMReady,probe,tglFramebufferInitReady,tglSonomaSurfaceFramebufferReady,laneReady,agdc,runtime,framebufferTrainingHooksReady,ready}; + auto root=IORegistryEntry::getRegistryRoot(); + root->setProperty("WEXFramebufferCommonHooks",record,sizeof(record)); + root->setProperty("TGLSonomaSurfaceFramebufferReady",tglSonomaSurfaceFramebufferReady); + root->setProperty("TGLHDMILaneRoute317",laneReady); + root->setProperty("TGLHDMILaneGuard316",framebufferLaneCaller!=0); + root->setProperty("TGLWEGProbeVtableFixed",probe); + root->setProperty("TGLWEGFramebufferInitRouted",tglFramebufferInitReady); + return ready; +} + +bool XE::wrapFramebufferStart(IOService *that,IOService *provider) { + auto s=callbackXE; + if(!s->framebufferDonorCommonReady || !s->framebufferBackendInstalled) { + if(that)that->setProperty("WEXFramebufferCommonRejected",true); + return false; + } + return s->display13FramebufferRequested ? wrapDisplay13FramebufferStart(that,provider) : wrapTGLFramebufferStart(that,provider); +} + +bool XE::wrapFramebufferInit(IOService *fb,IOService *controller,uint32_t index) { + if(!callbackXE->framebufferDonorCommonReady || !fb || !controller || index>=3 || + fb->getMetaClass()->getClassSize()<0x4B90 || controller->getMetaClass()->getClassSize()<0x1C20)return false; + if(!FunctionCast(wrapFramebufferInit,callbackXE->orgFramebufferInit)(fb,controller,index))return false; + struct Objects {size_t size(void *p) const {return p ? static_cast(p)->getMetaClass()->getClassSize() : 0;}} objects; + const bool bound=FramebufferDonor::bindManagers(objects,fb,controller,index); + fb->setProperty("WEXFramebufferManagersBound",bound); + if(callbackXE->display13FramebufferRequested)fb->setProperty("N97FBManagersBound",bound); + auto root=IORegistryEntry::getRegistryRoot(); + root->setProperty("TGLWEGFramebufferManagerBound",bound); + root->setProperty("TGLWEGPlaneManagersBound",bound?15:0,32); + root->setProperty("TGLWEGScalerManagersBound",bound?6:0,32); + return bound; +} + +bool XE::framebufferHDMIEligible(const uint32_t *config) const { + if(display13FramebufferRequested) + return Display13FBTopology::selected(adlnPlatform,config,adlnOutputWiring) && adlnOutputWiring.matchesHDMI(config); + return tglHDMIB && tglHDMIEligible(config); +} + +bool XE::wrapFramebufferComputeLanes(void *argument,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes) { + const auto caller=reinterpret_cast(__builtin_return_address(0)); + const auto native=FunctionCast(wrapFramebufferComputeLanes,callbackXE->orgFramebufferComputeLanes)(argument,timing,bpp,available,lanes); + const auto config=XELPDonor::hdmiLaneConfig(argument,caller,callbackXE->framebufferLaneCaller); + if(!config || !lanes)return native; + const bool corrected=XELPDonor::skipHDMIDPLanes(callbackXE->framebufferHDMIEligible(config),true,native,available,*lanes); + if(corrected) { + static uint32_t count {}; + IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneCorrections316",__sync_add_and_fetch(&count,1),32); + } + return native || corrected; +} + bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(framebufferDonorRuntimeReady)return true; bool hdcpReady=false,meiReady=false; @@ -11271,9 +11323,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (tglRequested) { if (!tglActive) return false; if (currentFramebuffer->loadIndex == index) { - processFramebufferDonorRuntime(patcher,index,address,size); - tglSonomaSurfaceFramebufferReady=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); - IORegistryEntry::getRegistryRoot()->setProperty("TGLSonomaSurfaceFramebufferReady",tglSonomaSurfaceFramebufferReady); if (modDVMTCalcFix.enabled && modDVMTCalcFix.available) modDVMTCalcFix.processFramebufferKext(patcher, index, address, size); // Exact donor transaction entries. Each installed wrapper arbitrates @@ -11322,7 +11371,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t tglConnectionImage = address; tglConnectionImageSize = size; tglConnectionLock = IOSimpleLockAlloc(); - tglPlatformNVRAMReady=xelpInstallNVRAMABI(patcher,index,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("TGLPlatformNVRAMABIReady", tglPlatformNVRAMReady); if (tglHDMIB) { // r319: the donor's combo PLL tables still contain ICL MMIO @@ -11609,27 +11658,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTopology", tglTopologyReady); patcher.clearError(); } - // DP1 KC vtable inheritance fixup replaces this externally supplied - // override with the base class's pure-virtual slot. Verify and repair - // only this donor slot; never replace the global pure-virtual handler. - auto table = patcher.solveSymbol(index, "__ZTV17AppleIntelPortHAL", address, size); - auto probe = reinterpret_cast(wrapTGLProbePort); - auto pure = patcher.solveSymbol(KernelPatcher::KernelID, "___cxa_pure_virtual"); - bool probeFixed = false; - if (table && probe && pure && xelpProviderProbe && xelpProviderConfigureProbe && table >= address && table - address == 0xCCAD0 && - *reinterpret_cast(table + 0x188) == pure) { - patcher.routeBlock(table + 0x188, reinterpret_cast(&probe), sizeof(probe)); - probeFixed = patcher.getError() == KernelPatcher::Error::NoError && - *reinterpret_cast(table + 0x188) == probe; - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGProbeVtableFixed", probeFixed); - tglProbeReady = probeFixed; - patcher.clearError(); - KernelPatcher::RouteRequest initRequest("__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj", - wrapTGLFramebufferInit, orgTGLFramebufferInit); - tglFramebufferInitReady = patcher.routeMultiple(index, &initRequest, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferInitRouted", tglFramebufferInitReady); - patcher.clearError(); { KernelPatcher::RouteRequest hdcp[] { @@ -11650,12 +11678,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (tglFBIRQReady) tglFBIRQReady=patcher.routeMultiple(index,irq,arrsize(irq),address,size); IORegistryEntry::getRegistryRoot()->setProperty("TGLFBIRQRouted",tglFBIRQReady);patcher.clearError(); } - KernelPatcher::RouteRequest request("__ZN31AppleIntelFramebufferController5startEP9IOService", - wrapTGLFramebufferStart, orgTGLFramebufferStart); - const bool ok = patcher.routeMultiple(index, &request, 1, address, size); - SYSLOG("igfx", "TGL framebuffer start route=%d", ok); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferStartRouted", ok); - patcher.clearError(); + { KernelPatcher::RouteRequest observe("__ZN21AppleIntelFramebuffer11getDDCBlockEijjjPhPy", wrapTGLTopDDC, orgTGLTopDDC); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -11681,18 +11704,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } { - if (tglHDMIB) { - const auto validate = patcher.solveSymbol(index, "__ZN21AppleIntelFramebuffer22validateDetailedTimingEPvy", address, size); - const uint8_t call[] {0xE8,0x2A,0xF7,0xFE,0xFF,0x84,0xC0,0x74,0x1A}; - if (validate && !memcmp(reinterpret_cast(validate+0x529), call, sizeof(call))) - tglHDMILaneCaller = validate+0x52E; - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneGuard316", tglHDMILaneCaller != 0); - patcher.clearError(); - KernelPatcher::RouteRequest laneRoute("__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj", wrapTGLComputeLanes, orgTGLComputeLanes); - const bool ok = patcher.routeMultiple(index, &laneRoute, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneRoute317", ok); - patcher.clearError(); - } + KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController22SetFbStatusOnNextProbeEP14AGDCFBOnline_t", wrapTGLFBOnline, orgTGLFBOnline); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteFBOnline", routed); @@ -11710,12 +11722,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteConnectionAttr", routed); patcher.clearError(); } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController12CallBackAGDCE31kAGDCRegisterLinkControlEvent_tmj", wrapTGLAGDCCallback, orgTGLAGDCCallback); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteAGDCCallback", routed); - patcher.clearError(); - } { KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController16GetGPUCapabilityEP19AGDCGPUCapability_t", wrapTGLGPUCapability, orgTGLGPUCapability); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -11758,12 +11764,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteFBSetAttr", routed); patcher.clearError(); } - { - KernelPatcher::RouteRequest observe("__ZN21AppleIntelFramebuffer13newUserClientEP4taskPvjPP12IOUserClient", wrapTGLFBUserClient, orgTGLFBUserClient); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteFBUserClient", routed); - patcher.clearError(); - } { KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController18GetStreamAccessAUXEP21AGDCStreamAccessAUX_tS1_", wrapTGLAGDCAUX, orgTGLAGDCAUX); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -11783,24 +11783,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController19doLinkTrainingBeginEP25AGDCDPLinkTrainingBegin_t", wrapTGLTrainingBegin, orgTGLTrainingBegin); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTrainingBegin", routed); - patcher.clearError(); - } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController14doLinkTrainingEP18AGDCDPPortConfig_tS1_", wrapTGLTraining, orgTGLTraining); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTraining", routed); - patcher.clearError(); - } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController17doLinkTrainingEndEP23AGDCDPLinkTrainingEnd_t", wrapTGLTrainingEnd, orgTGLTrainingEnd); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTrainingEnd", routed); - patcher.clearError(); - } { KernelPatcher::RouteRequest observe("__ZN17AppleIntelPortHAL10enablePortEv", wrapTGLEnablePort, orgTGLEnablePort); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -12006,8 +11988,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); KernelPatcher::RouteRequest observations[] { {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t", wrapTGLPortTraining, orgTGLPortTraining}, - {"__ZN21AppleIntelDisplayPath18getFirstPathByPortEP14AppleIntelPort", wrapTGLPathLookup, orgTGLPathLookup}, - {"__ZN21AppleIntelDisplayPath8freePathEv", wrapTGLFreePath, orgTGLFreePath}, {"__ZN31AppleIntelFramebufferController9LightUpDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2", wrapTGLLightUpDP, orgTGLLightUpDP}, {"__ZN31AppleIntelFramebufferController11LightUpTMDSEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2", wrapTGLLightUpTMDS, orgTGLLightUpTMDS}, {"__ZN31AppleIntelFramebufferController14disableDisplayEP21AppleIntelFramebufferP21AppleIntelDisplayPathh", wrapTGLDisableDisplay, orgTGLDisableDisplay}, @@ -12034,9 +12014,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t {"__ZN31AppleIntelFramebufferController31releaseResourceConfigurationSetEP28AGDCFBReleaseConfiguration_t", wrapTGLReleaseResources, orgTGLReleaseResources}, {"__ZN31AppleIntelFramebufferController30commitResourceConfigurationSetEP30AGDCResourceConfigurationSet_tS1_", wrapTGLCommitResources, orgTGLCommitResources}, {"__ZN31AppleIntelFramebufferController28testResourceConfigurationSetEP30AGDCResourceConfigurationSet_tS1_", wrapTGLTestResources, orgTGLTestResources}, - {"__ZN21AppleIntelFramebuffer17attachDisplayPathEP21AppleIntelDisplayPath", wrapTGLAttachPath, orgTGLAttachPath}, - {"__ZN21AppleIntelFramebuffer28allocateDisplayPathResourcesEjj", wrapTGLAllocatePathResources, orgTGLAllocatePathResources}, - {"__ZN21AppleIntelFramebuffer14addDisplayPathEj", wrapTGLAddPath, orgTGLAddPath} + {"__ZN21AppleIntelFramebuffer17attachDisplayPathEP21AppleIntelDisplayPath", wrapTGLAttachPath, orgTGLAttachPath} }; const bool routed = patcher.routeMultiple(index, observations, arrsize(observations), address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteResourceObservations", routed); @@ -12143,20 +12121,12 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } if (tglTrainingLeaseRequested) { - tglPathPoolLock = IORecursiveLockAlloc(); - for (uint32_t ddi = 0; ddi < TGLPortCount; ++ddi) tglTrainingLeases[ddi].ddi = ddi; KernelPatcher::SolveRequest symbols[] { {"__ZN17AppleIntelPortHAL11disablePortEv", tglDisablePort}, {"__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_", tglDisableClocks} }; - KernelPatcher::RouteRequest routes[] { - {"__ZN21AppleIntelDisplayPath12allocatePathEv", wrapTGLAllocatePathAny, orgTGLAllocatePathAny}, - {"__ZN21AppleIntelDisplayPath12allocatePathEj", wrapTGLAllocatePathFixed, orgTGLAllocatePathFixed}, - {"__ZN31AppleIntelFramebufferController4stopEP9IOService", wrapTGLControllerStop, orgTGLControllerStop} - }; - tglTrainingLeaseReady = tglPathPoolLock && - patcher.solveMultiple(index, symbols, arrsize(symbols), address, size) && - patcher.routeMultiple(index, routes, arrsize(routes), address, size); + tglTrainingLeaseReady = framebufferTrainingHooksReady && + patcher.solveMultiple(index, symbols, arrsize(symbols), address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTrainingLease", tglTrainingLeaseReady); IORegistryEntry::getRegistryRoot()->setProperty("TGLTrainingFreePath320", tglTrainingLeaseReady); patcher.clearError(); @@ -12182,8 +12152,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t { KernelPatcher::SolveRequest symbols[] { {"__ZN19AppleIntelPowerWell17enablePowerWellPGEj", tglEnablePG}, - {"__ZN19AppleIntelPowerWell18disablePowerWellPGEj", tglDisablePG}, - {"__ZN14AppleIntelPort12getPortByDDIEj", tglPortByDDI} + {"__ZN19AppleIntelPowerWell18disablePowerWellPGEj", tglDisablePG} }; if (patcher.solveMultiple(index, symbols, arrsize(symbols), address, size)) { KernelPatcher::RouteRequest routes[] { @@ -12798,7 +12767,7 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { tglTimeEvent(22, 0, false, 0, 0); SYSLOG("igfx", "TGL framebuffer start entered"); - bool result = FunctionCast(wrapTGLFramebufferStart, callbackXE->orgTGLFramebufferStart)(that, provider); + bool result = FunctionCast(wrapTGLFramebufferStart, callbackXE->orgFramebufferStart)(that, provider); tglTimeEvent(22, 0, true, 0, result); tglRecordStartOrder(4,that,9,0,0,result); tglFBIRQPublish(); @@ -15159,61 +15128,7 @@ bool XE::wrapTGLHDCP2Init(void *hdcp, void *path) { return stored == manager; } -bool XE::wrapTGLFramebufferInit(void *framebuffer, void *controller, uint32_t index) { - tglTimeEvent(23, index, false, 0, 0); - const auto initialized = FunctionCast(wrapTGLFramebufferInit, callbackXE->orgTGLFramebufferInit)(framebuffer, controller, index); - tglTimeEvent(23, index, true, 0, initialized); - if (!initialized) return false; - // This donor initializes the controller's shared MMIO manager but leaves - // the framebuffer's manager unset. Borrow it for direct register access; - // the controller owns it for the lifetime of its child framebuffers. - auto fb = static_cast(framebuffer); - auto ctl = static_cast(controller); - if (*reinterpret_cast(fb + 0x1D0) != controller) return false; - auto manager = *reinterpret_cast(ctl + 0xC40); - if (!manager || !*reinterpret_cast(manager + 0x50) || - *reinterpret_cast(manager + 0x60) <= 0x60404) return false; - // All 15 controller-owned planes are constructed before framebuffer init. - // Check every plane identity before changing any manager reference. - for (uint32_t i = 0; i < 15; ++i) { - auto plane = *reinterpret_cast(ctl + 0xF58 + i * sizeof(void *)); - if (!plane || *reinterpret_cast(plane + 0x7C) != i) { - SYSLOG("igfx", "TGL plane identity check failed index=%u", i); - return false; - } - } - // Six scalers precede the planes in controller construction order. - for (uint32_t i = 0; i < 6; ++i) { - auto scaler = *reinterpret_cast(ctl + 0xF28 + i * sizeof(void *)); - if (!scaler || *reinterpret_cast(scaler + 0x1C) != i) { - SYSLOG("igfx", "TGL scaler identity check failed index=%u", i); - return false; - } - } - uint32_t scalerBound = 0; - for (uint32_t i = 0; i < 6; ++i) { - auto scaler = *reinterpret_cast(ctl + 0xF28 + i * sizeof(void *)); - auto &scalerManager = *reinterpret_cast(scaler + 0x28); - if (!scalerManager) scalerManager = manager; - if (scalerManager == manager) ++scalerBound; - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGScalerManagersBound", scalerBound, 32); - SYSLOG("igfx", "TGL scaler managers bound=%u", scalerBound); - uint32_t bound = 0; - for (uint32_t i = 0; i < 15; ++i) { - auto plane = *reinterpret_cast(ctl + 0xF58 + i * sizeof(void *)); - auto &planeManager = *reinterpret_cast(plane + 0x90); - if (!planeManager) planeManager = manager; - if (planeManager == manager) ++bound; - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGPlaneManagersBound", bound, 32); - SYSLOG("igfx", "TGL plane managers bound=%u", bound); - auto &stored = *reinterpret_cast(fb + 0x4A40); - if (!stored) stored = manager; - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferManagerBound", stored == manager); - SYSLOG("igfx", "TGL framebuffer init index=%u manager bound=%d", index, stored == manager); - return true; -} + uint32_t XE::wrapTGLHPDState(void *port) { @@ -16239,22 +16154,7 @@ uint32_t XE::wrapTGLSetEDID(void *controller, void *request) { return r; } -bool XE::wrapTGLComputeLanes(void *port, const void *timing, uint32_t bpp, uint32_t available, uint32_t *lanes) { - const auto result = FunctionCast(wrapTGLComputeLanes, callbackXE->orgTGLComputeLanes)(port, timing, bpp, available, lanes); - const auto caller = reinterpret_cast(__builtin_return_address(0)); - auto config = XELPDonor::hdmiLaneConfig(port,caller,callbackXE->tglHDMILaneCaller); - if (!config) return result; - // r316: this exact validator incorrectly asks a TMDS connector for DP link lanes. - // Leave lane count zero and continue the remaining native HDMI/scaler checks. - // Preserve every DP call, other caller, positive result and nonzero lane result. - if (lanes && config && XELPDonor::skipHDMIDPLanes(callbackXE->tglHDMIEligible(config), - caller==callbackXE->tglHDMILaneCaller,result,available,*lanes)) { - static uint32_t corrected {}; - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneCorrections316", __sync_add_and_fetch(&corrected, 1), 32); - return true; - } - return result; -} + uint32_t XE::wrapTGLFBOnline(void *controller, void *request) { auto req = static_cast(request); @@ -16292,12 +16192,7 @@ uint32_t XE::wrapTGLConnectionAttr(void *fb, int connection, uint32_t attribute, return r; } -uint32_t XE::wrapTGLAGDCCallback(void *controller, uint32_t event, unsigned long argument, uint32_t value) { - tglTrace(7, event, false, event, value, 0, argument); - const auto r = FunctionCast(wrapTGLAGDCCallback, callbackXE->orgTGLAGDCCallback)(controller, event, argument, value); - tglTrace(7, event, true, event, value, r, argument); - return r; -} + uint32_t XE::wrapTGLGPUCapability(void *controller, void *output) { tglTrace(8, 0, false, output != nullptr, 0, 0, 0); @@ -16367,16 +16262,13 @@ uint32_t XE::wrapTGLFBSetAttr(void *fb, uint32_t attribute, unsigned long value) return r; } -uint32_t XE::wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t type, void **client) { - const auto id = *reinterpret_cast(static_cast(fb) + 0x1DC); - tglSnapshotFB(fb); - tglTrace(15, id, false, type, 0, 0, 0); - const auto r = FunctionCast(wrapTGLFBUserClient, callbackXE->orgTGLFBUserClient)(fb, task, security, type, client); - tglTrace(15, id, true, type, 0, r, r == 0 && client && *client ? 1 : 0); - tglSnapshotFB(fb); +void XE::observeFramebufferUserClient(void *fb,uint32_t type,void **client,uint32_t result) { + const auto id=FramebufferDonor::field(fb,0x1DC); + tglTrace(15,id,true,type,0,result,result==0 && client && *client ? 1 : 0); + tglSnapshotFB(fb); // A successful standard shared connection provides an on-demand read-only // boundary after desktop composition; never take WindowServer's type0 client. - if (type == 1 && r == 0) { + if (type == 1 && result == 0) { tglConnectionPublish(); tglProgramPublish(); tglPublishCursorLifetime(); @@ -16386,7 +16278,6 @@ uint32_t XE::wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t tglDPCDPublish(); } - return r; } uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 3432cd71..f9681daa 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -200,14 +200,12 @@ class XE { // describe installed routes, not successful runtime ownership or scanout. bool display13FBPowerBackendReady {false}; bool display13FBClockReady {false}, display13FBIRQReady {false}, display13FBMemoryReady {false}, display13FBABIReady {false}; - mach_vm_address_t orgDisplay13FramebufferStart {}, orgAlderLakeNPowerReferences[7] {}; + mach_vm_address_t orgAlderLakeNPowerReferences[7] {}; mach_vm_address_t orgAlderLakeNClockInit {}, orgAlderLakeNClockProbe {}; mach_vm_address_t orgAlderLakeNClockSet {}; mach_vm_address_t orgAlderLakeNScanoutAllocate {}, orgAlderLakeNScanoutSetup {}, orgAlderLakeNAperture {}; mach_vm_address_t adlnScanoutDisableFB {}; mach_vm_address_t orgAlderLakeNSetupWorkLoop {}; - mach_vm_address_t orgDisplay13FramebufferInit {}; - __attribute__((noinline)) bool processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); __attribute__((noinline)) bool processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); bool display13FBTCHooksReady {}; mach_vm_address_t orgDisplay13TCMode {},display13TCBaseMode {},orgDisplay13TCEnableClocks {},orgDisplay13TCDisableClocks {}; @@ -231,16 +229,15 @@ class XE { static bool wrapAlderLakeNGetOSInformation(IOService *controller); static uint32_t wrapAlderLakeNPortState(OSObject *port); mach_vm_address_t orgDisplay13GMBus {},orgDisplay13GMBusRead {},orgDisplay13GMBusWrite {},adlnGMBusFunction {}; - mach_vm_address_t orgDisplay13FBEnable {},orgAlderLakeNConnectionAttr {},orgAlderLakeNPortDDC {},orgDisplay13FBDDC {},orgAlderLakeNComputeLanes {},adlnHDMILaneCaller {}; + mach_vm_address_t orgDisplay13FBEnable {},orgAlderLakeNConnectionAttr {},orgAlderLakeNPortDDC {},orgDisplay13FBDDC {}; static uint32_t wrapDisplay13FBEnable(IOService *fb); - bool processDisplay13FramebufferAGDC(KernelPatcher &,size_t,mach_vm_address_t,size_t); - mach_vm_address_t orgDisplay13FBUserClient {},orgDisplay13AGDCCallback {}; - static uint32_t wrapDisplay13FBUserClient(IOService *,void *,void *,uint32_t,void **); - static uint32_t wrapDisplay13AGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); + bool processFramebufferAGDC(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgFramebufferUserClient {},orgFramebufferAGDCCallback {}; + static uint32_t wrapFramebufferUserClient(IOService *,void *,void *,uint32_t,void **); + static uint32_t wrapFramebufferAGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); static uint32_t wrapAlderLakeNConnectionAttr(IOService *fb,int connection,uint32_t attribute,unsigned long *value); static uint32_t wrapDisplay13FBDDC(IOService *fb,int connection,uint32_t block,uint32_t segment,uint32_t type,uint8_t *data,uint64_t *length); static uint32_t wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,bool force); - static bool wrapAlderLakeNComputeLanes(void *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes); static uint32_t wrapDisplay13GMBusRead(void *access,uint64_t reg); static void wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value); mach_vm_address_t orgAlderLakeNGamma10 {},adlnGammaTableUpdate {},orgAlderLakeNPreGamma {}; @@ -249,7 +246,6 @@ class XE { static void wrapAlderLakeNPreGamma(void *path,void *transaction,uint32_t control); static void wrapAlderLakeNGammaTable(void *path,uint16_t *table); static uint32_t wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request); - static bool wrapDisplay13FramebufferInit(IOService *framebuffer,IOService *controller,uint32_t index); static bool wrapDisplay13FramebufferStart(IOService *that, IOService *provider); static void wrapAlderLakeNPowerInit(void *power, void *controller); static void wrapAlderLakeNEngineEnable(void *power); @@ -401,6 +397,8 @@ class XE { IORecursiveLock *tglPathPoolLock {}; mach_vm_address_t orgTGLAllocatePathAny {}, orgTGLAllocatePathFixed {}, orgTGLControllerStop {}; mach_vm_address_t tglDisablePort {}, tglDisableClocks {}; + // Shared donor protocol despite historical TGL names. Registration belongs + // only to processFramebufferTraining; backends supply hardware adapters. // One reservation per physical port; the native pool limits simultaneous pipes. struct TGLTrainingLease { void *path {}, *port {}, *fb {}, *power {}, *adoptThread {}; @@ -588,7 +586,18 @@ class XE { static bool tglDekelAccess(void *port, uint32_t index, bool write, uint32_t &value); bool tglPlatformNVRAMReady {false}; bool tglFramebufferInitReady {false}; - mach_vm_address_t orgTGLFramebufferInit {}; + // The donor layer owns its hooks once, before the hardware backend runs. + 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); + __attribute__((noinline)) bool processFramebufferCommon(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool framebufferTrainingHooksReady {}; + __attribute__((noinline)) bool processFramebufferTraining(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static bool wrapFramebufferStart(IOService *,IOService *); + static bool wrapFramebufferInit(IOService *,IOService *,uint32_t); + static bool wrapFramebufferComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); + bool framebufferHDMIEligible(const uint32_t *) const; + static void observeFramebufferUserClient(void *,uint32_t,void **,uint32_t); bool framebufferDonorRuntimeReady {}; bool processFramebufferDonorRuntime(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgTGLHDCP2Init {}; @@ -598,8 +607,6 @@ class XE { static void wrapTGLHDCP1Timer(void *hdcp, void *timer); static uint32_t tglObserveHDCP1(void *hdcp, uint32_t stage, uint32_t event, bool done, uint32_t result, uint32_t pipe = 0xFFFFFFFFU); - static bool wrapTGLFramebufferInit(void *framebuffer, void *controller, uint32_t index); - mach_vm_address_t orgTGLFramebufferStart {}; bool tglFBIRQReady {true}; mach_vm_address_t orgTGLFBEnableIRQ {}, orgTGLFBSetupIRQ {}; static uint32_t wrapTGLFBEnableIRQ(void *controller); @@ -707,8 +714,6 @@ class XE { static uint32_t wrapTGLAGDCPortStatus(void *controller, void *request, void *output); mach_vm_address_t orgTGLAGDCAUX {}; static uint32_t wrapTGLAGDCAUX(void *controller, void *request, void *output); - mach_vm_address_t orgTGLFBUserClient {}; - static uint32_t wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t type, void **client); mach_vm_address_t orgTGLFBSetAttr {}; static uint32_t wrapTGLFBSetAttr(void *fb, uint32_t attribute, unsigned long value); mach_vm_address_t orgTGLFBGetAttr {}; @@ -723,8 +728,6 @@ class XE { static uint32_t wrapTGLLinkConfig(void *controller, void *request, void *output); mach_vm_address_t orgTGLGPUCapability {}; static uint32_t wrapTGLGPUCapability(void *controller, void *output); - mach_vm_address_t orgTGLAGDCCallback {}; - static uint32_t wrapTGLAGDCCallback(void *controller, uint32_t event, unsigned long argument, uint32_t value); mach_vm_address_t orgTGLTopDDC {}; static uint32_t wrapTGLTopDDC(void *fb, int connection, uint32_t block, uint32_t type, uint32_t options, uint8_t *data, uint64_t *length); mach_vm_address_t orgTGLPortDDC {}; @@ -733,8 +736,6 @@ class XE { static uint32_t wrapTGLStreamEDID(void *controller, void *request, void *output); mach_vm_address_t orgTGLSetEDID {}; static uint32_t wrapTGLSetEDID(void *controller, void *request); - mach_vm_address_t orgTGLComputeLanes {}, tglHDMILaneCaller {}; - static bool wrapTGLComputeLanes(void *port, const void *timing, uint32_t bpp, uint32_t available, uint32_t *lanes); mach_vm_address_t orgTGLFBOnline {}; static uint32_t wrapTGLFBOnline(void *controller, void *request); mach_vm_address_t orgTGLAGDCRegister {}; diff --git a/tests/display13_agdc.py b/tests/display13_agdc.py index 4c097973..1a1db6d5 100644 --- a/tests/display13_agdc.py +++ b/tests/display13_agdc.py @@ -3,13 +3,13 @@ from pathlib import Path import hashlib,re,subprocess,sys,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() -names=['processDisplay13FramebufferAGDC','wrapDisplay13FBUserClient','wrapDisplay13AGDCCallback'] +names=['processFramebufferAGDC','wrapFramebufferUserClient','wrapFramebufferAGDCCallback'] functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) arrays={n:re.search(n+r'\[\]=\{([^}]+)',functions).group(1) for n in ['clientEntry','callbackEntry','dispatch']} -assert 'if(!processDisplay13FramebufferAGDC(patcher,index,address,size))return false;' in s +assert s.count('processFramebufferAGDC(patcher,index,address,size)')==1 start=s[s.index('bool XE::wrapDisplay13FramebufferStart('):s.index('void XE::wrapAlderLakeNSetupWorkLoop(')] invalidate='if(s->tglObservationController==that)s->tglObservationController=nullptr;' -assert start.index(invalidate)tglObservationController=that;') if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes() @@ -47,10 +47,12 @@ struct XE { static XE *callbackXE;bool display13FramebufferRequested=true,display13FBABIReady=true; IOService *adlnTopologyController=nullptr,*tglObservationController=nullptr; - uintptr_t orgDisplay13FBUserClient=0,orgDisplay13AGDCCallback=0; - bool processDisplay13FramebufferAGDC(KernelPatcher &,size_t,uintptr_t,size_t); - static uint32_t wrapDisplay13FBUserClient(IOService *,void *,void *,uint32_t,void **); - static uint32_t wrapDisplay13AGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); + uintptr_t orgFramebufferUserClient=0,orgFramebufferAGDCCallback=0; + bool processFramebufferAGDC(KernelPatcher &,size_t,uintptr_t,size_t); + 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){}; + static void tglTrace(unsigned,unsigned,bool,unsigned,unsigned,unsigned,unsigned long){}; void restart(IOService *that){auto s=this;INVALIDATE} }; XE *XE::callbackXE; @@ -71,7 +73,7 @@ } void reset(XE &s){fb={};controller={};controller.meta.size=0x1c20;clientCalls=0; s.adlnTopologyController=s.tglObservationController=&controller;put(&fb,0x1d0,&controller);put(&controller,0x1bf8,(uintptr_t)receiver);} -uint32_t open(void **client,unsigned type=0){return XE::wrapDisplay13FBUserClient(&fb,(void*)0x1234,(void*)0x5678,type,client);} +uint32_t open(void **client,unsigned type=0){return XE::wrapFramebufferUserClient(&fb,(void*)0x1234,(void*)0x5678,type,client);} void clients(XE &s){ for(unsigned bad=0;bad<7;++bad){reset(s); switch(bad){case 0:put(&controller,0x1bf8,uintptr_t(0));break;case 1:s.tglObservationController=nullptr;break; @@ -92,30 +94,33 @@ put(&controller,0x1bf8,registered?(uintptr_t)receiver:0);put(&controller,0x1c00,(void*)nullptr); const uintptr_t arg=0x12345678fffffff0;const bool inc=event<=11&&(0x90fU&(1U<b(0x150000); memcpy(b.data()+0x273b4,clientEntry,sizeof(clientEntry));memcpy(b.data()+0x69df2,callbackEntry,sizeof(callbackEntry));memcpy(b.data()+0x69e1e,dispatch,sizeof(dispatch)); - for(unsigned bad=0;bad<8;++bad){KernelPatcher p;auto copy=b;s.display13FramebufferRequested=s.display13FBABIReady=true;size_t size=b.size(); + for(unsigned bad=2;bad<8;++bad){KernelPatcher p;auto copy=b;s.display13FramebufferRequested=s.display13FBABIReady=true;size_t size=b.size(); switch(bad){case 0:s.display13FramebufferRequested=false;break;case 1:s.display13FBABIReady=false;break;case 2:size=0x69e5f;break; case 3:p.delta=1;break;case 4:copy[0x273b4]^=1;break;case 5:copy[0x69df2]^=1;break;case 6:copy[0x69e21]^=1;break;case 7:p.fail=true;break;} - assert(!s.processDisplay13FramebufferAGDC(p,1,(uintptr_t)copy.data(),size));assert(p.routes==(bad==7?1U:0U)); + assert(!s.processFramebufferAGDC(p,1,(uintptr_t)copy.data(),size));assert(p.routes==(bad==7?1U:0U)); } s.display13FramebufferRequested=s.display13FBABIReady=true;KernelPatcher p; - assert(s.processDisplay13FramebufferAGDC(p,1,(uintptr_t)b.data(),b.size())&&p.routes==1); + assert(s.processFramebufferAGDC(p,1,(uintptr_t)b.data(),b.size())&&p.routes==1); if(argc>1){ std::ifstream f(argv[1],std::ios::binary);b.assign(std::istreambuf_iterator(f),{}); size_t length=((b.size()>0x150000?b.size():0x150000)+4095)&~size_t(4095); auto mem=(uint8_t*)mmap(nullptr,length,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);assert(mem!=MAP_FAILED);memcpy(mem,b.data(),b.size()); uintptr_t baseVtable[256]{};baseVtable[0x788/8]=(uintptr_t)originalClient;put(mem,0xc8088,(uintptr_t)baseVtable); assert(!mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)); - s.orgDisplay13FBUserClient=(uintptr_t)(mem+0x273b4);s.orgDisplay13AGDCCallback=(uintptr_t)(mem+0x69df2); + s.orgFramebufferUserClient=(uintptr_t)(mem+0x273b4);s.orgFramebufferAGDCCallback=(uintptr_t)(mem+0x69df2); clients(s);events(); reset(s);put(&controller,0x1bf8,uintptr_t(0));assert(open(&client)==kIOReturnNotReady); using Register=uint32_t(*)(void*,void*);void *request[]={nullptr,(void*)receiver}; diff --git a/tests/display13_fb_topology.cpp b/tests/display13_fb_topology.cpp index 4bb721d6..f6854b7d 100644 --- a/tests/display13_fb_topology.cpp +++ b/tests/display13_fb_topology.cpp @@ -50,15 +50,6 @@ int main() { assert(!apply(input.data(),Bytes,input.data(),wiring)); assert(!apply(output.data(),Bytes-1,input.data(),wiring)); assert(!apply(output.data(),Bytes,nullptr,wiring)); - assert(skipDPLanes(output.data(),output.config(1),wiring,true,false,0,0)); - assert(!skipDPLanes(output.data(),output.config(1),wiring,false,false,0,0)); - assert(!skipDPLanes(output.data(),output.config(1),wiring,true,true,0,0)); - assert(!skipDPLanes(output.data(),output.config(1),wiring,true,false,1,0)); - assert(!skipDPLanes(output.data(),output.config(1),wiring,true,false,0,1)); - assert(!skipDPLanes(output.data(),input.config(1),wiring,true,false,0,0)); - assert(!skipDPLanes(output.data(),output.config(0),wiring,true,false,0,0)); - auto dp=output;dp.config(1)[2]=1; - assert(!skipDPLanes(dp.data(),dp.config(1),wiring,true,false,0,0)); // Both physical S1 routes and their simultaneous board description. for(unsigned mask=1;mask<=7;++mask) { const uint32_t prop[] {1,mask,(mask&1)?1U:0U,(mask&2)?2U:0U,(mask&4)?3U:0U}; @@ -108,7 +99,6 @@ int main() { for(unsigned d=0;d<9;++d) { const bool on=w.selected()&(1U< +#include +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +struct IOService {void setProperty(const char*,bool) {}}; +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + templatevoid setProperty(T...){} +}; +bool surfaceOK=true,nvramOK=true,tglSonomaSurfaceFramebufferReady=false; +uintptr_t offset(const char *s){ + if(strstr(s,"5start"))return 0x3f1fc; + if(strstr(s,"__ZTV"))return 0xccad0; + if(strstr(s,"4init"))return 0x29ec8; + assert(strstr(s,"computeLaneCount"));return 0x1fc22; +} +struct KernelPatcher { + enum class Error {NoError,Failed};static constexpr unsigned KernelID=999; + struct RouteRequest {const char *name;uintptr_t *original; + templateRouteRequest(const char*s,F,uintptr_t &o):name(s),original(&o) {}}; + unsigned attempts=0,failAt=0;bool error=false; + templateT solveSymbol(size_t,const char *s,uintptr_t a=0,size_t=0){return a? a+offset(s):0x1234;} + bool routeMultiple(size_t,RouteRequest *rs,unsigned count,uintptr_t a,size_t){ + for(unsigned i=0;i(*rs[i].original)=0xcc;} + return true; + } + bool routeMultipleShort(size_t n,RouteRequest *r,unsigned c,uintptr_t a,size_t z){return routeMultiple(n,r,c,a,z);} + void routeBlock(uintptr_t at,const uint8_t *data,size_t size){++attempts;if(attempts==failAt){error=true;return;}memcpy((void*)at,data,size);} + Error getError(){return error?Error::Failed:Error::NoError;}void clearError(){error=false;} +}; +bool tglInstallSonomaSurfaceABI(KernelPatcher&,size_t,uintptr_t,size_t,bool){return surfaceOK;} +bool xelpInstallNVRAMABI(KernelPatcher&,size_t,uintptr_t,size_t){return nvramOK;} +struct XE { + bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; + bool display13FramebufferRequested=false,tglPlatformNVRAMReady=false,tglProbeReady=false,tglFramebufferInitReady=false; + bool runtimeOK=true,agdcOK=true,backendOK=true,trainingOK=true,framebufferTrainingHooksReady=false; + uintptr_t orgFramebufferStart=0,orgFramebufferInit=0,orgFramebufferComputeLanes=0,framebufferLaneCaller=0; + uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; + unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0; + bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_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;} + bool processFramebufferTraining(KernelPatcher&,size_t,uintptr_t,size_t){return trainingOK;} + bool backend(KernelPatcher&,size_t,uintptr_t,size_t){++backendCalls; + assert(framebufferDonorAttempted && orgFramebufferStart && commonRuntimeCalls==1); + IOService c;assert(!wrapFramebufferStart(&c,&c));return backendOK;} + bool processDisplay13Framebuffer(KernelPatcher&p,size_t i,uintptr_t a,size_t z){assert(display13FramebufferRequested);return backend(p,i,a,z);} + bool processKextFull(KernelPatcher&p,size_t i,uintptr_t a,size_t z){assert(!display13FramebufferRequested);return backend(p,i,a,z);} + static bool wrapFramebufferStart(IOService*,IOService*); + static bool wrapDisplay13FramebufferStart(IOService*,IOService*); + static bool wrapTGLFramebufferStart(IOService*,IOService*); + static void wrapTGLProbePort(){}static void wrapFramebufferInit(){}static void wrapFramebufferComputeLanes(){} +}; +XE *callbackXE; +bool XE::wrapDisplay13FramebufferStart(IOService*,IOService*){assert(callbackXE->display13FramebufferRequested);++callbackXE->startCalls;return true;} +bool XE::wrapTGLFramebufferStart(IOService*,IOService*){assert(!callbackXE->display13FramebufferRequested);++callbackXE->startCalls;return true;} +FUNCTIONS +int main(int argc,char **argv){ + std::vector image(0x150000); + if(argc>1){std::ifstream f(argv[1],std::ios::binary);image.assign(std::istreambuf_iterator(f),{});assert(image.size()>0xcccd8);} + const uint8_t start[]={START},init[]={INIT},lane[]={LANE},call[]={CALL},hal[]={HAL}; + for(auto span: {std::pair>{0x3f1fc,{start,start+sizeof(start)}}, + {0x29ec8,{init,init+sizeof(init)}},{0x1fc22,{lane,lane+sizeof(lane)}}, + {0x304f3,{call,call+sizeof(call)}},{0x5ee5e,{hal,hal+sizeof(hal)}}}){ + 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<11;++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; + 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)); + assert(x.startCalls==(failure==0?1U:0U)); + 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 + } + puts("PASS common-before-backend order, single ownership, partial failures and mandatory native-start gate"); +} +'''.replace('FUNCTIONS',f) +for key,name in [('START','entry'),('INIT','initEntry'),('LANE','laneEntry'),('CALL','laneCall'),('HAL','halSize')]: + code=code.replace(key,re.search(r'\b'+name+r'\[\]\s*=\s*\{([^}]+)',f).group(1)) +with tempfile.TemporaryDirectory(prefix='wex-common-') as d: + p=Path(d);(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')]+sys.argv[1:],check=True) diff --git a/tests/framebuffer_donor.cpp b/tests/framebuffer_donor.cpp new file mode 100644 index 00000000..96a923d8 --- /dev/null +++ b/tests/framebuffer_donor.cpp @@ -0,0 +1,53 @@ +#include "kern_framebuffer_donor.hpp" +#include +#include +#include +#include +#include +struct Graph { + std::vector fb=std::vector(0x4b90),controller=std::vector(0x1c20),manager=std::vector(0x68); + std::array,15> planes; + std::array,6> scalers; + std::map sizes; + size_t size(void *p){auto i=sizes.find(p);return i==sizes.end()?0:i->second;} + templatevoid put(void *p,size_t offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} + Graph(){ + sizes[fb.data()]=fb.size();sizes[controller.data()]=controller.size();sizes[manager.data()]=manager.size(); + put(fb.data(),0x1d0,controller.data());put(fb.data(),0x1dc,1U);put(controller.data(),0xc40,manager.data()); + put(manager.data(),0x50,uintptr_t(0x100000));put(manager.data(),0x60,0x1000000U); + for(unsigned i=0;i<15;++i){planes[i].resize(0x1c8);auto p=planes[i].data();sizes[p]=planes[i].size();put(p,0x7c,i);put(controller.data(),0xf58+i*8,p);} + for(unsigned i=0;i<6;++i){scalers[i].resize(0x58);auto p=scalers[i].data();sizes[p]=scalers[i].size();put(p,0x1c,i);put(controller.data(),0xf28+i*8,p);} + } + bool bind(){return FramebufferDonor::bindManagers(*this,fb.data(),controller.data(),1);} +}; +int main(){ + for(unsigned mapBytes:{0x200000U,0x1000000U}){ + Graph g;g.put(g.manager.data(),0x60,mapBytes);assert(g.bind()&&g.bind()); + for(auto &p:g.planes)assert(FramebufferDonor::field(p.data(),0x90)==g.manager.data()); + for(auto &p:g.scalers)assert(FramebufferDonor::field(p.data(),0x28)==g.manager.data()); + assert(FramebufferDonor::field(g.fb.data(),0x4a40)==g.manager.data()); + } + for(unsigned failure=0;failure<7;++failure){ + Graph g; + if(failure==0)g.put(g.fb.data(),0x1d0,static_cast(nullptr)); + if(failure==1)g.put(g.fb.data(),0x1dc,2U); + if(failure==2)g.put(g.manager.data(),0x60,0x60404U); + if(failure==3)g.put(g.manager.data(),0x50,uintptr_t(0)); + if(failure==4)g.put(g.controller.data(),0xc40,static_cast(nullptr)); + if(failure==5)g.sizes[g.controller.data()]=0x100; + if(failure==6)g.sizes[g.fb.data()]=0x100; + auto fb=g.fb;auto planes=g.planes;auto scalers=g.scalers;assert(!g.bind()); + assert(g.fb==fb&&g.planes==planes&&g.scalers==scalers); + } + // A failure at the last borrower must not mutate any earlier empty slot. + for(unsigned index=0;index<22;++index)for(unsigned kind=0;kind<3;++kind){ + Graph g;void *p=index<15?g.planes[index].data():index<21?g.scalers[index-15].data():g.fb.data(); + auto slot=index<15?0x90:index<21?0x28:0x4a40; + if(kind==0)g.put(p,slot,reinterpret_cast(0xdead)); + if(kind==1)g.sizes[p]=1; + if(kind==2)g.put(p,index<15?0x7c:index<21?0x1c:0x1dc,99U); + auto fb=g.fb;auto planes=g.planes;auto scalers=g.scalers;assert(!g.bind()); + assert(g.fb==fb&&g.planes==planes&&g.scalers==scalers); + } + puts("PASS common FB/plane/scaler binding: all-object validation, conflicting borrowers, no partial commit"); +} diff --git a/tests/framebuffer_donor_runtime.py b/tests/framebuffer_donor_runtime.py index e6fc99bd..838f489a 100644 --- a/tests/framebuffer_donor_runtime.py +++ b/tests/framebuffer_donor_runtime.py @@ -7,7 +7,7 @@ s = (r / 'WhateverXe/kern_xe.cpp').read_text() install = s[s.index('bool XE::processFramebufferDonorRuntime('):s.index('bool XE::processKextFull(')] wrapper = re.search(r'bool XE::wrapTGLHDCP2Init\(.*?\n\}', s, re.S).group() -assert s.count('processFramebufferDonorRuntime(patcher,index,address,size);') == 2 +assert s.count('processFramebufferDonorRuntime(patcher,index,address,size);') == 1 assert s.count('wrapTGLMEIWrite,orgTGLMEIWrite') == 1 assert 'wrapTGLMEIWrite, orgTGLMEIWrite' not in s assert 'hdcpReady && tglPanelPowerPropertyReady && meiReady' in install diff --git a/tests/framebuffer_training_registration.py b/tests/framebuffer_training_registration.py new file mode 100644 index 00000000..b0da766d --- /dev/null +++ b/tests/framebuffer_training_registration.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Replay the production common installer; hardware eligibility is separate.""" +from pathlib import Path +import re,subprocess,tempfile,sys +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +f=re.search(r'bool XE::processFramebufferTraining\(.*?\n\}',s,re.S).group() +# Single owner for the native protocol routes and pool lock. Hardware adapters +# may set lease eligibility, but must not re-install the common protocol. +wrappers=re.findall(r'\{entries\[\w+\].symbol,(wrap\w+),',f) +assert len(wrappers)==9 +for w in wrappers: + assert len(re.findall(r'(?:,|\s)'+w+r',org',s))==1,w +assert s.count('tglPathPoolLock=IORecursiveLockAlloc()')==1 +assert s.count('processFramebufferTraining(patcher,index,address,size)')==1 +originals=re.findall(r',(?:wrap\w+),(org\w+)\}',f)+['orgTGLPathLookup','tglPortByDDI'] +code=r''' +#include "kern_framebuffer_training_native.hpp" +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +#define arrsize(x) (sizeof(x)/sizeof((x)[0])) +unsigned lockCalls;bool failLock; +void *IORecursiveLockAlloc(){++lockCalls;return failLock?nullptr:(void*)1;} +struct KernelPatcher { + struct RouteRequest {const char *symbol;uintptr_t *original; + templateRouteRequest(const char*s,F,uintptr_t&o):symbol(s),original(&o){}}; + unsigned routed=0,failRoute=0;const char *badSymbol=nullptr; + templateT solveSymbol(size_t,const char *s,uintptr_t a,size_t){ + for(const auto&e:FramebufferTrainingNative::entries)if(!strcmp(s,e.symbol))return a+e.offset+(badSymbol==s?1:0); + assert(false);return 0; + } + void clearError(){} + bool routeMultipleShort(size_t,RouteRequest *rs,unsigned n,uintptr_t a,size_t size){ + for(unsigned i=0;i(0,rs[i].symbol,a,size); + *reinterpret_cast(*rs[i].original)=0xcc; + }return true; + } +}; +struct XE { + static constexpr unsigned TGLPortCount=9; + bool display13FramebufferRequested=false;void *tglPathPoolLock=nullptr; + struct {unsigned ddi=99;} tglTrainingLeases[TGLPortCount]; + ORIGINALS + WRAPPERS + bool processFramebufferTraining(KernelPatcher&,size_t,uintptr_t,size_t); +}; +FUNCTION +int main(int argc,char **argv){ + using namespace FramebufferTrainingNative; + std::vector image(0x150000); + if(argc>1){std::ifstream f(argv[1],std::ios::binary);image.assign(std::istreambuf_iterator(f),{});assert(image.size()>0x91da0);} + for(const auto&e:entries){if(argc>1)assert(!memcmp(image.data()+e.offset,e.prefix,16));else memcpy(image.data()+e.offset,e.prefix,16);} + for(const auto&t:spans){if(argc>1)assert(!memcmp(image.data()+t.offset,t.bytes,t.length));else memcpy(image.data()+t.offset,t.bytes,t.length);} + for(bool d13:{false,true}) { + for(unsigned failure=0;failure<=11;++failure){ + auto b=image;XE x;x.display13FramebufferRequested=d13;KernelPatcher p;lockCalls=0;failLock=failure==11; + if(failure<=10)p.failRoute=failure; + bool success=x.processFramebufferTraining(p,0,(uintptr_t)b.data(),b.size()); + assert(success==(failure==0||(d13&&failure==10))); + assert(lockCalls==1);if(failLock)assert(p.routed==0); + if(success){assert(p.routed==(d13?9U:10U));for(unsigned i=0;i<9;++i)assert(x.tglTrainingLeases[i].ddi==i);} + } + for(const auto&e:entries){ + auto b=image;XE x;x.display13FramebufferRequested=d13;KernelPatcher p;lockCalls=0;failLock=false; + b[e.offset]^=1;assert(!x.processFramebufferTraining(p,0,(uintptr_t)b.data(),b.size()));assert(!lockCalls&&!p.routed); + } + for(const auto&t:spans){ + auto b=image;XE x;x.display13FramebufferRequested=d13;KernelPatcher p;lockCalls=0; + b[t.offset]^=1;assert(!x.processFramebufferTraining(p,0,(uintptr_t)b.data(),b.size()));assert(!lockCalls&&!p.routed); + } + } + puts("PASS shared training registration: both backends, exact entries/spans, partial routes and lock failure"); +} +'''.replace('FUNCTION',f).replace('ORIGINALS','uintptr_t '+','.join(n+'=0' for n in originals)+';').replace('WRAPPERS','\n'.join('static void '+w+'(){}' for w in wrappers+['wrapTGLPathLookup'])) +with tempfile.TemporaryDirectory(prefix='wex-training-registration-') 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')]+sys.argv[1:],check=True) diff --git a/tests/tgl_hdmi_wiring.py b/tests/tgl_hdmi_wiring.py index 155305df..22b4478c 100644 --- a/tests/tgl_hdmi_wiring.py +++ b/tests/tgl_hdmi_wiring.py @@ -4,7 +4,7 @@ import hashlib,re,subprocess,sys,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() fields=h[h.index(' TGLHDMIWiring tglHDMIWiring;'):h.index('\tbool tglGMBusReady')] -functions='\n'.join(re.search(p,s,re.S).group() for p in [r'bool XE::tglLoadHDMIWiring\(.*?\n\}',r'bool XE::wrapTGLGetOSInformation\(.*?\n\}',r'uint32_t XE::wrapTGLGMBus\(.*?\n\}',r'bool XE::wrapTGLComputeLanes\(.*?\n\}',r'bool XE::wrapAlderLakeNComputeLanes\(.*?\n\}']) +functions='\n'.join(re.search(p,s,re.S).group() for p in [r'bool XE::tglLoadHDMIWiring\(.*?\n\}',r'bool XE::wrapTGLGetOSInformation\(.*?\n\}',r'uint32_t XE::wrapTGLGMBus\(.*?\n\}',r'bool XE::wrapFramebufferComputeLanes\(.*?\n\}',r'bool XE::framebufferHDMIEligible\(.*?\n\}']) observations=s[s.index('struct TGLGMBusObservation {'):s.index('struct TGLMEIRecord {')] code=(r/'tests/tgl_hdmi_wiring_fixture.cpp').read_text().replace('// FIELDS',fields).replace('// OBSERVATIONS',observations).replace('// FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='tgl-hdmi-wiring-') as d: diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index 4f24d054..a62f4232 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -33,14 +33,14 @@ struct XE { struct {uint32_t fStolenMemorySize=0;} framebufferPatch; XELP::VBT::Ports firmwarePorts; // FIELDS - uintptr_t orgTGLGetOSInformation {},orgTGLGMBus {},orgTGLComputeLanes {},tglHDMILaneCaller=0x100; + uintptr_t orgTGLGetOSInformation {},orgTGLGMBus {},orgFramebufferComputeLanes {},framebufferLaneCaller=0x100; uint64_t tglPlatformCopy[0x1C0/8] {}; static void tglBeginTCColdProtection(void*){} static bool wrapTGLGetOSInformation(void *); static uint32_t wrapTGLGMBus(void *,uint32_t,uint32_t,void *); - static bool wrapTGLComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); - static bool wrapAlderLakeNComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); - uintptr_t orgAlderLakeNComputeLanes {},adlnHDMILaneCaller=0x100; + static bool wrapFramebufferComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); + bool display13FramebufferRequested=false; + bool framebufferHDMIEligible(const uint32_t *) const; void *adlnPlatform {};Display13OutputWiring adlnOutputWiring; }; XE object,*callbackXE=&object; @@ -54,7 +54,7 @@ uint32_t nativeGMBus(void*,uint32_t,uint32_t,void*){++nativeGMBusCalls;edidSelec for(auto &o:tglGMBusObservations)if(o.thread==1)edidSelected=o.edid;return 0x91;} uint8_t *&platform(){return *reinterpret_cast(controller+0xD40);} void setup(){ - object=XE{};object.orgAlderLakeNComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGetOSInformation=(uintptr_t)nativeOS;object.orgTGLComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGMBus=(uintptr_t)nativeGMBus; + object=XE{};object.orgTGLGetOSInformation=(uintptr_t)nativeOS;object.orgFramebufferComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGMBus=(uintptr_t)nativeGMBus; memset(controller,0,sizeof(controller));memset(original,0x5a,sizeof(original));platform()=original;original[0x12]=9; for(unsigned d=0;d<9;++d){uint32_t cfg[]={d,d+1,1,d>=3?1u:0u,0x400,0x201};memcpy(original+0x20+24*d,cfg,24);} const uint32_t panel[]={0,0,0,0,2,0x18};memcpy(original+0x20,panel,24); @@ -108,23 +108,24 @@ int main(){ const auto page=static_cast(sysconf(_SC_PAGESIZE)); void *inaccessible=mmap(nullptr,page,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0); assert(inaccessible!=MAP_FAILED); - for(auto wrapper:{XE::wrapTGLComputeLanes,XE::wrapAlderLakeNComputeLanes}) { + for(bool display13:{false,true}) { + object.display13FramebufferRequested=display13;auto wrapper=XE::wrapFramebufferComputeLanes; testCaller=0x101;nativeLanesCalls=0; for(bool native:{false,true}){ nativeLanesResult=native;uint32_t lanes=4; assert(wrapper(inaccessible,nullptr,24,20,&lanes)==native); assert(lanes==4); } - object.tglHDMILaneCaller=object.adlnHDMILaneCaller=0;testCaller=0; + object.framebufferLaneCaller=0;testCaller=0; assert(wrapper(inaccessible,nullptr,24,20,nullptr)); assert(nativeLanesCalls==3); - object.tglHDMILaneCaller=object.adlnHDMILaneCaller=0x100; + object.framebufferLaneCaller=0x100; } assert(munmap(inaccessible,page)==0); // The production Display13 wrapper keeps HDMI correction and DP pass-through // under the exact validator, across both Combo and TC topology records. for(unsigned d:{0U,1U,3U,4U,5U,6U})for(bool hdmi:{false,true}) { - setup();Display13OutputWiring w;w.valid=true; + setup();object.display13FramebufferRequested=true;Display13OutputWiring w;w.valid=true; original[0x13]=3; for(unsigned i=3;i<9;++i)reinterpret_cast(original+0x20+24*i)[1]=i+6; if(hdmi){w.hdmi=1U<(platform()+0x38);assert(cfg[1]==pin&&object.tglHDMIEligible(cfg)); *reinterpret_cast(port+0x548)=cfg; - uint32_t lanes=0;assert(XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes)&&lanes==0&&nativeLanesCalls==1); - testCaller=0x101;assert(!XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes));testCaller=0x100; - lanes=1;assert(!XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes));lanes=0; - assert(!XE::wrapTGLComputeLanes(port,nullptr,32,1,&lanes)); - auto saved=cfg[1];cfg[1]=pin==1?2:1;assert(!XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes));cfg[1]=saved; + uint32_t lanes=0;assert(XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes)&&lanes==0&&nativeLanesCalls==1); + testCaller=0x101;assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes));testCaller=0x100; + lanes=1;assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes));lanes=0; + assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,1,&lanes)); + auto saved=cfg[1];cfg[1]=pin==1?2:1;assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes));cfg[1]=saved; for(unsigned bus=0;bus<=14;++bus)for(unsigned fault=0;fault<5;++fault){ uint32_t request[16] {};request[1]=1;request[3]=0xA0;request[14]=128; uint32_t speed=fault==1?1:0;if(fault==2)request[1]=0;if(fault==3)request[3]=0xA2;if(fault==4)request[14]=64; diff --git a/tools/test.py b/tools/test.py index cce284dc..017388a6 100644 --- a/tools/test.py +++ b/tools/test.py @@ -5,14 +5,14 @@ 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=['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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] 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 += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] -if a.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,[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']] +if a.donor:jobs += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.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 with (O/(n+'.log')).open('w') as log: From ab090d0e9779548fb5eafb8e2808773e73faeb77 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:23:02 +0900 Subject: [PATCH 36/60] Preserve framebuffer ownership across publication and failed teardown --- WhateverXe/kern_framebuffer_donor.hpp | 19 ++++ WhateverXe/kern_xe.cpp | 153 +++++++++++++++----------- WhateverXe/kern_xe.hpp | 5 +- tests/display13_agdc.py | 18 ++- tests/display13_training_lease.py | 16 ++- tests/framebuffer_common.py | 16 ++- tests/tgl_training_leases_fixture.cpp | 21 ++++ 7 files changed, 165 insertions(+), 83 deletions(-) diff --git a/WhateverXe/kern_framebuffer_donor.hpp b/WhateverXe/kern_framebuffer_donor.hpp index 7a6e2538..fcc40aa3 100644 --- a/WhateverXe/kern_framebuffer_donor.hpp +++ b/WhateverXe/kern_framebuffer_donor.hpp @@ -5,6 +5,25 @@ #include #include namespace FramebufferDonor { +// Native entry admission is closed before controller/pool destruction. The +// low bits count in-flight opens, training and resource adoption; stop never +// holds the pool lock while draining them. Closing is terminal for this owner. +class Call { + static constexpr uint32_t Closed=1U<<31; + uint32_t *state {}; +public: + explicit Call(uint32_t &value) { + auto old=__atomic_load_n(&value,__ATOMIC_ACQUIRE); + while(!(old&Closed) && old!=Closed-1) { + if(__atomic_compare_exchange_n(&value,&old,old+1,false,__ATOMIC_ACQ_REL,__ATOMIC_ACQUIRE)) {state=&value;break;} + } + } + ~Call(){if(state)__atomic_fetch_sub(state,1,__ATOMIC_RELEASE);} + Call(const Call &)=delete;Call &operator=(const Call &)=delete; + explicit operator bool() const {return state!=nullptr;} + static void close(uint32_t &value){__atomic_fetch_or(&value,Closed,__ATOMIC_ACQ_REL);} + static bool drained(uint32_t &value){return (__atomic_load_n(&value,__ATOMIC_ACQUIRE)&~Closed)==0;} +}; template T field(const void *object,size_t offset) { T value {};if(object)memcpy(&value,static_cast(object)+offset,sizeof(value));return value; } diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e93f104a..039eeca3 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -7560,6 +7560,8 @@ bool XE::processFramebufferAGDC(KernelPatcher &patcher,size_t index,mach_vm_addr } 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;} // Only type0 enters IOFramebuffer::open. Refuse before entering IOG: an // error from enableController permanently sets IOG's dead flag instead. // Native controller start publishes FB children before its AGDC nub. @@ -7571,9 +7573,9 @@ uint32_t XE::wrapFramebufferUserClient(IOService *fb,void *task,void *security,u Display13FBMemory::field(fb,0x1D0) : nullptr; const bool bound=controller && controller->getMetaClass()->getClassSize()>=0x1C20 && (!s->display13FramebufferRequested || controller==s->adlnTopologyController); - // Display13 additionally needs the hardware handoff complete before - // IOFramebuffer::open; callback safety itself is donor-wide. - const bool started=bound && (!s->display13FramebufferRequested || controller==s->tglObservationController); + // Publication follows the common start result, never a mutable diagnostic + // controller pointer that a training adapter can populate early. + const bool started=bound && controller==__atomic_load_n(&s->framebufferStartedController,__ATOMIC_ACQUIRE); const bool registered=bound && Display13FBMemory::field(controller,0x1BF8); uint64_t record[]={1,bound,started,registered, bound && Display13FBMemory::field(controller,0x1C00), @@ -11195,11 +11197,23 @@ bool XE::processFramebufferCommon(KernelPatcher &patcher,size_t index,mach_vm_ad bool XE::wrapFramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE; - if(!s->framebufferDonorCommonReady || !s->framebufferBackendInstalled) { + if(!s->framebufferDonorCommonReady || !s->framebufferBackendInstalled || !s->tglPathPoolLock) { if(that)that->setProperty("WEXFramebufferCommonRejected",true); return false; } - return s->display13FramebufferRequested ? wrapDisplay13FramebufferStart(that,provider) : wrapTGLFramebufferStart(that,provider); + IORecursiveLockLock(s->tglPathPoolLock); + if(s->framebufferStartInProgress || s->tglLeasesStopping || s->framebufferStartedController) { + IORecursiveLockUnlock(s->tglPathPoolLock);return false; + } + s->framebufferStartInProgress=true; + IORecursiveLockUnlock(s->tglPathPoolLock); + const bool native=s->display13FramebufferRequested ? wrapDisplay13FramebufferStart(that,provider) : wrapTGLFramebufferStart(that,provider); + IORecursiveLockLock(s->tglPathPoolLock); + const bool accepted=native && !s->tglLeasesStopping; + __atomic_store_n(&s->framebufferStartedController,accepted?static_cast(that):nullptr,__ATOMIC_RELEASE); + s->framebufferStartInProgress=false; + IORecursiveLockUnlock(s->tglPathPoolLock); + return accepted; } bool XE::wrapFramebufferInit(IOService *fb,IOService *controller,uint32_t index) { @@ -16540,6 +16554,22 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { SYSLOG("igfx", "TGL330 release enter ddi=%u disable=%u", lease.ddi, disableLink); auto s = callbackXE; + // A failed extra-reference put after adoption is not a training reservation. + // Native scanout owns (and may already have freed) the path. Retry only our + // retained power/object references, without inspecting/freeing that path. + if(lease.adopted) { + if(lease.powerOwned) { + if(s->trainingLeasePower) {if(!s->trainingLeasePower(lease.power,lease.pg,false))return false;} + else reinterpret_cast(s->tglDisablePG)(lease.power,lease.pg); + } + auto fb=static_cast(lease.fb),port=static_cast(lease.port),power=static_cast(lease.power); + IORecursiveLockLock(s->tglPathPoolLock); + lease.path=lease.port=lease.fb=lease.power=nullptr;lease.powerOwned=false; + lease.adopted=false;lease.adoptThread=nullptr;lease.endCleanupPending=false; + IORecursiveLockUnlock(s->tglPathPoolLock); + fb->release();port->release();power->release();return true; + } + const auto pipe = lease.pipe; if (!lease.path) return true; if (!lease.powerOwned || pipe >= 3 || lease.ddi >= TGLPortCount || @@ -16637,72 +16667,59 @@ void *XE::wrapTGLAllocatePathFixed(uint32_t pipe) { } void XE::wrapTGLControllerStop(void *controller, IOService *provider) { + auto s=callbackXE; + if(!__sync_bool_compare_and_swap(&s->tglStopInProgress,false,true))return; + if(__atomic_load_n(&s->tglStopComplete,__ATOMIC_ACQUIRE)) { + __atomic_store_n(&s->tglStopInProgress,false,__ATOMIC_RELEASE);return; + } if(__atomic_load_n(&tglBootstrapAUX.enabled,__ATOMIC_ACQUIRE))__atomic_fetch_add(&tglBootstrapAUX.stopSeen,1,__ATOMIC_RELAXED); tglRecordStartOrder(7,controller); - auto s = callbackXE; - IORecursiveLockLock(s->tglPathPoolLock); - s->tglLeasesStopping = true; - IORecursiveLockUnlock(s->tglPathPoolLock); - // Native resource allocation runs outside the pool lock too. Drain those - // FB claims before touching their leases or tearing down the native pool. - for (unsigned waited=0;;++waited) { - if(s->trainingLeaseValidate && waited==500) { - // A void stop cannot cancel an in-flight native call. Keep the - // controller/pool alive when refusing teardown; admission is closed. - if(__sync_bool_compare_and_swap(&s->tglStopRetained,false,true))static_cast(controller)->retain(); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13StopBusy",true);return; - } - IORecursiveLockLock(s->tglPathPoolLock); - bool allocating = false; - for (auto fb : s->tglAllocatingFB) allocating |= fb != nullptr; - IORecursiveLockUnlock(s->tglPathPoolLock); - if (!allocating) break; - IOSleep(1); - } - // Block new reservations first, then drain every existing port. Do not hold - // the shared pool lock across native power waits or port shutdown. - for (auto &lease : s->tglTrainingLeases) { - for (unsigned waited=0;;++waited) { - if(s->trainingLeaseValidate && waited==500) { - // A void stop cannot cancel an in-flight native call. Keep the - // controller/pool alive when refusing teardown; admission is closed. - if(__sync_bool_compare_and_swap(&s->tglStopRetained,false,true))static_cast(controller)->retain(); - IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13StopBusy",true);return; - } - IORecursiveLockLock(s->tglPathPoolLock); - if (!lease.busy) { lease.busy = true; IORecursiveLockUnlock(s->tglPathPoolLock); break; } - IORecursiveLockUnlock(s->tglPathPoolLock); - IOSleep(1); - } - if (lease.path && !tglReleaseTrainingLease(lease, true)) { - // Native controller teardown owns any still-active path. Never return - // it to the pool, or dereference it after native stop destroys it. - tglObserveTrainingLease(lease, 9, 1); - auto fb = static_cast(lease.fb), port = static_cast(lease.port); - auto power = static_cast(lease.power); - IORecursiveLockLock(s->tglPathPoolLock); - lease.path = lease.port = lease.fb = lease.power = nullptr; - lease.powerOwned = false; lease.adopted = false; lease.adoptThread = nullptr; - IORecursiveLockUnlock(s->tglPathPoolLock); - fb->release(); port->release(); power->release(); - } - if (s->trainingLeaseValidate) { - // This stop owns the claim. Admission remains closed; release it so - // a later stop can retry if another lease is still in flight. - IORecursiveLockLock(s->tglPathPoolLock); - lease.busy = false; - IORecursiveLockUnlock(s->tglPathPoolLock); - } - } - FunctionCast(wrapTGLControllerStop, s->orgTGLControllerStop)(controller, provider); - tglRecordStartOrder(8,controller); - if(__sync_bool_compare_and_swap(&s->tglStopRetained,true,false)) { - IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13StopBusy",false); - static_cast(controller)->release(); + IORecursiveLockLock(s->tglPathPoolLock); + s->tglLeasesStopping=true; + FramebufferDonor::Call::close(s->framebufferProtocolCalls); + __atomic_store_n(&s->framebufferStartedController,static_cast(nullptr),__ATOMIC_RELEASE); + IORecursiveLockUnlock(s->tglPathPoolLock); + const auto blocked=[&](unsigned reason) { + // The native stop deletes the pool; it cannot finish an unproven WEX + // reservation's hardware teardown. Preserve its owner and all held refs. + if(__sync_bool_compare_and_swap(&s->tglStopRetained,false,true))static_cast(controller)->retain(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferStopBlocked",reason,32); + __atomic_store_n(&s->tglStopInProgress,false,__ATOMIC_RELEASE); + }; + // Drain native startup/allocation before touching reservations. No pool + // lock crosses a wait, hardware transaction or native controller teardown. + for(unsigned waited=0;;++waited) { + IORecursiveLockLock(s->tglPathPoolLock); + bool active=s->framebufferStartInProgress || !FramebufferDonor::Call::drained(s->framebufferProtocolCalls); + for(auto fb:s->tglAllocatingFB)active|=fb!=nullptr; + IORecursiveLockUnlock(s->tglPathPoolLock); + if(!active)break; + if(waited==500){blocked(1);return;} + IOSleep(1); } + for(auto &lease:s->tglTrainingLeases) { + for(unsigned waited=0;;++waited) { + IORecursiveLockLock(s->tglPathPoolLock); + if(!lease.busy){lease.busy=true;IORecursiveLockUnlock(s->tglPathPoolLock);break;} + IORecursiveLockUnlock(s->tglPathPoolLock); + if(waited==500){blocked(1);return;} + IOSleep(1); + } + const bool released=!lease.path || tglReleaseTrainingLease(lease,true); + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock); + if(!released){blocked(2);return;} + } + FunctionCast(wrapTGLControllerStop,s->orgTGLControllerStop)(controller,provider); + tglRecordStartOrder(8,controller); + __atomic_store_n(&s->tglStopComplete,true,__ATOMIC_RELEASE); + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferStopBlocked",static_cast(0),32); + if(__sync_bool_compare_and_swap(&s->tglStopRetained,true,false))static_cast(controller)->release(); + __atomic_store_n(&s->tglStopInProgress,false,__ATOMIC_RELEASE); } uint32_t XE::wrapTGLTrainingBegin(void *controller, void *request) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return kIOReturnBusy;} static uint32_t observed {}; if (__sync_add_and_fetch(&observed, 1) == 1) tglAUXPublish(); auto req = static_cast(request); @@ -16738,6 +16755,8 @@ uint32_t XE::wrapTGLTrainingBegin(void *controller, void *request) { } uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return kIOReturnBusy;} auto req = static_cast(request); const uint32_t id = req ? *reinterpret_cast(req) : 9; uint32_t a = req ? *reinterpret_cast(req + 8) : 0; @@ -16804,6 +16823,8 @@ uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { } uint32_t XE::wrapTGLTrainingEnd(void *controller, void *request) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return kIOReturnBusy;} auto req = static_cast(request); const uint32_t id = req ? *reinterpret_cast(req) : 9; uint32_t a = req ? *reinterpret_cast(req + 8) : 0, b = 0; @@ -17048,6 +17069,8 @@ void XE::tglObserveConfig(void *controller, void *request, void *output, bool te } bool XE::wrapTGLAllocatePathResources(void *fb, uint32_t portIndex, uint32_t stream) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return false;} SYSLOG("igfx", "TGL330 resources enter ddi=%u stream=%u", portIndex, stream); auto caller = __builtin_return_address(0); tglObserveResource(fb, nullptr, 9, false, 0, portIndex, stream, caller); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f9681daa..610ff535 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -418,7 +418,7 @@ class XE { static constexpr uint32_t TGLPortCount = 9; TGLTrainingLease tglTrainingLeases[TGLPortCount] {}; void *tglAllocatingFB[TGLPortCount] {}; - bool tglLeasesStopping {},tglStopRetained {}; + bool tglLeasesStopping {},tglStopRetained {},tglStopInProgress {},tglStopComplete {}; static TGLTrainingLease *tglTrainingLeaseForDDI(uint32_t ddi); static TGLTrainingLease *tglAdoptingLease(); // caller holds the pool lock static bool tglLeaseFBReserved(void *fb, const TGLTrainingLease *except = nullptr); @@ -592,6 +592,9 @@ class XE { __attribute__((noinline)) bool processFramebufferKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); __attribute__((noinline)) bool processFramebufferCommon(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool framebufferTrainingHooksReady {}; + bool framebufferStartInProgress {}; + uint32_t framebufferProtocolCalls {}; + void *framebufferStartedController {}; __attribute__((noinline)) bool processFramebufferTraining(KernelPatcher &,size_t,mach_vm_address_t,size_t); static bool wrapFramebufferStart(IOService *,IOService *); static bool wrapFramebufferInit(IOService *,IOService *,uint32_t); diff --git a/tests/display13_agdc.py b/tests/display13_agdc.py index 1a1db6d5..f6d2c71a 100644 --- a/tests/display13_agdc.py +++ b/tests/display13_agdc.py @@ -7,16 +7,13 @@ functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) arrays={n:re.search(n+r'\[\]=\{([^}]+)',functions).group(1) for n in ['clientEntry','callbackEntry','dispatch']} assert s.count('processFramebufferAGDC(patcher,index,address,size)')==1 -start=s[s.index('bool XE::wrapDisplay13FramebufferStart('):s.index('void XE::wrapAlderLakeNSetupWorkLoop(')] -invalidate='if(s->tglObservationController==that)s->tglObservationController=nullptr;' -assert start.index(invalidate)tglObservationController=that;') if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes() assert hashlib.sha256(b).hexdigest() in ['c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731','8f83afbe21f216ed18ea798c10d91b7b4fd990b62a5740832c1f90137722c32f'] for key,offset in [('clientEntry',0x273b4),('callbackEntry',0x69df2),('dispatch',0x69e1e)]: expected=bytes(int(x,0) for x in arrays[key].split(','));assert b[offset:offset+len(expected)]==expected code=r''' +#include "kern_framebuffer_donor.hpp" #include #include #include @@ -47,13 +44,13 @@ struct XE { static XE *callbackXE;bool display13FramebufferRequested=true,display13FBABIReady=true; IOService *adlnTopologyController=nullptr,*tglObservationController=nullptr; + uint32_t framebufferProtocolCalls=0;void *framebufferStartedController=nullptr; uintptr_t orgFramebufferUserClient=0,orgFramebufferAGDCCallback=0; bool processFramebufferAGDC(KernelPatcher &,size_t,uintptr_t,size_t); 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){}; static void tglTrace(unsigned,unsigned,bool,unsigned,unsigned,unsigned,unsigned long){}; - void restart(IOService *that){auto s=this;INVALIDATE} }; XE *XE::callbackXE; FUNCTIONS @@ -72,13 +69,14 @@ return fn(Display13FBMemory::field(c,0x1c00),inc?uint32_t(a+1):a,v,e); } void reset(XE &s){fb={};controller={};controller.meta.size=0x1c20;clientCalls=0; + s.framebufferProtocolCalls=0;s.framebufferStartedController=&controller; s.adlnTopologyController=s.tglObservationController=&controller;put(&fb,0x1d0,&controller);put(&controller,0x1bf8,(uintptr_t)receiver);} uint32_t open(void **client,unsigned type=0){return XE::wrapFramebufferUserClient(&fb,(void*)0x1234,(void*)0x5678,type,client);} void clients(XE &s){ for(unsigned bad=0;bad<7;++bad){reset(s); - switch(bad){case 0:put(&controller,0x1bf8,uintptr_t(0));break;case 1:s.tglObservationController=nullptr;break; + switch(bad){case 0:put(&controller,0x1bf8,uintptr_t(0));break;case 1:s.framebufferStartedController=nullptr;break; case 2:s.adlnTopologyController=&other;break;case 3:fb.meta.size=0x100;break;case 4:controller.meta.size=0x100;break; - case 5:put(&fb,0x1d0,(IOService*)nullptr);break;case 6:s.restart(&controller);break;} + case 5:put(&fb,0x1d0,(IOService*)nullptr);break;case 6:FramebufferDonor::Call::close(s.framebufferProtocolCalls);break;} auto before=fb;void *client=(void*)1;assert(open(&client)==kIOReturnNotReady&&!client&&!clientCalls); assert(!memcmp(&before,&fb,sizeof(fb))); } @@ -128,11 +126,11 @@ assert(open(&client)==result&&client==&controller);munmap(mem,length); puts("PASS exact donor client tail call, registration and 512 guarded/native event cases"); } - puts("PASS production AGDC admission, retry without FB mutation, passthrough, restart invalidation and installer refusals"); + puts("PASS production AGDC admission, retry without FB mutation, passthrough, start/stop publication and installer refusals"); } -'''.replace('FUNCTIONS',functions).replace('INVALIDATE',invalidate) +'''.replace('FUNCTIONS',functions) for old,key in [('CLIENT_ENTRY','clientEntry'),('CALLBACK_ENTRY','callbackEntry'),('DISPATCH','dispatch')]:code=code.replace(old,arrays[key]) with tempfile.TemporaryDirectory(prefix='display13-agdc-') as d: p=Path(d);(p/'test.cpp').write_text(code) - subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/display13_training_lease.py b/tests/display13_training_lease.py index 3561c60b..53b10498 100644 --- a/tests/display13_training_lease.py +++ b/tests/display13_training_lease.py @@ -109,11 +109,25 @@ assert(!stopCalled&&obj.tglStopRetained&&retained[ctl.data()]==1); obj.tglTrainingLeases[5].busy=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); assert(stopCalled&&!obj.tglStopRetained);clean(); + // Adoption owns the native path even when our extra PG put fails. Stop + // retries only that extra reference and must not inspect the adopted path. + init(5,1);assert(!XE::wrapTGLTraining(ctl.data(),endReq,endOut));rejectPowerPut=true; + nativeBoundary=[&]{enablePG(power.data(),obj.tglTrainingLeases[5].pg);}; + assert(!XE::wrapTGLAllocatePathResources(fbs[0].data(),5,0));nativeBoundary={}; + auto &adopted=obj.tglTrainingLeases[5];auto nativePath=adopted.path;auto nativePG=adopted.pg; + assert(adopted.adopted&&adopted.powerOwned&&pgRefs[nativePG]==2); + adopted.path=(void*)1; // Native-owned path may already be destroyed. + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(!stopCalled&&obj.tglStopRetained&&adopted.adopted); + rejectPowerPut=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(stopCalled&&!adopted.path&&pgRefs[nativePG]==1); + disablePG(power.data(),nativePG);freePath(nativePath);clean(); // Failed power acquisition retains the reservation and cannot train on retry. init(5,1);uint32_t req[20]={6},out[20]={};rejectPowerGet=true; assert(XE::wrapTGLTraining(ctl.data(),req,out)==kIOReturnBusy&&linkCalls==0); assert(XE::wrapTGLTraining(ctl.data(),req,out)==kIOReturnBusy&&linkCalls==0); - XE::wrapTGLControllerStop(ctl.data(),nullptr);used[1]=false;clean(); + XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(!stopCalled&&obj.tglStopRetained&&obj.tglTrainingLeases[5].path&&!obj.tglTrainingLeases[5].powerOwned); + assert(retained[ctl.data()]==1); // Unproven transition remains quarantined, not synthetic cleanup. munmap(image,0x400000); puts("PASS production Display13 lease dispatch with native AGDC Begin/Train/retry/End and pool lookup; mock power/sink/adoption boundaries"); } diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index 4be40a54..b4b478f9 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -28,6 +28,7 @@ #include #include using mach_vm_address_t=uintptr_t; +void IORecursiveLockLock(void*){}void IORecursiveLockUnlock(void*){} struct IOService {void setProperty(const char*,bool) {}}; struct IORegistryEntry { static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} @@ -60,10 +61,11 @@ struct XE { bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; bool display13FramebufferRequested=false,tglPlatformNVRAMReady=false,tglProbeReady=false,tglFramebufferInitReady=false; + void *tglPathPoolLock=(void*)1,*framebufferStartedController=nullptr;bool framebufferStartInProgress=false,tglLeasesStopping=false; bool runtimeOK=true,agdcOK=true,backendOK=true,trainingOK=true,framebufferTrainingHooksReady=false; uintptr_t orgFramebufferStart=0,orgFramebufferInit=0,orgFramebufferComputeLanes=0,framebufferLaneCaller=0; uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; - unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0; + unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0;bool stopDuringStart=false,nativeOK=true; bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_t); bool processFramebufferCommon(KernelPatcher&,size_t,uintptr_t,size_t); bool processFramebufferDonorRuntime(KernelPatcher&,size_t,uintptr_t,size_t){++commonRuntimeCalls;return runtimeOK;} @@ -80,8 +82,8 @@ static void wrapTGLProbePort(){}static void wrapFramebufferInit(){}static void wrapFramebufferComputeLanes(){} }; XE *callbackXE; -bool XE::wrapDisplay13FramebufferStart(IOService*,IOService*){assert(callbackXE->display13FramebufferRequested);++callbackXE->startCalls;return true;} -bool XE::wrapTGLFramebufferStart(IOService*,IOService*){assert(!callbackXE->display13FramebufferRequested);++callbackXE->startCalls;return true;} +bool XE::wrapDisplay13FramebufferStart(IOService*,IOService*){assert(callbackXE->display13FramebufferRequested);assert(!callbackXE->framebufferStartedController);if(callbackXE->stopDuringStart)callbackXE->tglLeasesStopping=true;++callbackXE->startCalls;return callbackXE->nativeOK;} +bool XE::wrapTGLFramebufferStart(IOService*,IOService*){assert(!callbackXE->display13FramebufferRequested);assert(!callbackXE->framebufferStartedController);if(callbackXE->stopDuringStart)callbackXE->tglLeasesStopping=true;++callbackXE->startCalls;return callbackXE->nativeOK;} FUNCTIONS int main(int argc,char **argv){ std::vector image(0x150000); @@ -93,15 +95,17 @@ 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<11;++failure){ + for(bool display13:{false,true})for(unsigned failure=0;failure<13;++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; + 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)); - assert(x.startCalls==(failure==0?1U:0U)); + 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); 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/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index ec6a4478..d0f84c7c 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -1,4 +1,5 @@ #include "kern_alderlake_s.hpp" +#include "kern_framebuffer_donor.hpp" templatevoid tglTransitionRecord(T...){ } // Native boundaries are mocks; LEASE_FUNCTIONS is extracted unchanged from production. #include @@ -50,6 +51,7 @@ struct XE { // LEASE_FIELDS IORecursiveLock *tglPathPoolLock; bool tglTrainingLeaseReady = true; + bool framebufferStartInProgress=false;uint32_t framebufferProtocolCalls=0;void *framebufferStartedController=nullptr; void *tglObservationController; uintptr_t tglPortByDDI, orgTGLPathLookup, orgTGLAllocatePathAny, orgTGLAllocatePathFixed, orgTGLFreePath; uintptr_t tglEnablePG, tglDisablePG, tglDisablePort, tglDisableClocks, orgTGLControllerStop; @@ -244,6 +246,25 @@ void shutdownStatus() { setup();auto &end=reserve(4);mmio.at(0x64400)=0; assert(!XE::tglReleaseTrainingLease(end,false) && end.path && end.powerOwned); mmio.at(0x64400)=0x80;assert(XE::tglReleaseTrainingLease(end,false));clean(); + // Controller stop must not turn the same failed release into pool destruction. + setup();auto &held=reserve(4);bufferIdlePending=true; + XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(!stopCalled&&held.path&&held.powerOwned&&!held.busy&&obj.tglStopRetained&&retained[ctl.data()]==1); + bufferIdlePending=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(stopCalled&&obj.tglStopComplete&&!obj.tglStopRetained);clean(); + // The real unselected Begin wrapper also participates in the drain. + setup();uint32_t noPort[20]={0};nativeBoundary=[&]{ + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(!stopCalled&&obj.tglStopRetained); + }; + assert(!XE::wrapTGLTrainingBegin(ctl.data(),noPort));nativeBoundary={}; + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(stopCalled&&!obj.tglStopRetained);clean(); + // Unselected protocol calls are drained too, not just per-port busy leases. + setup();{ + FramebufferDonor::Call active(obj.framebufferProtocolCalls);assert(active); + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(!stopCalled&&obj.tglStopRetained); + FramebufferDonor::Call rejected(obj.framebufferProtocolCalls);assert(!rejected); + } + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(stopCalled&&!obj.tglStopRetained);clean(); puts("shutdown status: buffer idle missing / pipe enable or active / allones retain resources; no premature clock teardown; explicit retry succeeds on all 3 pipes"); } void trainingRouteRestoration() { From 17a7145ad7badb91955ede32792b91a3eda3f70e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:52:30 +0900 Subject: [PATCH 37/60] Preserve the first Display13 watermark latch rejection details --- WhateverXe/kern_display13_wm.hpp | 15 ++++++++++++++- WhateverXe/kern_xe.cpp | 4 ++++ tests/display13_wm.cpp | 23 ++++++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index caca62b9..74195179 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -289,9 +289,15 @@ 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}; struct Latch { Status status {Status::Unavailable}; uint32_t frameBefore {},frameAfter {},waitUs {},budgetUs {},lastReg {},expected {},observed {}; + // Reuse the values already read by the proof. No diagnostic MMIO reads, + // retries, or relaxed acceptance; preserve the first failed check in IORegistry. + LatchCheck check {LatchCheck::None}; + uint32_t pipeControl {},planeControl {},cursorControl {},checkRegister {},checkValue {}; + uint64_t clockHz {}; }; template bool compare(IO &io,Latch &r,uint32_t reg,uint32_t expected) { if(!io.readable(reg)) {r.status=Status::Unavailable;return false;} @@ -338,6 +344,7 @@ template Latch latch(IO &io,const Snapshot &s) { 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); + r.check=LatchCheck::Controls;r.pipeControl=pipe;r.planeControl=plane;r.cursorControl=cursor; if(pipe==UINT32_MAX || plane==UINT32_MAX || cursor==UINT32_MAX || (pipe&0xC0000000)!=0xC0000000 || !(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;} @@ -346,15 +353,19 @@ template Latch latch(IO &io,const Snapshot &s) { const auto reg=0x70180+bank+0x100*p; if(!io.readable(reg))return r; const auto ctrl=io.read(reg); + r.check=LatchCheck::OtherPlane;r.checkRegister=reg;r.checkValue=ctrl; if(ctrl==UINT32_MAX || (ctrl&Enabled)) {r.status=Status::Conflict;return r;} } for(unsigned d=0;d<3;++d) { const auto reg=Display13FBIRQ::DSBControl(s.pipeIndex,d); if(!io.readable(reg))return r; const auto ctrl=io.read(reg); + r.check=LatchCheck::DSB;r.checkRegister=reg;r.checkValue=ctrl; if(ctrl==UINT32_MAX || (ctrl&(Enabled|1))) {r.status=Status::Conflict;return r;} } - if(io.nativeClockHz()!=s.nativeClockHz) {r.status=Status::Conflict;return r;} + r.check=LatchCheck::Clock;r.clockHz=io.nativeClockHz(); + if(r.clockHz!=s.nativeClockHz) {r.status=Status::Conflict;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) || !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) || @@ -365,12 +376,14 @@ template Latch latch(IO &io,const Snapshot &s) { r.waitUs=static_cast(io.nowUs()-start); if(r.frameAfter==UINT32_MAX) {r.status=Status::Readback;return r;} if(r.frameAfter!=r.frameBefore) { + r.check=LatchCheck::Frame; if(uint32_t(r.frameAfter-r.frameBefore)>=0x80000000U) {r.status=Status::Conflict;return r;} // The frame counter can advance at VBlank before the live-surface // 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. + 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;} diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 039eeca3..a8f0bc67 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9176,6 +9176,10 @@ void XE::wrapDisplay13WMArmUpdate(IOService *fb) { record[11]=static_cast(retired);record[12]=old;record[13]=c.dbuf.unretired; record[14]=c.dbuf.owned;record[15]=c.pipe[pipe].watermarkClockHz; if(result.status!=Display13WM::Status::Complete && result.status!=Display13WM::Status::Unavailable) { + uint64_t proof[16] {1,pipe,static_cast(result.status),static_cast(result.check), + 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); uint64_t cursor[8] {1,pipe};Display13FBDBUFIO readout {c.map}; if(Display13FBIRQ::powered(readout.read(0x45404),pipe)) { cursor[2]=readout.read(0x70080+0x1000*pipe); diff --git a/tests/display13_wm.cpp b/tests/display13_wm.cpp index 80a91d05..faef393e 100644 --- a/tests/display13_wm.cpp +++ b/tests/display13_wm.cpp @@ -349,9 +349,30 @@ 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. + 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 && + controls.pipeControl==0xC0000000 && controls.planeControl==0x04000000 && + !controls.cursorControl && disabledPlane.reads.back()==0x70080+0x1000*pipe && disabledPlane.writes.empty()); + 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 && + peer.checkRegister==0x70280+0x1000*pipe && peer.checkValue==Enabled && + peerPlane.reads.back()==peer.checkRegister && peerPlane.writes.empty()); + LatchIO otherClock(p);otherClock.moveToPipe(pipe);auto beforeClock=otherClock.snapshot(p); + otherClock.clock=384000000; + const auto clockProof=latch(otherClock,beforeClock); + assert(clockProof.status==Status::Conflict && clockProof.check==LatchCheck::Clock && + clockProof.clockHz==384000000 && otherClock.writes.empty()); for(auto control:{1U,0x80000000U,UINT32_MAX}) { LatchIO dsb(p);dsb.moveToPipe(pipe);dsb.regs[0x70B08+0x1000*pipe]=control; - assert(latch(dsb,dsb.snapshot(p)).status==Status::Conflict && !dsb.elapsed); + const auto proof=latch(dsb,dsb.snapshot(p)); + assert(proof.status==Status::Conflict && !dsb.elapsed && proof.check==LatchCheck::DSB && + proof.checkRegister==0x70B08+0x1000*pipe && proof.checkValue==control && + dsb.reads.back()==proof.checkRegister && dsb.writes.empty()); } } // PW2 is a physical prerequisite for pipe B/C even if its own well is on. From 4a5fee738ed8d25fd448e417c8c70f516055502f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:02:30 +0900 Subject: [PATCH 38/60] Defer Display13 live latch proof until a scanout surface is attached --- WhateverXe/kern_display13_wm.hpp | 11 +++++++++-- tests/display13_wm.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index 74195179..abbe3772 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}; +enum class LatchCheck : uint32_t {None,Controls,OtherPlane,DSB,Clock,Registers,Live,Frame,AwaitingScanout}; struct Latch { Status status {Status::Unavailable}; uint32_t frameBefore {},frameAfter {},waitUs {},budgetUs {},lastReg {},expected {},observed {}; @@ -345,8 +345,14 @@ 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. 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; if(pipe==UINT32_MAX || plane==UINT32_MAX || cursor==UINT32_MAX || (pipe&0xC0000000)!=0xC0000000 || - !(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled || + (!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;} // No autonomous DSB writer is admitted by this direct-MMIO proof. for(unsigned p=1;p<5;++p) { @@ -365,6 +371,7 @@ template Latch latch(IO &io,const Snapshot &s) { } r.check=LatchCheck::Clock;r.clockHz=io.nativeClockHz(); 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) || !compare(io,r,0x70190+bank,((s.primary->height-1)<<16)|(s.primary->width-1)) || diff --git a/tests/display13_wm.cpp b/tests/display13_wm.cpp index faef393e..85130b0d 100644 --- a/tests/display13_wm.cpp +++ b/tests/display13_wm.cpp @@ -357,6 +357,34 @@ int main() { assert(controls.status==Status::Conflict && controls.check==LatchCheck::Controls && controls.pipeControl==0xC0000000 && controls.planeControl==0x04000000 && !controls.cursorControl && disabledPlane.reads.back()==0x70080+0x1000*pipe && disabledPlane.writes.empty()); + // 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 + // ordinary frame/live proof. The same state applies on every pipe. + LatchIO preparing(p);preparing.moveToPipe(pipe);const auto bank=0x1000*pipe; + preparing.regs[0x70180+bank]=0;preparing.regs[0x7019C+bank]=0; + preparing.regs[0x70040+bank]=0;preparing.surface=0; + const auto pending=latch(preparing,preparing.snapshot(p)); + assert(pending.status==Status::Unavailable && pending.check==LatchCheck::AwaitingScanout && + preparing.reads.back()==Display13FBIRQ::DSBControl(pipe,2) && preparing.writes.empty() && !preparing.elapsed); + // Pending cannot hide a foreign consumer or a changed shared clock. + preparing.regs[0x70280+bank]=Enabled; + assert(latch(preparing,preparing.snapshot(p)).status==Status::Conflict); + preparing.regs[0x70280+bank]=0;preparing.regs[0x70B08+bank]=1; + assert(latch(preparing,preparing.snapshot(p)).status==Status::Conflict); + preparing.regs[0x70B08+bank]=0;auto intendedClock=preparing.snapshot(p); + preparing.clock=384000000; + assert(latch(preparing,intendedClock).status==Status::Conflict); + preparing.clock=614400000; + preparing.regs[0x70180+bank]=0x84000000; + preparing.regs[0x7019C+bank]=preparing.surface=0x100000; + assert(latch(preparing,preparing.snapshot(p)).status==Status::Complete && preparing.elapsed==2000); + LatchIO badBlank(p);badBlank.moveToPipe(pipe);badBlank.surface=0; + badBlank.regs[0x70180+bank]=UINT32_MAX; + assert(latch(badBlank,badBlank.snapshot(p)).status==Status::Conflict); + 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); 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 d9af9ea749937d5fd94a71d5671729a79046b3be Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 19:59:46 +0900 Subject: [PATCH 39/60] Extend Display13 native DDI activation wait by physical port --- WhateverXe/kern_display13_port_wait.hpp | 34 +++++++++++ WhateverXe/kern_xe.cpp | 14 +++++ tests/display13_port_wait.py | 20 +++++++ tests/display13_port_wait_fixture.cpp | 77 +++++++++++++++++++++++++ tools/test.py | 4 +- 5 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 WhateverXe/kern_display13_port_wait.hpp create mode 100644 tests/display13_port_wait.py create mode 100644 tests/display13_port_wait_fixture.cpp diff --git a/WhateverXe/kern_display13_port_wait.hpp b/WhateverXe/kern_display13_port_wait.hpp new file mode 100644 index 00000000..48287e8d --- /dev/null +++ b/WhateverXe/kern_display13_port_wait.hpp @@ -0,0 +1,34 @@ +// Exact c004fc1a Display13 DDI activation wait. Keep native MMIO and return paths. +#ifndef KERN_DISPLAY13_PORT_WAIT_HPP +#define KERN_DISPLAY13_PORT_WAIT_HPP +#include +#include +#include +namespace Display13PortWait { +constexpr uint32_t WaitOffset=0x5fba0; +constexpr uint8_t WaitBefore[]={ + 0xbb,0x64,0x00,0x00,0x00,0x48,0xff,0x05,0xac,0x2d,0x08,0x00,0x48,0xff,0x05,0xbd,0x2d,0x08,0x00, + 0x41,0x8b,0xb7,0x90,0x05,0x00,0x00,0x4c,0x89,0xff,0xe8,0xcc,0x02,0xfc,0xff,0x84,0xc0,0x79,0x32, + 0xbf,0x05,0x00,0x00,0x00,0xe8,0x00,0x00,0x00,0x00,0xff,0xcb,0x75,0xd1,0x48,0xff,0x05,0x8d,0x2d, + 0x08,0x00,0x48,0x8d,0x3d,0xe6,0xc9,0x04,0x00,0x48,0x8d,0x35,0x33,0x0d,0x04,0x00,0x31,0xc0,0xe8, + 0x00,0x00,0x00,0x00,0x41,0xbe,0xd6,0x02,0x00,0xe0,0xeb,0x0a,0x48,0xff,0x05,0x61,0x2d,0x08,0x00,0x45,0x31,0xf6}; +inline bool waitKnown(const uint8_t *p,size_t n) { + if(!p || n!=sizeof(WaitBefore))return false; + for(size_t i=0;i=44 && i<48) && !(i>=76 && i<80) && p[i]!=WaitBefore[i])return false; + return true; +} + +inline void patch(uint8_t (&out)[sizeof(WaitBefore)],const uint8_t *before) { + memcpy(out,before,sizeof(out)); + // Display13 admitted DDIs 0/1 are Combo; 3..6 are TC (both HDMI and DP). + // Linux v6.12 intel_wait_ddi_buf_active: Combo 1000us, TC 3000us. + // Keep native 5us polling: 200 or 600 iterations. Replace only the + // initial count and two profiling increments; the write return PC stays. + // mov ebx,200; cmp dword [r15+0x120],3; jb +4; mov bx,600 + const uint8_t init[]={0xbb,0xc8,0,0,0,0x41,0x83,0xbf,0x20,0x01,0,0,0x03,0x72,0x04,0x66,0xbb,0x58,0x02}; + memcpy(out,init,sizeof(init)); + // Loop directly to the original read, never reinitialize the count. + out[51]=0xdf; // jne 0x5fbb3 from 0x5fbd2 +} +} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a8f0bc67..9fa74ddf 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -20,6 +20,7 @@ #include "kern_display13_tc_mode.hpp" #include "kern_display13_tc_lightup.hpp" #include "kern_display13_tc_native.hpp" +#include "kern_display13_port_wait.hpp" #include "kern_display13_tc_clock.hpp" #include "kern_display13_tc_dp.hpp" #include "kern_framebuffer_training_native.hpp" @@ -6982,6 +6983,19 @@ bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach memcmp(reinterpret_cast(address+0x60EB9),pcsABI,sizeof(pcsABI)) || memcmp(reinterpret_cast(address+0x61468),lanesABI,sizeof(lanesABI)) || memcmp(reinterpret_cast(address+0x61478),successABI,sizeof(successABI)))return false; + // Correct only the admitted Display13 native post-write wait. Native port + // ownership, DP stagger callsite, failure return and cleanup remain intact. + using namespace Display13PortWait; + const auto wait=reinterpret_cast(address+WaitOffset); + if(!waitKnown(wait,sizeof(WaitBefore)))return false; + uint8_t before[sizeof(WaitBefore)],after[sizeof(WaitBefore)]; + memcpy(before,wait,sizeof(before));Display13PortWait::patch(after,before); + KernelPatcher::LookupPatch waitPatch{currentFramebuffer,before,after,sizeof(after),1}; + patcher.applyLookupPatch(&waitPatch,wait,sizeof(after)+1); + const bool waitReady=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(wait,after,sizeof(after)); + patcher.clearError();if(!waitReady)return false; + uint32_t budgets[]={1,1000,3000}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13PortWaitBudget",budgets,sizeof(budgets)); KernelPatcher::RouteRequest routes[] { {pattern,wrapDisplay13TrainingPattern,orgDisplay13TrainingPattern}, {signal,wrapDisplay13SignalLevels,orgDisplay13SignalLevels}, diff --git a/tests/display13_port_wait.py b/tests/display13_port_wait.py new file mode 100644 index 00000000..6d313033 --- /dev/null +++ b/tests/display13_port_wait.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +"""Replay the original and wait-only patched exact donor; no hardware timing claim.""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +install=s[s.index('bool XE::processDisplay13FramebufferSST('):s.index('uint8_t *XE::display13SSTPort(')] +assert '!display13FramebufferRequested' in install +assert 'waitKnown(wait,sizeof(WaitBefore))' in install +assert 'Display13PortWait::patch(after,before)' in install +assert '!memcmp(wait,after,sizeof(after))' in install +assert '{enable,wrapDisplay13EnablePort,orgDisplay13EnablePort}' in install +print('PASS wait patch guarded inside existing Display13 installer; native wrappers retained') +if len(sys.argv)>1: + donor=Path(sys.argv[1]).resolve() + assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + with tempfile.TemporaryDirectory(prefix='d13-port-wait-') as d: + binary=Path(d)/'replay' + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-I'+str(r/'WhateverXe'),str(r/'tests/display13_port_wait_fixture.cpp'),'-o',str(binary)],check=True) + subprocess.run([str(binary),str(donor)],check=True) diff --git a/tests/display13_port_wait_fixture.cpp b/tests/display13_port_wait_fixture.cpp new file mode 100644 index 00000000..d95a5f1d --- /dev/null +++ b/tests/display13_port_wait_fixture.cpp @@ -0,0 +1,77 @@ +#include "kern_display13_port_wait.hpp" +#include +#include +#include +#include +#include +using namespace Display13PortWait; +alignas(8) uint8_t port[0x700]{}; +uint32_t config[6]{},buffer,baseResult; +unsigned writes,reads,delays,baseCalls,nowUS,ackUS; +bool unreadable; +uintptr_t expectedWritePC; +uint32_t read(void *p,uint32_t reg) { + assert(p==port && reg==0x64000+256*config[0]);++reads; + if(unreadable && writes)return ~0U; + if(writes && nowUS>=ackUS)buffer&=~0x80U; + return buffer; +} +void write(void *p,uint32_t reg,uint32_t value) { + assert(p==port && reg==0x64000+256*config[0]); + assert(reinterpret_cast(__builtin_return_address(0))==expectedWritePC); + ++writes;buffer=value; +} +uint32_t base(void *p) {assert(p==port);++baseCalls;return baseResult;} +void logMock(const char *,...) {} +void delay(unsigned us) {assert(us==5);nowUS+=us;++delays;assert(nowUS<=3000);} +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;} +void redirect(uint8_t *image,unsigned site,unsigned stub) {assert(image[site]==0xe8);int32_t rel=stub-site-5;memcpy(image+site+1,&rel,4);} +templatevoid field(unsigned off,T value){memcpy(port+off,&value,sizeof(value));} +int main(int argc,char **argv) { + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{}); + assert(b.size()>0x5fc55 && waitKnown(reinterpret_cast(b.data())+WaitOffset,sizeof(WaitBefore))); + // Every non-relocation byte is guarded, including loop-back and epilogue branches. + for(unsigned i=0;i=44&&i<48)||(i>=76&&i<80); + assert(waitKnown(reinterpret_cast(changed.data())+WaitOffset,sizeof(WaitBefore))==relocation); + } + unsigned lateAccepted=0; + for(bool patched:{false,true}) { + auto image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0)); + assert(image!=MAP_FAILED && b.size()<0x400000);memcpy(image,b.data(),b.size()); + if(patched) { + uint8_t after[sizeof(WaitBefore)];patch(after,image+WaitOffset); + // Only initialization/profiling bytes and loop-back displacement change. + for(unsigned i=19;i(image+0xc95a0)=reinterpret_cast(base); + jump(image+0x1fe8e,reinterpret_cast(read));jump(image+0x1fea8,reinterpret_cast(write)); + jump(image+0x300000,reinterpret_cast(logMock));jump(image+0x300020,reinterpret_cast(delay)); + for(unsigned site:{0x5fa33U,0x5fac4U,0x5fbebU,0x5fc4eU})redirect(image,site,0x300000); + redirect(image,0x5fbcb,0x300020); + auto native=reinterpret_cast(image+0x5f9f4); + expectedWritePC=reinterpret_cast(image+WaitOffset); + for(unsigned ddi:{0U,1U,3U,4U,5U,6U})for(unsigned mode:{1U,4U}) + for(unsigned ack:{0U,495U,500U,600U,995U,1000U,2000U,2995U,3000U,~0U}) + for(unsigned fault=0;fault<3;++fault) { + config[0]=ddi;config[2]=mode==1?2:1;config[3]=mode==1?0:1; + field(0x548,config+0);field(0x120,ddi);field(0x590,0x64000+256*ddi);field(0x550,mode); + field(0x124,4U);field(0x128,20U);port[0x114]=1; + writes=reads=delays=baseCalls=nowUS=0;buffer=0x80; + baseResult=fault==1?42:0;unreadable=fault==2;ackUS=ack; + const unsigned budget=patched?(ddi>=3?3000:1000):500; + const auto result=native(port); + const auto expected=fault==1?42U:(fault==2||ack>=budget)?0xe00002d6U:0; + assert(result==expected && baseCalls==1 && writes==(fault==1?0U:1U)); + assert(nowUS<=budget && delays==nowUS/5); + if(expected==0xe00002d6U)assert(nowUS==budget && reads==1+budget/5); + if(!fault && ack==0)assert(nowUS==0 && reads==2); // no unconditional delay + if(patched && !fault && ddi>=3 && ack==2000){assert(!result);++lateAccepted;} + } + munmap(image,0x400000); + } + assert(lateAccepted==8); + puts("PASS original vs patched exact enablePort: late ACK, bounded timeout, base failure, all-ones, immediate ACK, preserved write PC and native return; MMIO/time mocked"); +} diff --git a/tools/test.py b/tools/test.py index 017388a6..617ce183 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,10 +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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] -python_tests += ['display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] +python_tests += ['display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] 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 += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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 07cee2b6f75f667380279c11a7014c6210906307 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:25:04 +0900 Subject: [PATCH 40/60] Qualify scaled firmware scanout and park secondary fixed TC boot outputs --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_alderlake_n.hpp | 45 ++++++- WhateverXe/kern_display13_boot.hpp | 122 ++++++++++++++++-- WhateverXe/kern_xe.cpp | 1 + tests/display13_boot.cpp | 185 ++++++++++++++++++++++++++- tests/display13_boot_aux_fixture.cpp | 2 +- 7 files changed, 339 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9d58dd5e..06b8c0c2 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ from `HECI` or `MEI` to `IMEI` is unnecessary. Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support requires matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Fixed TC DP boot handoff is implemented but unverified on hardware; it requires one firmware SST scanout on pipe A, a supported linear RGB surface and the validated stolen-memory mapping. Multiple firmware DP scanouts, Combo DP, MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. +Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support requires matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Fixed TC DP boot handoff has been verified on ADL-H through a DP-to-HDMI adapter. It requires a supported linear RGB surface and validated stolen-memory mapping. Experimental multi-output boot handoff retains pipe A and stops the other firmware outputs before native startup. Firmware upscaling is accepted only with a verified pipe-bound scaler covering the full output; multi-output boot and scaled handoff remain unverified on hardware. MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. diff --git a/README_ja.md b/README_ja.md index 444be608..51403fb1 100644 --- a/README_ja.md +++ b/README_ja.md @@ -92,7 +92,7 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。固定TC DPからの起動引継ぎは実装済み・実機未検証です。firmwareがpipe Aで1つのSST表示を出力し、対応するlinear RGB surfaceと検証可能なstolen memory mappingを使っていることが条件です。複数のfirmware DP表示、Combo DP、MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。固定TC DPからの起動引継ぎは、ADL-HのDP→HDMI変換器を介した接続で確認済みです。対応するlinear RGB surfaceと検証可能なstolen memory mappingが必要です。複数出力の起動引継ぎは試験的実装で、pipe Aを維持し、他のfirmware出力を停止してからnative driverを開始します。firmwareによる拡大表示は、pipeに接続したscalerが出力全体を覆うことを検証できる場合だけ許可します。複数出力起動と拡大表示の引継ぎは実機未検証です。MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 0186bc94..5d257d01 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -176,6 +176,12 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { for (auto offset : transport) add(offset + 0x1000 * pipe); add(0x46140 + 4 * pipe); + // Display11+ has two scalers per powered pipe (Linux display runtime). + // Capture the binding and destination, without touching power/coefficients. + for(unsigned scaler=0;scaler<2;++scaler) { + const auto base=0x68170+0x800*pipe+0x100*scaler; + add(base);add(base+4);add(base+0x10); + } // Display13 DSC/joiner belongs to the corresponding pipe well. add(0x78000 + 0x200 * pipe);add(0x78004 + 0x200 * pipe); } @@ -308,6 +314,42 @@ struct LinearResult { uint32_t pipe {}, writes {}, framesBefore {}, framesAfter {}, restored {}; }; +// Qualify a firmware pipe scaler separately from an unscaled linear GOP. +// PIPESRC is width:height minus one; scaler WIN_SZ is width:height, not +// minus one. Only one pipe-bound scaler covering the complete output is +// accepted. Plane-bound/planar/programmed-factor scaling is not this contract. +inline bool bootGeometry(const DisplayReadout &r,unsigned pipe,const XELPGeometry::Linear &geometry, + uint32_t ht,uint32_t vt) { + if(XELPGeometry::timing(geometry,ht,vt))return true; + if(!geometry.valid || pipe>=3 || ht==UINT32_MAX || vt==UINT32_MAX || + (ht&0xC000C000) || (vt&0xC000C000))return false; + uint32_t src=0; + if(!value(r,0x6001C+0x1000*pipe,src) || + src!=(((geometry.width-1)<<16)|(geometry.height-1)))return false; + const auto width=(ht&0x3FFF)+1,height=(vt&0x3FFF)+1; + if(((ht>>16)&0x3FFF)+1>16)&0x3FFF)+1 bool bootGeometryUnchanged(IO &io,const DisplayReadout &r,unsigned pipe) { + // The accepted scaler stays live while the primary plane changes GGTT mode. + // Any captured source/timing/scaler drift invalidates the handoff. + const uint32_t regs[] {0x60000+0x1000*pipe,0x6000C+0x1000*pipe,0x6001C+0x1000*pipe, + 0x68170+0x800*pipe,0x68174+0x800*pipe,0x68180+0x800*pipe, + 0x68270+0x800*pipe,0x68274+0x800*pipe,0x68280+0x800*pipe}; + for(auto reg:regs) {uint32_t v=0;if(value(r,reg,v) && io.read(reg)!=v)return false;} + return true; +} + // First Display 13 operation: reinitialize an existing linear RGB primary // plane on the explicitly selected Combo, or fixed TC with a live link // certificate. The default remains B for @@ -342,7 +384,7 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d p.geometry=XELPGeometry::linear(p.control,p.stride,p.size,p.position,p.offset); if(!p.geometry.valid || (p.surface&4095) || p.surface!=surfLive || ((trans>>20)&7)!=0 || (pipeConf&0xC0000000)!=0xC0000000 || - !XELPGeometry::timing(p.geometry,htotal,vtotal))return LinearStatus::NotLinearFHD; + !bootGeometry(r,p.pipe,p.geometry,htotal,vtotal))return LinearStatus::NotLinearFHD; // DPT disable is global on Display 13. Exclude all other active planes // and cursors, including planes on powered but inactive pipes. for (unsigned pipe=0;pipe<3;++pipe) { @@ -372,6 +414,7 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout const auto write=[&](uint32_t reg,uint32_t v){io.write(reg,v);++out.writes;io.read(reg);}; bool linkChanged=false; const auto linkStable=[&] { + if(!bootGeometryUnchanged(io,r,p.pipe))linkChanged=true; if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi,0,tc))linkChanged=true; if(ddi>=3 && (!tc || !tc->unchanged(io)))linkChanged=true; return !linkChanged; diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index d1f81379..b3820973 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -1,4 +1,4 @@ -// Combo multi-GOP bootstrap: preserve pipe A, park other GOPs before native +// Multi-GOP bootstrap: preserve pipe A, park other GOPs before native // ownership exists. This does not pretend both GOPs were adopted by the donor. #ifndef KERN_DISPLAY13_BOOT_HPP #define KERN_DISPLAY13_BOOT_HPP @@ -6,6 +6,7 @@ #include "kern_display13_fb_irq.hpp" #include "kern_display13_fb_topology.hpp" #include "kern_xelp_memory_layout.hpp" +#include "kern_display13_tc_clock.hpp" namespace Display13Boot { // Certificate for the linear boot memory. Render qualification is // separate; callers must also qualify the physical owner, BAR and memory layout. @@ -19,7 +20,8 @@ template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout & if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi,protocol,tc)!=AlderLakeN::LinearStatus::Ready || plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false; const auto stable=[&] { - return (ddi<3 || (tc && tc->unchanged(io))) && + return AlderLakeN::bootGeometryUnchanged(io,display,plan.pipe) && + (ddi<3 || (tc && tc->unchanged(io))) && (protocol!=AlderLakeN::GOPProtocol::DP || AlderLakeN::dpSourceUnchanged(io,display,plan.pipe,ddi,0,tc)); }; if(!stable())return false; @@ -53,6 +55,11 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo if(!memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete || !(active&1) || active==1 || (active&~7U) || (active&~boot.poweredPipes))return r; uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0; + Display13TC::Boot tc[3]; + uint32_t power=0,ddiPower=0,auxPower=0,gate=0; + if(!AlderLakeN::value(boot,0x45404,power) || !AlderLakeN::value(boot,0x45454,ddiPower) || + !AlderLakeN::value(boot,0x45444,auxPower) || + !AlderLakeN::value(boot,Display13TC::Clock::Gate,gate))return r; // Every active firmware pipe must be a distinct selected linear GOP. // Pipe identity and physical DDI are independent. No foreign cursor/plane/DSB. for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo uint32_t ctl=0,trans=0,surface=0,live=0; if(!AlderLakeN::value(boot,0x70180+bank,ctl) || !AlderLakeN::value(boot,0x60400+bank,trans))return r; const auto port=Display13FBTopology::bootDDI(w,trans,true); - if(port>=3 || (used&(1U<>24)&7)==2) { - if(!AlderLakeN::dpSource(boot,p,port))return r; + if(port>=7 || (used&(1U<>24)&7)==2; + if(port>=3) { + tc[p]=Display13TC::Boot::capture(io,true,port,p,dp); + if(!tc[p].valid() || tc[p].power!=power || tc[p].ddiPower!=ddiPower)return r; + // A powered AUX controller must be idle before its PHY can be + // returned. An unpowered controller cannot have an active command. + const auto aux=0x64310+0x100*(port-3); + if((auxPower&(1U<<(2*port))) && + (!io.readable(aux) || (io.read(aux)&0x80000000U)))return r; + } + if(dp) { + if(!AlderLakeN::dpSource(boot,p,port,port>=3?&tc[p]:nullptr))return r; dpPipes|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo (conf&0xC0000000)!=0xC0000000 || !AlderLakeN::value(boot,0x7019C+bank,surface) || (surface&4095) || !AlderLakeN::value(boot,0x701AC+bank,live) || live!=surface)return r; const auto geometry=XELPGeometry::linear(ctl,stride,size,position,offset); - if(!XELPGeometry::timing(geometry,ht,vt) || uint64_t(surface)+geometry.bytes>memory.usable())return r; + if(!AlderLakeN::bootGeometry(boot,p,geometry,ht,vt) || uint64_t(surface)+geometry.bytes>memory.usable())return r; const auto clock=0x46140+4*p,buf=0x64000+0x100*port; if(!io.readable(clock) || !io.readable(buf) || !(io.read(buf)&0x80000000) || !io.readable(0x60404+bank) || io.read(0x60404+bank))return r; clocks[p]=io.read(clock); - if(clocks[p]==UINT32_MAX || (clocks[p]>>28)!=port+1)return r; + if(clocks[p]==UINT32_MAX || (clocks[p]>>28)!=port+(port>=3?3:1))return r; } for(unsigned i=0;i=3 && !tc[p].unchanged(io)) { + r.status=Status::Readback;return r; + } + uint32_t parked=0,current=0,stoppingTC=0; const auto peer=[&] { + // These are borrowed firmware requests. Keep them unchanged, including + // the stopped output's requests; native leases own subsequent changes. + if(io.read(0x45404)!=power || io.read(0x45454)!=ddiPower || io.read(0x45444)!=auxPower)return false; for(unsigned p=0;p<3;++p)if((active&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo for(unsigned p=1;p<3;++p)if((active&(1U<=0x60000+bank && reg<0x61000+bank) || - (reg>=0x70000+bank && reg<0x71000+bank) || reg==0x64000+0x100*ports[p] || + (reg>=0x70000+bank && reg<0x71000+bank) || + (reg>=0x68170+0x800*p && reg<=0x68280+0x800*p) || reg==0x64000+0x100*ports[p] || reg==0x46140+4*p)mutableReg=true; } if(reg==0x70040 || reg==0x71040 || reg==0x72040)continue; - if(!mutableReg && io.read(reg)!=pair.value) { - r.lastReg=reg;r.expected=pair.value;r.observed=io.read(reg);return false; + const auto expected=reg==Display13TC::Clock::Gate?gate:pair.value; + if(!mutableReg && io.read(reg)!=expected) { + r.lastReg=reg;r.expected=expected;r.observed=io.read(reg);return false; } } + for(unsigned p=0;p<3;++p)if((active&(1U<=3 && !(stoppingTC&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo } if(!wait(0x70180+bank,0x80000000,0))return r; // Real frame latch, STOP_ACK and buffer idle precede DDB release. - // Shared PLL/PHY/PW and predecessor mapping remain held. + // Keep link resources until consumers stop. The TC PLL/ownership is + // then returned below; shared power and predecessor memory stay held. r.stage=2;write(0x70008+bank,conf&~0x80000000U); if(!wait(0x70008+bank,0xC0000000,0))return r; + // Detach only enabled scalers on the stopped secondary pipe. Keep the + // primary pipe's firmware scaling untouched until native ownership. + for(unsigned scaler=0;scaler<2;++scaler) { + const auto base=0x68170+0x800*p+0x100*scaler; + uint32_t ctl=0; + if(AlderLakeN::value(boot,base+0x10,ctl) && (ctl&0x80000000U)) { + write(base+0x10,0);write(base,0);write(base+4,0); + if(!wait(base+0x10,UINT32_MAX,0) || !wait(base,UINT32_MAX,0) || !wait(base+4,UINT32_MAX,0))return r; + } + } r.stage=3;write(0x60404+bank,0);write(0x60400+bank,trans&~(0x80000000U|(15U<<27)|(7U<<24))); if(!wait(0x60400+bank,0x80000000,0))return r; r.stage=4;write(bufReg,buf&~0x80000000U); @@ -162,6 +207,57 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo r.stage=5;write(0x46140+4*p,clocks[p]&0x0FFFFFFF); if(!wait(0x46140+4*p,UINT32_MAX,clocks[p]&0x0FFFFFFF))return r; if(!Display13FBIRQ::consumersStopped(io,1U<=3) { + // The live GOP certificate authorized this bootstrap transaction. + // Reuse the runtime ownership and PLL operations after consumers + // stop. Leaving the firmware ownership bit set would make the + // later native Mode::connect reject this now-cold connector. + stoppingTC|=1U<(&io) && object==reinterpret_cast(&boot) && + d==boot.port.ddi && powerHeld(); + } + bool quiescent(unsigned d) { + const auto bank=boot.pipe*0x1000; + return d==boot.port.ddi && !(read(0x70008+bank)&0xC0000000U) && + !(read(0x60400+bank)&0x80000000U) && !(read(0x60540+bank)&0x80000000U) && + !(read(0x46140+4*boot.pipe)&0xF0000000U) && + (read(boot.port.buffer)&0x80000080U)==0x80; + } + bool inheritedVerified(uintptr_t owner,uintptr_t object,unsigned d) { + return powerHeld(owner,object,d) && quiescent(d); + } + bool owned(unsigned d){return d==boot.port.ddi && ownership.active;} + bool quiescent(uintptr_t owner,uintptr_t object,unsigned d) { + return powerHeld(owner,object,d) && quiescent(d) && + !(read(boot.port.pll)&0xCC000000U) && !(read(boot.port.clock)&0xF0000000U) && + (read(Display13TC::Clock::Gate)&Display13TC::Clock::gateBit(boot.port.tc)); + } + } access{io,tc[p],r,peer,write}; + const auto owner=reinterpret_cast(&io),object=reinterpret_cast(&tc[p]); + if(access.ownership.acquire(access,owner,object,ports[p],true).status!=Display13TC::Status::Complete || + !Display13TC::Clock::disable(access,tc[p].port) || + access.ownership.release(access,owner,object,ports[p]).status!=Display13TC::Status::Complete) { + if(r.status!=Status::PeerChanged)r.status=Status::Readback;return r; + } + } r.stage=6;write(0x7027C+bank,0);write(0x7037C+bank,0);write(0x7017C+bank,0); if(!wait(0x7027C+bank,UINT32_MAX,0) || !wait(0x7037C+bank,UINT32_MAX,0) || !wait(0x7017C+bank,UINT32_MAX,0))return r; parked|=1U<(base + offset); } void write(uint32_t offset,uint32_t value) { *reinterpret_cast(base + offset)=value; } void delay() { IODelay(100); } + void delayUs(unsigned us) { IODelay(us); } uint64_t readPTE(uint32_t offset) { return *reinterpret_cast(base+offset); } void writePTE(uint32_t offset,uint64_t value) { *reinterpret_cast(base+offset)=value; } uint32_t apertureRead(uint32_t offset) { return *reinterpret_cast(aperture+offset); } diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index e46a8135..05c34ea3 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -1,4 +1,6 @@ #include "kern_display13_boot.hpp" +#include "kern_display13_tc_mode.hpp" +#include "kern_display13_tc_dp.hpp" #include #include #include @@ -12,7 +14,8 @@ struct IO { assert(reg==0x800000+8*pteReads);++pteReads; return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); } - bool noFrames {},noStop {},noIdle {},changePeer {}; + bool noFrames {},noStop {},noIdle {},changePeer {},stuckPLL {}; + uint32_t timeUs {}; uint32_t changeRegister {},failWrite {}; IO() { regs[0x45404]=0xFC0F;regs[0x4438C]=0x1C000700; @@ -32,11 +35,15 @@ struct IO { void write(uint32_t reg,uint32_t value) { writes.push_back({reg,value});if(reg==failWrite)return;regs[reg]=value; if((reg==0x71008 || reg==0x72008) && !(value&0x80000000) && !noStop)regs[reg]&=~0x40000000; - if((reg==0x64000 || reg==0x64100 || reg==0x64200) && !(value&0x80000000) && !noIdle)regs[reg]|=0x80; + if((reg>=0x64000 && reg<=0x64600 && !(reg&255)) && !(value&0x80000000) && !noIdle)regs[reg]|=0x80; + for(unsigned t=0;t<4;++t)if(reg==0x46038+8*t && !stuckPLL) { + regs[reg]=(value&~0x44000000U)|((value&0x80000000U)?0x40000000U:0)|((value&0x08000000U)?0x04000000U:0); + } if(writes.size()==1)regs[0x4500C]=0; if(changePeer && writes.size()==1)regs[0x70180]^=8; if(changeRegister && writes.size()==1)regs[changeRegister]^=1; } + void delayUs(unsigned us) {timeUs+=us;} void delay() {if(!noFrames) {++regs[0x70040];if(regs[0x71008]&0xC0000000)++regs[0x71040];if(regs[0x72008]&0xC0000000)++regs[0x72040];}} }; const uint32_t prop[] {1,7,1,2,3}; @@ -96,8 +103,180 @@ static void prefixCertificates() { } printf("PASS %u Display boot certificates: physical layouts, PTE corruption, no-read rejection\n",cases); } +static Display13OutputWiring multiGOP(IO &io,const unsigned *ports,unsigned count,uint32_t dpMask,bool owned=true) { + Display13OutputWiring w;w.valid=true;w.dp=dpMask; + io.regs[0x42000]=0x8e380000;io.regs[Display13TC::Clock::Gate]=0x01e0ffff; + for(unsigned p=0;p<3;++p) { + const auto b=p*0x1000; + io.regs[0x70008+b]=io.regs[0x70180+b]=io.regs[0x60400+b]=0; + io.regs[0x46140+4*p]=0; + if(p>=count)continue; + const auto d=ports[p];if(!(dpMask&(1U<=3?6:1);} + io.regs[0x70008+b]=0xc0000000;io.regs[0x70180+b]=0x84000008; + // Linear 720p firmware geometry, independent of protocol and DDI. + io.regs[0x70188+b]=80;io.regs[0x70190+b]=0x02cf04ff; + io.regs[0x60000+b]=0x067104ff;io.regs[0x6000c+b]=0x02ed02cf; + io.regs[0x7027c+b]=0x03ff0000; + io.regs[0x60400+b]=0x80030100|((d+1)<<27); + io.regs[0x64000+0x100*d]=0x80000000; + if(dpMask&(1U<=3?3:1))<<28; + if(d>=3) { + Display13TC::Port port;assert(Display13TC::Port::resolve(d,port)); + io.regs[0x45454]|=3U<<(2*d);io.regs[0x45444]|=3U<<(2*d); + io.regs[port.ready]=0xc;io.regs[port.pll]=0xcc000000;io.regs[port.clock]=0x80000000; + io.regs[Display13TC::Clock::Gate]&=~Display13TC::Clock::gateBit(port.tc); + if(owned || !(dpMask&(1U<=3) { + const auto retained=Display13TC::Boot::capture(io,true,c.ports[0],0,(c.dp&(1U<=3) { + const auto d=c.ports[p];Display13TC::Port port;assert(Display13TC::Port::resolve(d,port)); + assert((io.regs[port.buffer]&0x800000c0U)==0x80 && !(io.regs[port.pll]&0xcc000000U)); + assert(!(io.regs[port.clock]&0xf0000000U) && !io.regs[0x46140+4*p]); + assert(io.regs[Display13TC::Clock::Gate]&Display13TC::Clock::gateBit(port.tc)); + Runtime runtime(io,d); + assert(runtime.mode.connect(runtime,1,2,d).status==Display13TC::Status::Complete); + // No firmware-adoption exemption is supplied to the new Mode. + const bool started=(c.dp&(1U<0x68280); + auto after=AlderLakeN::readDisplay(io,true); + auto tc=Display13TC::Boot::capture(io,true,5,0,true); + AlderLakeN::LinearPlan plan; + assert(AlderLakeN::linearPlan(after,plan,5,AlderLakeN::GOPProtocol::DP,&tc)==AlderLakeN::LinearStatus::Ready); + assert(plan.geometry.width==1280 && plan.geometry.height==720); + assert(Display13Boot::reservedPrefix(io,after,true,5,memory,&tc,AlderLakeN::GOPProtocol::DP)); + io.regs[0x68174+scaler*0x100]^=1; + assert(!Display13Boot::reservedPrefix(io,after,true,5,memory,&tc,AlderLakeN::GOPProtocol::DP)); + } + // A timing mismatch alone is never permission to accept scaling. + for(unsigned fault=0;fault<11;++fault) { + IO io;auto w=multiGOP(io,ports,3,1U<<5);setup(io,0,0); + if(fault==0)io.regs[0x68180]=0; + if(fault==1)io.regs[0x68180]|=1U<<25; + if(fault==2)io.regs[0x68180]|=1U<<29; + if(fault==3)io.regs[0x68180]|=1U<<19; + if(fault==4)io.regs[0x68170]=1; + if(fault==5)io.regs[0x68174]^=1; + if(fault==6)io.regs[0x6001C]^=1; + if(fault==7) {io.regs[0x68280]=0x80000000;io.regs[0x68274]=io.regs[0x68174];} + if(fault==8)io.regs[0x60000]=0x400004FF; + if(fault==9)io.regs[0x60000]=0x0100077F; + if(fault==10)io.regs[0x6000C]=0x046401FF; + auto n=Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory); + if(n.status!=Display13Boot::Status::Topology || !io.writes.empty())fprintf(stderr,"fault=%u status=%u writes=%zu\n",fault,unsigned(n.status),io.writes.size()); + assert(n.status==Display13Boot::Status::Topology && io.writes.empty()); + } + IO drift;auto w=multiGOP(drift,ports,3,1U<<5);setup(drift,0,0); + drift.changeRegister=0x68174; + auto n=Display13Boot::normalize(drift,AlderLakeN::readDisplay(drift,true),true,w,memory); + assert(n.status==Display13Boot::Status::PeerChanged && drift.writes.size()==2); + puts("PASS scaled multi-GOP: binding, full destination, primary preservation, secondary detach and drift rejection"); +} int main() { + scaledBoot(); prefixCertificates(); + tcComposition(); IO io;const auto before=io.regs; const auto r=Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,wiring,memory); assert(r.status==Display13Boot::Status::Complete && r.after==1 && r.stage==7 && r.writes==10); @@ -170,7 +349,7 @@ int main() { if(dpMask&(1U< Date: Sun, 25 Oct 2026 20:29:40 +0900 Subject: [PATCH 41/60] Capture fixed TC firmware ports outside active GOP routes --- WhateverXe/kern_xe.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index d910e450..402cf174 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3874,6 +3874,20 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97StolenReservation",reservation,sizeof(reservation)); auto result = AlderLakeN::readDisplay(io, barReady); pci->setProperty("N97Display13Boot", &result, sizeof(result)); + // Bounded pre-native evidence for selected fixed TC ports, including ports + // which firmware owns without routing a transcoder to them. + uint32_t bootTC[1+4*8] {1},bootPower=0; + if(barReady && AlderLakeN::value(result,0x45404,bootPower) && (bootPower&5)==5) { + for(unsigned d=3;d<7;++d)if(board.selected()&(1U<setProperty("WEXDisplay13TCBootPorts",bootTC,sizeof(bootTC)); + } + bool bootstrapReady=true; if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory); @@ -6339,7 +6353,11 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { result.power.writes,result.power.rollbackFailed,result.power.forcedOn,result.power.isolated}; if(!root->getProperty("WEXDisplay13TCFirstPowerError"))root->setProperty("WEXDisplay13TCFirstPowerError",power,sizeof(power)); } - if(!done && !root->getProperty("WEXDisplay13TCModeFailure"))root->setProperty("WEXDisplay13TCModeFailure",record,sizeof(record)); + if(!done && !root->getProperty("WEXDisplay13TCModeFailure")) { + root->setProperty("WEXDisplay13TCModeFailure",record,sizeof(record)); + uint32_t detail[] {1,ddi,result.phy.before,result.phy.after,result.phy.writes,result.phy.elapsedUs}; + root->setProperty("WEXDisplay13TCModeFailureState",detail,sizeof(detail)); + } return done ? kIOReturnSuccess : kIOReturnNotReady; } From 61fc632a279f683f86f2bf2b4ea70b8ba1caafc8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:33:16 +0900 Subject: [PATCH 42/60] Release cold unused fixed TC firmware ownership before native startup --- WhateverXe/kern_display13_boot.hpp | 57 ++++++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 5 +++ tests/display13_boot.cpp | 47 ++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index b3820973..8d756a91 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -46,6 +46,63 @@ 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. +// Linux intel_tc_port_sanitize_mode disconnects unused PHYs. Here admit only +// the already-cold case: disabled/idle buffer, stopped/gated PLL and clocks, +// no transcoder route and no AUX command. Do not stop an unclassified link. +struct UnusedTCResult { + uint32_t version {1};Status status {Status::Unavailable}; + uint32_t cleared {},writes {},ddi {},buffer {},ready {},clock {},pll {},gate {}; +}; +template UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::DisplayReadout &boot, + bool admitted,const Display13HDMIWiring &w) { + UnusedTCResult r; + if(!admitted)return r; + r.status=Status::Topology; + if(boot.status!=AlderLakeN::ReadoutStatus::Complete || boot.activePipes!=1 || + !(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; + const auto stable=[&] { + for(unsigned i=0;i>27)&15)==d+1 || (clock>>28)==d+3)routed=true; + } + if(routed)continue; + Display13TC::Port port;Display13TC::Port::resolve(d,port); + r.ddi=d;r.buffer=io.read(port.buffer); + if(r.buffer==UINT32_MAX){r.status=Status::Readback;return r;} + if(!(r.buffer&64))continue; + 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 || + r.clock==UINT32_MAX || (r.clock&0xF0000000U) || r.pll==UINT32_MAX || (r.pll&0xCC000000U) || + r.gate==UINT32_MAX || !(r.gate&Display13TC::Clock::gateBit(d-3)) || + ((auxPower&(1U<<(2*d))) && (!io.readable(aux) || (io.read(aux)&0x80000000U))))return r; + if(!stable() || io.read(port.buffer)!=r.buffer || io.read(port.ready)!=r.ready || + io.read(port.clock)!=r.clock || io.read(port.pll)!=r.pll) {r.status=Status::PeerChanged;return r;} + io.write(port.buffer,r.buffer&~64U);++r.writes; + if(io.read(port.buffer)!=(r.buffer&~64U) || !stable() || io.read(port.clock)!=r.clock || + io.read(port.pll)!=r.pll) {r.status=Status::Readback;return r;} + r.cleared|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, const Display13HDMIWiring &w,const XELPMemory::Layout &memory) { Result r;r.before=boot.activePipes; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 402cf174..ed1eeda3 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3896,6 +3896,11 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97DualGOPAfter",&result,sizeof(result)); bootstrapReady=parked.status==Display13Boot::Status::Complete; } + if(displayRequested && linearTest && bootstrapReady && Display13FBTopology::multiOutputWiring(board)) { + const auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board); + 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, diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index 05c34ea3..b1452155 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -273,7 +273,54 @@ static void scaledBoot() { assert(n.status==Display13Boot::Status::PeerChanged && drift.writes.size()==2); puts("PASS scaled multi-GOP: binding, full destination, primary preservation, secondary detach and drift rejection"); } +static void unusedTCBoot() { + const unsigned ports[] {5,4}; + const auto setup=[&](IO &io) { + auto w=multiGOP(io,ports,2,1U<<5);w.hdmi|=1U<<3;w.ddc[3]=9; + assert(Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory).status==Display13Boot::Status::Complete); + io.writes.clear();io.regs[0x64300]=0xC0;io.regs[0x161500]=0xC; + io.regs[0x4610C]=io.regs[0x46038]=0;io.regs[Display13TC::Clock::Gate]|=1U<<12; + io.regs[0x45444]|=3U<<6; + return w; + }; + IO io;auto w=setup(io);const auto before=io.regs; + auto r=Display13Boot::releaseUnusedTC(io,AlderLakeN::readDisplay(io,true),true,w); + assert(r.status==Display13Boot::Status::Complete && r.cleared==8 && r.writes==1 && io.regs[0x64300]==0x80); + for(auto x:before)if(x.first!=0x64300)assert(io.regs[x.first]==x.second); + 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); + // Any live/ambiguous resource prevents release of this unclassified port. + for(unsigned fault=0;fault<9;++fault) { + IO t;auto wiring=setup(t); + if(fault==0)t.regs[0x64300]|=0x80000000; + if(fault==1)t.regs[0x64300]&=~0x80U; + 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==6)t.regs[0x64310]=0x80000000; + if(fault==7)t.regs[0x64300]=UINT32_MAX; + if(fault==8)t.regs[0x161500]=UINT32_MAX; + auto r=Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring); + assert(r.status!=Display13Boot::Status::Complete && t.writes.empty()); + } + for(unsigned kind=0;kind<2;++kind) { + IO t;auto wiring=setup(t); + t.regs[kind?0x46148:0x62400]=kind?0x60000000:0x20000000; + auto r=Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring); + assert(r.status==Display13Boot::Status::Complete && r.writes==0 && t.regs[0x64300]==0xC0); + } + IO changed;auto wiring=setup(changed);auto boot=AlderLakeN::readDisplay(changed,true);changed.regs[0x68174]=1; + r=Display13Boot::releaseUnusedTC(changed,boot,true,wiring); + assert(r.status==Display13Boot::Status::PeerChanged && changed.writes.empty()); + IO failed;wiring=setup(failed);failed.failWrite=0x64300; + r=Display13Boot::releaseUnusedTC(failed,AlderLakeN::readDisplay(failed,true),true,wiring); + assert(r.status==Display13Boot::Status::Readback && failed.writes.size()==1); + puts("PASS unused fixed TC firmware ownership: cold-only release, peers, live-resource refusal and readback"); +} int main() { + unusedTCBoot(); scaledBoot(); prefixCertificates(); tcComposition(); From 32db4645d20a83ea9c5a476283a3129b55364727 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:34:09 +0900 Subject: [PATCH 43/60] Pass mutable diagnostic record to IOKit property publication --- WhateverXe/kern_xe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index ed1eeda3..70c2806c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3897,7 +3897,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { bootstrapReady=parked.status==Display13Boot::Status::Complete; } if(displayRequested && linearTest && bootstrapReady && Display13FBTopology::multiOutputWiring(board)) { - const auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board); + auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board); pci->setProperty("WEXDisplay13UnusedTC",&unused,sizeof(unused)); bootstrapReady=unused.status==Display13Boot::Status::Complete; } From 616dc2e2b029537f9625b340b58ee6b5d77df44e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:39:48 +0900 Subject: [PATCH 44/60] Document verified multi-output and scaled firmware boot handoff --- README.md | 2 +- README_ja.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 06b8c0c2..b7614878 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ from `HECI` or `MEI` to `IMEI` is unnecessary. Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support requires matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Fixed TC DP boot handoff has been verified on ADL-H through a DP-to-HDMI adapter. It requires a supported linear RGB surface and validated stolen-memory mapping. Experimental multi-output boot handoff retains pipe A and stops the other firmware outputs before native startup. Firmware upscaling is accepted only with a verified pipe-bound scaler covering the full output; multi-output boot and scaled handoff remain unverified on hardware. MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. +Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support requires matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Fixed TC DP boot handoff has been verified on ADL-H through a DP-to-HDMI adapter. It requires a supported linear RGB surface and validated stolen-memory mapping. Experimental multi-output boot handoff retains pipe A and stops the other firmware outputs before native startup. Firmware upscaling is accepted only with a verified pipe-bound scaler covering the full output. Dual-output scaled boot and three-output boot have been verified on ADL-H with a DP-to-HDMI adapter and fixed HDMI outputs. Cold unused fixed TC ports left owned by firmware are released only after checking that their output, clocks and transactions are inactive. MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. diff --git a/README_ja.md b/README_ja.md index 51403fb1..42831cd7 100644 --- a/README_ja.md +++ b/README_ja.md @@ -92,7 +92,7 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。固定TC DPからの起動引継ぎは、ADL-HのDP→HDMI変換器を介した接続で確認済みです。対応するlinear RGB surfaceと検証可能なstolen memory mappingが必要です。複数出力の起動引継ぎは試験的実装で、pipe Aを維持し、他のfirmware出力を停止してからnative driverを開始します。firmwareによる拡大表示は、pipeに接続したscalerが出力全体を覆うことを検証できる場合だけ許可します。複数出力起動と拡大表示の引継ぎは実機未検証です。MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。固定TC DPからの起動引継ぎは、ADL-HのDP→HDMI変換器を介した接続で確認済みです。対応するlinear RGB surfaceと検証可能なstolen memory mappingが必要です。複数出力の起動引継ぎは試験的実装で、pipe Aを維持し、他のfirmware出力を停止してからnative driverを開始します。firmwareによる拡大表示は、pipeに接続したscalerが出力全体を覆うことを検証できる場合だけ許可します。ADL-HのDP→HDMI変換器と固定HDMI出力で、拡大表示を伴う2画面接続起動と3画面接続起動を確認しています。firmwareが所有権を残した未使用の固定TC端子は、出力・clock・通信が停止済みと確認できる場合だけ所有権を返します。MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 From 9f4808734df7719ad68668fda177d4776c0dd56a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 05:32:07 +0900 Subject: [PATCH 45/60] Preserve Display13 path ownership when resource retirement fails --- WhateverXe/kern_display13_retirement.hpp | 25 ++++ WhateverXe/kern_xe.cpp | 160 +++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 10 +- tests/display13_retirement.py | 47 +++++++ tests/display13_retirement_fixture.cpp | 146 +++++++++++++++++++++ tests/tgl_training_leases_fixture.cpp | 2 + tools/test.py | 4 +- 7 files changed, 378 insertions(+), 16 deletions(-) create mode 100644 WhateverXe/kern_display13_retirement.hpp create mode 100644 tests/display13_retirement.py create mode 100644 tests/display13_retirement_fixture.cpp diff --git a/WhateverXe/kern_display13_retirement.hpp b/WhateverXe/kern_display13_retirement.hpp new file mode 100644 index 00000000..a6bcb17e --- /dev/null +++ b/WhateverXe/kern_display13_retirement.hpp @@ -0,0 +1,25 @@ +// Exact c004fc1a Display13 retirement boundary; native startup stays intact. +#ifndef KERN_DISPLAY13_RETIREMENT_HPP +#define KERN_DISPLAY13_RETIREMENT_HPP +#include +#include +namespace Display13Retirement { +constexpr uint32_t Loop=0x67ebd,Continue=0x67eec,Abort=0x67f85; +constexpr uint8_t Before[]={0x48,0xff,0x5,0x64,0xc6,0x7,0x0,0x83,0xbb,0xe8,0x49,0x0,0x0,0x0,0x74,0x1f,0x48,0xff,0x5,0xf4,0xc5,0x7,0x0,0x48,0xff,0x5,0x25,0xc8,0x7,0x0,0x48,0x8b,0xb3,0xf0,0x49,0x0,0x0,0x48,0x89,0xdf,0xe8,0xc4,0xb,0x0,0x0,0xeb,0xd1}; +// AGDC's void free call now returns status in EAX. Keep its native argument +// validation, call, and epilogue; remove only the unconditional success write. +constexpr uint32_t ReleaseCall=0x6ba3a,ReleaseResult=0x6ba3f; +constexpr uint8_t ReleaseBefore[]={0xe8,0x4f,0xc4,0xff,0xff,0x31,0xc0}; +constexpr uint8_t ReleaseAfter[]={0xe8,0x4f,0xc4,0xff,0xff,0x90,0x90}; +inline void patch(uint8_t (&out)[sizeof(Before)],uintptr_t handler) { + memset(out,0x90,sizeof(out)); + // RBX is the retained FB. Keep native StopTransactions/disableDisplay and + // post-retirement cleanup; refusal exits before scanout/logical cleanup. + const uint8_t code[]={0x48,0x89,0xdf,0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0, + 0x85,0xc0,0x0f,0x85,0,0,0,0,0xe9,0,0,0,0}; + memcpy(out,code,sizeof(code));memcpy(out+5,&handler,8); + const int32_t abortDelta=Abort-(Loop+23),doneDelta=Continue-(Loop+28); + memcpy(out+19,&abortDelta,4);memcpy(out+24,&doneDelta,4); +} +} +#endif diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 70c2806c..8b57fb64 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -21,6 +21,7 @@ #include "kern_display13_tc_lightup.hpp" #include "kern_display13_tc_native.hpp" #include "kern_display13_port_wait.hpp" +#include "kern_display13_retirement.hpp" #include "kern_display13_tc_clock.hpp" #include "kern_display13_tc_dp.hpp" #include "kern_framebuffer_training_native.hpp" @@ -5316,7 +5317,127 @@ bool XE::processDisplay13FramebufferLifecycle(KernelPatcher &patcher,size_t inde !memcmp(reinterpret_cast(address+0x68C38),removeCall,sizeof(removeCall)); KernelPatcher::RouteRequest pathRoute {pathSymbol,wrapAlderLakeNFreePath,orgAlderLakeNFreePath}; const bool pathRouted=disableRouted && pathKnown && patcher.routeMultipleShort(index,&pathRoute,1,address,size); - patcher.clearError();return pathRouted; + patcher.clearError(); + return pathRouted && (!Display13FBTopology::multiOutputWiring(adlnOutputWiring) || + processDisplay13Retirement(patcher,index,address,size)); +} + +// These hooks are installed only for Display13's existing multi-output owner. +// Keep native startup, StopTransactions, disableDisplay and successful cleanup. +bool XE::processDisplay13Retirement(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace Display13Retirement; + const char *freeAll="__ZN21AppleIntelFramebuffer20freeDisplayResourcesEv"; + const char *allocate="__ZN21AppleIntelFramebuffer24allocateDisplayResourcesEv"; + const uint8_t freeEntry[]={0x55,0x48,0x89,0xe5,0x53,0x50}; + const uint8_t allocateEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; + const auto f=patcher.solveSymbol(index,freeAll,address,size);patcher.clearError(); + const auto a=patcher.solveSymbol(index,allocate,address,size);patcher.clearError(); + if(size<=ReleaseCall+sizeof(ReleaseBefore) || f!=address+0x67e8e || a!=address+0x67272 || + memcmp(reinterpret_cast(f),freeEntry,sizeof(freeEntry)) || + memcmp(reinterpret_cast(a),allocateEntry,sizeof(allocateEntry)) || + memcmp(reinterpret_cast(address+Loop),Before,sizeof(Before)) || + memcmp(reinterpret_cast(address+ReleaseCall),ReleaseBefore,sizeof(ReleaseBefore)))return false; + uint8_t after[sizeof(Before)];patch(after,reinterpret_cast(retireDisplay13Paths)); + const struct {uint32_t offset;const uint8_t *before,*after;size_t bytes;} changes[]={ + {Loop,Before,after,sizeof(Before)}, + {ReleaseCall,ReleaseBefore,ReleaseAfter,sizeof(ReleaseBefore)}}; + for(const auto &change:changes) { + KernelPatcher::LookupPatch edit{currentFramebuffer,change.before,change.after,change.bytes,1}; + patcher.applyLookupPatch(&edit,reinterpret_cast(address+change.offset),change.bytes+1); + const bool ok=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+change.offset),change.after,change.bytes); + patcher.clearError();if(!ok)return false; + } + // Short prologues end before the native RIP-relative counters. + KernelPatcher::RouteRequest routes[]={ + {freeAll,wrapDisplay13FreeResources,orgDisplay13FreeResources}, + {allocate,wrapDisplay13AllocateResources,orgDisplay13AllocateResources}}; + const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13RetirementReady",ready); + return ready; +} + +bool XE::display13RetirementHealthy() { + if(!callbackXE->display13FramebufferRequested || + !Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring))return true; + auto &c=display13FBPower;if(!c.lock)return false; + IOLockLock(c.lock);const bool ok=!c.memoryFaulted && !c.dbuf.faulted;IOLockUnlock(c.lock); + return ok; +} + +bool XE::wrapDisplay13AllocateResources(void *fb) { + // Failed retirement retains the native allocated flag/path and WEX owner. + // Do not start another allocation after a partial native memory cleanup. + if(!display13RetirementHealthy())return false; + return FunctionCast(wrapDisplay13AllocateResources,callbackXE->orgDisplay13AllocateResources)(fb); +} + +uint32_t XE::retireDisplay13Paths(void *raw) { + auto fb=static_cast(raw);auto &c=display13FBPower; + const auto fail=[&]() { + if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + return kIOReturnNotReady; + }; + if(!fb || fb->getMetaClass()->getClassSize()<0x4b90 || + Display13FBMemory::field(fb,0x1d0)!=c.controller)return fail(); + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return fail(); + // The free wrapper already holds controller+0x9b8. Acquire FB only after + // native StopTransactions/disableDisplay (whose callbacks also take it). + IORecursiveLockLock(mutex); + uint32_t error=0; + for(unsigned n=0;n<2;++n) { + const auto count=Display13FBMemory::field(fb,0x49e8); + if(!count)break; + auto path=Display13FBMemory::field(fb,0x49f0); + if(count>2 || !path || path->getMetaClass()->getClassSize()<0x3730 || + Display13FBMemory::field(path,0x32b0)!=fb || + Display13FBMemory::field(path,0x3644)==2) {error=fail();break;} + error=releaseDisplay13Path(fb,path); + if(error || Display13FBMemory::field(fb,0x49e8)!=count-1) {error=fail();break;} + } + if(!error && Display13FBMemory::field(fb,0x49e8))error=fail(); + IORecursiveLockUnlock(mutex);return error; +} + +uint32_t XE::wrapDisplay13FreeResources(void *raw) { + auto fb=static_cast(raw);auto &c=display13FBPower; + if(!fb || fb->getMetaClass()->getClassSize()<0x4b90 || !c.controller || + Display13FBMemory::field(fb,0x1d0)!=c.controller)return kIOReturnNotReady; + auto mutex=Display13FBMemory::field(c.controller,0x9b8); + if(!mutex)return kIOReturnNotReady; + IORecursiveLockLock(mutex); + const auto before=Display13FBMemory::field(fb,0x49e8); + reinterpret_cast(callbackXE->orgDisplay13FreeResources)(raw); + const auto count=Display13FBMemory::field(fb,0x49e8); + const auto allocated=Display13FBMemory::field(fb,0x1e7); + const auto result=display13RetirementHealthy() && !count && !allocated ? 0U : uint32_t(kIOReturnNotReady); + if(result && c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + // The native abort epilogue preserves allocated/path count. The memory + // adapter also keeps a sticky fault if suffix cleanup could not finish. + IORecursiveLockUnlock(mutex); + uint32_t record[]={1,Display13FBMemory::field(fb,0x1dc),before,count,allocated,result}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13Retirement",record,sizeof(record)); + return result; +} + +bool XE::retireDisplay13Controller(void *raw) { + auto s=callbackXE; + if(!s->display13FramebufferRequested || !Display13FBTopology::multiOutputWiring(s->adlnOutputWiring))return true; + auto controller=static_cast(raw); + if(!controller || controller!=display13FBPower.controller || controller->getMetaClass()->getClassSize()<0x1c20 || + !s->orgDisplay13FreeResources || !display13RetirementHealthy())return false; + const auto count=Display13FBMemory::field(controller,0xd5c);if(count>3)return false; + for(unsigned i=0;i(controller,0xd60+8*i); + if(!fb)continue; + if(fb->getMetaClass()->getClassSize()<0x4b90 || Display13FBMemory::field(fb,0x1d0)!=raw)return false; + if((Display13FBMemory::field(fb,0x1e7) || Display13FBMemory::field(fb,0x49e8)) && + wrapDisplay13FreeResources(fb))return false; + } + auto &c=display13FBPower;IOLockLock(c.lock);bool clear=!c.memoryFaulted; + for(const auto &pipe:c.pipe)clear=clear && !pipe.memoryFramebuffer && !pipe.publishedScanout; + for(const auto &slot:display13FBSysMem.slot)clear=clear && !slot.buffer; + IOLockUnlock(c.lock);return clear; } bool XE::processDisplay13FramebufferDBUF(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { @@ -8174,10 +8295,10 @@ void XE::wrapAlderLakeNScanoutFree(IOService *fb) { } // A stopped-pipe release owns only its memory/IRQ/DDB consumers. It does not // stop shared CDCLK/engine or destroy the native path/DDI power lease. -void XE::adlnFreeStoppedPipe(IOService *fb) { +bool XE::adlnFreeStoppedPipe(IOService *fb) { auto &c=display13FBPower;auto &irq=display13FBIRQ; - if(!c.lock || !display13FBSysMem.retireLock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; - auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; + 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); 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; @@ -8275,18 +8396,19 @@ void XE::adlnFreeStoppedPipe(IOService *fb) { record[19]=pool.state;record[20]=binding.ddi;record[21]=held;record[22]=c.dbuf.consumers;record[23]=c.dbuf.unretired; IOLockUnlock(c.lock);IORecursiveLockUnlock(display13FBSysMem.retireLock);IORecursiveLockUnlock(mutex); adlnPublishMemory("N97FBStoppedMemoryFree",pipe,record,sizeof(record)); + return freed; } -void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { +uint32_t XE::releaseDisplay13Path(IOService *fb,OSObject *path) { if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { - FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return; + FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return 0; } auto &c=display13FBPower; if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || path->getMetaClass()->getClassSize()<0x3730) { - FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return; + return kIOReturnNotReady; } auto mutex=Display13FBMemory::field(fb,0x4220); - if(!mutex) {FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return;} + if(!mutex) {return kIOReturnNotReady;} fb->retain();path->retain();IORecursiveLockLock(mutex); uint64_t record[16] {1};bool released=false,detached=false;IOService *withdrawn=nullptr; IOLockLock(c.lock);const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); @@ -8296,12 +8418,12 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { const auto countOffset=Display13FBMemory::pathCountOffset(fb); record[4]=countOffset ? Display13FBMemory::field(fb,countOffset) : UINT32_MAX; IOLockUnlock(c.lock); - if(selected)adlnFreeStoppedPipe(fb); + const bool memoryReleased=!selected || adlnFreeStoppedPipe(fb); IOLockLock(c.lock); if(selected) { c.pipe[pipe].destroyingPath=true; bool pinned=false;for(const auto &stored:display13FBSysMem.slot)if(stored.framebuffer==fb && stored.buffer)pinned=true; - released=!c.memoryFaulted && Display13FBMemory::field(fb,0x4288)==0xFF && + released=memoryReleased && !c.memoryFaulted && Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && !pinned && !c.pipe[pipe].publishedScanout; if(released) { Display13FBDBUFIO io {c.map};const auto status=Display13DBUF::detach(io,c.dbuf,pipe); @@ -8309,9 +8431,14 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { } } IOLockUnlock(c.lock); - // Native freeDisplayResources loops until path count decreases. Always - // preserve original path release, including memory-retire failure; backing - // stays pinned/faulted instead of forging path-count progress or spinning. + // The enclosing native loop now handles refusal. Never discard the path + // association while WEX still owns its backing or DDB reservation. + if((pipe<3 && !selected) || (selected && (!released || !detached))) { + 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; + } FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path); IOLockLock(c.lock); record[6]=countOffset ? Display13FBMemory::field(fb,countOffset) : UINT32_MAX; @@ -8332,6 +8459,11 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex); OSSafeReleaseNULL(withdrawn);path->release();fb->release(); adlnPublishMemory("N97FBPathFree",pipe,record,sizeof(record)); + return selected && !removed ? kIOReturnNotReady : 0; +} + +void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { + (void)releaseDisplay13Path(fb,path); } IOMemoryDescriptor *XE::wrapAlderLakeNAperture(IOService *fb,int apertureIndex) { @@ -16751,6 +16883,7 @@ void XE::wrapTGLControllerStop(void *controller, IOService *provider) { IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock); if(!released){blocked(2);return;} } + if(!retireDisplay13Controller(controller)){blocked(3);return;} FunctionCast(wrapTGLControllerStop,s->orgTGLControllerStop)(controller,provider); tglRecordStartOrder(8,controller); __atomic_store_n(&s->tglStopComplete,true,__ATOMIC_RELEASE); @@ -17111,6 +17244,7 @@ void XE::tglObserveConfig(void *controller, void *request, void *output, bool te } bool XE::wrapTGLAllocatePathResources(void *fb, uint32_t portIndex, uint32_t stream) { + if(!display13RetirementHealthy())return false; FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); if(!admission){return false;} SYSLOG("igfx", "TGL330 resources enter ddi=%u stream=%u", portIndex, stream); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 610ff535..dd50bbee 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -303,9 +303,17 @@ class XE { static bool wrapAlderLakeNScanoutAllocate(IOService *framebuffer); static void wrapAlderLakeNScanoutSetup(IOService *framebuffer); static void wrapAlderLakeNScanoutFree(IOService *framebuffer); - static void adlnFreeStoppedPipe(IOService *framebuffer); + static bool adlnFreeStoppedPipe(IOService *framebuffer); static void wrapAlderLakeNFreePath(IOService *framebuffer,OSObject *path); mach_vm_address_t orgAlderLakeNFreePath {}; + bool processDisplay13Retirement(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgDisplay13FreeResources {},orgDisplay13AllocateResources {}; + static uint32_t releaseDisplay13Path(IOService *,OSObject *); + static uint32_t retireDisplay13Paths(void *); + static uint32_t wrapDisplay13FreeResources(void *); + static bool wrapDisplay13AllocateResources(void *); + static bool display13RetirementHealthy(); + static bool retireDisplay13Controller(void *); mach_vm_address_t orgAlderLakeNScanoutFree {}; static IOMemoryDescriptor *wrapAlderLakeNAperture(IOService *framebuffer,int aperture); static void wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop); diff --git a/tests/display13_retirement.py b/tests/display13_retirement.py new file mode 100644 index 00000000..72525538 --- /dev/null +++ b/tests/display13_retirement.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Run the production Display13 path/loop/return adapters with fault injection. + +Optional exact c004fc1a replay preserves free/AGDC control flow. MMIO, memory +allocator and native path disposal are leaves; this does not prove hardware. +""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['uint32_t XE::releaseDisplay13Path(', 'uint32_t XE::retireDisplay13Paths(', + 'uint32_t XE::wrapDisplay13FreeResources(', 'bool XE::display13RetirementHealthy(', + 'bool XE::wrapDisplay13AllocateResources(', 'bool XE::retireDisplay13Controller('] +functions='\n'.join(re.search(re.escape(n)+r'.*?\n\}',s,re.S).group() for n in names) +code=(r/'tests/display13_retirement_fixture.cpp').read_text().replace('// FUNCTIONS',functions) +# Keep the tested adapter connected at the real memory-return boundary and +# at both allocation entrances; declarations alone cannot satisfy this check. +assert 'return freed;' in s[s.index('bool XE::adlnFreeStoppedPipe('):s.index('uint32_t XE::releaseDisplay13Path(')] +assert 'if(!retireDisplay13Controller(controller)){blocked(3);return;}' in s +assert 'if(!display13RetirementHealthy())return false;' in s[s.index('bool XE::wrapTGLAllocatePathResources('):] +install=s[s.index('bool XE::processDisplay13Retirement('):s.index('bool XE::display13RetirementHealthy(')] +assert 'freeAll,wrapDisplay13FreeResources,orgDisplay13FreeResources' in install +assert 'allocate,wrapDisplay13AllocateResources,orgDisplay13AllocateResources' in install +assert 'processDisplay13Retirement(patcher,index,address,size)' in s +hooks='' +if len(sys.argv)>1: + donor=Path(sys.argv[1]).resolve();b=donor.read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + symbols={line.split()[-1]:int(line.split()[0],16) for line in subprocess.check_output(['nm','-n',str(donor)],text=True).splitlines() if len(line.split())==3 and re.fullmatch('[0-9a-fA-F]+',line.split()[0])} + for site,target in [(0x6780b,0x67e8e),(0x6ba3a,0x67e8e)]: + assert b[site]==0xe8 and site+5+int.from_bytes(b[site+1:site+5],'little',signed=True)==target + # Native allocation failure still returns false after cleanup; no new success. + assert b[0x67810:0x67816]==bytes.fromhex('4531f64489f0') + for symbol,fn in { + '__ZN21AppleIntelFramebuffer16StopTransactionsEv':'transactions', + '__ZN21AppleIntelFramebuffer14disableDisplayEb':'disable', + '__ZN21AppleIntelFramebuffer17freeScanoutMemoryEv':'post', + '__ZN21AppleIntelFramebuffer25cleanupLogicalFramebufferEv':'post', + '__ZN21AppleIntelFramebuffer21setPortNumberPropertyEv':'post'}.items(): + hooks+='hook(image+'+hex(symbols[symbol])+',(uintptr_t)'+fn+');\n' + code=code.replace('// NATIVE_HOOKS',hooks) +with tempfile.TemporaryDirectory(prefix='d13-retirement-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + cmd=['clang++','-std=c++17','-O0','-g','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')] + if len(sys.argv)==1:cmd+=['-fsanitize=address,undefined','-fno-sanitize-recover=all'] + subprocess.run(cmd,check=True) + subprocess.run([str(p/'test'),*([str(donor)] if len(sys.argv)>1 else [])],check=True) diff --git a/tests/display13_retirement_fixture.cpp b/tests/display13_retirement_fixture.cpp new file mode 100644 index 00000000..52613ec9 --- /dev/null +++ b/tests/display13_retirement_fixture.cpp @@ -0,0 +1,146 @@ +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_retirement.hpp" +#include +#include +#include +#include +#include +#include +using IORecursiveLock=int;using IOLock=int; +static int controllerMutex,fbMutex,ownerMutex;static unsigned ctlDepth,fbDepth,ownerDepth; +void IORecursiveLockLock(int *m){if(m==&controllerMutex){assert(ctlDepth||!fbDepth);++ctlDepth;}else{assert(ctlDepth);++fbDepth;}} +void IORecursiveLockUnlock(int *m){auto &depth=m==&controllerMutex?ctlDepth:fbDepth;assert(depth);--depth;} +void IOLockLock(int*){assert(!ownerDepth);++ownerDepth;}void IOLockUnlock(int*){assert(ownerDepth);--ownerDepth;} +struct OSObject {void **table;unsigned size,refs;OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} + void retain(){++refs;}void release(){assert(refs);--refs;}}; +using IOService=OSObject; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + templatevoid setProperty(T...) {}}; +#define FunctionCast(f,p) reinterpret_cast(p) +#define OSSafeReleaseNULL(p) do{if(p){p->release();p=nullptr;}}while(0) +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +namespace Display13FBTopology {bool multiOutputWiring(unsigned w){return w!=0;}} +namespace TGLFBBinding {enum class Status {Unavailable,Complete};struct Readout {Status status{};unsigned pipe{},ddi{};};} +namespace Display13WM {enum class Status {Unavailable,Complete};} +namespace Display13DBUF { + enum class Status {Complete,Conflict};struct State {bool faulted{};unsigned selected{},consumers{},unretired{};}; + static bool refuseDetach;static unsigned detachCalls; + templateStatus detach(IO&,State&,unsigned){++detachCalls;return refuseDetach?Status::Conflict:Status::Complete;} +} +struct Pipe {IOService *memoryFramebuffer{};uintptr_t publishedScanout{};bool destroyingPath{}; + int powerPath{},memory{},scanout{};uint64_t watermarkClockHz{};unsigned wmPending{};}; +struct Power {int *lock{};IOService *controller{};void *map{};bool memoryFaulted{}; + struct Bridge {unsigned forgotten{};void forgetBootPath(uintptr_t){++forgotten;}}bridge; + Pipe pipe[3];Display13DBUF::State dbuf;}display13FBPower; +struct {struct Slot{IOService *framebuffer{};void *buffer{};}slot[3];}display13FBSysMem; +struct {unsigned admittedPipes{};}display13FBIRQ; +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{}; + uintptr_t orgAlderLakeNFreePath{},orgDisplay13FreeResources{},orgDisplay13AllocateResources{}; + static bool adlnFreeStoppedPipe(IOService*); + static void wrapAlderLakeNFreePath(IOService*,OSObject*); + static uint32_t releaseDisplay13Path(IOService*,OSObject*),retireDisplay13Paths(void*),wrapDisplay13FreeResources(void*); + static bool display13RetirementHealthy(),wrapDisplay13AllocateResources(void*),retireDisplay13Controller(void*); +}state,*callbackXE=&state; +templatevoid put(void*p,unsigned off,T value){memcpy(static_cast(p)+off,&value,sizeof(value));} +TGLFBBinding::Readout adlnInspectPathBinding(IOService*f,OSObject*p,void*,unsigned){ + const auto pipe=Display13FBMemory::boundPipe(display13FBPower.pipe,f); + if(!p||Display13FBMemory::field(p,0x32b0)!=f)return {}; + return {TGLFBBinding::Status::Complete,pipe,3}; +} +// FUNCTIONS +alignas(16) static uint8_t ctl[0x1c20],fb[0x4b90],peer[0x4b90],path[0x3730]; +static unsigned failure,memoryCalls,pathCalls,postCalls,transactionStops,allocCalls; +static uintptr_t backing=0x1234000; +static unsigned (*agdc)(void*,void*); +static void *frame=fb; +bool XE::adlnFreeStoppedPipe(IOService*f){ + // Substitute the native memory/IRQ leaf, not releaseDisplay13Path. Production + // code must check its return AND actual slot/backing state before disposal. + ++memoryCalls;if(failure==1)return false; + if(failure==2)return true; // false success: outstanding backing must reject it + put(f,0x4288,uint8_t(0xff));put(f,0x4348,uintptr_t(0)); + display13FBPower.pipe[0].publishedScanout=0;return true; +} +void nativePath(IOService*f,OSObject*p){ + assert(ctlDepth&&fbDepth&&!ownerDepth);++pathCalls; + assert(!Display13FBMemory::field(f,0x4348)); + if(failure==4)return; // no progress must terminate the enclosing native loop + 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)); +} +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;} +void zero(void*p,size_t n){memset(p,0,n);} +void property(void*,const char*,const void*,unsigned){++postCalls;} +void syntheticFree(void*f){transactions(f);disable(f,false);if(XE::retireDisplay13Paths(f))return; + post(f);post(f);post(f);post(f);put(f,0x1e7,uint8_t(0));} +unsigned syntheticAGDC(void*,void*){return XE::wrapDisplay13FreeResources(frame);} +bool allocate(void*){++allocCalls;return true;} +void hook(uint8_t*p,uintptr_t target){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&target,8);p[10]=0xff;p[11]=0xe0;} +void call(uint8_t*m,unsigned at,unsigned target){assert(m[at]==0xe8);const int32_t d=target-(at+5);memcpy(m+at+1,&d,4);} +void setup(uintptr_t native){ + memset(ctl,0,sizeof(ctl));memset(fb,0,sizeof(fb));memset(peer,0,sizeof(peer));memset(path,0,sizeof(path)); + static void *table[82]{};table[0x288/8]=(void*)property; + for(auto p:{ctl,fb,peer,path}){auto o=reinterpret_cast(p);o->table=table;o->size=p==ctl?sizeof(ctl):p==path?sizeof(path):sizeof(fb);o->refs=1;} + put(ctl,0x9b8,&controllerMutex);put(ctl,0xd5c,1U);put(ctl,0xd60,frame); + put(fb,0x1d0,static_cast(ctl));put(fb,0x4220,&fbMutex);put(fb,0x3fd8,int32_t(-1)); + put(fb,0x1e7,uint8_t(1));put(fb,0x49e8,failure==6?3U:1U);put(fb,0x49f0,static_cast(path)); + put(fb,0x4288,uint8_t(0xff));put(fb,0x4348,backing); + put(path,0x32b0,frame);put(path,0x3648,static_cast(peer));put(path,0x3644,2U); + display13FBPower={};auto &c=display13FBPower;c.lock=&ownerMutex;c.controller=reinterpret_cast(ctl); + c.pipe[0].memoryFramebuffer=reinterpret_cast(fb);c.pipe[0].memoryFramebuffer->retain();c.pipe[0].publishedScanout=backing; + c.pipe[1].memoryFramebuffer=reinterpret_cast(peer);c.pipe[1].publishedScanout=0x5678000;c.pipe[1].wmPending=7; + display13FBSysMem={};display13FBIRQ.admittedPipes=3; + Display13DBUF::refuseDetach=failure==3;Display13DBUF::detachCalls=0; + state={};state.orgAlderLakeNFreePath=(uintptr_t)nativePath;state.orgDisplay13FreeResources=native; + state.orgDisplay13AllocateResources=(uintptr_t)allocate; + memoryCalls=pathCalls=postCalls=transactionStops=allocCalls=0; +} +int main(int argc,char**argv){ + uint8_t *image=nullptr;size_t bytes=0;uintptr_t native=(uintptr_t)syntheticFree;agdc=syntheticAGDC; + if(argc==2){ + std::ifstream file(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(file)),{}); + const auto cave=unsigned((b.size()+4095)&~4095ULL);bytes=cave+4096; + image=(uint8_t*)mmap(nullptr,bytes,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);assert(image!=MAP_FAILED);memcpy(image,b.data(),b.size()); + using namespace Display13Retirement; + assert(!memcmp(image+Loop,Before,sizeof(Before)));assert(!memcmp(image+ReleaseCall,ReleaseBefore,sizeof(ReleaseBefore))); + uint8_t patched[sizeof(Before)];patch(patched,(uintptr_t)XE::retireDisplay13Paths);memcpy(image+Loop,patched,sizeof(patched)); + memcpy(image+ReleaseCall,ReleaseAfter,sizeof(ReleaseAfter)); + // NATIVE_HOOKS + hook(image+cave,(uintptr_t)XE::wrapDisplay13FreeResources);call(image,ReleaseCall,cave); + hook(image+cave+32,(uintptr_t)zero);call(image,0x67f5e,cave+32); + native=(uintptr_t)(image+0x67e8e);agdc=(decltype(agdc))(image+0x6ba08); + } + for(failure=0;failure<7;++failure){ + setup(native);const auto peerBefore=display13FBPower.pipe[1];unsigned handle=1; + auto result=agdc(ctl,&handle);assert(result==(failure?kIOReturnNotReady:0U)); + assert(!ctlDepth&&!fbDepth&&!ownerDepth&&transactionStops==1); + assert(display13FBPower.pipe[1].memoryFramebuffer==peerBefore.memoryFramebuffer&&display13FBPower.pipe[1].publishedScanout==peerBefore.publishedScanout&&display13FBPower.pipe[1].wmPending==7); + if(failure){ + assert(!postCalls&&Display13FBMemory::field(fb,0x49e8)==(failure==6?3U:1U)); + assert(Display13FBMemory::field(path,0x32b0)==fb&&Display13FBMemory::field(path,0x3648)==peer); + assert(!XE::wrapDisplay13AllocateResources(fb)&&!allocCalls); + assert(!XE::retireDisplay13Controller(ctl)); + if(failure<=2)assert(Display13FBMemory::field(fb,0x4348)==backing&&!pathCalls); + if(failure==3)assert(!Display13FBMemory::field(fb,0x4348)&&!pathCalls); // partial backing release + }else{ + assert(postCalls==4&&pathCalls==1&&!Display13FBMemory::field(fb,0x49e8)); + assert(!display13FBPower.pipe[0].memoryFramebuffer&&!(display13FBIRQ.admittedPipes&1)); + assert(XE::wrapDisplay13AllocateResources(fb)&&allocCalls==1); + } + } + failure=0;setup(native); + // Unretired peer ownership prevents pool destruction even after this path + // 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); + setup(native);state.display13FramebufferRequested=false;display13FBPower.memoryFaulted=true; + assert(XE::display13RetirementHealthy()&&XE::retireDisplay13Controller(nullptr)); // other backends unchanged + if(image)munmap(image,bytes); + puts("PASS D13 production path/loop/return: memory refusal, false memory success, DDB refusal after partial release, no progress, active path, excess count, AGDC error, allocation/pool blocking, peer preservation and success"); +} diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index d0f84c7c..5f90829b 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -46,6 +46,8 @@ template void tglObserveResource(T...) {} void tglAUXPublish() {} void tglDPCDPublish() {} struct XE { + static bool display13RetirementHealthy(){return true;} + static bool retireDisplay13Controller(void*){return true;} bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // TC_WIRING_FIELDS // LEASE_FIELDS diff --git a/tools/test.py b/tools/test.py index 617ce183..134f0b89 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,10 +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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] -python_tests += ['display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] +python_tests += ['display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] 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 += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_retirement','display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.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 614abec7d5951630eefca7491dc4017370fc0781 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 06:27:50 +0900 Subject: [PATCH 46/60] Select external link waits and TC preparation by display and PHY contracts --- WhateverXe/kern_xe.cpp | 123 ++++++++++-------- WhateverXe/kern_xe.hpp | 4 + WhateverXe/kern_xelp_link_requirements.hpp | 30 +++++ ..._port_wait.hpp => kern_xelp_port_wait.hpp} | 26 ++-- tests/display13_port_wait.py | 37 +++--- tests/display13_port_wait_fixture.cpp | 22 ++-- tests/display13_tc_preparation.py | 16 +++ tests/display13_tc_preparation_fixture.cpp | 98 ++++++++++++++ tests/tgl_mode_native_fixture.cpp | 3 + tests/tgl_mode_serial_fixture.cpp | 3 + tests/tgl_tx_wrappers_fixture.cpp | 4 +- tests/xelp_port_wait_harness.hpp | 71 ++++++++++ tools/test.py | 4 +- 13 files changed, 344 insertions(+), 97 deletions(-) create mode 100644 WhateverXe/kern_xelp_link_requirements.hpp rename WhateverXe/{kern_display13_port_wait.hpp => kern_xelp_port_wait.hpp} (53%) create mode 100644 tests/display13_tc_preparation.py create mode 100644 tests/display13_tc_preparation_fixture.cpp create mode 100644 tests/xelp_port_wait_harness.hpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 8b57fb64..d139ed5b 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -20,7 +20,8 @@ #include "kern_display13_tc_mode.hpp" #include "kern_display13_tc_lightup.hpp" #include "kern_display13_tc_native.hpp" -#include "kern_display13_port_wait.hpp" +#include "kern_xelp_port_wait.hpp" +#include "kern_xelp_link_requirements.hpp" #include "kern_display13_retirement.hpp" #include "kern_display13_tc_clock.hpp" #include "kern_display13_tc_dp.hpp" @@ -6418,9 +6419,11 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { const auto tc=ddi-3;auto &mode=c.tcMode[tc]; if(!c.tcObject[tc]){c.tcObject[tc]=port;port->retain();} struct IO : Display13FBTCIO { - uint32_t (*base)(OSObject *,uint32_t);bool firmware; - IO(OSObject *p,unsigned t,uint32_t (*f)(OSObject *,uint32_t),bool inherited):Display13FBTCIO(p,t),base(f),firmware(inherited) {} - bool admissionReady(){return base && admitted(tc);} + using Preparation=XELP::LinkRequirements::Preparation; + uint32_t (*base)(OSObject *,uint32_t);Preparation preparation; + IO(OSObject *p,unsigned t,uint32_t (*f)(OSObject *,uint32_t),Preparation selected):Display13FBTCIO(p,t),base(f),preparation(selected) {} + bool admissionReady(){return base && admitted(tc) && + (preparation==Preparation::ColdTC13 || preparation==Preparation::FirmwareTC13);} uint32_t nativeMode(){return Display13FBMemory::field(object,0x550);} uint32_t nativeSetMode(uint32_t value) { // mode0 invokes the native reset callback. Retain this exact port @@ -6439,7 +6442,7 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { port==reinterpret_cast(object) && d==tc+3 && Display13FBTCIO::accessHeld(); } bool inheritedVerified(uintptr_t controller,uintptr_t port,unsigned d) { - return firmware && controller==reinterpret_cast(display13FBPower.controller) && + return preparation==Preparation::FirmwareTC13 && controller==reinterpret_cast(display13FBPower.controller) && port==reinterpret_cast(object) && d==tc+3; } bool quiescent(uintptr_t controller,uintptr_t port,unsigned d) { @@ -6464,7 +6467,8 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { const auto source=AlderLakeN::readDisplay(bootIO,true); inherited=source.activePipes==1 && AlderLakeN::dpSourceUnchanged(bootIO,source,0,ddi,0,&boot); } - IO io{port,tc,reinterpret_cast(self->display13TCBaseMode),inherited}; + const auto preparation=framebufferLinkRequirements(port,4).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 bool done=result.status==Display13TC::Status::Complete; @@ -7128,19 +7132,6 @@ bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach memcmp(reinterpret_cast(address+0x60EB9),pcsABI,sizeof(pcsABI)) || memcmp(reinterpret_cast(address+0x61468),lanesABI,sizeof(lanesABI)) || memcmp(reinterpret_cast(address+0x61478),successABI,sizeof(successABI)))return false; - // Correct only the admitted Display13 native post-write wait. Native port - // ownership, DP stagger callsite, failure return and cleanup remain intact. - using namespace Display13PortWait; - const auto wait=reinterpret_cast(address+WaitOffset); - if(!waitKnown(wait,sizeof(WaitBefore)))return false; - uint8_t before[sizeof(WaitBefore)],after[sizeof(WaitBefore)]; - memcpy(before,wait,sizeof(before));Display13PortWait::patch(after,before); - KernelPatcher::LookupPatch waitPatch{currentFramebuffer,before,after,sizeof(after),1}; - patcher.applyLookupPatch(&waitPatch,wait,sizeof(after)+1); - const bool waitReady=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(wait,after,sizeof(after)); - patcher.clearError();if(!waitReady)return false; - uint32_t budgets[]={1,1000,3000}; - IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13PortWaitBudget",budgets,sizeof(budgets)); KernelPatcher::RouteRequest routes[] { {pattern,wrapDisplay13TrainingPattern,orgDisplay13TrainingPattern}, {signal,wrapDisplay13SignalLevels,orgDisplay13SignalLevels}, @@ -11424,6 +11415,59 @@ bool XE::wrapFramebufferComputeLanes(void *argument,const void *timing,uint32_t return native || corrected; } +XELP::LinkRequirements XE::framebufferLinkRequirements(void *port,uint32_t mode) { + auto self=callbackXE;auto p=static_cast(port); + XELP::LinkRequirements req; + if(!p)return req; + auto config=Display13FBMemory::field(p,0x548); + if(!config || config[0]>=9 || p[0x117] || + Display13FBMemory::field(p,0x120)!=config[0] || + Display13FBMemory::field(p,0x590)!=0x64000+0x100*config[0])return req; + const auto ddi=config[0]; + using R=XELP::LinkRequirements; + if(self->display13FramebufferRequested) { + if(!Display13FBTopology::selected(self->adlnPlatform,config,self->adlnOutputWiring) || + mode!=(ddi>=3?4U:1U))return req; + req={self->xeProfile.display,ddi>=3?R::PHY::TC:R::PHY::Combo, + (self->adlnOutputWiring.dp&(1U<(self->tglPlatformCopy+0x20+24*ddi); + if(!bound)return req; + if(mode==1 && self->tglHDMIEligible(config)) + req={self->xeProfile.display,R::PHY::Combo,R::Protocol::HDMI}; + else { + const auto route=self->tglSSTRoutes().external(ddi,mode); + using T=XELP::Display12SSTRoute::Transport; + if(config[2]==1 && (route.transport==T::Combo || route.transport==T::DekelLegacy)) + req={self->xeProfile.display,route.transport==T::Combo?R::PHY::Combo:R::PHY::TC,R::Protocol::FixedDP}; + } + } + return req; +} + +uint32_t XE::framebufferPortWaitPolls(void *port) { + if(!port)return 100; + return framebufferLinkRequirements(port,Display13FBMemory::field(port,0x550)).activePolls(); +} + +bool XE::processFramebufferPortWait(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace XELPPortWait; + const char *symbol="__ZN17AppleIntelPortHAL10enablePortEv"; + const auto enable=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(size(address+WaitOffset); + if(!waitKnown(wait,sizeof(WaitBefore)))return false; + uint8_t before[sizeof(WaitBefore)],after[sizeof(WaitBefore)]; + memcpy(before,wait,sizeof(before));XELPPortWait::patch(after,before,reinterpret_cast(framebufferPortWaitPolls)); + KernelPatcher::LookupPatch change{currentFramebuffer,before,after,sizeof(after),1}; + patcher.applyLookupPatch(&change,wait,sizeof(after)+1); + const bool ready=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(wait,after,sizeof(after)); + patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXExternalLinkWaitReady",ready); + return ready; +} + bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(framebufferDonorRuntimeReady)return true; bool hdcpReady=false,meiReady=false; @@ -11498,7 +11542,8 @@ bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach IORegistryEntry::getRegistryRoot()->setProperty("TGLMEITXFIFOReadbackR364", known); patcher.clearError(); } - framebufferDonorRuntimeReady=hdcpReady && tglPanelPowerPropertyReady && meiReady; + framebufferDonorRuntimeReady=hdcpReady && tglPanelPowerPropertyReady && meiReady && + processFramebufferPortWait(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferDonorRuntimeReady",framebufferDonorRuntimeReady); return framebufferDonorRuntimeReady; } @@ -15411,8 +15456,12 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { if(transitionLock)IORecursiveLockUnlock(transitionLock); return kIOReturnNotReady; } + // Select fixed-link requirements after the existing native admission. Native + // owns cold/GOP mode acquisition on Display12, with our mapped FIA accesses. + using LR=XELP::LinkRequirements; + const LR fixed{self->xeProfile.display,self->tglLegacyTCEligible(traceDDI)?LR::PHY::TC:LR::PHY::None,LR::Protocol::FixedDP}; const bool fiaEligible=dpAltTransaction || ((mode==0 || mode==4) && traceDDI<9 && - (previousMode==0 || previousMode==4) && self->tglLegacyTCEligible(traceDDI)); + (previousMode==0 || previousMode==4) && fixed.preparation(false)==LR::Preparation::NativeTC12); TGLNativeFiaScope fiaScope(self->tglNativeFiaContext,reinterpret_cast(current_thread()),fiaEligible?port:nullptr, traceDDI,transitionLock && self->tglNativeFiaReady); auto result = FunctionCast(wrapTGLSetPortMode, callbackXE->orgTGLSetPortMode)(port, mode); @@ -18755,37 +18804,7 @@ uint32_t XE::wrapTGLEnablePort(void *port) { const auto original = FunctionCast(wrapTGLEnablePort, callbackXE->orgTGLEnablePort)(port); if(original)tglComboAUXReference(port,false); // only releases if buffer stayed off auto r = original; - // The donor only waits 100 * 5us. Linux allows 3ms for TGL TC PHYs. - // Observe another bounded 3ms and accept only a real enabled/non-idle state. - auto bytes = static_cast(port); - TGLTypeCPort mapping; - if (callbackXE->tglLegacyTCEligible(id) && TGLTypeCPort::resolve(id, mapping) && - original == 0xE00002D6 && - *reinterpret_cast(bytes + 0x550) == 4 && - *reinterpret_cast(bytes + 0x590) == mapping.ddiBuffer) { - auto access = *reinterpret_cast(bytes + 0x578); - auto base = access ? *reinterpret_cast(access + 0x50) : nullptr; - if (base && *reinterpret_cast(access + 0x60) >= mapping.ddiBuffer + 4) { - auto reg = reinterpret_cast(base + mapping.ddiBuffer); - uint32_t before = *reg, after = before, delay = 0; - while (delay < 3000 && after != 0xFFFFFFFFU && (after & 0x80000080U) == 0x80000080U) { - IODelay(100); - delay += 100; - after = *reg; - } - if ((after & 0x80000080U) == 0x80000000U) r = 0; - static uint32_t count {}; - const auto seq = __sync_add_and_fetch(&count, 1); - uint32_t record[] = {seq, original, r, delay, before, after, mapping.ddi}; - char key[48]; - snprintf(key, sizeof(key), "TGLEnableLate%uLast%u", mapping.ddi, seq % 64); - IORegistryEntry::getRegistryRoot()->setProperty(key, record, sizeof(record)); - if (seq <= 16) { - snprintf(key, sizeof(key), "TGLEnableLate%uFirst%u", mapping.ddi, seq); - IORegistryEntry::getRegistryRoot()->setProperty(key, record, sizeof(record)); - } - } - } + // Activation waiting is installed once in the native donor loop. tglSnapshotTransmitter(port, true, r); tglTrace(21, id, true, original, 0, r, 0); return r; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index dd50bbee..a67d8d63 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -21,6 +21,7 @@ #include "kern_xelp_panel_pwm.hpp" #include "kern_xelp_donor.hpp" #include "kern_xelp_display_contract.hpp" +#include "kern_xelp_link_requirements.hpp" #include "kern_dvmt.hpp" #include @@ -610,6 +611,9 @@ class XE { bool framebufferHDMIEligible(const uint32_t *) const; static void observeFramebufferUserClient(void *,uint32_t,void **,uint32_t); bool framebufferDonorRuntimeReady {}; + static XELP::LinkRequirements framebufferLinkRequirements(void *,uint32_t); + static uint32_t framebufferPortWaitPolls(void *); + bool processFramebufferPortWait(KernelPatcher &,size_t,mach_vm_address_t,size_t); bool processFramebufferDonorRuntime(KernelPatcher &,size_t,mach_vm_address_t,size_t); mach_vm_address_t orgTGLHDCP2Init {}; static bool wrapTGLHDCP2Init(void *hdcp, void *path); diff --git a/WhateverXe/kern_xelp_link_requirements.hpp b/WhateverXe/kern_xelp_link_requirements.hpp new file mode 100644 index 00000000..8ab49c6e --- /dev/null +++ b/WhateverXe/kern_xelp_link_requirements.hpp @@ -0,0 +1,30 @@ +// External HDMI/fixed-DP requirements, after platform/wiring admission. +// Coordinate mapping and generation-specific power/ownership stay in adapters. +#ifndef KERN_XELP_LINK_REQUIREMENTS_HPP +#define KERN_XELP_LINK_REQUIREMENTS_HPP +#include "kern_xelp_devices.hpp" +namespace XELP { +struct LinkRequirements { + enum class PHY {None,Combo,TC}; + enum class Protocol {None,HDMI,FixedDP}; + Display display {Display::Unknown};PHY phy {PHY::None};Protocol protocol {Protocol::None}; + bool valid() const {return (display==Display::XeD || display==Display::XeLPD) && + phy!=PHY::None && protocol!=Protocol::None;} + // Linux v6.12 intel_wait_ddi_buf_active: Display12/13 share these limits. + // The native loop reads before each 5us delay; the deadline itself times out. + uint32_t activeWaitUS() const {return valid() ? (phy==PHY::TC?3000:1000) : 500;} + enum class Preparation {None,NativeCombo,NativeTC12,ColdTC13,FirmwareTC13}; + // The evidence is supplied by a powered, identity-bound host readout. A mode + // number, HPD bit or an ownership bit alone is never firmware evidence. + Preparation preparation(bool verifiedFirmware) const { + if(!valid())return Preparation::None; + if(phy==PHY::Combo)return Preparation::NativeCombo; + // Display12 native mode owns AUX -> FIA. Display13 must acquire access -> + // ownership -> AUX before base mode. Their ordering must stay distinct. + if(display==Display::XeD)return Preparation::NativeTC12; + return verifiedFirmware?Preparation::FirmwareTC13:Preparation::ColdTC13; + } + uint32_t activePolls() const {return activeWaitUS()/5;} +}; +} +#endif diff --git a/WhateverXe/kern_display13_port_wait.hpp b/WhateverXe/kern_xelp_port_wait.hpp similarity index 53% rename from WhateverXe/kern_display13_port_wait.hpp rename to WhateverXe/kern_xelp_port_wait.hpp index 48287e8d..0612cb16 100644 --- a/WhateverXe/kern_display13_port_wait.hpp +++ b/WhateverXe/kern_xelp_port_wait.hpp @@ -1,10 +1,10 @@ -// Exact c004fc1a Display13 DDI activation wait. Keep native MMIO and return paths. -#ifndef KERN_DISPLAY13_PORT_WAIT_HPP -#define KERN_DISPLAY13_PORT_WAIT_HPP +// Exact c004fc1a DDI activation wait for admitted Display12/13 external links. Keep native MMIO and return paths. +#ifndef KERN_XELP_PORT_WAIT_HPP +#define KERN_XELP_PORT_WAIT_HPP #include #include #include -namespace Display13PortWait { +namespace XELPPortWait { constexpr uint32_t WaitOffset=0x5fba0; constexpr uint8_t WaitBefore[]={ 0xbb,0x64,0x00,0x00,0x00,0x48,0xff,0x05,0xac,0x2d,0x08,0x00,0x48,0xff,0x05,0xbd,0x2d,0x08,0x00, @@ -18,17 +18,15 @@ inline bool waitKnown(const uint8_t *p,size_t n) { return true; } -inline void patch(uint8_t (&out)[sizeof(WaitBefore)],const uint8_t *before) { +// The callback runs after the native buffer write, before its first poll. +// Native mode/clock writes, return PC and timeout propagation remain intact. +inline void patch(uint8_t (&out)[sizeof(WaitBefore)],const uint8_t *before,uintptr_t select) { memcpy(out,before,sizeof(out)); - // Display13 admitted DDIs 0/1 are Combo; 3..6 are TC (both HDMI and DP). - // Linux v6.12 intel_wait_ddi_buf_active: Combo 1000us, TC 3000us. - // Keep native 5us polling: 200 or 600 iterations. Replace only the - // initial count and two profiling increments; the write return PC stays. - // mov ebx,200; cmp dword [r15+0x120],3; jb +4; mov bx,600 - const uint8_t init[]={0xbb,0xc8,0,0,0,0x41,0x83,0xbf,0x20,0x01,0,0,0x03,0x72,0x04,0x66,0xbb,0x58,0x02}; - memcpy(out,init,sizeof(init)); - // Loop directly to the original read, never reinitialize the count. - out[51]=0xdf; // jne 0x5fbb3 from 0x5fbd2 + // mov rdi,r15; movabs rax,select; call rax; mov ebx,eax; nop; nop + // The donor already aligns its stack for calls here; r15 owns the port. + const uint8_t init[]={0x4c,0x89,0xff,0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,0x89,0xc3,0x90,0x90}; + memcpy(out,init,sizeof(init));memcpy(out+5,&select,sizeof(select)); + out[51]=0xdf; // jne original read, without reinitializing the count } } #endif diff --git a/tests/display13_port_wait.py b/tests/display13_port_wait.py index 6d313033..748a3925 100644 --- a/tests/display13_port_wait.py +++ b/tests/display13_port_wait.py @@ -1,20 +1,21 @@ #!/usr/bin/env python3 -"""Replay the original and wait-only patched exact donor; no hardware timing claim.""" +"""Real common installer/selector and exact native polling; MMIO/time are simulated.""" from pathlib import Path -import hashlib, subprocess, sys, tempfile -r=Path(__file__).resolve().parents[1] -s=(r/'WhateverXe/kern_xe.cpp').read_text() -install=s[s.index('bool XE::processDisplay13FramebufferSST('):s.index('uint8_t *XE::display13SSTPort(')] -assert '!display13FramebufferRequested' in install -assert 'waitKnown(wait,sizeof(WaitBefore))' in install -assert 'Display13PortWait::patch(after,before)' in install -assert '!memcmp(wait,after,sizeof(after))' in install -assert '{enable,wrapDisplay13EnablePort,orgDisplay13EnablePort}' in install -print('PASS wait patch guarded inside existing Display13 installer; native wrappers retained') -if len(sys.argv)>1: - donor=Path(sys.argv[1]).resolve() - assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' - with tempfile.TemporaryDirectory(prefix='d13-port-wait-') as d: - binary=Path(d)/'replay' - subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-I'+str(r/'WhateverXe'),str(r/'tests/display13_port_wait_fixture.cpp'),'-o',str(binary)],check=True) - subprocess.run([str(binary),str(donor)],check=True) +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:bool|uint32_t|XELP::LinkRequirements) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['framebufferLinkRequirements','framebufferPortWaitPolls','processFramebufferPortWait']) +assert s.count('processFramebufferPortWait(patcher,index,address,size)')==1 +runtime=re.search(r'bool XE::processFramebufferDonorRuntime\(.*?\n\}',s,re.S).group() +assert 'framebufferDonorRuntimeReady=hdcpReady && tglPanelPowerPropertyReady && meiReady &&\n processFramebufferPortWait' in runtime +assert 'while (delay < 3000' not in s +h=(r/'tests/xelp_port_wait_harness.hpp').read_text().replace('// FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='external-link-wait-') as d: + p=Path(d) + for native in [False]+([True] if len(sys.argv)>1 else []): + if native: + donor=Path(sys.argv[1]);assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + code=(r/'tests/display13_port_wait_fixture.cpp').read_text().replace('// PRODUCTION_HARNESS',h) + else:code=h+'\nint main(){checkInstallerAndSelection();}\n' + (p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+([str(donor)] if native else []),check=True) diff --git a/tests/display13_port_wait_fixture.cpp b/tests/display13_port_wait_fixture.cpp index d95a5f1d..32eb1665 100644 --- a/tests/display13_port_wait_fixture.cpp +++ b/tests/display13_port_wait_fixture.cpp @@ -1,12 +1,13 @@ -#include "kern_display13_port_wait.hpp" +#include "kern_xelp_port_wait.hpp" #include #include #include #include #include -using namespace Display13PortWait; +using namespace XELPPortWait; +// PRODUCTION_HARNESS alignas(8) uint8_t port[0x700]{}; -uint32_t config[6]{},buffer,baseResult; +uint32_t *config,buffer,baseResult; unsigned writes,reads,delays,baseCalls,nowUS,ackUS; bool unreadable; uintptr_t expectedWritePC; @@ -41,7 +42,7 @@ int main(int argc,char **argv) { auto image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0)); assert(image!=MAP_FAILED && b.size()<0x400000);memcpy(image,b.data(),b.size()); if(patched) { - uint8_t after[sizeof(WaitBefore)];patch(after,image+WaitOffset); + uint8_t after[sizeof(WaitBefore)];patch(after,image+WaitOffset,reinterpret_cast(XE::framebufferPortWaitPolls)); // Only initialization/profiling bytes and loop-back displacement change. for(unsigned i=19;i(image+0x5f9f4); expectedWritePC=reinterpret_cast(image+WaitOffset); - for(unsigned ddi:{0U,1U,3U,4U,5U,6U})for(unsigned mode:{1U,4U}) + for(unsigned platform=0;platform<3;++platform) + for(unsigned ddi:{0U,1U,3U,4U,5U,6U})for(unsigned protocol:{1U,2U}) for(unsigned ack:{0U,495U,500U,600U,995U,1000U,2000U,2995U,3000U,~0U}) for(unsigned fault=0;fault<3;++fault) { - config[0]=ddi;config[2]=mode==1?2:1;config[3]=mode==1?0:1; + setup(platform,ddi,platform==0 && ddi>=3?1:protocol); + memcpy(&config,selectionPort+0x548,sizeof(config)); + const unsigned mode=platform==1 || ddi<3?1:4; field(0x548,config+0);field(0x120,ddi);field(0x590,0x64000+256*ddi);field(0x550,mode); field(0x124,4U);field(0x128,20U);port[0x114]=1; writes=reads=delays=baseCalls=nowUS=0;buffer=0x80; baseResult=fault==1?42:0;unreadable=fault==2;ackUS=ack; - const unsigned budget=patched?(ddi>=3?3000:1000):500; + const unsigned budget=patched?(platform==1 || ddi<3?1000:3000):500; const auto result=native(port); const auto expected=fault==1?42U:(fault==2||ack>=budget)?0xe00002d6U:0; assert(result==expected && baseCalls==1 && writes==(fault==1?0U:1U)); assert(nowUS<=budget && delays==nowUS/5); if(expected==0xe00002d6U)assert(nowUS==budget && reads==1+budget/5); if(!fault && ack==0)assert(nowUS==0 && reads==2); // no unconditional delay - if(patched && !fault && ddi>=3 && ack==2000){assert(!result);++lateAccepted;} + if(patched && !fault && platform!=1 && ddi>=3 && ack==2000){assert(!result);++lateAccepted;} } munmap(image,0x400000); } - assert(lateAccepted==8); + assert(lateAccepted==16); puts("PASS original vs patched exact enablePort: late ACK, bounded timeout, base failure, all-ones, immediate ACK, preserved write PC and native return; MMIO/time mocked"); } diff --git a/tests/display13_tc_preparation.py b/tests/display13_tc_preparation.py new file mode 100644 index 00000000..e0006512 --- /dev/null +++ b/tests/display13_tc_preparation.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Call the real TC mode entry, selector, GOP proof and power/ownership core.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +f='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in ['XELP::LinkRequirements XE::framebufferLinkRequirements(', 'unsigned XE::display13TCTarget(', 'uint32_t XE::wrapDisplay13TCMode(']) +install=s[s.index('bool XE::processDisplay13FramebufferTC('):s.index('bool XE::processDisplay13FramebufferOutput(')] +assert '{entries[Mode].symbol,wrapDisplay13TCMode,orgDisplay13TCMode}' in install +assert 'display13FBTCHooksReady=processDisplay13FramebufferTC(patcher,index,address,size)' in s +# 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) +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) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_tc_preparation_fixture.cpp b/tests/display13_tc_preparation_fixture.cpp new file mode 100644 index 00000000..40956902 --- /dev/null +++ b/tests/display13_tc_preparation_fixture.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include "kern_display13_boot.hpp" +#include "kern_display13_tc_mode.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_sst_route.hpp" +struct OSObject {unsigned size=0x700;unsigned refs=0;const OSObject*getMetaClass(){return this;}unsigned getClassSize()const{return size;}void retain(){++refs;}void release(){assert(refs);--refs;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}templatevoid setProperty(T...){} bool getProperty(const char*){return false;}}; +constexpr unsigned kIOReturnSuccess=0,kIOReturnNotReady=0xe00002d8; +#define OSSafeReleaseNULL(p) do{(p)->release();(p)=nullptr;}while(0) +#define FunctionCast(f,p) reinterpret_cast(p) +bool locked=false;void IOLockLock(void*){assert(!locked);locked=true;}void IOLockUnlock(void*){assert(locked);locked=false;} +struct MMIO { + std::map regs;std::vector writes;bool failOwner=false; + bool readable(unsigned r){return r<0x1000000;}unsigned read(unsigned r){return regs[r];} + void delayUs(unsigned){} + void write(unsigned r,unsigned v){ + writes.push_back(r); + if(r==0x45404 || r==0x45444 || r==0x45454){ + const auto mask=r==0x45404?0xa80aU:0x2aaaU;unsigned ack=(v&mask)>>1; + if(r!=0x45404)for(unsigned t=0;t<4;++t){unsigned bit=1u<<(6+2*t); + if(!(regs[0x64300+256*t]&64) || (r==0x45454 && !(regs[0x45444]&bit)))ack&=~bit;} + regs[r]=(v&~(mask>>1))|ack; + } else if(!(failOwner && r>=0x64300 && r<=0x64600))regs[r]=v; + } +}; +struct Context { + Display13Power::NativeBridge bridge;Display13TC::Mode tcMode[4]; + bool tcSupported=true,tcBankFaulted=false,clockFaulted=false;unsigned tcBusy[4]{};OSObject*tcObject[4]{}; + void *lock=(void*)1,*controller=(void*)2,*access=(void*)3;MMIO*map=nullptr; + bool tcSignalReady[4]{};unsigned tcDPLink[4]{}; +} display13FBPower; +struct Display13FBPowerIO {MMIO*map;bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned t){return (map->read(0x45404)&5)==5 && (map->read(0x64300+256*t)&64);}bool tcAuxHealthy(unsigned){return true;}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 n){map->delayUs(n);}}; +using Display13FBBootIO=Display13FBPowerIO; +struct Display13FBTCIO:Display13FBPowerIO { + OSObject*object;unsigned tc; + Display13FBTCIO(OSObject*p,unsigned t):Display13FBPowerIO{display13FBPower.map},object(p),tc(t){} + bool admitted(unsigned t){return t==tc && t<4;} + bool accessHeld(){Display13FBPowerIO io{map};return Display13Power::held(io,display13FBPower.bridge.state,Display13TC::Mode::accessDomains());} + 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);} +}; +struct XE { + bool display13FramebufferRequested=true,display13FBTCHooksReady=true;XELP::Profile xeProfile; + alignas(8) uint8_t adlnPlatform[0x1c0]{},tglPlatformCopy[0x1c0]{};Display13OutputWiring adlnOutputWiring; + uintptr_t display13TCBaseMode=0,orgDisplay13TCMode=0; + bool tglHDMIEligible(const uint32_t*){return false;}XELP::Display12SSTRoutes tglSSTRoutes(){return {};} + static XELP::LinkRequirements framebufferLinkRequirements(void*,uint32_t); + static unsigned display13TCTarget(OSObject*,bool=false); + static uint32_t wrapDisplay13TCMode(OSObject*,uint32_t); +} instance,*callbackXE=&instance; +// FUNCTIONS +alignas(8) uint8_t portBytes[0x700]; +templatevoid put(unsigned o,T v){memcpy(portBytes+o,&v,sizeof(v));} +unsigned nativeCalls=0; +unsigned base(OSObject*p,unsigned mode){assert(!locked);++nativeCalls;memcpy(reinterpret_cast(p)+0x550,&mode,4);return 0;} +int main(){ + unsigned cases=0; + for(unsigned ddi=3;ddi<=6;++ddi)for(bool dp:{false,true})for(unsigned fault=0;fault<6;++fault){ + display13FBPower.~Context();new(&display13FBPower)Context;auto &c=display13FBPower; + instance=XE{};instance.xeProfile=XELP::identify(0x46a68086,3);instance.display13TCBaseMode=(uintptr_t)base; + MMIO map;c.map=↦map.regs[0x42000]=0x8e380000; + for(unsigned d=3;d<=6;++d){map.regs[0x161500+4*(d-3)]=4;map.regs[0x64000+256*d]=0;} + const unsigned peer=ddi==6?3:6;map.regs[0x64000+256*peer]=0x80000046; + Display13FBPowerIO power{&map};assert(Display13Power::initialize(power,c.bridge.state,Display13Power::nativeDomains(0x7b))); + c.bridge.begun=true;c.bridge.selectedPorts=0x78;c.bridge.bootPort=ddi; + auto &w=instance.adlnOutputWiring;w.valid=true;if(dp)w.dp=1u<(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; + put(0x548,cfg);put(0x578,c.access);put(0x120,ddi);put(0x590,0x64000+256*ddi);nativeCalls=0; + // cold; valid GOP; live without retained GOP; omitted route; owner readback failure; invalid record. + if(fault==1 || fault==2){ + map.regs[0x45404]=0xfc0f;map.regs[0x45454]=3u<<(2*ddi); + map.regs[0x64000+256*ddi]=0x80000046;map.regs[0x46038+8*(ddi-3)]=0xcc000000; + map.regs[0x46100+4*ddi]=0x80000000;map.regs[0x46140]=(ddi+3)<<28; + map.regs[0x60400]=(dp?0x82030006:0x80030100)|((ddi+1)<<27);map.regs[0x70008]=0xc0000000; + map.regs[0x60540]=0x80040300;map.regs[0x60030]=0x7e100000;map.regs[0x60034]=map.regs[0x60044]=0x800000;map.regs[0x60040]=0x100000; + if(fault==1){auto &l=c.bridge.ddiLease[ddi];l.owner=&c.bridge.state;l.domains=Display13Power::dependencies(Display13Power::ddiDomain(ddi));c.bridge.ddiRefs[ddi]=1;} + } + 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); + 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));} + for(unsigned other=3;other<=6;++other)if(other!=ddi){assert(map.regs[0x64000+256*other]==before.at(0x64000+256*other));assert(!c.tcMode[other-3].controller);} + assert(!locked);++cases; + } + puts("PASS real TC mode entry: HDMI/fixedDP cold and validated GOP, missing route/identity/ownership refusal, mandatory power/ownership/AUX ordering and peer preservation; simulated MMIO"); +} diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index 36c539ee..abfe1e03 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -1,6 +1,8 @@ #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_platform.hpp" #include void *current_thread(){thread_local char token;return &token;} #include @@ -28,6 +30,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + XELP::Profile xeProfile=XELP::identify(0x9a498086,1); bool tglActive=true,tglDPAltFiaReady=true; bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index 1828f11f..e365996d 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,6 +1,8 @@ #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_platform.hpp" #include void *current_thread(){thread_local char token;return &token;} #include @@ -27,6 +29,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + XELP::Profile xeProfile=XELP::identify(0x9a498086,1); bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; uint32_t tglTCAutoRegistered=0,tglTCAutoCandidates=0x10,tglAUXParentMask=0x18; diff --git a/tests/tgl_tx_wrappers_fixture.cpp b/tests/tgl_tx_wrappers_fixture.cpp index 50a3d2fc..28503a0e 100644 --- a/tests/tgl_tx_wrappers_fixture.cpp +++ b/tests/tgl_tx_wrappers_fixture.cpp @@ -79,7 +79,7 @@ int main(){ if(kind==4)reg(buffer())=0x80000000; if(kind==5){nativeResult=0xBAD;assert(!mprotect(mmio,span,PROT_NONE));} auto r=XE::wrapTGLEnablePort(port);assert(enableCalls==1); - assert(r==((kind==0||kind==4)?0u:nativeResult));assert(delays==(kind==0?2u:kind==1?30u:0u)); + assert(r==nativeResult && delays==0); // Waiting belongs to the common native-loop adapter. if(kind!=5)neighbors(); } for(unsigned reason=0;reason<4;++reason){reset(d); @@ -102,5 +102,5 @@ int main(){ reset(d);readable=false;XE::wrapTGLPhyClockGating(port,false);assert(clockCalls==1&&writes==0); } assert(!mprotect(mmio,span,PROT_READ|PROT_WRITE));munmap(mmio,span); - puts("PASS actual 5 TX wrappers across 6 mock-qualified ports, quiet/on: native coefficient/clock/mode semantics, all lane counts/rates, buffer-local stagger, 3ms late-enable success/timeout/all-ones, protected-MMIO wiring/backend exclusions, native errors and neighboring outputs preserved. No physical qualification."); + puts("PASS actual 5 TX wrappers across 6 mock-qualified ports, quiet/on: native coefficient/clock/mode semantics, all lane counts/rates, buffer-local stagger, native enable return (shared wait tested separately), protected-MMIO wiring/backend exclusions, native errors and neighboring outputs preserved. No physical qualification."); } diff --git a/tests/xelp_port_wait_harness.hpp b/tests/xelp_port_wait_harness.hpp new file mode 100644 index 00000000..4d436c9f --- /dev/null +++ b/tests/xelp_port_wait_harness.hpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include "kern_xelp_port_wait.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_sst_route.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_display13_fb_memory.hpp" +using mach_vm_address_t=uintptr_t; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char*,bool){}}; +struct KernelPatcher { + enum class Error{NoError,Failed};bool fail=false,corrupt=false;unsigned writes=0; + struct LookupPatch {void *info;const uint8_t *before,*after;size_t n;unsigned count;}; + templateT solveSymbol(size_t,const char*,uintptr_t a,size_t){return a+0x5f9f4;} + void clearError(){} Error getError(){return fail?Error::Failed:Error::NoError;} + void applyLookupPatch(LookupPatch *p,uint8_t *at,size_t){++writes;assert(!memcmp(at,p->before,p->n));if(!fail)memcpy(at,p->after,p->n);if(corrupt)at[0]^=1;} +}; +struct XE { + bool display13FramebufferRequested=false;XELP::Profile xeProfile; + alignas(8) uint8_t tglPlatformCopy[0x1c0]{},adlnPlatform[0x1c0]{}; + Display13OutputWiring adlnOutputWiring;XELP::Display12SSTRoutes routes; + unsigned hdmiMask=0;void *currentFramebuffer=nullptr; + bool tglHDMIEligible(const uint32_t *c) const {return (hdmiMask&(1u<(bytes+0x20+24*ddi); + c[0]=ddi;c[1]=ddi<3?ddi+1:ddi+6;c[2]=protocol;c[3]=ddi<3?0:1;c[4]=protocol==2?0x800:0x400;c[5]=protocol==2?0x202:0x201; + if(platform==1 || ddi<3)instance.routes.combo=1u< b(0x70000);memcpy(b.data()+WaitOffset,WaitBefore,sizeof(WaitBefore)); + if(fault==1)b[WaitOffset+50]^=1; + KernelPatcher p;p.fail=fault==2;p.corrupt=fault==3; + const bool ok=instance.processFramebufferPortWait(p,0,reinterpret_cast(b.data()),b.size()); + assert(ok==!fault);assert(p.writes==(fault==1?0:1)); + if(ok){uintptr_t target=0;memcpy(&target,b.data()+WaitOffset+5,8);assert(target==reinterpret_cast(XE::framebufferPortWaitPolls));} + } + for(unsigned platform=0;platform<3;++platform)for(unsigned ddi:{0u,1u,3u,4u,5u,6u})for(unsigned protocol:{1u,2u}){ + if(platform==0 && ddi>=3 && protocol==2)continue; + setup(platform,ddi,protocol); + assert(XE::framebufferPortWaitPolls(selectionPort)==(platform==1 || ddi<3?200:600)); + using P=XELP::LinkRequirements::Preparation; + const auto req=XE::framebufferLinkRequirements(selectionPort,platform==1 || ddi<3?1:4); + assert(req.preparation(false)==(platform==1 || ddi<3?P::NativeCombo:platform==0?P::NativeTC12:P::ColdTC13)); + assert(req.preparation(true)==(platform==1 || ddi<3?P::NativeCombo:platform==0?P::NativeTC12:P::FirmwareTC13)); + selectionPort[0x117]=1;assert(XE::framebufferPortWaitPolls(selectionPort)==100);selectionPort[0x117]=0; + selectionPort[0x590]^=1;assert(XE::framebufferPortWaitPolls(selectionPort)==100);selectionPort[0x590]^=1; + selectionPort[0x120]^=1;assert(XE::framebufferPortWaitPolls(selectionPort)==100);selectionPort[0x120]^=1; + unsigned mode=3;memcpy(selectionPort+0x550,&mode,4);assert(XE::framebufferPortWaitPolls(selectionPort)==100); + } + puts("PASS real common installer guard/failure/readback and production PHY/protocol selector, including ADL-S TC-numbered Combo, panel/alt/mismatched native exclusion"); +} diff --git a/tools/test.py b/tools/test.py index 134f0b89..032a6108 100644 --- a/tools/test.py +++ b/tools/test.py @@ -8,10 +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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] -python_tests += ['display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] +python_tests += ['display13_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] 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 += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_retirement','display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] +if a.donor:jobs += [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['display13_tc_preparation','display13_retirement','display13_port_wait','framebuffer_training_registration','framebuffer_common','tgl_hdmi_wiring','framebuffer_donor_runtime','display13_training_lease','display13_tc_native','display13_ddc','tgl_power_fuses', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing','tgl_agdc_port_status','tgl_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor','adls_clock_native']] if a.accelerator:jobs += [('xelp_forcewake_irq_native',[sys.executable,str(R/'tests/xelp_forcewake_irq.py'),'--donor',str(a.accelerator.resolve())],None),('tgl_rpcs_native',[sys.executable,str(R/'tests/tgl_rpcs_native.py'),str(a.accelerator.resolve())],None)] + [(n+'_native',[sys.executable,str(R/'tests'/(n+'.py')),str(a.accelerator.resolve())],None) for n in ['small_gt_urb','tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From c0f16c5deec813af7727393a52833ae27fc67750 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:01:37 +0900 Subject: [PATCH 47/60] Preserve active ADL-S PLL owners across Combo hotplug --- WhateverXe/kern_adls_clock.hpp | 82 ++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 101 +++++++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 2 + tests/adls_clock_native_fixture.cpp | 90 ++++++++++++++++++++++++- tests/adls_clock_runtime.py | 79 ++++++++++++++++++++++ tests/tgl_clock_order.py | 2 + tests/tgl_combo_pll_write.py | 2 +- tools/test.py | 2 +- 8 files changed, 352 insertions(+), 8 deletions(-) create mode 100644 WhateverXe/kern_adls_clock.hpp create mode 100644 tests/adls_clock_runtime.py diff --git a/WhateverXe/kern_adls_clock.hpp b/WhateverXe/kern_adls_clock.hpp new file mode 100644 index 00000000..b3267b0f --- /dev/null +++ b/WhateverXe/kern_adls_clock.hpp @@ -0,0 +1,82 @@ +// ADL-S Combo clocks are shared resources, not a PHY-numbered array of owners. +// Linux v6.12 intel_ddi.c (rkl_ddi_get_pll) and intel_dpll_mgr.c (adls_pll_mgr). +#ifndef KERN_ADLS_CLOCK_HPP +#define KERN_ADLS_CLOCK_HPP +#include "kern_alderlake_s.hpp" + +namespace AlderLakeS { +struct ClockSnapshot { + uint32_t mux[2] {},enable[4] {}; + bool valid() const { + for(auto v:mux)if(v==UINT32_MAX)return false; + for(auto v:enable)if(v==UINT32_MAX)return false; + return true; + } + uint32_t value(const Port &p) const {return mux[p.phy<3?0:1];} + bool active(const Port &p) const {return !(value(p)&p.clockOff);} + uint32_t selected(const Port &p) const {return (value(p)>>p.clockShift)&3;} + uint32_t peers(const Port &target) const { + uint32_t mask=0; + for(unsigned phy=0;phy<5;++phy) { + Port p;Port::resolve(phy?phy+2:0,p); + if(p.phy!=target.phy && active(p))mask|=1U<=3 || !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); + const auto previous=s.active(p)?s.selected(p):binding[p.phy]?binding[p.phy]-1:UINT32_MAX; + if(!on)return {previous,true,previous==UINT32_MAX || bool(peers&(1U<=0x6CE10 && pc<0x6DA26):(pc>=0x6DAE6 && pc<0x6DE58); + } + unsigned long reg(unsigned long r,uint32_t pc) const { + if(!caller(pc))return r; + PLL from,to; + if(!PLL::resolve(phy,from) || !PLL::resolve(pll,to))return r; + if(r==from.enable)return to.enable; + if(r==from.cfg0)return to.cfg0; + if(r==from.cfg1)return to.cfg1; + return r; + } + uint32_t mux(uint32_t value,uint32_t pc) const { + if(!enabling || pc!=0x6D77E || phy>=3 || pll>=4)return value; + return (value&~(3U<<(2*phy)))|(pll<<(2*phy)); + } + template void prepareMux(Read read,Write write,uint32_t value) const { + const auto old=read(0x164280); + Port route;Port::resolve(phy?phy+2:0,route); + if((old^value)&(3U<(current_thread()) && t.access==access; +} // Immutable first 16 snapshots per cursor lifecycle event; publish on demand. // No MMIO writes, table repair, allocations or registry publication in hooks. struct TGLCursorLifetimeRecord { uint64_t q[96]; volatile uint32_t ready; }; @@ -12543,7 +12557,9 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; const auto symbol="__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams"; const auto fn=patcher.solveSymbol(index,symbol,address,size); - bool ready=adlsPLLTableReady && orgTGLDisableClocks && tglClockOffCaller && tglComboPLLFractionCaller && + if(!adlsClockTransaction.lock)adlsClockTransaction.lock=IORecursiveLockAlloc(); + bool ready=adlsPLLTableReady && adlsClockTransaction.lock && tglTCNativeFunctions.complete() && + orgTGLRegisterRead && orgTGLRegisterWrite && orgTGLDisableClocks && tglClockOffCaller && tglComboPLLFractionCaller && fn==address+0x6CE10 && size>0x6DCBD && !memcmp(reinterpret_cast(fn),entry,sizeof(entry)); struct Site {uint32_t offset;const uint8_t *find,*replace;size_t bytes;}; const Site sites[]={{0x6CFD3,adlsClockOnFind,adlsClockOnReplace,sizeof(adlsClockOnFind)}, @@ -12987,6 +13003,11 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { that->setProperty("TGLHostProviderWorkLoopSelected",ok); if (!ok) return false; } + if(callbackXE->adlsDisplaySelected && adlsClockTransaction.lock) { + IORecursiveLockLock(adlsClockTransaction.lock); + adlsClockTransaction.allocation={}; + IORecursiveLockUnlock(adlsClockTransaction.lock); + } // A reused controller address must not inherit another start's PPS snapshot. callbackXE->tglPanelTimingBIOSValid=false; callbackXE->adlsPanelPWM={}; @@ -16153,6 +16174,7 @@ uint32_t XE::wrapTGLTopDDC(void *fb, int connection, uint32_t block, uint32_t ty } uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { + if(adlsClockAccess(access))reg=adlsClockTransaction.translation.reg(reg,tglConnectionCaller(__builtin_return_address(0))); 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); @@ -17538,6 +17560,10 @@ void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { if(callbackXE->adlsDisplaySelected && !adlsClockTarget(controller,port,otherPort)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; } + bool keepPLL=false; + if(callbackXE->adlsDisplaySelected && !adlsBeginClock(controller,port,false,keepPLL)) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; + } tglTransitionRecord(32,false,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); auto s = callbackXE; auto &tx = tglClockOffTransaction; @@ -17573,7 +17599,10 @@ void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { // If hardware/ownership checks did not admit the correction, preserve native // teardown, including any clock-off request deferred by this transaction. if (owned && tx.pending) { tx.pending = false; wrapTGLRegisterWrite(tx.access, tx.clockReg, tx.value); } - FunctionCast(wrapTGLDisableClocks, s->orgTGLDisableClocks)(controller, port, otherPort); + // The donor's self-sharing argument gates the output and releases its DDI + // reference, but leaves the shared PLL running and recalculates voltage. + FunctionCast(wrapTGLDisableClocks, s->orgTGLDisableClocks)(controller, port, keepPLL?port:otherPort); + if(s->adlsDisplaySelected)adlsEndClock(); tglTransitionRecord(32,true,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); } @@ -17606,7 +17635,54 @@ bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { XELP::ADLSComboBackend::clockSlot(XELP::Donor::C004FC1A,config[0],slot) && AlderLakeS::Port::resolve(config[0],route) && slot==route.phy && access && *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==route.phyBase()+0x10C && - *reinterpret_cast(access+0x60)>=0x16429C; + *reinterpret_cast(access+0x60)>=0x1642C8; +} + +bool XE::adlsBeginClock(void *controller,void *port,bool enable,bool &keepPLL) { + auto s=callbackXE;auto &t=adlsClockTransaction; + auto c=static_cast(controller); + auto access=*reinterpret_cast(c+0xC40); + auto power=adlsPowerTarget(*reinterpret_cast(c+0xC8)); + if(!t.lock || !power || !*reinterpret_cast(access+0x50) || !s->tglTCNativeFunctions.complete())return false; + IORecursiveLockLock(t.lock); + if(t.thread){IORecursiveLockUnlock(t.lock);return false;} + // Clock muxes and PLL state live in DISPLAY_CORE. Hold a real native PW1 + // reference through selection, native programming and result readback. + s->tglTCNativeFunctions.getPG(power,1); + auto read=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead); + const auto pw=read(access,0x45404); + if(pw==UINT32_MAX || (pw&3)!=3) { + s->tglTCNativeFunctions.putPG(power,1);IORecursiveLockUnlock(t.lock);return false; + } + AlderLakeS::Port route; + AlderLakeS::Port::resolve((*reinterpret_cast(static_cast(port)+0x548))[0],route); + AlderLakeS::ClockSnapshot snapshot; + snapshot.mux[0]=read(access,0x164280);snapshot.mux[1]=read(access,0x1642BC); + for(unsigned i=0;i<4;++i){AlderLakeS::PLL pll;AlderLakeS::PLL::resolve(i,pll);snapshot.enable[i]=read(access,pll.enable);} + const auto choice=t.allocation.choose(route,snapshot,enable); + if(!choice.valid) { + s->tglTCNativeFunctions.putPG(power,1);IORecursiveLockUnlock(t.lock);return false; + } + if(enable)t.allocation.binding[route.phy]=choice.pll+1; + t.translation={route.phy,choice.pll,enable};t.before=snapshot; + t.access=access;t.power=power;t.keepPLL=keepPLL=choice.keepPLL; + __atomic_store_n(&t.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE); + return true; +} + +void XE::adlsEndClock() { + auto s=callbackXE;auto &t=adlsClockTransaction; + auto read=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead); + auto tr=t.translation;AlderLakeS::PLL pll; + const auto state=AlderLakeS::PLL::resolve(tr.pll,pll)?read(t.access,pll.enable):0; + const auto mux=read(t.access,0x164280); + if(!tr.enabling && (t.keepPLL || !(state&0xCC000000U)))t.allocation.binding[tr.phy]=0; + uint32_t record[]={1,tr.phy,tr.pll,tr.enabling,t.keepPLL,t.before.mux[0],t.before.mux[1],mux,state}; + __atomic_store_n(&t.thread,0,__ATOMIC_RELEASE); + s->tglTCNativeFunctions.putPG(t.power,1); + IORecursiveLockUnlock(t.lock); + char key[40];snprintf(key,sizeof(key),"ADLSClockPHY%u%s",tr.phy,tr.enabling?"Enable":"Disable"); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); } void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params) { @@ -17616,8 +17692,13 @@ void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32 !AlderLakeS::HDMIClock::encode(params,*reinterpret_cast(static_cast(controller)+0xEA8),encoded))) { IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; } + bool keepPLL=false; + if(!adlsBeginClock(controller,port,true,keepPLL)) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; + } tglTransitionRecord(71,false,port,encoded.cfg0,encoded.cfg1,0); FunctionCast(wrapADLSEnableClocks,callbackXE->orgADLSEnableClocks)(controller,port,otherPort,params); + adlsEndClock(); tglTransitionRecord(71,true,port,encoded.cfg0,encoded.cfg1,0); } @@ -17899,6 +17980,20 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { } } } + if(adlsClockAccess(access)) { + const auto pc=tglConnectionCaller(__builtin_return_address(0)); + const auto &tr=adlsClockTransaction.translation; + reg=tr.reg(reg,pc); + if(reg==0x164280 && tr.enabling && pc==0x6D77E) { + value=tr.mux(value,pc); + // The selector and clock-ungate require separate writes. Preserve + // every peer field and gate our output before changing its source. + auto read=FunctionCast(wrapTGLRegisterRead,callbackXE->orgTGLRegisterRead); + auto write=FunctionCast(wrapTGLRegisterWrite,callbackXE->orgTGLRegisterWrite); + tr.prepareMux([&](uint32_t r){return read(access,r);}, + [&](uint32_t r,uint32_t v){write(access,r,v);},value); + } + } auto &tx = tglClockOffTransaction; if (callbackXE->tglClockOffCaller && reinterpret_cast(__builtin_return_address(0)) == callbackXE->tglClockOffCaller && reg == tx.clockReg && !(value & 0xF0000000U) && diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index a67d8d63..e87af5b4 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -106,6 +106,8 @@ class XE { static void wrapADLSPowerPG(void *power,bool enable,uint32_t requested); mach_vm_address_t orgADLSEnableClocks {}; static bool adlsClockTarget(void *controller,void *port,void *otherPort); + static bool adlsBeginClock(void *controller,void *port,bool enable,bool &keepPLL); + static void adlsEndClock(); static void wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params); mach_vm_address_t orgADLSPortInit {}; static bool wrapADLSPortInit(void *port,uint32_t *config); diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp index d3c55547..e61bcfec 100644 --- a/tests/adls_clock_native_fixture.cpp +++ b/tests/adls_clock_native_fixture.cpp @@ -8,6 +8,7 @@ #include #include "kern_alderlake_s.hpp" #include "kern_tgl_port_map.hpp" +#include "kern_adls_clock.hpp" templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} void jump(uint8_t *p,uintptr_t t){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&t,8);p[10]=0xff;p[11]=0xe0;} struct Event {uint32_t kind,reg,value;}; @@ -15,13 +16,29 @@ std::vector events;std::map mmio; alignas(8) uint8_t ctl[0xF00] {},port[0x700] {},access[0x70] {}; uintptr_t vtable[64] {};uint32_t config[]={3,9,2,0,0x800,0x202}; uint8_t *image;unsigned fault,delays,errors; -uint32_t readReg(void *p,unsigned long reg){assert(p==access);assert(mmio.count(reg));return mmio.at(reg);} +AlderLakeS::ClockAllocation allocation; +AlderLakeS::ClockTranslation translation; +bool managed; +uint32_t readReg(void *p,unsigned long reg){ + if(managed)reg=translation.reg(reg,reinterpret_cast(__builtin_return_address(0))-reinterpret_cast(image)); + assert(p==access);assert(mmio.count(reg));return mmio.at(reg); +} void writeReg(void *p,unsigned long reg,uint32_t v){ - assert(p==access && mmio.count(reg)); + assert(p==access); + const auto pc=reinterpret_cast(__builtin_return_address(0))-reinterpret_cast(image); if(reinterpret_cast(__builtin_return_address(0))==reinterpret_cast(image+0x6d973)) TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg==0x164294?0x164284:reg,v); + if(managed){ + reg=translation.reg(reg,pc); + if(reg==0x164280 && translation.enabling && pc==0x6d77e) { + v=translation.mux(v,pc); + translation.prepareMux([](uint32_t r){return mmio.at(r);}, + [](uint32_t r,uint32_t value){events.push_back({1,r,value});mmio[r]=value;},v); + } + } + assert(mmio.count(reg)); events.push_back({1,uint32_t(reg),v}); - if(reg==0x46010 || reg==0x46014 || reg==0x46018) { + if(reg==0x46010 || reg==0x46014 || reg==0x46018 || reg==0x46030) { // Model hardware ACK/LOCK independently from request bits. Failure cases // exercise the donor's bounded waits, not successful hardware operation. v&=~0x44000000U; @@ -94,6 +111,73 @@ int main(int argc,char **argv){ assert(mmio[pll.cfg0]==cfg0 && mmio[pll.cfg1]==cfg1); disable(ctl,port,nullptr);assert(!errors && !delays && !mmio[pll.enable] && (mmio[0x164280]&route.clockOff));++cases; } + // Firmware can route any live Combo PHY to any PLL. Replay the production + // allocation and operand translation with the real enable/disable bodies. + auto snapshot=[] { + AlderLakeS::ClockSnapshot s;s.mux[0]=mmio.at(0x164280);s.mux[1]=mmio.at(0x1642bc); + for(unsigned i=0;i<4;++i){AlderLakeS::PLL p;AlderLakeS::PLL::resolve(i,p);s.enable[i]=mmio.at(p.enable);}return s; + }; + auto setup=[] { + allocation={};fault=errors=delays=0;events.clear();mmio.clear(); + mmio[0x164280]=0x01e07c00;mmio[0x1642bc]=0x30; + for(unsigned i=0;i<4;++i){AlderLakeS::PLL p;AlderLakeS::PLL::resolve(i,p);mmio[p.enable]=mmio[p.cfg0]=mmio[p.cfg1]=0;} + }; + auto routeOn=[](unsigned phy,unsigned pll) { + AlderLakeS::Port p;AlderLakeS::Port::resolve(phy?phy+2:0,p); + auto &v=mmio[p.clock];v=(v&~(p.clockOff|(3U<(port,0x114)=1;at(port,0x128)=20; + AlderLakeS::Port p;AlderLakeS::Port::resolve(config[0],p); + auto choice=allocation.choose(p,snapshot(),on);assert(choice.valid); + translation={phy,choice.pll,on};managed=true; + if(on){allocation.binding[phy]=choice.pll+1;enable(ctl,port,nullptr,nullptr);} + else {disable(ctl,port,choice.keepPLL?port:nullptr); + AlderLakeS::PLL r;if(choice.keepPLL || (AlderLakeS::PLL::resolve(choice.pll,r)&&!(mmio[r.enable]&0xcc000000)))allocation.binding[phy]=0; + } + managed=false;assert(config[0]==(phy?phy+2:0));return choice; + }; + unsigned sharedCases=0; + for(unsigned target=0;target<3;++target)for(unsigned peer=0;peer<5;++peer)for(unsigned occupied=0;occupied<4;++occupied) { + if(target==peer)continue;setup();routeOn(peer,occupied); + const auto before=mmio; + // A disabled output has no ownership merely because its stale selector is0. + auto off=run(target,false);assert(off.keepPLL); + auto on=run(target,true);assert(on.pll!=occupied); + AlderLakeS::Port targetPort;AlderLakeS::Port::resolve(target?target+2:0,targetPort); + uint32_t lastMux=before.at(0x164280); + for(auto e:events)if(e.kind==1 && e.reg==0x164280) { + if((e.value^lastMux)&(3U< +#include +#include +struct IORecursiveLock {unsigned depth=0;}; +void IORecursiveLockLock(IORecursiveLock *l){++l->depth;} +void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} +uintptr_t thread=1; +void *current_thread(){return reinterpret_cast(thread);} +unsigned refs=0,getCount=0,putCount=0,reads=0; +std::map mmio; +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);} +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry o;return &o;} + void setProperty(const char *,void *,unsigned){} +}; +uint32_t readNative(void *a,unsigned long reg){assert(a==access && refs && mmio.count(reg));++reads;return mmio.at(reg);} +void getPG(void *p,uint32_t pg){assert(p==power && pg==1);++refs;++getCount;} +void putPG(void *p,uint32_t pg){assert(p==power && pg==1 && refs);--refs;++putCount;} +struct XE { + struct Native { + bool ready=true;bool complete(){return ready;} + void (*getPG)(void*,uint32_t)=::getPG,(*putPG)(void*,uint32_t)=::putPG; + } tglTCNativeFunctions; + uintptr_t orgTGLRegisterRead=reinterpret_cast(readNative); + static uint32_t wrapTGLRegisterRead(void*,unsigned long); + static uint8_t *adlsPowerTarget(void *p){return p==power?power:nullptr;} + static bool adlsBeginClock(void*,void*,bool,bool&); + static void adlsEndClock(); +} xe,*callbackXE=&xe; +#define FunctionCast(f,p) reinterpret_cast(p) +STATE +FUNCTIONS +int main(){ + IORecursiveLock lock;adlsClockTransaction.lock=&lock; + at(ctl,0xc40)=access;at(ctl,0xc8)=power;at(port,0x548)=config; + at(access,0x50)=ctl; + mmio={{0x45404,3},{0x164280,0x01e07420},{0x1642bc,0x30}, + {0x46010,0xcc000000},{0x46014,0},{0x46018,0},{0x46030,0}}; + bool keep=false; + assert(XE::adlsBeginClock(ctl,port,true,keep)); + assert(refs==1 && lock.depth==1 && adlsClockAccess(access)); + assert(!adlsClockAccess(port) && adlsClockTransaction.translation.pll==1); + assert(adlsClockTransaction.allocation.binding[0]==2); + assert(!XE::adlsBeginClock(ctl,port,true,keep));assert(refs==1 && lock.depth==1); + thread=2;assert(!adlsClockAccess(access));thread=1; + // Partial enable: retain the powered PLL's reservation until native teardown. + mmio[0x46014]=0x0c000000;XE::adlsEndClock(); + assert(!refs && !lock.depth && !adlsClockAccess(access)); + assert(XE::adlsBeginClock(ctl,port,false,keep) && !keep); + assert(adlsClockTransaction.translation.pll==1); + mmio[0x46014]=0;XE::adlsEndClock();assert(!adlsClockTransaction.allocation.binding[0]); + // A gated port without an owned attempt must not stop its stale selector0. + assert(XE::adlsBeginClock(ctl,port,false,keep) && keep);XE::adlsEndClock(); + assert(mmio[0x46010]==0xcc000000); + // PW1 not acknowledged: no clock-bank reads or unbalanced refs. + mmio[0x45404]=0;auto n=reads; + assert(!XE::adlsBeginClock(ctl,port,true,keep));assert(reads==n+1); + mmio[0x45404]=3;mmio[0x164280]=UINT32_MAX; + assert(!XE::adlsBeginClock(ctl,port,true,keep)); + assert(!refs && !lock.depth && getCount==putCount && !adlsClockTransaction.thread); + puts("PASS production ADL-S clock scope: core power, nested rejection, thread/access isolation, failed-enable reservation and balanced teardown"); +} +'''.replace('STATE',state).replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='adls-clock-runtime-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-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) diff --git a/tests/tgl_clock_order.py b/tests/tgl_clock_order.py index 5eb8a550..f27dbfcf 100644 --- a/tests/tgl_clock_order.py +++ b/tests/tgl_clock_order.py @@ -15,6 +15,8 @@ void IODelay(unsigned n){assert(n==8);++delays;} struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry v;return &v;}void setProperty(const char*,bool){} }; struct XE { + static bool adlsBeginClock(void*,void*,bool,bool&){assert(false);return false;} + static void adlsEndClock(){assert(false);} bool adlsDisplaySelected=false;static bool adlsClockTarget(void*,void*,void*){return false;} void *tglObservationController;uintptr_t tglClockOffCaller=1,orgTGLPathLookup,orgTGLDisableClocks; bool tglDPSSTMode(unsigned d,unsigned mode){return d>=3&&d<=8&&(mode==3||mode==4);} diff --git a/tests/tgl_combo_pll_write.py b/tests/tgl_combo_pll_write.py index 3c5ecaf9..ab791cfe 100644 --- a/tests/tgl_combo_pll_write.py +++ b/tests/tgl_combo_pll_write.py @@ -5,7 +5,7 @@ root=Path(__file__).resolve().parents[1] s=(root/'WhateverXe/kern_xe.cpp').read_text() start=s.index('void XE::wrapTGLRegisterWrite(') -guard=s[s.index('\n',start)+1:s.index('\tauto &tx = tglClockOffTransaction;',start)] +guard=s[s.index('\n',start)+1:s.index(' if(adlsClockAccess(access))',start)] guard=guard.replace('reinterpret_cast(__builtin_return_address(0))','caller') install=s[s.index('// Qualify the reference-clock field producer'):s.index('if (tglHDMIB)',s.index('// Qualify the reference-clock field producer'))] if len(sys.argv)>1: diff --git a/tools/test.py b/tools/test.py index 032a6108..1460382a 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','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=['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','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] From 36f405ebc713fa3f7991fda28992a06755317307 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:38:10 +0900 Subject: [PATCH 48/60] Retire unadopted ADL-S firmware consumers before native startup --- WhateverXe/kern_adls_boot.hpp | 148 ++++++++++++++++++++++++++++++++++ WhateverXe/kern_xe.cpp | 45 ++++++++++- WhateverXe/kern_xe.hpp | 2 + tests/adls_boot.cpp | 96 ++++++++++++++++++++++ tests/adls_boot_runtime.py | 31 +++++++ tests/boot_arguments.py | 1 + tools/test.py | 2 +- 7 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 WhateverXe/kern_adls_boot.hpp create mode 100644 tests/adls_boot.cpp create mode 100644 tests/adls_boot_runtime.py diff --git a/WhateverXe/kern_adls_boot.hpp b/WhateverXe/kern_adls_boot.hpp new file mode 100644 index 00000000..314cae1b --- /dev/null +++ b/WhateverXe/kern_adls_boot.hpp @@ -0,0 +1,148 @@ +// ADL-S firmware may scan out on several pipes; the donor adopts only the +// first enabled transcoder. Retire the other linear GOP consumers before +// native startup. Keep clocks, PLLs and power requests for native accounting. +// Ordering: Linux v6.12 intel_display.c / intel_ddi.c, Display12 SST disable. +#ifndef KERN_ADLS_BOOT_HPP +#define KERN_ADLS_BOOT_HPP +#include "kern_adls_clock.hpp" +#include "kern_alderlake_n.hpp" +#include "kern_display13_fb_irq.hpp" +namespace AlderLakeS { +namespace Boot { +enum class Status:uint32_t {Unavailable,Unchanged,Complete,Topology,Drift,Timeout}; +struct Result { + uint32_t version=1;Status status=Status::Unavailable; + uint32_t active {},primary=UINT32_MAX,parked {},stage {},writes {},reg {},value {}; +}; +template Result prepare(IO &io,bool admitted,const Wiring &w) { + Result r; + if(!admitted || !w.valid || !w.selected())return r; + AlderLakeN::DisplayReadout s;s.status=AlderLakeN::ReadoutStatus::Complete; + const auto add=[&](uint32_t reg) { + if(s.count>=160 || !io.readable(reg)){s.status=AlderLakeN::ReadoutStatus::Unavailable;return UINT32_MAX;} + const auto v=io.read(reg);s.registers[s.count++]={reg,v}; + if(v==UINT32_MAX)s.status=AlderLakeN::ReadoutStatus::Unavailable; + return v; + }; + const auto power=add(0x45404); + if(power==UINT32_MAX || !(power&1))return r; + const uint32_t common[] {0x45450,0x45454,0x45444,0x164280,0x1642BC,0x46010,0x46014,0x46018,0x46030}; + for(auto reg:common)add(reg); + const unsigned ddis[] {0,3,4,5,6}; + for(auto d:ddis)add(0x64000+0x100*d); + uint32_t ports[3] {UINT32_MAX,UINT32_MAX,UINT32_MAX},used=0; + for(unsigned p=0;p<3;++p) { + const uint32_t mask=p==0?1:p==1?0x15:0x55; + if((power&mask)!=mask)continue; + s.poweredPipes|=1U<>27)&15,protocol=(func>>24)&7; + if(!encoding || protocol>2 || ((func>>20)&7) || get(0x60404+bank))return r; + const auto d=encoding-1;Port port; + if(!Port::resolve(d,port) || !(w.selected()&(1U<>28)!=d+1 || (protocol==2 && (!(tp&0x80000000U) || (tp&(1U<<27)))))return r; + const auto geometry=XELPGeometry::linear(get(0x70180+bank),get(0x70188+bank),get(0x70190+bank),get(0x7018C+bank),get(0x701A4+bank)); + if(!AlderLakeN::bootGeometry(s,p,geometry,get(0x60000+bank),get(0x6000C+bank)) || + (get(0x7019C+bank)&4095) || get(0x7019C+bank)!=get(0x701AC+bank))return r; + } + uint32_t changed[32] {},count=0,current=UINT32_MAX; + const auto stable=[&] { + for(unsigned i=0;i=2 && x.offset==0x64000+0x100*ports[current] ? ~0x80U : UINT32_MAX; + if(!skip && ((io.read(x.offset)^x.value)&mask)){r.reg=x.offset;r.value=io.read(x.offset);return false;} + } + return true; + }; + if(!stable()){r.status=Status::Drift;return r;} + const auto write=[&](uint32_t reg,uint32_t v) { + if(!stable()){r.status=Status::Drift;return false;} + bool seen=false;for(unsigned i=0;i=32)return false;changed[count++]=reg;} + io.write(reg,v);++r.writes;io.read(reg);return true; + }; + const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t value) { + r.reg=reg; + for(unsigned i=0;i<1000;++i) { + r.value=io.read(reg); + if(r.value==UINT32_MAX || !stable()){r.status=Status::Drift;return false;} + if((r.value&mask)==value)return true; + io.delay(); + } + r.status=Status::Timeout;return false; + }; + for(unsigned p=r.primary+1;p<3;++p)if(r.active&(1U<=2 && uint32_t(now-frame)<0x10000000U){drained=true;break;} + io.delay(); + } + if(!drained){r.status=Status::Timeout;return r;} + r.stage=2; + if(!write(0x70008+bank,get(0x70008+bank)&~0x80000000U) || !wait(0x70008+bank,0xC0000000U,0))return r; + r.stage=3; + if(!write(0x60400+bank,get(0x60400+bank)&~(0x80000000U|(15U<<27)|(7U<<24))) || + !wait(0x60400+bank,0x80000000U,0))return r; + for(unsigned n=0;n<2;++n) { + const auto base=0x68170+0x800*p+0x100*n; + if(get(base+0x10)&0x80000000U) { + if(!write(base+0x10,0) || !wait(base+0x10,UINT32_MAX,0) || !write(base,0) || !write(base+4,0))return r; + } + } + r.stage=4; + if(!write(port.buffer,get(port.buffer)&~0x80000000U))return r; + if(((get(0x60400+bank)>>24)&7)==2 && + (!write(0x60540+bank,get(0x60540+bank)&~0x80000000U) || !wait(0x60540+bank,0x80000000U,0)))return r; + if(!wait(port.buffer,0x80000080U,0x80))return r; + r.stage=5; + if(!write(0x46140+4*p,get(0x46140+4*p)&0x0FFFFFFFU) || !wait(0x46140+4*p,0xF0000000U,0))return r; + r.parked|=1U<adlsDisplaySelected)return true; + static uint32_t stage {};static bool accepted {}; + if(__sync_val_compare_and_swap(&stage,0,1)!=0)return stage==2 && accepted; + AlderLakeS::Boot::Result result; + auto pci=OSDynamicCast(IOPCIDevice,provider); + if(pci && XELP::deviceProfile(pci).identity==self->xeProfile.identity && (pci->configRead16(4)&2)) { + auto map=pci->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { + struct IO { + volatile uint8_t *base; + bool readable(uint32_t reg){return reg<=0xFFFFFC;} + uint32_t read(uint32_t reg){return *reinterpret_cast(base+reg);} + void write(uint32_t reg,uint32_t value){*reinterpret_cast(base+reg)=value;} + void delay(){IODelay(100);} + } io {reinterpret_cast(map->getVirtualAddress())}; + result=AlderLakeS::Boot::prepare(io,true,self->adlsWiring); + } + if(map)map->release(); + } + // Unclassified firmware remains untouched. The live training guard still + // rejects it. A partially retired pipe must not enter native startup. + accepted=!result.writes || result.status==AlderLakeS::Boot::Status::Complete; + __atomic_store_n(&self->adlsBootConsumersFailed,!accepted,__ATOMIC_RELEASE); + IORegistryEntry::getRegistryRoot()->setProperty("ADLSBootConsumers",&result,sizeof(result)); + __atomic_store_n(&stage,2,__ATOMIC_RELEASE);return accepted; +} + bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady)) { @@ -12991,6 +13023,8 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { return false; } + if(!adlsPrepareBoot(provider))return false; + // r88: use the native provider-workloop mode so IRQ registration does not // depend on a WindowServer framebuffer open after accelerator startup. if (callbackXE->tglAcceleratorRequested && checkKernelArgument("-disablegfxfirmware")) { @@ -13075,7 +13109,8 @@ uint32_t XE::wrapTGLLaneMask(void *port) { bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || - (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return false; + (callbackXE->adlsDisplaySelected && (!callbackXE->adlsDisplayHooksReady || + __atomic_load_n(&callbackXE->adlsBootConsumersFailed,__ATOMIC_ACQUIRE))))return false; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { SYSLOG("igfx", "AlderLakeN native RCS profile qualification rejected accelerator start"); return false; @@ -16772,6 +16807,14 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) const uint32_t buffer = *reinterpret_cast(mmio + (0x64000 + 0x100 * lease.ddi)); // physical port, not pipe-indexed const uint32_t pipeState = *reinterpret_cast(mmio + 0x70008 + 0x1000*pipe); if (((func | tp | buffer) & 0x80000000U) || (clock & 0xF0000000U) || (pipeState & 0xC0000000U)) { + // Temporary diagnosis: report the guard's existing powered reads once + // per port. No extra MMIO or power lifetime is introduced. + static uint32_t guardObserved {}; + if (!(__sync_fetch_and_or(&guardObserved,1U<setProperty(key,values,sizeof(values)); + } // No training register was changed. Drop only this reservation and its // power reference; never restore over unexpected live hardware. tglObserveTrainingLease(lease, 0, 5); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index e87af5b4..f15d6416 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -395,6 +395,8 @@ class XE { bool tglHDMIWiringLoaded {},tglHDMIWiringExplicit {},tglHDMIWiringApplied {}; uint32_t tglHDMIBackendMask {3}; // TGL-U combo PHY A/B; C is absent static bool tglLoadHDMIWiring(IOService *provider); + static bool adlsPrepareBoot(IOService *provider); + bool adlsBootConsumersFailed {false}; bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDisplaySelected?adlsWiring.matches(config,false):tglHDMIWiring.matches(config));} bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDisplaySelected?adlsWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} diff --git a/tests/adls_boot.cpp b/tests/adls_boot.cpp new file mode 100644 index 00000000..cf1a2ca4 --- /dev/null +++ b/tests/adls_boot.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include "kern_adls_boot.hpp" +using namespace AlderLakeS; +struct IO { + std::map m;std::vector writes; + unsigned ticks=0;bool stallFrames=false,stallPipe=false,stallBuffer=false,drift=false,driftConfig=false; + bool readable(uint32_t r){return r<0x1000000;} + uint32_t read(uint32_t r){return m[r];} + void write(uint32_t r,uint32_t v){ + writes.push_back(r); + for(unsigned p=0;p<3;++p){ + auto b=p*0x1000; + if(r==0x70008+b && !(v&0x80000000)){ + assert(!(m[0x70180+b]&0x80000000) && ticks>=2); + if(!stallPipe)v&=~0x40000000U; + } + if(r==0x60400+b && !(v&0x80000000)){ + auto encoding=(m[r]>>27)&15; + if(encoding && !stallBuffer)m[0x64000+0x100*(encoding-1)]|=0x80; + if(encoding && driftConfig)m[0x64000+0x100*(encoding-1)]^=1; // live idle indication after transport consumer stops + } + if(r==0x46140+4*p && !(v&0xf0000000)){ + assert(!(m[0x70008+b]&0xc0000000) && !(m[0x60400+b]&0x80000000) && !(m[0x60540+b]&0x80000000)); + } + } + if((r==0x64000 || r==0x64300 || r==0x64400) && !(v&0x80000000)){ + if(!stallBuffer)v|=0x80; + } + m[r]=v; + } + void delay(){++ticks;for(unsigned p=0;p<3;++p)if(!stallFrames && (m[0x70008+0x1000*p]&0x80000000))++m[0x70040+0x1000*p]; + if(drift && !writes.empty())m[0x70188]^=1; + } + IO(){m[0x45404]=0xff;m[0x164280]=0x01e07c00;m[0x1642bc]=0x30;for(unsigned d:{0U,3U,4U,5U,6U})m[0x64000+0x100*d]=0x80;} + void gop(unsigned p,unsigned d,bool dp){ + auto b=0x1000*p;Port port;assert(Port::resolve(d,port)); + m[0x70008+b]=0xc0000024;m[0x60400+b]=0x80000000|((d+1)<<27)|(dp?2U<<24:0); + m[0x60540+b]=dp?0x80040300:0;m[0x46140+4*p]=(d+1)<<28; + m[port.buffer]=0x80000000|(dp?6:0);m[port.clock]&=~port.clockOff; + m[0x70180+b]=0x84000008;m[0x70188+b]=120;m[0x70190+b]=0x0437077f; + m[0x60000+b]=0x0897077f;m[0x6000c+b]=0x04640437; + } +}; +Wiring wiring(){Wiring w;w.valid=true;w.hdmi=1U<<3;w.dp=1|(1U<<4);return w;} +void protectedState(IO &io,const std::map&before,unsigned primary){ + for(auto pair:before){auto reg=pair.first; + bool peer=(reg>=0x60000+0x1000*primary && reg<0x61000+0x1000*primary) || + (reg>=0x70000+0x1000*primary && reg<0x71000+0x1000*primary && reg!=0x70040+0x1000*primary) || + reg==0x46140+4*primary || reg==0x45404 || reg==0x164280 || reg==0x1642bc || (reg>=0x46010&®<=0x46030); + if(peer)assert(io.m[reg]==pair.second); + } +} +int main(){ + unsigned cases=0; + for(unsigned first=0;first<2;++first)for(unsigned count=2;count<=3-first;++count){ + IO io;io.gop(first,3,false);io.gop(first+1,4,true);if(count==3)io.gop(2,0,true); + auto before=io.m;auto r=Boot::prepare(io,true,wiring()); + assert(r.status==Boot::Status::Complete && r.primary==first && r.parked==(((1U<=0x46010&®<=0x46030)); + ++cases; + } + // Physical output order is independent of pipe order, including a DP primary. + const unsigned orders[][3] {{0,3,4},{0,4,3},{3,0,4},{3,4,0},{4,0,3},{4,3,0}}; + for(const auto &ports:orders){IO io;for(unsigned p=0;p<3;++p)io.gop(p,ports[p],ports[p]!=3); + const auto before=io.m;auto r=Boot::prepare(io,true,wiring()); + assert(r.status==Boot::Status::Complete && r.primary==0 && r.parked==6);protectedState(io,before,0);++cases; + } + // Stop/ack failures retain remaining consumers and every primary/shared field. + for(unsigned f=0;f<5;++f){IO io;io.gop(0,3,false);io.gop(1,4,true); + io.stallFrames=f==0;io.stallPipe=f==1;io.stallBuffer=f==2;io.drift=f==3;io.driftConfig=f==4; + auto before=io.m;auto r=Boot::prepare(io,true,wiring());assert(r.status==Boot::Status::Timeout || r.status==Boot::Status::Drift); + assert(!r.parked);if(f!=3)protectedState(io,before,0); + assert(io.m[0x46144]==0x50000000);++cases; + } + // Reject foreign/ambiguous firmware before any write. + for(unsigned f=0;f<9;++f){IO io;io.gop(0,3,false);io.gop(1,4,true);auto w=wiring(); + switch(f){case 0:io.m[0x71280]=0x80000000;break; + case 1:io.m[0x71180]|=1U<<10;break;case 2:io.m[0x61540]|=1U<<27;break; + case 3:io.m[Display13FBIRQ::DSBControl(1,0)]=1;break; + case 4:io.m[0x61400]=io.m[0x60400];break;case 5:w.dp=1;break; + case 6:io.m[0x46144]=0x70000000;break;case 7:io.m[0x711ac]=4096;break; + case 8:io.m[0x61404]=1;break; + } + auto r=Boot::prepare(io,true,w);assert(r.status==Boot::Status::Topology && io.writes.empty());++cases; + } + {IO io;io.gop(0,3,false);assert(Boot::prepare(io,true,wiring()).status==Boot::Status::Unchanged && io.writes.empty());++cases;} + {IO io;io.gop(0,3,false);io.gop(1,4,true);assert(Boot::prepare(io,false,wiring()).status==Boot::Status::Unavailable && io.writes.empty());++cases;} + printf("PASS %u ADL-S firmware consumer cases: multi-GOP retirement, first-active preservation, timeout and foreign-state rejection\n",cases); +} diff --git a/tests/adls_boot_runtime.py b/tests/adls_boot_runtime.py new file mode 100644 index 00000000..cc5c80db --- /dev/null +++ b/tests/adls_boot_runtime.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""A rejected ADL-S boot consumer transition must not start the accelerator.""" +from pathlib import Path +import subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('bool XE::wrapTGLAcceleratorStart(');b=s.index(' if (callbackXE->adlnDetected',a) +entry=s[a:b]+' return true;\n}' +code=''' +#include +struct IOService {}; +struct XE { + bool display12SmallGTDetected=false,display12SmallGTReady=true; + bool adlsDisplaySelected=true,adlsDisplayHooksReady=true,adlsBootConsumersFailed=false; + static bool wrapTGLAcceleratorStart(IOService*,IOService*); +} xe,*callbackXE=&xe; +'''+entry+''' +int main(){ + assert(XE::wrapTGLAcceleratorStart(nullptr,nullptr)); + xe.adlsBootConsumersFailed=true; + assert(!XE::wrapTGLAcceleratorStart(nullptr,nullptr)); + xe.adlsDisplaySelected=false; + assert(XE::wrapTGLAcceleratorStart(nullptr,nullptr)); + xe.adlsDisplaySelected=true;xe.adlsBootConsumersFailed=false;xe.adlsDisplayHooksReady=false; + assert(!XE::wrapTGLAcceleratorStart(nullptr,nullptr)); +} +''' +with tempfile.TemporaryDirectory(prefix='adls-boot-runtime-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined',str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) +print('PASS actual accelerator admission: failed ADL-S boot retirement blocks native start; unrelated backend unchanged') diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 25b7ba2c..b5fe488d 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -67,6 +67,7 @@ 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; + bool adlsBootConsumersFailed=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; bool tglProgramEnabled=false, tglTrainingLeaseRequested=false; diff --git a/tools/test.py b/tools/test.py index 1460382a..dc01094c 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','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=['framebuffer_training_registration','framebuffer_common','display13_shared_registration','adlp_render','firmware_readout','display13_offline','adlp_fb_candidate','display13_tc_native','display13_ddc','adlp_load_only','small_gt_urb','display_backend_dispatch','tgl_agdc_port_status','display_contract_adapter','tgl_agdc_aux_observer','xe_data_access','adls_aux_parent','adls_lane_power','adls_clock_guard','adls_clock_runtime','adls_boot_runtime','xelp_mmio_bar','bigsur_pmap','tahoe_iosurface','tahoe_pmap','sequoia_pmap','sonoma_iosurface','sonoma_pmap','ventura_pmap','tgl_kernel_admission','monterey_pmap','xelp_forcewake_irq','boot_arguments','tgl_display_observers','tgl_stream_ack','tgl_transmitter_observer','tgl_transition_live','tgl_aux_live','tgl_bootstrap_host','tgl_clock_order','provider_binding','tgl_start_order','tgl_hdmi_wiring','tgl_bootstrap_aux','tgl_copy_routing','tgl_training_leases','tgl_wiring','tgl_boot_readout','tgl_pcode_gate','tgl_port_use','tgl_tc_cold','tgl_fia','tgl_port_map','tgl_dp_alt_fia','tgl_dp_mode_replace','tgl_phy_clock_gating','tgl_probe','dvmt','host_publication'] jobs=[(p.stem,['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(R/'WhateverXe'),str(p),'-o',str(O/p.stem)],[str(O/p.stem)]) for p in sorted((R/'tests').glob('*.cpp')) if not p.stem.endswith('_fixture')] python_tests += ['tgl_power_fuses', 'display13_boot_aux', 'display13_aux', 'display13_sst', 'display13_combo_clock', 'adls_panel_pwm', 'tgl_panel_timing', 'backlight_adapter', 'tgl_panel_power', 'tgl_edp_transcoder', 'tgl_training_pattern', 'tgl_combo_pll_write', 'tgl_sink_framing', 'tgl_combo_aux_gate', 'tgl_combo_signal_guard', 'tgl_combo_aux_lifetime', 'tgl_iosurface_compression', 'tgl_panel_depth_route', 'display13_tiling'] python_tests += ['display13_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] From d5f0f07689322f7f10d96b5f51bda79801edc8cf Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:54:13 +0900 Subject: [PATCH 49/60] Wait for Combo AUX readiness before enabling fixed DP links --- WhateverXe/kern_xe.cpp | 88 ++++++++++++++++++++++++++++----- tests/tgl_aux_live.py | 15 +++++- tests/tgl_combo_aux_lifetime.py | 33 +++++++++---- 3 files changed, 113 insertions(+), 23 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e8a36a37..3b188bc6 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3268,6 +3268,14 @@ bool tglMEILiveSerialize(void *, void *, OSSerialize *serializer) { return result; } +// Bounded main-link AUX decisions. Copy only values already inspected by the +// lifetime guard; collection does not issue MMIO or native power operations. +struct TGLComboAUXDecision { uint64_t ns; uint32_t word[14]; }; +static TGLComboAUXDecision tglComboAUXFirst[64] {},tglComboAUXLast[64] {}; +static uint32_t tglComboAUXCount {}; +static void tglComboAUXDecisionRecord(uint32_t *word); +static bool tglComboAUXDecisionSerialize(void *,void *,OSSerialize *); + struct TGLAUXRecord { uint64_t ns, thread; uint32_t word[28]; @@ -3281,6 +3289,27 @@ uint32_t tglAUXCounts[9] {}; TGLAUXRecord tglAUXControl[9][64] {}; uint32_t tglAUXControlCounts[9] {}; +static void tglComboAUXDecisionRecord(uint32_t *word) { + if(!tglAUXLock)return; + TGLComboAUXDecision r {};uint64_t ticks;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&r.ns); + memcpy(r.word,word,sizeof(r.word)); + IOSimpleLockLock(tglAUXLock);r.word[0]=++tglComboAUXCount; + if(r.word[0]<=64)tglComboAUXFirst[r.word[0]-1]=r; + tglComboAUXLast[(r.word[0]-1)%64]=r;IOSimpleLockUnlock(tglAUXLock); +} +static bool tglComboAUXDecisionSerialize(void *,void *,OSSerialize *serializer) { + if(!tglAUXLock)return false; + constexpr size_t size=16+sizeof(tglComboAUXFirst)+sizeof(tglComboAUXLast); + auto copy=static_cast(IOMalloc(size));if(!copy)return false; + auto h=reinterpret_cast(copy);h[0]=1;h[2]=64;h[3]=sizeof(TGLComboAUXDecision); + IOSimpleLockLock(tglAUXLock);h[1]=tglComboAUXCount; + memcpy(copy+16,tglComboAUXFirst,sizeof(tglComboAUXFirst)); + memcpy(copy+16+sizeof(tglComboAUXFirst),tglComboAUXLast,sizeof(tglComboAUXLast)); + IOSimpleLockUnlock(tglAUXLock); + auto data=OSData::withBytes(copy,size);IOFree(copy,size);if(!data)return false; + const bool ok=data->serialize(serializer);data->release();return ok; +} + // Bounded lifecycle evidence, separate from bulk AUX. Capture arguments and // existing native results only; never perform diagnostic hardware accesses. struct TGLTransitionRecord { uint64_t q[16]; }; @@ -12983,6 +13012,9 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(auto live=OSSerializer::forTarget(nullptr,tglDPCDSerialize)) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPCDLive",live);live->release(); } + if(auto live=OSSerializer::forTarget(nullptr,tglComboAUXDecisionSerialize)) { + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeComboAUXDecisions",live);live->release(); + } for(uintptr_t ddi=0;ddi<=8;++ddi) { if(ddi==2)continue; // TGL combo A/B and TC D..I; no combo C. if(auto live=OSSerializer::forTarget(nullptr,tglAUXLiveSerialize,reinterpret_cast(ddi))) { @@ -18873,46 +18905,66 @@ void XE::wrapTGLPortWriteReg(void *port, uint32_t reg, uint32_t value) { // must not accumulate refs and an unrelated port cannot release our reference. void XE::tglComboAUXReference(void *port,bool acquire) { auto s=callbackXE; + // seq, reason, DDI, acquire, type, flags, mode, owner, IO, buffer, + // native refs before/after, AUX state, wait us. UINT_MAX means unsampled. + uint32_t observed[14] {0,0,UINT32_MAX,acquire,UINT32_MAX,UINT32_MAX,UINT32_MAX,0, + UINT32_MAX,UINT32_MAX,UINT32_MAX,UINT32_MAX,UINT32_MAX,0}; + const auto finish=[&](uint32_t reason){observed[1]=reason;tglComboAUXDecisionRecord(observed);}; auto p=static_cast(port); auto ctl=static_cast(s->tglObservationController); auto &f=s->tglTCNativeFunctions; // shared native AUX API, not TC admission if(!p || !ctl || !s->tglActive || !s->tglComboAUXLifetimeReady || !f.complete() || - (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || *f.controllerSlot!=ctl)return; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || *f.controllerSlot!=ctl)return finish(1); auto config=*reinterpret_cast(p+0x548); - const bool adls=s->adlsDisplaySelected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); + if(config) {observed[2]=config[0];observed[4]=config[2];observed[5]=config[3];} + observed[6]=*reinterpret_cast(p+0x550); + const bool adls=s->adlsDisplaySelected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); const bool fixed=!s->adlsDisplaySelected && TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config); - if(s->adlsDisplaySelected && !adls)return; - if(!config || (config[0]>1 && !adls))return; + if(s->adlsDisplaySelected && !adls)return finish(2); + if(!config || (config[0]>1 && !adls))return finish(3); const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; - if(acquire ? owner!=nullptr : owner!=port)return; + observed[7]=owner==port?1:owner?2:0; + if(acquire ? owner!=nullptr : owner!=port)return finish(4); auto power=*reinterpret_cast(ctl+0xC8); auto access=*reinterpret_cast(ctl+0xC40); if(!power || !access || *reinterpret_cast(power+0x10)!=ctl || *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(p+0x578)!=access || *reinterpret_cast(access+0x60)<(0x64004U+0x100*ddi) || - !*reinterpret_cast(access+0x50))return; + !*reinterpret_cast(access+0x50))return finish(5); const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); - if(*reinterpret_cast(p+0x590)!=buffer)return; + if(*reinterpret_cast(p+0x590)!=buffer)return finish(6); XELP::Display12SSTRoute route; const XELP::Display12SSTRoutes routes{adls?s->adlsWiring.dp:fixed?s->tglComboDPMask:0,0,0}; if(acquire && (config[3]!=0 || p[0x117] || !routes.resolve(ddi,config[2],*reinterpret_cast(p+0x550),route) || !route.retainComboAUX() || !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[ddi].dp() || - (!adls && !fixed && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return; + (!adls && !fixed && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return finish(7); const auto io=wrapTGLRegisterRead(access,0x45454); - if(io==~0U || (io&mask)!=mask)return; + observed[8]=io; + if(io==~0U || (io&mask)!=mask)return finish(8); const auto bufferState=wrapTGLRegisterRead(access,buffer); - if(bufferState==~0U || (bufferState&0x80000000U))return; + observed[9]=bufferState; + if(bufferState==~0U || (bufferState&0x80000000U))return finish(9); auto &refs=*reinterpret_cast(power+0x54+4*ddi); - const uint32_t before=refs; + const uint32_t before=refs;observed[10]=before; uint32_t aux=0,reason=0; if(acquire) { - if(before==~0U)return; + if(before==~0U)return finish(10); f.getAux(port); aux=wrapTGLRegisterRead(access,0x45444); + // Native AUX get waits only 20 x IODelay(1). Linux v6.12 + // hsw_wait_for_power_well_enable allows 1ms (AUX has no specified + // 20us limit). Keep our reference while the owned request settles. + // Stop on request loss/unavailable MMIO, never turn on the transmitter + // to make AUX ready: Combo AUX must be ready before DDI_BUF_ENABLE. + for(unsigned elapsed=0;refs==before+1 && aux!=~0U && + (aux&mask)!=(mask) && (aux&(mask&0xAAAAAAAAU)) && elapsed<1000;elapsed+=10) { + IODelay(10);aux=wrapTGLRegisterRead(access,0x45444); + observed[13]=elapsed+10; + } if(refs==before+1 && aux!=~0U && (aux&mask)==mask)owner=port; else { reason=1; @@ -18925,6 +18977,8 @@ void XE::tglComboAUXReference(void *port,bool acquire) { } else reason=2; const uint64_t detail[]={before,refs,aux,bufferState,owner==port}; tglTransitionRecord(74,true,port,ddi,acquire,reason,detail,sizeof(detail)); + observed[7]=owner==port?1:owner?2:0;observed[11]=refs;observed[12]=aux; + finish(reason?10+reason:0); } uint32_t XE::wrapTGLDisablePort(void *port) { @@ -18939,6 +18993,16 @@ uint32_t XE::wrapTGLEnablePort(void *port) { tglTrace(21, id, false, 0, 0, 0, 0); tglSnapshotTransmitter(port, false, 0); tglComboAUXReference(port,true); + // A qualified fixed Combo link cannot train after its prerequisite failed. + // Return through the native failure path before any buffer-enable write. + const auto s=callbackXE; + const bool fixedDP=config && (s->adlsDisplaySelected ? + s->adlsWiring.boundSST(s->tglPlatformCopy,config) : + TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config)); + if(fixedDP && s->tglComboAUXOwners[id]!=port) { + tglTrace(21,id,true,kIOReturnNotReady,0,kIOReturnNotReady,0); + return kIOReturnNotReady; + } const auto original = FunctionCast(wrapTGLEnablePort, callbackXE->orgTGLEnablePort)(port); if(original)tglComboAUXReference(port,false); // only releases if buffer stayed off auto r = original; diff --git a/tests/tgl_aux_live.py b/tests/tgl_aux_live.py index 237bf07a..1bde887c 100644 --- a/tests/tgl_aux_live.py +++ b/tests/tgl_aux_live.py @@ -3,13 +3,15 @@ from pathlib import Path import re,subprocess,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() -bodies='\n'.join(re.search(p,s,re.S).group() for p in [r'void tglAUXRecord\(.*?\n\}',r'static bool tglAUXControlSerialize\(.*?\n\}',r'void tglDPCDRecord\(.*?\n\}',r'static bool tglDPCDSerialize\(.*?\n\}',r'static bool tglAUXLiveSerialize\(.*?\n\}']) +bodies='\n'.join(re.search(p,s,re.S).group() for p in [r'static void tglComboAUXDecisionRecord\(uint32_t \*word\) \{.*?\n\}',r'static bool tglComboAUXDecisionSerialize\(void \*,void \*,OSSerialize \*serializer\) \{.*?\n\}',r'void tglAUXRecord\(.*?\n\}',r'static bool tglAUXControlSerialize\(.*?\n\}',r'void tglDPCDRecord\(.*?\n\}',r'static bool tglDPCDSerialize\(.*?\n\}',r'static bool tglAUXLiveSerialize\(.*?\n\}']) f=r'''#include #include #include #include #include #include +struct TGLComboAUXDecision {uint64_t ns;uint32_t word[14];}; +TGLComboAUXDecision tglComboAUXFirst[64]{},tglComboAUXLast[64]{};uint32_t tglComboAUXCount{}; struct TGLAUXRecord { uint64_t ns,thread;uint32_t word[28]; }; TGLAUXRecord tglAUXFirst[9][256]{},tglAUXLast[9][128]{},tglAUXControl[9][64]{}; uint32_t tglAUXCounts[9]{},tglAUXControlCounts[9]{}; @@ -37,6 +39,17 @@ }; // BODIES int main(){ + {OSSerialize out;uint32_t w[14]{}; + for(unsigned i=0;i<160;++i){w[1]=i;tglComboAUXDecisionRecord(w);} + assert(tglComboAUXDecisionSerialize(nullptr,nullptr,&out)); + assert(out.bytes.size()==16+128*sizeof(TGLComboAUXDecision)); + uint32_t h[4];memcpy(h,out.bytes.data(),16);assert(h[0]==1&&h[1]==160&&h[2]==64&&h[3]==64); + for(unsigned i=0;i<64;++i){TGLComboAUXDecision v;memcpy(&v,&out.bytes[16+i*64],64);assert(v.word[0]==i+1&&v.word[1]==i);} + for(unsigned i=96;i<160;++i){TGLComboAUXDecision v;memcpy(&v,&out.bytes[16+4096+(i%64)*64],64);assert(v.word[0]==i+1&&v.word[1]==i);} + failAlloc=true;assert(!tglComboAUXDecisionSerialize(nullptr,nullptr,&out));failAlloc=false; + failData=true;assert(!tglComboAUXDecisionSerialize(nullptr,nullptr,&out));failData=false; + } + alignas(8) uint8_t port[0x600]{};uint32_t config[]={4}; *reinterpret_cast(port+0x548)=config; auto noRead=mmap(nullptr,4096,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0);assert(noRead!=MAP_FAILED); diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index c14e19e0..36b7678e 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -4,6 +4,7 @@ import subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() a=s.index('void XE::tglComboAUXReference(');fn=s[a:s.index('uint32_t XE::wrapTGLDisablePort',a)].replace('XE::tglComboAUXReference','run') +a=s.index('uint32_t XE::wrapTGLEnablePort(');enable=s[a:s.index('// Exact 20A2314a table.',a)].replace('XE::wrapTGLEnablePort','enable').replace('tglComboAUXReference','run') if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert b[0x5fc56:0x5fc5f]==bytes.fromhex('554889e553504889fb');assert b[0x5fc6d:0x5fc73]==bytes.fromhex('8bb790050000') code=r''' @@ -14,21 +15,28 @@ #include #include struct Functions {bool ready=true;void **controllerSlot=nullptr;void(*getAux)(void*)=nullptr;void(*putAux)(void*)=nullptr;bool complete(){return ready;}}; -struct State {uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +struct State {uint32_t (*orgTGLEnablePort)(void*)=nullptr;uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts;Functions tglTCNativeFunctions;void *tglComboAUXOwners[9]{}; } state,*callbackXE=&state; -std::map regs;unsigned getCount,putCount,reads;bool missingAck;uint32_t *refs; +std::map regs;unsigned getCount,putCount,reads,nativeEnableCount;bool missingAck;unsigned elapsed,ackDelay;bool dropRequest;unsigned badForGet;uint32_t *refs; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return regs[reg];} +void IODelay(unsigned us){elapsed+=us;if(dropRequest)regs[0x45444]=0;else if(ackDelay && elapsed>=ackDelay)regs[0x45444]=regs[0x45454];} void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} +void tglComboAUXDecisionRecord(uint32_t *){} FUNCTION +#define FunctionCast(name,target) (target) +constexpr uint32_t kIOReturnNotReady=0xe00002d8U; +template void tglTrace(A...){} +void tglSnapshotTransmitter(void*,bool,unsigned){} +ENABLE int main(){ - for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<15;++bad){ + for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<18;++bad){ const bool adls=kind==1||kind==3,fixed=kind==2;const bool panel=kind==3;if(panel && testDDI!=0)continue;if(adls?testDDI==1:testDDI>1)continue; alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t local[6]{};uint32_t *config=local; unsigned ddi=testDDI,mask=3U<<(2*ddi),buffer=0x64000+256*ddi; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; - state={};if(adls) { + state={};nativeEnableCount=0;state.orgTGLEnablePort=[](void*){++nativeEnableCount;return 0U;};if(adls) { state.adlsDisplaySelected=true;state.adlsWiring.valid=true;state.adlsWiring.dp=1U<(state.tglPlatformCopy+0x20+24*ddi);const uint32_t c[]={ddi,ddi?ddi+6:1,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); } @@ -43,9 +51,9 @@ store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(p,0x590,buffer); store(ctl,0xC8,power+0);store(ctl,0xC40,access+0);store(power,0x10,ctl+0);store(power,0x78,access+0); store(access,0x60,0x200000U);store(access,0x50,ctl+0); - refs=reinterpret_cast(power+0x54+4*ddi);*refs=2;getCount=putCount=reads=0;missingAck=bad==14; + badForGet=bad;refs=reinterpret_cast(power+0x54+4*ddi);*refs=2;getCount=putCount=reads=0;missingAck=bad>=14;elapsed=0;ackDelay=bad==15?500:0;dropRequest=bad==16; regs.clear();regs[0x45454]=mask;regs[buffer]=0x81; - state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?0:regs[0x45454];}; + state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?(badForGet==17?~0U:regs[0x45454]&0xAAAAAAAAU):regs[0x45454];}; state.tglTCNativeFunctions.putAux=[](void*){--*refs;++putCount;}; if(bad==1)state.tglActive=false; if(bad==2)state.tglComboAUXLifetimeReady=false; @@ -61,9 +69,14 @@ if(bad==12){state.xeProfile.family=XELP::Family::AlderLakeN;state.adlsDisplaySelected=false;} if(bad==13)*refs=~0U; auto before=*refs;run(p,true); - if(bad){assert(*refs==before&&!state.tglComboAUXOwners[ddi]);assert(getCount==(bad==14));assert(putCount==(bad==14));continue;} - assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[ddi]==p); - run(p,true);assert(getCount==1); // duplicate enable is idempotent + if(bad && bad!=15){assert(*refs==before&&!state.tglComboAUXOwners[ddi]);assert(getCount==(bad>=14));assert(putCount==(bad>=14)); + if(bad>=14) { + assert(elapsed==(bad==14?1000:bad==16?10:0)); + if(adls||fixed){assert(enable(p)==kIOReturnNotReady);assert(nativeEnableCount==0);} + } + continue;} + assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[ddi]==p);assert(elapsed==(bad==15?500:0)); + assert(enable(p)==0 && nativeEnableCount==1);run(p,true);assert(getCount==1); // duplicate enable is idempotent // Native per-transaction ref pairs cannot consume the held main-link ref. ++*refs;--*refs;assert(*refs==3); run(nullptr,false);assert(putCount==0); @@ -73,7 +86,7 @@ } puts("PASS main-link AUX acquire/duplicate/native transaction/disable/retry, inherited refs, ownership/power/ACK guards"); } -'''.replace('FUNCTION',fn) +'''.replace('FUNCTION',fn).replace('\nENABLE\n','\n'+enable+'\n') with tempfile.TemporaryDirectory() as tmp: p=Path(tmp);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) From e980280c1ed6379ac903856ecd2f3bbdbbe5ade4 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 08:58:28 +0900 Subject: [PATCH 50/60] Cover first and inherited AUX references in readiness tests --- tests/tgl_combo_aux_lifetime.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index 36b7678e..3e318600 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -31,7 +31,7 @@ void tglSnapshotTransmitter(void*,bool,unsigned){} ENABLE int main(){ - for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<18;++bad){ + for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<18;++bad)for(unsigned initialRefs:{0u,2u}){ const bool adls=kind==1||kind==3,fixed=kind==2;const bool panel=kind==3;if(panel && testDDI!=0)continue;if(adls?testDDI==1:testDDI>1)continue; alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t local[6]{};uint32_t *config=local; unsigned ddi=testDDI,mask=3U<<(2*ddi),buffer=0x64000+256*ddi; @@ -51,7 +51,7 @@ store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(p,0x590,buffer); store(ctl,0xC8,power+0);store(ctl,0xC40,access+0);store(power,0x10,ctl+0);store(power,0x78,access+0); store(access,0x60,0x200000U);store(access,0x50,ctl+0); - badForGet=bad;refs=reinterpret_cast(power+0x54+4*ddi);*refs=2;getCount=putCount=reads=0;missingAck=bad>=14;elapsed=0;ackDelay=bad==15?500:0;dropRequest=bad==16; + badForGet=bad;refs=reinterpret_cast(power+0x54+4*ddi);*refs=initialRefs;getCount=putCount=reads=0;missingAck=bad>=14;elapsed=0;ackDelay=bad==15?500:0;dropRequest=bad==16; regs.clear();regs[0x45454]=mask;regs[buffer]=0x81; state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?(badForGet==17?~0U:regs[0x45454]&0xAAAAAAAAU):regs[0x45454];}; state.tglTCNativeFunctions.putAux=[](void*){--*refs;++putCount;}; @@ -75,14 +75,14 @@ if(adls||fixed){assert(enable(p)==kIOReturnNotReady);assert(nativeEnableCount==0);} } continue;} - assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[ddi]==p);assert(elapsed==(bad==15?500:0)); + assert(getCount==1&&*refs==initialRefs+1&&state.tglComboAUXOwners[ddi]==p);assert(elapsed==(bad==15?500:0)); assert(enable(p)==0 && nativeEnableCount==1);run(p,true);assert(getCount==1); // duplicate enable is idempotent // Native per-transaction ref pairs cannot consume the held main-link ref. - ++*refs;--*refs;assert(*refs==3); + ++*refs;--*refs;assert(*refs==initialRefs+1); run(nullptr,false);assert(putCount==0); regs[buffer]=0x80000001;run(p,false);assert(putCount==0); // active main link - regs[buffer]=0x81;run(p,false);assert(putCount==1&&*refs==2&&!state.tglComboAUXOwners[ddi]); - run(p,false);assert(putCount==1);run(p,true);assert(getCount==2);run(p,false);assert(putCount==2&&*refs==2); + regs[buffer]=0x81;run(p,false);assert(putCount==1&&*refs==initialRefs&&!state.tglComboAUXOwners[ddi]); + run(p,false);assert(putCount==1);run(p,true);assert(getCount==2);run(p,false);assert(putCount==2&&*refs==initialRefs); } puts("PASS main-link AUX acquire/duplicate/native transaction/disable/retry, inherited refs, ownership/power/ACK guards"); } From f9d28a9a32fc5f1a32845096bb139ccc03429e29 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:21:18 +0900 Subject: [PATCH 51/60] Avoid absent ADL-S DDI C request during native release --- WhateverXe/kern_xe.cpp | 8 ++++++++ tests/adls_lane_power.py | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3b188bc6..37d6566f 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -18459,6 +18459,14 @@ void XE::wrapTGLPowerDDIState(void *power, bool enable, uint32_t requested) { // Native reference counts remain untouched. The PG target is the union of // the native pipe consumers and the current TC1/TC2 lane consumers. auto adls=adlsPowerTarget(power); + // C004FC1A disablePowerWellDDI (0x5AD86..0x5ADDF) derives the + // DDI C request from DDI TC1's refs a second time. ADL-S has no + // physical DDI C. Remove only that duplicate on release, preserving + // unexpected C consumers, every real port request and native ACK reads. + if(adls && !enable && (callbackXE->adlsWiring.selected()&8U) && + !*reinterpret_cast(adls+0x38) && + *reinterpret_cast(adls+0x3C) && (requested&0xA0U)==0xA0U) + requested&=~0x20U; const auto laneMask=callbackXE->adlsWiring.laneMask(); if(adls && (requested&laneMask)) { const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); diff --git a/tests/adls_lane_power.py b/tests/adls_lane_power.py index 299cfe36..26d2269b 100644 --- a/tests/adls_lane_power.py +++ b/tests/adls_lane_power.py @@ -53,11 +53,25 @@ calls.clear();XE::wrapADLSPowerPG(power,true,2); assert(calls.size()==1 && calls[0].target==(expected|2));++count; } + // Observed donor release masks duplicate TC1 into absent DDI C. Real + // TC1/TC2 and A consumers must survive; no ACK/ref state is fabricated. + for(unsigned refsC:{0U,1U,7U})for(unsigned refsTC1:{0U,1U,7U}) + for(bool on:{false,true})for(unsigned mask:{0x20U,0x80U,0xA0U,0xA2U,0x280U,0x2A0U,0x2A2U}){ + state.adlsWiring.hdmi=8;state.adlsWiring.dp=17; + at(power,0x38)=refsC;at(power,0x3C)=refsTC1; + uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power)); + calls.clear();XE::wrapTGLPowerDDIState(power,on,mask); + unsigned expected=mask; + if(!on && !refsC && refsTC1 && (mask&0xA0)==0xA0)expected&=~0x20U; + unsigned seen=0;for(auto c:calls)if(c.kind==1){assert(c.target==expected && c.enable==on);++seen;} + assert(seen==1 && !memcmp(before,power,sizeof(power)));++count; + } for(unsigned bad=0;bad<6;++bad){ state.adlsDisplaySelected=bad!=0;state.adlsPowerReady=bad!=1;state.adlsWiring.valid=bad!=2; state.adlsWiring.hdmi=bad==3?0:8;state.adlsWiring.dp=0;at(ctl,0xC8)=bad==4?nullptr:power;at(power,0x78)=bad==5?nullptr:access; assert(!XE::adlsPowerTarget(power));calls.clear();XE::wrapADLSPowerPG(power,false,2);assert(calls.size()==1&&calls[0].target==2); - calls.clear();XE::wrapTGLPowerDDIState(power,false,0x80);assert(calls.size()==1&&calls[0].kind==1); + at(power,0x38)=0;at(power,0x3C)=1; + calls.clear();XE::wrapTGLPowerDDIState(power,false,0xA0);assert(calls.size()==1&&calls[0].kind==1&&calls[0].target==0xA0); } printf("PASS %u lane/pipe conditions; acquire/release order; unchanged refs; 6 owner/profile rejections\n",count); } From 7bf717cdaeffe04d366a73fde0aa9b23ed024650 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 09:36:30 +0900 Subject: [PATCH 52/60] Replay ADL-S DDI power correction across supported connector assignments --- WhateverXe/kern_xe.cpp | 2 + tests/adls_ddi_power_native.py | 14 +++ tests/adls_ddi_power_native_fixture.cpp | 122 ++++++++++++++++++++++++ tools/test.py | 1 + 4 files changed, 139 insertions(+) create mode 100644 tests/adls_ddi_power_native.py create mode 100644 tests/adls_ddi_power_native_fixture.cpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 37d6566f..bf362846 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -18463,6 +18463,8 @@ void XE::wrapTGLPowerDDIState(void *power, bool enable, uint32_t requested) { // DDI C request from DDI TC1's refs a second time. ADL-S has no // physical DDI C. Remove only that duplicate on release, preserving // unexpected C consumers, every real port request and native ACK reads. + // The donor passes enable=false on both get and put. Direction is not + // inferred from that flag: only put generates this unreferenced C request. if(adls && !enable && (callbackXE->adlsWiring.selected()&8U) && !*reinterpret_cast(adls+0x38) && *reinterpret_cast(adls+0x3C) && (requested&0xA0U)==0xA0U) diff --git a/tests/adls_ddi_power_native.py b/tests/adls_ddi_power_native.py new file mode 100644 index 00000000..06dc7b26 --- /dev/null +++ b/tests/adls_ddi_power_native.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Replay exact donor DDI accounting and ACK polling through the production wrapper.""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile +r=Path(__file__).resolve().parents[1] +b=Path(sys.argv[1]).read_bytes() +assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('uint8_t *XE::adlsPowerTarget');z=s.index('// Software-only transmitter boundary.',a) +fixture=(r/'tests/adls_ddi_power_native_fixture.cpp').read_text().replace('// PRODUCTION_WRAPPERS',s[a:z]) +with tempfile.TemporaryDirectory(prefix='adls-ddi-native-') as d: + d=Path(d);(d/'fixture.cpp').write_text(fixture) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(r/'WhateverXe'),str(d/'fixture.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test'),str(Path(sys.argv[1]).resolve())],check=True) diff --git a/tests/adls_ddi_power_native_fixture.cpp b/tests/adls_ddi_power_native_fixture.cpp new file mode 100644 index 00000000..18bacd5d --- /dev/null +++ b/tests/adls_ddi_power_native_fixture.cpp @@ -0,0 +1,122 @@ +#include "kern_alderlake_s.hpp" +#include +#include +#include +#include +#include +#include +#define FunctionCast(fn,p) reinterpret_cast(p) +struct XE { + bool adlsDisplaySelected=true,adlsPowerReady=true;void *tglObservationController; + AlderLakeS::Wiring adlsWiring;uintptr_t orgADLSPowerPG,orgTGLPowerDDIState; + static uint8_t *adlsPowerTarget(void*); + static void wrapADLSPowerPG(void*,bool,uint32_t); + static void wrapTGLPowerDDIState(void*,bool,uint32_t); +}; +XE state;XE *callbackXE=&state; +templatevoid tglTransitionRecord(T...){} +templatevoid tglObserveActivation(T...){} +templateT &at(void*p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} +// PRODUCTION_WRAPPERS +alignas(8) uint8_t controller[0xF00]{},power[0x90]{},access[0x70]{},ports[9][0x600]{}; +uintptr_t vtable[64]{};uint8_t *image; +struct Event {unsigned kind,value;};std::vector events; +unsigned requested,ackMissing,delays,errors,reads; +void pg(void *p,bool,unsigned mask){assert(p==power);events.push_back({0,mask});} +void *lookup(unsigned d){assert(d<9);return ports[d];} +void writeReg(void *p,unsigned long reg,unsigned value){ + assert(p==access && (reg==0x45450 || reg==0x45454)); + events.push_back({unsigned(reg),value});if(reg==0x45454)requested=value; +} +uint32_t readReg(void *p,unsigned long reg){ + assert(p==access && reg==0x45454);++reads; + // Model only physical ADL-S IO wells. An unsupported request receives no + // ACK; explicit fault injection removes a real port's ACK as well. + unsigned ack=0; + for(unsigned d:{0u,3u,4u,5u,6u})if(!(ackMissing&(1U<>(2*d+1)&1)<<(2*d); + return requested|ack; +} +void delay(unsigned n){assert(n==1);++delays;} +void error(const char*,...){++errors;} +void jump(unsigned off,uintptr_t target){image[off]=0x48;image[off+1]=0xb8;memcpy(image+off+2,&target,8);image[off+10]=0xff;image[off+11]=0xe0;} +void redirect(unsigned site,unsigned stub,uintptr_t target,uint8_t opcode){assert(image[site]==opcode);jump(stub,target);int32_t rel=stub-site-5;memcpy(image+site+1,&rel,4);} +void setup(unsigned assignment){ + memset(power,0,sizeof(power));memset(controller,0,sizeof(controller)); + at(power,0x10)=controller;at(controller,0xC8)=power; + at(power,0x78)=access;at(controller,0xC40)=access; + XELP::VBT::Ports fw;fw.status=XELP::VBT::Status::Valid; + const unsigned physical[]={0,3,4},hdmiDVO[]={0,1,2},dpDVO[]={10,7,8}; + unsigned hdmi=0,dp=0; + for(unsigned n=0;n<3;++n){unsigned d=physical[n],kind=assignment%3;assignment/=3; + if(kind){ + AlderLakeS::Port route;assert(AlderLakeS::Port::resolve(d,route)); + fw.present|=1U<(ports[d],0x550)=0; + } + state.adlsWiring=AlderLakeS::Wiring::fromFirmware(fw); + assert(state.adlsWiring.valid && state.adlsWiring.hdmi==hdmi && state.adlsWiring.dp==dp && !state.adlsWiring.excluded); + events.clear();requested=ackMissing=delays=errors=reads=0; +} +unsigned expectedRequest(){unsigned out=0;for(unsigned d:{0u,3u,4u})if(at(power,0x30+4*d))out|=2U<<(2*d);return out;} +int main(int argc,char **argv){ + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{}); + image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0));assert(image!=MAP_FAILED);memcpy(image,b.data(),b.size()); + jump(0x1b1ea,(uintptr_t)lookup);jump(0x5e344,(uintptr_t)readReg); + redirect(0x5ae05,0x300000,(uintptr_t)XE::wrapTGLPowerDDIState,0xe9); + redirect(0x5c2a9,0x300000,(uintptr_t)XE::wrapTGLPowerDDIState,0xe9); + redirect(0x5c460,0x300020,(uintptr_t)delay,0xe8); + redirect(0x5c484,0x300040,(uintptr_t)error,0xe8); + redirect(0x5c4a3,0x300040,(uintptr_t)error,0xe9); + vtable[0x130/8]=(uintptr_t)writeReg;at(access,0)=vtable; + state.tglObservationController=controller;state.orgADLSPowerPG=(uintptr_t)pg; + state.orgTGLPowerDDIState=(uintptr_t)(image+0x5c2ae); + auto get=(void(*)(void*,unsigned))(image+0x5bfa8); + auto put=(void(*)(void*,unsigned))(image+0x5aa86); + // Reproduce the observed defect with the correction gated off: acquiring + // TC1/TC2 works, releasing TC2 requests absent C while TC1 remains held. + setup(12);state.adlsDisplaySelected=false; + get(power,3);get(power,4);assert(requested==0x280 && !errors); + put(power,4);assert(requested==0xA0 && delays==20 && errors==2); + state.adlsDisplaySelected=true; + unsigned assignments=0,sequences=0,operations=0; + for(unsigned assignment=1;assignment<27;++assignment){ + setup(assignment);std::vector acquire; + for(unsigned d:{0u,3u,4u})if(state.adlsWiring.selected()&(1U<(power,0x30+4*d);events.clear(); + if(on){get(power,d);++expectedRefs[d];}else{put(power,d);--expectedRefs[d];} + for(unsigned i=0;i<9;++i)assert(at(power,0x30+4*i)==expectedRefs[i]); + assert(!errors && !delays && requested==expectedRequest()); + const bool transition=on?before==0:before==1; + if(!transition)assert(events.empty()); + else { + assert(events.size()==3);bool lane=requested&0x280; + assert(events[lane?0:2].kind==0 && events[lane?0:2].value==(lane?0x2Au:0u)); + assert(events[lane?1:0].kind==0x45450 && events[lane?2:1].kind==0x45454); + assert(events[lane?1:0].value==requested && events[lane?2:1].value==requested); + } + ++operations; + }; + for(auto d:acquire)for(unsigned h=0;h Date: Mon, 26 Oct 2026 10:22:07 +0900 Subject: [PATCH 53/60] Share Combo PLL ownership and repair DDI requests in the common donor layer --- WhateverXe/kern_adls_clock.hpp | 82 +------- WhateverXe/kern_xe.cpp | 251 ++++++++++++++++-------- WhateverXe/kern_xe.hpp | 13 +- WhateverXe/kern_xelp_combo_clock.hpp | 112 +++++++++++ WhateverXe/kern_xelp_donor.hpp | 13 ++ tests/adls_clock_runtime.py | 59 +++--- tests/adls_ddi_power_native_fixture.cpp | 24 ++- tests/adls_lane_power.py | 5 +- tests/display13_combo_clock.py | 66 ++++++- tests/display13_port_wait.py | 2 +- tests/framebuffer_common.py | 4 +- tests/framebuffer_donor_runtime.py | 3 +- tests/tgl_clock_order.py | 6 +- tests/tgl_combo_pll_write.py | 2 +- tests/xelp_combo_clock.cpp | 34 ++++ tests/xelp_combo_clock_guard.py | 53 +++++ tools/test.py | 2 +- 17 files changed, 532 insertions(+), 199 deletions(-) create mode 100644 WhateverXe/kern_xelp_combo_clock.hpp create mode 100644 tests/xelp_combo_clock.cpp create mode 100644 tests/xelp_combo_clock_guard.py diff --git a/WhateverXe/kern_adls_clock.hpp b/WhateverXe/kern_adls_clock.hpp index b3267b0f..a2de8359 100644 --- a/WhateverXe/kern_adls_clock.hpp +++ b/WhateverXe/kern_adls_clock.hpp @@ -1,82 +1,12 @@ -// ADL-S Combo clocks are shared resources, not a PHY-numbered array of owners. -// Linux v6.12 intel_ddi.c (rkl_ddi_get_pll) and intel_dpll_mgr.c (adls_pll_mgr). +// ADL-S compatibility names; shared ownership and translation live in XELP. #ifndef KERN_ADLS_CLOCK_HPP #define KERN_ADLS_CLOCK_HPP #include "kern_alderlake_s.hpp" - +#include "kern_xelp_combo_clock.hpp" namespace AlderLakeS { -struct ClockSnapshot { - uint32_t mux[2] {},enable[4] {}; - bool valid() const { - for(auto v:mux)if(v==UINT32_MAX)return false; - for(auto v:enable)if(v==UINT32_MAX)return false; - return true; - } - uint32_t value(const Port &p) const {return mux[p.phy<3?0:1];} - bool active(const Port &p) const {return !(value(p)&p.clockOff);} - uint32_t selected(const Port &p) const {return (value(p)>>p.clockShift)&3;} - uint32_t peers(const Port &target) const { - uint32_t mask=0; - for(unsigned phy=0;phy<5;++phy) { - Port p;Port::resolve(phy?phy+2:0,p); - if(p.phy!=target.phy && active(p))mask|=1U<=3 || !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); - const auto previous=s.active(p)?s.selected(p):binding[p.phy]?binding[p.phy]-1:UINT32_MAX; - if(!on)return {previous,true,previous==UINT32_MAX || bool(peers&(1U<=0x6CE10 && pc<0x6DA26):(pc>=0x6DAE6 && pc<0x6DE58); - } - unsigned long reg(unsigned long r,uint32_t pc) const { - if(!caller(pc))return r; - PLL from,to; - if(!PLL::resolve(phy,from) || !PLL::resolve(pll,to))return r; - if(r==from.enable)return to.enable; - if(r==from.cfg0)return to.cfg0; - if(r==from.cfg1)return to.cfg1; - return r; - } - uint32_t mux(uint32_t value,uint32_t pc) const { - if(!enabling || pc!=0x6D77E || phy>=3 || pll>=4)return value; - return (value&~(3U<<(2*phy)))|(pll<<(2*phy)); - } - template void prepareMux(Read read,Write write,uint32_t value) const { - const auto old=read(0x164280); - Port route;Port::resolve(phy?phy+2:0,route); - if((old^value)&(3U<(current_thread()) && t.access==access; } // Immutable first 16 snapshots per cursor lifecycle event; publish on demand. @@ -6538,7 +6538,14 @@ uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { 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; - if(config && config[0]<3){FunctionCast(wrapDisplay13TCEnableClocks,self->orgDisplay13TCEnableClocks)(controller,raw,other,params);return;} + if(config && config[0]<3) { + bool keep=false; + if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,true,keep,other)) { + adlnFault(c.clockFaulted,__LINE__);return; + } + FunctionCast(wrapDisplay13TCEnableClocks,self->orgDisplay13TCEnableClocks)(controller,raw,other,params); + comboEndClock();return; + } if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); bool ok=false;uint32_t rate=0;Display13Power::Result power; auto stage=Display13TC::Clock::LinkStage::Preflight; @@ -6569,7 +6576,14 @@ void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; - if(config && config[0]<3){FunctionCast(wrapDisplay13TCDisableClocks,self->orgDisplay13TCDisableClocks)(controller,raw,other);return;} + if(config && config[0]<3) { + bool keep=false; + if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,false,keep)) { + adlnFault(c.clockFaulted,__LINE__);return; + } + FunctionCast(wrapDisplay13TCDisableClocks,self->orgDisplay13TCDisableClocks)(controller,raw,keep?raw:nullptr); + comboEndClock();return; + } // Native AGDC End ignores the port-disable return. Independently require // transcoder stop before dropping DDI/PLL, including retry and abort paths. if(config && config[0]<7 && (self->adlnOutputWiring.dp&(1U<display13ClockOrderReady && @@ -6873,7 +6887,21 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m }; const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGMBusObservationReady",ready); - const bool complete=ready && processDisplay13FramebufferColor(patcher,index,address,size) && + bool comboReady=true; + if(ready && !(adlnOutputWiring.selected()&0x78)) { + const char *off="__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_"; + const auto fn=patcher.solveSymbol(index,off,address,size);patcher.clearError(); + const uint8_t onPrefix[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + const uint8_t offPrefix[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xd7}; + comboReady=fn==address+0x6DAE6 && !memcmp(reinterpret_cast(clocks),onPrefix,sizeof(onPrefix)) && + !memcmp(reinterpret_cast(fn),offPrefix,sizeof(offPrefix)); + KernelPatcher::RouteRequest clocksRoutes[]={ + {"__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams",wrapDisplay13TCEnableClocks,orgDisplay13TCEnableClocks}, + {off,wrapDisplay13TCDisableClocks,orgDisplay13TCDisableClocks} + }; + comboReady=comboReady && patcher.routeMultipleShort(index,clocksRoutes,arrsize(clocksRoutes),address,size);patcher.clearError(); + } + const bool complete=ready && comboReady && processDisplay13FramebufferColor(patcher,index,address,size) && processDisplay13FramebufferSST(patcher,index,address,size) && processDisplay13FramebufferAUX(patcher,index,address,size); adlnComboPLLFractionCaller=complete ? address+0x6D973 : 0; return complete; @@ -7368,6 +7396,7 @@ uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t spee uint32_t XE::wrapDisplay13GMBusRead(void *access,uint64_t reg) { const auto caller=reinterpret_cast(__builtin_return_address(0)); + if(comboClockAccess(access))reg=comboClockTransaction.translation.reg(reg,XELPComboClock::donorPC(callbackXE->framebufferClockImage,caller)); auto value=FunctionCast(wrapDisplay13GMBusRead,callbackXE->orgDisplay13GMBusRead)(access,reg); const auto start=callbackXE->adlnGMBusFunction; adlnObserveGMBus(false,reg,value,caller,start); @@ -7603,6 +7632,8 @@ void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { TGLComboPLLFraction::adjust(*reinterpret_cast(controller+0xEA8),reg,value); } // DISPLAY13_COMBO_FRACTION_END + unsigned long clockReg=reg; + comboClockTranslateWrite(access,clockReg,value,reinterpret_cast(__builtin_return_address(0)));reg=clockReg; // This route also carries direct native cursor writes, outside the WM // trigger queue. Apply Display 13's slot WA only to our proven owner. if(reg==0x70080 || reg==0x71080 || reg==0x72080) { @@ -11416,6 +11447,11 @@ bool XE::wrapFramebufferStart(IOService *that,IOService *provider) { } s->framebufferStartInProgress=true; IORecursiveLockUnlock(s->tglPathPoolLock); + if(comboClockTransaction.lock) { + IORecursiveLockLock(comboClockTransaction.lock); + comboClockTransaction.allocation={}; + IORecursiveLockUnlock(comboClockTransaction.lock); + } const bool native=s->display13FramebufferRequested ? wrapDisplay13FramebufferStart(that,provider) : wrapTGLFramebufferStart(that,provider); IORecursiveLockLock(s->tglPathPoolLock); const bool accepted=native && !s->tglLeasesStopping; @@ -11515,6 +11551,20 @@ bool XE::processFramebufferPortWait(KernelPatcher &patcher,size_t index,mach_vm_ bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { if(framebufferDonorRuntimeReady)return true; bool hdcpReady=false,meiReady=false; + if(!comboClockTransaction.lock)comboClockTransaction.lock=IORecursiveLockAlloc(); + framebufferClockImage=address; + const auto ddiPut=patcher.solveSymbol(index, + "__ZN19AppleIntelPowerWell19disablePowerWellDDIEj",address,size);patcher.clearError(); + bool ddiRequestReady=ddiPut==address+0x5AA86 && + XELPDonor::ddiReleaseSite(reinterpret_cast(address),size); + if(ddiRequestReady) { + patcher.routeBlock(address+XELPDonor::DDIReleaseOffset,XELPDonor::DDIReleaseAfter,sizeof(XELPDonor::DDIReleaseAfter)); + ddiRequestReady=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+XELPDonor::DDIReleaseOffset),XELPDonor::DDIReleaseAfter,sizeof(XELPDonor::DDIReleaseAfter)); + patcher.clearError(); + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDonorDDIRequestReady",ddiRequestReady); + { const char *symbol="__ZN15AppleIntelHDCP24initEP21AppleIntelDisplayPath"; const uint8_t init[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x96,0x02,0x06,0x00,0x49,0x89}; @@ -11586,7 +11636,7 @@ bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach IORegistryEntry::getRegistryRoot()->setProperty("TGLMEITXFIFOReadbackR364", known); patcher.clearError(); } - framebufferDonorRuntimeReady=hdcpReady && tglPanelPowerPropertyReady && meiReady && + framebufferDonorRuntimeReady=comboClockTransaction.lock && hdcpReady && tglPanelPowerPropertyReady && meiReady && ddiRequestReady && processFramebufferPortWait(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferDonorRuntimeReady",framebufferDonorRuntimeReady); return framebufferDonorRuntimeReady; @@ -12423,6 +12473,14 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t ready = patcher.routeMultiple(index, &route, 1, address, size); } patcher.clearError(); + if(ready && !adlsDisplaySelected) { + const char *name="__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams"; + const auto fn=patcher.solveSymbol(index,name,address,size);patcher.clearError(); + const uint8_t prefix[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + ready=orgTGLRegisterRead && comboClockTransaction.lock && fn==address+0x6CE10 && !memcmp(reinterpret_cast(fn),prefix,sizeof(prefix)); + KernelPatcher::RouteRequest enable(name,wrapTGLComboEnableClocks,orgComboEnableClocks); + ready=ready && patcher.routeMultiple(index,&enable,1,address,size);patcher.clearError(); + } if (ready) tglClockOffCaller = address + 0x4D990; IORegistryEntry::getRegistryRoot()->setProperty("TGLClockOrderReady", ready); } @@ -12587,8 +12645,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; const auto symbol="__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams"; const auto fn=patcher.solveSymbol(index,symbol,address,size); - if(!adlsClockTransaction.lock)adlsClockTransaction.lock=IORecursiveLockAlloc(); - bool ready=adlsPLLTableReady && adlsClockTransaction.lock && tglTCNativeFunctions.complete() && + bool ready=adlsPLLTableReady && comboClockTransaction.lock && tglTCNativeFunctions.complete() && orgTGLRegisterRead && orgTGLRegisterWrite && orgTGLDisableClocks && tglClockOffCaller && tglComboPLLFractionCaller && fn==address+0x6CE10 && size>0x6DCBD && !memcmp(reinterpret_cast(fn),entry,sizeof(entry)); struct Site {uint32_t offset;const uint8_t *find,*replace;size_t bytes;}; @@ -12601,7 +12658,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t for(const auto &site:sites)ready=ready && !memcmp(reinterpret_cast(address+site.offset),site.find,site.bytes); patcher.clearError(); if(ready) { - KernelPatcher::RouteRequest route(symbol,wrapADLSEnableClocks,orgADLSEnableClocks); + KernelPatcher::RouteRequest route(symbol,wrapADLSEnableClocks,orgComboEnableClocks); ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); if(ready)for(const auto &site:sites) { KernelPatcher::LookupPatch patch {currentFramebuffer,site.find,site.replace,site.bytes,1}; @@ -13069,11 +13126,6 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { that->setProperty("TGLHostProviderWorkLoopSelected",ok); if (!ok) return false; } - if(callbackXE->adlsDisplaySelected && adlsClockTransaction.lock) { - IORecursiveLockLock(adlsClockTransaction.lock); - adlsClockTransaction.allocation={}; - IORecursiveLockUnlock(adlsClockTransaction.lock); - } // A reused controller address must not inherit another start's PPS snapshot. callbackXE->tglPanelTimingBIOSValid=false; callbackXE->adlsPanelPWM={}; @@ -16241,7 +16293,7 @@ uint32_t XE::wrapTGLTopDDC(void *fb, int connection, uint32_t block, uint32_t ty } uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { - if(adlsClockAccess(access))reg=adlsClockTransaction.translation.reg(reg,tglConnectionCaller(__builtin_return_address(0))); + if(comboClockAccess(access))reg=comboClockTransaction.translation.reg(reg,XELPComboClock::donorPC(callbackXE->framebufferClockImage,reinterpret_cast(__builtin_return_address(0)))); 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); @@ -17632,12 +17684,11 @@ volatile uint8_t *XE::tglClockOrderTarget(void *controller, void *port, void *pa } void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { - if(callbackXE->adlsDisplaySelected && !adlsClockTarget(controller,port,otherPort)) { - IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; - } + auto config=port?*reinterpret_cast(static_cast(port)+0x548):nullptr; + const bool combo=callbackXE->adlsDisplaySelected || (config && config[0]<2); bool keepPLL=false; - if(callbackXE->adlsDisplaySelected && !adlsBeginClock(controller,port,false,keepPLL)) { - IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; + if(combo && (!comboClockTarget(controller,port,otherPort) || !comboBeginClock(controller,port,false,keepPLL))) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXComboClockDisableRejected",true);return; } tglTransitionRecord(32,false,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); auto s = callbackXE; @@ -17676,8 +17727,8 @@ void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { if (owned && tx.pending) { tx.pending = false; wrapTGLRegisterWrite(tx.access, tx.clockReg, tx.value); } // The donor's self-sharing argument gates the output and releases its DDI // reference, but leaves the shared PLL running and recalculates voltage. - FunctionCast(wrapTGLDisableClocks, s->orgTGLDisableClocks)(controller, port, keepPLL?port:otherPort); - if(s->adlsDisplaySelected)adlsEndClock(); + FunctionCast(wrapTGLDisableClocks, s->orgTGLDisableClocks)(controller, port, combo?(keepPLL?port:nullptr):otherPort); + if(combo)comboEndClock(); tglTransitionRecord(32,true,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); } @@ -17713,30 +17764,69 @@ bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { *reinterpret_cast(access+0x60)>=0x1642C8; } -bool XE::adlsBeginClock(void *controller,void *port,bool enable,bool &keepPLL) { - auto s=callbackXE;auto &t=adlsClockTransaction; +bool XE::comboClockTarget(void *controller,void *port,void *otherPort) { + auto s=callbackXE; + if(s->adlsDisplaySelected)return adlsClockTarget(controller,port,otherPort); + if(!controller || !port || (otherPort && otherPort!=port && !comboClockTarget(controller,otherPort,nullptr)))return false; + auto p=static_cast(port),c=static_cast(controller); + auto config=*reinterpret_cast(p+0x548); + auto access=*reinterpret_cast(c+0xC40); + if(!config || config[0]>=2 || config[3] || config[2]>2 || !access || *reinterpret_cast(p+0x578)!=access || + *reinterpret_cast(access+0x60)<0x164294 || + *reinterpret_cast(p+0x5B8)!=(config[0]?0x6C10CU:0x16210CU))return false; + if(s->display13FramebufferRequested)return controller==s->adlnTopologyController && + Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring); + return s->tglActive && controller==s->tglObservationController && + XELP::displayAdapter(s->xeProfile.family)==XELP::DisplayAdapter::TGL && + config==reinterpret_cast(s->tglPlatformCopy+0x20+24*config[0]); +} + +bool XE::comboClockPower(void *power,bool acquire) { + auto s=callbackXE; + if(s->display13FramebufferRequested)return display13LeasePower(power,1,acquire); + if(!s->tglTCNativeFunctions.complete())return false; + if(acquire)s->tglTCNativeFunctions.getPG(power,1);else s->tglTCNativeFunctions.putPG(power,1); + return true; +} +uint32_t XE::comboClockRead(void *access,unsigned long reg) { + auto s=callbackXE; + return reinterpret_cast(s->display13FramebufferRequested? + s->orgDisplay13GMBusRead:s->orgTGLRegisterRead)(access,reg); +} +void XE::comboClockWrite(void *access,unsigned long reg,uint32_t value) { + auto s=callbackXE; + reinterpret_cast(s->display13FramebufferRequested? + s->orgDisplay13GMBusWrite:s->orgTGLRegisterWrite)(access,reg,value); +} + +bool XE::comboBeginClock(void *controller,void *port,bool enable,bool &keepPLL,void *other) { + auto s=callbackXE;auto &t=comboClockTransaction; auto c=static_cast(controller); auto access=*reinterpret_cast(c+0xC40); - auto power=adlsPowerTarget(*reinterpret_cast(c+0xC8)); - if(!t.lock || !power || !*reinterpret_cast(access+0x50) || !s->tglTCNativeFunctions.complete())return false; + auto power=*reinterpret_cast(c+0xC8); + if(!t.lock || !power || *reinterpret_cast(power+0x10)!=controller || + *reinterpret_cast(power+0x78)!=access || !*reinterpret_cast(access+0x50))return false; IORecursiveLockLock(t.lock); if(t.thread){IORecursiveLockUnlock(t.lock);return false;} - // Clock muxes and PLL state live in DISPLAY_CORE. Hold a real native PW1 - // reference through selection, native programming and result readback. - s->tglTCNativeFunctions.getPG(power,1); - auto read=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead); - const auto pw=read(access,0x45404); + if(!comboClockPower(power,true)){IORecursiveLockUnlock(t.lock);return false;} + // DISPLAY_CORE must remain referenced from peer scan through readback. + const auto pw=comboClockRead(access,0x45404); if(pw==UINT32_MAX || (pw&3)!=3) { - s->tglTCNativeFunctions.putPG(power,1);IORecursiveLockUnlock(t.lock);return false; + comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; } - AlderLakeS::Port route; - AlderLakeS::Port::resolve((*reinterpret_cast(static_cast(port)+0x548))[0],route); - AlderLakeS::ClockSnapshot snapshot; - snapshot.mux[0]=read(access,0x164280);snapshot.mux[1]=read(access,0x1642BC); - for(unsigned i=0;i<4;++i){AlderLakeS::PLL pll;AlderLakeS::PLL::resolve(i,pll);snapshot.enable[i]=read(access,pll.enable);} - const auto choice=t.allocation.choose(route,snapshot,enable); + const auto ddi=(*reinterpret_cast(static_cast(port)+0x548))[0]; + XELPComboClock::Port route(s->adlsDisplaySelected?(ddi?ddi-2:0):ddi); + XELPComboClock::ClockSnapshot snapshot; + snapshot.layout=s->adlsDisplaySelected?XELPComboClock::Layout::ADLS: + s->display13FramebufferRequested?XELPComboClock::Layout::Display13:XELPComboClock::Layout::TGL; + snapshot.mux[0]=comboClockRead(access,0x164280); + if(s->adlsDisplaySelected)snapshot.mux[1]=comboClockRead(access,0x1642BC); + for(unsigned i=0;i(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) { - s->tglTCNativeFunctions.putPG(power,1);IORecursiveLockUnlock(t.lock);return false; + comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; } if(enable)t.allocation.binding[route.phy]=choice.pll+1; t.translation={route.phy,choice.pll,enable};t.before=snapshot; @@ -17745,21 +17835,45 @@ bool XE::adlsBeginClock(void *controller,void *port,bool enable,bool &keepPLL) { return true; } -void XE::adlsEndClock() { - auto s=callbackXE;auto &t=adlsClockTransaction; - auto read=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead); - auto tr=t.translation;AlderLakeS::PLL pll; - const auto state=AlderLakeS::PLL::resolve(tr.pll,pll)?read(t.access,pll.enable):0; - const auto mux=read(t.access,0x164280); +void XE::comboEndClock() { + auto &t=comboClockTransaction; + auto tr=t.translation;XELPComboClock::PLL pll; + const auto state=XELPComboClock::PLL::resolve(tr.pll,pll)?comboClockRead(t.access,pll.enable):0; + const auto mux=comboClockRead(t.access,0x164280); if(!tr.enabling && (t.keepPLL || !(state&0xCC000000U)))t.allocation.binding[tr.phy]=0; - uint32_t record[]={1,tr.phy,tr.pll,tr.enabling,t.keepPLL,t.before.mux[0],t.before.mux[1],mux,state}; + uint32_t record[]={2,tr.phy,tr.pll,tr.enabling,t.keepPLL,t.before.mux[0],t.before.mux[1],mux,state}; __atomic_store_n(&t.thread,0,__ATOMIC_RELEASE); - s->tglTCNativeFunctions.putPG(t.power,1); + comboClockPower(t.power,false); IORecursiveLockUnlock(t.lock); - char key[40];snprintf(key,sizeof(key),"ADLSClockPHY%u%s",tr.phy,tr.enabling?"Enable":"Disable"); + char key[40];snprintf(key,sizeof(key),"WEXComboClockPHY%u%s",tr.phy,tr.enabling?"Enable":"Disable"); IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); } +void XE::comboClockTranslateWrite(void *access,unsigned long ®,uint32_t &value,uintptr_t caller) { + if(!comboClockAccess(access))return; + const auto pc=XELPComboClock::donorPC(callbackXE->framebufferClockImage,caller); + const auto &tr=comboClockTransaction.translation; + reg=tr.reg(reg,pc); + if(reg==0x164280 && tr.enabling && pc==0x6D77E) { + value=tr.mux(value,pc); + tr.prepareMux([&](uint32_t r){return comboClockRead(access,r);}, + [&](uint32_t r,uint32_t v){comboClockWrite(access,r,v);},value); + } +} + +void XE::wrapTGLComboEnableClocks(void *controller,void *port,void *other,uint32_t *params) { + auto config=port?*reinterpret_cast(static_cast(port)+0x548):nullptr; + if(config && config[0]<2) { + bool keep=false; + if(!comboClockTarget(controller,port,other) || !comboBeginClock(controller,port,true,keep,other)) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXComboClockEnableRejected",true);return; + } + FunctionCast(wrapTGLComboEnableClocks,callbackXE->orgComboEnableClocks)(controller,port,other,params); + comboEndClock();return; + } + FunctionCast(wrapTGLComboEnableClocks,callbackXE->orgComboEnableClocks)(controller,port,other,params); +} + void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params) { AlderLakeS::HDMIClock encoded; if(!adlsClockTarget(controller,port,otherPort) || @@ -17768,12 +17882,12 @@ void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32 IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; } bool keepPLL=false; - if(!adlsBeginClock(controller,port,true,keepPLL)) { + if(!comboBeginClock(controller,port,true,keepPLL)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; } tglTransitionRecord(71,false,port,encoded.cfg0,encoded.cfg1,0); - FunctionCast(wrapADLSEnableClocks,callbackXE->orgADLSEnableClocks)(controller,port,otherPort,params); - adlsEndClock(); + FunctionCast(wrapADLSEnableClocks,callbackXE->orgComboEnableClocks)(controller,port,otherPort,params); + comboEndClock(); tglTransitionRecord(71,true,port,encoded.cfg0,encoded.cfg1,0); } @@ -18055,20 +18169,7 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { } } } - if(adlsClockAccess(access)) { - const auto pc=tglConnectionCaller(__builtin_return_address(0)); - const auto &tr=adlsClockTransaction.translation; - reg=tr.reg(reg,pc); - if(reg==0x164280 && tr.enabling && pc==0x6D77E) { - value=tr.mux(value,pc); - // The selector and clock-ungate require separate writes. Preserve - // every peer field and gate our output before changing its source. - auto read=FunctionCast(wrapTGLRegisterRead,callbackXE->orgTGLRegisterRead); - auto write=FunctionCast(wrapTGLRegisterWrite,callbackXE->orgTGLRegisterWrite); - tr.prepareMux([&](uint32_t r){return read(access,r);}, - [&](uint32_t r,uint32_t v){write(access,r,v);},value); - } - } + comboClockTranslateWrite(access,reg,value,reinterpret_cast(__builtin_return_address(0))); auto &tx = tglClockOffTransaction; if (callbackXE->tglClockOffCaller && reinterpret_cast(__builtin_return_address(0)) == callbackXE->tglClockOffCaller && reg == tx.clockReg && !(value & 0xF0000000U) && @@ -18459,16 +18560,6 @@ void XE::wrapTGLPowerDDIState(void *power, bool enable, uint32_t requested) { // Native reference counts remain untouched. The PG target is the union of // the native pipe consumers and the current TC1/TC2 lane consumers. auto adls=adlsPowerTarget(power); - // C004FC1A disablePowerWellDDI (0x5AD86..0x5ADDF) derives the - // DDI C request from DDI TC1's refs a second time. ADL-S has no - // physical DDI C. Remove only that duplicate on release, preserving - // unexpected C consumers, every real port request and native ACK reads. - // The donor passes enable=false on both get and put. Direction is not - // inferred from that flag: only put generates this unreferenced C request. - if(adls && !enable && (callbackXE->adlsWiring.selected()&8U) && - !*reinterpret_cast(adls+0x38) && - *reinterpret_cast(adls+0x3C) && (requested&0xA0U)==0xA0U) - requested&=~0x20U; const auto laneMask=callbackXE->adlsWiring.laneMask(); if(adls && (requested&laneMask)) { const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f15d6416..e41739b7 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -104,10 +104,17 @@ class XE { mach_vm_address_t orgADLSPowerPG {}; static uint8_t *adlsPowerTarget(void *power); static void wrapADLSPowerPG(void *power,bool enable,uint32_t requested); - mach_vm_address_t orgADLSEnableClocks {}; + mach_vm_address_t orgComboEnableClocks {}; static bool adlsClockTarget(void *controller,void *port,void *otherPort); - static bool adlsBeginClock(void *controller,void *port,bool enable,bool &keepPLL); - static void adlsEndClock(); + mach_vm_address_t framebufferClockImage {}; + static bool comboClockTarget(void *,void *,void *); + static bool comboClockPower(void *,bool); + static uint32_t comboClockRead(void *,unsigned long); + static void comboClockWrite(void *,unsigned long,uint32_t); + static void comboClockTranslateWrite(void *,unsigned long &,uint32_t &,uintptr_t); + static void wrapTGLComboEnableClocks(void *,void *,void *,uint32_t *); + static bool comboBeginClock(void *controller,void *port,bool enable,bool &keepPLL,void *other=nullptr); + static void comboEndClock(); static void wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params); mach_vm_address_t orgADLSPortInit {}; static bool wrapADLSPortInit(void *port,uint32_t *config); diff --git a/WhateverXe/kern_xelp_combo_clock.hpp b/WhateverXe/kern_xelp_combo_clock.hpp new file mode 100644 index 00000000..0ee42e43 --- /dev/null +++ b/WhateverXe/kern_xelp_combo_clock.hpp @@ -0,0 +1,112 @@ +// Combo clocks are shared resources, not a PHY-numbered array of owners. +// Linux v6.12 intel_ddi.c and intel_dpll_mgr.c (icl_get_combo_phy_dpll). +#ifndef KERN_XELP_COMBO_CLOCK_HPP +#define KERN_XELP_COMBO_CLOCK_HPP +#include + +namespace XELPComboClock { +// Hardware inventory, independent of connector wiring or native port mode. +// TGL/ADL-P/N allocate only DPLL0/1: PLL2 is not a third Combo resource. +// ADL-S preserves the existing three-slot donor backend and fourth live peer. +enum class Layout {ADLS,TGL,Display13}; +inline uint32_t donorPC(uintptr_t image,uintptr_t caller) { + return image && caller>=image && caller-image<0x6DE58 ? uint32_t(caller-image):UINT32_MAX; +} +struct Port { + uint32_t phy {},clockShift {},clockOff {}; + explicit Port(uint32_t p):phy(p),clockShift(2*(p%3)),clockOff(1U<<(p==0?10:p==1?11:p==2?24:p+1)){} +}; +struct PLL { + uint32_t enable {},cfg0 {},cfg1 {}; + static bool resolve(uint32_t id,PLL &out) { + out={};if(id>3)return false; + const uint32_t enables[]={0x46010,0x46014,0x46018,0x46030}; + const uint32_t configs[]={0x164284,0x16428C,0x164294,0x1642C0}; + out={enables[id],configs[id],configs[id]+4};return true; + } +}; +struct ClockSnapshot { + uint32_t mux[2] {},enable[4] {}; + Layout layout=Layout::ADLS; + unsigned pllCount() const {return layout==Layout::ADLS?4:2;} + unsigned slots() const {return layout==Layout::ADLS?3:2;} + bool valid() const { + for(auto v:mux)if(v==UINT32_MAX)return false; + for(auto v:enable)if(v==UINT32_MAX)return false; + return true; + } + template uint32_t value(const P &p) const {return mux[p.phy<3?0:1];} + template bool active(const P &p) const {return !(value(p)&p.clockOff);} + template uint32_t selected(const P &p) const {return (value(p)>>p.clockShift)&3;} + template uint32_t peers(const P &target) const { + uint32_t mask=0; + for(unsigned phy=0;phy<(layout==Layout::ADLS?5U:2U);++phy) { + Port p(phy); + if(p.phy!=target.phy && active(p))mask|=1U< ClockChoice share(const P &source,const ClockSnapshot &s) const { + if(source.phy>=s.slots() || !s.valid() || !s.active(source))return {}; + const auto pll=s.selected(source); + return pll ClockChoice choose(const P &p,const ClockSnapshot &s,bool on) const { + if(p.phy>=s.slots() || !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); + const auto previous=s.active(p)?s.selected(p):binding[p.phy]?binding[p.phy]-1:UINT32_MAX; + if(previous!=UINT32_MAX && previous>=s.pllCount())return {}; + if(!on)return {previous,true,previous==UINT32_MAX || bool(peers&(1U<=0x6CE10 && pc<0x6DA26):(pc>=0x6DAE6 && pc<0x6DE58); + } + unsigned long reg(unsigned long r,uint32_t pc) const { + if(!caller(pc))return r; + PLL from,to; + if(!PLL::resolve(phy,from) || !PLL::resolve(pll,to))return r; + if(r==from.enable)return to.enable; + if(r==from.cfg0)return to.cfg0; + if(r==from.cfg1)return to.cfg1; + return r; + } + uint32_t mux(uint32_t value,uint32_t pc) const { + if(!enabling || pc!=0x6D77E || phy>=3 || pll>=4)return value; + return (value&~(3U<<(2*phy)))|(pll<<(2*phy)); + } + template void prepareMux(Read read,Write write,uint32_t value) const { + const auto old=read(0x164280); + Port route(phy); + if((old^value)&(3U< #include namespace XELPDonor { +// Exact C004FC1A disablePowerWellDDI incorrectly derives C's request from +// TC1's refs/mode. Match the whole producer block and use C's own counter, +// as enablePowerWellDDI already does. Hardware/domain selection belongs to +// callers; do not discard an actually referenced DDI C on capable hardware. +constexpr uint32_t DDIReleaseOffset=0x5AD86; +static constexpr uint8_t DDIReleaseBefore[] {0x83,0x7b,0x3c,0x00,0x74,0x37,0x48,0xff,0x05,0x3d,0x6c,0x08,0x00,0x48,0xff,0x05,0xd6,0x70,0x08,0x00,0xbf,0x03,0x00,0x00,0x00,0xe8,0x46,0x04,0xfc,0xff,0x48,0x85,0xc0,0x74,0x1e,0x48,0xff,0x05,0xd0,0x70,0x08,0x00,0x48,0xff,0x05,0xa9,0x75,0x08,0x00,0x83,0xb8,0x50,0x05,0x00,0x00,0x02,0x0f,0x94,0xc0,0xeb,0x06,0x31,0xc0,0xeb,0x1b,0x31,0xc0,0x0f,0xb6,0xc0,0x48,0x01,0x05,0xa5,0x70,0x08,0x00,0x34,0x01,0x0f,0xb6,0xc0,0x48,0x01,0x05,0xe9,0x6b,0x08,0x00,0xc1,0xe0,0x05}; +// xor eax,eax; cmp dword [rbx+38h],0; setne al; shl eax,5; +// jmp 5ADE2 (native union of all other DDI requests). +static constexpr uint8_t DDIReleaseAfter[] {0x31,0xc0,0x83,0x7b,0x38,0,0x0f,0x95,0xc0,0xc1,0xe0,5,0xe9,0x4b,0,0,0}; +inline bool ddiReleaseSite(const uint8_t *image,size_t size) { + return image && size>=DDIReleaseOffset+sizeof(DDIReleaseBefore) && + !memcmp(image+DDIReleaseOffset,DDIReleaseBefore,sizeof(DDIReleaseBefore)); +} inline bool displayPoolRequest(uint32_t enabled,uint32_t bytes) { return enabled && bytes && !(bytes&4095) && bytes<=0x4000000; } diff --git a/tests/adls_clock_runtime.py b/tests/adls_clock_runtime.py index edd9a402..72ea1b08 100644 --- a/tests/adls_clock_runtime.py +++ b/tests/adls_clock_runtime.py @@ -3,9 +3,9 @@ from pathlib import Path import re,subprocess,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() -state=re.search(r'struct ADLSClockTransaction \{.*?\n\}',s,re.S).group() -state+=' adlsClockTransaction;\n'+re.search(r'static bool adlsClockAccess\(.*?\n\}',s,re.S).group() -functions='\n'.join(re.search(r'(?:bool|void) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['adlsBeginClock','adlsEndClock']) +state=re.search(r'struct ComboClockTransaction \{.*?\n\}',s,re.S).group() +state+=' comboClockTransaction;\n'+re.search(r'static bool comboClockAccess\(.*?\n\}',s,re.S).group() +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 @@ -29,6 +29,7 @@ void getPG(void *p,uint32_t pg){assert(p==power && pg==1);++refs;++getCount;} void putPG(void *p,uint32_t pg){assert(p==power && pg==1 && refs);--refs;++putCount;} struct XE { + bool adlsDisplaySelected=true,display13FramebufferRequested=false; struct Native { bool ready=true;bool complete(){return ready;} void (*getPG)(void*,uint32_t)=::getPG,(*putPG)(void*,uint32_t)=::putPG; @@ -36,41 +37,53 @@ uintptr_t orgTGLRegisterRead=reinterpret_cast(readNative); static uint32_t wrapTGLRegisterRead(void*,unsigned long); static uint8_t *adlsPowerTarget(void *p){return p==power?power:nullptr;} - static bool adlsBeginClock(void*,void*,bool,bool&); - static void adlsEndClock(); + static bool comboClockPower(void *p,bool on){if(on)getPG(p,1);else putPG(p,1);return true;} + static uint32_t comboClockRead(void *a,unsigned long r){return readNative(a,r);} + static bool comboBeginClock(void*,void*,bool,bool&,void *other=nullptr); + static void comboEndClock(); } xe,*callbackXE=&xe; #define FunctionCast(f,p) reinterpret_cast(p) STATE FUNCTIONS int main(){ - IORecursiveLock lock;adlsClockTransaction.lock=&lock; + IORecursiveLock lock;comboClockTransaction.lock=&lock; at(ctl,0xc40)=access;at(ctl,0xc8)=power;at(port,0x548)=config; - at(access,0x50)=ctl; + at(access,0x50)=ctl;at(power,0x10)=ctl;at(power,0x78)=access; mmio={{0x45404,3},{0x164280,0x01e07420},{0x1642bc,0x30}, {0x46010,0xcc000000},{0x46014,0},{0x46018,0},{0x46030,0}}; bool keep=false; - assert(XE::adlsBeginClock(ctl,port,true,keep)); - assert(refs==1 && lock.depth==1 && adlsClockAccess(access)); - assert(!adlsClockAccess(port) && adlsClockTransaction.translation.pll==1); - assert(adlsClockTransaction.allocation.binding[0]==2); - assert(!XE::adlsBeginClock(ctl,port,true,keep));assert(refs==1 && lock.depth==1); - thread=2;assert(!adlsClockAccess(access));thread=1; + assert(XE::comboBeginClock(ctl,port,true,keep)); + assert(refs==1 && lock.depth==1 && comboClockAccess(access)); + assert(!comboClockAccess(port) && comboClockTransaction.translation.pll==1); + assert(comboClockTransaction.allocation.binding[0]==2); + assert(!XE::comboBeginClock(ctl,port,true,keep));assert(refs==1 && lock.depth==1); + thread=2;assert(!comboClockAccess(access));thread=1; // Partial enable: retain the powered PLL's reservation until native teardown. - mmio[0x46014]=0x0c000000;XE::adlsEndClock(); - assert(!refs && !lock.depth && !adlsClockAccess(access)); - assert(XE::adlsBeginClock(ctl,port,false,keep) && !keep); - assert(adlsClockTransaction.translation.pll==1); - mmio[0x46014]=0;XE::adlsEndClock();assert(!adlsClockTransaction.allocation.binding[0]); + mmio[0x46014]=0x0c000000;XE::comboEndClock(); + assert(!refs && !lock.depth && !comboClockAccess(access)); + assert(XE::comboBeginClock(ctl,port,false,keep) && !keep); + assert(comboClockTransaction.translation.pll==1); + mmio[0x46014]=0;XE::comboEndClock();assert(!comboClockTransaction.allocation.binding[0]); // A gated port without an owned attempt must not stop its stale selector0. - assert(XE::adlsBeginClock(ctl,port,false,keep) && keep);XE::adlsEndClock(); + assert(XE::comboBeginClock(ctl,port,false,keep) && keep);XE::comboEndClock(); assert(mmio[0x46010]==0xcc000000); // PW1 not acknowledged: no clock-bank reads or unbalanced refs. mmio[0x45404]=0;auto n=reads; - assert(!XE::adlsBeginClock(ctl,port,true,keep));assert(reads==n+1); + assert(!XE::comboBeginClock(ctl,port,true,keep));assert(reads==n+1); mmio[0x45404]=3;mmio[0x164280]=UINT32_MAX; - assert(!XE::adlsBeginClock(ctl,port,true,keep)); - assert(!refs && !lock.depth && getCount==putCount && !adlsClockTransaction.thread); - puts("PASS production ADL-S clock scope: core power, nested rejection, thread/access isolation, failed-enable reservation and balanced teardown"); + assert(!XE::comboBeginClock(ctl,port,true,keep)); + assert(!refs && !lock.depth && getCount==putCount && !comboClockTransaction.thread); + // TGL and Display13 must never scan or allocate ADL-S extra PLL banks. + for(bool d13:{false,true}) { + xe.adlsDisplaySelected=false;xe.display13FramebufferRequested=d13; + comboClockTransaction.allocation={}; + mmio={{0x45404,3},{0x164280,0x01000C00},{0x46010,0},{0x46014,0}}; + assert(XE::comboBeginClock(ctl,port,true,keep)); + assert(comboClockTransaction.translation.pll==0); + 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"); } '''.replace('STATE',state).replace('FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='adls-clock-runtime-') as d: diff --git a/tests/adls_ddi_power_native_fixture.cpp b/tests/adls_ddi_power_native_fixture.cpp index 18bacd5d..6aa04221 100644 --- a/tests/adls_ddi_power_native_fixture.cpp +++ b/tests/adls_ddi_power_native_fixture.cpp @@ -1,4 +1,5 @@ #include "kern_alderlake_s.hpp" +#include "kern_xelp_donor.hpp" #include #include #include @@ -21,7 +22,7 @@ templateT &at(void*p,unsigned o){return *reinterpret_cast(static_ca alignas(8) uint8_t controller[0xF00]{},power[0x90]{},access[0x70]{},ports[9][0x600]{}; uintptr_t vtable[64]{};uint8_t *image; struct Event {unsigned kind,value;};std::vector events; -unsigned requested,ackMissing,delays,errors,reads; +unsigned requested,ackMissing,delays,errors,reads,physicalMask=0x79; void pg(void *p,bool,unsigned mask){assert(p==power);events.push_back({0,mask});} void *lookup(unsigned d){assert(d<9);return ports[d];} void writeReg(void *p,unsigned long reg,unsigned value){ @@ -33,7 +34,7 @@ uint32_t readReg(void *p,unsigned long reg){ // Model only physical ADL-S IO wells. An unsupported request receives no // ACK; explicit fault injection removes a real port's ACK as well. unsigned ack=0; - for(unsigned d:{0u,3u,4u,5u,6u})if(!(ackMissing&(1U<>(2*d+1)&1)<<(2*d); + for(unsigned d=0;d<9;++d)if((physicalMask&(1U<>(2*d+1)&1)<<(2*d); return requested|ack; } void delay(unsigned n){assert(n==1);++delays;} @@ -81,7 +82,24 @@ int main(int argc,char **argv){ setup(12);state.adlsDisplaySelected=false; get(power,3);get(power,4);assert(requested==0x280 && !errors); put(power,4);assert(requested==0xA0 && delays==20 && errors==2); - state.adlsDisplaySelected=true; + assert(XELPDonor::ddiReleaseSite(image,b.size())); + assert(!XELPDonor::ddiReleaseSite(image,XELPDonor::DDIReleaseOffset+1)); + for(unsigned i=0;i(ports[3],0x550)=mode; + if(realC)get(power,2); + get(power,3);get(power,4);put(power,4); + assert(requested==((mode==2?0U:0x80U)|(realC?0x20U:0U)) && !delays && !errors); + put(power,3);assert(requested==(realC?0x20U:0U)); + if(realC)put(power,2); + assert(!requested && !delays && !errors); + } + physicalMask=0x79;state.adlsDisplaySelected=true; unsigned assignments=0,sequences=0,operations=0; for(unsigned assignment=1;assignment<27;++assignment){ setup(assignment);std::vector acquire; diff --git a/tests/adls_lane_power.py b/tests/adls_lane_power.py index 26d2269b..acb76d42 100644 --- a/tests/adls_lane_power.py +++ b/tests/adls_lane_power.py @@ -53,8 +53,8 @@ calls.clear();XE::wrapADLSPowerPG(power,true,2); assert(calls.size()==1 && calls[0].target==(expected|2));++count; } - // Observed donor release masks duplicate TC1 into absent DDI C. Real - // TC1/TC2 and A consumers must survive; no ACK/ref state is fabricated. + // Request generation is corrected in the common donor producer. This + // backend owns only lane parents and must preserve every supplied DDI bit. for(unsigned refsC:{0U,1U,7U})for(unsigned refsTC1:{0U,1U,7U}) for(bool on:{false,true})for(unsigned mask:{0x20U,0x80U,0xA0U,0xA2U,0x280U,0x2A0U,0x2A2U}){ state.adlsWiring.hdmi=8;state.adlsWiring.dp=17; @@ -62,7 +62,6 @@ uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power)); calls.clear();XE::wrapTGLPowerDDIState(power,on,mask); unsigned expected=mask; - if(!on && !refsC && refsTC1 && (mask&0xA0)==0xA0)expected&=~0x20U; unsigned seen=0;for(auto c:calls)if(c.kind==1){assert(c.target==expected && c.enable==on);++seen;} assert(seen==1 && !memcmp(before,power,sizeof(power)));++count; } diff --git a/tests/display13_combo_clock.py b/tests/display13_combo_clock.py index 35056a18..3046ce94 100644 --- a/tests/display13_combo_clock.py +++ b/tests/display13_combo_clock.py @@ -12,6 +12,7 @@ def array(name): code=r''' #include "kern_xelp_platform.hpp" #include "kern_tgl_port_map.hpp" +#include "kern_xelp_combo_clock.hpp" #include #include #include @@ -30,12 +31,25 @@ def array(name): alignas(8) uint8_t ctl[0xf00]{},port[0x700]{},access[0x80]{}; uint32_t config[6]{};std::map mmio; uint8_t *image;unsigned delays=0,errors=0,get=0,put=0,fault=0; -bool correction=true; +bool correction=true,managed=false; +XELPComboClock::ClockTranslation translation; +XELPComboClock::ClockAllocation allocation; void jump(uint8_t*p,uintptr_t to){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&to,8);p[10]=0xff;p[11]=0xe0;} void redirect(unsigned at,uintptr_t to,unsigned stub){assert(image[at]==0xe8);jump(image+stub,to);int32_t d=stub-at-5;memcpy(image+at+1,&d,4);} -uint32_t read(void*p,unsigned long reg){assert(p==access&&mmio.count(reg));return mmio.at(reg);} +uint32_t read(void*p,unsigned long reg){ + if(managed)reg=translation.reg(reg,(uintptr_t)__builtin_return_address(0)-(uintptr_t)image); + assert(p==access&&mmio.count(reg));return mmio.at(reg); +} void write(void*p,unsigned long reg,uint32_t value){assert(p==access&&mmio.count(reg)); if(correction)value=adjust(p,reg,value,(uintptr_t)__builtin_return_address(0)); + if(managed) { + const auto pc=(uintptr_t)__builtin_return_address(0)-(uintptr_t)image; + reg=translation.reg(reg,pc); + if(reg==0x164280 && translation.enabling && pc==0x6d77e) { + value=translation.mux(value,pc); + translation.prepareMux([](uint32_t r){return mmio.at(r);},[](uint32_t r,uint32_t v){mmio[r]=v;},value); + } + } if(reg==0x46010 || reg==0x46014){value&=~0x44000000U; if((value&0x08000000)&&fault!=1)value|=0x04000000; if((value&0x80000000)&&fault!=2)value|=0x40000000;} @@ -88,7 +102,53 @@ def array(name): assert(mmio[cfg1]==(hdmi?0xe84U:rate==6?0x90U:rate==10?0x88U:rate==20?0x48U:0x44U)); disable(ctl,port,nullptr);assert(!errors&&!delays&&put==1&&!mmio[pll]&&(mmio[0x164280]&off));++cases; } - assert(cases==60 && counterexamples==10);munmap(image,0x400000); + assert(cases==60 && counterexamples==10); + // Same native bodies and shared allocator on TGL/Display13. Occupied peer + // PLLs have deliberately different coefficients, making clobber visible. + unsigned sharing=0; + for(auto layout:{XELPComboClock::Layout::TGL,XELPComboClock::Layout::Display13}) + for(unsigned target=0;target<2;++target)for(unsigned peer=0;peer<2u;++peer) + for(unsigned occupied=0;occupied<2;++occupied)for(bool hdmi:{false,true}) { + if(peer==target)continue; + allocation={};get=put=delays=errors=fault=0;correction=true; + const auto off=1U<<(peer==2?24:10+peer),shift=2*peer; + mmio={{0x164280,(0x01000C00&~(off|(3U<(port,0x114)=hdmi?0:1;field(port,0x128)=20; + field(ctl,0xea8)=2; + translation={target,choice.pll,true};managed=true; + uint32_t params[]={464,2049,2,3,2};enable(ctl,port,nullptr,hdmi?params:nullptr); + assert(mmio[busy]==0xcc000000 && mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890); + assert(((mmio[0x164280]>>(2*target))&3)==choice.pll); + const auto nominal=hdmi?(464|(2049<<10)):0x1c001a5; + const auto expected=(nominal&~0x1fffc00U)|(((((nominal>>10)&32767)+1)/2)<<10); + assert(mmio[0x164284+8*choice.pll]==expected); + translation.enabling=false;disable(ctl,port,nullptr); + assert(!mmio[0x46010+4*choice.pll] && mmio[busy]==0xcc000000); + assert(mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890 && !errors && !delays && get==1 && put==1); + if(peer<2) { + alignas(8) uint8_t other[0x700]{};uint32_t otherConfig[]={peer,peer+1,hdmi?2U:1U,0,0,0}; + field(other,0x548)=otherConfig; + snap.mux[0]=mmio.at(0x164280);snap.enable[choice.pll]=0; + const auto shared=allocation.share(XELPComboClock::Port(peer),snap); + assert(shared.valid && shared.pll==occupied); + translation={target,shared.pll,true};enable(ctl,port,other,hdmi?params:nullptr); + assert(mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890 && mmio[busy]==0xcc000000); + assert(((mmio[0x164280]>>(2*target))&3)==occupied); + translation.enabling=false;disable(ctl,port,port); + assert(mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890 && mmio[busy]==0xcc000000); + assert(!errors && !delays && get==2 && put==2); + } + managed=false;++sharing; + } + printf("PASS %u TGL/Display13 native shared-PLL DP/HDMI cycles; live A/B peer coefficients retained\n",sharing); + munmap(image,0x400000); printf("PASS %u exact native Display13 Combo A/B DP/HDMI clock cycles, %u old 38.4 MHz counterexamples\n",cases,counterexamples); } puts("PASS production Display13 nominal-write owner/caller/reference/register guards"); diff --git a/tests/display13_port_wait.py b/tests/display13_port_wait.py index 748a3925..5e70a22b 100644 --- a/tests/display13_port_wait.py +++ b/tests/display13_port_wait.py @@ -6,7 +6,7 @@ functions='\n'.join(re.search(r'(?:bool|uint32_t|XELP::LinkRequirements) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['framebufferLinkRequirements','framebufferPortWaitPolls','processFramebufferPortWait']) assert s.count('processFramebufferPortWait(patcher,index,address,size)')==1 runtime=re.search(r'bool XE::processFramebufferDonorRuntime\(.*?\n\}',s,re.S).group() -assert 'framebufferDonorRuntimeReady=hdcpReady && tglPanelPowerPropertyReady && meiReady &&\n processFramebufferPortWait' in runtime +assert 'tglPanelPowerPropertyReady && meiReady && ddiRequestReady &&\n processFramebufferPortWait' in runtime assert 'while (delay < 3000' not in s h=(r/'tests/xelp_port_wait_harness.hpp').read_text().replace('// FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='external-link-wait-') as d: diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py index b4b478f9..38c9a3b3 100644 --- a/tests/framebuffer_common.py +++ b/tests/framebuffer_common.py @@ -20,6 +20,8 @@ assert s.count('processFramebufferDonorRuntime(patcher,index,address,size)')==1 code=r''' #include +#include "kern_xelp_combo_clock.hpp" +struct {void *lock=(void*)1;XELPComboClock::ClockAllocation allocation;} comboClockTransaction; #include #include #include @@ -117,5 +119,5 @@ code=code.replace(key,re.search(r'\b'+name+r'\[\]\s*=\s*\{([^}]+)',f).group(1)) with tempfile.TemporaryDirectory(prefix='wex-common-') as d: p=Path(d);(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(['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')]+sys.argv[1:],check=True) diff --git a/tests/framebuffer_donor_runtime.py b/tests/framebuffer_donor_runtime.py index 838f489a..ba6b912c 100644 --- a/tests/framebuffer_donor_runtime.py +++ b/tests/framebuffer_donor_runtime.py @@ -10,7 +10,8 @@ assert s.count('processFramebufferDonorRuntime(patcher,index,address,size);') == 1 assert s.count('wrapTGLMEIWrite,orgTGLMEIWrite') == 1 assert 'wrapTGLMEIWrite, orgTGLMEIWrite' not in s -assert 'hdcpReady && tglPanelPowerPropertyReady && meiReady' in install +assert 'tglPanelPowerPropertyReady && meiReady && ddiRequestReady' in install +assert 'XELPDonor::ddiReleaseSite' in install and 'WEXDonorDDIRequestReady' in install if len(sys.argv) > 1: b = Path(sys.argv[1]).read_bytes() for name, offset in [('init',0x8ab94), ('associations',0x8ac17), ('use',0x8b726), diff --git a/tests/tgl_clock_order.py b/tests/tgl_clock_order.py index f27dbfcf..29a17c2e 100644 --- a/tests/tgl_clock_order.py +++ b/tests/tgl_clock_order.py @@ -15,9 +15,9 @@ void IODelay(unsigned n){assert(n==8);++delays;} struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry v;return &v;}void setProperty(const char*,bool){} }; struct XE { - static bool adlsBeginClock(void*,void*,bool,bool&){assert(false);return false;} - static void adlsEndClock(){assert(false);} - bool adlsDisplaySelected=false;static bool adlsClockTarget(void*,void*,void*){return false;} + static bool comboBeginClock(void*,void*,bool,bool&){assert(false);return false;} + static void comboEndClock(){assert(false);} + bool adlsDisplaySelected=false;static bool comboClockTarget(void*,void*,void*){return false;} void *tglObservationController;uintptr_t tglClockOffCaller=1,orgTGLPathLookup,orgTGLDisableClocks; bool tglDPSSTMode(unsigned d,unsigned mode){return d>=3&&d<=8&&(mode==3||mode==4);} static volatile uint8_t *tglClockOrderTarget(void*,void*,void*,void*); diff --git a/tests/tgl_combo_pll_write.py b/tests/tgl_combo_pll_write.py index ab791cfe..4b8447fa 100644 --- a/tests/tgl_combo_pll_write.py +++ b/tests/tgl_combo_pll_write.py @@ -5,7 +5,7 @@ root=Path(__file__).resolve().parents[1] s=(root/'WhateverXe/kern_xe.cpp').read_text() start=s.index('void XE::wrapTGLRegisterWrite(') -guard=s[s.index('\n',start)+1:s.index(' if(adlsClockAccess(access))',start)] +guard=s[s.index('\n',start)+1:s.index(' comboClockTranslateWrite(',start)] guard=guard.replace('reinterpret_cast(__builtin_return_address(0))','caller') install=s[s.index('// Qualify the reference-clock field producer'):s.index('if (tglHDMIB)',s.index('// Qualify the reference-clock field producer'))] if len(sys.argv)>1: diff --git a/tests/xelp_combo_clock.cpp b/tests/xelp_combo_clock.cpp new file mode 100644 index 00000000..729c618e --- /dev/null +++ b/tests/xelp_combo_clock.cpp @@ -0,0 +1,34 @@ +#include "kern_xelp_combo_clock.hpp" +#include +#include +#include +using namespace XELPComboClock; +int main(){ + assert(donorPC(0x100000000,0x10006D77E)==0x6D77E); + assert(donorPC(0x100000000,0x20006D77E)==UINT32_MAX); + assert(donorPC(0x100000000,0xFFFF0000)==UINT32_MAX); + unsigned cases=0; + 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; + ClockSnapshot s;s.layout=layout;s.mux[0]=0x01000C00; + Port p(target),q(peer);s.mux[0]=(s.mux[0]&~(q.clockOff|(3U< +#include +#include +#include +struct XE { + bool adlsDisplaySelected=false,display13FramebufferRequested=false,tglActive=true; + struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; + void *tglObservationController,*adlnTopologyController; + uint8_t tglPlatformCopy[0x1c0]{},adlnPlatform[0x1c0]{}; + Display13OutputWiring adlnOutputWiring; + static bool adlsClockTarget(void*,void*,void*){return false;} + static bool comboClockTarget(void*,void*,void*); +} state,*callbackXE=&state; +FUNCTION +alignas(8) uint8_t ctl[0xf00]{},ports[2][0x700]{},access[0x80]{}; +templateT &at(void *p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} +int main(){ + for(bool d13:{false,true}) { + state.display13FramebufferRequested=d13;state.adlnTopologyController=state.tglObservationController=ctl; + state.adlnOutputWiring.valid=true;state.adlnOutputWiring.hdmi=3;state.adlnOutputWiring.ddc[0]=1;state.adlnOutputWiring.ddc[1]=2; + auto platform=d13?state.adlnPlatform:state.tglPlatformCopy; + at(ctl,0xc40)=access;at(access,0x60)=0x200000; + for(unsigned d=0;d<2;++d) { + const uint32_t c[]={d,d+1,2,0,0x800,0x202};memcpy(platform+0x20+24*d,c,sizeof(c)); + at(ports[d],0x548)=platform+0x20+24*d;at(ports[d],0x578)=access;at(ports[d],0x5b8)=d?0x6c10c:0x16210c; + } + for(unsigned d=0;d<2;++d) { + auto p=ports[d];assert(XE::comboClockTarget(ctl,p,nullptr));assert(XE::comboClockTarget(ctl,p,ports[1-d])); + assert(!XE::comboClockTarget(nullptr,p,nullptr));assert(!XE::comboClockTarget(access,p,nullptr)); + at(p,0x578)=ctl;assert(!XE::comboClockTarget(ctl,p,nullptr));at(p,0x578)=access; + auto config=at(p,0x548);uint32_t clone[6];memcpy(clone,config,sizeof(clone)); + at(p,0x548)=clone;assert(!XE::comboClockTarget(ctl,p,nullptr));at(p,0x548)=config; + config[3]=1;assert(!XE::comboClockTarget(ctl,p,nullptr));config[3]=0; + config[0]=3;assert(!XE::comboClockTarget(ctl,p,nullptr));config[0]=d; + at(p,0x5b8)^=0x1000;assert(!XE::comboClockTarget(ctl,p,nullptr));at(p,0x5b8)^=0x1000; + at(access,0x60)=0x164290;assert(!XE::comboClockTarget(ctl,p,nullptr));at(access,0x60)=0x200000; + } + } + puts("PASS actual TGL/Display13 Combo entry guards, bound shared source, TC/foreign/cloned/unmapped rejection"); +} +'''.replace('FUNCTION',f) +with tempfile.TemporaryDirectory() 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/tools/test.py b/tools/test.py index 90e48568..8217c70a 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', '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_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] +python_tests += ['xelp_combo_clock_guard','display13_tc_preparation','display13_retirement','display13_port_wait','display13_agdc','display13_training_frontend','framebuffer_donor_runtime'] 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 68dd7a565749a868bab335a50dc509c27baacf35 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:53:59 +0900 Subject: [PATCH 54/60] Share bounded GOP waits and buffer stop sequence --- WhateverXe/kern_adls_boot.hpp | 27 ++++++++------------------ WhateverXe/kern_display13_boot.hpp | 30 ++++++++--------------------- WhateverXe/kern_xelp_gop_stop.hpp | 31 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 41 deletions(-) create mode 100644 WhateverXe/kern_xelp_gop_stop.hpp diff --git a/WhateverXe/kern_adls_boot.hpp b/WhateverXe/kern_adls_boot.hpp index 314cae1b..f3ce595b 100644 --- a/WhateverXe/kern_adls_boot.hpp +++ b/WhateverXe/kern_adls_boot.hpp @@ -5,6 +5,7 @@ #ifndef KERN_ADLS_BOOT_HPP #define KERN_ADLS_BOOT_HPP #include "kern_adls_clock.hpp" +#include "kern_xelp_gop_stop.hpp" #include "kern_alderlake_n.hpp" #include "kern_display13_fb_irq.hpp" namespace AlderLakeS { @@ -99,13 +100,8 @@ template Result prepare(IO &io,bool admitted,const Wiring &w) { }; const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t value) { r.reg=reg; - for(unsigned i=0;i<1000;++i) { - r.value=io.read(reg); - if(r.value==UINT32_MAX || !stable()){r.status=Status::Drift;return false;} - if((r.value&mask)==value)return true; - io.delay(); - } - r.status=Status::Timeout;return false; + return XELPGOP::poll(io,stable,[&](uint32_t v){return (v&mask)==value;}, + reg,r.value,r.status,Status::Drift); }; for(unsigned p=r.primary+1;p<3;++p)if(r.active&(1U< Result prepare(IO &io,bool admitted,const Wiring &w) { const auto frame=io.read(0x70040+bank); if(frame==UINT32_MAX || !write(0x70180+bank,get(0x70180+bank)&~0x80000000U) || !write(0x7019C+bank,get(0x7019C+bank)))return r; - bool drained=false; - for(unsigned i=0;i<1000;++i) { - const auto now=io.read(0x70040+bank); - if(now==UINT32_MAX || !stable()){r.status=Status::Drift;return r;} - if(uint32_t(now-frame)>=2 && uint32_t(now-frame)<0x10000000U){drained=true;break;} - io.delay(); - } - if(!drained){r.status=Status::Timeout;return r;} + uint32_t now; + if(!XELPGOP::poll(io,stable,[&](uint32_t v){return uint32_t(v-frame)>=2 && uint32_t(v-frame)<0x10000000U;}, + 0x70040+bank,now,r.status,Status::Drift))return r; r.stage=2; if(!write(0x70008+bank,get(0x70008+bank)&~0x80000000U) || !wait(0x70008+bank,0xC0000000U,0))return r; r.stage=3; @@ -133,10 +124,8 @@ template Result prepare(IO &io,bool admitted,const Wiring &w) { } } r.stage=4; - if(!write(port.buffer,get(port.buffer)&~0x80000000U))return r; - if(((get(0x60400+bank)>>24)&7)==2 && - (!write(0x60540+bank,get(0x60540+bank)&~0x80000000U) || !wait(0x60540+bank,0x80000000U,0)))return r; - if(!wait(port.buffer,0x80000080U,0x80))return r; + if(!XELPGOP::stopBuffer(write,wait,bank,port.buffer,get(port.buffer),get(0x60540+bank), + ((get(0x60400+bank)>>24)&7)==2))return r; r.stage=5; if(!write(0x46140+4*p,get(0x46140+4*p)&0x0FFFFFFFU) || !wait(0x46140+4*p,0xF0000000U,0))return r; r.parked|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo const auto write=[&](uint32_t reg,uint32_t value) { io.write(reg,value);++r.writes; if(reg==Display13TC::Clock::Gate)gate=value; - io.read(reg); + io.read(reg);return true; }; const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t expected) { r.lastReg=reg;r.expected=expected; - for(unsigned n=0;n<1000;++n) { - r.observed=io.read(reg); - if(r.observed==UINT32_MAX || !peer()) {r.status=Status::PeerChanged;return false;} - if((r.observed&mask)==expected)return true; - io.delay(); - } - r.status=Status::Timeout;return false; + return XELPGOP::poll(io,peer,[&](uint32_t v){return (v&mask)==expected;}, + reg,r.observed,r.status,Status::PeerChanged); }; for(unsigned p=1;p<3;++p)if(active&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo AlderLakeN::value(boot,0x70008+bank,conf);AlderLakeN::value(boot,0x60400+bank,trans);buf=io.read(bufReg); r.stage=1;r.framesBefore=io.read(0x70040+bank); write(0x70180+bank,plane&~0x80000000U);write(0x7019C+bank,surface); - for(unsigned n=0;n<1000;++n) { - r.framesAfter=io.read(0x70040+bank); - if(r.framesAfter==UINT32_MAX || !peer()) {r.status=Status::PeerChanged;return r;} - if(uint32_t(r.framesAfter-r.framesBefore)>=2 && uint32_t(r.framesAfter-r.framesBefore)<0x10000000)break; - if(n==999) {r.status=Status::Timeout;return r;}io.delay(); - } + if(!XELPGOP::poll(io,peer,[&](uint32_t v){return uint32_t(v-r.framesBefore)>=2 && uint32_t(v-r.framesBefore)<0x10000000U;}, + 0x70040+bank,r.framesAfter,r.status,Status::PeerChanged,false))return r; if(!wait(0x70180+bank,0x80000000,0))return r; // Real frame latch, STOP_ACK and buffer idle precede DDB release. // Keep link resources until consumers stop. The TC PLL/ownership is @@ -253,14 +245,8 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo } r.stage=3;write(0x60404+bank,0);write(0x60400+bank,trans&~(0x80000000U|(15U<<27)|(7U<<24))); if(!wait(0x60400+bank,0x80000000,0))return r; - r.stage=4;write(bufReg,buf&~0x80000000U); - // Display12/13 disable_ddi_buf: buffer off, transport off, then idle. - // Stop only this transcoder; preserve the adopted GOP's M/N and link. - if(dpPipes&(1U< +namespace XELPGOP { +// Preserve the caller's diagnostic value, including changes made by stable(). +// Display13's frame-drain deadline omits the final delay; register waits do not. +template +bool poll(IO &io,Stable &stable,Ready ready,uint32_t reg,uint32_t &value, + Status &status,Status drift,bool delayOnTimeout=true) { + for(unsigned n=0;n<1000;++n) { + value=io.read(reg); + if(value==UINT32_MAX || !stable()){status=drift;return false;} + if(ready(value))return true; + if(n<999 || delayOnTimeout)io.delay(); + } + status=Status::Timeout;return false; +} +// Linux intel_ddi_disable_transcoder_func / disable_ddi_buf: the transcoder +// has stopped before entry. Keep shared PLLs/power held until buffer idle. +template +bool stopBuffer(Write &write,Wait &wait,uint32_t bank,uint32_t bufferReg, + uint32_t buffer,uint32_t transport,bool dp) { + if(!write(bufferReg,buffer&~0x80000000U))return false; + if(dp && (!write(0x60540+bank,transport&~0x80000000U) || + !wait(0x60540+bank,0x80000000U,0)))return false; + return wait(bufferReg,0x80000080U,0x80); +} +} +#endif From 6094ef9a571f5503944a01c304b855c6d7cb9f6f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Mon, 26 Oct 2026 20:01:48 +0900 Subject: [PATCH 55/60] Fix byte addressing of display platform records --- WhateverXe/kern_xe.cpp | 4 ++-- tests/display13_tc_preparation_fixture.cpp | 4 ++-- tests/xelp_combo_clock_guard.py | 4 ++-- tests/xelp_port_wait_harness.hpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 418329aa..60fb0493 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11512,7 +11512,7 @@ XELP::LinkRequirements XE::framebufferLinkRequirements(void *port,uint32_t mode) (self->adlnOutputWiring.dp&(1U<(self->tglPlatformCopy+0x20+24*ddi); + const bool bound=config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x20+24*ddi); if(!bound)return req; if(mode==1 && self->tglHDMIEligible(config)) req={self->xeProfile.display,R::PHY::Combo,R::Protocol::HDMI}; @@ -17778,7 +17778,7 @@ bool XE::comboClockTarget(void *controller,void *port,void *otherPort) { Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring); return s->tglActive && controller==s->tglObservationController && XELP::displayAdapter(s->xeProfile.family)==XELP::DisplayAdapter::TGL && - config==reinterpret_cast(s->tglPlatformCopy+0x20+24*config[0]); + config==reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*config[0]); } bool XE::comboClockPower(void *power,bool acquire) { diff --git a/tests/display13_tc_preparation_fixture.cpp b/tests/display13_tc_preparation_fixture.cpp index 40956902..df3abb25 100644 --- a/tests/display13_tc_preparation_fixture.cpp +++ b/tests/display13_tc_preparation_fixture.cpp @@ -48,7 +48,7 @@ struct Display13FBTCIO:Display13FBPowerIO { }; struct XE { bool display13FramebufferRequested=true,display13FBTCHooksReady=true;XELP::Profile xeProfile; - alignas(8) uint8_t adlnPlatform[0x1c0]{},tglPlatformCopy[0x1c0]{};Display13OutputWiring adlnOutputWiring; + uint64_t adlnPlatform[0x1c0/sizeof(uint64_t)]{},tglPlatformCopy[0x1c0/sizeof(uint64_t)]{};Display13OutputWiring adlnOutputWiring; uintptr_t display13TCBaseMode=0,orgDisplay13TCMode=0; bool tglHDMIEligible(const uint32_t*){return false;}XELP::Display12SSTRoutes tglSSTRoutes(){return {};} static XELP::LinkRequirements framebufferLinkRequirements(void*,uint32_t); @@ -71,7 +71,7 @@ int main(){ Display13FBPowerIO power{&map};assert(Display13Power::initialize(power,c.bridge.state,Display13Power::nativeDomains(0x7b))); c.bridge.begun=true;c.bridge.selectedPorts=0x78;c.bridge.bootPort=ddi; auto &w=instance.adlnOutputWiring;w.valid=true;if(dp)w.dp=1u<(instance.adlnPlatform+0x20+24*ddi); + auto cfg=reinterpret_cast(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; put(0x548,cfg);put(0x578,c.access);put(0x120,ddi);put(0x590,0x64000+256*ddi);nativeCalls=0; diff --git a/tests/xelp_combo_clock_guard.py b/tests/xelp_combo_clock_guard.py index a91b8c6b..7272e3f5 100644 --- a/tests/xelp_combo_clock_guard.py +++ b/tests/xelp_combo_clock_guard.py @@ -14,7 +14,7 @@ bool adlsDisplaySelected=false,display13FramebufferRequested=false,tglActive=true; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; void *tglObservationController,*adlnTopologyController; - uint8_t tglPlatformCopy[0x1c0]{},adlnPlatform[0x1c0]{}; + uint64_t tglPlatformCopy[0x1c0/sizeof(uint64_t)]{},adlnPlatform[0x1c0/sizeof(uint64_t)]{}; Display13OutputWiring adlnOutputWiring; static bool adlsClockTarget(void*,void*,void*){return false;} static bool comboClockTarget(void*,void*,void*); @@ -26,7 +26,7 @@ for(bool d13:{false,true}) { state.display13FramebufferRequested=d13;state.adlnTopologyController=state.tglObservationController=ctl; state.adlnOutputWiring.valid=true;state.adlnOutputWiring.hdmi=3;state.adlnOutputWiring.ddc[0]=1;state.adlnOutputWiring.ddc[1]=2; - auto platform=d13?state.adlnPlatform:state.tglPlatformCopy; + auto platform=reinterpret_cast(d13?state.adlnPlatform:state.tglPlatformCopy); at(ctl,0xc40)=access;at(access,0x60)=0x200000; for(unsigned d=0;d<2;++d) { const uint32_t c[]={d,d+1,2,0,0x800,0x202};memcpy(platform+0x20+24*d,c,sizeof(c)); diff --git a/tests/xelp_port_wait_harness.hpp b/tests/xelp_port_wait_harness.hpp index 4d436c9f..08972c67 100644 --- a/tests/xelp_port_wait_harness.hpp +++ b/tests/xelp_port_wait_harness.hpp @@ -18,7 +18,7 @@ struct KernelPatcher { }; struct XE { bool display13FramebufferRequested=false;XELP::Profile xeProfile; - alignas(8) uint8_t tglPlatformCopy[0x1c0]{},adlnPlatform[0x1c0]{}; + uint64_t tglPlatformCopy[0x1c0/sizeof(uint64_t)]{},adlnPlatform[0x1c0/sizeof(uint64_t)]{}; Display13OutputWiring adlnOutputWiring;XELP::Display12SSTRoutes routes; unsigned hdmiMask=0;void *currentFramebuffer=nullptr; bool tglHDMIEligible(const uint32_t *c) const {return (hdmiMask&(1u<(platform==2?instance.adlnPlatform:instance.tglPlatformCopy); bytes[0x12]=9;auto c=reinterpret_cast(bytes+0x20+24*ddi); c[0]=ddi;c[1]=ddi<3?ddi+1:ddi+6;c[2]=protocol;c[3]=ddi<3?0:1;c[4]=protocol==2?0x800:0x400;c[5]=protocol==2?0x202:0x201; if(platform==1 || ddi<3)instance.routes.combo=1u< Date: Mon, 26 Oct 2026 20:49:15 +0900 Subject: [PATCH 56/60] Exercise production wiring admission in port wait replay --- tests/display13_port_wait.py | 5 ++- tests/display13_port_wait_fixture.cpp | 1 + tests/xelp_port_wait_harness.hpp | 53 +++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/tests/display13_port_wait.py b/tests/display13_port_wait.py index 5e70a22b..f2752521 100644 --- a/tests/display13_port_wait.py +++ b/tests/display13_port_wait.py @@ -8,7 +8,10 @@ runtime=re.search(r'bool XE::processFramebufferDonorRuntime\(.*?\n\}',s,re.S).group() assert 'tglPanelPowerPropertyReady && meiReady && ddiRequestReady &&\n processFramebufferPortWait' in runtime assert 'while (delay < 3000' not in s -h=(r/'tests/xelp_port_wait_harness.hpp').read_text().replace('// FUNCTIONS',functions) +header=(r/'WhateverXe/kern_xe.hpp').read_text() +routing='\n'.join(re.search(pattern,header,re.S).group() for pattern in [ + r'bool tglHDMIEligible\(.*?\n',r'XELP::Display12SSTRoutes tglSSTRoutes\(.*?\n \}']) +h=(r/'tests/xelp_port_wait_harness.hpp').read_text().replace('// FUNCTIONS',functions).replace('// ROUTING',routing) with tempfile.TemporaryDirectory(prefix='external-link-wait-') as d: p=Path(d) for native in [False]+([True] if len(sys.argv)>1 else []): diff --git a/tests/display13_port_wait_fixture.cpp b/tests/display13_port_wait_fixture.cpp index 32eb1665..3ce2ce98 100644 --- a/tests/display13_port_wait_fixture.cpp +++ b/tests/display13_port_wait_fixture.cpp @@ -58,6 +58,7 @@ int main(int argc,char **argv) { for(unsigned ddi:{0U,1U,3U,4U,5U,6U})for(unsigned protocol:{1U,2U}) for(unsigned ack:{0U,495U,500U,600U,995U,1000U,2000U,2995U,3000U,~0U}) for(unsigned fault=0;fault<3;++fault) { + if(platform==1 && !(AlderLakeS::ComboBackendMask&(1u<=3?1:protocol); memcpy(&config,selectionPort+0x548,sizeof(config)); const unsigned mode=platform==1 || ddi<3?1:4; diff --git a/tests/xelp_port_wait_harness.hpp b/tests/xelp_port_wait_harness.hpp index 08972c67..42acc4aa 100644 --- a/tests/xelp_port_wait_harness.hpp +++ b/tests/xelp_port_wait_harness.hpp @@ -5,6 +5,9 @@ #include "kern_xelp_port_wait.hpp" #include "kern_xelp_link_requirements.hpp" #include "kern_xelp_sst_route.hpp" +#include "kern_tgl_tc_boot.hpp" +#include "kern_tgl_hdmi_wiring.hpp" +#include "kern_alderlake_s.hpp" #include "kern_display13_fb_topology.hpp" #include "kern_display13_fb_memory.hpp" using mach_vm_address_t=uintptr_t; @@ -19,10 +22,14 @@ struct KernelPatcher { struct XE { bool display13FramebufferRequested=false;XELP::Profile xeProfile; uint64_t tglPlatformCopy[0x1c0/sizeof(uint64_t)]{},adlnPlatform[0x1c0/sizeof(uint64_t)]{}; - Display13OutputWiring adlnOutputWiring;XELP::Display12SSTRoutes routes; - unsigned hdmiMask=0;void *currentFramebuffer=nullptr; - bool tglHDMIEligible(const uint32_t *c) const {return (hdmiMask&(1u<(platform==2?instance.adlnPlatform:instance.tglPlatformCopy); bytes[0x12]=9;auto c=reinterpret_cast(bytes+0x20+24*ddi); - c[0]=ddi;c[1]=ddi<3?ddi+1:ddi+6;c[2]=protocol;c[3]=ddi<3?0:1;c[4]=protocol==2?0x800:0x400;c[5]=protocol==2?0x202:0x201; - if(platform==1 || ddi<3)instance.routes.combo=1u<=3 && protocol==2)continue; + if(platform==1 && !(AlderLakeS::ComboBackendMask&(1u<(selectionPort,0x548); + uint32_t clone[6];memcpy(clone,config,sizeof(clone));auto clonePtr=clone; + memcpy(selectionPort+0x548,&clonePtr,sizeof(clonePtr));assert(XE::framebufferPortWaitPolls(selectionPort)==100); + memcpy(selectionPort+0x548,&config,sizeof(config)); + if(platform!=2 && protocol==2){ + instance.tglHDMIWiringApplied=false;assert(XE::framebufferPortWaitPolls(selectionPort)==100); + instance.tglHDMIWiringApplied=true;config[1]^=1;assert(XE::framebufferPortWaitPolls(selectionPort)==100);config[1]^=1; + } + if(platform==1 && protocol==1){ + instance.adlsWiring.valid=false;assert(XE::framebufferPortWaitPolls(selectionPort)==100);instance.adlsWiring.valid=true; + } + if(platform==0 && ddi>=3){ + instance.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);assert(XE::framebufferPortWaitPolls(selectionPort)==100); + instance.tglTCAutoCandidates=1u< Date: Tue, 27 Oct 2026 02:27:47 +0900 Subject: [PATCH 57/60] Prepare WEX 0.6.0 release --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 37 +++++++++++++++++++++++++++---------- README_ja.md | 17 +++++++++++------ tools/build.py | 2 +- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e01b57c1..1e2a7a5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.6.0 + +Use [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) for the new mobile GPU and fixed TC display support. Updating both components is recommended. + +### Added + +- Add Alder Lake-H/P/U and Raptor Lake-H/P/U GPU support with automatic resource detection and VBT-selected fixed HDMI routes. +- Add experimental fixed TC DisplayPort output with matching AUX wiring; Alder Lake-H has been tested through a DP-to-HDMI adapter. +- Support startup with multiple fixed TC displays, including supported firmware-scaled boot displays. + +### Fixed + +- Improve display clock ownership, output shutdown and reuse across the shared external-display paths. +- Stop display teardown and reuse when required shutdown steps fail. +- [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) expands shader input support on Sonoma–Tahoe and improves observed Tahoe App Store and Maps rendering. + +Alder Lake-H hardware checks cover Big Sur–Tahoe. Other newly accepted mobile configurations remain untested. Display13 internal panels and Type-C-managed output still need implementation; existing DisplayPort reconnection and Sequoia App Store video hangs remain unresolved. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for coverage and limits. + ## 0.5.0 The newly added GPUs require [WhateverXe Graphics 0.2.0](https://github.com/b00t0x/WhateverXe-Graphics/releases). Its rendering fixes also benefit previously supported GPUs; updating both components is recommended. diff --git a/README.md b/README.md index b7614878..f95af9cd 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,10 @@ This project is experimental and requires the signed graphics package from [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics). It installs the graphics stack under `/Library` and updates the Auxiliary Kernel Collection (AuxKC), without patching System files or snapshots. WEX and the graphics package -have independent release versions. Graphics **0.2.0** is needed for the GPUs -added in WEX **0.5.0** and supplies the latest rendering fixes. Previously -supported Tiger Lake/Alder Lake-N configurations can retain Graphics 0.1.0, -but will not receive the new package fixes or features requiring its updated -components. Use WEX 0.5.0 with Graphics 0.2.0 for the complete set of current features and fixes. +have independent release versions. Use WEX **0.6.0** with Graphics **0.3.0** +for the current GPU, display and shader-compiler improvements. The newly added +Alder Lake-H/P/U and Raptor Lake-H/P/U support requires both updates. +Previously supported GPUs can also benefit from the updated graphics package. ## Compatibility @@ -42,7 +41,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 on Big Sur | +| Alder Lake-H | 96-EU graphics (`46A6`); fixed TC HDMI and DisplayPort through a DP-to-HDMI adapter | | 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 @@ -54,7 +53,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. -- Alder Lake-H/P/U and Raptor Lake-H/P/U with supported fixed HDMI wiring. +- Other Alder Lake-H/P/U and Raptor Lake-H/P/U configurations with supported fixed HDMI or fixed TC DisplayPort wiring. - Other supported Alder Lake-S configurations, Alder Lake-HX, Raptor Lake-S and Raptor Lake-HX. These devices use implemented support shared with the tested families. Their @@ -95,7 +94,7 @@ supported. With the signed graphics package and linked Mod AMFIPass, Tahoe requires `CoreMedia / AllowOnePassMetalScaling` to be disabled; the WhateverXe Graphics installer applies this setting. -Boot the target OS with WEX **0.3.0 or newer** (0.5.0 for newly added GPUs) before installing the graphics +Boot the target OS with WEX **0.3.0 or newer** (0.5.0 for S/HX and H45 GPUs, 0.6.0 for H/P/U mobile Display13 GPUs) before installing the graphics pkg. If an older Xe System patch is installed, restore it using its original recovery procedure first. The pkg does not change EFI, System files, snapshots or SIP settings. See [installation and removal](https://github.com/b00t0x/WhateverXe-Graphics#install) @@ -127,10 +126,20 @@ from `HECI` or `MEI` to `IMEI` is unnecessary. Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses the same VBT-driven Combo A/B and fixed TC1–4 HDMI routing on ADL-N and ADL-P/RPL-P. A matching Compat provider with fixed-TC capability is required for TC routes. The older explicit ADL-N HDMI property remains Combo-only. Experimental fixed TC1–TC4 SST DisplayPort support requires matching VBT AUX wiring and a matching Compat build. Display and reconnect have been verified on ADL-H through a DP-to-HDMI adapter while retaining Metal, OpenGL and video acceleration. Other Display13 families and direct DP sinks remain unverified. Fixed TC DP boot handoff has been verified on ADL-H through a DP-to-HDMI adapter. It requires a supported linear RGB surface and validated stolen-memory mapping. Experimental multi-output boot handoff retains pipe A and stops the other firmware outputs before native startup. Firmware upscaling is accepted only with a verified pipe-bound scaler covering the full output. Dual-output scaled boot and three-output boot have been verified on ADL-H with a DP-to-HDMI adapter and fixed HDMI outputs. Cold unused fixed TC ports left owned by firmware are released only after checking that their output, clocks and transactions are inactive. MST, USB-C DP Alt Mode, Thunderbolt and cross-routed AUX remain unsupported. +Display13 uses VBT-described Combo A/B and fixed TC1–TC4 HDMI routes on +ADL-N and ADL-P/RPL-P. Experimental fixed TC1–TC4 SST DisplayPort requires +matching VBT AUX wiring. Use WEX 0.6.0 with Graphics 0.3.0 for these fixed TC +paths. The older explicit ADL-N HDMI property remains Combo-only. -ADL-P/RPL-P uses its physical ID and fuse-derived resources with the shared Display13 backend. Fixed HDMI routes come from VBT; `-wex-noaccel` retains framebuffer-only operation. A legacy 9A49 donor selector is accepted but unnecessary. Use matching Graphics payloads containing the mobile IDs and provider; Graphics 0.2.0 does not include them. The matching Compat provider supplies an AGDP personality only for the admitted Display13 framebuffer client. Its default policy keeps callback registration active without a board-ID-specific override. +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 Display13 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 Display13. +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. ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. @@ -196,6 +205,7 @@ Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resour | Tiger Lake / Alder Lake-N | Big Sur–Tahoe: dual 1920×1080 at 60 Hz output, Metal/MPS, OpenGL, OpenCL, hardware H.264 encode/decode, HEVC decode, display sleep/wake, Safari video decoding and Widevine playback | | Tiger Lake laptops | Big Sur–Tahoe: internal 1366×768 eDP display, brightness control, HDMI and USB-C DisplayPort Alt Mode, up to three simultaneous displays; USB-C reconnection remains unreliable | | Alder Lake-N | Big Sur/Tahoe: automatic Y tiling without an EFI `AAPL,GfxYTile` setting | +| Alder Lake-H | Big Sur–Tahoe: Metal, OpenGL, hardware H.264 encode/decode, HEVC decode and three displays using fixed HDMI and a DP-to-HDMI adapter | | Alder Lake-S | Big Sur–Tahoe: Metal, OpenGL, OpenCL, hardware H.264 encode/decode, HEVC decode and three displays (two 1080p60 and one 720p60); Safari video playback; Widevine checked on Big Sur and Tahoe | | Tiger Lake / Alder Lake-S | Tahoe: Chrome hardware-accelerated Canvas, WebGL and WebGPU | @@ -207,9 +217,16 @@ On Big Sur–Tahoe, WebKit GPU access to the Data GPU drivers uses its valid Apple platform signature and service identifier, independently of Safari’s installation or update location. +With Graphics 0.3.0, Sonoma–Tahoe use the OS-native AIR converter for shader +compilation. Tahoe checks confirmed improved App Store scrolling and Maps +labels, panning, zooming and 3D rendering. Compiler UUID changes alone no longer +disable this path; required APIs and compatible compiler images are still checked. + ### Known issues and limits - Some image-conversion operations can crash the application. +- App Store video playback can freeze the GPU on Sequoia; the shader-compiler + update does not resolve this separate issue. - Reconnecting a monitor to a dedicated DisplayPort connector can leave the screen black or freeze the computer. USB-C DisplayPort Alt Mode reconnection can also freeze the computer; this has been reproduced on both Big Sur and diff --git a/README_ja.md b/README_ja.md index 42831cd7..cd72cf2e 100644 --- a/README_ja.md +++ b/README_ja.md @@ -6,7 +6,7 @@ WhateverXeは、一部のIntel Xe-LP内蔵GPU向けにAppleのグラフィック 本プロジェクトは実験段階であり、[WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics)の署名済みグラフィックスpkgが必要です。資材を`/Library`へ配置してAuxiliary Kernel Collection(AuxKC)を更新し、Systemファイルやsnapshotにはパッチを適用しません。WEXとグラフィックスpkgは、それぞれ独立したバージョンでリリースします。 -WEX **0.5.0**で追加したGPUにはWhateverXe Graphics **0.2.0**が必要で、同pkgには最新の描画修正も含まれます。従来対応のTiger Lake/Alder Lake-N構成ではGraphics 0.1.0も利用できますが、新pkgの修正や更新部品を必要とする機能は利用できません。今回の追加機能と修正をすべて利用するには、WEX 0.5.0とGraphics 0.2.0を組み合わせてください。 +GPU・表示・シェーダーコンパイラーの最新の改善を利用するには、WEX **0.6.0**とGraphics **0.3.0**を組み合わせてください。今回追加したAlder Lake-H/P/U・Raptor Lake-H/P/Uへの対応には両方の更新が必要です。従来対応GPUにもグラフィックスpkgの改善が適用されます。 ## 対応範囲 @@ -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`)。Big Surの固定TC HDMI | +| Alder Lake-H | 96 EU(`46A6`)。固定TC HDMIとDP→HDMI変換器経由のDisplayPort | | Alder Lake-S | UHD Graphics 770(`4690`)。HDMI、DisplayPort、固定DisplayPortとして配線されたUSB-C端子 | 各ファミリーの一部構成で確認した結果であり、全モデル・全端子の動作保証ではありません。 @@ -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配線のAlder Lake-H/P/U、Raptor Lake-H/P/U。 +- 対応する固定HDMIまたは固定TC DisplayPort配線の、他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。 - 対応IDの他のAlder Lake-S構成、Alder Lake-HX、Raptor Lake-S、Raptor Lake-HX。 確認済みのファミリーと処理を共有する実装があり、IDは自動で受け付けます。 @@ -76,7 +76,7 @@ SIPは`csr-active-config=03080000`(値`0x803`)に設定してください。 Tahoeでは`CoreMedia / AllowOnePassMetalScaling`の無効化が必要です。WhateverXe Graphicsのインストーラーが設定します。 -グラフィックスpkgはWEX **0.3.0以上**(今回追加したGPUでは0.5.0)を読み込んだ対象OSから実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 +グラフィックスpkgはWEX **0.3.0以上**(S/HX・H45系GPUでは0.5.0、H/P/U系モバイルDisplay13 GPUでは0.6.0)を読み込んだ対象OSから実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 リンク先の **Mod版** アーカイブから`AMFIPass.kext`を展開し、`EFI/OC/Kexts`へ配置して、`Kernel > Add`で有効にしてください。既存のAMFIPassがある場合はこのMod版へ置き換え、両方を同時に読み込まないでください。upstreamのAMFIPassはMod版の署名チェーンに対応していません。 @@ -92,10 +92,11 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13ではADL-NとADL-P/RPL-Pに共通のVBT選択を使い、Combo A/Bと固定TC1〜4のHDMI配線を扱います。TC配線には固定TC capabilityを持つ対応Compat providerが必要です。従来のADL-N HDMI明示propertyはCombo専用のままです。固定TC1〜TC4のSST DisplayPortを試験的に実装しています。VBTのAUX配線が一致することと、対応するCompatビルドが必要です。ADL-HではDP→HDMI変換器を介した表示・再接続と、Metal・OpenGL・動画加速の維持を確認しています。他のDisplay13 familyと直接DP接続の表示先は未検証です。固定TC DPからの起動引継ぎは、ADL-HのDP→HDMI変換器を介した接続で確認済みです。対応するlinear RGB surfaceと検証可能なstolen memory mappingが必要です。複数出力の起動引継ぎは試験的実装で、pipe Aを維持し、他のfirmware出力を停止してからnative driverを開始します。firmwareによる拡大表示は、pipeに接続したscalerが出力全体を覆うことを検証できる場合だけ許可します。ADL-HのDP→HDMI変換器と固定HDMI出力で、拡大表示を伴う2画面接続起動と3画面接続起動を確認しています。firmwareが所有権を残した未使用の固定TC端子は、出力・clock・通信が停止済みと確認できる場合だけ所有権を返します。MST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +Display13では、ADL-NとADL-P/RPL-PのCombo A/B・固定TC1〜TC4 HDMI配線をVBTから選択します。試験的な固定TC1〜TC4 SST DisplayPortには、VBTのAUX配線の一致が必要です。これらの固定TC経路にはWEX 0.6.0とGraphics 0.3.0を使用してください。従来のADL-N HDMI明示propertyはCombo専用です。 -ADL-P/RPL-Pは物理IDとfuseから求めた資源で共有Display13バックエンドを選択します。固定HDMI配線はVBTから取得し、`-wex-noaccel`でFramebufferのみの動作を維持します。旧9A49 donor選択も受け付けますが不要です。モバイル系IDと対応providerを含むGraphics資材が必要で、Graphics 0.2.0には含まれていません。対応するCompat providerは、起動を許可したDisplay13 framebufferのclientだけにAGDP personalityを提供します。既定policyでcallback登録を有効にし、board-IDごとの上書き設定を不要にします。 +ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と他のDisplay13系統は未検証です。Display13のMST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。device-ID上書きや機種別AGDP kextは不要です。`-wex-noaccel`でFramebufferのみの動作を維持します。 ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 @@ -150,6 +151,7 @@ Display 13ではGT資源のprobeも省略し、FB独自の起動メモリ・MMIO | Tiger Lake / Alder Lake-N | Big Sur〜Tahoe:外部2画面の1920×1080・60Hz出力、Metal/MPS、OpenGL、OpenCL、ハードウェアH.264エンコード/デコード、HEVCデコード、画面の消灯/復帰、Safariの動画デコード、Widevine再生 | | Tiger Lakeラップトップ | Big Sur〜Tahoe:内蔵1366×768 eDP、輝度調整、HDMI、USB-C DisplayPort Alt Mode、最大3画面同時出力。USB-Cの再接続には未解決の問題あり | | Alder Lake-N | Big Sur/Tahoe:EFIの`AAPL,GfxYTile`設定なしでのYタイル自動選択 | +| Alder Lake-H | Big Sur〜Tahoe:Metal、OpenGL、ハードウェアH.264エンコード/デコード、HEVCデコード、固定HDMIとDP→HDMI変換器を使った3画面 | | Alder Lake-S | Big Sur〜Tahoe:Metal、OpenGL、OpenCL、ハードウェアH.264エンコード/デコード、HEVCデコード、3画面(1080p60×2+720p60×1)、Safari動画再生。WidevineはBig SurとTahoeで確認 | | Tiger Lake / Alder Lake-S | Tahoe:ChromeのCanvas、WebGL、WebGPUによるハードウェア描画 | @@ -159,9 +161,12 @@ Display 13ではGT資源のprobeも省略し、FB独自の起動メモリ・MMIO Big Sur〜Tahoeでは、有効なApple platform署名とサービス識別子でWebKit GPUを判定し、 Safariのインストール先や更新による配置変更に依存せずData側GPUドライバへのアクセスを扱います。 +Graphics 0.3.0では、Sonoma〜TahoeのシェーダーコンパイルにOS標準のAIR変換を使用します。TahoeではApp Storeのスクロールと、Mapsのラベル・移動・ズーム・3D描画の改善を確認しました。コンパイラーのUUID変更だけではこの経路を無効にせず、必要なAPIとコンパイラーimageの互換性を引き続き確認します。 + ### 既知の問題と制限 - 一部の画像変換処理でアプリがクラッシュすることがあります。 +- SequoiaではApp Storeの動画再生でGPUが停止することがあります。今回のシェーダーコンパイラー更新とは別の未解決事項です。 - 専用DisplayPort端子の抜き差しで黒画面やシステム停止が起きることがあります。 USB-C DisplayPort Alt Modeでも再接続時のシステム停止がBig SurとTahoeの両方で再現しています。 - Safariの動画再生で時折コマ落ちすることがあります。 diff --git a/tools/build.py b/tools/build.py index b2eb8045..2911ce80 100644 --- a/tools/build.py +++ b/tools/build.py @@ -17,7 +17,7 @@ def main(): base+=['-fdebug-compilation-dir=.', '-ffile-prefix-map='+str(ROOT)+'=/WhateverXe', '-ffile-prefix-map='+str(sdk)+'=/MacKernelSDK', '-ffile-prefix-map='+str(lilu)+'=/Lilu'] manifest={} - for name,version,sources in [('WhateverXe','0.5.0',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.5.0',[ROOT/'AppleIntelTGLCompat/AppleIntelTGLCompat.cpp'])]: + for name,version,sources in [('WhateverXe','0.6.0',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.6.0',[ROOT/'AppleIntelTGLCompat/AppleIntelTGLCompat.cpp'])]: bundle=out/(name+'.kext')/'Contents';(bundle/'MacOS').mkdir(parents=True,exist_ok=True);objdir=out/name;objdir.mkdir(exist_ok=True) objects=[] for source in sources: From 9337cd772d0111aed26b003ee589e2e09f8c97c5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 27 Oct 2026 02:29:19 +0900 Subject: [PATCH 58/60] Update Display13 connector and boot handoff limits --- docs/device-selection.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/device-selection.md b/docs/device-selection.md index b6fa1a45..f561c542 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -85,9 +85,12 @@ must match the implemented path; ambiguous or cross-routed AUX layouts are exclu - TGL panels requesting HOBL use standard signal settings; the HOBL power-saving optimization is disabled until its retry behavior is supported. - Display 13: fixed HDMI uses firmware Combo A/B and TC1–4 routes; TC requires - verified legacy mode. DP, USB-C and eDP need further integration. A single - TMDS firmware scanout on pipe A is required for TC boot adoption; simultaneous - firmware TC scanouts are not adopted by the Combo-only multi-GOP normalizer. + verified legacy mode. Experimental fixed TC1–4 SST DP requires matching VBT + AUX wiring. Boot handoff supports validated linear RGB scanout, including + supported firmware scaling, and retires secondary fixed TC firmware outputs + before native startup. ADL-H has been tested with three outputs and a + DP-to-HDMI adapter. Direct DP sinks remain unverified. Combo DP, eDP, MST, + Type-C-managed output and cross-routed AUX still need further implementation. 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 From 280c2b245df94ec2d92a6c95abcaea4ec28eea4a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 27 Oct 2026 02:52:44 +0900 Subject: [PATCH 59/60] Clarify release scope and keep user documentation current --- AGENTS.md | 7 +++++++ CHANGELOG.md | 6 +++--- README.md | 57 ++++++++++++++++++++++------------------------------ README_ja.md | 33 ++++++++++++++---------------- 4 files changed, 49 insertions(+), 54 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3b813996..54c70fea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,13 @@ Source attribution, licenses and reproducible test fixtures remain here. Keep release notes in CHANGELOG.md under an exact version heading. Release CI extracts that section; do not maintain a separate release-note template. +README describes the current state, not release-by-release changes. Recommend +latest component releases instead of accumulating per-version dependency tables. +Use GPU family names rather than internal display-generation labels in README +prose. CHANGELOG describes the affected behavior and OS range; applications used +for validation are not the scope of a general fix. Credit reference repositories +when their work informed a change. + ## Build and test Build with `python3 tools/build.py`; run CPU tests with `python3 tools/test.py`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2a7a5c..192fb6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.6.0 -Use [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) for the new mobile GPU and fixed TC display support. Updating both components is recommended. +Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics/releases) package. ### Added @@ -14,9 +14,9 @@ Use [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/re - Improve display clock ownership, output shutdown and reuse across the shared external-display paths. - Stop display teardown and reuse when required shutdown steps fail. -- [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) expands shader input support on Sonoma–Tahoe and improves observed Tahoe App Store and Maps rendering. +- [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) fixes shader compilation failures on Sonoma–Tahoe by expanding supported AIR inputs. -Alder Lake-H hardware checks cover Big Sur–Tahoe. Other newly accepted mobile configurations remain untested. Display13 internal panels and Type-C-managed output still need implementation; existing DisplayPort reconnection and Sequoia App Store video hangs remain unresolved. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for coverage and limits. +Alder Lake-H hardware checks cover Big Sur–Tahoe. Other newly accepted mobile configurations remain untested. Internal panels and Type-C-managed output on the newly supported mobile GPUs still need implementation; existing DisplayPort reconnection and Sequoia App Store video hangs remain unresolved. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for coverage and limits. ## 0.5.0 diff --git a/README.md b/README.md index f95af9cd..71287983 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,8 @@ This project is experimental and requires the signed graphics package from [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics). It installs the graphics stack under `/Library` and updates the Auxiliary Kernel Collection (AuxKC), without patching System files or snapshots. WEX and the graphics package -have independent release versions. Use WEX **0.6.0** with Graphics **0.3.0** -for the current GPU, display and shader-compiler improvements. The newly added -Alder Lake-H/P/U and Raptor Lake-H/P/U support requires both updates. -Previously supported GPUs can also benefit from the updated graphics package. +have independent release versions. Use the latest releases of both WEX and +WhateverXe Graphics. ## Compatibility @@ -63,8 +61,9 @@ limits are listed in [device selection](docs/device-selection.md). ### Further implementation required -- Display13 eDP, Combo DP and Type-C-managed output need complete transport - integration. Mobile-family ID admission does not enable those output paths. +- On Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U, + internal eDP panels, Combo-PHY DisplayPort and Type-C-managed output need + further implementation. Accepting the GPU ID does not enable these outputs. - Rocket Lake-S: its display power and clock handling differ from Tiger Lake. It is not supported by this release, and no hardware validation is scheduled. @@ -94,7 +93,7 @@ supported. With the signed graphics package and linked Mod AMFIPass, Tahoe requires `CoreMedia / AllowOnePassMetalScaling` to be disabled; the WhateverXe Graphics installer applies this setting. -Boot the target OS with WEX **0.3.0 or newer** (0.5.0 for S/HX and H45 GPUs, 0.6.0 for H/P/U mobile Display13 GPUs) before installing the graphics +Boot the target OS with the latest WEX before installing the latest graphics pkg. If an older Xe System patch is installed, restore it using its original recovery procedure first. The pkg does not change EFI, System files, snapshots or SIP settings. See [installation and removal](https://github.com/b00t0x/WhateverXe-Graphics#install) @@ -124,22 +123,23 @@ On supported GPUs, WhateverXe also normalizes the detected Intel Management Engine device name to `IMEI`, as WhateverGreen does. A separate ACPI rename from `HECI` or `MEI` to `IMEI` is unnecessary. -Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. Use the matching Compat build for non-modular FIA probing. TC5/TC6 and cross-routed AUX require further backend support. +Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. TC5/TC6 and cross-routed AUX require further backend support. -Display13 uses VBT-described Combo A/B and fixed TC1–TC4 HDMI routes on -ADL-N and ADL-P/RPL-P. Experimental fixed TC1–TC4 SST DisplayPort requires -matching VBT AUX wiring. Use WEX 0.6.0 with Graphics 0.3.0 for these fixed TC -paths. The older explicit ADL-N HDMI property remains Combo-only. +Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U use +VBT-described Combo A/B and fixed TC1–TC4 HDMI routes. Experimental fixed +TC1–TC4 SST DisplayPort requires matching VBT AUX wiring. The explicit +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 Display13 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 Display13. +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. 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. + ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. @@ -184,19 +184,20 @@ GPU backend selection is independent of hardware test coverage: untested PCI revisions use the same family implementation. Revision-specific behavior is added when a documented difference or observed failure requires it. -The supported Display 13 backend selects Y-tiled scanout automatically; -`AAPL,GfxYTile` is not required in OpenCore. Existing values are preserved. -Do not use the Apple driver's `-yTileDisable` argument with this backend: -its X-tiled scanout path is unsupported. Display 12 tiling selection is unchanged. +Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U select +Y-tiled scanout automatically; `AAPL,GfxYTile` is not required in OpenCore. +Existing values are preserved. Do not use the Apple driver's `-yTileDisable` +argument on these GPUs: their X-tiled scanout path is unsupported. For framebuffer bring-up, the optional `-wex-noaccel` argument disables accelerator -startup. On Display 13 it also skips GT resource probing while preserving the +startup. On Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U, +it also skips GPU resource probing while preserving the framebuffer's independent boot-memory and MMIO checks. It does not enable an otherwise unsupported display backend. Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. `-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on a selected ADL-P/RPL-P device, for load-only diagnostics without GPU initialization. -`-wex-display13-offline` keeps Display 13 native controller initialization enabled but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. +`-wex-display13-offline` keeps native display controller initialization enabled on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. ## Tested features and known issues @@ -213,20 +214,10 @@ These results cover tested systems, not every GPU, display, adapter or dock. The listed resolutions describe test coverage; they are not a fixed resolution limit imposed by WEX. -On Big Sur–Tahoe, WebKit GPU access to the Data GPU drivers uses its valid -Apple platform signature and service identifier, independently of Safari’s -installation or update location. - -With Graphics 0.3.0, Sonoma–Tahoe use the OS-native AIR converter for shader -compilation. Tahoe checks confirmed improved App Store scrolling and Maps -labels, panning, zooming and 3D rendering. Compiler UUID changes alone no longer -disable this path; required APIs and compatible compiler images are still checked. - ### Known issues and limits - Some image-conversion operations can crash the application. -- App Store video playback can freeze the GPU on Sequoia; the shader-compiler - update does not resolve this separate issue. +- App Store video playback can freeze the GPU on Sequoia. - Reconnecting a monitor to a dedicated DisplayPort connector can leave the screen black or freeze the computer. USB-C DisplayPort Alt Mode reconnection can also freeze the computer; this has been reproduced on both Big Sur and @@ -244,7 +235,7 @@ disable this path; required APIs and compatible compiler images are still checke ## Roadmap -- Complete Display 13 DP, USB-C and internal-display support. +- Complete DisplayPort, USB-C and internal-display support on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U. - Improve display reconnection reliability and investigate full system sleep/wake. ## Build and test diff --git a/README_ja.md b/README_ja.md index cd72cf2e..4e1ecf83 100644 --- a/README_ja.md +++ b/README_ja.md @@ -6,7 +6,7 @@ WhateverXeは、一部のIntel Xe-LP内蔵GPU向けにAppleのグラフィック 本プロジェクトは実験段階であり、[WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics)の署名済みグラフィックスpkgが必要です。資材を`/Library`へ配置してAuxiliary Kernel Collection(AuxKC)を更新し、Systemファイルやsnapshotにはパッチを適用しません。WEXとグラフィックスpkgは、それぞれ独立したバージョンでリリースします。 -GPU・表示・シェーダーコンパイラーの最新の改善を利用するには、WEX **0.6.0**とGraphics **0.3.0**を組み合わせてください。今回追加したAlder Lake-H/P/U・Raptor Lake-H/P/Uへの対応には両方の更新が必要です。従来対応GPUにもグラフィックスpkgの改善が適用されます。 +WEXとWhateverXe Graphicsは、どちらも最新版を使用してください。 ## 対応範囲 @@ -51,8 +51,9 @@ CPUモデル名や機種固有の設定は不要ですが、実機での動作 ### 対応に追加改修が必要 -- Display13のeDP・Combo DP・Type-C管理下の出力は、transportの接続に追加改修が必要です。 - モバイル系IDの受付だけでこれらの出力経路が有効になるわけではありません。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uの内蔵eDPパネル・ + Combo PHY経由のDisplayPort・Type-C管理下の出力には追加改修が必要です。 + GPUのIDを受け付けるだけで、これらの出力が有効になるわけではありません。 - Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 このリリースでは未対応で、実機検証の予定もありません。 @@ -76,7 +77,7 @@ SIPは`csr-active-config=03080000`(値`0x803`)に設定してください。 Tahoeでは`CoreMedia / AllowOnePassMetalScaling`の無効化が必要です。WhateverXe Graphicsのインストーラーが設定します。 -グラフィックスpkgはWEX **0.3.0以上**(S/HX・H45系GPUでは0.5.0、H/P/U系モバイルDisplay13 GPUでは0.6.0)を読み込んだ対象OSから実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 +最新版のWEXを読み込んだ対象OSから、最新版のグラフィックスpkgを実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 リンク先の **Mod版** アーカイブから`AMFIPass.kext`を展開し、`EFI/OC/Kexts`へ配置して、`Kernel > Add`で有効にしてください。既存のAMFIPassがある場合はこのMod版へ置き換え、両方を同時に読み込まないでください。upstreamのAMFIPassはMod版の署名チェーンに対応していません。 @@ -90,13 +91,14 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー 対応GPUでは、WhateverGreenと同様に、検出されたIntel Management Engineのデバイス名も`IMEI`へ統一します。`HECI`や`MEI`から`IMEI`へのACPI renameを別途追加する必要はありません。 -Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。非modular FIAのprobeには対応するCompatビルドも使用してください。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 +Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 -Display13では、ADL-NとADL-P/RPL-PのCombo A/B・固定TC1〜TC4 HDMI配線をVBTから選択します。試験的な固定TC1〜TC4 SST DisplayPortには、VBTのAUX配線の一致が必要です。これらの固定TC経路にはWEX 0.6.0とGraphics 0.3.0を使用してください。従来のADL-N HDMI明示propertyはCombo専用です。 +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接続の表示先と他のDisplay13系統は未検証です。Display13のMST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 +ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と、これらのファミリーの他のGPUは未検証です。これらのGPUではMST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。device-ID上書きや機種別AGDP kextは不要です。`-wex-noaccel`でFramebufferのみの動作を維持します。 + ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 @@ -133,16 +135,16 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 GPU backendの選択と実機検証範囲は独立しています。未検証のPCI revisionも同じfamily実装を使い、 文書化された差分や実際の不具合で必要になった場合にrevision別の処理を追加します。 -対応済みのDisplay 13バックエンドではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 -このバックエンドはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。Display 12のタイル選択は変更しません。 +Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 +これらのGPUはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。 FBの立上げ時は、任意の`-wex-noaccel`引数でAcceleratorの起動を止められます。 -Display 13ではGT資源のprobeも省略し、FB独自の起動メモリ・MMIOの確認は維持します。 +Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UではGPU資源のprobeも省略し、FB独自の起動メモリ・MMIOの確認は維持します。 未対応のDisplayバックエンドを有効にする引数ではありません。 開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 `-wex-adlp-load-only` は選択済みADL-P/RPL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 -`-wex-display13-offline` はDisplay 13のnative controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 +`-wex-display13-offline` はAlder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uのnative display controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 ## 動作確認済みの機能と既知の問題 @@ -158,15 +160,10 @@ Display 13ではGT資源のprobeも省略し、FB独自の起動メモリ・MMIO 検証した機器での結果であり、すべてのGPU・ディスプレイ・アダプター・ドックでの動作を保証するものではありません。 記載した解像度は検証範囲であり、WEXが設けた固定の解像度制限ではありません。 -Big Sur〜Tahoeでは、有効なApple platform署名とサービス識別子でWebKit GPUを判定し、 -Safariのインストール先や更新による配置変更に依存せずData側GPUドライバへのアクセスを扱います。 - -Graphics 0.3.0では、Sonoma〜TahoeのシェーダーコンパイルにOS標準のAIR変換を使用します。TahoeではApp Storeのスクロールと、Mapsのラベル・移動・ズーム・3D描画の改善を確認しました。コンパイラーのUUID変更だけではこの経路を無効にせず、必要なAPIとコンパイラーimageの互換性を引き続き確認します。 - ### 既知の問題と制限 - 一部の画像変換処理でアプリがクラッシュすることがあります。 -- SequoiaではApp Storeの動画再生でGPUが停止することがあります。今回のシェーダーコンパイラー更新とは別の未解決事項です。 +- SequoiaではApp Storeの動画再生でGPUが停止することがあります。 - 専用DisplayPort端子の抜き差しで黒画面やシステム停止が起きることがあります。 USB-C DisplayPort Alt Modeでも再接続時のシステム停止がBig SurとTahoeの両方で再現しています。 - Safariの動画再生で時折コマ落ちすることがあります。 @@ -180,7 +177,7 @@ Graphics 0.3.0では、Sonoma〜TahoeのシェーダーコンパイルにOS標 ## ロードマップ -- Display 13 GPUでのDP・USB-C・内蔵画面対応の拡充。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UでのDisplayPort・USB-C・内蔵画面対応の拡充。 - 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 ## ビルドとテスト From 9a6c40a9fdcd1d2b21639acb1f780715425d2af3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Tue, 27 Oct 2026 02:54:48 +0900 Subject: [PATCH 60/60] Acknowledge XPS13Plus compiler reference work --- README.md | 2 ++ README_ja.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 71287983..bdbe5049 100644 --- a/README.md +++ b/README.md @@ -264,3 +264,5 @@ WhateverGreen source is not a build dependency. WhateverXe retains WhateverGreen's copyright notices and BSD-3-Clause license. See [LICENSE.txt](LICENSE.txt). Lilu and MacKernelSDK are separate dependencies. + +Thanks to [JulioNectar/XPS13Plus](https://github.com/JulioNectar/Hackintosh-XPS13Plus-IrisXe-Tahoe) for sharing its Xe graphics work. Its compiler adapter and LLVM bridge informed the OS-native AIR conversion used in WhateverXe Graphics. diff --git a/README_ja.md b/README_ja.md index 4e1ecf83..81291d24 100644 --- a/README_ja.md +++ b/README_ja.md @@ -194,3 +194,5 @@ WhateverXeとAppleIntelTGLCompatのkext、およびソースのmanifestは`build ## ライセンスとクレジット WhateverXeはWhateverGreenの著作権表示とBSD-3-Clauseライセンスを継承しています。[LICENSE.txt](LICENSE.txt)を参照してください。LiluとMacKernelSDKは別の依存コンポーネントです。 + +Xeグラフィックスの取り組みを公開している[JulioNectar/XPS13Plus](https://github.com/JulioNectar/Hackintosh-XPS13Plus-IrisXe-Tahoe)に感謝します。同リポジトリのcompiler adapterとLLVM bridgeを、WhateverXe GraphicsのOS標準AIR変換の参考にしました。