From 5fd10faae9df087527a066163ee4f611b8f932b6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:46:26 +0900 Subject: [PATCH 01/58] Add qualified Alder Lake-S topology and HDMI adapter primitives --- WhateverXe/kern_alderlake_s.hpp | 131 ++++++++++++++++++++++++++++++++ WhateverXe/kern_vbt.hpp | 8 +- tests/adls_donor.py | 41 ++++++++++ tests/alderlake_s.cpp | 81 ++++++++++++++++++++ tests/firmware_ports.cpp | 7 +- tools/test.py | 2 +- 6 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 WhateverXe/kern_alderlake_s.hpp create mode 100644 tests/adls_donor.py create mode 100644 tests/alderlake_s.cpp diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp new file mode 100644 index 00000000..0536e187 --- /dev/null +++ b/WhateverXe/kern_alderlake_s.hpp @@ -0,0 +1,131 @@ +// ADL-S coordinates from Linux v6.12 intel_bios.c, intel_ddi.c and i915_reg.h. +// Physical DDI, Combo PHY and GMBUS pin are distinct namespaces. This module +// describes hardware; it does not grant donor or runtime admission. +#ifndef KERN_ALDERLAKE_S_HPP +#define KERN_ALDERLAKE_S_HPP +#include +#include "kern_vbt.hpp" + +namespace AlderLakeS { +constexpr uint32_t PortMask=0x79; // A, TC1, TC2, TC3, TC4 +// Initial GT qualification is the measured UHD770 D0 configuration. Do not +// infer fused EU/engine resources for other products from their family name. +inline bool identity(uint32_t pci,uint32_t revision) {return pci==0x46908086 && revision==0x0C;} +struct GT { + uint32_t slices {},dss {},nativeSubslices {},eus {},l3Banks {},urbKiB {}; + static bool decode(uint32_t pci,uint32_t revision,const uint32_t *fuses,GT &out) { + out={}; + if(!identity(pci,revision) || !fuses || fuses[0]!=1 || fuses[1]!=3 || + fuses[2]!=0 || fuses[3]!=0xF0 || fuses[4]!=0xE00FA)return false; + out={1,2,4,32,4,256};return true; + } +}; +struct Port { + uint32_t ddi {},phy {},buffer {},clock {},clockShift {},clockOff {},hpd {}; + static bool resolve(uint32_t ddi,Port &out) { + out={}; + if(ddi>6 || !(PortMask&(1U<=2 && vbtPin<=5?vbtPin+7:0; +} +struct HDMI { + uint32_t selected {},pin[XELP::PortCount] {}; + bool valid {}; + bool matches(const uint32_t *c) const { + return valid && c && c[0](platform); + if(p[0x12]!=9)return false; + for(unsigned d=0;d=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; + const uint32_t panel[]={0,0,0,0,2,0x18}; + if(memcmp(p+0x20+24*d,expected,sizeof(expected)) && + (d || memcmp(p+0x20,panel,sizeof(panel))))return false; + } + for(unsigned d=0;d(p+0x20+24*d); + c[1]=pin[d];c[2]=2;c[3]=0;c[4]=0x800;c[5]=0x202; + } + return true; + } + static HDMI fromFirmware(const XELP::VBT::Ports &ports) { + HDMI out; + if(!ports.valid())return out; + uint32_t used=0; + for(unsigned d=0;d3)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; + } +}; +// PortHAL::init(c004fc1a) constructs DDI controls independently from the +// Combo PHY table at +0x5AC. Its TC1 case leaves that table zero. Populate +// only PHY B's fields; retain TC1's buffer, physical record and AUX slots. +inline bool comboBRegisters(uint32_t *registers,size_t count) { + if(!registers || count!=0x128/4 || registers[(0x590-0x580)/4]!=0x64300 || + registers[(0x64C-0x580)/4]!=0x4610C)return false; + for(unsigned off=0x5AC;off<=0x648;off+=4)if(registers[(off-0x580)/4])return false; + const uint32_t phy[] { + 0x64C04,0x6C100,0x6C104,0x6C10C,0x6C124,0x6C128, + 0x6C014,0x6C028,0x6C030,0x6C03C, + 0x6C688,0x6C888,0x6C988,0x6CA88,0x6CB88,0x6C388, + 0x6C690,0x6C890,0x6C990,0x6CA90,0x6CB90,0x6C390, + 0x6C694,0x6C894,0x6C994,0x6CA94,0x6CB94,0x6C394, + 0x6C604,0x6C804,0x6C904,0x6CA04,0x6CB04,0x6C304, + 0x6C624,0x6C824,0x6C924,0x6CA24,0x6CB24,0x6C324 + }; + static_assert(sizeof(phy)==0x648-0x5AC+4,"Combo PHY register table span"); + memcpy(registers+(0x5AC-0x580)/4,phy,sizeof(phy));return true; +} +// Five-word ClockParams ABI at the exact donor's combo-HDMI call boundary. +// Keep logical divisors separate from register encodings. Display WA +// 22010492432 halves only the DCO fraction for a 38.4 MHz reference. +struct HDMIClock { + uint32_t cfg0 {},cfg1 {}; + static bool encode(const uint32_t *params,uint32_t refCode,HDMIClock &out) { + out={};if(!params || refCode>2 || !params[0] || params[0]>0x3FF || + params[1]>0x7FFF || !params[3] || params[3]>255 || !params[4] || params[4]>3)return false; + uint32_t p=0; + switch(params[2]) {case 2:p=1;break;case 3:p=2;break;case 5:p=4;break;case 7:p=8;break;default:return false;} + const auto fraction=refCode==2?(params[1]+1)/2:params[1]; + const uint32_t k[]={0,1,2,4}; + out.cfg0=params[0]|(fraction<<10); + out.cfg1=(p<<2)|(k[params[4]]<<6); + if(params[3]!=1)out.cfg1|=0x200|(params[3]<<10); + return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp index a5a50072..b6d5ca99 100644 --- a/WhateverXe/kern_vbt.hpp +++ b/WhateverXe/kern_vbt.hpp @@ -120,13 +120,15 @@ inline uint32_t ddi(Family family,uint32_t dvo) { for(unsigned p=0;p<9;++p)if(dvo==dp[p] || dvo==hd[p]) { if(family==Family::TigerLake)return p==2 ? PortCount : p; if(family==Family::AlderLakeN)return p<2 ? p : p>=5 ? p-2 : PortCount; + if(family==Family::AlderLakeS)return p==0 ? 0 : p<5 ? p+2 : PortCount; } return PortCount; } inline Ports parse(const void *data,size_t length,Family family) { Ports r; if(!data || !length)return r; - if(family!=Family::TigerLake && family!=Family::AlderLakeN){r.status=Status::Platform;return r;} + if(family!=Family::TigerLake && family!=Family::AlderLakeN && + family!=Family::AlderLakeS){r.status=Status::Platform;return r;} const auto p=static_cast(data);r.status=Status::Header; if(length<48 || memcmp(p,"$VBT",4))return r; const size_t header=u16(p+22),size=u16(p+24),bdb=u32(p+28); @@ -161,7 +163,9 @@ inline Ports parse(const void *data,size_t length,Family family) { if(stride<38 || stride>64 || (childBytes-5)%stride)return r; for(size_t at=5;at +#include +int main() { + const uint32_t phy[]={PHY};const uint32_t ports[]={PORTS}; + uint32_t registers[0x128/4] {};registers[4]=0x64300;registers[(0x64c-0x580)/4]=0x4610c; + assert(AlderLakeS::comboBRegisters(registers,sizeof(registers)/4)); + assert(!memcmp(registers+(0x5ac-0x580)/4,phy,sizeof(phy))); + alignas(8) uint8_t platform[0x1c0] {};platform[0x12]=9; + memcpy(platform+0x20,ports,sizeof(ports)); + XELP::VBT::Ports firmware;firmware.status=XELP::VBT::Status::Valid; + firmware.present=8;firmware.port[3]={0x60d2,1,2}; + const auto wiring=AlderLakeS::HDMI::fromFirmware(firmware); + assert(wiring.apply(platform,sizeof(platform))); + assert(wiring.matches(reinterpret_cast(platform+0x68))); + puts("PASS exact donor Combo B register table and physical TC1 connector adaptation"); +}'''.replace('PHY',','.join(map(str,phy))).replace('PORTS',','.join(map(str,ports))) +with tempfile.TemporaryDirectory(prefix='adls-donor-') as tmp: + tmp=Path(tmp);(tmp/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(repo/'WhateverXe'), + str(tmp/'test.cpp'),'-o',str(tmp/'test')],check=True) + subprocess.run([str(tmp/'test')],check=True) diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp new file mode 100644 index 00000000..a3b161dd --- /dev/null +++ b/tests/alderlake_s.cpp @@ -0,0 +1,81 @@ +#include "kern_alderlake_s.hpp" +#include +#include +#include +#include +using namespace AlderLakeS; +int main() { + uint32_t fuses[]={1,3,0,0xF0,0xE00FA};GT gt; + assert(GT::decode(0x46908086,0xC,fuses,gt) && gt.eus==32 && gt.nativeSubslices==4 && gt.l3Banks==4 && gt.urbKiB==256); + for(unsigned i=0;i<5;++i)for(unsigned bit=0;bit<32;++bit) { + fuses[i]^=1U<=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; + memcpy(platform+0x20+24*d,c,sizeof(c)); + } + uint8_t before[sizeof(platform)];memcpy(before,platform,sizeof(before)); + assert(h.apply(platform,sizeof(platform))); + assert(h.matches(reinterpret_cast(platform+0x68)) && h.usesDDC(9) && !h.usesDDC(2)); + assert(!memcmp(platform,before,0x68) && !memcmp(platform+0x80,before+0x80,sizeof(platform)-0x80)); + memcpy(platform,before,sizeof(platform));platform[0x68+12]=0; + memcpy(before,platform,sizeof(before));assert(!h.apply(platform,sizeof(platform))); + assert(!memcmp(before,platform,sizeof(platform))); // no partial mutation on a wrong donor + auto bad=firmware;bad.port[3].ddc=0;assert(!HDMI::fromFirmware(bad).valid); + bad=firmware;bad.port[4]=firmware.port[3];assert(!HDMI::fromFirmware(bad).valid); + bad=firmware;bad.port[3].capabilities=1;h=HDMI::fromFirmware(bad);assert(h.valid && !h.selected); + assert(!HDMI::fromFirmware({}).valid); + // Physical TC1 still uses Combo B's clock selector, not DKL PLL1. + Port hdmi;assert(Port::resolve(3,hdmi)); + assert(hdmi.clock==0x164280 && hdmi.clockShift==2 && hdmi.clockOff==0x800); + // The 148.5 MHz firmware state, encoded from logical divisors rather than + // copied into the implementation as a fixed display mode. + const uint32_t params[]={464,2048,2,3,2};HDMIClock clock; + assert(HDMIClock::encode(params,2,clock) && clock.cfg0==0x1001D0 && clock.cfg1==0xE84); + assert(HDMIClock::encode(params,1,clock) && clock.cfg0==0x2001D0); + for(unsigned word=0;word<5;++word) { + uint32_t bad[5];memcpy(bad,params,sizeof(bad));bad[word]=UINT32_MAX; + assert(!HDMIClock::encode(bad,2,clock) && !clock.cfg0 && !clock.cfg1); + } + assert(!HDMIClock::encode(nullptr,2,clock) && !HDMIClock::encode(params,3,clock)); + PLL pll;assert(PLL::resolve(2,pll) && pll.cfg0==0x164294 && pll.enable==0x46018); + assert(PLL::resolve(3,pll) && pll.cfg0==0x1642C0 && pll.enable==0x46030); + assert(!PLL::resolve(4,pll) && !pll.enable); + uint32_t registers[0x128/4] {};registers[4]=0x64300;registers[(0x64C-0x580)/4]=0x4610C; + uint32_t saved[0x128/4];memcpy(saved,registers,sizeof(saved)); + assert(comboBRegisters(registers,sizeof(registers)/4)); + assert(registers[(0x5AC-0x580)/4]==0x64C04 && registers[(0x5B8-0x580)/4]==0x6C10C); + assert(registers[(0x648-0x580)/4]==0x6C324 && registers[4]==0x64300); + assert(!memcmp(registers,saved,0x5AC-0x580)); + assert(!memcmp(registers+(0x64C-0x580)/4,saved+(0x64C-0x580)/4,0x6A8-0x64C)); + assert(!comboBRegisters(registers,sizeof(registers)/4)); // no repeated or foreign table + memcpy(registers,saved,sizeof(saved));registers[4]=0x64400; + assert(!comboBRegisters(registers,sizeof(registers)/4)); + puts("PASS ADL-S physical DDI/Combo PHY/HPD/PLL/DDC coordinates and ambiguous HDMI rejection"); +} diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index d77e23bf..17e52b4a 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -20,7 +20,8 @@ std::vector table(const std::vector&ports){ } int main(int argc,char **argv){ if(argc==3){std::ifstream f(argv[2],std::ios::binary);assert(f);std::vectorb{std::istreambuf_iterator(f),{}}; - auto r=parse(b.data(),b.size(),!strcmp(argv[1],"12")?Family::TigerLake:Family::AlderLakeN);auto w=r.hdmi(3); + const auto family=!strcmp(argv[1],"adls")?Family::AlderLakeS:!strcmp(argv[1],"12")?Family::TigerLake:Family::AlderLakeN; + auto r=parse(b.data(),b.size(),family);auto w=r.hdmi(3); printf("status=%u bdb=%u present=%x unmapped=%u ambiguous=%x hdmi=%x pins=%u,%u,%u valid=%u\n",unsigned(r.status),r.version,r.present,r.unmapped,r.ambiguous,w.hdmi,w.ddc[0],w.ddc[1],w.ddc[2],w.valid); for(unsigned d=0;d<9;++d)if(r.present&(1U< Date: Fri, 23 Oct 2026 02:58:41 +0900 Subject: [PATCH 02/58] Wire guarded ADL-S admission, shared probe and Combo PHY initialization --- AppleIntelTGLCompat/AppleIntelTGLCompat.cpp | 15 +-- WhateverXe/kern_host.cpp | 2 + WhateverXe/kern_xe.cpp | 123 ++++++++++++++++++-- WhateverXe/kern_xe.hpp | 11 +- WhateverXe/kern_xelp_devices.hpp | 4 +- WhateverXe/kern_xelp_platform.hpp | 8 +- WhateverXe/kern_xelp_probe.hpp | 2 + tests/boot_arguments.py | 2 +- tests/device_selection.cpp | 14 ++- tests/provider_binding.py | 2 +- tests/provider_binding_fixture.cpp | 15 ++- tests/tgl_hdmi_wiring_fixture.cpp | 15 +++ tests/tgl_kernel_admission.py | 11 +- tests/xelp_platform.cpp | 13 ++- 14 files changed, 196 insertions(+), 41 deletions(-) diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index 8641d340..b6b9e8c1 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp @@ -19,19 +19,20 @@ template static F method(void *object, size_t offset) { return reinterpret_cast((*static_cast(object))[offset / 8]); } static void *manager(void *controller) { return field(controller, 0xc40); } -static void *hardwareAccess; +static void *legacyTGLAccess; static XELP::ProbeBinding probeBinding; static uint32_t probeBindingState; // 0 unbound, 1 publishing, 2 immutable/pinned. -// Generic register/DSB operations still belong only to the qualified TGL -// backend. The separately bound Display13 port probe never opens this gate. +// Generic register/DSB operations belong to the qualified Display12 families. +// The separately bound Display13 port probe never opens this gate. static bool hardwareAllowed(void *controller) { if (!controller) return false; auto service = static_cast(controller); auto pci = OSDynamicCast(IOPCIDevice, service->getProvider()); const auto profile=XELP::deviceProfile(pci); - if(!profile.admitted() || profile.family!=XELP::Family::TigerLake)return false; - __atomic_store_n(&hardwareAccess, manager(controller), __ATOMIC_RELEASE); + if(!profile.admitted() || (profile.family!=XELP::Family::TigerLake && profile.family!=XELP::Family::AlderLakeS))return false; + if(profile.family==XELP::Family::TigerLake) + __atomic_store_n(&legacyTGLAccess,manager(controller),__ATOMIC_RELEASE); return true; } @@ -213,9 +214,9 @@ extern "C" uint32_t tglProbePort(void *port) { } // ABI0 TGL callers have no binding entry. Only a manager established by // hardwareAllowed with a validated TGL profile may use the legacy path. - // Other backend owners never establish hardwareAccess. + // ADL-S establishes generic access but never this unbound TGL fallback. auto access = field(port, 0x578); - if (!access || access != __atomic_load_n(&hardwareAccess, __ATOMIC_ACQUIRE)) return 0; + if (!access || access != __atomic_load_n(&legacyTGLAccess, __ATOMIC_ACQUIRE)) return 0; auto config = field(port, 0x548); if(!config)return 0; ProbeIO io {access}; diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 27717f49..cff0e5fb 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -42,6 +42,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { gpu->setProperty("WhateverXeDeviceSelection",selection,sizeof(selection)); 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) { 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 1f9ef59d..13e8ccfb 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3732,6 +3732,7 @@ void XE::init() { void XE::selectProfile(const XELP::Profile &profile) { xeProfile=profile; + adlsDetected=profile.admitted() && profile.family==XELP::Family::AlderLakeS; adlnNativeRCSProfileRequested=false; adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN && profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; @@ -3749,7 +3750,8 @@ void XE::selectProfile(const XELP::Profile &profile) { } return; } - tglRequested=profile.admitted() && profile.family==XELP::Family::TigerLake && + tglRequested=profile.admitted() && (profile.family==XELP::Family::TigerLake || + (adlsDetected && XELP::kernelOS(version)==XELP::OS::BigSur)) && !checkKernelArgument("-wex-tgl-off"); tglInactivePorts=tglPG1Poll=tglClockOrderRequested=tglTrainingLeaseRequested=tglRequested; tglProgramEnabled=!checkKernelArgument("-wex-tgl-prog-off"); @@ -4034,7 +4036,10 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { bool XE::xelpBindProbe(void *controller) { if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false; XELP::ProbeConfig config;config.profile=xeProfile; - if(xeProfile.display==XELP::Display::XeD) { + if(adlsDetected) { + if(!tglHDMIWiringApplied || !adlsHDMIWiring.valid || adlsHDMIWiring.selected!=8)return false; + config.combo=config.comboBackend=8; + } else if(xeProfile.display==XELP::Display::XeD) { if(!tglHDMIWiringApplied || !tglTCWiringLoaded || !tglTCWiring.valid)return false; config.combo=tglHDMIWiring.hdmi;config.comboBackend=7; config.legacy=tglTCProbeMask();config.typeC=tglTCWiring.typeC;config.excluded=tglTCWiring.excluded; @@ -4075,6 +4080,48 @@ 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)); + if(xeProfile.family==XELP::Family::AlderLakeS && xeProfile.identity==0x46908086 && + xeProfile.revision==0x0C && !xeProfile.overridden() && physicalGPU && + XELP::kernelOS(version)==XELP::OS::BigSur && (physicalGPU->configRead16(4)&2)) { + // Gen12 forcewake/fuse coordinates are shared with the existing reader. + // Its ADL-N profile verdict is not used to admit ADL-S. No engine, + // GGTT, display, PLL or reset programming is performed here. + 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); + adlsNativeReady=AlderLakeS::GT::decode(xeProfile.identity,xeProfile.revision,gt.fuses,adlsGT) && + (gt.status==AlderLakeN::GTStatus::Complete || gt.status==AlderLakeN::GTStatus::ProfileMismatch) && + gt.requestedMask==3 && gt.releasedMask==3 && !gt.releaseFailedMask && edram==0; + physicalGPU->setProperty("ADLSGTQualified",adlsNativeReady); + physicalGPU->setProperty("ADLSGTBoot",>,sizeof(gt)); + physicalGPU->setProperty("ADLSEDRAM",edram,32); + uint32_t registers[][2] { + {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, + {0x46010,0},{0x46014,0},{0x46018,0},{0x46030,0}, + {0x164280,0},{0x164284,0},{0x164288,0},{0x16428C,0},{0x164290,0},{0x1642BC,0}, + {0xC4000,0},{0xC4030,0},{0xC4034,0},{0xC5014,0}, + {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0}, + {0x70008,0},{0x71008,0},{0x72008,0}, + {0x60000,0},{0x6000C,0},{0x60400,0},{0x60404,0}, + {0x70180,0},{0x70188,0},{0x7018C,0},{0x70190,0},{0x7019C,0},{0x701A4,0}, + {0x1082C0,0},{0x1082C4,0} + }; + for(auto ®:registers)reg[1]=io.read(reg[0]); + physicalGPU->setProperty("ADLSDisplayBoot",registers,sizeof(registers)); + SYSLOG("igfx","ADL-S boot readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x", + static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], + gt.releasedMask,gt.releaseFailedMask); + } + if(map)map->release(); + } if(!xeProfile.admitted())return; xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; if (adlnDetected) { @@ -4114,7 +4161,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // Preserve firmware power/ownership; no MMIO writes. The guarded20G // route first qualifies the shared writable BAR map. All-ones FIA // reads are unavailable, not connected ports. - if (pci->configRead16(4) & 2) { + if (!adlsDetected && (pci->configRead16(4) & 2)) { auto mmio = pci->mapDeviceMemoryWithRegister(0x10, tglMMIOUCRouted ? 0 : kIOMapReadOnly | kIOMapInhibitCache); if (tglMMIOUCRouted) tglUpdateMMIOMap(mmio); @@ -8985,7 +9032,7 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add } // ADL-N and explicit overrides must reach the real selector2 // comparison regardless of the physical ID's signed ordering. - if (skuReady && (adlnDetected || xeProfile.overridden())) { + if (skuReady && (adlnDetected || adlsDetected || xeProfile.overridden())) { static const uint8_t findSplit[] {0x7F,0x15}, replaceSplit[] {0x90,0x90}; auto split=reinterpret_cast(address+0x23C25); skuReady=!memcmp(split,findSplit,sizeof(findSplit)); @@ -9075,7 +9122,8 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add // native subslice count and publish the separately admitted L3 capacity. 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; } - const auto urbReplacement = xeProfile.gt==XELP::GT::ADLNSmall ? urbReplaceAlderLakeN : + if(adlsDetected) {urbReplaceAlderLakeN[7]=adlsGT.nativeSubslices;urbReplaceAlderLakeN[17]=adlsGT.l3Banks;} + const auto urbReplacement = (xeProfile.gt==XELP::GT::ADLNSmall || xeProfile.gt==XELP::GT::ADLSSmall) ? urbReplaceAlderLakeN : (xeProfile.gt==XELP::GT::TGLG4 ? urbReplaceG4 : urbReplace); auto gpuInfo=patcher.solveSymbol(index, "__ZN16IntelAccelerator10getGPUInfoEv",address,size); @@ -10395,6 +10443,18 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (ready) tglPG1FuseReadCaller = fn + 0x2C7; IORegistryEntry::getRegistryRoot()->setProperty("TGLPG1PollReady", ready); } + if(adlsDetected) { + const auto symbol="__ZN17AppleIntelPortHAL4initEP10PortConfig"; + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xFE}; + const auto fn=patcher.solveSymbol(index,symbol,address,size); + const bool known=fn==address+0x5F058 && size>0x5F9F4 && + !memcmp(reinterpret_cast(fn),entry,sizeof(entry)); + patcher.clearError(); + KernelPatcher::RouteRequest route(symbol,wrapADLSPortInit,orgADLSPortInit); + adlsPortInitReady=known && patcher.routeMultiple(index,&route,1,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("ADLSPortInitReady",adlsPortInitReady); + patcher.clearError(); + } { KernelPatcher::RouteRequest routes[] { {"__ZN17AppleIntelPortHAL14enableComboPhyEv", wrapTGLEnableCombo, orgTGLEnableCombo}, @@ -10901,7 +10961,7 @@ bool XE::wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, O // explicit profile or a mismatched donor connector must not be partly applied. bool XE::wrapTGLGetOSInformation(void *controller) { const auto r=FunctionCast(wrapTGLGetOSInformation,callbackXE->orgTGLGetOSInformation)(controller); - if(r)tglBeginTCColdProtection(controller); + if(r && !callbackXE->adlsDetected)tglBeginTCColdProtection(controller); auto self=callbackXE; if(!r)return r; auto c=static_cast(controller); @@ -10924,13 +10984,14 @@ bool XE::wrapTGLGetOSInformation(void *controller) { } auto copy=reinterpret_cast(self->tglPlatformCopy); if(copy!=platform)memcpy(copy,platform,sizeof(self->tglPlatformCopy)); - if(!self->tglHDMIWiringLoaded || !self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) || - !self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy))) { + const bool wiringApplied=self->adlsDetected ? self->adlsHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) : + self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)); + if(!self->tglHDMIWiringLoaded || !wiringApplied) { IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringGuardFailed",true);return false; } auto config=reinterpret_cast(copy+0x20); platform=copy;self->tglHDMIWiringApplied=true; - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->tglHDMIWiring.hdmi,32); + IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDetected?self->adlsHDMIWiring.selected:self->tglHDMIWiring.hdmi,32); // Preserve the existing TC2 machine's diagnostic names for old collectors. IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBTopologyApplied",bool(self->tglHDMIWiring.hdmi&2)); if(self->tglHDMIWiring.hdmi&2)IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBPortConfig",config+6,24); @@ -10981,6 +11042,17 @@ bool XE::tglLoadTCWiring(IOService *provider) { // overrides firmware. Only unambiguous firmware TMDS uses an admitted backend. bool XE::tglLoadHDMIWiring(IOService *provider) { auto self=callbackXE; + if(self->adlsDetected) { + if(!self->tglHDMIWiringLoaded) { + self->tglHDMIWiringLoaded=true; + self->tglHDMIB=false; + // The legacy five-word property addresses TGL Combo A/B/C, not ADL-S. + if(provider && provider->getProperty("tgl-hdmi-wiring"))return false; + self->adlsHDMIWiring=AlderLakeS::HDMI::fromFirmware(self->firmwarePorts); + self->tglHDMIB=self->adlsHDMIWiring.valid && self->adlsHDMIWiring.selected==8; + } + return self->tglHDMIB; + } if(self->tglHDMIWiringLoaded)return self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB); self->tglHDMIWiringLoaded=true; auto property=provider?provider->getProperty("tgl-hdmi-wiring"):nullptr; @@ -10995,6 +11067,9 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { } bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { + if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady)) { + that->setProperty("ADLSFramebufferPrerequisitesMissing",true);return false; + } tglRecordStartOrder(3,that); if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglTransitionSerialize)) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTransitionLive",live);live->release(); @@ -11114,6 +11189,7 @@ uint32_t XE::wrapTGLLaneMask(void *port) { } bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { + if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady))return false; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { SYSLOG("igfx", "AlderLakeN native RCS profile qualification rejected accelerator start"); return false; @@ -11510,6 +11586,7 @@ void XE::tglObserveAccelerator(IOService *that, uint32_t stage, bool done, uint6 } IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SInt32 *score) { + if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady))return nullptr; if (!callbackXE->tglAcceleratorRequested) return nullptr; auto pci = OSDynamicCast(IOPCIDevice, provider); const uint32_t identity = pci ? pci->configRead32(0) : 0; @@ -15634,9 +15711,37 @@ uint32_t XE::wrapTGLLightUpTMDS(void *controller, void *fb, void *path, const vo // The 20A2314a donor nevertheless initializes DDI2 at 0x160000 (EHL PHY C). // r44's exact return PC 0x5a843 proves that this third call caused its panic. // Keep PHY A, including its shared reference role, and PHY B entirely native. +bool XE::wrapADLSPortInit(void *port,uint32_t *config) { + const bool result=FunctionCast(wrapADLSPortInit,callbackXE->orgADLSPortInit)(port,config); + if(!result || !callbackXE->adlsDetected || !config || config[0]!=3)return result; + auto bytes=static_cast(port); + const bool ready=callbackXE->adlsPortInitReady && callbackXE->adlsHDMIWiring.matches(config) && + config==reinterpret_cast(reinterpret_cast(callbackXE->tglPlatformCopy)+0x68) && + *reinterpret_cast(bytes+0x548)==config && + AlderLakeS::comboBRegisters(reinterpret_cast(bytes+0x580),0x128/4); + IORegistryEntry::getRegistryRoot()->setProperty("ADLSComboBPortReady",ready); + return ready; +} + void XE::wrapTGLEnableCombo(void *port) { auto b = static_cast(port); auto config = *reinterpret_cast(b + 0x548); + if(callbackXE->adlsDetected) { + if(!config || !callbackXE->adlsPortInitReady)return; + if(config[0]==0) {FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port);return;} + // The donor's power-engine startup enumerates Combo slots0..2. + // ADL-S Combo B belongs to physical TC1, constructed separately. + if(config[0]==1 && callbackXE->tglPortByDDI) + port=reinterpret_cast(callbackXE->tglPortByDDI)(3); + else if(config[0]!=3)return; + b=static_cast(port); + config=b?*reinterpret_cast(b+0x548):nullptr; + if(config && callbackXE->adlsHDMIWiring.matches(config) && + *reinterpret_cast(b+0x5B8)==0x6C10C) + FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port); + else IORegistryEntry::getRegistryRoot()->setProperty("ADLSComboBInitRejected",true); + return; + } const bool absent = callbackXE->tglActive && config && config[0] == 2 && *reinterpret_cast(b + 0x5B8) == 0x16010C; uint64_t r[20] {}, uptime; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index fff2f41f..ffceb781 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -15,6 +15,7 @@ #include "kern_display13_hdmi_wiring.hpp" #include "kern_xelp_platform.hpp" #include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" #include "kern_xelp_donor.hpp" #include "kern_dvmt.hpp" @@ -75,6 +76,11 @@ class XE { bool tglActive {false}; bool adlnRequested {false}; bool adlnDetected {false}; + bool adlsDetected {},adlsNativeReady {},adlsDisplayHooksReady {}; + bool adlsPortInitReady {}; + mach_vm_address_t orgADLSPortInit {}; + static bool wrapADLSPortInit(void *port,uint32_t *config); + AlderLakeS::GT adlsGT; XELP::Profile xeProfile; XELP::VBT::Ports firmwarePorts; void xelpReadFirmware(IOPCIDevice *pci); @@ -279,11 +285,12 @@ class XE { static bool wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, OSObject *value); bool tglHDMIB {false}; TGLHDMIWiring tglHDMIWiring; + AlderLakeS::HDMI adlsHDMIWiring; bool tglHDMIWiringLoaded {},tglHDMIWiringExplicit {},tglHDMIWiringApplied {}; uint32_t tglHDMIBackendMask {3}; // TGL-U combo PHY A/B; C is absent static bool tglLoadHDMIWiring(IOService *provider); - bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && tglHDMIWiring.matches(config);} - bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && tglHDMIWiring.usesDDC(pin);} + bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDetected?adlsHDMIWiring.matches(config):tglHDMIWiring.matches(config));} + bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDetected?adlsHDMIWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} bool tglGMBusReady {false}; mach_vm_address_t orgTGLGMBus {}; diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index b07c7572..e1fb9a11 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 }; +enum class GT : uint32_t { Unknown, TGLGT2, TGLG4, ADLNSmall, ADLSSmall }; enum class Donor : uint32_t { Unknown, C004FC1A = 1 }; struct DeviceEntry { uint16_t device; Family family; Display display; GT backend; @@ -20,7 +20,7 @@ constexpr DeviceEntry DeviceTable[] { {0x4682, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, {0x4688, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, {0x468B, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, - {0x4690, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 770"}, + {0x4690, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, {0x4692, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, {0x4693, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 710"}, {0x46A3, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 8200cac8..36d7ff41 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -16,7 +16,8 @@ struct Profile { bool overridden() const {return identity!=effectiveIdentity;} bool admitted() const {return (identity&0xFFFF)==0x8086 && revision<=255 && donor==Donor::C004FC1A && gt!=GT::Unknown && - (family!=Family::AlderLakeN || revision==0);} + (family!=Family::AlderLakeN || revision==0) && + (family!=Family::AlderLakeS || (identity==0x46908086 && revision==0x0C && !overridden()));} }; inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) { Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; @@ -70,6 +71,11 @@ struct ProbeConfig { bool valid() const { if(abi!=3 || bytes!=sizeof(ProbeConfig) || !profile.admitted() || !same(profile,identify(profile.identity,profile.revision,profile.effectiveIdentity)))return false; + // Initial ADL-S transport is fixed HDMI on physical TC1/Combo B. + // Its TC label grants neither Type-C nor Dekel register access. + if(profile.family==Family::AlderLakeS) + return profile.display==Display::XeD && combo==8 && comboBackend==8 && + !legacy && !typeC && !excluded && !legacyBackend; 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; diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index 3fb1a46e..b6427ab1 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -29,6 +29,8 @@ template uint32_t legacyTGLMode(IO &io,uint32_t ddi,uint32_t selected) } template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t manager,uintptr_t record,const uint32_t *c) { if(!b.bound(manager,record,c))return 0; + if(b.config.profile.family==Family::AlderLakeS) + return c[0]==3 && c[1]==9 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2 ? 1 : 0; if(b.config.profile.display==Display::XeD) return legacyTGLMode(io,c[0],b.config.legacy&b.config.legacyBackend); // Display13 fixed Combo HDMI remains the only qualified transport. diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 3ded6651..fddcd1e8 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -53,7 +53,7 @@ static struct { void onKextLoadForce(int *) {} } lilu; struct XE { XELP::Profile xeProfile; - bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false; + bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDetected=false; bool display13FramebufferRequested=false, display13TraceEnabled=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 2b3b80f9..956d8fc9 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -8,22 +8,24 @@ int main() { unsigned implemented=0; for(const auto &entry:DeviceTable) { const uint32_t pci=uint32_t(entry.device)<<16|0x8086; - auto p=identify(pci,0); + const auto revision=entry.family==Family::AlderLakeS?12U:0U; + auto p=identify(pci,revision); assert(p.identity==pci && p.effectiveIdentity==pci && !p.overridden()); assert(p.family==entry.family && p.display==entry.display && p.gt==entry.backend); assert(!strcmp(model(p),entry.model)); assert(p.admitted()==(entry.backend!=GT::Unknown)); implemented+=p.admitted(); const uint32_t value=entry.device; - assert(same(p,select(pci,0,true,&value,4))); - // Every catalogued backend can be selected for an unknown physical ID. - auto f=select(0x12348086,0,true,&value,4); + assert(same(p,select(pci,revision,true,&value,4))); + // The new ADL-S qualification requires physical identity; established + // TGL/ADL-N explicit override semantics remain unchanged. + auto f=select(0x12348086,revision,true,&value,4); assert(f.overridden() && f.identity==0x12348086 && f.effectiveIdentity==pci); - assert(f.admitted()==p.admitted()); + assert(f.admitted()==(p.admitted() && entry.family!=Family::AlderLakeS)); assert(!strcmp(model(f),entry.model)); auto nonIntel=select(0x123410DE,0,true,&value,4);assert(!nonIntel.admitted()); } - assert(implemented==8); // Recognition of other table rows adds no native backend. + assert(implemented==9); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); assert(!select(0x12348086,1,true,&id,4).admitted()); // Keep physical stepping guard. diff --git a/tests/provider_binding.py b/tests/provider_binding.py index ff23ed1c..0f157966 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"]: + 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"]: subprocess.run([str(exe), mode], check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index b941097b..f047affc 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -26,12 +26,15 @@ struct IOMemoryMap:OSObject {uintptr_t address {};uint64_t bytes {}; // PROVIDER_SOURCE int main(int argc,char **argv) { assert(argc==2);const bool adln=!strncmp(argv[1],"adln",4); + const bool adls=!strncmp(argv[1],"adls",4); const bool legacy=!strncmp(argv[1],"adln-abi1",9); const bool fake=strstr(argv[1],"fake")!=nullptr; - const bool rejected=!strcmp(argv[1],"unknown") || !strcmp(argv[1],"adln-revision"); + const bool rejected=!strcmp(argv[1],"unknown") || !strcmp(argv[1],"adln-revision") || !strcmp(argv[1],"adls-revision"); IOService controller;IOPCIDevice pci;OSObject access,port,foreign;IOMemoryMap map; std::vector mmio(0x1000000);alignas(8) uint8_t platform[0x1C0] {}; pci.identity=adln?0x46D18086:0x9A498086; + if(adls){pci.identity=0x46908086;pci.revision=12;} + if(!strcmp(argv[1],"adls-revision"))pci.revision=0; if(!strcmp(argv[1],"tgl-g4"))pci.identity=0x9A788086; if(!strcmp(argv[1],"tgl-9a40"))pci.identity=0x9A408086; if(!strcmp(argv[1],"adln-16eu"))pci.identity=0x46D28086; @@ -51,8 +54,8 @@ 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 && !rejected; - assert(hardwareAllowed(&controller)==physicalTGL); + const bool physicalTGL=!adln && !adls && !rejected; + assert(hardwareAllowed(&controller)==(!adln && !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); @@ -62,6 +65,8 @@ int main(int argc,char **argv) { IOPCIDevice foreignPCI;foreignPCI.identity=pci.identity;foreignPCI.revision=pci.revision;foreignPCI.property=pci.property; assert(!xelpSelectedDevice(&foreignPCI)); config.combo=3;config.comboBackend=adln?3:7;config.legacy=adln?0:0x1F8;config.legacyBackend=adln?0:0x1F8; + if(adls){config.combo=config.comboBackend=8;config.legacy=config.legacyBackend=0;} + if(adls && !rejected){assert(hardwareAllowed(&controller));assert(!tglProbePort(&port));} // The contract derives generation from physical identity, not boot args. assert(!xelpConfigurePortProbe(&controller,2,3,3)); if(!adln)assert(!xelpConfigurePortProbe(&controller,1,3,3)); @@ -92,14 +97,14 @@ int main(int argc,char **argv) { malformed=config;malformed.combo=1;assert(!xelpConfigureDisplay(&controller,&malformed)); if(adln){assert(xelpConfigurePortProbe(&controller,1,3,3));assert(!xelpConfigurePortProbe(&controller,2,3,3));} for(unsigned d=0;d<9;++d){auto rec=reinterpret_cast(platform+0x20+24*d); - const uint32_t values[]={d,d+1,2,0,0x800,0x202};memcpy(rec,values,sizeof(values)); + const uint32_t values[]={d,adls?9:d+1,2,0,0x800,0x202};memcpy(rec,values,sizeof(values)); field(&port,0x548)=rec; if(d>=3){*reinterpret_cast(mmio.data()+0xC4000)=1U<<(24+d-3); *reinterpret_cast(mmio.data()+0x1638A0)=0x10; const uint32_t bases[]={0x163000,0x16E000,0x16F000};const auto base=bases[(d-3)/2]; *reinterpret_cast(mmio.data()+base+0x8A0)=0x10; *reinterpret_cast(mmio.data()+base+0x890)=1U<<((d-3)%2);} - assert(tglProbePort(&port)==(adln?(d<2?1U:0U):(d<3?1U:4U))); + assert(tglProbePort(&port)==(adls?(d==3?1U:0U):adln?(d<2?1U:0U):(d<3?1U:4U))); field(&port,0x578)=&foreign;assert(!tglProbePort(&port));field(&port,0x578)=&access; field(&port,0x548)=reinterpret_cast(1);assert(!tglProbePort(&port)); } diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index 79eb2aca..2398cdbb 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -9,6 +9,7 @@ #include #include "kern_tgl_hdmi_wiring.hpp" #include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" #include "kern_xelp_donor.hpp" struct OSObject {virtual ~OSObject()=default;}; struct OSData:OSObject {std::vector data;void *getBytesNoCopy(){return data.data();}uint32_t getLength(){return data.size();}}; @@ -26,6 +27,7 @@ void clock_get_uptime(uint64_t *p){*p=0;}void absolutetime_to_nanoseconds(uint64 void *current_thread(){return reinterpret_cast(1);} struct XE { bool tglHDMIB=true,tglStolenMemoryReady=false; + bool adlsDetected=false; struct {uint32_t fStolenMemorySize=0;} framebufferPatch; XELP::VBT::Ports firmwarePorts; // FIELDS @@ -60,6 +62,19 @@ void expectUnchangedExcept(const uint8_t *after,std::initializer_list assert(!memcmp(after,expected.data(),sizeof(original))); } int main(){ + // ADL-S uses the same real entry points, while preserving the physical + // TC1 record and converting its PHY classification from Dekel to Combo. + setup();object.adlsDetected=true; + object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=8; + object.firmwarePorts.port[3]={0x60d2,1,2}; + reinterpret_cast(original+0x68)[1]=9;IOService adlsProvider; + assert(XE::tglLoadHDMIWiring(&adlsProvider));assert(XE::wrapTGLGetOSInformation(controller)); + auto adlsConfig=reinterpret_cast(platform()+0x68); + assert(object.tglHDMIEligible(adlsConfig) && object.tglHDMIDDCEligible(9) && !object.tglHDMIDDCEligible(2)); + assert(adlsConfig[0]==3 && adlsConfig[1]==9 && adlsConfig[3]==0); + expectUnchangedExcept(platform(),{0x70,0x74,0x78,0x7C}); + setup();object.adlsDetected=true;auto foreignWiring=property(2,0,2,0);IOService wrongADLS{&foreignWiring}; + assert(!XE::tglLoadHDMIWiring(&wrongADLS) && !XE::tglLoadHDMIWiring(&wrongADLS)); // The native setup caller supplies an interior output pointer as arg0. // Any object/config dereference on an unqualified caller must fault here. setup(); diff --git a/tests/tgl_kernel_admission.py b/tests/tgl_kernel_admission.py index 2d8a336f..b8618a5e 100644 --- a/tests/tgl_kernel_admission.py +++ b/tests/tgl_kernel_admission.py @@ -13,10 +13,10 @@ #include "kern_xelp_os.hpp" #include #include -namespace XELP { enum class Family {TigerLake,Other}; } +namespace XELP { enum class Family {TigerLake,Other,AlderLakeS}; } struct Profile { bool ok; XELP::Family family; bool admitted() const {return ok;} }; const char *version; -bool noaccel,off; +bool noaccel,off,adlsDetected; bool checkKernelArgument(const char *arg) {return !strcmp(arg,"-wex-noaccel")?noaccel:!strcmp(arg,"-wex-tgl-off")?off:false;} bool tglRequested,tglInactivePorts,tglPG1Poll,tglClockOrderRequested,tglTrainingLeaseRequested,tglProgramEnabled,tglAcceleratorRequested; int kextIntelTGL,kextIntelTGLFb;int *currentGraphics,*currentFramebuffer; @@ -35,10 +35,11 @@ "Darwin Kernel Version 23.6.0: unqualified", "Darwin Kernel Version 24.6.0: unqualified"}; unsigned cases=0; -for(unsigned k=0;k<11;++k)for(unsigned n=0;n<2;++n)for(unsigned o=0;o<2;++o)for(unsigned physical=0;physical<2;++physical)for(unsigned family=0;family<2;++family){ +for(unsigned k=0;k<11;++k)for(unsigned n=0;n<2;++n)for(unsigned o=0;o<2;++o)for(unsigned physical=0;physical<2;++physical)for(unsigned family=0;family<3;++family){ version=versions[k];noaccel=n;off=o;currentGraphics=currentFramebuffer=nullptr; -select({bool(physical),family?XELP::Family::TigerLake:XELP::Family::Other}); -bool expected=physical&&family&&!o&&(k<7); +adlsDetected=family==2; +select({bool(physical),family==2?XELP::Family::AlderLakeS:family?XELP::Family::TigerLake:XELP::Family::Other}); +bool expected=physical&&family&&!o&&(k<7)&&(family!=2 || k==1); assert(tglRequested==expected);assert(tglAcceleratorRequested==(expected&&!n)); assert((currentFramebuffer==&kextIntelTGLFb)==expected);++cases; }printf("PASS: %u actual TGL admission cases, including Tahoe acceleration, Sequoia acceleration, Sonoma acceleration, Ventura acceleration, noaccel and unknown-kernel rejection\n",cases); diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 997fae6d..52772623 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -12,10 +12,10 @@ struct IO { uint32_t read(uint32_t r){reads.push_back(r);return regs.at(r);} }; int main() { - for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,255U}) { + for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,12U,255U}) { const auto p=identify((id<<16)|0x8086,rev); const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78,adln=id>=0x46D0 && id<=0x46D4; - assert(p.admitted()==(tgl || (adln && rev==0))); + assert(p.admitted()==(tgl || (adln && rev==0) || (id==0x4690 && rev==12))); assert(!adln || p.display==Display::XeLPD); assert(!identify((id<<16)|0x1234,rev).admitted()); } @@ -51,6 +51,15 @@ int main() { const uint32_t v[]={d,d+1,2,0,0x800,0x202};__builtin_memcpy(r,v,sizeof(v)); assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d<2?1U:0U));} assert(io.reads.empty());auto other=b;other.access=3;assert(!b.sameBinding(other));assert(b.sameBinding(b)); + c={};c.profile=identify(0x46908086,12);c.combo=c.comboBackend=8;b.config=c;assert(c.valid()); + for(unsigned d=0;d<9;++d){auto r=reinterpret_cast(platform+0x20+24*d); + const uint32_t v[]={d,9,2,0,0x800,0x202};memcpy(r,v,sizeof(v)); + assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d==3?1U:0U));} + assert(io.reads.empty()); // ADL-S must never probe TGL FIA for its Combo TC1. + for(unsigned bad=0;bad<5;++bad){auto x=c;if(bad==0)x.combo=1;if(bad==1)x.comboBackend=0x79; + if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=0;assert(!x.valid());} + assert(!identify(0x46D18086,12,0x46908086).admitted()); + assert(!identify(0x46908086,12,0x46808086).admitted()); // CPU association and power-consumer accounting are independent of transport. NativePorts ports {platform,(1U<<0)|(1U<<4)|(1U<<6)}; assert(ports.bound(platform+0x20+24*4,4));assert(!ports.bound(platform+0x20+24*3,4)); From 53a211fbc6d868fb3c32ababac97159566f195b0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:14:53 +0900 Subject: [PATCH 03/58] Adapt ADL-S HDMI clock indices while preserving native power sequencing --- WhateverXe/kern_xe.cpp | 67 +++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 4 ++ tests/adls_clock_guard.py | 38 ++++++++++++++ tests/adls_clock_native.py | 23 +++++++++ tests/adls_clock_native_fixture.cpp | 78 +++++++++++++++++++++++++++++ tests/tgl_clock_order.py | 3 ++ tools/test.py | 4 +- 7 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 tests/adls_clock_guard.py create mode 100644 tests/adls_clock_native.py create mode 100644 tests/adls_clock_native_fixture.cpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 13e8ccfb..2d10eddf 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -9968,6 +9968,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t ready = !memcmp(site, after, sizeof(after)); } IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIComboPLLReady", ready); + adlsPLLTableReady=adlsDetected && ready; patcher.clearError(); } { @@ -10939,6 +10940,44 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPAltFiaReady",tglDPAltFiaReady); + if(adlsDetected) { + // Keep the physical DDI in every port/power/display object. Only the + // native Combo PLL indices are translated, under the single-HDMI entry + // guards below. The removed instructions are profiling counters. + const uint8_t adlsClockOnFind[]={0x48,0xff,0x05,0xbe,0x86,0x07,0x00,0x48,0x63,0x00,0x48,0x83,0xf8,0x03}; + const uint8_t adlsClockOnReplace[]={0x48,0x63,0x00,0x48,0x83,0xe8,0x02,0x48,0x83,0xf8,0x03,0x0f,0x1f,0x00}; + const uint8_t adlsClockSelectFind[]={0x83,0xf9,0x01}; + const uint8_t adlsClockSelectReplace[]={0x83,0xf9,0x03}; + const uint8_t adlsClockOffFind[]={0x48,0xff,0x05,0xe9,0x79,0x07,0x00,0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xfc,0x03}; + const uint8_t adlsClockOffReplace[]={0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xec,0x02,0x49,0x83,0xfc,0x03,0x0f,0x1f,0x00}; + 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 && + 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)}, + {0x6D222,adlsClockSelectFind,adlsClockSelectReplace,sizeof(adlsClockSelectFind)}, + {0x6DCA8,adlsClockOffFind,adlsClockOffReplace,sizeof(adlsClockOffFind)}}; + 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); + 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}; + patcher.applyLookupPatch(&patch,reinterpret_cast(address+site.offset),site.bytes+1); + ready &= !memcmp(reinterpret_cast(address+site.offset),site.replace,site.bytes); + patcher.clearError(); + } + } + adlsClockReady=ready; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockReady",ready); + // Native startup remains closed if any required ADL-S adapter is absent. + adlsDisplayHooksReady=ready && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && + orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); + } tglRecordStartOrder(1,nullptr); return true; } @@ -15657,6 +15696,9 @@ volatile uint8_t *XE::tglClockOrderTarget(void *controller, void *port, void *pa } void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { + if(callbackXE->adlsDetected && !adlsClockTarget(controller,port,otherPort)) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; + } tglTransitionRecord(32,false,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); auto s = callbackXE; auto &tx = tglClockOffTransaction; @@ -15711,6 +15753,31 @@ uint32_t XE::wrapTGLLightUpTMDS(void *controller, void *fb, void *path, const vo // The 20A2314a donor nevertheless initializes DDI2 at 0x160000 (EHL PHY C). // r44's exact return PC 0x5a843 proves that this third call caused its panic. // Keep PHY A, including its shared reference role, and PHY B entirely native. +bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { + auto self=callbackXE; + if(!self->adlsClockReady || !controller || controller!=self->tglObservationController || + !port || (otherPort && otherPort!=port) || + self->adlsHDMIWiring.selected!=8)return false; + auto p=static_cast(port); + auto config=*reinterpret_cast(p+0x548); + auto access=*reinterpret_cast(static_cast(controller)+0xC40); + return config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x68) && + self->adlsHDMIWiring.matches(config) && access && + *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==0x6C10C && + *reinterpret_cast(access+0x60)>=0x164294; +} + +void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params) { + AlderLakeS::HDMIClock encoded; + if(!adlsClockTarget(controller,port,otherPort) || + !AlderLakeS::HDMIClock::encode(params,*reinterpret_cast(static_cast(controller)+0xEA8),encoded)) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; + } + tglTransitionRecord(71,false,port,encoded.cfg0,encoded.cfg1,0); + FunctionCast(wrapADLSEnableClocks,callbackXE->orgADLSEnableClocks)(controller,port,otherPort,params); + tglTransitionRecord(71,true,port,encoded.cfg0,encoded.cfg1,0); +} + bool XE::wrapADLSPortInit(void *port,uint32_t *config) { const bool result=FunctionCast(wrapADLSPortInit,callbackXE->orgADLSPortInit)(port,config); if(!result || !callbackXE->adlsDetected || !config || config[0]!=3)return result; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ffceb781..a0ad36ef 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -78,6 +78,10 @@ class XE { bool adlnDetected {false}; bool adlsDetected {},adlsNativeReady {},adlsDisplayHooksReady {}; bool adlsPortInitReady {}; + bool adlsPLLTableReady {},adlsClockReady {}; + mach_vm_address_t orgADLSEnableClocks {}; + static bool adlsClockTarget(void *controller,void *port,void *otherPort); + 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); AlderLakeS::GT adlsGT; diff --git a/tests/adls_clock_guard.py b/tests/adls_clock_guard.py new file mode 100644 index 00000000..555f3423 --- /dev/null +++ b/tests/adls_clock_guard.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Exercise the runtime clock admission boundary against impostor objects.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +function=re.search(r'bool XE::adlsClockTarget\(.*?\n\}',s,re.S).group() +code='''#include "kern_alderlake_s.hpp" +#include +#include +struct XE { + bool adlsClockReady=true;void *tglObservationController;uint8_t tglPlatformCopy[0x1c0] {}; + AlderLakeS::HDMI adlsHDMIWiring;static bool adlsClockTarget(void*,void*,void*); +}; +XE state;XE *callbackXE=&state; +FUNCTION +templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +int main(){ + alignas(8) uint8_t ctl[0xF00] {},port[0x700] {},access[0x70] {}; + state.tglObservationController=ctl;state.adlsHDMIWiring.valid=true;state.adlsHDMIWiring.selected=8;state.adlsHDMIWiring.pin[3]=9; + uint32_t config[]={3,9,2,0,0x800,0x202};auto c=reinterpret_cast(state.tglPlatformCopy+0x68); + memcpy(c,config,sizeof(config));at(port,0x548)=c;at(port,0x578)=access; + at(ctl,0xC40)=access;at(port,0x5B8)=0x6C10C;at(access,0x60)=0x1000000; + assert(XE::adlsClockTarget(ctl,port,nullptr));assert(XE::adlsClockTarget(ctl,port,port)); + assert(!XE::adlsClockTarget(nullptr,port,nullptr));assert(!XE::adlsClockTarget(ctl,nullptr,nullptr)); + assert(!XE::adlsClockTarget(ctl,port,access));assert(!XE::adlsClockTarget(access,port,nullptr)); + at(port,0x548)=config;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x548)=c; + at(port,0x578)=nullptr;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x578)=access; + at(port,0x5B8)=0;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x5B8)=0x6C10C; + at(access,0x60)=0x100000;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; + for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} + state.adlsHDMIWiring.selected|=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsHDMIWiring.selected=8; + state.adlsClockReady=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); + puts("PASS ADL-S runtime clock guard: exact owner, immutable TC1 configuration, mapped Combo B and no shared port"); +}'''.replace('FUNCTION',function) +with tempfile.TemporaryDirectory(prefix='adls-clock-guard-') 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/adls_clock_native.py b/tests/adls_clock_native.py new file mode 100644 index 00000000..548f4509 --- /dev/null +++ b/tests/adls_clock_native.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Replay qualified ADL-S Combo clock translations in the real donor code.""" +from pathlib import Path +import hashlib,re,struct,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1] +b=bytearray(Path(sys.argv[1]).read_bytes()) +assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +s=(r/'WhateverXe/kern_xe.cpp').read_text() +for name,off in [('On',0x6cfd3),('Select',0x6d222),('Off',0x6dca8)]: + def array(suffix): + v=re.search(r'const uint8_t adlsClock'+name+suffix+r'\[\]=\{([^}]+)\}',s).group(1) + return bytes(int(n,0) for n in v.split(',')) + before,after=array('Find'),array('Replace') + assert len(before)==len(after) and b[off:off+len(before)]==before + b[off:off+len(after)]=after +before=[0x164000,0x164080,0x164200,0x164004,0x164084,0x164204] +assert list(struct.unpack_from('<6Q',b,0x9a0e8))==before +struct.pack_into('<6Q',b,0x9a0e8,0x164284,0x16428c,0x164200,0x164288,0x164290,0x164204) +with tempfile.TemporaryDirectory(prefix='adls-clock-') as d: + d=Path(d);(d/'donor').write_bytes(b) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined', + '-fno-omit-frame-pointer','-I'+str(r/'WhateverXe'),str(r/'tests/adls_clock_native_fixture.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test'),str(d/'donor')],check=True) diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp new file mode 100644 index 00000000..3c33bbd3 --- /dev/null +++ b/tests/adls_clock_native_fixture.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "kern_alderlake_s.hpp" +#include "kern_tgl_port_map.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;}; +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);} +void writeReg(void *p,unsigned long reg,uint32_t v){ + assert(p==access && mmio.count(reg)); + if(reinterpret_cast(__builtin_return_address(0))==reinterpret_cast(image+0x6d973)) + TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg,v); + events.push_back({1,uint32_t(reg),v}); + if(reg==0x46014) { + // Model hardware ACK/LOCK independently from request bits. Failure cases + // exercise the donor's bounded waits, not successful hardware operation. + v&=~0x44000000U; + if((v&0x08000000U) && fault!=1)v|=0x04000000U; + if((v&0x80000000U) && fault!=2)v|=0x40000000U; + } + mmio[reg]=v; +} +void mailbox(void *p,uint32_t command,uint32_t value,bool wait){assert(p==ctl && command==7 && !wait);events.push_back({2,command,value});} +uint32_t voltage(void *p){assert(p==ctl);return 1;} +void getDDI(void *p){assert(p==port && at(p,0x548)[0]==3);events.push_back({3,3,1});} +void putDDI(void *p){assert(p==port && at(p,0x548)[0]==3);events.push_back({3,3,0});} +void delay(unsigned n){assert(n==1);++delays;} +void error(const char *,...){++errors;} +void redirectCall(unsigned site,uintptr_t target,unsigned stub){ + assert(image[site]==0xe8);jump(image+stub,target);int32_t d=stub-site-5;memcpy(image+site+1,&d,4); +} +int main(int argc,char **argv){ + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vector bytes((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,bytes.data(),bytes.size()); + // Rebase only the four native profiling-pointer tables used on these paths. + for(auto table: {std::pair{0xcd330,3},{0xcd348,6},{0xcd378,3},{0xcd3c0,3}}) + for(unsigned i=0;i(image,table.first+8*i);assert(v>=0xd0000 && v<0x160000);v+=reinterpret_cast(image);} + jump(image+0x5e344,(uintptr_t)readReg);jump(image+0x4691e,(uintptr_t)mailbox); + jump(image+0x6bf6e,(uintptr_t)voltage);jump(image+0x1fefe,(uintptr_t)getDDI);jump(image+0x1ff54,(uintptr_t)putDDI); + for(unsigned site:{0x6d081u,0x6d9ecu,0x6dd6du,0x6de16u})redirectCall(site,(uintptr_t)delay,0x300000); + for(unsigned site:{0x6d54cu,0x6dda6u})redirectCall(site,(uintptr_t)error,0x300020); + vtable[0x130/8]=(uintptr_t)writeReg;at(access,0)=vtable;at(ctl,0xc40)=access; + at(port,0x548)=config; + auto enable=(void(*)(void*,void*,void*,uint32_t*))(image+0x6ce10); + auto disable=(void(*)(void*,void*,void*))(image+0x6dae6); + unsigned cases=0; + for(unsigned ref=0;ref<=2;++ref)for(unsigned p:{2u,3u,5u,7u})for(unsigned k:{1u,2u,3u})for(unsigned q:{1u,3u}) { + fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref; + mmio={{0x46014,0},{0x16428c,0},{0x164290,0},{0x164280,0x01e07400}}; + uint32_t params[]={464,2049,p,q,k};AlderLakeS::HDMIClock expected; + assert(AlderLakeS::HDMIClock::encode(params,ref,expected)); + enable(ctl,port,nullptr,params); + assert(!errors&&!delays && mmio[0x16428c]==expected.cfg0 && mmio[0x164290]==expected.cfg1); + assert(mmio[0x46014]==0xcc000000 && mmio[0x164280]==((0x01e07400&~0x80cU)|4)); + assert(events.front().kind==2 && events.front().value==3 && events.back().kind==3 && events.back().value==1); + assert(config[0]==3);events.clear();disable(ctl,port,nullptr); + assert(!errors&&!delays && mmio[0x46014]==0 && (mmio[0x164280]&0x800)); + assert(events.front().kind==3 && events.front().value==0 && events.back().kind==2 && events.back().value==1);++cases; + } + for(fault=1;fault<=2;++fault){ + events.clear();delays=errors=0;mmio={{0x46014,0},{0x16428c,0},{0x164290,0},{0x164280,0x01e07400}}; + uint32_t params[]={464,2048,2,3,2};enable(ctl,port,nullptr,params); + assert(delays==2000 && errors==1 && mmio[0x164280]==0x01e07400); + } + munmap(image,0x400000); + printf("PASS %u real donor Combo PLL enable/disable cases: physical TC1 preserved, PHY B selection, reference fraction, voltage/power order; 2 bounded timeout paths. MMIO and power hardware mocked.\n",cases); +} diff --git a/tests/tgl_clock_order.py b/tests/tgl_clock_order.py index 799ee4d3..0fe94684 100644 --- a/tests/tgl_clock_order.py +++ b/tests/tgl_clock_order.py @@ -13,7 +13,9 @@ #define FunctionCast(f,p) reinterpret_cast(p) void *current_thread(){return (void*)1;}unsigned delays=0,writes=0,natives=0;uint32_t lastReg; 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 { + bool adlsDetected=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);} static volatile uint8_t *tglClockOrderTarget(void*,void*,void*,void*); @@ -50,6 +52,7 @@ tglClockOffTransaction.path=d;tglClockOffTransaction.access=a;tglClockOffTransaction.clockReg=0x46148; tglClockOffTransaction.value=0x123;tglClockOffTransaction.pending=true; writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(writes==1&®(0x46148)==0x123&&!tglClockOffTransaction.pending); + obj.adlsDetected=true;natives=writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(!natives&&!writes); printf("PASS %u actual clock-order port/pipe/idle cases; invalid pipe and deferred native-write fallback\n",cases); } '''.replace('TX',tx).replace('FUNCTIONS',f) diff --git a/tools/test.py b/tools/test.py index da1d4c05..0c681e07 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=['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=['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 += ['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_tc_native','tgl_tc_mode','tgl_mode_native','tgl_panel_depth_route','adls_donor']] +if a.donor:jobs += [(n,[sys.executable,str(R/'tests'/(n+'.py')),str(a.donor.resolve())],None) for n in ['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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From 6abb1de1e106fcd4f484b44dcb3b44264662de3e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:25:28 +0900 Subject: [PATCH 04/58] Qualify ADL-S MMIO mappings and admit the wired GMBUS9 pin --- WhateverXe/kern_xe.cpp | 15 ++++++++++++--- tests/adls_clock_native.py | 2 +- tests/adls_clock_native_fixture.cpp | 12 ++++++++++++ tests/tgl_hdmi_wiring_fixture.cpp | 8 ++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2d10eddf..6c65020c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4161,7 +4161,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // Preserve firmware power/ownership; no MMIO writes. The guarded20G // route first qualifies the shared writable BAR map. All-ones FIA // reads are unavailable, not connected ports. - if (!adlsDetected && (pci->configRead16(4) & 2)) { + if (pci->configRead16(4) & 2) { auto mmio = pci->mapDeviceMemoryWithRegister(0x10, tglMMIOUCRouted ? 0 : kIOMapReadOnly | kIOMapInhibitCache); if (tglMMIOUCRouted) tglUpdateMMIOMap(mmio); @@ -4169,7 +4169,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { uint32_t registers[][2] {{0xC4000, 0}, {0x44470, 0}, {0x1638A0, 0}, {0x163890, 0}, {0x163894, 0}}; auto base = reinterpret_cast(mmio->getVirtualAddress()); - if (base && mmio->getLength() >= 0x1638A4) { + if (!adlsDetected && base && mmio->getLength() >= 0x1638A4) { for (auto ® : registers) reg[1] = *reinterpret_cast(base + reg[0]); pci->setProperty("TGLBootPortRegisters", registers, sizeof(registers)); @@ -10950,6 +10950,10 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t const uint8_t adlsClockSelectReplace[]={0x83,0xf9,0x03}; const uint8_t adlsClockOffFind[]={0x48,0xff,0x05,0xe9,0x79,0x07,0x00,0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xfc,0x03}; const uint8_t adlsClockOffReplace[]={0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xec,0x02,0x49,0x83,0xfc,0x03,0x0f,0x1f,0x00}; + // PCH GMBUS9 is a real ADL-S pin. The donor's ICL-era <8 check + // rejects it before MMIO. The entry wrapper admits only wired pin9. + const uint8_t adlsClockBusFind[]={0x83,0x7d,0xcc,0x08,0x72,0x2a}; + const uint8_t adlsClockBusReplace[]={0x83,0x7d,0xcc,0x0a,0x72,0x2a}; 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); @@ -10958,7 +10962,8 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t struct Site {uint32_t offset;const uint8_t *find,*replace;size_t bytes;}; const Site sites[]={{0x6CFD3,adlsClockOnFind,adlsClockOnReplace,sizeof(adlsClockOnFind)}, {0x6D222,adlsClockSelectFind,adlsClockSelectReplace,sizeof(adlsClockSelectFind)}, - {0x6DCA8,adlsClockOffFind,adlsClockOffReplace,sizeof(adlsClockOffFind)}}; + {0x6DCA8,adlsClockOffFind,adlsClockOffReplace,sizeof(adlsClockOffFind)}, + {0x53B66,adlsClockBusFind,adlsClockBusReplace,sizeof(adlsClockBusFind)}}; for(const auto &site:sites)ready=ready && !memcmp(reinterpret_cast(address+site.offset),site.find,site.bytes); patcher.clearError(); if(ready) { @@ -14452,6 +14457,10 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { } uint32_t XE::wrapTGLGMBus(void *controller, uint32_t bus, uint32_t speed, void *request) { + if(callbackXE->adlsDetected && (!callbackXE->tglHDMIDDCEligible(bus) || bus!=9)) { + if(request)*reinterpret_cast(static_cast(request)+0x1C)=0xE00002C2; + return 0xE00002C2; + } uint64_t r[24] {}, uptime; static uint32_t count {}; r[0] = __sync_add_and_fetch(&count, 1); diff --git a/tests/adls_clock_native.py b/tests/adls_clock_native.py index 548f4509..6ebebc42 100644 --- a/tests/adls_clock_native.py +++ b/tests/adls_clock_native.py @@ -6,7 +6,7 @@ b=bytearray(Path(sys.argv[1]).read_bytes()) assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' s=(r/'WhateverXe/kern_xe.cpp').read_text() -for name,off in [('On',0x6cfd3),('Select',0x6d222),('Off',0x6dca8)]: +for name,off in [('On',0x6cfd3),('Select',0x6d222),('Off',0x6dca8),('Bus',0x53b66)]: def array(suffix): v=re.search(r'const uint8_t adlsClock'+name+suffix+r'\[\]=\{([^}]+)\}',s).group(1) return bytes(int(n,0) for n in v.split(',')) diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp index 3c33bbd3..e9bb83f0 100644 --- a/tests/adls_clock_native_fixture.cpp +++ b/tests/adls_clock_native_fixture.cpp @@ -73,6 +73,18 @@ int main(int argc,char **argv){ uint32_t params[]={464,2048,2,3,2};enable(ctl,port,nullptr,params); assert(delays==2000 && errors==1 && mmio[0x164280]==0x01e07400); } + // Stop immediately after native GMBUS parameter validation, before MMIO. + // Native request validation and its original return/status path execute. + const uint8_t accepted[]={0x41,0xbc,0x78,0x56,0x34,0x12,0xe9}; + memcpy(image+0x53b96,accepted,sizeof(accepted)); + int32_t back=0x53b79-(0x53b96+11);memcpy(image+0x53b96+7,&back,4); + auto bus=(uint32_t(*)(void*,uint32_t,uint32_t,void*))(image+0x53a7c); + alignas(8) uint8_t request[0x80] {}; + for(unsigned pin:{7u,9u,10u,UINT32_MAX}) { + const auto result=bus(ctl,pin,3,request); + assert(result==(pin<10?0x12345678u:0xe00002c2u));assert(at(request,0x1c)==result); + } + at(request,0x38)=0x200;assert(bus(ctl,9,3,request)==0xe00002c2); munmap(image,0x400000); printf("PASS %u real donor Combo PLL enable/disable cases: physical TC1 preserved, PHY B selection, reference fraction, voltage/power order; 2 bounded timeout paths. MMIO and power hardware mocked.\n",cases); } diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index 2398cdbb..28dc3130 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -73,6 +73,14 @@ int main(){ assert(object.tglHDMIEligible(adlsConfig) && object.tglHDMIDDCEligible(9) && !object.tglHDMIDDCEligible(2)); assert(adlsConfig[0]==3 && adlsConfig[1]==9 && adlsConfig[3]==0); expectUnchangedExcept(platform(),{0x70,0x74,0x78,0x7C}); + for(unsigned bus=0;bus<=14;++bus) { + uint32_t request[16] {};request[1]=1;request[3]=0xA0;request[14]=128; + nativeGMBusCalls=0; + auto result=XE::wrapTGLGMBus(controller,bus,0,request); + assert(nativeGMBusCalls==unsigned(bus==9)); + assert(result==(bus==9?0x91u:0xE00002C2u)); + if(bus!=9)assert(request[7]==result); + } setup();object.adlsDetected=true;auto foreignWiring=property(2,0,2,0);IOService wrongADLS{&foreignWiring}; assert(!XE::tglLoadHDMIWiring(&wrongADLS) && !XE::tglLoadHDMIWiring(&wrongADLS)); // The native setup caller supplies an interior output pointer as arg0. From 5e98435b16e9b9f8774bcfc30901045185e1ac28 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 03:45:41 +0900 Subject: [PATCH 05/58] Preserve ADL-S TC1 lane power alongside native pipe consumers --- WhateverXe/kern_alderlake_s.hpp | 12 +++++++ WhateverXe/kern_xe.cpp | 43 +++++++++++++++++++++++- WhateverXe/kern_xe.hpp | 5 ++- tests/adls_lane_power.py | 58 +++++++++++++++++++++++++++++++++ tools/test.py | 2 +- 5 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 tests/adls_lane_power.py diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 0536e187..212636c1 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -33,6 +33,18 @@ struct Port { } bool connected(uint32_t sde) const {return sde!=UINT32_MAX && (sde&hpd);} }; +// ADL-S uses TGL PW_2/PW_3 for physical TC lane domains, including Combo +// PHY HDMI. Native pipe refs do not include this independent lane consumer. +struct LanePower { + static uint32_t target(uint32_t native,bool tc1) { + return native | (tc1 ? 0x2AU : 0U); + } + static uint32_t pipeRequests(const uint32_t *refs) { + uint32_t out=0; + for(unsigned i=0;i<4;++i)if(refs[i])out|=2U<<(2*i); + return out; + } +}; // VBT numbers A/TC1..4 as 1..5; GMBUS uses 1/9..12. Zero means absent. inline uint32_t gmbus(uint32_t vbtPin) { return vbtPin==1?1:vbtPin>=2 && vbtPin<=5?vbtPin+7:0; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 6c65020c..23cf950d 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10978,8 +10978,19 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t } adlsClockReady=ready; IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockReady",ready); + const char *pg="__ZN19AppleIntelPowerWell21hwSetPowerWellStatePGEbj"; + const auto pgfn=patcher.solveSymbol(index,pg,address,size); + const uint8_t pgentry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + adlsPowerReady=orgTGLPowerDDIState && pgfn==address+0x5B640 && size>0x5BD40 && + !memcmp(reinterpret_cast(pgfn),pgentry,sizeof(pgentry)); + patcher.clearError(); + if(adlsPowerReady) { + KernelPatcher::RouteRequest route(pg,wrapADLSPowerPG,orgADLSPowerPG); + adlsPowerReady=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + } + IORegistryEntry::getRegistryRoot()->setProperty("ADLSLanePowerReady",adlsPowerReady); // Native startup remains closed if any required ADL-S adapter is absent. - adlsDisplayHooksReady=ready && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && + adlsDisplayHooksReady=ready && adlsPowerReady && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK; IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); } @@ -16450,12 +16461,42 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { return result; } +uint8_t *XE::adlsPowerTarget(void *power) { + auto s=callbackXE;auto p=static_cast(power); + auto c=static_cast(s->tglObservationController); + if(!s->adlsDetected || !s->adlsPowerReady || !p || !c || + !s->adlsHDMIWiring.valid || s->adlsHDMIWiring.selected!=8 || + *reinterpret_cast(p+0x10)!=c || *reinterpret_cast(c+0xC8)!=p || + *reinterpret_cast(p+0x78)!=*reinterpret_cast(c+0xC40))return nullptr; + return p; +} + +void XE::wrapADLSPowerPG(void *power,bool enable,uint32_t requested) { + auto p=adlsPowerTarget(power); + const auto target=AlderLakeS::LanePower::target(requested,p && *reinterpret_cast(p+0x3C)); + tglTransitionRecord(73,false,power,requested,target,enable); + FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,enable,target); + tglTransitionRecord(73,true,power,requested,target,enable); +} + void XE::wrapTGLPowerDDIState(void *power, bool enable, uint32_t requested) { auto bytes=static_cast(power); tglTransitionRecord(33,false,power,requested,enable,bytes[0x18],bytes+0x20,52); auto controller = *reinterpret_cast(static_cast(power) + 0x10); tglObserveActivation(controller, nullptr, nullptr, 3, false, requested); + // Acquire parent wells before DDI IO turns on; release after IO turns off. + // Native reference counts remain untouched. The PG target is the union of + // the native pipe consumers and the current TC1 lane consumer. + auto adls=adlsPowerTarget(power); + if(adls && (requested&0x80)) { + const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); + FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,true,AlderLakeS::LanePower::target(native,true)); + } FunctionCast(wrapTGLPowerDDIState, callbackXE->orgTGLPowerDDIState)(power, enable, requested); + if(adls && !(requested&0x80)) { + const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); + FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,false,native); + } tglTransitionRecord(33,true,power,requested,enable,bytes[0x18],bytes+0x20,52); tglObserveActivation(controller, nullptr, nullptr, 3, true, requested); } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index a0ad36ef..7e3967bc 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -78,7 +78,10 @@ class XE { bool adlnDetected {false}; bool adlsDetected {},adlsNativeReady {},adlsDisplayHooksReady {}; bool adlsPortInitReady {}; - bool adlsPLLTableReady {},adlsClockReady {}; + bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {}; + 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 {}; static bool adlsClockTarget(void *controller,void *port,void *otherPort); static void wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params); diff --git a/tests/adls_lane_power.py b/tests/adls_lane_power.py new file mode 100644 index 00000000..57a0d1cc --- /dev/null +++ b/tests/adls_lane_power.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Actual ADL-S wrappers: parent-before-IO and IO-before-release, no ref edits.""" +from pathlib import Path +import subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('uint8_t *XE::adlsPowerTarget');b=s.index('// Software-only transmitter boundary.',a) +code=r''' +#include "kern_alderlake_s.hpp" +#include +#include +#include +#define FunctionCast(fn,p) reinterpret_cast(p) +struct XE { + bool adlsDetected=true,adlsPowerReady=true;void *tglObservationController; + AlderLakeS::HDMI adlsHDMIWiring;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; +struct Call{unsigned kind,target;bool enable;};std::vector calls; +void pg(void*,bool e,uint32_t t){calls.push_back({0,t,e});} +void ddi(void*,bool e,uint32_t t){calls.push_back({1,t,e});} +templatevoid tglTransitionRecord(T...){} +templatevoid tglObserveActivation(T...){} +templateT &at(void*p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} +FUNCTIONS +int main(){ + alignas(8) uint8_t ctl[0xF00]{},power[0x80]{},access[0x70]{}; + state.tglObservationController=ctl;state.adlsHDMIWiring.valid=true;state.adlsHDMIWiring.selected=8; + state.orgADLSPowerPG=reinterpret_cast(pg);state.orgTGLPowerDDIState=reinterpret_cast(ddi); + at(power,0x10)=ctl;at(ctl,0xC8)=power;at(power,0x78)=access;at(ctl,0xC40)=access; + assert(XE::adlsPowerTarget(power)==power);assert(!XE::adlsPowerTarget(nullptr));unsigned count=0; + for(unsigned active:{0U,1U,7U})for(unsigned mask=0;mask<256;++mask)for(bool on:{false,true}){ + at(power,0x3C)=active;calls.clear();XE::wrapADLSPowerPG(power,on,mask); + assert(calls.size()==1&&calls[0].target==(mask|(active?0x2A:0))&&calls[0].enable==on); + assert(at(power,0x3C)==active);++count; + } + for(unsigned bits=0;bits<16;++bits)for(unsigned tc:{0U,0x80U}){ + unsigned expected=0;for(unsigned i=0;i<4;++i){at(power,0x20+4*i)=(bits>>i)&1;if((bits>>i)&1)expected|=2U<<(2*i);} + uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power));calls.clear();XE::wrapTGLPowerDDIState(power,false,tc); + assert(calls.size()==2);assert(calls[tc?0:1].kind==0&&calls[tc?0:1].target==(expected|(tc?0x2A:0))); + assert(calls[tc?0:1].enable==bool(tc));assert(calls[tc?1:0].kind==1&&calls[tc?1:0].target==tc); + assert(!memcmp(before,power,sizeof(power)));++count; + } + for(unsigned bad=0;bad<6;++bad){ + state.adlsDetected=bad!=0;state.adlsPowerReady=bad!=1;state.adlsHDMIWiring.valid=bad!=2; + state.adlsHDMIWiring.selected=bad==3?9:8;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); + } + printf("PASS %u lane/pipe conditions; acquire/release order; unchanged refs; 6 owner/profile rejections\n",count); +} +'''.replace('FUNCTIONS',s[a:b]) +with tempfile.TemporaryDirectory(prefix='adls-lane-power-') 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/tools/test.py b/tools/test.py index 0c681e07..1a4c29bb 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=['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=['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 += ['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 09f5e27b231344c2c0661f14eeab3c43bc693242 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:19:24 +0900 Subject: [PATCH 06/58] Support qualified Alder Lake-S fixed DisplayPort on Combo C --- README.md | 6 +- README_ja.md | 5 +- WhateverXe/kern_alderlake_s.hpp | 38 +++++++++ WhateverXe/kern_tgl_combo_signal.hpp | 16 ++++ WhateverXe/kern_tgl_port_map.hpp | 5 +- WhateverXe/kern_xe.cpp | 105 ++++++++++++++++--------- WhateverXe/kern_xe.hpp | 9 ++- WhateverXe/kern_xelp_platform.hpp | 4 +- WhateverXe/kern_xelp_probe.hpp | 8 +- tests/adls_clock_guard.py | 14 +++- tests/adls_clock_native.py | 4 +- tests/adls_clock_native_fixture.cpp | 22 +++++- tests/adls_donor.py | 15 +++- tests/adls_lane_power.py | 11 +-- tests/alderlake_s.cpp | 6 ++ tests/tgl_combo_aux_lifetime.py | 39 +++++---- tests/tgl_combo_pll_write.py | 2 +- tests/tgl_combo_signal.cpp | 12 +++ tests/tgl_combo_signal_guard.py | 25 +++++- tests/tgl_port_map_fixture.cpp | 1 + tests/tgl_probe_fixture.cpp | 1 + tests/tgl_training_leases_fixture.cpp | 6 ++ tests/tgl_training_pattern_fixture.cpp | 1 + tests/tgl_wiring_fixture.cpp | 1 + tests/xelp_platform.cpp | 5 ++ 25 files changed, 277 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index dc0fc4b5..f136313e 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,11 @@ These results cover tested systems, not every GPU, display, adapter or dock. ### Planned -- Alder Lake and Raptor Lake S/H/P/U support +- Alder Lake and Raptor Lake S/H/P/U support. Development support for Alder Lake-S + `4690` currently covers Big Sur single-display HDMI or fixed DP with Metal and + OpenGL acceleration. It requires unreleased companion graphics changes and is + not supported by the published graphics pkg. USB-C, hotplug, sleep/wake and + other macOS versions remain unqualified. - Display 13 USB-C and eDP support ### Not Planned diff --git a/README_ja.md b/README_ja.md index 55b3b771..818c5c46 100644 --- a/README_ja.md +++ b/README_ja.md @@ -118,7 +118,10 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 ### Planned -- Alder Lake/Raptor LakeのS/H/P/U対応 +- Alder Lake/Raptor LakeのS/H/P/U対応。開発版のAlder Lake-S `4690`では、 + Big SurのHDMIまたは固定DP単独出力とMetal/OpenGLアクセラレーションを確認しています。 + 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 + USB-C、再接続、消灯/復帰、他のmacOSは未検証です。 - Display 13のUSB-C / eDP対応 ### Not Planned diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 212636c1..76547e08 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -93,6 +93,30 @@ struct HDMI { out.valid=true;return out; } }; +// First fixed external DP backend: VBT TC2 / AUX USBC2 / Combo C. +// Reject reversal, LSPCON, internal and Type-C intent instead of guessing. +struct DP { + static bool qualified(const XELP::VBT::Ports &ports) { + if(!ports.valid() || !(ports.present&16))return false; + const auto &p=ports.port[4]; + if(p.type!=0x60D6 || p.dvo!=8 || p.ddc!=3 || p.aux!=0x20 || + (p.flags&0x17) || (p.capabilities&3))return false; + for(unsigned d=0;d(platform); + const uint32_t expected[]={4,10,1,1,0x400,0x201}; + if(p[0x12]!=9 || memcmp(p+0x80,expected,sizeof(expected)))return false; + reinterpret_cast(p+0x80)[3]=0;return true; + } +}; struct PLL { uint32_t enable {},cfg0 {},cfg1 {}; static bool resolve(uint32_t id,PLL &out) { @@ -121,6 +145,20 @@ inline bool comboBRegisters(uint32_t *registers,size_t count) { static_assert(sizeof(phy)==0x648-0x5AC+4,"Combo PHY register table span"); memcpy(registers+(0x5AC-0x580)/4,phy,sizeof(phy));return true; } +// The donor's EHL Combo C bank matches ADL-S PHY C. Keep physical +// TC2's DDI/AUX register slots and translate only the separate PHY table. +inline bool comboCRegisters(uint32_t *registers,size_t count) { + if(!registers || count!=0x128/4 || registers[4]!=0x64400 || + registers[(0x64C-0x580)/4]!=0x46110)return false; + for(unsigned off=0x5AC;off<=0x648;off+=4)if(registers[(off-0x580)/4])return false; + uint32_t temporary[0x128/4] {};temporary[4]=0x64300;temporary[(0x64C-0x580)/4]=0x4610C; + if(!comboBRegisters(temporary,count))return false; + for(unsigned off=0x5AC;off<=0x648;off+=4) { + const auto index=(off-0x580)/4; + registers[index]=off==0x5AC ? 0x64C08 : temporary[index]-0x6C000+0x160000; + } + return true; +} // Five-word ClockParams ABI at the exact donor's combo-HDMI call boundary. // Keep logical divisors separate from register encodings. Display WA // 22010492432 halves only the DCO fraction for a 38.4 MHz reference. diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index ff1a2758..aae5e50e 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -46,6 +46,22 @@ struct TGLComboSignal { const auto row=low ? reduced[level]:normal[level]; swing=row[0];scalar=row[1];cursor=row[2];post1=row[3];return true; } + // ADL-S fixed DP on Combo C. RBR/HBR uses the TGL normal table; + // HBR2/HBR3 uses Intel's ADL-S external DP coefficients. + bool selectADLS(uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis) { + if(rate!=6 && rate!=10 && rate!=20 && rate!=30)return false; + if(!select(1,count,6,false,false,voltage,emphasis))return false; + base=0x160000; + if(rate>=20) { + static const uint8_t high[10][4]={ + {0xA,0x35,0x3F,0},{0xA,0x4F,0x37,8},{0xC,0x63,0x31,0xE},{6,0x7F,0x2C,0x13}, + {0xA,0x47,0x3F,0},{0xC,0x63,0x37,8},{6,0x73,0x32,0xD}, + {0xC,0x58,0x3F,0},{6,0x7F,0x35,0xA},{6,0x7F,0x3F,0} + }; + const auto row=high[level];swing=row[0];scalar=row[1];cursor=row[2];post1=row[3]; + } + return true; + } // Caller owns this live port's power. Pre-read all lane words before any // write; refuse an inaccessible bank without a partial programming sequence. template bool program(Read read,Write write) const { diff --git a/WhateverXe/kern_tgl_port_map.hpp b/WhateverXe/kern_tgl_port_map.hpp index 3ab89c14..6ac45b4c 100644 --- a/WhateverXe/kern_tgl_port_map.hpp +++ b/WhateverXe/kern_tgl_port_map.hpp @@ -178,7 +178,10 @@ struct TGLSSTTrainingPort { const bool edp = physicalDDI < 2 && kind == 0 && backend == 1; const bool tc = physicalDDI >= 3 && physicalDDI <= 8 && kind == 1 && (backend == 3 || backend == 4) && qualifiedTC; - if (!edp && !tc) return false; + // qualifiedTC is the caller's generation/wiring admission. ADL-S TC2 + // is Combo C in mode1, while its physical lane power remains PW3. + const bool adls = physicalDDI==4 && kind==1 && backend==1 && qualifiedTC; + if (!edp && !tc && !adls) return false; out.ddi = physicalDDI; out.ddiBuffer = 0x64000 + 0x100 * physicalDDI; out.combo = edp; return true; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 23cf950d..257927b7 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4038,7 +4038,7 @@ bool XE::xelpBindProbe(void *controller) { XELP::ProbeConfig config;config.profile=xeProfile; if(adlsDetected) { if(!tglHDMIWiringApplied || !adlsHDMIWiring.valid || adlsHDMIWiring.selected!=8)return false; - config.combo=config.comboBackend=8; + config.combo=config.comboBackend=adlsDPWiring?24:8; } else if(xeProfile.display==XELP::Display::XeD) { if(!tglHDMIWiringApplied || !tglTCWiringLoaded || !tglTCWiring.valid)return false; config.combo=tglHDMIWiring.hdmi;config.comboBackend=7; @@ -9953,9 +9953,10 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t // r319: the donor's combo PLL tables still contain ICL MMIO // addresses. TGL moved DPLL0/1 CFGCR0/1 (Linux v6.12 i915_reg.h). // Correct both reads and writes through the native tables; retain - // native divisors, power/lock sequencing and the unused third slot. + // native divisors and power/lock sequencing. ADL-S also uses slot2. const uint64_t before[] {0x164000,0x164080,0x164200,0x164004,0x164084,0x164204}; - const uint64_t after[] {0x164284,0x16428C,0x164200,0x164288,0x164290,0x164204}; + const uint64_t after[] {0x164284,0x16428C,adlsDetected?0x164294ULL:0x164200ULL, + 0x164288,0x164290,adlsDetected?0x164298ULL:0x164204ULL}; auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams", address, size); auto site = reinterpret_cast(address + 0x9A0E8); @@ -10946,6 +10947,10 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t // guards below. The removed instructions are profiling counters. const uint8_t adlsClockOnFind[]={0x48,0xff,0x05,0xbe,0x86,0x07,0x00,0x48,0x63,0x00,0x48,0x83,0xf8,0x03}; const uint8_t adlsClockOnReplace[]={0x48,0x63,0x00,0x48,0x83,0xe8,0x02,0x48,0x83,0xf8,0x03,0x0f,0x1f,0x00}; + const uint8_t adlsClockSelectCPLLFind[]={0x83,0xc8,0x10}; + const uint8_t adlsClockSelectCPLLReplace[]={0x83,0xc8,0x20}; + const uint8_t adlsClockSelectCFind[]={0x83,0xf9,0x02}; + const uint8_t adlsClockSelectCReplace[]={0x83,0xf9,0x04}; const uint8_t adlsClockSelectFind[]={0x83,0xf9,0x01}; const uint8_t adlsClockSelectReplace[]={0x83,0xf9,0x03}; const uint8_t adlsClockOffFind[]={0x48,0xff,0x05,0xe9,0x79,0x07,0x00,0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xfc,0x03}; @@ -10961,6 +10966,8 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t 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)}, + {0x6D4A2,adlsClockSelectCPLLFind,adlsClockSelectCPLLReplace,sizeof(adlsClockSelectCPLLFind)}, + {0x6D219,adlsClockSelectCFind,adlsClockSelectCReplace,sizeof(adlsClockSelectCFind)}, {0x6D222,adlsClockSelectFind,adlsClockSelectReplace,sizeof(adlsClockSelectFind)}, {0x6DCA8,adlsClockOffFind,adlsClockOffReplace,sizeof(adlsClockOffFind)}, {0x53B66,adlsClockBusFind,adlsClockBusReplace,sizeof(adlsClockBusFind)}}; @@ -10991,7 +10998,9 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("ADLSLanePowerReady",adlsPowerReady); // Native startup remains closed if any required ADL-S adapter is absent. adlsDisplayHooksReady=ready && adlsPowerReady && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && - orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK; + orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK && + (!adlsDPWiring || (tglTrainingLeaseReady && tglSinkFramingReady && tglComboSignalReady && + tglComboAUXLifetimeReady && tglAUXParentReady && tglTCNativeFunctions.complete())); IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); } tglRecordStartOrder(1,nullptr); @@ -11039,13 +11048,15 @@ bool XE::wrapTGLGetOSInformation(void *controller) { } auto copy=reinterpret_cast(self->tglPlatformCopy); if(copy!=platform)memcpy(copy,platform,sizeof(self->tglPlatformCopy)); - const bool wiringApplied=self->adlsDetected ? self->adlsHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) : + const bool wiringApplied=self->adlsDetected ? (self->adlsHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) && + (!self->adlsDPWiring || AlderLakeS::DP::apply(copy,sizeof(self->tglPlatformCopy)))) : self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)); if(!self->tglHDMIWiringLoaded || !wiringApplied) { IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringGuardFailed",true);return false; } auto config=reinterpret_cast(copy+0x20); platform=copy;self->tglHDMIWiringApplied=true; + if(self->adlsDetected)IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPWiringApplied",self->adlsDPWiring); IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDetected?self->adlsHDMIWiring.selected:self->tglHDMIWiring.hdmi,32); // Preserve the existing TC2 machine's diagnostic names for old collectors. IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBTopologyApplied",bool(self->tglHDMIWiring.hdmi&2)); @@ -11104,6 +11115,7 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { // The legacy five-word property addresses TGL Combo A/B/C, not ADL-S. if(provider && provider->getProperty("tgl-hdmi-wiring"))return false; self->adlsHDMIWiring=AlderLakeS::HDMI::fromFirmware(self->firmwarePorts); + self->adlsDPWiring=AlderLakeS::DP::qualified(self->firmwarePorts); self->tglHDMIB=self->adlsHDMIWiring.valid && self->adlsHDMIWiring.selected==8; } return self->tglHDMIB; @@ -15781,16 +15793,19 @@ bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { auto p=static_cast(port); auto config=*reinterpret_cast(p+0x548); auto access=*reinterpret_cast(static_cast(controller)+0xC40); - return config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x68) && - self->adlsHDMIWiring.matches(config) && access && - *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==0x6C10C && - *reinterpret_cast(access+0x60)>=0x164294; + if(!config)return false; + const bool hdmi=self->adlsHDMIWiring.matches(config); + const bool dp=self->adlsDPWiring && AlderLakeS::DP::matches(config); + return (hdmi || dp) && config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x20+24*config[0]) && access && + *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==(dp?0x16010CU:0x6C10CU) && + *reinterpret_cast(access+0x60)>=0x16429C; } void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params) { AlderLakeS::HDMIClock encoded; if(!adlsClockTarget(controller,port,otherPort) || - !AlderLakeS::HDMIClock::encode(params,*reinterpret_cast(static_cast(controller)+0xEA8),encoded)) { + ((*reinterpret_cast(static_cast(port)+0x548))[2]==2 && + !AlderLakeS::HDMIClock::encode(params,*reinterpret_cast(static_cast(controller)+0xEA8),encoded))) { IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; } tglTransitionRecord(71,false,port,encoded.cfg0,encoded.cfg1,0); @@ -15800,13 +15815,17 @@ void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32 bool XE::wrapADLSPortInit(void *port,uint32_t *config) { const bool result=FunctionCast(wrapADLSPortInit,callbackXE->orgADLSPortInit)(port,config); - if(!result || !callbackXE->adlsDetected || !config || config[0]!=3)return result; + if(!result || !callbackXE->adlsDetected || !config || (config[0]!=3 && config[0]!=4))return result; auto bytes=static_cast(port); - const bool ready=callbackXE->adlsPortInitReady && callbackXE->adlsHDMIWiring.matches(config) && - config==reinterpret_cast(reinterpret_cast(callbackXE->tglPlatformCopy)+0x68) && + const bool dp=config[0]==4; + if(dp && !callbackXE->adlsDPWiring)return result; + const bool ready=callbackXE->adlsPortInitReady && + (dp ? AlderLakeS::DP::matches(config):callbackXE->adlsHDMIWiring.matches(config)) && + config==reinterpret_cast(reinterpret_cast(callbackXE->tglPlatformCopy)+0x20+24*config[0]) && *reinterpret_cast(bytes+0x548)==config && - AlderLakeS::comboBRegisters(reinterpret_cast(bytes+0x580),0x128/4); - IORegistryEntry::getRegistryRoot()->setProperty("ADLSComboBPortReady",ready); + (dp ? AlderLakeS::comboCRegisters(reinterpret_cast(bytes+0x580),0x128/4): + AlderLakeS::comboBRegisters(reinterpret_cast(bytes+0x580),0x128/4)); + IORegistryEntry::getRegistryRoot()->setProperty(dp?"ADLSComboCPortReady":"ADLSComboBPortReady",ready); return ready; } @@ -15817,14 +15836,14 @@ void XE::wrapTGLEnableCombo(void *port) { if(!config || !callbackXE->adlsPortInitReady)return; if(config[0]==0) {FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port);return;} // The donor's power-engine startup enumerates Combo slots0..2. - // ADL-S Combo B belongs to physical TC1, constructed separately. - if(config[0]==1 && callbackXE->tglPortByDDI) - port=reinterpret_cast(callbackXE->tglPortByDDI)(3); - else if(config[0]!=3)return; + // ADL-S Combo B/C belong to physical TC1/TC2, constructed separately. + if((config[0]==1 || (config[0]==2 && callbackXE->adlsDPWiring)) && callbackXE->tglPortByDDI) + port=reinterpret_cast(callbackXE->tglPortByDDI)(config[0]+2); + else if(config[0]!=3 && !(config[0]==4 && callbackXE->adlsDPWiring))return; b=static_cast(port); config=b?*reinterpret_cast(b+0x548):nullptr; - if(config && callbackXE->adlsHDMIWiring.matches(config) && - *reinterpret_cast(b+0x5B8)==0x6C10C) + if(config && ((callbackXE->adlsHDMIWiring.matches(config) && *reinterpret_cast(b+0x5B8)==0x6C10C) || + (callbackXE->adlsDPWiring && AlderLakeS::DP::matches(config) && *reinterpret_cast(b+0x5B8)==0x16010C))) FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port); else IORegistryEntry::getRegistryRoot()->setProperty("ADLSComboBInitRejected",true); return; @@ -16069,7 +16088,7 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { if (ctl && *reinterpret_cast(ctl + 0xC40) == access) { const auto refCode = *reinterpret_cast(ctl + 0xEA8); const auto nominal = value; - if (TGLComboPLLFraction::adjust(refCode, reg, value)) { + if (TGLComboPLLFraction::adjust(refCode, callbackXE->adlsDetected && reg==0x164294 ? 0x164284 : reg, value)) { const uint64_t detail[] {refCode, nominal}; tglTransitionRecord(70,true,access,reg,nominal,value,detail,sizeof(detail)); } @@ -16473,7 +16492,8 @@ uint8_t *XE::adlsPowerTarget(void *power) { void XE::wrapADLSPowerPG(void *power,bool enable,uint32_t requested) { auto p=adlsPowerTarget(power); - const auto target=AlderLakeS::LanePower::target(requested,p && *reinterpret_cast(p+0x3C)); + const auto target=AlderLakeS::LanePower::target(requested,p && (*reinterpret_cast(p+0x3C) || + (callbackXE->adlsDPWiring && *reinterpret_cast(p+0x40)))); tglTransitionRecord(73,false,power,requested,target,enable); FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,enable,target); tglTransitionRecord(73,true,power,requested,target,enable); @@ -16486,14 +16506,15 @@ void XE::wrapTGLPowerDDIState(void *power, bool enable, uint32_t requested) { tglObserveActivation(controller, nullptr, nullptr, 3, false, requested); // Acquire parent wells before DDI IO turns on; release after IO turns off. // Native reference counts remain untouched. The PG target is the union of - // the native pipe consumers and the current TC1 lane consumer. + // the native pipe consumers and the current TC1/TC2 lane consumers. auto adls=adlsPowerTarget(power); - if(adls && (requested&0x80)) { + const auto laneMask=callbackXE->adlsDPWiring?0x280U:0x80U; + if(adls && (requested&laneMask)) { const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,true,AlderLakeS::LanePower::target(native,true)); } FunctionCast(wrapTGLPowerDDIState, callbackXE->orgTGLPowerDDIState)(power, enable, requested); - if(adls && !(requested&0x80)) { + if(adls && !(requested&laneMask)) { const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,false,native); } @@ -16540,18 +16561,21 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void auto p=static_cast(port); auto ctl=static_cast(self->tglObservationController); if(!p || !ctl || overrideConfig || !self->tglActive || !self->tglComboSignalReady || - self->xeProfile.family!=XELP::Family::TigerLake || !self->firmwarePorts.valid())return false; + (self->xeProfile.family!=XELP::Family::TigerLake && !self->adlsDetected) || !self->firmwarePorts.valid())return false; auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; - if(!config || config[0]>1 || config[2]!=0 || config[3]!=0 || - *reinterpret_cast(p+0x550)!=1 || p[0x117] || - !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17))return false; + const bool adls=self->adlsDetected && self->adlsDPWiring && AlderLakeS::DP::matches(config) && + config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x80); + if(!config || *reinterpret_cast(p+0x550)!=1 || p[0x117])return false; + if(!adls && (config[0]>1 || config[2]!=0 || config[3]!=0 || + !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17)))return false; auto access=*reinterpret_cast(p+0x578); if(!access || *reinterpret_cast(ctl+0xC40)!=access)return false; TGLComboSignal selected; const auto lanes=*reinterpret_cast(p+0x124); const auto rate=*reinterpret_cast(p+0x128); - if(!selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis))return false; + if(!(adls ? selected.selectADLS(lanes,rate,swing,emphasis): + selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis)))return false; // The native fields identify this very bank. No guessed framebuffer index // or cross-port reads; power must already belong to the training caller. if(*reinterpret_cast(p+0x620)!=selected.base+0x804 || @@ -16561,7 +16585,8 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void const auto main=wrapTGLRegisterRead(access,0x45404); const auto io=wrapTGLRegisterRead(access,0x45454); const uint32_t ioMask=3U<<(2*config[0]); - if(main==~0U || io==~0U || (main&3)!=3 || (io&ioMask)!=ioMask)return false; + const uint32_t mainMask=adls?0x3FU:3U; + if(main==~0U || io==~0U || (main&mainMask)!=mainMask || (io&ioMask)!=ioMask)return false; const auto read=[&](uint32_t reg){return wrapTGLRegisterRead(access,reg);}; const auto write=[&](uint32_t reg,uint32_t value){wrapTGLRegisterWrite(access,reg,value);}; const bool programmed=selected.program(read,write); @@ -16891,9 +16916,11 @@ void XE::tglComboAUXReference(void *port,bool acquire) { 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() || - s->xeProfile.family!=XELP::Family::TigerLake || *f.controllerSlot!=ctl)return; + (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected) || *f.controllerSlot!=ctl)return; auto config=*reinterpret_cast(p+0x548); - if(!config || config[0]>1)return; + const bool adls=s->adlsDetected && s->adlsDPWiring && AlderLakeS::DP::matches(config) && + config==reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x80); + if(!config || (config[0]>1 && !adls))return; const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; if(acquire ? owner!=nullptr : owner!=port)return; @@ -16902,14 +16929,14 @@ void XE::tglComboAUXReference(void *port,bool acquire) { if(!power || !access || *reinterpret_cast(power+0x10)!=ctl || *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(p+0x578)!=access || - *reinterpret_cast(access+0x60)<0x64200 || + *reinterpret_cast(access+0x60)<(adls?0x64404U:0x64200U) || !*reinterpret_cast(access+0x50))return; const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); if(*reinterpret_cast(p+0x590)!=buffer)return; - if(acquire && (config[2]!=0 || config[3]!=0 || p[0x117] || - *reinterpret_cast(p+0x550)!=1 || !s->firmwarePorts.valid() || - !(s->firmwarePorts.present&(1U<firmwarePorts.port[ddi].internal() || - !s->firmwarePorts.port[ddi].dp()))return; + if(acquire && (config[3]!=0 || p[0x117] || *reinterpret_cast(p+0x550)!=1 || + !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[ddi].dp() || + (!adls && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return; const auto io=wrapTGLRegisterRead(access,0x45454); if(io==~0U || (io&mask)!=mask)return; const auto bufferState=wrapTGLRegisterRead(access,buffer); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 7e3967bc..b7d766f2 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -293,6 +293,7 @@ class XE { bool tglHDMIB {false}; TGLHDMIWiring tglHDMIWiring; AlderLakeS::HDMI adlsHDMIWiring; + bool adlsDPWiring {}; bool tglHDMIWiringLoaded {},tglHDMIWiringExplicit {},tglHDMIWiringApplied {}; uint32_t tglHDMIBackendMask {3}; // TGL-U combo PHY A/B; C is absent static bool tglLoadHDMIWiring(IOService *provider); @@ -343,7 +344,7 @@ class XE { mach_vm_address_t orgTGLEnablePort {}; mach_vm_address_t orgTGLDisablePort {}; bool tglComboAUXLifetimeReady {}; - void *tglComboAUXOwners[2] {}; + void *tglComboAUXOwners[9] {}; static void tglComboAUXReference(void *port,bool acquire); static uint32_t wrapTGLDisablePort(void *port); mach_vm_address_t orgTGLSignalLevels {}; @@ -404,10 +405,10 @@ class XE { uint32_t tglTCAutoCandidates {},tglTCAutoRegistered {}; uint32_t tglDPAltCandidates {}; bool tglDPSSTEligible(uint32_t ddi) const { - return tglLegacyTCEligible(ddi) || (ddi>=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && (tglDPAltCandidates&(1U< uint32_t legacyTGLMode(IO &io,uint32_t ddi,uint32_t selected) } template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t manager,uintptr_t record,const uint32_t *c) { if(!b.bound(manager,record,c))return 0; - if(b.config.profile.family==Family::AlderLakeS) - return c[0]==3 && c[1]==9 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2 ? 1 : 0; + if(b.config.profile.family==Family::AlderLakeS) { + if(!(b.config.combo&b.config.comboBackend&(1U< struct XE { bool adlsClockReady=true;void *tglObservationController;uint8_t tglPlatformCopy[0x1c0] {}; - AlderLakeS::HDMI adlsHDMIWiring;static bool adlsClockTarget(void*,void*,void*); + bool adlsDPWiring=false;AlderLakeS::HDMI adlsHDMIWiring;static bool adlsClockTarget(void*,void*,void*); }; XE state;XE *callbackXE=&state; FUNCTION @@ -30,7 +30,17 @@ for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} state.adlsHDMIWiring.selected|=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsHDMIWiring.selected=8; state.adlsClockReady=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); - puts("PASS ADL-S runtime clock guard: exact owner, immutable TC1 configuration, mapped Combo B and no shared port"); + state.adlsClockReady=true;state.adlsDPWiring=true; + uint32_t dp[]={4,10,1,0,0x400,0x201};c=reinterpret_cast(state.tglPlatformCopy+0x80); + memcpy(c,dp,sizeof(dp));at(port,0x548)=c;at(port,0x5B8)=0x16010C; + assert(XE::adlsClockTarget(ctl,port,nullptr));assert(XE::adlsClockTarget(ctl,port,port)); + state.adlsDPWiring=false;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsDPWiring=true; + at(port,0x548)=dp;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x548)=c; + at(port,0x5B8)=0x6C10C;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x5B8)=0x16010C; + at(access,0x60)=0x164298;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; + for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} + assert(!XE::adlsClockTarget(ctl,port,access));assert(!XE::adlsClockTarget(access,port,nullptr)); + puts("PASS ADL-S runtime clock guard: exact owner, immutable TC1/TC2 configurations, mapped Combo B/C and no shared port"); }'''.replace('FUNCTION',function) with tempfile.TemporaryDirectory(prefix='adls-clock-guard-') as d: d=Path(d);(d/'test.cpp').write_text(code) diff --git a/tests/adls_clock_native.py b/tests/adls_clock_native.py index 6ebebc42..41fe45f4 100644 --- a/tests/adls_clock_native.py +++ b/tests/adls_clock_native.py @@ -6,7 +6,7 @@ b=bytearray(Path(sys.argv[1]).read_bytes()) assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' s=(r/'WhateverXe/kern_xe.cpp').read_text() -for name,off in [('On',0x6cfd3),('Select',0x6d222),('Off',0x6dca8),('Bus',0x53b66)]: +for name,off in [('On',0x6cfd3),('Select',0x6d222),('SelectC',0x6d219),('SelectCPLL',0x6d4a2),('Off',0x6dca8),('Bus',0x53b66)]: def array(suffix): v=re.search(r'const uint8_t adlsClock'+name+suffix+r'\[\]=\{([^}]+)\}',s).group(1) return bytes(int(n,0) for n in v.split(',')) @@ -15,7 +15,7 @@ def array(suffix): b[off:off+len(after)]=after before=[0x164000,0x164080,0x164200,0x164004,0x164084,0x164204] assert list(struct.unpack_from('<6Q',b,0x9a0e8))==before -struct.pack_into('<6Q',b,0x9a0e8,0x164284,0x16428c,0x164200,0x164288,0x164290,0x164204) +struct.pack_into('<6Q',b,0x9a0e8,0x164284,0x16428c,0x164294,0x164288,0x164290,0x164298) with tempfile.TemporaryDirectory(prefix='adls-clock-') as d: d=Path(d);(d/'donor').write_bytes(b) subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined', diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp index e9bb83f0..aefc01e6 100644 --- a/tests/adls_clock_native_fixture.cpp +++ b/tests/adls_clock_native_fixture.cpp @@ -19,9 +19,9 @@ uint32_t readReg(void *p,unsigned long reg){assert(p==access);assert(mmio.count( void writeReg(void *p,unsigned long reg,uint32_t v){ assert(p==access && mmio.count(reg)); if(reinterpret_cast(__builtin_return_address(0))==reinterpret_cast(image+0x6d973)) - TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg,v); + TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg==0x164294?0x164284:reg,v); events.push_back({1,uint32_t(reg),v}); - if(reg==0x46014) { + if(reg==0x46014 || reg==0x46018) { // Model hardware ACK/LOCK independently from request bits. Failure cases // exercise the donor's bounded waits, not successful hardware operation. v&=~0x44000000U; @@ -32,8 +32,8 @@ void writeReg(void *p,unsigned long reg,uint32_t v){ } void mailbox(void *p,uint32_t command,uint32_t value,bool wait){assert(p==ctl && command==7 && !wait);events.push_back({2,command,value});} uint32_t voltage(void *p){assert(p==ctl);return 1;} -void getDDI(void *p){assert(p==port && at(p,0x548)[0]==3);events.push_back({3,3,1});} -void putDDI(void *p){assert(p==port && at(p,0x548)[0]==3);events.push_back({3,3,0});} +void getDDI(void *p){assert(p==port && (at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,1});} +void putDDI(void *p){assert(p==port && (at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,0});} void delay(unsigned n){assert(n==1);++delays;} void error(const char *,...){++errors;} void redirectCall(unsigned site,uintptr_t target,unsigned stub){ @@ -73,6 +73,20 @@ int main(int argc,char **argv){ uint32_t params[]={464,2048,2,3,2};enable(ctl,port,nullptr,params); assert(delays==2000 && errors==1 && mmio[0x164280]==0x01e07400); } + // Real external DP Combo C uses physical TC2, dedicated DPLL2 and selector2. + config[0]=4;config[1]=10;config[2]=1;config[4]=0x400;config[5]=0x201; + at(port,0x114)=1; + for(unsigned ref=0;ref<=2;++ref)for(unsigned rate:{6u,10u,20u,30u}) { + fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref;at(port,0x128)=rate; + mmio={{0x46018,0},{0x164294,0},{0x164298,0},{0x164280,0x01e07400}}; + enable(ctl,port,nullptr,nullptr); + assert(!errors&&!delays && config[0]==4 && mmio[0x46018]==0xcc000000); + assert(mmio[0x164280]==((0x01e07400&~0x1000030U)|0x20)); + const uint32_t cfg0=ref==0?0x1000151:ref==1?0x1c001a5:0xe001a5; + const uint32_t cfg1=rate==6?0x90:rate==10?0x88:rate==20?0x48:0x44; + assert(mmio[0x164294]==cfg0 && mmio[0x164298]==cfg1); + disable(ctl,port,nullptr);assert(!errors && !delays && !mmio[0x46018] && (mmio[0x164280]&0x1000000));++cases; + } // Stop immediately after native GMBUS parameter validation, before MMIO. // Native request validation and its original return/status path execute. const uint8_t accepted[]={0x41,0xbc,0x78,0x56,0x34,0x12,0xe9}; diff --git a/tests/adls_donor.py b/tests/adls_donor.py index 0a22b4cc..cce18e6a 100644 --- a/tests/adls_donor.py +++ b/tests/adls_donor.py @@ -15,16 +15,27 @@ assert offset not in fields and offset+4 not in fields fields[offset]=lo;fields[offset+4]=hi phy=[fields[off] for off in range(0x5ac,0x64c,4)] +cfields={} +for at in range(0x5f468,0x5f610,17): + assert donor[at:at+2]==b'\x48\xb8' and donor[at+10:at+13]==b'\x49\x89\x86' + lo,hi=struct.unpack_from(' #include int main() { - const uint32_t phy[]={PHY};const uint32_t ports[]={PORTS}; + const uint32_t cphy[]={CPHY};const uint32_t phy[]={PHY};const uint32_t ports[]={PORTS}; uint32_t registers[0x128/4] {};registers[4]=0x64300;registers[(0x64c-0x580)/4]=0x4610c; assert(AlderLakeS::comboBRegisters(registers,sizeof(registers)/4)); assert(!memcmp(registers+(0x5ac-0x580)/4,phy,sizeof(phy))); + memset(registers,0,sizeof(registers));registers[4]=0x64400;registers[(0x64c-0x580)/4]=0x46110; + assert(AlderLakeS::comboCRegisters(registers,sizeof(registers)/4)); + assert(!memcmp(registers+(0x5ac-0x580)/4,cphy,sizeof(cphy))); alignas(8) uint8_t platform[0x1c0] {};platform[0x12]=9; memcpy(platform+0x20,ports,sizeof(ports)); XELP::VBT::Ports firmware;firmware.status=XELP::VBT::Status::Valid; @@ -33,7 +44,7 @@ assert(wiring.apply(platform,sizeof(platform))); assert(wiring.matches(reinterpret_cast(platform+0x68))); puts("PASS exact donor Combo B register table and physical TC1 connector adaptation"); -}'''.replace('PHY',','.join(map(str,phy))).replace('PORTS',','.join(map(str,ports))) +}'''.replace('CPHY',','.join(map(str,cphy))).replace('PHY',','.join(map(str,phy))).replace('PORTS',','.join(map(str,ports))) with tempfile.TemporaryDirectory(prefix='adls-donor-') as tmp: tmp=Path(tmp);(tmp/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(repo/'WhateverXe'), diff --git a/tests/adls_lane_power.py b/tests/adls_lane_power.py index 57a0d1cc..6a3cf360 100644 --- a/tests/adls_lane_power.py +++ b/tests/adls_lane_power.py @@ -12,7 +12,7 @@ #define FunctionCast(fn,p) reinterpret_cast(p) struct XE { bool adlsDetected=true,adlsPowerReady=true;void *tglObservationController; - AlderLakeS::HDMI adlsHDMIWiring;uintptr_t orgADLSPowerPG,orgTGLPowerDDIState; + bool adlsDPWiring=false;AlderLakeS::HDMI adlsHDMIWiring;uintptr_t orgADLSPowerPG,orgTGLPowerDDIState; static uint8_t *adlsPowerTarget(void*); static void wrapADLSPowerPG(void*,bool,uint32_t); static void wrapTGLPowerDDIState(void*,bool,uint32_t); @@ -31,12 +31,13 @@ state.orgADLSPowerPG=reinterpret_cast(pg);state.orgTGLPowerDDIState=reinterpret_cast(ddi); at(power,0x10)=ctl;at(ctl,0xC8)=power;at(power,0x78)=access;at(ctl,0xC40)=access; assert(XE::adlsPowerTarget(power)==power);assert(!XE::adlsPowerTarget(nullptr));unsigned count=0; - for(unsigned active:{0U,1U,7U})for(unsigned mask=0;mask<256;++mask)for(bool on:{false,true}){ - at(power,0x3C)=active;calls.clear();XE::wrapADLSPowerPG(power,on,mask); - assert(calls.size()==1&&calls[0].target==(mask|(active?0x2A:0))&&calls[0].enable==on); + for(bool dp:{false,true})for(unsigned activeDP:{0U,1U,7U})for(unsigned active:{0U,1U,7U})for(unsigned mask=0;mask<256;++mask)for(bool on:{false,true}){ + state.adlsDPWiring=dp;at(power,0x40)=activeDP;at(power,0x3C)=active;calls.clear();XE::wrapADLSPowerPG(power,on,mask); + assert(calls.size()==1&&calls[0].target==(mask|((active || (dp && activeDP))?0x2A:0))&&calls[0].enable==on); assert(at(power,0x3C)==active);++count; } - for(unsigned bits=0;bits<16;++bits)for(unsigned tc:{0U,0x80U}){ + state.adlsDPWiring=true; + for(unsigned bits=0;bits<16;++bits)for(unsigned tc:{0U,0x80U,0x200U,0x280U}){ unsigned expected=0;for(unsigned i=0;i<4;++i){at(power,0x20+4*i)=(bits>>i)&1;if((bits>>i)&1)expected|=2U<<(2*i);} uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power));calls.clear();XE::wrapTGLPowerDDIState(power,false,tc); assert(calls.size()==2);assert(calls[tc?0:1].kind==0&&calls[tc?0:1].target==(expected|(tc?0x2A:0))); diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp index a3b161dd..e1ab31d0 100644 --- a/tests/alderlake_s.cpp +++ b/tests/alderlake_s.cpp @@ -34,12 +34,18 @@ int main() { firmware.port[3]={0x60d2,1,2,0,0xc0,0}; firmware.port[4]={0x60d6,8,3,0x20,0xc0,0}; firmware.port[5]={0x68c6,9,0,0x30,0xc0,0}; + assert(DP::qualified(firmware)); + for(unsigned i=0;i<6;++i){auto bad=firmware;reinterpret_cast(&bad.port[4])[i]^=1;assert(!DP::qualified(bad));} + auto dupe=firmware;dupe.port[5].aux=0x20;assert(!DP::qualified(dupe)); auto h=HDMI::fromFirmware(firmware);assert(h.valid && h.selected==8 && h.pin[3]==9); alignas(8) uint8_t platform[0x1C0] {};platform[0x12]=9; for(unsigned d=0;d<9;++d) { const uint32_t c[]={d,d>=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; memcpy(platform+0x20+24*d,c,sizeof(c)); } + assert(DP::apply(platform,sizeof(platform)) && DP::matches(reinterpret_cast(platform+0x80))); + assert(!DP::apply(platform,sizeof(platform))); + reinterpret_cast(platform+0x80)[3]=1; uint8_t before[sizeof(platform)];memcpy(before,platform,sizeof(before)); assert(h.apply(platform,sizeof(platform))); assert(h.matches(reinterpret_cast(platform+0x68)) && h.usesDDC(9) && !h.usesDDC(2)); diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index b0e1065e..cfb7c79f 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -7,32 +7,37 @@ 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''' -#include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" #include #include #include struct Functions {bool ready=true;void **controllerSlot=nullptr;void(*getAux)(void*)=nullptr;void(*putAux)(void*)=nullptr;bool complete(){return ready;}}; -struct State {void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +struct State {bool adlsDetected=false,adlsDPWiring=false;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[2]{}; + XELP::VBT::Ports firmwarePorts;Functions tglTCNativeFunctions;void *tglComboAUXOwners[9]{}; } state,*callbackXE=&state; std::map regs;unsigned getCount,putCount,reads;bool missingAck;uint32_t *refs; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return regs[reg];} void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} FUNCTION int main(){ - for(unsigned bad=0;bad<15;++bad){ - alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t config[4]{}; + for(bool adls:{false,true})for(unsigned bad=0;bad<15;++bad){ + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t local[6]{};uint32_t *config=local; + unsigned ddi=adls?4:0,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={};state.tglObservationController=ctl;void *global=ctl; + state={};if(adls) { + state.adlsDetected=state.adlsDPWiring=true;state.xeProfile.family=XELP::Family::AlderLakeS; + config=reinterpret_cast(state.tglPlatformCopy+0x80);const uint32_t c[]={4,10,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); + } + state.tglObservationController=ctl;void *global=ctl; state.tglTCNativeFunctions.controllerSlot=&global; - state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.present=1;state.firmwarePorts.port[0].type=0x1806; - store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(p,0x590,0x64000U); + state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.present=1U<(power+0x54);*refs=2;getCount=putCount=reads=0;missingAck=bad==14; - regs.clear();regs[0x45454]=3;regs[0x64000]=0x81; - state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?2:3;}; + refs=reinterpret_cast(power+0x54+4*ddi);*refs=2;getCount=putCount=reads=0;missingAck=bad==14; + regs.clear();regs[0x45454]=mask;regs[buffer]=0x81; + state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?0:regs[0x45454];}; state.tglTCNativeFunctions.putAux=[](void*){--*refs;++putCount;}; if(bad==1)state.tglActive=false; if(bad==2)state.tglComboAUXLifetimeReady=false; @@ -44,18 +49,18 @@ if(bad==8)store(power,0x78,ctl+0); if(bad==9)store(p,0x590,0x64100U); if(bad==10)regs[0x45454]=0; - if(bad==11)regs[0x64000]=0x80000001; - if(bad==12)state.xeProfile.family=XELP::Family::AlderLakeN; + if(bad==11)regs[buffer]=0x80000001; + if(bad==12){state.xeProfile.family=XELP::Family::AlderLakeN;state.adlsDetected=false;} if(bad==13)*refs=~0U; auto before=*refs;run(p,true); - if(bad){assert(*refs==before&&!state.tglComboAUXOwners[0]);assert(getCount==(bad==14));assert(putCount==(bad==14));continue;} - assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[0]==p); + 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 // Native per-transaction ref pairs cannot consume the held main-link ref. ++*refs;--*refs;assert(*refs==3); run(nullptr,false);assert(putCount==0); - regs[0x64000]=0x80000001;run(p,false);assert(putCount==0); // active main link - regs[0x64000]=0x81;run(p,false);assert(putCount==1&&*refs==2&&!state.tglComboAUXOwners[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); } puts("PASS main-link AUX acquire/duplicate/native transaction/disable/retry, inherited refs, ownership/power/ACK guards"); diff --git a/tests/tgl_combo_pll_write.py b/tests/tgl_combo_pll_write.py index 1d4a2376..fec4077a 100644 --- a/tests/tgl_combo_pll_write.py +++ b/tests/tgl_combo_pll_write.py @@ -20,7 +20,7 @@ #include #include #include -struct State { bool tglActive=true; uintptr_t tglComboPLLFractionCaller=0x1000; void *tglObservationController=nullptr; } state,*callbackXE=&state; +struct State { bool adlsDetected=false; bool tglActive=true; uintptr_t tglComboPLLFractionCaller=0x1000; void *tglObservationController=nullptr; } state,*callbackXE=&state; unsigned records=0; void tglTransitionRecord(unsigned stage,bool done,void *,unsigned long reg,uint32_t nominal,uint32_t actual,const uint64_t *detail,unsigned size) { assert(stage==70 && done && reg==0x164284 && nominal==0x1c001a5 && actual==0xe001a5); diff --git a/tests/tgl_combo_signal.cpp b/tests/tgl_combo_signal.cpp index 327117db..7af95611 100644 --- a/tests/tgl_combo_signal.cpp +++ b/tests/tgl_combo_signal.cpp @@ -35,6 +35,18 @@ int main() { // Missing bank declines before the first write. writes.clear();regs[s.base+0xA9C]=~0U;assert(!s.program(read,write)&&writes.empty()); } + for(unsigned lanes:{0u,1u,2u,3u,4u})for(unsigned rate:{0u,6u,10u,12u,20u,30u}) + for(unsigned v=0;v<5;++v)for(unsigned e=0;e<5;++e) { + TGLComboSignal s;bool ok=(lanes==1||lanes==2||lanes==4)&&(rate==6||rate==10||rate==20||rate==30)&&v<4&&e<4&&v+e<=3; + assert(s.selectADLS(lanes,rate,v,e)==ok);if(!ok)continue; + assert(s.base==0x160000&&!s.low); + std::map regs; + assert(s.program([&](unsigned r){return regs[r];},[&](unsigned r,unsigned value){ + assert(r>=0x160000&&r<0x160BA0); + if(r==0x160694)for(unsigned n=0;n<4;++n)regs[0x160894+0x100*n]=value; + regs[r]=value; + })); + } TGLComboSignal s;assert(s.select(0,1,10,false,false,0,0)); assert(s.swing==0xA&&s.scalar==0x32&&s.cursor==0x3F&&!s.post1); // Silent dropped coefficient write must not report successful programming. diff --git a/tests/tgl_combo_signal_guard.py b/tests/tgl_combo_signal_guard.py index 23b01bca..2cd4ddfa 100644 --- a/tests/tgl_combo_signal_guard.py +++ b/tests/tgl_combo_signal_guard.py @@ -12,12 +12,13 @@ expected=bytes(int(x.strip(),0) for x in re.search(name+r'\[\] \{([^}]+)',s).group(1).split(',')) assert b[at:at+len(expected)]==expected code=r''' -#include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" #include "kern_tgl_combo_signal.hpp" #include #include #include struct State { + bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr;bool tglActive=true,tglComboSignalReady=true; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts; @@ -26,6 +27,7 @@ std::map registers;unsigned reads=0,writes=0,records=0; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return registers[reg];} void wrapTGLRegisterWrite(void *,unsigned long reg,uint32_t value){++writes; + if(reg==0x160694)for(unsigned n=0;n<4;++n)registers[0x160894+0x100*n]=value; if(reg==0x162694)for(unsigned n=0;n<4;++n)registers[0x162894+0x100*n]=value; registers[reg]=value; } @@ -67,6 +69,27 @@ if(bad==19)assert(result==kIOReturnNotReady); if(bad!=0&&bad!=17&&bad!=18&&bad!=19)assert(reads==0); } + for(unsigned bad=0;bad<12;++bad) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.adlsDetected=state.adlsDPWiring=true;state.xeProfile.family=XELP::Family::AlderLakeS; + state.tglObservationController=ctl;state.firmwarePorts.status=XELP::VBT::Status::Valid; + const uint32_t c[]={4,10,1,0,0x400,0x201};auto config=reinterpret_cast(state.tglPlatformCopy+0x80);memcpy(config,c,sizeof(c)); + store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,4U);store(p,0x128,20U);store(p,0x620,0x160804U);store(p,0x5C8,0x160028U); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=0x3f;registers[0x45454]=0x300;reads=writes=records=0; + if(bad==1)state.adlsDPWiring=false; + if(bad==2)store(p,0x548,c+0); + if(bad==3)store(p,0x550,4U); + if(bad==4)store(p,0x128,12U); + if(bad==5)registers[0x45404]=3; + if(bad==6)registers[0x45454]=3; + if(bad==7)store(p,0x620,0x6c804U); + if(bad>=8)config[bad-8]^=1; + uint32_t result=99;assert(run(p,0,0,nullptr,result)==(bad==0)); + assert((writes!=0)==(bad==0));if(!bad)assert(!result && records==1 && registers[0x16089c]==0x35000000); + } puts("PASS production combo guard: family, port, firmware, ownership, power and missing bank"); } '''.replace('FUNCTION',fn) diff --git a/tests/tgl_port_map_fixture.cpp b/tests/tgl_port_map_fixture.cpp index 721a53bf..a9157188 100644 --- a/tests/tgl_port_map_fixture.cpp +++ b/tests/tgl_port_map_fixture.cpp @@ -17,6 +17,7 @@ struct IORegistryEntry { void *getProperty(const char *) { return nullptr; } }; struct XE { + bool adlsDetected=false,adlsDPWiring=false; // FIELDS IOSimpleLock *tglDekelLock; uintptr_t orgTGLReadDekel,orgTGLWriteDekel; diff --git a/tests/tgl_probe_fixture.cpp b/tests/tgl_probe_fixture.cpp index f9561610..f81aa99f 100644 --- a/tests/tgl_probe_fixture.cpp +++ b/tests/tgl_probe_fixture.cpp @@ -30,6 +30,7 @@ void tglAUXRecord(void *,uint32_t,bool,uint32_t,uint32_t,uint32_t) {} uint32_t providerProbe(void *); uint32_t nativeHPD(void *) {return 1;} struct XE { + bool adlsDetected=false,adlsDPWiring=false; uint32_t tglModularProbed=0; uint64_t tglPlatformCopy[0x1c0/8] {}; bool tglActive=true; diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index 5360fd90..25898c8d 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -44,6 +44,7 @@ template void tglObserveResource(T...) {} void tglAUXPublish() {} void tglDPCDPublish() {} struct XE { + bool adlsDetected=false,adlsDPWiring=false; // TC_WIRING_FIELDS // LEASE_FIELDS IORecursiveLock *tglPathPoolLock; @@ -439,6 +440,11 @@ int main(int argc, char **argv) { assert(used[0]&&fbs[0].at(0x49e8)==1);used[0]=false;clean(); ports[3].at(0x550)=2; assert(XE::tglPrepareTrainingLease(ctl.data(),obj.tglTrainingLeases[3])==1); // no TBT + // ADL-S fixed Combo C uses physical TC2 in mode1 and the same pool. + setup();obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=obj.adlsDPWiring=true;obj.tglAUXParentReady=true; + assert(XE::tglTrainingLeaseForDDI(4)&&!XE::tglTrainingLeaseForDDI(3)); + assert(!obj.tglLegacyTCEligible(4)&&obj.tglAUXParentEligible(4)); + ports[4].at(0x550)=1;reserve(4);release(4);clean(); // Nested and concurrent training must not unmute another invocation. TGLTrainingScopes scopes; auto one=scopes.enter(1), nested=scopes.enter(1), two=scopes.enter(2); diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index 50d59b9b..81518a3c 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -23,6 +23,7 @@ void clock_get_uptime(uint64_t *p){*p=1;} void absolutetime_to_nanoseconds(uint64_t t,uint64_t *p){*p=t;} unsigned observations,nativeCalls; struct XE { + bool adlsDetected=false,adlsDPWiring=false; // FIELDS bool tglActive=true,tglSinkFramingReady=true;void *tglObservationController{};uintptr_t orgTGLTrainingPattern{}; static uint32_t wrapTGLTrainingPattern(void *,uint32_t); diff --git a/tests/tgl_wiring_fixture.cpp b/tests/tgl_wiring_fixture.cpp index b3de4971..da62bca0 100644 --- a/tests/tgl_wiring_fixture.cpp +++ b/tests/tgl_wiring_fixture.cpp @@ -20,6 +20,7 @@ struct IORegistryEntry { #define OSDynamicCast(type,p) dynamic_cast(p) #define FunctionCast(f,p) reinterpret_cast(p) struct XE { + bool adlsDetected=false,adlsDPWiring=false; bool tglActive=true; XELP::Profile xeProfile=XELP::identify(0x9a498086,1);XELP::VBT::Ports firmwarePorts; // FIELDS diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 52772623..83845092 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -55,6 +55,11 @@ int main() { for(unsigned d=0;d<9;++d){auto r=reinterpret_cast(platform+0x20+24*d); const uint32_t v[]={d,9,2,0,0x800,0x202};memcpy(r,v,sizeof(v)); assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d==3?1U:0U));} + c.combo=c.comboBackend=24;b.config=c;assert(c.valid()); + auto dp=reinterpret_cast(platform+0x80);const uint32_t dpc[]={4,10,1,0,0x400,0x201};memcpy(dp,dpc,sizeof(dpc)); + assert(probeMode(io,b,2,reinterpret_cast(dp),dp)==1); + for(unsigned i=0;i<6;++i){dp[i]^=1;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp));dp[i]^=1;} + c.combo=c.comboBackend=8;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp)); assert(io.reads.empty()); // ADL-S must never probe TGL FIA for its Combo TC1. for(unsigned bad=0;bad<5;++bad){auto x=c;if(bad==0)x.combo=1;if(bad==1)x.comboBackend=0x79; if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=0;assert(!x.valid());} From b7b9c4a99f4312a95d0d296e4ce82601537de663 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 10:35:50 +0900 Subject: [PATCH 07/58] Preserve fixed Combo DP mode on Alder Lake-S hotplug --- README.md | 5 +++-- README_ja.md | 3 ++- WhateverXe/kern_alderlake_s.hpp | 4 ++++ WhateverXe/kern_xe.cpp | 13 ++++++++++++- tests/alderlake_s.cpp | 10 ++++++++++ tests/tgl_mode_native_fixture.cpp | 12 ++++++++++++ tests/tgl_mode_serial_fixture.cpp | 2 ++ 7 files changed, 45 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f136313e..2957451d 100644 --- a/README.md +++ b/README.md @@ -168,8 +168,9 @@ These results cover tested systems, not every GPU, display, adapter or dock. - Alder Lake and Raptor Lake S/H/P/U support. Development support for Alder Lake-S `4690` currently covers Big Sur single-display HDMI or fixed DP with Metal and OpenGL acceleration. It requires unreleased companion graphics changes and is - not supported by the published graphics pkg. USB-C, hotplug, sleep/wake and - other macOS versions remain unqualified. + not supported by the published graphics pkg. Fixed DP reconnection has been + tested, but display recovery can be delayed. USB-C, sleep/wake and other + macOS versions remain unqualified. - Display 13 USB-C and eDP support ### Not Planned diff --git a/README_ja.md b/README_ja.md index 818c5c46..18a973f4 100644 --- a/README_ja.md +++ b/README_ja.md @@ -121,7 +121,8 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 - Alder Lake/Raptor LakeのS/H/P/U対応。開発版のAlder Lake-S `4690`では、 Big SurのHDMIまたは固定DP単独出力とMetal/OpenGLアクセラレーションを確認しています。 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 - USB-C、再接続、消灯/復帰、他のmacOSは未検証です。 + 固定DPの再接続は確認済みですが、表示復帰に時間がかかる場合があります。 + USB-C、消灯/復帰、他のmacOSは未検証です。 - Display 13のUSB-C / eDP対応 ### Not Planned diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 76547e08..6cf25375 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -109,6 +109,10 @@ struct DP { const uint32_t expected[]={4,10,1,0,0x400,0x201}; return c && !memcmp(c,expected,sizeof(expected)); } + static uint32_t hotplugMode(bool qualified,const uint32_t *config,const uint32_t *bound, + uintptr_t caller,uintptr_t hpdCaller,uint32_t requested) { + return qualified && config==bound && matches(config) && hpdCaller && caller==hpdCaller && requested==4 ? 1 : requested; + } static bool apply(void *platform,size_t bytes) { if(!platform || bytes<0x1C0)return false; auto p=static_cast(platform); diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 257927b7..bac789d8 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11000,7 +11000,8 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t adlsDisplayHooksReady=ready && adlsPowerReady && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK && (!adlsDPWiring || (tglTrainingLeaseReady && tglSinkFramingReady && tglComboSignalReady && - tglComboAUXLifetimeReady && tglAUXParentReady && tglTCNativeFunctions.complete())); + tglComboAUXLifetimeReady && tglAUXParentReady && tglTCNativeFunctions.complete() && + tglModeResultReady && tglNativeLegacyHPDCaller)); IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); } tglRecordStartOrder(1,nullptr); @@ -13675,6 +13676,16 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { if(mode==3 && traceDDI<9 && (self->tglDPAltCandidates&(1U<tglObservationController))return kIOReturnNotReady; auto caller = __builtin_return_address(0); + // The donor's HPD dispatch classifies physical TC2 as Dekel mode4. + // Our firmware-qualified fixed DP uses Combo C mode1, as at boot. + const auto requestedMode=mode; + mode=AlderLakeS::DP::hotplugMode(self->adlsDetected && self->adlsDPWiring && self->tglModeResultReady, + traceConfig,reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x80), + reinterpret_cast(caller),self->tglNativeLegacyHPDCaller,mode); + if(mode!=requestedMode) { + uint32_t record[]={traceDDI,requestedMode,mode}; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPHotplugMode",record,sizeof(record)); + } if(!self->tglTCModeAllowed(traceDDI,mode) && self->tglRegisterLegacyHPD(traceDDI,mode,reinterpret_cast(caller))) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTCDPRegistered", diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp index e1ab31d0..f5fa0b7a 100644 --- a/tests/alderlake_s.cpp +++ b/tests/alderlake_s.cpp @@ -35,6 +35,16 @@ int main() { firmware.port[4]={0x60d6,8,3,0x20,0xc0,0}; firmware.port[5]={0x68c6,9,0,0x30,0xc0,0}; assert(DP::qualified(firmware)); + const uint32_t dpConfig[]={4,10,1,0,0x400,0x201}; + assert(DP::hotplugMode(true,dpConfig,dpConfig,0x100,0x100,4)==1); + assert(DP::hotplugMode(false,dpConfig,dpConfig,0x100,0x100,4)==4); + assert(DP::hotplugMode(true,dpConfig,nullptr,0x100,0x100,4)==4); + assert(DP::hotplugMode(true,dpConfig,dpConfig,0x101,0x100,4)==4); + assert(DP::hotplugMode(true,dpConfig,dpConfig,0,0,4)==4); + for(unsigned m:{0u,1u,2u,3u})assert(DP::hotplugMode(true,dpConfig,dpConfig,0x100,0x100,m)==m); + for(unsigned n=0;n<6;++n){uint32_t bad[6];memcpy(bad,dpConfig,sizeof(bad));bad[n]^=1; + assert(DP::hotplugMode(true,bad,bad,0x100,0x100,4)==4);} + for(unsigned i=0;i<6;++i){auto bad=firmware;reinterpret_cast(&bad.port[4])[i]^=1;assert(!DP::qualified(bad));} auto dupe=firmware;dupe.port[5].aux=0x20;assert(!DP::qualified(dupe)); auto h=HDMI::fromFirmware(firmware);assert(h.valid && h.selected==8 && h.pin[3]==9); diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index e1aac6fd..1196062c 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" #include @@ -27,6 +28,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; static void tglRecordStartOrder(unsigned,void*,unsigned=9,uint64_t=0,uint64_t=0,uint64_t=0) {} static void tglRecordNativeModeBinding(void *,uint32_t,uint32_t) {} @@ -101,6 +103,16 @@ int main(int argc,char **argv){ reset(3,1);instance.tglDPAltCandidates=8;coldReady=false; assert(XE::wrapTGLSetPortMode(port,3)==kIOReturnNotReady && !reads && !writes && !powerGets); coldReady=true;instance.tglDPAltCandidates=0; + // Qualified ADL-S HPD mode4 must execute native Combo mode1, without FIA. + reset(4);instance.adlsDetected=instance.adlsDPWiring=true; + auto bound=reinterpret_cast(instance.tglPlatformCopy+0x80); + const uint32_t fixed[]={4,10,1,0,0x400,0x201};memcpy(bound,fixed,sizeof(fixed)); + *reinterpret_cast(port+0x548)=bound; + instance.tglNativeLegacyHPDCaller=testCaller=0x74a59; + assert(XE::wrapTGLSetPortMode(port,4)==0 && mode()==1); + assert(!reads && !writes && !powerGets && !powerPuts && !sleeps); + instance.adlsDetected=instance.adlsDPWiring=false;testCaller=0;instance.tglNativeLegacyHPDCaller=0; + *reinterpret_cast(port+0x548)=config; unsigned cases=0; for(bool skipPower:{false,true})for(unsigned d=3;d<=8;++d)for(unsigned requested:{3u,4u})for(bool isReady:{false,true}){ controller[0xc58]=skipPower?2:0;const unsigned powerCalls=skipPower?0:1; diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index f3212562..7cc800bc 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" #include @@ -26,6 +27,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; uint32_t tglTCAutoRegistered=0,tglTCAutoCandidates=0x10,tglAUXParentMask=0x18; // LEGACY_HPD_POLICY From 0ee6bc29123481aa2a8273d16085d870e0b23d0e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:12:01 +0900 Subject: [PATCH 08/58] Derive Alder Lake-S display routes from VBT --- README.md | 9 +- README_ja.md | 4 + WhateverXe/kern_alderlake_s.hpp | 172 +++++++++++++------------ WhateverXe/kern_tgl_combo_signal.hpp | 9 +- WhateverXe/kern_tgl_port_map.hpp | 8 +- WhateverXe/kern_xe.cpp | 108 ++++++++-------- WhateverXe/kern_xe.hpp | 13 +- WhateverXe/kern_xelp_platform.hpp | 6 +- WhateverXe/kern_xelp_probe.hpp | 4 +- tests/adls_clock_guard.py | 22 +++- tests/adls_clock_native_fixture.cpp | 41 +++--- tests/adls_donor.py | 22 +++- tests/adls_lane_power.py | 21 ++- tests/alderlake_s.cpp | 104 +++++++++------ tests/provider_binding_fixture.cpp | 2 +- tests/tgl_combo_aux_lifetime.py | 12 +- tests/tgl_combo_signal.cpp | 11 +- tests/tgl_combo_signal_guard.py | 24 ++-- tests/tgl_mode_native_fixture.cpp | 12 +- tests/tgl_mode_serial_fixture.cpp | 2 +- tests/tgl_port_map_fixture.cpp | 3 +- tests/tgl_probe_fixture.cpp | 3 +- tests/tgl_training_leases_fixture.cpp | 5 +- tests/tgl_training_pattern_fixture.cpp | 3 +- tests/tgl_wiring_fixture.cpp | 3 +- tests/xelp_platform.cpp | 8 +- 26 files changed, 359 insertions(+), 272 deletions(-) diff --git a/README.md b/README.md index 2957451d..a5c7f5e9 100644 --- a/README.md +++ b/README.md @@ -169,8 +169,13 @@ These results cover tested systems, not every GPU, display, adapter or dock. `4690` currently covers Big Sur single-display HDMI or fixed DP with Metal and OpenGL acceleration. It requires unreleased companion graphics changes and is not supported by the published graphics pkg. Fixed DP reconnection has been - tested, but display recovery can be delayed. USB-C, sleep/wake and other - macOS versions remain unqualified. + tested, but display recovery can be delayed. HDMI/fixed-DP routing is derived + from VBT rather than a board or connector preset, within the current Combo + PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability + and PHY D/E are excluded. GPU admission remains limited to `4690` revision + `0C` with the qualified fuse configuration. Other port layouts have CPU-test + coverage but are not hardware-qualified. USB-C, sleep/wake and other macOS + versions remain unqualified. - Display 13 USB-C and eDP support ### Not Planned diff --git a/README_ja.md b/README_ja.md index 18a973f4..00e8846b 100644 --- a/README_ja.md +++ b/README_ja.md @@ -122,6 +122,10 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 Big SurのHDMIまたは固定DP単独出力とMetal/OpenGLアクセラレーションを確認しています。 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 固定DPの再接続は確認済みですが、表示復帰に時間がかかる場合があります。 + HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 + 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 + GPUの適用対象は、確認済みのfuse構成を持つ `4690` revision `0C` に限定しています。 + 別のポート配置はCPUテストで確認していますが、実機では未検証です。 USB-C、消灯/復帰、他のmacOSは未検証です。 - Display 13のUSB-C / eDP対応 diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 6cf25375..0c63ba61 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -20,6 +20,8 @@ struct GT { out={1,2,4,32,4,256};return true; } }; +// The donor has three Combo clock slots. D/E need a separate clock allocator. +constexpr uint32_t ComboBackendMask=0x19; // physical A/TC1/TC2, PHY A/B/C struct Port { uint32_t ddi {},phy {},buffer {},clock {},clockShift {},clockOff {},hpd {}; static bool resolve(uint32_t ddi,Port &out) { @@ -31,6 +33,14 @@ struct Port { 2*(phy%3),1U<=2 && vbtPin<=5?vbtPin+7:0; } -struct HDMI { - uint32_t selected {},pin[XELP::PortCount] {}; +// Immutable firmware-derived routing shared by startup, clocks, training and +// hotplug. Unsupported or ambiguous routes stay excluded; never guess a port. +struct Wiring { + uint32_t hdmi {},dp {},excluded {},pin[XELP::PortCount] {}; bool valid {}; - bool matches(const uint32_t *c) const { - return valid && c && c[0]=XELP::PortCount || + !((wantDP?dp:hdmi)&(1U<( + static_cast(platform)+0x20+24*c[0]); + } + bool boundDP(const void *platform,const uint32_t *c) const {return matches(c,true) && bound(platform,c);} bool usesDDC(uint32_t bus) const { if(!valid)return false; - for(unsigned d=0;d(platform); if(p[0x12]!=9)return false; - for(unsigned d=0;d=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; const uint32_t panel[]={0,0,0,0,2,0x18}; if(memcmp(p+0x20+24*d,expected,sizeof(expected)) && (d || memcmp(p+0x20,panel,sizeof(panel))))return false; } - for(unsigned d=0;d(p+0x20+24*d); - c[1]=pin[d];c[2]=2;c[3]=0;c[4]=0x800;c[5]=0x202; + for(unsigned d=0;d(p+0x20+24*d);const bool isDP=dp&(1U<=3?d+6:d+1); + if(!bus || (isDP && p.aux!=port.aux()))continue; + bool shared=false; + for(unsigned other=0;other(platform); - const uint32_t expected[]={4,10,1,1,0x400,0x201}; - if(p[0x12]!=9 || memcmp(p+0x80,expected,sizeof(expected)))return false; - reinterpret_cast(p+0x80)[3]=0;return true; - } -}; struct PLL { uint32_t enable {},cfg0 {},cfg1 {}; static bool resolve(uint32_t id,PLL &out) { @@ -130,37 +150,31 @@ struct PLL { out={enables[id],configs[id],configs[id]+4};return true; } }; -// PortHAL::init(c004fc1a) constructs DDI controls independently from the -// Combo PHY table at +0x5AC. Its TC1 case leaves that table zero. Populate -// only PHY B's fields; retain TC1's buffer, physical record and AUX slots. -inline bool comboBRegisters(uint32_t *registers,size_t count) { - if(!registers || count!=0x128/4 || registers[(0x590-0x580)/4]!=0x64300 || - registers[(0x64C-0x580)/4]!=0x4610C)return false; - for(unsigned off=0x5AC;off<=0x648;off+=4)if(registers[(off-0x580)/4])return false; - const uint32_t phy[] { - 0x64C04,0x6C100,0x6C104,0x6C10C,0x6C124,0x6C128, - 0x6C014,0x6C028,0x6C030,0x6C03C, - 0x6C688,0x6C888,0x6C988,0x6CA88,0x6CB88,0x6C388, - 0x6C690,0x6C890,0x6C990,0x6CA90,0x6CB90,0x6C390, - 0x6C694,0x6C894,0x6C994,0x6CA94,0x6CB94,0x6C394, - 0x6C604,0x6C804,0x6C904,0x6CA04,0x6CB04,0x6C304, - 0x6C624,0x6C824,0x6C924,0x6CA24,0x6CB24,0x6C324 +// Keep physical DDI/AUX registers; populate only the separate Combo bank. +// PHY A has a native table: validate it and correct its duplicated lane2 slot. +inline bool comboRegisters(uint32_t ddi,uint32_t *registers,size_t count) { + Port port; + if(!Port::resolve(ddi,port) || !(ComboBackendMask&(1U< +#include "kern_alderlake_s.hpp" struct TGLComboSignal { uint32_t base {},lanes {},level {},swing {},scalar {},cursor {},post1 {}; bool low {}; @@ -46,12 +47,14 @@ struct TGLComboSignal { const auto row=low ? reduced[level]:normal[level]; swing=row[0];scalar=row[1];cursor=row[2];post1=row[3];return true; } - // ADL-S fixed DP on Combo C. RBR/HBR uses the TGL normal table; + // ADL-S fixed DP on the resolved Combo PHY. RBR/HBR uses the TGL normal table; // HBR2/HBR3 uses Intel's ADL-S external DP coefficients. - bool selectADLS(uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis) { + bool selectADLS(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis) { if(rate!=6 && rate!=10 && rate!=20 && rate!=30)return false; if(!select(1,count,6,false,false,voltage,emphasis))return false; - base=0x160000; + AlderLakeS::Port route; + if(!AlderLakeS::Port::resolve(ddi,route) || !(AlderLakeS::ComboBackendMask&(1U<=20) { static const uint8_t high[10][4]={ {0xA,0x35,0x3F,0},{0xA,0x4F,0x37,8},{0xC,0x63,0x31,0xE},{6,0x7F,0x2C,0x13}, diff --git a/WhateverXe/kern_tgl_port_map.hpp b/WhateverXe/kern_tgl_port_map.hpp index 6ac45b4c..4dbb9156 100644 --- a/WhateverXe/kern_tgl_port_map.hpp +++ b/WhateverXe/kern_tgl_port_map.hpp @@ -178,12 +178,12 @@ struct TGLSSTTrainingPort { const bool edp = physicalDDI < 2 && kind == 0 && backend == 1; const bool tc = physicalDDI >= 3 && physicalDDI <= 8 && kind == 1 && (backend == 3 || backend == 4) && qualifiedTC; - // qualifiedTC is the caller's generation/wiring admission. ADL-S TC2 - // is Combo C in mode1, while its physical lane power remains PW3. - const bool adls = physicalDDI==4 && kind==1 && backend==1 && qualifiedTC; + // qualifiedTC includes generation and firmware admission. ADL-S + // physical A/TC1/TC2 use Combo mode1; TC lane power remains PW3. + const bool adls = (physicalDDI==0 || physicalDDI==3 || physicalDDI==4) && kind==1 && backend==1 && qualifiedTC; if (!edp && !tc && !adls) return false; out.ddi = physicalDDI; out.ddiBuffer = 0x64000 + 0x100 * physicalDDI; - out.combo = edp; + out.combo = edp || (adls && physicalDDI==0); return true; } uint32_t mainPowerMask(uint32_t pipe) const { diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index bac789d8..70bde3fe 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4037,8 +4037,9 @@ bool XE::xelpBindProbe(void *controller) { if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false; XELP::ProbeConfig config;config.profile=xeProfile; if(adlsDetected) { - if(!tglHDMIWiringApplied || !adlsHDMIWiring.valid || adlsHDMIWiring.selected!=8)return false; - config.combo=config.comboBackend=adlsDPWiring?24:8; + if(!tglHDMIWiringApplied || !adlsWiring.valid || !adlsWiring.selected())return false; + config.combo=adlsWiring.selected();config.comboBackend=AlderLakeS::ComboBackendMask; + config.excluded=adlsWiring.excluded; } else if(xeProfile.display==XELP::Display::XeD) { if(!tglHDMIWiringApplied || !tglTCWiringLoaded || !tglTCWiring.valid)return false; config.combo=tglHDMIWiring.hdmi;config.comboBackend=7; @@ -10943,10 +10944,10 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPAltFiaReady",tglDPAltFiaReady); if(adlsDetected) { // Keep the physical DDI in every port/power/display object. Only the - // native Combo PLL indices are translated, under the single-HDMI entry - // guards below. The removed instructions are profiling counters. + // native Combo PLL indices are translated, under the firmware-route entry + // guards below (physical 0/3/4 >> 1 gives PHY 0/1/2). The removed instructions are profiling counters. const uint8_t adlsClockOnFind[]={0x48,0xff,0x05,0xbe,0x86,0x07,0x00,0x48,0x63,0x00,0x48,0x83,0xf8,0x03}; - const uint8_t adlsClockOnReplace[]={0x48,0x63,0x00,0x48,0x83,0xe8,0x02,0x48,0x83,0xf8,0x03,0x0f,0x1f,0x00}; + const uint8_t adlsClockOnReplace[]={0x48,0x63,0x00,0x48,0xd1,0xe8,0x90,0x48,0x83,0xf8,0x03,0x0f,0x1f,0x00}; const uint8_t adlsClockSelectCPLLFind[]={0x83,0xc8,0x10}; const uint8_t adlsClockSelectCPLLReplace[]={0x83,0xc8,0x20}; const uint8_t adlsClockSelectCFind[]={0x83,0xf9,0x02}; @@ -10954,11 +10955,11 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t const uint8_t adlsClockSelectFind[]={0x83,0xf9,0x01}; const uint8_t adlsClockSelectReplace[]={0x83,0xf9,0x03}; const uint8_t adlsClockOffFind[]={0x48,0xff,0x05,0xe9,0x79,0x07,0x00,0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xfc,0x03}; - const uint8_t adlsClockOffReplace[]={0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xec,0x02,0x49,0x83,0xfc,0x03,0x0f,0x1f,0x00}; - // PCH GMBUS9 is a real ADL-S pin. The donor's ICL-era <8 check - // rejects it before MMIO. The entry wrapper admits only wired pin9. + const uint8_t adlsClockOffReplace[]={0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0xd1,0xec,0x90,0x49,0x83,0xfc,0x03,0x0f,0x1f,0x00}; + // ADL-S GMBUS uses pins 1,9..12. The wrapper admits VBT-wired + // pins only; extend the donor's legacy range check before MMIO. const uint8_t adlsClockBusFind[]={0x83,0x7d,0xcc,0x08,0x72,0x2a}; - const uint8_t adlsClockBusReplace[]={0x83,0x7d,0xcc,0x0a,0x72,0x2a}; + const uint8_t adlsClockBusReplace[]={0x83,0x7d,0xcc,0x0d,0x72,0x2a}; 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); @@ -10999,7 +11000,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t // Native startup remains closed if any required ADL-S adapter is absent. adlsDisplayHooksReady=ready && adlsPowerReady && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK && - (!adlsDPWiring || (tglTrainingLeaseReady && tglSinkFramingReady && tglComboSignalReady && + (!adlsWiring.dp || (tglTrainingLeaseReady && tglSinkFramingReady && tglComboSignalReady && tglComboAUXLifetimeReady && tglAUXParentReady && tglTCNativeFunctions.complete() && tglModeResultReady && tglNativeLegacyHPDCaller)); IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); @@ -11049,16 +11050,15 @@ bool XE::wrapTGLGetOSInformation(void *controller) { } auto copy=reinterpret_cast(self->tglPlatformCopy); if(copy!=platform)memcpy(copy,platform,sizeof(self->tglPlatformCopy)); - const bool wiringApplied=self->adlsDetected ? (self->adlsHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) && - (!self->adlsDPWiring || AlderLakeS::DP::apply(copy,sizeof(self->tglPlatformCopy)))) : + const bool wiringApplied=self->adlsDetected ? self->adlsWiring.apply(copy,sizeof(self->tglPlatformCopy)) : self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)); if(!self->tglHDMIWiringLoaded || !wiringApplied) { IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringGuardFailed",true);return false; } auto config=reinterpret_cast(copy+0x20); platform=copy;self->tglHDMIWiringApplied=true; - if(self->adlsDetected)IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPWiringApplied",self->adlsDPWiring); - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDetected?self->adlsHDMIWiring.selected:self->tglHDMIWiring.hdmi,32); + if(self->adlsDetected)IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPWiringApplied",self->adlsWiring.dp,32); + IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDetected?self->adlsWiring.hdmi:self->tglHDMIWiring.hdmi,32); // Preserve the existing TC2 machine's diagnostic names for old collectors. IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBTopologyApplied",bool(self->tglHDMIWiring.hdmi&2)); if(self->tglHDMIWiring.hdmi&2)IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBPortConfig",config+6,24); @@ -11115,9 +11115,10 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { self->tglHDMIB=false; // The legacy five-word property addresses TGL Combo A/B/C, not ADL-S. if(provider && provider->getProperty("tgl-hdmi-wiring"))return false; - self->adlsHDMIWiring=AlderLakeS::HDMI::fromFirmware(self->firmwarePorts); - self->adlsDPWiring=AlderLakeS::DP::qualified(self->firmwarePorts); - self->tglHDMIB=self->adlsHDMIWiring.valid && self->adlsHDMIWiring.selected==8; + self->adlsWiring=AlderLakeS::Wiring::fromFirmware(self->firmwarePorts); + self->tglHDMIB=self->adlsWiring.valid && self->adlsWiring.selected(); + uint32_t routes[]={self->adlsWiring.hdmi,self->adlsWiring.dp,self->adlsWiring.excluded,AlderLakeS::ComboBackendMask}; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSFirmwareRoutes",routes,sizeof(routes)); } return self->tglHDMIB; } @@ -13676,11 +13677,11 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { if(mode==3 && traceDDI<9 && (self->tglDPAltCandidates&(1U<tglObservationController))return kIOReturnNotReady; auto caller = __builtin_return_address(0); - // The donor's HPD dispatch classifies physical TC2 as Dekel mode4. - // Our firmware-qualified fixed DP uses Combo C mode1, as at boot. + // TC-labelled fixed DP routes use Combo mode1 on ADL-S at both + // boot and hotplug. Preserve the original caller and immutable record guards. const auto requestedMode=mode; - mode=AlderLakeS::DP::hotplugMode(self->adlsDetected && self->adlsDPWiring && self->tglModeResultReady, - traceConfig,reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x80), + mode=self->adlsWiring.hotplugMode(self->adlsDetected && self->tglModeResultReady, + traceConfig,self->tglPlatformCopy, reinterpret_cast(caller),self->tglNativeLegacyHPDCaller,mode); if(mode!=requestedMode) { uint32_t record[]={traceDDI,requestedMode,mode}; @@ -14491,7 +14492,7 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { } uint32_t XE::wrapTGLGMBus(void *controller, uint32_t bus, uint32_t speed, void *request) { - if(callbackXE->adlsDetected && (!callbackXE->tglHDMIDDCEligible(bus) || bus!=9)) { + if(callbackXE->adlsDetected && !callbackXE->tglHDMIDDCEligible(bus)) { if(request)*reinterpret_cast(static_cast(request)+0x1C)=0xE00002C2; return 0xE00002C2; } @@ -15800,15 +15801,14 @@ bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { auto self=callbackXE; if(!self->adlsClockReady || !controller || controller!=self->tglObservationController || !port || (otherPort && otherPort!=port) || - self->adlsHDMIWiring.selected!=8)return false; + !self->adlsWiring.valid || !self->adlsWiring.selected())return false; auto p=static_cast(port); auto config=*reinterpret_cast(p+0x548); auto access=*reinterpret_cast(static_cast(controller)+0xC40); if(!config)return false; - const bool hdmi=self->adlsHDMIWiring.matches(config); - const bool dp=self->adlsDPWiring && AlderLakeS::DP::matches(config); - return (hdmi || dp) && config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x20+24*config[0]) && access && - *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==(dp?0x16010CU:0x6C10CU) && + AlderLakeS::Port route; + return self->adlsWiring.bound(self->tglPlatformCopy,config) && AlderLakeS::Port::resolve(config[0],route) && access && + *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==route.phyBase()+0x10C && *reinterpret_cast(access+0x60)>=0x16429C; } @@ -15826,37 +15826,34 @@ void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32 bool XE::wrapADLSPortInit(void *port,uint32_t *config) { const bool result=FunctionCast(wrapADLSPortInit,callbackXE->orgADLSPortInit)(port,config); - if(!result || !callbackXE->adlsDetected || !config || (config[0]!=3 && config[0]!=4))return result; + if(!result || !callbackXE->adlsDetected || !config || !callbackXE->adlsWiring.matches(config))return result; auto bytes=static_cast(port); - const bool dp=config[0]==4; - if(dp && !callbackXE->adlsDPWiring)return result; const bool ready=callbackXE->adlsPortInitReady && - (dp ? AlderLakeS::DP::matches(config):callbackXE->adlsHDMIWiring.matches(config)) && - config==reinterpret_cast(reinterpret_cast(callbackXE->tglPlatformCopy)+0x20+24*config[0]) && + callbackXE->adlsWiring.bound(callbackXE->tglPlatformCopy,config) && *reinterpret_cast(bytes+0x548)==config && - (dp ? AlderLakeS::comboCRegisters(reinterpret_cast(bytes+0x580),0x128/4): - AlderLakeS::comboBRegisters(reinterpret_cast(bytes+0x580),0x128/4)); - IORegistryEntry::getRegistryRoot()->setProperty(dp?"ADLSComboCPortReady":"ADLSComboBPortReady",ready); + AlderLakeS::comboRegisters(config[0],reinterpret_cast(bytes+0x580),0x128/4); + char key[40];snprintf(key,sizeof(key),"ADLSComboPort%uReady",config[0]); + IORegistryEntry::getRegistryRoot()->setProperty(key,ready); return ready; } void XE::wrapTGLEnableCombo(void *port) { - auto b = static_cast(port); - auto config = *reinterpret_cast(b + 0x548); + auto b=static_cast(port); + auto config=b?*reinterpret_cast(b+0x548):nullptr; if(callbackXE->adlsDetected) { if(!config || !callbackXE->adlsPortInitReady)return; + // PHY A also supplies the shared reference even with no wired port. if(config[0]==0) {FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port);return;} - // The donor's power-engine startup enumerates Combo slots0..2. - // ADL-S Combo B/C belong to physical TC1/TC2, constructed separately. - if((config[0]==1 || (config[0]==2 && callbackXE->adlsDPWiring)) && callbackXE->tglPortByDDI) + // Native startup enumerates PHY slots 0..2, not physical DDI IDs. + if(config[0]==1 || config[0]==2) { + if(!callbackXE->tglPortByDDI)return; port=reinterpret_cast(callbackXE->tglPortByDDI)(config[0]+2); - else if(config[0]!=3 && !(config[0]==4 && callbackXE->adlsDPWiring))return; - b=static_cast(port); - config=b?*reinterpret_cast(b+0x548):nullptr; - if(config && ((callbackXE->adlsHDMIWiring.matches(config) && *reinterpret_cast(b+0x5B8)==0x6C10C) || - (callbackXE->adlsDPWiring && AlderLakeS::DP::matches(config) && *reinterpret_cast(b+0x5B8)==0x16010C))) + } + b=static_cast(port);config=b?*reinterpret_cast(b+0x548):nullptr; + AlderLakeS::Port route; + if(callbackXE->adlsWiring.bound(callbackXE->tglPlatformCopy,config) && + AlderLakeS::Port::resolve(config[0],route) && *reinterpret_cast(b+0x5B8)==route.phyBase()+0x10C) FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port); - else IORegistryEntry::getRegistryRoot()->setProperty("ADLSComboBInitRejected",true); return; } const bool absent = callbackXE->tglActive && config && config[0] == 2 && @@ -16495,7 +16492,7 @@ uint8_t *XE::adlsPowerTarget(void *power) { auto s=callbackXE;auto p=static_cast(power); auto c=static_cast(s->tglObservationController); if(!s->adlsDetected || !s->adlsPowerReady || !p || !c || - !s->adlsHDMIWiring.valid || s->adlsHDMIWiring.selected!=8 || + !s->adlsWiring.valid || !s->adlsWiring.selected() || *reinterpret_cast(p+0x10)!=c || *reinterpret_cast(c+0xC8)!=p || *reinterpret_cast(p+0x78)!=*reinterpret_cast(c+0xC40))return nullptr; return p; @@ -16503,8 +16500,7 @@ uint8_t *XE::adlsPowerTarget(void *power) { void XE::wrapADLSPowerPG(void *power,bool enable,uint32_t requested) { auto p=adlsPowerTarget(power); - const auto target=AlderLakeS::LanePower::target(requested,p && (*reinterpret_cast(p+0x3C) || - (callbackXE->adlsDPWiring && *reinterpret_cast(p+0x40)))); + const auto target=AlderLakeS::LanePower::target(requested,p && callbackXE->adlsWiring.laneConsumer(reinterpret_cast(p+0x30))); tglTransitionRecord(73,false,power,requested,target,enable); FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,enable,target); tglTransitionRecord(73,true,power,requested,target,enable); @@ -16519,7 +16515,7 @@ 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); - const auto laneMask=callbackXE->adlsDPWiring?0x280U:0x80U; + const auto laneMask=callbackXE->adlsWiring.laneMask(); if(adls && (requested&laneMask)) { const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,true,AlderLakeS::LanePower::target(native,true)); @@ -16575,8 +16571,7 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void (self->xeProfile.family!=XELP::Family::TigerLake && !self->adlsDetected) || !self->firmwarePorts.valid())return false; auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; - const bool adls=self->adlsDetected && self->adlsDPWiring && AlderLakeS::DP::matches(config) && - config==reinterpret_cast(reinterpret_cast(self->tglPlatformCopy)+0x80); + const bool adls=self->adlsDetected && self->adlsWiring.boundDP(self->tglPlatformCopy,config); if(!config || *reinterpret_cast(p+0x550)!=1 || p[0x117])return false; if(!adls && (config[0]>1 || config[2]!=0 || config[3]!=0 || !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17)))return false; @@ -16585,7 +16580,7 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void TGLComboSignal selected; const auto lanes=*reinterpret_cast(p+0x124); const auto rate=*reinterpret_cast(p+0x128); - if(!(adls ? selected.selectADLS(lanes,rate,swing,emphasis): + if(!(adls ? selected.selectADLS(config[0],lanes,rate,swing,emphasis): selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis)))return false; // The native fields identify this very bank. No guessed framebuffer index // or cross-port reads; power must already belong to the training caller. @@ -16596,7 +16591,7 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void const auto main=wrapTGLRegisterRead(access,0x45404); const auto io=wrapTGLRegisterRead(access,0x45454); const uint32_t ioMask=3U<<(2*config[0]); - const uint32_t mainMask=adls?0x3FU:3U; + const uint32_t mainMask=adls && config[0]>=3?0x3FU:3U; if(main==~0U || io==~0U || (main&mainMask)!=mainMask || (io&ioMask)!=ioMask)return false; const auto read=[&](uint32_t reg){return wrapTGLRegisterRead(access,reg);}; const auto write=[&](uint32_t reg,uint32_t value){wrapTGLRegisterWrite(access,reg,value);}; @@ -16929,8 +16924,7 @@ void XE::tglComboAUXReference(void *port,bool acquire) { if(!p || !ctl || !s->tglActive || !s->tglComboAUXLifetimeReady || !f.complete() || (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected) || *f.controllerSlot!=ctl)return; auto config=*reinterpret_cast(p+0x548); - const bool adls=s->adlsDetected && s->adlsDPWiring && AlderLakeS::DP::matches(config) && - config==reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x80); + const bool adls=s->adlsDetected && s->adlsWiring.boundDP(s->tglPlatformCopy,config); if(!config || (config[0]>1 && !adls))return; const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; @@ -16940,7 +16934,7 @@ void XE::tglComboAUXReference(void *port,bool acquire) { if(!power || !access || *reinterpret_cast(power+0x10)!=ctl || *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(p+0x578)!=access || - *reinterpret_cast(access+0x60)<(adls?0x64404U:0x64200U) || + *reinterpret_cast(access+0x60)<(0x64004U+0x100*ddi) || !*reinterpret_cast(access+0x50))return; const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); if(*reinterpret_cast(p+0x590)!=buffer)return; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index b7d766f2..607216d8 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -292,13 +292,12 @@ class XE { static bool wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, OSObject *value); bool tglHDMIB {false}; TGLHDMIWiring tglHDMIWiring; - AlderLakeS::HDMI adlsHDMIWiring; - bool adlsDPWiring {}; + AlderLakeS::Wiring adlsWiring; bool tglHDMIWiringLoaded {},tglHDMIWiringExplicit {},tglHDMIWiringApplied {}; uint32_t tglHDMIBackendMask {3}; // TGL-U combo PHY A/B; C is absent static bool tglLoadHDMIWiring(IOService *provider); - bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDetected?adlsHDMIWiring.matches(config):tglHDMIWiring.matches(config));} - bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDetected?adlsHDMIWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} + bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDetected?adlsWiring.matches(config,false):tglHDMIWiring.matches(config));} + bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDetected?adlsWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} bool tglGMBusReady {false}; mach_vm_address_t orgTGLGMBus {}; @@ -405,10 +404,10 @@ class XE { uint32_t tglTCAutoCandidates {},tglTCAutoRegistered {}; uint32_t tglDPAltCandidates {}; bool tglDPSSTEligible(uint32_t ddi) const { - return (adlsDetected && adlsDPWiring && ddi==4) || tglLegacyTCEligible(ddi) || (ddi>=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && adlsWiring.hasDP(ddi))); } static void wrapTGLEnableAUXPower(void *power, uint32_t ddi); static void wrapTGLDisableAUXPower(void *power, uint32_t ddi); diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 17838b4f..77761f53 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -71,11 +71,11 @@ struct ProbeConfig { bool valid() const { if(abi!=3 || bytes!=sizeof(ProbeConfig) || !profile.admitted() || !same(profile,identify(profile.identity,profile.revision,profile.effectiveIdentity)))return false; - // ADL-S fixed HDMI TC1/Combo B and optional DP TC2/Combo C. + // ADL-S firmware routes use the donor's three Combo PHY slots. // Its TC label grants neither Type-C nor Dekel register access. if(profile.family==Family::AlderLakeS) - return profile.display==Display::XeD && (combo==8 || combo==24) && comboBackend==combo && - !legacy && !typeC && !excluded && !legacyBackend; + return profile.display==Display::XeD && combo && !(combo&~0x19U) && comboBackend==0x19 && + !(excluded&~0x1FFU) && !(excluded&combo) && !legacy && !typeC && !legacyBackend; 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; diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index 70975b73..f8522a0d 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -31,8 +31,8 @@ template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man if(!b.bound(manager,record,c))return 0; if(b.config.profile.family==Family::AlderLakeS) { if(!(b.config.combo&b.config.comboBackend&(1U<=9 && c[1]<=12)) && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; + const bool dp=c[1]==(c[0]>=3?c[0]+6:c[0]+1) && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; return hdmi || dp ? 1 : 0; } if(b.config.profile.display==Display::XeD) diff --git a/tests/adls_clock_guard.py b/tests/adls_clock_guard.py index a7a78bf5..b93080b3 100644 --- a/tests/adls_clock_guard.py +++ b/tests/adls_clock_guard.py @@ -7,16 +7,17 @@ code='''#include "kern_alderlake_s.hpp" #include #include +#include struct XE { bool adlsClockReady=true;void *tglObservationController;uint8_t tglPlatformCopy[0x1c0] {}; - bool adlsDPWiring=false;AlderLakeS::HDMI adlsHDMIWiring;static bool adlsClockTarget(void*,void*,void*); + AlderLakeS::Wiring adlsWiring;static bool adlsClockTarget(void*,void*,void*); }; XE state;XE *callbackXE=&state; FUNCTION templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} int main(){ alignas(8) uint8_t ctl[0xF00] {},port[0x700] {},access[0x70] {}; - state.tglObservationController=ctl;state.adlsHDMIWiring.valid=true;state.adlsHDMIWiring.selected=8;state.adlsHDMIWiring.pin[3]=9; + state.tglObservationController=ctl;state.adlsWiring.valid=true;state.adlsWiring.hdmi=8;state.adlsWiring.pin[3]=9; uint32_t config[]={3,9,2,0,0x800,0x202};auto c=reinterpret_cast(state.tglPlatformCopy+0x68); memcpy(c,config,sizeof(config));at(port,0x548)=c;at(port,0x578)=access; at(ctl,0xC40)=access;at(port,0x5B8)=0x6C10C;at(access,0x60)=0x1000000; @@ -28,18 +29,29 @@ at(port,0x5B8)=0;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x5B8)=0x6C10C; at(access,0x60)=0x100000;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} - state.adlsHDMIWiring.selected|=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsHDMIWiring.selected=8; + state.adlsWiring.hdmi|=1;assert(XE::adlsClockTarget(ctl,port,nullptr));state.adlsWiring.hdmi=8; state.adlsClockReady=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); - state.adlsClockReady=true;state.adlsDPWiring=true; + state.adlsClockReady=true;state.adlsWiring.valid=true;state.adlsWiring.dp=16;state.adlsWiring.pin[4]=10; uint32_t dp[]={4,10,1,0,0x400,0x201};c=reinterpret_cast(state.tglPlatformCopy+0x80); memcpy(c,dp,sizeof(dp));at(port,0x548)=c;at(port,0x5B8)=0x16010C; assert(XE::adlsClockTarget(ctl,port,nullptr));assert(XE::adlsClockTarget(ctl,port,port)); - state.adlsDPWiring=false;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsDPWiring=true; + state.adlsWiring.dp=0;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsWiring.valid=true;state.adlsWiring.dp=16;state.adlsWiring.pin[4]=10; at(port,0x548)=dp;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x548)=c; at(port,0x5B8)=0x6C10C;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x5B8)=0x16010C; at(access,0x60)=0x164298;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} assert(!XE::adlsClockTarget(ctl,port,access));assert(!XE::adlsClockTarget(access,port,nullptr)); + for(unsigned ddi:{0u,3u,4u})for(bool isDP:{false,true}) { + state.adlsWiring={};state.adlsWiring.valid=true; + (isDP?state.adlsWiring.dp:state.adlsWiring.hdmi)=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(bound,config,sizeof(config)); + at(port,0x548)=bound;at(port,0x5B8)=route.phyBase()+0x10C; + assert(XE::adlsClockTarget(ctl,port,nullptr)); + state.adlsWiring.valid=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); + } puts("PASS ADL-S runtime clock guard: exact owner, immutable TC1/TC2 configurations, mapped Combo B/C and no shared port"); }'''.replace('FUNCTION',function) with tempfile.TemporaryDirectory(prefix='adls-clock-guard-') as d: diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp index aefc01e6..f089d63f 100644 --- a/tests/adls_clock_native_fixture.cpp +++ b/tests/adls_clock_native_fixture.cpp @@ -21,7 +21,7 @@ void writeReg(void *p,unsigned long reg,uint32_t v){ if(reinterpret_cast(__builtin_return_address(0))==reinterpret_cast(image+0x6d973)) TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg==0x164294?0x164284:reg,v); events.push_back({1,uint32_t(reg),v}); - if(reg==0x46014 || reg==0x46018) { + if(reg==0x46010 || reg==0x46014 || reg==0x46018) { // Model hardware ACK/LOCK independently from request bits. Failure cases // exercise the donor's bounded waits, not successful hardware operation. v&=~0x44000000U; @@ -32,8 +32,8 @@ void writeReg(void *p,unsigned long reg,uint32_t v){ } void mailbox(void *p,uint32_t command,uint32_t value,bool wait){assert(p==ctl && command==7 && !wait);events.push_back({2,command,value});} uint32_t voltage(void *p){assert(p==ctl);return 1;} -void getDDI(void *p){assert(p==port && (at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,1});} -void putDDI(void *p){assert(p==port && (at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,0});} +void getDDI(void *p){assert(p==port && (at(p,0x548)[0]==0 || at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,1});} +void putDDI(void *p){assert(p==port && (at(p,0x548)[0]==0 || at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,0});} void delay(unsigned n){assert(n==1);++delays;} void error(const char *,...){++errors;} void redirectCall(unsigned site,uintptr_t target,unsigned stub){ @@ -55,19 +55,22 @@ int main(int argc,char **argv){ auto enable=(void(*)(void*,void*,void*,uint32_t*))(image+0x6ce10); auto disable=(void(*)(void*,void*,void*))(image+0x6dae6); unsigned cases=0; - for(unsigned ref=0;ref<=2;++ref)for(unsigned p:{2u,3u,5u,7u})for(unsigned k:{1u,2u,3u})for(unsigned q:{1u,3u}) { + for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned p:{2u,3u,5u,7u})for(unsigned k:{1u,2u,3u})for(unsigned q:{1u,3u}) { + config[0]=ddi;config[1]=ddi?ddi+6:1; + AlderLakeS::Port route;AlderLakeS::PLL pll;assert(AlderLakeS::Port::resolve(ddi,route));assert(AlderLakeS::PLL::resolve(route.phy,pll)); fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref; - mmio={{0x46014,0},{0x16428c,0},{0x164290,0},{0x164280,0x01e07400}}; + mmio={{pll.enable,0},{pll.cfg0,0},{pll.cfg1,0},{0x164280,0x01e07400}}; uint32_t params[]={464,2049,p,q,k};AlderLakeS::HDMIClock expected; assert(AlderLakeS::HDMIClock::encode(params,ref,expected)); enable(ctl,port,nullptr,params); - assert(!errors&&!delays && mmio[0x16428c]==expected.cfg0 && mmio[0x164290]==expected.cfg1); - assert(mmio[0x46014]==0xcc000000 && mmio[0x164280]==((0x01e07400&~0x80cU)|4)); + assert(!errors&&!delays && mmio[pll.cfg0]==expected.cfg0 && mmio[pll.cfg1]==expected.cfg1); + assert(mmio[pll.enable]==0xcc000000 && mmio[0x164280]==((0x01e07400&~(route.clockOff|(3U<(port,0x114)=1; - for(unsigned ref=0;ref<=2;++ref)for(unsigned rate:{6u,10u,20u,30u}) { + for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned rate:{6u,10u,20u,30u}) { + config[0]=ddi;config[1]=ddi?ddi+6:1; + AlderLakeS::Port route;AlderLakeS::PLL pll;assert(AlderLakeS::Port::resolve(ddi,route));assert(AlderLakeS::PLL::resolve(route.phy,pll)); fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref;at(port,0x128)=rate; - mmio={{0x46018,0},{0x164294,0},{0x164298,0},{0x164280,0x01e07400}}; + mmio={{pll.enable,0},{pll.cfg0,0},{pll.cfg1,0},{0x164280,0x01e07400}}; enable(ctl,port,nullptr,nullptr); - assert(!errors&&!delays && config[0]==4 && mmio[0x46018]==0xcc000000); - assert(mmio[0x164280]==((0x01e07400&~0x1000030U)|0x20)); + assert(!errors&&!delays && config[0]==ddi && mmio[pll.enable]==0xcc000000); + assert(mmio[0x164280]==((0x01e07400&~(route.clockOff|(3U<(request,0x1c)==result); + assert(result==(pin<13?0x12345678u:0xe00002c2u));assert(at(request,0x1c)==result); } at(request,0x38)=0x200;assert(bus(ctl,9,3,request)==0xe00002c2); munmap(image,0x400000); - printf("PASS %u real donor Combo PLL enable/disable cases: physical TC1 preserved, PHY B selection, reference fraction, voltage/power order; 2 bounded timeout paths. MMIO and power hardware mocked.\n",cases); + printf("PASS %u real donor Combo PLL enable/disable cases: physical A/TC1/TC2 preserved, PHY A/B/C selection, reference fraction, voltage/power order; 2 bounded timeout paths. MMIO and power hardware mocked.\n",cases); } diff --git a/tests/adls_donor.py b/tests/adls_donor.py index cce18e6a..872a8022 100644 --- a/tests/adls_donor.py +++ b/tests/adls_donor.py @@ -23,28 +23,38 @@ assert donor[0x5f611:0x5f618]==bytes.fromhex('41c78648060000') cfields[0x648]=struct.unpack_from(' #include int main() { - const uint32_t cphy[]={CPHY};const uint32_t phy[]={PHY};const uint32_t ports[]={PORTS}; + const uint32_t aphy[]={APHY};const uint32_t cphy[]={CPHY};const uint32_t phy[]={PHY};const uint32_t ports[]={PORTS}; uint32_t registers[0x128/4] {};registers[4]=0x64300;registers[(0x64c-0x580)/4]=0x4610c; - assert(AlderLakeS::comboBRegisters(registers,sizeof(registers)/4)); + assert(AlderLakeS::comboRegisters(3,registers,sizeof(registers)/4)); assert(!memcmp(registers+(0x5ac-0x580)/4,phy,sizeof(phy))); memset(registers,0,sizeof(registers));registers[4]=0x64400;registers[(0x64c-0x580)/4]=0x46110; - assert(AlderLakeS::comboCRegisters(registers,sizeof(registers)/4)); + assert(AlderLakeS::comboRegisters(4,registers,sizeof(registers)/4)); assert(!memcmp(registers+(0x5ac-0x580)/4,cphy,sizeof(cphy))); + memcpy(registers,aphy,sizeof(aphy)); + assert(AlderLakeS::comboRegisters(0,registers,sizeof(registers)/4)); + assert(registers[(0x640-0x580)/4]==0x162A24); // donor duplicated lane3; restore lane2 alignas(8) uint8_t platform[0x1c0] {};platform[0x12]=9; memcpy(platform+0x20,ports,sizeof(ports)); XELP::VBT::Ports firmware;firmware.status=XELP::VBT::Status::Valid; firmware.present=8;firmware.port[3]={0x60d2,1,2}; - const auto wiring=AlderLakeS::HDMI::fromFirmware(firmware); + const auto wiring=AlderLakeS::Wiring::fromFirmware(firmware); assert(wiring.apply(platform,sizeof(platform))); assert(wiring.matches(reinterpret_cast(platform+0x68))); - puts("PASS exact donor Combo B register table and physical TC1 connector adaptation"); -}'''.replace('CPHY',','.join(map(str,cphy))).replace('PHY',','.join(map(str,phy))).replace('PORTS',','.join(map(str,ports))) + puts("PASS exact donor Combo A/B/C register tables and firmware connector adaptation"); +}'''.replace('APHY',','.join(map(str,aphy))).replace('CPHY',','.join(map(str,cphy))).replace('PHY',','.join(map(str,phy))).replace('PORTS',','.join(map(str,ports))) with tempfile.TemporaryDirectory(prefix='adls-donor-') as tmp: tmp=Path(tmp);(tmp/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(repo/'WhateverXe'), diff --git a/tests/adls_lane_power.py b/tests/adls_lane_power.py index 6a3cf360..e898eb44 100644 --- a/tests/adls_lane_power.py +++ b/tests/adls_lane_power.py @@ -12,7 +12,7 @@ #define FunctionCast(fn,p) reinterpret_cast(p) struct XE { bool adlsDetected=true,adlsPowerReady=true;void *tglObservationController; - bool adlsDPWiring=false;AlderLakeS::HDMI adlsHDMIWiring;uintptr_t orgADLSPowerPG,orgTGLPowerDDIState; + 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); @@ -27,16 +27,16 @@ FUNCTIONS int main(){ alignas(8) uint8_t ctl[0xF00]{},power[0x80]{},access[0x70]{}; - state.tglObservationController=ctl;state.adlsHDMIWiring.valid=true;state.adlsHDMIWiring.selected=8; + state.tglObservationController=ctl;state.adlsWiring.valid=true;state.adlsWiring.hdmi=8; state.orgADLSPowerPG=reinterpret_cast(pg);state.orgTGLPowerDDIState=reinterpret_cast(ddi); at(power,0x10)=ctl;at(ctl,0xC8)=power;at(power,0x78)=access;at(ctl,0xC40)=access; assert(XE::adlsPowerTarget(power)==power);assert(!XE::adlsPowerTarget(nullptr));unsigned count=0; for(bool dp:{false,true})for(unsigned activeDP:{0U,1U,7U})for(unsigned active:{0U,1U,7U})for(unsigned mask=0;mask<256;++mask)for(bool on:{false,true}){ - state.adlsDPWiring=dp;at(power,0x40)=activeDP;at(power,0x3C)=active;calls.clear();XE::wrapADLSPowerPG(power,on,mask); + state.adlsWiring.dp=dp?16:0;at(power,0x40)=activeDP;at(power,0x3C)=active;calls.clear();XE::wrapADLSPowerPG(power,on,mask); assert(calls.size()==1&&calls[0].target==(mask|((active || (dp && activeDP))?0x2A:0))&&calls[0].enable==on); assert(at(power,0x3C)==active);++count; } - state.adlsDPWiring=true; + state.adlsWiring.valid=true;state.adlsWiring.dp=16;state.adlsWiring.pin[4]=10; for(unsigned bits=0;bits<16;++bits)for(unsigned tc:{0U,0x80U,0x200U,0x280U}){ unsigned expected=0;for(unsigned i=0;i<4;++i){at(power,0x20+4*i)=(bits>>i)&1;if((bits>>i)&1)expected|=2U<<(2*i);} uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power));calls.clear();XE::wrapTGLPowerDDIState(power,false,tc); @@ -44,9 +44,18 @@ assert(calls[tc?0:1].enable==bool(tc));assert(calls[tc?1:0].kind==1&&calls[tc?1:0].target==tc); assert(!memcmp(before,power,sizeof(power)));++count; } + // A DP-only route on TC1 must own the same parents as HDMI there. + // Unwired consumers and physical A must not acquire TC lane parents. + for(unsigned selected:{1u,8u,16u,9u,17u,24u,25u})for(unsigned live=0;live<32;++live) { + state.adlsWiring.hdmi=0;state.adlsWiring.dp=selected;unsigned expected=0; + for(unsigned d=0;d<5;++d){at(power,0x30+4*d)=(live>>d)&1; + if(d>=3 && (selected&live&(1U<(ctl,0xC8)=bad==4?nullptr:power;at(power,0x78)=bad==5?nullptr:access; + state.adlsDetected=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); } diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp index f5fa0b7a..5a2eccb5 100644 --- a/tests/alderlake_s.cpp +++ b/tests/alderlake_s.cpp @@ -28,45 +28,67 @@ int main() { Port port;assert(!Port::resolve(d,port) && !port.buffer && !port.hpd); } for(auto pin:{0U,6U,7U,8U,9U,UINT32_MAX})assert(!gmbus(pin)); - // Recorded firmware topology: USB-C A, HDMI TC1, DP TC2, unwired TC3. - XELP::VBT::Ports firmware;firmware.status=XELP::VBT::Status::Valid;firmware.present=0x39; - firmware.port[0]={0x60d6,10,1,0x40,0xc0,0}; - firmware.port[3]={0x60d2,1,2,0,0xc0,0}; - firmware.port[4]={0x60d6,8,3,0x20,0xc0,0}; - firmware.port[5]={0x68c6,9,0,0x30,0xc0,0}; - assert(DP::qualified(firmware)); - const uint32_t dpConfig[]={4,10,1,0,0x400,0x201}; - assert(DP::hotplugMode(true,dpConfig,dpConfig,0x100,0x100,4)==1); - assert(DP::hotplugMode(false,dpConfig,dpConfig,0x100,0x100,4)==4); - assert(DP::hotplugMode(true,dpConfig,nullptr,0x100,0x100,4)==4); - assert(DP::hotplugMode(true,dpConfig,dpConfig,0x101,0x100,4)==4); - assert(DP::hotplugMode(true,dpConfig,dpConfig,0,0,4)==4); - for(unsigned m:{0u,1u,2u,3u})assert(DP::hotplugMode(true,dpConfig,dpConfig,0x100,0x100,m)==m); - for(unsigned n=0;n<6;++n){uint32_t bad[6];memcpy(bad,dpConfig,sizeof(bad));bad[n]^=1; - assert(DP::hotplugMode(true,bad,bad,0x100,0x100,4)==4);} - - for(unsigned i=0;i<6;++i){auto bad=firmware;reinterpret_cast(&bad.port[4])[i]^=1;assert(!DP::qualified(bad));} - auto dupe=firmware;dupe.port[5].aux=0x20;assert(!DP::qualified(dupe)); - auto h=HDMI::fromFirmware(firmware);assert(h.valid && h.selected==8 && h.pin[3]==9); - alignas(8) uint8_t platform[0x1C0] {};platform[0x12]=9; - for(unsigned d=0;d<9;++d) { - const uint32_t c[]={d,d>=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; - memcpy(platform+0x20+24*d,c,sizeof(c)); + // All connector assignments over the supported physical coordinates, + // including DP-only, HDMI-only, mixed and multiple fixed DP routes. + for(unsigned assignment=1;assignment<27;++assignment) { + XELP::VBT::Ports fw;fw.status=XELP::VBT::Status::Valid; + unsigned code=assignment,hdmi=0,dp=0; + for(unsigned n=0;n<3;++n) { + const unsigned kind=code%3;code/=3;if(!kind)continue; + const unsigned d=physical[n];Port route;assert(Port::resolve(d,route)); + fw.present|=1U<=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; + memcpy(platform+0x20+24*d,c,sizeof(c)); + } + uint8_t saved[sizeof(platform)];memcpy(saved,platform,sizeof(saved)); + const unsigned last=fw.present&16?4:fw.present&8?3:0; + platform[0x20+24*last+16]^=1; + uint8_t bad[sizeof(platform)];memcpy(bad,platform,sizeof(bad)); + assert(!w.apply(platform,sizeof(platform)) && !memcmp(platform,bad,sizeof(bad))); + memcpy(platform,saved,sizeof(saved));assert(w.apply(platform,sizeof(platform))); + for(unsigned d=0;d<9;++d)if(fw.present&(1U<(platform+0x20+24*d); + assert(w.matches(c) && w.bound(platform,c)); + const bool isDP=dp&(1U<(platform+0x80))); - assert(!DP::apply(platform,sizeof(platform))); - reinterpret_cast(platform+0x80)[3]=1; - uint8_t before[sizeof(platform)];memcpy(before,platform,sizeof(before)); - assert(h.apply(platform,sizeof(platform))); - assert(h.matches(reinterpret_cast(platform+0x68)) && h.usesDDC(9) && !h.usesDDC(2)); - assert(!memcmp(platform,before,0x68) && !memcmp(platform+0x80,before+0x80,sizeof(platform)-0x80)); - memcpy(platform,before,sizeof(platform));platform[0x68+12]=0; - memcpy(before,platform,sizeof(before));assert(!h.apply(platform,sizeof(platform))); - assert(!memcmp(before,platform,sizeof(platform))); // no partial mutation on a wrong donor - auto bad=firmware;bad.port[3].ddc=0;assert(!HDMI::fromFirmware(bad).valid); - bad=firmware;bad.port[4]=firmware.port[3];assert(!HDMI::fromFirmware(bad).valid); - bad=firmware;bad.port[3].capabilities=1;h=HDMI::fromFirmware(bad);assert(h.valid && !h.selected); - assert(!HDMI::fromFirmware({}).valid); + XELP::VBT::Ports fw;fw.status=XELP::VBT::Status::Valid;fw.present=8; + fw.port[3]={0x60D6,7,0,0x10,0xC0,0}; + assert(Wiring::fromFirmware(fw).dp==8); // DP does not require GMBUS or HDMI + for(unsigned bad=0;bad<8;++bad) { + auto f=fw; + if(bad==0)f.port[3].aux=0x20; + if(bad==1)f.port[3].capabilities=1; + if(bad==2)f.port[3].capabilities=2; + if(bad==3)f.port[3].flags|=1; + if(bad==4)f.port[3].type|=0x1000; + if(bad==5)f.port[3].dvo=8; + if(bad==6){f.present|=16;f.port[4]=f.port[3];} + if(bad==7)f.status=XELP::VBT::Status::Conflict; + assert(!Wiring::fromFirmware(f).dp); + } + fw.port[3]={0x60D2,1,5,0,0xC0,0};auto w=Wiring::fromFirmware(fw); + assert(w.hdmi==8 && w.pin[3]==12 && w.usesDDC(12) && !w.usesDDC(9)); + fw.present|=16;fw.port[4]={0x60D2,2,5,0,0xC0,0};w=Wiring::fromFirmware(fw); + assert(!w.selected() && w.excluded==24); // both ambiguous owners excluded + fw={};fw.status=XELP::VBT::Status::Valid;fw.present=0x60; + fw.port[5]={0x60D6,9,0,0x30,0xC0,0};fw.port[6]={0x60D2,4,5}; + w=Wiring::fromFirmware(fw);assert(w.valid && !w.selected() && w.excluded==0x60); // Physical TC1 still uses Combo B's clock selector, not DKL PLL1. Port hdmi;assert(Port::resolve(3,hdmi)); assert(hdmi.clock==0x164280 && hdmi.clockShift==2 && hdmi.clockOff==0x800); @@ -85,13 +107,13 @@ int main() { assert(!PLL::resolve(4,pll) && !pll.enable); uint32_t registers[0x128/4] {};registers[4]=0x64300;registers[(0x64C-0x580)/4]=0x4610C; uint32_t saved[0x128/4];memcpy(saved,registers,sizeof(saved)); - assert(comboBRegisters(registers,sizeof(registers)/4)); + assert(comboRegisters(3,registers,sizeof(registers)/4)); assert(registers[(0x5AC-0x580)/4]==0x64C04 && registers[(0x5B8-0x580)/4]==0x6C10C); assert(registers[(0x648-0x580)/4]==0x6C324 && registers[4]==0x64300); assert(!memcmp(registers,saved,0x5AC-0x580)); assert(!memcmp(registers+(0x64C-0x580)/4,saved+(0x64C-0x580)/4,0x6A8-0x64C)); - assert(!comboBRegisters(registers,sizeof(registers)/4)); // no repeated or foreign table + assert(!comboRegisters(3,registers,sizeof(registers)/4)); // no repeated or foreign table memcpy(registers,saved,sizeof(saved));registers[4]=0x64400; - assert(!comboBRegisters(registers,sizeof(registers)/4)); + assert(!comboRegisters(3,registers,sizeof(registers)/4)); puts("PASS ADL-S physical DDI/Combo PHY/HPD/PLL/DDC coordinates and ambiguous HDMI rejection"); } diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index f047affc..3facc692 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -65,7 +65,7 @@ int main(int argc,char **argv) { IOPCIDevice foreignPCI;foreignPCI.identity=pci.identity;foreignPCI.revision=pci.revision;foreignPCI.property=pci.property; assert(!xelpSelectedDevice(&foreignPCI)); config.combo=3;config.comboBackend=adln?3:7;config.legacy=adln?0:0x1F8;config.legacyBackend=adln?0:0x1F8; - if(adls){config.combo=config.comboBackend=8;config.legacy=config.legacyBackend=0;} + if(adls){config.combo=8;config.comboBackend=0x19;config.legacy=config.legacyBackend=0;} if(adls && !rejected){assert(hardwareAllowed(&controller));assert(!tglProbePort(&port));} // The contract derives generation from physical identity, not boot args. assert(!xelpConfigurePortProbe(&controller,2,3,3)); diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index cfb7c79f..46bacdad 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -12,7 +12,7 @@ #include #include struct Functions {bool ready=true;void **controllerSlot=nullptr;void(*getAux)(void*)=nullptr;void(*putAux)(void*)=nullptr;bool complete(){return ready;}}; -struct State {bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +struct State {bool adlsDetected=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; @@ -21,13 +21,13 @@ void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} FUNCTION int main(){ - for(bool adls:{false,true})for(unsigned bad=0;bad<15;++bad){ + for(unsigned testDDI:{0u,3u,4u})for(bool adls:{false,true})for(unsigned bad=0;bad<15;++bad){ alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t local[6]{};uint32_t *config=local; - unsigned ddi=adls?4:0,mask=3U<<(2*ddi),buffer=0x64000+256*ddi; + unsigned ddi=adls?testDDI:0,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.adlsDetected=state.adlsDPWiring=true;state.xeProfile.family=XELP::Family::AlderLakeS; - config=reinterpret_cast(state.tglPlatformCopy+0x80);const uint32_t c[]={4,10,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); + state.adlsDetected=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)); } state.tglObservationController=ctl;void *global=ctl; state.tglTCNativeFunctions.controllerSlot=&global; @@ -43,7 +43,7 @@ if(bad==2)state.tglComboAUXLifetimeReady=false; if(bad==3)state.tglTCNativeFunctions.ready=false; if(bad==4)global=nullptr; - if(bad==5)config[0]=3; + if(bad==5)config[0]=2; if(bad==6)config[2]=2; if(bad==7)state.firmwarePorts.present=0; if(bad==8)store(power,0x78,ctl+0); diff --git a/tests/tgl_combo_signal.cpp b/tests/tgl_combo_signal.cpp index 7af95611..c717b0c6 100644 --- a/tests/tgl_combo_signal.cpp +++ b/tests/tgl_combo_signal.cpp @@ -35,15 +35,16 @@ int main() { // Missing bank declines before the first write. writes.clear();regs[s.base+0xA9C]=~0U;assert(!s.program(read,write)&&writes.empty()); } - for(unsigned lanes:{0u,1u,2u,3u,4u})for(unsigned rate:{0u,6u,10u,12u,20u,30u}) + for(unsigned ddi:{0u,3u,4u})for(unsigned lanes:{0u,1u,2u,3u,4u})for(unsigned rate:{0u,6u,10u,12u,20u,30u}) for(unsigned v=0;v<5;++v)for(unsigned e=0;e<5;++e) { TGLComboSignal s;bool ok=(lanes==1||lanes==2||lanes==4)&&(rate==6||rate==10||rate==20||rate==30)&&v<4&&e<4&&v+e<=3; - assert(s.selectADLS(lanes,rate,v,e)==ok);if(!ok)continue; - assert(s.base==0x160000&&!s.low); + assert(s.selectADLS(ddi,lanes,rate,v,e)==ok);if(!ok)continue; + AlderLakeS::Port route;assert(AlderLakeS::Port::resolve(ddi,route)); + assert(s.base==route.phyBase()&&!s.low); std::map regs; assert(s.program([&](unsigned r){return regs[r];},[&](unsigned r,unsigned value){ - assert(r>=0x160000&&r<0x160BA0); - if(r==0x160694)for(unsigned n=0;n<4;++n)regs[0x160894+0x100*n]=value; + assert(r>=s.base&&r #include struct State { - bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr;bool tglActive=true,tglComboSignalReady=true; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts; @@ -27,6 +27,7 @@ std::map registers;unsigned reads=0,writes=0,records=0; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return registers[reg];} void wrapTGLRegisterWrite(void *,unsigned long reg,uint32_t value){++writes; + if(reg==0x6C694)for(unsigned n=0;n<4;++n)registers[0x6C894+0x100*n]=value; if(reg==0x160694)for(unsigned n=0;n<4;++n)registers[0x160894+0x100*n]=value; if(reg==0x162694)for(unsigned n=0;n<4;++n)registers[0x162894+0x100*n]=value; registers[reg]=value; @@ -69,26 +70,27 @@ if(bad==19)assert(result==kIOReturnNotReady); if(bad!=0&&bad!=17&&bad!=18&&bad!=19)assert(reads==0); } - for(unsigned bad=0;bad<12;++bad) { + for(unsigned ddi:{0u,3u,4u})for(unsigned bad=0;bad<12;++bad) { alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; - state={};state.adlsDetected=state.adlsDPWiring=true;state.xeProfile.family=XELP::Family::AlderLakeS; + state={};state.adlsDetected=true;state.adlsWiring.valid=true;state.adlsWiring.dp=1U<(state.tglPlatformCopy+0x80);memcpy(config,c,sizeof(c)); + const uint32_t c[]={ddi,ddi?ddi+6:1,1,0,0x400,0x201};auto config=reinterpret_cast(state.tglPlatformCopy+0x20+24*ddi);memcpy(config,c,sizeof(c)); store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); - store(p,0x124,4U);store(p,0x128,20U);store(p,0x620,0x160804U);store(p,0x5C8,0x160028U); + store(p,0x124,4U);store(p,0x128,20U);store(p,0x620,base+0x804);store(p,0x5C8,base+0x28); store(access,0x60,0x200000U);store(access,0x50,ctl+0); - registers.clear();registers[0x45404]=0x3f;registers[0x45454]=0x300;reads=writes=records=0; - if(bad==1)state.adlsDPWiring=false; + registers.clear();registers[0x45404]=0x3f;registers[0x45454]=3U<<(2*ddi);reads=writes=records=0; + if(bad==1)state.adlsWiring.dp=0; if(bad==2)store(p,0x548,c+0); if(bad==3)store(p,0x550,4U); if(bad==4)store(p,0x128,12U); - if(bad==5)registers[0x45404]=3; - if(bad==6)registers[0x45454]=3; - if(bad==7)store(p,0x620,0x6c804U); + if(bad==5)registers[0x45404]=0; + if(bad==6)registers[0x45454]=0; + if(bad==7)store(p,0x620,base+0x904); if(bad>=8)config[bad-8]^=1; uint32_t result=99;assert(run(p,0,0,nullptr,result)==(bad==0)); - assert((writes!=0)==(bad==0));if(!bad)assert(!result && records==1 && registers[0x16089c]==0x35000000); + assert((writes!=0)==(bad==0));if(!bad)assert(!result && records==1 && registers[base+0x89c]==0x35000000); } puts("PASS production combo guard: family, port, firmware, ownership, power and missing bank"); } diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index 1196062c..2d1b68d8 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -28,7 +28,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { - bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; static void tglRecordStartOrder(unsigned,void*,unsigned=9,uint64_t=0,uint64_t=0,uint64_t=0) {} static void tglRecordNativeModeBinding(void *,uint32_t,uint32_t) {} @@ -104,14 +104,16 @@ int main(int argc,char **argv){ assert(XE::wrapTGLSetPortMode(port,3)==kIOReturnNotReady && !reads && !writes && !powerGets); coldReady=true;instance.tglDPAltCandidates=0; // Qualified ADL-S HPD mode4 must execute native Combo mode1, without FIA. - reset(4);instance.adlsDetected=instance.adlsDPWiring=true; - auto bound=reinterpret_cast(instance.tglPlatformCopy+0x80); - const uint32_t fixed[]={4,10,1,0,0x400,0x201};memcpy(bound,fixed,sizeof(fixed)); + for(unsigned ddi:{0u,3u,4u}) { + reset(ddi);instance.adlsDetected=true;instance.adlsWiring.valid=true;instance.adlsWiring.dp=1U<(instance.tglPlatformCopy+0x20+24*ddi); + const uint32_t fixed[]={ddi,ddi?ddi+6:1,1,0,0x400,0x201};memcpy(bound,fixed,sizeof(fixed)); *reinterpret_cast(port+0x548)=bound; instance.tglNativeLegacyHPDCaller=testCaller=0x74a59; assert(XE::wrapTGLSetPortMode(port,4)==0 && mode()==1); assert(!reads && !writes && !powerGets && !powerPuts && !sleeps); - instance.adlsDetected=instance.adlsDPWiring=false;testCaller=0;instance.tglNativeLegacyHPDCaller=0; + } + instance.adlsDetected=false;instance.adlsWiring={};testCaller=0;instance.tglNativeLegacyHPDCaller=0; *reinterpret_cast(port+0x548)=config; unsigned cases=0; for(bool skipPower:{false,true})for(unsigned d=3;d<=8;++d)for(unsigned requested:{3u,4u})for(bool isReady:{false,true}){ diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index 7cc800bc..b2102f98 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -27,7 +27,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { - bool adlsDetected=false,adlsDPWiring=false;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + bool adlsDetected=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; // LEGACY_HPD_POLICY diff --git a/tests/tgl_port_map_fixture.cpp b/tests/tgl_port_map_fixture.cpp index a9157188..bb71fd78 100644 --- a/tests/tgl_port_map_fixture.cpp +++ b/tests/tgl_port_map_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include #include @@ -17,7 +18,7 @@ struct IORegistryEntry { void *getProperty(const char *) { return nullptr; } }; struct XE { - bool adlsDetected=false,adlsDPWiring=false; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; // FIELDS IOSimpleLock *tglDekelLock; uintptr_t orgTGLReadDekel,orgTGLWriteDekel; diff --git a/tests/tgl_probe_fixture.cpp b/tests/tgl_probe_fixture.cpp index f81aa99f..a5ae2825 100644 --- a/tests/tgl_probe_fixture.cpp +++ b/tests/tgl_probe_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include #include @@ -30,7 +31,7 @@ void tglAUXRecord(void *,uint32_t,bool,uint32_t,uint32_t,uint32_t) {} uint32_t providerProbe(void *); uint32_t nativeHPD(void *) {return 1;} struct XE { - bool adlsDetected=false,adlsDPWiring=false; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; uint32_t tglModularProbed=0; uint64_t tglPlatformCopy[0x1c0/8] {}; bool tglActive=true; diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index 25898c8d..fa588b75 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" templatevoid tglTransitionRecord(T...){ } // Native boundaries are mocks; LEASE_FUNCTIONS is extracted unchanged from production. #include @@ -44,7 +45,7 @@ template void tglObserveResource(T...) {} void tglAUXPublish() {} void tglDPCDPublish() {} struct XE { - bool adlsDetected=false,adlsDPWiring=false; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; // TC_WIRING_FIELDS // LEASE_FIELDS IORecursiveLock *tglPathPoolLock; @@ -441,7 +442,7 @@ int main(int argc, char **argv) { ports[3].at(0x550)=2; assert(XE::tglPrepareTrainingLease(ctl.data(),obj.tglTrainingLeases[3])==1); // no TBT // ADL-S fixed Combo C uses physical TC2 in mode1 and the same pool. - setup();obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=obj.adlsDPWiring=true;obj.tglAUXParentReady=true; + setup();obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=true;obj.adlsWiring.valid=true;obj.adlsWiring.dp=16;obj.adlsWiring.pin[4]=10;obj.tglAUXParentReady=true; assert(XE::tglTrainingLeaseForDDI(4)&&!XE::tglTrainingLeaseForDDI(3)); assert(!obj.tglLegacyTCEligible(4)&&obj.tglAUXParentEligible(4)); ports[4].at(0x550)=1;reserve(4);release(4);clean(); diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index 81518a3c..01bccebb 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include #include @@ -23,7 +24,7 @@ void clock_get_uptime(uint64_t *p){*p=1;} void absolutetime_to_nanoseconds(uint64_t t,uint64_t *p){*p=t;} unsigned observations,nativeCalls; struct XE { - bool adlsDetected=false,adlsDPWiring=false; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; // FIELDS bool tglActive=true,tglSinkFramingReady=true;void *tglObservationController{};uintptr_t orgTGLTrainingPattern{}; static uint32_t wrapTGLTrainingPattern(void *,uint32_t); diff --git a/tests/tgl_wiring_fixture.cpp b/tests/tgl_wiring_fixture.cpp index da62bca0..7091ee58 100644 --- a/tests/tgl_wiring_fixture.cpp +++ b/tests/tgl_wiring_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include #include @@ -20,7 +21,7 @@ struct IORegistryEntry { #define OSDynamicCast(type,p) dynamic_cast(p) #define FunctionCast(f,p) reinterpret_cast(p) struct XE { - bool adlsDetected=false,adlsDPWiring=false; + bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; bool tglActive=true; XELP::Profile xeProfile=XELP::identify(0x9a498086,1);XELP::VBT::Ports firmwarePorts; // FIELDS diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 83845092..05a0e883 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -51,17 +51,17 @@ int main() { const uint32_t v[]={d,d+1,2,0,0x800,0x202};__builtin_memcpy(r,v,sizeof(v)); assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d<2?1U:0U));} assert(io.reads.empty());auto other=b;other.access=3;assert(!b.sameBinding(other));assert(b.sameBinding(b)); - c={};c.profile=identify(0x46908086,12);c.combo=c.comboBackend=8;b.config=c;assert(c.valid()); + c={};c.profile=identify(0x46908086,12);c.combo=8;c.comboBackend=0x19;b.config=c;assert(c.valid()); for(unsigned d=0;d<9;++d){auto r=reinterpret_cast(platform+0x20+24*d); const uint32_t v[]={d,9,2,0,0x800,0x202};memcpy(r,v,sizeof(v)); assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d==3?1U:0U));} - c.combo=c.comboBackend=24;b.config=c;assert(c.valid()); + c.combo=24;c.comboBackend=0x19;b.config=c;assert(c.valid()); auto dp=reinterpret_cast(platform+0x80);const uint32_t dpc[]={4,10,1,0,0x400,0x201};memcpy(dp,dpc,sizeof(dpc)); assert(probeMode(io,b,2,reinterpret_cast(dp),dp)==1); for(unsigned i=0;i<6;++i){dp[i]^=1;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp));dp[i]^=1;} - c.combo=c.comboBackend=8;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp)); + c.combo=8;c.comboBackend=0x19;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp)); assert(io.reads.empty()); // ADL-S must never probe TGL FIA for its Combo TC1. - for(unsigned bad=0;bad<5;++bad){auto x=c;if(bad==0)x.combo=1;if(bad==1)x.comboBackend=0x79; + for(unsigned bad=0;bad<5;++bad){auto x=c;if(bad==0)x.combo=2;if(bad==1)x.comboBackend=0x79; if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=0;assert(!x.valid());} assert(!identify(0x46D18086,12,0x46908086).admitted()); assert(!identify(0x46908086,12,0x46808086).admitted()); From 47e533f5845b9ee53fb730cdd1d0521e17a0e645 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:04:52 +0900 Subject: [PATCH 09/58] Hold display power for VBT-routed Alder Lake-S AUX A --- README.md | 5 ++- README_ja.md | 3 +- WhateverXe/kern_xe.cpp | 9 +++-- WhateverXe/kern_xe.hpp | 5 ++- tests/adls_aux_parent.py | 82 ++++++++++++++++++++++++++++++++++++++++ tools/test.py | 2 +- 6 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 tests/adls_aux_parent.py diff --git a/README.md b/README.md index a5c7f5e9..c575f0fb 100644 --- a/README.md +++ b/README.md @@ -174,8 +174,9 @@ These results cover tested systems, not every GPU, display, adapter or dock. PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability and PHY D/E are excluded. GPU admission remains limited to `4690` revision `0C` with the qualified fuse configuration. Other port layouts have CPU-test - coverage but are not hardware-qualified. USB-C, sleep/wake and other macOS - versions remain unqualified. + coverage but are not hardware-qualified. A USB-C connector exposed by VBT + as fixed DP on Combo PHY A has also been tested. This does not qualify + Type-C/TBT-managed routes. Sleep/wake and other macOS versions remain unqualified. - Display 13 USB-C and eDP support ### Not Planned diff --git a/README_ja.md b/README_ja.md index 00e8846b..86b7126f 100644 --- a/README_ja.md +++ b/README_ja.md @@ -126,7 +126,8 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 GPUの適用対象は、確認済みのfuse構成を持つ `4690` revision `0C` に限定しています。 別のポート配置はCPUテストで確認していますが、実機では未検証です。 - USB-C、消灯/復帰、他のmacOSは未検証です。 + VBT上でCombo PHY Aの固定DPとして扱われるUSB-C端子でも実表示を確認しています。 + これはType-C/TBT管理経路の動作確認ではありません。消灯/復帰、他のmacOSは未検証です。 - Display 13のUSB-C / eDP対応 ### Not Planned diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 70bde3fe..fe69b31e 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -14114,8 +14114,9 @@ static bool tglPcodeGateSerialize(void *,void *,OSSerialize *serializer) { const bool result=out->serialize(serializer);out->release();return result; } -// TGL TC AUX depends on PW1/2/3, independently of the AUX-specific well. -// Donor PG argument 3 acquires refs 1..3; pair every AUX reference with it. +// AUX wells also need their parent display power: PW1 for ADL-S DDI A, +// PW1/2/3 for TC AUX. Donor PG argument N acquires refs 1..N. Pair every +// owned AUX reference with its physical parent, including after all pipes stop. // Extra refs are port-local; the donor power object/PG1..3 remain shared. // Preserve the donor caller-serialization contract. This bookkeeping is not // a lock for native power transitions or a claim of concurrent AUX support. @@ -14128,7 +14129,7 @@ void XE::wrapTGLEnableAUXPower(void *power, uint32_t ddi) { auto bytes = static_cast(power); if (hold) { tglAUXRecord(port, 5, false, 0, *reinterpret_cast(bytes + 0x28), *reinterpret_cast(bytes + 0x54 + 4 * ddi), self->tglAUXParentRefs[ddi]); - reinterpret_cast(self->tglEnablePG)(power, 3); + reinterpret_cast(self->tglEnablePG)(power, self->tglAUXParentPG(ddi)); tglAUXRecord(port,9,true,0,*reinterpret_cast(static_cast(power)+0x28), *reinterpret_cast(static_cast(power)+0x54+4*ddi),self->tglAUXParentRefs[ddi],1); ++self->tglAUXParentRefs[ddi]; @@ -14155,7 +14156,7 @@ void XE::wrapTGLDisableAUXPower(void *power, uint32_t ddi) { --self->tglAUXParentRefs[ddi]; tglAUXRecord(port,9,false,0,*reinterpret_cast(static_cast(power)+0x28), *reinterpret_cast(static_cast(power)+0x54+4*ddi),self->tglAUXParentRefs[ddi],0); - reinterpret_cast(self->tglDisablePG)(power, 3); + reinterpret_cast(self->tglDisablePG)(power, self->tglAUXParentPG(ddi)); } tglAUXRecord(port, 6, true, release ? 0 : 1, *reinterpret_cast(bytes + 0x28), *reinterpret_cast(bytes + 0x54 + 4 * ddi), self->tglAUXParentRefs[ddi]); } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 607216d8..38555e11 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -433,7 +433,10 @@ class XE { } uint32_t tglAUXParentRefs[9] {}; bool tglAUXParentEligible(uint32_t ddi) const { - return tglAUXParentReady && (tglLegacyTCEligible(ddi) || (adlsDetected && ddi>=3 && adlsWiring.hasDP(ddi))); + return tglAUXParentReady && (tglLegacyTCEligible(ddi) || (adlsDetected && adlsWiring.hasDP(ddi))); + } + uint32_t tglAUXParentPG(uint32_t ddi) const { + return adlsDetected && ddi==0 ? 1 : 3; } static void wrapTGLEnableAUXPower(void *power, uint32_t ddi); static void wrapTGLDisableAUXPower(void *power, uint32_t ddi); diff --git a/tests/adls_aux_parent.py b/tests/adls_aux_parent.py new file mode 100644 index 00000000..5186faa2 --- /dev/null +++ b/tests/adls_aux_parent.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Exercise production AUX parent wrappers with mixed ADL-S physical domains.""" +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() +fields=h[h.index('\tbool tglAUXParentReady'):h.index('\tstatic void wrapTGLEnableAUXPower')] +body='\n'.join(re.search(p,s,re.S).group() for p in [ + r'void XE::wrapTGLEnableAUXPower\(.*?\n\}',r'void XE::wrapTGLDisableAUXPower\(.*?\n\}']) +code=r''' +#include +#include +#include +#include +#include +#include +#include "kern_alderlake_s.hpp" +#include "kern_tgl_tc_boot.hpp" +struct IOService; +struct XE { + bool adlsDetected=true;AlderLakeS::Wiring adlsWiring; + FIELDS + uintptr_t tglPortByDDI,tglEnablePG,tglDisablePG,orgTGLEnableAUXPower,orgTGLDisableAUXPower; + static void wrapTGLEnableAUXPower(void*,uint32_t); + static void wrapTGLDisableAUXPower(void*,uint32_t); +}; +XE state,*callbackXE=&state; +#define FunctionCast(f,p) reinterpret_cast(p) +templatevoid tglAUXRecord(T...){} +BODY +alignas(8) uint8_t power[0x90],other[0x90]; +uint32_t &ref(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +std::vector calls; +void *port(unsigned d){assert(d<9);return reinterpret_cast(uintptr_t(d+1));} +void getPG(void *p,unsigned level){assert(level==1||level==3);calls.push_back(10+level);for(unsigned i=1;i<=level;++i)++ref(p,0x1c+4*i);} +void putPG(void *p,unsigned level){assert(level==1||level==3);calls.push_back(20+level);for(unsigned i=1;i<=level;++i){assert(ref(p,0x1c+4*i));--ref(p,0x1c+4*i);}} +void getAUX(void *p,unsigned d){calls.push_back(1);assert(ref(p,0x20));if(d>=3)assert(ref(p,0x28));++ref(p,0x54+4*d);} +void putAUX(void *p,unsigned d){calls.push_back(2);if(ref(p,0x54+4*d))--ref(p,0x54+4*d);} +void reset(unsigned mask){ + state={};state.adlsWiring.valid=true;state.adlsWiring.dp=mask;state.tglAUXParentReady=true; + state.tglPortByDDI=(uintptr_t)port;state.tglEnablePG=(uintptr_t)getPG;state.tglDisablePG=(uintptr_t)putPG; + state.orgTGLEnableAUXPower=(uintptr_t)getAUX;state.orgTGLDisableAUXPower=(uintptr_t)putAUX; + memset(power,0,sizeof(power));memset(other,0,sizeof(other));calls.clear(); +} +void check(){ + const auto tc=state.tglAUXParentRefs[3]+state.tglAUXParentRefs[4]; + assert(ref(power,0x20)==tc+state.tglAUXParentRefs[0]); + assert(ref(power,0x24)==tc&&ref(power,0x28)==tc); +} +int main(){ + unsigned cases=0; + for(unsigned mask:{1U,8U,16U,9U,17U,24U,25U}) { + std::vector order;for(unsigned d=0;d<5;++d)if(mask&(1U<{d?13U:11U,1}));check();} + } + for(auto d:release){ + // Cross-object release cannot spend the real owner's PG reference. + ref(other,0x54+4*d)=1;calls.clear();XE::wrapTGLDisableAUXPower(other,d); + assert(calls==std::vector{2});check(); + for(unsigned n=0;n<3;++n){calls.clear();XE::wrapTGLDisableAUXPower(power,d); + assert(calls==(n?std::vector{2,d?23U:21U}:std::vector{2}));check();} + } + assert(!ref(power,0x20));++cases; + }while(std::next_permutation(release.begin(),release.end()));}while(std::next_permutation(order.begin(),order.end())); + } + reset(0);assert(!state.tglAUXParentEligible(0)); + reset(1);state.adlsWiring.valid=false;assert(!state.tglAUXParentEligible(0)); + reset(1);state.adlsDetected=false;assert(!state.tglAUXParentEligible(0)); + reset(1);state.tglAUXParentReady=false;assert(!state.tglAUXParentEligible(0)); + reset(1);assert(!state.tglAUXParentEligible(9)&&!state.tglAUXParentEligible(~0U)); + printf("PASS %u mixed AUX A/TC parent orderings; nested/inherited refs; foreign owner; admission guards\n",cases); +} +'''.replace('FIELDS',fields).replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='adls-aux-parent-') 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/tools/test.py b/tools/test.py index 1a4c29bb..5922a7b6 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=['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=['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 += ['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 10ebd2de7b1a5300a552fbd924c7c0b217ddb496 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:06:32 +0900 Subject: [PATCH 10/58] Enable qualified Alder Lake-S on supported host macOS versions --- README.md | 11 ++++++----- README_ja.md | 6 +++--- WhateverXe/kern_xe.cpp | 4 ++-- WhateverXe/kern_xelp_os.hpp | 3 +++ tests/kernel_os_updates.cpp | 4 ++++ tests/tgl_kernel_admission.py | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c575f0fb..b2307281 100644 --- a/README.md +++ b/README.md @@ -166,17 +166,18 @@ These results cover tested systems, not every GPU, display, adapter or dock. ### Planned - Alder Lake and Raptor Lake S/H/P/U support. Development support for Alder Lake-S - `4690` currently covers Big Sur single-display HDMI or fixed DP with Metal and - OpenGL acceleration. It requires unreleased companion graphics changes and is - not supported by the published graphics pkg. Fixed DP reconnection has been - tested, but display recovery can be delayed. HDMI/fixed-DP routing is derived + `4690` has been tested from Big Sur through Tahoe with Metal and OpenGL + acceleration and three simultaneous displays (two 1080p60 and one 720p60). + It requires unreleased companion graphics changes and is not supported by the + published graphics pkg. Fixed DP reconnection has been tested on Big Sur, but + initial display output and reconnection can be delayed. HDMI/fixed-DP routing is derived from VBT rather than a board or connector preset, within the current Combo PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability and PHY D/E are excluded. GPU admission remains limited to `4690` revision `0C` with the qualified fuse configuration. Other port layouts have CPU-test coverage but are not hardware-qualified. A USB-C connector exposed by VBT as fixed DP on Combo PHY A has also been tested. This does not qualify - Type-C/TBT-managed routes. Sleep/wake and other macOS versions remain unqualified. + Type-C/TBT-managed routes. Sleep/wake remains unqualified. - Display 13 USB-C and eDP support ### Not Planned diff --git a/README_ja.md b/README_ja.md index 86b7126f..b3e2f2a9 100644 --- a/README_ja.md +++ b/README_ja.md @@ -119,15 +119,15 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 ### Planned - Alder Lake/Raptor LakeのS/H/P/U対応。開発版のAlder Lake-S `4690`では、 - Big SurのHDMIまたは固定DP単独出力とMetal/OpenGLアクセラレーションを確認しています。 + Big Sur〜TahoeでMetal/OpenGLアクセラレーションと3画面同時表示(1080p60×2+720p60×1)を確認しています。 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 - 固定DPの再接続は確認済みですが、表示復帰に時間がかかる場合があります。 + Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 GPUの適用対象は、確認済みのfuse構成を持つ `4690` revision `0C` に限定しています。 別のポート配置はCPUテストで確認していますが、実機では未検証です。 VBT上でCombo PHY Aの固定DPとして扱われるUSB-C端子でも実表示を確認しています。 - これはType-C/TBT管理経路の動作確認ではありません。消灯/復帰、他のmacOSは未検証です。 + これはType-C/TBT管理経路の動作確認ではありません。消灯/復帰は未検証です。 - Display 13のUSB-C / eDP対応 ### Not Planned diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index fe69b31e..5e7ecc0e 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3751,7 +3751,7 @@ void XE::selectProfile(const XELP::Profile &profile) { return; } tglRequested=profile.admitted() && (profile.family==XELP::Family::TigerLake || - (adlsDetected && XELP::kernelOS(version)==XELP::OS::BigSur)) && + (adlsDetected && XELP::hostOS(XELP::kernelOS(version)))) && !checkKernelArgument("-wex-tgl-off"); tglInactivePorts=tglPG1Poll=tglClockOrderRequested=tglTrainingLeaseRequested=tglRequested; tglProgramEnabled=!checkKernelArgument("-wex-tgl-prog-off"); @@ -4083,7 +4083,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { selectProfile(XELP::deviceProfile(physicalGPU)); if(xeProfile.family==XELP::Family::AlderLakeS && xeProfile.identity==0x46908086 && xeProfile.revision==0x0C && !xeProfile.overridden() && physicalGPU && - XELP::kernelOS(version)==XELP::OS::BigSur && (physicalGPU->configRead16(4)&2)) { + XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { // Gen12 forcewake/fuse coordinates are shared with the existing reader. // Its ADL-N profile verdict is not used to admit ADL-S. No engine, // GGTT, display, PLL or reset programming is performed here. diff --git a/WhateverXe/kern_xelp_os.hpp b/WhateverXe/kern_xelp_os.hpp index 63f17a46..ad85a66e 100644 --- a/WhateverXe/kern_xelp_os.hpp +++ b/WhateverXe/kern_xelp_os.hpp @@ -4,6 +4,9 @@ #define KERN_XELP_OS_HPP namespace XELP { enum class OS { Unknown, Donor, BigSur, Monterey, Ventura, Sonoma, Sequoia, Tahoe }; +inline bool hostOS(OS os) { + return os>=OS::BigSur && os<=OS::Tahoe; +} inline OS kernelOS(const char *version) { static const char *const versions[] = { "Darwin Kernel Version 20.0.0: Sat Jun 13 17:58:16 PDT 2020; root:xnu-7090.0.0.111.5~1/RELEASE_X86_64", diff --git a/tests/kernel_os_updates.cpp b/tests/kernel_os_updates.cpp index 7c464324..faf7d820 100644 --- a/tests/kernel_os_updates.cpp +++ b/tests/kernel_os_updates.cpp @@ -3,6 +3,10 @@ int main() { using namespace XELP; + assert(!hostOS(OS::Unknown) && !hostOS(OS::Donor)); + for (unsigned os=unsigned(OS::BigSur);os<=unsigned(OS::Tahoe);++os) + assert(hostOS(static_cast(os))); + assert(!hostOS(static_cast(unsigned(OS::Tahoe)+1))); assert(kernelOS(nullptr)==OS::Unknown); assert(kernelOS("")==OS::Unknown); assert(kernelOS("Darwin Kernel Version 24.6.1: update/RELEASE_X86_64")==OS::Sequoia); diff --git a/tests/tgl_kernel_admission.py b/tests/tgl_kernel_admission.py index b8618a5e..2e456e34 100644 --- a/tests/tgl_kernel_admission.py +++ b/tests/tgl_kernel_admission.py @@ -39,7 +39,7 @@ version=versions[k];noaccel=n;off=o;currentGraphics=currentFramebuffer=nullptr; adlsDetected=family==2; select({bool(physical),family==2?XELP::Family::AlderLakeS:family?XELP::Family::TigerLake:XELP::Family::Other}); -bool expected=physical&&family&&!o&&(k<7)&&(family!=2 || k==1); +bool expected=physical&&family&&!o&&(k<7)&&(family!=2 || k>=1); assert(tglRequested==expected);assert(tglAcceleratorRequested==(expected&&!n)); assert((currentFramebuffer==&kextIntelTGLFb)==expected);++cases; }printf("PASS: %u actual TGL admission cases, including Tahoe acceleration, Sequoia acceleration, Sonoma acceleration, Ventura acceleration, noaccel and unknown-kernel rejection\n",cases); From 85284fe31ee54879f15eba66ea557bcc1ac3ea89 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:40:48 +0900 Subject: [PATCH 11/58] Admit Safari App Cryptex GPU service to fixed Data driver access --- README.md | 3 +++ README_ja.md | 3 +++ WhateverXe/kern_xe_data_policy.hpp | 9 +++++++-- tests/xe_data_policy.cpp | 12 ++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2307281..a1e08bb9 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,9 @@ These results cover tested systems, not every GPU, display, adapter or dock. - Alder Lake and Raptor Lake S/H/P/U support. Development support for Alder Lake-S `4690` has been tested from Big Sur through Tahoe with Metal and OpenGL acceleration and three simultaneous displays (two 1080p60 and one 720p60). + With the revised VA assets, hardware H.264 encode/decode, HEVC decode, + Safari hardware video decoding and Widevine playback have also been tested + on Big Sur and Tahoe. Safari's App Cryptex WebKit layout is supported. It requires unreleased companion graphics changes and is not supported by the published graphics pkg. Fixed DP reconnection has been tested on Big Sur, but initial display output and reconnection can be delayed. HDMI/fixed-DP routing is derived diff --git a/README_ja.md b/README_ja.md index b3e2f2a9..fd1e62fc 100644 --- a/README_ja.md +++ b/README_ja.md @@ -120,6 +120,9 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 - Alder Lake/Raptor LakeのS/H/P/U対応。開発版のAlder Lake-S `4690`では、 Big Sur〜TahoeでMetal/OpenGLアクセラレーションと3画面同時表示(1080p60×2+720p60×1)を確認しています。 + 更新版VA資材との組み合わせでは、Big SurとTahoeでハードウェアH.264エンコード/デコード、 + HEVCデコード、Safariのハードウェア動画デコード、Widevine再生も確認しています。 + SafariのApp Cryptex内WebKit配置にも対応しています。 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 diff --git a/WhateverXe/kern_xe_data_policy.hpp b/WhateverXe/kern_xe_data_policy.hpp index 0fe3217f..93a385ff 100644 --- a/WhateverXe/kern_xe_data_policy.hpp +++ b/WhateverXe/kern_xe_data_policy.hpp @@ -6,8 +6,13 @@ namespace XeDataPolicy { enum class Client { None, Compiler, WebKitGPU }; inline bool service(const char *path, Client client = Client::Compiler) { if (!path || client == Client::None) return false; - // WebKit's executable vnode is on the OS Cryptex on newer systems. - // Accept only the two OS mount roots, followed by the exact service path. + // Safari updates can place WebKit in the App Cryptex instead of the OS. + // Admit the exact GPU service, never arbitrary staged frameworks or apps. + if (client == Client::WebKitGPU && !strcmp(path, + "/System/Volumes/Preboot/Cryptexes/App/System/Library/StagedFrameworks/Safari/" + "WebKit.framework/Versions/A/XPCServices/com.apple.WebKit.GPU.xpc/" + "Contents/MacOS/com.apple.WebKit.GPU")) return true; + // Preserve the two OS mount roots and their original exact service path. if (client == Client::WebKitGPU) { constexpr const char os[] = "/System/Volumes/Preboot/Cryptexes/OS"; constexpr const char incoming[] = "/System/Volumes/Preboot/Cryptexes/Incoming/OS"; diff --git a/tests/xe_data_policy.cpp b/tests/xe_data_policy.cpp index 2b0c58c2..6cac9d72 100644 --- a/tests/xe_data_policy.cpp +++ b/tests/xe_data_policy.cpp @@ -39,6 +39,18 @@ int main() { for (auto name : {"", "other", "TGLVA.old", "../Info.plist"}) assert(clientForFile((va+name).c_str(), true) == Client::None); assert(service(web, Client::WebKitGPU)); + const std::string safari = "/System/Volumes/Preboot/Cryptexes/App/System/Library/StagedFrameworks/Safari/" + "WebKit.framework/Versions/A/XPCServices/com.apple.WebKit.GPU.xpc/Contents/MacOS/com.apple.WebKit.GPU"; + assert(service(safari.c_str(), Client::WebKitGPU)); + assert(!service(safari.c_str(), Client::Compiler)); + assert(!service((safari+".old").c_str(), Client::WebKitGPU)); + assert(!service(("/System/Volumes/Data"+safari).c_str(), Client::WebKitGPU)); + auto other = safari; + other.replace(other.find("StagedFrameworks/Safari"), 23, "StagedFrameworks/Other"); + assert(!service(other.c_str(), Client::WebKitGPU)); + other = safari; + other.replace(other.rfind("com.apple.WebKit.GPU"), 20, "com.apple.WebKit.WebContent"); + assert(!service(other.c_str(), Client::WebKitGPU)); for (auto root : {"/System/Volumes/Preboot/Cryptexes/OS", "/System/Volumes/Preboot/Cryptexes/Incoming/OS"}) { assert(service((std::string(root)+web).c_str(), Client::WebKitGPU)); assert(!service((std::string(root)+web).c_str(), Client::Compiler)); From 66743b9872f7e3f77da8c718592bda35c819ff56 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:22:30 +0900 Subject: [PATCH 12/58] Identify WebKit GPU by kernel signing identity across supported macOS --- README.md | 6 ++- README_ja.md | 4 +- WhateverXe/kern_xe_data_access.cpp | 25 ++++++++++- WhateverXe/kern_xe_data_policy.hpp | 28 +++++------- tests/xe_data_access.py | 68 ++++++++++++++++++++++++++++++ tests/xe_data_policy.cpp | 34 ++++++--------- tools/test.py | 2 +- 7 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 tests/xe_data_access.py diff --git a/README.md b/README.md index a1e08bb9..ccfec36b 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,10 @@ its X-tiled scanout path is unsupported. Display 12 tiling selection is unchange These results cover tested systems, not every GPU, display, adapter or dock. +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. + ### Known issues and limits - Some image-conversion operations can crash the application. @@ -170,7 +174,7 @@ These results cover tested systems, not every GPU, display, adapter or dock. acceleration and three simultaneous displays (two 1080p60 and one 720p60). With the revised VA assets, hardware H.264 encode/decode, HEVC decode, Safari hardware video decoding and Widevine playback have also been tested - on Big Sur and Tahoe. Safari's App Cryptex WebKit layout is supported. + on Big Sur and Tahoe. It requires unreleased companion graphics changes and is not supported by the published graphics pkg. Fixed DP reconnection has been tested on Big Sur, but initial display output and reconnection can be delayed. HDMI/fixed-DP routing is derived diff --git a/README_ja.md b/README_ja.md index fd1e62fc..0dc617f8 100644 --- a/README_ja.md +++ b/README_ja.md @@ -106,6 +106,9 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 検証した機器での結果であり、すべてのGPU・ディスプレイ・アダプター・ドックでの動作を保証するものではありません。 +Big Sur〜Tahoeでは、有効なApple platform署名とサービス識別子でWebKit GPUを判定し、 +Safariのインストール先や更新による配置変更に依存せずData側GPUドライバへのアクセスを扱います。 + ### 既知の問題と制限 - 一部の画像変換処理で、アプリケーションがクラッシュすることがあります。 @@ -122,7 +125,6 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 Big Sur〜TahoeでMetal/OpenGLアクセラレーションと3画面同時表示(1080p60×2+720p60×1)を確認しています。 更新版VA資材との組み合わせでは、Big SurとTahoeでハードウェアH.264エンコード/デコード、 HEVCデコード、Safariのハードウェア動画デコード、Widevine再生も確認しています。 - SafariのApp Cryptex内WebKit配置にも対応しています。 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 diff --git a/WhateverXe/kern_xe_data_access.cpp b/WhateverXe/kern_xe_data_access.cpp index e1d3779f..e9d86ba7 100644 --- a/WhateverXe/kern_xe_data_access.cpp +++ b/WhateverXe/kern_xe_data_access.cpp @@ -14,6 +14,9 @@ void *(*executableVnode)(void *); void *(*fileVnode)(void *); int (*getPath)(void *, char *, int *); int (*putVnode)(void *); +const char *(*signingIdentity)(void *); +int (*platformBinary)(void *); +int (*signatureValid)(void *); using Open = int (*)(void *, void *, void *, int); using Map = int (*)(void *, void *, void *, int, int, uint64_t, int *); Open originalOpen; @@ -23,7 +26,7 @@ volatile SInt32 readGrants, mapGrants; bool knownOS() { const auto v = getKernelVersion(); - return v >= KernelVersion::Sonoma && v <= KernelVersion::Tahoe; + return v >= KernelVersion::BigSur && v <= KernelVersion::Tahoe; } bool allowed(void *vnode, bool executable) { if (!ready || !vnode) return false; @@ -34,7 +37,17 @@ bool allowed(void *vnode, bool executable) { const auto client = XeDataPolicy::clientForFile(path, executable); if (client == XeDataPolicy::Client::None) return false; auto process = currentProcess(); - auto image = process ? executableVnode(process) : nullptr; + if (!process) return false; + if (client == XeDataPolicy::Client::WebKitGPU) { + // Current process lifetime covers these kernel queries; do not cache by + // PID/path (exec and PID reuse would invalidate such a cache). + return signingIdentity && platformBinary && signatureValid && + XeDataPolicy::webKitGPU(signingIdentity(process), + platformBinary(process) != 0, signatureValid(process) != 0); + } + // The native compiler exception is only needed on Sonoma and later. + if (getKernelVersion() < KernelVersion::Sonoma) return false; + auto image = executableVnode(process); if (!image) return false; length = sizeof(path); const auto error = getPath(image, path, &length); @@ -74,6 +87,14 @@ void XeDataAccess::processKernel(KernelPatcher &p) { p.clearError(); putVnode = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_vnode_put")); p.clearError(); + signingIdentity = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_csproc_get_identity")); + p.clearError(); + platformBinary = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_csproc_get_platform_binary")); + p.clearError(); + signatureValid = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_cs_valid")); + p.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeWebKitIdentitySymbols", + signingIdentity && platformBinary && signatureValid); enabled = currentProcess && executableVnode && fileVnode && getPath && putVnode; IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDataAccessSymbols", enabled); } diff --git a/WhateverXe/kern_xe_data_policy.hpp b/WhateverXe/kern_xe_data_policy.hpp index 93a385ff..a3982a10 100644 --- a/WhateverXe/kern_xe_data_policy.hpp +++ b/WhateverXe/kern_xe_data_policy.hpp @@ -4,25 +4,17 @@ namespace XeDataPolicy { enum class Client { None, Compiler, WebKitGPU }; +// Identity comes from the running process's kernel code-signing state, never +// Info.plist, a basename, or caller-provided metadata. Safari updates and moves +// must not require enumerating executable locations. +inline bool webKitGPU(const char *identity, bool platformBinary, bool valid) { + return platformBinary && valid && identity && !strcmp(identity, "com.apple.WebKit.GPU"); +} inline bool service(const char *path, Client client = Client::Compiler) { - if (!path || client == Client::None) return false; - // Safari updates can place WebKit in the App Cryptex instead of the OS. - // Admit the exact GPU service, never arbitrary staged frameworks or apps. - if (client == Client::WebKitGPU && !strcmp(path, - "/System/Volumes/Preboot/Cryptexes/App/System/Library/StagedFrameworks/Safari/" - "WebKit.framework/Versions/A/XPCServices/com.apple.WebKit.GPU.xpc/" - "Contents/MacOS/com.apple.WebKit.GPU")) return true; - // Preserve the two OS mount roots and their original exact service path. - if (client == Client::WebKitGPU) { - constexpr const char os[] = "/System/Volumes/Preboot/Cryptexes/OS"; - constexpr const char incoming[] = "/System/Volumes/Preboot/Cryptexes/Incoming/OS"; - if (!strncmp(path, os, sizeof(os)-1)) path += sizeof(os)-1; - else if (!strncmp(path, incoming, sizeof(incoming)-1)) path += sizeof(incoming)-1; - } - if (client == Client::WebKitGPU) return path && !strcmp(path, - "/System/Library/Frameworks/WebKit.framework/Versions/A/XPCServices/" - "com.apple.WebKit.GPU.xpc/Contents/MacOS/com.apple.WebKit.GPU"); - return path && !strcmp(path, "/System/Library/Frameworks/Metal.framework/Versions/A/" + // Compiler access keeps its original exact-path contract. WebKit has no + // path-based fallback: even a familiar location must pass signature checks. + return client == Client::Compiler && path && !strcmp(path, + "/System/Library/Frameworks/Metal.framework/Versions/A/" "XPCServices/MTLCompilerService.xpc/Contents/MacOS/MTLCompilerService"); } inline bool file(const char *path, bool executable) { diff --git a/tests/xe_data_access.py b/tests/xe_data_access.py new file mode 100644 index 00000000..2674db32 --- /dev/null +++ b/tests/xe_data_access.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Exercise the production access decision with mocked kernel signing queries.""" +import pathlib, subprocess, tempfile +root = pathlib.Path(__file__).resolve().parents[1] +source = (root/'WhateverXe/kern_xe_data_access.cpp').read_text() +functions = source[source.index('bool knownOS()'):source.index('int checkOpen(')] +harness = r''' +#include "kern_xe_data_policy.hpp" +#include +#include +#include +namespace KernelVersion { enum { Catalina=19, BigSur, Monterey, Ventura, Sonoma, Sequoia, Tahoe }; } +int version=20; int getKernelVersion() { return version; } +bool ready=true, processPresent=true, platform=true, valid=true, pathError=false; +const char *identity="com.apple.WebKit.GPU"; +const char *target="/Library/GPUBundles/AppleIntelTGLGraphicsVADriver.bundle/Contents/MacOS/TGLVA"; +const char *imagePath="/arbitrary/updated/Safari/WebKitGPU"; +int imageReads=0, releases=0; +void *currentProcess() { return processPresent ? (void*)2 : nullptr; } +void *executableVnode(void*) { ++imageReads; return (void*)3; } +int getPath(void *node,char *path,int*) { if(pathError)return 1; strcpy(path,node==(void*)1?target:imagePath);return 0; } +int putVnode(void*) { ++releases; return 0; } +const char *getIdentity(void*) { return identity; } +int getPlatform(void*) { return platform; } +int getValid(void*) { return valid; } +const char *(*signingIdentity)(void*)=getIdentity; +int (*platformBinary)(void*)=getPlatform; +int (*signatureValid)(void*)=getValid; +'''+functions+r''' +int main() { + for(version=19;version<=26;++version) { + assert(knownOS()==(version>=20&&version<=25)); + if(!knownOS())continue; + for(const char *location : {"/System/Library/Frameworks/WebKit.framework/service", + "/System/Volumes/Preboot/Cryptexes/App/staged/service", "/Applications/Safari Copy.app/service", "/tmp/service"}) { + imagePath=location; + assert(allowed((void*)1,true)); + assert(imageReads==0); // WebKit's executable path is never queried. + } + platform=false; assert(!allowed((void*)1,true)); platform=true; + valid=false; assert(!allowed((void*)1,true)); valid=true; + identity="com.apple.WebKit.WebContent"; assert(!allowed((void*)1,true)); + identity=nullptr; assert(!allowed((void*)1,true)); identity="com.apple.WebKit.GPU"; + signingIdentity=nullptr; assert(!allowed((void*)1,true)); signingIdentity=getIdentity; + platformBinary=nullptr; assert(!allowed((void*)1,true)); platformBinary=getPlatform; + signatureValid=nullptr; assert(!allowed((void*)1,true)); signatureValid=getValid; + assert(!allowed((void*)1,false)); + } + ready=false; assert(!allowed((void*)1,true)); ready=true; + assert(!allowed(nullptr,true)); + processPresent=false; assert(!allowed((void*)1,true)); processPresent=true; + pathError=true; assert(!allowed((void*)1,true)); pathError=false; + target="/Library/GPUBundles/AppleIntelTGLGraphicsVADriver.bundle/Contents/MacOS/unlisted"; + assert(!allowed((void*)1,true)); + target="/Library/GPUBundles/XeCompilerAdapter.bundle/Contents/MacOS/adapter.dylib"; + imagePath="/System/Library/Frameworks/Metal.framework/Versions/A/XPCServices/MTLCompilerService.xpc/Contents/MacOS/MTLCompilerService"; + for(version=20;version<=25;++version) assert(allowed((void*)1,true)==(version>=23)); + imagePath="/tmp/MTLCompilerService"; assert(!allowed((void*)1,true)); + assert(imageReads==releases); + puts("PASS: production access decision, six OS gates, relocation, trust failures, compiler boundary"); +} +''' +harness = harness.replace('#include ', '#include \n#include ') +with tempfile.TemporaryDirectory() as temp: + cpp=pathlib.Path(temp)/'test.cpp'; exe=pathlib.Path(temp)/'test' + cpp.write_text(harness) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(cpp),'-o',str(exe)],check=True) + subprocess.run([str(exe)],check=True) diff --git a/tests/xe_data_policy.cpp b/tests/xe_data_policy.cpp index 6cac9d72..106342e6 100644 --- a/tests/xe_data_policy.cpp +++ b/tests/xe_data_policy.cpp @@ -38,27 +38,21 @@ int main() { } for (auto name : {"", "other", "TGLVA.old", "../Info.plist"}) assert(clientForFile((va+name).c_str(), true) == Client::None); - assert(service(web, Client::WebKitGPU)); - const std::string safari = "/System/Volumes/Preboot/Cryptexes/App/System/Library/StagedFrameworks/Safari/" - "WebKit.framework/Versions/A/XPCServices/com.apple.WebKit.GPU.xpc/Contents/MacOS/com.apple.WebKit.GPU"; - assert(service(safari.c_str(), Client::WebKitGPU)); - assert(!service(safari.c_str(), Client::Compiler)); - assert(!service((safari+".old").c_str(), Client::WebKitGPU)); - assert(!service(("/System/Volumes/Data"+safari).c_str(), Client::WebKitGPU)); - auto other = safari; - other.replace(other.find("StagedFrameworks/Safari"), 23, "StagedFrameworks/Other"); - assert(!service(other.c_str(), Client::WebKitGPU)); - other = safari; - other.replace(other.rfind("com.apple.WebKit.GPU"), 20, "com.apple.WebKit.WebContent"); - assert(!service(other.c_str(), Client::WebKitGPU)); - for (auto root : {"/System/Volumes/Preboot/Cryptexes/OS", "/System/Volumes/Preboot/Cryptexes/Incoming/OS"}) { - assert(service((std::string(root)+web).c_str(), Client::WebKitGPU)); - assert(!service((std::string(root)+web).c_str(), Client::Compiler)); - assert(!service((std::string(root)+metal).c_str(), Client::WebKitGPU)); - assert(!service((std::string(root)+web+".old").c_str(), Client::WebKitGPU)); + // Same signing identifier alone is not authority: both kernel trust and + // current signature validity are required. Other Apple services are denied. + for (const char *identity : {static_cast(nullptr), "", "com.apple.WebKit.GPU", + "com.apple.WebKit.GPU.extra", "com.apple.WebKit.WebContent", + "com.apple.WebKit.Networking", "com.apple.Safari", "MTLCompilerService"}) { + for (bool platform : {false, true}) for (bool valid : {false, true}) + assert(webKitGPU(identity, platform, valid) == + (identity && std::string(identity) == "com.apple.WebKit.GPU" && platform && valid)); } - for (auto root : {"/tmp", "/System/Volumes/Data", "/System/Volumes/Preboot/Cryptexes/OS-extra", - "/System/Volumes/Preboot/Cryptexes/Incoming/OS/..", "/System/Volumes/Preboot/Cryptexes/App"}) + // No path-only fallback, including formerly accepted OS/App Cryptex roots, + // updated/staged layouts, and arbitrary relocated or renamed copies. + for (const char *root : {"", "/System/Volumes/Preboot/Cryptexes/OS", + "/System/Volumes/Preboot/Cryptexes/Incoming/OS", + "/System/Volumes/Preboot/Cryptexes/App", "/System/Cryptexes/App", + "/Applications/Relocated Safari.app", "/tmp"}) assert(!service((std::string(root)+web).c_str(), Client::WebKitGPU)); assert(!service(nullptr, Client::WebKitGPU)); const std::string mtl = "/Library/GPUBundles/AppleIntelTGLGraphicsMTLDriver.bundle/Contents/MacOS/"; diff --git a/tools/test.py b/tools/test.py index 5922a7b6..04551ba7 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=['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=['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 += ['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 17a06d208b35b9bb802d33ca94681a7615940de0 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:24:05 +0900 Subject: [PATCH 13/58] Unify Display12 SST transport and power contracts --- WhateverXe/kern_tgl_port_map.hpp | 23 -------- WhateverXe/kern_tgl_tc_boot.hpp | 4 ++ WhateverXe/kern_xe.cpp | 18 +++---- WhateverXe/kern_xe.hpp | 21 ++++---- WhateverXe/kern_xelp_sst_route.hpp | 72 ++++++++++++++++++++++++++ tests/tgl_sink_framing.py | 7 +-- tests/tgl_training_leases_fixture.cpp | 20 +++++-- tests/tgl_training_pattern_fixture.cpp | 15 ++++++ tests/xelp_sst_route.cpp | 72 ++++++++++++++++++++++++++ 9 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 WhateverXe/kern_xelp_sst_route.hpp create mode 100644 tests/xelp_sst_route.cpp diff --git a/WhateverXe/kern_tgl_port_map.hpp b/WhateverXe/kern_tgl_port_map.hpp index 4dbb9156..e05624df 100644 --- a/WhateverXe/kern_tgl_port_map.hpp +++ b/WhateverXe/kern_tgl_port_map.hpp @@ -169,28 +169,5 @@ struct TGLTypeCPort { // A native SST path may use a combo eDP port or a qualified legacy TC port. // This selects register coordinates, not firmware wiring or a fixed pipe. -struct TGLSSTTrainingPort { - uint32_t ddi {}, ddiBuffer {}; - bool combo {}; - static bool resolve(uint32_t physicalDDI, uint32_t kind, uint32_t backend, - bool qualifiedTC, TGLSSTTrainingPort &out) { - out = {}; - const bool edp = physicalDDI < 2 && kind == 0 && backend == 1; - const bool tc = physicalDDI >= 3 && physicalDDI <= 8 && - kind == 1 && (backend == 3 || backend == 4) && qualifiedTC; - // qualifiedTC includes generation and firmware admission. ADL-S - // physical A/TC1/TC2 use Combo mode1; TC lane power remains PW3. - const bool adls = (physicalDDI==0 || physicalDDI==3 || physicalDDI==4) && kind==1 && backend==1 && qualifiedTC; - if (!edp && !tc && !adls) return false; - out.ddi = physicalDDI; out.ddiBuffer = 0x64000 + 0x100 * physicalDDI; - out.combo = edp || (adls && physicalDDI==0); - return true; - } - uint32_t mainPowerMask(uint32_t pipe) const { - // TGL PW1 owns combo lanes and pipe A; PW3 adds pipe B / TC lanes, - // and PW4 adds pipe C (Linux v6.12 intel_display_power_map.c). - return pipe == 2 ? 0xFFU : (pipe == 0 && combo ? 3U : 0x3FU); - } -}; #endif diff --git a/WhateverXe/kern_tgl_tc_boot.hpp b/WhateverXe/kern_tgl_tc_boot.hpp index f1855cf6..c0e9a505 100644 --- a/WhateverXe/kern_tgl_tc_boot.hpp +++ b/WhateverXe/kern_tgl_tc_boot.hpp @@ -5,6 +5,7 @@ #ifndef kern_tgl_tc_boot_hpp #define kern_tgl_tc_boot_hpp #include "kern_tgl_tc_mode.hpp" +#include "kern_xelp_sst_route.hpp" #include "kern_tgl_boot_readout.hpp" struct TGLTCWiring { static constexpr uint32_t Mask=0x1F8; // physical DDI3..8, not framebuffer IDs @@ -23,6 +24,9 @@ struct TGLTCWiring { for(unsigned i=0;i<4;++i)for(unsigned j=0;j<4;++j)q[i]|=uint32_t(p[4*i+j])<<(8*j); return q[0]==1?fromMasks(q[1],q[2],q[3]):TGLTCWiring{}; } + XELP::Display12SSTRoutes sstRoutes(uint32_t backend,uint32_t registered,uint32_t alt) const { + return {0,((valid?legacy:0)|registered)&backend&Mask,alt&Mask}; + } bool legacyEligible(uint32_t d,uint32_t backend) const { return valid && d>=3 && d<=8 && (legacy&backend&(1u<(port); auto config = *reinterpret_cast(p + 0x548); auto ctl = static_cast(callbackXE->tglObservationController); - TGLSSTTrainingPort mapping; - if (config && ctl && TGLSSTTrainingPort::resolve(config[0],config[2], - *reinterpret_cast(p + 0x550),callbackXE->tglDPSSTMode(config[0],*reinterpret_cast(p + 0x550)),mapping) && + XELP::Display12SSTRoute mapping; + if (config && ctl && callbackXE->tglSSTRoutes().resolve(config[0],config[2], + *reinterpret_cast(p + 0x550),mapping) && *reinterpret_cast(p + 0x578) && *reinterpret_cast(p + 0x578) == *reinterpret_cast(ctl + 0xC40) && p[0x115] == 1 && p[0x117] == 0 && p[0x10] <= 1 && p[0x11A] <= 1) { @@ -16368,9 +16368,9 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto ctl = fb ? *reinterpret_cast(fb + 0x1D0) : nullptr; auto access = ctl ? *reinterpret_cast(ctl + 0xC40) : nullptr; const auto transcoder = *reinterpret_cast(static_cast(path) + 0x3640); - TGLSSTTrainingPort mapping; - if (config && TGLSSTTrainingPort::resolve(config[0],config[2], - *reinterpret_cast(pb + 0x550),callbackXE->tglDPSSTMode(config[0],*reinterpret_cast(pb + 0x550)),mapping) && !pb[0x117] && + XELP::Display12SSTRoute mapping; + if (config && callbackXE->tglSSTRoutes().resolve(config[0],config[2], + *reinterpret_cast(pb + 0x550),mapping) && !pb[0x117] && transcoder < 3 && ctl == callbackXE->tglObservationController && access && *reinterpret_cast(pb + 0x578) == access && *reinterpret_cast(fb + 0x4A40) == access) { @@ -16445,9 +16445,9 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto ctl = fb ? *reinterpret_cast(fb + 0x1D0) : nullptr; auto access = ctl ? *reinterpret_cast(ctl + 0xC40) : nullptr; const auto transcoder = *reinterpret_cast(static_cast(path) + 0x3640); - TGLSSTTrainingPort mapping; - if (config && TGLSSTTrainingPort::resolve(config[0],config[2], - *reinterpret_cast(p + 0x550),callbackXE->tglDPSSTMode(config[0],*reinterpret_cast(p + 0x550)),mapping) && + XELP::Display12SSTRoute mapping; + if (config && callbackXE->tglSSTRoutes().resolve(config[0],config[2], + *reinterpret_cast(p + 0x550),mapping) && !p[0x117] && p[0x11A] <= 1 && transcoder < 3 && ctl == callbackXE->tglObservationController && access && *reinterpret_cast(p + 0x578) == access && diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 38555e11..b9113995 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -403,12 +403,16 @@ class XE { // instances need the new mode backend, regardless of available PHY math. uint32_t tglTCAutoCandidates {},tglTCAutoRegistered {}; uint32_t tglDPAltCandidates {}; - bool tglDPSSTEligible(uint32_t ddi) const { - return (adlsDetected && adlsWiring.hasDP(ddi)) || tglLegacyTCEligible(ddi) || (ddi>=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && - (tglTCAutoCandidates & tglAUXParentMask & __atomic_load_n(&tglTCAutoRegistered,__ATOMIC_ACQUIRE) & (1U< + +namespace XELP { +struct Display12SSTRoute { + enum class Transport : uint8_t { None, Combo, DekelLegacy, DekelAlt }; + uint32_t ddi {}, ddiBuffer {}, mode {}, lanePG {}, auxParentPG {}; + Transport transport {Transport::None}; + bool panel {}; + bool valid() const {return transport!=Transport::None;} + bool needsLease() const {return valid() && !panel;} + bool retainComboAUX() const {return transport==Transport::Combo;} + uint32_t mainPowerMask(uint32_t pipe) const { + // Pipe A/B/C require PW1/PW3/PW4, independently of lane consumers. + if(!valid() || pipe>=3)return 0; + const uint32_t pipePG[]={1,3,4}; + const auto pg=lanePG>pipePG[pipe]?lanePG:pipePG[pipe]; + return (1U<<(2*pg))-1; + } +}; + +// These are qualified backend selections, not raw VBT masks. The platform +// adapter retains responsibility for identity, physical mapping and native ABI. +// Keeping the three transports separate prevents TC-numbered Combo ports from +// accidentally acquiring Dekel/FIA semantics. +struct Display12SSTRoutes { + uint32_t combo {}, legacy {}, alt {}; + Display12SSTRoute external(uint32_t ddi,uint32_t mode) const { + Display12SSTRoute r; + if(ddi>=9)return r; + const auto bit=1U< #include #include @@ -20,7 +20,8 @@ struct State { bool tglActive=true,tglSinkFramingReady=true,eligible=true; void *tglObservationController=nullptr; - bool tglDPSSTMode(uint32_t,uint32_t mode){return eligible && (mode==3 || mode==4);} + XELP::Display12SSTRoutes tglSSTRoutes(){return kind==2?XELP::Display12SSTRoutes{0,0,8}:XELP::Display12SSTRoutes{0,8,0};} + unsigned kind=0; } state,*callbackXE=&state; unsigned records; void tglTransitionRecord(unsigned stage,bool done,void *,uint32_t ddi,uint64_t before,uint8_t after,const uint64_t *d,unsigned size){ @@ -32,7 +33,7 @@ for(unsigned kind=0;kind<3;++kind)for(unsigned cap=0;cap<3;++cap)for(unsigned requested=0;requested<3;++requested) for(unsigned bad=0;bad<10;++bad){ memset(p,0,sizeof(p));memset(ctl,0,sizeof(ctl));memset(config,0,sizeof(config)); - state={};state.tglObservationController=ctl; + state={};state.kind=kind;state.tglObservationController=ctl; config[0]=kind?3:0;config[2]=kind?1:0; uint32_t *cp=config;memcpy(p+0x548,&cp,sizeof(cp)); uint32_t backend=kind==2?3:(kind?4:1);memcpy(p+0x550,&backend,4); diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index fa588b75..beba44ce 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -441,11 +441,21 @@ int main(int argc, char **argv) { assert(used[0]&&fbs[0].at(0x49e8)==1);used[0]=false;clean(); ports[3].at(0x550)=2; assert(XE::tglPrepareTrainingLease(ctl.data(),obj.tglTrainingLeases[3])==1); // no TBT - // ADL-S fixed Combo C uses physical TC2 in mode1 and the same pool. - setup();obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=true;obj.adlsWiring.valid=true;obj.adlsWiring.dp=16;obj.adlsWiring.pin[4]=10;obj.tglAUXParentReady=true; - assert(XE::tglTrainingLeaseForDDI(4)&&!XE::tglTrainingLeaseForDDI(3)); - assert(!obj.tglLegacyTCEligible(4)&&obj.tglAUXParentEligible(4)); - ports[4].at(0x550)=1;reserve(4);release(4);clean(); + // ADL-S Combo A/B/C use the same pool and mode1 contract. Allocate all + // three simultaneously and vary both allocation and teardown order. + unsigned comboOrders=0,ins[]={0,3,4}; + do {unsigned outs[]={0,3,4};do { + setup();obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=true; + obj.adlsWiring.valid=true;obj.adlsWiring.dp=0x19;obj.tglAUXParentReady=true; + for(auto d:ins){ + assert(XE::tglTrainingLeaseForDDI(d)&&!obj.tglLegacyTCEligible(d)&&obj.tglAUXParentEligible(d)); + ports[d].at(0x550)=1;reserve(d); + } + for(auto d:outs)release(d); + clean();++comboOrders; + }while(std::next_permutation(outs,outs+3));}while(std::next_permutation(ins,ins+3)); + assert(comboOrders==36); + std::puts("PASS ADL-S Combo A/B/C: 36 three-port allocation/teardown orders using the shared lease implementation"); // Nested and concurrent training must not unmute another invocation. TGLTrainingScopes scopes; auto one=scopes.enter(1), nested=scopes.enter(1), two=scopes.enter(2); diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index 01bccebb..8bd85db4 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -93,6 +93,21 @@ int main(){ else {assert(r.pre.empty());assert(reg(0x46140+4*p)==0x3456&®(0x60400+0x1000*p)==0x01000020);} ++cases; } + // Reuse the actual wrapper for every admitted ADL-S Combo route on every + // pipe, independent of which connector/pipe a physical fixture happened to use. + for(unsigned d:{0u,3u,4u})for(unsigned p=0;p<3;++p) + for(unsigned lanes:{1u,2u,4u})for(unsigned framing=0;framing<2;++framing) + for(unsigned pat=0;pat<6;++pat){ + reset(d,p,pat);object.tglTCWiring=TGLTCWiring::fromMasks(0,0,0); + object.adlsDetected=true;object.adlsWiring.valid=true;object.adlsWiring.dp=1U< +#include +#include +using namespace XELP; + +int main() { + // Exercise composition, not machine connector lists: all 4^9 assignments + // of absent / Combo / Dekel legacy / DP-alt, then each pipe independently. + unsigned checked=0; + for(unsigned assignment=0;assignment<(1U<<18);++assignment) { + Display12SSTRoutes routes; + for(unsigned d=0;d<9;++d) { + const auto type=(assignment>>(2*d))&3; + if(type==1)routes.combo|=1U<>(2*d))&3; + const unsigned expectedMode[]={0,1,4,3}; + const auto r=routes.external(d,expectedMode[type]); + const bool valid=type && (type==1 || d>=3); + assert(r.valid()==valid); + if(!valid)continue; + assert(r.needsLease() && !r.panel); + assert(r.ddi==d && r.ddiBuffer==0x64000+0x100*d); + assert(r.retainComboAUX()==(type==1)); + // A fixed DP AUX transaction must work with all pipes stopped. + // DP-alt deliberately delegates that ownership to the TC backend. + assert((r.auxParentPG!=0)==(type!=3)); + assert(r.auxParentPG==(type==3?0U:d<3?1U:3U)); + for(unsigned mode=0;mode<6;++mode) { + Display12SSTRoute resolved; + assert(routes.resolve(d,1,mode,resolved)==(mode==expectedMode[type])); + assert(resolved.valid()==(mode==expectedMode[type])); + assert(!routes.resolve(d,2,mode,resolved)); // HDMI is not SST. + } + // Reference domain unions: PW1 / PW1..3 / PW1..4. + const unsigned masks[2][3]={{3,0x3f,0xff},{0x3f,0x3f,0xff}}; + for(unsigned pipe=0;pipe<3;++pipe)assert(r.mainPowerMask(pipe)==masks[d>=3][pipe]); + assert(!r.mainPowerMask(3) && !r.mainPowerMask(~0U)); + ++checked; + } + } + for(unsigned d=0;d<9;++d) { + const auto bit=1U<=3)); + assert(tc.external(d,4).valid()==(d>=3)); + assert(!tc.external(d,1).valid()); + Display12SSTRoute panel; + assert(Display12SSTRoutes{}.resolve(d,0,1,panel)==(d<2)); + if(d<2)assert(panel.panel && !panel.needsLease() && !panel.auxParentPG && panel.retainComboAUX()); + } + assert((!Display12SSTRoutes{~0U,0,0}.eligible(9))); + assert((!Display12SSTRoutes{~0U,0,0}.eligible(~0U))); + // Firmware intent and qualified backend are an intersection. Automatic + // legacy registration supplies a route only after the native HPD decision. + for(unsigned wiring=0;wiring<64;++wiring)for(unsigned backend=0;backend<64;++backend) { + auto w=TGLTCWiring::fromMasks(wiring<<3,0,0); + for(unsigned d=3;d<9;++d) { + assert(w.sstRoutes(backend<<3,0,0).external(d,4).valid()==bool((wiring&backend)&(1U<<(d-3)))); + assert(w.sstRoutes(0,0,0).external(d,4).valid()==false); + } + } + std::printf("PASS %u SST routes across 4^9 transport assignments, all pipes/modes; ambiguous selection and backend intersection\n",checked); +} From 2f73638a8f45faaa85018cba3a85b4cedacfdce8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:33:55 +0900 Subject: [PATCH 14/58] Own lane and pipe power throughout SST training --- WhateverXe/kern_xe.cpp | 40 +++++++++------------- WhateverXe/kern_xelp_sst_route.hpp | 10 ++++-- tests/tgl_agdc_aux_observer.py | 48 +++++++++++++++++++++++++++ tests/tgl_combo_aux_lifetime.py | 1 + tests/tgl_combo_signal_guard.py | 1 + tests/tgl_training_leases_fixture.cpp | 21 +++++++++++- tools/test.py | 2 +- 7 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 tests/tgl_agdc_aux_observer.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 5c47c959..7e800581 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -14809,22 +14809,6 @@ uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { IORegistryEntry::getRegistryRoot()->setProperty(key, failure, sizeof(failure)); } tglTrace(16, id, true, a, b, r, value); - // One read-only receiver-CRC capability probe after a successful late link - // status diagnostic. Reuse the native AGDC AUX entry and its exact request - // contract, outside IRQ context. Do not start a test or write sink state. - static uint32_t crcProbed {}; - uint64_t uptime, ns; - clock_get_uptime(&uptime); absolutetime_to_nanoseconds(uptime, &ns); - if (!r && req && id == 5 && a == 0x200 && !req[13] && req[12] >= 8 && - ns > 30000000000ULL && __sync_bool_compare_and_swap(&crcProbed, 0, 1)) { - uint8_t input[32] {}, out[32] {}; - *reinterpret_cast(input) = 5; - *reinterpret_cast(input + 8) = 0x246; - input[12] = 1; - const auto result = FunctionCast(wrapTGLAGDCAUX, callbackXE->orgTGLAGDCAUX)(controller, input, out); - uint64_t record[] {ns, result, out[14], out[15], out[16], r}; - IORegistryEntry::getRegistryRoot()->setProperty("TGLSinkCRCCapability", record, sizeof(record)); - } return r; } @@ -14944,9 +14928,10 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) auto s = callbackXE; auto ctl = static_cast(controller); auto port = static_cast(reinterpret_cast(s->tglPortByDDI)(lease.ddi)); - if (!ctl || !port || - *reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || - !s->tglDPSSTMode(lease.ddi,*reinterpret_cast(port + 0x550)) || port[0x117]) return 1; + 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; auto access = *reinterpret_cast(ctl + 0xC40); auto power = *reinterpret_cast(ctl + 0xC8); if (!access || !power || *reinterpret_cast(access + 0x60) < 0x16A000 || @@ -14992,8 +14977,9 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) lease.path = path; lease.port = port; lease.fb = fb; lease.power = power; lease.busy = true; lease.adopted = false; ++lease.generation; lease.clocksActive = false; lease.endCleanupPending = false; lease.pipe = pipe; - // Same power-domain selection as native allocateDisplayPathResources. - lease.pg = pipe ? pipe + 2 : 1; + // 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); static_cast(lease.fb)->retain(); static_cast(lease.port)->retain(); static_cast(lease.power)->retain(); @@ -16573,7 +16559,10 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; const bool adls=self->adlsDetected && self->adlsWiring.boundDP(self->tglPlatformCopy,config); - if(!config || *reinterpret_cast(p+0x550)!=1 || p[0x117])return false; + XELP::Display12SSTRoute route; + const XELP::Display12SSTRoutes routes{adls?self->adlsWiring.dp:0,0,0}; + if(!config || !routes.resolve(config[0],config[2],*reinterpret_cast(p+0x550),route) || + !route.retainComboAUX() || p[0x117])return false; if(!adls && (config[0]>1 || config[2]!=0 || config[3]!=0 || !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17)))return false; auto access=*reinterpret_cast(p+0x578); @@ -16592,7 +16581,7 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void const auto main=wrapTGLRegisterRead(access,0x45404); const auto io=wrapTGLRegisterRead(access,0x45454); const uint32_t ioMask=3U<<(2*config[0]); - const uint32_t mainMask=adls && config[0]>=3?0x3FU:3U; + const uint32_t mainMask=route.lanePowerMask(); if(main==~0U || io==~0U || (main&mainMask)!=mainMask || (io&ioMask)!=ioMask)return false; const auto read=[&](uint32_t reg){return wrapTGLRegisterRead(access,reg);}; const auto write=[&](uint32_t reg,uint32_t value){wrapTGLRegisterWrite(access,reg,value);}; @@ -16939,7 +16928,10 @@ void XE::tglComboAUXReference(void *port,bool acquire) { !*reinterpret_cast(access+0x50))return; const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); if(*reinterpret_cast(p+0x590)!=buffer)return; - if(acquire && (config[3]!=0 || p[0x117] || *reinterpret_cast(p+0x550)!=1 || + XELP::Display12SSTRoute route; + const XELP::Display12SSTRoutes routes{adls?s->adlsWiring.dp: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 && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return; diff --git a/WhateverXe/kern_xelp_sst_route.hpp b/WhateverXe/kern_xelp_sst_route.hpp index b6fc1de9..b9628a4d 100644 --- a/WhateverXe/kern_xelp_sst_route.hpp +++ b/WhateverXe/kern_xelp_sst_route.hpp @@ -14,12 +14,16 @@ struct Display12SSTRoute { bool valid() const {return transport!=Transport::None;} bool needsLease() const {return valid() && !panel;} bool retainComboAUX() const {return transport==Transport::Combo;} - uint32_t mainPowerMask(uint32_t pipe) const { + uint32_t lanePowerMask() const {return valid()?(1U<<(2*lanePG))-1:0;} + uint32_t powerGroup(uint32_t pipe) const { // Pipe A/B/C require PW1/PW3/PW4, independently of lane consumers. if(!valid() || pipe>=3)return 0; const uint32_t pipePG[]={1,3,4}; - const auto pg=lanePG>pipePG[pipe]?lanePG:pipePG[pipe]; - return (1U<<(2*pg))-1; + return lanePG>pipePG[pipe]?lanePG:pipePG[pipe]; + } + uint32_t mainPowerMask(uint32_t pipe) const { + const auto pg=powerGroup(pipe); + return pg?(1U<<(2*pg))-1:0; } }; diff --git a/tests/tgl_agdc_aux_observer.py b/tests/tgl_agdc_aux_observer.py new file mode 100644 index 00000000..0ada9aeb --- /dev/null +++ b/tests/tgl_agdc_aux_observer.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""The production AUX observer must never inject a port-specific transaction.""" +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() +body=re.search(r'uint32_t XE::wrapTGLAGDCAUX\(.*?\n\}',s,re.S).group() +code=r''' +#include +#include +#include +#include +struct XE {uintptr_t orgTGLAGDCAUX;static uint32_t wrapTGLAGDCAUX(void*,void*,void*);}; +XE state,*callbackXE=&state; +#define FunctionCast(f,p) reinterpret_cast(p) +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry e;return &e;} + templatevoid setProperty(A...){} +}; +templatevoid tglTrace(A...){} +void clock_get_uptime(uint64_t *v){*v=60000000000ULL;} +void absolutetime_to_nanoseconds(uint64_t v,uint64_t *n){*n=v;} +unsigned calls,result;void *expectedRequest,*expectedOutput; +uint32_t native(void *c,void *request,void *output){ + assert(c==&state && request==expectedRequest && output==expectedOutput); + assert(++calls==1);return result; +} +BODY +int main(){ + state.orgTGLAGDCAUX=reinterpret_cast(native); + for(unsigned ddi=0;ddi<10;++ddi)for(unsigned write=0;write<2;++write) + for(unsigned error=0;error<2;++error)for(unsigned length=0;length<32;++length){ + alignas(8) uint8_t request[64]{},output[64]{},before[64]{}; + request[0]=ddi;request[9]=2;request[12]=length;request[13]=write; + memcpy(before,request,sizeof(request));expectedRequest=request;expectedOutput=output; + calls=0;result=error?0xe00002d6:0; + assert(XE::wrapTGLAGDCAUX(&state,request,output)==result); + assert(calls==1&&!memcmp(before,request,sizeof(request))); + } + calls=0;expectedRequest=expectedOutput=nullptr; + assert(XE::wrapTGLAGDCAUX(&state,nullptr,nullptr)==result&&calls==1); + puts("PASS 1280 AUX observer cases: one native call, original arguments/result, no port-specific probe"); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='tgl-aux-observer-') 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/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index 46bacdad..a2187368 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -8,6 +8,7 @@ b=Path(sys.argv[1]).read_bytes();assert b[0x5fc56:0x5fc5f]==bytes.fromhex('554889e553504889fb');assert b[0x5fc6d:0x5fc73]==bytes.fromhex('8bb790050000') code=r''' #include "kern_alderlake_s.hpp" +#include "kern_xelp_sst_route.hpp" #include #include #include diff --git a/tests/tgl_combo_signal_guard.py b/tests/tgl_combo_signal_guard.py index e70ff6a9..cde539f9 100644 --- a/tests/tgl_combo_signal_guard.py +++ b/tests/tgl_combo_signal_guard.py @@ -13,6 +13,7 @@ assert b[at:at+len(expected)]==expected code=r''' #include "kern_alderlake_s.hpp" +#include "kern_xelp_sst_route.hpp" #include "kern_tgl_combo_signal.hpp" #include #include diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index beba44ce..f7494b28 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -101,7 +101,8 @@ void freePath(void *p) { } assert(false); } -void enablePG(void *p, uint32_t pg) { std::lock_guard lock(refLock); assert(p==power.data() && pg<8); ++pgRefs[pg]; } +unsigned expectedPG=0; +void enablePG(void *p, uint32_t pg) { std::lock_guard lock(refLock); assert(p==power.data() && pg<8); if(expectedPG)assert(pg==expectedPG); ++pgRefs[pg]; } void disablePG(void *p, uint32_t pg) { std::lock_guard lock(refLock); assert(p==power.data() && pg<8 && pgRefs[pg]>0); --pgRefs[pg]; if (protectOnPowerDrop && pgRefs[pg]==0) assert(mprotect(protectedMMIO, sizeof(mmio), PROT_NONE)==0); } uint32_t disablePort(void *p) { ++portDisables; @@ -456,6 +457,24 @@ int main(int argc, char **argv) { }while(std::next_permutation(outs,outs+3));}while(std::next_permutation(ins,ins+3)); assert(comboOrders==36); std::puts("PASS ADL-S Combo A/B/C: 36 three-port allocation/teardown orders using the shared lease implementation"); + // Each reservation must own the union of lane and pipe domains itself. + // No inherited GOP/AUX refs or another monitor are available to mask a gap. + unsigned powerCases=0; + for(unsigned mode:{1u,3u,4u})for(unsigned d=0;d<9;++d)for(unsigned pipe=0;pipe<3;++pipe){ + if(mode==1 ? (d!=0 && d!=3 && d!=4) : d<3)continue; + setup(); + if(mode==1){obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=true;obj.adlsWiring.valid=true;obj.adlsWiring.dp=1U<(0x550)=mode; + for(unsigned i=0;i=3?3:1); + auto &lease=reserve(d);assert(lease.pipe==pipe&&lease.pg==expectedPG); + release(d);expectedPG=0; + for(unsigned i=0;i Date: Fri, 23 Oct 2026 21:43:55 +0900 Subject: [PATCH 15/58] Separate Combo electrical policy from physical port programming --- WhateverXe/kern_tgl_combo_signal.hpp | 43 ++--- WhateverXe/kern_xelp_combo_policy.hpp | 236 ++++++++++++++++++++++++++ tests/xelp_combo_policy.cpp | 44 +++++ 3 files changed, 295 insertions(+), 28 deletions(-) create mode 100644 WhateverXe/kern_xelp_combo_policy.hpp create mode 100644 tests/xelp_combo_policy.cpp diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index b1e63e65..9e528d35 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -22,6 +22,7 @@ #define KERN_TGL_COMBO_SIGNAL_HPP #include #include "kern_alderlake_s.hpp" +#include "kern_xelp_combo_policy.hpp" struct TGLComboSignal { uint32_t base {},lanes {},level {},swing {},scalar {},cursor {},post1 {}; bool low {}; @@ -32,39 +33,25 @@ struct TGLComboSignal { if(ddi>1 || (count!=1 && count!=2 && count!=4) || hobl || (rate!=6 && rate!=10 && !(lowSwing && rate==20)) || voltage>3 || emphasis>3 || voltage+emphasis>3)return false; - static const uint8_t first[]={0,4,7,9}; - static const uint8_t normal[10][4]={ - {0xA,0x32,0x3F,0},{0xA,0x4F,0x37,8},{0xC,0x71,0x2F,0x10},{6,0x7D,0x2B,0x14}, - {0xA,0x4C,0x3F,0},{0xC,0x73,0x34,0xB},{6,0x7F,0x2F,0x10}, - {0xC,0x6C,0x3C,3},{6,0x7F,0x35,0xA},{6,0x7F,0x3F,0} - }; - static const uint8_t reduced[10][4]={ - {0,0x7F,0x3F,0},{8,0x7F,0x38,7},{1,0x7F,0x33,0xC},{9,0x7F,0x31,0xE}, - {8,0x7F,0x3F,0},{1,0x7F,0x38,7},{9,0x7F,0x35,0xA}, - {1,0x7F,0x3F,0},{9,0x7F,0x38,7},{9,0x7F,0x3F,0} - }; - base=ddi ? 0x6C000:0x162000;lanes=count;level=first[voltage]+emphasis;low=lowSwing; - const auto row=low ? reduced[level]:normal[level]; - swing=row[0];scalar=row[1];cursor=row[2];post1=row[3];return true; + XELP::ComboPolicy policy; + if(!policy.select(XELP::ComboPolicy::Platform::TGL,rate,true,lowSwing,false,voltage,emphasis))return false; + base=ddi?0x6C000:0x162000;lanes=count;low=lowSwing;assign(policy);return true; } - // ADL-S fixed DP on the resolved Combo PHY. RBR/HBR uses the TGL normal table; - // HBR2/HBR3 uses Intel's ADL-S external DP coefficients. + // The platform adapter chooses coordinates; electrical policy contains no + // physical DDI, donor field or board-specific connector condition. bool selectADLS(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis) { - if(rate!=6 && rate!=10 && rate!=20 && rate!=30)return false; - if(!select(1,count,6,false,false,voltage,emphasis))return false; + if(count!=1 && count!=2 && count!=4)return false; AlderLakeS::Port route; if(!AlderLakeS::Port::resolve(ddi,route) || !(AlderLakeS::ComboBackendMask&(1U<=20) { - static const uint8_t high[10][4]={ - {0xA,0x35,0x3F,0},{0xA,0x4F,0x37,8},{0xC,0x63,0x31,0xE},{6,0x7F,0x2C,0x13}, - {0xA,0x47,0x3F,0},{0xC,0x63,0x37,8},{6,0x73,0x32,0xD}, - {0xC,0x58,0x3F,0},{6,0x7F,0x35,0xA},{6,0x7F,0x3F,0} - }; - const auto row=high[level];swing=row[0];scalar=row[1];cursor=row[2];post1=row[3]; - } - return true; + XELP::ComboPolicy policy; + if(!policy.select(XELP::ComboPolicy::Platform::ADLS,rate,false,false,false,voltage,emphasis))return false; + base=route.phyBase();lanes=count;low=false;assign(policy);return true; + } + void assign(const XELP::ComboPolicy &p) { + level=p.level;swing=p.coefficients.swing;scalar=p.coefficients.scalar; + cursor=p.coefficients.cursor;post1=p.coefficients.post1; } + // Caller owns this live port's power. Pre-read all lane words before any // write; refuse an inaccessible bank without a partial programming sequence. template bool program(Read read,Write write) const { diff --git a/WhateverXe/kern_xelp_combo_policy.hpp b/WhateverXe/kern_xelp_combo_policy.hpp new file mode 100644 index 00000000..762712e6 --- /dev/null +++ b/WhateverXe/kern_xelp_combo_policy.hpp @@ -0,0 +1,236 @@ +// Combo electrical policy, independent of PHY coordinates and port admission. +// Tables and selection rules: Linux v6.12 intel_ddi_buf_trans.c (MIT). +// Reference coefficient tables: Copyright (c) 2020 Intel Corporation. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +#ifndef KERN_XELP_COMBO_POLICY_HPP +#define KERN_XELP_COMBO_POLICY_HPP +#include +namespace XELP { +struct ComboPolicy { + enum class Platform : uint8_t {Unknown,TGL,TGLUY,RKL,ADLS,ADLP}; + enum class Table : uint8_t {Invalid,TGLHBR,TGLHigh,TGLUYHigh,ICLHigh,LowSwing,HOBL,RKLHBR,RKLHigh,ADLSHigh,ADLSLow,ADLPHBR,ADLPHigh,ADLPLow}; + struct Coefficients {uint8_t swing,scalar,cursor,post2,post1;}; + Table table {Table::Invalid}; + Coefficients coefficients {}; + uint32_t level {}; + bool hobl {}; + static Table choose(Platform platform,uint32_t rate,bool panel,bool lowSwing,bool useHOBL) { + if(platform==Platform::Unknown || platform>Platform::ADLP || + (rate!=6 && rate!=10 && rate!=20 && rate!=30))return Table::Invalid; + if(panel) { + if(rate==30)return platform==Platform::ADLS?Table::ADLSHigh:platform==Platform::ADLP?Table::ADLPHigh:Table::ICLHigh; + if(useHOBL)return Table::HOBL; + if(lowSwing)return platform==Platform::ADLS?Table::ADLSLow:platform==Platform::ADLP?Table::ADLPLow:Table::LowSwing; + } + if(platform==Platform::ADLP)return rate>10?Table::ADLPHigh:Table::ADLPHBR; + if(platform==Platform::RKL)return rate>10?Table::RKLHigh:Table::RKLHBR; + if(rate<=10)return Table::TGLHBR; + return platform==Platform::ADLS?Table::ADLSHigh:platform==Platform::TGLUY?Table::TGLUYHigh:Table::TGLHigh; + } + static bool row(Table table,uint32_t index,Coefficients &out) { + out={}; + static const Coefficients TGLHBR[] { + {0x0A,0x32,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x2F,0x00,0x10}, + {0x06,0x7D,0x2B,0x00,0x14}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x6C,0x3C,0x00,0x03}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients TGLHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x63,0x2F,0x00,0x10}, + {0x06,0x7F,0x2B,0x00,0x14}, + {0x0A,0x47,0x3F,0x00,0x00}, + {0x0C,0x63,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x61,0x3C,0x00,0x03}, + {0x06,0x7B,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients TGLUYHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x36,0x00,0x09}, + {0x0C,0x60,0x32,0x00,0x0D}, + {0x0C,0x7F,0x2D,0x00,0x12}, + {0x0C,0x47,0x3F,0x00,0x00}, + {0x0C,0x6F,0x36,0x00,0x09}, + {0x06,0x7D,0x32,0x00,0x0D}, + {0x06,0x60,0x3C,0x00,0x03}, + {0x06,0x7F,0x34,0x00,0x0B}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ICLHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x2F,0x00,0x10}, + {0x06,0x7F,0x2B,0x00,0x14}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x6C,0x3C,0x00,0x03}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients LowSwing[] { + {0x00,0x7F,0x3F,0x00,0x00}, + {0x08,0x7F,0x38,0x00,0x07}, + {0x01,0x7F,0x33,0x00,0x0C}, + {0x09,0x7F,0x31,0x00,0x0E}, + {0x08,0x7F,0x3F,0x00,0x00}, + {0x01,0x7F,0x38,0x00,0x07}, + {0x09,0x7F,0x35,0x00,0x0A}, + {0x01,0x7F,0x3F,0x00,0x00}, + {0x09,0x7F,0x38,0x00,0x07}, + {0x09,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients HOBL[] { + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients RKLHBR[] { + {0x0A,0x2F,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x63,0x2F,0x00,0x10}, + {0x06,0x7D,0x2A,0x00,0x15}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x6E,0x3E,0x00,0x01}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients RKLHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x50,0x38,0x00,0x07}, + {0x0C,0x61,0x33,0x00,0x0C}, + {0x06,0x7F,0x2E,0x00,0x11}, + {0x0A,0x47,0x3F,0x00,0x00}, + {0x0C,0x5F,0x38,0x00,0x07}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x5F,0x3F,0x00,0x00}, + {0x06,0x7E,0x36,0x00,0x09}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLSHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x63,0x31,0x00,0x0E}, + {0x06,0x7F,0x2C,0x00,0x13}, + {0x0A,0x47,0x3F,0x00,0x00}, + {0x0C,0x63,0x37,0x00,0x08}, + {0x06,0x73,0x32,0x00,0x0D}, + {0x0C,0x58,0x3F,0x00,0x00}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLSLow[] { + {0x09,0x73,0x3D,0x00,0x02}, + {0x09,0x7A,0x3C,0x00,0x03}, + {0x09,0x7F,0x3B,0x00,0x04}, + {0x04,0x6C,0x33,0x00,0x0C}, + {0x02,0x73,0x3A,0x00,0x05}, + {0x02,0x7C,0x38,0x00,0x07}, + {0x04,0x5A,0x36,0x00,0x09}, + {0x04,0x57,0x3D,0x00,0x02}, + {0x04,0x65,0x38,0x00,0x07}, + {0x04,0x6C,0x3A,0x00,0x05}, + }; + static const Coefficients ADLPHBR[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x31,0x00,0x0E}, + {0x06,0x7F,0x2C,0x00,0x13}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x7C,0x3C,0x00,0x03}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLPHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x30,0x00,0x0F}, + {0x06,0x7F,0x2B,0x00,0x14}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x30,0x00,0x0F}, + {0x0C,0x63,0x3F,0x00,0x00}, + {0x06,0x7F,0x38,0x00,0x07}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLPLow[] { + {0x04,0x50,0x38,0x00,0x07}, + {0x04,0x58,0x35,0x00,0x0A}, + {0x04,0x60,0x34,0x00,0x0B}, + {0x04,0x6A,0x32,0x00,0x0D}, + {0x04,0x5E,0x38,0x00,0x07}, + {0x04,0x61,0x36,0x00,0x09}, + {0x04,0x6B,0x34,0x00,0x0B}, + {0x04,0x69,0x39,0x00,0x06}, + {0x04,0x73,0x37,0x00,0x08}, + {0x04,0x7A,0x38,0x00,0x07}, + }; + const Coefficients *entries=nullptr;uint32_t count=0; + switch(table) { + case Table::TGLHBR:entries=TGLHBR;count=10;break; + case Table::TGLHigh:entries=TGLHigh;count=10;break; + case Table::TGLUYHigh:entries=TGLUYHigh;count=10;break; + case Table::ICLHigh:entries=ICLHigh;count=10;break; + case Table::LowSwing:entries=LowSwing;count=10;break; + case Table::HOBL:entries=HOBL;count=9;break; + case Table::RKLHBR:entries=RKLHBR;count=10;break; + case Table::RKLHigh:entries=RKLHigh;count=10;break; + case Table::ADLSHigh:entries=ADLSHigh;count=10;break; + case Table::ADLSLow:entries=ADLSLow;count=10;break; + case Table::ADLPHBR:entries=ADLPHBR;count=10;break; + case Table::ADLPHigh:entries=ADLPHigh;count=10;break; + case Table::ADLPLow:entries=ADLPLow;count=10;break; + default:return false; + } + if(index>=count)return false; + out=entries[index];return true; + } + bool select(Platform platform,uint32_t rate,bool panel,bool lowSwing,bool useHOBL,uint32_t voltage,uint32_t emphasis) { + *this={}; + if(voltage>3 || emphasis>3 || voltage+emphasis>3)return false; + const auto chosen=choose(platform,rate,panel,lowSwing,useHOBL); + if(chosen==Table::Invalid)return false; + static const uint8_t first[]={0,4,7,9}; + const auto requested=first[voltage]+emphasis; + // Linux intel_ddi_level clamps to the final table entry. HOBL has nine + // identical entries and no distinct voltage-3 row. + level=chosen==Table::HOBL && requested>=9?8:requested; + if(!row(chosen,level,coefficients)){*this={};return false;} + table=chosen;hobl=chosen==Table::HOBL;return true; + } +}; +} +#endif diff --git a/tests/xelp_combo_policy.cpp b/tests/xelp_combo_policy.cpp new file mode 100644 index 00000000..478f0e12 --- /dev/null +++ b/tests/xelp_combo_policy.cpp @@ -0,0 +1,44 @@ +#include "kern_xelp_combo_policy.hpp" +#include +#include +#include +using Policy=XELP::ComboPolicy;using Platform=Policy::Platform;using Table=Policy::Table; +int main() { + // FNV-1a of all five-byte rows in Linux v6.12 intel_ddi_buf_trans.c, + // in Table enum order. This also checks the post_cursor_2 field. + const uint32_t expected[]={0x9C7E3F56U,0x004ED0CBU,0x49762BB3U,0x16ACF609U,0xEB4FB3B0U,0xBE6E1ADFU,0x231944E5U,0x67165B6FU,0x52DE18D6U,0x2DFE14A9U,0x4A81F931U,0xE7F29A4EU,0x9C126509U}; + for(unsigned table=1;table<=13;++table) { + uint32_t hash=2166136261U;const unsigned count=table==6?9:10; + for(unsigned i=0;i10]; + if(panel) { + if(rate==30)want=platform==Platform::ADLS?Table::ADLSHigh:platform==Platform::ADLP?Table::ADLPHigh:Table::ICLHigh; + else if(hobl)want=Table::HOBL; + else if(low)want=platform==Platform::ADLS?Table::ADLSLow:platform==Platform::ADLP?Table::ADLPLow:Table::LowSwing; + } + assert(p.table==want&&p.hobl==(want==Table::HOBL)); + const unsigned levels[][4]={{0,1,2,3},{4,5,6,0},{7,8,0,0},{9,0,0,0}}; + assert(p.level==(want==Table::HOBL&&vs==3?8:levels[vs][pre]));++cases; + } + for(auto rate:{0U,12U,19U,31U,~0U}){Policy p;assert(!p.select(Platform::TGL,rate,true,false,false,0,0));} + for(auto platform:{Platform::Unknown,Platform(255)}){Policy p;assert(!p.select(platform,10,true,false,false,0,0));} + Policy p;assert(p.select(Platform::ADLS,20,false,false,false,0,0)); + assert(!p.select(Platform::ADLS,20,false,false,false,4,0)&&p.table==Table::Invalid); + std::printf("PASS 129 Linux coefficient rows and %u platform/protocol/rate/panel/training selections\n",cases); +} From 4b97b60aeaa87b067b20cb020ba2f9470587647d Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:49:36 +0900 Subject: [PATCH 16/58] Clear Combo loadgen at HBR3 link rates --- WhateverXe/kern_tgl_combo_signal.hpp | 9 +++++---- tests/tgl_combo_signal.cpp | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index 9e528d35..19552c32 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -24,7 +24,7 @@ #include "kern_alderlake_s.hpp" #include "kern_xelp_combo_policy.hpp" struct TGLComboSignal { - uint32_t base {},lanes {},level {},swing {},scalar {},cursor {},post1 {}; + uint32_t base {},lanes {},linkRate {},level {},swing {},scalar {},cursor {},post1 {}; bool low {}; bool select(uint32_t ddi,uint32_t count,uint32_t rate,bool lowSwing,bool hobl, uint32_t voltage,uint32_t emphasis) { @@ -35,7 +35,7 @@ struct TGLComboSignal { voltage>3 || emphasis>3 || voltage+emphasis>3)return false; XELP::ComboPolicy policy; if(!policy.select(XELP::ComboPolicy::Platform::TGL,rate,true,lowSwing,false,voltage,emphasis))return false; - base=ddi?0x6C000:0x162000;lanes=count;low=lowSwing;assign(policy);return true; + base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=lowSwing;assign(policy);return true; } // The platform adapter chooses coordinates; electrical policy contains no // physical DDI, donor field or board-specific connector condition. @@ -45,7 +45,7 @@ struct TGLComboSignal { if(!AlderLakeS::Port::resolve(ddi,route) || !(AlderLakeS::ComboBackendMask&(1U< regs; + // HBR3 must clear even inherited loadgen on every lane (Linux >600 MHz). + for(unsigned n=0;n<4;++n)regs[s.base+0x890+0x100*n]=0x80000000U; assert(s.program([&](unsigned r){return regs[r];},[&](unsigned r,unsigned value){ assert(r>=s.base&&r Date: Fri, 23 Oct 2026 21:52:38 +0900 Subject: [PATCH 17/58] Select TGL panel signal policy from physical subplatform and link rate --- README.md | 1 + README_ja.md | 1 + WhateverXe/kern_tgl_combo_signal.hpp | 14 +++++++------- WhateverXe/kern_xe.cpp | 3 ++- WhateverXe/kern_xelp_combo_policy.hpp | 11 +++++++++++ tests/tgl_combo_signal.cpp | 6 +++--- tests/tgl_combo_signal_guard.py | 27 ++++++++++++++++++++++++--- tests/xelp_combo_policy.cpp | 7 +++++++ 8 files changed, 56 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ccfec36b..0f56cae2 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ installation or update location. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not receive an image. +- TGL eDP signal selection covers RBR through HBR3 using the physical GPU subplatform and VBT panel flags. HOBL training below HBR3 remains donor-managed. High-rate selection has CPU coverage; the hardware results above do not qualify every link rate. - Full system sleep/wake and resolutions above those listed above are unverified. USB-C display output currently prevents part of the display hardware from entering a low-power state. diff --git a/README_ja.md b/README_ja.md index 0dc617f8..3fc86725 100644 --- a/README_ja.md +++ b/README_ja.md @@ -115,6 +115,7 @@ Safariのインストール先や更新による配置変更に依存せずData - DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでは、上記の抜き差し試験を通過しています。 - Safariの動画再生で、ときどきコマ落ちが発生します。 - 同時出力は最大3画面です。4画面目には映像が出ません。 +- TGL eDPの信号設定は、物理GPUのsubplatformとVBTのpanel情報からRBR〜HBR3を選択します。HBR3未満のHOBL訓練は引き続きdonorが管理します。高速度の選択はCPU試験済みですが、上記の実機結果は全link rateの動作保証ではありません。 - システム全体のスリープ/復帰と、上記を超える解像度は未検証です。USB-C映像出力では現在、表示回路の一部を省電力状態へ移行させずに動作させています。 ## ロードマップ diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index 19552c32..5a3f20e8 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -27,14 +27,14 @@ struct TGLComboSignal { uint32_t base {},lanes {},linkRate {},level {},swing {},scalar {},cursor {},post1 {}; bool low {}; bool select(uint32_t ddi,uint32_t count,uint32_t rate,bool lowSwing,bool hobl, - uint32_t voltage,uint32_t emphasis) { - // Normal HBR2 has a separate U/Y-family table. Leave that unreviewed - // path, HOBL and HBR3 with the donor until their contracts are covered. - if(ddi>1 || (count!=1 && count!=2 && count!=4) || hobl || - (rate!=6 && rate!=10 && !(lowSwing && rate==20)) || - voltage>3 || emphasis>3 || voltage+emphasis>3)return false; + uint32_t voltage,uint32_t emphasis, + XELP::ComboPolicy::Platform platform=XELP::ComboPolicy::Platform::TGL) { + // HOBL needs a training-failure retry policy, so keep that native + // operation intact. At HBR3 Linux ignores both panel swing flags. + if(ddi>1 || (count!=1 && count!=2 && count!=4) || (hobl && rate!=30) || + (platform!=XELP::ComboPolicy::Platform::TGL && platform!=XELP::ComboPolicy::Platform::TGLUY))return false; XELP::ComboPolicy policy; - if(!policy.select(XELP::ComboPolicy::Platform::TGL,rate,true,lowSwing,false,voltage,emphasis))return false; + if(!policy.select(platform,rate,true,lowSwing,false,voltage,emphasis))return false; base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=lowSwing;assign(policy);return true; } // The platform adapter chooses coordinates; electrical policy contains no diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 7e800581..119ad2f0 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -16571,7 +16571,8 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void const auto lanes=*reinterpret_cast(p+0x124); const auto rate=*reinterpret_cast(p+0x128); if(!(adls ? selected.selectADLS(config[0],lanes,rate,swing,emphasis): - selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis)))return false; + selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis, + XELP::ComboPolicy::tigerLake(self->xeProfile.identity))))return false; // The native fields identify this very bank. No guessed framebuffer index // or cross-port reads; power must already belong to the training caller. if(*reinterpret_cast(p+0x620)!=selected.base+0x804 || diff --git a/WhateverXe/kern_xelp_combo_policy.hpp b/WhateverXe/kern_xelp_combo_policy.hpp index 762712e6..bf73a92c 100644 --- a/WhateverXe/kern_xelp_combo_policy.hpp +++ b/WhateverXe/kern_xelp_combo_policy.hpp @@ -28,6 +28,17 @@ struct ComboPolicy { Coefficients coefficients {}; uint32_t level {}; bool hobl {}; + // Linux TGL U/Y is the physical GT2 ID set, not WEX's render backend. + // In particular 9A78 uses U/Y electrical values despite its G4 backend. + static Platform tigerLake(uint32_t physicalIdentity) { + if((physicalIdentity&0xFFFF)!=0x8086)return Platform::Unknown; + switch(physicalIdentity>>16) { + case 0x9A60:case 0x9A68:case 0x9A70:return Platform::TGL; + case 0x9A40:case 0x9A49:case 0x9A59:case 0x9A78: + case 0x9AC0:case 0x9AC9:case 0x9AD9:case 0x9AF8:return Platform::TGLUY; + default:return Platform::Unknown; + } + } static Table choose(Platform platform,uint32_t rate,bool panel,bool lowSwing,bool useHOBL) { if(platform==Platform::Unknown || platform>Platform::ADLP || (rate!=6 && rate!=10 && rate!=20 && rate!=30))return Table::Invalid; diff --git a/tests/tgl_combo_signal.cpp b/tests/tgl_combo_signal.cpp index 14b780ab..0a106376 100644 --- a/tests/tgl_combo_signal.cpp +++ b/tests/tgl_combo_signal.cpp @@ -8,8 +8,8 @@ int main() { for(unsigned rate:{6,10,20,30})for(bool low:{false,true})for(bool hobl:{false,true}) for(unsigned vs=0;vs<5;++vs)for(unsigned pre=0;pre<5;++pre) { TGLComboSignal s; - bool valid=ddi<2&&(lanes==1||lanes==2||lanes==4)&&!hobl&& - (rate==6||rate==10||(low&&rate==20))&&vs<4&&pre<4&&vs+pre<=3; + bool valid=ddi<2&&(lanes==1||lanes==2||lanes==4)&&(!hobl||rate==30)&& + (rate==6||rate==10||rate==20||rate==30)&&vs<4&&pre<4&&vs+pre<=3; assert(s.select(ddi,lanes,rate,low,hobl,vs,pre)==valid); if(!valid)continue; std::map regs; @@ -30,7 +30,7 @@ int main() { for(unsigned n=0;n<4;++n) { auto value=regs[s.base+0x890+0x100*n]; assert((value&0x7FFC0000)==0x12340000); - assert(bool(value&0x80000000)==(n==1||n==2||(lanes==4&&n==3))); + assert(bool(value&0x80000000)==(rate!=30&&(n==1||n==2||(lanes==4&&n==3)))); } // Missing bank declines before the first write. writes.clear();regs[s.base+0xA9C]=~0U;assert(!s.program(read,write)&&writes.empty()); diff --git a/tests/tgl_combo_signal_guard.py b/tests/tgl_combo_signal_guard.py index cde539f9..ccdd5415 100644 --- a/tests/tgl_combo_signal_guard.py +++ b/tests/tgl_combo_signal_guard.py @@ -21,7 +21,7 @@ struct State { bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr;bool tglActive=true,tglComboSignalReady=true; - struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; + struct {uint32_t identity=0x9A498086;XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts; } state,*callbackXE=&state; constexpr uint32_t kIOReturnNotReady=0xe00002d8; @@ -36,7 +36,7 @@ void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==73);++records;} FUNCTION int main(){ - for(unsigned bad=0;bad<22;++bad){ + for(unsigned bad=0;bad<23;++bad){ alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{};uint32_t config[8]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; state={};state.tglObservationController=ctl;state.firmwarePorts.status=XELP::VBT::Status::Valid; @@ -57,13 +57,14 @@ if(bad==10)state.firmwarePorts.port[0].flags=2; if(bad==11)store(p,0x578,ctl+0); if(bad==12)store(p,0x124,3U); - if(bad==13)store(p,0x128,20U); + if(bad==13)store(p,0x128,12U); if(bad==14)state.firmwarePorts.panel.hobl=true; if(bad==15)store(p,0x620,0x6C804U); if(bad==16)store(access,0x60,0x100000U); if(bad==17)registers[0x45404]=0; if(bad==18)registers[0x45454]=~0U; if(bad==19)registers[0x162A9C]=~0U; + if(bad==22)state.xeProfile.identity=0xFFFF8086; uint32_t result=99; bool applied=run(bad==20?nullptr:p,0,0,bad==21?p:nullptr,result); assert(applied==(bad==0||bad==19));assert((writes!=0)==(bad==0)); @@ -71,6 +72,26 @@ if(bad==19)assert(result==kIOReturnNotReady); if(bad!=0&&bad!=17&&bad!=18&&bad!=19)assert(reads==0); } + + // Known physical subplatforms must select electrical policy independently + // of a render backend. HBR2 U/Y differs at voltage0/emphasis1; HBR3 eDP + // uses the ICL table regardless of VBT low-swing/HOBL flags. + for(unsigned id:{0x9A498086u,0x9A788086u,0x9A608086u})for(unsigned rate:{20u,30u}) + for(bool low:{false,true})for(bool hobl:{false,true}) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{};uint32_t config[8]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.xeProfile.identity=id;state.tglObservationController=ctl; + state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.panel={true,low,hobl,0,0}; + store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,4U);store(p,0x128,rate);store(p,0x620,0x162804U);store(p,0x5C8,0x162028U); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=3;registers[0x45454]=3;reads=writes=records=0; + uint32_t result=99;assert(run(p,0,1,nullptr,result)==(!hobl||rate==30)); + if(hobl&&rate!=30){assert(!writes);continue;} + assert(result==0); + const unsigned post=rate==30?8:low?7:id==0x9A608086?8:9; + assert(((registers[0x162890]>>12)&63)==post); + } for(unsigned ddi:{0u,3u,4u})for(unsigned bad=0;bad<12;++bad) { alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; diff --git a/tests/xelp_combo_policy.cpp b/tests/xelp_combo_policy.cpp index 478f0e12..79b1d87e 100644 --- a/tests/xelp_combo_policy.cpp +++ b/tests/xelp_combo_policy.cpp @@ -4,6 +4,13 @@ #include using Policy=XELP::ComboPolicy;using Platform=Policy::Platform;using Table=Policy::Table; int main() { + for(auto id:{0x9A40,0x9A49,0x9A59,0x9A78,0x9AC0,0x9AC9,0x9AD9,0x9AF8}) + assert(XELP::ComboPolicy::tigerLake((uint32_t(id)<<16)|0x8086)==XELP::ComboPolicy::Platform::TGLUY); + for(auto id:{0x9A60,0x9A68,0x9A70}) + assert(XELP::ComboPolicy::tigerLake((uint32_t(id)<<16)|0x8086)==XELP::ComboPolicy::Platform::TGL); + assert(XELP::ComboPolicy::tigerLake(0x46908086)==XELP::ComboPolicy::Platform::Unknown); + assert(XELP::ComboPolicy::tigerLake(0x9A491234)==XELP::ComboPolicy::Platform::Unknown); + // FNV-1a of all five-byte rows in Linux v6.12 intel_ddi_buf_trans.c, // in Table enum order. This also checks the post_cursor_2 field. const uint32_t expected[]={0x9C7E3F56U,0x004ED0CBU,0x49762BB3U,0x16ACF609U,0xEB4FB3B0U,0xBE6E1ADFU,0x231944E5U,0x67165B6FU,0x52DE18D6U,0x2DFE14A9U,0x4A81F931U,0xE7F29A4EU,0x9C126509U}; From 8b650326e9e2bacb046c519e63076887f3a5ea7f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:59:20 +0900 Subject: [PATCH 18/58] Close display startup dependencies over qualified output routes --- WhateverXe/kern_xe.cpp | 126 +++++++++++++---- WhateverXe/kern_xe.hpp | 5 +- WhateverXe/kern_xelp_display_contract.hpp | 160 ++++++++++++++++++++++ tests/display_contract_adapter.py | 54 ++++++++ tests/xelp_display_contract.cpp | 49 +++++++ tools/test.py | 2 +- 6 files changed, 366 insertions(+), 30 deletions(-) create mode 100644 WhateverXe/kern_xelp_display_contract.hpp create mode 100644 tests/display_contract_adapter.py create mode 100644 tests/xelp_display_contract.cpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 119ad2f0..5163a7cd 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4952,12 +4952,11 @@ 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); - display13FBPowerBackendReady=display13FBPowerReferencesReady && display13FBEngineEnableHookReady && - display13FBLifecycleHooksReady && display13FBDBUFHooksReady && display13FBWatermarkHooksReady; - display13FBClockReady=display13FBClockReadoutHooksReady && display13FBClockSetHookReady && display13FBLifecycleHooksReady; - display13FBMemoryReady=display13FBMemoryHooksReady && display13FBGTTHooksReady && display13FBABIReady && display13FBBootHandoffHookReady; - const bool backend=display13FBPowerBackendReady && display13FBClockReady && display13FBMemoryReady && - display13FBIRQReady && display13FBWatermarkLayoutReady && display13FBOutputHooksReady && display13FBTopologyHooksReady; + using C=XELP::DisplayContract;const auto installed=display13Available(); + display13FBPowerBackendReady=C{C::Power13,installed}.ready(); + display13FBClockReady=C{C::Clock13,installed}.ready(); + display13FBMemoryReady=C{C::MemoryGroup13,installed}.ready(); + const bool backend=C::display13(installed).ready(); IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferBackendReady",backend); return true; // Installed routes; actual startup/scanout still require runtime checks. } @@ -6561,12 +6560,12 @@ bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32 bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); - const bool ready=s->adlnDetected && s->adlnKernelReady && s->display13FBPowerReferencesReady && - s->display13FBPowerBackendReady && s->display13FBClockReadoutHooksReady && s->display13FBClockSetHookReady && - s->display13FBClockReady && s->display13FBIRQReady && s->display13FBMemoryHooksReady && - s->display13FBEngineEnableHookReady && s->display13FBLifecycleHooksReady && s->display13FBDBUFHooksReady && s->display13FBWatermarkLayoutReady && s->display13FBWatermarkHooksReady && s->display13FBOutputHooksReady && - s->display13FBBootHandoffHookReady && s->display13FBGTTHooksReady && s->display13FBMemoryReady && s->display13FBABIReady && s->display13FBTopologyHooksReady && - pci && xelpAlderLakeNDevice(pci); + const auto contract=XELP::DisplayContract::display13(s->display13Available()); + const bool ready=s->adlnDetected && s->adlnKernelReady && contract.ready() && pci && xelpAlderLakeNDevice(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; auto property=provider->getProperty("xelp-hdmi-wiring"); @@ -10920,6 +10919,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t KernelPatcher::RouteRequest hpdRequest("__ZN17AppleIntelPortHAL11getHPDStateEv", wrapTGLHPDState, orgTGLHPDState); const bool hpdOK = patcher.routeMultiple(index, &hpdRequest, 1, address, size); + tglHPDHookReady=hpdOK; IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGHPDObservationRouted", hpdOK); patcher.clearError(); KernelPatcher::RouteRequest laneRequest("__ZN17AppleIntelPortHAL26getDklPhyLaneConfigBitMaskEv", @@ -10998,11 +10998,10 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t } IORegistryEntry::getRegistryRoot()->setProperty("ADLSLanePowerReady",adlsPowerReady); // Native startup remains closed if any required ADL-S adapter is absent. - adlsDisplayHooksReady=ready && adlsPowerReady && adlsPortInitReady && orgTGLEnableCombo && tglPortByDDI && - orgTGLGetOSInformation && orgTGLRegisterWrite && orgTGLRegisterRead && hpdOK && - (!adlsWiring.dp || (tglTrainingLeaseReady && tglSinkFramingReady && tglComboSignalReady && - tglComboAUXLifetimeReady && tglAUXParentReady && tglTCNativeFunctions.complete() && - tglModeResultReady && tglNativeLegacyHPDCaller)); + const auto contract=XELP::DisplayContract::adls(display12Available(),adlsWiring.dp!=0); + adlsDisplayHooksReady=contract.ready(); + uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayStartupContract",contractRecord,sizeof(contractRecord)); IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); } tglRecordStartOrder(1,nullptr); @@ -11135,6 +11134,71 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiring",q,sizeof(q));return admitted; } +XELP::DisplayContract::Mask XE::display12Available() const { + using C=XELP::DisplayContract;C::Mask a=0; + if(tglSonomaSurfaceFramebufferReady)a|=C::Surface; + if(tglPlatformNVRAMReady)a|=C::NVRAM; + if(tglFBIRQReady)a|=C::IRQ; + if(tglProbeReady)a|=C::Probe; + if(tglFramebufferInitReady)a|=C::Framebuffer; + if(tglTopologyReady)a|=C::Topology; + if(tglPanelPowerPropertyReady)a|=C::PanelPower; + if(tglEDPTranscoderReady)a|=C::PanelTranscoder; + if(tglDekelReady)a|=C::Dekel; + if(tglGMBusReady)a|=C::GMBus; + if(tglTrainingLeaseReady)a|=C::Lease; + if(tglEnablePG)a|=C::PowerGet; + if(tglDisablePG)a|=C::PowerPut; + if(tglPortByDDI)a|=C::PortLookup; + if(orgTGLPathLookup)a|=C::PathLookup; + if(orgTGLAddPath)a|=C::PathAdd; + if(orgTGLFreePath)a|=C::PathFree; + if(orgTGLTrainingEnd)a|=C::TrainingEnd; + if(orgTGLTraining)a|=C::Training; + if(orgTGLTrainingBegin)a|=C::TrainingBegin; + if(orgTGLAllocatePathResources)a|=C::PathAllocate; + if(tglSinkFramingReady)a|=C::Framing; + if(tglComboSignalReady)a|=C::ComboSignal; + if(tglComboAUXLifetimeReady)a|=C::ComboAUX; + if(tglAUXParentReady)a|=C::AUXParent; + if(tglTCNativeFunctions.complete())a|=C::TCNative; + if(tglModeResultReady)a|=C::Mode; + if(tglNativeLegacyHPDCaller)a|=C::HPDCaller; + if(tglDPAltFiaReady)a|=C::AltFIA; + if(tglNativeFiaReady)a|=C::NativeFIA; + if(tglModeTransitionLock)a|=C::ModeLock; + if(adlsClockReady)a|=C::ClockAdapter; + if(adlsPowerReady)a|=C::PowerAdapter; + if(adlsPortInitReady)a|=C::PortInit; + if(orgTGLEnableCombo)a|=C::EnableCombo; + if(orgTGLGetOSInformation)a|=C::OSInformation; + if(orgTGLRegisterRead)a|=C::MMIORead; + if(orgTGLRegisterWrite)a|=C::MMIOWrite; + if(tglHPDHookReady)a|=C::HPD; + if(tglClockOffCaller)a|=C::ClockOrder; + return a; +} + +XELP::DisplayContract::Mask XE::display13Available() const { + using C=XELP::DisplayContract;C::Mask a=0; + if(display13FBPowerReferencesReady)a|=C::PowerRefs13; + if(display13FBEngineEnableHookReady)a|=C::Engine13; + if(display13FBLifecycleHooksReady)a|=C::Lifecycle13; + if(display13FBDBUFHooksReady)a|=C::DBUF13; + if(display13FBWatermarkHooksReady)a|=C::WM13; + if(display13FBClockReadoutHooksReady)a|=C::ClockRead13; + if(display13FBClockSetHookReady)a|=C::ClockSet13; + if(display13FBMemoryHooksReady)a|=C::Memory13; + if(display13FBGTTHooksReady)a|=C::GTT13; + if(display13FBABIReady)a|=C::ABI13; + if(display13FBBootHandoffHookReady)a|=C::Handoff13; + if(display13FBIRQReady)a|=C::IRQ13; + if(display13FBWatermarkLayoutReady)a|=C::WMLayout13; + if(display13FBOutputHooksReady)a|=C::Output13; + if(display13FBTopologyHooksReady)a|=C::Topology13; + return a; +} + bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady)) { that->setProperty("ADLSFramebufferPrerequisitesMissing",true);return false; @@ -11171,17 +11235,23 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(!tglLoadHDMIWiring(provider)) { that->setProperty("TGLStartBlockedByHDMIWiring",true);return false; } - if (!tglSonomaSurfaceFramebufferReady || !callbackXE->tglPlatformNVRAMReady || !callbackXE->tglFBIRQReady || !callbackXE->tglProbeReady || !callbackXE->tglFramebufferInitReady || !callbackXE->tglDekelReady || - (callbackXE->tglHDMIB && !callbackXE->tglGMBusReady) || - !callbackXE->tglTopologyReady || !callbackXE->tglPanelPowerPropertyReady || !callbackXE->tglEDPTranscoderReady || - (callbackXE->tglTrainingLeaseRequested && (!callbackXE->tglTrainingLeaseReady || - !callbackXE->tglEnablePG || !callbackXE->tglDisablePG || - !callbackXE->tglPortByDDI || !callbackXE->orgTGLPathLookup || - !callbackXE->orgTGLAddPath || !callbackXE->orgTGLFreePath || !callbackXE->orgTGLTrainingEnd || - !callbackXE->orgTGLTraining || !callbackXE->orgTGLTrainingBegin || !callbackXE->orgTGLAllocatePathResources))) { - IORegistryEntry::getRegistryRoot()->setProperty("TGLFramebufferStartBlockedByVtable", true); - return false; - } + // Evaluate firmware candidates, not only currently registered/live ports: + // a hotplug route must have the same operations available as a boot route. + auto self=callbackXE; + XELP::DisplayContract::Routes routes; + routes.hdmi=self->tglHDMIB;routes.panel=self->firmwarePorts.panel.valid; + routes.combo=self->adlsDetected && self->adlsWiring.dp; + routes.legacy=self->tglTCWiring.legacy || self->tglTCAutoCandidates; + routes.alt=self->tglDPAltCandidates;routes.leaseRequested=self->tglTrainingLeaseRequested; + const auto contract=XELP::DisplayContract::display12(self->display12Available(),routes); + 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","Display12 missing operation %s",XELP::DisplayContract::name(bit)); + IORegistryEntry::getRegistryRoot()->setProperty("TGLFramebufferStartBlockedByVtable",true); + return false; + } // r88: use the native provider-workloop mode so IRQ registration does not // depend on a WindowServer framebuffer open after accelerator startup. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index b9113995..f150dc31 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -17,6 +17,7 @@ #include "kern_vbt.hpp" #include "kern_alderlake_s.hpp" #include "kern_xelp_donor.hpp" +#include "kern_xelp_display_contract.hpp" #include "kern_dvmt.hpp" #include @@ -58,6 +59,8 @@ class XE { bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); private: + XELP::DisplayContract::Mask display12Available() const; + XELP::DisplayContract::Mask display13Available() const; // Do not inline either helper into the small kext-load dispatcher. __attribute__((noinline)) bool processAlderLakeNLoadOnly(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); __attribute__((noinline)) bool processAlderLakeNRejectedAccelerator(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); @@ -77,7 +80,7 @@ class XE { bool adlnRequested {false}; bool adlnDetected {false}; bool adlsDetected {},adlsNativeReady {},adlsDisplayHooksReady {}; - bool adlsPortInitReady {}; + bool adlsPortInitReady {},tglHPDHookReady {}; bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {}; mach_vm_address_t orgADLSPowerPG {}; static uint8_t *adlsPowerTarget(void *power); diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp new file mode 100644 index 00000000..83f79195 --- /dev/null +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -0,0 +1,160 @@ +// Startup contracts describe installed operations, separately from physical +// GPU admission, firmware wiring and runtime resource ownership. +#ifndef KERN_XELP_DISPLAY_CONTRACT_HPP +#define KERN_XELP_DISPLAY_CONTRACT_HPP +#include +namespace XELP { +struct DisplayContract { + using Mask=uint64_t; + enum Operation:Mask { + Surface=1ULL<<0, + NVRAM=1ULL<<1, + IRQ=1ULL<<2, + Probe=1ULL<<3, + Framebuffer=1ULL<<4, + Topology=1ULL<<5, + PanelPower=1ULL<<6, + PanelTranscoder=1ULL<<7, + Dekel=1ULL<<8, + GMBus=1ULL<<9, + Lease=1ULL<<10, + PowerGet=1ULL<<11, + PowerPut=1ULL<<12, + PortLookup=1ULL<<13, + PathLookup=1ULL<<14, + PathAdd=1ULL<<15, + PathFree=1ULL<<16, + TrainingEnd=1ULL<<17, + Training=1ULL<<18, + TrainingBegin=1ULL<<19, + PathAllocate=1ULL<<20, + Framing=1ULL<<21, + ComboSignal=1ULL<<22, + ComboAUX=1ULL<<23, + AUXParent=1ULL<<24, + TCNative=1ULL<<25, + Mode=1ULL<<26, + HPDCaller=1ULL<<27, + AltFIA=1ULL<<28, + NativeFIA=1ULL<<29, + ModeLock=1ULL<<30, + ClockAdapter=1ULL<<31, + PowerAdapter=1ULL<<32, + PortInit=1ULL<<33, + EnableCombo=1ULL<<34, + OSInformation=1ULL<<35, + MMIORead=1ULL<<36, + MMIOWrite=1ULL<<37, + HPD=1ULL<<38, + PowerRefs13=1ULL<<39, + Engine13=1ULL<<40, + Lifecycle13=1ULL<<41, + DBUF13=1ULL<<42, + WM13=1ULL<<43, + ClockRead13=1ULL<<44, + ClockSet13=1ULL<<45, + Memory13=1ULL<<46, + GTT13=1ULL<<47, + ABI13=1ULL<<48, + Handoff13=1ULL<<49, + IRQ13=1ULL<<50, + WMLayout13=1ULL<<51, + Output13=1ULL<<52, + Topology13=1ULL<<53, + ClockOrder=1ULL<<54, + }; + 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 Training12=Lease|PowerGet|PowerPut|PortLookup|PathLookup|PathAdd|PathFree|TrainingEnd|Training|TrainingBegin|PathAllocate|ClockOrder; + static constexpr Mask Combo12=Framing|ComboSignal|ComboAUX; + 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 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; + struct Routes { + bool hdmi {},panel {},combo {},legacy {},alt {},leaseRequested {}; + }; + static DisplayContract display12(Mask installed,const Routes &r) { + // These donor startup hooks are controller-wide even without a panel or + // connected Dekel sink. Route-specific operations are added below. + Mask required=Base12; + if(r.hdmi)required|=GMBus; + if(r.leaseRequested||r.combo||r.legacy||r.alt)required|=Training12; + if(r.panel||r.combo)required|=Combo12; + if(r.combo||r.legacy)required|=Fixed12; + if(r.legacy||r.alt)required|=Framing|ModeLock; + if(r.alt)required|=Alt12; + return {required,installed}; + } + static DisplayContract adls(Mask installed,bool dp) { + return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0),installed}; + } + static DisplayContract display13(Mask installed) {return {Backend13,installed};} + static const char *name(Mask bit) { + switch(bit) { + case Surface:return "Surface"; + case NVRAM:return "NVRAM"; + case IRQ:return "IRQ"; + case Probe:return "Probe"; + case Framebuffer:return "Framebuffer"; + case Topology:return "Topology"; + case PanelPower:return "PanelPower"; + case PanelTranscoder:return "PanelTranscoder"; + case Dekel:return "Dekel"; + case GMBus:return "GMBus"; + case Lease:return "Lease"; + case PowerGet:return "PowerGet"; + case PowerPut:return "PowerPut"; + case PortLookup:return "PortLookup"; + case PathLookup:return "PathLookup"; + case PathAdd:return "PathAdd"; + case PathFree:return "PathFree"; + case TrainingEnd:return "TrainingEnd"; + case Training:return "Training"; + case TrainingBegin:return "TrainingBegin"; + case PathAllocate:return "PathAllocate"; + case Framing:return "Framing"; + case ComboSignal:return "ComboSignal"; + case ComboAUX:return "ComboAUX"; + case AUXParent:return "AUXParent"; + case TCNative:return "TCNative"; + case Mode:return "Mode"; + case HPDCaller:return "HPDCaller"; + case AltFIA:return "AltFIA"; + case NativeFIA:return "NativeFIA"; + case ModeLock:return "ModeLock"; + case ClockAdapter:return "ClockAdapter"; + case PowerAdapter:return "PowerAdapter"; + case PortInit:return "PortInit"; + case EnableCombo:return "EnableCombo"; + case OSInformation:return "OSInformation"; + case MMIORead:return "MMIORead"; + case MMIOWrite:return "MMIOWrite"; + case HPD:return "HPD"; + case PowerRefs13:return "PowerRefs13"; + case Engine13:return "Engine13"; + case Lifecycle13:return "Lifecycle13"; + case DBUF13:return "DBUF13"; + case WM13:return "WM13"; + case ClockRead13:return "ClockRead13"; + case ClockSet13:return "ClockSet13"; + case Memory13:return "Memory13"; + case GTT13:return "GTT13"; + case ABI13:return "ABI13"; + case Handoff13:return "Handoff13"; + case IRQ13:return "IRQ13"; + case WMLayout13:return "WMLayout13"; + case Output13:return "Output13"; + case Topology13:return "Topology13"; + case ClockOrder:return "ClockOrder"; + default:return "Unknown"; + } + } +}; +} +#endif diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py new file mode 100644 index 00000000..6795879f --- /dev/null +++ b/tests/display_contract_adapter.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Exercise production hook inventory and route-to-contract adapter, without IOKit.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +methods=[re.search(r'XELP::DisplayContract::Mask XE::display'+n+r'Available\(\) const \{.*?\n\}',s,re.S).group().replace('XE::','') for n in ('12','13')] +fields=re.findall(r' if\((\w+)\)a\|=', '\n'.join(methods)) +# Real callback invocation inside start, through to the immutable contract. +begin=s.index(' // Evaluate firmware candidates, not only currently registered/live ports:') +end=s.index(' uint64_t contractRecord[]',begin) +adapter=s[begin:end].replace('auto self=callbackXE;','auto self=this;')+'return contract;' +code=r''' +#include "kern_xelp_display_contract.hpp" +#include +#include +using C=XELP::DisplayContract; +struct State { +FIELDS + struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; + bool tglHDMIB=false,adlsDetected=false,tglTrainingLeaseRequested=true; + struct {struct {bool valid=false;}panel;}firmwarePorts; + struct {unsigned dp=0;}adlsWiring; + struct {unsigned legacy=0;}tglTCWiring; + unsigned tglTCAutoCandidates=0,tglDPAltCandidates=0; +METHODS + C start() const {ADAPTER} +}; +int main() { + State s; + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder)); + assert(s.display13Available()==C::Backend13); + // 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()); + s.tglSinkFramingReady=false;assert(s.start().missing()==C::Framing);s.tglSinkFramingReady=true; + s.tglAUXParentReady=false;assert(s.start().missing()==C::AUXParent);s.tglAUXParentReady=true; + s.tglModeTransitionLock=false;assert(s.start().missing()==C::ModeLock);s.tglModeTransitionLock=true; + s.tglClockOffCaller=false;assert(s.start().missing()==C::ClockOrder);s.tglClockOffCaller=true; + s.tglTCAutoCandidates=0;s.tglDPAltCandidates=8; + s.tglDPAltFiaReady=false;assert(s.start().missing()==C::AltFIA);s.tglDPAltFiaReady=true; + s.tglAUXParentReady=false;assert(s.start().ready()); // TC owns DP-alt AUX power. + s.tglAUXParentReady=true;s.tglDPAltCandidates=0; + s.adlsDetected=true;s.adlsWiring.dp=1; + s.tglComboSignalReady=false;assert(s.start().missing()==C::ComboSignal); + s.tglComboSignalReady=true; + return 0; +} +''' +code=code.replace('FIELDS','\n'.join(' bool '+f+'=true;' for f in 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) + subprocess.run([str(p/'test')],check=True) +print('PASS production startup inventory: future hotplug, framing, AUX, mode, cleanup and DP-alt dependency failures') diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp new file mode 100644 index 00000000..4d8a42ed --- /dev/null +++ b/tests/xelp_display_contract.cpp @@ -0,0 +1,49 @@ +#include "kern_xelp_display_contract.hpp" +#include +#include +#include +using C=XELP::DisplayContract; +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<55;++i) { + auto broken=c;auto bit=1ULL< Date: Fri, 23 Oct 2026 22:03:01 +0900 Subject: [PATCH 19/58] Derive stale AGDC port exclusion from immutable wiring --- WhateverXe/kern_xe.cpp | 19 +++++--- tests/tgl_agdc_port_status.py | 87 +++++++++++++++++++++++++++++++++++ tools/test.py | 4 +- 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 tests/tgl_agdc_port_status.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 5163a7cd..3af1ff49 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -14890,21 +14890,28 @@ uint32_t XE::wrapTGLAGDCPortStatus(void *controller, void *request, void *output const auto r = FunctionCast(wrapTGLAGDCPortStatus, callbackXE->orgTGLAGDCPortStatus)(controller, request, output); uint64_t value = r == 0 && output ? *reinterpret_cast(static_cast(output) + 8) : 0; auto s = callbackXE; - if (s->tglInactivePorts && s->tglPortByIndex && r == 0 && output && - (id == 6 || id == 7) && controller && controller == s->tglObservationController && + const auto &w=s->tglTCWiring;const auto &v=s->firmwarePorts; + // Explicitly unspecified wiring remains unknown. Firmware absence is useful + // only when every digital child was mapped; do not hide an unparsed output. + const uint32_t excluded=!s->tglTCWiringLoaded || !w.valid ? 0 : s->tglTCWiringExplicit ? w.excluded : + v.valid() && !v.unmapped ? TGLTCWiring::Mask&~v.present : 0; + if (req && s->tglInactivePorts && s->tglPortByIndex && r == 0 && output && + s->xeProfile.family==XELP::Family::TigerLake && !s->adlsDetected && + id>=4 && id<=9 && (excluded&(1U<<(id-1))) && controller && controller == s->tglObservationController && *reinterpret_cast(static_cast(controller) + 0xD40) == s->tglPlatformCopy) { auto port = static_cast(reinterpret_cast(s->tglPortByIndex)(id - 1)); auto config = port ? *reinterpret_cast(port + 0x548) : nullptr; - if (config && config[0] == id - 1 && config[1] == id + 5 && config[2] == 1) { + if (config == reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*(id-1)) && + config[0] == id - 1 && config[1] == id + 5 && config[2] == 1) { const uint32_t mode = *reinterpret_cast(port + 0x550); const uint32_t state = *reinterpret_cast(port + 0x554); const uint32_t sinkCount = port[0xF8]; const uint64_t native = value; // DP1 probePortState resets mode to 0 on D0/AUX failure but leaves // state=2. GetPortStatus tests only state>1, causing AGDC to spend - // seconds reading capabilities of these inactive TC ports. Report - // disconnection only for the measured DDI5/6 mode-0 case. Keep - // native state, HPD, power and all active ports untouched. + // seconds reading capabilities of absent TC ports. The immutable + // wiring excludes this physical port and the exact native record is bound. + // Change only the reported stale result, never native mode/state or power. if (value == 1 && mode == 0 && state == 2 && sinkCount == 0) { *reinterpret_cast(static_cast(output) + 8) = 0; value = 0; diff --git a/tests/tgl_agdc_port_status.py b/tests/tgl_agdc_port_status.py new file mode 100644 index 00000000..afa8908e --- /dev/null +++ b/tests/tgl_agdc_port_status.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Production AGDC stale status policy across physical ports and wiring sources.""" +from pathlib import Path +import re,subprocess,tempfile,sys,hashlib +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + # Exact native GetPortStatus: lookup(id-1), state>1, write output+8. + for offset,expected in [(0x6a9fa,'8b3effcfe89907fbff'),(0x6aa11,'83b854050000010f9fc1'),(0x6aa22,'894b08')]: + assert b[offset:offset+len(bytes.fromhex(expected))]==bytes.fromhex(expected) +body=re.search(r'uint32_t XE::wrapTGLAGDCPortStatus\(.*?\n\}',s,re.S).group() +code=r''' +#include "kern_vbt.hpp" +#include "kern_tgl_tc_boot.hpp" +#include +#include +#include +struct XE { + uintptr_t orgTGLAGDCPortStatus{},tglPortByIndex{};bool tglInactivePorts=true,adlsDetected=false; + bool tglTCWiringLoaded=true,tglTCWiringExplicit=false; + TGLTCWiring tglTCWiring=TGLTCWiring::fromMasks(0,0,0);XELP::VBT::Ports firmwarePorts; + struct {XELP::Family family=XELP::Family::TigerLake;}xeProfile; + void *tglObservationController{};alignas(8) uint8_t tglPlatformCopy[0x200]{}; + static uint32_t wrapTGLAGDCPortStatus(void*,void*,void*); +} state,*callbackXE=&state; +#define FunctionCast(f,p) reinterpret_cast(p) +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry e;return &e;} + templatevoid setProperty(A...){} }; +templatevoid tglTrace(A...){} +void clock_get_uptime(uint64_t *v){*v=1;}void absolutetime_to_nanoseconds(uint64_t v,uint64_t *n){*n=v;} +alignas(8) uint8_t ports[9][0x600]{},ctl[0xD50]{}; +unsigned calls=0,lookups=0,nativeStatus=1,nativeResult=0; +void *get(unsigned index){assert(index<9);++lookups;return ports[index];} +uint32_t native(void*,void*,void *out){++calls;if(out)memcpy(static_cast(out)+8,&nativeStatus,4);return nativeResult;} +templatevoid put(void *base,unsigned at,T value){memcpy(static_cast(base)+at,&value,sizeof(value));} +BODY +int main() { + unsigned cases=0; + for(unsigned ddi=0;ddi<9;++ddi)for(unsigned mode=0;mode<5;++mode)for(unsigned status=0;status<4;++status) + for(unsigned sinks=0;sinks<2;++sinks)for(unsigned wiring=0;wiring<8;++wiring){ + state={};memset(ports,0,sizeof(ports));state.orgTGLAGDCPortStatus=reinterpret_cast(native); + state.tglPortByIndex=reinterpret_cast(get);state.tglObservationController=ctl; + state.firmwarePorts.status=XELP::VBT::Status::Valid; + state.firmwarePorts.present=1; // declared panel; no other child. + if(wiring==1)state.firmwarePorts.present|=1U<(state.tglPlatformCopy+0x20+24*ddi); + c[0]=ddi;c[1]=ddi+6;c[2]=1;put(ports[ddi],0x548,c);put(ports[ddi],0x550,mode);put(ports[ddi],0x554,status); + ports[ddi][0xF8]=sinks;put(ctl,0xD40,state.tglPlatformCopy+0); + alignas(8) uint32_t req[4]={ddi+1},out[4]={}; + uint8_t before[sizeof(ports)];memcpy(before,ports,sizeof(ports));calls=lookups=0; + assert(!XE::wrapTGLAGDCPortStatus(ctl,req,out)&&calls==1); + const bool absent=ddi>=3&&(wiring==0||wiring==5); + assert(out[2]==(absent&&mode==0&&status==2&&!sinks?0u:1u)); + assert(!memcmp(before,ports,sizeof(ports))); // no mode/state/power mutations. + assert(lookups==unsigned(absent));++cases; + } + // Exact controller/config association is mandatory even for excluded wiring. + for(unsigned bad=0;bad<7;++bad) { + state={};state.orgTGLAGDCPortStatus=reinterpret_cast(native);state.tglPortByIndex=reinterpret_cast(get); + state.tglObservationController=ctl;state.tglTCWiringExplicit=true;state.tglTCWiring=TGLTCWiring::fromMasks(0,0,0x1f8); + auto c=reinterpret_cast(state.tglPlatformCopy+0x20+24*5);c[0]=5;c[1]=11;c[2]=1; + memset(ports[5],0,sizeof(ports[5]));put(ports[5],0x548,c);put(ports[5],0x554,2u);put(ctl,0xD40,state.tglPlatformCopy+0); + uint32_t copy[6];memcpy(copy,c,sizeof(copy)); + if(bad==0)put(ports[5],0x548,copy+0); + if(bad==1)state.tglObservationController=nullptr; + if(bad==2)state.xeProfile.family=XELP::Family::AlderLakeN; + if(bad==3)c[0]=4; + if(bad==4)c[1]=10; + if(bad==5)c[2]=0; + if(bad==6)state.tglInactivePorts=false; + uint32_t req[4]={6},out[4]={};calls=0; + assert(!XE::wrapTGLAGDCPortStatus(ctl,req,out)&&out[2]==1&&calls==1); + } + printf("PASS %u port/mode/state/sink/wiring combinations plus binding guards; native state untouched\n",cases); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='agdc-status-') 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 7b9018d6..97458552 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=['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=['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 += ['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_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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From 4b30a8b4126288fd05047a86fe1524358e2e5788 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:03:02 +0900 Subject: [PATCH 20/58] Verify Combo loadgen state along with electrical coefficients --- WhateverXe/kern_tgl_combo_signal.hpp | 2 +- tests/tgl_combo_signal.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index 5a3f20e8..0bdda330 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -91,7 +91,7 @@ struct TGLComboSignal { (read(base+0x28)&0xFCU)==laneMask; for(unsigned n=0;n<4;++n) { ok&=(read(base+0x888+0x100*n)&0xB8FFU)==(((swing&8)<<12)|((swing&7)<<11)|0x98U); - ok&=(read(base+0x890+0x100*n)&0x3FFFFU)==((post1<<12)|cursor); + ok&=(read(base+0x890+0x100*n)&0x8003FFFFU)==((dw4[n]&0x80000000U)|(post1<<12)|cursor); ok&=(read(base+0x89C+0x100*n)&0x7F000000U)==(scalar<<24); } return ok; diff --git a/tests/tgl_combo_signal.cpp b/tests/tgl_combo_signal.cpp index 0a106376..1c00c592 100644 --- a/tests/tgl_combo_signal.cpp +++ b/tests/tgl_combo_signal.cpp @@ -52,6 +52,16 @@ int main() { for(unsigned n=0;n<4;++n)assert(bool(regs[s.base+0x890+0x100*n]&0x80000000U)== (rate!=30 && (n==1 || n==2 || (lanes==4 && n==3)))); } + // A PHY that drops only the loadgen clear must not report success. + { + TGLComboSignal hbr3;assert(hbr3.selectADLS(3,4,30,0,0)); + std::map regs; + assert(!hbr3.program([&](unsigned r){return regs[r];},[&](unsigned r,unsigned value){ + if(r==hbr3.base+0x694)for(unsigned n=0;n<4;++n)regs[hbr3.base+0x894+0x100*n]=value; + for(unsigned n=0;n<4;++n)if(r==hbr3.base+0x890+0x100*n)value|=0x80000000U; + regs[r]=value; + })); + } TGLComboSignal s;assert(s.select(0,1,10,false,false,0,0)); assert(s.swing==0xA&&s.scalar==0x32&&s.cursor==0x3F&&!s.post1); // Silent dropped coefficient write must not report successful programming. From 9a576e82792de3dfc4be9c89c8fe81ed20f09625 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:05:15 +0900 Subject: [PATCH 21/58] Exercise shared SST lifecycle across transport, pipe and inherited state --- tests/tgl_training_leases_fixture.cpp | 73 +++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index f7494b28..40ae335d 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -79,7 +79,7 @@ bool used[4] {}, existingPath, badPipe, failAdoption, stopCalled; int pgRefs[8] {}; std::atomic nativeCalls {0}; std::function nativeBoundary; -uint32_t nativeEndResult; +uint32_t nativeEndResult,nativeTrainingResult; void *protectedMMIO; bool protectOnPowerDrop, bufferIdlePending; std::atomic portDisables{0}, clockDisables{0}; @@ -128,7 +128,7 @@ void disableClocks(void *, void *port, void *) { } void nativeStop(void *, IOService *) { stopCalled=true; } uint32_t beginEnd(void *, void *) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); return nativeEndResult; } -uint32_t train(void *, void *, void *) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); return 0; } +uint32_t train(void *, void *, void *) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); return nativeTrainingResult; } bool allocateResources(void *, uint32_t, uint32_t) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); @@ -150,7 +150,7 @@ void setup() { obj.orgTGLTrainingBegin=(uintptr_t)beginEnd; obj.orgTGLTrainingEnd=(uintptr_t)beginEnd; obj.orgTGLTraining=(uintptr_t)train; obj.orgTGLAllocatePathResources=(uintptr_t)allocateResources; ctl={}; access={}; power={}; mmio={}; - existingPath=badPipe=failAdoption=stopCalled=false; nativeCalls=0; nativeEndResult=0; + existingPath=badPipe=failAdoption=stopCalled=false; nativeCalls=0; nativeEndResult=nativeTrainingResult=0; nativeBoundary={}; clockBoundary={}; protectedMMIO=nullptr; protectOnPowerDrop=bufferIdlePending=false;portDisables=clockDisables=0; std::fill(std::begin(used),std::end(used),false); std::fill(std::begin(pgRefs),std::end(pgRefs),0); ctl.at(0xd40)=obj.tglPlatformCopy; ctl.at(0xc40)=access.data(); ctl.at(0xc8)=power.data(); @@ -345,6 +345,71 @@ void endCompletion() { } puts("End completion: all 3 pipes retain failed cleanup and return Busy; retries consume native End once, training/adoption blocked, later End/Begin finishes; native error/commit semantics preserved"); } + +void routeLifecycleMatrix() { + unsigned sequences=0; + for(unsigned mode:{1u,3u,4u})for(unsigned ddi=0;ddi<9;++ddi)for(unsigned pipe=0;pipe<3;++pipe) + for(unsigned initial=0;initial<3;++initial) { + if(mode==1 ? (ddi!=0&&ddi!=3&&ddi!=4) : ddi<3)continue; + setup(); + if(mode==1){obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=true;obj.adlsWiring.valid=true;obj.adlsWiring.dp=1U<(0x550)=mode; + for(unsigned i=0;i(func)=savedFunc;mmio.at(tp)=savedTP; + uint32_t request[20]={ddi+1},output[20]={}; + if(initial==2) { + // An inherited native scanout remains owned by native. A training call + // must not borrow its pipe/power or restore over its register state. + existingPath=true;mmio.at(func)|=0x80000000; + mmio.at(tp)|=0x80000000;mmio.at(buffer)=0x80000000; + assert(XE::wrapTGLTrainingBegin(ctl.data(),request)==0); + assert(XE::wrapTGLTraining(ctl.data(),request,output)==0); + request[2]=1;assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==0);request[2]=0; + assert(!obj.tglTrainingLeases[ddi].path && !obj.tglTrainingLeases[ddi].powerOwned); + assert(mmio.at(func)==(savedFunc|0x80000000)); + for(int refs:pgRefs)assert(!refs); + // Model the native owner's completed disconnect; not an OS sleep/wake. + existingPath=false;mmio.at(func)=savedFunc;mmio.at(tp)=savedTP;mmio.at(buffer)=0x80; + } + auto &lease=obj.tglTrainingLeases[ddi]; + // Repeat without resetting controller, pool or mode. Successful training, + // native failure and delayed abort completion all precede a reconnection. + for(unsigned cycle=0;cycle<4;++cycle) { + const auto generation=lease.generation; + assert(XE::wrapTGLTrainingBegin(ctl.data(),request)==0); + nativeTrainingResult=cycle==1?0xe00002bc:0; + assert(XE::wrapTGLTraining(ctl.data(),request,output)==nativeTrainingResult); + assert(lease.path && lease.pipe==pipe && lease.generation==generation+1); + assert(lease.powerOwned && !lease.busy && pgRefs[lease.pg]==1); + assert(mmio.at(func)==savedFunc); + if(cycle==2) { + mmio.at(buffer)=0; + assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==kIOReturnBusy); + const auto calls=nativeCalls.load(); + assert(lease.endCleanupPending && lease.powerOwned); + assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==kIOReturnBusy && nativeCalls==calls); + assert(XE::wrapTGLTraining(ctl.data(),request,output)==kIOReturnBusy && nativeCalls==calls); + mmio.at(buffer)=0x80; + assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==0 && nativeCalls==calls); + } else assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==0); + assert(!lease.path && !lease.busy && !lease.powerOwned && !lease.endCleanupPending); + assert(mmio.at(func)==savedFunc && mmio.at(tp)==savedTP); + for(int refs:pgRefs)assert(!refs); + for(unsigned i=0;i Date: Fri, 23 Oct 2026 22:12:07 +0900 Subject: [PATCH 22/58] Include native AUX and panel prerequisites in Combo startup closure --- WhateverXe/kern_xe.cpp | 2 ++ WhateverXe/kern_xelp_display_contract.hpp | 7 ++++++- tests/display_contract_adapter.py | 6 +++++- tests/xelp_display_contract.cpp | 4 ++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3af1ff49..41a740b1 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11176,6 +11176,8 @@ XELP::DisplayContract::Mask XE::display12Available() const { if(orgTGLRegisterWrite)a|=C::MMIOWrite; if(tglHPDHookReady)a|=C::HPD; if(tglClockOffCaller)a|=C::ClockOrder; + if(tglComboAUXGateReady)a|=C::ComboAUXGate; + if(tglPanelDepthReady)a|=C::PanelDepth; return a; } diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 83f79195..b9742057 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -62,13 +62,15 @@ struct DisplayContract { Output13=1ULL<<52, Topology13=1ULL<<53, ClockOrder=1ULL<<54, + ComboAUXGate=1ULL<<55, + PanelDepth=1ULL<<56, }; 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 Training12=Lease|PowerGet|PowerPut|PortLookup|PathLookup|PathAdd|PathFree|TrainingEnd|Training|TrainingBegin|PathAllocate|ClockOrder; - static constexpr Mask Combo12=Framing|ComboSignal|ComboAUX; + 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; @@ -86,6 +88,7 @@ struct DisplayContract { if(r.hdmi)required|=GMBus; if(r.leaseRequested||r.combo||r.legacy||r.alt)required|=Training12; if(r.panel||r.combo)required|=Combo12; + if(r.panel)required|=PanelDepth; if(r.combo||r.legacy)required|=Fixed12; if(r.legacy||r.alt)required|=Framing|ModeLock; if(r.alt)required|=Alt12; @@ -152,6 +155,8 @@ struct DisplayContract { case Output13:return "Output13"; case Topology13:return "Topology13"; case ClockOrder:return "ClockOrder"; + case ComboAUXGate:return "ComboAUXGate"; + case PanelDepth:return "PanelDepth"; default:return "Unknown"; } } diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index 6795879f..4050af13 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -27,7 +27,7 @@ }; int main() { State s; - assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder)); + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth)); assert(s.display13Available()==C::Backend13); // TGL: candidate not yet registered, native donor setup is complete. // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. @@ -43,6 +43,10 @@ s.adlsDetected=true;s.adlsWiring.dp=1; s.tglComboSignalReady=false;assert(s.start().missing()==C::ComboSignal); s.tglComboSignalReady=true; + s.adlsDetected=false;s.adlsWiring.dp=0;s.firmwarePorts.panel.valid=true; + s.tglTCNativeFunctions.valid=false;assert(s.start().missing()==C::TCNative);s.tglTCNativeFunctions.valid=true; + s.tglComboAUXGateReady=false;assert(s.start().missing()==C::ComboAUXGate);s.tglComboAUXGateReady=true; + s.tglPanelDepthReady=false;assert(s.start().missing()==C::PanelDepth); return 0; } ''' diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index 4d8a42ed..5913b149 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<55;++i) { + for(unsigned i=0;i<57;++i) { auto broken=c;auto bit=1ULL< Date: Fri, 23 Oct 2026 22:18:32 +0900 Subject: [PATCH 23/58] Compose firmware-wired TGL Combo DP with shared SST operations --- README.md | 1 + README_ja.md | 1 + WhateverXe/kern_tgl_combo_dp.hpp | 49 +++++++++++++++++++++++ WhateverXe/kern_tgl_combo_signal.hpp | 8 ++-- WhateverXe/kern_xe.cpp | 21 ++++++---- WhateverXe/kern_xe.hpp | 5 ++- WhateverXe/kern_xelp_display_contract.hpp | 3 +- tests/display_contract_adapter.py | 2 +- tests/tgl_combo_aux_lifetime.py | 16 +++++--- tests/tgl_combo_dp.cpp | 44 ++++++++++++++++++++ tests/tgl_combo_signal_guard.py | 27 ++++++++++++- tests/tgl_hdmi_wiring_fixture.cpp | 17 +++++++- tests/tgl_sink_framing.py | 8 ++-- tests/tgl_training_leases_fixture.cpp | 12 +++--- tests/tgl_training_pattern_fixture.cpp | 6 ++- 15 files changed, 187 insertions(+), 33 deletions(-) create mode 100644 WhateverXe/kern_tgl_combo_dp.hpp create mode 100644 tests/tgl_combo_dp.cpp diff --git a/README.md b/README.md index 0f56cae2..59d9e116 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ installation or update location. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not receive an image. +- Development builds also derive fixed external DP on TGL Combo A/B from VBT and share the SST training, power and AUX lifetime with other DP routes. This composition has CPU coverage and is not yet hardware-qualified. Cross-routed AUX and Type-C/TBT-managed Combo wiring remain excluded. - TGL eDP signal selection covers RBR through HBR3 using the physical GPU subplatform and VBT panel flags. HOBL training below HBR3 remains donor-managed. High-rate selection has CPU coverage; the hardware results above do not qualify every link rate. - Full system sleep/wake and resolutions above those listed above are unverified. USB-C display output currently prevents part of the display hardware from diff --git a/README_ja.md b/README_ja.md index 3fc86725..4ba91fa2 100644 --- a/README_ja.md +++ b/README_ja.md @@ -115,6 +115,7 @@ Safariのインストール先や更新による配置変更に依存せずData - DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでは、上記の抜き差し試験を通過しています。 - Safariの動画再生で、ときどきコマ落ちが発生します。 - 同時出力は最大3画面です。4画面目には映像が出ません。 +- 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 - TGL eDPの信号設定は、物理GPUのsubplatformとVBTのpanel情報からRBR〜HBR3を選択します。HBR3未満のHOBL訓練は引き続きdonorが管理します。高速度の選択はCPU試験済みですが、上記の実機結果は全link rateの動作保証ではありません。 - システム全体のスリープ/復帰と、上記を超える解像度は未検証です。USB-C映像出力では現在、表示回路の一部を省電力状態へ移行させずに動作させています。 diff --git a/WhateverXe/kern_tgl_combo_dp.hpp b/WhateverXe/kern_tgl_combo_dp.hpp new file mode 100644 index 00000000..0b8e2d67 --- /dev/null +++ b/WhateverXe/kern_tgl_combo_dp.hpp @@ -0,0 +1,49 @@ +// Fixed DP on TGL Combo A/B. Wiring comes from VBT; this adapter only +// connects existing Combo PHY/AUX/PLL and shared SST operations. +#ifndef KERN_TGL_COMBO_DP_HPP +#define KERN_TGL_COMBO_DP_HPP +#include "kern_vbt.hpp" +struct TGLComboDP { + static constexpr uint32_t Mask=3; + static uint32_t fromFirmware(const XELP::VBT::Ports &ports,uint32_t hdmi) { + if(!ports.valid() || ports.unmapped)return 0; + uint32_t selected=0; + for(unsigned d=0;d<2;++d) { + const auto bit=1U<=2 || !(selected&(1U<(static_cast(platform)+0x20+24*d) && + !memcmp(config,expected,sizeof(expected)); + } + static bool accepts(uint32_t selected,const void *platform,size_t length) { + if((selected&~Mask) || !platform || length<0x1C0)return false; + auto p=static_cast(platform);if(p[0x12]!=9)return false; + for(unsigned d=0;d<2;++d)if(selected&(1U<(platform); + for(unsigned d=0;d<2;++d)if(selected&(1U<1 || (count!=1 && count!=2 && count!=4) || (hobl && rate!=30) || + if(ddi>1 || (count!=1 && count!=2 && count!=4) || (panel && hobl && rate!=30) || (platform!=XELP::ComboPolicy::Platform::TGL && platform!=XELP::ComboPolicy::Platform::TGLUY))return false; XELP::ComboPolicy policy; - if(!policy.select(platform,rate,true,lowSwing,false,voltage,emphasis))return false; - base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=lowSwing;assign(policy);return true; + if(!policy.select(platform,rate,panel,lowSwing,false,voltage,emphasis))return false; + base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=panel && lowSwing;assign(policy);return true; } // The platform adapter chooses coordinates; electrical policy contains no // physical DDI, donor field or board-specific connector condition. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 41a740b1..e4b12680 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -11050,7 +11050,10 @@ bool XE::wrapTGLGetOSInformation(void *controller) { auto copy=reinterpret_cast(self->tglPlatformCopy); if(copy!=platform)memcpy(copy,platform,sizeof(self->tglPlatformCopy)); const bool wiringApplied=self->adlsDetected ? self->adlsWiring.apply(copy,sizeof(self->tglPlatformCopy)) : - self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)); + self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && + TGLComboDP::accepts(self->tglComboDPMask,copy,sizeof(self->tglPlatformCopy)) && + self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) && + TGLComboDP::apply(self->tglComboDPMask,copy,sizeof(self->tglPlatformCopy)); if(!self->tglHDMIWiringLoaded || !wiringApplied) { IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringGuardFailed",true);return false; } @@ -11130,6 +11133,8 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { const auto &w=self->tglHDMIWiring; self->tglHDMIB=w.valid && w.hdmi && !(w.hdmi&~self->tglHDMIBackendMask); const bool admitted=w.supported(self->tglHDMIBackendMask,self->tglHDMIB); + self->tglComboDPMask=admitted?TGLComboDP::fromFirmware(self->firmwarePorts,w.hdmi):0; + IORegistryEntry::getRegistryRoot()->setProperty("TGLComboDPRoutes",self->tglComboDPMask,32); uint32_t q[]={361,self->tglHDMIWiringExplicit,w.valid,w.hdmi,w.ddc[0],w.ddc[1],w.ddc[2],self->tglHDMIBackendMask,admitted}; IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiring",q,sizeof(q));return admitted; } @@ -11242,7 +11247,7 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { auto self=callbackXE; XELP::DisplayContract::Routes routes; routes.hdmi=self->tglHDMIB;routes.panel=self->firmwarePorts.panel.valid; - routes.combo=self->adlsDetected && self->adlsWiring.dp; + routes.combo=self->adlsDetected ? self->adlsWiring.dp!=0 : self->tglComboDPMask!=0; routes.legacy=self->tglTCWiring.legacy || self->tglTCAutoCandidates; routes.alt=self->tglDPAltCandidates;routes.leaseRequested=self->tglTrainingLeaseRequested; const auto contract=XELP::DisplayContract::display12(self->display12Available(),routes); @@ -16638,11 +16643,12 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; const bool adls=self->adlsDetected && self->adlsWiring.boundDP(self->tglPlatformCopy,config); + const bool fixed=!self->adlsDetected && TGLComboDP::bound(self->tglComboDPMask,self->tglPlatformCopy,config); XELP::Display12SSTRoute route; - const XELP::Display12SSTRoutes routes{adls?self->adlsWiring.dp:0,0,0}; + const XELP::Display12SSTRoutes routes{adls?self->adlsWiring.dp:fixed?self->tglComboDPMask:0,0,0}; if(!config || !routes.resolve(config[0],config[2],*reinterpret_cast(p+0x550),route) || !route.retainComboAUX() || p[0x117])return false; - if(!adls && (config[0]>1 || config[2]!=0 || config[3]!=0 || + if(!adls && !fixed && (config[0]>1 || config[2]!=0 || config[3]!=0 || !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17)))return false; auto access=*reinterpret_cast(p+0x578); if(!access || *reinterpret_cast(ctl+0xC40)!=access)return false; @@ -16651,7 +16657,7 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void const auto rate=*reinterpret_cast(p+0x128); if(!(adls ? selected.selectADLS(config[0],lanes,rate,swing,emphasis): selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis, - XELP::ComboPolicy::tigerLake(self->xeProfile.identity))))return false; + XELP::ComboPolicy::tigerLake(self->xeProfile.identity),!fixed)))return false; // The native fields identify this very bank. No guessed framebuffer index // or cross-port reads; power must already belong to the training caller. if(*reinterpret_cast(p+0x620)!=selected.base+0x804 || @@ -16995,6 +17001,7 @@ void XE::tglComboAUXReference(void *port,bool acquire) { (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected) || *f.controllerSlot!=ctl)return; auto config=*reinterpret_cast(p+0x548); const bool adls=s->adlsDetected && s->adlsWiring.boundDP(s->tglPlatformCopy,config); + const bool fixed=!s->adlsDetected && TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config); if(!config || (config[0]>1 && !adls))return; const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; @@ -17009,12 +17016,12 @@ void XE::tglComboAUXReference(void *port,bool acquire) { const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); if(*reinterpret_cast(p+0x590)!=buffer)return; XELP::Display12SSTRoute route; - const XELP::Display12SSTRoutes routes{adls?s->adlsWiring.dp:0,0,0}; + 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 && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return; + (!adls && !fixed && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return; const auto io=wrapTGLRegisterRead(access,0x45454); if(io==~0U || (io&mask)!=mask)return; const auto bufferState=wrapTGLRegisterRead(access,buffer); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index f150dc31..41293e32 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -12,6 +12,7 @@ #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" +#include "kern_tgl_combo_dp.hpp" #include "kern_display13_hdmi_wiring.hpp" #include "kern_xelp_platform.hpp" #include "kern_vbt.hpp" @@ -405,11 +406,11 @@ class XE { // Native mode setter owns FIA1 bits 0/1 correctly on modular TGL. Later // instances need the new mode backend, regardless of available PHY math. uint32_t tglTCAutoCandidates {},tglTCAutoRegistered {}; - uint32_t tglDPAltCandidates {}; + uint32_t tglDPAltCandidates {},tglComboDPMask {}; XELP::Display12SSTRoutes tglSSTRoutes() const { auto routes=tglTCWiring.sstRoutes(tglAUXParentMask, tglTCAutoCandidates&__atomic_load_n(&tglTCAutoRegistered,__ATOMIC_ACQUIRE),tglDPAltCandidates); - if(adlsDetected && adlsWiring.valid)routes.combo=adlsWiring.dp; + routes.combo=adlsDetected ? (adlsWiring.valid?adlsWiring.dp:0) : tglComboDPMask; return routes; } bool tglDPSSTEligible(uint32_t ddi) const {return tglSSTRoutes().eligible(ddi);} diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index b9742057..e2f6d277 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -89,7 +89,8 @@ struct DisplayContract { if(r.leaseRequested||r.combo||r.legacy||r.alt)required|=Training12; if(r.panel||r.combo)required|=Combo12; if(r.panel)required|=PanelDepth; - if(r.combo||r.legacy)required|=Fixed12; + if(r.combo)required|=AUXParent; + if(r.legacy)required|=Fixed12; if(r.legacy||r.alt)required|=Framing|ModeLock; if(r.alt)required|=Alt12; return {required,installed}; diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index 4050af13..c91da08e 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -21,7 +21,7 @@ struct {struct {bool valid=false;}panel;}firmwarePorts; struct {unsigned dp=0;}adlsWiring; struct {unsigned legacy=0;}tglTCWiring; - unsigned tglTCAutoCandidates=0,tglDPAltCandidates=0; + unsigned tglTCAutoCandidates=0,tglDPAltCandidates=0,tglComboDPMask=0; METHODS C start() const {ADAPTER} }; diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index a2187368..7b274275 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -8,12 +8,13 @@ b=Path(sys.argv[1]).read_bytes();assert b[0x5fc56:0x5fc5f]==bytes.fromhex('554889e553504889fb');assert b[0x5fc6d:0x5fc73]==bytes.fromhex('8bb790050000') code=r''' #include "kern_alderlake_s.hpp" +#include "kern_tgl_combo_dp.hpp" #include "kern_xelp_sst_route.hpp" #include #include #include struct Functions {bool ready=true;void **controllerSlot=nullptr;void(*getAux)(void*)=nullptr;void(*putAux)(void*)=nullptr;bool complete(){return ready;}}; -struct State {bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +struct State {uint32_t tglComboDPMask=0;bool adlsDetected=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; @@ -22,17 +23,22 @@ void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} FUNCTION int main(){ - for(unsigned testDDI:{0u,3u,4u})for(bool adls:{false,true})for(unsigned bad=0;bad<15;++bad){ + for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<3;++kind)for(unsigned bad=0;bad<15;++bad){ + const bool adls=kind==1,fixed=kind==2;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=adls?testDDI:0,mask=3U<<(2*ddi),buffer=0x64000+256*ddi; + 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.adlsDetected=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)); } + if(fixed) { + state.tglComboDPMask=1U<(state.tglPlatformCopy+0x20+24*ddi); + const uint32_t c[]={ddi,ddi+1,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); + } else if(!adls)config[0]=ddi; state.tglObservationController=ctl;void *global=ctl; state.tglTCNativeFunctions.controllerSlot=&global; - state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.present=1U< +#include +#include +#include +int main() { + for(unsigned mask=0;mask<4;++mask)for(unsigned hdmi=0;hdmi<4;++hdmi) { + XELP::VBT::Ports v;v.status=XELP::VBT::Status::Valid;v.present=mask; + v.port[0]={0x60d6,10,0,0x40,0xc0,0};v.port[1]={0x60d6,7,0,0x10,0xc0,0}; + const auto selected=TGLComboDP::fromFirmware(v,hdmi);assert(selected==(mask&~hdmi)); + alignas(8) uint8_t p[0x1c0]{};p[0x12]=9; + const uint32_t panel[]={0,0,0,0,2,0x18},dp[]={1,2,1,0,0x400,0x201}; + memcpy(p+0x20,panel,24);memcpy(p+0x38,dp,24); + uint8_t before[sizeof(p)];memcpy(before,p,sizeof(p)); + assert(TGLComboDP::apply(selected,p,sizeof(p))); + for(unsigned d=0;d<9;++d) { + auto config=reinterpret_cast(p+0x20+24*d); + if(selected&(1U< #include #include struct State { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + uint32_t tglComboDPMask=0;bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr;bool tglActive=true,tglComboSignalReady=true; struct {uint32_t identity=0x9A498086;XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts; @@ -92,6 +93,30 @@ const unsigned post=rate==30?8:low?7:id==0x9A608086?8:9; assert(((registers[0x162890]>>12)&63)==post); } + + // External TGL Combo uses DP electrical values even if a different panel + // advertises low-swing/HOBL. It requires an exact immutable port record. + for(unsigned ddi:{0u,1u})for(unsigned rate:{6u,10u,20u,30u})for(unsigned bad=0;bad<5;++bad) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.tglComboDPMask=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(config,c,sizeof(c)); + const auto base=ddi?0x6c000U:0x162000U; + store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,4U);store(p,0x128,rate);store(p,0x620,base+0x804);store(p,0x5C8,base+0x28); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=3;registers[0x45454]=3U<<(2*ddi);reads=writes=records=0; + if(bad==1)state.tglComboDPMask=0; + if(bad==2)store(p,0x548,c+0); + if(bad==3)config[1]^=1; + if(bad==4)store(p,0x550,3U); + uint32_t result=99;assert(run(p,0,1,nullptr,result)==(bad==0)); + assert(bool(writes)==(bad==0)); + if(!bad){assert(result==0);assert(((registers[base+0x890]>>12)&63)==(rate>10?9u:8u));} + } for(unsigned ddi:{0u,3u,4u})for(unsigned bad=0;bad<12;++bad) { alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index 28dc3130..8c2bf5cd 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -10,6 +10,7 @@ #include "kern_tgl_hdmi_wiring.hpp" #include "kern_vbt.hpp" #include "kern_alderlake_s.hpp" +#include "kern_tgl_combo_dp.hpp" #include "kern_xelp_donor.hpp" struct OSObject {virtual ~OSObject()=default;}; struct OSData:OSObject {std::vector data;void *getBytesNoCopy(){return data.data();}uint32_t getLength(){return data.size();}}; @@ -27,7 +28,7 @@ void clock_get_uptime(uint64_t *p){*p=0;}void absolutetime_to_nanoseconds(uint64 void *current_thread(){return reinterpret_cast(1);} struct XE { bool tglHDMIB=true,tglStolenMemoryReady=false; - bool adlsDetected=false; + uint32_t tglComboDPMask=0;bool adlsDetected=false; struct {uint32_t fStolenMemorySize=0;} framebufferPatch; XELP::VBT::Ports firmwarePorts; // FIELDS @@ -116,6 +117,20 @@ int main(){ assert(XE::tglLoadHDMIWiring(&firmware)&&!object.tglHDMIWiringExplicit&&object.tglHDMIWiring.hdmi==2); assert(XE::wrapTGLGetOSInformation(controller)); assert(reinterpret_cast(platform()+0x38)[1]==2); + // Fixed Combo DP composes with HDMI and a panel-free A/B native topology. + for(unsigned mask:{1u,2u,3u}) { + setup();object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=3; + for(unsigned d=0;d<2;++d)object.firmwarePorts.port[d]=mask&(1U<(platform()+0x20+24*d); + assert(config[2]==(mask&(1U<1 : adapter==1 ? (ddi!=0&&ddi!=3&&ddi!=4) : ddi<3)continue; setup(); - if(mode==1){obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDetected=true;obj.adlsWiring.valid=true;obj.adlsWiring.dp=1U<(0x550)=mode; for(unsigned i=0;i1)continue; reset(d,p,pat);object.tglTCWiring=TGLTCWiring::fromMasks(0,0,0); - object.adlsDetected=true;object.adlsWiring.valid=true;object.adlsWiring.dp=1U< Date: Fri, 23 Oct 2026 22:26:39 +0900 Subject: [PATCH 24/58] Use standard panel coefficients when HOBL retry is unavailable --- README.md | 2 +- README_ja.md | 2 +- WhateverXe/kern_tgl_combo_signal.hpp | 9 ++++++--- tests/tgl_combo_signal.cpp | 2 +- tests/tgl_combo_signal_guard.py | 5 ++--- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 59d9e116..1d2c35ea 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ installation or update location. - Up to three displays can be used simultaneously; a fourth display will not receive an image. - Development builds also derive fixed external DP on TGL Combo A/B from VBT and share the SST training, power and AUX lifetime with other DP routes. This composition has CPU coverage and is not yet hardware-qualified. Cross-routed AUX and Type-C/TBT-managed Combo wiring remain excluded. -- TGL eDP signal selection covers RBR through HBR3 using the physical GPU subplatform and VBT panel flags. HOBL training below HBR3 remains donor-managed. High-rate selection has CPU coverage; the hardware results above do not qualify every link rate. +- TGL eDP signal selection covers RBR through HBR3 using the physical GPU subplatform and VBT panel flags. Firmware requesting HOBL uses standard Linux fallback coefficients; the HOBL optimization remains disabled until its retry lifecycle is supported. High-rate selection has CPU coverage; the hardware results above do not qualify every link rate. - Full system sleep/wake and resolutions above those listed above are unverified. USB-C display output currently prevents part of the display hardware from entering a low-power state. diff --git a/README_ja.md b/README_ja.md index 4ba91fa2..e7a19d17 100644 --- a/README_ja.md +++ b/README_ja.md @@ -116,7 +116,7 @@ Safariのインストール先や更新による配置変更に依存せずData - Safariの動画再生で、ときどきコマ落ちが発生します。 - 同時出力は最大3画面です。4画面目には映像が出ません。 - 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 -- TGL eDPの信号設定は、物理GPUのsubplatformとVBTのpanel情報からRBR〜HBR3を選択します。HBR3未満のHOBL訓練は引き続きdonorが管理します。高速度の選択はCPU試験済みですが、上記の実機結果は全link rateの動作保証ではありません。 +- TGL eDPの信号設定は、物理GPUのsubplatformとVBTのpanel情報からRBR〜HBR3を選択します。HOBLを要求するfirmwareでも標準のLinux fallback係数を使用します。HOBL最適化は再試行処理を実装するまで無効です。高速度の選択はCPU試験済みですが、上記の実機結果は全link rateの動作保証ではありません。 - システム全体のスリープ/復帰と、上記を超える解像度は未検証です。USB-C映像出力では現在、表示回路の一部を省電力状態へ移行させずに動作させています。 ## ロードマップ diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index c1cfb2ab..c2ed8f88 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -29,9 +29,12 @@ struct TGLComboSignal { bool select(uint32_t ddi,uint32_t count,uint32_t rate,bool lowSwing,bool hobl, uint32_t voltage,uint32_t emphasis, XELP::ComboPolicy::Platform platform=XELP::ComboPolicy::Platform::TGL,bool panel=true) { - // HOBL needs a training-failure retry policy, so keep that native - // operation intact. At HBR3 Linux ignores both panel swing flags. - if(ddi>1 || (count!=1 && count!=2 && count!=4) || (panel && hobl && rate!=30) || + // HOBL is an optional optimization. Until a retry backend owns its + // hobl_active/hobl_failed lifecycle, select Linux's standard fallback + // directly. Never fall through to unqualified donor coefficients just + // because firmware requests HOBL. At HBR3 neither panel flag applies. + (void)hobl; + if(ddi>1 || (count!=1 && count!=2 && count!=4) || (platform!=XELP::ComboPolicy::Platform::TGL && platform!=XELP::ComboPolicy::Platform::TGLUY))return false; XELP::ComboPolicy policy; if(!policy.select(platform,rate,panel,lowSwing,false,voltage,emphasis))return false; diff --git a/tests/tgl_combo_signal.cpp b/tests/tgl_combo_signal.cpp index 1c00c592..73d68900 100644 --- a/tests/tgl_combo_signal.cpp +++ b/tests/tgl_combo_signal.cpp @@ -8,7 +8,7 @@ int main() { for(unsigned rate:{6,10,20,30})for(bool low:{false,true})for(bool hobl:{false,true}) for(unsigned vs=0;vs<5;++vs)for(unsigned pre=0;pre<5;++pre) { TGLComboSignal s; - bool valid=ddi<2&&(lanes==1||lanes==2||lanes==4)&&(!hobl||rate==30)&& + bool valid=ddi<2&&(lanes==1||lanes==2||lanes==4)&& (rate==6||rate==10||rate==20||rate==30)&&vs<4&&pre<4&&vs+pre<=3; assert(s.select(ddi,lanes,rate,low,hobl,vs,pre)==valid); if(!valid)continue; diff --git a/tests/tgl_combo_signal_guard.py b/tests/tgl_combo_signal_guard.py index 1a5b1f6d..616734ce 100644 --- a/tests/tgl_combo_signal_guard.py +++ b/tests/tgl_combo_signal_guard.py @@ -59,7 +59,7 @@ if(bad==11)store(p,0x578,ctl+0); if(bad==12)store(p,0x124,3U); if(bad==13)store(p,0x128,12U); - if(bad==14)state.firmwarePorts.panel.hobl=true; + if(bad==14)state.xeProfile.identity=0x9A491234; if(bad==15)store(p,0x620,0x6C804U); if(bad==16)store(access,0x60,0x100000U); if(bad==17)registers[0x45404]=0; @@ -87,8 +87,7 @@ store(p,0x124,4U);store(p,0x128,rate);store(p,0x620,0x162804U);store(p,0x5C8,0x162028U); store(access,0x60,0x200000U);store(access,0x50,ctl+0); registers.clear();registers[0x45404]=3;registers[0x45454]=3;reads=writes=records=0; - uint32_t result=99;assert(run(p,0,1,nullptr,result)==(!hobl||rate==30)); - if(hobl&&rate!=30){assert(!writes);continue;} + uint32_t result=99;assert(run(p,0,1,nullptr,result)); assert(result==0); const unsigned post=rate==30?8:low?7:id==0x9A608086?8:9; assert(((registers[0x162890]>>12)&63)==post); From 6193904ca6e587251343ce8e41f3325bc864ee7f Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:51:11 +0900 Subject: [PATCH 25/58] Compose VBT panel delays with native PPS sequencing --- README.md | 1 + README_ja.md | 1 + WhateverXe/kern_vbt.hpp | 21 ++- WhateverXe/kern_xe.cpp | 78 +++++++++ WhateverXe/kern_xe.hpp | 7 + WhateverXe/kern_xelp_display_contract.hpp | 4 +- WhateverXe/kern_xelp_panel_power.hpp | 59 +++++++ tests/display_contract_adapter.py | 5 +- tests/firmware_ports.cpp | 27 +++ tests/tgl_panel_timing.py | 190 ++++++++++++++++++++++ tests/xelp_display_contract.cpp | 4 +- tests/xelp_panel_power.cpp | 52 ++++++ tools/test.py | 4 +- 13 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 WhateverXe/kern_xelp_panel_power.hpp create mode 100644 tests/tgl_panel_timing.py create mode 100644 tests/xelp_panel_power.cpp diff --git a/README.md b/README.md index 1d2c35ea..86080d80 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ installation or update location. receive an image. - Development builds also derive fixed external DP on TGL Combo A/B from VBT and share the SST training, power and AUX lifetime with other DP routes. This composition has CPU coverage and is not yet hardware-qualified. Cross-routed AUX and Type-C/TBT-managed Combo wiring remain excluded. - TGL eDP signal selection covers RBR through HBR3 using the physical GPU subplatform and VBT panel flags. Firmware requesting HOBL uses standard Linux fallback coefficients; the HOBL optimization remains disabled until its retry lifecycle is supported. High-rate selection has CPU coverage; the hardware results above do not qualify every link rate. +- Qualified TGL PWM/PPS 0 panels combine VBT delays, initial firmware timing and explicit panel properties while retaining native power sequencing. The new timing adapter has CPU and exact-donor coverage; other backlight methods and PPS controllers remain outside its scope. - Full system sleep/wake and resolutions above those listed above are unverified. USB-C display output currently prevents part of the display hardware from entering a low-power state. diff --git a/README_ja.md b/README_ja.md index e7a19d17..fcfab32b 100644 --- a/README_ja.md +++ b/README_ja.md @@ -117,6 +117,7 @@ Safariのインストール先や更新による配置変更に依存せずData - 同時出力は最大3画面です。4画面目には映像が出ません。 - 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 - TGL eDPの信号設定は、物理GPUのsubplatformとVBTのpanel情報からRBR〜HBR3を選択します。HOBLを要求するfirmwareでも標準のLinux fallback係数を使用します。HOBL最適化は再試行処理を実装するまで無効です。高速度の選択はCPU試験済みですが、上記の実機結果は全link rateの動作保証ではありません。 +- 資格を満たすTGLのPWM/PPS 0 panelでは、VBTの待ち時間・firmwareの初期値・明示panelプロパティを統合し、nativeの電源シーケンスを維持します。新しい待ち時間adapterはCPU・実donorコードで検証済みです。他のbacklight方式やPPS controllerはこのadapterの対象外です。 - システム全体のスリープ/復帰と、上記を超える解像度は未検証です。USB-C映像出力では現在、表示回路の一部を省電力状態へ移行させずに動作させています。 ## ロードマップ diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp index b6d5ca99..4254377a 100644 --- a/WhateverXe/kern_vbt.hpp +++ b/WhateverXe/kern_vbt.hpp @@ -29,6 +29,11 @@ struct Ports { struct Panel { bool valid {},lowSwing {},hobl {}; uint32_t type {},ddi {PortCount}; + // BDB 27: five little-endian 100us delays per panel, including T12. + uint16_t delays[5] {}; + // BDB 43 is optional and independent of the signal-policy facts. + bool backlightValid {},pwmPresent {},activeLowPWM {}; + uint32_t backlightType {},controller {},pwmHz {}; } panel; Port port[PortCount]; bool valid() const {return status==Status::Valid;} @@ -80,9 +85,9 @@ struct Ports { // Optional panel facts must never weaken General Definitions admission or // make an unrelated malformed firmware tail reject usable connector facts. inline void panelFacts(const uint8_t *b,size_t header,size_t size,Ports &ports) { - const uint8_t *options=nullptr,*edp=nullptr,*power=nullptr; - size_t optionsSize=0,edpSize=0,powerSize=0; - bool duplicate=false; + const uint8_t *options=nullptr,*edp=nullptr,*power=nullptr,*backlight=nullptr; + size_t optionsSize=0,edpSize=0,powerSize=0,backlightSize=0; + bool duplicate=false,backlightDuplicate=false; for(size_t at=header;at>(4*p.type))&15)==0; p.hobl=ports.version>=232 && (u16(power+54)&(1U<=129 && backlight[0]==6) { + const auto entry=backlight+1+6*p.type; + p.pwmPresent=(entry[0]&3)==2;p.activeLowPWM=entry[0]&4; + p.pwmHz=u16(entry+1); + p.backlightType=backlight[113+p.type]&15; + p.controller=backlight[113+p.type]>>4; + p.backlightValid=true; + } } // Return the donor's physical-DDI slot, never an FB index. Display13 VBT // retains C/D/E labels even on ADL-N, whose physical Combo set is only A/B. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e4b12680..76fcf6cb 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -29,6 +29,7 @@ #include "kern_tgl_port_map.hpp" #include "kern_tgl_combo_signal.hpp" #include "kern_tgl_panel_depth.hpp" +#include "kern_xelp_panel_power.hpp" #include "kern_tgl_pcode_gate.hpp" #include "kern_tgl_pcode_edram.hpp" #include "kern_tgl_tc_cold.hpp" @@ -10106,6 +10107,26 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } + if(tglActive) { + // PANEL_TIMING_INSTALL: exact property writer and its native consumer. + const char *symbol="__ZN31AppleIntelFramebufferController26hwGetPanelTimingPropertiesEv"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x08,0x60,0x09,0x00}; + const uint8_t call[]={0xe8,0xb5,0x4c,0x00,0x00,0x8b,0x93,0x48,0x15,0x00,0x00}; + const auto target=patcher.solveSymbol(index,symbol,address,size); + const auto config=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",address,size); + const bool known=size>=0x48651 && target==address+0x4848c && config==address+0x437a6 && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && + !memcmp(reinterpret_cast(address+0x437d2),call,sizeof(call)); + KernelPatcher::RouteRequest routes[] { + {symbol,wrapTGLPanelTiming,orgTGLPanelTiming}, + {"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",wrapTGLPanelConfig,orgTGLPanelConfig} + }; + tglPanelTimingReady=known && patcher.routeMultiple(index,routes,2,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXPanelTimingReady",tglPanelTimingReady); + patcher.clearError(); + // PANEL_TIMING_INSTALL_END + } + if (tglActive) { // TGL/Xe-LP has no dedicated eDP transcoder. Keep the allocated // pipe (or getTranscoderOffset's pipe argument), including when @@ -11014,6 +11035,60 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t return false; } +// Retain hwSetPanelPowerConfig's caller-serialization contract. The wrapper +// completes its timing-property input; it never writes PPS control/status or +// calls the panel on/off sequence recursively. Capture before either config +// branch can overwrite BIOS delays; reset on each native controller start. +void XE::tglApplyPanelTiming(void *controller,bool apply) { + auto s=callbackXE; + auto c=static_cast(controller); + const auto &v=s->firmwarePorts; + if(!c || c!=s->tglObservationController || !s->tglActive || !s->tglPanelTimingReady || + !XELP::PanelPower::nativePPS0(v,s->xeProfile.family) || !s->tglTCNativeFunctions.complete() || + *s->tglTCNativeFunctions.controllerSlot!=controller || !s->orgTGLRegisterRead || + c[0x918] || *reinterpret_cast(c+0xD0) || + *reinterpret_cast(c+0xD40)!=s->tglPlatformCopy)return; + const auto ddi=v.panel.ddi; + auto port=static_cast(s->tglTCNativeFunctions.portByDDI(ddi)); + const auto cfg=reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*ddi); + if(!port || port[0x117] || *reinterpret_cast(port+0x548)!=cfg || + cfg[0]!=ddi || cfg[2]!=0 || cfg[3]!=0 || *reinterpret_cast(port+0x550)!=1)return; + auto access=*reinterpret_cast(c+0xC40); + auto power=*reinterpret_cast(c+0xC8); + if(!access || !power || *reinterpret_cast(power+0x10)!=controller || power[0x18] || + *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(port+0x578)!=access || + !*reinterpret_cast(access+0x50) || *reinterpret_cast(access+0x60)<0xC7210)return; + if(!s->tglPanelTimingBIOSValid) { + auto &f=s->tglTCNativeFunctions; + f.getPG(power,1); + const auto read=[&](uint32_t reg){return FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,reg);}; + const auto wells=read(0x45404),fuse=read(0x42000); + if(wells!=~0U && fuse!=~0U && (wells&1) && (fuse&0x01000000)) { + const auto on=read(0xC7208),off=read(0xC720C),control=read(0xC7204); + if(on!=~0U && off!=~0U && control!=~0U) { + s->tglPanelTimingBIOS[0]=on;s->tglPanelTimingBIOS[1]=off;s->tglPanelTimingBIOS[2]=control; + s->tglPanelTimingBIOSValid=true; + } + } + f.putPG(power,1); + } + if(!apply || !s->tglPanelTimingBIOSValid)return; + const auto &bios=s->tglPanelTimingBIOS; + const auto plan=XELP::PanelPower::select(v.panel,reinterpret_cast(c+0x1548),bios[0],bios[1],bios[2]); + if(plan.valid)memcpy(c+0x1548,plan.fields,sizeof(plan.fields)); + uint32_t detail[]={1,ddi,plan.valid,plan.fields[0],plan.fields[1],plan.fields[2],plan.fields[3],plan.fields[4]}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXPanelTimingLast",detail,sizeof(detail)); +} +void XE::wrapTGLPanelConfig(void *controller,uint32_t config) { + tglApplyPanelTiming(controller,false); + FunctionCast(wrapTGLPanelConfig,callbackXE->orgTGLPanelConfig)(controller,config); +} +uint32_t XE::wrapTGLPanelTiming(void *controller) { + const auto result=FunctionCast(wrapTGLPanelTiming,callbackXE->orgTGLPanelTiming)(controller); + if(!result)tglApplyPanelTiming(controller,true); + return result; +} + bool XE::wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, OSObject *value) { if(framebuffer)return framebuffer->setProperty("AAPL,LCD-PowerState-ON",value); static uint32_t skipped {}; @@ -11183,6 +11258,7 @@ XELP::DisplayContract::Mask XE::display12Available() const { if(tglClockOffCaller)a|=C::ClockOrder; if(tglComboAUXGateReady)a|=C::ComboAUXGate; if(tglPanelDepthReady)a|=C::PanelDepth; + if(tglPanelTimingReady)a|=C::PanelTiming; return a; } @@ -11272,6 +11348,8 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { that->setProperty("TGLHostProviderWorkLoopSelected",ok); if (!ok) return false; } + // A reused controller address must not inherit another start's PPS snapshot. + callbackXE->tglPanelTimingBIOSValid=false; callbackXE->tglObservationController = that; if (checkKernelArgument("-disablegfxfirmware")) { auto live=OSSerializer::forTarget(nullptr,tglIRQLiveSerialize); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 41293e32..ae0640b4 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -255,6 +255,13 @@ class XE { mach_vm_address_t orgTGLPlaneSetup {}, orgTGLColorMode {}, orgTGLPixelInfo {}; mach_vm_address_t orgTGLModeTiming {}; bool tglPanelDepthReady {false}; + bool tglPanelTimingReady {}; + mach_vm_address_t orgTGLPanelTiming {},orgTGLPanelConfig {}; + uint32_t tglPanelTimingBIOS[3] {}; + bool tglPanelTimingBIOSValid {}; + static void tglApplyPanelTiming(void *controller,bool apply); + static void wrapTGLPanelConfig(void *controller,uint32_t config); + static uint32_t wrapTGLPanelTiming(void *controller); static uint32_t tglPanelDepthPort(void *port); static void wrapTGLModeTiming(void *controller,void *fb,void *timing); mach_vm_address_t orgTGLGamma10 {}, orgTGLRegisterWrite {}, orgTGLFastRegisterWrite {}; diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index e2f6d277..54e1e3d3 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -64,6 +64,7 @@ struct DisplayContract { ClockOrder=1ULL<<54, ComboAUXGate=1ULL<<55, PanelDepth=1ULL<<56, + PanelTiming=1ULL<<57, }; Mask required {},available {}; Mask missing() const {return required&~available;} @@ -88,7 +89,7 @@ struct DisplayContract { if(r.hdmi)required|=GMBus; if(r.leaseRequested||r.combo||r.legacy||r.alt)required|=Training12; if(r.panel||r.combo)required|=Combo12; - if(r.panel)required|=PanelDepth; + if(r.panel)required|=PanelDepth|PanelTiming|PowerGet|PowerPut; if(r.combo)required|=AUXParent; if(r.legacy)required|=Fixed12; if(r.legacy||r.alt)required|=Framing|ModeLock; @@ -158,6 +159,7 @@ struct DisplayContract { case ClockOrder:return "ClockOrder"; case ComboAUXGate:return "ComboAUXGate"; case PanelDepth:return "PanelDepth"; + case PanelTiming:return "PanelTiming"; default:return "Unknown"; } } diff --git a/WhateverXe/kern_xelp_panel_power.hpp b/WhateverXe/kern_xelp_panel_power.hpp new file mode 100644 index 00000000..84c404d8 --- /dev/null +++ b/WhateverXe/kern_xelp_panel_power.hpp @@ -0,0 +1,59 @@ +// Panel sequencing facts and a bounded bridge to the exact donor's PPS fields. +// Units/layout checked against Linux v6.12 intel_bios.h/intel_pps.c. No Linux +// implementation is included. Native PPS sequencing remains the single owner. +#ifndef KERN_XELP_PANEL_POWER_HPP +#define KERN_XELP_PANEL_POWER_HPP +#include "kern_vbt.hpp" +namespace XELP { +struct PanelPower { + // The current donor has one panel and addresses PPS/PWM controller 0. + // This qualification does not claim PPS1, AUX backlight or panel admission + // on another platform; those need their own register/lifecycle adapters. + static bool nativePPS0(const VBT::Ports &v,Family family) { + const auto &p=v.panel; + if(family!=Family::TigerLake || !v.valid() || v.unmapped || !p.valid || p.ddi>=2 || + !p.backlightValid || !p.pwmPresent || p.backlightType!=2 || p.controller)return false; + const auto &port=v.port[p.ddi];const auto aux=p.ddi?0x10U:0x40U; + if(!(v.present&(1U<0x1fff)return p; + if(native[4]>31)return p; + const uint32_t bios[]={on>>16&0x1fff,on&0x1fff,off&0x1fff,off>>16&0x1fff,(control>>4&31)*1000}; + const uint32_t defaults[]={2100,500,500,5000,6100}; + const unsigned index[]={2,0,1,3,4}; + bool any=false;for(auto v:panel.delays)any|=v!=0; + for(unsigned i=0;i<5;++i) { + uint32_t v=panel.delays[i]; + // Linux adjusts VBT T12 once, before merging; never add to a BIOS readback. + if(i==4 && any)v+=1000; + if(v(i==4?31U:0x1fffU))return {}; + p.fields[index[i]]=v; + } + // Unlike i915, the donor owns the hardware backlight waits. Keep T8/T9; + // do not copy i915's value 1, which relies on separate software sleeps. + p.valid=true;return p; + } +}; +} +#endif diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index c91da08e..b9d7348e 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -27,7 +27,7 @@ }; int main() { State s; - assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth)); + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming)); assert(s.display13Available()==C::Backend13); // TGL: candidate not yet registered, native donor setup is complete. // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. @@ -46,7 +46,8 @@ s.adlsDetected=false;s.adlsWiring.dp=0;s.firmwarePorts.panel.valid=true; s.tglTCNativeFunctions.valid=false;assert(s.start().missing()==C::TCNative);s.tglTCNativeFunctions.valid=true; s.tglComboAUXGateReady=false;assert(s.start().missing()==C::ComboAUXGate);s.tglComboAUXGateReady=true; - s.tglPanelDepthReady=false;assert(s.start().missing()==C::PanelDepth); + s.tglPanelDepthReady=false;assert(s.start().missing()==C::PanelDepth);s.tglPanelDepthReady=true; + s.tglPanelTimingReady=false;assert(s.start().missing()==C::PanelTiming); return 0; } ''' diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index 17e52b4a..2697c060 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -141,5 +141,32 @@ int main(int argc,char **argv){ auto q=parse(b.data(),b.size(),Family::TigerLake); assert(q.valid()&&!q.panel.valid); } + // PPS sequences select all five fields for all 16 panel types. Backlight + // corruption is independent of signal/PPS facts and unrelated connectors. + auto bl=append(panelTable,43,129);panelTable[bl]=6; + for(unsigned n=0;n<16;++n) { + for(unsigned i=0;i<5;++i)w16(panelTable,edp+10*n+2*i,100*n+i+1); + panelTable[bl+1+6*n]=6;w16(panelTable,bl+2+6*n,200+n); + panelTable[bl+113+n]=uint8_t(n<<4|2); + } + for(unsigned n=0;n<16;++n) { + auto b=panelTable;b[options]=n;auto q=parse(b.data(),b.size(),Family::TigerLake); + assert(q.panel.valid && q.panel.backlightValid && q.panel.pwmPresent && q.panel.activeLowPWM); + assert(q.panel.controller==n && q.panel.backlightType==2 && q.panel.pwmHz==200+n); + for(unsigned i=0;i<5;++i)assert(q.panel.delays[i]==100*n+i+1); + } + for(unsigned bad=0;bad<3;++bad) { + auto b=panelTable; + if(bad==0)b[bl]=7; + if(bad==1)w16(b,bl-2,128); + if(bad==2)append(b,43,129); + auto q=parse(b.data(),b.size(),Family::TigerLake); + assert(q.panel.valid && !q.panel.backlightValid && q.hdmi(3).hdmi==2); + } + for(size_t n=bl;n1: + b=Path(sys.argv[1]).read_bytes() + assert b[0x4848c:0x4848c+20]==bytes(int(x,0) for x in entry.split(',')) + assert b[0x437d2:0x437d2+11]==bytes(int(x,0) for x in call.split(',')) + for at,key in [(0xa9951,b'AAPL00,PanelPowerUp'),(0xa9965,b'AAPL00,PanelPowerOn'),(0xa9979,b'AAPL00,PanelPowerDown'),(0xa998f,b'AAPL00,PanelPowerOff'),(0xa99a4,b'AAPL00,PanelCycleDelay')]: + assert b[at:at+len(key)+1]==key+b'\0' +code=r''' +#include "kern_xelp_panel_power.hpp" +#include "kern_tgl_tc_native.hpp" +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +#define FunctionCast(fn,addr) reinterpret_cast(addr) +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&) {}}; + uintptr_t solveSymbol(unsigned,const char *s,uintptr_t a,size_t){return a+(strstr(s,"GetPanel")?0x4848c:0x437a6)+delta;} + bool routeMultiple(unsigned,RouteRequest*,unsigned,uintptr_t,size_t){++routes;return !fail;} + void clearError(){} +}; +struct XE { + static XE *callbackXE; + bool tglActive=true,tglPanelTimingReady=true; + uintptr_t orgTGLPanelTiming=0,orgTGLRegisterRead=0,orgTGLPanelConfig=0; + uint32_t tglPanelTimingBIOS[3]{};bool tglPanelTimingBIOSValid=false; + XELP::VBT::Ports firmwarePorts; + struct {XELP::Family family=XELP::Family::TigerLake;}xeProfile; + TGLTCNativeFunctions tglTCNativeFunctions; + void *tglObservationController=nullptr; + uint64_t tglPlatformCopy[0x1c0/8]{}; + static uint32_t wrapTGLPanelTiming(void *); + static void wrapTGLPanelConfig(void *,uint32_t); + static void tglApplyPanelTiming(void *,bool); + void start(void *that){START} + static uint32_t wrapTGLRegisterRead(void *,unsigned long); + void install(KernelPatcher &patcher,unsigned index,uintptr_t address,size_t size){INSTALL} +}; +XE *XE::callbackXE; +WRAPPER +alignas(8) uint8_t ctl[0x1600]{},port[0x630]{},access[0x80]{},power[0x90]{}; +static void *owner=ctl; +static uint32_t input[5]{},result=0,on=0,off=0,control=0,wells=1,fuse=0x1000000; +static unsigned calls=0,refs=0,get=0,put=0,reads=0,writes=0; +template T& at(void *p,unsigned n){return *reinterpret_cast(static_cast(p)+n);} +static uint32_t original(void *c){++calls;memcpy(static_cast(c)+0x1548,input,sizeof(input));return result;} +static void *lookup(uint32_t d){assert(d<2);return port;} +static void pgGet(void *p,uint32_t n){assert(p==power&&n==1);++get;++refs;} +static void pgPut(void *p,uint32_t n){assert(p==power&&n==1&&refs==1);++put;--refs;} +static uint32_t read(void *a,unsigned long reg){ + assert(a==access&&refs==1);++reads; + switch(reg){case 0x45404:return wells;case 0x42000:return fuse;case 0xC7208:return on;case 0xC720C:return off;case 0xC7204:return control;default:assert(false);return ~0U;} +} +static void write(void *a,unsigned long reg,uint32_t value){ + assert(a==access&&refs==0);++writes; + switch(reg){case 0xC7208:on=value;break;case 0xC720C:off=value;break;default:assert(false);} +} +static uint32_t base(void *,uint32_t){return 0;} +static void aux(void *){} +struct Datum {uintptr_t *vtable;uint32_t value;}; +static Datum properties[5]{};static unsigned propertyCalls=0; +static void *property(void *,const char *key){ + const char *keys[]={"AAPL00,PanelPowerUp","AAPL00,PanelPowerOn","AAPL00,PanelPowerDown","AAPL00,PanelPowerOff","AAPL00,PanelCycleDelay"}; + for(unsigned i=0;i<5;++i)if(!strcmp(key,keys[i])){++propertyCalls;return &properties[i];} + assert(false);return nullptr; +} +static void *bytes(Datum *d){return &d->value;} +static void *cast(void *object,void *){return object;} +static void jump(uint8_t *p,uintptr_t target){const uint8_t code[]={0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xe0};memcpy(p,code,sizeof(code));memcpy(p+2,&target,sizeof(target));} +static void patchCall(uint8_t *mem,unsigned at,unsigned target){const int32_t displacement=target-at-5;mem[at]=0xe8;memcpy(mem+at+1,&displacement,4);} + +static void setup(XE &s){ + XE::callbackXE=&s;memset(ctl,0,sizeof(ctl));memset(port,0,sizeof(port));memset(access,0,sizeof(access));memset(power,0,sizeof(power)); + memset(input,0,sizeof(input));calls=get=put=reads=writes=refs=result=on=off=control=0;wells=1;fuse=0x1000000;owner=ctl; + s.orgTGLPanelTiming=(uintptr_t)original;s.orgTGLRegisterRead=(uintptr_t)read;s.tglObservationController=ctl; + s.tglTCNativeFunctions={base,aux,aux,pgGet,pgPut,lookup,&owner,true}; + auto &v=s.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78C6,10,0,0x40,0,0}; + auto &p=v.panel;p.valid=true;p.ddi=0;p.backlightValid=true;p.pwmPresent=true;p.backlightType=2; + const uint16_t delay[]={2500,700,800,5500,5000};memcpy(p.delays,delay,sizeof(delay)); + at(ctl,0xc40)=access;at(ctl,0xc8)=power;at(ctl,0xd40)=s.tglPlatformCopy; + at(port,0x548)=reinterpret_cast(s.tglPlatformCopy)+0x20;at(port,0x550)=1;at(port,0x578)=access; + at(power,0x10)=ctl;at(power,0x78)=access;at(access,0x50)=access;at(access,0x60)=0x200000; +} +int main(int argc,char **argv){ + // Execute the production installer, including short image/symbol/byte/route failures. + std::vector image(0x49000);const uint8_t entry[]={ENTRY},call[]={CALL}; + memcpy(image.data()+0x4848c,entry,sizeof(entry));memcpy(image.data()+0x437d2,call,sizeof(call)); + for(unsigned n=0;n<6;++n){ + XE s;KernelPatcher p;size_t size=image.size(); + if(n==0)size=0x48650;if(n==1)p.delta=1;if(n==2)image[0x4848c]^=1;if(n==3)image[0x437d2]^=1;if(n==4)p.fail=true; + s.install(p,0,(uintptr_t)image.data(),size);assert(s.tglPanelTimingReady==(n==5)); + assert(p.routes==(n>=4?1U:0U)); + memcpy(image.data()+0x4848c,entry,sizeof(entry));memcpy(image.data()+0x437d2,call,sizeof(call)); + } + for(unsigned n=0;n<25;++n){ + XE s;setup(s);auto &p=s.firmwarePorts.panel; + switch(n){ + case 0:s.tglActive=false;break;case 1:s.tglPanelTimingReady=false;break;case 2:s.tglObservationController=nullptr;break; + case 3:s.xeProfile.family=XELP::Family::AlderLakeS;break;case 4:p.controller=1;break;case 5:p.backlightType=5;break; + case 6:p.backlightValid=false;break;case 7:s.tglTCNativeFunctions.qualified=false;break;case 8:owner=nullptr;break; + case 9:s.orgTGLRegisterRead=0;break;case 10:ctl[0x918]=1;break;case 11:at(ctl,0xd0)=port;break; + case 12:at(ctl,0xd40)=nullptr;break;case 13:port[0x117]=1;break;case 14:at(port,0x548)=ctl;break; + case 15:at(port,0x550)=0;break;case 16:at(port,0x578)=nullptr;break; + case 17:at(power,0x10)=nullptr;break;case 18:power[0x18]=1;break;case 19:at(power,0x78)=nullptr;break; + case 20:at(access,0x50)=nullptr;break;case 21:at(access,0x60)=0xc720c;break; + case 22:result=0xe00002bc;break;case 23:s.firmwarePorts.unmapped=1;break;case 24:s.firmwarePorts.port[0].aux=0x10;break; + } + assert(XE::wrapTGLPanelTiming(ctl)==result && calls==1 && get==0 && reads==0 && writes==0); + assert(!memcmp(ctl+0x1548,input,sizeof(input))); + } + for(unsigned n=0;n<7;++n){ + XE s;setup(s); + if(n==0)wells=0;if(n==1)wells=~0U;if(n==2)fuse=0;if(n==3)fuse=~0U; + if(n==4)on=~0U;if(n==5)s.firmwarePorts.panel.delays[3]=9000; + assert(XE::wrapTGLPanelTiming(ctl)==0&&calls==1&&get==1&&put==1&&refs==0&&writes==0); + assert(reads==(n<4?2U:5U)); + const uint32_t expected[]={700,800,2500,5500,6}; + assert(!memcmp(ctl+0x1548,n==6?expected:input,sizeof(input))); + } + // Optional exact native hwSetPanelPowerConfig replay. Only its property + // callsite, imported services and virtual register writer are replaced; the encoding/masking, + // property conversions, defaults, cached fields and write order are real donor code. + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{});assert(b.size()>0xde4e0); + const auto stubs=unsigned((b.size()+4095)&~4095U);const size_t length=stubs+4096; + 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()); + jump(mem+stubs,(uintptr_t)XE::wrapTGLPanelTiming);patchCall(mem,0x437d2,stubs); + jump(mem+stubs+16,(uintptr_t)memset);patchCall(mem,0x484b1,stubs+16); + jump(mem+stubs+32,(uintptr_t)cast); + for(unsigned site: {0x484ddU,0x48524U,0x48568U,0x485afU,0x485f6U})patchCall(mem,site,stubs+32); + uintptr_t meta=0;const uintptr_t metaPointer=(uintptr_t)&meta;memcpy(mem+0xc8050,&metaPointer,sizeof(metaPointer)); + assert(mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)==0); + auto nativeConfig=reinterpret_cast(mem+0x437a6); + uintptr_t vtable[40]{};vtable[0x130/8]=(uintptr_t)write; + uintptr_t providerTable[90]{},dataTable[50]{};providerTable[0x2b8/8]=(uintptr_t)property;dataTable[0x178/8]=(uintptr_t)bytes; + auto provider=providerTable;for(auto &p:properties)p.vtable=dataTable; + XE s;setup(s);at(access,0)=vtable;s.orgTGLPanelConfig=(uintptr_t)nativeConfig; + s.orgTGLPanelTiming=(uintptr_t)(mem+0x4848c);at(ctl,0xc60)=&provider; + const uint32_t milliseconds[]={300,100,600,90,800};for(unsigned i=0;i<5;++i)properties[i].value=milliseconds[i]; + const auto config=XE::wrapTGLPanelConfig; + control=0xabcd0080;at(ctl,0xd80)=control; + on=4000U<<16|1200;off=8000U<<16|1100; + // First config disables PPS and overwrites the hardware with short delays. + // The wrapper must capture BIOS before that first write, not at first ON. + config(ctl,0);assert(on==0&&off==3000&&get==1&&put==1&&calls==0); + writes=0; + // BIOS 800ms cycle beats VBT 600ms; native property 900ms then beats both. + for(unsigned n=0;n<16;++n){ + s.firmwarePorts.panel.type=n; + properties[4].value=n*100+1;const auto expectedCycle=n+2<8?8:n+2; + config(ctl,1); + assert(on==(4000U<<16|1200)&&off==(8000U<<16|1100)); + assert(at(ctl,0xd78)==on&&at(ctl,0xd7c)==off); + assert(at(ctl,0xd80)==(0xabcd0000|(expectedCycle<<4))); + assert(refs==0&&get==1&&put==1&&propertyCalls==5*(n+1)&&writes==4*n+2); + config(ctl,0);assert(on==0&&off==3000); + } + config(ctl,0);assert(on==0&&off==3000&&at(ctl,0xd80)==0xabcd0000); + assert(propertyCalls==80&&get==1&&put==1); + // Execute the actual startup invalidation, even for a reused address. + s.start(ctl);assert(!s.tglPanelTimingBIOSValid); + on=5000U<<16|1300;off=8100U<<16|1150; + config(ctl,1);assert(on==(5000U<<16|1300)&&off==(8100U<<16|1150)&&get==2&&put==2); + munmap(mem,length); + } + puts("PASS production PPS timing installer, ownership/method guards, balanced PG, native consumer replay"); +} +'''.replace('INSTALL',install).replace('START',start).replace('WRAPPER',wrapper).replace('ENTRY',entry).replace('CALL',call) +with tempfile.TemporaryDirectory(prefix='panel-timing-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True,env=dict(os.environ,UBSAN_OPTIONS="halt_on_error=1")) diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index 5913b149..83eb3576 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<57;++i) { + for(unsigned i=0;i<58;++i) { auto broken=c;auto bit=1ULL< +#include +#include +using P=XELP::PanelPower; +int main() { + XELP::VBT::Ports v;v.status=XELP::VBT::Status::Valid;v.present=1; + v.port[0]={0x78C6,10,0,0x40,0,0}; + auto &panel=v.panel;panel.valid=true;panel.ddi=0; + panel.backlightValid=true;panel.pwmPresent=true;panel.backlightType=2; + assert(P::nativePPS0(v,XELP::Family::TigerLake)); + for(unsigned controller=0;controller<16;++controller)for(unsigned type=0;type<16;++type) { + panel.controller=controller;panel.backlightType=type; + assert(P::nativePPS0(v,XELP::Family::TigerLake)==(!controller && type==2)); + } + panel.controller=0;panel.backlightType=2; + assert(!P::nativePPS0(v,XELP::Family::AlderLakeS)); + v.present|=2;v.port[1]=v.port[0];assert(!P::nativePPS0(v,XELP::Family::TigerLake));v.present=1; + const uint32_t zero[5]={}; + auto plan=P::select(panel,zero,0,0,0); + const uint32_t defaults[]={500,500,2100,5000,7}; + assert(plan.valid && !memcmp(plan.fields,defaults,sizeof(defaults))); + // Each source wins independently; rounding never shortens T12. BIOS cycle + // readback is already encoded, so repeated power calls cannot grow it. + unsigned cases=0; + for(unsigned i=0;i<5;++i)for(unsigned n: {0U,1U,999U,1000U,1001U,6000U,8191U,8192U,30000U,30001U,65535U}) { + auto p=panel;p.delays[i]=n; + const auto q=P::select(p,zero,0,0,0); + const unsigned index[]={2,0,1,3,4}; + const auto expected=i==4 ? (n+1000+999)/1000 : n; + assert(q.valid==(i==4 ? expected<=31 : n<=8191)); + if(q.valid) { + if(n)assert(q.fields[index[i]]==expected); + uint32_t on=(q.fields[2]<<16)|q.fields[0],off=(q.fields[3]<<16)|q.fields[1]; + auto again=P::select(p,zero,on,off,q.fields[4]<<4); + assert(again.valid && !memcmp(q.fields,again.fields,sizeof(q.fields))); + } + ++cases; + } + uint32_t native[]={3000,2000,7000,8000,20}; + panel.delays[0]=7100;panel.delays[1]=4000;panel.delays[2]=1;panel.delays[3]=2;panel.delays[4]=5000; + plan=P::select(panel,native,(6000U<<16)|100,100U<<16|2500,12<<4); + const uint32_t mixed[]={4000,2500,7100,8000,20}; + assert(plan.valid && !memcmp(plan.fields,mixed,sizeof(mixed))); + for(unsigned i=0;i<5;++i) {auto saved=native[i];native[i]=i==4?32:8192;assert(!P::select(panel,native,0,0,0).valid);native[i]=saved;} + assert(!P::select(panel,native,~0U,0,0).valid); + assert(!P::select(panel,native,0,~0U,0).valid); + assert(!P::select(panel,native,0,0,~0U).valid); + // An explicit property remains effective even if the donor's T8 is absent. + native[0]=0;plan=P::select(panel,native,0,0,0);assert(plan.valid && plan.fields[3]==8000); + printf("PASS panel controller/method qualification; %u delay boundaries, source merging, overflow and idempotence\n",cases); +} diff --git a/tools/test.py b/tools/test.py index 97458552..87845e5c 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,9 +7,9 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['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 += ['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 += ['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_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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From 2dee88c6b6c1d0d4d36a6654d776e6a045a40f4a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:20:52 +0900 Subject: [PATCH 26/58] Compose ADL-S native eDP with VBT panel and PWM policy --- README.md | 7 +- README_ja.md | 5 +- WhateverXe/kern_alderlake_s.hpp | 28 ++++- WhateverXe/kern_host.cpp | 2 +- WhateverXe/kern_tgl_combo_signal.hpp | 6 +- WhateverXe/kern_xe.cpp | 113 ++++++++++++++++-- WhateverXe/kern_xe.hpp | 11 +- WhateverXe/kern_xelp_display_contract.hpp | 7 +- WhateverXe/kern_xelp_panel_power.hpp | 6 +- WhateverXe/kern_xelp_panel_pwm.hpp | 24 ++++ WhateverXe/kern_xelp_probe.hpp | 3 +- WhateverXe/kern_xelp_sst_route.hpp | 7 +- tests/adls_aux_parent.py | 3 +- tests/adls_clock_guard.py | 6 + tests/adls_clock_native_fixture.cpp | 8 +- tests/adls_donor.py | 8 ++ tests/adls_panel_pwm.py | 135 ++++++++++++++++++++++ tests/alderlake_s.cpp | 36 ++++++ tests/display_contract_adapter.py | 2 +- tests/host_publication.py | 11 +- tests/tgl_combo_aux_lifetime.py | 7 +- tests/tgl_combo_signal_guard.py | 21 ++++ tests/tgl_hdmi_wiring_fixture.cpp | 14 +++ tests/tgl_panel_depth_route.py | 18 ++- tests/tgl_panel_timing.py | 15 ++- tests/tgl_training_pattern_fixture.cpp | 11 ++ tests/xelp_display_contract.cpp | 9 +- tests/xelp_panel_power.cpp | 2 +- tests/xelp_panel_pwm.cpp | 21 ++++ tests/xelp_platform.cpp | 6 + tests/xelp_sst_route.cpp | 8 ++ tools/test.py | 4 +- 32 files changed, 518 insertions(+), 46 deletions(-) create mode 100644 WhateverXe/kern_xelp_panel_pwm.hpp create mode 100644 tests/adls_panel_pwm.py create mode 100644 tests/xelp_panel_pwm.cpp diff --git a/README.md b/README.md index 86080d80..873ff225 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,12 @@ installation or update location. `0C` with the qualified fuse configuration. Other port layouts have CPU-test coverage but are not hardware-qualified. A USB-C connector exposed by VBT as fixed DP on Combo PHY A has also been tested. This does not qualify - Type-C/TBT-managed routes. Sleep/wake remains unqualified. + Type-C/TBT-managed routes. Development eDP routing also composes a + VBT-qualified DDI A panel with native PPS0 and PWM controller 0, including + panel timing, signal settings and firmware-derived PWM period/polarity. + CPU tests and exact-donor replay cover this path; ADL-S eDP has not been + tested on hardware. PPS1 and AUX-controlled backlights remain unsupported. + Sleep/wake remains unqualified. - Display 13 USB-C and eDP support ### Not Planned diff --git a/README_ja.md b/README_ja.md index fcfab32b..d068eb23 100644 --- a/README_ja.md +++ b/README_ja.md @@ -135,7 +135,10 @@ Safariのインストール先や更新による配置変更に依存せずData GPUの適用対象は、確認済みのfuse構成を持つ `4690` revision `0C` に限定しています。 別のポート配置はCPUテストで確認していますが、実機では未検証です。 VBT上でCombo PHY Aの固定DPとして扱われるUSB-C端子でも実表示を確認しています。 - これはType-C/TBT管理経路の動作確認ではありません。消灯/復帰は未検証です。 + これはType-C/TBT管理経路の動作確認ではありません。開発版のeDP経路は、VBTで確認したDDI Aのpanelを + native PPS0/PWM controller 0に接続し、panel timing、信号設定、firmware由来のPWM周期・極性を適用します。 + この経路はCPU試験と実donor再生で検証していますが、ADL-S eDPの実機検証は未実施です。 + PPS1とAUX制御のbacklightは未対応です。消灯/復帰は未検証です。 - Display 13のUSB-C / eDP対応 ### Not Planned diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 0c63ba61..6bdfb403 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -5,6 +5,7 @@ #define KERN_ALDERLAKE_S_HPP #include #include "kern_vbt.hpp" +#include "kern_xelp_panel_power.hpp" namespace AlderLakeS { constexpr uint32_t PortMask=0x79; // A, TC1, TC2, TC3, TC4 @@ -62,9 +63,9 @@ inline uint32_t gmbus(uint32_t vbtPin) { // Immutable firmware-derived routing shared by startup, clocks, training and // hotplug. Unsupported or ambiguous routes stay excluded; never guess a port. struct Wiring { - uint32_t hdmi {},dp {},excluded {},pin[XELP::PortCount] {}; + uint32_t hdmi {},dp {},excluded {},pin[XELP::PortCount] {},panel {}; bool valid {}; - uint32_t selected() const {return hdmi|dp;} + uint32_t selected() const {return hdmi|dp|panel;} bool hasDP(uint32_t d) const {return valid && d=XELP::PortCount || @@ -72,12 +73,18 @@ struct Wiring { return c[1]==pin[c[0]] && c[2]==(wantDP?1U:2U) && !c[3] && c[4]==(wantDP?0x400U:0x800U) && c[5]==(wantDP?0x201U:0x202U); } - bool matches(const uint32_t *c) const {return matches(c,false) || matches(c,true);} + bool matchesPanel(const uint32_t *c) const { + const uint32_t expected[]={0,0,0,0,2,0x18}; + return valid && panel==1 && !(panel&(hdmi|dp)) && c && !memcmp(c,expected,sizeof(expected)); + } + bool matches(const uint32_t *c) const {return matches(c,false) || matches(c,true) || matchesPanel(c);} bool bound(const void *platform,const uint32_t *c) const { return platform && matches(c) && c==reinterpret_cast( static_cast(platform)+0x20+24*c[0]); } bool boundDP(const void *platform,const uint32_t *c) const {return matches(c,true) && bound(platform,c);} + bool boundPanel(const void *platform,const uint32_t *c) const {return matchesPanel(c) && bound(platform,c);} + bool boundSST(const void *platform,const uint32_t *c) const {return boundDP(platform,c) || boundPanel(platform,c);} bool usesDDC(uint32_t bus) const { if(!valid)return false; for(unsigned d=0;d(platform); if(p[0x12]!=9)return false; + // The exact donor's platform tail selects no TCON / Camellia / Banksia. + // VBT-qualified PWM uses native PPS, not an Apple TCON protocol. + const auto tcon=*reinterpret_cast(p+0x190); + if(panel && tcon!=0 && tcon!=2 && tcon!=3)return false; // Check every selected predecessor before mutating any record. for(unsigned d=0;d=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; @@ -109,15 +120,22 @@ struct Wiring { (d || memcmp(p+0x20,panel,sizeof(panel))))return false; } for(unsigned d=0;d(p+0x20+24*d);const bool isDP=dp&(1U<(p+0x20+24*d); + if(panel&(1U<(p+0x190)=0; return true; } static Wiring fromFirmware(const XELP::VBT::Ports &ports) { Wiring out; if(!ports.valid())return out; out.excluded=ports.present; + if(XELP::PanelPower::nativePPS0(ports,XELP::Family::AlderLakeS)) { + const uint32_t zero[5]={}; + if(XELP::PanelPower::select(ports.panel,zero,0,0,0).valid){out.panel=1;out.excluded&=~1U;} + } for(unsigned d=0;dfirmwareVendor!=DeviceInfo::FirmwareVendor::Apple; backlightGPU=backlightEnabled ? gpu : nullptr; if(published) { diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index c2ed8f88..9f08aa43 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -42,13 +42,13 @@ struct TGLComboSignal { } // The platform adapter chooses coordinates; electrical policy contains no // physical DDI, donor field or board-specific connector condition. - bool selectADLS(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis) { + bool selectADLS(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis,bool panel=false,bool lowSwing=false) { if(count!=1 && count!=2 && count!=4)return false; AlderLakeS::Port route; if(!AlderLakeS::Port::resolve(ddi,route) || !(AlderLakeS::ComboBackendMask&(1U<=0x508c4 && lightFn==address+0x5063e && brightnessFn==address+0x4a56a && + !memcmp(reinterpret_cast(lightFn),lightEntry,sizeof(lightEntry)) && + !memcmp(reinterpret_cast(brightnessFn),brightnessEntry,sizeof(brightnessEntry)) && + !memcmp(reinterpret_cast(address+0x507e9),enable,sizeof(enable)); + KernelPatcher::RouteRequest routes[] {{light,wrapADLSLightUpEDP,orgADLSLightUpEDP}, + {brightness,wrapADLSPanelBacklight,orgADLSPanelBacklight}}; + if(known && patcher.routeMultiple(index,routes,2,address,size)) { + uint8_t bridge[]={0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,0x90,0x90,0x90,0x90}; + const auto handler=reinterpret_cast(wrapADLSPanelPWMEnable);memcpy(bridge+2,&handler,sizeof(handler)); + patcher.routeBlock(address+0x507e9,bridge,sizeof(bridge)); + adlsPanelPWMReady=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+0x507e9),bridge,sizeof(bridge)); + } + IORegistryEntry::getRegistryRoot()->setProperty("ADLSPanelPWMReady",adlsPanelPWMReady); + patcher.clearError(); + // ADLS_PANEL_PWM_INSTALL_END + } + if (tglActive) { // TGL/Xe-LP has no dedicated eDP transcoder. Keep the allocated // pipe (or getTranscoderOffset's pipe argument), including when @@ -11019,7 +11046,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t } IORegistryEntry::getRegistryRoot()->setProperty("ADLSLanePowerReady",adlsPowerReady); // Native startup remains closed if any required ADL-S adapter is absent. - const auto contract=XELP::DisplayContract::adls(display12Available(),adlsWiring.dp!=0); + const auto contract=XELP::DisplayContract::adls(display12Available(),adlsWiring.dp!=0,adlsWiring.panel!=0); adlsDisplayHooksReady=contract.ready(); uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayStartupContract",contractRecord,sizeof(contractRecord)); @@ -11052,7 +11079,8 @@ void XE::tglApplyPanelTiming(void *controller,bool apply) { auto port=static_cast(s->tglTCNativeFunctions.portByDDI(ddi)); const auto cfg=reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*ddi); if(!port || port[0x117] || *reinterpret_cast(port+0x548)!=cfg || - cfg[0]!=ddi || cfg[2]!=0 || cfg[3]!=0 || *reinterpret_cast(port+0x550)!=1)return; + cfg[0]!=ddi || cfg[2]!=0 || cfg[3]!=0 || *reinterpret_cast(port+0x550)!=1 || + (s->adlsDetected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg)))return; auto access=*reinterpret_cast(c+0xC40); auto power=*reinterpret_cast(c+0xC8); if(!access || !power || *reinterpret_cast(power+0x10)!=controller || power[0x18] || @@ -11089,6 +11117,72 @@ uint32_t XE::wrapTGLPanelTiming(void *controller) { return result; } +// ADL-S native PWM panel adapter. The panel lifecycle remains synchronous +// and donor-owned; only its period, polarity and duty arithmetic are replaced. +bool XE::adlsPanelPWMTarget(void *controller,void *&access,void *&power) { + auto s=callbackXE;auto c=static_cast(controller);access=power=nullptr; + if(!s->adlsDetected || s->xeProfile.family!=XELP::Family::AlderLakeS || !s->adlsPanelPWMReady || !s->tglActive || !c || c!=s->tglObservationController || + !s->tglTCNativeFunctions.complete() || *s->tglTCNativeFunctions.controllerSlot!=controller || + !XELP::PanelPower::nativePPS0(s->firmwarePorts,s->xeProfile.family) || c[0x918] || + *reinterpret_cast(c+0xD0) || *reinterpret_cast(c+0xD40)!=s->tglPlatformCopy || + !s->orgTGLRegisterRead || !s->orgTGLRegisterWrite)return false; + auto port=static_cast(s->tglTCNativeFunctions.portByDDI(0)); + if(!port || port[0x117] || *reinterpret_cast(port+0x550)!=1 || !s->adlsWiring.boundPanel(s->tglPlatformCopy,*reinterpret_cast(port+0x548)))return false; + auto a=*reinterpret_cast(c+0xC40);auto p=*reinterpret_cast(c+0xC8); + if(!a || !p || *reinterpret_cast(p+0x10)!=controller || p[0x18] || + *reinterpret_cast(p+0x78)!=a || *reinterpret_cast(port+0x578)!=a || + !*reinterpret_cast(a+0x50) || *reinterpret_cast(a+0x60)<0xC825C)return false; + access=a;power=p;return true; +} +bool XE::adlsCapturePanelPWM(void *controller) { + auto s=callbackXE;void *access,*power; + if(!adlsPanelPWMTarget(controller,access,power))return false; + if(s->adlsPanelPWM.valid())return true; + auto &f=s->tglTCNativeFunctions;f.getPG(power,1); + const auto read=[&](uint32_t reg){return FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,reg);}; + const auto wells=read(0x45404),fuse=read(0x42000); + if(wells!=~0U && fuse!=~0U && (wells&1) && (fuse&0x01000000)) { + const auto period=read(0xC8254),control=read(0xC8250); + const auto &p=s->firmwarePorts.panel; + s->adlsPanelPWM=XELP::PanelPWM::select(period,control,period?0:read(0xC2014),p.pwmHz,p.activeLowPWM); + } + f.putPG(power,1);return s->adlsPanelPWM.valid(); +} +uint32_t XE::wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void *timing) { + auto s=callbackXE; + if(s->adlsDetected && s->adlsWiring.panel) { + if(!adlsCapturePanelPWM(controller))return kIOReturnNotReady; + *reinterpret_cast(static_cast(controller)+0xE60)=s->adlsPanelPWM.period; + } + return FunctionCast(wrapADLSLightUpEDP,s->orgADLSLightUpEDP)(controller,fb,path,timing); +} +uint32_t XE::wrapADLSPanelBacklight(void *controller,uint32_t brightness) { + auto s=callbackXE; + if(!s->adlsDetected || !s->adlsWiring.panel) + return FunctionCast(wrapADLSPanelBacklight,s->orgADLSPanelBacklight)(controller,brightness); + void *access,*power;uint32_t duty; + if(!adlsCapturePanelPWM(controller) || !adlsPanelPWMTarget(controller,access,power) || + !s->adlsPanelPWM.duty(brightness,duty))return kIOReturnNotReady; + auto &f=s->tglTCNativeFunctions;f.getPG(power,1); + const auto wells=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,0x45404); + const auto fuse=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,0x42000); + const bool ready=wells!=~0U && fuse!=~0U && (wells&1) && (fuse&0x01000000); + if(ready) { + wrapTGLRegisterWrite(access,0xC8258,duty); + *reinterpret_cast(static_cast(controller)+0xE58)=brightness; + } + f.putPG(power,1);return ready?0:kIOReturnNotReady; +} +void XE::wrapADLSPanelPWMEnable(void *access) { + auto s=callbackXE;uint32_t value=0x80000000; + if(s->adlsDetected && s->adlsWiring.panel) { + void *expected,*power; + if(!s->adlsPanelPWM.valid() || !adlsPanelPWMTarget(s->tglObservationController,expected,power) || access!=expected)return; + value|=s->adlsPanelPWM.polarity; + } + wrapTGLRegisterWrite(access,0xC8250,value); +} + bool XE::wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, OSObject *value) { if(framebuffer)return framebuffer->setProperty("AAPL,LCD-PowerState-ON",value); static uint32_t skipped {}; @@ -11194,7 +11288,7 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { if(provider && provider->getProperty("tgl-hdmi-wiring"))return false; self->adlsWiring=AlderLakeS::Wiring::fromFirmware(self->firmwarePorts); self->tglHDMIB=self->adlsWiring.valid && self->adlsWiring.selected(); - uint32_t routes[]={self->adlsWiring.hdmi,self->adlsWiring.dp,self->adlsWiring.excluded,AlderLakeS::ComboBackendMask}; + uint32_t routes[]={self->adlsWiring.hdmi,self->adlsWiring.dp,self->adlsWiring.excluded,AlderLakeS::ComboBackendMask,self->adlsWiring.panel}; IORegistryEntry::getRegistryRoot()->setProperty("ADLSFirmwareRoutes",routes,sizeof(routes)); } return self->tglHDMIB; @@ -11259,6 +11353,7 @@ XELP::DisplayContract::Mask XE::display12Available() const { if(tglComboAUXGateReady)a|=C::ComboAUXGate; if(tglPanelDepthReady)a|=C::PanelDepth; if(tglPanelTimingReady)a|=C::PanelTiming; + if(adlsPanelPWMReady)a|=C::PanelPWM; return a; } @@ -11350,6 +11445,7 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { } // A reused controller address must not inherit another start's PPS snapshot. callbackXE->tglPanelTimingBIOSValid=false; + callbackXE->adlsPanelPWM={}; callbackXE->tglObservationController = that; if (checkKernelArgument("-disablegfxfirmware")) { auto live=OSSerializer::forTarget(nullptr,tglIRQLiveSerialize); @@ -14716,12 +14812,13 @@ uint32_t XE::tglPanelDepthPort(void *port) { auto s=callbackXE;auto p=static_cast(port); auto c=static_cast(s->tglObservationController); if(!p || !c || !s->tglActive || !s->tglPanelDepthReady || !tglAUXLock || - s->xeProfile.family!=XELP::Family::TigerLake)return 9; + (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected))return 9; auto cfg=*reinterpret_cast(p+0x548); if(!cfg || cfg[0]>1 || cfg[2]!=0 || cfg[3]!=0 || p[0x117] || *reinterpret_cast(p+0x578)!=*reinterpret_cast(c+0xC40) || !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[cfg[0]].internal() || !s->firmwarePorts.port[cfg[0]].dp())return 9; + if(s->adlsDetected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg))return 9; return cfg[0]; } void XE::wrapTGLModeTiming(void *controller,void *fb,void *timing) { @@ -16720,8 +16817,9 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void (self->xeProfile.family!=XELP::Family::TigerLake && !self->adlsDetected) || !self->firmwarePorts.valid())return false; auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; - const bool adls=self->adlsDetected && self->adlsWiring.boundDP(self->tglPlatformCopy,config); + const bool adls=self->adlsDetected && self->adlsWiring.boundSST(self->tglPlatformCopy,config); const bool fixed=!self->adlsDetected && TGLComboDP::bound(self->tglComboDPMask,self->tglPlatformCopy,config); + if(self->adlsDetected && !adls)return false; XELP::Display12SSTRoute route; const XELP::Display12SSTRoutes routes{adls?self->adlsWiring.dp:fixed?self->tglComboDPMask:0,0,0}; if(!config || !routes.resolve(config[0],config[2],*reinterpret_cast(p+0x550),route) || @@ -16733,7 +16831,7 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void TGLComboSignal selected; const auto lanes=*reinterpret_cast(p+0x124); const auto rate=*reinterpret_cast(p+0x128); - if(!(adls ? selected.selectADLS(config[0],lanes,rate,swing,emphasis): + if(!(adls ? selected.selectADLS(config[0],lanes,rate,swing,emphasis,route.panel,panel.lowSwing): selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis, XELP::ComboPolicy::tigerLake(self->xeProfile.identity),!fixed)))return false; // The native fields identify this very bank. No guessed framebuffer index @@ -17078,8 +17176,9 @@ void XE::tglComboAUXReference(void *port,bool acquire) { if(!p || !ctl || !s->tglActive || !s->tglComboAUXLifetimeReady || !f.complete() || (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected) || *f.controllerSlot!=ctl)return; auto config=*reinterpret_cast(p+0x548); - const bool adls=s->adlsDetected && s->adlsWiring.boundDP(s->tglPlatformCopy,config); + const bool adls=s->adlsDetected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); const bool fixed=!s->adlsDetected && TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config); + if(s->adlsDetected && !adls)return; if(!config || (config[0]>1 && !adls))return; const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ae0640b4..ef9020f9 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -17,6 +17,7 @@ #include "kern_xelp_platform.hpp" #include "kern_vbt.hpp" #include "kern_alderlake_s.hpp" +#include "kern_xelp_panel_pwm.hpp" #include "kern_xelp_donor.hpp" #include "kern_xelp_display_contract.hpp" @@ -82,7 +83,14 @@ class XE { bool adlnDetected {false}; bool adlsDetected {},adlsNativeReady {},adlsDisplayHooksReady {}; bool adlsPortInitReady {},tglHPDHookReady {}; - bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {}; + bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {},adlsPanelPWMReady {}; + XELP::PanelPWM adlsPanelPWM; + mach_vm_address_t orgADLSLightUpEDP {},orgADLSPanelBacklight {}; + static bool adlsPanelPWMTarget(void *controller,void *&access,void *&power); + static bool adlsCapturePanelPWM(void *controller); + static uint32_t wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void *timing); + static uint32_t wrapADLSPanelBacklight(void *controller,uint32_t brightness); + static void wrapADLSPanelPWMEnable(void *access); mach_vm_address_t orgADLSPowerPG {}; static uint8_t *adlsPowerTarget(void *power); static void wrapADLSPowerPG(void *power,bool enable,uint32_t requested); @@ -418,6 +426,7 @@ class XE { auto routes=tglTCWiring.sstRoutes(tglAUXParentMask, tglTCAutoCandidates&__atomic_load_n(&tglTCAutoRegistered,__ATOMIC_ACQUIRE),tglDPAltCandidates); routes.combo=adlsDetected ? (adlsWiring.valid?adlsWiring.dp:0) : tglComboDPMask; + routes.panel=adlsDetected && adlsWiring.valid ? adlsWiring.panel : 0; return routes; } bool tglDPSSTEligible(uint32_t ddi) const {return tglSSTRoutes().eligible(ddi);} diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 54e1e3d3..89f8fc7a 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -65,6 +65,7 @@ struct DisplayContract { ComboAUXGate=1ULL<<55, PanelDepth=1ULL<<56, PanelTiming=1ULL<<57, + PanelPWM=1ULL<<58, }; Mask required {},available {}; Mask missing() const {return required&~available;} @@ -96,8 +97,9 @@ struct DisplayContract { if(r.alt)required|=Alt12; return {required,installed}; } - static DisplayContract adls(Mask installed,bool dp) { - return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0),installed}; + static DisplayContract adls(Mask installed,bool dp,bool panel=false) { + return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0)| + (panel?Combo12|PanelDepth|PanelTiming|PanelPWM|AUXParent|PowerGet|PowerPut:0),installed}; } static DisplayContract display13(Mask installed) {return {Backend13,installed};} static const char *name(Mask bit) { @@ -160,6 +162,7 @@ struct DisplayContract { case ComboAUXGate:return "ComboAUXGate"; case PanelDepth:return "PanelDepth"; case PanelTiming:return "PanelTiming"; + case PanelPWM:return "PanelPWM"; default:return "Unknown"; } } diff --git a/WhateverXe/kern_xelp_panel_power.hpp b/WhateverXe/kern_xelp_panel_power.hpp index 84c404d8..d1d2c9a5 100644 --- a/WhateverXe/kern_xelp_panel_power.hpp +++ b/WhateverXe/kern_xelp_panel_power.hpp @@ -8,10 +8,12 @@ namespace XELP { struct PanelPower { // The current donor has one panel and addresses PPS/PWM controller 0. // This qualification does not claim PPS1, AUX backlight or panel admission - // on another platform; those need their own register/lifecycle adapters. + // outside the native Combo panel coordinates; each needs its own adapter. static bool nativePPS0(const VBT::Ports &v,Family family) { const auto &p=v.panel; - if(family!=Family::TigerLake || !v.valid() || v.unmapped || !p.valid || p.ddi>=2 || + const bool backend=(family==Family::TigerLake && p.ddi<2) || + (family==Family::AlderLakeS && p.ddi==0); + if(!backend || !v.valid() || v.unmapped || !p.valid || !p.backlightValid || !p.pwmPresent || p.backlightType!=2 || p.controller)return false; const auto &port=v.port[p.ddi];const auto aux=p.ddi?0x10U:0x40U; if(!(v.present&(1U< +namespace XELP { +struct PanelPWM { + uint32_t period {},polarity {}; + bool valid() const {return period && period!=~0U;} + static PanelPWM select(uint32_t period,uint32_t control,uint32_t strap,uint32_t hz,bool activeLow) { + if(period==~0U || control==~0U)return {}; + if(period)return {period,control&(1U<<29)}; + if(strap==~0U || !hz || hz>65535)return {}; + const auto clock=(strap&256)?24000000U:19200000U; + return {(clock+hz/2)/hz,activeLow?1U<<29:0}; + } + bool duty(uint32_t brightness,uint32_t &value) const { + if(!valid() || brightness>65535)return false; + // The donor's 32-bit multiply overflows for ordinary low-frequency panels. + value=uint32_t(uint64_t(period)*brightness/65535);return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index f8522a0d..ab77f6e9 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -33,7 +33,8 @@ template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man if(!(b.config.combo&b.config.comboBackend&(1U<=9 && c[1]<=12)) && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; const bool dp=c[1]==(c[0]>=3?c[0]+6:c[0]+1) && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; - return hdmi || dp ? 1 : 0; + const bool panel=c[0]==0 && !c[1] && !c[2] && !c[3] && c[4]==2 && c[5]==0x18; + return hdmi || dp || panel ? 1 : 0; } if(b.config.profile.display==Display::XeD) return legacyTGLMode(io,c[0],b.config.legacy&b.config.legacyBackend); diff --git a/WhateverXe/kern_xelp_sst_route.hpp b/WhateverXe/kern_xelp_sst_route.hpp index b9628a4d..d47d0ad7 100644 --- a/WhateverXe/kern_xelp_sst_route.hpp +++ b/WhateverXe/kern_xelp_sst_route.hpp @@ -32,7 +32,7 @@ struct Display12SSTRoute { // Keeping the three transports separate prevents TC-numbered Combo ports from // accidentally acquiring Dekel/FIA semantics. struct Display12SSTRoutes { - uint32_t combo {}, legacy {}, alt {}; + uint32_t combo {}, legacy {}, alt {}, panel {}; Display12SSTRoute external(uint32_t ddi,uint32_t mode) const { Display12SSTRoute r; if(ddi>=9)return r; @@ -54,6 +54,11 @@ struct Display12SSTRoutes { return r; } Display12SSTRoute fixed(uint32_t ddi) const { + if(ddi<2 && (panel&(1U< order;for(unsigned d=0;d<5;++d)if(mask&(1U<(state.tglPlatformCopy+0x20);memcpy(panelConfig,panel,sizeof(panel)); + at(port,0x548)=panelConfig;at(port,0x578)=access;at(ctl,0xc40)=access; + at(port,0x5b8)=0x16210c;at(access,0x60)=0x200000; + assert(XE::adlsClockTarget(ctl,port,nullptr)); + state.adlsWiring.panel=0;assert(!XE::adlsClockTarget(ctl,port,nullptr)); puts("PASS ADL-S runtime clock guard: exact owner, immutable TC1/TC2 configurations, mapped Combo B/C and no shared port"); }'''.replace('FUNCTION',function) with tempfile.TemporaryDirectory(prefix='adls-clock-guard-') as d: diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp index f089d63f..d3c55547 100644 --- a/tests/adls_clock_native_fixture.cpp +++ b/tests/adls_clock_native_fixture.cpp @@ -56,7 +56,7 @@ int main(int argc,char **argv){ auto disable=(void(*)(void*,void*,void*))(image+0x6dae6); unsigned cases=0; for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned p:{2u,3u,5u,7u})for(unsigned k:{1u,2u,3u})for(unsigned q:{1u,3u}) { - config[0]=ddi;config[1]=ddi?ddi+6:1; + config[0]=ddi;config[1]=config[2]==0?0:ddi?ddi+6:1; AlderLakeS::Port route;AlderLakeS::PLL pll;assert(AlderLakeS::Port::resolve(ddi,route));assert(AlderLakeS::PLL::resolve(route.phy,pll)); fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref; mmio={{pll.enable,0},{pll.cfg0,0},{pll.cfg1,0},{0x164280,0x01e07400}}; @@ -79,8 +79,10 @@ int main(int argc,char **argv){ // Real external DP Combo C uses physical TC2, dedicated DPLL2 and selector2. config[0]=4;config[1]=10;config[2]=1;config[4]=0x400;config[5]=0x201; at(port,0x114)=1; - for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned rate:{6u,10u,20u,30u}) { - config[0]=ddi;config[1]=ddi?ddi+6:1; + for(unsigned kind:{0u,1u})for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned rate:{6u,10u,20u,30u}) { + if(kind==0 && ddi!=0)continue;config[2]=kind; + config[4]=kind?0x400:2;config[5]=kind?0x201:0x18; + config[0]=ddi;config[1]=config[2]==0?0:ddi?ddi+6:1; AlderLakeS::Port route;AlderLakeS::PLL pll;assert(AlderLakeS::Port::resolve(ddi,route));assert(AlderLakeS::PLL::resolve(route.phy,pll)); fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref;at(port,0x128)=rate; mmio={{pll.enable,0},{pll.cfg0,0},{pll.cfg1,0},{0x164280,0x01e07400}}; diff --git a/tests/adls_donor.py b/tests/adls_donor.py index 872a8022..09df2029 100644 --- a/tests/adls_donor.py +++ b/tests/adls_donor.py @@ -31,6 +31,7 @@ afields[0x648]=struct.unpack_from(' @@ -53,6 +54,13 @@ const auto wiring=AlderLakeS::Wiring::fromFirmware(firmware); assert(wiring.apply(platform,sizeof(platform))); assert(wiring.matches(reinterpret_cast(platform+0x68))); + memcpy(platform+0x20,ports,sizeof(ports)); + firmware.present|=1;firmware.port[0]={0x78c6,10,0,0x40,0,0}; + firmware.panel.valid=true;firmware.panel.ddi=0;firmware.panel.backlightValid=true; + firmware.panel.pwmPresent=true;firmware.panel.backlightType=2; + const auto panel=AlderLakeS::Wiring::fromFirmware(firmware); + assert(panel.panel==1 && panel.apply(platform,sizeof(platform))); + assert(!memcmp(platform+0x20,ports,24) && panel.boundPanel(platform,reinterpret_cast(platform+0x20))); puts("PASS exact donor Combo A/B/C register tables and firmware connector adaptation"); }'''.replace('APHY',','.join(map(str,aphy))).replace('CPHY',','.join(map(str,cphy))).replace('PHY',','.join(map(str,phy))).replace('PORTS',','.join(map(str,ports))) with tempfile.TemporaryDirectory(prefix='adls-donor-') as tmp: diff --git a/tests/adls_panel_pwm.py b/tests/adls_panel_pwm.py new file mode 100644 index 00000000..37a85db9 --- /dev/null +++ b/tests/adls_panel_pwm.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Run production PWM binding/lifecycle and the exact donor PWM write tail.""" +from pathlib import Path +import hashlib,os,re,subprocess,tempfile,sys +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['adlsPanelPWMTarget','adlsCapturePanelPWM','wrapADLSLightUpEDP','wrapADLSPanelBacklight','wrapADLSPanelPWMEnable'] +functions='\n'.join(re.search(r'(?:void|bool|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) +install=s[s.index(' // ADLS_PANEL_PWM_INSTALL:'):s.index(' // ADLS_PANEL_PWM_INSTALL_END')] +arrays={n:re.search(n+r'\[\]=\{([^}]+)',install).group(1) for n in ['lightEntry','brightnessEntry','enable']} +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + for n,off in [('lightEntry',0x5063e),('brightnessEntry',0x4a56a),('enable',0x507e9)]: + raw=bytes(int(x,0) for x in arrays[n].split(','));assert b[off:off+len(raw)]==raw + # Native start chooses Camellia/Banksia from platform+190 only. + assert b[0x40216:0x4021d]==bytes.fromhex('83b89001000002') + assert b[0x402bb:0x402c2]==bytes.fromhex('83b89001000003') +code=r''' +#include "kern_alderlake_s.hpp" +#include "kern_xelp_panel_pwm.hpp" +#include "kern_tgl_tc_native.hpp" +#include +#include +#include +#include +#include +#include +#define FunctionCast(f,p) reinterpret_cast(p) +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +struct IORegistryEntry {static IORegistryEntry* getRegistryRoot(){static IORegistryEntry o;return &o;}void setProperty(const char*,bool){}}; +struct KernelPatcher { + enum class Error{NoError,Failed};bool fail=false;unsigned routes=0,blocks=0;uintptr_t delta=0; + struct RouteRequest {template RouteRequest(const char*,T,uintptr_t&) {}}; + uintptr_t solveSymbol(unsigned,const char*s,uintptr_t a,size_t){return a+(strstr(s,"LightUp")?0x5063e:0x4a56a)+delta;} + bool routeMultiple(unsigned,RouteRequest*,unsigned,uintptr_t,size_t){++routes;return !fail;} + void routeBlock(uintptr_t a,const uint8_t*b,size_t n){++blocks;memcpy((void*)a,b,n);} + Error getError(){return Error::NoError;}void clearError(){} +}; +struct XE { + static XE*callbackXE; + bool adlsDetected=true,adlsPanelPWMReady=true,tglActive=true;XELP::PanelPWM adlsPanelPWM; + XELP::VBT::Ports firmwarePorts;AlderLakeS::Wiring adlsWiring; + struct {XELP::Family family=XELP::Family::AlderLakeS;}xeProfile; + TGLTCNativeFunctions tglTCNativeFunctions;void*tglObservationController=nullptr; + uint64_t tglPlatformCopy[0x1c0/8]{}; + uintptr_t orgTGLRegisterRead=0,orgTGLRegisterWrite=0,orgADLSLightUpEDP=0,orgADLSPanelBacklight=0; + static bool adlsPanelPWMTarget(void*,void*&,void*&);static bool adlsCapturePanelPWM(void*); + static uint32_t wrapADLSLightUpEDP(void*,void*,void*,const void*);static uint32_t wrapADLSPanelBacklight(void*,uint32_t); + static void wrapADLSPanelPWMEnable(void*); + static uint32_t wrapTGLRegisterRead(void*,unsigned long);static void wrapTGLRegisterWrite(void*,unsigned long,uint32_t); + void install(KernelPatcher&patcher,unsigned index,uintptr_t address,size_t size){INSTALL} +};XE*XE::callbackXE; +FUNCTIONS +alignas(8) uint8_t ctl[0x1600]{},port[0x600]{},access[0x80]{},power[0x90]{}; +templateT&field(void*p,unsigned n){return *reinterpret_cast(static_cast(p)+n);} +static void*owner=ctl;static uint32_t period=120000,control=1U<<29,duty=0,strap=256,wells=1; +static unsigned get=0,put=0,refs=0,reads=0,writes=0,nativeLight=0,nativeBacklight=0; +static uint32_t base(void*,uint32_t){return 0;}static void aux(void*){}static void*lookup(uint32_t d){assert(d==0);return port;} +static void getPG(void*p,uint32_t n){assert(p==power&&n==1);++get;++refs;}static void putPG(void*p,uint32_t n){assert(p==power&&n==1&&refs);++put;--refs;} +static uint32_t read(void*a,unsigned long reg){assert(a==access&&refs);++reads;switch(reg){case 0x45404:return wells;case 0x42000:return 0x1000000;case 0xc8254:return period;case 0xc8250:return control;case 0xc2014:return strap;default:assert(false);return ~0U;}} +void XE::wrapTGLRegisterWrite(void*a,unsigned long reg,uint32_t v){assert(a==access);++writes; + switch(reg){case 0xc8254:period=v;break;case 0xc8250:control=v;break;case 0xc8258:duty=v;break;default:assert(false);}} +static uint32_t light(void*c,void*,void*,const void*){assert(c==ctl);++nativeLight;return 0x1234;} +static uint32_t backlight(void*c,uint32_t){assert(c==ctl);++nativeBacklight;return 0x5678;} +static void setup(XE&s){ + XE::callbackXE=&s;memset(ctl,0,sizeof(ctl));memset(port,0,sizeof(port));memset(access,0,sizeof(access));memset(power,0,sizeof(power)); + owner=ctl;period=120000;control=1U<<29;duty=0;strap=256;wells=1;get=put=refs=reads=writes=nativeLight=nativeBacklight=0; + s.tglObservationController=ctl;s.tglTCNativeFunctions={base,aux,aux,getPG,putPG,lookup,&owner,true}; + s.orgTGLRegisterRead=(uintptr_t)read;s.orgTGLRegisterWrite=(uintptr_t)XE::wrapTGLRegisterWrite; + s.orgADLSLightUpEDP=(uintptr_t)light;s.orgADLSPanelBacklight=(uintptr_t)backlight; + auto &v=s.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78c6,10,0,0x40,0,0}; + v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2;v.panel.pwmHz=200;v.panel.activeLowPWM=true; + s.adlsWiring=AlderLakeS::Wiring::fromFirmware(v);assert(s.adlsWiring.panel==1); + const uint32_t record[]={0,0,0,0,2,0x18};memcpy((uint8_t*)s.tglPlatformCopy+0x20,record,sizeof(record)); + field(ctl,0xc40)=access;field(ctl,0xc8)=power;field(ctl,0xd40)=s.tglPlatformCopy; + field(power,0x10)=ctl;field(power,0x78)=access; + field(port,0x548)=(uint8_t*)s.tglPlatformCopy+0x20;field(port,0x578)=access;field(port,0x550)=1; + field(access,0x50)=access;field(access,0x60)=0x200000; +} +static void patchCall(uint8_t*m,unsigned site,unsigned target){assert(m[site]==0xe8);int32_t d=target-site-5;memcpy(m+site+1,&d,4);} +static void jump(uint8_t*p,uintptr_t target){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&target,8);p[10]=0xff;p[11]=0xe0;} +int main(int argc,char**argv){ + std::vector image(0x51000);const uint8_t l[]={LIGHT},b[]={BRIGHT},e[]={ENABLE}; + for(unsigned n=0;n<6;++n){ + memcpy(image.data()+0x5063e,l,sizeof(l));memcpy(image.data()+0x4a56a,b,sizeof(b));memcpy(image.data()+0x507e9,e,sizeof(e)); + XE s;s.adlsPanelPWMReady=false;KernelPatcher p;size_t size=image.size();if(n==0)size=0x508c3;if(n==1)p.delta=1;if(n==2)image[0x507e9]^=1;if(n==3)image[0x4a56a]^=1;if(n==4)p.fail=true; + s.install(p,0,(uintptr_t)image.data(),size);assert(s.adlsPanelPWMReady==(n==5));assert(p.blocks==(n==5)); + } + for(unsigned bad=0;bad<16;++bad){XE s;setup(s); + if(bad==0)s.adlsPanelPWMReady=false;if(bad==1)s.tglActive=false;if(bad==2)owner=nullptr;if(bad==3)s.firmwarePorts.panel.controller=1; + if(bad==4)field(ctl,0xd0)=port;if(bad==5)field(ctl,0xd40)=nullptr;if(bad==6)field(port,0x548)=nullptr; + if(bad==7)field(power,0x78)=nullptr;if(bad==8)field(access,0x60)=0xc8258; + if(bad==9)wells=0;if(bad==10)period=~0U;if(bad==11){period=0;s.firmwarePorts.panel.pwmHz=0;} + if(bad==14)field(port,0x550)=3;if(bad==15)s.xeProfile.family=XELP::Family::TigerLake; + if(bad==12)control=~0U;if(bad==13){period=0;strap=~0U;} + assert(XE::wrapADLSLightUpEDP(ctl,nullptr,nullptr,nullptr)==kIOReturnNotReady&&nativeLight==0&&writes==0&&get==put&&!refs); + } + XE s;setup(s);assert(XE::wrapADLSLightUpEDP(ctl,nullptr,nullptr,nullptr)==0x1234&&nativeLight==1&&field(ctl,0xe60)==120000); + assert(get==put&&get==1&&!refs);const auto capturedReads=reads; + for(unsigned v:{0U,1U,32768U,65535U}){assert(!XE::wrapADLSPanelBacklight(ctl,v));assert(duty==uint64_t(120000)*v/65535&&field(ctl,0xe58)==v&&get==put&&!refs);} + assert(!nativeBacklight);auto oldWrites=writes;assert(XE::wrapADLSPanelBacklight(ctl,65536)==kIOReturnNotReady&&writes==oldWrites); + XE::wrapADLSPanelPWMEnable(access);assert(control==0xa0000000); + control=0;period=0;assert(XE::adlsCapturePanelPWM(ctl)&&s.adlsPanelPWM.period==120000); // native disable cannot erase snapshot + s.adlsPanelPWM={};strap=0;assert(XE::adlsCapturePanelPWM(ctl)&&s.adlsPanelPWM.period==96000&&s.adlsPanelPWM.polarity==(1U<<29)); + s.adlsWiring.panel=0;assert(XE::wrapADLSPanelBacklight(ctl,7)==0x5678&&nativeBacklight==1); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vectorraw((std::istreambuf_iterator(f)),{});assert(raw.size()>0xdfb00); + const unsigned stubs=(raw.size()+4095)&~4095U;const size_t length=stubs+4096; + auto mem=(uint8_t*)mmap(nullptr,length,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);assert(mem!=MAP_FAILED);memcpy(mem,raw.data(),raw.size()); + XE state;setup(state);state.adlsPanelPWMReady=false;KernelPatcher patcher;state.install(patcher,0,(uintptr_t)mem,length);assert(state.adlsPanelPWMReady); + jump(mem+stubs,(uintptr_t)XE::wrapADLSPanelBacklight);patchCall(mem,0x507da,stubs); + // Execute the native PWM tail with the exact controller register convention. + uint8_t entry[]={0x53,0x48,0x89,0xfb,0xe9,0,0,0,0};int32_t d=0x507b6-(stubs+32+sizeof(entry));memcpy(entry+5,&d,4);memcpy(mem+stubs+32,entry,sizeof(entry)); + mem[0x507f9]=0x5b;mem[0x507fa]=0xc3; + assert(mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)==0); + uintptr_t vtable[40]{};vtable[0x130/8]=(uintptr_t)XE::wrapTGLRegisterWrite;field(access,0)=vtable; + auto tail=(void(*)(void*))(mem+stubs+32); + state.orgADLSLightUpEDP=(uintptr_t)+[](void*c,void*,void*,const void*)->uint32_t{assert(c==ctl);return 0;}; + for(unsigned cycle=0;cycle<16;++cycle){ + assert(XE::wrapADLSLightUpEDP(ctl,nullptr,nullptr,nullptr)==0); + field(ctl,0xe58)=65535;tail(ctl); + assert(period==120000&&duty==120000&&control==0xa0000000&&get==put&&!refs); + XE::wrapTGLRegisterWrite(access,0xc8250,0);XE::wrapTGLRegisterWrite(access,0xc8258,0); + } + // Real unadapted duty math provides the overflow counterexample. + auto native=(uint32_t(*)(void*,uint32_t))(mem+0x4a56a);native(ctl,65535);assert(duty!=120000); + munmap(mem,length); + } + puts("PASS production ADL-S PWM qualification, faults, balanced power, BIOS/VBT fallback, native PWM tail and 32-bit overflow counterexample"); +} +'''.replace('INSTALL',install).replace('FUNCTIONS',functions).replace('LIGHT',arrays['lightEntry']).replace('BRIGHT',arrays['brightnessEntry']).replace('ENABLE',arrays['enable']) +with tempfile.TemporaryDirectory(prefix='adls-panel-pwm-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True,env=dict(os.environ,UBSAN_OPTIONS='halt_on_error=1')) diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp index 5a2eccb5..1e7dcf81 100644 --- a/tests/alderlake_s.cpp +++ b/tests/alderlake_s.cpp @@ -115,5 +115,41 @@ int main() { assert(!comboRegisters(3,registers,sizeof(registers)/4)); // no repeated or foreign table memcpy(registers,saved,sizeof(saved));registers[4]=0x64400; assert(!comboRegisters(3,registers,sizeof(registers)/4)); + // Compose a firmware-qualified native panel with every external B/C + // assignment. Panel remains kind0 and never gets external hotplug mode. + for(unsigned assignment=0;assignment<9;++assignment) { + XELP::VBT::Ports v;v.status=XELP::VBT::Status::Valid;v.present=1; + v.port[0]={0x78C6,10,0,0x40,0,0};v.panel.valid=true;v.panel.ddi=0; + v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2; + unsigned code=assignment; + for(unsigned d:{3U,4U}){unsigned kind=code%3;code/=3;if(!kind)continue; + v.present|=1U<(platform+0x190)=mode; + uint8_t saved[sizeof(platform)];memcpy(saved,platform,sizeof(saved)); + assert(!panel.apply(platform,sizeof(platform)) && !memcmp(saved,platform,sizeof(saved))); + } + for(unsigned mode:{0U,2U,3U}) { + *reinterpret_cast(platform+0x190)=mode; + assert(panel.apply(platform,sizeof(platform)) && !*reinterpret_cast(platform+0x190)); + // Restore the native external predecessors for the next application. + for(unsigned d:{3U,4U}){const uint32_t rec[]={d,d+6,1,1,0x400,0x201};memcpy(platform+0x20+24*d,rec,sizeof(rec));} + } + assert(panel.apply(platform,sizeof(platform)));auto c=reinterpret_cast(platform+0x20); + assert(panel.boundPanel(platform,c) && panel.boundSST(platform,c) && !panel.boundDP(platform,c)); + assert(!memcmp(c,nativePanel,sizeof(nativePanel)) && panel.hotplugMode(true,c,platform,1,1,4)==4); + for(unsigned field=0;field<6;++field){c[field]^=1;assert(!panel.boundPanel(platform,c));c[field]^=1;} + for(unsigned bad=0;bad<8;++bad){auto invalid=v; + if(bad==0)invalid.panel.controller=1;if(bad==1)invalid.panel.backlightType=5; + if(bad==2)invalid.panel.delays[0]=8192;if(bad==3)invalid.panel.delays[4]=30001; + if(bad==4)invalid.panel.ddi=3;if(bad==5)invalid.port[0].aux=0x10; + if(bad==6)invalid.unmapped=1;if(bad==7)invalid.panel.backlightValid=false; + assert(!Wiring::fromFirmware(invalid).panel); + } + } puts("PASS ADL-S physical DDI/Combo PHY/HPD/PLL/DDC coordinates and ambiguous HDMI rejection"); } diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index b9d7348e..b2b65ac3 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -27,7 +27,7 @@ }; int main() { State s; - assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming)); + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM)); assert(s.display13Available()==C::Backend13); // 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/host_publication.py b/tests/host_publication.py index 6f0aaca8..41f2a7a7 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -13,14 +13,14 @@ #include #include #include "kern_xelp_platform.hpp" -std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086; +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;}}; OSData fake;Object customModel;bool hasModel=false;unsigned modelWrites=0;Object *deviceProperty=nullptr; struct IOPCIDevice { const char *name="GFX0";unsigned renames=0; uint32_t configRead32(int){assert(published);events.push_back(2);return identity;} - uint8_t configRead8(int){assert(published);events.push_back(3);return 0;} + uint8_t configRead8(int){assert(published);events.push_back(3);return revision;} uint16_t configRead16(int){assert(published);events.push_back(4);return 0x300;} const char *getName(){return name;}Object *getProperty(const char *key){return !strcmp(key,"device-id")?deviceProperty:!strcmp(key,"model")&&hasModel?&customModel:nullptr;} void setProperty(const char *,const char *){}void setProperty(const char *key,const void *value,unsigned size){if(!strcmp(key,"model")){assert(value && size>1);++modelWrites;}}void setProperty(const char *,uint64_t,unsigned){} @@ -63,6 +63,13 @@ else if(test==2 || test==5){assert(!host.active&&graphics.off);assert(events.size()==4);} else {assert(host.active&&!graphics.off);assert(events.front()==1);assert(events[events.size()-2]==8);} } +for(unsigned rev:{0U,12U,255U})for(bool apple:{false,true}) { + DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; + DeviceInfo::value.firmwareVendor=apple?DeviceInfo::FirmwareVendor::Apple:DeviceInfo::FirmwareVendor::Other; + publish=true;published=false;deviceProperty=nullptr;identity=0x46908086;revision=rev;events.clear(); + XeHost host;host.processKernel(patcher);assert(host.active==(rev==12)); + assert(host.backlightEnabled==(rev==12&&!apple));assert(host.backlightGPU==(rev==12&&!apple?&gpu:nullptr)); +} 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/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index 7b274275..ee19dc9c 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -23,8 +23,8 @@ void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} FUNCTION int main(){ - for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<3;++kind)for(unsigned bad=0;bad<15;++bad){ - const bool adls=kind==1,fixed=kind==2;if(adls?testDDI==1:testDDI>1)continue; + for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<15;++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));}; @@ -32,13 +32,14 @@ state.adlsDetected=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)); } + if(panel){state.adlsWiring.dp=0;state.adlsWiring.panel=1;const uint32_t c[]={0,0,0,0,2,0x18};memcpy(config,c,sizeof(c));} if(fixed) { state.tglComboDPMask=1U<(state.tglPlatformCopy+0x20+24*ddi); const uint32_t c[]={ddi,ddi+1,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); } else if(!adls)config[0]=ddi; state.tglObservationController=ctl;void *global=ctl; state.tglTCNativeFunctions.controllerSlot=&global; - state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.present=1U<(b)+o,&v,sizeof(v));}; + state={};state.adlsDetected=true;state.xeProfile.family=XELP::Family::AlderLakeS; + state.tglObservationController=ctl;auto &v=state.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78C6,10,0,0x40,0,0}; + v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2;v.panel.lowSwing=low;v.panel.hobl=hobl; + state.adlsWiring=AlderLakeS::Wiring::fromFirmware(v);assert(state.adlsWiring.panel==1); + auto config=reinterpret_cast(state.tglPlatformCopy+0x20);const uint32_t record[]={0,0,0,0,2,0x18};memcpy(config,record,sizeof(record)); + store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,2U);store(p,0x128,rate);store(p,0x620,0x162804U);store(p,0x5C8,0x162028U); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=3;registers[0x45454]=3;reads=writes=records=0; + uint32_t result=99;assert(run(p,0,0,nullptr,result)&&!result&&records==1); + XELP::ComboPolicy expected;assert(expected.select(XELP::ComboPolicy::Platform::ADLS,rate,true,low,false,0,0)); + assert((registers[0x16289C]>>24&127)==expected.coefficients.scalar); + assert((registers[0x162890]&0x3ffff)==(expected.coefficients.post1<<12|expected.coefficients.cursor)); + assert(!(registers[0x162028]&12)); + state.adlsWiring.panel=0;writes=0;assert(!run(p,0,0,nullptr,result)&&!writes); + } puts("PASS production combo guard: family, port, firmware, ownership, power and missing bank"); } '''.replace('FUNCTION',fn) diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index 8c2bf5cd..463661e8 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -84,6 +84,20 @@ int main(){ } setup();object.adlsDetected=true;auto foreignWiring=property(2,0,2,0);IOService wrongADLS{&foreignWiring}; assert(!XE::tglLoadHDMIWiring(&wrongADLS) && !XE::tglLoadHDMIWiring(&wrongADLS)); + // Panel + external HDMI/DP use the real firmware loader and topology copy. + for(unsigned kind:{1U,2U})for(unsigned tcon:{0U,2U,3U}) { + setup();object.adlsDetected=true;auto &v=object.firmwarePorts; + v.status=XELP::VBT::Status::Valid;v.present=9;v.port[0]={0x78c6,10,0,0x40,0,0}; + v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2; + v.port[3]={kind==1?0x60d2U:0x60d6U,kind==1?1U:7U,2,kind==1?0U:0x10U,0,0}; + reinterpret_cast(original+0x68)[1]=9;*reinterpret_cast(original+0x190)=tcon; + IOService provider;assert(XE::tglLoadHDMIWiring(&provider)&&object.adlsWiring.panel==1); + assert(XE::wrapTGLGetOSInformation(controller)); + assert(object.adlsWiring.boundPanel(platform(),reinterpret_cast(platform()+0x20))); + assert(object.adlsWiring.matches(reinterpret_cast(platform()+0x68))); + assert(!*reinterpret_cast(platform()+0x190)); + expectUnchangedExcept(platform(),{0x70,0x74,0x78,0x7c,0x190}); + } // The native setup caller supplies an interior output pointer as arg0. // Any object/config dereference on an unqualified caller must fault here. setup(); diff --git a/tests/tgl_panel_depth_route.py b/tests/tgl_panel_depth_route.py index c7a6c705..773ff5a9 100644 --- a/tests/tgl_panel_depth_route.py +++ b/tests/tgl_panel_depth_route.py @@ -28,11 +28,12 @@ end=s.index('uint32_t XE::wrapTGLPortDDC(',start) code=r''' #include "kern_tgl_panel_depth.hpp" +#include "kern_alderlake_s.hpp" #include #include #include using mach_vm_address_t=uintptr_t; -namespace XELP {enum class Family {TigerLake,Other};} + unsigned calls=0,published=0;uint64_t record[5]; struct Root {void setProperty(const char *,void *p,size_t n){assert(n==40);memcpy(record,p,n);++published;}} root; struct IORegistryEntry {static Root *getRegistryRoot(){return &root;}}; @@ -40,7 +41,8 @@ void IOSimpleLockLock(void *){} void IOSimpleLockUnlock(void *){} struct Ports {struct Port {bool internal(){return true;}bool dp(){return true;}} port[2];unsigned present=1;bool valid(){return true;}}; struct XE { - bool tglActive=true,tglPanelDepthReady=true; + bool tglActive=true,tglPanelDepthReady=true,adlsDetected=false; + AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; Ports firmwarePorts;mach_vm_address_t orgTGLModeTiming; @@ -67,12 +69,22 @@ for(unsigned test=0;test<7;++test){ put(timing,0x8A,uint16_t(2));unsigned before=published; state.tglActive=test!=0;state.tglPanelDepthReady=test!=1; - state.xeProfile.family=test==2?XELP::Family::Other:XELP::Family::TigerLake; + state.xeProfile.family=test==2?XELP::Family::Unknown:XELP::Family::TigerLake; cfg[2]=test==3?2:0;cfg[0]=test==4?2:0;port[0x117]=test==5; put(port,0x578,test==6?nullptr:access); XE::wrapTGLModeTiming(ctl,fb,timing); assert(published==before && *reinterpret_cast(timing+0x8A)==2); } + // Same depth contract on qualified ADL-S panel; retained record is required. + state.tglActive=state.tglPanelDepthReady=true;state.adlsDetected=true; + state.xeProfile.family=XELP::Family::AlderLakeS;port[0x117]=0;put(port,0x578,access); + state.adlsWiring.valid=true;state.adlsWiring.panel=1; + const uint32_t panelRecord[]={0,0,0,0,2,0x18};memcpy(state.tglPlatformCopy+0x20,panelRecord,sizeof(panelRecord)); + put(port,0x548,static_cast(state.tglPlatformCopy+0x20)); + put(timing,0x8A,uint16_t(2));auto before=published; + XE::wrapTGLModeTiming(nullptr,fb,timing);assert(published==before+1 && *reinterpret_cast(timing+0x8A)==1); + state.adlsWiring.panel=0;put(timing,0x8A,uint16_t(2));before=published; + XE::wrapTGLModeTiming(nullptr,fb,timing);assert(published==before && *reinterpret_cast(timing+0x8A)==2); puts("PASS: production owner/port guards and unused-controller argument"); } '''.replace('PRODUCTION',s[start:end]) diff --git a/tests/tgl_panel_timing.py b/tests/tgl_panel_timing.py index 9aebd9bc..13520846 100644 --- a/tests/tgl_panel_timing.py +++ b/tests/tgl_panel_timing.py @@ -16,6 +16,8 @@ assert b[at:at+len(key)+1]==key+b'\0' code=r''' #include "kern_xelp_panel_power.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_xelp_panel_pwm.hpp" #include "kern_tgl_tc_native.hpp" #include #include @@ -39,7 +41,8 @@ }; struct XE { static XE *callbackXE; - bool tglActive=true,tglPanelTimingReady=true; + bool tglActive=true,tglPanelTimingReady=true,adlsDetected=false; + AlderLakeS::Wiring adlsWiring;XELP::PanelPWM adlsPanelPWM; uintptr_t orgTGLPanelTiming=0,orgTGLRegisterRead=0,orgTGLPanelConfig=0; uint32_t tglPanelTimingBIOS[3]{};bool tglPanelTimingBIOSValid=false; XELP::VBT::Ports firmwarePorts; @@ -114,7 +117,7 @@ XE s;setup(s);auto &p=s.firmwarePorts.panel; switch(n){ case 0:s.tglActive=false;break;case 1:s.tglPanelTimingReady=false;break;case 2:s.tglObservationController=nullptr;break; - case 3:s.xeProfile.family=XELP::Family::AlderLakeS;break;case 4:p.controller=1;break;case 5:p.backlightType=5;break; + case 3:s.xeProfile.family=XELP::Family::AlderLakeN;break;case 4:p.controller=1;break;case 5:p.backlightType=5;break; case 6:p.backlightValid=false;break;case 7:s.tglTCNativeFunctions.qualified=false;break;case 8:owner=nullptr;break; case 9:s.orgTGLRegisterRead=0;break;case 10:ctl[0x918]=1;break;case 11:at(ctl,0xd0)=port;break; case 12:at(ctl,0xd40)=nullptr;break;case 13:port[0x117]=1;break;case 14:at(port,0x548)=ctl;break; @@ -138,7 +141,8 @@ // Optional exact native hwSetPanelPowerConfig replay. Only its property // callsite, imported services and virtual register writer are replaced; the encoding/masking, // property conversions, defaults, cached fields and write order are real donor code. - if(argc>1){ + for(unsigned platform=0;platform<2;++platform)if(argc>1){ + propertyCalls=0; std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{});assert(b.size()>0xde4e0); const auto stubs=unsigned((b.size()+4095)&~4095U);const size_t length=stubs+4096; 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()); @@ -155,6 +159,11 @@ XE s;setup(s);at(access,0)=vtable;s.orgTGLPanelConfig=(uintptr_t)nativeConfig; s.orgTGLPanelTiming=(uintptr_t)(mem+0x4848c);at(ctl,0xc60)=&provider; const uint32_t milliseconds[]={300,100,600,90,800};for(unsigned i=0;i<5;++i)properties[i].value=milliseconds[i]; + if(platform){ + s.adlsDetected=true;s.xeProfile.family=XELP::Family::AlderLakeS; + s.adlsWiring=AlderLakeS::Wiring::fromFirmware(s.firmwarePorts);assert(s.adlsWiring.panel==1); + const uint32_t record[]={0,0,0,0,2,0x18};memcpy(reinterpret_cast(s.tglPlatformCopy)+0x20,record,sizeof(record)); + } const auto config=XE::wrapTGLPanelConfig; control=0xabcd0080;at(ctl,0xd80)=control; on=4000U<<16|1200;off=8000U<<16|1100; diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index 4a5fac02..7dd6f9ed 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -110,6 +110,17 @@ int main(){ assert(!r.post[11]); // No eDP Alternate SR on external Combo DP, including A. ++cases; } + // ADL-S panel composes with every native pipe/lane/training pattern and + // negotiated Alternate SR, without requiring a lease for an external port. + for(unsigned p=0;p<3;++p)for(unsigned lanes:{1U,2U,4U})for(unsigned framing=0;framing<2;++framing) + for(unsigned pat=0;pat<6;++pat)for(unsigned sr=0;sr<2;++sr) { + reset(0,p,pat);object.adlsDetected=true;object.adlsWiring.valid=true;object.adlsWiring.panel=1; + object.tglTCWiring=TGLTCWiring::fromMasks(0,0,0); + field(port,0x124,lanes);port[0x11A]=framing;port[0x118]=sr;emitTP=true;expectPre=pat==1; + reg(0x45454)=3;reg(0x45404)=p==2?0xFF:p==1?0x3F:3; + invoke();neighbors();assert(reg(0x60540+0x1000*p)==(0x80100A00|(framing<<18)|(sr<<6))); + auto &r=*IORegistryEntry::getRegistryRoot();assert(r.post[11]&&r.post[12]==sr);++cases; + } // Normal/fast pattern transitions must agree with the sink's negotiated // Alternate SR mode. Preserve unrelated bits, external ports, invalid // negotiation, and unknown donor layouts; never touch another transcoder. diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index 83eb3576..e4b5f42b 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<58;++i) { + for(unsigned i=0;i<59;++i) { auto broken=c;auto bit=1ULL< +#include +#include +using P=XELP::PanelPWM; +int main(){ + for(unsigned strap:{0U,256U})for(unsigned hz:{1U,200U,1000U,65535U})for(bool low:{false,true}) { + const auto p=P::select(0,0,strap,hz,low); + assert(p.valid() && p.period==((strap?24000000U:19200000U)+hz/2)/hz && p.polarity==(low?1U<<29:0)); + } + for(auto period:{1U,65535U,65537U,96000U,120000U,24000000U,0xfffffffeU})for(bool low:{false,true}) { + const auto p=P::select(period,low?1U<<29:0,~0U,0,!low);assert(p.period==period&&p.polarity==(low?1U<<29:0)); + uint32_t last=0; + for(uint32_t brightness=0;brightness<=65535;++brightness){uint32_t duty=~0U;assert(p.duty(brightness,duty)); + assert(duty==uint64_t(period)*brightness/65535 && duty>=last && duty<=period);last=duty;} + assert(last==period);assert(!p.duty(65536,last)); + } + assert(!P::select(0,0,~0U,200,false).valid());assert(!P::select(0,0,0,0,false).valid()); + assert(!P::select(~0U,0,0,200,false).valid());assert(!P::select(1,~0U,0,200,false).valid()); + puts("PASS 917504 PWM brightness cases; BIOS/VBT period and polarity, rawclk, monotonic wide arithmetic and invalid inputs"); +} diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 05a0e883..c484d233 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -65,6 +65,12 @@ int main() { if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=0;assert(!x.valid());} assert(!identify(0x46D18086,12,0x46908086).admitted()); assert(!identify(0x46908086,12,0x46808086).admitted()); + // Panel mode requires selected Combo A and the exact native panel record. + c.combo=1;b.config=c;auto panel=reinterpret_cast(platform+0x20); + const uint32_t panelRecord[]={0,0,0,0,2,0x18};memcpy(panel,panelRecord,sizeof(panelRecord)); + assert(probeMode(io,b,2,reinterpret_cast(panel),panel)==1 && io.reads.empty()); + for(unsigned field=0;field<6;++field){panel[field]^=1;assert(!probeMode(io,b,2,reinterpret_cast(panel),panel));panel[field]^=1;} + c.combo=8;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(panel),panel)); // CPU association and power-consumer accounting are independent of transport. NativePorts ports {platform,(1U<<0)|(1U<<4)|(1U<<6)}; assert(ports.bound(platform+0x20+24*4,4));assert(!ports.bound(platform+0x20+24*3,4)); diff --git a/tests/xelp_sst_route.cpp b/tests/xelp_sst_route.cpp index 6871a549..83128a76 100644 --- a/tests/xelp_sst_route.cpp +++ b/tests/xelp_sst_route.cpp @@ -68,5 +68,13 @@ int main() { assert(w.sstRoutes(0,0,0).external(d,4).valid()==false); } } + // Panel AUX owns core power, but the panel path never becomes an + // external training-lease candidate. Mixed external ports stay separate. + for(unsigned d=0;d<2;++d) { + Display12SSTRoutes routes{0x18,0,0,1U< Date: Fri, 23 Oct 2026 23:25:17 +0900 Subject: [PATCH 27/58] Separate physical Combo resources from donor clock capacity --- WhateverXe/kern_alderlake_s.hpp | 7 +++-- WhateverXe/kern_xe.cpp | 6 ++-- WhateverXe/kern_xelp_display_resources.hpp | 35 ++++++++++++++++++++++ WhateverXe/kern_xelp_platform.hpp | 9 ++++-- tests/adls_clock_guard.py | 4 +-- tests/xelp_display_resources.cpp | 31 +++++++++++++++++++ 6 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 WhateverXe/kern_xelp_display_resources.hpp create mode 100644 tests/xelp_display_resources.cpp diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 6bdfb403..107e79f2 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -6,9 +6,10 @@ #include #include "kern_vbt.hpp" #include "kern_xelp_panel_power.hpp" +#include "kern_xelp_display_resources.hpp" namespace AlderLakeS { -constexpr uint32_t PortMask=0x79; // A, TC1, TC2, TC3, TC4 +constexpr uint32_t PortMask=XELP::ADLSComboResources::physicalMask(); // A, TC1, TC2, TC3, TC4 // Initial GT qualification is the measured UHD770 D0 configuration. Do not // infer fused EU/engine resources for other products from their family name. inline bool identity(uint32_t pci,uint32_t revision) {return pci==0x46908086 && revision==0x0C;} @@ -22,13 +23,13 @@ struct GT { } }; // The donor has three Combo clock slots. D/E need a separate clock allocator. -constexpr uint32_t ComboBackendMask=0x19; // physical A/TC1/TC2, PHY A/B/C +constexpr uint32_t ComboBackendMask=XELP::ADLSComboBackend::mask(XELP::Donor::C004FC1A); // physical A/TC1/TC2, PHY A/B/C struct Port { uint32_t ddi {},phy {},buffer {},clock {},clockShift {},clockOff {},hpd {}; static bool resolve(uint32_t ddi,Port &out) { out={}; if(ddi>6 || !(PortMask&(1U<(p+0x548); auto access=*reinterpret_cast(static_cast(controller)+0xC40); if(!config)return false; - AlderLakeS::Port route; - return self->adlsWiring.bound(self->tglPlatformCopy,config) && AlderLakeS::Port::resolve(config[0],route) && access && + AlderLakeS::Port route;uint32_t slot; + return self->adlsWiring.bound(self->tglPlatformCopy,config) && + 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; } diff --git a/WhateverXe/kern_xelp_display_resources.hpp b/WhateverXe/kern_xelp_display_resources.hpp new file mode 100644 index 00000000..81720be1 --- /dev/null +++ b/WhateverXe/kern_xelp_display_resources.hpp @@ -0,0 +1,35 @@ +// Hardware coordinates and donor capacity are separate from firmware wiring. +// A resource description does not grant GPU, protocol or live-owner admission. +#ifndef KERN_XELP_DISPLAY_RESOURCES_HPP +#define KERN_XELP_DISPLAY_RESOURCES_HPP +#include "kern_xelp_devices.hpp" +namespace XELP { +struct ADLSComboResources { + // Linux v6.12: ADL-S Combo PHY A..E map to DDI A, TC1..TC4. + static constexpr uint32_t physicalCount=5; + static constexpr uint32_t ddi(uint32_t phy) {return phy=3 && port<=6?port-2:physicalCount;} + static constexpr uint32_t physicalMask() { + uint32_t mask=0;for(uint32_t i=0;i #include #include "kern_xelp_devices.hpp" +#include "kern_xelp_display_resources.hpp" namespace XELP { struct Profile { uint32_t identity {}, revision {}; // Physical config-space identity, never spoofed. @@ -73,9 +74,11 @@ struct ProbeConfig { !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. - if(profile.family==Family::AlderLakeS) - return profile.display==Display::XeD && combo && !(combo&~0x19U) && comboBackend==0x19 && - !(excluded&~0x1FFU) && !(excluded&combo) && !legacy && !typeC && !legacyBackend; + if(profile.family==Family::AlderLakeS) { + const auto capacity=ADLSComboBackend::mask(profile.donor); + return profile.display==Display::XeD && combo && !(combo&~capacity) && comboBackend==capacity && + !(excluded&~PortMask) && !(excluded&combo) && !legacy && !typeC && !legacyBackend; + } 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; diff --git a/tests/adls_clock_guard.py b/tests/adls_clock_guard.py index 2b1cb6b6..6a9dbd9e 100644 --- a/tests/adls_clock_guard.py +++ b/tests/adls_clock_guard.py @@ -41,7 +41,7 @@ at(access,0x60)=0x164298;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} assert(!XE::adlsClockTarget(ctl,port,access));assert(!XE::adlsClockTarget(access,port,nullptr)); - for(unsigned ddi:{0u,3u,4u})for(bool isDP:{false,true}) { + for(unsigned ddi:{0u,3u,4u,5u,6u})for(bool isDP:{false,true}) { state.adlsWiring={};state.adlsWiring.valid=true; (isDP?state.adlsWiring.dp:state.adlsWiring.hdmi)=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(bound,config,sizeof(config)); at(port,0x548)=bound;at(port,0x5B8)=route.phyBase()+0x10C; - assert(XE::adlsClockTarget(ctl,port,nullptr)); + assert(XE::adlsClockTarget(ctl,port,nullptr)==(ddi<=4)); state.adlsWiring.valid=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); } state={};state.adlsClockReady=true;state.tglObservationController=ctl;state.adlsWiring.valid=true;state.adlsWiring.panel=1; diff --git a/tests/xelp_display_resources.cpp b/tests/xelp_display_resources.cpp new file mode 100644 index 00000000..0c022221 --- /dev/null +++ b/tests/xelp_display_resources.cpp @@ -0,0 +1,31 @@ +#include "kern_alderlake_s.hpp" +#include +#include +#include +using namespace XELP; +static_assert(ADLSComboResources::physicalMask()==0x79,"physical ADL-S ports"); +static_assert(ADLSComboBackend::mask(Donor::C004FC1A)==0x19,"native Combo capacity"); +int main() { + const uint32_t physical[]={0,3,4,5,6}; + for(unsigned n=0;n<5;++n) { + assert(ADLSComboResources::ddi(n)==physical[n]);assert(ADLSComboResources::phy(physical[n])==n); + AlderLakeS::Port p;assert(AlderLakeS::Port::resolve(physical[n],p) && p.phy==n); + } + assert(ADLSComboResources::ddi(5)==9 && ADLSComboResources::ddi(~0U)==9); + for(unsigned d=0;d<32;++d)for(auto donor:{Donor::Unknown,Donor::C004FC1A,Donor(2)}) { + uint32_t slot=0;const bool known=donor==Donor::C004FC1A&&(d==0||d==3||d==4); + assert(ADLSComboBackend::clockSlot(donor,d,slot)==known); + // The native index replacement is d>>1, only within the selected capacity. + assert(slot==(known?d>>1:~0U)); + } + // All masks over the nine donor port records, independent of VBT protocol. + // Physical D/E remain recognizable but never become usable native slots. + for(unsigned mask=0;mask<512;++mask) { + ProbeConfig c;c.profile=identify(0x46908086,12);c.combo=mask; + c.comboBackend=ADLSComboBackend::mask(c.profile.donor); + assert(c.valid()==(mask && !(mask&~0x19U))); + c.comboBackend=ADLSComboResources::physicalMask();assert(!c.valid()); + } + assert(!ADLSComboBackend::mask(Donor::Unknown)); + puts("PASS physical PHY mapping separate from donor slots: 96 slot cases and 512 provider masks, no D/E resource inference"); +} From f98b17bdbdf8e67d6ab8957b253f6596857ff958 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:35:48 +0900 Subject: [PATCH 28/58] Separate Display13 output protocols from resource selection --- WhateverXe/kern_display13_fb_topology.hpp | 65 +++++----- WhateverXe/kern_display13_hdmi_wiring.hpp | 5 +- WhateverXe/kern_display13_output_wiring.hpp | 48 +++++++ WhateverXe/kern_tgl_fb_binding.hpp | 4 +- WhateverXe/kern_xe.cpp | 134 ++++++++++---------- WhateverXe/kern_xe.hpp | 2 +- tests/adls_donor.py | 13 ++ tests/display13_fb_topology.cpp | 32 +++++ tests/display13_hdmi_wiring.cpp | 2 + tests/display13_power_bridge.cpp | 23 ++++ tests/tgl_fb_binding.cpp | 16 ++- 11 files changed, 235 insertions(+), 109 deletions(-) create mode 100644 WhateverXe/kern_display13_output_wiring.hpp diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index 0f96dbbd..a2689ca6 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -7,38 +7,30 @@ #include "kern_xelp_donor.hpp" namespace Display13FBTopology { constexpr uint32_t Bytes=0x1C0,Ports=9; -inline bool selectedWiring(const TGLHDMIWiring &w) { - if(!w.valid || !w.hdmi || (w.hdmi&~7U))return false; - uint32_t used=0; - for(unsigned d=0;d<3;++d) { - const auto pin=w.ddc[d]; - if(w.hdmi&(1U<3 || (used&(1U<>24)&7)>1)return UINT32_MAX; const auto selector=(trans>>27)&15; if(selector<1 || selector>3)return UINT32_MAX; @@ -47,13 +39,13 @@ inline uint32_t bootDDI(const TGLHDMIWiring &w,uint32_t trans) { } // Current handoff adopts one real GOP on pipe A. A dual board policy does not // authorize ignoring a second firmware scanout or an unknown/unpowered bank. -inline uint32_t bootstrapDDI(const TGLHDMIWiring &w,bool complete,uint32_t powered,uint32_t active,uint32_t trans) { +inline uint32_t bootstrapDDI(const Display13OutputWiring &w,bool complete,uint32_t powered,uint32_t active,uint32_t trans) { return complete && active==1 && (powered&1) ? bootDDI(w,trans) : UINT32_MAX; } // Preserve donor record count, DDI identities, memory sizing and unrelated // settings. Native objects retain pointers to this stable copy. Preflight // every identity and the existing HDMI-B record before any write. -inline bool apply(void *destination,uint32_t bytes,const void *source,const TGLHDMIWiring &w) { +inline bool apply(void *destination,uint32_t bytes,const void *source,const Display13OutputWiring &w) { if(!destination || !source || destination==source || bytes(source); if(src[0x12]!=Ports)return false; @@ -63,46 +55,47 @@ inline bool apply(void *destination,uint32_t bytes,const void *source,const TGLH for(unsigned d=0;d(destination);memcpy(dst,src,Bytes); - if(!w.apply(dst,bytes))return false; - for(unsigned d=0;d(config)==static_cast(platform)+0x20+24*ddi; } -inline bool selected(const void *platform,const void *config,const TGLHDMIWiring &w) { +inline bool selected(const void *platform,const void *config,const Display13OutputWiring &w) { if(!selectedWiring(w))return false; - for(unsigned d=0;d<3;++d)if((w.hdmi&(1U<(config)))return true; return false; } // Transport adapter validates records under the native FB mutex. The binding // core below consumes only validated port identities, independent of transport. -inline XELP::NativePorts boundPorts(const void *platform,const TGLHDMIWiring &w) { +inline XELP::NativePorts boundPorts(const void *platform,const Display13OutputWiring &w) { if(!platform || !selectedWiring(w))return {}; - for(unsigned d=0;d<3;++d)if((w.hdmi&(1U<(platform)+0x20+24*d,w))return {}; - return {platform,w.hdmi}; + 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 TGLHDMIWiring &w, +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),exactCaller,native,available,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. // Authorize masking only from the stable native connector policy: no TC -// record can be enabled or have panel/connection flags on the Combo HDMI path. -inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const TGLHDMIWiring &w) { +// 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) { if(!platform || bytes(platform); if(p[0x12]!=Ports)return 0; - for(unsigned d=0;d<3;++d)if((w.hdmi&(1U<(p+0x20+24*d); if(config[0]!=d || (d>=3 && (config[4]!=1 || config[5])))return 0; diff --git a/WhateverXe/kern_display13_hdmi_wiring.hpp b/WhateverXe/kern_display13_hdmi_wiring.hpp index 9f105c1c..158cc8ce 100644 --- a/WhateverXe/kern_display13_hdmi_wiring.hpp +++ b/WhateverXe/kern_display13_hdmi_wiring.hpp @@ -3,6 +3,7 @@ // TC HDMI has been deferred; fixed DP requires its own transport policy. #ifndef KERN_DISPLAY13_HDMI_WIRING_HPP #define KERN_DISPLAY13_HDMI_WIRING_HPP -#include "kern_tgl_hdmi_wiring.hpp" -using Display13HDMIWiring = TGLHDMIWiring; +// Compatibility name for the version1 HDMI property and older callers. +#include "kern_display13_output_wiring.hpp" +using Display13HDMIWiring = Display13OutputWiring; #endif diff --git a/WhateverXe/kern_display13_output_wiring.hpp b/WhateverXe/kern_display13_output_wiring.hpp new file mode 100644 index 00000000..031bdee4 --- /dev/null +++ b/WhateverXe/kern_display13_output_wiring.hpp @@ -0,0 +1,48 @@ +// Protocol selection is separate from native path/power/clock resources. +// Describing a route does not install its hardware backend or authorize boot +// handoff. Keep those admission decisions in the caller's operation contract. +#ifndef KERN_DISPLAY13_OUTPUT_WIRING_HPP +#define KERN_DISPLAY13_OUTPUT_WIRING_HPP +#include "kern_tgl_hdmi_wiring.hpp" +#include "kern_tgl_combo_dp.hpp" +struct Display13OutputWiring : private TGLHDMIWiring { + using TGLHDMIWiring::hdmi; + using TGLHDMIWiring::ddc; + using TGLHDMIWiring::valid; + uint32_t dp {}; + Display13OutputWiring()=default; + Display13OutputWiring(const TGLHDMIWiring &w):TGLHDMIWiring(w) {} + static Display13OutputWiring fromProperty(const void *data,uint32_t bytes) { + return TGLHDMIWiring::fromProperty(data,bytes); + } + bool matchesHDMI(const uint32_t *c) const {return described() && TGLHDMIWiring::matches(c);} + bool usesDDC(uint32_t bus) const {return described() && TGLHDMIWiring::usesDDC(bus);} + uint32_t selected() const {return hdmi|dp;} + bool described() const { + if(!valid || !selected() || (selected()&~7U) || (dp&~3U) || (hdmi&dp))return false; + uint32_t used=0; + for(unsigned d=0;d<3;++d) { + const auto pin=ddc[d]; + if(hdmi&(1U<3 || (used&(1U<=3)return false; + if(hdmi&(1U<(xelpProviderConfigureProbe)(controller,&config); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound); @@ -4384,7 +4384,7 @@ static bool display13FBAssociations(const void *controller,size_t controllerByte // framebuffer mutex and display-owner lock keep its graph stable; retained FB // identities and published backing are checked independently by the caller. static TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path, - const void *platform,const Display13HDMIWiring &wiring) { + const void *platform,const Display13OutputWiring &wiring) { auto controller=static_cast(display13FBPower.controller); if(!controller || !fb || !path || controller->getMetaClass()->getClassSize()<0x1C20 || fb->getMetaClass()->getClassSize()<0x4B90 || path->getMetaClass()->getClassSize()<0x3730)return {}; @@ -5342,7 +5342,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->adlnHDMIWiring) : 0; + Display13FBTopology::excludedTypeC(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. @@ -5357,13 +5357,13 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { check[17]=io.read(0xC400C);check[18]=io.read(0xC4004); constexpr uint32_t otherBanks[] {0x44448,0x44468,0x44478,0x44488}; for(unsigned i=0;i<4;++i)check[19+i]=io.read(otherBanks[i]+4)&~io.read(otherBanks[i]-4); - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); const auto actual=Display13FBIRQ::readActivePipes(io,admitted); c.status=c.quiescing ? Display13FBIRQ::Status::Power : actual.status; if(c.status==Display13FBIRQ::Status::Complete)c.status=c.state.begun && c.state.enabled ? - Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnHDMIWiring.hdmi); - } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnHDMIWiring.hdmi); + Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnOutputWiring.selected()); + } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); adlnIRQCache(controller,io);adlnIRQRecord(record,1); check[23]=static_cast(c.status);check[24]=c.state.begun; check[25]=c.state.enabled;check[26]=c.state.faulted;check[27]=c.state.active;check[28]=c.state.configured; @@ -5399,23 +5399,23 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec const bool valid=display13FBAssociations(controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(controller)) && path->getMetaClass()->getClassSize()>=0x3730; const auto pipe=valid ? Display13FBMemory::field(path,0x3640) : 3; - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) && (c.quiescing || (pipe<3 && (c.blockedPipes&(1U<adlnOutputWiring) && (c.quiescing || (pipe<3 && (c.blockedPipes&(1U<adlnHDMIWiring) && enabled && !c.quiescing) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && enabled && !c.quiescing) { const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); c.status=(admitted&(1U<adlnHDMIWiring) && enabled && c.state.begun && c.state.enabled && !c.state.active && !c.state.faulted && + 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->adlnHDMIWiring) : 0; - c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnHDMIWiring.hdmi); + Display13FBTopology::excludedTypeC(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); } @@ -5562,13 +5562,13 @@ bool XE::processAlderLakeNSysMem(KernelPatcher &patcher,size_t index,mach_vm_add IORegistryEntry::getRegistryRoot()->setProperty("N97FixedSysMemEnabled",adlnFixedSysMemEnabled);return ready; } void XE::wrapAlderLakeNSysMemAllocate(IOService *controller,uint32_t slot) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) || !slot) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) || !slot) { FunctionCast(wrapAlderLakeNSysMemAllocate,callbackXE->orgAlderLakeNSysMemAllocate)(controller,slot);return; } adlnSysMemOperation(controller,slot,false); } void XE::wrapAlderLakeNSysMemFree(IOService *controller,uint32_t slot) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) || !slot) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) || !slot) { FunctionCast(wrapAlderLakeNSysMemFree,callbackXE->orgAlderLakeNSysMemFree)(controller,slot);return; } adlnSysMemOperation(controller,slot,true); @@ -5638,7 +5638,7 @@ void XE::adlnSysMemOperation(IOService *controller,uint32_t slot,bool release) { !layout.slots[slot].descriptor && adlnSysMemClaim(controller,slot)==fb; if(ready) { stage=7;auto path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); pipe=binding.pipe;ready=binding.status==TGLFBBinding::Status::Complete && pipe<3; } if(ready) { @@ -5737,11 +5737,11 @@ void XE::adlnSysMemOperation(IOService *controller,uint32_t slot,bool release) { uint32_t XE::adlnRequestedSharedReservation(IOService *controller) { if(!controller || controller->getMetaClass()->getClassSize()<0x1C20)return 0; - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto prefix=(uint64_t(Display13FBMemory::field(controller,0xDF8))+0xFFF)&~0xFFFULL; return prefix<0x10000000 ? static_cast(prefix) : 0; } - if(!Display13FBTopology::adlnWiring(callbackXE->adlnHDMIWiring))return 0; + if(!Display13FBTopology::adlnWiring(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); @@ -5751,13 +5751,13 @@ uint32_t XE::adlnRequestedSharedReservation(IOService *controller) { IOReturn XE::wrapDisplay13FramebufferPlatformCall(IOService *controller,const OSSymbol *name,bool wait, void *arg1,void *arg2,void *arg3,void *arg4) { auto self=callbackXE; - const bool fixed=Display13FBTopology::multiOutputWiring(self->adlnHDMIWiring) && controller && controller==display13FBGTT.controller && + const bool fixed=Display13FBTopology::multiOutputWiring(self->adlnOutputWiring) && controller && controller==display13FBGTT.controller && 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); const auto plan=Display13FBMemory::sharedReservation(layout,0x10000000,adlnFixedSysMemEnabled); - const bool ready=Display13FBTopology::adlnWiring(self->adlnHDMIWiring) && plan.valid && + const bool ready=Display13FBTopology::adlnWiring(self->adlnOutputWiring) && plan.valid && display13FBGTT.initialized && !display13FBGTT.faulted && !display13FBPower.memoryFaulted && arg1 && arg1==__atomic_load_n(&adlnSharedOwner,__ATOMIC_ACQUIRE) && arg2 && arg3; if(!ready)return kIOReturnNotReady; @@ -5920,10 +5920,10 @@ 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->adlnHDMIWiring) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring) || !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->adlnHDMIWiring))return false; + if(!Display13FBTopology::apply(s->adlnPlatform,sizeof(s->adlnPlatform),platform,s->adlnOutputWiring))return false; const auto nativePool=Display13FBMemory::field(controller,0xDE8); const auto profilePool=Display13FBMemory::field(s->adlnPlatform,0x14); const auto requested=s->framebufferPatchFlags.bits.FPFStolenMemorySize ? s->framebufferPatch.fStolenMemorySize : 0; @@ -5950,7 +5950,7 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { auto s=callbackXE; 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->adlnHDMIWiring)) { + if(Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring)) { // 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)) { @@ -5970,7 +5970,7 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { } // Deliberately excluded connector policy. Do not enter TC/AUX hardware // probing, and do not substitute a connected result for the selected port. - for(unsigned d=0;dadlnHDMIWiring.hdmi&(1U<adlnPlatform,config,d)) { + for(unsigned d=0;dadlnOutputWiring.selected()&(1U<adlnPlatform,config,d)) { *reinterpret_cast(reinterpret_cast(port)+0x554)=0;return 0; } return 0; @@ -6071,7 +6071,7 @@ uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t spee // Both S1 HDMI ports use this donor wait boundary. Qualify the actual // board-selected DDC pin; an A-only boot must not inherit the B-only gate. o.edid=controller==callbackXE->adlnTopologyController && - callbackXE->adlnHDMIWiring.usesDDC(bus) && speed==0 && req && + callbackXE->adlnOutputWiring.usesDDC(bus) && speed==0 && req && *reinterpret_cast(req)==0 && *reinterpret_cast(req+4)==1 && *reinterpret_cast(req+0x0C)==0xA0 && *reinterpret_cast(req+0x38)==128; scope=&o;break; @@ -6467,7 +6467,7 @@ uint32_t XE::wrapDisplay13FBDDC(IOService *fb,int connection,uint32_t block,uint } uint32_t XE::wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,bool force) { auto config=Display13FBMemory::field(port,0x548); - const bool selected=Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring); + const bool selected=Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring); uint64_t r[8] {1,block,force,0,0,config ? config[0] : 9}; auto root=IORegistryEntry::getRegistryRoot();if(selected)root->setProperty("N97FBPortDDCEntry",r,sizeof(r)); const auto result=FunctionCast(wrapAlderLakeNPortDDC,callbackXE->orgAlderLakeNPortDDC)(port,block,data,force);r[3]=result; @@ -6484,7 +6484,7 @@ bool XE::wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t b 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); - const bool corrected=lanes && Display13FBTopology::skipDPLanes(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring, + 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}; @@ -6572,15 +6572,15 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto property=provider->getProperty("xelp-hdmi-wiring"); if(!property)property=provider->getProperty("tgl-hdmi-wiring"); auto wiring=OSDynamicCast(OSData,property); - s->adlnHDMIWiring=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, + s->adlnOutputWiring=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, wiring?wiring->getLength():0,s->firmwarePorts,3); - if(!Display13FBTopology::supportedWiring(s->adlnHDMIWiring))return false; + if(!Display13FBTopology::supportedWiring(s->adlnOutputWiring))return false; const bool dualReady=s->adlnNativeReady && s->adlnScanoutDisableFB && s->orgAlderLakeNSysMemAllocate && s->orgAlderLakeNSysMemFree && (!adlnFixedSysMemEnabled || display13FBSysMem.lock) && display13FBSysMem.retireLock && s->adlnWorkLoopInGate && s->adlnWorkLoopTryClose && s->adlnWorkLoopOpen; - uint64_t dualPolicy[] {1,s->adlnHDMIWiring.hdmi,dualReady,s->adlnNativeReady,s->adlnScanoutDisableFB!=0, + uint64_t dualPolicy[] {1,s->adlnOutputWiring.selected(),dualReady,s->adlnNativeReady,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->adlnHDMIWiring) && !dualReady)return false; + if(Display13FBTopology::multiOutputWiring(s->adlnOutputWiring) && !dualReady)return false; // Native provider-workloop mode creates/binds the PCI interrupt source in // controller start, rather than waiting for an accelerator or FB open. // This backend supports linear boot surfaces and Y-tiled native scanout, @@ -6713,7 +6713,7 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) if(valid) { auto config=Display13FBMemory::field(port,0x548); valid=Display13FBTopology::bound(callbackXE->adlnPlatform,config,c.bridge.bootPort) && - Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring) && + Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring) && Display13Power::objectAssociations(c.power,0x80,controller,0x1C20); } check[35]=valid; @@ -6748,12 +6748,12 @@ bool XE::wrapAlderLakeNScanoutAllocate(IOService *fb) { auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; IORecursiveLockLock(mutex); bool admitted=false,allocated=false,bootScanout=false;Display13FBMemory::Readout r; - unsigned pipe=0;const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring); + unsigned pipe=0;const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring); uint64_t record[15] {2,1};IOLockLock(c.lock); auto controller=static_cast(c.controller); TGLFBBinding::Readout binding; if(dual) { - binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); pipe=binding.pipe; } record[12]=pipe;record[13]=static_cast(binding.status);record[14]=dual; @@ -6780,7 +6780,7 @@ bool XE::wrapAlderLakeNScanoutAllocate(IOService *fb) { // before publishing an IRQ-visible identity or surface. if(dual) { const auto path=Display13FBMemory::singlePath(fb); - const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); allocated=allocated && current.status==TGLFBBinding::Status::Complete && current.pipe==pipe && current.ddi==binding.ddi && Display13FBMemory::identityAvailable(c.pipe,fb,pipe); if(allocated) { @@ -6823,10 +6823,10 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { IOWorkLoop *acquiredSwapGate=nullptr;OSObject *stopPath=nullptr;bool stopSwap=false; IOLockLock(c.lock); pipe=Display13FBMemory::boundPipe(c.pipe,fb);record[18]=pipe; - const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); record[19]=static_cast(binding.status); if(callbackXE->display13FBMemoryHooksReady && pipe<3 && !c.pipe[pipe].destroyingPath && !c.memoryFaulted && - (!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) || (binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe))) { + (!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) || (binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe))) { r=display13FBInspectMemory(fb,false,pipe); admitted=r.status==Display13FBMemory::Status::Complete; record[2]=reinterpret_cast(fb);record[3]=reinterpret_cast(c.controller); @@ -6834,7 +6834,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { record[6]=Display13FBMemory::field(c.controller,0xDF0);record[7]=Display13FBMemory::field(fb,0x4348); record[8]=Display13FBMemory::field(fb,0x4330);record[9]=static_cast(r.status); } - if(admitted && Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(admitted && Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { auto controller=static_cast(c.controller); const auto slot=Display13FBMemory::field(fb,0x4288); const auto surface=Display13FBMemory::field(fb,0x4348); @@ -6880,7 +6880,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { swap[14]=1; reinterpret_cast(callbackXE->adlnScanoutDisableFB)(fb,false); IOLockLock(c.lock); - const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); swap[15]=static_cast(current.status); Display13FBIRQIO io {c.map->getVirtualAddress()};swap[16]=Display13FBIRQ::consumersStopped(io,1U<adlnHDMIWiring)) { - const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { + const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); admitted=current.status==TGLFBBinding::Status::Complete && current.pipe==pipe && current.ddi==binding.ddi; if(!admitted) { r.status=Display13FBMemory::Status::Association; @@ -6924,7 +6924,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { adlnPublishMemory("N97FBScanoutSetup",pipe,record,sizeof(record));adlnPublishMemory("N97FBBacking",pipe,&r,sizeof(r)); } void XE::wrapAlderLakeNScanoutFree(IOService *fb) { - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) {adlnFreeStoppedPipe(fb);return;} + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) {adlnFreeStoppedPipe(fb);return;} auto &c=display13FBPower;if(!c.lock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; IORecursiveLockLock(mutex); @@ -7014,7 +7014,7 @@ void XE::adlnFreeStoppedPipe(IOService *fb) { const auto mask=controller ? Display13FBMemory::field(controller,0xDF0) : 0; const auto surface=Display13FBMemory::field(fb,0x4348); const auto pool=slot<3 ? Display13FBMemory::pool(controller,slot) : Display13FBMemory::Pool {}; - const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); bool pinned=false;for(const auto &stored:display13FBSysMem.slot)if(stored.framebuffer==fb && stored.buffer)pinned=true; const bool noResource=controller && slot==0xFF && !surface && !pinned && Display13FBMemory::bitmapValid(count,used,mask) && @@ -7104,7 +7104,7 @@ void XE::adlnFreeStoppedPipe(IOService *fb) { } void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return; } auto &c=display13FBPower; @@ -7116,7 +7116,7 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { 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); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool selected=pipe<3 && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe; record[1]=pipe;record[2]=selected;record[3]=binding.ddi; const auto countOffset=Display13FBMemory::pathCountOffset(fb); @@ -7249,7 +7249,7 @@ void XE::wrapAlderLakeNClockInit(IOService *controller) { } uint64_t XE::wrapAlderLakeNClockProbe(IOService *controller) {return adlnClockReadout(controller).nativeHz();} void XE::wrapAlderLakeNClockSet(IOService *controller,uint64_t nativeHz) { - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) {adlnSetDualClock(controller,nativeHz);return;} + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) {adlnSetDualClock(controller,nativeHz);return;} auto &c=display13FBPower;if(!c.lock)return; Display13Clock::Change result;result.requestedHz=nativeHz; auto fb=c.pipe[0].memoryFramebuffer; @@ -7374,12 +7374,12 @@ void XE::adlnSetDualClock(IOService *controller,uint64_t nativeHz) { if(ready) { TGLFBBinding::PublishedPath paths[3];uintptr_t owners[3]; for(unsigned p=0;p<3;++p) {paths[p]=c.pipe[p].powerPath;owners[p]=reinterpret_cast(c.pipe[p].memoryFramebuffer);} - const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnHDMIWiring.hdmi); + const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnOutputWiring.selected()); uint32_t published=0;for(unsigned d=0;d<7;++d)if(map.known&(1U<(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); ready=fb==c.pipe[p].memoryFramebuffer && !c.pipe[p].destroyingPath && TGLFBBinding::publish(paths[p],reinterpret_cast(fb),reinterpret_cast(path),binding,p); } @@ -7531,7 +7531,7 @@ void XE::adlnWMSetup(OSObject *plane,OSObject *path,bool cursor) { {plane,plane ? plane->getMetaClass()->getClassSize() : 0}, {port,port ? port->getMetaClass()->getClassSize() : 0},display13FBExpectedOwner(controller) }; - const auto binding=TGLFBBinding::inspect(objects,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=TGLFBBinding::inspect(objects,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); uint64_t bindingRecord[] {1,static_cast(binding.status),binding.framebuffer,binding.pipe,binding.ddi,binding.plane, binding.status==TGLFBBinding::Status::Complete ? Display13FBMemory::allocatedSlot(controller,fb) : UINT32_MAX}; const auto pipe=binding.status==TGLFBBinding::Status::Complete ? binding.pipe : UINT32_MAX; @@ -7635,7 +7635,7 @@ void XE::wrapAlderLakeNPipeWMSetup(IOService *controller,IOService *fb,OSObject // memory ownership; programming still checks the published pipe owner. auto port=associated ? Display13FBMemory::field(path,0x3648) : nullptr; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548) : nullptr; - const bool valid=associated && Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring); + const bool valid=associated && Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring); auto scaler=valid ? Display13FBMemory::field(path,0x32C0) : nullptr; if(valid && scaler && scaler->getMetaClass()->getClassSize()>=0x58) { // This CPU method is also called before memory/plane programming. Use @@ -7675,7 +7675,7 @@ void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObje if(c.lock && controller==c.controller && fb && fb->getMetaClass()->getClassSize()>=0x4B90 && path && path->getMetaClass()->getClassSize()>=0x3730) { IOLockLock(c.lock); pipe=Display13FBMemory::boundPipe(c.pipe,fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); selected=pipe<3 && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::field(path,0x3644)==2 && !c.bridge.state.faulted && !c.clockFaulted && !(c.dbuf.updating&(1U<(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool pathValid=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && display13FBAssociations(c.controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(c.controller)); auto scaler=pathValid ? Display13FBMemory::field(path,0x32C0) : nullptr; @@ -7929,7 +7929,7 @@ void XE::wrapDisplay13WMArmUpdate(IOService *fb) { if(mutex && c.engineEnabled && c.map && c.access && !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && (owner || !c.pipe[pipe].wmCommitting)) { path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && display13FBAssociations(c.controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(c.controller)) && !Display13FBMemory::field(fb,0x4A48) && adlnWriterMappingBound(); @@ -8097,7 +8097,7 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { !c.bridge.state.suspended && acquired.status==Display13Power::Status::Complete && !c.clockFaulted && clock.status==Display13Clock::Status::Complete && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { Display13FBDBUFIO io {c.map}; - status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnHDMIWiring.hdmi)>=3 ? 7U : 0U); + status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnOutputWiring.selected())>=3 ? 7U : 0U); if(status==Display13DBUF::Status::Complete) { if(acquired.status==Display13Power::Status::Complete)status=Display13DBUF::resumeEngine(io,c.dbuf); else status=Display13DBUF::Status::Power; @@ -8130,7 +8130,7 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { uint64_t record[12] {},hotplug[16] {},irqRelease[12] {};Display13Power::Result released;Display13Clock::Change clock; IOWorkLoop *acquiredGate=nullptr;bool gateAttempted=false,gateReady=false;uint64_t gateEvidence[8] {}; Display13DBUF::Status status=Display13DBUF::Status::Unavailable;IOLockLock(c.lock); - const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring); + const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring); auto ensureGate=[&]() { if(!dual)return true; if(gateAttempted)return gateReady; @@ -8291,7 +8291,7 @@ void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,b if(pipe>=3 || c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} Display13DBUF::Status status=Display13DBUF::Status::Unavailable; auto path=fb ? Display13FBMemory::field(plane,0x70) : nullptr; - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::field(path,0x32C8)==plane; if(callbackXE->display13FBDBUFHooksReady && associated && c.engineEnabled && @@ -8310,7 +8310,7 @@ void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,b IOLockUnlock(c.lock); FunctionCast(wrapAlderLakeNPrimaryDBUF,callbackXE->orgAlderLakeNPrimaryDBUF)(plane,start,end,wait); IOLockLock(c.lock); - const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); status=current.status==TGLFBBinding::Status::Complete && current.pipe==pipe && current.ddi==binding.ddi ? Display13DBUF::finishPrimary(io,c.dbuf,pipe) : Display13DBUF::fail(c.dbuf,Display13DBUF::Status::Topology); if(status==Display13DBUF::Status::Complete && Display13FBMemory::field(plane,0x10C)!=Display13DBUF::pendingHardwareRaw(c.dbuf,pipe)) @@ -8341,7 +8341,7 @@ void XE::wrapAlderLakeNPlanarDBUFWrite(void *access,uint64_t reg,uint32_t value) uint64_t record[14] {2,reg,value};auto status=Display13DBUF::Status::Unavailable; auto path=Display13FBMemory::singlePath(fb); auto plane=path && path->getMetaClass()->getClassSize()>=0x3730 ? Display13FBMemory::field(path,0x32C8) : nullptr; - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=plane && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::boundPipe(c.pipe,fb)==pipe && Display13FBMemory::field(plane,0x90)==access && access==c.access && Display13FBMemory::field(plane,0xA0)==reg && Display13FBMemory::field(plane,0x10C)==value && @@ -8377,7 +8377,7 @@ void XE::wrapDisplay13CursorDBUF(OSObject *plane) { const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); if(pipe>=3 || c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} auto path=Display13FBMemory::field(plane,0x70); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::field(path,0x32C8)==plane; if(callbackXE->display13FBDBUFHooksReady && callbackXE->display13FBWatermarkHooksReady && associated && @@ -8422,7 +8422,7 @@ void XE::adlnSetupDBUF(IOService *controller,bool optimized) { // transaction, update only the mutexes this thread already owns. Do not // wait for another FB while holding its peer's mutex. A global caller // owning none can process retained identities one at a time. - const auto selected=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) ? (held ? (held&published) : published) : (published&1U); + const auto selected=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) ? (held ? (held&published) : published) : (published&1U); uint64_t dispatch[] {1,optimized,published,held,selected}; IORegistryEntry::getRegistryRoot()->setProperty("N97FBDBUFDispatch",dispatch,sizeof(dispatch)); for(unsigned pipe=0;pipe<3;++pipe) { @@ -8438,7 +8438,7 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo IORecursiveLockLock(mutex);IOLockLock(c.lock); Display13DBUF::Layout layout;Display13DBUF::Range relativePrimary;OSObject *plane=nullptr;uint64_t record[18] {},resume[6] {}; auto path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=Display13FBMemory::boundPipe(c.pipe,fb)==pipe && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe; // A cold DDB demand may precede engine resume. Restore its clock/power @@ -8458,7 +8458,7 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo if(plane && plane->getMetaClass()->getClassSize()>=0x1C8 && Display13FBMemory::field(plane,0x68)==fb && Display13FBMemory::field(plane,0x70)==path) { Display13FBDBUFIO io {c.map};const auto bound=Display13DBUF::bindOffPipe(io,c.dbuf,pipe); - if(bound==Display13DBUF::Status::Complete)layout=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) ? + if(bound==Display13DBUF::Status::Complete)layout=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) ? Display13DBUF::pipeLayout(io,c.dbuf,pipe) : Display13DBUF::singlePipeLayout(io,c.dbuf); else layout.status=bound; } @@ -8529,7 +8529,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { service->retain();static_cast(power)->retain();static_cast(c.access)->retain(); Display13FBPowerIO io {map}; const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); - const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnHDMIWiring,a); + const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a); const auto ddi=bootPort<3 ? io.read(0x64000+0x100*bootPort) : UINT32_MAX; // The initial protected references belong to the measured pipe A / // selected Combo boot display. Validate that live topology before acquiring @@ -8540,7 +8540,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { const auto trans=io.read(0x60400+pipe*0x1000); boot=boot && trans!=UINT32_MAX && !(trans&(1U<<31)); } - if(boot)result=c.bridge.begin(io,bootPort,callbackXE->adlnHDMIWiring.hdmi); + 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 && !Display13Power::publishObject(c.bridge,power,0x80,controller,0xC60)) { result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__); @@ -8625,19 +8625,19 @@ void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { check[14]=released; if(display13TraceEnabled)c.powerContext[22]=released; uint32_t ddiA=7,ddiB=1,ddiC=7,tcPipes[4] {7,7,7,7};bool mapValid=true;uint32_t known=0; - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) && released) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && released) { TGLFBBinding::PublishedPath paths[3];uintptr_t owners[3]; for(unsigned pipe=0;pipe<3;++pipe) { paths[pipe]=c.pipe[pipe].powerPath;owners[pipe]=reinterpret_cast(c.pipe[pipe].memoryFramebuffer); } - const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnHDMIWiring.hdmi); + const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnOutputWiring.selected()); mapValid=map.valid;known=map.known;ddiA=map.mask[0];ddiB=map.mask[1];ddiC=map.mask[2]; for(unsigned tc=0;tc<4;++tc)tcPipes[tc]=map.mask[tc+3]; } const auto affected=Display13FBIRQ::affectedPipes(released,ddiA,ddiB,ddiC,tcPipes[0],tcPipes[1],tcPipes[2],tcPipes[3]); check[16]=affected;check[17]=mapValid;check[18]=ddiA;check[19]=ddiB;check[20]=known; bool gateReady=true; - if(released && Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(released && Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const bool bound=c.workLoop && Display13FBMemory::field(c.controller,0xE00)==c.workLoop; const bool api=callbackXE->adlnWorkLoopInGate && callbackXE->adlnWorkLoopTryClose && callbackXE->adlnWorkLoopOpen; gateReady=false;gateEvidence[0]=1;gateEvidence[1]=released;gateEvidence[2]=bound;gateEvidence[3]=api; @@ -8654,7 +8654,7 @@ void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { !(Display13WM::busyPipes(c.pipe,true)&affected) && adlnIRQOwner(static_cast(c.controller))) { auto &irq=display13FBIRQ;Display13FBIRQIO irqIO {irq.base}; const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto queued=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE08),__ATOMIC_ACQUIRE); const auto aux=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE0C),__ATOMIC_ACQUIRE); const auto hpd=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE10),__ATOMIC_ACQUIRE); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index ef9020f9..1779e7cf 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -122,7 +122,7 @@ class XE { mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; - Display13HDMIWiring adlnHDMIWiring; + Display13OutputWiring adlnOutputWiring; uint64_t adlnPlatform[0x1C0/8] {}; IOService *adlnTopologyController {}; mach_vm_address_t orgAlderLakeNGetOSInformation {},orgAlderLakeNPortState {}; diff --git a/tests/adls_donor.py b/tests/adls_donor.py index 09df2029..96f27c82 100644 --- a/tests/adls_donor.py +++ b/tests/adls_donor.py @@ -34,6 +34,7 @@ assert ports[:6]==[0,0,0,0,2,0x18] assert ports[18:24]==[3,9,1,1,0x400,0x201] code='''#include "kern_alderlake_s.hpp" +#include "kern_display13_fb_topology.hpp" #include #include int main() { @@ -61,6 +62,18 @@ const auto panel=AlderLakeS::Wiring::fromFirmware(firmware); assert(panel.panel==1 && panel.apply(platform,sizeof(platform))); assert(!memcmp(platform+0x20,ports,24) && panel.boundPanel(platform,reinterpret_cast(platform+0x20))); + // The same captured predecessor records feed Display13 CPU topology. + for(unsigned assignment=1;assignment<9;++assignment) { + Display13OutputWiring w;w.valid=true;unsigned code=assignment; + for(unsigned d=0;d<2;++d){unsigned kind=code%3;code/=3; + if(kind==1){w.hdmi|=1U< #include +#include #include "kern_display13_fb_topology.hpp" using namespace Display13FBTopology; +static_assert(!std::is_convertible::value,"Do not silently drop DP policy through a legacy HDMI API"); int main() { for(unsigned mask=1;mask<=7;++mask) { const uint32_t legacy[] {1,mask,(mask&1)?1U:0U,(mask&2)?2U:0U,(mask&4)?3U:0U}; diff --git a/tests/display13_power_bridge.cpp b/tests/display13_power_bridge.cpp index 57bedcd4..4ca59fad 100644 --- a/tests/display13_power_bridge.cpp +++ b/tests/display13_power_bridge.cpp @@ -4,6 +4,7 @@ #include #include #include "kern_display13_power_bridge.hpp" +#include "kern_display13_output_wiring.hpp" using namespace Display13Power; struct IO { std::map regs {{0x45404,0xFC0F},{0x45454,0xC},{0x45444,0},{0x42000,0x8E380000}}; @@ -29,6 +30,28 @@ 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() { + // 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. + for(unsigned assignment=1;assignment<9;++assignment) { + Display13OutputWiring w;w.valid=true;unsigned code=assignment; + for(unsigned d=0;d<2;++d){const unsigned kind=code%3;code/=3; + if(kind==1){w.hdmi|=1U< void put(void *p,size_t off,T value) {memcpy(static_castinspect().ddi]==7);paths[p]=saved;owners[p]=owner; } } + // Mixed HDMI/DP topology feeds the same native binding and withdrawal + // map. Change protocol, FB index and pipe independently of physical DDI. + for(unsigned dp=1;dp<4;++dp)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned fb=0;fb<3;++fb) { + for(unsigned ddi=0;ddi<2;++ddi) { + Fixture x(fb,pipe,ddi);x.wiring.hdmi=3&~dp;x.wiring.dp=dp; + for(unsigned d=0;d<3;++d)x.wiring.ddc[d]=(x.wiring.hdmi&(1U<(x.fb); + assert(publish(paths[pipe],owners[pipe],reinterpret_cast(x.path),r,pipe)); + auto m=powerPipes(paths,owners,x.wiring.selected());assert(m.valid&&m.known==(1U< Date: Fri, 23 Oct 2026 23:41:26 +0900 Subject: [PATCH 29/58] Apply Combo PLL fractional workaround on Display13 --- WhateverXe/kern_xe.cpp | 20 ++++++- WhateverXe/kern_xe.hpp | 1 + tests/display13_combo_clock.py | 105 +++++++++++++++++++++++++++++++++ tools/test.py | 4 +- 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 tests/display13_combo_clock.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index d4848513..13e4b09c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -5992,6 +5992,10 @@ 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}; + // 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, + 0x48,0x8b,0x75,0xb8,0xff,0x90,0x30,0x01,0x00,0x00}; const uint8_t readEntry[] {0x55,0x48,0x89,0xE5,0x53,0x50}; const uint8_t resetBefore[] {0xBE,0x04,0x51,0x0C,0x00,0xBA,0x01,0x00,0x00,0x00,0xFF,0x90,0x30,0x01,0x00,0x00}; const uint8_t resetAfter[] {0xBE,0x04,0x51,0x0C,0x00,0xBA,0x00,0x00,0x00,0x80,0xFF,0x90,0x30,0x01,0x00,0x00}; @@ -6021,8 +6025,9 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m "__ZN31AppleIntelRegisterAccessManager14ReadRegister32Em",address,size);patcher.clearError(); auto write=patcher.solveSymbol(index, "__ZN31AppleIntelRegisterAccessManager15WriteRegister32Emj",address,size);patcher.clearError(); - if(!adlnDetected || !adlnKernelReady || !display13FBABIReady || size<=0x9A118 || + if(!adlnDetected || !adlnKernelReady || !display13FBABIReady || !display13FBClockReadoutHooksReady || size<=0x9A118 || clocks!=address+0x6CE10 || bus!=address+0x53A7C || + memcmp(reinterpret_cast(address+0x6D95D),fractionWrite,sizeof(fractionWrite)) || memcmp(reinterpret_cast(bus),busEntry,sizeof(busEntry)) || read!=address+0x5E344 || write!=address+0x5EB5C || memcmp(reinterpret_cast(read),readEntry,sizeof(readEntry)) || @@ -6058,7 +6063,9 @@ 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); - return ready && processDisplay13FramebufferColor(patcher,index,address,size); + const bool complete=ready && processDisplay13FramebufferColor(patcher,index,address,size); + adlnComboPLLFractionCaller=complete ? address+0x6D973 : 0; + return complete; } uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request) { static uint32_t count {}; const auto call=__sync_add_and_fetch(&count,1); @@ -6318,6 +6325,15 @@ void XE::wrapAlderLakeNGamma10(void *path,void *transaction,uint32_t control) { } void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { + // DISPLAY13_COMBO_FRACTION: qualified nominal writer, physical reference, + // retained controller/manager. No MMIO or other port's PLL inference. + if(callbackXE->adlnDetected && callbackXE->xeProfile.display==XELP::Display::XeLPD && + callbackXE->adlnComboPLLFractionCaller && reinterpret_cast(__builtin_return_address(0))==callbackXE->adlnComboPLLFractionCaller) { + auto controller=reinterpret_cast(callbackXE->adlnTopologyController); + if(controller && access && *reinterpret_cast(controller+0xC40)==access) + TGLComboPLLFraction::adjust(*reinterpret_cast(controller+0xEA8),reg,value); + } + // DISPLAY13_COMBO_FRACTION_END // 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) { diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 1779e7cf..5e9b318e 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -123,6 +123,7 @@ class XE { bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; Display13OutputWiring adlnOutputWiring; + mach_vm_address_t adlnComboPLLFractionCaller {}; uint64_t adlnPlatform[0x1C0/8] {}; IOService *adlnTopologyController {}; mach_vm_address_t orgAlderLakeNGetOSInformation {},orgAlderLakeNPortState {}; diff --git a/tests/display13_combo_clock.py b/tests/display13_combo_clock.py new file mode 100644 index 00000000..42624008 --- /dev/null +++ b/tests/display13_combo_clock.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Production Display13 fractional write boundary and optional native PLL replay.""" +from pathlib import Path +import hashlib,os,re,struct,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +guard=s[s.index(' // DISPLAY13_COMBO_FRACTION:'):s.index(' // DISPLAY13_COMBO_FRACTION_END')] +guard=guard.replace('reinterpret_cast(__builtin_return_address(0))','caller') +install=s[s.index('bool XE::processDisplay13FramebufferOutput('):s.index('uint32_t XE::wrapDisplay13GMBus(')] +def array(name): + text=re.search(r'const uint(?:8|64)_t '+name+r'\[\] \{([^}]+)\}',install).group(1) + return [int(x.strip(),0) for x in text.split(',')] +code=r''' +#include "kern_xelp_platform.hpp" +#include "kern_tgl_port_map.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +struct IOService; +struct State { + bool adlnDetected=true;struct {XELP::Display display=XELP::Display::XeLPD;}xeProfile; + uintptr_t adlnComboPLLFractionCaller=0x1000;IOService *adlnTopologyController=nullptr; +}state,*callbackXE=&state; +uint32_t adjust(void *access,uint64_t reg,uint32_t value,uintptr_t caller) {GUARD return value;} +templateT &field(void*p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +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; +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);} +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(reg==0x46010 || reg==0x46014){value&=~0x44000000U; + if((value&0x08000000)&&fault!=1)value|=0x04000000; + if((value&0x80000000)&&fault!=2)value|=0x40000000;} + mmio[reg]=value; +} +void mailbox(void*p,uint32_t command,uint32_t,bool wait){assert(p==ctl&&command==7&&!wait);} +uint32_t voltage(void*p){assert(p==ctl);return 1;} +void getDDI(void*p){assert(p==port);++get;}void putDDI(void*p){assert(p==port);++put;} +void delay(unsigned n){assert(n==1);++delays;}void error(const char*,...){++errors;} +int main(int argc,char**argv){ + state.adlnTopologyController=reinterpret_cast(ctl);field(ctl,0xc40)=access;field(ctl,0xea8)=2; + for(unsigned bad=0;bad<9;++bad){State saved=state; + if(bad==0)state.adlnDetected=false;if(bad==1)state.xeProfile.display=XELP::Display::XeD; + if(bad==2)state.adlnComboPLLFractionCaller=0;if(bad==3)state.adlnTopologyController=nullptr; + if(bad==4)field(ctl,0xea8)=1;if(bad==8)field(ctl,0xc40)=port; + assert(adjust(bad==5?nullptr:access,bad==6?0x164288:0x164284,0x1c001a5,bad==7?0x1001:0x1000)==0x1c001a5); + state=saved;field(ctl,0xea8)=2;field(ctl,0xc40)=access; + } + assert(adjust(access,0x164284,0x1c001a5,0x1000)==0xe001a5); + assert(adjust(access,0x16428c,0x1c001a5,0x1000)==0xe001a5); + assert(adjust(access,0x164294,0x1c001a5,0x1000)==0x1c001a5); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vectorraw((std::istreambuf_iterator(f)),{}); + image=(uint8_t*)mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);assert(image!=MAP_FAILED); + memcpy(image,raw.data(),raw.size()); + for(auto table:{std::pair{0xcd330,3},{0xcd348,6},{0xcd378,3},{0xcd3c0,3}}) + for(unsigned i=0;i(image,table.first+8*i);assert(v>=0xd0000&&v<0x160000);v+=(uintptr_t)image;} + jump(image+0x5e344,(uintptr_t)read);jump(image+0x4691e,(uintptr_t)mailbox);jump(image+0x6bf6e,(uintptr_t)voltage); + jump(image+0x1fefe,(uintptr_t)getDDI);jump(image+0x1ff54,(uintptr_t)putDDI); + for(unsigned at:{0x6d081U,0x6d9ecU,0x6dd6dU,0x6de16U})redirect(at,(uintptr_t)delay,0x300000); + for(unsigned at:{0x6d54cU,0x6dda6U})redirect(at,(uintptr_t)error,0x300020); + uintptr_t vt[64]{};vt[0x130/8]=(uintptr_t)write;field(access,0)=vt;field(port,0x548)=config; + state.adlnComboPLLFractionCaller=(uintptr_t)image+0x6d973; + auto enable=(void(*)(void*,void*,void*,uint32_t*))(image+0x6ce10);auto disable=(void(*)(void*,void*,void*))(image+0x6dae6); + unsigned cases=0,counterexamples=0; + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned ref=0;ref<3;++ref) + for(unsigned rate:{6U,10U,20U,30U})for(bool hdmi:{false,true})for(bool fixed:{false,true}) { + if(hdmi && rate!=6)continue; + correction=fixed;get=put=delays=errors=0;field(ctl,0xea8)=ref; + const uint32_t record[]={ddi,ddi+1,hdmi?2U:1U,0,hdmi?0x800U:0x400U,hdmi?0x202U:0x201U};memcpy(config,record,sizeof(record)); + field(port,0x114)=hdmi?0:1;field(port,0x128)=rate; + const auto pll=0x46010+4*ddi,cfg0=0x164284+8*ddi,cfg1=cfg0+4,off=1U<<(10+ddi); + mmio={{pll,0},{cfg0,0},{cfg1,0},{0x164280,0x01e07400}}; + uint32_t params[]={464,2049,2,3,2};enable(ctl,port,nullptr,hdmi?params:nullptr); + assert(!errors&&!delays&&get==1&&mmio[pll]==0xcc000000); + assert(mmio[0x164280]==((0x01e07400&~(off|(3U<<(2*ddi))))|(ddi<<(2*ddi)))); + const auto nominal=hdmi?(464|(2049<<10)):ref==0?0x1000151U:0x1c001a5U; + const auto expected=ref==2?(nominal&~0x1fffc00U)|(((((nominal>>10)&32767)+1)/2)<<10):nominal; + if(fixed)assert(mmio[cfg0]==expected);else if(ref==2){assert(mmio[cfg0]!=expected);++counterexamples;} + 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); + 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"); +} +'''.replace('GUARD',guard) +with tempfile.TemporaryDirectory(prefix='display13-combo-clock-') as d: + p=Path(d);(p/'test.cpp').write_text(code);args=[] + if len(sys.argv)>1: + b=bytearray(Path(sys.argv[1]).read_bytes());assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + assert list(struct.unpack_from('<6Q',b,0x9a0e8))==array('before') + assert b[0x6d95d:0x6d973]==bytes(array('fractionWrite')) + struct.pack_into('<6Q',b,0x9a0e8,*array('after'));(p/'donor').write_bytes(b);args=[str(p/'donor')] + 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')]+args,check=True,env=dict(os.environ,UBSAN_OPTIONS='halt_on_error=1')) diff --git a/tools/test.py b/tools/test.py index ff2ba6b9..de682498 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,9 +7,9 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['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 += ['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_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 ['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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From ad8994b8e80500b73fc709f0b77c528f974eeed1 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:51:45 +0900 Subject: [PATCH 30/58] refactor(display): separate SST programming from platform power --- WhateverXe/kern_xe.cpp | 51 +++++++---------------- WhateverXe/kern_xe.hpp | 1 + WhateverXe/kern_xelp_sst_program.hpp | 44 ++++++++++++++++++++ tests/tgl_training_pattern_fixture.cpp | 1 + tests/xelp_sst_program.cpp | 57 ++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 WhateverXe/kern_xelp_sst_program.hpp create mode 100644 tests/xelp_sst_program.cpp diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 13e4b09c..0b8528f9 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -16640,12 +16640,8 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto base = *reinterpret_cast(access + 0x50); const auto size = *reinterpret_cast(access + 0x60); if (base && size >= mapping.ddiBuffer + 4 && size >= 0x60544 + 0x1000 * transcoder) { - auto clock = reinterpret_cast(base + 0x46140 + 4 * transcoder); - auto func = reinterpret_cast(base + 0x60400 + 0x1000 * transcoder); auto buffer = reinterpret_cast(base + mapping.ddiBuffer); const auto lanes = *reinterpret_cast(pb + 0x124); - const uint32_t routeClock = (config[0] + 1) << 28; - const uint32_t routePort = (config[0] + 1) << 27; const auto mainPower = *reinterpret_cast(base + 0x45404); const auto ioPower = *reinterpret_cast(base + 0x45454); // The selected port and pipe determine the required TGL power wells. @@ -16654,29 +16650,13 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { const uint32_t ioMask = 3U << (2 * mapping.ddi); const bool powered = mainPower != 0xFFFFFFFFU && ioPower != 0xFFFFFFFFU && (mainPower & mainMask) == mainMask && (ioPower & ioMask) == ioMask; - // Do not sample the route/output bank when its power contract failed. - const auto clockBefore = powered ? *clock : 0U; - const auto funcBefore = powered ? *func : 0U; - const auto bufferBefore = powered ? *buffer : 0U; - uint32_t reason = 0; - if (!powered) reason = 5; - else if (lanes != 1 && lanes != 2 && lanes != 4) reason = 1; - else if ((funcBefore | bufferBefore) & 0x80000000U) reason = 2; - else if ((clockBefore & 0xF0000000U) && (clockBefore & 0xF0000000U) != routeClock) reason = 3; - else if ((funcBefore & 0x78000000U) && (funcBefore & 0x78000000U) != routePort) reason = 4; - const uint32_t clockWanted = (clockBefore & 0x0FFFFFFFU) | routeClock; - const uint32_t funcWanted = (funcBefore & ~0x7F00000EU) | routePort | 0x02000000U | - ((lanes - 1) << 1 & 0xEU); - uint32_t clockAfter = clockBefore, funcAfter = funcBefore; - if (!reason) { - *clock = clockWanted; - clockAfter = *clock; - if (clockAfter == clockWanted) { - *func = funcWanted; - funcAfter = *func; - if (funcAfter != funcWanted) reason = 7; - } else reason = 6; - } + const auto read=[&](uint32_t reg){return *reinterpret_cast(base+reg);}; + const auto write=[&](uint32_t reg,uint32_t value){*reinterpret_cast(base+reg)=value;}; + const auto programmed=XELP::SSTProgram::prepare(read,write,mapping.ddi,transcoder,lanes,powered); + const auto reason=programmed.reason; + const auto clockBefore=programmed.clockBefore,clockWanted=programmed.clockWanted,clockAfter=programmed.clockAfter; + const auto funcBefore=programmed.funcBefore,funcWanted=programmed.funcWanted,funcAfter=programmed.funcAfter; + const auto bufferBefore=programmed.bufferBefore; static uint32_t sequence {}; uint32_t record[] {__sync_add_and_fetch(&sequence, 1), reason, transcoder, lanes, clockBefore, clockWanted, clockAfter, funcBefore, funcWanted, funcAfter, bufferBefore, powered ? *buffer : 0U, @@ -16718,7 +16698,6 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto base = *reinterpret_cast(access + 0x50); const uint32_t reg = 0x60540 + 0x1000*transcoder; if (base && *reinterpret_cast(access + 0x60) >= reg + 4) { - auto tp = reinterpret_cast(base + reg); const auto mainPower = *reinterpret_cast(base + 0x45404); const auto ioPower = *reinterpret_cast(base + 0x45454); const uint32_t mainMask = mapping.mainPowerMask(transcoder); @@ -16728,18 +16707,16 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { tglObserveActivation(callbackXE->tglObservationController, port, path, 1, true, result); return result; } - const auto before = *tp; - auto wanted = (before & ~0x40000U) | (uint32_t(p[0x11A]) << 18); - // Match the negotiated sink setting, not merely its capability. - // External DP never receives this eDP-only DPCD write. - const bool alternateSR = config[2] == 0 && callbackXE->tglSinkFramingReady && p[0x118] <= 1; - if (alternateSR) wanted = (wanted & ~0x40U) | (uint32_t(p[0x118]) << 6); - const bool activeSST = (before & 0x88000000U) == 0x80000000U; - if (activeSST && before != wanted) *tp = wanted; + const auto read=[&](uint32_t off){return *reinterpret_cast(base+off);}; + const auto write=[&](uint32_t off,uint32_t value){*reinterpret_cast(base+off)=value;}; + const auto framing=XELP::SSTProgram::framing(read,write,transcoder,p[0x11A], + config[2]==0,callbackXE->tglSinkFramingReady,p[0x118],true); + const auto before=framing.before,wanted=framing.wanted; + const auto activeSST=framing.active,alternateSR=framing.alternate; uint64_t record[13] {}, uptime; clock_get_uptime(&uptime); absolutetime_to_nanoseconds(uptime, record); record[1] = pattern; record[2] = p[0x11A]; record[3] = before; - record[4] = wanted; record[5] = *tp; record[6] = activeSST; + record[4] = wanted; record[5] = framing.after; record[6] = activeSST; record[7] = *reinterpret_cast(p + 0x124); record[8] = reinterpret_cast(path); record[9] = transcoder; record[10] = mapping.ddi; record[11] = alternateSR; record[12] = p[0x118]; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 5e9b318e..77827967 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -15,6 +15,7 @@ #include "kern_tgl_combo_dp.hpp" #include "kern_display13_hdmi_wiring.hpp" #include "kern_xelp_platform.hpp" +#include "kern_xelp_sst_program.hpp" #include "kern_vbt.hpp" #include "kern_alderlake_s.hpp" #include "kern_xelp_panel_pwm.hpp" diff --git a/WhateverXe/kern_xelp_sst_program.hpp b/WhateverXe/kern_xelp_sst_program.hpp new file mode 100644 index 00000000..8e71c5f2 --- /dev/null +++ b/WhateverXe/kern_xelp_sst_program.hpp @@ -0,0 +1,44 @@ +// Display12/13 SST transcoder programming. Platform adapters must retain +// the route owner and prove their own generation's power contract first. +// This layer knows neither power-well numbers nor firmware/connector policy. +#ifndef KERN_XELP_SST_PROGRAM_HPP +#define KERN_XELP_SST_PROGRAM_HPP +#include +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) { + 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; + 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; + else if((r.clockBefore&0xF0000000U) && (r.clockBefore&0xF0000000U)!=routeClock)r.reason=3; + else if((r.funcBefore&0x78000000U) && (r.funcBefore&0x78000000U)!=routePort)r.reason=4; + r.clockWanted=(r.clockBefore&0x0FFFFFFFU)|routeClock; + r.funcWanted=(r.funcBefore&~0x7F00000EU)|routePort|0x02000000U|((lanes-1)<<1&0xEU); + if(!r.reason) { + write(clock,r.clockWanted);r.clockAfter=read(clock); + if(r.clockAfter!=r.clockWanted)r.reason=6; + else {write(func,r.funcWanted);r.funcAfter=read(func);if(r.funcAfter!=r.funcWanted)r.reason=7;} + } + return r; +} +struct Framing {uint32_t before {},wanted {},after {};bool active {},alternate {},ready {};}; +template Framing framing(Read read,Write write,uint32_t pipe,uint32_t enhanced, + bool panel,bool sinkReady,uint32_t alternate,bool powered) { + Framing r;if(pipe>=3 || enhanced>1 || !powered)return r; + const auto reg=0x60540+0x1000*pipe; + r.before=read(reg);r.wanted=(r.before&~0x40000U)|(enhanced<<18); + r.alternate=panel && sinkReady && alternate<=1; + if(r.alternate)r.wanted=(r.wanted&~0x40U)|(alternate<<6); + r.active=(r.before&0x88000000U)==0x80000000U; + if(r.active && r.before!=r.wanted)write(reg,r.wanted); + r.after=read(reg);r.ready=true;return r; +} +}} +#endif diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index 7dd6f9ed..220ad698 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -1,4 +1,5 @@ #include "kern_alderlake_s.hpp" +#include "kern_xelp_sst_program.hpp" #include #include #include diff --git a/tests/xelp_sst_program.cpp b/tests/xelp_sst_program.cpp new file mode 100644 index 00000000..e98df247 --- /dev/null +++ b/tests/xelp_sst_program.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include "kern_xelp_sst_program.hpp" +struct IO { + std::map regs; + std::vector reads,writes; + uint32_t drop {}; + uint32_t read(uint32_t reg){reads.push_back(reg);return regs.at(reg);} + void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg!=drop)regs.at(reg)=value;} +}; +int main(){ + unsigned count=0; + for(unsigned ddi=0;ddi<9;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned lanes:{1U,2U,4U}) { + const auto clock=0x46140+4*pipe,func=0x60400+0x1000*pipe,buffer=0x64000+0x100*ddi; + for(unsigned fault=0;fault<9;++fault){ + IO io;io.regs={{clock,0x12345},{func,0xA05670},{buffer,0x40}}; + const auto read=[&](uint32_t r){return io.read(r);};const auto write=[&](uint32_t r,uint32_t v){io.write(r,v);}; + if(fault==1)io.regs[func]|=0x80000000; + if(fault==2)io.regs[buffer]|=0x80000000; + if(fault==3)io.regs[clock]|=((ddi+1)%9+1)<<28; + if(fault==4)io.regs[func]|=((ddi+1)%9+1)<<27; + if(fault==5)io.drop=clock; + if(fault==6)io.drop=func; + const auto r=XELP::SSTProgram::prepare(read,write,ddi,pipe,fault==7?3:lanes,fault!=8); + const unsigned reason[]={0,2,2,3,4,6,7,1,5};assert(r.reason==reason[fault]); + if(!fault){assert(io.regs[clock]==(((ddi+1)<<28)|0x12345)); + assert(io.regs[func]==(((ddi+1)<<27)|0x02000000|0xA05670|((lanes-1)<<1))); + assert(io.writes.size()==2&&io.writes[0]==clock&&io.writes[1]==func);} + else if(fault==5)assert(io.writes.size()==1&&io.writes[0]==clock); + else if(fault==6)assert(io.writes.size()==2); + else assert(io.writes.empty()); + if(fault==8)assert(io.reads.empty()); + assert(io.regs[buffer]==(fault==2?0x80000040:0x40));++count; + } + } + for(unsigned pipe=0;pipe<3;++pipe)for(unsigned mode:{0U,0x80000000U,0x88000000U}) + for(unsigned enhanced=0;enhanced<2;++enhanced)for(unsigned panel=0;panel<2;++panel) + for(unsigned sink=0;sink<2;++sink)for(unsigned alt=0;alt<3;++alt)for(unsigned powered=0;powered<2;++powered){ + const auto reg=0x60540+0x1000*pipe;IO io;io.regs[reg]=mode|0x40040; + const auto r=XELP::SSTProgram::framing([&](uint32_t x){return io.read(x);},[&](uint32_t x,uint32_t v){io.write(x,v);},pipe,enhanced,panel,sink,alt,powered); + assert(r.ready==bool(powered)); + if(!powered)assert(io.reads.empty()&&io.writes.empty()); + else if(mode!=0x80000000U)assert(io.writes.empty()&&io.regs[reg]==(mode|0x40040)); + else {assert((io.regs[reg]&0x40000)==(enhanced<<18)); + assert((io.regs[reg]&0x40)==((panel&&sink&&alt<=1)?alt<<6:0x40));} + ++count; + } + IO io;const auto read=[&](uint32_t r){return io.read(r);};const auto write=[&](uint32_t r,uint32_t v){io.write(r,v);}; + assert(XELP::SSTProgram::prepare(read,write,9,0,4,true).reason==8); + assert(XELP::SSTProgram::prepare(read,write,0,3,4,true).reason==8); + assert(!XELP::SSTProgram::framing(read,write,3,1,false,false,0,true).ready); + assert(!XELP::SSTProgram::framing(read,write,0,2,false,false,0,true).ready); + assert(io.reads.empty()&&io.writes.empty()); + printf("PASS common SST programming: %u route/framing/fault cases; no unpowered or foreign-route writes\n",count); +} From 84f45fb277f6b7a5ebc288132bb43f460ef7a7fc Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:05:29 +0900 Subject: [PATCH 31/58] feat(display13): bind Combo signal and SST programming to power leases --- WhateverXe/kern_display13_power.hpp | 23 +++ WhateverXe/kern_display13_power_bridge.hpp | 18 ++ WhateverXe/kern_tgl_combo_signal.hpp | 6 + WhateverXe/kern_xe.cpp | 126 +++++++++++++- WhateverXe/kern_xe.hpp | 7 + tests/display13_sst.py | 23 +++ tests/display13_sst_fixture.cpp | 182 +++++++++++++++++++++ tools/test.py | 4 +- 8 files changed, 384 insertions(+), 5 deletions(-) create mode 100644 tests/display13_sst.py create mode 100644 tests/display13_sst_fixture.cpp diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index 0e027f7c..42a6f477 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -68,6 +68,29 @@ template bool valid(IO &io, uint32_t reg, uint32_t &value) { return value != UINT32_MAX; } +// Read-only proof for a caller already holding leases. The same owner lock +// must cover this snapshot and the subsequent register operation. Hardware +// left on by firmware is not a logical reference; no acquisition or polling +// is performed here. TC ownership/health is a separate transport contract. +template bool held(IO &io,const State &s,uint32_t domains) { + if(!s.ready || s.faulted || s.suspended || !domains || (domains&~All))return false; + domains=dependencies(domains); + if(domains&~s.allowed)return false; + uint32_t driverMask=0,ddiMask=0,auxMask=0,fuseMask=1U<<27; + for(unsigned d=0;d bool initialize(IO &io, State &s,uint32_t allowed=LegacyDomains) { diff --git a/WhateverXe/kern_display13_power_bridge.hpp b/WhateverXe/kern_display13_power_bridge.hpp index c13d1c46..b0cfcd66 100644 --- a/WhateverXe/kern_display13_power_bridge.hpp +++ b/WhateverXe/kern_display13_power_bridge.hpp @@ -60,6 +60,24 @@ struct NativeBridge { uint32_t nativeDDIBRefs() const { return nativeDDIRefs(1); } uint32_t nativeAUXBRefs() const { return nativeAUXRefs(1); } + // Require the native resource leases, not merely another consumer's + // physical PW1 hold. Caller serializes this proof through its MMIO use. + bool retainsDDI(unsigned port) const { + return begun && acceptsPort(port) && ddiRefs[port] && ddiLease[port].owner==&state && + ddiLease[port].domains==dependencies(ddiDomain(port)); + } + 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; + 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)); + } + template Result begin(IO &io, unsigned port=1, uint32_t ports=2) { Result r; if (begun || state.ready) return r; diff --git a/WhateverXe/kern_tgl_combo_signal.hpp b/WhateverXe/kern_tgl_combo_signal.hpp index 9f08aa43..4943f08a 100644 --- a/WhateverXe/kern_tgl_combo_signal.hpp +++ b/WhateverXe/kern_tgl_combo_signal.hpp @@ -42,6 +42,12 @@ struct TGLComboSignal { } // The platform adapter chooses coordinates; electrical policy contains no // physical DDI, donor field or board-specific connector condition. + bool selectADLP(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis,bool panel=false,bool lowSwing=false) { + if(ddi>1 || (count!=1 && count!=2 && count!=4))return false; + XELP::ComboPolicy policy; + if(!policy.select(XELP::ComboPolicy::Platform::ADLP,rate,panel,lowSwing,false,voltage,emphasis))return false; + base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=panel && lowSwing;assign(policy);return true; + } bool selectADLS(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis,bool panel=false,bool lowSwing=false) { if(count!=1 && count!=2 && count!=4)return false; AlderLakeS::Port route; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 0b8528f9..13c3965f 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4384,7 +4384,7 @@ static bool display13FBAssociations(const void *controller,size_t controllerByte // framebuffer mutex and display-owner lock keep its graph stable; retained FB // identities and published backing are checked independently by the caller. static TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path, - const void *platform,const Display13OutputWiring &wiring) { + const void *platform,const Display13OutputWiring &wiring,bool attached=true) { auto controller=static_cast(display13FBPower.controller); if(!controller || !fb || !path || controller->getMetaClass()->getClassSize()<0x1C20 || fb->getMetaClass()->getClassSize()<0x4B90 || path->getMetaClass()->getClassSize()<0x3730)return {}; @@ -4395,7 +4395,7 @@ static TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path {path,path->getMetaClass()->getClassSize()}, {plane,plane ? plane->getMetaClass()->getClassSize() : 0}, {port,port ? port->getMetaClass()->getClassSize() : 0}, display13FBExpectedOwner(controller) }; - return TGLFBBinding::inspect(objects,platform,wiring,true); + return TGLFBBinding::inspect(objects,platform,wiring,attached); } @@ -4532,6 +4532,13 @@ struct Display13FBPowerIO { } 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 { + IOMemoryMap *map; + uint32_t read(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} + void write(uint32_t reg,uint32_t value) {*reinterpret_cast(map->getVirtualAddress()+reg)=value;} +}; struct Display13FBDBUFIO { IOMemoryMap *map; bool readable(uint32_t reg) const {return map && map->getLength()==0x1000000 && map->getVirtualAddress() && !(reg&3) && reg<=0xFFFFFC;} @@ -6063,10 +6070,123 @@ 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); + const bool complete=ready && processDisplay13FramebufferColor(patcher,index,address,size) && + processDisplay13FramebufferSST(patcher,index,address,size); adlnComboPLLFractionCaller=complete ? address+0x6D973 : 0; return complete; } + +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. + const char *pattern="__ZN21AppleIntelDisplayPath22setLinkTrainingPatternE19LinkTrainingPattern"; + const char *signal="__ZN17AppleIntelPortHAL29setVoltageSwingAndPreEmphasisEjjP15PortDriveConfig"; + const uint8_t patternEntry[]={0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x05,0x73,0x05,0x00,0x48,0xff,0x05,0x3e,0x6b}; + const uint8_t signalEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec}; + const uint8_t pcsABI[]={0x8b,0xb3,0x20,0x06,0x00,0x00},lanesABI[]={0x8b,0xb3,0xc8,0x05,0x00,0x00}; + const uint8_t successABI[]={0x31,0xc0,0x48,0x83,0xc4,0x28}; + if(!adlnDetected || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x955C8)return false; + const auto p=patcher.solveSymbol(index,pattern,address,size);patcher.clearError(); + const auto s=patcher.solveSymbol(index,signal,address,size);patcher.clearError(); + if(p!=address+0x955B8 || s!=address+0x60AE0 || + memcmp(reinterpret_cast(p),patternEntry,sizeof(patternEntry)) || + memcmp(reinterpret_cast(s),signalEntry,sizeof(signalEntry)) || + 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; + KernelPatcher::RouteRequest routes[] { + {pattern,wrapDisplay13TrainingPattern,orgDisplay13TrainingPattern}, + {signal,wrapDisplay13SignalLevels,orgDisplay13SignalLevels} + }; + display13FBSSTHooksReady=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError(); + return display13FBSSTHooksReady; +} + +// Called with the display owner lock held. The native caller owns the port; +// require its retained platform record and BAR association before sampling it. +uint8_t *XE::display13SSTPort(void *port) { + auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); + if(!s->adlnDetected || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBSSTHooksReady || + !object || object->getMetaClass()->getClassSize()<0x624 || !c.controller || + c.controller!=s->adlnTopologyController || !adlnWriterMappingBound() || + c.clockFaulted || c.memoryFaulted || c.clockStopped || !s->adlnOutputWiring.described())return nullptr; + 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,0x578)!=c.access)return nullptr; + return p; +} + +bool XE::display13HDMIPort(void *port) { + auto s=callbackXE;auto object=static_cast(port); + if(!object || object->getMetaClass()->getClassSize()<0x580)return false; + auto config=Display13FBMemory::field(port,0x548); + return Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) && s->adlnOutputWiring.matchesHDMI(config); +} + +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. + if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13SignalLevels,s->orgDisplay13SignalLevels)(port,swing,emphasis,overrideConfig); + if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);auto p=display13SSTPort(port);uint32_t result=kIOReturnNotReady; + if(p && !overrideConfig) { + const auto ddi=Display13FBMemory::field(p,0x120);TGLComboSignal selected; + Display13FBPowerIO io{c.map}; + if(selected.selectADLP(ddi,Display13FBMemory::field(p,0x124),Display13FBMemory::field(p,0x128),swing,emphasis) && + Display13FBMemory::field(p,0x620)==selected.base+0x804 && + Display13FBMemory::field(p,0x5C8)==selected.base+0x28 && + Display13FBMemory::field(c.access,0x60)>=selected.base+0xBA0 && c.bridge.ddiHeld(io,ddi)) { + Display13FBSSTIO registers{c.map}; + if(selected.program([&](uint32_t r){return registers.read(r);}, + [&](uint32_t r,uint32_t v){registers.write(r,v);} ))result=0; + } + } + IOLockUnlock(c.lock);return result; +} + +uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->adlnOutputWiring.dp)return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + auto object=static_cast(path); + if(!c.lock || !object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; + if(display13HDMIPort(Display13FBMemory::field(path,0x3648))) + return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + // 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{}; + Display13FBPowerIO io{c.map}; + if(binding.status==TGLFBBinding::Status::Complete && c.bridge.outputHeld(io,binding.ddi,binding.pipe)) { + Display13FBSSTIO registers{c.map}; + const auto read=[&](uint32_t r){return registers.read(r);}; + const auto write=[&](uint32_t r,uint32_t v){registers.write(r,v);}; + if(before) { + ok=true; + if(pattern==1) { + const auto r=XELP::SSTProgram::prepare(read,write,binding.ddi,binding.pipe,Display13FBMemory::field(p,0x124),true); + // 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 && + (!r.reason || (r.reason==2 && r.clockBefore==r.clockWanted && r.funcBefore==r.funcWanted)); + } + } else { + const auto r=XELP::SSTProgram::framing(read,write,binding.pipe,p[0x11A],false,false,0,true); + ok=r.ready && r.after!=UINT32_MAX && !(r.after&0x08000000U) && (!r.active || r.after==r.wanted); + } + } + IOLockUnlock(c.lock);return ok; + }; + if(!program(true))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + if(result || pattern>5)return result; + return program(false)?0:kIOReturnNotReady; +} uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request) { 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); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 77827967..a6796e05 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -120,6 +120,13 @@ class XE { bool display13FBWatermarkLayoutReady {false}; bool display13FBWatermarkHooksReady {false}; bool display13FBOutputHooksReady {false}; + bool display13FBSSTHooksReady {}; + mach_vm_address_t orgDisplay13TrainingPattern {},orgDisplay13SignalLevels {}; + bool processDisplay13FramebufferSST(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static uint8_t *display13SSTPort(void *port); + static bool display13HDMIPort(void *port); + 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 xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; diff --git a/tests/display13_sst.py b/tests/display13_sst.py new file mode 100644 index 00000000..12a28b51 --- /dev/null +++ b/tests/display13_sst.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Production Display13 SST adapters with real bridge/binding and mock MMIO.""" +from pathlib import Path +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(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in [ + 'uint8_t *XE::display13SSTPort(', 'bool XE::display13HDMIPort(', + 'uint32_t XE::wrapDisplay13SignalLevels(', 'uint32_t XE::wrapDisplay13TrainingPattern(']) +binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S).group() +fixture=(r/'tests/tgl_fb_binding.cpp').read_text().split('int main()')[0] +fixture=fixture.replace('port[0x580]','port[0x700]') +code=(r/'tests/display13_sst_fixture.cpp').read_text().replace('// NATIVE_OBJECTS',fixture).replace('// BINDING',binding).replace('// FUNCTIONS',functions) +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + install=s[s.index('bool XE::processDisplay13FramebufferSST('):s.index('uint8_t *XE::display13SSTPort(')] + for name,offset in [('patternEntry',0x955b8),('signalEntry',0x60ae0),('pcsABI',0x60eb9),('lanesABI',0x61468),('successABI',0x61478)]: + a=re.search(name+r'\[\]=\{([^}]+)\}',install).group(1);v=bytes(int(x,0) for x in a.split(',')) + assert b[offset:offset+len(v)]==v + print('PASS exact donor Display13 SST hook and Combo register-field preflight') +with tempfile.TemporaryDirectory(prefix='display13-sst-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp new file mode 100644 index 00000000..cbcc8ea1 --- /dev/null +++ b/tests/display13_sst_fixture.cpp @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include "kern_display13_power_bridge.hpp" +#include "kern_tgl_combo_signal.hpp" +#include "kern_xelp_sst_program.hpp" +// NATIVE_OBJECTS +struct OSObject {unsigned size;const OSObject *getMetaClass() const{return this;}unsigned getClassSize() const{return size;}}; +struct IOService:OSObject {}; +constexpr unsigned kIOReturnNotReady=0xE00002D8; +#define FunctionCast(fn,ptr) reinterpret_cast(ptr) +bool locked=false,bound=true,loseAfterNative=false;unsigned calls=0,nativeResult=0; +void IOLockLock(void*){assert(!locked);locked=true;}void IOLockUnlock(void*){assert(locked);locked=false;} +struct Map { + std::map regs; + std::vector reads,writes; + uint32_t base=0,drop=0; + Map():regs{{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8E380000}}{} + 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==base+0x694)for(unsigned n=0;n<4;++n)regs.at(base+0x894+0x100*n)=v; + regs.at(reg)=v; + } +}; +struct Context { + Display13Power::NativeBridge bridge;void *lock=reinterpret_cast(1),*controller=nullptr,*access=nullptr; + 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);} + void write(uint32_t r,uint32_t v){map->write(r,v);}void delayUs(unsigned){} +}; +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);} +}; +bool adlnWriterMappingBound(){return bound;} +uintptr_t display13FBExpectedOwner(void*){return 0;} +struct XE { + bool adlnDetected=true,display13FBSSTHooksReady=true;XELP::Profile xeProfile; + Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; + uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; + static uint8_t *display13SSTPort(void*);static bool display13HDMIPort(void*); + static uint32_t wrapDisplay13SignalLevels(void*,uint32_t,uint32_t,void*); + static uint32_t wrapDisplay13TrainingPattern(void*,uint32_t); +}state,*callbackXE=&state; +// BINDING +// FUNCTIONS +unsigned nativeSignal(void*,unsigned,unsigned,void*){assert(!locked);++calls;return nativeResult;} +uint32_t (*realPattern)(void *,uint32_t)=nullptr; +void nativeWrite(void *access,uint64_t reg,uint32_t value){ + assert(!locked&&access==display13FBPower.access);display13FBPower.map->write(static_cast(reg),value); +} +unsigned nativePattern(void *path,unsigned pattern){ + assert(!locked);++calls; + const auto pipe=Display13FBMemory::field(path,0x3640); + if(!nativeResult) { + if(realPattern)assert(realPattern(path,pattern)==0); + else {const uint32_t patterns[]={0x200,0,0x100,0x400,0x500,0x300}; + assert(pattern<6);display13FBPower.map->regs[0x60540+0x1000*pipe]=0x80000000|patterns[pattern];} + } + if(loseAfterNative)display13FBPower.map->regs[0x45454]=0; + return nativeResult; +} +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; + auto &c=display13FBPower;c.controller=f.ctl;c.access=f.access;c.map=↦ + f.wiring.hdmi=1U<<(1-ddi);f.wiring.dp=1U<(nativePattern);state.orgDisplay13SignalLevels=reinterpret_cast(nativeSignal); + 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; + map.base=ddi?0x6C000:0x162000; + put(f.port,0x620,map.base+0x804);put(f.port,0x5C8,map.base+0x28);put(f.access,0x60,0x200000U); + for(auto reg:{0x804U,0x14U,0x28U,0x894U,0x604U,0x694U})map.regs[map.base+reg]=0; + 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); + 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.outputHeld(io,ddi,pipe));map.reads.clear();map.writes.clear(); +} +int main(int argc,char **argv){ + uint8_t *image=nullptr; + if(argc>1){ + std::ifstream input(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(input)),{}); + assert(!bytes.empty()&&bytes.size()<0x400000); + image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0));assert(image!=MAP_FAILED); + memcpy(image,bytes.data(),bytes.size());realPattern=reinterpret_cast(image+0x955B8); + } + unsigned count=0,replays=0; + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned lanes:{1U,2U,4U}) + for(unsigned rate:{6U,10U,20U,30U})for(unsigned swing=0;swing<4;++swing)for(unsigned emphasis=0;emphasis<4-swing;++emphasis){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,lanes,rate); + assert(!XE::wrapDisplay13SignalLevels(f.port,swing,emphasis,nullptr)&&!calls); + XELP::ComboPolicy policy;assert(policy.select(XELP::ComboPolicy::Platform::ADLP,rate,false,false,false,swing,emphasis)); + assert(((map.regs[map.base+0x89C]>>24)&0x7F)==policy.coefficients.scalar); + assert(!XE::wrapDisplay13TrainingPattern(f.path,1)&&calls==1); + assert(map.regs[0x46140+4*pipe]==((ddi+1)<<28)); + assert(map.regs[0x60400+0x1000*pipe]==(((ddi+1)<<27)|0x02000000|((lanes-1)<<1))); + assert(map.regs[0x60540+0x1000*pipe]==0x80040000); + assert(!XE::wrapDisplay13TrainingPattern(f.path,2)&&calls==2); + assert(!XE::wrapDisplay13TrainingPattern(f.path,0)&&calls==3); + // Actual native instruction replay: all six pattern values lose enhanced + // framing. The adapter restores only the negotiated bit, preserving the + // pattern and SST mode for both enhanced-framing settings. + if(realPattern){ + const uint32_t patterns[]={0x200,0,0x100,0x400,0x500,0x300}; + for(unsigned enhanced=0;enhanced<2;++enhanced)for(unsigned pattern=0;pattern<6;++pattern){ + f.port[0x11A]=enhanced; + assert(realPattern(f.path,pattern)==0&&map.regs[0x60540+0x1000*pipe]==(0x80000000|patterns[pattern])); + assert(!XE::wrapDisplay13TrainingPattern(f.path,pattern)); + assert(map.regs[0x60540+0x1000*pipe]==(0x80000000|patterns[pattern]|(enhanced<<18)));++replays; + } + } + const auto completedCalls=calls; + Display13FBPowerIO io{&map};using Display13Power::Status; + assert(display13FBPower.bridge.putDDI(io,ddi).status==Status::Complete); + assert(display13FBPower.bridge.putPG(io,pipe?pipe+2:1).status==Status::Complete); + for(auto refs:display13FBPower.bridge.state.refs)assert(!refs); + // Deliberately leave BIOS-like enabled/request bits. No native ownership. + map.regs[0x45404]=0xFC0F;map.regs[0x45454]=15;map.writes.clear();map.reads.clear(); + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==kIOReturnNotReady&&calls==completedCalls&&map.writes.empty()&&map.reads.empty()); + ++count; + } + 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; + if(fault==2)c.bridge.ddiRefs[1]=0;if(fault==3)c.bridge.ddiLease[1].owner=nullptr; + if(fault==4)map.regs[0x45454]&=~8U;if(fault==5)map.regs[0x45454]&=~4U; + if(fault==6)map.regs[0x45404]&=~0x2000U;if(fault==7)map.regs[0x45404]&=~0x1000U; + if(fault==8)map.regs[0x42000]&=~0x100000U;if(fault==9)map.regs[0x45454]=~0U; + if(fault==10)c.bridge.pgRefs[2]=0;if(fault==11)bound=false; + if(fault==12)c.clockStopped=true;if(fault==13)put(f.port,0x548,reinterpret_cast(1)); + if(fault==14)f.port[0x117]=1;if(fault==15)put(f.port,0x578,static_cast(f.ctl)); + if(fault==16)put(f.path,0x3640,0U);if(fault==17)put(f.plane,0x7C,0U); + if(fault==18)state.adlnDetected=false;if(fault==19)state.display13FBSSTHooksReady=false; + if(fault==20)map.drop=0x46144;if(fault==21)map.drop=0x61400; + if(fault==22)map.regs[0x46144]=0x10000000; + if(fault==23)map.regs[0x61400]=0x88000000; + if(fault==24)c.bridge.state.refs[0]=0; + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==kIOReturnNotReady&&!calls); + if(fault!=20&&fault!=21)assert(map.writes.empty()); + } + for(unsigned fault=0;fault<14;++fault){ + Fixture f(2,0,0);Map map;setup(f,map,0,0,4,30);auto &c=display13FBPower; + if(fault==0)c.bridge.ddiRefs[0]=0;if(fault==1)c.bridge.state.suspended=true; + if(fault==2)c.bridge.state.refs[0]=0;if(fault==3)map.regs[0x45454]&=~2U; + if(fault==4)map.regs[0x42000]&=~0x4000000U; + if(fault==5)put(f.port,0x620,map.base+0x804+4);if(fault==6)put(f.port,0x5C8,map.base+0x28+4); + if(fault==7)put(f.access,0x60,map.base+0xB9C); + if(fault==8)map.regs[map.base+0xA9C]=~0U; + if(fault==9)put(f.port,0x124,3U);if(fault==10)put(f.port,0x128,12U); + if(fault==11)map.drop=map.base+0x694; + const auto result=XE::wrapDisplay13SignalLevels(f.port,fault==12?4:0,0,fault==13?f.path:nullptr); + assert(result==kIOReturnNotReady&&!calls);if(fault!=11)assert(map.writes.empty()); + } + {Fixture f(2,0,0);Map map;setup(f,map,0,0,2,10);nativeResult=42; + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==42&&calls==1); + map.writes.clear();loseAfterNative=true;nativeResult=0; + assert(XE::wrapDisplay13TrainingPattern(f.path,2)==kIOReturnNotReady&&calls==2); + if(realPattern)assert(map.writes.size()==1&&map.writes[0]==0x60540);else assert(map.writes.empty());} + // HDMI peer in a mixed wiring must retain native behavior and result. + {Fixture f(2,0,0);Map map;setup(f,map,0,0,2,10);put(f.port,0x548,static_cast(f.platform+0x38));nativeResult=17; + assert(XE::wrapDisplay13SignalLevels(f.port,0,0,nullptr)==17&&calls==1); + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==17&&calls==2&&map.writes.empty());} + printf("PASS production Display13 signal/SST: %u compositions after peer release, 39 admission/power/MMIO faults, native error/power-loss/HDMI preservation\n",count); + if(image){munmap(image,0x400000);printf("PASS %u exact native pattern/framing calls and unmodified donor counterexamples\n",replays);} +} diff --git a/tools/test.py b/tools/test.py index de682498..a8e180af 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,9 +7,9 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['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 += ['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_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_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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From 21d4c49403cdeb2d212e2f6da35630d8212efd77 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:19:39 +0900 Subject: [PATCH 32/58] feat(display13): retain AUX power across Combo DP link lifetime --- WhateverXe/kern_display13_power_bridge.hpp | 44 ++++++++ WhateverXe/kern_xe.cpp | 56 +++++++++- WhateverXe/kern_xe.hpp | 4 + tests/display13_sst.py | 5 +- tests/display13_sst_fixture.cpp | 124 ++++++++++++++++++++- 5 files changed, 225 insertions(+), 8 deletions(-) diff --git a/WhateverXe/kern_display13_power_bridge.hpp b/WhateverXe/kern_display13_power_bridge.hpp index b0cfcd66..12fbb272 100644 --- a/WhateverXe/kern_display13_power_bridge.hpp +++ b/WhateverXe/kern_display13_power_bridge.hpp @@ -206,6 +206,50 @@ struct NativeBridge { }; +// Linux main_link_aux_power_domain_get/put: an active Combo DP link retains +// AUX independently of the donor's per-transaction AUX counters. Caller holds +// the bridge owner lock and retains the port object while owner is nonzero. +// TC has additional ownership/health contracts and is not admitted here. +struct MainLinkAUX { + Lease lease; + uintptr_t owner {}; + unsigned port {UINT32_MAX}; + template Result begin(IO &io,NativeBridge &b,uintptr_t target,unsigned ddi) { + Result r; + if(!target || ddi>=2 || !b.ddiHeld(io,ddi))return r; + if(owner) { + if(owner==target && port==ddi && lease.owner==&b.state && + lease.domains==dependencies(auxDomain(ddi)) && held(io,b.state,lease.domains))r.status=Status::Complete; + return r; + } + uint32_t buffer; + if(lease.owner || !valid(io,0x64000+0x100*ddi,buffer) || (buffer&0x80000000U))return r; + r=acquire(io,b.state,lease,auxDomain(ddi)); + if(r.status==Status::Complete){owner=target;port=ddi;} + return r; + } + template Result end(IO &io,NativeBridge &b,uintptr_t target,unsigned ddi) { + Result r; + if(!target || ddi>=2)return r; + if(!owner && !lease.owner){r.status=Status::Complete;return r;} + if(owner!=target || port!=ddi || lease.owner!=&b.state || lease.domains!=dependencies(auxDomain(ddi)) || + !b.ddiHeld(io,ddi) || !held(io,b.state,lease.domains))return r; + // A real DDI lease also retains PW1, so this release cannot remove + // display/IRQ power; only AUX may be the final physical consumer. + // A partial enable/disable failure must keep both owner and lease. + for(unsigned us=0;;++us) { + uint32_t buffer; + if(!valid(io,0x64000+0x100*ddi,buffer) || (buffer&0x80000000U))return r; + if(buffer&0x80)break; + if(us==8){r.status=Status::StateTimeout;return r;} + io.delayUs(1); + } + r=release(io,b.state,lease); + if(r.status==Status::Complete){owner=0;port=UINT32_MAX;} + return r; + } +}; + // Native object layout for this donor only. The kernel route additionally // checks exact image/prologues and the controller's physical PCI identity. // Validate CPU associations before any bridge begin/MMIO operation. diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 13c3965f..2898d948 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4260,6 +4260,7 @@ struct Display13FBPipeContext { struct Display13FBPowerContext { Display13FBPipeContext pipe[3]; Display13Power::NativeBridge bridge; + Display13Power::MainLinkAUX mainLink[2]; Display13Power::Lease engine; Display13DBUF::State dbuf; Display13WM::Latency latency; @@ -6085,10 +6086,17 @@ bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach const uint8_t signalEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec}; const uint8_t pcsABI[]={0x8b,0xb3,0x20,0x06,0x00,0x00},lanesABI[]={0x8b,0xb3,0xc8,0x05,0x00,0x00}; const uint8_t successABI[]={0x31,0xc0,0x48,0x83,0xc4,0x28}; + const char *enable="__ZN17AppleIntelPortHAL10enablePortEv",*disable="__ZN17AppleIntelPortHAL11disablePortEv"; + const uint8_t enableEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x49,0x89,0xff}; + const uint8_t disableEntry[]={0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb}; if(!adlnDetected || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x955C8)return false; const auto p=patcher.solveSymbol(index,pattern,address,size);patcher.clearError(); const auto s=patcher.solveSymbol(index,signal,address,size);patcher.clearError(); - if(p!=address+0x955B8 || s!=address+0x60AE0 || + const auto e=patcher.solveSymbol(index,enable,address,size);patcher.clearError(); + const auto d=patcher.solveSymbol(index,disable,address,size);patcher.clearError(); + if(p!=address+0x955B8 || s!=address+0x60AE0 || e!=address+0x5F9F4 || d!=address+0x5FC56 || + memcmp(reinterpret_cast(e),enableEntry,sizeof(enableEntry)) || + memcmp(reinterpret_cast(d),disableEntry,sizeof(disableEntry)) || memcmp(reinterpret_cast(p),patternEntry,sizeof(patternEntry)) || memcmp(reinterpret_cast(s),signalEntry,sizeof(signalEntry)) || memcmp(reinterpret_cast(address+0x60EB9),pcsABI,sizeof(pcsABI)) || @@ -6096,7 +6104,9 @@ bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach memcmp(reinterpret_cast(address+0x61478),successABI,sizeof(successABI)))return false; KernelPatcher::RouteRequest routes[] { {pattern,wrapDisplay13TrainingPattern,orgDisplay13TrainingPattern}, - {signal,wrapDisplay13SignalLevels,orgDisplay13SignalLevels} + {signal,wrapDisplay13SignalLevels,orgDisplay13SignalLevels}, + {enable,wrapDisplay13EnablePort,orgDisplay13EnablePort}, + {disable,wrapDisplay13DisablePort,orgDisplay13DisablePort} }; display13FBSSTHooksReady=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError(); return display13FBSSTHooksReady; @@ -6125,6 +6135,48 @@ bool XE::display13HDMIPort(void *port) { return Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) && s->adlnOutputWiring.matchesHDMI(config); } +bool XE::display13MainLinkAUX(void *port,bool acquire) { + auto &c=display13FBPower;if(!c.lock)return false; + OSObject *retired=nullptr;bool ok=false;IOLockLock(c.lock); + auto p=display13SSTPort(port); + if(p && Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60) && + Display13FBMemory::field(c.power,0x10)==c.controller && + Display13FBMemory::field(c.power,0x78)==c.access) { + const auto ddi=Display13FBMemory::field(p,0x120); + 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) : + link.end(io,c.bridge,reinterpret_cast(port),ddi); + ok=result.status==Display13Power::Status::Complete; + if(!previous && link.owner)static_cast(port)->retain(); + if(previous && !link.owner)retired=reinterpret_cast(previous); + } + } + IOLockUnlock(c.lock); + if(retired)retired->release(); // A destructor must not run under the owner lock. + return ok; +} + +uint32_t XE::wrapDisplay13EnablePort(void *port) { + auto s=callbackXE; + if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13EnablePort,s->orgDisplay13EnablePort)(port); + if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDisplay13EnablePort,s->orgDisplay13EnablePort)(port); + if(result)display13MainLinkAUX(port,false); // Retain AUX if partial enable left the buffer active. + return result ? result : display13MainLinkAUX(port,true) ? 0 : kIOReturnNotReady; +} + +uint32_t XE::wrapDisplay13DisablePort(void *port) { + auto s=callbackXE; + if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); + if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); + const bool released=display13MainLinkAUX(port,false); + return result ? result : released ? 0 : kIOReturnNotReady; +} + 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. diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index a6796e05..9a107b81 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -122,6 +122,10 @@ class XE { bool display13FBOutputHooksReady {false}; bool display13FBSSTHooksReady {}; mach_vm_address_t orgDisplay13TrainingPattern {},orgDisplay13SignalLevels {}; + mach_vm_address_t orgDisplay13EnablePort {},orgDisplay13DisablePort {}; + static bool display13MainLinkAUX(void *port,bool acquire); + static uint32_t wrapDisplay13EnablePort(void *port); + static uint32_t wrapDisplay13DisablePort(void *port); bool processDisplay13FramebufferSST(KernelPatcher &,size_t,mach_vm_address_t,size_t); static uint8_t *display13SSTPort(void *port); static bool display13HDMIPort(void *port); diff --git a/tests/display13_sst.py b/tests/display13_sst.py index 12a28b51..ac34a11f 100644 --- a/tests/display13_sst.py +++ b/tests/display13_sst.py @@ -4,7 +4,8 @@ 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(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in [ - 'uint8_t *XE::display13SSTPort(', 'bool XE::display13HDMIPort(', + '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(']) binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S).group() fixture=(r/'tests/tgl_fb_binding.cpp').read_text().split('int main()')[0] @@ -13,7 +14,7 @@ if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' install=s[s.index('bool XE::processDisplay13FramebufferSST('):s.index('uint8_t *XE::display13SSTPort(')] - for name,offset in [('patternEntry',0x955b8),('signalEntry',0x60ae0),('pcsABI',0x60eb9),('lanesABI',0x61468),('successABI',0x61478)]: + for name,offset in [('patternEntry',0x955b8),('signalEntry',0x60ae0),('enableEntry',0x5f9f4),('disableEntry',0x5fc56),('pcsABI',0x60eb9),('lanesABI',0x61468),('successABI',0x61478)]: a=re.search(name+r'\[\]=\{([^}]+)\}',install).group(1);v=bytes(int(x,0) for x in a.split(',')) assert b[offset:offset+len(v)]==v print('PASS exact donor Display13 SST hook and Combo register-field preflight') diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index cbcc8ea1..481bd5cc 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -8,7 +8,10 @@ #include "kern_tgl_combo_signal.hpp" #include "kern_xelp_sst_program.hpp" // NATIVE_OBJECTS -struct OSObject {unsigned size;const OSObject *getMetaClass() const{return this;}unsigned getClassSize() const{return size;}}; +std::map objectRefs; +extern bool locked; +struct OSObject {unsigned size;const OSObject *getMetaClass() const{return this;}unsigned getClassSize() const{return size;} + void retain(){assert(locked);++objectRefs[this];}void release(){assert(!locked&&objectRefs[this]);if(!--objectRefs[this])objectRefs.erase(this);}}; struct IOService:OSObject {}; constexpr unsigned kIOReturnNotReady=0xE00002D8; #define FunctionCast(fn,ptr) reinterpret_cast(ptr) @@ -17,7 +20,7 @@ void IOLockLock(void*){assert(!locked);locked=true;}void IOLockUnlock(void*){ass struct Map { std::map regs; std::vector reads,writes; - uint32_t base=0,drop=0; + uint32_t base=0,drop=0,delay=0,idleAfter=0,idleReg=0; Map():regs{{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8E380000}}{} 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; @@ -29,11 +32,12 @@ struct Map { }; struct Context { Display13Power::NativeBridge bridge;void *lock=reinterpret_cast(1),*controller=nullptr,*access=nullptr; + Display13Power::MainLinkAUX mainLink[2];alignas(void *) uint8_t powerBytes[0x80]{};void *power=powerBytes; Map *map=nullptr;bool clockFaulted=false,memoryFaulted=false,clockStopped=false; }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);} - void write(uint32_t r,uint32_t v){map->write(r,v);}void delayUs(unsigned){} + 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;} }; struct Display13FBSSTIO { Map *map;uint32_t read(uint32_t r){assert(locked);return map->read(r);} @@ -45,6 +49,9 @@ struct XE { bool adlnDetected=true,display13FBSSTHooksReady=true;XELP::Profile xeProfile; Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; + uintptr_t orgDisplay13EnablePort=0,orgDisplay13DisablePort=0; + static bool display13MainLinkAUX(void*,bool); + static uint32_t wrapDisplay13EnablePort(void*);static uint32_t wrapDisplay13DisablePort(void*); static uint8_t *display13SSTPort(void*);static bool display13HDMIPort(void*); static uint32_t wrapDisplay13SignalLevels(void*,uint32_t,uint32_t,void*); static uint32_t wrapDisplay13TrainingPattern(void*,uint32_t); @@ -52,10 +59,37 @@ struct XE { // BINDING // FUNCTIONS unsigned nativeSignal(void*,unsigned,unsigned,void*){assert(!locked);++calls;return nativeResult;} +unsigned linkCalls=0,linkResult=0;bool changeBuffer=true,bufferIdle=true,loseLinkPower=false; +unsigned (*realEnable)(void*)=nullptr,(*realDisable)(void*)=nullptr;void *currentPath=nullptr;unsigned portReplays=0; +unsigned nativeEnable(void *port){assert(!locked);++linkCalls; + auto &c=display13FBPower;const auto ddi=Display13FBMemory::field(port,0x120); + Display13FBPowerIO io{c.map};assert(Display13Power::held(io,c.bridge.state,Display13Power::auxDomain(ddi))); + unsigned result=linkResult; + if(realEnable&&changeBuffer&&!linkResult){++portReplays;result=realEnable(port);} + else if(changeBuffer)c.map->regs.at(0x64000+0x100*ddi)=0x80000000; + if(loseLinkPower)c.map->regs.at(0x45444)=0; + return result;} +unsigned nativeDisable(void *port){assert(!locked);++linkCalls; + const auto ddi=Display13FBMemory::field(port,0x120); + if(realDisable&&changeBuffer&&!linkResult){++portReplays;return realDisable(port);} + if(changeBuffer)display13FBPower.map->regs.at(0x64000+0x100*ddi)=bufferIdle?0x80:0;return linkResult;} uint32_t (*realPattern)(void *,uint32_t)=nullptr; void nativeWrite(void *access,uint64_t reg,uint32_t value){ assert(!locked&&access==display13FBPower.access);display13FBPower.map->write(static_cast(reg),value); } +unsigned nativeRead(void *access,uint64_t reg){assert(!locked&&access==display13FBPower.access);return display13FBPower.map->read(static_cast(reg));} +unsigned portRead(void *port,uint32_t reg){assert(!locked&®==Display13FBMemory::field(port,0x590));return display13FBPower.map->read(reg);} +void portWrite(void *port,uint32_t reg,uint32_t value){ + assert(!locked&®==Display13FBMemory::field(port,0x590)); + // Hardware model: DDI buffer enable clears idle; stop may acknowledge late. + value=(value&~0x80U)|(!(value&0x80000000U)&&bufferIdle?0x80U:0); + display13FBPower.map->write(reg,value); +} +void *firstPath(void *port){return Display13FBMemory::field(currentPath,0x3648)==port?currentPath:nullptr;} +void nativeDelay(unsigned us){assert(us==5||us==8);} +void nativeLog(const char*,...){} +void jump(uint8_t *to,uintptr_t target){to[0]=0x48;to[1]=0xB8;memcpy(to+2,&target,8);to[10]=0xFF;to[11]=0xE0;} +void redirect(uint8_t *image,unsigned at,unsigned stub,uintptr_t target){assert(image[at]==0xE8);jump(image+stub,target);const int32_t displacement=static_cast(stub-at-5);memcpy(image+at+1,&displacement,4);} unsigned nativePattern(void *path,unsigned pattern){ assert(!locked);++calls; const auto pipe=Display13FBMemory::field(path,0x3640); @@ -69,17 +103,22 @@ 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; 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<(nativePattern);state.orgDisplay13SignalLevels=reinterpret_cast(nativeSignal); + state.orgDisplay13EnablePort=reinterpret_cast(nativeEnable);state.orgDisplay13DisablePort=reinterpret_cast(nativeDisable); 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; 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); for(auto reg:{0x804U,0x14U,0x28U,0x894U,0x604U,0x694U})map.regs[map.base+reg]=0; 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; @@ -100,6 +139,13 @@ int main(int argc,char **argv){ assert(!bytes.empty()&&bytes.size()<0x400000); image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0));assert(image!=MAP_FAILED); memcpy(image,bytes.data(),bytes.size());realPattern=reinterpret_cast(image+0x955B8); + realEnable=reinterpret_cast(image+0x5F9F4);realDisable=reinterpret_cast(image+0x5FC56); + assert(Display13FBMemory::field(image,0xC95A0)==0x1B446&&Display13FBMemory::field(image,0xC95A8)==0x1B456); + put(image,0xC95A0,reinterpret_cast(image+0x1B446));put(image,0xC95A8,reinterpret_cast(image+0x1B456)); + jump(image+0x1FE8E,reinterpret_cast(portRead));jump(image+0x1FEA8,reinterpret_cast(portWrite)); + jump(image+0x5E344,reinterpret_cast(nativeRead));jump(image+0x91BCA,reinterpret_cast(firstPath)); + for(unsigned at:{0x5FA33U,0x5FAC4U,0x5FBEBU,0x5FC4EU})redirect(image,at,0x300000,reinterpret_cast(nativeLog)); + for(unsigned at:{0x5FBCBU,0x5FC99U})redirect(image,at,0x300020,reinterpret_cast(nativeDelay)); } unsigned count=0,replays=0; for(unsigned ddi=0;ddi<2;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned lanes:{1U,2U,4U}) @@ -128,6 +174,17 @@ int main(int argc,char **argv){ } const auto completedCalls=calls; Display13FBPowerIO io{&map};using Display13Power::Status; + auto &bridge=display13FBPower.bridge;const auto auxIndex=ddi?6:8; + assert(!XE::wrapDisplay13EnablePort(f.port)&&linkCalls==1&&objectRefs[f.port]==1); + assert(bridge.state.refs[auxIndex]==1&&!bridge.nativeAUXRefs(ddi)); + assert(!XE::wrapDisplay13EnablePort(f.port)&&linkCalls==2&&objectRefs[f.port]==1&&bridge.state.refs[auxIndex]==1); + // Transactions retain/release their own lease while the link stays active. + for(unsigned transaction=0;transaction<3;++transaction){ + assert(bridge.getAUX(io,ddi).status==Status::Complete&&bridge.state.refs[auxIndex]==2); + assert(bridge.putAUX(io,ddi).status==Status::Complete&&bridge.state.refs[auxIndex]==1); + assert(map.regs[0x45444]&(ddi?8:2)); + } + assert(!XE::wrapDisplay13DisablePort(f.port)&&linkCalls==3&&objectRefs.empty()&&!bridge.state.refs[auxIndex]); assert(display13FBPower.bridge.putDDI(io,ddi).status==Status::Complete); assert(display13FBPower.bridge.putPG(io,pipe?pipe+2:1).status==Status::Complete); for(auto refs:display13FBPower.bridge.state.refs)assert(!refs); @@ -177,6 +234,65 @@ int main(int argc,char **argv){ {Fixture f(2,0,0);Map map;setup(f,map,0,0,2,10);put(f.port,0x548,static_cast(f.platform+0x38));nativeResult=17; assert(XE::wrapDisplay13SignalLevels(f.port,0,0,nullptr)==17&&calls==1); assert(XE::wrapDisplay13TrainingPattern(f.path,1)==17&&calls==2&&map.writes.empty());} + // Partial enable keeps AUX until a real stop; failure before enabling can + // unwind immediately. Late idle succeeds; absent idle retains the owner. + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned fault=0;fault<6;++fault){ + Fixture f(0,0,ddi);Map map;setup(f,map,ddi,0,2,10);auto &c=display13FBPower;const auto buffer=0x64000+0x100*ddi; + if(fault<2){linkResult=42;changeBuffer=fault==0;map.regs[buffer]=0x80; + assert(XE::wrapDisplay13EnablePort(f.port)==42&&linkCalls==1); + assert(bool(c.mainLink[ddi].owner)==(fault==0));} + else { + assert(!XE::wrapDisplay13EnablePort(f.port)); + if(fault==2){bufferIdle=false;map.idleReg=buffer;map.idleAfter=4;} + if(fault==3)bufferIdle=false; + if(fault==4)changeBuffer=false; + if(fault==5)linkResult=42; + const auto result=XE::wrapDisplay13DisablePort(f.port); + assert(result==(fault==3||fault==4?kIOReturnNotReady:fault==5?42:0)); + assert(bool(c.mainLink[ddi].owner)==(fault==3||fault==4)); + if(fault==2)assert(map.delay==4);if(fault==3)assert(map.delay==8); + } + changeBuffer=bufferIdle=true;linkResult=0; + if(c.mainLink[ddi].owner)assert(!XE::wrapDisplay13DisablePort(f.port)); + assert(objectRefs.empty()&&!c.mainLink[ddi].owner); + } + // Alien power owner/bank/record must never acquire a main-link reference. + for(unsigned fault=0;fault<6;++fault){ + Fixture f(0,0,0);Map map;setup(f,map,0,0,4,20);auto &c=display13FBPower; + if(fault==0)put(f.ctl,0xC8,static_cast(f.port)); + if(fault==1)put(c.power,0x10,static_cast(f.port)); + if(fault==2)put(c.power,0x78,static_cast(f.port)); + if(fault==3)put(f.port,0x590,0x64100U); + if(fault==4)map.regs[0x64000]=0x80000000; + if(fault==5)map.regs[0x64000]=~0U; + assert(XE::wrapDisplay13EnablePort(f.port)==kIOReturnNotReady&&!linkCalls&&objectRefs.empty()&&map.writes.empty()); + } + // A native success with a lost AUX request is not a successful enable. + {Fixture f(0,0,0);Map map;setup(f,map,0,0,4,20);loseLinkPower=true; + assert(XE::wrapDisplay13EnablePort(f.port)==kIOReturnNotReady&&linkCalls==1&&objectRefs[f.port]==1); + assert(display13FBPower.mainLink[0].owner==reinterpret_cast(f.port)); + loseLinkPower=false;map.regs[0x45444]=3; + assert(!XE::wrapDisplay13DisablePort(f.port)&&objectRefs.empty());} + // Two live Combo DP links share PW1, but neither owns the other's AUX. + for(unsigned first=0;first<2;++first){ + Fixture f(0,0,0);Map map;setup(f,map,0,0,4,20);auto &c=display13FBPower;Display13FBPowerIO io{&map}; + state.adlnOutputWiring.hdmi=0;state.adlnOutputWiring.dp=3; + for(auto &pin:state.adlnOutputWiring.ddc)pin=0; + assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,state.adlnOutputWiring)); + alignas(void *) uint8_t peer[sizeof(f.port)];memcpy(peer,f.port,sizeof(peer)); + put(peer,0x120,1U);put(peer,0x548,static_cast(f.platform+0x38));put(peer,0x590,0x64100U);map.regs[0x64100]=0x80; + using Display13Power::Status;assert(c.bridge.getDDI(io,1).status==Status::Complete); + void *ports[]={f.port,peer}; + assert(!XE::wrapDisplay13EnablePort(ports[0])&&!XE::wrapDisplay13EnablePort(ports[1])); + assert(c.bridge.state.refs[8]==1&&c.bridge.state.refs[6]==1&&objectRefs.size()==2); + assert(!XE::wrapDisplay13DisablePort(ports[first])&&c.bridge.putDDI(io,first).status==Status::Complete); + const auto survivor=1-first; + assert(c.mainLink[survivor].owner==reinterpret_cast(ports[survivor])); + assert(map.regs[0x45444]&(survivor?8:2)); + assert(c.bridge.getAUX(io,survivor).status==Status::Complete&&c.bridge.putAUX(io,survivor).status==Status::Complete); + assert(!XE::wrapDisplay13DisablePort(ports[survivor])&&objectRefs.empty()); + } printf("PASS production Display13 signal/SST: %u compositions after peer release, 39 admission/power/MMIO faults, native error/power-loss/HDMI preservation\n",count); - if(image){munmap(image,0x400000);printf("PASS %u exact native pattern/framing calls and unmodified donor counterexamples\n",replays);} + puts("PASS main-link AUX: transaction independence, 720 repeated-enable/stop cycles, partial failure/idle timeout/foreign owner guards"); + if(image){munmap(image,0x400000);printf("PASS %u exact native pattern/framing calls and unmodified donor counterexamples; %u native port enable/disable calls\n",replays,portReplays);} } From bbd1890fbd91b28881b3019dabf068e1aa8e1d9e Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:45:43 +0900 Subject: [PATCH 33/58] Guard Display13 AUX commands and propagate power failures --- WhateverXe/kern_display13_power_bridge.hpp | 28 ++++- WhateverXe/kern_xe.cpp | 94 ++++++++++++++- WhateverXe/kern_xe.hpp | 9 ++ tests/display13_aux.py | 38 ++++++ tests/display13_aux_fixture.cpp | 129 +++++++++++++++++++++ tools/test.py | 4 +- 6 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 tests/display13_aux.py create mode 100644 tests/display13_aux_fixture.cpp diff --git a/WhateverXe/kern_display13_power_bridge.hpp b/WhateverXe/kern_display13_power_bridge.hpp index 12fbb272..a2685b0a 100644 --- a/WhateverXe/kern_display13_power_bridge.hpp +++ b/WhateverXe/kern_display13_power_bridge.hpp @@ -36,7 +36,7 @@ inline uint32_t nativeDomains(uint32_t ports) { struct NativeBridge { State state; Lease pgLease[4], ddiLease[7], auxLease[7]; - uint32_t pgRefs[4] {}, ddiRefs[7] {}, auxRefs[7] {}; + uint32_t pgRefs[4] {}, ddiRefs[7] {}, auxRefs[7] {}, auxCommands[7] {}; uint32_t selectedPorts {2}; unsigned bootPort {1}; // Boot pipe remains A until path admission is generalized. bool begun {}; @@ -60,6 +60,30 @@ struct NativeBridge { uint32_t nativeDDIBRefs() const { return nativeDDIRefs(1); } uint32_t nativeAUXBRefs() const { return nativeAUXRefs(1); } + bool auxBusy() const { + for(auto count:auxCommands)if(count)return true; + return false; + } + template bool auxHeld(IO &io,unsigned port) const { + return begun && acceptsPort(port) && auxRefs[port] && auxLease[port].owner==&state && + auxLease[port].domains==dependencies(auxDomain(port)) && held(io,state,auxDomain(port)); + } + // The native read/write/I2C callers own getAUX until their HAL call returns. + // Pin that scope against last-put/suspend while the donor waits unlocked. + // Main-link AUX or firmware-on alone is not a transaction reference. + template bool beginAUXCommand(IO &io,unsigned port) { + if(!auxHeld(io,port) || auxCommands[port]==UINT32_MAX)return false; + ++auxCommands[port];return true; + } + template bool endAUXCommand(IO &io,unsigned port) { + if(port>=7 || !auxCommands[port])return false; + const bool ready=auxHeld(io,port);--auxCommands[port];return ready; + } + template Result suspend(IO &io) { + if(auxBusy())return {}; + return Display13Power::suspend(io,state); + } + // Require the native resource leases, not merely another consumer's // physical PW1 hold. Caller serializes this proof through its MMIO use. bool retainsDDI(unsigned port) const { @@ -175,7 +199,7 @@ struct NativeBridge { Result r; if (!begun || state.faulted || state.suspended) return r; if (!acceptsPort(port)) { r.status = Status::InvalidDomains; return r; } - if (!auxRefs[port]) { r.status = Status::Unbalanced; return r; } + if (!auxRefs[port] || (auxRefs[port]==1 && auxCommands[port])) { r.status = Status::Unbalanced; return r; } if (auxRefs[port] == 1) { r = release(io,state,auxLease[port]); if (r.status != Status::Complete) return r; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 2898d948..548e06b4 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -6072,11 +6072,99 @@ 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) && - processDisplay13FramebufferSST(patcher,index,address,size); + processDisplay13FramebufferSST(patcher,index,address,size) && processDisplay13FramebufferAUX(patcher,index,address,size); adlnComboPLLFractionCaller=complete ? address+0x6D973 : 0; return complete; } +// The native get/put AUX methods return void. Gate the actual HAL transfer +// after get, then recheck the public operation after put so neither failure +// becomes successful DPCD/EDID data. No ACK/status or retry policy is emulated. +bool XE::processDisplay13FramebufferAUX(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *names[]={"__ZN14AppleIntelPort7readAUXEjPvj","__ZN14AppleIntelPort8writeAUXEjPvj", + "__ZN14AppleIntelPort13runI2COverAUXENS_11AUX_CommandEPjjh", + "__ZN17AppleIntelPortHAL13runAUXCommandEN14AppleIntelPort11AUX_CommandEPjjb"}; + const uint32_t offsets[]={0x1CC72,0x1CEB0,0x1D2B6,0x61748}; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + if(!adlnDetected || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x138BB0)return false; + for(unsigned i=0;i<4;++i) { + auto fn=patcher.solveSymbol(index,names[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entry,sizeof(entry)))return false; + } + auto global=patcher.solveSymbol(index,"_gController",address,size);patcher.clearError(); + if(global!=address+0x138BA8)return false; + KernelPatcher::RouteRequest routes[]={ + {names[0],wrapDisplay13ReadAUX,orgDisplay13ReadAUX},{names[1],wrapDisplay13WriteAUX,orgDisplay13WriteAUX}, + {names[2],wrapDisplay13I2CAUX,orgDisplay13I2CAUX},{names[3],wrapDisplay13RunAUX,orgDisplay13RunAUX} + }; + display13FBAUXHooksReady=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError(); + display13AUXControllerSlot=display13FBAUXHooksReady ? global : 0; + return display13FBAUXHooksReady; +} + +// Caller holds the display owner lock. Probe may run before setPortMode, +// hence mode0 is allowed; a DDI/main-link lease is deliberately not required. +bool XE::display13AUXReady(void *port,bool command) { + auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); + if(!s->adlnDetected || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBAUXHooksReady || + !object || object->getMetaClass()->getClassSize()<0x5AC || !c.controller || + c.controller!=s->adlnTopologyController || !s->display13AUXControllerSlot || + *reinterpret_cast(s->display13AUXControllerSlot)!=c.controller || !adlnWriterMappingBound() || + !c.bridge.begun || !c.bridge.state.ready || c.bridge.state.faulted || c.bridge.state.suspended || + c.clockFaulted || c.memoryFaulted || !Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60) || + Display13FBMemory::field(c.controller,0xD0) || + Display13FBMemory::field(c.controller,0xB48) || + 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 || + Display13FBMemory::field(p,0x578)!=c.access || + Display13FBMemory::field(c.power,0x10)!=c.controller || + Display13FBMemory::field(c.power,0x78)!=c.access)return false; + // Exact c004 HAL uses CTL + five DATA offsets from the port object. + const auto ctl=0x64010+0x100*config[0]; + for(unsigned n=0;n<6;++n)if(Display13FBMemory::field(p,0x594+4*n)!=ctl+4*n)return false; + if(!command)return true; + Display13FBPowerIO io {c.map};return c.bridge.beginAUXCommand(io,config[0]); +} + +uint32_t XE::wrapDisplay13ReadAUX(void *port,uint32_t address,void *data,uint32_t length) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,false);IOLockUnlock(c.lock); + if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13ReadAUX,callbackXE->orgDisplay13ReadAUX)(port,address,data,length); + IOLockLock(c.lock);const bool complete=display13AUXReady(port,false);IOLockUnlock(c.lock); + return r ? r : complete ? 0 : kIOReturnNotReady; +} +uint32_t XE::wrapDisplay13WriteAUX(void *port,uint32_t address,void *data,uint32_t length) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,false);IOLockUnlock(c.lock); + if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13WriteAUX,callbackXE->orgDisplay13WriteAUX)(port,address,data,length); + IOLockLock(c.lock);const bool complete=display13AUXReady(port,false);IOLockUnlock(c.lock); + return r ? r : complete ? 0 : kIOReturnNotReady; +} +uint32_t XE::wrapDisplay13I2CAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,uint8_t flag) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,false);IOLockUnlock(c.lock); + if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13I2CAUX,callbackXE->orgDisplay13I2CAUX)(port,command,packet,length,flag); + IOLockLock(c.lock);const bool complete=display13AUXReady(port,false);IOLockUnlock(c.lock); + return r ? r : complete ? 0 : kIOReturnNotReady; +} +uint32_t XE::wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,bool flag) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,true); + const auto ddi=ready ? Display13FBMemory::field(port,0x120) : 7; + IOLockUnlock(c.lock);if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13RunAUX,callbackXE->orgDisplay13RunAUX)(port,command,packet,length,flag); + IOLockLock(c.lock);Display13FBPowerIO io {c.map};const bool held=c.bridge.endAUXCommand(io,ddi); + const bool complete=display13AUXReady(port,false) && held; + if(!complete)adlnFault(c.bridge.state.faulted,__LINE__); + IOLockUnlock(c.lock);return r ? r : complete ? 0 : kIOReturnNotReady; +} + 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. @@ -8359,7 +8447,7 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { if(callbackXE->display13FBLifecycleHooksReady && c.bridge.begun && !c.bridge.state.faulted && c.bridge.state.suspended && !c.engineEnabled && c.clockStopped && !c.engine.domains)status=Display13DBUF::Status::Complete; else if(callbackXE->display13FBLifecycleHooksReady && c.bridge.begun && !c.bridge.state.faulted && - !c.bridge.state.suspended && !c.clockFaulted && !Display13WM::busyPipes(c.pipe) && !c.dbuf.updating && + !c.bridge.state.suspended && !c.bridge.auxBusy() && !c.clockFaulted && !Display13WM::busyPipes(c.pipe) && !c.dbuf.updating && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { if(!c.engineEnabled && c.clockStopped && !c.engine.domains)status=Display13DBUF::Status::Complete; else { @@ -8443,7 +8531,7 @@ void XE::wrapAlderLakeNPowerOverride(void *power,bool enable) { if(quiet) {irq.quiescing=true;irq.blockedPipes=7;} IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); if(quiet) { - Display13FBPowerIO io {c.map};result=Display13Power::suspend(io,c.bridge.state); + Display13FBPowerIO io {c.map};result=c.bridge.suspend(io); const auto done=IOSimpleLockLockDisableInterrupt(irq.lock); irq.quiescing=false;irq.blockedPipes=0;IOSimpleLockUnlockEnableInterrupt(irq.lock,done); } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 9a107b81..36739ca9 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -121,6 +121,15 @@ class XE { bool display13FBWatermarkHooksReady {false}; bool display13FBOutputHooksReady {false}; bool display13FBSSTHooksReady {}; + bool display13FBAUXHooksReady {}; + mach_vm_address_t display13AUXControllerSlot {}; + mach_vm_address_t orgDisplay13ReadAUX {},orgDisplay13WriteAUX {},orgDisplay13I2CAUX {},orgDisplay13RunAUX {}; + bool processDisplay13FramebufferAUX(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static bool display13AUXReady(void *port,bool command); + static uint32_t wrapDisplay13ReadAUX(void *port,uint32_t address,void *data,uint32_t length); + static uint32_t wrapDisplay13WriteAUX(void *port,uint32_t address,void *data,uint32_t length); + static uint32_t wrapDisplay13I2CAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,uint8_t flag); + static uint32_t wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,bool flag); mach_vm_address_t orgDisplay13TrainingPattern {},orgDisplay13SignalLevels {}; mach_vm_address_t orgDisplay13EnablePort {},orgDisplay13DisablePort {}; static bool display13MainLinkAUX(void *port,bool acquire); diff --git a/tests/display13_aux.py b/tests/display13_aux.py new file mode 100644 index 00000000..c64106d6 --- /dev/null +++ b/tests/display13_aux.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Actual AUX wrappers + native bridge; optional exact donor caller 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() +functions='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig 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::display13AUXReady(', 'uint32_t XE::wrapDisplay13ReadAUX(', 'uint32_t XE::wrapDisplay13WriteAUX(', + 'uint32_t XE::wrapDisplay13I2CAUX(', 'uint32_t XE::wrapDisplay13RunAUX(']) +binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S).group() +objects=(r/'tests/tgl_fb_binding.cpp').read_text().split('int main()')[0].replace('port[0x580]','port[0x700]') +base=(r/'tests/display13_sst_fixture.cpp').read_text().split('int main(')[0] +base=base.replace('// NATIVE_OBJECTS',objects).replace('// BINDING',binding).replace('// FUNCTIONS',functions) +base=base.replace('struct OSObject {','void *nativePortObject=nullptr;\nstruct OSObject {').replace('return size;','return this==nativePortObject?0x700:size;') +base=base.replace('bool adlnWriterMappingBound()', 'void adlnFault(bool &fault,unsigned){fault=true;}\nbool adlnWriterMappingBound()') +header=(r/'WhateverXe/kern_xe.hpp').read_text() +extra=header[header.index(' bool display13FBAUXHooksReady'):header.index(' mach_vm_address_t orgDisplay13TrainingPattern')] +extra=extra.replace(' bool processDisplay13FramebufferAUX(KernelPatcher &,size_t,mach_vm_address_t,size_t);','').replace('mach_vm_address_t','uintptr_t') +base=base.replace('struct XE {','struct XE {\n'+extra) +code=base+(r/'tests/display13_aux_fixture.cpp').read_text() +assert 'c.bridge.suspend(io)' in s +engine=s[s.index('void XE::wrapAlderLakeNEngineDisable('):s.index('void XE::wrapAlderLakeNPowerOverride(')] +assert '!c.bridge.auxBusy()' in engine +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + install=s[s.index('bool XE::processDisplay13FramebufferAUX('):s.index('bool XE::display13AUXReady(')] + entry=bytes(int(x,0) for x in re.search(r'entry\[\]=\{([^}]+)',install).group(1).split(',')) + for off in [0x1cc72,0x1ceb0,0x1d2b6,0x61748]:assert b[off:off+len(entry)]==entry + # AUX bank coordinates in both native Combo init branches. + for start,ctl in [(0x5f0e2,0x64010),(0x5f2c2,0x64110)]: + assert (ctl.to_bytes(4,'little')) in b[start:start+130] + print('PASS exact donor AUX hooks, Combo bank coordinates and runtime suspend wiring') +with tempfile.TemporaryDirectory(prefix='display13-aux-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/display13_aux_fixture.cpp b/tests/display13_aux_fixture.cpp new file mode 100644 index 00000000..f9eea3f6 --- /dev/null +++ b/tests/display13_aux_fixture.cpp @@ -0,0 +1,129 @@ +// Uses display13_sst_fixture's real binding/bridge setup. Only the transport +// completion and kernel imports are mocked; optional native callers execute +// their original get/put, retry, buffer-copy and result paths. +bool failGet=false,failPut=false,failCommand=false;unsigned auxCalls=0,auxResult=0; +void getAux(void *power,unsigned ddi){ + assert(!locked&&power==display13FBPower.power);auto &c=display13FBPower;Display13FBPowerIO io{c.map}; + if(failGet)c.map->drop=0x45444; + auto r=c.bridge.getAUX(io,ddi);c.map->drop=0; + if(r.status!=Display13Power::Status::Complete)c.bridge.state.faulted=true; +} +void putAux(void *power,unsigned ddi){ + assert(!locked&&power==display13FBPower.power);auto &c=display13FBPower;Display13FBPowerIO io{c.map}; + if(failPut)c.map->drop=0x45444; + auto r=c.bridge.putAUX(io,ddi);c.map->drop=0; + if(r.status!=Display13Power::Status::Complete)c.bridge.state.faulted=true; +} +unsigned auxTransport(void *port,unsigned,uint32_t *packet,unsigned,bool){ + assert(!locked);++auxCalls;auto &c=display13FBPower;Display13FBPowerIO io{c.map}; + const auto ddi=Display13FBMemory::field(port,0x120); + assert(c.bridge.auxBusy()&&c.bridge.auxHeld(io,ddi)); + const auto writes=c.map->writes.size(); + assert(c.bridge.suspend(io).status==Display13Power::Status::NotReady&&c.map->writes.size()==writes); + assert(c.bridge.putAUX(io,ddi).status==Display13Power::Status::Unbalanced&&c.map->writes.size()==writes); + // Nested peer-native transactions preserve the active command's scope. + assert(c.bridge.getAUX(io,ddi).status==Display13Power::Status::Complete); + assert(c.bridge.putAUX(io,ddi).status==Display13Power::Status::Complete&&c.bridge.auxBusy()); + packet[0]=0;packet[1]=0x12345678; + if(failCommand)c.map->regs[0x45444]=0; + return auxResult; +} +unsigned syntheticFront(void *port,unsigned,void *data,unsigned){ + auto &c=display13FBPower;const auto ddi=Display13FBMemory::field(port,0x120);uint32_t packet[5]{}; + getAux(c.power,ddi);auto r=XE::wrapDisplay13RunAUX(port,0,packet,4,false);putAux(c.power,ddi); + if(!r)memcpy(data,packet+1,4);return r; +} +unsigned syntheticI2C(void *port,unsigned command,uint32_t *data,unsigned length,uint8_t){return syntheticFront(port,command,data,length);} +unsigned unsafeTransport(void*,unsigned,uint32_t *packet,unsigned,bool){++auxCalls;packet[0]=0;packet[1]=0x12345678;return 0;} +void waitAUX(unsigned){assert(!locked);} +void copyAUX(const void *src,void *dst,size_t bytes){memcpy(dst,src,bytes);} +void stackFail(){assert(false);} +void auxSetup(Fixture &f,Map &map,unsigned ddi,bool hdmi,unsigned mode){ + setup(f,map,ddi,0,2,20);auto &c=display13FBPower;state.display13FBAUXHooksReady=true; + if(hdmi){state.adlnOutputWiring.dp=0;state.adlnOutputWiring.hdmi=3;state.adlnOutputWiring.ddc[0]=1;state.adlnOutputWiring.ddc[1]=2; + assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,state.adlnOutputWiring));} + state.display13AUXControllerSlot=reinterpret_cast(&c.controller);put(f.ctl,0xC30,c.lock); + for(unsigned n=0;n<6;++n)put(f.port,0x594+4*n,0x64010U+0x100*ddi+4*n); + put(f.port,0x550,mode); + state.orgDisplay13ReadAUX=state.orgDisplay13WriteAUX=reinterpret_cast(syntheticFront); + state.orgDisplay13I2CAUX=reinterpret_cast(syntheticI2C);state.orgDisplay13RunAUX=reinterpret_cast(auxTransport); + failGet=failPut=failCommand=false;auxCalls=auxResult=0; + // Probe must work after all DDI/pipe owners are gone. + Display13FBPowerIO io{&map};assert(c.bridge.putDDI(io,ddi).status==Display13Power::Status::Complete); + assert(c.bridge.putPG(io,1).status==Display13Power::Status::Complete); + assert(!c.bridge.state.refs[0]); +} +int main(int argc,char **argv){ + uint8_t *image=nullptr;uint64_t canary=0x123456789ABCDEF0ULL;uintptr_t portVT[64]{}; + portVT[0x170/8]=reinterpret_cast(XE::wrapDisplay13RunAUX); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vector bytes((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,bytes.data(),bytes.size());put(image,0xC80A8,reinterpret_cast(&canary)); + jump(image+0x5C4BA,reinterpret_cast(getAux));jump(image+0x5AE0A,reinterpret_cast(putAux)); + for(unsigned at:{0x1CDF6U,0x1D0F7U,0x1D3B2U,0x1D46DU,0x1D52AU})redirect(image,at,0x300000,reinterpret_cast(waitAUX)); + for(unsigned at:{0x1CE6CU,0x1D0AFU})redirect(image,at,0x300020,reinterpret_cast(copyAUX)); + for(unsigned at:{0x1CEABU,0x1D19CU})redirect(image,at,0x300040,reinterpret_cast(stackFail)); + } + unsigned cases=0; + for(unsigned ddi=0;ddi<2;++ddi)for(bool hdmi:{false,true})for(unsigned mode:{0U,1U}) + for(unsigned op=0;op<3;++op)for(unsigned fault=0;fault<5;++fault){ + Fixture f(0,0,ddi);Map map;auxSetup(f,map,ddi,hdmi,mode);auto &c=display13FBPower; + if(image){nativePortObject=f.port;put(f.port,0,reinterpret_cast(portVT));put(image,0x138BA8,c.controller); + state.orgDisplay13ReadAUX=reinterpret_cast(image+0x1CC72); + state.orgDisplay13WriteAUX=reinterpret_cast(image+0x1CEB0); + state.orgDisplay13I2CAUX=reinterpret_cast(image+0x1D2B6);} + failGet=fault==1;failPut=fault==2;failCommand=fault==3;auxResult=fault==4?0xE00002BCU:0; + uint32_t data[5]{};auto result=op==0?XE::wrapDisplay13ReadAUX(f.port,0x100,data,4): + op==1?XE::wrapDisplay13WriteAUX(f.port,0x100,data,4):XE::wrapDisplay13I2CAUX(f.port,0,data,4,0); + assert(result==(fault==4?auxResult:fault?kIOReturnNotReady:0)); + assert(!c.bridge.auxBusy() && (fault==4 && image ? auxCalls==7 : auxCalls==(fault==1?0U:1U))); + if(!fault||fault==4){assert(!c.bridge.nativeAUXRefs(ddi)&&!c.bridge.state.refs[0]);} + if(fault==1)assert(c.bridge.state.faulted&&!c.bridge.nativeAUXRefs(ddi)); + if(fault==2||fault==3)assert(c.bridge.state.faulted&&c.bridge.nativeAUXRefs(ddi)==1); + nativePortObject=nullptr;++cases; + } + // All-ones power, retained main-link-only AUX, unbound objects and unknown + // banks must not reach the transport, even when the firmware left AUX on. + for(unsigned bad=0;bad<21;++bad){ + Fixture f(0,0,0);Map map;auxSetup(f,map,0,false,0);auto &c=display13FBPower;Display13FBPowerIO io{&map}; + assert(c.bridge.getAUX(io,0).status==Display13Power::Status::Complete); + if(bad==0)state.adlnDetected=false;if(bad==1)state.display13FBAUXHooksReady=false; + if(bad==2)state.xeProfile.display=XELP::Display::XeD;if(bad==3)state.adlnTopologyController=nullptr; + if(bad==4)state.display13AUXControllerSlot=0;if(bad==5)bound=false; + if(bad==6)c.bridge.state.faulted=true;if(bad==7)c.bridge.state.suspended=true; + if(bad==8)put(f.ctl,0xD0,static_cast(f.port));if(bad==9)put(f.port,0x550,2U); + if(bad==10)put(f.port,0x578,static_cast(f.port));if(bad==11)put(f.port,0x598,0x64114U); + if(bad==12)put(f.port,0x120,1U);if(bad==13)map.regs[0x45444]=UINT32_MAX; + if(bad==14)c.bridge.auxRefs[0]=0;if(bad==15)c.bridge.auxLease[0].owner=nullptr; + if(bad==16)map.regs[0x45404]=0;if(bad==17)c.bridge.auxCommands[0]=UINT32_MAX; + if(bad==18)put(f.ctl,0xB48,1U);if(bad==19)f.ctl[0x1BD8]=1;if(bad==20)put(f.ctl,0xC30,static_cast(nullptr)); + uint32_t packet[5]{};auto reads=map.reads.size();assert(XE::wrapDisplay13RunAUX(f.port,0,packet,4,false)==kIOReturnNotReady&&!auxCalls); + if(bad<13)assert(map.reads.size()==reads); + } + // Two native callers waiting on the donor channel lock retain independent + // command counts; the first completion cannot release the second caller. + {Fixture f(0,0,0);Map map;auxSetup(f,map,0,false,0);auto &b=display13FBPower.bridge;Display13FBPowerIO io{&map}; + assert(b.getAUX(io,0).status==Display13Power::Status::Complete&&b.beginAUXCommand(io,0)); + assert(b.getAUX(io,0).status==Display13Power::Status::Complete&&b.beginAUXCommand(io,0)); + assert(b.auxCommands[0]==2&&b.endAUXCommand(io,0)); + assert(b.putAUX(io,0).status==Display13Power::Status::Complete&&b.auxBusy()); + assert(b.endAUXCommand(io,0)&&b.putAUX(io,0).status==Display13Power::Status::Complete); + assert(!b.auxBusy()&&!b.state.refs[0]);} + unsigned counterexamples=0; + if(image){ + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned op=0;op<3;++op)for(unsigned fault=1;fault<=2;++fault){ + Fixture f(0,0,ddi);Map map;auxSetup(f,map,ddi,false,0);auto &c=display13FBPower; + nativePortObject=f.port;put(f.port,0,reinterpret_cast(portVT));put(image,0x138BA8,c.controller); + portVT[0x170/8]=reinterpret_cast(unsafeTransport);failGet=fault==1;failPut=fault==2; + uint32_t data[5]{};auto read=reinterpret_cast(image+0x1CC72); + auto write=reinterpret_cast(image+0x1CEB0); + auto i2c=reinterpret_cast(image+0x1D2B6); + const auto result=op==0?read(f.port,0x100,data,4):op==1?write(f.port,0x100,data,4):i2c(f.port,0,data,4,0); + assert(!result&&auxCalls==1&&c.bridge.state.faulted);++counterexamples;nativePortObject=nullptr; + } + assert(counterexamples==12);munmap(image,0x400000); + printf("PASS %u old native false-success counterexamples (failed AUX get/put)\n",counterexamples); + } + printf("PASS Display13 AUX: %u %s transactions; get/put/HAL fault propagation, native errors, no-DDI probe, active-command suspend/put guards, 21 rejected bindings\n",cases,image?"exact-donor":"synthetic"); +} diff --git a/tools/test.py b/tools/test.py index a8e180af..d9ab70a4 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,9 +7,9 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['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 += ['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_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_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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From 6e98864f8dbdeb3fe721ff57aa36edb611aa5017 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:57:29 +0900 Subject: [PATCH 34/58] Compose Display13 DP probe capabilities and startup requirements --- AppleIntelTGLCompat/AppleIntelTGLCompat.cpp | 3 +++ .../AppleIntelTGLCompatPortProbe.hpp | 4 +-- WhateverXe/kern_xe.cpp | 27 +++++++++++++------ WhateverXe/kern_xe.hpp | 1 + WhateverXe/kern_xelp_display_contract.hpp | 11 +++++++- WhateverXe/kern_xelp_platform.hpp | 11 ++++++-- WhateverXe/kern_xelp_probe.hpp | 7 +++-- tests/display_contract_adapter.py | 11 +++++++- tests/provider_binding_fixture.cpp | 11 ++++++++ tests/xelp_display_contract.cpp | 7 +++-- tests/xelp_platform.cpp | 15 +++++++++++ 11 files changed, 90 insertions(+), 18 deletions(-) diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index b6b9e8c1..bd6ca41a 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp @@ -50,6 +50,9 @@ static uint32_t read32(void *access, unsigned long offset) { // and board-policy checks. The provider independently verifies physical identity; // 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; +} extern "C" bool xelpConfigureDisplay(void *controller,const XELP::ProbeConfig *config) { if(!controller || !config || !config->valid())return false; auto service=static_cast(controller); diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp b/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp index a01798eb..11f1e51e 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BSD-2-Clause -// Shared c004fc1a donor port-mode provider. ADL-N currently admits Combo -// HDMI only; TC HDMI is deferred and fixed DP needs a separate policy. +// Shared c004fc1a donor port-mode provider. Fixed Combo HDMI/DP classification +// does not grant PHY, AUX, boot-handoff or Type-C ownership. #ifndef TGL_COMPAT_PORT_PROBE_HPP #define TGL_COMPAT_PORT_PROBE_HPP #include diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 548e06b4..a26adced 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4048,7 +4048,8 @@ bool XE::xelpBindProbe(void *controller) { config.legacyBackend=tglAUXParentMask; } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || - !Display13FBTopology::supportedWiring(adlnOutputWiring))return false; + !Display13FBTopology::adlnWiring(adlnOutputWiring) || + (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; config.combo=adlnOutputWiring.selected();config.comboBackend=3; } const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config); @@ -4197,7 +4198,12 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add // or fall back to a second generation implementation in WEG. if(probe>=address && probe-address=address && configure-address(index,"_xelpProbeCapabilities",address,size); + patcher.clearError(); + display13DPProbeReady=query>=address && query-address(query)); } + IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13DPProbeAvailable",display13DPProbeReady); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); if(adlnRequested)IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); return true; @@ -6837,7 +6843,14 @@ bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32 bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); - const auto contract=XELP::DisplayContract::display13(s->display13Available()); + 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 contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); const bool ready=s->adlnDetected && s->adlnKernelReady && contract.ready() && pci && xelpAlderLakeNDevice(pci); uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; that->setProperty("WEXDisplayStartupContract",contractRecord,sizeof(contractRecord)); @@ -6845,12 +6858,6 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { SYSLOG("igfx","Display13 missing operation %s",XELP::DisplayContract::name(bit)); that->setProperty("N97FramebufferBackendReady",ready); if(!ready)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 bool dualReady=s->adlnNativeReady && 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, @@ -11650,6 +11657,10 @@ XELP::DisplayContract::Mask XE::display13Available() const { if(display13FBWatermarkLayoutReady)a|=C::WMLayout13; if(display13FBOutputHooksReady)a|=C::Output13; if(display13FBTopologyHooksReady)a|=C::Topology13; + 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. return a; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 36739ca9..dd5006d5 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -141,6 +141,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 {}; bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; Display13OutputWiring adlnOutputWiring; diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 89f8fc7a..9d21b320 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -66,6 +66,10 @@ struct DisplayContract { PanelDepth=1ULL<<56, PanelTiming=1ULL<<57, PanelPWM=1ULL<<58, + SST13=1ULL<<59, + AUX13=1ULL<<60, + DPProbe13=1ULL<<61, + DPBoot13=1ULL<<62, }; Mask required {},available {}; Mask missing() const {return required&~available;} @@ -101,7 +105,8 @@ struct DisplayContract { return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0)| (panel?Combo12|PanelDepth|PanelTiming|PanelPWM|AUXParent|PowerGet|PowerPut:0),installed}; } - static DisplayContract display13(Mask installed) {return {Backend13,installed};} + static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13; + static DisplayContract display13(Mask installed,bool dp=false) {return {Backend13|(dp?DP13:0),installed};} static const char *name(Mask bit) { switch(bit) { case Surface:return "Surface"; @@ -163,6 +168,10 @@ struct DisplayContract { case PanelDepth:return "PanelDepth"; case PanelTiming:return "PanelTiming"; case PanelPWM:return "PanelPWM"; + case SST13:return "SST13"; + case AUX13:return "AUX13"; + case DPProbe13:return "DPProbe13"; + case DPBoot13:return "DPBoot13"; default:return "Unknown"; } } diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 247100fb..81beb4b5 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -65,12 +65,19 @@ 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 still checks FIA. // Type-C intent is represented but not admitted by either current backend. +constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U; +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); +} struct ProbeConfig { - uint32_t abi {3},bytes {sizeof(ProbeConfig)}; + uint32_t abi {ProbeABI},bytes {sizeof(ProbeConfig)}; Profile profile; uint32_t combo {},legacy {},typeC {},excluded {},comboBackend {},legacyBackend {}; bool valid() const { - if(abi!=3 || bytes!=sizeof(ProbeConfig) || !profile.admitted() || + if(abi!=ProbeABI || bytes!=sizeof(ProbeConfig) || !profile.admitted() || !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/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index ab77f6e9..9539a434 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -38,9 +38,12 @@ 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); - // Display13 fixed Combo HDMI remains the only qualified transport. + // 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<=1 && c[1]<=3 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2 ? 1 : 0; + const bool hdmi=c[1]>=1 && c[1]<=3 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; + const bool dp=c[0]<2 && c[1]==c[0]+1 && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; + return hdmi || dp ? 1 : 0; } } #endif diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index b2b65ac3..71ee8a98 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -28,7 +28,16 @@ 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); + assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13)); + assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); + s.display13DPProbeReady=false; + assert(C::display13(s.display13Available()).ready()); // Existing provider/HDMI stays valid. + assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::DPProbe13)); + s.display13DPProbeReady=true;s.display13FBAUXHooksReady=false; + assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::AUX13)); + s.display13FBAUXHooksReady=true;s.display13FBSSTHooksReady=false; + assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::SST13)); + s.display13FBSSTHooksReady=true; // 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 3facc692..3348c8b0 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -25,6 +25,12 @@ struct IOMemoryMap:OSObject {uintptr_t address {};uint64_t bytes {}; #define IOLog(...) ((void)0) // PROVIDER_SOURCE int main(int argc,char **argv) { + assert(XELP::display13ProbeCapable(xelpProbeCapabilities)); + assert(!XELP::display13ProbeCapable(nullptr)); + 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(!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); const bool adls=!strncmp(argv[1],"adls",4); const bool legacy=!strncmp(argv[1],"adln-abi1",9); @@ -105,6 +111,11 @@ int main(int argc,char **argv) { *reinterpret_cast(mmio.data()+base+0x8A0)=0x10; *reinterpret_cast(mmio.data()+base+0x890)=1U<<((d-3)%2);} assert(tglProbePort(&port)==(adls?(d==3?1U:0U):adln?(d<2?1U:0U):(d<3?1U:4U))); + if(adln){ + const uint32_t dp[]={d,d+1,1,0,0x400,0x201};memcpy(rec,dp,sizeof(dp)); + assert(tglProbePort(&port)==(d<2?1U:0U)); + if(d<2)for(unsigned n=0;n<6;++n){rec[n]^=1;assert(!tglProbePort(&port));rec[n]^=1;} + } field(&port,0x578)=&foreign;assert(!tglProbePort(&port));field(&port,0x578)=&access; field(&port,0x548)=reinterpret_cast(1);assert(!tglProbePort(&port)); } diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index e4b5f42b..616af9c2 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<59;++i) { + for(unsigned i=0;i<63;++i) { auto broken=c;auto bit=1ULL<(r),r)==(d<2?1U:0U));} assert(io.reads.empty());auto other=b;other.access=3;assert(!b.sameBinding(other));assert(b.sameBinding(b)); + // Display13 Combo protocol/port arrangements compose without FIA reads. + unsigned dpCases=0; + for(unsigned selected=1;selected<4;++selected)for(unsigned dpMask=0;dpMask<4;++dpMask){ + if(dpMask&~selected)continue; + c.combo=selected;c.comboBackend=3;b.config=c; + for(unsigned d=0;d<2;++d){ + auto rec=reinterpret_cast(platform+0x20+24*d);const bool dp=dpMask&(1U<(rec),rec)==((selected&(1U<(rec),rec));rec[field]^=1;} + ++dpCases; + } + } + assert(dpCases==16&&io.reads.empty()); c={};c.profile=identify(0x46908086,12);c.combo=8;c.comboBackend=0x19;b.config=c;assert(c.valid()); for(unsigned d=0;d<9;++d){auto r=reinterpret_cast(platform+0x20+24*d); const uint32_t v[]={d,9,2,0,0x800,0x202};memcpy(r,v,sizeof(v)); From e14adae00bc6704a1d5c3f2dd5270e9d41769ca4 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:05:11 +0900 Subject: [PATCH 35/58] Preserve DP source state while parking mixed Display13 GOPs --- WhateverXe/kern_alderlake_n.hpp | 12 +++- WhateverXe/kern_display13_boot.hpp | 45 +++++++++++-- WhateverXe/kern_display13_fb_topology.hpp | 8 ++- tests/alderlake_n.cpp | 11 ++- tests/display13_boot.cpp | 81 ++++++++++++++++++++++- 5 files changed, 146 insertions(+), 11 deletions(-) diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 765665ff..1756750e 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -133,10 +133,10 @@ template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullp } enum class ReadoutStatus : uint32_t { NotAdmitted, Complete, Unavailable, PowerChanged }; struct DisplayReadout { - uint32_t version {1}; + uint32_t version {2}; ReadoutStatus status {ReadoutStatus::NotAdmitted}; uint32_t count {}, poweredPipes {}, activePipes {}, hdmiBPipes {}; - Register registers[128] {}; + Register registers[160] {}; }; // Read only. Check Display 13 PW_A/B/C before pipe-local registers; do not @@ -180,6 +180,14 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { 0x70180, 0x70188, 0x7018C, 0x70190, 0x7019C, 0x701A4, 0x701AC, 0x7027C, 0x7037C, 0x70280, 0x70380, 0x70480, 0x70580, 0x70680, 0x70780}; for (auto offset : local) add(offset + 0x1000 * pipe); + // TGL+ DP transport is transcoder-indexed, not DDI-indexed. M2/N2 + // are absent on Display13 (Linux intel_cpu_transcoder_has_m2_n2). + const uint32_t transport[] {0x60404,0x60540,0x60030,0x60034,0x60040,0x60044}; + for (auto offset : transport) + add(offset + 0x1000 * pipe); + add(0x46140 + 4 * pipe); + // Display13 DSC/joiner belongs to the corresponding pipe well. + add(0x78000 + 0x200 * pipe);add(0x78004 + 0x200 * pipe); } const auto after = add(0x45404); if (r.status == ReadoutStatus::Complete && after != power) diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index 0ee04ea0..aa1801ed 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -12,6 +12,32 @@ struct Result { uint32_t version {1};Status status {Status::Unavailable}; uint32_t stage {},writes {},before {},after {},lastReg {},expected {},observed {},framesBefore {},framesAfter {}; }; +// 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 AlderLakeN::DisplayReadout &boot,unsigned pipe,unsigned ddi) { + if(pipe>=3 || ddi>=2 || boot.status!=AlderLakeN::ReadoutStatus::Complete || + !(boot.activePipes&boot.poweredPipes&(1U<>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. + 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 && + (tp&~(1U<<18))==0x80000300U && + !(dss1&0xF0300000U) && !(dss2&0x80008000U) && + !(dm&0x81000000U) && (dm&0xFFFFFF) && + dn && !(dn&0xFF000000U) && lm && !(lm&0xFF000000U) && ln && !(ln&0xFF000000U) && + (dm&0xFFFFFF)<=dn; +} template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, const Display13HDMIWiring &w,const XELPMemory::Layout &memory) { Result r;r.before=boot.activePipes; @@ -20,8 +46,8 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo const auto active=boot.activePipes; 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] {},used=0; - // Every active firmware pipe must be a distinct selected linear TMDS GOP. + uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0; + // 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); + const auto port=Display13FBTopology::bootDDI(w,trans,true); if(port>=3 || (used&(1U<>24)&7)==2) { + if(!dpSource(boot,p,port))return r; + dpPipes|=1U<>20)&7) || !AlderLakeN::value(boot,0x70188+bank,stride) || @@ -74,7 +104,8 @@ template 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])mutableReg=true; + (reg>=0x70000+bank && reg<0x71000+bank) || 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) { @@ -115,6 +146,12 @@ 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<>24)&7)>1)return UINT32_MAX; +inline uint32_t bootDDI(const Display13OutputWiring &w,uint32_t trans,bool dpSource=false) { + if(!selectedWiring(w) || trans==UINT32_MAX || !(trans&(1U<<31)))return UINT32_MAX; const auto selector=(trans>>27)&15; if(selector<1 || selector>3)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<1)return UINT32_MAX; return (w.hdmi&(1U< Combo B, with the ADL-N PW_B at index 6. IO b;b.regs[0x45404]=0x1005;b.regs[0x61400]=0x90000000; auto rb=AlderLakeN::readDisplay(b,true); @@ -237,6 +242,10 @@ int main() { IO shortMap;shortMap.limit=0x40000; assert(AlderLakeN::readDisplay(shortMap,true).status==AlderLakeN::ReadoutStatus::Unavailable); assert(shortMap.reads.empty()); + for(auto reg:{0x60540U,0x60030U,0x60034U,0x60040U,0x60044U,0x46140U,0x78000U,0x78004U}) { + auto bad=a;bad.regs[reg]=UINT32_MAX; + assert(AlderLakeN::readDisplay(bad,true).status==AlderLakeN::ReadoutStatus::Unavailable); + } a.changePower=true;a.reads.clear(); assert(AlderLakeN::readDisplay(a,true).status==AlderLakeN::ReadoutStatus::PowerChanged); auto live=firmware();auto snapshot=AlderLakeN::readDisplay(live,true);AlderLakeN::LinearPlan plan; diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index 25b15aef..723a56d4 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -7,6 +7,7 @@ struct IO { std::map regs; std::vector writes; bool noFrames {},noStop {},noIdle {},changePeer {}; + uint32_t changeRegister {},failWrite {}; IO() { regs[0x45404]=0xFC0F;regs[0x4438C]=0x1C000700; regs[0x64000]=regs[0x64100]=0x80000000; @@ -23,17 +24,31 @@ struct IO { bool readable(uint32_t reg) {return reg<0x1000000;} uint32_t read(uint32_t reg) {return regs[reg];} void write(uint32_t reg,uint32_t value) { - writes.push_back({reg,value});regs[reg]=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(writes.size()==1)regs[0x4500C]=0; if(changePeer && writes.size()==1)regs[0x70180]^=8; + if(changeRegister && writes.size()==1)regs[changeRegister]^=1; } 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}; const auto wiring=TGLHDMIWiring::fromProperty(prop,sizeof(prop)); const auto memory=XELPMemory::decode(0x03C1,0x7A800000,0); +static void dp(IO &io,unsigned pipe,unsigned port,unsigned lanes,bool enhanced) { + const auto bank=pipe*0x1000; + io.regs[0x60400+bank]=0x82030000|((port+1)<<27)|((lanes-1)<<1); + io.regs[0x64000+port*0x100]=0x80000000|((lanes-1)<<1); + io.regs[0x60540+bank]=0x80000300|(enhanced?1U<<18:0); + io.regs[0x60030+bank]=0x7E100000;io.regs[0x60034+bank]=0x800000; + io.regs[0x60040+bank]=0x100000;io.regs[0x60044+bank]=0x800000; +} +static Display13OutputWiring protocols(unsigned dpMask) { + Display13OutputWiring w=wiring;w.hdmi=3&~dpMask;w.dp=dpMask; + for(unsigned d=0;d<3;++d)if(!(w.hdmi&(1U<=0x78000?p*0x200:p*0x1000); + auto val=bad.value;if(bad.offset==0x60400)val+=p<<27; + t.regs[reg]=val; + assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology); + assert(t.writes.empty()); + } + for(auto reg:{0x64000U,0x64100U,0x46140U,0x46144U}) { + IO t;dp(t,0,0,4,true);dp(t,1,1,4,true);t.regs[reg]=0; + assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology && t.writes.empty()); + } + // Snapshot freshness and peer preservation cover DP metadata as well. + for(auto reg:{0x60540U,0x60030U,0x60034U,0x60040U,0x60044U,0x46140U,0x78004U}) { + IO t;dp(t,0,0,4,true);dp(t,1,1,4,true);const auto boot=AlderLakeN::readDisplay(t,true); + t.regs[reg]^=1; + assert(Display13Boot::normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::Readback && t.writes.empty()); + t.regs[reg]^=1;t.changeRegister=reg; + assert(Display13Boot::normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::PeerChanged && t.writes.size()==2); + } + IO tp;dp(tp,0,0,4,true);dp(tp,1,1,4,true);tp.failWrite=0x61540; + auto failure=Display13Boot::normalize(tp,AlderLakeN::readDisplay(tp,true),true,mixed,memory); + assert(failure.status==Display13Boot::Status::Timeout && failure.stage==4 && tp.writes.size()==7 && tp.regs[0x7127C]); + assert(Display13FBTopology::bootDDI(mixed,tp.regs[0x60400])==UINT32_MAX); // Runtime stays closed. + printf("PASS GOP bootstrap: %u HDMI/DP combinations, STOP/transport/idle ordering, peer/M-N preservation and bounded failures\n",combinations); } From 76ccc2cfb9fc8d39ca9c745384a85d25398dfcf3 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:08:25 +0900 Subject: [PATCH 36/58] Share Display13 DP source inspection with plane bootstrap --- WhateverXe/kern_alderlake_n.hpp | 26 ++++++++++++++++++++++++++ WhateverXe/kern_display13_boot.hpp | 28 +--------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 1756750e..afb07ebd 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -202,6 +202,32 @@ inline bool value(const DisplayReadout &r, uint32_t offset, uint32_t &out) { return false; } +// 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 || + !(boot.activePipes&boot.poweredPipes&(1U<>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. + 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 && + (tp&~(1U<<18))==0x80000300U && + !(dss1&0xF0300000U) && !(dss2&0x80008000U) && + !(dm&0x81000000U) && (dm&0xFFFFFF) && + dn && !(dn&0xFF000000U) && lm && !(lm&0xFF000000U) && ln && !(ln&0xFF000000U) && + (dm&0xFFFFFF)<=dn; +} enum class LinearStatus : uint32_t { NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive, StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index aa1801ed..a877c647 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -12,32 +12,6 @@ struct Result { uint32_t version {1};Status status {Status::Unavailable}; uint32_t stage {},writes {},before {},after {},lastReg {},expected {},observed {},framesBefore {},framesAfter {}; }; -// 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 AlderLakeN::DisplayReadout &boot,unsigned pipe,unsigned ddi) { - if(pipe>=3 || ddi>=2 || boot.status!=AlderLakeN::ReadoutStatus::Complete || - !(boot.activePipes&boot.poweredPipes&(1U<>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. - 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 && - (tp&~(1U<<18))==0x80000300U && - !(dss1&0xF0300000U) && !(dss2&0x80008000U) && - !(dm&0x81000000U) && (dm&0xFFFFFF) && - dn && !(dn&0xFF000000U) && lm && !(lm&0xFF000000U) && ln && !(ln&0xFF000000U) && - (dm&0xFFFFFF)<=dn; -} template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, const Display13HDMIWiring &w,const XELPMemory::Layout &memory) { Result r;r.before=boot.activePipes; @@ -67,7 +41,7 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo const auto port=Display13FBTopology::bootDDI(w,trans,true); if(port>=3 || (used&(1U<>24)&7)==2) { - if(!dpSource(boot,p,port))return r; + if(!AlderLakeN::dpSource(boot,p,port))return r; dpPipes|=1U< Date: Sat, 24 Oct 2026 01:11:50 +0900 Subject: [PATCH 37/58] Reinitialize Display13 DP planes while preserving source and power state --- WhateverXe/kern_alderlake_n.hpp | 46 ++++++++++++++---- tests/display13_boot.cpp | 2 +- tests/display13_linear_dp.cpp | 82 +++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 tests/display13_linear_dp.cpp diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index afb07ebd..b374e2b0 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -159,7 +159,7 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { if (r.status != ReadoutStatus::Complete) return r; // Always-on / PW1 display state, including BIOS and DDI I/O ownership. const uint32_t common[] {0x45400, 0x45408, 0x4540C, 0x45450, 0x45454, 0x4545C, - 0x45420, 0x45424, 0x42000, 0x42084, + 0x45420, 0x45424, 0x45444, 0x42000, 0x42084, 0x44300, 0x4438C, 0x45008, 0x4500C, 0x45010, 0x45014, 0x46000, 0x46004, 0x164280, 0x164284, 0x164288, 0x16428C, 0x64000, 0x64100, 0x64200}; @@ -228,9 +228,25 @@ inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) { dn && !(dn&0xFF000000U) && lm && !(lm&0xFF000000U) && ln && !(ln&0xFF000000U) && (dm&0xFFFFFF)<=dn; } +enum class GOPProtocol : uint32_t {TMDS,DP}; +// Source and power must remain unchanged while only the plane is reinitialized. +// This is an early, serialized bootstrap transaction, not a modeset or link +// adoption certificate. A changed link stops further writes, including rollback. +template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi) { + if(pipe>=3 || ddi>=2)return false; + const uint32_t bank=pipe*0x1000; + const uint32_t regs[] {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}; + for(auto reg:regs) { + uint32_t expected=0; + if(!value(r,reg,expected) || !io.readable(reg) || io.read(reg)!=expected)return false; + } + return true; +} enum class LinearStatus : uint32_t { NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive, - StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed + StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource }; struct LinearPlan { uint32_t pipe {}, chicken {}, arb {}, control {}, stride {}, position {}, size {}, offset {}, surface {}; @@ -246,11 +262,12 @@ struct LinearResult { // plane on the explicitly selected Combo A/B/C. 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) { +inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS) { p={}; if (ddi>=3 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || (r.activePipes & (r.activePipes-1)) || (r.activePipes&~r.poweredPipes) || - (ddi==1 && r.activePipes!=r.hdmiBPipes)) + (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; @@ -263,8 +280,10 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d !value(r,0x701AC+delta,surfLive) || !value(r,0x60400+delta,trans) || !value(r,0x70008+delta,pipeConf) || !value(r,0x60000+delta,htotal) || !value(r,0x6000C+delta,vtotal)) return LinearStatus::NotLinearFHD; - if(!(trans&(1U<<31)) || ((trans>>27)&15)!=ddi+1 || ((trans>>24)&7)>1) + 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; 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 || @@ -285,24 +304,32 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d return LinearStatus::Ready; } -template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1) { +template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1, + GOPProtocol protocol=GOPProtocol::TMDS) { LinearResult out; if (!admitted) return out; LinearPlan p; - out.status=linearPlan(r,p,ddi); + out.status=linearPlan(r,p,ddi,protocol); if(out.status!=LinearStatus::Ready)return out; out.pipe=p.pipe; const auto d=0x1000*p.pipe; const uint32_t ctl=0x70180+d, surf=0x7019C+d, frame=0x70040+d; 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; + return !linkChanged; + }; // Check all captured words used by the transaction before the first write. uint32_t trans=0;value(r,0x60400+d,trans); const Register expected[] {{0x60400+d,trans},{0x42084,p.chicken},{0x70028+d,p.arb},{ctl,p.control}, {0x70188+d,p.stride},{0x7018C+d,p.position},{0x70190+d,p.size}, {0x701A4+d,p.offset},{surf,p.surface}}; for(auto pair:expected)if(io.read(pair.offset)!=pair.value){out.status=LinearStatus::StateChanged;return out;} + if(!linkStable()){out.status=LinearStatus::StateChanged;return out;} const auto waitFrames=[&](uint32_t start) { for(unsigned i=0;i<1000;++i) { + if(!linkStable())return false; const auto now=io.read(frame); if(now==UINT32_MAX)return false; if(uint32_t(now-start)>=2 && uint32_t(now-start)<0x10000000)return true; @@ -312,14 +339,17 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout }; out.framesBefore=io.read(frame); if(out.framesBefore==UINT32_MAX || !waitFrames(out.framesBefore)) { - out.status=LinearStatus::NoVblank;return out; + out.status=linkChanged ? LinearStatus::StateChanged : LinearStatus::NoVblank;return out; } const auto rollback=[&] { + if(!linkStable()){out.status=LinearStatus::StateChanged;return;} write(ctl,p.control & ~0x80000000U);write(surf,p.surface); const bool stopped=waitFrames(io.read(frame)); + if(linkChanged){out.status=LinearStatus::StateChanged;return;} write(0x42084,p.chicken);write(0x70028+d,p.arb); write(ctl,p.control);write(surf,p.surface); const bool running=waitFrames(io.read(frame)); + if(linkChanged){out.status=LinearStatus::StateChanged;return;} out.restored=stopped && running && io.read(ctl)==p.control && io.read(surf)==p.surface && io.read(0x42084)==p.chicken && io.read(0x70028+d)==p.arb; if(!out.restored)out.status=LinearStatus::RollbackFailed; diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index 723a56d4..f1dd2020 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -122,7 +122,7 @@ int main() { if(dpMask&(1U< +#include +#include +#include +using namespace AlderLakeN; +struct IO { + std::map regs; + std::vector writes; + unsigned pipe {},ticks {},changeTick {}; + uint32_t changeReg {}; + bool failEnable {},noFrames {}; + bool readable(uint32_t reg) {return reg<0x1000000;} + uint32_t read(uint32_t reg) {return reg==0x70040+pipe*0x1000 ? ticks : regs[reg];} + void write(uint32_t reg,uint32_t v) { + writes.push_back({reg,v}); + if(failEnable && reg==0x70180+pipe*0x1000 && v==0x94000008){failEnable=false;return;} + regs[reg]=v; + } + void delay() { + if(!noFrames)++ticks; + if(changeTick && ticks==changeTick)regs[changeReg]^=1; + } +}; +static IO firmware(unsigned pipe,unsigned ddi,unsigned lanes,bool enhanced,unsigned width=1920) { + IO io;io.pipe=pipe;const auto b=pipe*0x1000; + io.regs={{0x45404,0xFC0F},{0x45454,3U<<(2*ddi)},{0x45444,0},{0x42000,0x8E380000}, + {0x60400+b,0x82030000|((ddi+1)<<27)|((lanes-1)<<1)}, + {0x60540+b,0x80000300|(enhanced?1U<<18:0)}, + {0x64000+ddi*0x100,0x80000000|((lanes-1)<<1)},{0x46140+4*pipe,(ddi+1)<<28}, + {0x60030+b,0x7E100000},{0x60034+b,0x800000},{0x60040+b,0x100000},{0x60044+b,0x800000}, + {0x60000+b,((width+279)<<16)|(width-1)},{0x6000C+b,0x04640437},{0x70008+b,0xC0000000}, + {0x70028+b,0xA0F},{0x70180+b,0x84000008},{0x70188+b,(width*4+63)/64}, + {0x70190+b,0x04370000|(width-1)}}; + return io; +} +int main() { + unsigned cases=0; + for(unsigned pipe=0;pipe<3;++pipe)for(unsigned ddi=0;ddi<2;++ddi)for(unsigned lanes:{1U,2U,4U}) + for(bool enhanced:{false,true})for(unsigned width:{1280U,1920U,2560U,3840U}) { + auto io=firmware(pipe,ddi,lanes,enhanced,width);auto boot=readDisplay(io,true);const auto before=io.regs; + LinearPlan plan; + assert(linearPlan(boot,plan,ddi)==LinearStatus::NotSingleHDMI); + assert(linearPlan(boot,plan,ddi,GOPProtocol::DP)==LinearStatus::Ready && plan.pipe==pipe); + auto result=reinitializeLinear(io,boot,true,ddi,GOPProtocol::DP); + assert(result.status==LinearStatus::Success && result.writes==10 && result.framesAfter>=6); + assert(dpSourceUnchanged(io,boot,pipe,ddi)); + const uint32_t b=pipe*0x1000; + for(auto w:io.writes)assert(w.offset==0x42084 || w.offset==0x70028+b || w.offset==0x70180+b || + w.offset==0x70188+b || w.offset==0x7018C+b || w.offset==0x70190+b || w.offset==0x701A4+b || w.offset==0x7019C+b); + for(auto r:before)if(r.first!=0x42084 && r.first!=0x70028+b && r.first!=0x70180+b) + assert(io.regs[r.first]==r.second); + ++cases; + } + const uint32_t link[] {0x60400,0x60404,0x60540,0x60030,0x60034,0x60040,0x60044,0x70008, + 0x46140,0x64000,0x78000,0x78004,0x45404,0x45454,0x45444,0x42000}; + for(auto reg:link) { + auto io=firmware(0,0,4,true);auto boot=readDisplay(io,true);io.regs[reg]^=1; + auto r=reinitializeLinear(io,boot,true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::StateChanged && io.writes.empty()); + // Loss before first write, after plane stop, and after plane restart. + // Do not attempt rollback through a changed power/link configuration. + for(unsigned tick:{1U,3U,5U}) { + auto io=firmware(0,0,4,true);auto boot=readDisplay(io,true);io.changeReg=reg;io.changeTick=tick; + auto r=reinitializeLinear(io,boot,true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); + } + } + auto bad=firmware(0,0,4,true);bad.regs[0x60540]=0x80000000; + auto r=reinitializeLinear(bad,readDisplay(bad,true),true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::InvalidDPSource && bad.writes.empty()); + auto stopped=firmware(0,0,4,true);stopped.noFrames=true; + r=reinitializeLinear(stopped,readDisplay(stopped,true),true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::NoVblank && stopped.writes.empty()); + auto rollback=firmware(0,0,4,true);const auto boot=readDisplay(rollback,true);rollback.failEnable=true; + r=reinitializeLinear(rollback,boot,true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::EnableTimeout && r.restored && dpSourceUnchanged(rollback,boot,0,0)); + auto lost=firmware(0,0,4,true);lost.failEnable=true;lost.changeReg=0x60540;lost.changeTick=7; + r=reinitializeLinear(lost,readDisplay(lost,true),true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==12); + printf("PASS DP plane bootstrap: %u route/lane/framing/geometry cases, source/power drift and bounded rollback\n",cases); +} From 449c84681b64b2a5ccbdad76bfa60dc676942a1c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:20:24 +0900 Subject: [PATCH 38/58] Hold DP GOP AUX power through native port ownership handoff --- WhateverXe/kern_alderlake_n.hpp | 42 +++++++++- WhateverXe/kern_display13_power_bridge.hpp | 5 +- WhateverXe/kern_xe.cpp | 34 +++++++- tests/display13_boot_aux.py | 13 +++ tests/display13_boot_aux_fixture.cpp | 98 ++++++++++++++++++++++ tools/test.py | 2 +- 6 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 tests/display13_boot_aux.py create mode 100644 tests/display13_boot_aux_fixture.cpp diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index b374e2b0..9fab4911 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -232,18 +232,54 @@ enum class GOPProtocol : uint32_t {TMDS,DP}; // Source and power must remain unchanged while only the plane is reinitialized. // This is an early, serialized bootstrap transaction, not a modeset or link // adoption certificate. A changed link stops further writes, including rollback. -template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi) { - if(pipe>=3 || ddi>=2)return false; +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; const uint32_t bank=pipe*0x1000; const uint32_t regs[] {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}; for(auto reg:regs) { uint32_t expected=0; - if(!value(r,reg,expected) || !io.readable(reg) || io.read(reg)!=expected)return false; + if(!value(r,reg,expected) || !io.readable(reg))return false; + if(reg==Display13Power::AUX)expected|=addedAUXRequest; + if(io.read(reg)!=expected)return false; } return true; } +// The GOP is already driving DP. Keep its physical AUX domain on without +// inventing a native transaction reference or claiming the firmware request. +// Caller holds the display owner lock throughout both proof and acquisition. +template Display13Power::Result holdDPBootAUX(IO &io,const DisplayReadout &boot, + Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,unsigned ddi) { + using namespace Display13Power;Result r; + if(ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi) || !dpSourceUnchanged(io,boot,0,ddi) || + bridge.bootPort!=ddi || bridge.bootAdopted || !bridge.bootPGProtected || !bridge.bootDDIProtected || + !bridge.outputHeld(io,ddi,0) || link.owner || link.bootPending || link.lease.owner)return r; + uint32_t aux=0; + if(!value(boot,AUX,aux) || !(aux&(1U<<(2*ddi))))return r; // No active-link cold power-up. + r=acquire(io,bridge.state,link.lease,auxDomain(ddi)); + if(r.status!=Status::Complete)return r; + link.port=ddi;link.bootPending=true; + if(!dpSourceUnchanged(io,boot,0,ddi,2U<<(2*ddi)) || !held(io,bridge.state,link.lease.domains)) { + // Do not release a newly acquired request under a still-active GOP. + bridge.state.faulted=true;r.status=Status::RequestLost; + } + return r; +} +// Bind the protected lease to the actual, independently validated native port. +// The caller retains that object on success and then adopts the boot path. +// No hardware request or native AUX transaction counter changes at handoff. +template Display13Power::Result adoptDPBootAUX(IO &io,const DisplayReadout &boot, + Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,uintptr_t target,unsigned ddi) { + using namespace Display13Power;Result r; + if(!target || ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi) || !dpSourceUnchanged(io,boot,0,ddi) || + bridge.bootPort!=ddi || bridge.bootAdopted || !bridge.bootPGProtected || !bridge.bootDDIProtected || + !bridge.outputHeld(io,ddi,0) || !link.bootPending || link.owner || link.port!=ddi || + link.lease.owner!=&bridge.state || link.lease.domains!=dependencies(auxDomain(ddi)) || + !held(io,bridge.state,link.lease.domains))return r; + link.owner=target;link.bootPending=false;r.status=Status::Complete;return r; +} enum class LinearStatus : uint32_t { NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive, StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource diff --git a/WhateverXe/kern_display13_power_bridge.hpp b/WhateverXe/kern_display13_power_bridge.hpp index a2685b0a..39ec311a 100644 --- a/WhateverXe/kern_display13_power_bridge.hpp +++ b/WhateverXe/kern_display13_power_bridge.hpp @@ -238,9 +238,10 @@ struct MainLinkAUX { Lease lease; uintptr_t owner {}; unsigned port {UINT32_MAX}; + bool bootPending {}; // Protected GOP lease, before an actual port object exists. template Result begin(IO &io,NativeBridge &b,uintptr_t target,unsigned ddi) { Result r; - if(!target || ddi>=2 || !b.ddiHeld(io,ddi))return r; + if(bootPending || !target || ddi>=2 || !b.ddiHeld(io,ddi))return r; if(owner) { if(owner==target && port==ddi && lease.owner==&b.state && lease.domains==dependencies(auxDomain(ddi)) && held(io,b.state,lease.domains))r.status=Status::Complete; @@ -254,7 +255,7 @@ struct MainLinkAUX { } template Result end(IO &io,NativeBridge &b,uintptr_t target,unsigned ddi) { Result r; - if(!target || ddi>=2)return r; + if(bootPending || !target || ddi>=2)return r; if(!owner && !lease.owner){r.status=Status::Complete;return r;} if(owner!=target || port!=ddi || lease.owner!=&b.state || lease.domains!=dependencies(auxDomain(ddi)) || !b.ddiHeld(io,ddi) || !held(io,b.state,lease.domains))return r; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a26adced..47bc9bdd 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4539,6 +4539,14 @@ struct Display13FBPowerIO { } void delayUs(unsigned us) { IODelay(us); } }; +// The static boot reader selects powered banks and fixed Display13 offsets. +// Broader read access does not expand the power-only write allowlist above. +struct Display13FBBootIO : Display13FBPowerIO { + explicit Display13FBBootIO(IOMemoryMap *m):Display13FBPowerIO{m} {} + bool readable(uint32_t r) const { + return map && map->getVirtualAddress() && map->getLength()==0x1000000 && r<=0x1000000-4; + } +}; // 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 { @@ -7011,7 +7019,18 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) } check[37]=valid; if(valid) { - if(!c.bridge.bootAdopted)result=c.bridge.adoptBootPath(reinterpret_cast(path),0,c.bridge.bootPort); + if(!c.bridge.bootAdopted) { + const auto ddi=c.bridge.bootPort; + const bool dp=ddi<2 && (callbackXE->adlnOutputWiring.dp&(1U<(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 if(c.bridge.bootPath==reinterpret_cast(path))result.status=Display13Power::Status::Complete; if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,c.power,0x80,controller,0x1C20))result.status=Display13Power::Status::NotReady; @@ -8812,7 +8831,8 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { service->retain();static_cast(power)->retain();static_cast(c.access)->retain(); Display13FBPowerIO io {map}; const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); - const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a); + const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); + const bool dpBoot=bootPort<2 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.selected()); else {result.status=Display13Power::Status::InvalidDomains;adlnFault(c.bridge.state.faulted,__LINE__);} + if(result.status==Display13Power::Status::Complete && dpBoot) { + 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__); + } if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,power,0x80,controller,0xC60)) { result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__); } diff --git a/tests/display13_boot_aux.py b/tests/display13_boot_aux.py new file mode 100644 index 00000000..5d909dd5 --- /dev/null +++ b/tests/display13_boot_aux.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Compile the actual power/boot MMIO adapters with the DP bootstrap helpers.""" +from pathlib import Path +import subprocess,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +start=s.index('struct Display13FBPowerIO {') +end=s.index('// Only used after native object/BAR binding',start) +code=(r/'tests/display13_boot_aux_fixture.cpp').read_text().replace('// PRODUCTION_IO',s[start:end]) +with tempfile.TemporaryDirectory(prefix='display13-boot-aux-') 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_boot_aux_fixture.cpp b/tests/display13_boot_aux_fixture.cpp new file mode 100644 index 00000000..2cbffc25 --- /dev/null +++ b/tests/display13_boot_aux_fixture.cpp @@ -0,0 +1,98 @@ +#include "kern_alderlake_n.hpp" +#include +#include +#include +struct IOMemoryMap { + std::vector memory=std::vector(0x400000); + uintptr_t getVirtualAddress() const {return reinterpret_cast(memory.data());} + uint64_t getLength() const {return 0x1000000;} + uint32_t &at(uint32_t reg) {return memory[reg/4];} +}; +void IODelay(unsigned) {} +// PRODUCTION_IO +using namespace Display13Power; +using namespace AlderLakeN; +static void firmware(IOMemoryMap &map,unsigned ddi,bool request) { + map.at(Driver)=0xFC0F;map.at(DDI)=15;map.at(Fuses)=0x8E380000; + map.at(AUX)=(request?3U:1U)<<(2*ddi); + map.at(0x60400)=0x82030006|((ddi+1)<<27);map.at(0x70008)=0xC0000000; + map.at(0x64000+ddi*0x100)=0x80000006;map.at(0x46140)=(ddi+1)<<28; + map.at(0x60540)=0x80040300;map.at(0x60030)=0x7E100000; + map.at(0x60034)=map.at(0x60044)=0x800000;map.at(0x60040)=0x100000; +} +struct FaultIO:Display13FBBootIO { + using Display13FBBootIO::Display13FBBootIO; + bool loseSource {},loseRequest {}; + void write(uint32_t reg,uint32_t value) { + Display13FBBootIO::write(reg,value); + if(reg==AUX) { + if(loseSource)map->at(0x60540)^=1; + if(loseRequest)map->at(AUX)&=~10U; + } + } +}; +int main() { + unsigned cases=0; + for(unsigned ddi=0;ddi<2;++ddi)for(bool requested:{false,true})for(bool peer:{false,true}) { + IOMemoryMap map;firmware(map,ddi,requested);const auto other=1-ddi; + if(peer)map.at(AUX)|=3U<<(2*other); + const auto initial=map.at(AUX);Display13FBBootIO io{&map};NativeBridge bridge;MainLinkAUX link; + assert(bridge.begin(io,ddi,3).status==Status::Complete); + const auto snapshot=readDisplay(io,true);assert(snapshot.status==ReadoutStatus::Complete && snapshot.count==139); + const auto heldResult=holdDPBootAUX(io,snapshot,bridge,link,ddi); + assert(heldResult.status==Status::Complete && heldResult.writes==(requested?0U:1U)); + assert(!link.owner && link.bootPending && link.port==ddi && !bridge.nativeAUXRefs(ddi)); + assert(link.lease.owner==&bridge.state && bridge.state.refs[ddi?6:8]==1); + assert(link.begin(io,bridge,7,ddi).status==Status::NotReady && link.end(io,bridge,7,ddi).status==Status::NotReady); + assert(holdDPBootAUX(io,readDisplay(io,true),bridge,link,ddi).status==Status::NotReady); + assert(bridge.getAUX(io,ddi).status==Status::Complete && bridge.putAUX(io,ddi).status==Status::Complete); + assert(bridge.state.refs[ddi?6:8]==1 && !bridge.nativeAUXRefs(ddi)); + const auto after=readDisplay(io,true);const auto requestBefore=map.at(AUX); + assert(adoptDPBootAUX(io,after,bridge,link,7,ddi).status==Status::Complete); + assert(link.owner==7 && !link.bootPending && !bridge.nativeAUXRefs(ddi) && map.at(AUX)==requestBefore); + assert(adoptDPBootAUX(io,after,bridge,link,8,ddi).status==Status::NotReady); + assert(bridge.adoptBootPath(9,0,ddi).status==Status::Complete); + assert(link.begin(io,bridge,7,ddi).status==Status::Complete); + assert(link.end(io,bridge,7,ddi).status==Status::NotReady); // Live GOP cannot release. + map.at(0x64000+ddi*0x100)=0x86; + assert(link.end(io,bridge,7,ddi).status==Status::Complete && !link.owner && !link.lease.owner); + assert(map.at(AUX)==initial); // Borrowed firmware/peer requests survive; only our request clears. + assert(bridge.putDDI(io,ddi).status==Status::Complete && bridge.putPG(io,1).status==Status::Complete); + for(auto refs:bridge.state.refs)assert(!refs); + io.write(0x60540,0);assert(map.at(0x60540)==0x80040300); // Boot IO cannot write link registers. + assert(!io.readable(0x1000000) && !Display13FBBootIO{nullptr}.readable(0)); + ++cases; + } + for(unsigned fault=0;fault<9;++fault) { + IOMemoryMap map;firmware(map,0,false);Display13FBBootIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete);auto boot=readDisplay(io,true); + if(fault==0)map.at(AUX)=0; + if(fault==1)map.at(0x60540)=0x80000000; + if(fault==2)b.bootPGProtected=false; + if(fault==3)b.bootDDIProtected=false; + if(fault==4)b.bootAdopted=true; + if(fault==5)b.bootPort=1; + if(fault==6)b.ddiRefs[0]=0; + if(fault==7)b.state.suspended=true; + if(fault==8)b.state.faulted=true; + assert(holdDPBootAUX(io,boot,b,l,0).status==Status::NotReady && !l.lease.owner && !l.bootPending); + } + for(unsigned fault=0;fault<7;++fault) { + IOMemoryMap map;firmware(map,0,false);Display13FBBootIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete && holdDPBootAUX(io,readDisplay(io,true),b,l,0).status==Status::Complete); + auto boot=readDisplay(io,true);uintptr_t target=7;unsigned port=0; + if(fault==0)target=0;if(fault==1)port=1;if(fault==2)map.at(AUX)=0; + if(fault==3)map.at(0x60540)^=1;if(fault==4)b.bootPGProtected=false; + if(fault==5)b.bootAdopted=true;if(fault==6)b.state.suspended=true; + assert(adoptDPBootAUX(io,boot,b,l,target,port).status==Status::NotReady && l.bootPending && !l.owner && l.lease.owner); + } + {IOMemoryMap map;firmware(map,0,false);FaultIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete);io.loseSource=true; + assert(holdDPBootAUX(io,readDisplay(io,true),b,l,0).status==Status::RequestLost); + assert(b.state.faulted && l.bootPending && l.lease.owner && map.at(AUX)==3 && !b.nativeAUXRefs(0));} + {IOMemoryMap map;firmware(map,0,false);FaultIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete);io.loseRequest=true; + assert(holdDPBootAUX(io,readDisplay(io,true),b,l,0).status==Status::RequestLost); + assert(b.state.faulted && !l.bootPending && !l.lease.owner && map.at(AUX)==1);} + printf("PASS DP boot AUX: %u real-IO ownership lifecycles, transaction independence, refusal/fault retention and power-only writes\n",cases); +} diff --git a/tools/test.py b/tools/test.py index d9ab70a4..5527b82a 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,7 +7,7 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['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 += ['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_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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] From 01ac1b570309e65776f8856ad4202bec2b8e93ea Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:36:39 +0900 Subject: [PATCH 39/58] Separate shared Display adapter selection from product qualification --- AppleIntelTGLCompat/AppleIntelTGLCompat.cpp | 5 +++-- WhateverXe/kern_vbt.hpp | 10 +++++----- WhateverXe/kern_xelp_display_resources.hpp | 14 ++++++++++++++ WhateverXe/kern_xelp_panel_power.hpp | 5 +++-- WhateverXe/kern_xelp_platform.hpp | 2 +- WhateverXe/kern_xelp_probe.hpp | 2 +- tests/firmware_ports.cpp | 16 ++++++++++++++++ tests/provider_binding.py | 2 +- tests/provider_binding_fixture.cpp | 3 ++- tests/xelp_panel_power.cpp | 1 + tests/xelp_platform.cpp | 9 +++++++++ 11 files changed, 56 insertions(+), 13 deletions(-) diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index bd6ca41a..d34c89b2 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp @@ -30,8 +30,9 @@ static bool hardwareAllowed(void *controller) { auto service = static_cast(controller); auto pci = OSDynamicCast(IOPCIDevice, service->getProvider()); const auto profile=XELP::deviceProfile(pci); - if(!profile.admitted() || (profile.family!=XELP::Family::TigerLake && profile.family!=XELP::Family::AlderLakeS))return false; - if(profile.family==XELP::Family::TigerLake) + const auto adapter=XELP::displayAdapter(profile.family); + if(!profile.admitted() || (adapter!=XELP::DisplayAdapter::TGL && adapter!=XELP::DisplayAdapter::ADLS))return false; + if(adapter==XELP::DisplayAdapter::TGL) __atomic_store_n(&legacyTGLAccess,manager(controller),__ATOMIC_RELEASE); return true; } diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp index 4254377a..1294f998 100644 --- a/WhateverXe/kern_vbt.hpp +++ b/WhateverXe/kern_vbt.hpp @@ -130,20 +130,20 @@ inline void panelFacts(const uint8_t *b,size_t header,size_t size,Ports &ports) // Return the donor's physical-DDI slot, never an FB index. Display13 VBT // retains C/D/E labels even on ADL-N, whose physical Combo set is only A/B. inline uint32_t ddi(Family family,uint32_t dvo) { + const auto adapter=displayAdapter(family); const uint8_t dp[]={10,7,8,9,11,13,15,17,19}; 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(family==Family::TigerLake)return p==2 ? PortCount : p; - if(family==Family::AlderLakeN)return p<2 ? p : p>=5 ? p-2 : PortCount; - if(family==Family::AlderLakeS)return p==0 ? 0 : p<5 ? p+2 : PortCount; + 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::ADLS)return p==0 ? 0 : p<5 ? p+2 : PortCount; } return PortCount; } inline Ports parse(const void *data,size_t length,Family family) { Ports r; if(!data || !length)return r; - if(family!=Family::TigerLake && family!=Family::AlderLakeN && - family!=Family::AlderLakeS){r.status=Status::Platform;return r;} + if(displayAdapter(family)==DisplayAdapter::Unknown){r.status=Status::Platform;return r;} const auto p=static_cast(data);r.status=Status::Header; if(length<48 || memcmp(p,"$VBT",4))return r; const size_t header=u16(p+22),size=u16(p+24),bdb=u32(p+28); diff --git a/WhateverXe/kern_xelp_display_resources.hpp b/WhateverXe/kern_xelp_display_resources.hpp index 81720be1..ad5e7fd0 100644 --- a/WhateverXe/kern_xelp_display_resources.hpp +++ b/WhateverXe/kern_xelp_display_resources.hpp @@ -4,6 +4,20 @@ #define KERN_XELP_DISPLAY_RESOURCES_HPP #include "kern_xelp_devices.hpp" namespace XELP { +// Select an implemented Display adapter independently of GT admission. Linux +// v6.12 intel_display_device.c binds ADL-S and RPL-S to adl_s_desc; intel_bios.c +// consequently uses the same PHY-based VBT mapping for both. This selector +// grants no physical identity, stepping, fuse or donor qualification. +enum class DisplayAdapter : uint32_t { Unknown, TGL, ADLS, ADLN }; +constexpr DisplayAdapter displayAdapter(Family family) { + switch(family) { + case Family::TigerLake:return DisplayAdapter::TGL; + case Family::AlderLakeS: + case Family::RaptorLakeS:return DisplayAdapter::ADLS; + case Family::AlderLakeN:return DisplayAdapter::ADLN; + default:return DisplayAdapter::Unknown; + } +} struct ADLSComboResources { // Linux v6.12: ADL-S Combo PHY A..E map to DDI A, TC1..TC4. static constexpr uint32_t physicalCount=5; diff --git a/WhateverXe/kern_xelp_panel_power.hpp b/WhateverXe/kern_xelp_panel_power.hpp index d1d2c9a5..dcd3ca57 100644 --- a/WhateverXe/kern_xelp_panel_power.hpp +++ b/WhateverXe/kern_xelp_panel_power.hpp @@ -11,8 +11,9 @@ struct PanelPower { // outside the native Combo panel coordinates; each needs its own adapter. static bool nativePPS0(const VBT::Ports &v,Family family) { const auto &p=v.panel; - const bool backend=(family==Family::TigerLake && p.ddi<2) || - (family==Family::AlderLakeS && p.ddi==0); + const auto adapter=displayAdapter(family); + const bool backend=(adapter==DisplayAdapter::TGL && p.ddi<2) || + (adapter==DisplayAdapter::ADLS && p.ddi==0); if(!backend || !v.valid() || v.unmapped || !p.valid || !p.backlightValid || !p.pwmPresent || p.backlightType!=2 || p.controller)return false; const auto &port=v.port[p.ddi];const auto aux=p.ddi?0x10U:0x40U; diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 81beb4b5..54c19eb4 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -81,7 +81,7 @@ struct ProbeConfig { !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. - if(profile.family==Family::AlderLakeS) { + if(displayAdapter(profile.family)==DisplayAdapter::ADLS) { const auto capacity=ADLSComboBackend::mask(profile.donor); return profile.display==Display::XeD && combo && !(combo&~capacity) && comboBackend==capacity && !(excluded&~PortMask) && !(excluded&combo) && !legacy && !typeC && !legacyBackend; diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index 9539a434..ace6f8bf 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -29,7 +29,7 @@ template uint32_t legacyTGLMode(IO &io,uint32_t ddi,uint32_t selected) } template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t manager,uintptr_t record,const uint32_t *c) { if(!b.bound(manager,record,c))return 0; - if(b.config.profile.family==Family::AlderLakeS) { + if(displayAdapter(b.config.profile.family)==DisplayAdapter::ADLS) { if(!(b.config.combo&b.config.comboBackend&(1U<=9 && c[1]<=12)) && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; const bool dp=c[1]==(c[0]>=3?c[0]+6:c[0]+1) && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index 2697c060..206cab7e 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -72,6 +72,22 @@ int main(int argc,char **argv){ {0x68c6,9,0,0x30},{1,0,0},{0x100,0,0}}); auto ds=parse(desktop.data(),desktop.size(),Family::AlderLakeS); assert(ds.valid() && ds.present==0x39 && ds.port[3].fixedTMDS() && ds.port[3].ddc==2); + // Same Display adapter across product families, without admitting a new GT. + auto rs=parse(desktop.data(),desktop.size(),Family::RaptorLakeS); + assert(rs.valid() && rs.present==ds.present && rs.unmapped==ds.unmapped && rs.ambiguous==ds.ambiguous); + for(unsigned d=0;d mmio(0x1000000);alignas(8) uint8_t platform[0x1C0] {}; pci.identity=adln?0x46D18086:0x9A498086; if(adls){pci.identity=0x46908086;pci.revision=12;} + if(!strcmp(argv[1],"rpls")){pci.identity=0xA7808086;pci.revision=12;} if(!strcmp(argv[1],"adls-revision"))pci.revision=0; if(!strcmp(argv[1],"tgl-g4"))pci.identity=0x9A788086; if(!strcmp(argv[1],"tgl-9a40"))pci.identity=0x9A408086; diff --git a/tests/xelp_panel_power.cpp b/tests/xelp_panel_power.cpp index 392ef144..50d5aa11 100644 --- a/tests/xelp_panel_power.cpp +++ b/tests/xelp_panel_power.cpp @@ -15,6 +15,7 @@ int main() { } panel.controller=0;panel.backlightType=2; assert(P::nativePPS0(v,XELP::Family::AlderLakeS)); + assert(P::nativePPS0(v,XELP::Family::RaptorLakeS)); v.present|=2;v.port[1]=v.port[0];assert(!P::nativePPS0(v,XELP::Family::TigerLake));v.present=1; const uint32_t zero[5]={}; auto plan=P::select(panel,zero,0,0,0); diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index fe8a391d..950d1c60 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -12,6 +12,15 @@ struct IO { uint32_t read(uint32_t r){reads.push_back(r);return regs.at(r);} }; int main() { + 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); + assert(displayAdapter(p.family)==DisplayAdapter::ADLS && p.gt==GT::Unknown && !p.admitted()); + ProbeConfig c;c.profile=p;c.combo=0x19;c.comboBackend=0x19; + assert(!c.valid()); // A known Display adapter is not runtime qualification. + assert(!identify(id,12,0x46908086).admitted()); + } + for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,12U,255U}) { const auto p=identify((id<<16)|0x8086,rev); const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78,adln=id>=0x46D0 && id<=0x46D4; From 48da4c3a121d72a270b3735b0977924ff02795b8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:44:06 +0900 Subject: [PATCH 40/58] Separate Render qualification from Display12 startup and port dispatch --- WhateverXe/kern_host.cpp | 3 +- WhateverXe/kern_xe.cpp | 94 ++++++++++++++------------ WhateverXe/kern_xe.hpp | 13 ++-- tests/adls_aux_parent.py | 4 +- tests/adls_lane_power.py | 4 +- tests/adls_panel_pwm.py | 2 +- tests/boot_arguments.py | 45 +++++++++++- tests/display_contract_adapter.py | 6 +- tests/tgl_agdc_port_status.py | 4 +- tests/tgl_clock_order.py | 4 +- tests/tgl_combo_aux_lifetime.py | 6 +- tests/tgl_combo_pll_write.py | 2 +- tests/tgl_combo_signal_guard.py | 6 +- tests/tgl_hdmi_wiring_fixture.cpp | 8 +-- tests/tgl_kernel_admission.py | 8 +-- tests/tgl_mode_native_fixture.cpp | 6 +- tests/tgl_mode_serial_fixture.cpp | 2 +- tests/tgl_panel_depth_route.py | 4 +- tests/tgl_panel_timing.py | 4 +- tests/tgl_port_map_fixture.cpp | 2 +- tests/tgl_probe_fixture.cpp | 2 +- tests/tgl_training_leases_fixture.cpp | 8 +-- tests/tgl_training_pattern_fixture.cpp | 6 +- tests/tgl_wiring_fixture.cpp | 2 +- 24 files changed, 147 insertions(+), 98 deletions(-) diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 0f974c8b..f4eb7fff 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -32,7 +32,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { const bool published=gpu && WIOKit::awaitPublishing(gpu); const auto profile=published ? XELP::deviceProfile(gpu) : XELP::Profile {}; active=profile.admitted(); - backlightEnabled=active && (profile.family==XELP::Family::TigerLake || profile.family==XELP::Family::AlderLakeS) && + const auto display=XELP::displayAdapter(profile.family); + backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS) && info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple; backlightGPU=backlightEnabled ? gpu : nullptr; if(published) { diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 47bc9bdd..a3216296 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3733,7 +3733,8 @@ void XE::init() { void XE::selectProfile(const XELP::Profile &profile) { xeProfile=profile; - adlsDetected=profile.admitted() && profile.family==XELP::Family::AlderLakeS; + adlsDetected=profile.admitted() && profile.gt==XELP::GT::ADLSSmall; + adlsDisplaySelected=profile.admitted() && XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLS; adlnNativeRCSProfileRequested=false; adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN && profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; @@ -3751,8 +3752,8 @@ void XE::selectProfile(const XELP::Profile &profile) { } return; } - tglRequested=profile.admitted() && (profile.family==XELP::Family::TigerLake || - (adlsDetected && XELP::hostOS(XELP::kernelOS(version)))) && + tglRequested=profile.admitted() && (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::TGL || + (adlsDisplaySelected && XELP::hostOS(XELP::kernelOS(version)))) && !checkKernelArgument("-wex-tgl-off"); tglInactivePorts=tglPG1Poll=tglClockOrderRequested=tglTrainingLeaseRequested=tglRequested; tglProgramEnabled=!checkKernelArgument("-wex-tgl-prog-off"); @@ -4037,7 +4038,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { bool XE::xelpBindProbe(void *controller) { if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false; XELP::ProbeConfig config;config.profile=xeProfile; - if(adlsDetected) { + if(adlsDisplaySelected) { if(!tglHDMIWiringApplied || !adlsWiring.valid || !adlsWiring.selected())return false; config.combo=adlsWiring.selected();config.comboBackend=AlderLakeS::ComboBackendMask; config.excluded=adlsWiring.excluded; @@ -4172,7 +4173,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { uint32_t registers[][2] {{0xC4000, 0}, {0x44470, 0}, {0x1638A0, 0}, {0x163890, 0}, {0x163894, 0}}; auto base = reinterpret_cast(mmio->getVirtualAddress()); - if (!adlsDetected && base && mmio->getLength() >= 0x1638A4) { + if (!adlsDisplaySelected && base && mmio->getLength() >= 0x1638A4) { for (auto ® : registers) reg[1] = *reinterpret_cast(base + reg[0]); pci->setProperty("TGLBootPortRegisters", registers, sizeof(registers)); @@ -10269,8 +10270,8 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t // Correct both reads and writes through the native tables; retain // native divisors and power/lock sequencing. ADL-S also uses slot2. const uint64_t before[] {0x164000,0x164080,0x164200,0x164004,0x164084,0x164204}; - const uint64_t after[] {0x164284,0x16428C,adlsDetected?0x164294ULL:0x164200ULL, - 0x164288,0x164290,adlsDetected?0x164298ULL:0x164204ULL}; + const uint64_t after[] {0x164284,0x16428C,adlsDisplaySelected?0x164294ULL:0x164200ULL, + 0x164288,0x164290,adlsDisplaySelected?0x164298ULL:0x164204ULL}; auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams", address, size); auto site = reinterpret_cast(address + 0x9A0E8); @@ -10283,7 +10284,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t ready = !memcmp(site, after, sizeof(after)); } IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIComboPLLReady", ready); - adlsPLLTableReady=adlsDetected && ready; + adlsPLLTableReady=adlsDisplaySelected && ready; patcher.clearError(); } { @@ -10440,7 +10441,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t // PANEL_TIMING_INSTALL_END } - if(adlsDetected) { + if(adlsDisplaySelected) { // ADLS_PANEL_PWM_INSTALL: preflight the native lifecycle and PWM enable site. const char *light="__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; const char *brightness="__ZN31AppleIntelFramebufferController14hwSetBacklightEj"; @@ -10806,7 +10807,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (ready) tglPG1FuseReadCaller = fn + 0x2C7; IORegistryEntry::getRegistryRoot()->setProperty("TGLPG1PollReady", ready); } - if(adlsDetected) { + if(adlsDisplaySelected) { const auto symbol="__ZN17AppleIntelPortHAL4initEP10PortConfig"; const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xFE}; const auto fn=patcher.solveSymbol(index,symbol,address,size); @@ -11303,7 +11304,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPAltFiaReady",tglDPAltFiaReady); - if(adlsDetected) { + if(adlsDisplaySelected) { // Keep the physical DDI in every port/power/display object. Only the // native Combo PLL indices are translated, under the firmware-route entry // guards below (physical 0/3/4 >> 1 gives PHY 0/1/2). The removed instructions are profiling counters. @@ -11393,7 +11394,7 @@ void XE::tglApplyPanelTiming(void *controller,bool apply) { const auto cfg=reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*ddi); if(!port || port[0x117] || *reinterpret_cast(port+0x548)!=cfg || cfg[0]!=ddi || cfg[2]!=0 || cfg[3]!=0 || *reinterpret_cast(port+0x550)!=1 || - (s->adlsDetected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg)))return; + (s->adlsDisplaySelected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg)))return; auto access=*reinterpret_cast(c+0xC40); auto power=*reinterpret_cast(c+0xC8); if(!access || !power || *reinterpret_cast(power+0x10)!=controller || power[0x18] || @@ -11434,7 +11435,7 @@ uint32_t XE::wrapTGLPanelTiming(void *controller) { // and donor-owned; only its period, polarity and duty arithmetic are replaced. bool XE::adlsPanelPWMTarget(void *controller,void *&access,void *&power) { auto s=callbackXE;auto c=static_cast(controller);access=power=nullptr; - if(!s->adlsDetected || s->xeProfile.family!=XELP::Family::AlderLakeS || !s->adlsPanelPWMReady || !s->tglActive || !c || c!=s->tglObservationController || + if(!s->adlsDisplaySelected || XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::ADLS || !s->adlsPanelPWMReady || !s->tglActive || !c || c!=s->tglObservationController || !s->tglTCNativeFunctions.complete() || *s->tglTCNativeFunctions.controllerSlot!=controller || !XELP::PanelPower::nativePPS0(s->firmwarePorts,s->xeProfile.family) || c[0x918] || *reinterpret_cast(c+0xD0) || *reinterpret_cast(c+0xD40)!=s->tglPlatformCopy || @@ -11463,7 +11464,7 @@ bool XE::adlsCapturePanelPWM(void *controller) { } uint32_t XE::wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void *timing) { auto s=callbackXE; - if(s->adlsDetected && s->adlsWiring.panel) { + if(s->adlsDisplaySelected && s->adlsWiring.panel) { if(!adlsCapturePanelPWM(controller))return kIOReturnNotReady; *reinterpret_cast(static_cast(controller)+0xE60)=s->adlsPanelPWM.period; } @@ -11471,7 +11472,7 @@ uint32_t XE::wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void } uint32_t XE::wrapADLSPanelBacklight(void *controller,uint32_t brightness) { auto s=callbackXE; - if(!s->adlsDetected || !s->adlsWiring.panel) + if(!s->adlsDisplaySelected || !s->adlsWiring.panel) return FunctionCast(wrapADLSPanelBacklight,s->orgADLSPanelBacklight)(controller,brightness); void *access,*power;uint32_t duty; if(!adlsCapturePanelPWM(controller) || !adlsPanelPWMTarget(controller,access,power) || @@ -11488,7 +11489,7 @@ uint32_t XE::wrapADLSPanelBacklight(void *controller,uint32_t brightness) { } void XE::wrapADLSPanelPWMEnable(void *access) { auto s=callbackXE;uint32_t value=0x80000000; - if(s->adlsDetected && s->adlsWiring.panel) { + if(s->adlsDisplaySelected && s->adlsWiring.panel) { void *expected,*power; if(!s->adlsPanelPWM.valid() || !adlsPanelPWMTarget(s->tglObservationController,expected,power) || access!=expected)return; value|=s->adlsPanelPWM.polarity; @@ -11508,7 +11509,7 @@ bool XE::wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, O // explicit profile or a mismatched donor connector must not be partly applied. bool XE::wrapTGLGetOSInformation(void *controller) { const auto r=FunctionCast(wrapTGLGetOSInformation,callbackXE->orgTGLGetOSInformation)(controller); - if(r && !callbackXE->adlsDetected)tglBeginTCColdProtection(controller); + if(r && !callbackXE->adlsDisplaySelected)tglBeginTCColdProtection(controller); auto self=callbackXE; if(!r)return r; auto c=static_cast(controller); @@ -11531,7 +11532,7 @@ bool XE::wrapTGLGetOSInformation(void *controller) { } auto copy=reinterpret_cast(self->tglPlatformCopy); if(copy!=platform)memcpy(copy,platform,sizeof(self->tglPlatformCopy)); - const bool wiringApplied=self->adlsDetected ? self->adlsWiring.apply(copy,sizeof(self->tglPlatformCopy)) : + const bool wiringApplied=self->adlsDisplaySelected ? self->adlsWiring.apply(copy,sizeof(self->tglPlatformCopy)) : self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && TGLComboDP::accepts(self->tglComboDPMask,copy,sizeof(self->tglPlatformCopy)) && self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) && @@ -11541,8 +11542,8 @@ bool XE::wrapTGLGetOSInformation(void *controller) { } auto config=reinterpret_cast(copy+0x20); platform=copy;self->tglHDMIWiringApplied=true; - if(self->adlsDetected)IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPWiringApplied",self->adlsWiring.dp,32); - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDetected?self->adlsWiring.hdmi:self->tglHDMIWiring.hdmi,32); + if(self->adlsDisplaySelected)IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPWiringApplied",self->adlsWiring.dp,32); + IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDisplaySelected?self->adlsWiring.hdmi:self->tglHDMIWiring.hdmi,32); // Preserve the existing TC2 machine's diagnostic names for old collectors. IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBTopologyApplied",bool(self->tglHDMIWiring.hdmi&2)); if(self->tglHDMIWiring.hdmi&2)IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBPortConfig",config+6,24); @@ -11572,12 +11573,12 @@ bool XE::tglLoadTCWiring(IOService *provider) { self->tglTCWiringExplicit=property!=nullptr; auto data=OSDynamicCast(OSData,property); self->tglTCWiring=property ? (data?TGLTCWiring::fromProperty(data->getBytesNoCopy(),data->getLength()):TGLTCWiring{}) : TGLTCWiring::fromMasks(0,0,0); - if(!property && self->xeProfile.family==XELP::Family::TigerLake) + if(!property && XELP::displayAdapter(self->xeProfile.family)==XELP::DisplayAdapter::TGL) self->tglTCAutoCandidates=self->firmwarePorts.tglDPCandidates(self->tglAUXParentMask&0x18); 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. - if(!property && self->xeProfile.family==XELP::Family::TigerLake) + if(!property && XELP::displayAdapter(self->xeProfile.family)==XELP::DisplayAdapter::TGL) self->tglDPAltCandidates=self->firmwarePorts.tglDPAltCandidates(self->tglAUXParentMask&0x78); IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPAltCandidates",self->tglDPAltCandidates,32); const auto &w=self->tglTCWiring; @@ -11593,7 +11594,7 @@ bool XE::tglLoadTCWiring(IOService *provider) { // overrides firmware. Only unambiguous firmware TMDS uses an admitted backend. bool XE::tglLoadHDMIWiring(IOService *provider) { auto self=callbackXE; - if(self->adlsDetected) { + if(self->adlsDisplaySelected) { if(!self->tglHDMIWiringLoaded) { self->tglHDMIWiringLoaded=true; self->tglHDMIB=false; @@ -11695,7 +11696,8 @@ XELP::DisplayContract::Mask XE::display13Available() const { } bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { - if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady)) { + if((callbackXE->adlsDetected && !callbackXE->adlsNativeReady) || + (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady)) { that->setProperty("ADLSFramebufferPrerequisitesMissing",true);return false; } tglRecordStartOrder(3,that); @@ -11735,7 +11737,7 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { auto self=callbackXE; XELP::DisplayContract::Routes routes; routes.hdmi=self->tglHDMIB;routes.panel=self->firmwarePorts.panel.valid; - routes.combo=self->adlsDetected ? self->adlsWiring.dp!=0 : self->tglComboDPMask!=0; + routes.combo=self->adlsDisplaySelected ? self->adlsWiring.dp!=0 : self->tglComboDPMask!=0; routes.legacy=self->tglTCWiring.legacy || self->tglTCAutoCandidates; routes.alt=self->tglDPAltCandidates;routes.leaseRequested=self->tglTrainingLeaseRequested; const auto contract=XELP::DisplayContract::display12(self->display12Available(),routes); @@ -11826,7 +11828,8 @@ uint32_t XE::wrapTGLLaneMask(void *port) { } bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { - if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady))return false; + if((callbackXE->adlsDetected && !callbackXE->adlsNativeReady) || + (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return false; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { SYSLOG("igfx", "AlderLakeN native RCS profile qualification rejected accelerator start"); return false; @@ -12223,7 +12226,8 @@ void XE::tglObserveAccelerator(IOService *that, uint32_t stage, bool done, uint6 } IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SInt32 *score) { - if(callbackXE->adlsDetected && (!callbackXE->adlsNativeReady || !callbackXE->adlsDisplayHooksReady))return nullptr; + if((callbackXE->adlsDetected && !callbackXE->adlsNativeReady) || + (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return nullptr; if (!callbackXE->tglAcceleratorRequested) return nullptr; auto pci = OSDynamicCast(IOPCIDevice, provider); const uint32_t identity = pci ? pci->configRead32(0) : 0; @@ -14248,7 +14252,7 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { // TC-labelled fixed DP routes use Combo mode1 on ADL-S at both // boot and hotplug. Preserve the original caller and immutable record guards. const auto requestedMode=mode; - mode=self->adlsWiring.hotplugMode(self->adlsDetected && self->tglModeResultReady, + mode=self->adlsWiring.hotplugMode(self->adlsDisplaySelected && self->tglModeResultReady, traceConfig,self->tglPlatformCopy, reinterpret_cast(caller),self->tglNativeLegacyHPDCaller,mode); if(mode!=requestedMode) { @@ -15061,7 +15065,7 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { } uint32_t XE::wrapTGLGMBus(void *controller, uint32_t bus, uint32_t speed, void *request) { - if(callbackXE->adlsDetected && !callbackXE->tglHDMIDDCEligible(bus)) { + if(callbackXE->adlsDisplaySelected && !callbackXE->tglHDMIDDCEligible(bus)) { if(request)*reinterpret_cast(static_cast(request)+0x1C)=0xE00002C2; return 0xE00002C2; } @@ -15129,13 +15133,13 @@ uint32_t XE::tglPanelDepthPort(void *port) { auto s=callbackXE;auto p=static_cast(port); auto c=static_cast(s->tglObservationController); if(!p || !c || !s->tglActive || !s->tglPanelDepthReady || !tglAUXLock || - (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected))return 9; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected))return 9; auto cfg=*reinterpret_cast(p+0x548); if(!cfg || cfg[0]>1 || cfg[2]!=0 || cfg[3]!=0 || p[0x117] || *reinterpret_cast(p+0x578)!=*reinterpret_cast(c+0xC40) || !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[cfg[0]].internal() || !s->firmwarePorts.port[cfg[0]].dp())return 9; - if(s->adlsDetected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg))return 9; + if(s->adlsDisplaySelected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg))return 9; return cfg[0]; } void XE::wrapTGLModeTiming(void *controller,void *fb,void *timing) { @@ -15395,7 +15399,7 @@ uint32_t XE::wrapTGLAGDCPortStatus(void *controller, void *request, void *output const uint32_t excluded=!s->tglTCWiringLoaded || !w.valid ? 0 : s->tglTCWiringExplicit ? w.excluded : v.valid() && !v.unmapped ? TGLTCWiring::Mask&~v.present : 0; if (req && s->tglInactivePorts && s->tglPortByIndex && r == 0 && output && - s->xeProfile.family==XELP::Family::TigerLake && !s->adlsDetected && + XELP::displayAdapter(s->xeProfile.family)==XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected && id>=4 && id<=9 && (excluded&(1U<<(id-1))) && controller && controller == s->tglObservationController && *reinterpret_cast(static_cast(controller) + 0xD40) == s->tglPlatformCopy) { auto port = static_cast(reinterpret_cast(s->tglPortByIndex)(id - 1)); @@ -16303,7 +16307,7 @@ volatile uint8_t *XE::tglClockOrderTarget(void *controller, void *port, void *pa } void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { - if(callbackXE->adlsDetected && !adlsClockTarget(controller,port,otherPort)) { + if(callbackXE->adlsDisplaySelected && !adlsClockTarget(controller,port,otherPort)) { IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; } tglTransitionRecord(32,false,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); @@ -16391,7 +16395,7 @@ void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32 bool XE::wrapADLSPortInit(void *port,uint32_t *config) { const bool result=FunctionCast(wrapADLSPortInit,callbackXE->orgADLSPortInit)(port,config); - if(!result || !callbackXE->adlsDetected || !config || !callbackXE->adlsWiring.matches(config))return result; + if(!result || !callbackXE->adlsDisplaySelected || !config || !callbackXE->adlsWiring.matches(config))return result; auto bytes=static_cast(port); const bool ready=callbackXE->adlsPortInitReady && callbackXE->adlsWiring.bound(callbackXE->tglPlatformCopy,config) && @@ -16405,7 +16409,7 @@ bool XE::wrapADLSPortInit(void *port,uint32_t *config) { void XE::wrapTGLEnableCombo(void *port) { auto b=static_cast(port); auto config=b?*reinterpret_cast(b+0x548):nullptr; - if(callbackXE->adlsDetected) { + if(callbackXE->adlsDisplaySelected) { if(!config || !callbackXE->adlsPortInitReady)return; // PHY A also supplies the shared reference even with no wired port. if(config[0]==0) {FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port);return;} @@ -16661,7 +16665,7 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { if (ctl && *reinterpret_cast(ctl + 0xC40) == access) { const auto refCode = *reinterpret_cast(ctl + 0xEA8); const auto nominal = value; - if (TGLComboPLLFraction::adjust(refCode, callbackXE->adlsDetected && reg==0x164294 ? 0x164284 : reg, value)) { + if (TGLComboPLLFraction::adjust(refCode, callbackXE->adlsDisplaySelected && reg==0x164294 ? 0x164284 : reg, value)) { const uint64_t detail[] {refCode, nominal}; tglTransitionRecord(70,true,access,reg,nominal,value,detail,sizeof(detail)); } @@ -17033,7 +17037,7 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { uint8_t *XE::adlsPowerTarget(void *power) { auto s=callbackXE;auto p=static_cast(power); auto c=static_cast(s->tglObservationController); - if(!s->adlsDetected || !s->adlsPowerReady || !p || !c || + if(!s->adlsDisplaySelected || !s->adlsPowerReady || !p || !c || !s->adlsWiring.valid || !s->adlsWiring.selected() || *reinterpret_cast(p+0x10)!=c || *reinterpret_cast(c+0xC8)!=p || *reinterpret_cast(p+0x78)!=*reinterpret_cast(c+0xC40))return nullptr; @@ -17110,12 +17114,12 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void auto p=static_cast(port); auto ctl=static_cast(self->tglObservationController); if(!p || !ctl || overrideConfig || !self->tglActive || !self->tglComboSignalReady || - (self->xeProfile.family!=XELP::Family::TigerLake && !self->adlsDetected) || !self->firmwarePorts.valid())return false; + (XELP::displayAdapter(self->xeProfile.family)!=XELP::DisplayAdapter::TGL && !self->adlsDisplaySelected) || !self->firmwarePorts.valid())return false; auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; - const bool adls=self->adlsDetected && self->adlsWiring.boundSST(self->tglPlatformCopy,config); - const bool fixed=!self->adlsDetected && TGLComboDP::bound(self->tglComboDPMask,self->tglPlatformCopy,config); - if(self->adlsDetected && !adls)return false; + const bool adls=self->adlsDisplaySelected && self->adlsWiring.boundSST(self->tglPlatformCopy,config); + const bool fixed=!self->adlsDisplaySelected && TGLComboDP::bound(self->tglComboDPMask,self->tglPlatformCopy,config); + if(self->adlsDisplaySelected && !adls)return false; XELP::Display12SSTRoute route; const XELP::Display12SSTRoutes routes{adls?self->adlsWiring.dp:fixed?self->tglComboDPMask:0,0,0}; if(!config || !routes.resolve(config[0],config[2],*reinterpret_cast(p+0x550),route) || @@ -17470,11 +17474,11 @@ void XE::tglComboAUXReference(void *port,bool acquire) { 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() || - (s->xeProfile.family!=XELP::Family::TigerLake && !s->adlsDetected) || *f.controllerSlot!=ctl)return; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || *f.controllerSlot!=ctl)return; auto config=*reinterpret_cast(p+0x548); - const bool adls=s->adlsDetected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); - const bool fixed=!s->adlsDetected && TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config); - if(s->adlsDetected && !adls)return; + 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; const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index dd5006d5..0331935a 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -82,7 +82,10 @@ class XE { bool tglActive {false}; bool adlnRequested {false}; bool adlnDetected {false}; - bool adlsDetected {},adlsNativeReady {},adlsDisplayHooksReady {}; + // Render and Display selections are independent; shared PHY/port code + // must not depend on which GT implementation the product uses. + bool adlsDetected {},adlsNativeReady {}; + bool adlsDisplaySelected {},adlsDisplayHooksReady {}; bool adlsPortInitReady {},tglHPDHookReady {}; bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {},adlsPanelPWMReady {}; XELP::PanelPWM adlsPanelPWM; @@ -338,8 +341,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); - bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDetected?adlsWiring.matches(config,false):tglHDMIWiring.matches(config));} - bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDetected?adlsWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} + 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));} bool tglGMBusReady {false}; mach_vm_address_t orgTGLGMBus {}; @@ -448,8 +451,8 @@ class XE { XELP::Display12SSTRoutes tglSSTRoutes() const { auto routes=tglTCWiring.sstRoutes(tglAUXParentMask, tglTCAutoCandidates&__atomic_load_n(&tglTCAutoRegistered,__ATOMIC_ACQUIRE),tglDPAltCandidates); - routes.combo=adlsDetected ? (adlsWiring.valid?adlsWiring.dp:0) : tglComboDPMask; - routes.panel=adlsDetected && adlsWiring.valid ? adlsWiring.panel : 0; + routes.combo=adlsDisplaySelected ? (adlsWiring.valid?adlsWiring.dp:0) : tglComboDPMask; + routes.panel=adlsDisplaySelected && adlsWiring.valid ? adlsWiring.panel : 0; return routes; } bool tglDPSSTEligible(uint32_t ddi) const {return tglSSTRoutes().eligible(ddi);} diff --git a/tests/adls_aux_parent.py b/tests/adls_aux_parent.py index 28d19fec..4f4bd7c1 100644 --- a/tests/adls_aux_parent.py +++ b/tests/adls_aux_parent.py @@ -18,7 +18,7 @@ #include "kern_tgl_tc_boot.hpp" struct IOService; struct XE { - bool adlsDetected=true;AlderLakeS::Wiring adlsWiring; + bool adlsDisplaySelected=true;AlderLakeS::Wiring adlsWiring; FIELDS uintptr_t tglPortByDDI,tglEnablePG,tglDisablePG,orgTGLEnableAUXPower,orgTGLDisableAUXPower; static void wrapTGLEnableAUXPower(void*,uint32_t); @@ -71,7 +71,7 @@ } reset(0);assert(!state.tglAUXParentEligible(0)); reset(1);state.adlsWiring.valid=false;assert(!state.tglAUXParentEligible(0)); - reset(1);state.adlsDetected=false;assert(!state.tglAUXParentEligible(0)); + reset(1);state.adlsDisplaySelected=false;assert(!state.tglAUXParentEligible(0)); reset(1);state.tglAUXParentReady=false;assert(!state.tglAUXParentEligible(0)); reset(1);assert(!state.tglAUXParentEligible(9)&&!state.tglAUXParentEligible(~0U)); printf("PASS %u mixed AUX A/TC parent orderings; nested/inherited refs; foreign owner; admission guards\n",cases); diff --git a/tests/adls_lane_power.py b/tests/adls_lane_power.py index e898eb44..299cfe36 100644 --- a/tests/adls_lane_power.py +++ b/tests/adls_lane_power.py @@ -11,7 +11,7 @@ #include #define FunctionCast(fn,p) reinterpret_cast(p) struct XE { - bool adlsDetected=true,adlsPowerReady=true;void *tglObservationController; + 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); @@ -54,7 +54,7 @@ assert(calls.size()==1 && calls[0].target==(expected|2));++count; } for(unsigned bad=0;bad<6;++bad){ - state.adlsDetected=bad!=0;state.adlsPowerReady=bad!=1;state.adlsWiring.valid=bad!=2; + 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); diff --git a/tests/adls_panel_pwm.py b/tests/adls_panel_pwm.py index 37a85db9..64a9bba8 100644 --- a/tests/adls_panel_pwm.py +++ b/tests/adls_panel_pwm.py @@ -38,7 +38,7 @@ }; struct XE { static XE*callbackXE; - bool adlsDetected=true,adlsPanelPWMReady=true,tglActive=true;XELP::PanelPWM adlsPanelPWM; + bool adlsDisplaySelected=true,adlsPanelPWMReady=true,tglActive=true;XELP::PanelPWM adlsPanelPWM; XELP::VBT::Ports firmwarePorts;AlderLakeS::Wiring adlsWiring; struct {XELP::Family family=XELP::Family::AlderLakeS;}xeProfile; TGLTCNativeFunctions tglTCNativeFunctions;void*tglObservationController=nullptr; diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index fddcd1e8..10064e2c 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -35,6 +35,16 @@ init = xe[xe.index('void XE::init() {'):xe.index('void XE::deinit()')] assert 'BaseDeviceInfo' not in init and 'CPU_MODEL' not in init entry = re.search(r'extern "C" kern_return_t tglStart\(.*?\n\}', provider, re.S).group() +# Execute the production prerequisite expressions at all three entry points. +prerequisites = [] +for method,stop in [('wrapTGLFramebufferStart',' tglRecordStartOrder'), + ('wrapTGLAcceleratorStart',' if (callbackXE->adlnDetected'), + ('wrapTGLAcceleratorProbe',' if (!callbackXE->tglAcceleratorRequested')]: + at=xe.index('XE::'+method+'(') + body=xe[xe.index('{',at)+1:xe.index(stop,at)] + expression=re.search(r'if\((.*?)\)(?:return| \{)',body,re.S).group(1) + prerequisites.append('bool '+method+'Gate() { return !('+expression+'); }') + fixture = r''' #include #include @@ -53,8 +63,9 @@ static struct { void onKextLoadForce(int *) {} } lilu; struct XE { XELP::Profile xeProfile; - bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDetected=false; + bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, adlsDetected=false; bool display13FramebufferRequested=false, display13TraceEnabled=false; + bool adlsNativeReady=false, adlsDisplayHooksReady=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; bool tglProgramEnabled=false, tglTrainingLeaseRequested=false; @@ -69,12 +80,23 @@ struct kmod_info_t {}; static constexpr int KERN_FAILURE=5, KERN_SUCCESS=0; // ACTUAL_SOURCE +// PREREQUISITES static XE boot(std::initializer_list names, uint32_t pci=0x46D28086,uint32_t effective=0) { args.clear(); for (const auto name:names) args.insert(name); XE xe; xe.init(); xe.selectProfile(XELP::identify(pci,0,effective?effective:pci)); assert(tglStart(nullptr,nullptr)==KERN_SUCCESS); return xe; } int main() { + // Rendering and Display prerequisites can fail independently. The Display + // callbacks themselves are replayed elsewhere without any Render flag. + for(unsigned bits=0;bits<16;++bits) { + XE selected;selected.adlsDetected=bits&1;selected.adlsDisplaySelected=bits&2; + selected.adlsNativeReady=bits&4;selected.adlsDisplayHooksReady=bits&8;callbackXE=&selected; + const bool ready=(!(bits&1) || (bits&4)) && (!(bits&2) || (bits&8)); + assert(wrapTGLFramebufferStartGate()==ready); + assert(wrapTGLAcceleratorStartGate()==ready); + assert(wrapTGLAcceleratorProbeGate()==ready); + } // Every implemented ADL-N/Twin ID enables the qualified normal path without args. XE xe; for(uint32_t pci:{0x46D08086U,0x46D18086U,0x46D28086U,0x46D38086U,0x46D48086U}) { @@ -115,6 +137,25 @@ if(id<0x9000)assert(xe.adlnKernelReady && xe.adlnNativeRequested && xe.display13FramebufferRequested); assert(tglStart(nullptr,nullptr)==KERN_SUCCESS); } + // Exercise the complete production selection, including the shared Display + // adapter on unadmitted products. Expected identities are independent of the + // new selector; RPL-S remains rejected despite its known Display adapter. + for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x4690U,0x4680U,0xA780U,0x46A6U,0x46D2U}) + for(auto rev:{0U,12U,255U})for(unsigned option=0;option<4;++option) { + version=kernel;args={}; + if(option&1)args.insert("-wex-tgl-off"); + if(option&2)args.insert("-wex-noaccel"); + XE selected;selected.init();selected.selectProfile(XELP::identify((id<<16)|0x8086,rev)); + const bool desktop=id==0x4690 && rev==12; + const bool mobile=id==0x9A49 || id==0x9A78; + const bool small13=id==0x46D2 && rev==0; + assert(selected.adlsDetected==desktop && selected.adlsDisplaySelected==desktop); + 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.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); @@ -142,7 +183,7 @@ xe=XE{};xe.selectProfile(XELP::identify(0x9A498086,0));assert(!xe.tglRequested); } -'''.replace('// ACTUAL_SOURCE', init + entry) +'''.replace('// ACTUAL_SOURCE', init + entry).replace('// PREREQUISITES', '\n'.join(prerequisites)) with tempfile.TemporaryDirectory(prefix='wex-boot-args-') as directory: directory = Path(directory) cpp, exe = directory / 'test.cpp', directory / 'test' diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index 71ee8a98..a1800d1b 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -17,7 +17,7 @@ struct State { FIELDS struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; - bool tglHDMIB=false,adlsDetected=false,tglTrainingLeaseRequested=true; + bool tglHDMIB=false,adlsDisplaySelected=false,tglTrainingLeaseRequested=true; struct {struct {bool valid=false;}panel;}firmwarePorts; struct {unsigned dp=0;}adlsWiring; struct {unsigned legacy=0;}tglTCWiring; @@ -49,10 +49,10 @@ s.tglDPAltFiaReady=false;assert(s.start().missing()==C::AltFIA);s.tglDPAltFiaReady=true; s.tglAUXParentReady=false;assert(s.start().ready()); // TC owns DP-alt AUX power. s.tglAUXParentReady=true;s.tglDPAltCandidates=0; - s.adlsDetected=true;s.adlsWiring.dp=1; + s.adlsDisplaySelected=true;s.adlsWiring.dp=1; s.tglComboSignalReady=false;assert(s.start().missing()==C::ComboSignal); s.tglComboSignalReady=true; - s.adlsDetected=false;s.adlsWiring.dp=0;s.firmwarePorts.panel.valid=true; + s.adlsDisplaySelected=false;s.adlsWiring.dp=0;s.firmwarePorts.panel.valid=true; s.tglTCNativeFunctions.valid=false;assert(s.start().missing()==C::TCNative);s.tglTCNativeFunctions.valid=true; s.tglComboAUXGateReady=false;assert(s.start().missing()==C::ComboAUXGate);s.tglComboAUXGateReady=true; s.tglPanelDepthReady=false;assert(s.start().missing()==C::PanelDepth);s.tglPanelDepthReady=true; diff --git a/tests/tgl_agdc_port_status.py b/tests/tgl_agdc_port_status.py index afa8908e..98c521fa 100644 --- a/tests/tgl_agdc_port_status.py +++ b/tests/tgl_agdc_port_status.py @@ -17,7 +17,7 @@ #include #include struct XE { - uintptr_t orgTGLAGDCPortStatus{},tglPortByIndex{};bool tglInactivePorts=true,adlsDetected=false; + uintptr_t orgTGLAGDCPortStatus{},tglPortByIndex{};bool tglInactivePorts=true,adlsDisplaySelected=false; bool tglTCWiringLoaded=true,tglTCWiringExplicit=false; TGLTCWiring tglTCWiring=TGLTCWiring::fromMasks(0,0,0);XELP::VBT::Ports firmwarePorts; struct {XELP::Family family=XELP::Family::TigerLake;}xeProfile; @@ -49,7 +49,7 @@ if(wiring==4)state.tglTCWiringExplicit=true; // unspecified != excluded if(wiring==5){state.tglTCWiringExplicit=true;state.tglTCWiring=TGLTCWiring::fromMasks(0,0,TGLTCWiring::Mask);} if(wiring==6)state.tglTCWiringLoaded=false; - if(wiring==7)state.adlsDetected=true; + if(wiring==7)state.adlsDisplaySelected=true; auto c=reinterpret_cast(state.tglPlatformCopy+0x20+24*ddi); c[0]=ddi;c[1]=ddi+6;c[2]=1;put(ports[ddi],0x548,c);put(ports[ddi],0x550,mode);put(ports[ddi],0x554,status); ports[ddi][0xF8]=sinks;put(ctl,0xD40,state.tglPlatformCopy+0); diff --git a/tests/tgl_clock_order.py b/tests/tgl_clock_order.py index 0fe94684..5eb8a550 100644 --- a/tests/tgl_clock_order.py +++ b/tests/tgl_clock_order.py @@ -15,7 +15,7 @@ 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 { - bool adlsDetected=false;static bool adlsClockTarget(void*,void*,void*){return 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);} static volatile uint8_t *tglClockOrderTarget(void*,void*,void*,void*); @@ -52,7 +52,7 @@ tglClockOffTransaction.path=d;tglClockOffTransaction.access=a;tglClockOffTransaction.clockReg=0x46148; tglClockOffTransaction.value=0x123;tglClockOffTransaction.pending=true; writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(writes==1&®(0x46148)==0x123&&!tglClockOffTransaction.pending); - obj.adlsDetected=true;natives=writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(!natives&&!writes); + obj.adlsDisplaySelected=true;natives=writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(!natives&&!writes); printf("PASS %u actual clock-order port/pipe/idle cases; invalid pipe and deferred native-write fallback\n",cases); } '''.replace('TX',tx).replace('FUNCTIONS',f) diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index ee19dc9c..c14e19e0 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -14,7 +14,7 @@ #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 adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +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 {XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts;Functions tglTCNativeFunctions;void *tglComboAUXOwners[9]{}; } state,*callbackXE=&state; @@ -29,7 +29,7 @@ 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.adlsDetected=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)); } if(panel){state.adlsWiring.dp=0;state.adlsWiring.panel=1;const uint32_t c[]={0,0,0,0,2,0x18};memcpy(config,c,sizeof(c));} @@ -58,7 +58,7 @@ if(bad==9)store(p,0x590,buffer+0x100); if(bad==10)regs[0x45454]=0; if(bad==11)regs[buffer]=0x80000001; - if(bad==12){state.xeProfile.family=XELP::Family::AlderLakeN;state.adlsDetected=false;} + 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;} diff --git a/tests/tgl_combo_pll_write.py b/tests/tgl_combo_pll_write.py index fec4077a..3c5ecaf9 100644 --- a/tests/tgl_combo_pll_write.py +++ b/tests/tgl_combo_pll_write.py @@ -20,7 +20,7 @@ #include #include #include -struct State { bool adlsDetected=false; bool tglActive=true; uintptr_t tglComboPLLFractionCaller=0x1000; void *tglObservationController=nullptr; } state,*callbackXE=&state; +struct State { bool adlsDisplaySelected=false; bool tglActive=true; uintptr_t tglComboPLLFractionCaller=0x1000; void *tglObservationController=nullptr; } state,*callbackXE=&state; unsigned records=0; void tglTransitionRecord(unsigned stage,bool done,void *,unsigned long reg,uint32_t nominal,uint32_t actual,const uint64_t *detail,unsigned size) { assert(stage==70 && done && reg==0x164284 && nominal==0x1c001a5 && actual==0xe001a5); diff --git a/tests/tgl_combo_signal_guard.py b/tests/tgl_combo_signal_guard.py index d17117e5..91dffe70 100644 --- a/tests/tgl_combo_signal_guard.py +++ b/tests/tgl_combo_signal_guard.py @@ -20,7 +20,7 @@ #include #include struct State { - uint32_t tglComboDPMask=0;bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr;bool tglActive=true,tglComboSignalReady=true; struct {uint32_t identity=0x9A498086;XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts; @@ -119,7 +119,7 @@ for(unsigned ddi:{0u,3u,4u})for(unsigned bad=0;bad<12;++bad) { alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; - state={};state.adlsDetected=true;state.adlsWiring.valid=true;state.adlsWiring.dp=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(config,c,sizeof(c)); @@ -143,7 +143,7 @@ for(unsigned rate:{6U,10U,20U,30U})for(unsigned low=0;low<2;++low)for(unsigned hobl=0;hobl<2;++hobl) { alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; - state={};state.adlsDetected=true;state.xeProfile.family=XELP::Family::AlderLakeS; + state={};state.adlsDisplaySelected=true;state.xeProfile.family=XELP::Family::AlderLakeS; state.tglObservationController=ctl;auto &v=state.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78C6,10,0,0x40,0,0}; v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2;v.panel.lowSwing=low;v.panel.hobl=hobl; state.adlsWiring=AlderLakeS::Wiring::fromFirmware(v);assert(state.adlsWiring.panel==1); diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index 463661e8..a173b4cc 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -28,7 +28,7 @@ void clock_get_uptime(uint64_t *p){*p=0;}void absolutetime_to_nanoseconds(uint64 void *current_thread(){return reinterpret_cast(1);} struct XE { bool tglHDMIB=true,tglStolenMemoryReady=false; - uint32_t tglComboDPMask=0;bool adlsDetected=false; + uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false; struct {uint32_t fStolenMemorySize=0;} framebufferPatch; XELP::VBT::Ports firmwarePorts; // FIELDS @@ -65,7 +65,7 @@ void expectUnchangedExcept(const uint8_t *after,std::initializer_list int main(){ // ADL-S uses the same real entry points, while preserving the physical // TC1 record and converting its PHY classification from Dekel to Combo. - setup();object.adlsDetected=true; + setup();object.adlsDisplaySelected=true; object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=8; object.firmwarePorts.port[3]={0x60d2,1,2}; reinterpret_cast(original+0x68)[1]=9;IOService adlsProvider; @@ -82,11 +82,11 @@ int main(){ assert(result==(bus==9?0x91u:0xE00002C2u)); if(bus!=9)assert(request[7]==result); } - setup();object.adlsDetected=true;auto foreignWiring=property(2,0,2,0);IOService wrongADLS{&foreignWiring}; + setup();object.adlsDisplaySelected=true;auto foreignWiring=property(2,0,2,0);IOService wrongADLS{&foreignWiring}; assert(!XE::tglLoadHDMIWiring(&wrongADLS) && !XE::tglLoadHDMIWiring(&wrongADLS)); // Panel + external HDMI/DP use the real firmware loader and topology copy. for(unsigned kind:{1U,2U})for(unsigned tcon:{0U,2U,3U}) { - setup();object.adlsDetected=true;auto &v=object.firmwarePorts; + setup();object.adlsDisplaySelected=true;auto &v=object.firmwarePorts; v.status=XELP::VBT::Status::Valid;v.present=9;v.port[0]={0x78c6,10,0,0x40,0,0}; v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2; v.port[3]={kind==1?0x60d2U:0x60d6U,kind==1?1U:7U,2,kind==1?0U:0x10U,0,0}; diff --git a/tests/tgl_kernel_admission.py b/tests/tgl_kernel_admission.py index 2e456e34..4439fcba 100644 --- a/tests/tgl_kernel_admission.py +++ b/tests/tgl_kernel_admission.py @@ -11,12 +11,12 @@ code=r''' #include #include "kern_xelp_os.hpp" +#include "kern_xelp_platform.hpp" #include #include -namespace XELP { enum class Family {TigerLake,Other,AlderLakeS}; } struct Profile { bool ok; XELP::Family family; bool admitted() const {return ok;} }; const char *version; -bool noaccel,off,adlsDetected; +bool noaccel,off,adlsDisplaySelected; bool checkKernelArgument(const char *arg) {return !strcmp(arg,"-wex-noaccel")?noaccel:!strcmp(arg,"-wex-tgl-off")?off:false;} bool tglRequested,tglInactivePorts,tglPG1Poll,tglClockOrderRequested,tglTrainingLeaseRequested,tglProgramEnabled,tglAcceleratorRequested; int kextIntelTGL,kextIntelTGLFb;int *currentGraphics,*currentFramebuffer; @@ -37,8 +37,8 @@ unsigned cases=0; for(unsigned k=0;k<11;++k)for(unsigned n=0;n<2;++n)for(unsigned o=0;o<2;++o)for(unsigned physical=0;physical<2;++physical)for(unsigned family=0;family<3;++family){ version=versions[k];noaccel=n;off=o;currentGraphics=currentFramebuffer=nullptr; -adlsDetected=family==2; -select({bool(physical),family==2?XELP::Family::AlderLakeS:family?XELP::Family::TigerLake:XELP::Family::Other}); +adlsDisplaySelected=family==2; +select({bool(physical),family==2?XELP::Family::AlderLakeS:family?XELP::Family::TigerLake:XELP::Family::Unknown}); bool expected=physical&&family&&!o&&(k<7)&&(family!=2 || k>=1); assert(tglRequested==expected);assert(tglAcceleratorRequested==(expected&&!n)); assert((currentFramebuffer==&kextIntelTGLFb)==expected);++cases; diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index 2d1b68d8..4ce03f5f 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -28,7 +28,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; static void tglRecordStartOrder(unsigned,void*,unsigned=9,uint64_t=0,uint64_t=0,uint64_t=0) {} static void tglRecordNativeModeBinding(void *,uint32_t,uint32_t) {} @@ -105,7 +105,7 @@ int main(int argc,char **argv){ coldReady=true;instance.tglDPAltCandidates=0; // Qualified ADL-S HPD mode4 must execute native Combo mode1, without FIA. for(unsigned ddi:{0u,3u,4u}) { - reset(ddi);instance.adlsDetected=true;instance.adlsWiring.valid=true;instance.adlsWiring.dp=1U<(instance.tglPlatformCopy+0x20+24*ddi); const uint32_t fixed[]={ddi,ddi?ddi+6:1,1,0,0x400,0x201};memcpy(bound,fixed,sizeof(fixed)); *reinterpret_cast(port+0x548)=bound; @@ -113,7 +113,7 @@ int main(int argc,char **argv){ assert(XE::wrapTGLSetPortMode(port,4)==0 && mode()==1); assert(!reads && !writes && !powerGets && !powerPuts && !sleeps); } - instance.adlsDetected=false;instance.adlsWiring={};testCaller=0;instance.tglNativeLegacyHPDCaller=0; + instance.adlsDisplaySelected=false;instance.adlsWiring={};testCaller=0;instance.tglNativeLegacyHPDCaller=0; *reinterpret_cast(port+0x548)=config; unsigned cases=0; for(bool skipPower:{false,true})for(unsigned d=3;d<=8;++d)for(unsigned requested:{3u,4u})for(bool isReady:{false,true}){ diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index b2102f98..91a53551 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -27,7 +27,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; + 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; // LEGACY_HPD_POLICY diff --git a/tests/tgl_panel_depth_route.py b/tests/tgl_panel_depth_route.py index 773ff5a9..47660fac 100644 --- a/tests/tgl_panel_depth_route.py +++ b/tests/tgl_panel_depth_route.py @@ -41,7 +41,7 @@ void IOSimpleLockLock(void *){} void IOSimpleLockUnlock(void *){} struct Ports {struct Port {bool internal(){return true;}bool dp(){return true;}} port[2];unsigned present=1;bool valid(){return true;}}; struct XE { - bool tglActive=true,tglPanelDepthReady=true,adlsDetected=false; + bool tglActive=true,tglPanelDepthReady=true,adlsDisplaySelected=false; AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; @@ -76,7 +76,7 @@ assert(published==before && *reinterpret_cast(timing+0x8A)==2); } // Same depth contract on qualified ADL-S panel; retained record is required. - state.tglActive=state.tglPanelDepthReady=true;state.adlsDetected=true; + state.tglActive=state.tglPanelDepthReady=true;state.adlsDisplaySelected=true; state.xeProfile.family=XELP::Family::AlderLakeS;port[0x117]=0;put(port,0x578,access); state.adlsWiring.valid=true;state.adlsWiring.panel=1; const uint32_t panelRecord[]={0,0,0,0,2,0x18};memcpy(state.tglPlatformCopy+0x20,panelRecord,sizeof(panelRecord)); diff --git a/tests/tgl_panel_timing.py b/tests/tgl_panel_timing.py index 13520846..a0759abf 100644 --- a/tests/tgl_panel_timing.py +++ b/tests/tgl_panel_timing.py @@ -41,7 +41,7 @@ }; struct XE { static XE *callbackXE; - bool tglActive=true,tglPanelTimingReady=true,adlsDetected=false; + bool tglActive=true,tglPanelTimingReady=true,adlsDisplaySelected=false; AlderLakeS::Wiring adlsWiring;XELP::PanelPWM adlsPanelPWM; uintptr_t orgTGLPanelTiming=0,orgTGLRegisterRead=0,orgTGLPanelConfig=0; uint32_t tglPanelTimingBIOS[3]{};bool tglPanelTimingBIOSValid=false; @@ -160,7 +160,7 @@ s.orgTGLPanelTiming=(uintptr_t)(mem+0x4848c);at(ctl,0xc60)=&provider; const uint32_t milliseconds[]={300,100,600,90,800};for(unsigned i=0;i<5;++i)properties[i].value=milliseconds[i]; if(platform){ - s.adlsDetected=true;s.xeProfile.family=XELP::Family::AlderLakeS; + s.adlsDisplaySelected=true;s.xeProfile.family=XELP::Family::AlderLakeS; s.adlsWiring=AlderLakeS::Wiring::fromFirmware(s.firmwarePorts);assert(s.adlsWiring.panel==1); const uint32_t record[]={0,0,0,0,2,0x18};memcpy(reinterpret_cast(s.tglPlatformCopy)+0x20,record,sizeof(record)); } diff --git a/tests/tgl_port_map_fixture.cpp b/tests/tgl_port_map_fixture.cpp index bb71fd78..a7154748 100644 --- a/tests/tgl_port_map_fixture.cpp +++ b/tests/tgl_port_map_fixture.cpp @@ -18,7 +18,7 @@ struct IORegistryEntry { void *getProperty(const char *) { return nullptr; } }; struct XE { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // FIELDS IOSimpleLock *tglDekelLock; uintptr_t orgTGLReadDekel,orgTGLWriteDekel; diff --git a/tests/tgl_probe_fixture.cpp b/tests/tgl_probe_fixture.cpp index a5ae2825..0888b8d6 100644 --- a/tests/tgl_probe_fixture.cpp +++ b/tests/tgl_probe_fixture.cpp @@ -31,7 +31,7 @@ void tglAUXRecord(void *,uint32_t,bool,uint32_t,uint32_t,uint32_t) {} uint32_t providerProbe(void *); uint32_t nativeHPD(void *) {return 1;} struct XE { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; uint32_t tglModularProbed=0; uint64_t tglPlatformCopy[0x1c0/8] {}; bool tglActive=true; diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index 22681e33..ec6a4478 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -45,7 +45,7 @@ template void tglObserveResource(T...) {} void tglAUXPublish() {} void tglDPCDPublish() {} struct XE { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // TC_WIRING_FIELDS // LEASE_FIELDS IORecursiveLock *tglPathPoolLock; @@ -354,7 +354,7 @@ void routeLifecycleMatrix() { if(adapter==0 ? ddi>1 : adapter==1 ? (ddi!=0&&ddi!=3&&ddi!=4) : ddi<3)continue; setup(); if(adapter==0){obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.tglComboDPMask=1U<(0x550)=mode; for(unsigned i=0;i(0x550)=mode; for(unsigned i=0;i1)continue; reset(d,p,pat);object.tglTCWiring=TGLTCWiring::fromMasks(0,0,0); - object.adlsDetected=adls;object.adlsWiring.valid=adls;object.adlsWiring.dp=adls?1U<(p) #define FunctionCast(f,p) reinterpret_cast(p) struct XE { - bool adlsDetected=false;AlderLakeS::Wiring adlsWiring; + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; bool tglActive=true; XELP::Profile xeProfile=XELP::identify(0x9a498086,1);XELP::VBT::Ports firmwarePorts; // FIELDS From 65498e5a31629cde25d40859c5ebac2fd58b7d4b Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:56:55 +0900 Subject: [PATCH 41/58] Dispatch existing Display13 backend independently of Render selection --- WhateverXe/kern_xe.cpp | 58 +++++++++++++++------------- WhateverXe/kern_xe.hpp | 4 +- tests/display13_aux_fixture.cpp | 2 +- tests/display13_combo_clock.py | 4 +- tests/display13_sst_fixture.cpp | 4 +- tests/display_backend_dispatch.py | 62 ++++++++++++++++++++++++++++++ tests/provider_binding_fixture.cpp | 2 + tools/test.py | 2 +- 8 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 tests/display_backend_dispatch.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index a3216296..a40622a7 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -210,6 +210,10 @@ static bool xelpSelectedDevice(IOPCIDevice *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 && + xelpSelectedProfile.display==XELP::Display::XeLPD && xelpSelectedDevice(pci); +} // Detailed read-only display evidence is opt-in; control and first-fault // receipts remain active in the ordinary AlderLakeN profile. static bool display13TraceEnabled; @@ -3740,13 +3744,15 @@ void XE::selectProfile(const XELP::Profile &profile) { profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; adlnDetected=adlnRequested; modDVMTCalcFix.available=profile.admitted(); + 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; + display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); + if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb; if(adlnRequested) { - adlnKernelReady=XELP::kernelOS(version)!=XELP::OS::Unknown && XELP::kernelOS(version)!=XELP::OS::Donor; + adlnKernelReady=nativeOS; adlnNativeRequested=adlnKernelReady; - display13FramebufferRequested=adlnKernelReady; adlnNativeRCSProfileRequested=adlnNativeRequested && !checkKernelArgument("-wex-adln-kernel-ctx"); - display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); - if(display13FramebufferRequested || adlnNativeRequested)currentFramebuffer=&kextIntelTGLFb; if(adlnNativeRequested) { tglRequested=tglAcceleratorRequested=true;currentGraphics=&kextIntelTGL; } @@ -4209,11 +4215,11 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add if(adlnRequested)IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); return true; } - // Keep AlderLakeN accelerator patching outside the large framebuffer patch frame. - // An early return inside processKextFull still allocates that frame. + // 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 (adlnRequested || adlnDetected) { - if (adlnDetected && adlnKernelReady && display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) - return processDisplay13Framebuffer(patcher,index,address,size); if (!adlnDetected || !adlnNativeRequested || !currentGraphics || currentGraphics->loadIndex != index) return false; // Matching is persistent. A rejected bootstrap must not leave the // original 46D1-incompatible donor probe reachable and panic before SSH. @@ -4680,7 +4686,7 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; if(pipe>=3 || !fb || !pci || !c.bridge.begun || c.bridge.state.faulted || c.memoryFaulted || !c.map || c.map->getLength()!=0x1000000 || !c.map->getVirtualAddress() || - !xelpAlderLakeNDevice(pci) || + !xelpDisplay13Device(pci) || !xelpMemoryMatches(pci) || !(pci->configRead16(4)&2))return r; auto aperture=pci->getDeviceMemoryWithRegister(0x18); if(!aperture || aperture->getLength()!=0x10000000 || @@ -4788,7 +4794,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm {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} }; - bool known=adlnKernelReady && adlnDetected && size>0x5C4D2; + bool known=display13FramebufferRequested && size>0x5C4D2; for(unsigned i=0;i(index,symbols[i],address,size); known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),prefixes[i],sizeof(prefixes[i])); @@ -5197,7 +5203,7 @@ bool XE::processDisplay13FramebufferWatermarks(KernelPatcher &patcher,size_t ind {0x3645,6,{0x81,0xE1,0xFF,0x07,0x00,0x00},{0x81,0xE1,0xFF,0x0F,0x00,0x00}}, {0x38F1,6,{0x81,0xE6,0xFF,0x07,0x00,0x00},{0x81,0xE6,0xFF,0x0F,0x00,0x00}}, }; - bool known=adlnDetected && adlnKernelReady && display13FBDBUFHooksReady && size>0x3D85; + bool known=display13FramebufferRequested && display13FBDBUFHooksReady && size>0x3D85; for(auto &e:entries) { const auto fn=patcher.solveSymbol(index,e.symbol,address,size); known=known && fn==address+e.offset && !memcmp(reinterpret_cast(fn),e.bytes,sizeof(e.bytes)); @@ -5306,7 +5312,7 @@ bool XE::processDisplay13FramebufferIRQ(KernelPatcher &patcher,size_t index,mach {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38,0x48,0xFF,0x05,0xCE,0x33,0x07,0x00}, {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28,0x48,0xFF,0x05,0xAE,0x37,0x07,0x00}, }; - bool known=adlnKernelReady && adlnDetected && size>0x75D44; + bool known=display13FramebufferRequested && size>0x75D44; for(unsigned i=0;i(index,symbols[i],address,size); known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),entries[i],sizeof(entries[i])); @@ -5812,7 +5818,7 @@ void XE::wrapDisplay13FBMemoryInit(IOService *controller) { const auto systemMapper=reinterpret_cast(IOMapper::gSystem); const bool physicalOptions=mapperOptions==kIOMemoryMapperNone || (!mapperOptions && !systemMapper); bool valid=s->display13FBGTTHooksReady && !g.controller && !g.faulted && pci && native && access && - xelpAlderLakeNDevice(pci) && xelpMemoryMatches(pci) && + xelpDisplay13Device(pci) && xelpMemoryMatches(pci) && (pci->configRead16(4)&2) && native->getLength()==0x1000000 && native->getVirtualAddress() && access->getMetaClass()->getClassSize()>=0x68 && Display13FBMemory::field(access,0x50)==native->getVirtualAddress() && @@ -6048,7 +6054,7 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m "__ZN31AppleIntelRegisterAccessManager14ReadRegister32Em",address,size);patcher.clearError(); auto write=patcher.solveSymbol(index, "__ZN31AppleIntelRegisterAccessManager15WriteRegister32Emj",address,size);patcher.clearError(); - if(!adlnDetected || !adlnKernelReady || !display13FBABIReady || !display13FBClockReadoutHooksReady || size<=0x9A118 || + if(!display13FramebufferRequested || !display13FBABIReady || !display13FBClockReadoutHooksReady || size<=0x9A118 || clocks!=address+0x6CE10 || bus!=address+0x53A7C || memcmp(reinterpret_cast(address+0x6D95D),fractionWrite,sizeof(fractionWrite)) || memcmp(reinterpret_cast(bus),busEntry,sizeof(busEntry)) || @@ -6101,7 +6107,7 @@ bool XE::processDisplay13FramebufferAUX(KernelPatcher &patcher,size_t index,mach "__ZN17AppleIntelPortHAL13runAUXCommandEN14AppleIntelPort11AUX_CommandEPjjb"}; const uint32_t offsets[]={0x1CC72,0x1CEB0,0x1D2B6,0x61748}; const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; - if(!adlnDetected || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x138BB0)return false; + if(!display13FramebufferRequested || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x138BB0)return false; for(unsigned i=0;i<4;++i) { auto fn=patcher.solveSymbol(index,names[i],address,size);patcher.clearError(); if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entry,sizeof(entry)))return false; @@ -6121,7 +6127,7 @@ bool XE::processDisplay13FramebufferAUX(KernelPatcher &patcher,size_t index,mach // hence mode0 is allowed; a DDI/main-link lease is deliberately not required. bool XE::display13AUXReady(void *port,bool command) { auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); - if(!s->adlnDetected || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBAUXHooksReady || + if(!s->display13FramebufferRequested || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBAUXHooksReady || !object || object->getMetaClass()->getClassSize()<0x5AC || !c.controller || c.controller!=s->adlnTopologyController || !s->display13AUXControllerSlot || *reinterpret_cast(s->display13AUXControllerSlot)!=c.controller || !adlnWriterMappingBound() || @@ -6192,7 +6198,7 @@ bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach const char *enable="__ZN17AppleIntelPortHAL10enablePortEv",*disable="__ZN17AppleIntelPortHAL11disablePortEv"; const uint8_t enableEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x49,0x89,0xff}; const uint8_t disableEntry[]={0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb}; - if(!adlnDetected || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x955C8)return false; + if(!display13FramebufferRequested || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x955C8)return false; const auto p=patcher.solveSymbol(index,pattern,address,size);patcher.clearError(); const auto s=patcher.solveSymbol(index,signal,address,size);patcher.clearError(); const auto e=patcher.solveSymbol(index,enable,address,size);patcher.clearError(); @@ -6219,7 +6225,7 @@ bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach // require its retained platform record and BAR association before sampling it. uint8_t *XE::display13SSTPort(void *port) { auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); - if(!s->adlnDetected || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBSSTHooksReady || + if(!s->display13FramebufferRequested || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBSSTHooksReady || !object || object->getMetaClass()->getClassSize()<0x624 || !c.controller || c.controller!=s->adlnTopologyController || !adlnWriterMappingBound() || c.clockFaulted || c.memoryFaulted || c.clockStopped || !s->adlnOutputWiring.described())return nullptr; @@ -6602,7 +6608,7 @@ void XE::wrapAlderLakeNGamma10(void *path,void *transaction,uint32_t control) { void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { // DISPLAY13_COMBO_FRACTION: qualified nominal writer, physical reference, // retained controller/manager. No MMIO or other port's PLL inference. - if(callbackXE->adlnDetected && callbackXE->xeProfile.display==XELP::Display::XeLPD && + if(callbackXE->display13FramebufferRequested && callbackXE->xeProfile.display==XELP::Display::XeLPD && callbackXE->adlnComboPLLFractionCaller && reinterpret_cast(__builtin_return_address(0))==callbackXE->adlnComboPLLFractionCaller) { auto controller=reinterpret_cast(callbackXE->adlnTopologyController); if(controller && access && *reinterpret_cast(controller+0xC40)==access) @@ -6657,7 +6663,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->adlnDetected && callbackXE->adlnNativeReady && access==display.access && + if(arbitrationRegister && callbackXE->display13FramebufferRequested && callbackXE->adlnNativeReady && access==display.access && display.engineEnabled && writerBase && Display13FBMemory::field(access,0x50)==writerBase) { for(unsigned pipe=0;pipe<3;++pipe) { const auto bank=0x1000*pipe; @@ -6786,7 +6792,7 @@ bool XE::wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t b 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(!adlnDetected || !adlnKernelReady || size<=0xCCCD8)return false; + 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); @@ -6820,7 +6826,7 @@ bool XE::processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach 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 || - !xelpAlderLakeNDevice(pci) || + !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); @@ -6860,7 +6866,7 @@ 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->adlnDetected && s->adlnKernelReady && contract.ready() && pci && xelpAlderLakeNDevice(pci); + const bool ready=s->display13FramebufferRequested && 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) @@ -7510,7 +7516,7 @@ Display13Clock::Readout XE::adlnClockReadout(IOService *controller) { Display13Clock::Readout r; auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; if(callbackXE->display13FBClockReadoutHooksReady && pci && - xelpAlderLakeNDevice(pci) && (pci->configRead16(4)&2) && + xelpDisplay13Device(pci) && (pci->configRead16(4)&2) && controller->getMetaClass()->getClassSize()>=0xEAC && *reinterpret_cast(reinterpret_cast(controller)+0xC60)==pci && *reinterpret_cast(reinterpret_cast(controller)+0xC40)) { @@ -8818,7 +8824,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { auto pci=service ? OSDynamicCast(IOPCIDevice,service->getProvider()) : nullptr; if(callbackXE->display13FBPowerReferencesReady && !c.controller && power && pci && !c.clockFaulted && c.clock.status==Display13Clock::Status::Complete && - xelpAlderLakeNDevice(pci) && (pci->configRead16(4)&2) && + xelpDisplay13Device(pci) && (pci->configRead16(4)&2) && Display13Power::objectAssociations(power,static_cast(power)->getMetaClass()->getClassSize(), controller,service->getMetaClass()->getClassSize())) { auto bar=pci->getDeviceMemoryWithRegister(0x10); @@ -12506,7 +12512,7 @@ void XE::wrapTGLHostIRQEnable(void *bridge) { // Native owner enables primary/display IRQ under its existing interrupt lock. // Later enableController skips duplicate setup in mode0; FD4 makes this call // idempotent. Never forge bridge flags, completion stamps, or CSB identities. - if(callbackXE->adlnDetected)wrapDisplay13FBEnableIRQ(static_cast(controller)); + if(callbackXE->display13FramebufferRequested)wrapDisplay13FBEnableIRQ(static_cast(controller)); else wrapTGLFBEnableIRQ(controller); } tglFBIRQEnd(r,0); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 0331935a..5f6a5b00 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -112,7 +112,9 @@ class XE { uint32_t adlnNativeSubslices {}, adlnL3Banks {}, adlnNativeEUs {}; bool adlnKernelReady {false}; bool adlnNativeRequested {false}; - bool adlnNativeReady {false}; + // Shared platform preflight: GT fuses, reserved GGTT/GOP, mapper, + // aperture and native boot policy. Both Render and Display consume it. + bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; bool display13FBPowerReferencesReady {false}; bool display13FBClockReadoutHooksReady {false}; diff --git a/tests/display13_aux_fixture.cpp b/tests/display13_aux_fixture.cpp index f9eea3f6..8852799c 100644 --- a/tests/display13_aux_fixture.cpp +++ b/tests/display13_aux_fixture.cpp @@ -88,7 +88,7 @@ int main(int argc,char **argv){ for(unsigned bad=0;bad<21;++bad){ Fixture f(0,0,0);Map map;auxSetup(f,map,0,false,0);auto &c=display13FBPower;Display13FBPowerIO io{&map}; assert(c.bridge.getAUX(io,0).status==Display13Power::Status::Complete); - if(bad==0)state.adlnDetected=false;if(bad==1)state.display13FBAUXHooksReady=false; + if(bad==0)state.display13FramebufferRequested=false;if(bad==1)state.display13FBAUXHooksReady=false; if(bad==2)state.xeProfile.display=XELP::Display::XeD;if(bad==3)state.adlnTopologyController=nullptr; if(bad==4)state.display13AUXControllerSlot=0;if(bad==5)bound=false; if(bad==6)c.bridge.state.faulted=true;if(bad==7)c.bridge.state.suspended=true; diff --git a/tests/display13_combo_clock.py b/tests/display13_combo_clock.py index 42624008..35056a18 100644 --- a/tests/display13_combo_clock.py +++ b/tests/display13_combo_clock.py @@ -22,7 +22,7 @@ def array(name): #include struct IOService; struct State { - bool adlnDetected=true;struct {XELP::Display display=XELP::Display::XeLPD;}xeProfile; + bool display13FramebufferRequested=true;struct {XELP::Display display=XELP::Display::XeLPD;}xeProfile; uintptr_t adlnComboPLLFractionCaller=0x1000;IOService *adlnTopologyController=nullptr; }state,*callbackXE=&state; uint32_t adjust(void *access,uint64_t reg,uint32_t value,uintptr_t caller) {GUARD return value;} @@ -48,7 +48,7 @@ def array(name): int main(int argc,char**argv){ state.adlnTopologyController=reinterpret_cast(ctl);field(ctl,0xc40)=access;field(ctl,0xea8)=2; for(unsigned bad=0;bad<9;++bad){State saved=state; - if(bad==0)state.adlnDetected=false;if(bad==1)state.xeProfile.display=XELP::Display::XeD; + if(bad==0)state.display13FramebufferRequested=false;if(bad==1)state.xeProfile.display=XELP::Display::XeD; if(bad==2)state.adlnComboPLLFractionCaller=0;if(bad==3)state.adlnTopologyController=nullptr; if(bad==4)field(ctl,0xea8)=1;if(bad==8)field(ctl,0xc40)=port; assert(adjust(bad==5?nullptr:access,bad==6?0x164288:0x164284,0x1c001a5,bad==7?0x1001:0x1000)==0x1c001a5); diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index 481bd5cc..ab8b8a44 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -46,7 +46,7 @@ struct Display13FBSSTIO { bool adlnWriterMappingBound(){return bound;} uintptr_t display13FBExpectedOwner(void*){return 0;} struct XE { - bool adlnDetected=true,display13FBSSTHooksReady=true;XELP::Profile xeProfile; + bool display13FramebufferRequested=true,display13FBSSTHooksReady=true;XELP::Profile xeProfile; Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; uintptr_t orgDisplay13EnablePort=0,orgDisplay13DisablePort=0; @@ -204,7 +204,7 @@ int main(int argc,char **argv){ if(fault==12)c.clockStopped=true;if(fault==13)put(f.port,0x548,reinterpret_cast(1)); if(fault==14)f.port[0x117]=1;if(fault==15)put(f.port,0x578,static_cast(f.ctl)); if(fault==16)put(f.path,0x3640,0U);if(fault==17)put(f.plane,0x7C,0U); - if(fault==18)state.adlnDetected=false;if(fault==19)state.display13FBSSTHooksReady=false; + if(fault==18)state.display13FramebufferRequested=false;if(fault==19)state.display13FBSSTHooksReady=false; if(fault==20)map.drop=0x46144;if(fault==21)map.drop=0x61400; if(fault==22)map.regs[0x46144]=0x10000000; if(fault==23)map.regs[0x61400]=0x88000000; diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py new file mode 100644 index 00000000..b3097f7f --- /dev/null +++ b/tests/display_backend_dispatch.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Replay the production kext dispatcher with independent Render/Display inputs.""" +from pathlib import Path +import subprocess,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('\t// Display dispatch is independent') +b=s.index('\nstatic bool adlnWriterMappingBound();',a) +body=s[a:b].rstrip()[:-1] +code=r''' +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +struct KernelPatcher {}; +struct Kext {size_t loadIndex;}; +bool loadOnly=false; +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; + 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 processTGLAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Render;return true;} + bool processAlderLakeNRejectedAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} + bool processAlderLakeNLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=LoadOnly;return true;} + bool processKextFull(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display12;return true;} + bool dispatch(KernelPatcher&patcher,size_t index,mach_vm_address_t address,size_t size) {BODY} +}; +int main() { + Kext fb{1},graphics{2};KernelPatcher patcher;unsigned cases=0; + // Removing/rejecting Render must not redirect a selected Display13 kext + // through the Display12 frame, nor make a graphics kext run Display code. + for(unsigned render=0;render<4;++render)for(bool display:{false,true}) + for(bool framebuffer:{false,true})for(unsigned index=1;index<=3;++index) { + XE x;x.currentFramebuffer=framebuffer?&fb:nullptr;x.currentGraphics=&graphics; + x.display13FramebufferRequested=display;x.adlnRequested=x.adlnDetected=x.adlnNativeRequested=render!=0; + x.adlnNativeReady=x.tglActive=render>=2;loadOnly=render==3; + const bool result=x.dispatch(patcher,index,0x1000,0x2000); + if(display && framebuffer && index==1)assert(result && x.call==XE::Display13); + else if(!render)assert(result && x.call==XE::Display12); + else if(index!=2)assert(!result && x.call==XE::None); + else assert(result && x.call==(render==1?XE::Rejected:render==2?XE::Render:XE::LoadOnly)); + ++cases; + } + // Missing graphics object and missing native request never bypass rejection. + for(bool detected:{false,true}) { + XE x;x.adlnRequested=true;x.adlnDetected=detected;x.adlnNativeRequested=true; + assert(!x.dispatch(patcher,2,0x1000,0x2000) && x.call==XE::None); + x.currentGraphics=&graphics;x.adlnNativeRequested=false; + assert(!x.dispatch(patcher,2,0x1000,0x2000) && x.call==XE::None); + } + printf("PASS %u independent Display/Render dispatch cases, native preflight rejection and load-only routing\n",cases); +} +'''.replace('BODY',body).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) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 6cbcf567..20ec7c10 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -69,8 +69,10 @@ int main(int argc,char **argv) { xelpSelectedGPU=&pci;xelpSelectedProfile=config.profile; assert(xelpSelectedDevice(&pci)==config.profile.admitted()); assert(xelpAlderLakeNDevice(&pci)==(adln && !rejected)); + assert(xelpDisplay13Device(&pci)==(adln && !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));} diff --git a/tools/test.py b/tools/test.py index 5527b82a..5b2e1e56 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=['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=['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 += ['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 54e642d0c994c293793458e59cdd3fc27d01f4ad Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:08:01 +0900 Subject: [PATCH 42/58] Separate Display boot certificates from Render preflight --- WhateverXe/kern_display13_boot.hpp | 12 ++++++ WhateverXe/kern_xe.cpp | 67 ++++++++++++++++-------------- tests/display13_boot.cpp | 48 +++++++++++++++++++++ tests/display_backend_dispatch.py | 64 +++++++++++++++++++++++++++- 4 files changed, 158 insertions(+), 33 deletions(-) diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index a877c647..07e28cd6 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -7,6 +7,18 @@ #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 +// 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) { + AlderLakeN::LinearPlan plan; + if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi)!=AlderLakeN::LinearStatus::Ready || + plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false; + for(unsigned i=0;isetProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); const auto revision = pci->configRead8(8); - const bool admitted = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci); + const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci); + const bool displayRequested = display13FramebufferRequested && xelpDisplay13Device(pci); + const bool admitted = renderRequested || displayRequested; pci->setProperty("N97Admission", admitted); pci->setProperty("N97PhysicalIdentity", pci->configRead32(0), 32); pci->setProperty("N97PhysicalRevision", revision, 32); @@ -3793,11 +3795,11 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { auto wiring=OSDynamicCast(OSData,property); const auto board=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, wiring?wiring->getLength():0,firmwarePorts,3); - if(adlnNativeRequested && !Display13FBTopology::supportedWiring(board)) { + if(displayRequested && !Display13FBTopology::supportedWiring(board)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } - auto bootDDI=adlnNativeRequested ? UINT32_MAX : 1U; - uint64_t connector[] {1,adlnNativeRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; + auto bootDDI=displayRequested ? UINT32_MAX : 1U; + uint64_t connector[] {1,displayRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); pci->setProperty("N97DisplayVersion", AlderLakeN::DisplayVersion, 32); pci->setProperty("N97GTStep", "A0"); @@ -3805,7 +3807,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97GGC", pci->configRead16(0x50), 32); if (!(pci->configRead16(4) & 2)) return; const bool linearTest = display13FramebufferRequested; - const bool gtTest = checkKernelArgument("-wex-adln-gt") || adlnNativeRequested; + const bool gtTest = renderRequested && (checkKernelArgument("-wex-adln-gt") || adlnNativeRequested); auto map = pci->mapDeviceMemoryWithRegister(0x10, (linearTest || gtTest ? 0 : kIOMapReadOnly) | kIOMapInhibitCache); if (!map) return; @@ -3839,14 +3841,14 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { auto result = AlderLakeN::readDisplay(io, barReady); pci->setProperty("N97Display13Boot", &result, sizeof(result)); bool bootstrapReady=true; - if(adlnNativeRequested && linearTest && (result.activePipes&(result.activePipes-1))) { + if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory); pci->setProperty("N97DualGOPNormalize",&parked,sizeof(parked)); result=AlderLakeN::readDisplay(io,barReady); pci->setProperty("N97DualGOPAfter",&result,sizeof(result)); bootstrapReady=parked.status==Display13Boot::Status::Complete; } - if(adlnNativeRequested && bootstrapReady) { + 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); @@ -3955,35 +3957,15 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { scan.planeRestored,scan.ptesRestored,scan.retained); } } + bool renderReady=false; if (gtTest) { uint32_t edram=UINT32_MAX; auto gt=AlderLakeN::readGT(io,barReady && xelpMemoryMatches(pci), adlnNativeRequested ? &edram : nullptr); if (adlnNativeRequested) { adlnNativeSubslices=gt.resources.nativeSubslices; adlnL3Banks=gt.resources.l3Banks; adlnNativeEUs=gt.resources.eus; - AlderLakeN::LinearPlan plan; - auto display=AlderLakeN::readDisplay(io,barReady); - auto optionProperty=pci->getProperty("IOMemoryDescriptorOptions"); - auto opts=OSDynamicCast(OSNumber,optionProperty); - const uint64_t mapperOptions=opts ? opts->unsigned64BitValue() : 0; - const auto systemMapper=reinterpret_cast(IOMapper::gSystem); - const bool physicalOptions=(opts && (mapperOptions&kIOMemoryMapperNone)) || - ((!optionProperty || opts) && !mapperOptions && !systemMapper); - uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; - IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); - bool prefix=bootstrapReady && AlderLakeN::linearPlan(display,plan,bootDDI)==AlderLakeN::LinearStatus::Ready && plan.pipe==0 && - xelpMemory.range(plan.surface,AlderLakeN::ScanoutBytes,true); - for (unsigned i=0;prefix && igetDeviceMemoryWithRegister(0x18); - adlnNativeReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1) && prefix && - aperture && aperture->getLength()==0x10000000 && page_size==4096 && - physicalOptions && (pci->configRead16(4)&6)==6 && - checkKernelArgument("-allow3d") && checkKernelArgument("-disablegfxfirmware") && - !checkKernelArgument("-wex-adln-scanout") && !checkKernelArgument("-wex-adln-gpu"); - pci->setProperty("N97NativePreflight",adlnNativeReady); - pci->setProperty("N97NativeReservedGGTT",prefix); - pci->setProperty("N97NativeEDRAM",edram,32); + renderReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1); + pci->setProperty("N97NativeEDRAM",edram,32); } pci->setProperty("N97GTReadout",>,sizeof(gt)); pci->setProperty("N97GTTopologyReady",gt.status==AlderLakeN::GTStatus::Complete); @@ -3991,6 +3973,29 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { static_cast(gt.status),gt.requestedMask,gt.releasedMask,gt.releaseFailedMask, gt.resources.eus,gt.resources.l3Banks); } + if(displayRequested) { + auto display=AlderLakeN::readDisplay(io,barReady); + auto optionProperty=pci->getProperty("IOMemoryDescriptorOptions"); + auto opts=OSDynamicCast(OSNumber,optionProperty); + const uint64_t mapperOptions=opts ? opts->unsigned64BitValue() : 0; + const auto systemMapper=reinterpret_cast(IOMapper::gSystem); + const bool physicalOptions=(opts && (mapperOptions&kIOMemoryMapperNone)) || + ((!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); + auto aperture=pci->getDeviceMemoryWithRegister(0x18); + const bool memoryReady= + aperture && aperture->getLength()==0x10000000 && page_size==4096 && + 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; + pci->setProperty("N97NativePreflight",adlnNativeReady); + pci->setProperty("N97NativeReservedGGTT",prefix); + } if (gtTest && checkKernelArgument("-wex-adln-gpu")) { AlderLakeN::GPUResult gpu; static IOBufferMemoryDescriptor *pinnedGPU=nullptr; @@ -4134,7 +4139,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } if(!xeProfile.admitted())return; xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; - if (adlnDetected) { + if (adlnDetected || display13FramebufferRequested) { xelpReadFirmware(physicalGPU); tglDeviceId=physicalGPU->configRead16(2); modDVMTCalcFix.processKernel(patcher,info); diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index f1dd2020..e46a8135 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -6,6 +6,12 @@ struct IO { std::map regs; std::vector writes; + uint32_t pteReads {},badPTE {UINT32_MAX}; + XELPMemory::Layout memory=XELPMemory::decode(0x03C1,0x7A800000,0); + uint64_t readPTE(uint32_t reg) { + assert(reg==0x800000+8*pteReads);++pteReads; + return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); + } bool noFrames {},noStop {},noIdle {},changePeer {}; uint32_t changeRegister {},failWrite {}; IO() { @@ -49,7 +55,49 @@ static Display13OutputWiring protocols(unsigned dpMask) { for(unsigned d=0;d<3;++d)if(!(w.hdmi&(1U< #include @@ -28,9 +34,57 @@ bool processAlderLakeNRejectedAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} bool processAlderLakeNLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=LoadOnly;return true;} bool processKextFull(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display12;return true;} + int firmware=0,pool=0,bootstrap=0,vm=0,mmio=0; + unsigned tglDeviceId=0; + struct {bool enabled=true;}tglVM; + bool tglMMIOUCRouted=true,tglMMIOUCReady=true; + struct GPU { + unsigned configRead16(unsigned r){assert(r==2);return 0x46d0;} + void setProperty(const char*,bool){} + struct Map {void release(){}}map; + Map *mapDeviceMemoryWithRegister(unsigned r){assert(r==0x10);return ↦} + }gpu; + struct {void processKernel(KernelPatcher&,void*){}}modDVMTCalcFix; + void xelpReadFirmware(GPU*){++firmware;} + void xelpLoadDisplayPool(GPU*){++pool;} + void adlnBootstrap(GPU*){++bootstrap;} + void tglVMPrepare(KernelPatcher&,GPU*){++vm;} + void tglPrepareMMIOUC(KernelPatcher&,GPU*){++mmio;} + void tglUpdateMMIOMap(GPU::Map*){} + void kernel(KernelPatcher&patcher) {auto physicalGPU=&gpu;void *info=nullptr;KERNEL} bool dispatch(KernelPatcher&patcher,size_t index,mach_vm_address_t address,size_t size) {BODY} }; +namespace AlderLakeN {enum class GTStatus {Complete,Failed};} +void certificates() { + unsigned args=0; + const auto checkKernelArgument=[&](const char *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); + }; + 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) { + gt.status=(mask&1)?AlderLakeN::GTStatus::Complete:AlderLakeN::GTStatus::Failed; + unsigned edram=(mask&2)?0:1; + bool prefix=mask&4; + auto aperture=(mask&8)?&bar:nullptr;bar.length=(mask&16)?0x10000000:0x8000000; + 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 memoryReady=MEMORY; + const bool adlnNativeReady=COMBINED; + // All eight platform prerequisites + both required arguments, no diagnostics. + assert(adlnNativeReady==(mask==0x3ff)); + } + puts("PASS 4096 shared preflight combinations retain every admission prerequisite"); +} int main() { + certificates(); Kext fb{1},graphics{2};KernelPatcher patcher;unsigned cases=0; // Removing/rejecting Render must not redirect a selected Display13 kext // through the Display12 frame, nor make a graphics kext run Display code. @@ -46,6 +100,12 @@ 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}) { + XE x;x.adlnDetected=render;x.display13FramebufferRequested=display; + x.adlnNativeRequested=render;x.adlnNativeReady=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); + } // Missing graphics object and missing native request never bypass rejection. for(bool detected:{false,true}) { XE x;x.adlnRequested=true;x.adlnDetected=detected;x.adlnNativeRequested=true; @@ -55,7 +115,7 @@ } printf("PASS %u independent Display/Render dispatch cases, native preflight rejection and load-only routing\n",cases); } -'''.replace('BODY',body).replace('#include ','#include \n#include ') +'''.replace('BODY',body).replace('KERNEL',kernel).replace('RENDER',expression('renderReady')).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 af3194818b8db24bf216a2d1efdacff8efc06add Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:36:25 +0900 Subject: [PATCH 43/58] Share desktop D0 GT admission across Alder Lake-S and Raptor Lake-S --- README.md | 8 ++++-- README_ja.md | 6 ++++- WhateverXe/kern_alderlake_n.hpp | 23 ++++------------- WhateverXe/kern_alderlake_s.hpp | 22 ++++++++++------ WhateverXe/kern_xe.cpp | 4 +-- WhateverXe/kern_xelp_devices.hpp | 36 +++++++++++++++++++-------- WhateverXe/kern_xelp_gt_resources.hpp | 27 ++++++++++++++++++++ WhateverXe/kern_xelp_platform.hpp | 3 ++- tests/alderlake_s.cpp | 27 ++++++++++++++++++-- tests/boot_arguments.py | 7 +++--- tests/device_selection.cpp | 6 ++--- tests/provider_binding.py | 2 +- tests/provider_binding_fixture.cpp | 7 +++--- tests/xelp_platform.cpp | 11 +++++--- 14 files changed, 130 insertions(+), 59 deletions(-) create mode 100644 WhateverXe/kern_xelp_gt_resources.hpp diff --git a/README.md b/README.md index 873ff225..9979b661 100644 --- a/README.md +++ b/README.md @@ -183,8 +183,12 @@ installation or update location. initial display output and reconnection can be delayed. HDMI/fixed-DP routing is derived from VBT rather than a board or connector preset, within the current Combo PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability - and PHY D/E are excluded. GPU admission remains limited to `4690` revision - `0C` with the qualified fuse configuration. Other port layouts have CPU-test + and PHY D/E are excluded. Development GPU admission covers ADL-S IDs `4680/4682/4688/468A/468B/4690/4692/4693` + at revision `0C`, and RPL-S IDs `A780/A781/A782/A783/A788/A789/A78A/A78B` + at revisions `04/0C` (GT D0). Physical identity must match, with one slice, + up to two DSS, 16/24/32 EUs, four L3 banks and the supported media-engine mask. + Other steppings and resource layouts are rejected. Only `4690` has hardware validation; + the other IDs have CPU coverage and need the updated companion ID dispatch. Other port layouts have CPU-test coverage but are not hardware-qualified. A USB-C connector exposed by VBT as fixed DP on Combo PHY A has also been tested. This does not qualify Type-C/TBT-managed routes. Development eDP routing also composes a diff --git a/README_ja.md b/README_ja.md index d068eb23..374ce081 100644 --- a/README_ja.md +++ b/README_ja.md @@ -132,7 +132,11 @@ Safariのインストール先や更新による配置変更に依存せずData Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 - GPUの適用対象は、確認済みのfuse構成を持つ `4690` revision `0C` に限定しています。 + 開発版のGPU受付は、ADL-S `4680/4682/4688/468A/468B/4690/4692/4693` のrevision `0C`と、 + RPL-S `A780/A781/A782/A783/A788/A789/A78A/A78B` のrevision `04/0C`(GT D0)が対象です。 + 物理IDの一致、1 slice・最大2 DSS・16/24/32 EU・4 L3 bank・対応media engine構成を要求し、 + 他のsteppingや資源構成は拒否します。実機確認済みは `4690` のみで、他IDはCPU試験の範囲です。 + 追加IDには更新版のcompanion側ID受付も必要です。 別のポート配置はCPUテストで確認していますが、実機では未検証です。 VBT上でCombo PHY Aの固定DPとして扱われるUSB-C端子でも実表示を確認しています。 これはType-C/TBT管理経路の動作確認ではありません。開発版のeDP経路は、VBTで確認したDDI Aのpanelを diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 9fab4911..40a15ad1 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -7,6 +7,7 @@ #define kern_alderlake_n_hpp #include #include "kern_xelp_platform.hpp" +#include "kern_xelp_gt_resources.hpp" #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_geometry.hpp" #include @@ -30,24 +31,10 @@ inline bool matches(uint32_t identity, uint8_t revision) { return p.family==XELP::Family::AlderLakeN && p.admitted(); } -struct Resources { - uint32_t slices {}, dss {}, nativeSubslices {}, eus {}, l3Banks {}, urbKiB {}; -}; - -// 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 -// the physical LP backend; capacity is independent of the PCI product label. -// Four L3 banks/256KiB is the supported small-GT capacity, not eus*constant. -inline bool resources(uint32_t slice, uint32_t dss, uint32_t euDisable, - uint32_t l3Disable, Resources &out) { - out = {}; - if (slice != 1 || !dss || (dss&~0x3FU) || (euDisable&~0xFFU) || l3Disable != 0xF0) - return false; - const auto count=static_cast(__builtin_popcount(dss)); - const auto eus=2*count*(8-static_cast(__builtin_popcount(euDisable))); - if (count>2 || (eus!=16 && eus!=24 && eus!=32)) return false; - out = {1, count, 2*count, eus, 4, 256}; - return true; +using Resources=XELP::SmallGTResources; +using XELP::smallGTResources; +inline bool resources(uint32_t slice,uint32_t dss,uint32_t euDisable,uint32_t l3Disable,Resources &out) { + return smallGTResources(slice,dss,euDisable,l3Disable,out); } struct Register { uint32_t offset {}, value {}; }; diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 107e79f2..76c074cd 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -7,21 +7,27 @@ #include "kern_vbt.hpp" #include "kern_xelp_panel_power.hpp" #include "kern_xelp_display_resources.hpp" +#include "kern_xelp_gt_resources.hpp" namespace AlderLakeS { constexpr uint32_t PortMask=XELP::ADLSComboResources::physicalMask(); // A, TC1, TC2, TC3, TC4 -// Initial GT qualification is the measured UHD770 D0 configuration. Do not -// infer fused EU/engine resources for other products from their family name. -inline bool identity(uint32_t pci,uint32_t revision) {return pci==0x46908086 && revision==0x0C;} -struct GT { - uint32_t slices {},dss {},nativeSubslices {},eus {},l3Banks {},urbKiB {}; +// ADL-S/RPL-S share the D0 GT backend; physical identity is never spoofed. +inline bool identity(uint32_t pci,uint32_t revision) {return XELP::desktopGTD0(pci,revision);} +struct GT : XELP::SmallGTResources { static bool decode(uint32_t pci,uint32_t revision,const uint32_t *fuses,GT &out) { out={}; - if(!identity(pci,revision) || !fuses || fuses[0]!=1 || fuses[1]!=3 || - fuses[2]!=0 || fuses[3]!=0xF0 || fuses[4]!=0xE00FA)return false; - out={1,2,4,32,4,256};return true; + if(!identity(pci,revision) || !fuses)return false; + for(unsigned i=0;i<5;++i)if(fuses[i]==UINT32_MAX)return false; + // Gen12 slice/DSS/EU-pair fields. Keep the supported four-bank + // capacity; do not derive URB size from EU count or a marketing ID. + const auto video=(~fuses[4])&0xFF,enhance=((~fuses[4])>>16)&0xF; + // Existing donor paths cover VCS0, optional VCS2, and VECS0. + if((video!=1 && video!=5) || enhance!=1)return false; + return XELP::smallGTResources(fuses[0]&0xFF,fuses[1]&0x3F, + fuses[2]&0xFF,fuses[3]&0xFF,out); } }; +static_assert(sizeof(GT)==6*sizeof(uint32_t),"Preserve GT resource layout"); // The donor has three Combo clock slots. D/E need a separate clock allocator. constexpr uint32_t ComboBackendMask=XELP::ADLSComboBackend::mask(XELP::Donor::C004FC1A); // physical A/TC1/TC2, PHY A/B/C struct Port { diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 3b53da6e..c5284398 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -4095,8 +4095,8 @@ 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)); - if(xeProfile.family==XELP::Family::AlderLakeS && xeProfile.identity==0x46908086 && - xeProfile.revision==0x0C && !xeProfile.overridden() && physicalGPU && + if(adlsDetected && AlderLakeS::identity(xeProfile.identity,xeProfile.revision) && + !xeProfile.overridden() && physicalGPU && XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { // Gen12 forcewake/fuse coordinates are shared with the existing reader. // Its ADL-N profile verdict is not used to admit ADL-S. No engine, diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index e1fb9a11..8a74347c 100644 --- a/WhateverXe/kern_xelp_devices.hpp +++ b/WhateverXe/kern_xelp_devices.hpp @@ -16,13 +16,14 @@ struct DeviceEntry { }; // GT::Unknown means catalogued, without an implemented automatic backend. constexpr DeviceEntry DeviceTable[] { - {0x4680, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 770"}, - {0x4682, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, - {0x4688, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, - {0x468B, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, + {0x4680, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, + {0x4682, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 730"}, + {0x4688, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0x468A, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0x468B, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, {0x4690, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, - {0x4692, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, - {0x4693, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 710"}, + {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"}, @@ -40,10 +41,14 @@ constexpr DeviceEntry DeviceTable[] { {0x9A49, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, {0x9A60, Family::TigerLake, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, {0x9A78, Family::TigerLake, Display::XeD, GT::TGLG4, "Intel UHD Graphics"}, - {0xA780, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 770"}, - {0xA782, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, - {0xA788, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, - {0xA78B, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, + {0xA780, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, + {0xA781, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA782, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 730"}, + {0xA783, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA788, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {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"}, @@ -58,5 +63,16 @@ inline const DeviceEntry *deviceEntry(uint32_t pci) { for(const auto &entry:DeviceTable)if(entry.device==(pci>>16))return &entry; return nullptr; } +inline bool desktopGTDevice(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && (e->family==Family::AlderLakeS || e->family==Family::RaptorLakeS); +} +// Linux v6.12 intel_step.c: ADL-S 0C and RPL-S 04/0C are GT D0. +// Earlier ADL-S and unknown revisions need a separate stepping/WA policy. +inline bool desktopGTD0(uint32_t pci,uint32_t revision) { + const auto e=deviceEntry(pci); + return e && ((e->family==Family::AlderLakeS && revision==0xC) || + (e->family==Family::RaptorLakeS && (revision==4 || revision==0xC))); +} } #endif diff --git a/WhateverXe/kern_xelp_gt_resources.hpp b/WhateverXe/kern_xelp_gt_resources.hpp new file mode 100644 index 00000000..61c88aeb --- /dev/null +++ b/WhateverXe/kern_xelp_gt_resources.hpp @@ -0,0 +1,27 @@ +// Shared Gen12 small-GT resource contract, independent of product identity. +#ifndef KERN_XELP_GT_RESOURCES_HPP +#define KERN_XELP_GT_RESOURCES_HPP +#include +namespace XELP { +struct SmallGTResources { + uint32_t slices {}, dss {}, nativeSubslices {}, eus {}, l3Banks {}, urbKiB {}; +}; + +// 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 +// the physical LP backend; capacity is independent of the PCI product label. +// Four L3 banks/256KiB is the supported small-GT capacity, not eus*constant. +inline bool smallGTResources(uint32_t slice, uint32_t dss, uint32_t euDisable, + uint32_t l3Disable, SmallGTResources &out) { + out = {}; + if (slice != 1 || !dss || (dss&~0x3FU) || (euDisable&~0xFFU) || l3Disable != 0xF0) + return false; + const auto count=static_cast(__builtin_popcount(dss)); + const auto eus=2*count*(8-static_cast(__builtin_popcount(euDisable))); + if (count>2 || (eus!=16 && eus!=24 && eus!=32)) return false; + out = {1, count, 2*count, eus, 4, 256}; + return true; +} + +} +#endif diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 54c19eb4..863ed730 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -18,7 +18,8 @@ struct Profile { bool admitted() const {return (identity&0xFFFF)==0x8086 && revision<=255 && donor==Donor::C004FC1A && gt!=GT::Unknown && (family!=Family::AlderLakeN || revision==0) && - (family!=Family::AlderLakeS || (identity==0x46908086 && revision==0x0C && !overridden()));} + ((gt!=GT::ADLSSmall && !desktopGTDevice(identity)) || + (gt==GT::ADLSSmall && desktopGTD0(identity,revision) && !overridden()));} }; inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) { Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp index 1e7dcf81..4c5a6534 100644 --- a/tests/alderlake_s.cpp +++ b/tests/alderlake_s.cpp @@ -7,11 +7,34 @@ using namespace AlderLakeS; int main() { uint32_t fuses[]={1,3,0,0xF0,0xE00FA};GT gt; assert(GT::decode(0x46908086,0xC,fuses,gt) && gt.eus==32 && gt.nativeSubslices==4 && gt.l3Banks==4 && gt.urbKiB==256); - for(unsigned i=0;i<5;++i)for(unsigned bit=0;bit<32;++bit) { - fuses[i]^=1U<=0xA780 && rev==4); + assert(GT::decode((id<<16)|0x8086,rev,fuses,gt)==d0); + assert(!GT::decode((id<<16)|0x1234,rev,fuses,gt));++cases; + } + for(unsigned dss=1;dss<64;++dss)for(unsigned eu=0;eu<256;++eu)for(unsigned video:{1U,5U}) { + fuses[1]=dss;fuses[2]=eu;fuses[4]=0xE0000|(0xFF^video); + const unsigned count=__builtin_popcount(dss),eus=2*count*(8-__builtin_popcount(eu)); + const bool supported=count<=2 && (eus==16 || eus==24 || eus==32); + assert(GT::decode(0xA7808086,4,fuses,gt)==supported); + if(supported)assert(gt.dss==count && gt.nativeSubslices==count*2 && gt.eus==eus && gt.l3Banks==4 && gt.urbKiB==256); + else assert(!gt.eus); + ++cases; + } + const uint32_t original[]={1,3,0,0xF0,0xE00FA};memcpy(fuses,original,sizeof(fuses)); + for(unsigned i=0;i<5;++i) { + fuses[i]=UINT32_MAX;assert(!GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; + fuses[i]|=0x80000000;assert(GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; + } + for(unsigned i:{0U,3U,4U}) { + fuses[i]^=1;assert(!GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; } assert(!GT::decode(0x46D18086,0,fuses,gt));assert(!GT::decode(0x46908086,0,fuses,gt)); assert(!GT::decode(0x46908086,0xC,nullptr,gt)); + printf("PASS %u desktop GT identity/step/resource cases\n",cases); const uint32_t physical[]={0,3,4,5,6},dvo[]={10,1,8,9,11}; const uint32_t expectedPin[]={1,9,10,11,12}; const uint32_t expectedHPD[]={0x10000,0x1000000,0x2000000,0x4000000,0x8000000}; diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 10064e2c..80786566 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -138,15 +138,14 @@ assert(tglStart(nullptr,nullptr)==KERN_SUCCESS); } // Exercise the complete production selection, including the shared Display - // adapter on unadmitted products. Expected identities are independent of the - // new selector; RPL-S remains rejected despite its known Display adapter. + // adapter on registered desktop products and the still-unimplemented ADL-P. for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x4690U,0x4680U,0xA780U,0x46A6U,0x46D2U}) - for(auto rev:{0U,12U,255U})for(unsigned option=0;option<4;++option) { + for(auto rev:{0U,4U,12U,255U})for(unsigned option=0;option<4;++option) { version=kernel;args={}; if(option&1)args.insert("-wex-tgl-off"); if(option&2)args.insert("-wex-noaccel"); XE selected;selected.init();selected.selectProfile(XELP::identify((id<<16)|0x8086,rev)); - const bool desktop=id==0x4690 && rev==12; + const bool desktop=((id==0x4690 || id==0x4680) && rev==12) || (id==0xA780 && (rev==4 || rev==12)); const bool mobile=id==0x9A49 || id==0x9A78; const bool small13=id==0x46D2 && rev==0; assert(selected.adlsDetected==desktop && selected.adlsDisplaySelected==desktop); diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 956d8fc9..77701e61 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -8,7 +8,7 @@ int main() { unsigned implemented=0; for(const auto &entry:DeviceTable) { const uint32_t pci=uint32_t(entry.device)<<16|0x8086; - const auto revision=entry.family==Family::AlderLakeS?12U:0U; + const auto revision=entry.backend==GT::ADLSSmall?12U:0U; auto p=identify(pci,revision); assert(p.identity==pci && p.effectiveIdentity==pci && !p.overridden()); assert(p.family==entry.family && p.display==entry.display && p.gt==entry.backend); @@ -21,11 +21,11 @@ int main() { // TGL/ADL-N explicit override semantics remain unchanged. auto f=select(0x12348086,revision,true,&value,4); assert(f.overridden() && f.identity==0x12348086 && f.effectiveIdentity==pci); - assert(f.admitted()==(p.admitted() && entry.family!=Family::AlderLakeS)); + assert(f.admitted()==(p.admitted() && entry.backend!=GT::ADLSSmall)); assert(!strcmp(model(f),entry.model)); auto nonIntel=select(0x123410DE,0,true,&value,4);assert(!nonIntel.admitted()); } - assert(implemented==9); // Recognition of other table rows adds no native backend. + assert(implemented==24); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); assert(!select(0x12348086,1,true,&id,4).admitted()); // Keep physical stepping guard. diff --git a/tests/provider_binding.py b/tests/provider_binding.py index e74ba7e1..f67ba41d 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"]: + 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"]: subprocess.run([str(exe), mode], check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 20ec7c10..be8b26f4 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -32,15 +32,16 @@ int main(int argc,char **argv) { 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); - const bool adls=!strncmp(argv[1],"adls",4); + const bool rpls=!strncmp(argv[1],"rpls",4); + const bool adls=!strncmp(argv[1],"adls",4) || rpls; const bool legacy=!strncmp(argv[1],"adln-abi1",9); const bool fake=strstr(argv[1],"fake")!=nullptr; - const bool rejected=!strcmp(argv[1],"rpls") || !strcmp(argv[1],"unknown") || !strcmp(argv[1],"adln-revision") || !strcmp(argv[1],"adls-revision"); + const bool rejected=!strcmp(argv[1],"rpls-revision") || !strcmp(argv[1],"unknown") || !strcmp(argv[1],"adln-revision") || !strcmp(argv[1],"adls-revision"); IOService controller;IOPCIDevice pci;OSObject access,port,foreign;IOMemoryMap map; std::vector mmio(0x1000000);alignas(8) uint8_t platform[0x1C0] {}; pci.identity=adln?0x46D18086:0x9A498086; if(adls){pci.identity=0x46908086;pci.revision=12;} - if(!strcmp(argv[1],"rpls")){pci.identity=0xA7808086;pci.revision=12;} + if(rpls){pci.identity=0xA7808086;pci.revision=!strcmp(argv[1],"rpls-revision")?8:4;} if(!strcmp(argv[1],"adls-revision"))pci.revision=0; if(!strcmp(argv[1],"tgl-g4"))pci.identity=0x9A788086; if(!strcmp(argv[1],"tgl-9a40"))pci.identity=0x9A408086; diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 950d1c60..4beef90b 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -15,16 +15,19 @@ int main() { 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); - assert(displayAdapter(p.family)==DisplayAdapter::ADLS && p.gt==GT::Unknown && !p.admitted()); + assert(displayAdapter(p.family)==DisplayAdapter::ADLS && p.gt==GT::ADLSSmall && p.admitted()); ProbeConfig c;c.profile=p;c.combo=0x19;c.comboBackend=0x19; - assert(!c.valid()); // A known Display adapter is not runtime qualification. + assert(c.valid()); // Registered desktop GT uses the common Display adapter. assert(!identify(id,12,0x46908086).admitted()); } - for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,12U,255U}) { + for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,4U,8U,12U,255U}) { const auto p=identify((id<<16)|0x8086,rev); const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78,adln=id>=0x46D0 && id<=0x46D4; - assert(p.admitted()==(tgl || (adln && rev==0) || (id==0x4690 && rev==12))); + 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 && rev==0) || (adls && rev==12) || (rpls && (rev==4 || rev==12)))); + 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 e0689ca1ceb44472a5941e26d4cff29ad77b4850 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:47:37 +0900 Subject: [PATCH 44/58] Select implemented GPU families independently of revision test coverage --- README.md | 9 ++++++--- README_ja.md | 8 +++++--- WhateverXe/kern_alderlake_n.hpp | 2 +- WhateverXe/kern_alderlake_s.hpp | 4 ++-- WhateverXe/kern_xe.cpp | 4 ++-- WhateverXe/kern_xelp_devices.hpp | 8 +------- WhateverXe/kern_xelp_platform.hpp | 5 +++-- docs/device-selection.md | 9 +++++++-- tests/alderlake_n.cpp | 5 +++-- tests/alderlake_s.cpp | 5 ++--- tests/boot_arguments.py | 6 +++--- tests/device_selection.cpp | 5 +++-- tests/host_publication.py | 4 ++-- tests/provider_binding_fixture.cpp | 2 +- tests/xelp_platform.cpp | 4 ++-- 15 files changed, 43 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 9979b661..b128603f 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,9 @@ WEX automatically handles systems with or without an internal display. No family-selection or per-feature enable arguments are required for Alder Lake-N / Twin Lake. +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. @@ -184,10 +187,10 @@ installation or update location. from VBT rather than a board or connector preset, within the current Combo PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability and PHY D/E are excluded. Development GPU admission covers ADL-S IDs `4680/4682/4688/468A/468B/4690/4692/4693` - at revision `0C`, and RPL-S IDs `A780/A781/A782/A783/A788/A789/A78A/A78B` - at revisions `04/0C` (GT D0). Physical identity must match, with one slice, + and RPL-S IDs `A780/A781/A782/A783/A788/A789/A78A/A78B`. + All PCI revisions select the shared family backend. Physical identity must match, with one slice, up to two DSS, 16/24/32 EUs, four L3 banks and the supported media-engine mask. - Other steppings and resource layouts are rejected. Only `4690` has hardware validation; + Resource layouts outside that implementation remain unsupported. Only `4690` revision `0C` has hardware validation; the other IDs have CPU coverage and need the updated companion ID dispatch. Other port layouts have CPU-test coverage but are not hardware-qualified. A USB-C connector exposed by VBT as fixed DP on Combo PHY A has also been tested. This does not qualify diff --git a/README_ja.md b/README_ja.md index 374ce081..4bdd866d 100644 --- a/README_ja.md +++ b/README_ja.md @@ -92,6 +92,8 @@ OpenCoreの`boot-args`に以下を追加してください。 `-wex-nopanel`ブート引数は廃止され、不要になりました。内蔵ディスプレイの有無は自動で判定します。 Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追加する必要はありません。 +GPU backendの選択と実機検証範囲は独立しています。未検証のPCI revisionも同じfamily実装を使い、 +文書化された差分や実際の不具合で必要になった場合にrevision別の処理を追加します。 対応済みのDisplay 13バックエンドではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 このバックエンドはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。Display 12のタイル選択は変更しません。 @@ -132,10 +134,10 @@ Safariのインストール先や更新による配置変更に依存せずData Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 - 開発版のGPU受付は、ADL-S `4680/4682/4688/468A/468B/4690/4692/4693` のrevision `0C`と、 - RPL-S `A780/A781/A782/A783/A788/A789/A78A/A78B` のrevision `04/0C`(GT D0)が対象です。 + 開発版のGPU受付は、ADL-S `4680/4682/4688/468A/468B/4690/4692/4693` と、 + RPL-S `A780/A781/A782/A783/A788/A789/A78A/A78B` が対象で、すべてのPCI revisionが共通backendを選択します。 物理IDの一致、1 slice・最大2 DSS・16/24/32 EU・4 L3 bank・対応media engine構成を要求し、 - 他のsteppingや資源構成は拒否します。実機確認済みは `4690` のみで、他IDはCPU試験の範囲です。 + この実装範囲外の資源構成は未対応です。実機確認済みは `4690` revision `0C` のみで、他IDはCPU試験の範囲です。 追加IDには更新版のcompanion側ID受付も必要です。 別のポート配置はCPUテストで確認していますが、実機では未検証です。 VBT上でCombo PHY Aの固定DPとして扱われるUSB-C端子でも実表示を確認しています。 diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 40a15ad1..2b0e5505 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -26,7 +26,7 @@ inline bool family(uint32_t identity) { return XELP::identify(identity,0).family==XELP::Family::AlderLakeN; } inline bool matches(uint32_t identity, uint8_t revision) { - // adlp_n_revids has only revision0. Unknown steppings need their own WA review. + // Revision identifies the hardware; untested revisions use the family backend. const auto p=XELP::identify(identity,revision); return p.family==XELP::Family::AlderLakeN && p.admitted(); } diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index 76c074cd..c5bec525 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -11,8 +11,8 @@ namespace AlderLakeS { constexpr uint32_t PortMask=XELP::ADLSComboResources::physicalMask(); // A, TC1, TC2, TC3, TC4 -// ADL-S/RPL-S share the D0 GT backend; physical identity is never spoofed. -inline bool identity(uint32_t pci,uint32_t revision) {return XELP::desktopGTD0(pci,revision);} +// ADL-S/RPL-S share the GT backend across revisions; physical identity is never spoofed. +inline bool identity(uint32_t pci,uint32_t revision) {return revision<=255 && XELP::desktopGTDevice(pci);} struct GT : XELP::SmallGTResources { static bool decode(uint32_t pci,uint32_t revision,const uint32_t *fuses,GT &out) { out={}; diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index c5284398..6b0046a6 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3802,8 +3802,8 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { uint64_t connector[] {1,displayRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); pci->setProperty("N97DisplayVersion", AlderLakeN::DisplayVersion, 32); - pci->setProperty("N97GTStep", "A0"); - pci->setProperty("N97DisplayStep", "D0"); + pci->setProperty("N97GTStep", revision==0 ? "A0" : "unknown"); + pci->setProperty("N97DisplayStep", revision==0 ? "D0" : "unknown"); pci->setProperty("N97GGC", pci->configRead16(0x50), 32); if (!(pci->configRead16(4) & 2)) return; const bool linearTest = display13FramebufferRequested; diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index 8a74347c..0b1a11ce 100644 --- a/WhateverXe/kern_xelp_devices.hpp +++ b/WhateverXe/kern_xelp_devices.hpp @@ -67,12 +67,6 @@ inline bool desktopGTDevice(uint32_t pci) { const auto e=deviceEntry(pci); return e && (e->family==Family::AlderLakeS || e->family==Family::RaptorLakeS); } -// Linux v6.12 intel_step.c: ADL-S 0C and RPL-S 04/0C are GT D0. -// Earlier ADL-S and unknown revisions need a separate stepping/WA policy. -inline bool desktopGTD0(uint32_t pci,uint32_t revision) { - const auto e=deviceEntry(pci); - return e && ((e->family==Family::AlderLakeS && revision==0xC) || - (e->family==Family::RaptorLakeS && (revision==4 || revision==0xC))); -} + } #endif diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 863ed730..08d3df18 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -15,11 +15,12 @@ struct Profile { Donor donor {Donor::Unknown}; uint32_t effectiveIdentity {}; // Table selector; may come from device-id. 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 && - (family!=Family::AlderLakeN || revision==0) && ((gt!=GT::ADLSSmall && !desktopGTDevice(identity)) || - (gt==GT::ADLSSmall && desktopGTD0(identity,revision) && !overridden()));} + (gt==GT::ADLSSmall && desktopGTDevice(identity) && !overridden()));} }; inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) { Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; diff --git a/docs/device-selection.md b/docs/device-selection.md index 97544c89..d3a4b3df 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -7,8 +7,11 @@ machine-name setting is required. By default, the physical Intel GPU device ID selects the backend. The implemented IDs are Tiger Lake `9A40`, `9A49`, `9A78` and Alder Lake-N / Twin Lake -`46D0`–`46D4`. The latter require PCI revision 0. OS, driver and hardware-resource -checks still apply after selection. +`46D0`–`46D4`, plus the development ADL-S/RPL-S IDs listed in the README. +All PCI revisions use the selected family backend. Hardware test coverage is +recorded separately and does not gate selection. A stepping-specific branch +requires a concrete documented difference or an observed failure. OS, driver +and hardware-resource checks still apply after selection. Other entries in the table supply identification and display names only. A listed ID does not imply an implemented backend or a tested machine. See the @@ -31,6 +34,8 @@ non-Intel physical devices are rejected. An invalid override does not silently fall back to the physical ID. Remove an obsolete ICL or other unsupported spoof to use automatic selection. A restart is required after changing the selection. +The development ADL-S/RPL-S backend requires its physical ID without an override. + Do not use an override merely to change the displayed GPU name. ## Display name diff --git a/tests/alderlake_n.cpp b/tests/alderlake_n.cpp index 4e01fca1..763a7fa2 100644 --- a/tests/alderlake_n.cpp +++ b/tests/alderlake_n.cpp @@ -191,9 +191,10 @@ int main() { assert(stuck.status==AlderLakeN::GTStatus::ReleaseFailed && stuck.releaseFailedMask==1 && stuck.releasedMask==2); std::puts("AlderLakeN GT forcewake ownership, ACK failure, fuse profile and release fixtures passed"); assert(AlderLakeN::matches(0x46d18086,0)); - for(unsigned id=0x46d0;id<=0x46d4;++id) {assert(AlderLakeN::matches((id<<16)|0x8086,0));assert(!AlderLakeN::matches((id<<16)|0x8086,1));} + for(unsigned id=0x46d0;id<=0x46d4;++id)for(unsigned rev=0;rev<256;++rev) + assert(AlderLakeN::matches((id<<16)|0x8086,rev)); assert(!AlderLakeN::family(0x46cf8086) && !AlderLakeN::family(0x46d58086) && !AlderLakeN::family(0x46d11234)); - assert(!AlderLakeN::matches(0x46d18086,1)); + assert(AlderLakeN::matches(0x46d18086,1)); assert(!AlderLakeN::matches(0x9a498086,0)); AlderLakeN::Resources res; assert(AlderLakeN::resources(1,3,0x88,0xf0,res)); diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp index 4c5a6534..6297f0b2 100644 --- a/tests/alderlake_s.cpp +++ b/tests/alderlake_s.cpp @@ -11,8 +11,7 @@ int main() { const unsigned ids[]={0x4680,0x4682,0x4688,0x468A,0x468B,0x4690,0x4692,0x4693, 0xA780,0xA781,0xA782,0xA783,0xA788,0xA789,0xA78A,0xA78B}; for(auto id:ids)for(unsigned rev=0;rev<256;++rev) { - const bool d0=rev==12 || (id>=0xA780 && rev==4); - assert(GT::decode((id<<16)|0x8086,rev,fuses,gt)==d0); + assert(GT::decode((id<<16)|0x8086,rev,fuses,gt)); assert(!GT::decode((id<<16)|0x1234,rev,fuses,gt));++cases; } for(unsigned dss=1;dss<64;++dss)for(unsigned eu=0;eu<256;++eu)for(unsigned video:{1U,5U}) { @@ -32,7 +31,7 @@ int main() { for(unsigned i:{0U,3U,4U}) { fuses[i]^=1;assert(!GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; } - assert(!GT::decode(0x46D18086,0,fuses,gt));assert(!GT::decode(0x46908086,0,fuses,gt)); + assert(!GT::decode(0x46D18086,0,fuses,gt));assert(!GT::decode(0x46908086,256,fuses,gt)); assert(!GT::decode(0x46908086,0xC,nullptr,gt)); printf("PASS %u desktop GT identity/step/resource cases\n",cases); const uint32_t physical[]={0,3,4,5,6},dvo[]={10,1,8,9,11}; diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 80786566..7368e14b 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -145,9 +145,9 @@ if(option&1)args.insert("-wex-tgl-off"); if(option&2)args.insert("-wex-noaccel"); XE selected;selected.init();selected.selectProfile(XELP::identify((id<<16)|0x8086,rev)); - const bool desktop=((id==0x4690 || id==0x4680) && rev==12) || (id==0xA780 && (rev==4 || rev==12)); + const bool desktop=id==0x4690 || id==0x4680 || id==0xA780; const bool mobile=id==0x9A49 || id==0x9A78; - const bool small13=id==0x46D2 && rev==0; + const bool small13=id==0x46D2; assert(selected.adlsDetected==desktop && selected.adlsDisplaySelected==desktop); assert(selected.adlnDetected==small13 && selected.display13FramebufferRequested==small13); const bool d12=(desktop || mobile) && !(option&1); @@ -171,7 +171,7 @@ xe=boot({},pci);assert(!xe.adlnNativeRequested && !xe.display13FramebufferRequested && !adlnNativeRCSProfileRequested); } xe=XE{};xe.selectProfile(XELP::identify(0x46D28086,1)); - assert(!xe.adlnNativeRequested && !xe.display13FramebufferRequested && !adlnNativeRCSProfileRequested); + assert(xe.adlnNativeRequested && xe.display13FramebufferRequested && adlnNativeRCSProfileRequested); auto malformed=XELP::identify(0x46D28086,0);malformed.display=XELP::Display::XeD; xe=XE{};xe.selectProfile(malformed);assert(!xe.adlnRequested && !adlnNativeRCSProfileRequested); args={};version="unqualified"; diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 77701e61..a78b9711 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -8,7 +8,7 @@ int main() { unsigned implemented=0; for(const auto &entry:DeviceTable) { const uint32_t pci=uint32_t(entry.device)<<16|0x8086; - const auto revision=entry.backend==GT::ADLSSmall?12U:0U; + const auto revision=0U; auto p=identify(pci,revision); assert(p.identity==pci && p.effectiveIdentity==pci && !p.overridden()); assert(p.family==entry.family && p.display==entry.display && p.gt==entry.backend); @@ -28,7 +28,8 @@ int main() { assert(implemented==24); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); - assert(!select(0x12348086,1,true,&id,4).admitted()); // Keep physical stepping guard. + assert(select(0x12348086,1,true,&id,4).admitted()); + assert(!identify(0x46908086,256).admitted()); // Malformed PCI revision width. assert(!select(0x12348086,0,false,nullptr,0).admitted()); for(size_t n:{size_t(0),size_t(1),size_t(2),size_t(3),size_t(5),size_t(8)}) assert(!select(0x9A498086,0,true,&id,n).admitted()); diff --git a/tests/host_publication.py b/tests/host_publication.py index 41f2a7a7..21050ccc 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -67,8 +67,8 @@ DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; DeviceInfo::value.firmwareVendor=apple?DeviceInfo::FirmwareVendor::Apple:DeviceInfo::FirmwareVendor::Other; publish=true;published=false;deviceProperty=nullptr;identity=0x46908086;revision=rev;events.clear(); - XeHost host;host.processKernel(patcher);assert(host.active==(rev==12)); - assert(host.backlightEnabled==(rev==12&&!apple));assert(host.backlightGPU==(rev==12&&!apple?&gpu:nullptr)); + XeHost host;host.processKernel(patcher);assert(host.active); + assert(host.backlightEnabled==!apple);assert(host.backlightGPU==(!apple?&gpu:nullptr)); } puts("PASS actual host admission: absent/unpublished/unsupported GPUs never enter Xe; published PCI reads precede selected-driver dispatch");} ''' diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index be8b26f4..64720428 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -36,7 +36,7 @@ int main(int argc,char **argv) { const bool adls=!strncmp(argv[1],"adls",4) || rpls; const bool legacy=!strncmp(argv[1],"adln-abi1",9); const bool fake=strstr(argv[1],"fake")!=nullptr; - const bool rejected=!strcmp(argv[1],"rpls-revision") || !strcmp(argv[1],"unknown") || !strcmp(argv[1],"adln-revision") || !strcmp(argv[1],"adls-revision"); + const bool rejected=!strcmp(argv[1],"unknown"); IOService controller;IOPCIDevice pci;OSObject access,port,foreign;IOMemoryMap map; std::vector mmio(0x1000000);alignas(8) uint8_t platform[0x1C0] {}; pci.identity=adln?0x46D18086:0x9A498086; diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 4beef90b..21b69198 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -26,7 +26,7 @@ int main() { const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78,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 && rev==0) || (adls && rev==12) || (rpls && (rev==4 || rev==12)))); + assert(p.admitted()==(tgl || adln || adls || rpls)); if(adls||rpls)assert(!identify((id<<16)|0x8086,rev,0x9A498086).admitted()); assert(!adln || p.display==Display::XeLPD); assert(!identify((id<<16)|0x1234,rev).admitted()); @@ -89,7 +89,7 @@ int main() { c.combo=8;c.comboBackend=0x19;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp)); assert(io.reads.empty()); // ADL-S must never probe TGL FIA for its Combo TC1. for(unsigned bad=0;bad<5;++bad){auto x=c;if(bad==0)x.combo=2;if(bad==1)x.comboBackend=0x79; - if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=0;assert(!x.valid());} + if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=256;assert(!x.valid());} assert(!identify(0x46D18086,12,0x46908086).admitted()); assert(!identify(0x46908086,12,0x46808086).admitted()); // Panel mode requires selected Combo A and the exact native panel record. From 427d61b7c62b46e4ad1a1f75ae19a5ebb754d441 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:13:15 +0900 Subject: [PATCH 45/58] Wait for PG2 through PG4 power well fuse readiness --- README.md | 2 + README_ja.md | 1 + WhateverXe/kern_xe.cpp | 17 +++++++ WhateverXe/kern_xe.hpp | 1 + WhateverXe/kern_xelp_donor.hpp | 28 ++++++++++++ tests/tgl_power_fuses.py | 84 ++++++++++++++++++++++++++++++++++ tools/test.py | 4 +- 7 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 tests/tgl_power_fuses.py diff --git a/README.md b/README.md index b128603f..8213b7e9 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ its X-tiled scanout path is unsupported. Display 12 tiling selection is unchange | Alder Lake-N | Big Sur/Tahoe: automatic Y tiling without an EFI `AAPL,GfxYTile` setting | 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 diff --git a/README_ja.md b/README_ja.md index 4bdd866d..698922d6 100644 --- a/README_ja.md +++ b/README_ja.md @@ -107,6 +107,7 @@ GPU backendの選択と実機検証範囲は独立しています。未検証の | Alder Lake-N | Big Sur/Tahoe:EFIの`AAPL,GfxYTile`設定なしでのYタイル自動選択 | 検証した機器での結果であり、すべてのGPU・ディスプレイ・アダプター・ドックでの動作を保証するものではありません。 +記載した解像度は検証範囲であり、WEXが設けた固定の解像度制限ではありません。 Big Sur〜Tahoeでは、有効なApple platform署名とサービス識別子でWebKit GPUを判定し、 Safariのインストール先や更新による配置変更に依存せずData側GPUドライバへのアクセスを扱います。 diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 6b0046a6..48c485da 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -10817,6 +10817,11 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t } if (ready) tglPG1FuseReadCaller = fn + 0x2C7; IORegistryEntry::getRegistryRoot()->setProperty("TGLPG1PollReady", ready); + const bool fuseReady=ready && size>=0x5B640+0x6E1 && + XELPDonor::powerWellFuseSites(reinterpret_cast(fn),size-0x5B640); + if(fuseReady)tglPGFuseWaitFunction=fn; + IORegistryEntry::getRegistryRoot()->setProperty("TGLPG234PollReady",fuseReady); + if(!fuseReady)SYSLOG("igfx","PG2/3/4 fuse wait: donor sites unavailable"); } if(adlsDisplaySelected) { const auto symbol="__ZN17AppleIntelPortHAL4initEP10PortConfig"; @@ -14996,6 +15001,18 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { if(trace)tglTransitionRecord(35,true,access,reg,tglConnectionCaller(__builtin_return_address(0)),value); const auto caller = reinterpret_cast(__builtin_return_address(0)); auto s = callbackXE; + // Only the qualified donor post-ACK fuse reads receive this bounded poll. + if(reg==0x42000) { + const auto mask=XELPDonor::powerWellFuseMask(s->tglPGFuseWaitFunction,caller); + if(mask) { + struct IO { + void *access;uint32_t (*read)(void *,unsigned long); + void delayUs(unsigned us){IODelay(us);} + uint32_t readStatus(){return read(access,0x42000);} + } io {access,reinterpret_cast(s->orgTGLRegisterRead)}; + value=XELPDonor::waitPowerWellFuse(io,value,mask).value; + } + } if (reg == 0x42000 && s->tglPG1FuseReadCaller && caller == s->tglPG1FuseReadCaller) { // Exact donor PG1 fuse check, after its power-state ACK and 5us delay. // Return only real hardware status. A missing bit still terminates; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 5f6a5b00..529fbe95 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -311,6 +311,7 @@ class XE { static volatile uint8_t *tglClockOrderTarget(void *controller, void *port, void *path, void *fb); static void wrapTGLDisableClocks(void *controller, void *port, void *otherPort); mach_vm_address_t tglPG1FuseReadCaller {}; + mach_vm_address_t tglPGFuseWaitFunction {}; mach_vm_address_t tglComboPLLFractionCaller {}; bool tglSinkFramingReady {}; bool tglComboAUXGateReady {}; diff --git a/WhateverXe/kern_xelp_donor.hpp b/WhateverXe/kern_xelp_donor.hpp index 9ae4d992..f81d366a 100644 --- a/WhateverXe/kern_xelp_donor.hpp +++ b/WhateverXe/kern_xelp_donor.hpp @@ -2,11 +2,39 @@ #ifndef KERN_XELP_DONOR_HPP #define KERN_XELP_DONOR_HPP #include +#include namespace XELPDonor { inline bool displayPoolRequest(uint32_t enabled,uint32_t bytes) { return enabled && bytes && !(bytes&4095) && bytes<=0x4000000; } struct Poll {uint32_t value {},elapsedUs {},reads {};}; +// hwSetPowerWellStatePG checks PG2/3/4 once, 1 us after the state ACK. +// Linux gen9_wait_for_power_well_fuses polls the actual fuse status instead. +// Match the complete donor read/test instructions, including the call target. +constexpr uint32_t fuseReadSites[] {0x39D,0x53F,0x6D3}; +inline bool powerWellFuseSites(const uint8_t *function,size_t size) { + static const uint8_t expected[][14] { + {0xBE,0x00,0x20,0x04,0x00,0xE8,0x5D,0x29,0x00,0x00,0x0F,0xBA,0xE0,0x19}, + {0xBE,0x00,0x20,0x04,0x00,0xE8,0xBB,0x27,0x00,0x00,0x0F,0xBA,0xE0,0x18}, + {0xBE,0x00,0x20,0x04,0x00,0xE8,0x27,0x26,0x00,0x00,0x0F,0xBA,0xE0,0x17} + }; + if(!function || size=function)for(unsigned i=0;i<3;++i) + if(caller-function==fuseReadSites[i]+10)return 1U<<(25-i); + return 0; +} +template Poll waitPowerWellFuse(IO &io,uint32_t initial,uint32_t mask) { + Poll r {initial,0,0}; + while(mask && r.value!=UINT32_MAX && !(r.value&mask) && r.elapsedUs<1000) { + io.delayUs(10);r.elapsedUs+=10;r.value=io.readStatus();++r.reads; + } + return r; // Never fabricate readiness or change a power request/refcount. +} template Poll waitEDIDReady(IO &io,uint32_t initial) { Poll r {initial,0,0}; while(!(r.value&0xC00) && r.elapsedUs<50000) { diff --git a/tests/tgl_power_fuses.py b/tests/tgl_power_fuses.py new file mode 100644 index 00000000..681de9a8 --- /dev/null +++ b/tests/tgl_power_fuses.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Replay the production PG2/3/4 poll with delayed, absent and failed MMIO.""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile + +root = Path(__file__).resolve().parents[1] +source = (root/'WhateverXe/kern_xe.cpp').read_text() +begin = source.index('\t// Only the qualified donor post-ACK fuse reads') +body = source[begin:source.index('\tif (reg == 0x42000 && s->tglPG1FuseReadCaller', begin)] +sites = [0x39D, 0x53F, 0x6D3] +expected = [bytes.fromhex(x) for x in [ + 'be00200400e85d2900000fbae019', 'be00200400e8bb2700000fbae018', + 'be00200400e8272600000fbae017']] +function = bytearray(0x700) +for off, seq in zip(sites, expected): + function[off:off+len(seq)] = seq +if len(sys.argv) > 1: + donor = Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest() == 'c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + function = donor[0x5B640:0x5BD40] + for off, seq in zip(sites, expected): + assert function[off:off+14] == seq +code = r''' +#include "kern_xelp_donor.hpp" +#include +#include +#include +#include +static unsigned elapsed,reads,readyAt,failAt; +static uint32_t statusMask; +static void *expectedAccess; +static void IODelay(unsigned us){assert(us==10);elapsed+=us;} +static uint32_t read(void *access,unsigned long reg){ + assert(access==expectedAccess && reg==0x42000);++reads; + if(elapsed>=failAt)return UINT32_MAX; + return 0x80000000U | (elapsed>=readyAt ? statusMask : 0); +} +struct State {uintptr_t tglPGFuseWaitFunction=0x100000;uintptr_t orgTGLRegisterRead=reinterpret_cast(read);} state; +uint32_t run(void *access,unsigned long reg,uintptr_t caller,uint32_t value){ + auto s=&state; + BODY + return value; +} +int main(){ + uint8_t function[]={BYTES}; + assert(XELPDonor::powerWellFuseSites(function,sizeof(function))); + assert(!XELPDonor::powerWellFuseSites(nullptr,sizeof(function))); + for(unsigned n=0;n<0x6E1;++n)assert(!XELPDonor::powerWellFuseSites(function,n)); + // Every instruction byte, including the callee displacement, is guarded. + for(auto off:XELPDonor::fuseReadSites)for(unsigned j=0;j<14;++j){ + function[off+j]^=1;assert(!XELPDonor::powerWellFuseSites(function,sizeof(function)));function[off+j]^=1; + } + expectedAccess=function;unsigned cases=0; + for(unsigned pg=0;pg<3;++pg)for(unsigned delay=0;delay<=1010;delay+=10) + for(unsigned failure:{UINT32_MAX,20U}) { + elapsed=reads=0;readyAt=delay;failAt=failure;statusMask=1U<<(25-pg); + uint32_t initial=0x80000000U|(delay==0?statusMask:0); + auto result=run(expectedAccess,0x42000,0x100000+XELPDonor::fuseReadSites[pg]+10,initial); + unsigned end=delay<1000?delay:1000;if(failure=failure?UINT32_MAX:0x80000000U|(end>=delay?statusMask:0))); + ++cases; + } + for(uintptr_t caller=0x100000;caller<0x100700;++caller){ + auto mask=XELPDonor::powerWellFuseMask(0x100000,caller); + for(auto reg:{0x42000UL,0x45404UL})for(auto initial:{0U,UINT32_MAX,0xFFFFFFFFU ^ mask}){ + elapsed=reads=0;readyAt=failAt=UINT32_MAX; + auto result=run(expectedAccess,reg,caller,initial); + const bool wait=reg==0x42000 && mask && initial!=UINT32_MAX; + assert(elapsed==(wait?1000U:0U) && reads==elapsed/10); + assert(result==(wait?0x80000000U:initial));++cases; + } + } + state.tglPGFuseWaitFunction=0;elapsed=reads=0; + assert(run(expectedAccess,0x42000,0x1003A7,0)==0 && !reads && !elapsed); + assert(!XELPDonor::powerWellFuseMask(0x100000,0xFFFFF)); + printf("PASS: %u production fuse poll cases; exact sites, timeout, read failure, unrelated callers\n",cases); +} +'''.replace('BODY', body).replace('BYTES', ','.join(map(str, function))) +with tempfile.TemporaryDirectory() as tmp: + p=Path(tmp); (p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined', + '-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tools/test.py b/tools/test.py index 5b2e1e56..a6a568d5 100644 --- a/tools/test.py +++ b/tools/test.py @@ -7,9 +7,9 @@ O=R/'build/tests';O.mkdir(parents=True,exist_ok=True) python_tests=['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 += ['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 += ['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_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_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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] def check(job): n,compile,execute=job From 33742121d30930928bb2c5632373b640925ec9d7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:18:52 +0900 Subject: [PATCH 46/58] Use a millisecond fuse wait budget for Display 13 --- WhateverXe/kern_display13_power.hpp | 12 +++++--- tests/display13_power.cpp | 47 +++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index 42a6f477..2685b86a 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -16,6 +16,10 @@ constexpr uint32_t LegacyDomains = (1U << 7) - 1; // Existing B backend scope. constexpr uint32_t BootHDMIA = bit(Domain::PipeA) | bit(Domain::DDIA); constexpr uint32_t BootHDMIB = bit(Domain::PipeA) | bit(Domain::DDIB); constexpr uint32_t Driver = 0x45404, DDI = 0x45454, AUX = 0x45444, Fuses = 0x42000; +// gen9_wait_for_power_well_fuses passes 1 to intel_de_wait_for_set. +// That API reaches intel_wait_for_register(timeout_ms), despite the 1/5 us +// hardware timing comment. Keep an explicit microsecond budget here. +constexpr unsigned FuseTimeoutUs = 1000; constexpr bool tcAux(unsigned d) { return d>=12 && d=11 && d Result acquire(IO &io, State &s, Lease &lease, uint32_t domai if (!(domains & mask)) continue; r.domain = d; if (d == 0) { - r.status = wait(io, Fuses, 1U << 27, 5); // PG0 before PW1. + r.status = wait(io, Fuses, 1U << 27, FuseTimeoutUs); // PG0 before PW1. if (r.status != Status::Complete) break; } if (!valid(io, control(d), value)) { r.status = Status::Unavailable; break; } @@ -196,7 +200,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai r.status=waitTCAuxHealth(io,d);if(r.status!=Status::Complete)break; } if (d < 5) { - r.status = wait(io, Fuses, fuse(d), 1); + r.status = wait(io, Fuses, fuse(d), FuseTimeoutUs); if (r.status != Status::Complete) break; } } @@ -283,7 +287,7 @@ template Result restore(IO &io,State &s) { r.status=Status::Complete; for(unsigned d=0;d Result restore(IO &io,State &s) { r.status=wait(io,control(d),enabled(d),tcAux(d)?500000:1000); if(r.status==Status::Complete && tcAux(d))r.status=waitTCAuxHealth(io,d); if(r.status!=Status::Complete)break; - if(d<5)r.status=wait(io,Fuses,fuse(d),1); + if(d<5)r.status=wait(io,Fuses,fuse(d),FuseTimeoutUs); if(r.status!=Status::Complete)break; } if(r.status==Status::Complete) {s.suspended=false;r.domain=Count;} diff --git a/tests/display13_power.cpp b/tests/display13_power.cpp index e1070fd1..0a802c51 100644 --- a/tests/display13_power.cpp +++ b/tests/display13_power.cpp @@ -15,13 +15,18 @@ struct IO { {0x45400,0}, {0x45408,0}, {0x4540C,0}, {0x45450,0}, {0x4545C,0}}; std::vector> writes; uint32_t deny {}, allones {}, failedEnable {}, failedEnableReg {}, stickyRelease {}; - unsigned time {}, delayEnable {}; + unsigned time {}, delayEnable {}, delayFuse {}, fuseUnavailableAt {UINT32_MAX}; + uint32_t delayedFuse {}; bool loseDDIAfterWrite {}; bool readable(uint32_t reg) { return reg != deny && regs.count(reg); } uint32_t read(uint32_t reg) { assert(regs.count(reg)); if (reg == allones || (loseDDIAfterWrite && reg==0x45454 && writes.size()>=3)) return UINT32_MAX; update(); + if(reg==0x42000) { + if(time>=fuseUnavailableAt)return UINT32_MAX; + return regs[reg] & ~(time Date: Sat, 24 Oct 2026 14:25:34 +0900 Subject: [PATCH 47/58] Add bounded hotplug and training completion diagnostics --- WhateverXe/kern_xe.cpp | 43 ++++++++++++++++++++++++++ WhateverXe/kern_xe.hpp | 5 +++ tests/tgl_aux_live.py | 3 ++ tests/tgl_training_pattern_fixture.cpp | 3 ++ tests/tgl_transition_live.py | 6 ++++ 5 files changed, 60 insertions(+) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 48c485da..67310274 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3354,6 +3354,7 @@ void tglDPCDRecord(void *port, uint32_t address, void *data, uint32_t length, w[6] = length < 16 ? length : 16; memcpy(w + 8, data, w[6]); } + tglTransitionRecord(98,done,port,address,(uint64_t(write)<<32)|length,result,w+8,16); IOSimpleLockLock(tglAUXLock); w[0] = ++tglDPCDCount; if (w[0] <= arrsize(tglDPCDFirst)) tglDPCDFirst[w[0]-1] = record; @@ -11135,6 +11136,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t tglAUXLock = IOSimpleLockAlloc(); tglDisplayTraceEnabled=checkKernelArgument("-wex-display-trace"); tglInitTransitionTrace(tglDisplayTraceEnabled); + if (tglConnectionLock) { KernelPatcher::RouteRequest link("__ZN14AppleIntelPort15checkLinkStatusEv", wrapTGLCheckLinkStatus, orgTGLCheckLinkStatus); @@ -11382,6 +11384,20 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayStartupContract",contractRecord,sizeof(contractRecord)); IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); } + if (tglDisplayTraceEnabled) { + // Install after native entry-byte qualification. Software + // boundaries around the empty-dongle path add no extra + // register accesses or changes to the donor's return contracts. + KernelPatcher::RouteRequest traceRoutes[] { + {"__ZN31AppleIntelFramebufferController20invokeHotplugHandlerEjN14AppleIntelPort8PortModeE", wrapTGLTraceHotplug, orgTGLTraceHotplug}, + {"__ZN14AppleIntelPort17clearACPIHPDCountEv", wrapTGLTraceClearHPD, orgTGLTraceClearHPD}, + {"__ZN19AppleIntelPowerWell17enablePowerWellPGEj", wrapTGLTraceEnablePG, orgTGLTraceEnablePG}, + {"__ZN19AppleIntelPowerWell18disablePowerWellPGEj", wrapTGLTraceDisablePG, orgTGLTraceDisablePG} + }; + const bool traced=patcher.routeMultiple(index,traceRoutes,arrsize(traceRoutes),address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeHotplugBoundaryTrace",traced); + patcher.clearError(); + } tglRecordStartOrder(1,nullptr); return true; } @@ -14407,8 +14423,10 @@ uint32_t XE::wrapTGLDPPower(void *port, uint8_t state) { uint32_t XE::wrapTGLSinkCount(void *port, uint8_t *count) { auto caller = __builtin_return_address(0); + tglTransitionRecord(95,false,port,0,0,0); tglConnectionRecord(port, 5, false, 0, 0, caller); const auto result = FunctionCast(wrapTGLSinkCount, callbackXE->orgTGLSinkCount)(port, count); + tglTransitionRecord(95,true,port,0,0,result==0 && count ? *count : 0xFFFFFFFFU); tglConnectionRecord(port, 5, true, result, !result && count ? *count : 0xFFFFFFFFU, caller); auto config = *reinterpret_cast(static_cast(port) + 0x548); if (config && config[0] < 9) { @@ -14420,6 +14438,27 @@ uint32_t XE::wrapTGLSinkCount(void *port, uint8_t *count) { return result; } +void XE::wrapTGLTraceHotplug(void *controller, uint32_t ddi, uint32_t mode) { + tglTransitionRecord(90,false,controller,ddi,mode,0); + FunctionCast(wrapTGLTraceHotplug,callbackXE->orgTGLTraceHotplug)(controller,ddi,mode); + tglTransitionRecord(90,true,controller,ddi,mode,0); +} +void XE::wrapTGLTraceClearHPD(void *port) { + tglTransitionRecord(91,false,port,0,0,0); + FunctionCast(wrapTGLTraceClearHPD,callbackXE->orgTGLTraceClearHPD)(port); + tglTransitionRecord(91,true,port,0,0,0); +} +void XE::wrapTGLTraceEnablePG(void *power, uint32_t pg) { + tglTransitionRecord(92,false,power,pg,0,0); + FunctionCast(wrapTGLTraceEnablePG,callbackXE->orgTGLTraceEnablePG)(power,pg); + tglTransitionRecord(92,true,power,pg,0,0); +} +void XE::wrapTGLTraceDisablePG(void *power, uint32_t pg) { + tglTransitionRecord(93,false,power,pg,0,0); + FunctionCast(wrapTGLTraceDisablePG,callbackXE->orgTGLTraceDisablePG)(power,pg); + tglTransitionRecord(93,true,power,pg,0,0); +} + uint32_t XE::wrapTGLCheckLinkStatus(void *port) { auto caller = __builtin_return_address(0); tglConnectionRecord(port, 6, false, 0, 0, caller); @@ -16951,6 +16990,7 @@ uint32_t XE::wrapTGLI2CAUX(void *port, uint32_t command, uint32_t *packet, uint3 } uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { + tglTransitionRecord(96,false,path,pattern,0,0); auto port = *reinterpret_cast(static_cast(path) + 0x3648); tglObserveActivation(callbackXE->tglObservationController, port, path, 1, false, pattern); // TGL needs the transcoder clock and DDI route before enabling DP_TP_CTL. @@ -17010,6 +17050,7 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { } } const auto result = FunctionCast(wrapTGLTrainingPattern, callbackXE->orgTGLTrainingPattern)(path, pattern); + tglTransitionRecord(97,true,path,pattern,0,result); // The donor constructs DP_TP_CTL from enable/MST/pattern only, losing bit18 // and eDP Alternate SR bit6 (TGL PRM Vol2c Part1, DP_TP_CTL). // Its own normal and fast training write port+11a to DPCD101 bit7. Use that @@ -17038,6 +17079,7 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { if (mainPower == 0xFFFFFFFFU || ioPower == 0xFFFFFFFFU || (mainPower & mainMask) != mainMask || (ioPower & ioMask) != ioMask) { tglObserveActivation(callbackXE->tglObservationController, port, path, 1, true, result); + tglTransitionRecord(96,true,path,pattern,0,result); return result; } const auto read=[&](uint32_t off){return *reinterpret_cast(base+off);}; @@ -17059,6 +17101,7 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { } } tglObserveActivation(callbackXE->tglObservationController, port, path, 1, true, result); + tglTransitionRecord(96,true,path,pattern,0,result); return result; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 529fbe95..5aa969b2 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -494,6 +494,11 @@ class XE { static void tglTimeEvent(uint32_t stage, uint32_t id, bool done, uint32_t arg, uint32_t result); mach_vm_address_t orgTGLTrainingPattern {}, orgTGLPowerDDIState {}; + mach_vm_address_t orgTGLTraceHotplug {}, orgTGLTraceClearHPD {}, orgTGLTraceEnablePG {}, orgTGLTraceDisablePG {}; + static void wrapTGLTraceHotplug(void *controller, uint32_t ddi, uint32_t mode); + static void wrapTGLTraceClearHPD(void *port); + static void wrapTGLTraceEnablePG(void *power, uint32_t pg); + static void wrapTGLTraceDisablePG(void *power, uint32_t pg); static uint32_t wrapTGLTrainingPattern(void *path, uint32_t pattern); mach_vm_address_t orgTGLPortTraining {}, orgTGLPathLookup {}, orgTGLFreePath {}; mach_vm_address_t orgTGLLightUpDP {}, orgTGLDisableDisplay {}, orgTGLEnablePipe {}; diff --git a/tests/tgl_aux_live.py b/tests/tgl_aux_live.py index 237bf07a..ebbfb0c9 100644 --- a/tests/tgl_aux_live.py +++ b/tests/tgl_aux_live.py @@ -20,6 +20,9 @@ uint32_t tglConnectionCaller(void*){return 0x1234;} bool quiet=false,failAlloc=false,failData=false; int lock,*tglAUXLock=&lock;bool held=false; +void tglTransitionRecord(uint32_t stage,bool,void*,uint64_t,uint64_t,uint64_t,const void *payload,size_t size){ + assert(!held && stage==98 && size==16);uint8_t copy[16];memcpy(copy,payload,size); +} void IOSimpleLockLock(int*) {assert(!held);held=true;} void IOSimpleLockUnlock(int*) {assert(held);held=false;} bool tglQuietTraining(){return quiet;} diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index 72b9bfe8..eab6e663 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -24,6 +24,9 @@ struct IORegistryEntry { void clock_get_uptime(uint64_t *p){*p=1;} void absolutetime_to_nanoseconds(uint64_t t,uint64_t *p){*p=t;} unsigned observations,nativeCalls; +void tglTransitionRecord(uint32_t stage,bool,void*,uint64_t,uint64_t,uint64_t) { + assert(stage==96 || stage==97); +} struct XE { bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // FIELDS diff --git a/tests/tgl_transition_live.py b/tests/tgl_transition_live.py index 70a5b6fc..e0e01d7a 100644 --- a/tests/tgl_transition_live.py +++ b/tests/tgl_transition_live.py @@ -6,6 +6,12 @@ root = Path(__file__).resolve().parents[1] source = (root / 'WhateverXe/kern_xe.cpp').read_text() +# Tracing rewrites PG entry bytes. Qualify the native ABI before routing those +# entries, or enabling diagnostics alone blocks framebuffer startup. +trace_install = source.index('KernelPatcher::RouteRequest traceRoutes[]') +assert source.index('!memcmp(reinterpret_cast(tglDisablePG),tcPGPutEntry') < trace_install +assert source.index('tglNativeLegacyHPDCaller=hotplug+0x185') < trace_install +assert source.rfind('if (tglDisplayTraceEnabled)', 0, trace_install) >= 0 body = source[source.index('struct TGLTransitionRecord {'): source.index('// Independent and nested training calls')] fixture = r''' From 2cec523f97c6b639012d2c141fa1955179c3a15c Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:28:52 +0900 Subject: [PATCH 48/58] Cover all Display 12 pipe and DDI control registers in traces --- WhateverXe/kern_xe.cpp | 11 ++++++++--- tests/tgl_transition_live.py | 14 +++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 67310274..b13372a6 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3308,14 +3308,19 @@ static bool tglTransitionRegister(unsigned long reg) { // change AUX reachability when the transmitter is enabled; retain native // PHY, PLL, route and panel-power ordering without additional hardware IO. if (reg & 3) return false; + // Cover each Display 12 pipe and physical DDI, independent of which + // connector owns the current path. These are filters, not MMIO probes. + const bool pipe=reg>=0x60000 && reg<0x63000 && + ((reg&0xFFF)==0x400 || (reg&0xFFF)==0x540); + const bool buffer=reg>=0x64000 && reg<=0x64800 && !(reg&0xFF); + const bool clock=reg>=0x46140 && reg<=0x46148; return reg==0x45400 || reg==0x45404 || reg==0x45408 || reg==0x4540C || reg==0x45450 || reg==0x45454 || reg==0x45440 || reg==0x45444 || reg==0x164280 || reg==0x46010 || reg==0x46014 || (reg>=0x162000 && reg<0x163000) || (reg>=0x6C000 && reg<0x6D000) || (reg>=0x164284 && reg<=0x164290) || (reg>=0xC7200 && reg<=0xC7210) || - reg==0x64000 || reg==0x64100 || reg==0x46140 || reg==0x46144 || - reg==0x60400 || reg==0x61400 || reg==0x60540 || reg==0x61540 || + pipe || buffer || clock || (reg>=0x163880 && reg<=0x1638A4) || reg==0x1638C0 || - (reg>=0x4610C && reg<=0x46120) || reg==0x64300 || reg==0x64400; + (reg>=0x4610C && reg<=0x46120); } // Independent and nested training calls retain their own suppression scope. diff --git a/tests/tgl_transition_live.py b/tests/tgl_transition_live.py index e0e01d7a..ff07281c 100644 --- a/tests/tgl_transition_live.py +++ b/tests/tgl_transition_live.py @@ -109,8 +109,20 @@ 0x64000ul,0x64100ul,0x46140ul,0x46144ul,0x60400ul,0x61400ul,0x60540ul,0x61540ul}) assert(tglTransitionRegister(reg)); for(auto reg:{0ul,0x45410ul,0x45451ul,0x138124ul,0x162001ul,0x163000ul,0x6D000ul, - 0x164294ul,0xC7214ul,0x64200ul,0x64010ul}) + 0x164294ul,0xC7214ul,0x64010ul,0x64900ul,0x4614Cul,0x63400ul,0x63540ul}) assert(!tglTransitionRegister(reg)); + for(unsigned pipe=0;pipe<3;++pipe){ + assert(tglTransitionRegister(0x60400+0x1000*pipe)); + assert(tglTransitionRegister(0x60540+0x1000*pipe)); + assert(tglTransitionRegister(0x46140+4*pipe)); + assert(!tglTransitionRegister(0x60404+0x1000*pipe)); + } + for(unsigned ddi=0;ddi<9;++ddi){ + assert(tglTransitionRegister(0x64000+0x100*ddi)); + assert(!tglTransitionRegister(0x64004+0x100*ddi)); + } + tglDisplayTraceEnabled=false; + for(auto reg:{0x62400ul,0x62540ul,0x64500ul,0x46148ul})assert(!tglTransitionRegister(reg)); munmap(inaccessible,4096); IOFree(tglTransitions,tglTransitionSlots*sizeof(TGLTransitionRecord)); IOFree(tglTransitionFirst,tglTransitionFirstSlots*sizeof(TGLTransitionRecord)); From 6c0ac724c6da49159635bb08e40c9e8e712f75e6 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:37:29 +0900 Subject: [PATCH 49/58] Retain concurrent display activity in compact live diagnostics --- WhateverXe/kern_xe.cpp | 87 ++++++++++++++++++++++++++++++++++++ tests/tgl_transition_live.py | 48 +++++++++++++++++++- tests/xelp_forcewake_irq.py | 1 + 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index b13372a6..82ca0d07 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3302,6 +3302,75 @@ static bool tglTransitionSerialize(void *,void *reference,OSSerialize *serialize auto data=OSData::withBytes(copy,size);IOFree(copy,size);if(!data)return false; const bool ok=data->serialize(serializer);data->release();return ok; } + +// Concurrent native calls are retained separately from the ordered transition +// ring. Never overwrite a pending call when another CPU/IRQ makes progress. +// Observers copy memory only; the original caller still owns every HW access. +static uint32_t tglDisplayTraceScopes {}; +static uint64_t tglDisplayActivityCount {}, tglDisplayActivityDropped {}; +static TGLTransitionRecord tglDisplayActivity[64] {}; +struct TGLDisplayTraceScope { + bool enabled; + TGLDisplayTraceScope():enabled(tglTransitionLock!=nullptr) { + if(enabled)__atomic_fetch_add(&tglDisplayTraceScopes,1U,__ATOMIC_RELAXED); + } + ~TGLDisplayTraceScope() { + if(enabled)__atomic_fetch_sub(&tglDisplayTraceScopes,1U,__ATOMIC_RELAXED); + } +}; +static uint64_t tglDisplayActivityBegin(unsigned kind,void *object,uint64_t argument) { + if(!tglTransitionLock || !__atomic_load_n(&tglDisplayTraceScopes,__ATOMIC_RELAXED))return 0; + TGLTransitionRecord r {};uint64_t ticks; + clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,r.q); + r.q[2]=reinterpret_cast(current_thread());r.q[4]=kind; + r.q[5]=reinterpret_cast(object);r.q[6]=argument; + const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); + unsigned slot=arrsize(tglDisplayActivity); + for(unsigned i=0;i=arrsize(tglDisplayActivity))return; + uint64_t ticks,ns;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&ns); + const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); + auto &q=tglDisplayActivity[slot].q; + if(q[3]==(token>>8) && !q[8]){q[1]=ns;q[7]=result;q[8]=1;} + IOSimpleLockUnlockEnableInterrupt(tglTransitionLock,irq); +} +struct TGLDisplayActivity { + uint64_t token; + TGLDisplayActivity(unsigned kind,void *object,uint64_t argument=0):token(tglDisplayActivityBegin(kind,object,argument)) {} + void finish(uint64_t result) {tglDisplayActivityEnd(token,result);token=0;} + ~TGLDisplayActivity() {finish(0);} +}; +static bool tglDisplayLiveSerialize(void *,void *,OSSerialize *serializer) { + if(!tglTransitionLock)return false; + constexpr unsigned slots=256; + const size_t size=64+slots*sizeof(TGLTransitionRecord)+sizeof(tglDisplayActivity); + auto copy=static_cast(IOMalloc(size));if(!copy)return false; + auto h=reinterpret_cast(copy);bzero(copy,size);uint64_t ticks; + clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,h+1); + h[0]=1;h[3]=slots;h[5]=arrsize(tglDisplayActivity); + const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); + h[2]=tglTransitionCount;h[4]=tglDisplayActivityCount;h[6]=tglDisplayActivityDropped; + h[7]=__atomic_load_n(&tglDisplayTraceScopes,__ATOMIC_RELAXED); + auto out=reinterpret_cast(copy+64); + const unsigned count=tglTransitionCountserialize(serializer);data->release();return ok; +} static bool tglTransitionRegister(unsigned long reg) { if(!tglDisplayTraceEnabled)return false; // Record only accesses already issued by the donor. Combo training can @@ -11744,6 +11813,9 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglTransitionSerialize,reinterpret_cast(1))) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTransitionFirst",live);live->release(); } + if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglDisplayLiveSerialize)) { + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDisplayLive",live);live->release(); + } if(auto live=OSSerializer::forTarget(nullptr,tglDPCDSerialize)) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPCDLive",live);live->release(); } @@ -12550,16 +12622,19 @@ void XE::wrapTGLHostIRQEnable(void *bridge) { } void XE::wrapTGLHostIRQProcess(void *bridge) { + TGLDisplayActivity activity(0,bridge); auto r=tglHostIRQBegin(0,bridge,0,0,reinterpret_cast(__builtin_return_address(0))); FunctionCast(wrapTGLHostIRQProcess,callbackXE->orgTGLHostIRQProcess)(bridge); tglHostIRQEnd(r); } void XE::wrapTGLHostIRQService(void *bridge,const uint64_t *bits) { + TGLDisplayActivity activity(1,bridge,bits?*bits:0); auto r=tglHostIRQBegin(1,bridge,bits ? *bits : 0,0,reinterpret_cast(__builtin_return_address(0))); FunctionCast(wrapTGLHostIRQService,callbackXE->orgTGLHostIRQService)(bridge,bits); tglHostIRQEnd(r); } void XE::wrapTGLHostSWInterrupt(void *streamer,void *source,int count) { + TGLDisplayActivity activity(2,streamer,static_cast(count)); auto r=tglHostIRQBegin(2,streamer,reinterpret_cast(source),count,reinterpret_cast(__builtin_return_address(0))); FunctionCast(wrapTGLHostSWInterrupt,callbackXE->orgTGLHostSWInterrupt)(streamer,source,count); tglHostIRQEnd(r); @@ -12580,6 +12655,7 @@ mach_vm_address_t xelpRegularForceWakeOriginal {}; // >=100us, >=1ms, bypass, total native ns, >=10ms. No MMIO from readers. static uint64_t xelpForceWakeIRQ[9] {}; void xelpRegularForceWakeProtected(void *owner,bool enable,unsigned domains) { + TGLDisplayActivity activity(3,owner,(uint64_t(enable)<<32)|domains); uint64_t flags=0; if(owner)memcpy(&flags,static_cast(owner)+0x1190,sizeof(flags)); if(!owner || !XELPForceWakeIRQ::required(flags,domains)) { @@ -14444,6 +14520,8 @@ uint32_t XE::wrapTGLSinkCount(void *port, uint8_t *count) { } void XE::wrapTGLTraceHotplug(void *controller, uint32_t ddi, uint32_t mode) { + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(7,controller,(uint64_t(ddi)<<32)|mode); tglTransitionRecord(90,false,controller,ddi,mode,0); FunctionCast(wrapTGLTraceHotplug,callbackXE->orgTGLTraceHotplug)(controller,ddi,mode); tglTransitionRecord(90,true,controller,ddi,mode,0); @@ -14454,11 +14532,13 @@ void XE::wrapTGLTraceClearHPD(void *port) { tglTransitionRecord(91,true,port,0,0,0); } void XE::wrapTGLTraceEnablePG(void *power, uint32_t pg) { + TGLDisplayActivity activity(5,power,pg); tglTransitionRecord(92,false,power,pg,0,0); FunctionCast(wrapTGLTraceEnablePG,callbackXE->orgTGLTraceEnablePG)(power,pg); tglTransitionRecord(92,true,power,pg,0,0); } void XE::wrapTGLTraceDisablePG(void *power, uint32_t pg) { + TGLDisplayActivity activity(6,power,pg); tglTransitionRecord(93,false,power,pg,0,0); FunctionCast(wrapTGLTraceDisablePG,callbackXE->orgTGLTraceDisablePG)(power,pg); tglTransitionRecord(93,true,power,pg,0,0); @@ -14900,6 +14980,7 @@ void XE::wrapTGLMEIInit(void *driver) { } void XE::wrapTGLMEIInterrupt(void *driver, void *source, int count) { + TGLDisplayActivity activity(4,driver,static_cast(count)); if (!tglMEIMapping[2]) { auto map = *reinterpret_cast(static_cast(driver) + 0x138); if (map) { @@ -16294,11 +16375,14 @@ uint32_t XE::wrapTGLPortTraining(void *port, void *request) { } } tglAUXRecord(port,10,false,0,0,0); + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(8,port,reinterpret_cast(request)); SYSLOG("igfx", "TGL330 port-training enter"); auto caller = __builtin_return_address(0); tglObservePath(port, nullptr, 0, false, 0, request != nullptr, caller); const auto token = tglTrainingScopes.enter(reinterpret_cast(current_thread())); const auto r = FunctionCast(wrapTGLPortTraining, callbackXE->orgTGLPortTraining)(port, request); + activity.finish(r); tglTrainingScopes.leave(token); tglAUXRecord(port,10,true,r,0,0); SYSLOG("igfx", "TGL330 port-training return result=%x", r); @@ -16328,11 +16412,14 @@ void XE::wrapTGLFreePath(void *path) { } uint32_t XE::wrapTGLLightUpDP(void *controller, void *fb, void *path, const void *timing) { + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(9,path,reinterpret_cast(fb)); auto port=path?*reinterpret_cast(static_cast(path)+0x3648):nullptr; tglAUXRecord(port,11,false,0,0,0); auto caller = __builtin_return_address(0); tglObservePath(nullptr, path, 3, false, 0, 0, caller); const auto r = FunctionCast(wrapTGLLightUpDP, callbackXE->orgTGLLightUpDP)(controller, fb, path, timing); + activity.finish(r); tglAUXRecord(port,11,true,r,0,0); tglObservePath(nullptr, path, 3, true, r, 0, caller); tglDPCDPublish(); diff --git a/tests/tgl_transition_live.py b/tests/tgl_transition_live.py index ff07281c..1b8278b1 100644 --- a/tests/tgl_transition_live.py +++ b/tests/tgl_transition_live.py @@ -123,11 +123,57 @@ } tglDisplayTraceEnabled=false; for(auto reg:{0x62400ul,0x62540ul,0x64500ul,0x46148ul})assert(!tglTransitionRegister(reg)); + // Concurrent progress is independent of transition ring wrap. An inactive + // scope costs no record and opaque objects must never be dereferenced. + assert(!tglDisplayActivityBegin(0,inaccessible,1)); + assert(!tglDisplayActivityCount && !tglDisplayTraceScopes); + { + TGLDisplayTraceScope scope;assert(tglDisplayTraceScopes==1); + {TGLDisplayTraceScope nested;assert(tglDisplayTraceScopes==2);} + assert(tglDisplayTraceScopes==1); + uint64_t pending[64]; + for(unsigned i=0;i<64;++i)pending[i]=tglDisplayActivityBegin(i,inaccessible,i+1); + assert(tglDisplayActivityCount==64); + assert(!tglDisplayActivityBegin(64,inaccessible,0));assert(tglDisplayActivityDropped==1); + assert(tglDisplayLiveSerialize(nullptr,nullptr,&snapshot)); + uint64_t h[8];memcpy(h,snapshot.data.data(),sizeof(h)); + assert(h[0]==1 && h[2]==10051 && h[3]==256 && h[4]==64 && h[5]==64 && h[6]==1 && h[7]==1); + assert(snapshot.data.size()==64+(256+64)*128); + for(unsigned i=0;i<256;++i){ + TGLTransitionRecord q;memcpy(&q,snapshot.data.data()+64+i*128,128); + assert(q.q[2]==10051-255+i); + } + tglDisplayActivityEnd(pending[63],7); + auto reused=tglDisplayActivityBegin(99,inaccessible,4);assert(reused && reused!=pending[63]); + tglDisplayActivityEnd(pending[63],123); // stale completion cannot close a reused slot + assert(!tglDisplayActivity[63].q[8]); + tglDisplayActivityEnd(reused,9);assert(tglDisplayActivity[63].q[7]==9); + for(unsigned i=0;i<63;++i)assert(!tglDisplayActivity[i].q[8]); + for(unsigned i=0;i<63;++i)tglDisplayActivityEnd(pending[i],0); + for(bool prior:{false,true}){ + irqEnabled=prior; + {TGLDisplayActivity call(3,inaccessible,1);} + assert(irqEnabled==prior&&!held); + assert(tglDisplayLiveSerialize(nullptr,nullptr,&snapshot));assert(irqEnabled==prior); + } + irqEnabled=true; + auto activityWrite=[&]{for(unsigned i=0;i<2000;++i){TGLDisplayActivity call(3,inaccessible,i);call.finish(i);}}; + std::thread c(activityWrite),d(activityWrite); + for(unsigned i=0;i<100;++i){ + assert(tglDisplayLiveSerialize(nullptr,nullptr,&snapshot)); + for(unsigned j=0;j<64;++j){ + TGLTransitionRecord q;memcpy(&q,snapshot.data.data()+64+(256+j)*128,128); + if(q.q[8])assert(q.q[1]>=q.q[0]);else assert(!q.q[1]); + } + } + c.join();d.join();assert(tglDisplayActivityDropped==1); + } + assert(!tglDisplayTraceScopes && !tglDisplayActivityBegin(0,inaccessible,0)); munmap(inaccessible,4096); IOFree(tglTransitions,tglTransitionSlots*sizeof(TGLTransitionRecord)); IOFree(tglTransitionFirst,tglTransitionFirstSlots*sizeof(TGLTransitionRecord)); IOSimpleLockFree(tglTransitionLock); - puts("PASS: 10051 events, concurrent coherent snapshots, ring wrap, allocation failure, IRQ restore, no object/MMIO access"); + puts("PASS: 10051 events; compact chronological snapshots; concurrent/pending activity, overflow and stale tokens; IRQ restore; no object/MMIO access"); } '''.replace('// BODY', body) with tempfile.TemporaryDirectory(prefix='tgl-transition-') as tmp: diff --git a/tests/xelp_forcewake_irq.py b/tests/xelp_forcewake_irq.py index 92069d01..d78bfe1a 100644 --- a/tests/xelp_forcewake_irq.py +++ b/tests/xelp_forcewake_irq.py @@ -30,6 +30,7 @@ using boolean_t=bool; using TGLCSBForceWake=Wake; static uintptr_t xelpRegularForceWakeOriginal; +struct TGLDisplayActivity {TGLDisplayActivity(unsigned,void*,uint64_t) {}}; #include "kern_xelp_forcewake_irq.hpp" void xelpRegularForceWakeProtected(void*,bool,unsigned); static uint64_t ticks; From 3d3b507255d791a85dff815251b7b0cf1076cdc5 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:05:30 +0900 Subject: [PATCH 50/58] Distinguish AUX native calls from surrounding diagnostic work --- WhateverXe/kern_xe.cpp | 43 ++++++++++++- tests/tgl_agdc_aux_observer.py | 26 +++++++- tests/tgl_aux_boundaries.py | 99 +++++++++++++++++++++++++++++ tests/tgl_bootstrap_aux_fixture.cpp | 6 ++ tools/test.py | 2 +- 5 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 tests/tgl_aux_boundaries.py diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 82ca0d07..e3bcf938 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -14875,11 +14875,19 @@ void XE::wrapTGLDisableAUXPower(void *power, uint32_t ddi) { } uint32_t XE::wrapTGLRunAUX(void *port, uint32_t command, uint32_t *packet, uint32_t length, bool flag) { + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(13, port, command); const auto header = packet && length >= 4 ? packet[0] : 0; const uint32_t args = length | (static_cast(flag) << 16); + // Metadata only: do not copy arbitrary AUX/HDCP packet contents into this ring. + tglTransitionRecord(101, false, port, command, args, 0); tglAUXRecord(port, 0, false, 0, command, args, header, header); + tglTransitionRecord(104, false, port, command, args, 0); const auto result = FunctionCast(wrapTGLRunAUX, callbackXE->orgTGLRunAUX)(port, command, packet, length, flag); + tglTransitionRecord(104, true, port, command, args, result); tglAUXRecord(port, 0, true, result, command, args, header, packet && length >= 4 ? packet[0] : 0); + tglTransitionRecord(101, true, port, command, args, result); + activity.finish(result); return result; } @@ -15444,10 +15452,17 @@ uint32_t XE::wrapTGLLinkConfig(void *controller, void *request, void *output) { } uint32_t XE::wrapTGLClientAttribute(void *client, uint32_t attribute, unsigned long *input, unsigned long inputSize, unsigned long *output, unsigned long *outputSize, void *arguments) { + // Keep concurrent activity visible through post-training AUX/End/stream + // dispatch and diagnostic publication, not only inside linkTraining(). + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(10, client, attribute); // The dispatcher owns validation. Observe sizes without reading its buffers. tglTrace(10, 9, false, attribute, static_cast(inputSize), 0, 0); + tglTransitionRecord(102, false, client, attribute, inputSize, 0); const auto r = FunctionCast(wrapTGLClientAttribute, callbackXE->orgTGLClientAttribute)(client, attribute, input, inputSize, output, outputSize, arguments); + tglTransitionRecord(102, true, client, attribute, inputSize, r); tglTrace(10, 9, true, attribute, static_cast(inputSize), r, 0); + activity.finish(r); return r; } @@ -15513,12 +15528,18 @@ uint32_t XE::wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t } uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(11, controller); auto req = static_cast(request); const uint32_t id = req ? *reinterpret_cast(req) : 9; uint32_t a = req ? *reinterpret_cast(req + 8) : 0; uint32_t b = req ? req[12] | (static_cast(req[13]) << 8) : 0; tglTrace(16, id, false, a, b, 0, 0); + // Separate native entry/return from IORegistry publication on either side. + // Record address, direction and length only, including vendor DPCD addresses. + tglTransitionRecord(99, false, controller, id, (uint64_t(a) << 32) | b, 0); const auto r = FunctionCast(wrapTGLAGDCAUX, callbackXE->orgTGLAGDCAUX)(controller, request, output); + tglTransitionRecord(99, true, controller, id, (uint64_t(a) << 32) | b, r); uint64_t value = 0; // Retain bounded data per port and outcome; unrelated ports cannot overwrite it. if (request && (req[13] || (r == 0 && output))) { @@ -15535,6 +15556,7 @@ uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { IORegistryEntry::getRegistryRoot()->setProperty(key, failure, sizeof(failure)); } tglTrace(16, id, true, a, b, r, value); + activity.finish(r); return r; } @@ -17056,22 +17078,39 @@ void XE::wrapTGLEnablePipe(void *controller, void *fb, void *path, const void *t } uint32_t XE::wrapTGLReadAUX(void *port, uint32_t address, void *data, uint32_t length) { + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(12, port, address); auto caller = __builtin_return_address(0); + tglTransitionRecord(100, false, port, address, length, 0); tglDPCDRecord(port, address, data, length, false, false, 0, caller); const auto r = tglInvokeBootstrapAUX(callbackXE->tglBootstrapAUXReady, 0, port, [&] { - return FunctionCast(wrapTGLReadAUX, callbackXE->orgTGLReadAUX)(port, address, data, length); + tglTransitionRecord(103, false, port, address, length, 0); + const auto result = FunctionCast(wrapTGLReadAUX, callbackXE->orgTGLReadAUX)(port, address, data, length); + tglTransitionRecord(103, true, port, address, length, result); + return result; }); tglDPCDRecord(port, address, data, length, false, true, r, caller); + tglTransitionRecord(100, true, port, address, length, r); + activity.finish(r); return r; } uint32_t XE::wrapTGLWriteAUX(void *port, uint32_t address, void *data, uint32_t length) { + TGLDisplayTraceScope scope; + TGLDisplayActivity activity(12, port, (1ULL << 32) | address); auto caller = __builtin_return_address(0); + const auto args = (1ULL << 32) | length; + tglTransitionRecord(100, false, port, address, args, 0); tglDPCDRecord(port, address, data, length, true, false, 0, caller); const auto r = tglInvokeBootstrapAUX(callbackXE->tglBootstrapAUXReady, 1, port, [&] { - return FunctionCast(wrapTGLWriteAUX, callbackXE->orgTGLWriteAUX)(port, address, data, length); + tglTransitionRecord(103, false, port, address, args, 0); + const auto result = FunctionCast(wrapTGLWriteAUX, callbackXE->orgTGLWriteAUX)(port, address, data, length); + tglTransitionRecord(103, true, port, address, args, result); + return result; }); tglDPCDRecord(port, address, data, length, true, true, r, caller); + tglTransitionRecord(100, true, port, address, args, r); + activity.finish(r); return r; } diff --git a/tests/tgl_agdc_aux_observer.py b/tests/tgl_agdc_aux_observer.py index 0ada9aeb..b3071608 100644 --- a/tests/tgl_agdc_aux_observer.py +++ b/tests/tgl_agdc_aux_observer.py @@ -10,12 +10,26 @@ #include #include #include +#include +unsigned scopes=0,pending=0; +struct TGLDisplayTraceScope {TGLDisplayTraceScope(){++scopes;}~TGLDisplayTraceScope(){--scopes;}}; +struct TGLDisplayActivity { + bool open=true; + templateTGLDisplayActivity(A...){assert(scopes);++pending;} + void finish(uint32_t){assert(open);open=false;--pending;} + ~TGLDisplayActivity(){assert(!open);} +}; +struct Boundary {unsigned stage;bool done;void *object;uint64_t a,b,result;}; +std::vector boundaries; +void tglTransitionRecord(unsigned stage,bool done,void *object,uint64_t a,uint64_t b,uint64_t result){ + assert(scopes && pending);boundaries.push_back({stage,done,object,a,b,result}); +} struct XE {uintptr_t orgTGLAGDCAUX;static uint32_t wrapTGLAGDCAUX(void*,void*,void*);}; XE state,*callbackXE=&state; #define FunctionCast(f,p) reinterpret_cast(p) struct IORegistryEntry { static IORegistryEntry *getRegistryRoot(){static IORegistryEntry e;return &e;} - templatevoid setProperty(A...){} + templatevoid setProperty(A...){assert(scopes && pending);} }; templatevoid tglTrace(A...){} void clock_get_uptime(uint64_t *v){*v=60000000000ULL;} @@ -23,6 +37,7 @@ unsigned calls,result;void *expectedRequest,*expectedOutput; uint32_t native(void *c,void *request,void *output){ assert(c==&state && request==expectedRequest && output==expectedOutput); + assert(scopes && pending && boundaries.size()==1 && !boundaries[0].done); assert(++calls==1);return result; } BODY @@ -33,11 +48,16 @@ alignas(8) uint8_t request[64]{},output[64]{},before[64]{}; request[0]=ddi;request[9]=2;request[12]=length;request[13]=write; memcpy(before,request,sizeof(request));expectedRequest=request;expectedOutput=output; - calls=0;result=error?0xe00002d6:0; + calls=0;boundaries.clear();result=error?0xe00002d6:0; assert(XE::wrapTGLAGDCAUX(&state,request,output)==result); assert(calls==1&&!memcmp(before,request,sizeof(request))); + assert(!scopes && !pending && boundaries.size()==2); + for(unsigned i=0;i<2;++i){const auto &e=boundaries[i]; + assert(e.stage==99 && e.done==bool(i) && e.object==&state && e.a==ddi); + assert(e.b==((uint64_t(0x200)<<32)|length|(write<<8)) && e.result==(i?result:0)); + } } - calls=0;expectedRequest=expectedOutput=nullptr; + calls=0;boundaries.clear();expectedRequest=expectedOutput=nullptr; assert(XE::wrapTGLAGDCAUX(&state,nullptr,nullptr)==result&&calls==1); puts("PASS 1280 AUX observer cases: one native call, original arguments/result, no port-specific probe"); } diff --git a/tests/tgl_aux_boundaries.py b/tests/tgl_aux_boundaries.py new file mode 100644 index 00000000..8d10eacb --- /dev/null +++ b/tests/tgl_aux_boundaries.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Replay actual AUX/dispatch wrappers, including denial and opaque buffers.""" +from pathlib import Path +import re +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[1] +source = (root / 'WhateverXe/kern_xe.cpp').read_text() +names = ['ReadAUX', 'WriteAUX', 'RunAUX', 'ClientAttribute'] +bodies = '\n'.join(re.search(r'uint32_t XE::wrapTGL' + name + r'\(.*?\n\}', + source, re.S).group() for name in names) +fixture = r''' +#include +#include +#include +#include +#include +unsigned scopes,pending,calls; +bool deny; +uint32_t result,address,length; +void *opaque; +std::vector events; +struct TGLDisplayTraceScope {TGLDisplayTraceScope(){++scopes;}~TGLDisplayTraceScope(){--scopes;}}; +struct TGLDisplayActivity { + bool open=true; + templateTGLDisplayActivity(A...){assert(scopes);++pending;} + void finish(uint32_t r){assert(open);open=false;--pending;assert(r==(deny?0xe00002d8:result));} + ~TGLDisplayActivity(){assert(!open);} +}; +void tglTransitionRecord(unsigned stage,bool done,void *object,uint64_t a,uint64_t b,uint64_t r){ + assert(object==opaque && scopes && pending); + assert(r==(done?(deny?0xe00002d8:result):0)); + if(stage==100 || stage==103){assert(a==address && uint32_t(b)==length && (b>>32)<=1);} + events.push_back(stage*2+done); +} +templatevoid tglTrace(A...){assert(scopes && pending);} +templatevoid tglDPCDRecord(A...){assert(scopes && pending);} +templatevoid tglAUXRecord(A...){assert(scopes && pending);} +templateuint32_t tglInvokeBootstrapAUX(bool ready,unsigned kind,void *port,F f){ + assert(ready && kind<2 && port==opaque && scopes && pending); + return deny?0xe00002d8:f(); +} +struct XE { + bool tglBootstrapAUXReady=true; + uintptr_t orgTGLReadAUX,orgTGLWriteAUX,orgTGLRunAUX,orgTGLClientAttribute; + static uint32_t wrapTGLReadAUX(void*,uint32_t,void*,uint32_t); + static uint32_t wrapTGLWriteAUX(void*,uint32_t,void*,uint32_t); + static uint32_t wrapTGLRunAUX(void*,uint32_t,uint32_t*,uint32_t,bool); + static uint32_t wrapTGLClientAttribute(void*,uint32_t,unsigned long*,unsigned long,unsigned long*,unsigned long*,void*); +}; +XE instance,*callbackXE=&instance; +#define FunctionCast(f,p) reinterpret_cast(p) +uint32_t nativeAUX(void *port,uint32_t a,void *data,uint32_t n){ + assert(port==opaque && data==opaque && a==address && n==length && scopes && pending); + assert(events.back()==206);++calls;return result; +} +uint32_t nativeRun(void *port,uint32_t command,uint32_t *packet,uint32_t n,bool flag){ + assert(port==opaque && packet==opaque && command==8 && n==0 && flag && scopes && pending); + assert(events.back()==208);++calls;return result; +} +uint32_t nativeClient(void *client,uint32_t attribute,unsigned long *input,unsigned long size, + unsigned long *output,unsigned long *outputSize,void *arguments){ + assert(client==opaque && input==opaque && output==opaque && outputSize==opaque && arguments==opaque); + assert(attribute==0x985 && size==0x50 && scopes && pending && events.back()==204); + ++calls;return result; +} +// BODIES +int main(){ + opaque=mmap(nullptr,4096,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0);assert(opaque!=MAP_FAILED); + instance.orgTGLReadAUX=(uintptr_t)nativeAUX;instance.orgTGLWriteAUX=(uintptr_t)nativeAUX; + instance.orgTGLRunAUX=(uintptr_t)nativeRun;instance.orgTGLClientAttribute=(uintptr_t)nativeClient; + for(auto a:{0U,0x102U,0xE000CU,0xFFFFFFFFU})for(auto n:{0U,1U,16U,255U}) + for(auto error:{0U,0xe00002d6U})for(unsigned blocked=0;blocked<2;++blocked)for(unsigned write=0;write<2;++write){ + address=a;length=n;result=error;deny=blocked;calls=0;events.clear(); + auto value=write?XE::wrapTGLWriteAUX(opaque,a,opaque,n):XE::wrapTGLReadAUX(opaque,a,opaque,n); + assert(value==(deny?0xe00002d8:result) && calls==(deny?0U:1U) && !scopes && !pending); + assert(events==(deny?std::vector{200,201}:std::vector{200,206,207,201})); + } + deny=false; + for(auto error:{0U,0xe00002d6U}){ + result=error;calls=0;events.clear(); + assert(XE::wrapTGLRunAUX(opaque,8,(uint32_t*)opaque,0,true)==result); + assert(calls==1 && !scopes && !pending && events==std::vector({202,208,209,203})); + calls=0;events.clear(); + assert(XE::wrapTGLClientAttribute(opaque,0x985,(unsigned long*)opaque,0x50, + (unsigned long*)opaque,(unsigned long*)opaque,opaque)==result); + assert(calls==1 && !scopes && !pending && events==std::vector({204,205})); + } + munmap(opaque,4096); + puts("PASS: 128 AUX paths plus transport/dispatch; native args/results, denied calls, opaque buffers and publication scopes"); +} +'''.replace('// BODIES', bodies) +with tempfile.TemporaryDirectory(prefix='tgl-aux-boundaries-') as directory: + path = Path(directory) + (path / 'check.cpp').write_text(fixture) + subprocess.run(['clang++', '-std=c++14', '-O1', '-g', '-fsanitize=address,undefined', + str(path / 'check.cpp'), '-o', str(path / 'check')], check=True) + subprocess.run([str(path / 'check')], check=True) diff --git a/tests/tgl_bootstrap_aux_fixture.cpp b/tests/tgl_bootstrap_aux_fixture.cpp index 2e39c1b0..4e050652 100644 --- a/tests/tgl_bootstrap_aux_fixture.cpp +++ b/tests/tgl_bootstrap_aux_fixture.cpp @@ -20,6 +20,12 @@ constexpr uint32_t kIOReturnNotReady=0xe00002d8; #include "adapter.inc" #define FunctionCast(f,p) reinterpret_cast(p) template void tglDPCDRecord(T...){} +template void tglTransitionRecord(T...){} +struct TGLDisplayTraceScope {}; +struct TGLDisplayActivity { + templateTGLDisplayActivity(T...){} + void finish(uint32_t){} +}; struct XE { bool tglBootstrapAUXReady {};uintptr_t orgTGLReadAUX {},orgTGLWriteAUX {},orgTGLI2CAUX {}; static uint32_t wrapTGLReadAUX(void*,uint32_t,void*,uint32_t); diff --git a/tools/test.py b/tools/test.py index a6a568d5..1f06da31 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=['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=['display_backend_dispatch','tgl_agdc_port_status','display_contract_adapter','tgl_agdc_aux_observer','tgl_aux_boundaries','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 03597317e0d59a3840a0325a1df6e7751a5ad8a7 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:15:22 +0900 Subject: [PATCH 51/58] Retain display interrupt activity across post-training requests --- WhateverXe/kern_xe.cpp | 57 ++++++++++++++++++++++++++++++++++-- WhateverXe/kern_xe.hpp | 4 +++ tests/tgl_transition_live.py | 49 +++++++++++++++++++++++++++++-- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index e3bcf938..22d355e5 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3307,6 +3307,7 @@ static bool tglTransitionSerialize(void *,void *reference,OSSerialize *serialize // ring. Never overwrite a pending call when another CPU/IRQ makes progress. // Observers copy memory only; the original caller still owns every HW access. static uint32_t tglDisplayTraceScopes {}; +static uint64_t tglDisplayTraceUntil {}; static uint64_t tglDisplayActivityCount {}, tglDisplayActivityDropped {}; static TGLTransitionRecord tglDisplayActivity[64] {}; struct TGLDisplayTraceScope { @@ -3315,13 +3316,23 @@ struct TGLDisplayTraceScope { if(enabled)__atomic_fetch_add(&tglDisplayTraceScopes,1U,__ATOMIC_RELAXED); } ~TGLDisplayTraceScope() { - if(enabled)__atomic_fetch_sub(&tglDisplayTraceScopes,1U,__ATOMIC_RELAXED); + if(enabled) { + uint64_t ticks,ns;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&ns); + // Include asynchronous IRQ work between successive AGDC requests and + // after their return. No timer, callback or hardware access is scheduled. + const auto until=ns+2000000000ULL; + auto previous=__atomic_load_n(&tglDisplayTraceUntil,__ATOMIC_RELAXED); + while(previous__atomic_load_n(&tglDisplayTraceUntil,__ATOMIC_ACQUIRE))return 0; r.q[2]=reinterpret_cast(current_thread());r.q[4]=kind; r.q[5]=reinterpret_cast(object);r.q[6]=argument; const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); @@ -11471,6 +11482,31 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t const bool traced=patcher.routeMultiple(index,traceRoutes,arrsize(traceRoutes),address,size); IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeHotplugBoundaryTrace",traced); patcher.clearError(); + const char *irqNames[] { + "__ZN31AppleIntelFramebufferController16ProcessInterruptEv", + "__ZN31AppleIntelFramebufferController18SWInterruptHandlerEP8OSObjectP22IOInterruptEventSourcei", + "__ZN31AppleIntelFramebufferController18dpProcessInterruptEP14AppleIntelPort" + }; + const uint32_t irqOffsets[] {0x72F92,0x73FE0,0x75FFA}; + const uint8_t irqEntries[][17] { + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18}, + {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x10,0x48,0xFF} + }; + bool irqKnown=size>0x761BA; + for(unsigned i=0;i(index,irqNames[i],address,size); + irqKnown=entry==address+irqOffsets[i] && !memcmp(reinterpret_cast(entry),irqEntries[i],sizeof(irqEntries[i])); + } + patcher.clearError(); + KernelPatcher::RouteRequest irqRoutes[] { + {irqNames[0],wrapTGLTraceFBIRQ,orgTGLTraceFBIRQ}, + {irqNames[1],wrapTGLTraceFBSWIRQ,orgTGLTraceFBSWIRQ}, + {irqNames[2],wrapTGLTraceDPIRQ,orgTGLTraceDPIRQ} + }; + const bool irqTraced=irqKnown && patcher.routeMultiple(index,irqRoutes,arrsize(irqRoutes),address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDisplayIRQBoundaryTrace",irqTraced); + patcher.clearError(); } tglRecordStartOrder(1,nullptr); return true; @@ -14544,6 +14580,23 @@ void XE::wrapTGLTraceDisablePG(void *power, uint32_t pg) { tglTransitionRecord(93,true,power,pg,0,0); } +// These observers may run with interrupts disabled. Keep objects opaque and +// use only the IRQ-safe pending-call recorder, never IORegistry publication. +bool XE::wrapTGLTraceFBIRQ(void *controller) { + TGLDisplayActivity activity(14,controller); + const auto result=FunctionCast(wrapTGLTraceFBIRQ,callbackXE->orgTGLTraceFBIRQ)(controller); + activity.finish(result); + return result; +} +void XE::wrapTGLTraceFBSWIRQ(void *owner, void *source, int count) { + TGLDisplayActivity activity(15,owner,static_cast(count)); + FunctionCast(wrapTGLTraceFBSWIRQ,callbackXE->orgTGLTraceFBSWIRQ)(owner,source,count); +} +void XE::wrapTGLTraceDPIRQ(void *controller, void *port) { + TGLDisplayActivity activity(16,controller,reinterpret_cast(port)); + FunctionCast(wrapTGLTraceDPIRQ,callbackXE->orgTGLTraceDPIRQ)(controller,port); +} + uint32_t XE::wrapTGLCheckLinkStatus(void *port) { auto caller = __builtin_return_address(0); tglConnectionRecord(port, 6, false, 0, 0, caller); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 5aa969b2..aae60cb4 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -495,6 +495,10 @@ class XE { mach_vm_address_t orgTGLTrainingPattern {}, orgTGLPowerDDIState {}; mach_vm_address_t orgTGLTraceHotplug {}, orgTGLTraceClearHPD {}, orgTGLTraceEnablePG {}, orgTGLTraceDisablePG {}; + mach_vm_address_t orgTGLTraceFBIRQ {}, orgTGLTraceFBSWIRQ {}, orgTGLTraceDPIRQ {}; + static bool wrapTGLTraceFBIRQ(void *controller); + static void wrapTGLTraceFBSWIRQ(void *owner, void *source, int count); + static void wrapTGLTraceDPIRQ(void *controller, void *port); static void wrapTGLTraceHotplug(void *controller, uint32_t ddi, uint32_t mode); static void wrapTGLTraceClearHPD(void *port); static void wrapTGLTraceEnablePG(void *power, uint32_t pg); diff --git a/tests/tgl_transition_live.py b/tests/tgl_transition_live.py index 1b8278b1..9bc2b987 100644 --- a/tests/tgl_transition_live.py +++ b/tests/tgl_transition_live.py @@ -2,10 +2,23 @@ """Actual lifecycle recorder: concurrent snapshots, IRQ state and opaque objects.""" from pathlib import Path import subprocess +import re +import argparse import tempfile root = Path(__file__).resolve().parents[1] source = (root / 'WhateverXe/kern_xe.cpp').read_text() +p = argparse.ArgumentParser(); p.add_argument('--donor', type=Path); args = p.parse_args() +if args.donor: + data = args.donor.read_bytes() + guard = source[source.index('const uint32_t irqOffsets[]'):source.index('bool irqKnown=size>0x761BA')] + offsets = [int(x, 16) for x in re.search(r'irqOffsets\[\] \{([^}]+)', guard).group(1).split(',')] + rows = re.findall(r'\{((?:0x[0-9A-F]+,?)+)\}', guard)[1:] + assert len(offsets) == len(rows) == 3 + for offset, row in zip(offsets, rows): + entry = bytes(int(x, 16) for x in row.split(',')) + assert data[offset:offset+len(entry)] == entry + print('PASS: exact donor display IRQ entry guards') # Tracing rewrites PG entry bytes. Qualify the native ABI before routing those # entries, or enabling diagnostics alone blocks framebuffer startup. trace_install = source.index('KernelPatcher::RouteRequest traceRoutes[]') @@ -14,6 +27,7 @@ assert source.rfind('if (tglDisplayTraceEnabled)', 0, trace_install) >= 0 body = source[source.index('struct TGLTransitionRecord {'): source.index('// Independent and nested training calls')] +irq_wrappers = '\n'.join(re.search(r'(?:bool|void) XE::wrapTGLTrace' + name + r'\(.*?\n\}', source, re.S).group() for name in ['FBIRQ', 'FBSWIRQ', 'DPIRQ']) fixture = r''' #include #include @@ -54,6 +68,19 @@ void release(){assert(!held);delete this;} }; // BODY +struct XE { + uintptr_t orgTGLTraceFBIRQ{},orgTGLTraceFBSWIRQ{},orgTGLTraceDPIRQ{}; + static bool wrapTGLTraceFBIRQ(void*); + static void wrapTGLTraceFBSWIRQ(void*,void*,int); + static void wrapTGLTraceDPIRQ(void*,void*); +}; +XE driver,*callbackXE=&driver; +#define FunctionCast(f,p) reinterpret_cast(p) +// IRQ_WRAPPERS +void *expectedOpaque;bool nativeResult,expectedIRQ;unsigned nativeCalls; +bool nativeIRQ(void*p){assert(p==expectedOpaque && irqEnabled==expectedIRQ && !held);++nativeCalls;return nativeResult;} +void nativeSW(void*p,void*q,int count){assert(p==expectedOpaque && q==p && count==-2);assert(irqEnabled==expectedIRQ && !held);++nativeCalls;} +void nativeDP(void*p,void*q){assert(p==expectedOpaque && q==p);assert(irqEnabled==expectedIRQ && !held);++nativeCalls;} void validate(OSSerialize&s){ assert(s.data.size()==16+1024*128);uint32_t h[4];memcpy(h,s.data.data(),16); assert(h[0]==1&&h[2]==1024&&h[3]==128); @@ -168,14 +195,32 @@ } c.join();d.join();assert(tglDisplayActivityDropped==1); } - assert(!tglDisplayTraceScopes && !tglDisplayActivityBegin(0,inaccessible,0)); + assert(!tglDisplayTraceScopes); + auto tail=tglDisplayActivityBegin(14,inaccessible,0);assert(tail); + ticks=__atomic_load_n(&tglDisplayTraceUntil,__ATOMIC_RELAXED)+1; + assert(!tglDisplayActivityBegin(14,inaccessible,0)); + // A call that entered during the tail remains pending and may finish later. + tglDisplayActivityEnd(tail,7); + assert(tglDisplayActivity[(tail&255)-1].q[7]==7); + expectedOpaque=inaccessible; + driver.orgTGLTraceFBIRQ=(uintptr_t)nativeIRQ;driver.orgTGLTraceFBSWIRQ=(uintptr_t)nativeSW;driver.orgTGLTraceDPIRQ=(uintptr_t)nativeDP; + { + TGLDisplayTraceScope scope; + for(bool prior:{false,true})for(bool value:{false,true}){ + irqEnabled=expectedIRQ=prior;nativeResult=value;nativeCalls=0; + assert(XE::wrapTGLTraceFBIRQ(inaccessible)==value && nativeCalls==1); + XE::wrapTGLTraceFBSWIRQ(inaccessible,inaccessible,-2); + XE::wrapTGLTraceDPIRQ(inaccessible,inaccessible); + assert(nativeCalls==3 && irqEnabled==prior && !held); + } + } munmap(inaccessible,4096); IOFree(tglTransitions,tglTransitionSlots*sizeof(TGLTransitionRecord)); IOFree(tglTransitionFirst,tglTransitionFirstSlots*sizeof(TGLTransitionRecord)); IOSimpleLockFree(tglTransitionLock); puts("PASS: 10051 events; compact chronological snapshots; concurrent/pending activity, overflow and stale tokens; IRQ restore; no object/MMIO access"); } -'''.replace('// BODY', body) +'''.replace('// BODY', body).replace('// IRQ_WRAPPERS', irq_wrappers) with tempfile.TemporaryDirectory(prefix='tgl-transition-') as tmp: tmp = Path(tmp) cpp, binary = tmp / 'check.cpp', tmp / 'check' From 78d37d1c6449e2214b157ce65e896c4024c6f3ab Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:48:34 +0900 Subject: [PATCH 52/58] Remove unsuccessful hotplug diagnostics and document remaining hang --- README.md | 5 +- README_ja.md | 2 +- WhateverXe/kern_xe.cpp | 237 +------------------------ WhateverXe/kern_xe.hpp | 9 - tests/tgl_agdc_aux_observer.py | 26 +-- tests/tgl_aux_boundaries.py | 99 ----------- tests/tgl_aux_live.py | 3 - tests/tgl_bootstrap_aux_fixture.cpp | 6 - tests/tgl_training_pattern_fixture.cpp | 3 - tests/tgl_transition_live.py | 115 +----------- tests/xelp_forcewake_irq.py | 1 - tools/test.py | 2 +- 12 files changed, 16 insertions(+), 492 deletions(-) delete mode 100644 tests/tgl_aux_boundaries.py diff --git a/README.md b/README.md index 8213b7e9..abadcca0 100644 --- a/README.md +++ b/README.md @@ -161,8 +161,9 @@ installation or update location. - Some image-conversion operations can crash the application. - Reconnecting a monitor to a dedicated DisplayPort connector can leave the - screen black or freeze the computer. USB-C DisplayPort Alt Mode passed the - reconnection checks above. + screen black or freeze the computer. USB-C DisplayPort Alt Mode reconnection + can also freeze the computer on Big Sur. The successful reconnection checks + above do not establish reliability across all systems and macOS versions. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not receive an image. diff --git a/README_ja.md b/README_ja.md index 698922d6..f25771ca 100644 --- a/README_ja.md +++ b/README_ja.md @@ -115,7 +115,7 @@ Safariのインストール先や更新による配置変更に依存せずData ### 既知の問題と制限 - 一部の画像変換処理で、アプリケーションがクラッシュすることがあります。 -- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでは、上記の抜き差し試験を通過しています。 +- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。Big SurではUSB-CのDisplayPort Alt Modeでも再接続時のハングを確認しています。上記の抜き差し試験の成功は、全機種・全macOSでの安定性を保証するものではありません。 - Safariの動画再生で、ときどきコマ落ちが発生します。 - 同時出力は最大3画面です。4画面目には映像が出ません。 - 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 22d355e5..48c485da 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3302,105 +3302,20 @@ static bool tglTransitionSerialize(void *,void *reference,OSSerialize *serialize auto data=OSData::withBytes(copy,size);IOFree(copy,size);if(!data)return false; const bool ok=data->serialize(serializer);data->release();return ok; } - -// Concurrent native calls are retained separately from the ordered transition -// ring. Never overwrite a pending call when another CPU/IRQ makes progress. -// Observers copy memory only; the original caller still owns every HW access. -static uint32_t tglDisplayTraceScopes {}; -static uint64_t tglDisplayTraceUntil {}; -static uint64_t tglDisplayActivityCount {}, tglDisplayActivityDropped {}; -static TGLTransitionRecord tglDisplayActivity[64] {}; -struct TGLDisplayTraceScope { - bool enabled; - TGLDisplayTraceScope():enabled(tglTransitionLock!=nullptr) { - if(enabled)__atomic_fetch_add(&tglDisplayTraceScopes,1U,__ATOMIC_RELAXED); - } - ~TGLDisplayTraceScope() { - if(enabled) { - uint64_t ticks,ns;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&ns); - // Include asynchronous IRQ work between successive AGDC requests and - // after their return. No timer, callback or hardware access is scheduled. - const auto until=ns+2000000000ULL; - auto previous=__atomic_load_n(&tglDisplayTraceUntil,__ATOMIC_RELAXED); - while(previous__atomic_load_n(&tglDisplayTraceUntil,__ATOMIC_ACQUIRE))return 0; - r.q[2]=reinterpret_cast(current_thread());r.q[4]=kind; - r.q[5]=reinterpret_cast(object);r.q[6]=argument; - const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); - unsigned slot=arrsize(tglDisplayActivity); - for(unsigned i=0;i=arrsize(tglDisplayActivity))return; - uint64_t ticks,ns;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&ns); - const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); - auto &q=tglDisplayActivity[slot].q; - if(q[3]==(token>>8) && !q[8]){q[1]=ns;q[7]=result;q[8]=1;} - IOSimpleLockUnlockEnableInterrupt(tglTransitionLock,irq); -} -struct TGLDisplayActivity { - uint64_t token; - TGLDisplayActivity(unsigned kind,void *object,uint64_t argument=0):token(tglDisplayActivityBegin(kind,object,argument)) {} - void finish(uint64_t result) {tglDisplayActivityEnd(token,result);token=0;} - ~TGLDisplayActivity() {finish(0);} -}; -static bool tglDisplayLiveSerialize(void *,void *,OSSerialize *serializer) { - if(!tglTransitionLock)return false; - constexpr unsigned slots=256; - const size_t size=64+slots*sizeof(TGLTransitionRecord)+sizeof(tglDisplayActivity); - auto copy=static_cast(IOMalloc(size));if(!copy)return false; - auto h=reinterpret_cast(copy);bzero(copy,size);uint64_t ticks; - clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,h+1); - h[0]=1;h[3]=slots;h[5]=arrsize(tglDisplayActivity); - const auto irq=IOSimpleLockLockDisableInterrupt(tglTransitionLock); - h[2]=tglTransitionCount;h[4]=tglDisplayActivityCount;h[6]=tglDisplayActivityDropped; - h[7]=__atomic_load_n(&tglDisplayTraceScopes,__ATOMIC_RELAXED); - auto out=reinterpret_cast(copy+64); - const unsigned count=tglTransitionCountserialize(serializer);data->release();return ok; -} static bool tglTransitionRegister(unsigned long reg) { if(!tglDisplayTraceEnabled)return false; // Record only accesses already issued by the donor. Combo training can // change AUX reachability when the transmitter is enabled; retain native // PHY, PLL, route and panel-power ordering without additional hardware IO. if (reg & 3) return false; - // Cover each Display 12 pipe and physical DDI, independent of which - // connector owns the current path. These are filters, not MMIO probes. - const bool pipe=reg>=0x60000 && reg<0x63000 && - ((reg&0xFFF)==0x400 || (reg&0xFFF)==0x540); - const bool buffer=reg>=0x64000 && reg<=0x64800 && !(reg&0xFF); - const bool clock=reg>=0x46140 && reg<=0x46148; return reg==0x45400 || reg==0x45404 || reg==0x45408 || reg==0x4540C || reg==0x45450 || reg==0x45454 || reg==0x45440 || reg==0x45444 || reg==0x164280 || reg==0x46010 || reg==0x46014 || (reg>=0x162000 && reg<0x163000) || (reg>=0x6C000 && reg<0x6D000) || (reg>=0x164284 && reg<=0x164290) || (reg>=0xC7200 && reg<=0xC7210) || - pipe || buffer || clock || + reg==0x64000 || reg==0x64100 || reg==0x46140 || reg==0x46144 || + reg==0x60400 || reg==0x61400 || reg==0x60540 || reg==0x61540 || (reg>=0x163880 && reg<=0x1638A4) || reg==0x1638C0 || - (reg>=0x4610C && reg<=0x46120); + (reg>=0x4610C && reg<=0x46120) || reg==0x64300 || reg==0x64400; } // Independent and nested training calls retain their own suppression scope. @@ -3439,7 +3354,6 @@ void tglDPCDRecord(void *port, uint32_t address, void *data, uint32_t length, w[6] = length < 16 ? length : 16; memcpy(w + 8, data, w[6]); } - tglTransitionRecord(98,done,port,address,(uint64_t(write)<<32)|length,result,w+8,16); IOSimpleLockLock(tglAUXLock); w[0] = ++tglDPCDCount; if (w[0] <= arrsize(tglDPCDFirst)) tglDPCDFirst[w[0]-1] = record; @@ -11221,7 +11135,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t tglAUXLock = IOSimpleLockAlloc(); tglDisplayTraceEnabled=checkKernelArgument("-wex-display-trace"); tglInitTransitionTrace(tglDisplayTraceEnabled); - if (tglConnectionLock) { KernelPatcher::RouteRequest link("__ZN14AppleIntelPort15checkLinkStatusEv", wrapTGLCheckLinkStatus, orgTGLCheckLinkStatus); @@ -11469,45 +11382,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayStartupContract",contractRecord,sizeof(contractRecord)); IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); } - if (tglDisplayTraceEnabled) { - // Install after native entry-byte qualification. Software - // boundaries around the empty-dongle path add no extra - // register accesses or changes to the donor's return contracts. - KernelPatcher::RouteRequest traceRoutes[] { - {"__ZN31AppleIntelFramebufferController20invokeHotplugHandlerEjN14AppleIntelPort8PortModeE", wrapTGLTraceHotplug, orgTGLTraceHotplug}, - {"__ZN14AppleIntelPort17clearACPIHPDCountEv", wrapTGLTraceClearHPD, orgTGLTraceClearHPD}, - {"__ZN19AppleIntelPowerWell17enablePowerWellPGEj", wrapTGLTraceEnablePG, orgTGLTraceEnablePG}, - {"__ZN19AppleIntelPowerWell18disablePowerWellPGEj", wrapTGLTraceDisablePG, orgTGLTraceDisablePG} - }; - const bool traced=patcher.routeMultiple(index,traceRoutes,arrsize(traceRoutes),address,size); - IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeHotplugBoundaryTrace",traced); - patcher.clearError(); - const char *irqNames[] { - "__ZN31AppleIntelFramebufferController16ProcessInterruptEv", - "__ZN31AppleIntelFramebufferController18SWInterruptHandlerEP8OSObjectP22IOInterruptEventSourcei", - "__ZN31AppleIntelFramebufferController18dpProcessInterruptEP14AppleIntelPort" - }; - const uint32_t irqOffsets[] {0x72F92,0x73FE0,0x75FFA}; - const uint8_t irqEntries[][17] { - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x18}, - {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x10,0x48,0xFF} - }; - bool irqKnown=size>0x761BA; - for(unsigned i=0;i(index,irqNames[i],address,size); - irqKnown=entry==address+irqOffsets[i] && !memcmp(reinterpret_cast(entry),irqEntries[i],sizeof(irqEntries[i])); - } - patcher.clearError(); - KernelPatcher::RouteRequest irqRoutes[] { - {irqNames[0],wrapTGLTraceFBIRQ,orgTGLTraceFBIRQ}, - {irqNames[1],wrapTGLTraceFBSWIRQ,orgTGLTraceFBSWIRQ}, - {irqNames[2],wrapTGLTraceDPIRQ,orgTGLTraceDPIRQ} - }; - const bool irqTraced=irqKnown && patcher.routeMultiple(index,irqRoutes,arrsize(irqRoutes),address,size); - IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDisplayIRQBoundaryTrace",irqTraced); - patcher.clearError(); - } tglRecordStartOrder(1,nullptr); return true; } @@ -11849,9 +11723,6 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglTransitionSerialize,reinterpret_cast(1))) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTransitionFirst",live);live->release(); } - if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglDisplayLiveSerialize)) { - IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDisplayLive",live);live->release(); - } if(auto live=OSSerializer::forTarget(nullptr,tglDPCDSerialize)) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPCDLive",live);live->release(); } @@ -12658,19 +12529,16 @@ void XE::wrapTGLHostIRQEnable(void *bridge) { } void XE::wrapTGLHostIRQProcess(void *bridge) { - TGLDisplayActivity activity(0,bridge); auto r=tglHostIRQBegin(0,bridge,0,0,reinterpret_cast(__builtin_return_address(0))); FunctionCast(wrapTGLHostIRQProcess,callbackXE->orgTGLHostIRQProcess)(bridge); tglHostIRQEnd(r); } void XE::wrapTGLHostIRQService(void *bridge,const uint64_t *bits) { - TGLDisplayActivity activity(1,bridge,bits?*bits:0); auto r=tglHostIRQBegin(1,bridge,bits ? *bits : 0,0,reinterpret_cast(__builtin_return_address(0))); FunctionCast(wrapTGLHostIRQService,callbackXE->orgTGLHostIRQService)(bridge,bits); tglHostIRQEnd(r); } void XE::wrapTGLHostSWInterrupt(void *streamer,void *source,int count) { - TGLDisplayActivity activity(2,streamer,static_cast(count)); auto r=tglHostIRQBegin(2,streamer,reinterpret_cast(source),count,reinterpret_cast(__builtin_return_address(0))); FunctionCast(wrapTGLHostSWInterrupt,callbackXE->orgTGLHostSWInterrupt)(streamer,source,count); tglHostIRQEnd(r); @@ -12691,7 +12559,6 @@ mach_vm_address_t xelpRegularForceWakeOriginal {}; // >=100us, >=1ms, bypass, total native ns, >=10ms. No MMIO from readers. static uint64_t xelpForceWakeIRQ[9] {}; void xelpRegularForceWakeProtected(void *owner,bool enable,unsigned domains) { - TGLDisplayActivity activity(3,owner,(uint64_t(enable)<<32)|domains); uint64_t flags=0; if(owner)memcpy(&flags,static_cast(owner)+0x1190,sizeof(flags)); if(!owner || !XELPForceWakeIRQ::required(flags,domains)) { @@ -14540,10 +14407,8 @@ uint32_t XE::wrapTGLDPPower(void *port, uint8_t state) { uint32_t XE::wrapTGLSinkCount(void *port, uint8_t *count) { auto caller = __builtin_return_address(0); - tglTransitionRecord(95,false,port,0,0,0); tglConnectionRecord(port, 5, false, 0, 0, caller); const auto result = FunctionCast(wrapTGLSinkCount, callbackXE->orgTGLSinkCount)(port, count); - tglTransitionRecord(95,true,port,0,0,result==0 && count ? *count : 0xFFFFFFFFU); tglConnectionRecord(port, 5, true, result, !result && count ? *count : 0xFFFFFFFFU, caller); auto config = *reinterpret_cast(static_cast(port) + 0x548); if (config && config[0] < 9) { @@ -14555,48 +14420,6 @@ uint32_t XE::wrapTGLSinkCount(void *port, uint8_t *count) { return result; } -void XE::wrapTGLTraceHotplug(void *controller, uint32_t ddi, uint32_t mode) { - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(7,controller,(uint64_t(ddi)<<32)|mode); - tglTransitionRecord(90,false,controller,ddi,mode,0); - FunctionCast(wrapTGLTraceHotplug,callbackXE->orgTGLTraceHotplug)(controller,ddi,mode); - tglTransitionRecord(90,true,controller,ddi,mode,0); -} -void XE::wrapTGLTraceClearHPD(void *port) { - tglTransitionRecord(91,false,port,0,0,0); - FunctionCast(wrapTGLTraceClearHPD,callbackXE->orgTGLTraceClearHPD)(port); - tglTransitionRecord(91,true,port,0,0,0); -} -void XE::wrapTGLTraceEnablePG(void *power, uint32_t pg) { - TGLDisplayActivity activity(5,power,pg); - tglTransitionRecord(92,false,power,pg,0,0); - FunctionCast(wrapTGLTraceEnablePG,callbackXE->orgTGLTraceEnablePG)(power,pg); - tglTransitionRecord(92,true,power,pg,0,0); -} -void XE::wrapTGLTraceDisablePG(void *power, uint32_t pg) { - TGLDisplayActivity activity(6,power,pg); - tglTransitionRecord(93,false,power,pg,0,0); - FunctionCast(wrapTGLTraceDisablePG,callbackXE->orgTGLTraceDisablePG)(power,pg); - tglTransitionRecord(93,true,power,pg,0,0); -} - -// These observers may run with interrupts disabled. Keep objects opaque and -// use only the IRQ-safe pending-call recorder, never IORegistry publication. -bool XE::wrapTGLTraceFBIRQ(void *controller) { - TGLDisplayActivity activity(14,controller); - const auto result=FunctionCast(wrapTGLTraceFBIRQ,callbackXE->orgTGLTraceFBIRQ)(controller); - activity.finish(result); - return result; -} -void XE::wrapTGLTraceFBSWIRQ(void *owner, void *source, int count) { - TGLDisplayActivity activity(15,owner,static_cast(count)); - FunctionCast(wrapTGLTraceFBSWIRQ,callbackXE->orgTGLTraceFBSWIRQ)(owner,source,count); -} -void XE::wrapTGLTraceDPIRQ(void *controller, void *port) { - TGLDisplayActivity activity(16,controller,reinterpret_cast(port)); - FunctionCast(wrapTGLTraceDPIRQ,callbackXE->orgTGLTraceDPIRQ)(controller,port); -} - uint32_t XE::wrapTGLCheckLinkStatus(void *port) { auto caller = __builtin_return_address(0); tglConnectionRecord(port, 6, false, 0, 0, caller); @@ -14928,19 +14751,11 @@ void XE::wrapTGLDisableAUXPower(void *power, uint32_t ddi) { } uint32_t XE::wrapTGLRunAUX(void *port, uint32_t command, uint32_t *packet, uint32_t length, bool flag) { - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(13, port, command); const auto header = packet && length >= 4 ? packet[0] : 0; const uint32_t args = length | (static_cast(flag) << 16); - // Metadata only: do not copy arbitrary AUX/HDCP packet contents into this ring. - tglTransitionRecord(101, false, port, command, args, 0); tglAUXRecord(port, 0, false, 0, command, args, header, header); - tglTransitionRecord(104, false, port, command, args, 0); const auto result = FunctionCast(wrapTGLRunAUX, callbackXE->orgTGLRunAUX)(port, command, packet, length, flag); - tglTransitionRecord(104, true, port, command, args, result); tglAUXRecord(port, 0, true, result, command, args, header, packet && length >= 4 ? packet[0] : 0); - tglTransitionRecord(101, true, port, command, args, result); - activity.finish(result); return result; } @@ -15041,7 +14856,6 @@ void XE::wrapTGLMEIInit(void *driver) { } void XE::wrapTGLMEIInterrupt(void *driver, void *source, int count) { - TGLDisplayActivity activity(4,driver,static_cast(count)); if (!tglMEIMapping[2]) { auto map = *reinterpret_cast(static_cast(driver) + 0x138); if (map) { @@ -15505,17 +15319,10 @@ uint32_t XE::wrapTGLLinkConfig(void *controller, void *request, void *output) { } uint32_t XE::wrapTGLClientAttribute(void *client, uint32_t attribute, unsigned long *input, unsigned long inputSize, unsigned long *output, unsigned long *outputSize, void *arguments) { - // Keep concurrent activity visible through post-training AUX/End/stream - // dispatch and diagnostic publication, not only inside linkTraining(). - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(10, client, attribute); // The dispatcher owns validation. Observe sizes without reading its buffers. tglTrace(10, 9, false, attribute, static_cast(inputSize), 0, 0); - tglTransitionRecord(102, false, client, attribute, inputSize, 0); const auto r = FunctionCast(wrapTGLClientAttribute, callbackXE->orgTGLClientAttribute)(client, attribute, input, inputSize, output, outputSize, arguments); - tglTransitionRecord(102, true, client, attribute, inputSize, r); tglTrace(10, 9, true, attribute, static_cast(inputSize), r, 0); - activity.finish(r); return r; } @@ -15581,18 +15388,12 @@ uint32_t XE::wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t } uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(11, controller); auto req = static_cast(request); const uint32_t id = req ? *reinterpret_cast(req) : 9; uint32_t a = req ? *reinterpret_cast(req + 8) : 0; uint32_t b = req ? req[12] | (static_cast(req[13]) << 8) : 0; tglTrace(16, id, false, a, b, 0, 0); - // Separate native entry/return from IORegistry publication on either side. - // Record address, direction and length only, including vendor DPCD addresses. - tglTransitionRecord(99, false, controller, id, (uint64_t(a) << 32) | b, 0); const auto r = FunctionCast(wrapTGLAGDCAUX, callbackXE->orgTGLAGDCAUX)(controller, request, output); - tglTransitionRecord(99, true, controller, id, (uint64_t(a) << 32) | b, r); uint64_t value = 0; // Retain bounded data per port and outcome; unrelated ports cannot overwrite it. if (request && (req[13] || (r == 0 && output))) { @@ -15609,7 +15410,6 @@ uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { IORegistryEntry::getRegistryRoot()->setProperty(key, failure, sizeof(failure)); } tglTrace(16, id, true, a, b, r, value); - activity.finish(r); return r; } @@ -16450,14 +16250,11 @@ uint32_t XE::wrapTGLPortTraining(void *port, void *request) { } } tglAUXRecord(port,10,false,0,0,0); - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(8,port,reinterpret_cast(request)); SYSLOG("igfx", "TGL330 port-training enter"); auto caller = __builtin_return_address(0); tglObservePath(port, nullptr, 0, false, 0, request != nullptr, caller); const auto token = tglTrainingScopes.enter(reinterpret_cast(current_thread())); const auto r = FunctionCast(wrapTGLPortTraining, callbackXE->orgTGLPortTraining)(port, request); - activity.finish(r); tglTrainingScopes.leave(token); tglAUXRecord(port,10,true,r,0,0); SYSLOG("igfx", "TGL330 port-training return result=%x", r); @@ -16487,14 +16284,11 @@ void XE::wrapTGLFreePath(void *path) { } uint32_t XE::wrapTGLLightUpDP(void *controller, void *fb, void *path, const void *timing) { - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(9,path,reinterpret_cast(fb)); auto port=path?*reinterpret_cast(static_cast(path)+0x3648):nullptr; tglAUXRecord(port,11,false,0,0,0); auto caller = __builtin_return_address(0); tglObservePath(nullptr, path, 3, false, 0, 0, caller); const auto r = FunctionCast(wrapTGLLightUpDP, callbackXE->orgTGLLightUpDP)(controller, fb, path, timing); - activity.finish(r); tglAUXRecord(port,11,true,r,0,0); tglObservePath(nullptr, path, 3, true, r, 0, caller); tglDPCDPublish(); @@ -17131,39 +16925,22 @@ void XE::wrapTGLEnablePipe(void *controller, void *fb, void *path, const void *t } uint32_t XE::wrapTGLReadAUX(void *port, uint32_t address, void *data, uint32_t length) { - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(12, port, address); auto caller = __builtin_return_address(0); - tglTransitionRecord(100, false, port, address, length, 0); tglDPCDRecord(port, address, data, length, false, false, 0, caller); const auto r = tglInvokeBootstrapAUX(callbackXE->tglBootstrapAUXReady, 0, port, [&] { - tglTransitionRecord(103, false, port, address, length, 0); - const auto result = FunctionCast(wrapTGLReadAUX, callbackXE->orgTGLReadAUX)(port, address, data, length); - tglTransitionRecord(103, true, port, address, length, result); - return result; + return FunctionCast(wrapTGLReadAUX, callbackXE->orgTGLReadAUX)(port, address, data, length); }); tglDPCDRecord(port, address, data, length, false, true, r, caller); - tglTransitionRecord(100, true, port, address, length, r); - activity.finish(r); return r; } uint32_t XE::wrapTGLWriteAUX(void *port, uint32_t address, void *data, uint32_t length) { - TGLDisplayTraceScope scope; - TGLDisplayActivity activity(12, port, (1ULL << 32) | address); auto caller = __builtin_return_address(0); - const auto args = (1ULL << 32) | length; - tglTransitionRecord(100, false, port, address, args, 0); tglDPCDRecord(port, address, data, length, true, false, 0, caller); const auto r = tglInvokeBootstrapAUX(callbackXE->tglBootstrapAUXReady, 1, port, [&] { - tglTransitionRecord(103, false, port, address, args, 0); - const auto result = FunctionCast(wrapTGLWriteAUX, callbackXE->orgTGLWriteAUX)(port, address, data, length); - tglTransitionRecord(103, true, port, address, args, result); - return result; + return FunctionCast(wrapTGLWriteAUX, callbackXE->orgTGLWriteAUX)(port, address, data, length); }); tglDPCDRecord(port, address, data, length, true, true, r, caller); - tglTransitionRecord(100, true, port, address, args, r); - activity.finish(r); return r; } @@ -17174,7 +16951,6 @@ uint32_t XE::wrapTGLI2CAUX(void *port, uint32_t command, uint32_t *packet, uint3 } uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { - tglTransitionRecord(96,false,path,pattern,0,0); auto port = *reinterpret_cast(static_cast(path) + 0x3648); tglObserveActivation(callbackXE->tglObservationController, port, path, 1, false, pattern); // TGL needs the transcoder clock and DDI route before enabling DP_TP_CTL. @@ -17234,7 +17010,6 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { } } const auto result = FunctionCast(wrapTGLTrainingPattern, callbackXE->orgTGLTrainingPattern)(path, pattern); - tglTransitionRecord(97,true,path,pattern,0,result); // The donor constructs DP_TP_CTL from enable/MST/pattern only, losing bit18 // and eDP Alternate SR bit6 (TGL PRM Vol2c Part1, DP_TP_CTL). // Its own normal and fast training write port+11a to DPCD101 bit7. Use that @@ -17263,7 +17038,6 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { if (mainPower == 0xFFFFFFFFU || ioPower == 0xFFFFFFFFU || (mainPower & mainMask) != mainMask || (ioPower & ioMask) != ioMask) { tglObserveActivation(callbackXE->tglObservationController, port, path, 1, true, result); - tglTransitionRecord(96,true,path,pattern,0,result); return result; } const auto read=[&](uint32_t off){return *reinterpret_cast(base+off);}; @@ -17285,7 +17059,6 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { } } tglObserveActivation(callbackXE->tglObservationController, port, path, 1, true, result); - tglTransitionRecord(96,true,path,pattern,0,result); return result; } diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index aae60cb4..529fbe95 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -494,15 +494,6 @@ class XE { static void tglTimeEvent(uint32_t stage, uint32_t id, bool done, uint32_t arg, uint32_t result); mach_vm_address_t orgTGLTrainingPattern {}, orgTGLPowerDDIState {}; - mach_vm_address_t orgTGLTraceHotplug {}, orgTGLTraceClearHPD {}, orgTGLTraceEnablePG {}, orgTGLTraceDisablePG {}; - mach_vm_address_t orgTGLTraceFBIRQ {}, orgTGLTraceFBSWIRQ {}, orgTGLTraceDPIRQ {}; - static bool wrapTGLTraceFBIRQ(void *controller); - static void wrapTGLTraceFBSWIRQ(void *owner, void *source, int count); - static void wrapTGLTraceDPIRQ(void *controller, void *port); - static void wrapTGLTraceHotplug(void *controller, uint32_t ddi, uint32_t mode); - static void wrapTGLTraceClearHPD(void *port); - static void wrapTGLTraceEnablePG(void *power, uint32_t pg); - static void wrapTGLTraceDisablePG(void *power, uint32_t pg); static uint32_t wrapTGLTrainingPattern(void *path, uint32_t pattern); mach_vm_address_t orgTGLPortTraining {}, orgTGLPathLookup {}, orgTGLFreePath {}; mach_vm_address_t orgTGLLightUpDP {}, orgTGLDisableDisplay {}, orgTGLEnablePipe {}; diff --git a/tests/tgl_agdc_aux_observer.py b/tests/tgl_agdc_aux_observer.py index b3071608..0ada9aeb 100644 --- a/tests/tgl_agdc_aux_observer.py +++ b/tests/tgl_agdc_aux_observer.py @@ -10,26 +10,12 @@ #include #include #include -#include -unsigned scopes=0,pending=0; -struct TGLDisplayTraceScope {TGLDisplayTraceScope(){++scopes;}~TGLDisplayTraceScope(){--scopes;}}; -struct TGLDisplayActivity { - bool open=true; - templateTGLDisplayActivity(A...){assert(scopes);++pending;} - void finish(uint32_t){assert(open);open=false;--pending;} - ~TGLDisplayActivity(){assert(!open);} -}; -struct Boundary {unsigned stage;bool done;void *object;uint64_t a,b,result;}; -std::vector boundaries; -void tglTransitionRecord(unsigned stage,bool done,void *object,uint64_t a,uint64_t b,uint64_t result){ - assert(scopes && pending);boundaries.push_back({stage,done,object,a,b,result}); -} struct XE {uintptr_t orgTGLAGDCAUX;static uint32_t wrapTGLAGDCAUX(void*,void*,void*);}; XE state,*callbackXE=&state; #define FunctionCast(f,p) reinterpret_cast(p) struct IORegistryEntry { static IORegistryEntry *getRegistryRoot(){static IORegistryEntry e;return &e;} - templatevoid setProperty(A...){assert(scopes && pending);} + templatevoid setProperty(A...){} }; templatevoid tglTrace(A...){} void clock_get_uptime(uint64_t *v){*v=60000000000ULL;} @@ -37,7 +23,6 @@ unsigned calls,result;void *expectedRequest,*expectedOutput; uint32_t native(void *c,void *request,void *output){ assert(c==&state && request==expectedRequest && output==expectedOutput); - assert(scopes && pending && boundaries.size()==1 && !boundaries[0].done); assert(++calls==1);return result; } BODY @@ -48,16 +33,11 @@ alignas(8) uint8_t request[64]{},output[64]{},before[64]{}; request[0]=ddi;request[9]=2;request[12]=length;request[13]=write; memcpy(before,request,sizeof(request));expectedRequest=request;expectedOutput=output; - calls=0;boundaries.clear();result=error?0xe00002d6:0; + calls=0;result=error?0xe00002d6:0; assert(XE::wrapTGLAGDCAUX(&state,request,output)==result); assert(calls==1&&!memcmp(before,request,sizeof(request))); - assert(!scopes && !pending && boundaries.size()==2); - for(unsigned i=0;i<2;++i){const auto &e=boundaries[i]; - assert(e.stage==99 && e.done==bool(i) && e.object==&state && e.a==ddi); - assert(e.b==((uint64_t(0x200)<<32)|length|(write<<8)) && e.result==(i?result:0)); - } } - calls=0;boundaries.clear();expectedRequest=expectedOutput=nullptr; + calls=0;expectedRequest=expectedOutput=nullptr; assert(XE::wrapTGLAGDCAUX(&state,nullptr,nullptr)==result&&calls==1); puts("PASS 1280 AUX observer cases: one native call, original arguments/result, no port-specific probe"); } diff --git a/tests/tgl_aux_boundaries.py b/tests/tgl_aux_boundaries.py deleted file mode 100644 index 8d10eacb..00000000 --- a/tests/tgl_aux_boundaries.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -"""Replay actual AUX/dispatch wrappers, including denial and opaque buffers.""" -from pathlib import Path -import re -import subprocess -import tempfile - -root = Path(__file__).resolve().parents[1] -source = (root / 'WhateverXe/kern_xe.cpp').read_text() -names = ['ReadAUX', 'WriteAUX', 'RunAUX', 'ClientAttribute'] -bodies = '\n'.join(re.search(r'uint32_t XE::wrapTGL' + name + r'\(.*?\n\}', - source, re.S).group() for name in names) -fixture = r''' -#include -#include -#include -#include -#include -unsigned scopes,pending,calls; -bool deny; -uint32_t result,address,length; -void *opaque; -std::vector events; -struct TGLDisplayTraceScope {TGLDisplayTraceScope(){++scopes;}~TGLDisplayTraceScope(){--scopes;}}; -struct TGLDisplayActivity { - bool open=true; - templateTGLDisplayActivity(A...){assert(scopes);++pending;} - void finish(uint32_t r){assert(open);open=false;--pending;assert(r==(deny?0xe00002d8:result));} - ~TGLDisplayActivity(){assert(!open);} -}; -void tglTransitionRecord(unsigned stage,bool done,void *object,uint64_t a,uint64_t b,uint64_t r){ - assert(object==opaque && scopes && pending); - assert(r==(done?(deny?0xe00002d8:result):0)); - if(stage==100 || stage==103){assert(a==address && uint32_t(b)==length && (b>>32)<=1);} - events.push_back(stage*2+done); -} -templatevoid tglTrace(A...){assert(scopes && pending);} -templatevoid tglDPCDRecord(A...){assert(scopes && pending);} -templatevoid tglAUXRecord(A...){assert(scopes && pending);} -templateuint32_t tglInvokeBootstrapAUX(bool ready,unsigned kind,void *port,F f){ - assert(ready && kind<2 && port==opaque && scopes && pending); - return deny?0xe00002d8:f(); -} -struct XE { - bool tglBootstrapAUXReady=true; - uintptr_t orgTGLReadAUX,orgTGLWriteAUX,orgTGLRunAUX,orgTGLClientAttribute; - static uint32_t wrapTGLReadAUX(void*,uint32_t,void*,uint32_t); - static uint32_t wrapTGLWriteAUX(void*,uint32_t,void*,uint32_t); - static uint32_t wrapTGLRunAUX(void*,uint32_t,uint32_t*,uint32_t,bool); - static uint32_t wrapTGLClientAttribute(void*,uint32_t,unsigned long*,unsigned long,unsigned long*,unsigned long*,void*); -}; -XE instance,*callbackXE=&instance; -#define FunctionCast(f,p) reinterpret_cast(p) -uint32_t nativeAUX(void *port,uint32_t a,void *data,uint32_t n){ - assert(port==opaque && data==opaque && a==address && n==length && scopes && pending); - assert(events.back()==206);++calls;return result; -} -uint32_t nativeRun(void *port,uint32_t command,uint32_t *packet,uint32_t n,bool flag){ - assert(port==opaque && packet==opaque && command==8 && n==0 && flag && scopes && pending); - assert(events.back()==208);++calls;return result; -} -uint32_t nativeClient(void *client,uint32_t attribute,unsigned long *input,unsigned long size, - unsigned long *output,unsigned long *outputSize,void *arguments){ - assert(client==opaque && input==opaque && output==opaque && outputSize==opaque && arguments==opaque); - assert(attribute==0x985 && size==0x50 && scopes && pending && events.back()==204); - ++calls;return result; -} -// BODIES -int main(){ - opaque=mmap(nullptr,4096,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0);assert(opaque!=MAP_FAILED); - instance.orgTGLReadAUX=(uintptr_t)nativeAUX;instance.orgTGLWriteAUX=(uintptr_t)nativeAUX; - instance.orgTGLRunAUX=(uintptr_t)nativeRun;instance.orgTGLClientAttribute=(uintptr_t)nativeClient; - for(auto a:{0U,0x102U,0xE000CU,0xFFFFFFFFU})for(auto n:{0U,1U,16U,255U}) - for(auto error:{0U,0xe00002d6U})for(unsigned blocked=0;blocked<2;++blocked)for(unsigned write=0;write<2;++write){ - address=a;length=n;result=error;deny=blocked;calls=0;events.clear(); - auto value=write?XE::wrapTGLWriteAUX(opaque,a,opaque,n):XE::wrapTGLReadAUX(opaque,a,opaque,n); - assert(value==(deny?0xe00002d8:result) && calls==(deny?0U:1U) && !scopes && !pending); - assert(events==(deny?std::vector{200,201}:std::vector{200,206,207,201})); - } - deny=false; - for(auto error:{0U,0xe00002d6U}){ - result=error;calls=0;events.clear(); - assert(XE::wrapTGLRunAUX(opaque,8,(uint32_t*)opaque,0,true)==result); - assert(calls==1 && !scopes && !pending && events==std::vector({202,208,209,203})); - calls=0;events.clear(); - assert(XE::wrapTGLClientAttribute(opaque,0x985,(unsigned long*)opaque,0x50, - (unsigned long*)opaque,(unsigned long*)opaque,opaque)==result); - assert(calls==1 && !scopes && !pending && events==std::vector({204,205})); - } - munmap(opaque,4096); - puts("PASS: 128 AUX paths plus transport/dispatch; native args/results, denied calls, opaque buffers and publication scopes"); -} -'''.replace('// BODIES', bodies) -with tempfile.TemporaryDirectory(prefix='tgl-aux-boundaries-') as directory: - path = Path(directory) - (path / 'check.cpp').write_text(fixture) - subprocess.run(['clang++', '-std=c++14', '-O1', '-g', '-fsanitize=address,undefined', - str(path / 'check.cpp'), '-o', str(path / 'check')], check=True) - subprocess.run([str(path / 'check')], check=True) diff --git a/tests/tgl_aux_live.py b/tests/tgl_aux_live.py index ebbfb0c9..237bf07a 100644 --- a/tests/tgl_aux_live.py +++ b/tests/tgl_aux_live.py @@ -20,9 +20,6 @@ uint32_t tglConnectionCaller(void*){return 0x1234;} bool quiet=false,failAlloc=false,failData=false; int lock,*tglAUXLock=&lock;bool held=false; -void tglTransitionRecord(uint32_t stage,bool,void*,uint64_t,uint64_t,uint64_t,const void *payload,size_t size){ - assert(!held && stage==98 && size==16);uint8_t copy[16];memcpy(copy,payload,size); -} void IOSimpleLockLock(int*) {assert(!held);held=true;} void IOSimpleLockUnlock(int*) {assert(held);held=false;} bool tglQuietTraining(){return quiet;} diff --git a/tests/tgl_bootstrap_aux_fixture.cpp b/tests/tgl_bootstrap_aux_fixture.cpp index 4e050652..2e39c1b0 100644 --- a/tests/tgl_bootstrap_aux_fixture.cpp +++ b/tests/tgl_bootstrap_aux_fixture.cpp @@ -20,12 +20,6 @@ constexpr uint32_t kIOReturnNotReady=0xe00002d8; #include "adapter.inc" #define FunctionCast(f,p) reinterpret_cast(p) template void tglDPCDRecord(T...){} -template void tglTransitionRecord(T...){} -struct TGLDisplayTraceScope {}; -struct TGLDisplayActivity { - templateTGLDisplayActivity(T...){} - void finish(uint32_t){} -}; struct XE { bool tglBootstrapAUXReady {};uintptr_t orgTGLReadAUX {},orgTGLWriteAUX {},orgTGLI2CAUX {}; static uint32_t wrapTGLReadAUX(void*,uint32_t,void*,uint32_t); diff --git a/tests/tgl_training_pattern_fixture.cpp b/tests/tgl_training_pattern_fixture.cpp index eab6e663..72b9bfe8 100644 --- a/tests/tgl_training_pattern_fixture.cpp +++ b/tests/tgl_training_pattern_fixture.cpp @@ -24,9 +24,6 @@ struct IORegistryEntry { void clock_get_uptime(uint64_t *p){*p=1;} void absolutetime_to_nanoseconds(uint64_t t,uint64_t *p){*p=t;} unsigned observations,nativeCalls; -void tglTransitionRecord(uint32_t stage,bool,void*,uint64_t,uint64_t,uint64_t) { - assert(stage==96 || stage==97); -} struct XE { bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // FIELDS diff --git a/tests/tgl_transition_live.py b/tests/tgl_transition_live.py index 9bc2b987..70a5b6fc 100644 --- a/tests/tgl_transition_live.py +++ b/tests/tgl_transition_live.py @@ -2,32 +2,12 @@ """Actual lifecycle recorder: concurrent snapshots, IRQ state and opaque objects.""" from pathlib import Path import subprocess -import re -import argparse import tempfile root = Path(__file__).resolve().parents[1] source = (root / 'WhateverXe/kern_xe.cpp').read_text() -p = argparse.ArgumentParser(); p.add_argument('--donor', type=Path); args = p.parse_args() -if args.donor: - data = args.donor.read_bytes() - guard = source[source.index('const uint32_t irqOffsets[]'):source.index('bool irqKnown=size>0x761BA')] - offsets = [int(x, 16) for x in re.search(r'irqOffsets\[\] \{([^}]+)', guard).group(1).split(',')] - rows = re.findall(r'\{((?:0x[0-9A-F]+,?)+)\}', guard)[1:] - assert len(offsets) == len(rows) == 3 - for offset, row in zip(offsets, rows): - entry = bytes(int(x, 16) for x in row.split(',')) - assert data[offset:offset+len(entry)] == entry - print('PASS: exact donor display IRQ entry guards') -# Tracing rewrites PG entry bytes. Qualify the native ABI before routing those -# entries, or enabling diagnostics alone blocks framebuffer startup. -trace_install = source.index('KernelPatcher::RouteRequest traceRoutes[]') -assert source.index('!memcmp(reinterpret_cast(tglDisablePG),tcPGPutEntry') < trace_install -assert source.index('tglNativeLegacyHPDCaller=hotplug+0x185') < trace_install -assert source.rfind('if (tglDisplayTraceEnabled)', 0, trace_install) >= 0 body = source[source.index('struct TGLTransitionRecord {'): source.index('// Independent and nested training calls')] -irq_wrappers = '\n'.join(re.search(r'(?:bool|void) XE::wrapTGLTrace' + name + r'\(.*?\n\}', source, re.S).group() for name in ['FBIRQ', 'FBSWIRQ', 'DPIRQ']) fixture = r''' #include #include @@ -68,19 +48,6 @@ void release(){assert(!held);delete this;} }; // BODY -struct XE { - uintptr_t orgTGLTraceFBIRQ{},orgTGLTraceFBSWIRQ{},orgTGLTraceDPIRQ{}; - static bool wrapTGLTraceFBIRQ(void*); - static void wrapTGLTraceFBSWIRQ(void*,void*,int); - static void wrapTGLTraceDPIRQ(void*,void*); -}; -XE driver,*callbackXE=&driver; -#define FunctionCast(f,p) reinterpret_cast(p) -// IRQ_WRAPPERS -void *expectedOpaque;bool nativeResult,expectedIRQ;unsigned nativeCalls; -bool nativeIRQ(void*p){assert(p==expectedOpaque && irqEnabled==expectedIRQ && !held);++nativeCalls;return nativeResult;} -void nativeSW(void*p,void*q,int count){assert(p==expectedOpaque && q==p && count==-2);assert(irqEnabled==expectedIRQ && !held);++nativeCalls;} -void nativeDP(void*p,void*q){assert(p==expectedOpaque && q==p);assert(irqEnabled==expectedIRQ && !held);++nativeCalls;} void validate(OSSerialize&s){ assert(s.data.size()==16+1024*128);uint32_t h[4];memcpy(h,s.data.data(),16); assert(h[0]==1&&h[2]==1024&&h[3]==128); @@ -136,91 +103,15 @@ 0x64000ul,0x64100ul,0x46140ul,0x46144ul,0x60400ul,0x61400ul,0x60540ul,0x61540ul}) assert(tglTransitionRegister(reg)); for(auto reg:{0ul,0x45410ul,0x45451ul,0x138124ul,0x162001ul,0x163000ul,0x6D000ul, - 0x164294ul,0xC7214ul,0x64010ul,0x64900ul,0x4614Cul,0x63400ul,0x63540ul}) + 0x164294ul,0xC7214ul,0x64200ul,0x64010ul}) assert(!tglTransitionRegister(reg)); - for(unsigned pipe=0;pipe<3;++pipe){ - assert(tglTransitionRegister(0x60400+0x1000*pipe)); - assert(tglTransitionRegister(0x60540+0x1000*pipe)); - assert(tglTransitionRegister(0x46140+4*pipe)); - assert(!tglTransitionRegister(0x60404+0x1000*pipe)); - } - for(unsigned ddi=0;ddi<9;++ddi){ - assert(tglTransitionRegister(0x64000+0x100*ddi)); - assert(!tglTransitionRegister(0x64004+0x100*ddi)); - } - tglDisplayTraceEnabled=false; - for(auto reg:{0x62400ul,0x62540ul,0x64500ul,0x46148ul})assert(!tglTransitionRegister(reg)); - // Concurrent progress is independent of transition ring wrap. An inactive - // scope costs no record and opaque objects must never be dereferenced. - assert(!tglDisplayActivityBegin(0,inaccessible,1)); - assert(!tglDisplayActivityCount && !tglDisplayTraceScopes); - { - TGLDisplayTraceScope scope;assert(tglDisplayTraceScopes==1); - {TGLDisplayTraceScope nested;assert(tglDisplayTraceScopes==2);} - assert(tglDisplayTraceScopes==1); - uint64_t pending[64]; - for(unsigned i=0;i<64;++i)pending[i]=tglDisplayActivityBegin(i,inaccessible,i+1); - assert(tglDisplayActivityCount==64); - assert(!tglDisplayActivityBegin(64,inaccessible,0));assert(tglDisplayActivityDropped==1); - assert(tglDisplayLiveSerialize(nullptr,nullptr,&snapshot)); - uint64_t h[8];memcpy(h,snapshot.data.data(),sizeof(h)); - assert(h[0]==1 && h[2]==10051 && h[3]==256 && h[4]==64 && h[5]==64 && h[6]==1 && h[7]==1); - assert(snapshot.data.size()==64+(256+64)*128); - for(unsigned i=0;i<256;++i){ - TGLTransitionRecord q;memcpy(&q,snapshot.data.data()+64+i*128,128); - assert(q.q[2]==10051-255+i); - } - tglDisplayActivityEnd(pending[63],7); - auto reused=tglDisplayActivityBegin(99,inaccessible,4);assert(reused && reused!=pending[63]); - tglDisplayActivityEnd(pending[63],123); // stale completion cannot close a reused slot - assert(!tglDisplayActivity[63].q[8]); - tglDisplayActivityEnd(reused,9);assert(tglDisplayActivity[63].q[7]==9); - for(unsigned i=0;i<63;++i)assert(!tglDisplayActivity[i].q[8]); - for(unsigned i=0;i<63;++i)tglDisplayActivityEnd(pending[i],0); - for(bool prior:{false,true}){ - irqEnabled=prior; - {TGLDisplayActivity call(3,inaccessible,1);} - assert(irqEnabled==prior&&!held); - assert(tglDisplayLiveSerialize(nullptr,nullptr,&snapshot));assert(irqEnabled==prior); - } - irqEnabled=true; - auto activityWrite=[&]{for(unsigned i=0;i<2000;++i){TGLDisplayActivity call(3,inaccessible,i);call.finish(i);}}; - std::thread c(activityWrite),d(activityWrite); - for(unsigned i=0;i<100;++i){ - assert(tglDisplayLiveSerialize(nullptr,nullptr,&snapshot)); - for(unsigned j=0;j<64;++j){ - TGLTransitionRecord q;memcpy(&q,snapshot.data.data()+64+(256+j)*128,128); - if(q.q[8])assert(q.q[1]>=q.q[0]);else assert(!q.q[1]); - } - } - c.join();d.join();assert(tglDisplayActivityDropped==1); - } - assert(!tglDisplayTraceScopes); - auto tail=tglDisplayActivityBegin(14,inaccessible,0);assert(tail); - ticks=__atomic_load_n(&tglDisplayTraceUntil,__ATOMIC_RELAXED)+1; - assert(!tglDisplayActivityBegin(14,inaccessible,0)); - // A call that entered during the tail remains pending and may finish later. - tglDisplayActivityEnd(tail,7); - assert(tglDisplayActivity[(tail&255)-1].q[7]==7); - expectedOpaque=inaccessible; - driver.orgTGLTraceFBIRQ=(uintptr_t)nativeIRQ;driver.orgTGLTraceFBSWIRQ=(uintptr_t)nativeSW;driver.orgTGLTraceDPIRQ=(uintptr_t)nativeDP; - { - TGLDisplayTraceScope scope; - for(bool prior:{false,true})for(bool value:{false,true}){ - irqEnabled=expectedIRQ=prior;nativeResult=value;nativeCalls=0; - assert(XE::wrapTGLTraceFBIRQ(inaccessible)==value && nativeCalls==1); - XE::wrapTGLTraceFBSWIRQ(inaccessible,inaccessible,-2); - XE::wrapTGLTraceDPIRQ(inaccessible,inaccessible); - assert(nativeCalls==3 && irqEnabled==prior && !held); - } - } munmap(inaccessible,4096); IOFree(tglTransitions,tglTransitionSlots*sizeof(TGLTransitionRecord)); IOFree(tglTransitionFirst,tglTransitionFirstSlots*sizeof(TGLTransitionRecord)); IOSimpleLockFree(tglTransitionLock); - puts("PASS: 10051 events; compact chronological snapshots; concurrent/pending activity, overflow and stale tokens; IRQ restore; no object/MMIO access"); + puts("PASS: 10051 events, concurrent coherent snapshots, ring wrap, allocation failure, IRQ restore, no object/MMIO access"); } -'''.replace('// BODY', body).replace('// IRQ_WRAPPERS', irq_wrappers) +'''.replace('// BODY', body) with tempfile.TemporaryDirectory(prefix='tgl-transition-') as tmp: tmp = Path(tmp) cpp, binary = tmp / 'check.cpp', tmp / 'check' diff --git a/tests/xelp_forcewake_irq.py b/tests/xelp_forcewake_irq.py index d78bfe1a..92069d01 100644 --- a/tests/xelp_forcewake_irq.py +++ b/tests/xelp_forcewake_irq.py @@ -30,7 +30,6 @@ using boolean_t=bool; using TGLCSBForceWake=Wake; static uintptr_t xelpRegularForceWakeOriginal; -struct TGLDisplayActivity {TGLDisplayActivity(unsigned,void*,uint64_t) {}}; #include "kern_xelp_forcewake_irq.hpp" void xelpRegularForceWakeProtected(void*,bool,unsigned); static uint64_t ticks; diff --git a/tools/test.py b/tools/test.py index 1f06da31..a6a568d5 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=['display_backend_dispatch','tgl_agdc_port_status','display_contract_adapter','tgl_agdc_aux_observer','tgl_aux_boundaries','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=['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 c575adcdb82f368b6b0b13f01b675a319f83dda8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:54:05 +0900 Subject: [PATCH 53/58] Avoid implying USB-C reconnection hangs are OS-specific --- README.md | 5 +++-- README_ja.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abadcca0..176965e0 100644 --- a/README.md +++ b/README.md @@ -162,8 +162,9 @@ installation or update location. - Some image-conversion operations can crash the application. - 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 on Big Sur. The successful reconnection checks - above do not establish reliability across all systems and macOS versions. + can also freeze the computer. Whether this depends on the macOS version is + unknown. The successful reconnection checks above do not rule out intermittent + failures. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not receive an image. diff --git a/README_ja.md b/README_ja.md index f25771ca..5a3d2dda 100644 --- a/README_ja.md +++ b/README_ja.md @@ -115,7 +115,7 @@ Safariのインストール先や更新による配置変更に依存せずData ### 既知の問題と制限 - 一部の画像変換処理で、アプリケーションがクラッシュすることがあります。 -- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。Big SurではUSB-CのDisplayPort Alt Modeでも再接続時のハングを確認しています。上記の抜き差し試験の成功は、全機種・全macOSでの安定性を保証するものではありません。 +- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでも再接続時にハングする場合があります。macOSのバージョンに依存するかは未確定です。上記の抜き差し試験の成功だけでは、間欠的な失敗を否定できません。 - Safariの動画再生で、ときどきコマ落ちが発生します。 - 同時出力は最大3画面です。4画面目には映像が出ません。 - 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 From 9e4a7725033fcec680b6d493f314b3e2e09a8c10 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:56:01 +0900 Subject: [PATCH 54/58] Document USB-C reconnection hangs reproduced on Big Sur and Tahoe --- README.md | 4 ++-- README_ja.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 176965e0..1cf1ab70 100644 --- a/README.md +++ b/README.md @@ -162,8 +162,8 @@ installation or update location. - Some image-conversion operations can crash the application. - 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. Whether this depends on the macOS version is - unknown. The successful reconnection checks above do not rule out intermittent + can also freeze the computer; this has been reproduced on both Big Sur and + Tahoe. The successful reconnection checks above do not rule out intermittent failures. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not diff --git a/README_ja.md b/README_ja.md index 5a3d2dda..fb2777aa 100644 --- a/README_ja.md +++ b/README_ja.md @@ -115,7 +115,7 @@ Safariのインストール先や更新による配置変更に依存せずData ### 既知の問題と制限 - 一部の画像変換処理で、アプリケーションがクラッシュすることがあります。 -- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでも再接続時にハングする場合があります。macOSのバージョンに依存するかは未確定です。上記の抜き差し試験の成功だけでは、間欠的な失敗を否定できません。 +- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでも再接続時にハングする場合があり、Big SurとTahoeの両方で再現しています。上記の抜き差し試験の成功だけでは、間欠的な失敗を否定できません。 - Safariの動画再生で、ときどきコマ落ちが発生します。 - 同時出力は最大3画面です。4画面目には映像が出ません。 - 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 From e7e29d3a46dd21d3087fba26d431dfe94fdb198a Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:21:55 +0900 Subject: [PATCH 55/58] Connect Tiger Lake GT1 to fused small-GT resources and cover HX selection --- README.md | 12 +++-- README_ja.md | 9 ++-- WhateverXe/kern_alderlake_s.hpp | 10 +---- WhateverXe/kern_xe.cpp | 62 +++++++++++++------------- WhateverXe/kern_xe.hpp | 4 +- WhateverXe/kern_xelp_devices.hpp | 12 ++++- WhateverXe/kern_xelp_gt_resources.hpp | 12 +++++ WhateverXe/kern_xelp_platform.hpp | 4 +- docs/device-selection.md | 11 +++-- tests/boot_arguments.py | 17 +++---- tests/device_selection.cpp | 6 +-- tests/small_gt_urb.py | 64 +++++++++++++++++++++++++++ tests/xelp_platform.cpp | 2 +- tools/test.py | 4 +- 14 files changed, 161 insertions(+), 68 deletions(-) create mode 100644 tests/small_gt_urb.py diff --git a/README.md b/README.md index 1cf1ab70..3c2aa8a3 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,15 @@ Graphics; GPU generation and connector routing are handled by WEX. | GPU family | Device IDs with an implemented backend | | --- | --- | -| Tiger Lake | `9A40`, `9A49`, `9A78` | +| Tiger Lake GT2 / G4 | `9A40`, `9A49`, `9A78` | +| Tiger Lake GT1 (H45) | `9A60`, `9A68`, `9A70` | | Alder Lake-N / Twin Lake | `46D0`–`46D4` | +TGL GT1 uses the TGL display path and fuse-derived 16/24/32-EU, four-bank +GT resources. This composition has CPU and exact-donor test coverage, but no +TGL-H hardware validation. It requires matching updated WEX Graphics payloads; +the published Graphics 0.1.0 package does not admit these IDs. + Support varies by system and connector layout. The table includes implemented backends, not a guarantee for every machine. See [device selection](docs/device-selection.md) for detailed selection conditions and overrides. @@ -190,8 +196,8 @@ installation or update location. initial display output and reconnection can be delayed. HDMI/fixed-DP routing is derived from VBT rather than a board or connector preset, within the current Combo PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability - and PHY D/E are excluded. Development GPU admission covers ADL-S IDs `4680/4682/4688/468A/468B/4690/4692/4693` - and RPL-S IDs `A780/A781/A782/A783/A788/A789/A78A/A78B`. + and PHY D/E are excluded. Development GPU admission covers ADL-S/HX IDs `4680/4682/4688/468A/468B/4690/4692/4693` + and RPL-S/HX IDs `A780/A781/A782/A783/A788/A789/A78A/A78B`. All PCI revisions select the shared family backend. Physical identity must match, with one slice, up to two DSS, 16/24/32 EUs, four L3 banks and the supported media-engine mask. Resource layouts outside that implementation remain unsupported. Only `4690` revision `0C` has hardware validation; diff --git a/README_ja.md b/README_ja.md index fb2777aa..6fee9eec 100644 --- a/README_ja.md +++ b/README_ja.md @@ -25,9 +25,12 @@ WEXはOSのABIとGPUバックエンドを独立して選択します。OS別の | GPUファミリー | バックエンド実装があるdevice ID | | --- | --- | -| Tiger Lake | `9A40`、`9A49`、`9A78` | +| Tiger Lake GT2 / G4 | `9A40`、`9A49`、`9A78` | +| Tiger Lake GT1(H45) | `9A60`、`9A68`、`9A70` | | Alder Lake-N / Twin Lake | `46D0`–`46D4` | +TGL GT1はTGLの表示経路と、ヒューズ由来の16/24/32 EU・4-bank GT資源処理を組み合わせます。CPU試験と実donor再生で検証していますが、TGL-H実機では未検証です。対応する更新済みWEX Graphics資材が必要で、公開済みGraphics 0.1.0はこれらのIDを受け付けません。 + 対応状況は機種やコネクタ構成によって異なります。この表はバックエンドの実装範囲を示すもので、すべての機種での動作を保証するものではありません。詳細な選択条件と上書き設定は[デバイス選択](docs/device-selection.md)を参照してください。 ## 必須構成 @@ -135,8 +138,8 @@ Safariのインストール先や更新による配置変更に依存せずData Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 - 開発版のGPU受付は、ADL-S `4680/4682/4688/468A/468B/4690/4692/4693` と、 - RPL-S `A780/A781/A782/A783/A788/A789/A78A/A78B` が対象で、すべてのPCI revisionが共通backendを選択します。 + 開発版のGPU受付は、ADL-S/HX `4680/4682/4688/468A/468B/4690/4692/4693` と、 + RPL-S/HX `A780/A781/A782/A783/A788/A789/A78A/A78B` が対象で、すべてのPCI revisionが共通backendを選択します。 物理IDの一致、1 slice・最大2 DSS・16/24/32 EU・4 L3 bank・対応media engine構成を要求し、 この実装範囲外の資源構成は未対応です。実機確認済みは `4690` revision `0C` のみで、他IDはCPU試験の範囲です。 追加IDには更新版のcompanion側ID受付も必要です。 diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp index c5bec525..58c48009 100644 --- a/WhateverXe/kern_alderlake_s.hpp +++ b/WhateverXe/kern_alderlake_s.hpp @@ -16,15 +16,7 @@ inline bool identity(uint32_t pci,uint32_t revision) {return revision<=255 && XE struct GT : XELP::SmallGTResources { static bool decode(uint32_t pci,uint32_t revision,const uint32_t *fuses,GT &out) { out={}; - if(!identity(pci,revision) || !fuses)return false; - for(unsigned i=0;i<5;++i)if(fuses[i]==UINT32_MAX)return false; - // Gen12 slice/DSS/EU-pair fields. Keep the supported four-bank - // capacity; do not derive URB size from EU count or a marketing ID. - const auto video=(~fuses[4])&0xFF,enhance=((~fuses[4])>>16)&0xF; - // Existing donor paths cover VCS0, optional VCS2, and VECS0. - if((video!=1 && video!=5) || enhance!=1)return false; - return XELP::smallGTResources(fuses[0]&0xFF,fuses[1]&0x3F, - fuses[2]&0xFF,fuses[3]&0xFF,out); + return identity(pci,revision) && XELP::decodeSmallGT(fuses,out); } }; static_assert(sizeof(GT)==6*sizeof(uint32_t),"Preserve GT resource layout"); diff --git a/WhateverXe/kern_xe.cpp b/WhateverXe/kern_xe.cpp index 48c485da..bfc0eb1c 100644 --- a/WhateverXe/kern_xe.cpp +++ b/WhateverXe/kern_xe.cpp @@ -3737,7 +3737,7 @@ void XE::init() { void XE::selectProfile(const XELP::Profile &profile) { xeProfile=profile; - adlsDetected=profile.admitted() && profile.gt==XELP::GT::ADLSSmall; + display12SmallGTDetected=profile.admitted() && XELP::display12SmallGT(profile.gt); adlsDisplaySelected=profile.admitted() && XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLS; adlnNativeRCSProfileRequested=false; adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN && @@ -4095,11 +4095,11 @@ 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)); - if(adlsDetected && AlderLakeS::identity(xeProfile.identity,xeProfile.revision) && + if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) && !xeProfile.overridden() && physicalGPU && XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { // Gen12 forcewake/fuse coordinates are shared with the existing reader. - // Its ADL-N profile verdict is not used to admit ADL-S. No engine, + // Its ADL-N profile verdict is not used to admit Display12 GT1. No engine, // GGTT, display, PLL or reset programming is performed here. auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { @@ -4112,26 +4112,28 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } io {reinterpret_cast(map->getVirtualAddress())}; uint32_t edram=UINT32_MAX; auto gt=AlderLakeN::readGT(io,true,&edram); - adlsNativeReady=AlderLakeS::GT::decode(xeProfile.identity,xeProfile.revision,gt.fuses,adlsGT) && + display12SmallGTReady=XELP::decodeSmallGT(gt.fuses,display12SmallGT) && (gt.status==AlderLakeN::GTStatus::Complete || gt.status==AlderLakeN::GTStatus::ProfileMismatch) && gt.requestedMask==3 && gt.releasedMask==3 && !gt.releaseFailedMask && edram==0; - physicalGPU->setProperty("ADLSGTQualified",adlsNativeReady); - physicalGPU->setProperty("ADLSGTBoot",>,sizeof(gt)); - physicalGPU->setProperty("ADLSEDRAM",edram,32); - uint32_t registers[][2] { - {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, - {0x46010,0},{0x46014,0},{0x46018,0},{0x46030,0}, - {0x164280,0},{0x164284,0},{0x164288,0},{0x16428C,0},{0x164290,0},{0x1642BC,0}, - {0xC4000,0},{0xC4030,0},{0xC4034,0},{0xC5014,0}, - {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0}, - {0x70008,0},{0x71008,0},{0x72008,0}, - {0x60000,0},{0x6000C,0},{0x60400,0},{0x60404,0}, - {0x70180,0},{0x70188,0},{0x7018C,0},{0x70190,0},{0x7019C,0},{0x701A4,0}, - {0x1082C0,0},{0x1082C4,0} - }; - for(auto ®:registers)reg[1]=io.read(reg[0]); - physicalGPU->setProperty("ADLSDisplayBoot",registers,sizeof(registers)); - SYSLOG("igfx","ADL-S boot readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x", + physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTQualified":"XELPSmallGTQualified",display12SmallGTReady); + physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTBoot":"XELPSmallGTBoot",>,sizeof(gt)); + physicalGPU->setProperty(adlsDisplaySelected?"ADLSEDRAM":"XELPSmallGTEDRAM",edram,32); + if(adlsDisplaySelected) { + uint32_t registers[][2] { + {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, + {0x46010,0},{0x46014,0},{0x46018,0},{0x46030,0}, + {0x164280,0},{0x164284,0},{0x164288,0},{0x16428C,0},{0x164290,0},{0x1642BC,0}, + {0xC4000,0},{0xC4030,0},{0xC4034,0},{0xC5014,0}, + {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0}, + {0x70008,0},{0x71008,0},{0x72008,0}, + {0x60000,0},{0x6000C,0},{0x60400,0},{0x60404,0}, + {0x70180,0},{0x70188,0},{0x7018C,0},{0x70190,0},{0x7019C,0},{0x701A4,0}, + {0x1082C0,0},{0x1082C4,0} + }; + for(auto ®:registers)reg[1]=io.read(reg[0]); + physicalGPU->setProperty("ADLSDisplayBoot",registers,sizeof(registers)); + } + SYSLOG("igfx","Display12 small-GT boot readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x", static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], gt.releasedMask,gt.releaseFailedMask); } @@ -9356,9 +9358,9 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add patcher.applyLookupPatch(&patch, site, sizeof(find) + 1); skuReady = !memcmp(site, replace, sizeof(replace)); } - // ADL-N and explicit overrides must reach the real selector2 + // Small GTs and explicit overrides must reach the real selector2 // comparison regardless of the physical ID's signed ordering. - if (skuReady && (adlnDetected || adlsDetected || xeProfile.overridden())) { + if (skuReady && (adlnDetected || display12SmallGTDetected || xeProfile.overridden())) { static const uint8_t findSplit[] {0x7F,0x15}, replaceSplit[] {0x90,0x90}; auto split=reinterpret_cast(address+0x23C25); skuReady=!memcmp(split,findSplit,sizeof(findSplit)); @@ -9444,12 +9446,12 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add // G4 (9A78) is GT2 with 3 DSS / 6 native subslices and 6 L3 banks // (TGL PRM Vol4 p3/p9). Keep the 80/96-EU patch byte-for-byte. static const uint8_t urbReplaceG4[] {0x83,0xFE,0x01,0x75,0x59,0x83,0xFA,0x06,0x75,0x54,0x41,0xC7,0x87,0x64,0x11,0x00,0x00,0x06,0x00,0x00,0x00}; - // ADL-N/Twin Lake keep LP silicon selector2. Validate the actual + // Small GT families keep LP silicon selector2. Validate the actual // native subslice count and publish the separately admitted L3 capacity. 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(adlsDetected) {urbReplaceAlderLakeN[7]=adlsGT.nativeSubslices;urbReplaceAlderLakeN[17]=adlsGT.l3Banks;} - const auto urbReplacement = (xeProfile.gt==XELP::GT::ADLNSmall || xeProfile.gt==XELP::GT::ADLSSmall) ? urbReplaceAlderLakeN : + if(display12SmallGTDetected) {urbReplaceAlderLakeN[7]=display12SmallGT.nativeSubslices;urbReplaceAlderLakeN[17]=display12SmallGT.l3Banks;} + const auto urbReplacement = (xeProfile.gt==XELP::GT::ADLNSmall || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : (xeProfile.gt==XELP::GT::TGLG4 ? urbReplaceG4 : urbReplace); auto gpuInfo=patcher.solveSymbol(index, "__ZN16IntelAccelerator10getGPUInfoEv",address,size); @@ -11712,9 +11714,9 @@ XELP::DisplayContract::Mask XE::display13Available() const { } bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { - if((callbackXE->adlsDetected && !callbackXE->adlsNativeReady) || + if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady)) { - that->setProperty("ADLSFramebufferPrerequisitesMissing",true);return false; + that->setProperty(callbackXE->adlsDisplaySelected?"ADLSFramebufferPrerequisitesMissing":"XELPSmallGTPrerequisitesMissing",true);return false; } tglRecordStartOrder(3,that); if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglTransitionSerialize)) { @@ -11844,7 +11846,7 @@ uint32_t XE::wrapTGLLaneMask(void *port) { } bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { - if((callbackXE->adlsDetected && !callbackXE->adlsNativeReady) || + if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return false; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { SYSLOG("igfx", "AlderLakeN native RCS profile qualification rejected accelerator start"); @@ -12242,7 +12244,7 @@ void XE::tglObserveAccelerator(IOService *that, uint32_t stage, bool done, uint6 } IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SInt32 *score) { - if((callbackXE->adlsDetected && !callbackXE->adlsNativeReady) || + if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return nullptr; if (!callbackXE->tglAcceleratorRequested) return nullptr; auto pci = OSDynamicCast(IOPCIDevice, provider); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 529fbe95..71bd7350 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -84,7 +84,7 @@ class XE { bool adlnDetected {false}; // Render and Display selections are independent; shared PHY/port code // must not depend on which GT implementation the product uses. - bool adlsDetected {},adlsNativeReady {}; + bool display12SmallGTDetected {},display12SmallGTReady {}; bool adlsDisplaySelected {},adlsDisplayHooksReady {}; bool adlsPortInitReady {},tglHPDHookReady {}; bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {},adlsPanelPWMReady {}; @@ -103,7 +103,7 @@ class XE { 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); - AlderLakeS::GT adlsGT; + XELP::SmallGTResources display12SmallGT; XELP::Profile xeProfile; XELP::VBT::Ports firmwarePorts; void xelpReadFirmware(IOPCIDevice *pci); diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index 0b1a11ce..4abc239c 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 }; +enum class GT : uint32_t { Unknown, TGLGT2, TGLG4, ADLNSmall, ADLSSmall, TGLGT1 }; enum class Donor : uint32_t { Unknown, C004FC1A = 1 }; struct DeviceEntry { uint16_t device; Family family; Display display; GT backend; @@ -39,7 +39,9 @@ constexpr DeviceEntry DeviceTable[] { {0x4C8B, Family::RocketLake, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, {0x9A40, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, {0x9A49, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, - {0x9A60, Family::TigerLake, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, + {0x9A60, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, + {0x9A68, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, + {0x9A70, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, {0x9A78, Family::TigerLake, Display::XeD, GT::TGLG4, "Intel UHD Graphics"}, {0xA780, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, {0xA781, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, @@ -68,5 +70,11 @@ inline bool desktopGTDevice(uint32_t pci) { return e && (e->family==Family::AlderLakeS || e->family==Family::RaptorLakeS); } +inline bool display12SmallGT(GT gt) {return gt==GT::ADLSSmall || gt==GT::TGLGT1;} +inline bool display12SmallGTDevice(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && display12SmallGT(e->backend); +} + } #endif diff --git a/WhateverXe/kern_xelp_gt_resources.hpp b/WhateverXe/kern_xelp_gt_resources.hpp index 61c88aeb..4d1dc7cc 100644 --- a/WhateverXe/kern_xelp_gt_resources.hpp +++ b/WhateverXe/kern_xelp_gt_resources.hpp @@ -23,5 +23,17 @@ inline bool smallGTResources(uint32_t slice, uint32_t dss, uint32_t euDisable, return true; } +// TGL GT1 and ADL/RPL-S use the same Gen12 fuse fields. VCS2 is optional; +// both require VCS0 and VECS0. Product admission is the caller's policy. +inline bool decodeSmallGT(const uint32_t *fuses, SmallGTResources &out) { + out={}; + if(!fuses)return false; + for(unsigned i=0;i<5;++i)if(fuses[i]==UINT32_MAX)return false; + const auto video=(~fuses[4])&0xFF,enhance=((~fuses[4])>>16)&0xF; + if((video!=1 && video!=5) || enhance!=1)return false; + return smallGTResources(fuses[0]&0xFF,fuses[1]&0x3F, + fuses[2]&0xFF,fuses[3]&0xFF,out); +} + } #endif diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 08d3df18..d2276056 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -19,8 +19,8 @@ struct Profile { // 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::ADLSSmall && !desktopGTDevice(identity)) || - (gt==GT::ADLSSmall && desktopGTDevice(identity) && !overridden()));} + ((!display12SmallGT(gt) && !display12SmallGTDevice(identity)) || + (display12SmallGT(gt) && display12SmallGTDevice(identity) && !overridden()));} }; inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) { Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; diff --git a/docs/device-selection.md b/docs/device-selection.md index d3a4b3df..de9a4a34 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -6,8 +6,8 @@ machine-name setting is required. ## Automatic selection By default, the physical Intel GPU device ID selects the backend. The implemented -IDs are Tiger Lake `9A40`, `9A49`, `9A78` and Alder Lake-N / Twin Lake -`46D0`–`46D4`, plus the development ADL-S/RPL-S IDs listed in the README. +IDs are Tiger Lake `9A40`, `9A49`, `9A60`, `9A68`, `9A70`, `9A78` and Alder Lake-N / Twin Lake +`46D0`–`46D4`, plus the development ADL-S/HX and RPL-S/HX IDs listed in the README. All PCI revisions use the selected family backend. Hardware test coverage is recorded separately and does not gate selection. A stepping-specific branch requires a concrete documented difference or an observed failure. OS, driver @@ -34,7 +34,12 @@ non-Intel physical devices are rejected. An invalid override does not silently fall back to the physical ID. Remove an obsolete ICL or other unsupported spoof to use automatic selection. A restart is required after changing the selection. -The development ADL-S/RPL-S backend requires its physical ID without an override. +The Display12 small-GT backends (TGL GT1, ADL-S/HX and RPL-S/HX) require +their physical ID without an override. Topology comes from the Gen12 slice, +DSS and EU-pair fuses; the supported four-bank capacity is checked separately. +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. Do not use an override merely to change the displayed GPU name. diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 7368e14b..a07e0be7 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -63,9 +63,9 @@ static struct { void onKextLoadForce(int *) {} } lilu; struct XE { XELP::Profile xeProfile; - bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, adlsDetected=false; + bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; bool display13FramebufferRequested=false, display13TraceEnabled=false; - bool adlsNativeReady=false, adlsDisplayHooksReady=false; + bool display12SmallGTReady=false, adlsDisplayHooksReady=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; bool tglProgramEnabled=false, tglTrainingLeaseRequested=false; @@ -90,8 +90,8 @@ // Rendering and Display prerequisites can fail independently. The Display // callbacks themselves are replayed elsewhere without any Render flag. for(unsigned bits=0;bits<16;++bits) { - XE selected;selected.adlsDetected=bits&1;selected.adlsDisplaySelected=bits&2; - selected.adlsNativeReady=bits&4;selected.adlsDisplayHooksReady=bits&8;callbackXE=&selected; + XE selected;selected.display12SmallGTDetected=bits&1;selected.adlsDisplaySelected=bits&2; + selected.display12SmallGTReady=bits&4;selected.adlsDisplayHooksReady=bits&8;callbackXE=&selected; const bool ready=(!(bits&1) || (bits&4)) && (!(bits&2) || (bits&8)); assert(wrapTGLFramebufferStartGate()==ready); assert(wrapTGLAcceleratorStartGate()==ready); @@ -139,16 +139,17 @@ } // Exercise the complete production selection, including the shared Display // adapter on registered desktop products and the still-unimplemented ADL-P. - for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x4690U,0x4680U,0xA780U,0x46A6U,0x46D2U}) + for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x4690U,0x4680U,0xA780U,0x4688U,0x468BU,0xA788U,0xA78BU,0x9A60U,0x9A68U,0x9A70U,0x46A6U,0x46D2U}) for(auto rev:{0U,4U,12U,255U})for(unsigned option=0;option<4;++option) { version=kernel;args={}; if(option&1)args.insert("-wex-tgl-off"); if(option&2)args.insert("-wex-noaccel"); XE selected;selected.init();selected.selectProfile(XELP::identify((id<<16)|0x8086,rev)); - const bool desktop=id==0x4690 || id==0x4680 || id==0xA780; - const bool mobile=id==0x9A49 || id==0x9A78; + const bool desktop=id==0x4690 || id==0x4680 || id==0xA780 || id==0x4688 || id==0x468B || id==0xA788 || id==0xA78B; + const bool gt1=id==0x9A60 || id==0x9A68 || id==0x9A70; + const bool mobile=id==0x9A49 || id==0x9A78 || gt1; const bool small13=id==0x46D2; - assert(selected.adlsDetected==desktop && selected.adlsDisplaySelected==desktop); + assert(selected.display12SmallGTDetected==(desktop || gt1) && selected.adlsDisplaySelected==desktop); assert(selected.adlnDetected==small13 && selected.display13FramebufferRequested==small13); const bool d12=(desktop || mobile) && !(option&1); assert(selected.tglRequested==(small13 || d12)); diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index a78b9711..56fb2e1d 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -17,15 +17,15 @@ int main() { implemented+=p.admitted(); const uint32_t value=entry.device; assert(same(p,select(pci,revision,true,&value,4))); - // The new ADL-S qualification requires physical identity; established + // Display12 small-GT qualification requires physical identity; established // TGL/ADL-N explicit override semantics remain unchanged. auto f=select(0x12348086,revision,true,&value,4); assert(f.overridden() && f.identity==0x12348086 && f.effectiveIdentity==pci); - assert(f.admitted()==(p.admitted() && entry.backend!=GT::ADLSSmall)); + assert(f.admitted()==(p.admitted() && !display12SmallGT(entry.backend))); assert(!strcmp(model(f),entry.model)); auto nonIntel=select(0x123410DE,0,true,&value,4);assert(!nonIntel.admitted()); } - assert(implemented==24); // Recognition of other table rows adds no native backend. + assert(implemented==27); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); assert(select(0x12348086,1,true,&id,4).admitted()); diff --git a/tests/small_gt_urb.py b/tests/small_gt_urb.py new file mode 100644 index 00000000..88dd24d5 --- /dev/null +++ b/tests/small_gt_urb.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Replay production URB patch selection and its native store on CPU, no GPU.""" +import hashlib,re,subprocess,sys,tempfile +from pathlib import Path +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('static const uint8_t urbFind[]');b=s.index('auto gpuInfo=',a) +body=s[a:b] +if len(sys.argv)>1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' + old=bytes(int(x,0) for x in re.search(r'urbFind\[\] \{([^}]+)',body)[1].split(',')) + assert donor[0x28781:0x28781+len(old)]==old +code=r'''#include "kern_xelp_platform.hpp" +#include "kern_xelp_gt_resources.hpp" +#include +#include +#include +#include +#include +void check(unsigned id,unsigned rev,unsigned dss,unsigned eu,unsigned media) { + auto xeProfile=XELP::identify((id<<16)|0x8086,rev); + bool display12SmallGTDetected=XELP::display12SmallGT(xeProfile.gt); + XELP::SmallGTResources display12SmallGT; + uint32_t fuses[]={1,dss,eu,0xf0,media}; + assert(xeProfile.admitted() && display12SmallGTDetected && XELP::decodeSmallGT(fuses,display12SmallGT)); + bool adlnDetected=false;unsigned adlnNativeSubslices=0,adlnL3Banks=0; + BODY + assert(urbReplacement==urbReplaceAlderLakeN); + auto program=(unsigned char *)mmap(nullptr,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0); + assert(program!=MAP_FAILED);memset(program,0xcc,4096); + const unsigned char prefix[]={0x41,0x57,0x49,0x89,0xff}; // save R15, destination from RDI + const unsigned char done[]={0x41,0x5f,0xc3}; + memcpy(program,prefix,5);memcpy(program+5,urbReplacement,sizeof(urbFind)); + memcpy(program+5+sizeof(urbFind),done,3);memcpy(program+5+94,done,3); + unsigned char state[0x1200]; + for(unsigned slice:{0U,1U,2U})for(unsigned ss:{0U,2U,4U,6U,10U,12U}) { + memset(state,0xa5,sizeof(state)); + reinterpret_cast(program)(state,slice,ss); + unsigned result;memcpy(&result,state+0x1164,4); + assert(result==((slice==1 && ss==display12SmallGT.nativeSubslices)?4U:0xa5a5a5a5U)); + for(unsigned i=0;i=0x1168)assert(state[i]==0xa5); + } + munmap(program,4096); +} +int main() { + // Three GT1 identities and 16/32-EU HX identities use fuse-derived topology. + for(auto id:{0x9a60U,0x9a68U,0x9a70U,0x4688U,0x468bU,0xa788U,0xa78bU}) + for(auto rev:{0U,1U,4U,12U,255U})for(auto video:{0xe00feU,0xe00faU}) { + check(id,rev,1,0,video); // 16 EU, one DSS, two native subslices + check(id,rev,3,0xc0,video); // 24 EU, two DSS + check(id,rev,0x30,0,video); // 32 EU, sparse DSS mask + } + XELP::SmallGTResources resource; + assert(!XELP::decodeSmallGT(nullptr,resource)); + for(auto banks:{0U,0xe0U,0xfcU,0xffU}) { + uint32_t f[]={1,3,0,banks,0xe00fe};assert(!XELP::decodeSmallGT(f,resource)); + } + puts("PASS TGL GT1/HX production patch selection, fuse shapes and 3780 native URB stores/rejections"); +}'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='small-gt-urb-') as tmp: + p=Path(tmp);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 21b69198..8fd2fa17 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -23,7 +23,7 @@ int main() { for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,4U,8U,12U,255U}) { const auto p=identify((id<<16)|0x8086,rev); - const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78,adln=id>=0x46D0 && id<=0x46D4; + 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)); diff --git a/tools/test.py b/tools/test.py index a6a568d5..c4b43266 100644 --- a/tools/test.py +++ b/tools/test.py @@ -5,12 +5,12 @@ 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=['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=['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.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 ['tgl_tlb_preparser','tgl_semaphore_tlb','tgl_iosurface_compression']] +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 with (O/(n+'.log')).open('w') as log: From 31826afc837977e1f09f5109173d5a2b577aff78 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:33:24 +0900 Subject: [PATCH 56/58] Prepare WEX 0.5.0 and distinguish tested and expected GPU support --- CHANGELOG.md | 18 ++++++++ README.md | 98 +++++++++++++++++++--------------------- README_ja.md | 93 ++++++++++++++++++++------------------ docs/device-selection.md | 31 ++++++++++++- tools/build.py | 2 +- 5 files changed, 145 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0965b184..80d1e0c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.5.0 + +Requires **WhateverXe Graphics 0.2.0 or newer**. Update both components. + +### Added + +- Add Alder Lake-S graphics acceleration and HDMI/DisplayPort output, including compatible USB-C wiring and up to three displays. +- Accept Tiger Lake-H45, Alder Lake-HX and Raptor Lake-S/HX using shared support. These configurations still need hardware testing. +- Use firmware information to configure more display layouts automatically. + +### Fixed + +- Avoid some startup panics caused by display hardware not being ready. +- Restore Safari graphics-driver access regardless of where macOS installs Safari updates. +- With the updated graphics package, fix observed Chrome drawing failures and rendering errors on smaller integrated GPUs. + +DisplayPort/USB-C reconnection can still freeze the computer. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for tested configurations and other known issues. + ## 0.4.0 Add experimental Tiger Lake laptop display support and automatic Display 13 tiling selection. diff --git a/README.md b/README.md index 3c2aa8a3..62cc17fb 100644 --- a/README.md +++ b/README.md @@ -9,7 +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. +have independent release versions. WEX **0.5.0** requires WhateverXe Graphics +**0.2.0 or newer**; update both components together. ## Compatibility @@ -32,20 +33,41 @@ WEX selects the OS ABI and GPU backend independently. OS-specific graphics payloads and the common AppleIntelTGLCompat provider are installed by WhateverXe Graphics; GPU generation and connector routing are handled by WEX. -| GPU family | Device IDs with an implemented backend | +### Tested on hardware + +| GPU family | Tested configurations | | --- | --- | -| Tiger Lake GT2 / G4 | `9A40`, `9A49`, `9A78` | -| Tiger Lake GT1 (H45) | `9A60`, `9A68`, `9A70` | -| Alder Lake-N / Twin Lake | `46D0`–`46D4` | +| 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-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 +model or connector. See [tested features and known issues](#tested-features-and-known-issues) +for OS coverage and remaining problems. + +### Expected to work, not yet tested on hardware + +- Other supported Tiger Lake-U/H35 configurations, including 96-EU graphics. +- Tiger Lake-H45 (GT1): `9A60`, `9A68`, `9A70`. +- Other supported Alder Lake-N configurations and Twin Lake. +- 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 +IDs are accepted automatically; no CPU-model or machine-specific setting is +needed. Hardware validation is still outstanding. All accepted IDs and resource +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. +- 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. -TGL GT1 uses the TGL display path and fuse-derived 16/24/32-EU, four-bank -GT resources. This composition has CPU and exact-donor test coverage, but no -TGL-H hardware validation. It requires matching updated WEX Graphics payloads; -the published Graphics 0.1.0 package does not admit these IDs. +### No support planned -Support varies by system and connector layout. The table includes implemented -backends, not a guarantee for every machine. See [device selection](docs/device-selection.md) -for detailed selection conditions and overrides. +- Xe-LPG, including Meteor Lake integrated graphics. +- Xe2 and later graphics architectures. ## Required components @@ -68,7 +90,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** before installing the graphics +Boot the target OS with WEX **0.5.0 or newer** 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) @@ -152,8 +174,10 @@ its X-tiled scanout path is unsupported. Display 12 tiling selection is unchange | Hardware | Verified features | | --- | --- | | 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; HDMI and both USB-C ports reconnected successfully on Tahoe | +| 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-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 | These results cover tested systems, not every GPU, display, adapter or dock. The listed resolutions describe test coverage; they are not a fixed resolution @@ -169,53 +193,23 @@ installation or update location. - 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 - Tahoe. The successful reconnection checks above do not rule out intermittent - failures. + Tahoe. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not receive an image. -- Development builds also derive fixed external DP on TGL Combo A/B from VBT and share the SST training, power and AUX lifetime with other DP routes. This composition has CPU coverage and is not yet hardware-qualified. Cross-routed AUX and Type-C/TBT-managed Combo wiring remain excluded. -- TGL eDP signal selection covers RBR through HBR3 using the physical GPU subplatform and VBT panel flags. Firmware requesting HOBL uses standard Linux fallback coefficients; the HOBL optimization remains disabled until its retry lifecycle is supported. High-rate selection has CPU coverage; the hardware results above do not qualify every link rate. -- Qualified TGL PWM/PPS 0 panels combine VBT delays, initial firmware timing and explicit panel properties while retaining native power sequencing. The new timing adapter has CPU and exact-donor coverage; other backlight methods and PPS controllers remain outside its scope. +- DisplayPort output or reconnection can be slow on Alder Lake-S. +- Some connector layouts, Thunderbolt-managed outputs and panel backlight + methods are not supported. See [display limits](docs/device-selection.md#display-limits). +- FairPlay-protected playback is not supported. - Full system sleep/wake and resolutions above those listed above are unverified. USB-C display output currently prevents part of the display hardware from entering a low-power state. ## Roadmap -### Planned - -- Alder Lake and Raptor Lake S/H/P/U support. Development support for Alder Lake-S - `4690` has been tested from Big Sur through Tahoe with Metal and OpenGL - acceleration and three simultaneous displays (two 1080p60 and one 720p60). - With the revised VA assets, hardware H.264 encode/decode, HEVC decode, - Safari hardware video decoding and Widevine playback have also been tested - on Big Sur and Tahoe. - It requires unreleased companion graphics changes and is not supported by the - published graphics pkg. Fixed DP reconnection has been tested on Big Sur, but - initial display output and reconnection can be delayed. HDMI/fixed-DP routing is derived - from VBT rather than a board or connector preset, within the current Combo - PHY A–C backend. Ambiguous routes, cross-routed AUX, Type-C/TBT capability - and PHY D/E are excluded. Development GPU admission covers ADL-S/HX IDs `4680/4682/4688/468A/468B/4690/4692/4693` - and RPL-S/HX IDs `A780/A781/A782/A783/A788/A789/A78A/A78B`. - All PCI revisions select the shared family backend. Physical identity must match, with one slice, - up to two DSS, 16/24/32 EUs, four L3 banks and the supported media-engine mask. - Resource layouts outside that implementation remain unsupported. Only `4690` revision `0C` has hardware validation; - the other IDs have CPU coverage and need the updated companion ID dispatch. Other port layouts have CPU-test - coverage but are not hardware-qualified. A USB-C connector exposed by VBT - as fixed DP on Combo PHY A has also been tested. This does not qualify - Type-C/TBT-managed routes. Development eDP routing also composes a - VBT-qualified DDI A panel with native PPS0 and PWM controller 0, including - panel timing, signal settings and firmware-derived PWM period/polarity. - CPU tests and exact-donor replay cover this path; ADL-S eDP has not been - tested on hardware. PPS1 and AUX-controlled backlights remain unsupported. - Sleep/wake remains unqualified. -- Display 13 USB-C and eDP support - -### Not Planned - -- Xe-LPG support -- Xe2 and later architectures +- Alder Lake-H/P/U and Raptor Lake-H/P/U support. +- USB-C and internal-display support on Display 13 GPUs. +- Improve display reconnection reliability and investigate full system sleep/wake. ## Build and test diff --git a/README_ja.md b/README_ja.md index 6fee9eec..9431f311 100644 --- a/README_ja.md +++ b/README_ja.md @@ -6,6 +6,8 @@ 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**ではWhateverXe Graphics **0.2.0以上**が必要です。両方を更新してください。 + ## 対応範囲 単一のWEXバイナリと1つのグラフィックスpkgで、以下のmacOSに対応します。 @@ -23,15 +25,39 @@ Big Sur〜Sonomaは検証済みカーネルの識別を必要とします。Sequ WEXはOSのABIとGPUバックエンドを独立して選択します。OS別のグラフィックス資材と共通のAppleIntelTGLCompatはWhateverXe Graphicsが導入し、GPU世代・コネクタ配線への対応はWEXが担います。 -| GPUファミリー | バックエンド実装があるdevice ID | +### 実機で動作確認済み + +| GPUファミリー | 確認した構成 | | --- | --- | -| Tiger Lake GT2 / G4 | `9A40`、`9A49`、`9A78` | -| Tiger Lake GT1(H45) | `9A60`、`9A68`、`9A70` | -| Alder Lake-N / Twin Lake | `46D0`–`46D4` | +| Tiger Lake-U | 48 EU・80 EU。外部画面を使う構成とノートPCの画面構成 | +| Alder Lake-N | 16 EU・24 EU。HDMI 2画面 | +| Alder Lake-S | UHD Graphics 770(`4690`)。HDMI、DisplayPort、固定DisplayPortとして配線されたUSB-C端子 | + +各ファミリーの一部構成で確認した結果であり、全モデル・全端子の動作保証ではありません。 +OS別の検証範囲と残る問題は[動作確認済みの機能と既知の問題](#動作確認済みの機能と既知の問題)を参照してください。 + +### 動作を期待できるが実機未検証 + +- 96 EUを含む、対応IDの他のTiger Lake-U/H35構成。 +- Tiger Lake-H45(GT1):`9A60`、`9A68`、`9A70`。 +- 対応IDの他のAlder Lake-N構成とTwin Lake。 +- 対応IDの他のAlder Lake-S構成、Alder Lake-HX、Raptor Lake-S、Raptor Lake-HX。 + +確認済みのファミリーと処理を共有する実装があり、IDは自動で受け付けます。 +CPUモデル名や機種固有の設定は不要ですが、実機での動作確認は残っています。 +受付IDの一覧と対応する資源構成は[デバイス選択](docs/device-selection.md)を参照してください。 + +### 対応に追加改修が必要 + +- Alder Lake-H/P/U、Raptor Lake-H/P/U:描画・表示処理の追加対応が必要です。 + 上記のHX製品とは別の系統です。 +- Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 + このリリースでは未対応で、実機検証の予定もありません。 -TGL GT1はTGLの表示経路と、ヒューズ由来の16/24/32 EU・4-bank GT資源処理を組み合わせます。CPU試験と実donor再生で検証していますが、TGL-H実機では未検証です。対応する更新済みWEX Graphics資材が必要で、公開済みGraphics 0.1.0はこれらのIDを受け付けません。 +### 対応予定なし -対応状況は機種やコネクタ構成によって異なります。この表はバックエンドの実装範囲を示すもので、すべての機種での動作を保証するものではありません。詳細な選択条件と上書き設定は[デバイス選択](docs/device-selection.md)を参照してください。 +- Meteor Lakeの内蔵GPUなど、Xe-LPG。 +- Xe2以降のグラフィックス世代。 ## 必須構成 @@ -48,7 +74,7 @@ SIPは`csr-active-config=03080000`(値`0x803`)に設定してください。 Tahoeでは`CoreMedia / AllowOnePassMetalScaling`の無効化が必要です。WhateverXe Graphicsのインストーラーが設定します。 -グラフィックスpkgはWEX **0.3.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.5.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版の署名チェーンに対応していません。 @@ -106,8 +132,10 @@ GPU backendの選択と実機検証範囲は独立しています。未検証の | ハードウェア | 確認済みの機能 | | --- | --- | | 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画面同時出力。TahoeではHDMIとUSB-Cの2端子で抜き差しも確認 | +| 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-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によるハードウェア描画 | 検証した機器での結果であり、すべてのGPU・ディスプレイ・アダプター・ドックでの動作を保証するものではありません。 記載した解像度は検証範囲であり、WEXが設けた固定の解像度制限ではありません。 @@ -117,44 +145,23 @@ Safariのインストール先や更新による配置変更に依存せずData ### 既知の問題と制限 -- 一部の画像変換処理で、アプリケーションがクラッシュすることがあります。 -- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでも再接続時にハングする場合があり、Big SurとTahoeの両方で再現しています。上記の抜き差し試験の成功だけでは、間欠的な失敗を否定できません。 -- Safariの動画再生で、ときどきコマ落ちが発生します。 -- 同時出力は最大3画面です。4画面目には映像が出ません。 -- 開発版ではTGL Combo A/Bの固定外部DPもVBTから配線を生成し、他のDP経路とSST訓練・電源・AUX保持を共有します。この組合せはCPU試験済みで実機未検証です。AUXの交差配線やType-C/TBT管理を必要とするCombo配線は除外します。 -- TGL eDPの信号設定は、物理GPUのsubplatformとVBTのpanel情報からRBR〜HBR3を選択します。HOBLを要求するfirmwareでも標準のLinux fallback係数を使用します。HOBL最適化は再試行処理を実装するまで無効です。高速度の選択はCPU試験済みですが、上記の実機結果は全link rateの動作保証ではありません。 -- 資格を満たすTGLのPWM/PPS 0 panelでは、VBTの待ち時間・firmwareの初期値・明示panelプロパティを統合し、nativeの電源シーケンスを維持します。新しい待ち時間adapterはCPU・実donorコードで検証済みです。他のbacklight方式やPPS controllerはこのadapterの対象外です。 -- システム全体のスリープ/復帰と、上記を超える解像度は未検証です。USB-C映像出力では現在、表示回路の一部を省電力状態へ移行させずに動作させています。 +- 一部の画像変換処理でアプリがクラッシュすることがあります。 +- 専用DisplayPort端子の抜き差しで黒画面やシステム停止が起きることがあります。 + USB-C DisplayPort Alt Modeでも再接続時のシステム停止がBig SurとTahoeの両方で再現しています。 +- Safariの動画再生で時折コマ落ちすることがあります。 +- 同時表示は最大3画面です。4画面目には映像が出ません。 +- Alder Lake-SではDisplayPortの表示開始や再接続後の復帰に時間がかかる場合があります。 +- 一部の端子配線、Thunderbolt管理の出力、内蔵画面の輝度制御方式は未対応です。 + 詳細は[表示の制限](docs/device-selection.md#display-limits)を参照してください。 +- FairPlayで保護されたコンテンツの再生には未対応です。 +- システム全体のスリープ/復帰と、上記を超える解像度は未検証です。 + USB-C映像出力中は表示ハードウェアの一部が低消費電力状態へ移行しません。 ## ロードマップ -### Planned - -- Alder Lake/Raptor LakeのS/H/P/U対応。開発版のAlder Lake-S `4690`では、 - Big Sur〜TahoeでMetal/OpenGLアクセラレーションと3画面同時表示(1080p60×2+720p60×1)を確認しています。 - 更新版VA資材との組み合わせでは、Big SurとTahoeでハードウェアH.264エンコード/デコード、 - HEVCデコード、Safariのハードウェア動画デコード、Widevine再生も確認しています。 - 未公開のグラフィックス資材変更が必要で、公開済みpkgでは対応しません。 - Big Surで固定DPの再接続は確認済みですが、起動時の表示や再接続後の復帰に時間がかかる場合があります。 - HDMI/固定DPの配線は機種・コネクタのプリセットではなくVBTから生成し、現在のバックエンドはCombo PHY A~Cを扱います。 - 曖昧な配線、DDIと異なるAUXへの配線、Type-C/TBT機能、PHY D/Eは除外します。 - 開発版のGPU受付は、ADL-S/HX `4680/4682/4688/468A/468B/4690/4692/4693` と、 - RPL-S/HX `A780/A781/A782/A783/A788/A789/A78A/A78B` が対象で、すべてのPCI revisionが共通backendを選択します。 - 物理IDの一致、1 slice・最大2 DSS・16/24/32 EU・4 L3 bank・対応media engine構成を要求し、 - この実装範囲外の資源構成は未対応です。実機確認済みは `4690` revision `0C` のみで、他IDはCPU試験の範囲です。 - 追加IDには更新版のcompanion側ID受付も必要です。 - 別のポート配置はCPUテストで確認していますが、実機では未検証です。 - VBT上でCombo PHY Aの固定DPとして扱われるUSB-C端子でも実表示を確認しています。 - これはType-C/TBT管理経路の動作確認ではありません。開発版のeDP経路は、VBTで確認したDDI Aのpanelを - native PPS0/PWM controller 0に接続し、panel timing、信号設定、firmware由来のPWM周期・極性を適用します。 - この経路はCPU試験と実donor再生で検証していますが、ADL-S eDPの実機検証は未実施です。 - PPS1とAUX制御のbacklightは未対応です。消灯/復帰は未検証です。 -- Display 13のUSB-C / eDP対応 - -### Not Planned - -- Xe-LPG対応 -- Xe2以降のアーキテクチャへの対応 +- Alder Lake-H/P/UとRaptor Lake-H/P/Uへの対応。 +- Display 13 GPUでのUSB-C・内蔵画面対応。 +- 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 ## ビルドとテスト diff --git a/docs/device-selection.md b/docs/device-selection.md index de9a4a34..8ec87e47 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -7,7 +7,13 @@ 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 development ADL-S/HX and RPL-S/HX IDs listed in the README. +`46D0`–`46D4`, plus the ADL-S/HX and RPL-S/HX IDs below. + +| Family | Accepted IDs | +| --- | --- | +| ADL-S / HX | `4680`, `4682`, `4688`, `468A`, `468B`, `4690`, `4692`, `4693` | +| RPL-S / HX | `A780`, `A781`, `A782`, `A783`, `A788`, `A789`, `A78A`, `A78B` | + All PCI revisions use the selected family backend. Hardware test coverage is recorded separately and does not gate selection. A stepping-specific branch requires a concrete documented difference or an observed failure. OS, driver @@ -53,3 +59,26 @@ Metal may still report the Apple driver's internal name. PCI ID/family groupings were checked against Linux v6.12's i915 tables; individual marketing names are not a claim of independently verified SKU branding. + +## Display limits + +Connector labels alone do not identify the electrical path. Firmware VBT routing +must match the implemented path; ambiguous or cross-routed AUX layouts are excluded. + +- TGL: fixed Combo A/B DP and existing HDMI, eDP and USB-C paths share display + operations. Additional fixed-DP layouts have CPU coverage, not hardware coverage. +- ADL-S/HX and RPL-S/HX: Combo PHY A–C are implemented; PHY D/E and Type-C/TBT-managed + routes are not. A USB-C socket wired as fixed DP is different from a Type-C-managed + path. Firmware-derived DDI A eDP is implemented but not hardware-tested. +- TGL and ADL-S panel handling supports the qualified PPS0/PWM0 path. Other PPS + controllers and AUX-controlled backlights need additional work. Panel timing + comes from VBT, firmware state and explicit properties. High-rate link selection + has CPU coverage; not every panel or link rate has been tested on hardware. +- 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. + +The Display12 small-GT implementation accepts one slice, up to two DSS, +16/24/32 EUs, four L3 banks and the supported media engines. Different resource +layouts need additional implementation. These checks are independent of whether +an individual PCI revision has been tested. diff --git a/tools/build.py b/tools/build.py index bc3f6827..75e9049f 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.4.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.5.0',sorted((ROOT/'WhateverXe').glob('*.cpp'))+[lilu/'Library/plugin_start.cpp']),('AppleIntelTGLCompat','0.5.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 a815e8180a912c8281351c7456c07c684ca045d8 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:47:57 +0900 Subject: [PATCH 57/58] Distinguish required graphics updates from existing-version compatibility --- CHANGELOG.md | 2 +- README.md | 9 ++++++--- README_ja.md | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d1e0c1..51047c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.5.0 -Requires **WhateverXe Graphics 0.2.0 or newer**. Update both components. +The newly added GPUs require **WhateverXe Graphics 0.2.0**. Its rendering fixes also benefit previously supported GPUs; updating both components is recommended. ### Added diff --git a/README.md b/README.md index 62cc17fb..f720223b 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,11 @@ 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. WEX **0.5.0** requires WhateverXe Graphics -**0.2.0 or newer**; update both components together. +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. ## Compatibility @@ -90,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.5.0 or newer** before installing the graphics +Boot the target OS with WEX **0.3.0 or newer** (0.5.0 for newly added 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) diff --git a/README_ja.md b/README_ja.md index 9431f311..338368e4 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**ではWhateverXe Graphics **0.2.0以上**が必要です。両方を更新してください。 +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を組み合わせてください。 ## 対応範囲 @@ -74,7 +74,7 @@ SIPは`csr-active-config=03080000`(値`0x803`)に設定してください。 Tahoeでは`CoreMedia / AllowOnePassMetalScaling`の無効化が必要です。WhateverXe Graphicsのインストーラーが設定します。 -グラフィックスpkgはWEX **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以上**(今回追加した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#インストール)を参照してください。 リンク先の **Mod版** アーカイブから`AMFIPass.kext`を展開し、`EFI/OC/Kexts`へ配置して、`Kernel > Add`で有効にしてください。既存のAMFIPassがある場合はこのMod版へ置き換え、両方を同時に読み込まないでください。upstreamのAMFIPassはMod版の署名チェーンに対応していません。 From 96d537fec6e9b30cae9646e533b0a841b9f10f74 Mon Sep 17 00:00:00 2001 From: b00t0x <2325377+b00t0x@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:50:38 +0900 Subject: [PATCH 58/58] Link Graphics 0.2.0 fixes explicitly in WEX release notes --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51047c49..e01b57c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.5.0 -The newly added GPUs require **WhateverXe Graphics 0.2.0**. Its rendering fixes also benefit previously supported GPUs; updating both components is recommended. +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. ### Added @@ -14,7 +14,7 @@ The newly added GPUs require **WhateverXe Graphics 0.2.0**. Its rendering fixes - Avoid some startup panics caused by display hardware not being ready. - Restore Safari graphics-driver access regardless of where macOS installs Safari updates. -- With the updated graphics package, fix observed Chrome drawing failures and rendering errors on smaller integrated GPUs. +- [WhateverXe Graphics 0.2.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) fixes observed Chrome drawing failures and rendering errors on smaller integrated GPUs. DisplayPort/USB-C reconnection can still freeze the computer. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for tested configurations and other known issues.