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