diff --git a/AGENTS.md b/AGENTS.md index 3b813996..54c70fea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,13 @@ Source attribution, licenses and reproducible test fixtures remain here. Keep release notes in CHANGELOG.md under an exact version heading. Release CI extracts that section; do not maintain a separate release-note template. +README describes the current state, not release-by-release changes. Recommend +latest component releases instead of accumulating per-version dependency tables. +Use GPU family names rather than internal display-generation labels in README +prose. CHANGELOG describes the affected behavior and OS range; applications used +for validation are not the scope of a general fix. Credit reference repositories +when their work informed a change. + ## Build and test Build with `python3 tools/build.py`; run CPU tests with `python3 tools/test.py`. diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index d34c89b2..8c31560c 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp @@ -52,7 +52,7 @@ static uint32_t read32(void *access, unsigned long offset) { // it does not read plugin boot arguments or depend on framebuffer globals. // The provider pins the immutable binding until reboot, like the donor owner. extern "C" uint32_t xelpProbeCapabilities(uint32_t schema,uint32_t configABI) { - return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP : 0; + return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP : 0; } extern "C" bool xelpConfigureDisplay(void *controller,const XELP::ProbeConfig *config) { if(!controller || !config || !config->valid())return false; diff --git a/AppleIntelTGLCompat/GraphicsPolicy.plist b/AppleIntelTGLCompat/GraphicsPolicy.plist new file mode 100644 index 00000000..e8cf2fe7 --- /dev/null +++ b/AppleIntelTGLCompat/GraphicsPolicy.plist @@ -0,0 +1,38 @@ + + + + + WEX Display13 graphics policy + + CFBundleIdentifier + com.apple.driver.AppleGraphicsDevicePolicy + Default + + unload + + + IOClass + AppleGraphicsDevicePolicy + IOMatchCategory + AppleGraphicsDevicePolicy + IONameMatch + IntelFBClientControl + IOParentMatch + + IOPropertyMatch + + N97FramebufferBackendReady + + + IOProviderClass + AppleIntelFramebufferController + + IOProbeScore + 1000 + IOProviderClass + IntelFBClientControl + IOResourceMatch + IOKit + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index e01b57c1..192fb6b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 0.6.0 + +Use the latest [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics/releases) package. + +### Added + +- Add Alder Lake-H/P/U and Raptor Lake-H/P/U GPU support with automatic resource detection and VBT-selected fixed HDMI routes. +- Add experimental fixed TC DisplayPort output with matching AUX wiring; Alder Lake-H has been tested through a DP-to-HDMI adapter. +- Support startup with multiple fixed TC displays, including supported firmware-scaled boot displays. + +### Fixed + +- Improve display clock ownership, output shutdown and reuse across the shared external-display paths. +- Stop display teardown and reuse when required shutdown steps fail. +- [WhateverXe Graphics 0.3.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) fixes shader compilation failures on Sonoma–Tahoe by expanding supported AIR inputs. + +Alder Lake-H hardware checks cover Big Sur–Tahoe. Other newly accepted mobile configurations remain untested. Internal panels and Type-C-managed output on the newly supported mobile GPUs still need implementation; existing DisplayPort reconnection and Sequoia App Store video hangs remain unresolved. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for coverage and limits. + ## 0.5.0 The newly added GPUs require [WhateverXe Graphics 0.2.0](https://github.com/b00t0x/WhateverXe-Graphics/releases). Its rendering fixes also benefit previously supported GPUs; updating both components is recommended. diff --git a/README.md b/README.md index f720223b..bdbe5049 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,8 @@ This project is experimental and requires the signed graphics package from [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics). It installs the graphics stack under `/Library` and updates the Auxiliary Kernel Collection (AuxKC), without patching System files or snapshots. WEX and the graphics package -have independent release versions. Graphics **0.2.0** is needed for the GPUs -added in WEX **0.5.0** and supplies the latest rendering fixes. Previously -supported Tiger Lake/Alder Lake-N configurations can retain Graphics 0.1.0, -but will not receive the new package fixes or features requiring its updated -components. Use WEX 0.5.0 with Graphics 0.2.0 for the complete set of current features and fixes. +have independent release versions. Use the latest releases of both WEX and +WhateverXe Graphics. ## Compatibility @@ -42,6 +39,7 @@ Graphics; GPU generation and connector routing are handled by WEX. | --- | --- | | Tiger Lake-U | 48- and 80-EU graphics; desktop-style and laptop display layouts | | Alder Lake-N | 16- and 24-EU graphics; two HDMI displays | +| Alder Lake-H | 96-EU graphics (`46A6`); fixed TC HDMI and DisplayPort through a DP-to-HDMI adapter | | Alder Lake-S | UHD Graphics 770 (`4690`); HDMI, DisplayPort and a USB-C port wired as fixed DisplayPort | These are tested configurations within each family, not a guarantee for every @@ -53,6 +51,7 @@ for OS coverage and remaining problems. - Other supported Tiger Lake-U/H35 configurations, including 96-EU graphics. - Tiger Lake-H45 (GT1): `9A60`, `9A68`, `9A70`. - Other supported Alder Lake-N configurations and Twin Lake. +- Other Alder Lake-H/P/U and Raptor Lake-H/P/U configurations with supported fixed HDMI or fixed TC DisplayPort wiring. - Other 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 +61,9 @@ limits are listed in [device selection](docs/device-selection.md). ### Further implementation required -- Alder Lake-H/P/U and Raptor Lake-H/P/U: additional graphics and display support - is needed. These are distinct from the HX products listed above. +- On Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U, + internal eDP panels, Combo-PHY DisplayPort and Type-C-managed output need + further implementation. Accepting the GPU ID does not enable these outputs. - Rocket Lake-S: its display power and clock handling differ from Tiger Lake. It is not supported by this release, and no hardware validation is scheduled. @@ -93,7 +93,7 @@ supported. With the signed graphics package and linked Mod AMFIPass, Tahoe requires `CoreMedia / AllowOnePassMetalScaling` to be disabled; the WhateverXe Graphics installer applies this setting. -Boot the target OS with WEX **0.3.0 or newer** (0.5.0 for newly added GPUs) before installing the graphics +Boot the target OS with the latest WEX before installing the latest graphics pkg. If an older Xe System patch is installed, restore it using its original recovery procedure first. The pkg does not change EFI, System files, snapshots or SIP settings. See [installation and removal](https://github.com/b00t0x/WhateverXe-Graphics#install) @@ -123,6 +123,23 @@ On supported GPUs, WhateverXe also normalizes the detected Intel Management Engine device name to `IMEI`, as WhateverGreen does. A separate ACPI rename from `HECI` or `MEI` to `IMEI` is unnecessary. +Tiger Lake automatically selects VBT-described fixed DisplayPort routes on TC1–TC4 when AUX wiring matches the native controller. Fixed DP and DP Alt Mode use the detected FIA layout. TC3/TC4 fixed DP and non-modular FIA support are implemented but have not been verified on hardware. TC5/TC6 and cross-routed AUX require further backend support. + +Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U use +VBT-described Combo A/B and fixed TC1–TC4 HDMI routes. Experimental fixed +TC1–TC4 SST DisplayPort requires matching VBT AUX wiring. The explicit +Alder Lake-N HDMI property applies only to Combo routes. + +ADL-H has been tested with fixed HDMI outputs and a DP-to-HDMI adapter, +including three connected displays at startup, firmware-scaled boot displays +and DP reconnection. Direct DP sinks and other GPUs in these families have +not been verified with the fixed TC DP path. MST, USB-C DP Alt Mode, +Thunderbolt and cross-routed AUX remain unsupported on these GPUs. + +ADL-P/RPL-P uses its physical ID and detected GPU resources automatically. +No device-ID override or machine-specific AGDP kext is required. +`-wex-noaccel` retains framebuffer-only operation. + ### Internal-panel brightness Add `SSDT-PNLF` to OpenCore to enable internal-display brightness control. @@ -167,10 +184,20 @@ GPU backend selection is independent of hardware test coverage: untested PCI revisions use the same family implementation. Revision-specific behavior is added when a documented difference or observed failure requires it. -The supported Display 13 backend selects Y-tiled scanout automatically; -`AAPL,GfxYTile` is not required in OpenCore. Existing values are preserved. -Do not use the Apple driver's `-yTileDisable` argument with this backend: -its X-tiled scanout path is unsupported. Display 12 tiling selection is unchanged. +Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U select +Y-tiled scanout automatically; `AAPL,GfxYTile` is not required in OpenCore. +Existing values are preserved. Do not use the Apple driver's `-yTileDisable` +argument on these GPUs: their X-tiled scanout path is unsupported. + +For framebuffer bring-up, the optional `-wex-noaccel` argument disables accelerator +startup. On Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U, +it also skips GPU resource probing while preserving the +framebuffer's independent boot-memory and MMIO checks. It does not enable an +otherwise unsupported display backend. + +Temporary development diagnostic: `-wex-adlp-readout` captures ADL-P boot resources while keeping GPU driver admission disabled. It is not required for supported hardware. +`-wex-adlp-load-only` blocks both native framebuffer and accelerator probes/starts on a selected ADL-P/RPL-P device, for load-only diagnostics without GPU initialization. +`-wex-display13-offline` keeps native display controller initialization enabled on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U but reports selected outputs as disconnected, isolating HPD/EDID and mode programming for framebuffer diagnostics. It does not establish display operation. ## Tested features and known issues @@ -179,6 +206,7 @@ its X-tiled scanout path is unsupported. Display 12 tiling selection is unchange | Tiger Lake / Alder Lake-N | Big Sur–Tahoe: dual 1920×1080 at 60 Hz output, Metal/MPS, OpenGL, OpenCL, hardware H.264 encode/decode, HEVC decode, display sleep/wake, Safari video decoding and Widevine playback | | Tiger Lake laptops | Big Sur–Tahoe: internal 1366×768 eDP display, brightness control, HDMI and USB-C DisplayPort Alt Mode, up to three simultaneous displays; USB-C reconnection remains unreliable | | Alder Lake-N | Big Sur/Tahoe: automatic Y tiling without an EFI `AAPL,GfxYTile` setting | +| Alder Lake-H | Big Sur–Tahoe: Metal, OpenGL, hardware H.264 encode/decode, HEVC decode and three displays using fixed HDMI and a DP-to-HDMI adapter | | Alder Lake-S | Big Sur–Tahoe: Metal, OpenGL, OpenCL, hardware H.264 encode/decode, HEVC decode and three displays (two 1080p60 and one 720p60); Safari video playback; Widevine checked on Big Sur and Tahoe | | Tiger Lake / Alder Lake-S | Tahoe: Chrome hardware-accelerated Canvas, WebGL and WebGPU | @@ -186,13 +214,10 @@ These results cover tested systems, not every GPU, display, adapter or dock. The listed resolutions describe test coverage; they are not a fixed resolution limit imposed by WEX. -On Big Sur–Tahoe, WebKit GPU access to the Data GPU drivers uses its valid -Apple platform signature and service identifier, independently of Safari’s -installation or update location. - ### Known issues and limits - Some image-conversion operations can crash the application. +- App Store video playback can freeze the GPU on Sequoia. - Reconnecting a monitor to a dedicated DisplayPort connector can leave the screen black or freeze the computer. USB-C DisplayPort Alt Mode reconnection can also freeze the computer; this has been reproduced on both Big Sur and @@ -210,8 +235,7 @@ installation or update location. ## Roadmap -- Alder Lake-H/P/U and Raptor Lake-H/P/U support. -- USB-C and internal-display support on Display 13 GPUs. +- Complete DisplayPort, USB-C and internal-display support on Alder Lake-N / Twin Lake and Alder Lake-H/P/U / Raptor Lake-H/P/U. - Improve display reconnection reliability and investigate full system sleep/wake. ## Build and test @@ -240,3 +264,5 @@ WhateverGreen source is not a build dependency. WhateverXe retains WhateverGreen's copyright notices and BSD-3-Clause license. See [LICENSE.txt](LICENSE.txt). Lilu and MacKernelSDK are separate dependencies. + +Thanks to [JulioNectar/XPS13Plus](https://github.com/JulioNectar/Hackintosh-XPS13Plus-IrisXe-Tahoe) for sharing its Xe graphics work. Its compiler adapter and LLVM bridge informed the OS-native AIR conversion used in WhateverXe Graphics. diff --git a/README_ja.md b/README_ja.md index 338368e4..81291d24 100644 --- a/README_ja.md +++ b/README_ja.md @@ -6,7 +6,7 @@ WhateverXeは、一部のIntel Xe-LP内蔵GPU向けにAppleのグラフィック 本プロジェクトは実験段階であり、[WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics)の署名済みグラフィックスpkgが必要です。資材を`/Library`へ配置してAuxiliary Kernel Collection(AuxKC)を更新し、Systemファイルやsnapshotにはパッチを適用しません。WEXとグラフィックスpkgは、それぞれ独立したバージョンでリリースします。 -WEX **0.5.0**で追加したGPUにはWhateverXe Graphics **0.2.0**が必要で、同pkgには最新の描画修正も含まれます。従来対応のTiger Lake/Alder Lake-N構成ではGraphics 0.1.0も利用できますが、新pkgの修正や更新部品を必要とする機能は利用できません。今回の追加機能と修正をすべて利用するには、WEX 0.5.0とGraphics 0.2.0を組み合わせてください。 +WEXとWhateverXe Graphicsは、どちらも最新版を使用してください。 ## 対応範囲 @@ -31,6 +31,7 @@ WEXはOSのABIとGPUバックエンドを独立して選択します。OS別の | --- | --- | | Tiger Lake-U | 48 EU・80 EU。外部画面を使う構成とノートPCの画面構成 | | Alder Lake-N | 16 EU・24 EU。HDMI 2画面 | +| Alder Lake-H | 96 EU(`46A6`)。固定TC HDMIとDP→HDMI変換器経由のDisplayPort | | Alder Lake-S | UHD Graphics 770(`4690`)。HDMI、DisplayPort、固定DisplayPortとして配線されたUSB-C端子 | 各ファミリーの一部構成で確認した結果であり、全モデル・全端子の動作保証ではありません。 @@ -41,6 +42,7 @@ OS別の検証範囲と残る問題は[動作確認済みの機能と既知の - 96 EUを含む、対応IDの他のTiger Lake-U/H35構成。 - Tiger Lake-H45(GT1):`9A60`、`9A68`、`9A70`。 - 対応IDの他のAlder Lake-N構成とTwin Lake。 +- 対応する固定HDMIまたは固定TC DisplayPort配線の、他のAlder Lake-H/P/U・Raptor Lake-H/P/U構成。 - 対応IDの他のAlder Lake-S構成、Alder Lake-HX、Raptor Lake-S、Raptor Lake-HX。 確認済みのファミリーと処理を共有する実装があり、IDは自動で受け付けます。 @@ -49,8 +51,9 @@ CPUモデル名や機種固有の設定は不要ですが、実機での動作 ### 対応に追加改修が必要 -- Alder Lake-H/P/U、Raptor Lake-H/P/U:描画・表示処理の追加対応が必要です。 - 上記のHX製品とは別の系統です。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uの内蔵eDPパネル・ + Combo PHY経由のDisplayPort・Type-C管理下の出力には追加改修が必要です。 + GPUのIDを受け付けるだけで、これらの出力が有効になるわけではありません。 - Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 このリリースでは未対応で、実機検証の予定もありません。 @@ -74,7 +77,7 @@ SIPは`csr-active-config=03080000`(値`0x803`)に設定してください。 Tahoeでは`CoreMedia / AllowOnePassMetalScaling`の無効化が必要です。WhateverXe Graphicsのインストーラーが設定します。 -グラフィックスpkgはWEX **0.3.0以上**(今回追加したGPUでは0.5.0)を読み込んだ対象OSから実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 +最新版のWEXを読み込んだ対象OSから、最新版のグラフィックスpkgを実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 リンク先の **Mod版** アーカイブから`AMFIPass.kext`を展開し、`EFI/OC/Kexts`へ配置して、`Kernel > Add`で有効にしてください。既存のAMFIPassがある場合はこのMod版へ置き換え、両方を同時に読み込まないでください。upstreamのAMFIPassはMod版の署名チェーンに対応していません。 @@ -88,6 +91,14 @@ GPUはPCI device IDから自動選択します。対応範囲内ではファー 対応GPUでは、WhateverGreenと同様に、検出されたIntel Management Engineのデバイス名も`IMEI`へ統一します。`HECI`や`MEI`から`IMEI`へのACPI renameを別途追加する必要はありません。 +Tiger Lakeはnative controllerとAUX配線が一致するVBTの固定DisplayPortをTC1〜TC4で自動選択します。固定DPとDP Alt Modeは検出したFIA構成を使用します。TC3/TC4の固定DPと非modular FIAは実装済み・実機未検証です。TC5/TC6と交差AUX配線には追加のbackend対応が必要です。 + +Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uでは、Combo A/B・固定TC1〜TC4 HDMI配線をVBTから選択します。試験的な固定TC1〜TC4 SST DisplayPortには、VBTのAUX配線の一致が必要です。Alder Lake-NのHDMI明示propertyはCombo配線にのみ適用します。 + +ADL-Hでは、固定HDMIとDP→HDMI変換器を使い、3画面接続での起動、ファームウェアによる拡大表示からの起動、DP再接続を確認しています。固定TC DP経路での直接DP接続の表示先と、これらのファミリーの他のGPUは未検証です。これらのGPUではMST、USB-C DP Alt Mode、Thunderbolt、交差AUX配線は未対応です。 + +ADL-P/RPL-Pは物理IDと検出したGPU資源を自動で使用します。device-ID上書きや機種別AGDP kextは不要です。`-wex-noaccel`でFramebufferのみの動作を維持します。 + ### 内蔵パネルの輝度 内蔵ディスプレイの輝度調整には、OpenCoreへ`SSDT-PNLF`を追加してください。 @@ -124,8 +135,16 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 GPU backendの選択と実機検証範囲は独立しています。未検証のPCI revisionも同じfamily実装を使い、 文書化された差分や実際の不具合で必要になった場合にrevision別の処理を追加します。 -対応済みのDisplay 13バックエンドではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 -このバックエンドはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。Display 12のタイル選択は変更しません。 +Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 +これらのGPUはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。 + +FBの立上げ時は、任意の`-wex-noaccel`引数でAcceleratorの起動を止められます。 +Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UではGPU資源のprobeも省略し、FB独自の起動メモリ・MMIOの確認は維持します。 +未対応のDisplayバックエンドを有効にする引数ではありません。 + +開発時の一時診断用に `-wex-adlp-readout` を指定すると、GPUドライバの受付を閉じたままADL-Pの起動資源を採取します。対応済みハードウェアでは不要です。 +`-wex-adlp-load-only` は選択済みADL-P/RPL-Pで、FramebufferとAccelerator両方のnative probe/startを止め、GPUを初期化せずにロード段階を診断します。 +`-wex-display13-offline` はAlder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/Uのnative display controller初期化を有効に保ち、選択した出力を未接続として扱います。FB診断でHPD/EDIDとmode設定を切り離すための設定で、表示成立を意味しません。 ## 動作確認済みの機能と既知の問題 @@ -134,18 +153,17 @@ GPU backendの選択と実機検証範囲は独立しています。未検証の | Tiger Lake / Alder Lake-N | Big Sur〜Tahoe:外部2画面の1920×1080・60Hz出力、Metal/MPS、OpenGL、OpenCL、ハードウェアH.264エンコード/デコード、HEVCデコード、画面の消灯/復帰、Safariの動画デコード、Widevine再生 | | Tiger Lakeラップトップ | Big Sur〜Tahoe:内蔵1366×768 eDP、輝度調整、HDMI、USB-C DisplayPort Alt Mode、最大3画面同時出力。USB-Cの再接続には未解決の問題あり | | Alder Lake-N | Big Sur/Tahoe:EFIの`AAPL,GfxYTile`設定なしでのYタイル自動選択 | +| Alder Lake-H | Big Sur〜Tahoe:Metal、OpenGL、ハードウェアH.264エンコード/デコード、HEVCデコード、固定HDMIとDP→HDMI変換器を使った3画面 | | Alder Lake-S | Big Sur〜Tahoe:Metal、OpenGL、OpenCL、ハードウェアH.264エンコード/デコード、HEVCデコード、3画面(1080p60×2+720p60×1)、Safari動画再生。WidevineはBig SurとTahoeで確認 | | Tiger Lake / Alder Lake-S | Tahoe:ChromeのCanvas、WebGL、WebGPUによるハードウェア描画 | 検証した機器での結果であり、すべてのGPU・ディスプレイ・アダプター・ドックでの動作を保証するものではありません。 記載した解像度は検証範囲であり、WEXが設けた固定の解像度制限ではありません。 -Big Sur〜Tahoeでは、有効なApple platform署名とサービス識別子でWebKit GPUを判定し、 -Safariのインストール先や更新による配置変更に依存せずData側GPUドライバへのアクセスを扱います。 - ### 既知の問題と制限 - 一部の画像変換処理でアプリがクラッシュすることがあります。 +- SequoiaではApp Storeの動画再生でGPUが停止することがあります。 - 専用DisplayPort端子の抜き差しで黒画面やシステム停止が起きることがあります。 USB-C DisplayPort Alt Modeでも再接続時のシステム停止がBig SurとTahoeの両方で再現しています。 - Safariの動画再生で時折コマ落ちすることがあります。 @@ -159,8 +177,7 @@ Safariのインストール先や更新による配置変更に依存せずData ## ロードマップ -- Alder Lake-H/P/UとRaptor Lake-H/P/Uへの対応。 -- Display 13 GPUでのUSB-C・内蔵画面対応。 +- Alder Lake-N / Twin LakeとAlder Lake-H/P/U / Raptor Lake-H/P/UでのDisplayPort・USB-C・内蔵画面対応の拡充。 - 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 ## ビルドとテスト @@ -177,3 +194,5 @@ WhateverXeとAppleIntelTGLCompatのkext、およびソースのmanifestは`build ## ライセンスとクレジット WhateverXeはWhateverGreenの著作権表示とBSD-3-Clauseライセンスを継承しています。[LICENSE.txt](LICENSE.txt)を参照してください。LiluとMacKernelSDKは別の依存コンポーネントです。 + +Xeグラフィックスの取り組みを公開している[JulioNectar/XPS13Plus](https://github.com/JulioNectar/Hackintosh-XPS13Plus-IrisXe-Tahoe)に感謝します。同リポジトリのcompiler adapterとLLVM bridgeを、WhateverXe GraphicsのOS標準AIR変換の参考にしました。 diff --git a/WhateverXe/kern_adls_boot.hpp b/WhateverXe/kern_adls_boot.hpp new file mode 100644 index 00000000..f3ce595b --- /dev/null +++ b/WhateverXe/kern_adls_boot.hpp @@ -0,0 +1,137 @@ +// ADL-S firmware may scan out on several pipes; the donor adopts only the +// first enabled transcoder. Retire the other linear GOP consumers before +// native startup. Keep clocks, PLLs and power requests for native accounting. +// Ordering: Linux v6.12 intel_display.c / intel_ddi.c, Display12 SST disable. +#ifndef KERN_ADLS_BOOT_HPP +#define KERN_ADLS_BOOT_HPP +#include "kern_adls_clock.hpp" +#include "kern_xelp_gop_stop.hpp" +#include "kern_alderlake_n.hpp" +#include "kern_display13_fb_irq.hpp" +namespace AlderLakeS { +namespace Boot { +enum class Status:uint32_t {Unavailable,Unchanged,Complete,Topology,Drift,Timeout}; +struct Result { + uint32_t version=1;Status status=Status::Unavailable; + uint32_t active {},primary=UINT32_MAX,parked {},stage {},writes {},reg {},value {}; +}; +template Result prepare(IO &io,bool admitted,const Wiring &w) { + Result r; + if(!admitted || !w.valid || !w.selected())return r; + AlderLakeN::DisplayReadout s;s.status=AlderLakeN::ReadoutStatus::Complete; + const auto add=[&](uint32_t reg) { + if(s.count>=160 || !io.readable(reg)){s.status=AlderLakeN::ReadoutStatus::Unavailable;return UINT32_MAX;} + const auto v=io.read(reg);s.registers[s.count++]={reg,v}; + if(v==UINT32_MAX)s.status=AlderLakeN::ReadoutStatus::Unavailable; + return v; + }; + const auto power=add(0x45404); + if(power==UINT32_MAX || !(power&1))return r; + const uint32_t common[] {0x45450,0x45454,0x45444,0x164280,0x1642BC,0x46010,0x46014,0x46018,0x46030}; + for(auto reg:common)add(reg); + const unsigned ddis[] {0,3,4,5,6}; + for(auto d:ddis)add(0x64000+0x100*d); + uint32_t ports[3] {UINT32_MAX,UINT32_MAX,UINT32_MAX},used=0; + for(unsigned p=0;p<3;++p) { + const uint32_t mask=p==0?1:p==1?0x15:0x55; + if((power&mask)!=mask)continue; + s.poweredPipes|=1U<>27)&15,protocol=(func>>24)&7; + if(!encoding || protocol>2 || ((func>>20)&7) || get(0x60404+bank))return r; + const auto d=encoding-1;Port port; + if(!Port::resolve(d,port) || !(w.selected()&(1U<>28)!=d+1 || (protocol==2 && (!(tp&0x80000000U) || (tp&(1U<<27)))))return r; + const auto geometry=XELPGeometry::linear(get(0x70180+bank),get(0x70188+bank),get(0x70190+bank),get(0x7018C+bank),get(0x701A4+bank)); + if(!AlderLakeN::bootGeometry(s,p,geometry,get(0x60000+bank),get(0x6000C+bank)) || + (get(0x7019C+bank)&4095) || get(0x7019C+bank)!=get(0x701AC+bank))return r; + } + uint32_t changed[32] {},count=0,current=UINT32_MAX; + const auto stable=[&] { + for(unsigned i=0;i=2 && x.offset==0x64000+0x100*ports[current] ? ~0x80U : UINT32_MAX; + if(!skip && ((io.read(x.offset)^x.value)&mask)){r.reg=x.offset;r.value=io.read(x.offset);return false;} + } + return true; + }; + if(!stable()){r.status=Status::Drift;return r;} + const auto write=[&](uint32_t reg,uint32_t v) { + if(!stable()){r.status=Status::Drift;return false;} + bool seen=false;for(unsigned i=0;i=32)return false;changed[count++]=reg;} + io.write(reg,v);++r.writes;io.read(reg);return true; + }; + const auto wait=[&](uint32_t reg,uint32_t mask,uint32_t value) { + r.reg=reg; + return XELPGOP::poll(io,stable,[&](uint32_t v){return (v&mask)==value;}, + reg,r.value,r.status,Status::Drift); + }; + for(unsigned p=r.primary+1;p<3;++p)if(r.active&(1U<=2 && uint32_t(v-frame)<0x10000000U;}, + 0x70040+bank,now,r.status,Status::Drift))return r; + r.stage=2; + if(!write(0x70008+bank,get(0x70008+bank)&~0x80000000U) || !wait(0x70008+bank,0xC0000000U,0))return r; + r.stage=3; + if(!write(0x60400+bank,get(0x60400+bank)&~(0x80000000U|(15U<<27)|(7U<<24))) || + !wait(0x60400+bank,0x80000000U,0))return r; + for(unsigned n=0;n<2;++n) { + const auto base=0x68170+0x800*p+0x100*n; + if(get(base+0x10)&0x80000000U) { + if(!write(base+0x10,0) || !wait(base+0x10,UINT32_MAX,0) || !write(base,0) || !write(base+4,0))return r; + } + } + r.stage=4; + if(!XELPGOP::stopBuffer(write,wait,bank,port.buffer,get(port.buffer),get(0x60540+bank), + ((get(0x60400+bank)>>24)&7)==2))return r; + r.stage=5; + if(!write(0x46140+4*p,get(0x46140+4*p)&0x0FFFFFFFU) || !wait(0x46140+4*p,0xF0000000U,0))return r; + r.parked|=1U< #include "kern_display13_power_bridge.hpp" #include "kern_display13_clock.hpp" +#include "kern_display13_tc.hpp" namespace AlderLakeN { constexpr uint32_t Identity = 0x46D18086; @@ -55,7 +56,8 @@ static_assert(sizeof(GTReadout)==60*4,"AlderLakeN GT readout layout"); // Linux v6.12 gen12 forcewake domains: fuses and BCS are GT, RCS is render. // Only acquire an unowned kernel bit0. Never reset domains or alter engines. -template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullptr) { +template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullptr, + bool (*decode)(const uint32_t *,Resources &)=nullptr) { GTReadout out; if(!admitted)return out; constexpr uint32_t request[] {0xA188,0xA278},ack[] {0x130044,0xD84}; @@ -95,8 +97,9 @@ template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullp if(out.fuses[i]==UINT32_MAX)out.status=GTStatus::Unavailable; } if(out.status==GTStatus::Complete && - (!resources(out.fuses[0],out.fuses[1],out.fuses[2],out.fuses[3],out.resources) || - out.fuses[4]!=0x000E00FE))out.status=GTStatus::ProfileMismatch; + !(decode ? decode(out.fuses,out.resources) : + (resources(out.fuses[0],out.fuses[1],out.fuses[2],out.fuses[3],out.resources) && + out.fuses[4]==0x000E00FE)))out.status=GTStatus::ProfileMismatch; if(edram) { *edram=io.readable(0x120010) ? io.read(0x120010) : UINT32_MAX; if(*edram==UINT32_MAX)out.status=GTStatus::Unavailable; @@ -173,6 +176,12 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { for (auto offset : transport) add(offset + 0x1000 * pipe); add(0x46140 + 4 * pipe); + // Display11+ has two scalers per powered pipe (Linux display runtime). + // Capture the binding and destination, without touching power/coefficients. + for(unsigned scaler=0;scaler<2;++scaler) { + const auto base=0x68170+0x800*pipe+0x100*scaler; + add(base);add(base+4);add(base+0x10); + } // Display13 DSC/joiner belongs to the corresponding pipe well. add(0x78000 + 0x200 * pipe);add(0x78004 + 0x200 * pipe); } @@ -191,13 +200,23 @@ inline bool value(const DisplayReadout &r, uint32_t offset, uint32_t &out) { // Source-side certificate for an already-running, uncompressed external SST // GOP. This does not prove sink DPCD state, PLL rate or native AUX ownership. -inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) { - if(pipe>=3 || ddi>=2 || boot.status!=ReadoutStatus::Complete || +inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi,const Display13TC::Boot *tc=nullptr) { + const bool typeC=ddi>=3 && ddi<=6; + if(pipe>=3 || (ddi>=2 && !typeC) || boot.status!=ReadoutStatus::Complete || !(boot.activePipes&boot.poweredPipes&(1U<valid() || tc->port.ddi!=ddi || tc->pipe!=pipe || + !value(boot,0x45404,power) || power!=tc->power || + !value(boot,0x45454,ddiPower) || ddiPower!=tc->ddiPower)return false; + buf=tc->buffer; + } else if(!value(boot,0x64000+ddi*0x100,buf))return false; if(!value(boot,0x60400+bank,trans) || !value(boot,0x60540+bank,tp) || - !value(boot,0x64000+ddi*0x100,buf) || !value(boot,0x46140+4*pipe,clock) || + !value(boot,0x46140+4*pipe,clock) || !value(boot,0x60404+bank,ctl2) || !value(boot,0x78000+pipe*0x200,dss1) || !value(boot,0x78004+pipe*0x200,dss2) || !value(boot,0x60030+bank,dm) || !value(boot,0x60034+bank,dn) || !value(boot,0x60040+bank,lm) || @@ -205,10 +224,16 @@ inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) { const unsigned lanes=((trans>>1)&7)+1; // Linux v6.12 i915_reg.h: SST, normal training, scrambling, no FEC/MST, // port sync, HDCP, DSC or joined pipes. Enhanced framing may be on or off. + // intel_ddi_read_func_ctl classifies SST from MODE_SELECT, not the MST + // payload-allocation bit. Firmware may leave bit8 set on an SST GOP; + // preserve it and include it in the unchanged-source check, never clear it + // on an enabled transcoder or infer an MST transport from that bit alone. return (trans&0xFF700000U)==(0x82000000U|((ddi+1)<<27)) && - !(trans&((1U<<15)|(1U<<9)|(1U<<8))) && !ctl2 && + !(trans&((1U<<15)|(1U<<9))) && !ctl2 && (lanes==1 || lanes==2 || lanes==4) && ((buf>>1)&7)==lanes-1 && - (buf&0x800000C0U)==0x80000000U && (clock>>28)==ddi+1 && + (buf&(typeC && tc->ownershipOptional?0x80000080U:0x800000C0U))== + (typeC && !tc->ownershipOptional?0x80000040U:0x80000000U) && + (clock>>28)==ddi+(typeC?3:1) && (!typeC || clock==tc->transClock) && (tp&~(1U<<18))==0x80000300U && !(dss1&0xF0300000U) && !(dss2&0x80008000U) && !(dm&0x81000000U) && (dm&0xFFFFFF) && @@ -220,15 +245,23 @@ enum class GOPProtocol : uint32_t {TMDS,DP}; // This is an early, serialized bootstrap transaction, not a modeset or link // adoption certificate. A changed link stops further writes, including rollback. template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi, - uint32_t addedAUXRequest=0) { - if(pipe>=3 || ddi>=2 || (addedAUXRequest && addedAUXRequest!=(2U<<(2*ddi))))return false; + uint32_t addedAUXRequest=0,const Display13TC::Boot *tc=nullptr) { + // addedAUXRequest is the existing Combo boot-lease transition only. + // TC lifetime uses Mode's own AUX lease, not the Combo MainLinkAUX slot. + if(!dpSource(r,pipe,ddi,tc) || (addedAUXRequest && (ddi>=2 || addedAUXRequest!=(2U<<(2*ddi)))))return false; + if(ddi>=3 && !tc->unchanged(io))return false; const uint32_t bank=pipe*0x1000; - const uint32_t regs[] {0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank, + // Recheck power before touching pipe-local registers. A captured snapshot + // is not permission to read a bank whose well has since disappeared. + const uint32_t regs[] {0x45404,0x45454,0x45444,0x42000, + 0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank, 0x60040+bank,0x60044+bank,0x70008+bank,0x46140+4*pipe,0x64000+0x100*ddi, - 0x78000+0x200*pipe,0x78004+0x200*pipe,0x45404,0x45454,0x45444,0x42000}; + 0x78000+0x200*pipe,0x78004+0x200*pipe}; for(auto reg:regs) { uint32_t expected=0; - if(!value(r,reg,expected) || !io.readable(reg))return false; + if(ddi>=3 && reg==0x64000+0x100*ddi)expected=tc->buffer; + else if(!value(r,reg,expected))return false; + if(!io.readable(reg))return false; if(reg==Display13Power::AUX)expected|=addedAUXRequest; if(io.read(reg)!=expected)return false; } @@ -269,7 +302,7 @@ template Display13Power::Result adoptDPBootAUX(IO &io,const DisplayRea } enum class LinearStatus : uint32_t { NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive, - StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource + StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource, InvalidTCSource }; struct LinearPlan { uint32_t pipe {}, chicken {}, arb {}, control {}, stride {}, position {}, size {}, offset {}, surface {}; @@ -281,19 +314,60 @@ struct LinearResult { uint32_t pipe {}, writes {}, framesBefore {}, framesAfter {}, restored {}; }; +// Qualify a firmware pipe scaler separately from an unscaled linear GOP. +// PIPESRC is width:height minus one; scaler WIN_SZ is width:height, not +// minus one. Only one pipe-bound scaler covering the complete output is +// accepted. Plane-bound/planar/programmed-factor scaling is not this contract. +inline bool bootGeometry(const DisplayReadout &r,unsigned pipe,const XELPGeometry::Linear &geometry, + uint32_t ht,uint32_t vt) { + if(XELPGeometry::timing(geometry,ht,vt))return true; + if(!geometry.valid || pipe>=3 || ht==UINT32_MAX || vt==UINT32_MAX || + (ht&0xC000C000) || (vt&0xC000C000))return false; + uint32_t src=0; + if(!value(r,0x6001C+0x1000*pipe,src) || + src!=(((geometry.width-1)<<16)|(geometry.height-1)))return false; + const auto width=(ht&0x3FFF)+1,height=(vt&0x3FFF)+1; + if(((ht>>16)&0x3FFF)+1>16)&0x3FFF)+1 bool bootGeometryUnchanged(IO &io,const DisplayReadout &r,unsigned pipe) { + // The accepted scaler stays live while the primary plane changes GGTT mode. + // Any captured source/timing/scaler drift invalidates the handoff. + const uint32_t regs[] {0x60000+0x1000*pipe,0x6000C+0x1000*pipe,0x6001C+0x1000*pipe, + 0x68170+0x800*pipe,0x68174+0x800*pipe,0x68180+0x800*pipe, + 0x68270+0x800*pipe,0x68274+0x800*pipe,0x68280+0x800*pipe}; + for(auto reg:regs) {uint32_t v=0;if(value(r,reg,v) && io.read(reg)!=v)return false;} + return true; +} + // First Display 13 operation: reinitialize an existing linear RGB primary -// plane on the explicitly selected Combo A/B/C. The default remains B for +// plane on the explicitly selected Combo, or fixed TC with a live link +// certificate. The default remains B for // the original standalone diagnostics. No new PLL/PHY/power-well cold // initialization, a new GGTT mapping, full modeset or accelerated scanout. -inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS) { +inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS, + const Display13TC::Boot *tc=nullptr) { p={}; - if (ddi>=3 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || + if (ddi>=7 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || (r.activePipes & (r.activePipes-1)) || (r.activePipes&~r.poweredPipes) || (protocol!=GOPProtocol::TMDS && protocol!=GOPProtocol::DP) || (protocol==GOPProtocol::TMDS && ddi==1 && r.activePipes!=r.hdmiBPipes)) return LinearStatus::NotSingleHDMI; while (!(r.activePipes & (1U<=3) return LinearStatus::NotSingleHDMI; + if(ddi>=3 && (!tc || !tc->valid() || tc->port.ddi!=ddi || tc->pipe!=p.pipe || + (protocol!=GOPProtocol::DP && tc->ownershipOptional))) + return LinearStatus::InvalidTCSource; const uint32_t delta=0x1000*p.pipe; uint32_t trans {}, cursor {}, surfLive {}, plane {}, pipeConf {}, htotal {}, vtotal {}; if (!value(r,0x42084,p.chicken) || !value(r,0x70028+delta,p.arb) || @@ -306,11 +380,11 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d if(!(trans&(1U<<31)) || ((trans>>27)&15)!=ddi+1 || (protocol==GOPProtocol::TMDS && ((trans>>24)&7)>1)) return LinearStatus::NotSingleHDMI; - if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi))return LinearStatus::InvalidDPSource; + if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi,tc))return LinearStatus::InvalidDPSource; p.geometry=XELPGeometry::linear(p.control,p.stride,p.size,p.position,p.offset); if(!p.geometry.valid || (p.surface&4095) || p.surface!=surfLive || ((trans>>20)&7)!=0 || (pipeConf&0xC0000000)!=0xC0000000 || - !XELPGeometry::timing(p.geometry,htotal,vtotal))return LinearStatus::NotLinearFHD; + !bootGeometry(r,p.pipe,p.geometry,htotal,vtotal))return LinearStatus::NotLinearFHD; // DPT disable is global on Display 13. Exclude all other active planes // and cursors, including planes on powered but inactive pipes. for (unsigned pipe=0;pipe<3;++pipe) { @@ -328,11 +402,11 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d } template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1, - GOPProtocol protocol=GOPProtocol::TMDS) { + GOPProtocol protocol=GOPProtocol::TMDS,const Display13TC::Boot *tc=nullptr) { LinearResult out; if (!admitted) return out; LinearPlan p; - out.status=linearPlan(r,p,ddi,protocol); + out.status=linearPlan(r,p,ddi,protocol,tc); if(out.status!=LinearStatus::Ready)return out; out.pipe=p.pipe; const auto d=0x1000*p.pipe; @@ -340,7 +414,9 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout const auto write=[&](uint32_t reg,uint32_t v){io.write(reg,v);++out.writes;io.read(reg);}; bool linkChanged=false; const auto linkStable=[&] { - if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi))linkChanged=true; + if(!bootGeometryUnchanged(io,r,p.pipe))linkChanged=true; + if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi,0,tc))linkChanged=true; + if(ddi>=3 && (!tc || !tc->unchanged(io)))linkChanged=true; return !linkChanged; }; // Check all captured words used by the transaction before the first write. diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index 07e28cd6..d048343b 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -1,29 +1,109 @@ -// Combo multi-GOP bootstrap: preserve pipe A, park other GOPs before native +// Multi-GOP bootstrap: preserve pipe A, park other GOPs before native // ownership exists. This does not pretend both GOPs were adopted by the donor. #ifndef KERN_DISPLAY13_BOOT_HPP #define KERN_DISPLAY13_BOOT_HPP #include "kern_alderlake_n.hpp" +#include "kern_xelp_gop_stop.hpp" #include "kern_display13_fb_irq.hpp" #include "kern_display13_fb_topology.hpp" #include "kern_xelp_memory_layout.hpp" +#include "kern_display13_tc_clock.hpp" namespace Display13Boot { -// Certificate for the existing linear TMDS boot path. Render qualification is +// Certificate for the linear boot memory. Render qualification is // separate; callers must also qualify the physical owner, BAR and memory layout. -// DP/other-pipe adoption requires a separate boot contract, not a wider default. +// DP requires an explicit source certificate; native link adoption remains a +// separate contract. Existing runtime callers retain their TMDS default. template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout &display, - bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory) { + bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory, + const Display13TC::Boot *tc=nullptr, + AlderLakeN::GOPProtocol protocol=AlderLakeN::GOPProtocol::TMDS) { AlderLakeN::LinearPlan plan; - if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi)!=AlderLakeN::LinearStatus::Ready || + if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi,protocol,tc)!=AlderLakeN::LinearStatus::Ready || plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false; + 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)); + }; + if(!stable())return false; for(unsigned i=0;i AlderLakeN::LinearResult prepareLinear(IO &io,const AlderLakeN::DisplayReadout &boot, + bool admitted,unsigned ddi,const XELPMemory::Layout &memory,const Display13TC::Boot *tc=nullptr, + AlderLakeN::GOPProtocol protocol=AlderLakeN::GOPProtocol::TMDS) { + using namespace AlderLakeN; + LinearResult r; + if(!admitted)return r; + if(!reservedPrefix(io,boot,true,ddi,memory,tc,protocol)){r.status=LinearStatus::StateChanged;return r;} + return reinitializeLinear(io,boot,true,ddi,protocol,tc); } enum class Status : uint32_t {Unavailable,Complete,Topology,Readback,Timeout,PeerChanged}; struct Result { uint32_t version {1};Status status {Status::Unavailable}; uint32_t stage {},writes {},before {},after {},lastReg {},expected {},observed {},framesBefore {},framesAfter {}; }; +// Firmware can leave ownership on a selected fixed TC port that has no GOP. +// Linux intel_tc_port_sanitize_mode disconnects unused PHYs. Here admit only +// the already-cold case: disabled/idle buffer, stopped/gated PLL and clocks, +// no transcoder route and no AUX command. Do not stop an unclassified link. +struct UnusedTCResult { + uint32_t version {1};Status status {Status::Unavailable}; + uint32_t cleared {},writes {},ddi {},buffer {},ready {},clock {},pll {},gate {}; +}; +template UnusedTCResult releaseUnusedTC(IO &io,const AlderLakeN::DisplayReadout &boot, + bool admitted,const Display13HDMIWiring &w) { + UnusedTCResult r; + if(!admitted)return r; + r.status=Status::Topology; + if(boot.status!=AlderLakeN::ReadoutStatus::Complete || boot.activePipes!=1 || + !(boot.poweredPipes&1) || !Display13FBTopology::multiOutputWiring(w))return r; + uint32_t power=0,auxPower=0; + if(!AlderLakeN::value(boot,0x45404,power) || (power&5)!=5 || + !AlderLakeN::value(boot,0x45444,auxPower))return r; + const auto stable=[&] { + for(unsigned i=0;i>27)&15)==d+1 || (clock>>28)==d+3)routed=true; + } + if(routed)continue; + Display13TC::Port port;Display13TC::Port::resolve(d,port); + r.ddi=d;r.buffer=io.read(port.buffer); + if(r.buffer==UINT32_MAX){r.status=Status::Readback;return r;} + if(!(r.buffer&64))continue; + r.ready=io.read(port.ready);r.clock=io.read(port.clock);r.pll=io.read(port.pll); + r.gate=io.read(Display13TC::Clock::Gate); + const auto aux=0x64310+0x100*(d-3); + if((r.buffer&0x800000C0U)!=0xC0 || r.ready==UINT32_MAX || (r.ready&7)!=4 || + r.clock==UINT32_MAX || (r.clock&0xF0000000U) || r.pll==UINT32_MAX || (r.pll&0xCC000000U) || + r.gate==UINT32_MAX || !(r.gate&Display13TC::Clock::gateBit(d-3)) || + ((auxPower&(1U<<(2*d))) && (!io.readable(aux) || (io.read(aux)&0x80000000U))))return r; + if(!stable() || io.read(port.buffer)!=r.buffer || io.read(port.ready)!=r.ready || + io.read(port.clock)!=r.clock || io.read(port.pll)!=r.pll) {r.status=Status::PeerChanged;return r;} + io.write(port.buffer,r.buffer&~64U);++r.writes; + if(io.read(port.buffer)!=(r.buffer&~64U) || !stable() || io.read(port.clock)!=r.clock || + io.read(port.pll)!=r.pll) {r.status=Status::Readback;return r;} + r.cleared|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boot,bool admitted, const Display13HDMIWiring &w,const XELPMemory::Layout &memory) { Result r;r.before=boot.activePipes; @@ -33,6 +113,11 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo if(!memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete || !(active&1) || active==1 || (active&~7U) || (active&~boot.poweredPipes))return r; uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0; + Display13TC::Boot tc[3]; + uint32_t power=0,ddiPower=0,auxPower=0,gate=0; + if(!AlderLakeN::value(boot,0x45404,power) || !AlderLakeN::value(boot,0x45454,ddiPower) || + !AlderLakeN::value(boot,0x45444,auxPower) || + !AlderLakeN::value(boot,Display13TC::Clock::Gate,gate))return r; // Every active firmware pipe must be a distinct selected linear GOP. // Pipe identity and physical DDI are independent. No foreign cursor/plane/DSB. for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo uint32_t ctl=0,trans=0,surface=0,live=0; if(!AlderLakeN::value(boot,0x70180+bank,ctl) || !AlderLakeN::value(boot,0x60400+bank,trans))return r; const auto port=Display13FBTopology::bootDDI(w,trans,true); - if(port>=3 || (used&(1U<>24)&7)==2) { - if(!AlderLakeN::dpSource(boot,p,port))return r; + if(port>=7 || (used&(1U<>24)&7)==2; + if(port>=3) { + tc[p]=Display13TC::Boot::capture(io,true,port,p,dp); + if(!tc[p].valid() || tc[p].power!=power || tc[p].ddiPower!=ddiPower)return r; + // A powered AUX controller must be idle before its PHY can be + // returned. An unpowered controller cannot have an active command. + const auto aux=0x64310+0x100*(port-3); + if((auxPower&(1U<<(2*port))) && + (!io.readable(aux) || (io.read(aux)&0x80000000U)))return r; + } + if(dp) { + if(!AlderLakeN::dpSource(boot,p,port,port>=3?&tc[p]:nullptr))return r; dpPipes|=1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo (conf&0xC0000000)!=0xC0000000 || !AlderLakeN::value(boot,0x7019C+bank,surface) || (surface&4095) || !AlderLakeN::value(boot,0x701AC+bank,live) || live!=surface)return r; const auto geometry=XELPGeometry::linear(ctl,stride,size,position,offset); - if(!XELPGeometry::timing(geometry,ht,vt) || uint64_t(surface)+geometry.bytes>memory.usable())return r; + if(!AlderLakeN::bootGeometry(boot,p,geometry,ht,vt) || uint64_t(surface)+geometry.bytes>memory.usable())return r; const auto clock=0x46140+4*p,buf=0x64000+0x100*port; if(!io.readable(clock) || !io.readable(buf) || !(io.read(buf)&0x80000000) || !io.readable(0x60404+bank) || io.read(0x60404+bank))return r; clocks[p]=io.read(clock); - if(clocks[p]==UINT32_MAX || (clocks[p]>>28)!=port+1)return r; + if(clocks[p]==UINT32_MAX || (clocks[p]>>28)!=port+(port>=3?3:1))return r; } for(unsigned i=0;i=3 && !tc[p].unchanged(io)) { + r.status=Status::Readback;return r; + } + uint32_t parked=0,current=0,stoppingTC=0; const auto peer=[&] { + // These are borrowed firmware requests. Keep them unchanged, including + // the stopped output's requests; native leases own subsequent changes. + if(io.read(0x45404)!=power || io.read(0x45454)!=ddiPower || io.read(0x45444)!=auxPower)return false; for(unsigned p=0;p<3;++p)if((active&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo for(unsigned p=1;p<3;++p)if((active&(1U<=0x60000+bank && reg<0x61000+bank) || - (reg>=0x70000+bank && reg<0x71000+bank) || reg==0x64000+0x100*ports[p] || + (reg>=0x70000+bank && reg<0x71000+bank) || + (reg>=0x68170+0x800*p && reg<=0x68280+0x800*p) || reg==0x64000+0x100*ports[p] || reg==0x46140+4*p)mutableReg=true; } if(reg==0x70040 || reg==0x71040 || reg==0x72040)continue; - if(!mutableReg && io.read(reg)!=pair.value) { - r.lastReg=reg;r.expected=pair.value;r.observed=io.read(reg);return false; + const auto expected=reg==Display13TC::Clock::Gate?gate:pair.value; + if(!mutableReg && io.read(reg)!=expected) { + r.lastReg=reg;r.expected=expected;r.observed=io.read(reg);return false; } } + for(unsigned p=0;p<3;++p)if((active&(1U<=3 && !(stoppingTC&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo AlderLakeN::value(boot,0x70008+bank,conf);AlderLakeN::value(boot,0x60400+bank,trans);buf=io.read(bufReg); r.stage=1;r.framesBefore=io.read(0x70040+bank); write(0x70180+bank,plane&~0x80000000U);write(0x7019C+bank,surface); - for(unsigned n=0;n<1000;++n) { - r.framesAfter=io.read(0x70040+bank); - if(r.framesAfter==UINT32_MAX || !peer()) {r.status=Status::PeerChanged;return r;} - if(uint32_t(r.framesAfter-r.framesBefore)>=2 && uint32_t(r.framesAfter-r.framesBefore)<0x10000000)break; - if(n==999) {r.status=Status::Timeout;return r;}io.delay(); - } + if(!XELPGOP::poll(io,peer,[&](uint32_t v){return uint32_t(v-r.framesBefore)>=2 && uint32_t(v-r.framesBefore)<0x10000000U;}, + 0x70040+bank,r.framesAfter,r.status,Status::PeerChanged,false))return r; if(!wait(0x70180+bank,0x80000000,0))return r; // Real frame latch, STOP_ACK and buffer idle precede DDB release. - // Shared PLL/PHY/PW and predecessor mapping remain held. + // Keep link resources until consumers stop. The TC PLL/ownership is + // then returned below; shared power and predecessor memory stay held. r.stage=2;write(0x70008+bank,conf&~0x80000000U); if(!wait(0x70008+bank,0xC0000000,0))return r; + // Detach only enabled scalers on the stopped secondary pipe. Keep the + // primary pipe's firmware scaling untouched until native ownership. + for(unsigned scaler=0;scaler<2;++scaler) { + const auto base=0x68170+0x800*p+0x100*scaler; + uint32_t ctl=0; + if(AlderLakeN::value(boot,base+0x10,ctl) && (ctl&0x80000000U)) { + write(base+0x10,0);write(base,0);write(base+4,0); + if(!wait(base+0x10,UINT32_MAX,0) || !wait(base,UINT32_MAX,0) || !wait(base+4,UINT32_MAX,0))return r; + } + } r.stage=3;write(0x60404+bank,0);write(0x60400+bank,trans&~(0x80000000U|(15U<<27)|(7U<<24))); if(!wait(0x60400+bank,0x80000000,0))return r; - r.stage=4;write(bufReg,buf&~0x80000000U); - // Display12/13 disable_ddi_buf: buffer off, transport off, then idle. - // Stop only this transcoder; preserve the adopted GOP's M/N and link. - if(dpPipes&(1U<=3) { + // The live GOP certificate authorized this bootstrap transaction. + // Reuse the runtime ownership and PLL operations after consumers + // stop. Leaving the firmware ownership bit set would make the + // later native Mode::connect reject this now-cold connector. + stoppingTC|=1U<(&io) && object==reinterpret_cast(&boot) && + d==boot.port.ddi && powerHeld(); + } + bool quiescent(unsigned d) { + const auto bank=boot.pipe*0x1000; + return d==boot.port.ddi && !(read(0x70008+bank)&0xC0000000U) && + !(read(0x60400+bank)&0x80000000U) && !(read(0x60540+bank)&0x80000000U) && + !(read(0x46140+4*boot.pipe)&0xF0000000U) && + (read(boot.port.buffer)&0x80000080U)==0x80; + } + bool inheritedVerified(uintptr_t owner,uintptr_t object,unsigned d) { + return powerHeld(owner,object,d) && quiescent(d); + } + bool owned(unsigned d){return d==boot.port.ddi && ownership.active;} + bool quiescent(uintptr_t owner,uintptr_t object,unsigned d) { + return powerHeld(owner,object,d) && quiescent(d) && + !(read(boot.port.pll)&0xCC000000U) && !(read(boot.port.clock)&0xF0000000U) && + (read(Display13TC::Clock::Gate)&Display13TC::Clock::gateBit(boot.port.tc)); + } + } access{io,tc[p],r,peer,write}; + const auto owner=reinterpret_cast(&io),object=reinterpret_cast(&tc[p]); + if(access.ownership.acquire(access,owner,object,ports[p],true).status!=Display13TC::Status::Complete || + !Display13TC::Clock::disable(access,tc[p].port) || + access.ownership.release(access,owner,object,ports[p]).status!=Display13TC::Status::Complete) { + if(r.status!=Status::PeerChanged)r.status=Status::Readback;return r; + } + } r.stage=6;write(0x7027C+bank,0);write(0x7037C+bank,0);write(0x7017C+bank,0); if(!wait(0x7027C+bank,UINT32_MAX,0) || !wait(0x7037C+bank,UINT32_MAX,0) || !wait(0x7017C+bank,UINT32_MAX,0))return r; parked|=1U<>3)<<24);} +// Linux GEN11_DE_HPD: TC1..6 at bits16..21, TBT1..6 at bits0..5. +// Apply the same connector exclusion to both PCH and north HPD banks. +// Reference: samranahmad09/WhateverXe-RaptorLakeP, 2195924, this header. +constexpr uint32_t HPDBank=0x44478; +constexpr uint32_t hpdExcluded(uint32_t excludedPCH) { + const uint32_t ports=(excludedPCH>>24)&0x3FU;return ports|(ports<<16); +} inline uint32_t ddiMask(const State &s,uint32_t value) { uint32_t m=0;for(unsigned p=0;p<3;++p)if(s.ports&(1U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t if(s.faulted)return Status::Faulted; if(!ports || (ports&~0x7FU) || (s.begun && ports!=s.ports))return fail(io,s,Status::Topology); s.ports=ports; - if((excludedPCH&pchOwned(s)) || (excludedPCH&~PCHTypeC) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology); + if((excludedPCH&pchOwned(s)) || (excludedPCH&~(PCHTypeC|PCHCombo)) || (sharedGT && sharedGT!=NativeGT) || !expectedPipes || (expectedPipes&~7U))return fail(io,s,Status::Topology); if(!available(io) || ((ports&0x78) && !io.readable(TCControl)))return Status::Unavailable; // Native repeated enable is idempotent. Do not drop a pending flip/DSB // completion or undo hwSetInterruptForTransaction's VBL mask. @@ -142,9 +149,15 @@ template Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t configured|=1U< Status enable(IO &io,State &s,uint32_t excludedPCH=0,uint32_t if((io.read(0xC400C)&excludedPCH) || (~io.read(0xC4004)&excludedPCH)) return fail(io,s,Status::Readback); } + if(excludedHPD) { + // Keep pending north status and every unrelated source intact. + io.write(HPDBank-4,io.read(HPDBank-4)|excludedHPD); + io.write(HPDBank+4,io.read(HPDBank+4)&~excludedHPD); + if((io.read(HPDBank+4)&excludedHPD) || (~io.read(HPDBank-4)&excludedHPD)) + return fail(io,s,Status::Readback); + } s.excludedPCH=excludedPCH; for(unsigned p=0;p<3;++p)if(configured&(1U< #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_geometry.hpp" +#include "kern_display13_tc.hpp" +#include "kern_alderlake_n.hpp" #include #include @@ -326,12 +328,21 @@ template SurfaceReadout inspectSurface(IO &io,const void *controller,s template Readout inspect(IO &io,const void *controller,size_t controllerBytes, const void *framebuffer,size_t framebufferBytes, uint64_t apertureBytes,const XELPMemory::Layout &memory,bool bootScanout=true,uintptr_t expectedOwner=0, - unsigned expectedDDI=1) { + unsigned expectedDDI=1,const Display13TC::Boot *tc=nullptr, + const AlderLakeN::DisplayReadout *dp=nullptr) { Readout r; if (!associations(controller,controllerBytes,framebuffer,framebufferBytes,expectedOwner)) { r.status=Status::Association;return r; } - if(bootScanout && expectedDDI>=3) {r.status=Status::DisplayState;return r;} + if(bootScanout && (expectedDDI>=7 || (expectedDDI>=3 && + (!tc || !tc->valid() || tc->port.ddi!=expectedDDI || tc->pipe!=0 || + !(tc->buffer&64) || (!dp && tc->ownershipOptional) || !tc->unchanged(io))))) { + r.status=Status::DisplayState;return r; + } + if(bootScanout && dp && (dp->activePipes!=1 || + !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))) { + r.status=Status::DisplayState;return r; + } r.pool=pool(controller);const auto &p=r.pool; if (field(framebuffer,0x4288)!=0 || !field(controller,0xDF5) || field(controller,0xDF5)>3 || !field(controller,0xDF4) || @@ -362,7 +373,7 @@ template Readout inspect(IO &io,const void *controller,size_t controll r.surface=io.read(0x7019C);r.control=io.read(0x70180);r.stride=io.read(0x70188);r.size=io.read(0x70190); const auto geometry=XELPGeometry::linear(r.control,r.stride,r.size,position,offset); if (!(dpt&(1U<<30)) || !(trans&(1U<<31)) || ((trans>>27)&15)!=expectedDDI+1 || - ((trans>>24)&7)>1 || ((trans>>20)&7) || (conf&0xC0000000)!=0xC0000000 || + (dp ? ((trans>>24)&7)!=2 : ((trans>>24)&7)>1) || ((trans>>20)&7) || (conf&0xC0000000)!=0xC0000000 || !geometry.valid || position || offset || (r.surface&4095) || r.surface!=io.read(0x701AC) || r.surface Readout inspect(IO &io,const void *controller,size_t controll (bootScanout && (io.read(0x42084)!=dpt || io.read(0x60400)!=trans || io.read(0x70008)!=conf || io.read(0x7018C)!=position || io.read(0x701A4)!=offset || io.read(0x7019C)!=r.surface || io.read(0x701AC)!=r.surface || - io.read(0x70180)!=r.control || io.read(0x70188)!=r.stride || io.read(0x70190)!=r.size))) { + io.read(0x70180)!=r.control || io.read(0x70188)!=r.stride || io.read(0x70190)!=r.size || + (expectedDDI>=3 && !tc->unchanged(io)) || + (dp && !AlderLakeN::dpSourceUnchanged(io,*dp,0,expectedDDI,0,tc))))) { r.status=Status::StateChanged;return r; } r.status=Status::Complete;return r; diff --git a/WhateverXe/kern_display13_fb_topology.hpp b/WhateverXe/kern_display13_fb_topology.hpp index b27cb2de..19157990 100644 --- a/WhateverXe/kern_display13_fb_topology.hpp +++ b/WhateverXe/kern_display13_fb_topology.hpp @@ -1,4 +1,4 @@ -// Explicit Combo A/B/C wiring. Board wiring and native backend +// Explicit Combo/TC wiring. Board wiring and native backend // qualification are separate: describing a port does not attach unsupported resources. #ifndef KERN_DISPLAY13_FB_TOPOLOGY_HPP #define KERN_DISPLAY13_FB_TOPOLOGY_HPP @@ -15,7 +15,16 @@ inline bool selectedWiring(const Display13OutputWiring &w) { inline bool adlnWiring(const Display13OutputWiring &w) {return selectedWiring(w) && !(w.selected()&~3U);} // DP topology is composable, but runtime admission stays closed until its // complete signal/training/bootstrap operation contract is installed. -inline bool supportedWiring(const Display13OutputWiring &w) {return adlnWiring(w) && !w.dp;} +inline bool supportedWiring(const Display13OutputWiring &w,bool fixedTC=false) { + // Fixed HDMI is composable across the physical Combo A/B and TC1..4. + // TC provider capability, legacy mode and clock hooks are checked separately. + return !(w.dp&~0x78U) && w.described() && !(w.selected()&~(fixedTC ? Display13OutputWiring::Physical : 3U)); +} +// A TC route uses the same validated handoff regardless of the GT family. +// Keep the established Combo-only ADL-N startup ordering unchanged. +inline bool deferredBootPrepare(const Display13OutputWiring &w,bool mobile) { + return mobile || (w.selected()&0x78); +} inline bool multiOutputWiring(const Display13OutputWiring &w) { return selectedWiring(w) && (w.selected()&(w.selected()-1)); } @@ -24,7 +33,7 @@ inline bool singleOutputWiring(const Display13OutputWiring &w) { } inline uint32_t singleDDI(const Display13OutputWiring &w) { if(!singleOutputWiring(w))return UINT32_MAX; - for(unsigned d=0;d<3;++d)if(w.selected()&(1U<>27)&15; - if(selector<1 || selector>3)return UINT32_MAX; + if(selector<1 || selector>7)return UINT32_MAX; const auto port=selector-1; const auto mode=(trans>>24)&7; - // Explicit source inspection only; runtime adoption keeps the TMDS default. - if(dpSource && mode==2 && port<2 && (w.dp&(1U<=3) && (w.dp&(1U<1)return UINT32_MAX; return (w.hdmi&(1U<(config)))return true; return false; } @@ -82,29 +96,25 @@ inline bool selected(const void *platform,const void *config,const Display13Outp // core below consumes only validated port identities, independent of transport. inline XELP::NativePorts boundPorts(const void *platform,const Display13OutputWiring &w) { if(!platform || !selectedWiring(w))return {}; - for(unsigned d=0;d<3;++d)if((w.selected()&(1U<(platform)+0x20+24*d,w))return {}; return {platform,w.selected()}; } -// Same exact donor CPU defect corrected for MI646: the mode validator -// asks a TMDS connector for DP lanes. Keep its remaining HDMI constraints. -inline bool skipDPLanes(const void *platform,const void *config,const Display13OutputWiring &w, - bool exactCaller,bool native,uint32_t available,uint32_t lanes) { - return XELPDonor::skipHDMIDPLanes(selected(platform,config,w) && w.matchesHDMI(static_cast(config)),exactCaller,native,available,lanes); -} // ICP/TGP SDE bits24..29 are TC1..6 hotplug, not legacy GMBUS bit24. -// Authorize masking only from the stable native connector policy: no TC -// record can be enabled or have panel/connection flags on a Combo route. -inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const Display13OutputWiring &w) { +// Combo A..C hotplug uses bits16..18. Authorize exclusions only after +// every unselected native record is disabled and has no connection flags. +// Other PCH sources (including DDI D) remain outside this policy. +inline uint32_t excludedHotplug(const void *platform,uint32_t bytes,const Display13OutputWiring &w) { if(!platform || bytes(platform); if(p[0x12]!=Ports)return 0; - for(unsigned d=0;d<3;++d)if((w.selected()&(1U<(p+0x20+24*d); - if(config[0]!=d || (d>=3 && (config[4]!=1 || config[5])))return 0; + if(config[0]!=d || (!(w.selected()&(1U<>3)<<24)) | + ((7U & ~w.selected())<<16); } } #endif diff --git a/WhateverXe/kern_display13_firmware.hpp b/WhateverXe/kern_display13_firmware.hpp new file mode 100644 index 00000000..abbb98f1 --- /dev/null +++ b/WhateverXe/kern_display13_firmware.hpp @@ -0,0 +1,40 @@ +#ifndef KERN_DISPLAY13_FIRMWARE_HPP +#define KERN_DISPLAY13_FIRMWARE_HPP +#include "kern_vbt.hpp" +#include "kern_display13_output_wiring.hpp" +namespace XELP { namespace VBT { + // ADLP/ADLN VBT DDC1/2 => Combo pins1/2, DDC3..6 => TC pins9..12. + // Capability bits are not live modes. HDMI-only records may advertise + // USB-C/TBT capability; the TC runtime must separately prove legacy mode. + inline Display13OutputWiring display13HDMI(const Ports &ports,uint32_t backend) { + Display13OutputWiring w;w.valid=true; + if(!ports.valid())return w; + uint32_t used=0; + for(unsigned d=0;d<7;++d) { + if(!(ports.present&backend&Display13OutputWiring::Physical&(1U<=3 && selector<=6 ? selector+6 : selector<=2 ? selector : 0; + if(!pin || (d<3 ? pin>2 : pin<9) || (used&(1U<3 || (used&(1U<3) : (pin<9 || pin>12))return false; + if(used&(1U<=3?1U:0U) && + c[4]==0x800 && (c[5]&0xB)==2; + } + bool usesDDC(uint32_t bus) const { + if(!described())return false; + for(unsigned d=0;d=3)return false; - if(hdmi&(1U<=Ports)return false; + if(hdmi&(1U<(platform);if(p[0x12]!=Ports)return false; + for(unsigned d=0;d(p+0x20+24*d); + const uint32_t panel[]={0,0,0,0,2,0x18}; + if(!d && !memcmp(c,panel,24))continue; + if(c[0]!=d || c[1]!=(d<3?d+1:d+6) || c[2]!=1 || c[3]!=(d<3?0U:1U) || + c[4]!=0x400 || c[5]!=0x201)return false; + } + for(unsigned d=0;d(p+0x20+24*d); + const bool panel=!d && !c[2]; + c[1]=ddc[d];c[2]=2;c[4]=0x800;c[5]=panel?0x202:(c[5]&~9U)|2U; + } + return TGLComboDP::apply(dp&3,platform,bytes); } }; #endif diff --git a/WhateverXe/kern_display13_power.hpp b/WhateverXe/kern_display13_power.hpp index 2685b86a..d036f4d9 100644 --- a/WhateverXe/kern_display13_power.hpp +++ b/WhateverXe/kern_display13_power.hpp @@ -34,9 +34,10 @@ constexpr uint32_t fuse(unsigned d) { return 1U << (26 - index(d)); } // PW2 is not a prerequisite of PW_A in the XE_LPD power-domain map. inline uint32_t dependencies(uint32_t domains) { if (domains & ~All) return 0; - // XE_LPD TC legacy AUX uses PW2 even while its scanout is pipe A. - constexpr uint32_t tcAuxMask=bit(Domain::AUXTC1)|bit(Domain::AUXTC2)|bit(Domain::AUXTC3)|bit(Domain::AUXTC4); - if (domains & (bit(Domain::PipeB) | bit(Domain::PipeC) | tcAuxMask)) domains |= bit(Domain::PW2); + // XE_LPD PW2 includes both TC DDI lanes and AUX, even on pipe A. + // Releasing an AUX transaction must not gate an active TMDS main link. + constexpr uint32_t tcMask=All&~((1U<<11)-1); + if (domains & (bit(Domain::PipeB) | bit(Domain::PipeC) | tcMask)) domains |= bit(Domain::PW2); if (domains) domains |= bit(Domain::PW1); return domains; } @@ -48,6 +49,7 @@ enum class Status : uint32_t { struct Result { Status status {Status::NotReady}; uint32_t domain {Count}, writes {}, rollbackFailed {}, forcedOn {}; + bool isolated {}; // Failed new TC leaf, rollback and retained scopes proven. }; struct State { bool ready {}, faulted {}, suspended {}; @@ -155,7 +157,9 @@ template Status waitTCAuxHealth(IO &io,unsigned d) { } } -template Result acquire(IO &io, State &s, Lease &lease, uint32_t domains) { +// Isolation is opt-in for a caller that can propagate a failed acquisition. +// Native void getPower wrappers must retain controller-wide failure behavior. +template Result acquire(IO &io, State &s, Lease &lease, uint32_t domains,bool isolateTC=false) { Result r; if (!s.ready || s.faulted || s.suspended) return r; if (lease.owner || !domains || (domains & ~s.allowed)) { r.status = Status::InvalidDomains; return r; } @@ -172,6 +176,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai if ((domains & (1U << d)) && s.refs[d] == UINT32_MAX) { r.status = Status::Overflow; r.domain = d; return r; } + const uint32_t previousOwned=s.owned; uint32_t added = 0; r.status = Status::Complete; for (unsigned d = 0; d < Count; ++d) { @@ -210,10 +215,13 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai r.domain = Count; return r; } - // Failure is latched; no caller may continue using partially acquired - // domains. Roll back only requests made by this transaction, in reverse - // dependency order. Stop at a failed release to retain its prerequisites. - s.faulted = true; + // A newly requested, unshared TC leaf may fail without invalidating other + // outputs. Only isolate bounded ack/health timeouts after proving rollback + // and every previously held scope. Lost/borrowed/shared resources, unknown + // MMIO and failed rollback still poison the controller. + const bool local=isolateTC && (tcDDI(r.domain)||tcAux(r.domain)) && !s.refs[r.domain] && + (added&(1U< 0;) { if (!(added & (1U << d))) continue; if (clear(io, s, d, r) != Status::Complete) { @@ -221,6 +229,11 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai break; } } + uint32_t retained=0; + for(unsigned d=0;d bool ddiHeld(IO &io,unsigned port) const { return retainsDDI(port) && held(io,state,ddiDomain(port)); } - template bool outputHeld(IO &io,unsigned port,unsigned pipe) const { - if(pipe>=3 || !retainsDDI(port))return false; + template bool pipeHeld(IO &io,unsigned pipe) const { + if(pipe>=3)return false; const auto domain=bit(static_cast(static_cast(Domain::PipeA)+pipe)); bool retained=false; for(unsigned pg=0;pg<4;++pg)if(pgRefs[pg] && pgLease[pg].owner==&state && pgLease[pg].domains==dependencies(nativeScope(pg+1)) && (pgLease[pg].domains&domain))retained=true; - return retained && held(io,state,domain|ddiDomain(port)); + return retained && held(io,state,domain); + } + template bool outputHeld(IO &io,unsigned port,unsigned pipe) const { + return pipeHeld(io,pipe) && ddiHeld(io,port); } template Result begin(IO &io, unsigned port=1, uint32_t ports=2) { diff --git a/WhateverXe/kern_display13_retirement.hpp b/WhateverXe/kern_display13_retirement.hpp new file mode 100644 index 00000000..a6bcb17e --- /dev/null +++ b/WhateverXe/kern_display13_retirement.hpp @@ -0,0 +1,25 @@ +// Exact c004fc1a Display13 retirement boundary; native startup stays intact. +#ifndef KERN_DISPLAY13_RETIREMENT_HPP +#define KERN_DISPLAY13_RETIREMENT_HPP +#include +#include +namespace Display13Retirement { +constexpr uint32_t Loop=0x67ebd,Continue=0x67eec,Abort=0x67f85; +constexpr uint8_t Before[]={0x48,0xff,0x5,0x64,0xc6,0x7,0x0,0x83,0xbb,0xe8,0x49,0x0,0x0,0x0,0x74,0x1f,0x48,0xff,0x5,0xf4,0xc5,0x7,0x0,0x48,0xff,0x5,0x25,0xc8,0x7,0x0,0x48,0x8b,0xb3,0xf0,0x49,0x0,0x0,0x48,0x89,0xdf,0xe8,0xc4,0xb,0x0,0x0,0xeb,0xd1}; +// AGDC's void free call now returns status in EAX. Keep its native argument +// validation, call, and epilogue; remove only the unconditional success write. +constexpr uint32_t ReleaseCall=0x6ba3a,ReleaseResult=0x6ba3f; +constexpr uint8_t ReleaseBefore[]={0xe8,0x4f,0xc4,0xff,0xff,0x31,0xc0}; +constexpr uint8_t ReleaseAfter[]={0xe8,0x4f,0xc4,0xff,0xff,0x90,0x90}; +inline void patch(uint8_t (&out)[sizeof(Before)],uintptr_t handler) { + memset(out,0x90,sizeof(out)); + // RBX is the retained FB. Keep native StopTransactions/disableDisplay and + // post-retirement cleanup; refusal exits before scanout/logical cleanup. + const uint8_t code[]={0x48,0x89,0xdf,0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0, + 0x85,0xc0,0x0f,0x85,0,0,0,0,0xe9,0,0,0,0}; + memcpy(out,code,sizeof(code));memcpy(out+5,&handler,8); + const int32_t abortDelta=Abort-(Loop+23),doneDelta=Continue-(Loop+28); + memcpy(out+19,&abortDelta,4);memcpy(out+24,&doneDelta,4); +} +} +#endif diff --git a/WhateverXe/kern_display13_tc.hpp b/WhateverXe/kern_display13_tc.hpp index f8dd5a99..c30d1ac0 100644 --- a/WhateverXe/kern_display13_tc.hpp +++ b/WhateverXe/kern_display13_tc.hpp @@ -1,4 +1,4 @@ -// ADL-N/Twin Lake fixed TC HDMI/DP coordinates and PHY ownership. +// ADL-P/N fixed TC HDMI/DP coordinates and PHY ownership. // Linux v6.12 intel_tc.c/i915_reg.h; T8 Pro TC1 Linux values are an // independent live fixture. These are not TGL FIA ownership registers. #ifndef KERN_DISPLAY13_TC_HPP @@ -24,8 +24,55 @@ struct Port { }; enum class Status : uint32_t {Invalid,NoPower,NeedsAdoption,InUse,Unavailable,UnsupportedMode,NotReady,Readback,Complete}; struct Result {Status status {Status::Invalid};uint32_t before {},after {},writes {},elapsedUs {};}; -// Caller keeps lifecycle/port exclusion, live mapping, and separate DDI, -// lane/PW2 and qualified legacy AUX leases for the entire transaction. +// Read-only physical certificate for an inherited fixed link. This never takes +// ownership or programs the PLL. Callers must select the physical Display13 +// backend before reading TCSS; TGL uses a different ownership mechanism. +// Protocol/source, sink and PLL frequency are separate certificates. An +// enabled/locked PLL alone does not prove a valid DP link or authorize adoption. +struct Boot { + Port port;unsigned pipe {3}; + uint32_t power {},ddiPower {},ready {},buffer {},clock {},pll {},transClock {}; + // Linux intel_tc_port_init_mode reconnects an enabled fixed legacy port + // left unowned by firmware. Only explicit DP source validation may request + // this witness; the later Mode acquisition must still take ownership. + bool ownershipOptional {}; + bool valid() const { + const auto powered=pipe<3 ? 5U|(1U<<(10+2*pipe)) : 0U; + return port.ddi>=3 && port.ddi<=6 && pipe<3 && + power!=UINT32_MAX && (power&powered)==powered && ddiPower!=UINT32_MAX && + (ddiPower&(1U<<(2*port.ddi))) && ready!=UINT32_MAX && (ready&7)==4 && + buffer!=UINT32_MAX && (buffer&0x80000080U)==0x80000000U && (ownershipOptional || (buffer&64)) && + clock!=UINT32_MAX && (clock>>28)==8 && pll!=UINT32_MAX && + (pll&0xCC000000U)==0xCC000000U && transClock!=UINT32_MAX && (transClock>>28)==port.ddi+3; + } + template static Boot capture(IO &io,bool admitted,unsigned ddi,unsigned pipe,bool ownershipOptional=false) { + Boot b; + if(!admitted || pipe>=3 || !Port::resolve(ddi,b.port))return b; + b.pipe=pipe;b.ownershipOptional=ownershipOptional; + if(!io.readable(0x45404) || !io.readable(0x45454))return {}; + b.power=io.read(0x45404);b.ddiPower=io.read(0x45454); + const auto powered=5U|(1U<<(10+2*pipe)); + if(b.power==UINT32_MAX || (b.power&powered)!=powered || b.ddiPower==UINT32_MAX || + !(b.ddiPower&(1U<<(2*ddi))))return {}; + const uint32_t regs[]={b.port.ready,b.port.buffer,b.port.clock,b.port.pll,0x46140+4*pipe}; + uint32_t *values[]={&b.ready,&b.buffer,&b.clock,&b.pll,&b.transClock}; + for(unsigned i=0;i<5;++i) { + if(!io.readable(regs[i]))return {}; + *values[i]=io.read(regs[i]); + } + if(io.read(0x45404)!=b.power || io.read(0x45454)!=b.ddiPower || !b.valid())return {}; + return b; + } + template bool unchanged(IO &io) const { + if(!valid())return false; + const auto now=capture(io,true,port.ddi,pipe,ownershipOptional); + return now.valid() && now.power==power && now.ddiPower==ddiPower && now.ready==ready && + now.buffer==buffer && now.clock==clock && now.pll==pll && now.transClock==transClock; + } +}; +// Caller keeps lifecycle/port exclusion, live mapping and lane/PW2 access. +// ADLP ownership precedes AUX cold blocking and DDI IO acquisition. Requiring +// those later scopes here creates an inverted dependency on a cold port. // A matching owner identity is insufficient without that power witness. struct Ownership { uintptr_t owner {},identity {};Port port;bool active {}; @@ -35,14 +82,9 @@ struct Ownership { if(owner && (owner!=who || identity!=object || port.ddi!=ddi)){r.status=Status::InUse;return r;} if(!io.powerHeld(who,object,ddi)){r.status=Status::NoPower;return r;} if(!io.readable(p.ready) || !io.readable(p.buffer)){r.status=Status::Unavailable;return r;} - for(;;) { - const auto ready=io.read(p.ready); - if(ready==UINT32_MAX){r.status=Status::Unavailable;return r;} - if(ready&3){r.status=Status::UnsupportedMode;return r;} // DP-alt/TBT excluded here. - if(ready&4)break; - if(r.elapsedUs==500000){r.status=Status::NotReady;return r;} - io.delayUs(100);r.elapsedUs+=100; - } + const auto initialReady=io.read(p.ready); + if(initialReady==UINT32_MAX){r.status=Status::Unavailable;return r;} + if(initialReady&3){r.status=Status::UnsupportedMode;return r;} r.before=io.read(p.buffer);if(r.before==UINT32_MAX){r.status=Status::Unavailable;return r;} if(owner && active && !(r.before&64)) {r.status=Status::Readback;return r;} if(!owner && ((r.before&0x80000040U)!=0) && (!adopt || !io.inheritedVerified(who,object,ddi))) { @@ -55,6 +97,16 @@ struct Ownership { r.after=io.read(p.buffer); if(r.after==UINT32_MAX){r.status=Status::Unavailable;return r;} if(r.after!=(r.before|64)){r.status=Status::Readback;return r;} + // Linux adlp_tc_phy_connect takes ownership before testing ready. + // Retain the identity on timeout so cleanup can release only our bit. + for(;;) { + const auto ready=io.read(p.ready); + if(ready==UINT32_MAX){r.status=Status::Unavailable;return r;} + if(ready&3){r.status=Status::UnsupportedMode;return r;} + if(ready&4)break; + if(r.elapsedUs==500000){r.status=Status::NotReady;return r;} + io.delayUs(100);r.elapsedUs+=100; + } active=true;r.status=Status::Complete;return r; } template Result release(IO &io,uintptr_t who,uintptr_t object,unsigned ddi) { diff --git a/WhateverXe/kern_display13_tc_clock.hpp b/WhateverXe/kern_display13_tc_clock.hpp new file mode 100644 index 00000000..d97c8e04 --- /dev/null +++ b/WhateverXe/kern_display13_tc_clock.hpp @@ -0,0 +1,186 @@ +// Fixed TMDS on ADLP Dekel PHY. Equations/register fields checked against +// Intel display definitions and Linux v6.12; independent integer implementation. +#ifndef KERN_DISPLAY13_TC_CLOCK_HPP +#define KERN_DISPLAY13_TC_CLOCK_HPP +#include "kern_display13_tc.hpp" +namespace Display13TC { +struct TMDSClock { + struct Word {uint32_t reg,mask,value;}; + uint32_t rate {},reference {},dco {},hs {},ds {}; + Word words[8] {}; + bool valid() const {return rate && reference && dco && hs && ds;} + static TMDSClock calculate(uint32_t rate,uint32_t reference) { + TMDSClock p; + if(rate<25000 || rate>600000 || (reference!=19200 && reference!=24000 && reference!=38400))return p; + const uint32_t high[]={7,5,3,2}; + for(auto h:high) { + for(unsigned d=10;d;--d) { + const auto oscillator=uint64_t(rate)*5*h*d; + if(oscillator<7992000 || oscillator>10000000)continue; + const auto divider=reference*2,integer=uint32_t(oscillator/divider),remainder=uint32_t(oscillator%divider); + if(!integer || integer>255)return {}; + const auto fraction=uint32_t((uint64_t(remainder)<<22)/divider); + const uint32_t integral=oscillator>=9000000?10:8,proportional=integral/2; + const auto target=uint32_t((2000000000ULL/(132*reference)+5)/10); + const auto gain=remainder?uint32_t(200000000ULL/(oscillator*3/10)):0; + if(target>255 || gain>255)return {}; + const uint32_t ratio=h==7?3:h==5?2:h==3?1:0; + p.rate=rate;p.reference=reference;p.dco=uint32_t(oscillator);p.hs=h;p.ds=d; + p.words[0]={0x212c,0x700,0x100}; + p.words[1]={0x20d8,0xff00,0x500}; + p.words[2]={0x20d4,0x1ff00,0x10000|(d>=2?0x4000U:0U)|(ratio<<12)|(d<<8)}; + // Preserve firmware AFC startup unless VBT supplies an explicit policy. + p.words[3]={0x2200,0x1fffff,(integral<<16)|(proportional<<12)|0x200|integer}; + p.words[4]={0x2204,0x1f00ff,((reference==24000?25U:28U)<<16)|target}; + p.words[5]={0x2210,0xe0ff3a00,(reference==38400?0x40000000U:0x20000000U)|0x2000}; + p.words[6]={0x2214,0x7fffff00,(fraction?0x40000000U:0U)|(fraction<<8)}; + p.words[7]={0x2218,0xffff,gain}; + return p; + } + } + return {}; + } + // Exact donor ClockParams is DCO integer/fraction15, P/Q/K. Its + // calculatePllDivisors uses 19.2 MHz for both DSSM codes1 and2, and + // 24 MHz for code0. Recover TMDS rate before encoding the real Dekel PLL. + static uint32_t nativeRate(const uint32_t *params,uint32_t refCode) { + if(!params || refCode>2 || !params[0] || params[0]>1023 || params[1]>32767 || + !params[3] || params[3]>255 || !params[4] || params[4]>3)return 0; + const auto p=params[2];if(p!=2 && p!=3 && p!=5 && p!=7)return 0; + const uint64_t denominator=uint64_t(5)*p*params[3]*params[4]*32768; + const auto rate=(uint64_t(refCode?19200:24000)*(uint64_t(params[0])*32768+params[1])+denominator/2)/denominator; + return rate>=25000 && rate<=600000 ? uint32_t(rate) : 0; + } + static uint32_t frequency(uint32_t reference,uint32_t div0,uint32_t bias,uint32_t hsctl) { + if((reference!=19200 && reference!=24000 && reference!=38400) || + div0==UINT32_MAX || bias==UINT32_MAX || hsctl==UINT32_MAX)return 0; + const uint32_t high[]={2,3,5,7};const auto h=high[(hsctl>>12)&3],d=(hsctl>>8)&15; + const auto pre=(div0>>8)&15,integer=div0&255,fraction=(bias&0x40000000)?((bias>>8)&0x3fffff):0; + if(!d || !pre || !integer)return 0; + return uint32_t((uint64_t(reference)*pre*((uint64_t(integer)<<22)+fraction)+(uint64_t(5*h*d)<<21))/(uint64_t(5*h*d)<<22)); + } +}; +// One masked bank transaction. The caller holds the native port/lifecycle +// mutex and all leases; HIP has its own IRQ-safe lock shared across ports. +struct PHY { + template static bool access(IO &io,const Port &p,uint32_t internal,uint32_t mask,uint32_t &value,bool write) { + Port checked;if(!Port::resolve(p.ddi,checked) || p.phy!=checked.phy || p.hip!=checked.hip || p.shift!=checked.shift || + internal>0x2fff || (internal&3) || !io.powerHeld() || !io.readable(p.hip) || !io.readable(p.phyRegister(internal)))return false; + const auto irq=io.lock();const auto prior=io.read(p.hip);bool changed=false,ok=prior!=UINT32_MAX; + if(ok) { + const auto bank=p.bank(prior,internal);changed=bank!=prior; + if(changed)io.write(p.hip,bank); + ok=io.read(p.hip)==bank; + if(ok) { + const auto reg=p.phyRegister(internal),old=io.read(reg);ok=old!=UINT32_MAX; + if(ok && write) {const auto desired=(old&~mask)|(value&mask);io.write(reg,desired);ok=io.read(reg)==desired;value=desired;} + else if(ok)value=old; + } + if(changed) {io.write(p.hip,prior);if(io.read(p.hip)!=prior){ok=false;io.bankFault();}} + } + io.unlock(irq);return ok; + } +}; +struct TMDSSignal { + // Public Intel HDMI preset coefficients: vswing/preshoot/de-emphasis. + // ADLP lane0 uses LOADGENSELECT 0/2, lane1 3/3. This is independent of + // the electrical preset and must not inherit DP's 0/0 settings. + template static bool program(IO &io,const Port &p,uint32_t preset) { + if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false; + if(preset==UINT32_MAX)preset=9; + if(preset>=10)return false; + const uint32_t swing[]={7,6,4,2,0,0,0,0,0,0}; + const uint32_t emphasis[]={0,0,0,0,0,5,6,7,8,10}; + for(unsigned lane=0;lane<2;++lane) { + const auto bank=0x1000*lane; + // Fixed legacy HDMI occupies both lane pairs (X2 in each bank). + uint32_t mode=0x80; + if(!PHY::access(io,p,bank+0xa0,0xc0,mode,true))return false; + uint32_t wake=0; + if(!PHY::access(io,p,bank+0xd00,UINT32_MAX,wake,true))return false; + for(unsigned tx=0;tx<2;++tx) { + uint32_t value=swing[preset]|(emphasis[preset]<<8); + if(!PHY::access(io,p,bank+0x2c0+4*tx,0x3ff07,value,true))return false; + } + uint32_t load=lane?0x78:0x40; + if(!PHY::access(io,p,bank+0x2c8,0x7c,load,true))return false; + } + return true; + } +}; + +struct Clock { + static constexpr uint32_t Gate=0x164280,Enable=1U<<31,Locked=1U<<30,Power=1U<<27,Powered=1U<<26; + static uint32_t gateBit(unsigned tc) {return tc<3?1U<<(12+tc):tc==3?1U<<21:0;} + // Runtime IO validates the immutable owner/config/path bindings. A ready + // register alone never authorizes touching an active/foreign clock. + template static bool preflight(IO &io,const Port &p) { + Port checked; + if(!Port::resolve(p.ddi,checked) || p.pll!=checked.pll || p.clock!=checked.clock || p.ready!=checked.ready || + p.buffer!=checked.buffer || p.tc!=checked.tc || p.phy!=checked.phy || p.hip!=checked.hip || p.shift!=checked.shift || + !io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi))return false; + const uint32_t registers[]={p.pll,p.clock,p.ready,p.buffer,Gate}; + for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false; + return (io.read(p.ready)&7)==4 && (io.read(p.buffer)&0x80000040)==64; + } + template static bool rmw(IO &io,uint32_t reg,uint32_t mask,uint32_t value) { + const auto irq=io.lock();const auto old=io.read(reg); + bool ok=old!=UINT32_MAX; + if(ok) {const auto desired=(old&~mask)|(value&mask);io.write(reg,desired);ok=(io.read(reg)&mask)==(desired&mask);} + io.unlock(irq);return ok; + } + template static bool wait(IO &io,uint32_t reg,uint32_t mask,uint32_t expected) { + for(unsigned elapsed=0;;elapsed+=10) { + const auto v=io.read(reg);if(v==UINT32_MAX)return false; + if((v&mask)==expected)return true; + if(elapsed==1000)return false;io.delayUs(10); + } + } + template static bool disable(IO &io,const Port &p) { + if(!preflight(io,p))return false; + return rmw(io,Gate,gateBit(p.tc),gateBit(p.tc)) && rmw(io,p.clock,0xf0000000,0) && + rmw(io,p.pll,Enable,0) && wait(io,p.pll,Locked,0) && + rmw(io,p.pll,Power,0) && wait(io,p.pll,Powered,0); + } + template static bool enable(IO &io,const Port &p,const TMDSClock &plan) { + if(!plan.valid() || !preflight(io,p))return false; + // No reprogramming of running PLLs. Preserve an enabled GOP until + // its native path has explicitly quiesced and disabled the clock. + if((io.read(p.pll)&(Enable|Locked)) || !(io.read(Gate)&gateBit(p.tc)) || (io.read(p.clock)&0xf0000000))return false; + if(!rmw(io,p.pll,Power,Power) || !wait(io,p.pll,Powered,Powered))return false; + for(const auto &word:plan.words) { + auto value=word.value;if(!PHY::access(io,p,word.reg,word.mask,value,true))return false; + } + return rmw(io,p.pll,Enable,Enable) && wait(io,p.pll,Locked,Locked) && + rmw(io,p.clock,0xf0000000,0x80000000) && rmw(io,Gate,gateBit(p.tc),0); + } + // ADLP uses modular FIA, two TC ports per instance (intel_tc.c). + // Fixed HDMI uses all four main-link lanes. PHY ownership registers are + // generation-specific, but the FIA lane-enable register is still required. + static uint32_t laneRegister(unsigned tc) {return tc<2?0x1638c0:0x16e8c0;} + static uint32_t laneMask(unsigned tc) {return 15U<<(4*(tc&1));} + template static bool lanes(IO &io,const Port &p,unsigned count=4) { + return (count==1 || count==2 || count==4) && preflight(io,p) && io.readable(laneRegister(p.tc)) && + rmw(io,laneRegister(p.tc),laneMask(p.tc),((1U< static bool startLink(IO &io,const Port &p,const TMDSClock &plan,uint32_t preset,LinkStage *stage=nullptr) { + auto step=[&](LinkStage s){if(stage)*stage=s;}; + step(LinkStage::Preflight);if(!plan.valid() || !preflight(io,p))return false; + step(LinkStage::ReleaseDDI);if(!io.linkPower(false))return false; + step(LinkStage::StopPLL);if(!disable(io,p))return false; + step(LinkStage::Lanes);if(!lanes(io,p))return false; + step(LinkStage::PLL);if(!enable(io,p,plan))return false; + step(LinkStage::DDI);if(!io.linkPower(true))return false; + step(LinkStage::Signal);if(!TMDSSignal::program(io,p,preset))return false; + step(LinkStage::Complete);return true; + } + template static bool stopLink(IO &io,const Port &p) { + return io.unstarted() || (preflight(io,p) && io.linkPower(false) && disable(io,p)); + } + +}; +} +#endif diff --git a/WhateverXe/kern_display13_tc_dp.hpp b/WhateverXe/kern_display13_tc_dp.hpp new file mode 100644 index 00000000..228fecad --- /dev/null +++ b/WhateverXe/kern_display13_tc_dp.hpp @@ -0,0 +1,99 @@ +// Fixed SST DP on Display13 Dekel. Linux v6.12 intel_dpll_mgr.c, +// intel_ddi.c and ADLP DKL signal tables; no board-specific coefficients. +#ifndef KERN_DISPLAY13_TC_DP_HPP +#define KERN_DISPLAY13_TC_DP_HPP +#include "kern_display13_tc_clock.hpp" +namespace Display13TC { +struct DPClock : TMDSClock { + static DPClock calculate(uint32_t linkRate,uint32_t ref) { + DPClock p; + if((linkRate!=6 && linkRate!=10 && linkRate!=20 && linkRate!=30) || + (ref!=19200 && ref!=24000 && ref!=38400))return p; + const auto clock=linkRate*27000; + const unsigned dividers[]={7,5,3,2}; + for(unsigned h:dividers)for(unsigned d=10;d;--d) { + if(uint64_t(clock)*5*h*d!=8100000)continue; + const auto integer=8100000/(ref*2),rem=8100000%(ref*2); + const auto fraction=uint32_t((uint64_t(rem)<<22)/(ref*2)); + const auto target=uint32_t((2000000000ULL/(132*ref)+5)/10); + const auto ratio=h==7?3:h==5?2:h==3?1:0; + p.rate=clock;p.reference=ref;p.dco=8100000;p.hs=h;p.ds=d; + p.words[0]={0x212c,0x700,0x100}; + p.words[1]={0x20d8,0xff00,(d>=2?10U:5U)<<8}; + p.words[2]={0x20d4,0x1ff00,(d>=2?0x4000U:0U)|(ratio<<12)|(d<<8)}; + p.words[3]={0x2200,0x1fffff,0x84200|integer}; + p.words[4]={0x2204,0x1f00ff,((ref==24000?25U:28U)<<16)|target}; + p.words[5]={0x2210,0xe0ff3a00,(ref==38400?0x40000000U:0x20000000U)|0x2000}; + p.words[6]={0x2214,0x7fffff00,(fraction?0x40000000U:0U)|(fraction<<8)}; + p.words[7]={0x2218,0xffff,rem?82U:0U}; // 200000000 / (8100000 * 3/10) + return p; + } + return {}; + } +}; +// Retain only a link programmed by this owner, with matching live PLL/FIA. +// AGDC success leaves it enabled before native adoption and LightUp. +struct DPRetainedLink { + uint32_t rate {},reference {},lanes {}; + template bool matches(IO &io,const Port &p,const TMDSClock &plan,unsigned count) const { + if(!plan.valid() || !rate || rate!=plan.rate || reference!=plan.reference || lanes!=count || + (count!=1 && count!=2 && count!=4) || !io.owned(p.ddi) || !io.linkHeld())return false; + const uint32_t registers[]={p.pll,p.clock,Clock::Gate,Clock::laneRegister(p.tc)}; + for(auto reg:registers)if(!io.readable(reg) || io.read(reg)==UINT32_MAX)return false; + if((io.read(p.pll)&0xcc000000U)!=0xcc000000U || (io.read(p.clock)&0xf0000000U)!=0x80000000U || + (io.read(Clock::Gate)&Clock::gateBit(p.tc)) || + (io.read(Clock::laneRegister(p.tc))&Clock::laneMask(p.tc))!=((1U<3 || emphasis>3 || swing+emphasis>3)return false; + constexpr uint8_t starts[]={0,4,7,9},voltage[]={7,5,2,0,5,2,0,2,0,0}; + constexpr uint8_t low[]={1,6,11,23,0,8,20,0,11,0},high[]={0,4,10,24,0,6,20,0,9,0}; + const auto index=starts[swing]+emphasis; + value=voltage[index]|(uint32_t(rate<=10?low[index]:high[index])<<8);return true; + } + template static bool mode(IO &io,const Port &p,unsigned lanes) { + if(!io.powerHeld() || !io.owned(p.ddi) || !io.quiescent(p.ddi) || (lanes!=1 && lanes!=2 && lanes!=4))return false; + for(unsigned group=0;group<2;++group) { + uint32_t value=lanes==1?(group?0x40U:0U):0x80U; + if(!PHY::access(io,p,0x1000*group+0xa0,0xc0,value,true))return false; + } + return true; + } + // The donor negotiates a common swing/emphasis for the active lanes. + // Training can change the electrical level while the DDI buffer is on. + template static bool program(IO &io,const Port &p,unsigned rate,unsigned swing,unsigned emphasis) { + uint32_t selected; + if(!io.powerHeld() || !io.owned(p.ddi) || !coefficients(rate,swing,emphasis,selected))return false; + for(unsigned group=0;group<2;++group) { + const auto bank=0x1000*group;uint32_t zero=0; + if(!PHY::access(io,p,bank+0xd00,UINT32_MAX,zero,true))return false; + for(unsigned tx=0;tx<2;++tx) { + auto value=selected; + if(!PHY::access(io,p,bank+0x2c0+4*tx,0x3ff07,value,true))return false; + } + zero=0;if(!PHY::access(io,p,bank+0x2c8,0x7c,zero,true))return false; + } + return true; + } + template static bool startLink(IO &io,const Port &p,const DPClock &plan,unsigned lanes,Clock::LinkStage *stage=nullptr) { + auto step=[&](Clock::LinkStage s){if(stage)*stage=s;}; + step(Clock::LinkStage::Preflight); + if(!plan.valid() || (lanes!=1 && lanes!=2 && lanes!=4) || !Clock::preflight(io,p))return false; + step(Clock::LinkStage::ReleaseDDI);if(!io.linkPower(false))return false; + step(Clock::LinkStage::StopPLL);if(!Clock::disable(io,p))return false; + step(Clock::LinkStage::Lanes);if(!Clock::lanes(io,p,lanes))return false; + step(Clock::LinkStage::PLL);if(!Clock::enable(io,p,plan))return false; + step(Clock::LinkStage::DDI);if(!io.linkPower(true))return false; + step(Clock::LinkStage::Signal); + if(!mode(io,p,lanes) || !program(io,p,plan.rate/27000,0,0))return false; + step(Clock::LinkStage::Complete);return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_display13_tc_lightup.hpp b/WhateverXe/kern_display13_tc_lightup.hpp new file mode 100644 index 00000000..d2438a2c --- /dev/null +++ b/WhateverXe/kern_display13_tc_lightup.hpp @@ -0,0 +1,49 @@ +// Fixed-TMDS ordering for the exact Display13 native LightUpTMDS adapter. +// The caller's hwSetMode performs disableDisplay on any returned error. +#ifndef KERN_DISPLAY13_TC_LIGHTUP_HPP +#define KERN_DISPLAY13_TC_LIGHTUP_HPP +#include +namespace Display13TC { +enum class LightStage : uint32_t {Binding,Clocks,TransClock,Plane,Scaler,Pipe,Signal,Port,Packets,Complete,Training}; +struct LightResult {LightStage stage {LightStage::Binding};uint32_t status {0xe00002d8U};}; +template LightResult lightUpTMDS(IO &io,unsigned ddi,unsigned pipe) { + LightResult r; + if(ddi<3 || ddi>6 || pipe>=3 || !io.ready(false))return r; + r.stage=LightStage::Clocks;io.clocks(); + if(!io.ready(true))return r; + r.stage=LightStage::TransClock; + if(!io.transClock(0x46140+4*pipe,(ddi+3)<<28))return r; + r.stage=LightStage::Plane;io.plane();if(!io.ready(true))return r; + r.stage=LightStage::Scaler;io.scaler();if(!io.ready(true))return r; + r.stage=LightStage::Pipe;io.pipe();if(!io.ready(true))return r; + r.stage=LightStage::Signal;io.signal();if(!io.ready(true))return r; + r.stage=LightStage::Port; + const auto enabled=io.port();if(enabled){r.status=enabled;return r;} + if(!io.ready(true))return r; + r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r; + r.stage=LightStage::Complete;r.status=0;return r; +} +// Fixed SST retains native training and SDP setup, but stops on failure +// instead of the donor's unconditional success/pipe programming afterward. +template LightResult lightUpDP(IO &io,unsigned ddi,unsigned pipe) { + LightResult r; + if(ddi<3 || ddi>6 || pipe>=3 || !io.ready(false))return r; + r.stage=LightStage::Clocks;io.clocks();if(!io.ready(true))return r; + r.stage=LightStage::Training; + const auto trained=io.train();if(trained){r.status=trained;return r;} + if(!io.ready(true))return r; + r.stage=LightStage::TransClock;if(!io.transClock(0x46140+4*pipe,(ddi+3)<<28))return r; + r.stage=LightStage::Plane;io.plane();if(!io.ready(true))return r; + r.stage=LightStage::Scaler;io.scaler();if(!io.ready(true))return r; + r.stage=LightStage::Pipe;io.pipe();if(!io.ready(true))return r; + r.stage=LightStage::Packets;io.packets();if(!io.ready(true))return r; + r.stage=LightStage::Complete;r.status=0;return r; +} +// Native hwSetMode sends the begin notification before the path call. Its +// error branch skips post-mode writes and the normal end notification; +// balance that notification without replacing the returned error. +template uint32_t finishMode(Run run,Notify notify) { + const auto result=run();if(result)notify();return result; +} +} +#endif diff --git a/WhateverXe/kern_display13_tc_mode.hpp b/WhateverXe/kern_display13_tc_mode.hpp new file mode 100644 index 00000000..1b181f88 --- /dev/null +++ b/WhateverXe/kern_display13_tc_mode.hpp @@ -0,0 +1,83 @@ +// Fixed legacy TC lifetime. The host serializes native clients and proves +// firmware adoption/quiescence; this core never executes the TGL FIA setter. +#ifndef KERN_DISPLAY13_TC_MODE_HPP +#define KERN_DISPLAY13_TC_MODE_HPP +#include "kern_display13_tc.hpp" +#include "kern_display13_power_bridge.hpp" +namespace Display13TC { +struct Mode { + enum class Phase : uint32_t {Idle,Active,Cleanup}; + struct Change { + Status status {Status::Invalid};Result phy; + Display13Power::Result power;uint32_t native {}; + }; + uintptr_t controller {},object {};unsigned ddi {}; + Phase phase {Phase::Idle};bool disconnectPending {}; + Ownership ownership; + // ADLP: register/lane access -> ownership -> AUX cold block. + // DDI IO belongs to the clock lifecycle, after the PLL is running. + // An already-powered firmware link is not evidence that these scopes can + // be requested together on an unowned, cold PHY. + Display13Power::Lease accessLease,auxLease; + static uint32_t accessDomains() {return Display13Power::bit(Display13Power::Domain::PW2);} + Mode()=default;Mode(const Mode&)=delete;Mode& operator=(const Mode&)=delete; + static uint32_t domains(unsigned d) { + Port p;return Port::resolve(d,p) ? Display13Power::ddiDomain(d)| + Display13Power::auxDomain(d)|Display13Power::bit(Display13Power::Domain::PW2) : 0; + } + template Change connect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) { + Change r; + if(!owner || !identity || !domains(d))return r; + if(controller && (owner!=controller || identity!=object || d!=ddi)){r.status=Status::InUse;return r;} + if(!io.admissionReady()){r.status=Status::NotReady;return r;} + if(phase==Phase::Cleanup){r.status=Status::InUse;return r;} + if(phase==Phase::Active) { + if(io.nativeMode()!=4){r.status=Status::Readback;return r;} + r.phy=ownership.acquire(io,owner,identity,d); + r.status=r.phy.status;if(r.status==Status::Complete)disconnectPending=false; + return r; + } + if(io.nativeMode()!=0){r.status=Status::NeedsAdoption;return r;} + // Keep failed acquisitions bound, including their exact power/PHY + // cleanup obligations. Never replace them with another native object. + controller=owner;object=identity;ddi=d;phase=Phase::Cleanup; + r.power=io.acquireDomains(accessLease,accessDomains()); + if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} + const bool firmware=io.inheritedVerified(owner,identity,d); + r.phy=ownership.acquire(io,owner,identity,d,firmware); + if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} + r.power=io.acquireDomains(auxLease,Display13Power::auxDomain(d)); + if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} + r.native=io.nativeSetMode(4); + if(r.native || io.nativeMode()!=4){r.status=Status::Readback;return r;} + phase=Phase::Active;disconnectPending=false;r.status=Status::Complete;return r; + } + template Change disconnect(IO &io,uintptr_t owner,uintptr_t identity,unsigned d) { + Change r; + if(!owner || !identity || !domains(d))return r; + if(!controller){r.status=io.nativeMode()==0 ? Status::Complete : Status::NeedsAdoption;return r;} + if(owner!=controller || identity!=object || d!=ddi){r.status=Status::InUse;return r;} + disconnectPending=true; + if(!io.quiescent(owner,identity,d)){r.status=Status::InUse;return r;} + phase=Phase::Cleanup; + if(io.nativeMode()!=0) { + r.native=io.nativeSetMode(0); + if(r.native || io.nativeMode()!=0){r.status=Status::Readback;return r;} + } + if(auxLease.owner) { + r.power=io.releaseDomains(auxLease); + if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} + } + if(ownership.owner) { + r.phy=ownership.release(io,owner,identity,d); + if(r.phy.status!=Status::Complete){r.status=r.phy.status;return r;} + } + if(accessLease.owner) { + r.power=io.releaseDomains(accessLease); + if(r.power.status!=Display13Power::Status::Complete){r.status=Status::NoPower;return r;} + } + controller=object=0;ddi=0;phase=Phase::Idle;disconnectPending=false;r.status=Status::Complete;return r; + } +}; +} +#endif diff --git a/WhateverXe/kern_display13_tc_native.hpp b/WhateverXe/kern_display13_tc_native.hpp new file mode 100644 index 00000000..2fad5123 --- /dev/null +++ b/WhateverXe/kern_display13_tc_native.hpp @@ -0,0 +1,70 @@ +// Exact c004fc1a entry/ABI and failure branches for fixed TC outputs. +#ifndef KERN_DISPLAY13_TC_NATIVE_HPP +#define KERN_DISPLAY13_TC_NATIVE_HPP +#include +namespace Display13TC { namespace Native { +enum EntryIndex {Mode,BaseMode,Clocks,DisableClocks,LightUp,SetMode,Notify,FullClocks,Plane,Scaler,Pipe,Packets,DPLightUp,Training,SDPs,PhyGating,LaneMask,PortWrite}; +struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[20];}; +static constexpr Entry entries[]={ + {"__ZN17AppleIntelPortHAL11setPortModeEN14AppleIntelPort8PortModeE",0x5fcb8,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x41,0x89,0xf7}}, + {"__ZN14AppleIntelPort11setPortModeENS_8PortModeE",0x1b4c8,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x89,0xf3,0x49,0x89,0xfe,0x48,0xff,0x05,0x45,0xae,0x0b,0x00,0x85}}, + {"__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams",0x6ce10,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x49,0x89,0xcf}}, + {"__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_",0x6dae6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xd7,0x48,0x89,0xf3,0x49,0x89,0xfe}}, + {"__ZN31AppleIntelFramebufferController11LightUpTMDSEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x50b22,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6}}, + {"__ZN31AppleIntelFramebufferController9hwSetModeEP21AppleIntelFramebufferPK29IODetailedTimingInformationV2",0x693ec,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0x89,0x55}}, + {"__ZN31AppleIntelFramebufferController16sendNotificationEP21AppleIntelFramebufferNS_17ControllerEvent_tE",0x52f60,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x45,0xd0,0x08,0x00,0x83,0xfa,0x01,0x74,0x0d,0x85,0xd2,0x75,0x1c}}, + {"__ZN31AppleIntelFramebufferController12enableClocksEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x6ccb4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x12,0x87,0x07,0x00,0x48,0xff}}, + {"__ZN15AppleIntelPlane11enablePlaneEb",0x1108,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x5a,0xf8,0x0c,0x00,0x48,0x8b,0x47,0x68,0x83,0xb8}}, + {"__ZN16AppleIntelScaler17programPipeScalerEP21AppleIntelDisplayPath",0x65fd4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05}}, + {"__ZN31AppleIntelFramebufferController10enablePipeEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x48bb6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x48,0x8b,0x05}}, + {"__ZN31AppleIntelFramebufferController23enableDataIslandPacketsEP21AppleIntelFramebufferP21AppleIntelDisplayPath",0x52106,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0xff,0x05,0x11,0xdd,0x08,0x00,0x49,0x89,0xd6}}, + {"__ZN31AppleIntelFramebufferController9LightUpDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x508c4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89,0xce,0x48,0x89,0xd3}}, + {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t",0x1ba02,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x48,0x48,0xff,0x5}}, + {"__ZN31AppleIntelFramebufferController20configureSDPsForHDMIEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2",0x51f56,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x5,0x45,0xde,0x8}}, + {"__ZN17AppleIntelPortHAL17setPhyClockGatingEb",0x606b4,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x5,0x9f,0x25,0x8}}, + {"__ZN17AppleIntelPortHAL26getDklPhyLaneConfigBitMaskEv",0x60160,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x5,0x6a,0x29,0x8,0x0,0x48,0xff,0x5,0xb3,0x29,0x8}}, + {"__ZN14AppleIntelPort15writeRegister32Ejj",0x1fea8,{0x55,0x48,0x89,0xe5,0x48,0xff,0x5,0x9d,0x73,0xb,0x0,0x48,0x8b,0xbf,0x78,0x5,0x0,0x0,0x89,0xf6}}, + +}; +// Native clock replacements retain these DDI reference boundaries. +// Boot Mode is acquired in PowerWell::init: native ports must already exist. +// Full modesets stop the inherited link before LightUpDP programs its PLL. +static constexpr uint32_t bootPortInitOffset=0x3fdd0; +static constexpr uint8_t bootPortInit[]={0x89,0xdf,0xe8,0xc5,0xb3,0xfd,0xff,0x49,0x8b,0xb5,0x40,0x0d,0x00,0x00,0x4c,0x01,0xe6,0x48,0x8b,0x08,0x48,0x89,0xc7,0xff,0x91,0x18,0x01,0x00,0x00,0x84,0xc0,0x0f,0x84,0x9a,0x01,0x00,0x00,0x48,0xff,0x05,0x8c,0xd6,0x09,0x00,0x48,0xff,0xc3,0x48,0x8d,0x05,0xb2,0x8c,0x0f,0x00,0x8b,0x00,0x49,0x83,0xc4,0x18,0x48,0x39,0xc3,0x72,0xb8}; +static constexpr uint32_t bootPowerCallOffset=0x3fe36; +static constexpr uint8_t bootPowerCall[]={0x48,0x89,0xdf,0x4c,0x89,0xee,0xe8,0x47,0xa1,0x01,0x00}; +static constexpr uint32_t bootSetupCallOffset=0x3ff41; +static constexpr uint8_t bootSetupCall[]={0x4c,0x89,0xef,0xe8,0xc1,0x36,0x00,0x00}; +static constexpr uint32_t modeStopCallOffset=0x4febb; +static constexpr uint8_t modeStopCall[]={0x0f,0xb6,0xcb,0x4c,0x89,0xe7,0x4c,0x89,0xf6,0x4c,0x89,0xfa,0xe8,0x80,0xd4,0xff,0xff}; +static constexpr uint32_t dpLightCallOffset=0x4ffc4; +static constexpr uint8_t dpLightCall[]={0x48,0x8d,0x8d,0xf8,0xfe,0xff,0xff,0x4c,0x89,0xe7,0x4c,0x89,0xf6,0x4c,0x89,0xfa,0xe8,0xeb,0x08,0x00,0x00}; +static constexpr uint32_t ddiEnableTailOffset=0x6d7a8; +static constexpr uint8_t ddiEnableTail[]={0xe9,0x51,0x27,0xfb,0xff}; +static constexpr uint32_t ddiDisableCallOffset=0x6db04; +static constexpr uint8_t ddiDisableCall[]={0xe8,0x4b,0x24,0xfb,0xff}; +static constexpr uint32_t lightBodyOffset=0x50b22; +static constexpr uint8_t lightBody[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xcc,0x48,0x89,0xd3,0x49,0x89,0xf6,0x49,0x89,0xff,0x48,0xff,0x05,0xd8,0xef,0x08,0x00,0xe8,0x6f,0xc1,0x01,0x00,0x49,0x8b,0xbf,0x40,0x0c,0x00,0x00,0x48,0xff,0x05,0x65,0xdb,0x08,0x00,0x8b,0x83,0x40,0x36,0x00,0x00,0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x8b,0x93,0x28,0x01,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00,0x48,0x8b,0xbb,0xc8,0x32,0x00,0x00,0x31,0xf6,0xe8,0x8b,0x05,0xfb,0xff,0x48,0x8b,0xbb,0xc0,0x32,0x00,0x00,0x48,0x89,0xde,0xe8,0x48,0x54,0x01,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0x4c,0x89,0xe1,0xe8,0x19,0x80,0xff,0xff,0x48,0xff,0x05,0x94,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0x31,0xf6,0x31,0xd2,0x31,0xc9,0xff,0x90,0x40,0x01,0x00,0x00,0x48,0xff,0x05,0x77,0xda,0x08,0x00,0x48,0x8b,0xbb,0x48,0x36,0x00,0x00,0x48,0x8b,0x07,0xff,0x90,0x28,0x01,0x00,0x00,0x4c,0x89,0xff,0x4c,0x89,0xf6,0x48,0x89,0xda,0xe8,0x27,0x15,0x00,0x00,0x31,0xc0,0x5b,0x41,0x5c,0x41,0x5e,0x41,0x5f,0x5d,0xc3}; +static constexpr uint32_t innerBeforeOffset=0x4ffd9; +static constexpr uint8_t innerBefore[]={0x45,0x31,0xed}; +static constexpr uint32_t outerBeforeOffset=0x695ac; +static constexpr uint8_t outerBefore[]={0x41,0xf6,0x86,0x5c,0x0c,0x00,0x00,0x01,0x74,0x46,0x48,0xff,0x05,0x73,0xb3,0x07,0x00,0x48,0xff,0x05,0xec,0xae,0x07,0x00}; +static constexpr uint32_t modeExitOffset=0x698ed; +static constexpr uint8_t modeExit[]={0x48,0x83,0xc4,0x28,0x5b,0x41,0x5c,0x41,0x5d,0x41,0x5e,0x41,0x5f,0x5d,0xc3}; +static constexpr uint32_t innerCallOffset=0x4ffb6; +static constexpr uint8_t innerCall[]={0xe8,0x67,0x0b,0x00,0x00,0xeb,0x1c}; +static constexpr uint32_t outerCallOffset=0x695a7; +static constexpr uint8_t outerCall[]={0xe8,0x28,0x64,0xfe,0xff}; +static constexpr uint32_t modeBeginOffset=0x6940e; +static constexpr uint8_t modeBegin[]={0x45,0x31,0xff,0x31,0xd2,0xe8,0x48,0x9b,0xfe,0xff}; +static constexpr uint32_t modeEndOffset=0x698db; +static constexpr uint8_t modeEnd[]={0x4c,0x89,0xf7,0x4c,0x89,0xe6,0xba,0x01,0x00,0x00,0x00,0xe8,0x75,0x96,0xfe,0xff,0x31,0xc0}; +// Preserve the returned LightUp status so native hwSetMode disables the path. +static constexpr uint8_t innerAfter[]={0x41,0x89,0xc5}; +// Replace two profiling counters with a returned-status branch. On success, +// retain the original configuration-bit test and both of its destinations. +// Error jumps to the stack epilogue (preserving eax); the C++ outer adapter +// balances the begin notification. It does not execute post-mode MMIO. +static constexpr uint8_t outerAfter[]={0x85,0xc0,0x0f,0x85,0x39,0x03,0x00,0x00,0x41,0xf6,0x86,0x5c,0x0c,0x00,0x00,0x01,0x74,0x3e,0x90,0x90,0x90,0x90,0x90,0x90}; +}} +#endif diff --git a/WhateverXe/kern_display13_wm.hpp b/WhateverXe/kern_display13_wm.hpp index caca62b9..abbe3772 100644 --- a/WhateverXe/kern_display13_wm.hpp +++ b/WhateverXe/kern_display13_wm.hpp @@ -289,9 +289,15 @@ inline bool clockFits(const Snapshot &s,uint64_t nativeHz) { (!s.cursorActive || (s.cursor && s.cursor->status==Status::Complete && s.cursor->pixelHz && s.cursor->pixelHz<=nativeHz/2)); } +enum class LatchCheck : uint32_t {None,Controls,OtherPlane,DSB,Clock,Registers,Live,Frame,AwaitingScanout}; struct Latch { Status status {Status::Unavailable}; uint32_t frameBefore {},frameAfter {},waitUs {},budgetUs {},lastReg {},expected {},observed {}; + // Reuse the values already read by the proof. No diagnostic MMIO reads, + // retries, or relaxed acceptance; preserve the first failed check in IORegistry. + LatchCheck check {LatchCheck::None}; + uint32_t pipeControl {},planeControl {},cursorControl {},checkRegister {},checkValue {}; + uint64_t clockHz {}; }; template bool compare(IO &io,Latch &r,uint32_t reg,uint32_t expected) { if(!io.readable(reg)) {r.status=Status::Unavailable;return false;} @@ -338,23 +344,35 @@ template Latch latch(IO &io,const Snapshot &s) { const auto power=io.read(0x45404); if(!Display13FBIRQ::powered(power,s.pipeIndex)) {r.status=Status::Power;return r;} const auto pipe=io.read(0x70008+bank),plane=io.read(0x70180+bank),cursor=io.read(0x70080+bank); + r.check=LatchCheck::Controls;r.pipeControl=pipe;r.planeControl=plane;r.cursorControl=cursor; + // Native enablePlane may return before any scanout is attached, while + // programPipeScaler still flushes a zero SURF trigger via armUpdate. + // A disabled primary with no intended surface is not a live latch to + // prove yet. Keep WM/DDB pending; never retire storage or certify the + // shared clock until a later real plane/surface update passes below. + const bool preparing=plane!=UINT32_MAX && !(plane&Enabled) && !s.primarySurface && !cursor && !s.cursorActive; if(pipe==UINT32_MAX || plane==UINT32_MAX || cursor==UINT32_MAX || (pipe&0xC0000000)!=0xC0000000 || - !(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled || + (!preparing && (!(plane&Enabled) || !supportedRGBControl(plane) || (((plane>>10)&7)==4)!=s.primary->yTiled)) || bool(cursor&0x27)!=s.cursorActive || (s.cursorActive && !cursorMatches(cursor,s.cursor->width))) {r.status=Status::Conflict;return r;} // No autonomous DSB writer is admitted by this direct-MMIO proof. for(unsigned p=1;p<5;++p) { const auto reg=0x70180+bank+0x100*p; if(!io.readable(reg))return r; const auto ctrl=io.read(reg); + r.check=LatchCheck::OtherPlane;r.checkRegister=reg;r.checkValue=ctrl; if(ctrl==UINT32_MAX || (ctrl&Enabled)) {r.status=Status::Conflict;return r;} } for(unsigned d=0;d<3;++d) { const auto reg=Display13FBIRQ::DSBControl(s.pipeIndex,d); if(!io.readable(reg))return r; const auto ctrl=io.read(reg); + r.check=LatchCheck::DSB;r.checkRegister=reg;r.checkValue=ctrl; if(ctrl==UINT32_MAX || (ctrl&(Enabled|1))) {r.status=Status::Conflict;return r;} } - if(io.nativeClockHz()!=s.nativeClockHz) {r.status=Status::Conflict;return r;} + r.check=LatchCheck::Clock;r.clockHz=io.nativeClockHz(); + if(r.clockHz!=s.nativeClockHz) {r.status=Status::Conflict;return r;} + if(preparing) {r.check=LatchCheck::AwaitingScanout;return r;} + r.check=LatchCheck::Registers; if(!compare(io,r,0x60000+bank,ht) || !compare(io,r,0x6000C+bank,vt) || !compare(io,r,0x45270+4*s.pipeIndex,s.pipeLineTime) || !compare(io,r,0x70190+bank,((s.primary->height-1)<<16)|(s.primary->width-1)) || !compare(io,r,0x7027C+bank,s.primaryDDB) || !compare(io,r,0x7017C+bank,s.cursorDDB) || @@ -365,12 +383,14 @@ template Latch latch(IO &io,const Snapshot &s) { r.waitUs=static_cast(io.nowUs()-start); if(r.frameAfter==UINT32_MAX) {r.status=Status::Readback;return r;} if(r.frameAfter!=r.frameBefore) { + r.check=LatchCheck::Frame; if(uint32_t(r.frameAfter-r.frameBefore)>=0x80000000U) {r.status=Status::Conflict;return r;} // The frame counter can advance at VBlank before the live-surface // readback catches up (observed for a native direct cursor swap). // Keep every programmed/control/clock check on each poll; qualify // retirement only after both actual live surfaces match. A stable // mismatch at the existing deadline remains a Readback failure. + r.check=LatchCheck::Live; const bool live=compare(io,r,0x701AC+bank,s.primarySurface) && (!s.cursorActive || compare(io,r,0x700AC+bank,s.cursorSurface)); if(live) {r.status=Status::Complete;return r;} diff --git a/WhateverXe/kern_firmware.cpp b/WhateverXe/kern_firmware.cpp index 60c553ba..751eb82f 100644 --- a/WhateverXe/kern_firmware.cpp +++ b/WhateverXe/kern_firmware.cpp @@ -10,7 +10,8 @@ static void publishBytes(IOPCIDevice *pci,const char *key,const void *bytes,unsi // Firmware is read once, before topology/hook selection. No PCI BAR/ROM scan, // power acquisition, or MMIO probing occurs here. Explicit properties win later. void XE::xelpReadFirmware(IOPCIDevice *pci) { - if(!pci || !xeProfile.admitted())return; + // Wiring is needed by framebuffer-only profiles before Render is admitted. + if(!pci || !xeProfile.displayAdmitted())return; const uint32_t asls=pci->configRead32(0xFC); uint32_t source=0; if(asls && asls<=0xFFFFE000U) { diff --git a/WhateverXe/kern_framebuffer_donor.hpp b/WhateverXe/kern_framebuffer_donor.hpp new file mode 100644 index 00000000..fcc40aa3 --- /dev/null +++ b/WhateverXe/kern_framebuffer_donor.hpp @@ -0,0 +1,54 @@ +// Object contracts of the c004fc1a framebuffer donor, independent of Display IP. +#ifndef KERN_FRAMEBUFFER_DONOR_HPP +#define KERN_FRAMEBUFFER_DONOR_HPP +#include +#include +#include +namespace FramebufferDonor { +// Native entry admission is closed before controller/pool destruction. The +// low bits count in-flight opens, training and resource adoption; stop never +// holds the pool lock while draining them. Closing is terminal for this owner. +class Call { + static constexpr uint32_t Closed=1U<<31; + uint32_t *state {}; +public: + explicit Call(uint32_t &value) { + auto old=__atomic_load_n(&value,__ATOMIC_ACQUIRE); + while(!(old&Closed) && old!=Closed-1) { + if(__atomic_compare_exchange_n(&value,&old,old+1,false,__ATOMIC_ACQ_REL,__ATOMIC_ACQUIRE)) {state=&value;break;} + } + } + ~Call(){if(state)__atomic_fetch_sub(state,1,__ATOMIC_RELEASE);} + Call(const Call &)=delete;Call &operator=(const Call &)=delete; + explicit operator bool() const {return state!=nullptr;} + static void close(uint32_t &value){__atomic_fetch_or(&value,Closed,__ATOMIC_ACQ_REL);} + static bool drained(uint32_t &value){return (__atomic_load_n(&value,__ATOMIC_ACQUIRE)&~Closed)==0;} +}; +template T field(const void *object,size_t offset) { + T value {};if(object)memcpy(&value,static_cast(object)+offset,sizeof(value));return value; +} +// Native FB init leaves borrowed MMIO-manager fields empty. Validate the whole +// controller-owned graph before changing any reference. Never retain/release a +// borrowed manager, overwrite a foreign owner, or partly bind a rejected graph. +template bool bindManagers(Objects &objects,void *fb,void *controller,uint32_t index) { + if(index>=3 || objects.size(fb)<0x4B90 || objects.size(controller)<0x1C20 || + field(fb,0x1D0)!=controller || field(fb,0x1DC)!=index)return false; + auto manager=field(controller,0xC40); + if(objects.size(manager)<0x68 || !field(manager,0x50) || field(manager,0x60)<=0x60404)return false; + const size_t arrays[] {0xF58,0xF28},identities[] {0x7C,0x1C},slots[] {0x90,0x28}; + const unsigned counts[] {15,6},minimum[] {0x1C8,0x58}; + for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+sizeof(void *)*i); + if(objects.size(object)(object,identities[group])!=i)return false; + auto stored=field(object,slots[group]); + if(stored && stored!=manager)return false; + } + auto stored=field(fb,0x4A40);if(stored && stored!=manager)return false; + for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+sizeof(void *)*i); + memcpy(object+slots[group],&manager,sizeof(manager)); + } + memcpy(static_cast(fb)+0x4A40,&manager,sizeof(manager));return true; +} +} +#endif diff --git a/WhateverXe/kern_framebuffer_training_native.hpp b/WhateverXe/kern_framebuffer_training_native.hpp new file mode 100644 index 00000000..b0b7c726 --- /dev/null +++ b/WhateverXe/kern_framebuffer_training_native.hpp @@ -0,0 +1,35 @@ +// Exact c004fc1a donor interfaces for pre-modeset SST training and adoption. +#ifndef KERN_FRAMEBUFFER_TRAINING_NATIVE_HPP +#define KERN_FRAMEBUFFER_TRAINING_NATIVE_HPP +#include +namespace FramebufferTrainingNative { +enum Index {Begin,Train,End,AllocateAny,AllocateFixed,Free,Add,Resources,Stop,PortTrain,Lookup,Port}; +struct Entry {const char *symbol;uint32_t offset;uint8_t prefix[16];}; +static constexpr Entry entries[]={ + {"__ZN31AppleIntelFramebufferController19doLinkTrainingBeginEP25AGDCDPLinkTrainingBegin_t",0x6aae0,{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0xff,0x05,0xd3,0xa1,0x07,0x00,0x48,0x85,0xf6}}, + {"__ZN31AppleIntelFramebufferController14doLinkTrainingEP18AGDCDPPortConfig_tS1_",0x6ab7e,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x68,0xa1}}, + {"__ZN31AppleIntelFramebufferController17doLinkTrainingEndEP23AGDCDPLinkTrainingEnd_t",0x6ad06,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x28,0xa0}}, + {"__ZN21AppleIntelDisplayPath12allocatePathEv",0x9188a,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x23,0xa7,0x05,0x00,0xe8,0x4e,0x9a,0xf8,0xff}}, + {"__ZN21AppleIntelDisplayPath12allocatePathEj",0x91950,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x8d,0xa6,0x05,0x00,0xe8,0x64,0xfe,0xff,0xff}}, + {"__ZN21AppleIntelDisplayPath8freePathEv",0x91d88,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x85,0xa3,0x05,0x00,0x5d,0xe9,0xb3,0xf7,0xff}}, + {"__ZN21AppleIntelFramebuffer14addDisplayPathEj",0x68d3e,{0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0xd4,0xb9,0x07,0x00,0x48,0x8b}}, + {"__ZN21AppleIntelFramebuffer28allocateDisplayPathResourcesEjj",0x679a6,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0xff,0x05,0x38,0xca}}, + {"__ZN31AppleIntelFramebufferController4stopEP9IOService",0x44982,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xf6,0x49,0x89}}, + {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t",0x1ba02,{0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec}}, + {"__ZN21AppleIntelDisplayPath18getFirstPathByPortEP14AppleIntelPort",0x91bca,{0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0xff,0x05,0xc1,0xa4,0x05,0x00,0x48,0x85,0xff}}, + {"__ZN14AppleIntelPort12getPortByDDIEj",0x1b1ea,{0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x53,0xb0,0x0b,0x00,0x44,0x8b,0x05,0xbc,0xd8}}, +}; +struct Span {uint32_t offset;uint8_t length;uint8_t bytes[40];}; +static constexpr Span spans[]={ + {0x6ac66,12,{0x48,0x8d,0x73,0x08,0x4c,0x89,0xff,0xe8,0xaa,0x08,0xfb,0xff}}, + {0x6ac7d,15,{0x4c,0x89,0xe7,0x4c,0x89,0xfe,0x31,0xd2,0x31,0xc9,0xe8,0x84,0x21,0x00,0x00}}, + {0x6acc1,11,{0x4c,0x89,0xff,0x4c,0x89,0xf6,0xe8,0x36,0x0d,0xfb,0xff}}, + {0x6ad44,28,{0x48,0x8b,0x00,0x4c,0x89,0xff,0xff,0x90,0x30,0x01,0x00,0x00,0x45,0x31,0xe4,0x4c,0x89,0xf7,0x4c,0x89,0xfe,0x31,0xd2,0xe8,0x86,0x2d,0x00,0x00}}, + {0x67a2d,8,{0x48,0x89,0xdf,0xe8,0x09,0x13,0x00,0x00}}, + {0x67a75,22,{0x44,0x89,0xe7,0xe8,0x1f,0x37,0xfb,0xff,0x48,0xff,0x05,0xec,0xcc,0x07,0x00,0x49,0x89,0x86,0x48,0x36,0x00,0x00}}, + {0x1e328,39,{0x41,0xc6,0x47,0x10,0x00,0x41,0x38,0x57,0x0e,0x75,0x0e,0x48,0xff,0x05,0xb6,0x89,0x0b,0x00,0x41,0x80,0x7f,0x0d,0x00,0x74,0x0e,0x48,0xff,0x05,0xa0,0x89,0x0b,0x00,0xc0,0xe8,0x07,0x41,0x88,0x47,0x10}}, + {0x1b607,20,{0x0f,0xb6,0x43,0x1e,0x48,0x01,0x05,0x66,0xad,0x0b,0x00,0x34,0x01,0x88,0x83,0x1a,0x01,0x00,0x00,0x0f}}, + {0x4d980,16,{0x8d,0x34,0x85,0x40,0x61,0x04,0x00,0x48,0x8b,0x07,0xff,0x90,0x30,0x01,0x00,0x00}}, +}; +} +#endif diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index f4eb7fff..a0b5de2c 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -31,7 +31,9 @@ void XeHost::processKernel(KernelPatcher &patcher) { // Match WEG ordering: PCI config reads require a published IOPCIDevice. const bool published=gpu && WIOKit::awaitPublishing(gpu); const auto profile=published ? XELP::deviceProfile(gpu) : XELP::Profile {}; - active=profile.admitted(); + // The host must dispatch framebuffer patches and accelerator rejection + // hooks even when the selected profile deliberately has no Render backend. + active=profile.displayAdmitted(); const auto display=XELP::displayAdapter(profile.family); backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS) && info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple; @@ -44,7 +46,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { if(!gpu->getProperty("model"))if(const auto name=XELP::model(profile))gpu->setProperty("model",const_cast(name),strlen(name)+1); } // Capture the unmodified firmware/GT state before admitting a new backend. - if(!active && profile.family==XELP::Family::AlderLakeS)xe.processKernel(patcher,info); + if(!active && (profile.family==XELP::Family::AlderLakeS || + profile.family==XELP::Family::AlderLakeP))xe.processKernel(patcher,info); if(active) { XeDataAccess::processKernel(patcher); // Xe admission reads physical PCI identity. Do not install WEG's ID spoof hooks. diff --git a/WhateverXe/kern_tgl_native_fia.hpp b/WhateverXe/kern_tgl_native_fia.hpp index 487142f4..3b03a38f 100644 --- a/WhateverXe/kern_tgl_native_fia.hpp +++ b/WhateverXe/kern_tgl_native_fia.hpp @@ -5,7 +5,7 @@ #include struct TGLNativeFiaContext { - uintptr_t owner {}; void *port {}; uint32_t ddi {}; + uintptr_t owner {}; void *port {}; uint32_t ddi {}; bool failed {}; bool matches(uintptr_t thread,void *p) const { return thread && __atomic_load_n(&owner,__ATOMIC_ACQUIRE)==thread && port==p; } @@ -13,21 +13,23 @@ struct TGLNativeFiaContext { // The existing recursive mode mutex serializes writers. Only the owning // thread reads port/ddi; other threads stop at the atomic owner check. struct TGLNativeFiaScope { - TGLNativeFiaContext &ctx; uintptr_t previousOwner; void *previousPort; uint32_t previousDDI; bool active; + TGLNativeFiaContext &ctx; uintptr_t previousOwner; void *previousPort; uint32_t previousDDI; bool previousFailed,active; TGLNativeFiaScope(TGLNativeFiaContext &c,uintptr_t thread,void *p,uint32_t d,bool enable): - ctx(c),previousOwner(0),previousPort(nullptr),previousDDI(0),active(enable) { + ctx(c),previousOwner(0),previousPort(nullptr),previousDDI(0),previousFailed(false),active(enable) { if(!active)return; - previousOwner=__atomic_load_n(&ctx.owner,__ATOMIC_RELAXED);previousPort=ctx.port;previousDDI=ctx.ddi; + previousOwner=__atomic_load_n(&ctx.owner,__ATOMIC_RELAXED);previousPort=ctx.port;previousDDI=ctx.ddi;previousFailed=ctx.failed;ctx.failed=false; ctx.port=p;ctx.ddi=d;__atomic_store_n(&ctx.owner,thread,__ATOMIC_RELEASE); } + bool failed()const{return active && ctx.failed;} void finish(){if(!active)return;__atomic_store_n(&ctx.owner,0,__ATOMIC_RELEASE); - ctx.port=previousPort;ctx.ddi=previousDDI;__atomic_store_n(&ctx.owner,previousOwner,__ATOMIC_RELEASE);active=false;} + ctx.port=previousPort;ctx.ddi=previousDDI;ctx.failed=previousFailed;__atomic_store_n(&ctx.owner,previousOwner,__ATOMIC_RELEASE);active=false;} ~TGLNativeFiaScope(){finish();} }; struct TGLNativeFia { TGLFiaPort fia {}; uint32_t donorMask {}; - // The caller has already observed modular FIA in the powered mode4 probe. + // Initial modular coordinates; the host resolves the live layout at the + // donor's powered access before using them. No boot-probe prerequisite. // TC5/6 remain excluded: the native ready switch has no case for them. bool resolve(uint32_t ddi){TGLTypeCPort tc; if(ddi<3 || ddi>6 || !TGLTypeCPort::resolve(ddi,tc) || !tc.resolveFia(0x10,fia))return false; diff --git a/WhateverXe/kern_vbt.hpp b/WhateverXe/kern_vbt.hpp index 1294f998..a8022a21 100644 --- a/WhateverXe/kern_vbt.hpp +++ b/WhateverXe/kern_vbt.hpp @@ -15,12 +15,14 @@ inline uint64_t u64(const uint8_t *p) {return uint64_t(u32(p)) | uint64_t(u32(p+ enum class Status : uint32_t { Missing, Valid, Header, Checksum, Blocks, Version, Children, Conflict, Platform }; struct Port { uint32_t type {},dvo {},ddc {},aux {},flags {},capabilities {}; + uint32_t hdmiPreset {UINT32_MAX}; // BDB child byte7, table index; absent firmware uses default. // flags preserves byte23 (lane reversal, LSPCON, HPD polarity, lane count). // capabilities preserves byte33 (USB-C/TBT capabilities, NOT current mode). bool dp() const {return type&4;} bool internal() const {return type&0x1000;} - bool fixedTMDS() const {return (type&0x12)==0x12 && !(type&(4|0x1000|0x400|0x20|1)) && - !(flags&0x17) && !(capabilities&3);} + bool tmdsOnly() const {return (type&0x12)==0x12 && !(type&(4|0x1000|0x400|0x20|1)) && + !(flags&0x17);} + bool fixedTMDS() const {return tmdsOnly() && !(capabilities&3);} }; struct Ports { Status status {Status::Missing}; @@ -37,6 +39,8 @@ struct Ports { } panel; Port port[PortCount]; bool valid() const {return status==Status::Valid;} + // Linux direct_aux_ch_map: A=0x40, B/C/D=0x10/0x20/0x30, E..I=0x50..0x90. + static uint32_t tglTCAux(unsigned ddi) {return ddi>=3 && ddi=5 ? p-2 : PortCount; + if((adapter==DisplayAdapter::ADLN || adapter==DisplayAdapter::ADLP))return p<2 ? p : p>=5 ? p-2 : PortCount; if(adapter==DisplayAdapter::ADLS)return p==0 ? 0 : p<5 ? p+2 : PortCount; } return PortCount; @@ -184,7 +188,7 @@ inline Ports parse(const void *data,size_t length,Family family) { const auto d=ddi(family,c[16]); if(d==PortCount){++r.unmapped;continue;} if(r.present&(1U<(current_thread()) && t.access==access; +} // Immutable first 16 snapshots per cursor lifecycle event; publish on demand. // No MMIO writes, table repair, allocations or registry publication in hooks. struct TGLCursorLifetimeRecord { uint64_t q[96]; volatile uint32_t ready; }; @@ -3239,6 +3268,14 @@ bool tglMEILiveSerialize(void *, void *, OSSerialize *serializer) { return result; } +// Bounded main-link AUX decisions. Copy only values already inspected by the +// lifetime guard; collection does not issue MMIO or native power operations. +struct TGLComboAUXDecision { uint64_t ns; uint32_t word[14]; }; +static TGLComboAUXDecision tglComboAUXFirst[64] {},tglComboAUXLast[64] {}; +static uint32_t tglComboAUXCount {}; +static void tglComboAUXDecisionRecord(uint32_t *word); +static bool tglComboAUXDecisionSerialize(void *,void *,OSSerialize *); + struct TGLAUXRecord { uint64_t ns, thread; uint32_t word[28]; @@ -3252,6 +3289,27 @@ uint32_t tglAUXCounts[9] {}; TGLAUXRecord tglAUXControl[9][64] {}; uint32_t tglAUXControlCounts[9] {}; +static void tglComboAUXDecisionRecord(uint32_t *word) { + if(!tglAUXLock)return; + TGLComboAUXDecision r {};uint64_t ticks;clock_get_uptime(&ticks);absolutetime_to_nanoseconds(ticks,&r.ns); + memcpy(r.word,word,sizeof(r.word)); + IOSimpleLockLock(tglAUXLock);r.word[0]=++tglComboAUXCount; + if(r.word[0]<=64)tglComboAUXFirst[r.word[0]-1]=r; + tglComboAUXLast[(r.word[0]-1)%64]=r;IOSimpleLockUnlock(tglAUXLock); +} +static bool tglComboAUXDecisionSerialize(void *,void *,OSSerialize *serializer) { + if(!tglAUXLock)return false; + constexpr size_t size=16+sizeof(tglComboAUXFirst)+sizeof(tglComboAUXLast); + auto copy=static_cast(IOMalloc(size));if(!copy)return false; + auto h=reinterpret_cast(copy);h[0]=1;h[2]=64;h[3]=sizeof(TGLComboAUXDecision); + IOSimpleLockLock(tglAUXLock);h[1]=tglComboAUXCount; + memcpy(copy+16,tglComboAUXFirst,sizeof(tglComboAUXFirst)); + memcpy(copy+16+sizeof(tglComboAUXFirst),tglComboAUXLast,sizeof(tglComboAUXLast)); + IOSimpleLockUnlock(tglAUXLock); + auto data=OSData::withBytes(copy,size);IOFree(copy,size);if(!data)return false; + const bool ok=data->serialize(serializer);data->release();return ok; +} + // Bounded lifecycle evidence, separate from bulk AUX. Capture arguments and // existing native results only; never perform diagnostic hardware accesses. struct TGLTransitionRecord { uint64_t q[16]; }; @@ -3742,19 +3800,29 @@ void XE::selectProfile(const XELP::Profile &profile) { adlnNativeRCSProfileRequested=false; adlnRequested=profile.admitted() && profile.family==XELP::Family::AlderLakeN && profile.display==XELP::Display::XeLPD && profile.gt==XELP::GT::ADLNSmall; - adlnDetected=adlnRequested; - modDVMTCalcFix.available=profile.admitted(); + modDVMTCalcFix.available=profile.displayAdmitted(); const bool nativeOS=XELP::kernelOS(version)!=XELP::OS::Unknown && XELP::kernelOS(version)!=XELP::OS::Donor; - display13FramebufferRequested=profile.admitted() && profile.display==XELP::Display::XeLPD && - XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN && nativeOS; + adlpDisplaySelected=profile.mobileDisplay13() && nativeOS; + // Preserve the accepted Display13/TC handoff when enabling Render. + // noaccel still selects the exact FB-only rejection path. + adlpRenderRequested=adlpDisplaySelected && !checkKernelArgument("-wex-noaccel"); + adlnRequested=adlnRequested || adlpRenderRequested; + adlnDetected=adlnRequested; + display13FramebufferRequested=profile.displayAdmitted() && profile.display==XELP::Display::XeLPD && + (XELP::displayAdapter(profile.family)==XELP::DisplayAdapter::ADLN || adlpDisplaySelected) && nativeOS; display13TraceEnabled=display13FramebufferRequested && checkKernelArgument("-wex-display13-trace"); + display13Offline=display13FramebufferRequested && checkKernelArgument("-wex-display13-offline"); if(display13FramebufferRequested)currentFramebuffer=&kextIntelTGLFb; + if(adlpDisplaySelected && !adlpRenderRequested) { + tglRequested=tglAcceleratorRequested=false;currentGraphics=&kextIntelTGL;return; + } if(adlnRequested) { adlnKernelReady=nativeOS; adlnNativeRequested=adlnKernelReady; adlnNativeRCSProfileRequested=adlnNativeRequested && !checkKernelArgument("-wex-adln-kernel-ctx"); if(adlnNativeRequested) { - tglRequested=tglAcceleratorRequested=true;currentGraphics=&kextIntelTGL; + tglRequested=true;tglAcceleratorRequested=!checkKernelArgument("-wex-noaccel"); + currentGraphics=&kextIntelTGL; } return; } @@ -3774,11 +3842,26 @@ void XE::selectProfile(const XELP::Profile &profile) { void XE::deinit() {} +Display13OutputWiring XE::display13BoardWiring(IOService *provider) { + auto property=provider->getProperty("xelp-hdmi-wiring"); + if(!property)property=provider->getProperty("tgl-hdmi-wiring"); + if(!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); + // Preserve the older ADL-N Combo property contract. It cannot select TC. + if(adlpDisplaySelected)return {}; + auto wiring=OSDynamicCast(OSData,property); + return XELP::VBT::selectHDMI(true,wiring?wiring->getBytesNoCopy():nullptr, + wiring?wiring->getLength():0,firmwarePorts,3); +} + void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); const auto revision = pci->configRead8(8); - const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci); + const bool renderRequested = adlnRequested && adlnKernelReady && xelpDisplay13RenderDevice(pci) && + !checkKernelArgument("-wex-noaccel"); const bool displayRequested = display13FramebufferRequested && xelpDisplay13Device(pci); const bool admitted = renderRequested || displayRequested; pci->setProperty("N97Admission", admitted); @@ -3790,16 +3873,12 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97PhysicalMemoryLayout",memory,sizeof(memory)); // Bootstrap runs before framebuffer start loads its board policy. Native // GOP qualification must already use that physical port, not assume B. - auto property=pci->getProperty("xelp-hdmi-wiring"); - if(!property)property=pci->getProperty("tgl-hdmi-wiring"); - auto wiring=OSDynamicCast(OSData,property); - const auto board=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, - wiring?wiring->getLength():0,firmwarePorts,3); - if(displayRequested && !Display13FBTopology::supportedWiring(board)) { + const auto board=display13BoardWiring(pci);adlnOutputWiring=board; + if(displayRequested && !Display13FBTopology::supportedWiring(board,xeProfile.display==XELP::Display::XeLPD)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } auto bootDDI=displayRequested ? UINT32_MAX : 1U; - uint64_t connector[] {1,displayRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; + uint64_t connector[] {1,displayRequested,pci->getProperty("xelp-hdmi-wiring")!=nullptr,board.valid,board.hdmi,bootDDI}; pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); pci->setProperty("N97DisplayVersion", AlderLakeN::DisplayVersion, 32); pci->setProperty("N97GTStep", revision==0 ? "A0" : "unknown"); @@ -3819,6 +3898,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { uint32_t read(uint32_t offset) const { return *reinterpret_cast(base + offset); } void write(uint32_t offset,uint32_t value) { *reinterpret_cast(base + offset)=value; } void delay() { IODelay(100); } + void delayUs(unsigned us) { IODelay(us); } uint64_t readPTE(uint32_t offset) { return *reinterpret_cast(base+offset); } void writePTE(uint32_t offset,uint64_t value) { *reinterpret_cast(base+offset)=value; } uint32_t apertureRead(uint32_t offset) { return *reinterpret_cast(aperture+offset); } @@ -3840,6 +3920,20 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97StolenReservation",reservation,sizeof(reservation)); auto result = AlderLakeN::readDisplay(io, barReady); pci->setProperty("N97Display13Boot", &result, sizeof(result)); + // Bounded pre-native evidence for selected fixed TC ports, including ports + // which firmware owns without routing a transcoder to them. + uint32_t bootTC[1+4*8] {1},bootPower=0; + if(barReady && AlderLakeN::value(result,0x45404,bootPower) && (bootPower&5)==5) { + for(unsigned d=3;d<7;++d)if(board.selected()&(1U<setProperty("WEXDisplay13TCBootPorts",bootTC,sizeof(bootTC)); + } + bool bootstrapReady=true; if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory); @@ -3848,17 +3942,27 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { pci->setProperty("N97DualGOPAfter",&result,sizeof(result)); bootstrapReady=parked.status==Display13Boot::Status::Complete; } + if(displayRequested && linearTest && bootstrapReady && Display13FBTopology::multiOutputWiring(board)) { + auto unused=Display13Boot::releaseUnusedTC(io,result,barReady && xelpMemoryMatches(pci),board); + pci->setProperty("WEXDisplay13UnusedTC",&unused,sizeof(unused)); + bootstrapReady=unused.status==Display13Boot::Status::Complete; + } if(displayRequested && bootstrapReady) { uint32_t trans=UINT32_MAX; if(AlderLakeN::value(result,0x60400,trans))bootDDI=Display13FBTopology::bootstrapDDI(board, - result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans); + result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans,true); connector[5]=bootDDI;pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); } pci->setProperty("N97DisplayReadoutStatus", static_cast(result.status), 32); SYSLOG("igfx", "AlderLakeN Display13 readout status=%u powered=%x active=%x hdmiB=%x; donor blocked", static_cast(result.status), result.poweredPipes, result.activePipes, result.hdmiBPipes); - if (linearTest) { - auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI); + // Mobile and TC configurations adopt a validated firmware surface at + // controller start. Preserve the established ADL-N Combo bootstrap. + if (linearTest && !Display13FBTopology::deferredBootPrepare(board,adlpDisplaySelected)) { + auto tc=Display13TC::Boot::capture(io,barReady && bootDDI>=3 && xelpMemoryMatches(pci),bootDDI,0); + if(bootDDI>=3)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); + auto linear=AlderLakeN::reinitializeLinear(io,result,barReady && xelpMemoryMatches(pci),bootDDI, + 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)); @@ -3961,7 +4065,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { if (gtTest) { uint32_t edram=UINT32_MAX; auto gt=AlderLakeN::readGT(io,barReady && xelpMemoryMatches(pci), - adlnNativeRequested ? &edram : nullptr); + adlnNativeRequested ? &edram : nullptr,adlpRenderRequested ? XELP::decodeADLPGT : nullptr); if (adlnNativeRequested) { adlnNativeSubslices=gt.resources.nativeSubslices; adlnL3Banks=gt.resources.l3Banks; adlnNativeEUs=gt.resources.eus; renderReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1); @@ -3983,16 +4087,27 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { ((!optionProperty || opts) && !mapperOptions && !systemMapper); uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); - const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory); + const bool dp=bootDDI<7 && (board.dp&(1U<=3 && xelpMemoryMatches(pci),bootDDI,0,dp); + if(bootDDI>=3 && bootDDI<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); + AlderLakeN::LinearPlan bootPlan; + uint32_t sourceProof[]={1,bootDDI,uint32_t(protocol),uint32_t(AlderLakeN::linearPlan( + display,bootPlan,bootDDI,protocol,bootDDI>=3?&tc:nullptr)),tc.valid()}; + pci->setProperty("N97NativeBootSource",sourceProof,sizeof(sourceProof)); + const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory, + bootDDI>=3 ? &tc : nullptr,protocol); auto aperture=pci->getDeviceMemoryWithRegister(0x18); const bool memoryReady= aperture && aperture->getLength()==0x10000000 && page_size==4096 && physicalOptions && (pci->configRead16(4)&6)==6 && checkKernelArgument("-allow3d") && checkKernelArgument("-disablegfxfirmware") && !checkKernelArgument("-wex-adln-scanout") && !checkKernelArgument("-wex-adln-gpu"); - // This shared admission remains the conjunction of all three - // certificates; selecting a Display adapter never admits a GT. - adlnNativeReady=renderReady && prefix && memoryReady; + // FB can adopt its boot memory without initializing a render engine. + // Keep the stricter GT certificate for every accelerator consumer. + display13BootReady=prefix && memoryReady; + adlnNativeReady=renderReady && display13BootReady; + pci->setProperty("WEXDisplay13BootReady",display13BootReady); pci->setProperty("N97NativePreflight",adlnNativeReady); pci->setProperty("N97NativeReservedGGTT",prefix); } @@ -4047,7 +4162,7 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { } bool XE::xelpBindProbe(void *controller) { - if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false; + if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.displayAdmitted())return false; XELP::ProbeConfig config;config.profile=xeProfile; if(adlsDisplaySelected) { if(!tglHDMIWiringApplied || !adlsWiring.valid || !adlsWiring.selected())return false; @@ -4060,9 +4175,11 @@ bool XE::xelpBindProbe(void *controller) { config.legacyBackend=tglAUXParentMask; } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || - !Display13FBTopology::adlnWiring(adlnOutputWiring) || - (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; - config.combo=adlnOutputWiring.selected();config.comboBackend=3; + !Display13FBTopology::supportedWiring(adlnOutputWiring,xeProfile.display==XELP::Display::XeLPD) || + ((adlnOutputWiring.selected()&0x78) && (!display13TCProbeReady || !display13FBTCHooksReady)) || + (adlnOutputWiring.dp && (!display13TCDPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; + config.combo=adlnOutputWiring.selected()&3;config.comboBackend=3; + config.legacy=adlnOutputWiring.selected()&0x78;config.legacyBackend=config.legacy?0x78:0; } const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound); @@ -4095,6 +4212,56 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // This must precede tglLoadHDMIWiring, tglPrepareMMIOUC and TGL FIA reads. auto physicalGPU = OSDynamicCast(IOPCIDevice, info->videoBuiltin); selectProfile(XELP::deviceProfile(physicalGPU)); + // ADLP_LOAD_ONLY_KERNEL: do not enter TGL MMIO/FIA, bootstrap or VM setup. + const auto physicalEntry=XELP::deviceEntry(xeProfile.identity); + adlpLoadOnly=physicalEntry && xeProfile.mobileDisplay13() && + XELP::hostOS(XELP::kernelOS(version)) && checkKernelArgument("-wex-adlp-load-only"); + if(adlpLoadOnly) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyKernel",true); + SYSLOG("igfx","ADL-P load-only: native FB/accelerator probes will be rejected; no GPU initialization"); + return; + } + // ADLP_LOAD_ONLY_KERNEL_END + // Explicit diagnostic keeps both native drivers blocked even after the + // mobile family is admitted automatically. Only the bounded readout runs. + if(xeProfile.mobileDisplay13() && physicalGPU && checkKernelArgument("-wex-adlp-readout") && + XELP::hostOS(XELP::kernelOS(version))) { + adlpLoadOnly=true; + if(!(physicalGPU->configRead16(4)&2))return; + auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { + struct IO { + volatile uint8_t *base; + bool readable(uint32_t o) const {return o<=0xFFFFFC;} + uint32_t read(uint32_t o) {return *reinterpret_cast(base+o);} + void write(uint32_t o,uint32_t v) {*reinterpret_cast(base+o)=v;} + void delay() {IODelay(100);} + } io {reinterpret_cast(map->getVirtualAddress())}; + uint32_t edram=UINT32_MAX; + auto gt=AlderLakeN::readGT(io,true,&edram,XELP::decodeADLPGT); + physicalGPU->setProperty("ADLPGTBoot",>,sizeof(gt)); + physicalGPU->setProperty("ADLPEDRAM",edram,32); + uint32_t registers[][2] { + {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, + {0x46000,0},{0x46004,0},{0x46070,0},{0x46078,0}, + {0x46038,0},{0x46040,0},{0x46048,0},{0x46050,0}, + {0x4610C,0},{0x46110,0},{0x46114,0},{0x46118,0}, + {0xC4000,0},{0xC4030,0},{0xC5014,0},{0x44470,0}, + {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0},{0x64500,0},{0x64600,0}, + {0x161500,0},{0x161504,0},{0x161508,0},{0x16150C,0}, + {0x1010A0,0},{0x1082C0,0},{0x1082C4,0} + }; + for(auto ®:registers)reg[1]=io.read(reg[0]); + physicalGPU->setProperty("ADLPDisplayBoot",registers,sizeof(registers)); + auto display=AlderLakeN::readDisplay(io,true); + physicalGPU->setProperty("ADLPScanoutBoot",&display,sizeof(display)); + SYSLOG("igfx","ADL-P readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x; donor blocked", + static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], + gt.releasedMask,gt.releaseFailedMask); + } + if(map)map->release(); + return; + } if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) && !xeProfile.overridden() && physicalGPU && XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { @@ -4139,7 +4306,8 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } if(map)map->release(); } - if(!xeProfile.admitted())return; + if(!xeProfile.displayAdmitted())return; + SYSLOG("igfx","Xe display bootstrap physical=%x FB-only=%u",xeProfile.identity,adlpDisplaySelected); xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; if (adlnDetected || display13FramebufferRequested) { xelpReadFirmware(physicalGPU); @@ -4147,7 +4315,8 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { modDVMTCalcFix.processKernel(patcher,info); xelpLoadDisplayPool(physicalGPU); adlnBootstrap(physicalGPU); - if (adlnNativeRequested && adlnNativeReady) { + SYSLOG("igfx","Display13 bootstrap ready=%u",display13BootReady); + if (display13BootReady) { tglVMPrepare(patcher,physicalGPU); tglPrepareMMIOUC(patcher,physicalGPU); auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10); @@ -4203,6 +4372,12 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { } bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { + // No full donor patch set or native display/GT dispatcher in this stage. + if(adlpLoadOnly) { + if(index==kextIntelTGL.loadIndex || index==kextIntelTGLFb.loadIndex) + return processADLPLoadOnly(patcher,index,address,size); + return false; + } if((tglRequested || display13FramebufferRequested) && kextAppleIntelTGLCompat.loadIndex==index) { const auto probe=patcher.solveSymbol(index,"__ZN17AppleIntelPortHAL13probePortModeEv",address,size); patcher.clearError(); @@ -4216,16 +4391,26 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add patcher.clearError(); display13DPProbeReady=query>=address && query-address(query)); + display13TCProbeReady=query>=address && query-address(query)); + display13TCDPProbeReady=query>=address && query-address(query)); } IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13DPProbeAvailable",display13DPProbeReady); + IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13TCProbeAvailable",display13TCProbeReady); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); if(adlnRequested)IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); return true; } // Display dispatch is independent of the selected Render implementation. // Keep both adapters outside the large Display12 framebuffer patch frame. - if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) - return processDisplay13Framebuffer(patcher,index,address,size); + if(currentFramebuffer && currentFramebuffer->loadIndex==index && + (display13FramebufferRequested || (tglRequested && tglActive))) + return processFramebufferKext(patcher,index,address,size); + if(adlpDisplaySelected && !adlpRenderRequested) { + if(index==kextIntelTGL.loadIndex)return processADLPLoadOnly(patcher,index,address,size); + return false; + } if (adlnRequested || adlnDetected) { if (!adlnDetected || !adlnNativeRequested || !currentGraphics || currentGraphics->loadIndex != index) return false; // Matching is persistent. A rejected bootstrap must not leave the @@ -4278,6 +4463,14 @@ struct Display13FBPipeContext { uint64_t watermarkClockHz {}; }; struct Display13FBPowerContext { + IOSimpleLock *tcBankLock {}; + bool tcSupported {},tcBankFaulted {}; + Display13TC::Mode tcMode[4]; + OSObject *tcObject[4] {}; + uint32_t tcBusy[4] {}; + bool tcSignalReady[4] {}; + bool bootDP {}; // Board protocol captured with the protected boot reference. + Display13TC::DPRetainedLink tcDPLink[4]; Display13FBPipeContext pipe[3]; Display13Power::NativeBridge bridge; Display13Power::MainLinkAUX mainLink[2]; @@ -4535,7 +4728,45 @@ static void adlnIRQRecord(uint64_t (&r)[32],unsigned event) { r[29]=reinterpret_cast(c.lock);r[30]=c.handlers;r[31]=c.quiescing|(c.blockedPipes<<8)|(uint64_t(c.softwareHandlers)<<32); } +// These operations run under the display owner lock. AUX power qualification +// needs the TC bank lock but never enters the native port or power callbacks. +struct Display13FBTCAuxIO { + IOMemoryMap *map;unsigned tc; + bool admitted(unsigned candidate) const { + auto &c=display13FBPower; + return candidate==tc && tc<4 && c.tcSupported && c.tcBankLock && !c.tcBankFaulted && + c.bridge.begun && (c.bridge.selectedPorts&(1U<<(tc+3))) && map==c.map && + map && map->getLength()==0x1000000 && map->getVirtualAddress(); + } + bool readable(uint32_t r) const {return admitted(tc) && !(r&3) && r<=0xfffffc;} + uint32_t read(uint32_t r) const {return *reinterpret_cast(map->getVirtualAddress()+r);} + void write(uint32_t r,uint32_t v) {*reinterpret_cast(map->getVirtualAddress()+r)=v;} + bool lanesPowered() const { + if(!admitted(tc))return false; + const auto pw=read(Display13Power::Driver),buffer=read(0x64300+0x100*tc),mode=read(0x161500+4*tc); + return pw!=UINT32_MAX && buffer!=UINT32_MAX && mode!=UINT32_MAX && (pw&5)==5 && + (buffer&64) && (mode&7)==4; + } + bool auxPowered(unsigned candidate) const { + if(!admitted(candidate))return false;const auto value=read(Display13Power::AUX); + return value!=UINT32_MAX && (value&(1U<<(2*(tc+3)))); + } + IOInterruptState lock(){return IOSimpleLockLockDisableInterrupt(display13FBPower.tcBankLock);} + void unlock(IOInterruptState irq){IOSimpleLockUnlockEnableInterrupt(display13FBPower.tcBankLock,irq);} + void bankFault(){display13FBPower.tcBankFaulted=true;} +}; +static bool display13TCAuxSupported() {return display13FBPower.tcSupported && display13FBPower.tcBankLock && !display13FBPower.tcBankFaulted;} +static bool display13TCPrepareAUX(IOMemoryMap *map,unsigned tc) { + Display13FBTCAuxIO io{map,tc};return Display13TC::Aux::prepare(io,tc).status==Display13TC::Status::Complete; +} +static bool display13TCHealthyAUX(IOMemoryMap *map,unsigned tc) { + Display13FBTCAuxIO io{map,tc};bool healthy=false; + return Display13TC::Aux::health(io,tc,healthy).status==Display13TC::Status::Complete && healthy; +} struct Display13FBPowerIO { + bool tcAuxSupported(){return display13TCAuxSupported();} + bool prepareTCAux(unsigned tc){return display13TCPrepareAUX(map,tc);} + bool tcAuxHealthy(unsigned tc){return display13TCHealthyAUX(map,tc);} IOMemoryMap *map; bool readable(uint32_t r) const { return map && map->getVirtualAddress() && map->getLength()==0x1000000 && @@ -4561,6 +4792,70 @@ struct Display13FBBootIO : Display13FBPowerIO { return map && map->getVirtualAddress() && map->getLength()==0x1000000 && r<=0x1000000-4; } }; +struct Display13FBTCIO : Display13FBTCAuxIO { + OSObject *object;Display13Power::Result linkResult; + Display13FBTCIO(OSObject *p,unsigned t):Display13FBTCAuxIO{display13FBPower.map,t},object(p) {} + bool accessHeld() { + if(!admitted(tc))return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; + if(!object || c.tcObject[tc]!=object || mode.controller!=reinterpret_cast(c.controller) || + mode.object!=reinterpret_cast(object) || mode.accessLease.owner!=&c.bridge.state || + mode.accessLease.domains!=Display13Power::dependencies(Display13TC::Mode::accessDomains()))return false; + Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.accessLease.domains); + } + bool powerHeld() { + if(!accessHeld())return false;auto &c=display13FBPower;const auto &mode=c.tcMode[tc]; + if(mode.auxLease.owner!=&c.bridge.state || + mode.auxLease.domains!=Display13Power::dependencies(Display13Power::auxDomain(tc+3)))return false; + Display13FBPowerIO io{map};return Display13Power::held(io,c.bridge.state,mode.auxLease.domains); + } + bool linkHeld() { + Display13FBPowerIO io{map};return powerHeld() && display13FBPower.bridge.ddiHeld(io,tc+3); + } + // Native enableClocks/disableClocks also own the DDI reference. Our + // replacement must reproduce that contract after/before the PLL change. + bool linkPower(bool enable) { + auto &c=display13FBPower;Display13FBPowerIO io{map};const auto ddi=tc+3; + if(!Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60))return false; + auto &r=linkResult;r.status=Display13Power::Status::Complete; + if(enable) { + if(!c.bridge.retainsDDI(ddi))r=c.bridge.getDDI(io,ddi); + } else if(c.bridge.nativeDDIRefs(ddi)) { + if(c.bridge.nativeDDIRefs(ddi)!=1)return false; + // The protected GOP ref is transferred by adoptBootPath, not us. + if(!(c.bridge.bootPort==ddi && c.bridge.bootDDIProtected))r=c.bridge.putDDI(io,ddi); + } + return r.status==Display13Power::Status::Complete && + Display13Power::publishObject(c.bridge,c.power,0x80,c.controller,0xC60); + } + bool unstarted() { + // A failed mode acquisition never started a PLL. Cleanup must not + // demand full output power or turn that known local failure global. + auto &c=display13FBPower;Display13TC::Port p; + if(!accessHeld() || !Display13TC::Port::resolve(tc+3,p) || c.bridge.nativeDDIRefs(tc+3) || + !quiescent(tc+3))return false; + const auto pll=read(p.pll); + return pll!=UINT32_MAX && !(pll&0xcc000000U) && !c.tcSignalReady[tc]; + } + bool owned(unsigned ddi) { + const auto &m=display13FBPower.tcMode[tc]; + return ddi==tc+3 && powerHeld() && m.phase==Display13TC::Mode::Phase::Active && + m.ownership.active && m.ownership.identity==reinterpret_cast(object) && + Display13FBMemory::field(object,0x550)==4; + } + bool quiescent(unsigned ddi) { + if(ddi!=tc+3 || !accessHeld())return false; + const auto buffer=read(0x64000+0x100*ddi),power=read(Display13Power::Driver); + if(buffer==UINT32_MAX || power==UINT32_MAX || (buffer&0x80000000))return false; + for(unsigned pipe=0;pipe<3;++pipe)if(power&(1U<<(10+2*pipe))) { + const auto trans=read(0x60400+0x1000*pipe),conf=read(0x70008+0x1000*pipe),clock=read(0x46140+4*pipe); + if(trans==UINT32_MAX || conf==UINT32_MAX || clock==UINT32_MAX)return false; + if(((trans>>27)&15)==ddi+1 && (trans&0x80000000))return false; + if((clock>>28)==ddi+3 && (conf&0xc0000000))return false; + } + return true; + } + void delayUs(unsigned us){IODelay(us);} +}; // Only used after native object/BAR binding and the power lease proof. The // common SST/signal sequencers choose all addresses; no caller register input. struct Display13FBSSTIO { @@ -4772,8 +5067,12 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo uint32_t read(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} uint64_t readPTE(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} } io {c.map}; + const auto tc=Display13TC::Boot::capture(io,bootScanout && c.bridge.bootPort>=3,c.bridge.bootPort,0); + const bool dp=bootScanout && c.bootDP; + const auto source=AlderLakeN::readDisplay(io,dp); return Display13FBMemory::inspect(io,controller,controller->getMetaClass()->getClassSize(), - fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort); + fb,fb->getMetaClass()->getClassSize(),aperture->getLength(),xelpMemory,bootScanout,display13FBExpectedOwner(controller),c.bridge.bootPort, + c.bridge.bootPort>=3 ? &tc : nullptr,dp?&source:nullptr); } } @@ -4787,19 +5086,17 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm "__ZN19AppleIntelPowerWell18enablePowerWellDDIEj", "__ZN19AppleIntelPowerWell19disablePowerWellDDIEj", "__ZN19AppleIntelPowerWell18enablePowerWellAuxEj", - "__ZN19AppleIntelPowerWell19disablePowerWellAuxEj", - "__ZN31AppleIntelFramebufferController5startEP9IOService" + "__ZN19AppleIntelPowerWell19disablePowerWellAuxEj" }; - static const uintptr_t offsets[] {0x59F88,0x5BEDC,0x5B366,0x5BFA8,0x5AA86,0x5C4BA,0x5AE0A,0x3F1FC}; - static const uint8_t prefixes[8][24] { + static const uintptr_t offsets[] {0x59F88,0x5BEDC,0x5B366,0x5BFA8,0x5AA86,0x5C4BA,0x5AE0A}; + static const uint8_t prefixes[7][24] { {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05,0xe8,0x76,0x08,0x00}, {0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0xac,0x5e,0x08,0x00,0x85,0xf6,0x74,0x18,0xb8,0x01,0x00,0x00}, {0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x5a,0x67,0x08,0x00,0x85,0xf6,0x74,0x23,0xb8,0x01,0x00,0x00}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x48,0xff,0x05,0x23,0x5e,0x08,0x00,0x89,0xf0,0x8b}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18,0x48,0xff,0x05,0x92,0x6e,0x08,0x00}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x48,0x48,0xff,0x05,0x26,0x5a,0x08,0x00}, - {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00}, - {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x49,0x89,0xf6,0x49,0x89,0xfd,0x48} + {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00} }; bool known=display13FramebufferRequested && size>0x5C4D2; for(unsigned i=0;i(address+0x3FE26),ownerStore,sizeof(ownerStore)); if(!known)return false; if(!display13FBPower.lock)display13FBPower.lock=IOLockAlloc(); - KernelPatcher::RouteRequest start(symbols[7],wrapDisplay13FramebufferStart,orgDisplay13FramebufferStart); - const bool startRouted=patcher.routeMultiple(index,&start,1,address,size);patcher.clearError(); - if(!startRouted)return false; // Apply DVMT calculation before routing FBMemMgr_Init: the common // module scans its native entry, not our later trampoline. Startup is // already guarded, so missing opt-in/failed patch cannot run the bad formula. @@ -4979,11 +5273,13 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm display13FBIRQReady=processDisplay13FramebufferIRQ(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBIRQHooksReady",display13FBIRQReady); - display13FBABIReady=processDisplay13FramebufferABI(patcher,index,address,size); + display13FBABIReady=framebufferDonorCommonReady; IORegistryEntry::getRegistryRoot()->setProperty("N97FBCommonABIReady",display13FBABIReady); display13FBLifecycleHooksReady=processDisplay13FramebufferLifecycle(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBLifecycleHooksReady",display13FBLifecycleHooksReady); display13FBOutputHooksReady=processDisplay13FramebufferOutput(patcher,index,address,size); + display13FBTCHooksReady=processDisplay13FramebufferTC(patcher,index,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCHooksReady",display13FBTCHooksReady); IORegistryEntry::getRegistryRoot()->setProperty("N97FBOutputHooksReady",display13FBOutputHooksReady); display13FBGTTHooksReady=processDisplay13FramebufferGTT(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); @@ -5066,7 +5362,127 @@ bool XE::processDisplay13FramebufferLifecycle(KernelPatcher &patcher,size_t inde !memcmp(reinterpret_cast(address+0x68C38),removeCall,sizeof(removeCall)); KernelPatcher::RouteRequest pathRoute {pathSymbol,wrapAlderLakeNFreePath,orgAlderLakeNFreePath}; const bool pathRouted=disableRouted && pathKnown && patcher.routeMultipleShort(index,&pathRoute,1,address,size); - patcher.clearError();return pathRouted; + patcher.clearError(); + return pathRouted && (!Display13FBTopology::multiOutputWiring(adlnOutputWiring) || + processDisplay13Retirement(patcher,index,address,size)); +} + +// These hooks are installed only for Display13's existing multi-output owner. +// Keep native startup, StopTransactions, disableDisplay and successful cleanup. +bool XE::processDisplay13Retirement(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace Display13Retirement; + const char *freeAll="__ZN21AppleIntelFramebuffer20freeDisplayResourcesEv"; + const char *allocate="__ZN21AppleIntelFramebuffer24allocateDisplayResourcesEv"; + const uint8_t freeEntry[]={0x55,0x48,0x89,0xe5,0x53,0x50}; + const uint8_t allocateEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x18}; + const auto f=patcher.solveSymbol(index,freeAll,address,size);patcher.clearError(); + const auto a=patcher.solveSymbol(index,allocate,address,size);patcher.clearError(); + if(size<=ReleaseCall+sizeof(ReleaseBefore) || f!=address+0x67e8e || a!=address+0x67272 || + memcmp(reinterpret_cast(f),freeEntry,sizeof(freeEntry)) || + memcmp(reinterpret_cast(a),allocateEntry,sizeof(allocateEntry)) || + memcmp(reinterpret_cast(address+Loop),Before,sizeof(Before)) || + memcmp(reinterpret_cast(address+ReleaseCall),ReleaseBefore,sizeof(ReleaseBefore)))return false; + uint8_t after[sizeof(Before)];patch(after,reinterpret_cast(retireDisplay13Paths)); + const struct {uint32_t offset;const uint8_t *before,*after;size_t bytes;} changes[]={ + {Loop,Before,after,sizeof(Before)}, + {ReleaseCall,ReleaseBefore,ReleaseAfter,sizeof(ReleaseBefore)}}; + for(const auto &change:changes) { + KernelPatcher::LookupPatch edit{currentFramebuffer,change.before,change.after,change.bytes,1}; + patcher.applyLookupPatch(&edit,reinterpret_cast(address+change.offset),change.bytes+1); + const bool ok=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+change.offset),change.after,change.bytes); + patcher.clearError();if(!ok)return false; + } + // Short prologues end before the native RIP-relative counters. + KernelPatcher::RouteRequest routes[]={ + {freeAll,wrapDisplay13FreeResources,orgDisplay13FreeResources}, + {allocate,wrapDisplay13AllocateResources,orgDisplay13AllocateResources}}; + const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13RetirementReady",ready); + return ready; +} + +bool XE::display13RetirementHealthy() { + if(!callbackXE->display13FramebufferRequested || + !Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring))return true; + auto &c=display13FBPower;if(!c.lock)return false; + IOLockLock(c.lock);const bool ok=!c.memoryFaulted && !c.dbuf.faulted;IOLockUnlock(c.lock); + return ok; +} + +bool XE::wrapDisplay13AllocateResources(void *fb) { + // Failed retirement retains the native allocated flag/path and WEX owner. + // Do not start another allocation after a partial native memory cleanup. + if(!display13RetirementHealthy())return false; + return FunctionCast(wrapDisplay13AllocateResources,callbackXE->orgDisplay13AllocateResources)(fb); +} + +uint32_t XE::retireDisplay13Paths(void *raw) { + auto fb=static_cast(raw);auto &c=display13FBPower; + const auto fail=[&]() { + if(c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + return kIOReturnNotReady; + }; + if(!fb || fb->getMetaClass()->getClassSize()<0x4b90 || + Display13FBMemory::field(fb,0x1d0)!=c.controller)return fail(); + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return fail(); + // The free wrapper already holds controller+0x9b8. Acquire FB only after + // native StopTransactions/disableDisplay (whose callbacks also take it). + IORecursiveLockLock(mutex); + uint32_t error=0; + for(unsigned n=0;n<2;++n) { + const auto count=Display13FBMemory::field(fb,0x49e8); + if(!count)break; + auto path=Display13FBMemory::field(fb,0x49f0); + if(count>2 || !path || path->getMetaClass()->getClassSize()<0x3730 || + Display13FBMemory::field(path,0x32b0)!=fb || + Display13FBMemory::field(path,0x3644)==2) {error=fail();break;} + error=releaseDisplay13Path(fb,path); + if(error || Display13FBMemory::field(fb,0x49e8)!=count-1) {error=fail();break;} + } + if(!error && Display13FBMemory::field(fb,0x49e8))error=fail(); + IORecursiveLockUnlock(mutex);return error; +} + +uint32_t XE::wrapDisplay13FreeResources(void *raw) { + auto fb=static_cast(raw);auto &c=display13FBPower; + if(!fb || fb->getMetaClass()->getClassSize()<0x4b90 || !c.controller || + Display13FBMemory::field(fb,0x1d0)!=c.controller)return kIOReturnNotReady; + auto mutex=Display13FBMemory::field(c.controller,0x9b8); + if(!mutex)return kIOReturnNotReady; + IORecursiveLockLock(mutex); + const auto before=Display13FBMemory::field(fb,0x49e8); + reinterpret_cast(callbackXE->orgDisplay13FreeResources)(raw); + const auto count=Display13FBMemory::field(fb,0x49e8); + const auto allocated=Display13FBMemory::field(fb,0x1e7); + const auto result=display13RetirementHealthy() && !count && !allocated ? 0U : uint32_t(kIOReturnNotReady); + if(result && c.lock){IOLockLock(c.lock);adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock);} + // The native abort epilogue preserves allocated/path count. The memory + // adapter also keeps a sticky fault if suffix cleanup could not finish. + IORecursiveLockUnlock(mutex); + uint32_t record[]={1,Display13FBMemory::field(fb,0x1dc),before,count,allocated,result}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13Retirement",record,sizeof(record)); + return result; +} + +bool XE::retireDisplay13Controller(void *raw) { + auto s=callbackXE; + if(!s->display13FramebufferRequested || !Display13FBTopology::multiOutputWiring(s->adlnOutputWiring))return true; + auto controller=static_cast(raw); + if(!controller || controller!=display13FBPower.controller || controller->getMetaClass()->getClassSize()<0x1c20 || + !s->orgDisplay13FreeResources || !display13RetirementHealthy())return false; + const auto count=Display13FBMemory::field(controller,0xd5c);if(count>3)return false; + for(unsigned i=0;i(controller,0xd60+8*i); + if(!fb)continue; + if(fb->getMetaClass()->getClassSize()<0x4b90 || Display13FBMemory::field(fb,0x1d0)!=raw)return false; + if((Display13FBMemory::field(fb,0x1e7) || Display13FBMemory::field(fb,0x49e8)) && + wrapDisplay13FreeResources(fb))return false; + } + auto &c=display13FBPower;IOLockLock(c.lock);bool clear=!c.memoryFaulted; + for(const auto &pipe:c.pipe)clear=clear && !pipe.memoryFramebuffer && !pipe.publishedScanout; + for(const auto &slot:display13FBSysMem.slot)clear=clear && !slot.buffer; + IOLockUnlock(c.lock);return clear; } bool XE::processDisplay13FramebufferDBUF(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { @@ -5378,7 +5794,7 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { if(!callbackXE->display13FBIRQReady || !adlnIRQOwner(controller))return kIOReturnNotReady; auto &c=display13FBIRQ;uint64_t record[32] {},check[42] {3};Display13FBIRQIO io {c.base}; const auto excluded=controller==callbackXE->adlnTopologyController ? - Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; + Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; const auto saved=IOSimpleLockLockDisableInterrupt(c.lock); // Preserve enable evidence even when start rejects its unpublished // controller. Inspect only pipe banks already qualified as powered. @@ -5450,7 +5866,7 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && enabled && c.state.begun && c.state.enabled && !c.state.active && !c.state.faulted && Display13FBIRQ::powered(io.read(Display13FBIRQ::Driver),pipe) && (io.read(0x70008+0x1000*pipe)&Display13FBIRQ::Enable)) { const auto excluded=controller==callbackXE->adlnTopologyController ? - Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; + Display13FBTopology::excludedHotplug(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); } if(!c.state.faulted)c.status=Display13FBIRQ::vblank(io,c.state,pipe,enabled); @@ -5777,7 +6193,7 @@ uint32_t XE::adlnRequestedSharedReservation(IOService *controller) { const auto prefix=(uint64_t(Display13FBMemory::field(controller,0xDF8))+0xFFF)&~0xFFFULL; return prefix<0x10000000 ? static_cast(prefix) : 0; } - if(!Display13FBTopology::adlnWiring(callbackXE->adlnOutputWiring))return 0; + if(!Display13FBTopology::xeLPDWiring(callbackXE->adlnOutputWiring))return 0; // Allocation/free may change descriptor/state fields later. The validated // fixed layout's registration promise is stable across those transitions. const auto prefix=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); @@ -5791,9 +6207,14 @@ IOReturn XE::wrapDisplay13FramebufferPlatformCall(IOService *controller,const OS controller->getMetaClass()->getClassSize()>=0x1C20 && name && name==Display13FBMemory::field(controller,0x1560); if(!fixed)return FunctionCast(wrapDisplay13FramebufferPlatformCall,self->orgDisplay13FramebufferPlatformCall)(controller,name,wait,arg1,arg2,arg3,arg4); - const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(),0x10000000); + // Disabled SysMem windows are native descriptions, not aperture allocations. + // A large stolen prefix can place their unused tail beyond the aperture. + // sharedReservation rejects every occupied secondary window and bounds the + // actual promised prefix to the aperture before any native registration. + const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(), + adlnFixedSysMemEnabled ? 0x10000000 : UINT32_MAX); const auto plan=Display13FBMemory::sharedReservation(layout,0x10000000,adlnFixedSysMemEnabled); - const bool ready=Display13FBTopology::adlnWiring(self->adlnOutputWiring) && plan.valid && + const bool ready=Display13FBTopology::xeLPDWiring(self->adlnOutputWiring) && plan.valid && display13FBGTT.initialized && !display13FBGTT.faulted && !display13FBPower.memoryFaulted && arg1 && arg1==__atomic_load_n(&adlnSharedOwner,__ATOMIC_ACQUIRE) && arg2 && arg3; if(!ready)return kIOReturnNotReady; @@ -5956,7 +6377,7 @@ bool XE::processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { auto s=callbackXE; if(!s->display13FBTopologyHooksReady || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !Display13FBTopology::supportedWiring(s->adlnOutputWiring) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring,s->xeProfile.display==XELP::Display::XeLPD) || !checkKernelArgument("-disablegfxfirmware"))return false; if(!FunctionCast(wrapAlderLakeNGetOSInformation,s->orgAlderLakeNGetOSInformation)(controller))return false; auto platform=Display13FBMemory::field(controller,0xD40); if(!Display13FBTopology::apply(s->adlnPlatform,sizeof(s->adlnPlatform),platform,s->adlnOutputWiring))return false; @@ -5987,6 +6408,13 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { if(!s->display13FBTopologyHooksReady || !s->adlnTopologyController || !port || port->getMetaClass()->getClassSize()<0x580)return 0; auto config=Display13FBMemory::field(port,0x548); if(Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring)) { + // Keep controller/memory/IRQ initialization reachable while isolating + // HPD, EDID and mode programming during framebuffer bring-up. + if(display13Offline) { + *reinterpret_cast(reinterpret_cast(port)+0x554)=0; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13OfflineProbe",true); + return 0; + } // CPU binding only. TC bits are added when mode/AUX/PLL/GOP become // qualified together; a board description alone is insufficient. if(!s->xelpBindProbe(s->adlnTopologyController)) { @@ -6012,6 +6440,367 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { return 0; } +unsigned XE::display13TCTarget(OSObject *port,bool cleanup) { + auto &c=display13FBPower;auto self=callbackXE; + if(!self->display13FBTCHooksReady || !c.tcSupported || !c.controller || !c.bridge.begun || + c.tcBankFaulted || c.bridge.state.faulted || (!cleanup && c.clockFaulted) || !port || port->getMetaClass()->getClassSize()<0x6b0)return UINT32_MAX; + auto config=Display13FBMemory::field(port,0x548); + if(!config || !self->adlnOutputWiring.matches(config) || config[0]<3 || config[0]>6 || + !Display13FBTopology::bound(self->adlnPlatform,config,config[0]) || + Display13FBMemory::field(port,0x578)!=c.access || + Display13FBMemory::field(port,0x120)!=config[0])return UINT32_MAX; + return config[0]; +} + +uint32_t XE::wrapDisplay13TCMode(OSObject *port,uint32_t wanted) { + auto self=callbackXE;auto &c=display13FBPower; + auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548) : nullptr; + if(config && config[0]<3)return FunctionCast(wrapDisplay13TCMode,self->orgDisplay13TCMode)(port,wanted); + if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const auto ddi=display13TCTarget(port,wanted==0); + if(ddi>=7 || (wanted!=0 && wanted!=4) || c.tcBusy[ddi-3] || + (c.tcObject[ddi-3] && c.tcObject[ddi-3]!=port)) {IOLockUnlock(c.lock);return kIOReturnNotReady;} + const auto tc=ddi-3;auto &mode=c.tcMode[tc]; + if(!c.tcObject[tc]){c.tcObject[tc]=port;port->retain();} + struct IO : Display13FBTCIO { + using Preparation=XELP::LinkRequirements::Preparation; + uint32_t (*base)(OSObject *,uint32_t);Preparation preparation; + IO(OSObject *p,unsigned t,uint32_t (*f)(OSObject *,uint32_t),Preparation selected):Display13FBTCIO(p,t),base(f),preparation(selected) {} + bool admissionReady(){return base && admitted(tc) && + (preparation==Preparation::ColdTC13 || preparation==Preparation::FirmwareTC13);} + uint32_t nativeMode(){return Display13FBMemory::field(object,0x550);} + uint32_t nativeSetMode(uint32_t value) { + // mode0 invokes the native reset callback. Retain this exact port + // transaction, but drop the owner lock across possible callbacks. + auto &c=display13FBPower;++c.tcBusy[tc];IOLockUnlock(c.lock); + auto result=base(object,value);IOLockLock(c.lock);--c.tcBusy[tc];return result; + } + Display13Power::Result acquireDomains(Display13Power::Lease &lease,uint32_t domains) { + Display13FBPowerIO io{map};return Display13Power::acquire(io,display13FBPower.bridge.state,lease,domains,true); + } + Display13Power::Result releaseDomains(Display13Power::Lease &lease) { + Display13FBPowerIO io{map};return Display13Power::release(io,display13FBPower.bridge.state,lease); + } + bool powerHeld(uintptr_t controller,uintptr_t port,unsigned d) { + return controller==reinterpret_cast(display13FBPower.controller) && + port==reinterpret_cast(object) && d==tc+3 && Display13FBTCIO::accessHeld(); + } + bool inheritedVerified(uintptr_t controller,uintptr_t port,unsigned d) { + return preparation==Preparation::FirmwareTC13 && controller==reinterpret_cast(display13FBPower.controller) && + port==reinterpret_cast(object) && d==tc+3; + } + bool quiescent(uintptr_t controller,uintptr_t port,unsigned d) { + auto &c=display13FBPower; + if(controller!=reinterpret_cast(c.controller) || port!=reinterpret_cast(object) || + d!=tc+3 || c.tcBusy[tc] || c.bridge.nativeDDIRefs(d) || c.bridge.nativeAUXRefs(d) || + Display13FBMemory::field(object,0x570) || !Display13FBTCIO::quiescent(d))return false; + Display13TC::Port p;Display13TC::Port::resolve(d,p); + const auto pll=read(p.pll),aux=read(0x64310+0x100*tc); + return pll!=UINT32_MAX && aux!=UINT32_MAX && !(pll&0xc0000000) && !(aux&0x80000000); + } + }; + // Adopt only the validated, protected firmware boot route. Merely finding + // a set ownership bit on another connector is not a handoff certificate. + Display13FBBootIO bootIO{c.map}; + const bool dpBoot=ddi==c.bridge.bootPort && (self->adlnOutputWiring.dp&(1U<adlnOutputWiring,bootIO.read(0x60400),true)==ddi && + (bootIO.read(0x70008)&0xc0000000)==0xc0000000 && boot.unchanged(bootIO); + if(inherited && dpBoot) { + const auto source=AlderLakeN::readDisplay(bootIO,true); + inherited=source.activePipes==1 && AlderLakeN::dpSourceUnchanged(bootIO,source,0,ddi,0,&boot); + } + const auto preparation=framebufferLinkRequirements(port,4).preparation(inherited); + IO io{port,tc,reinterpret_cast(self->display13TCBaseMode),preparation}; + const auto owner=reinterpret_cast(c.controller),object=reinterpret_cast(port); + const auto result=wanted?mode.connect(io,owner,object,ddi):mode.disconnect(io,owner,object,ddi); + const bool done=result.status==Display13TC::Status::Complete; + if(!wanted && done){c.tcSignalReady[tc]=false;c.tcDPLink[tc]={};OSSafeReleaseNULL(c.tcObject[tc]);} + uint32_t record[]={1,ddi,wanted,uint32_t(result.status),uint32_t(result.phy.status),uint32_t(result.power.status),uint32_t(mode.phase),mode.disconnectPending}; + IOLockUnlock(c.lock);auto root=IORegistryEntry::getRegistryRoot(); + root->setProperty("WEXDisplay13TCMode",record,sizeof(record)); + // Keep the initiating power-domain result; subsequent native getPG errors + // can only observe the shared fault and cannot reconstruct this boundary. + if(result.status==Display13TC::Status::NoPower) { + uint32_t power[]={1,ddi,wanted,uint32_t(result.power.status),result.power.domain, + result.power.writes,result.power.rollbackFailed,result.power.forcedOn,result.power.isolated}; + if(!root->getProperty("WEXDisplay13TCFirstPowerError"))root->setProperty("WEXDisplay13TCFirstPowerError",power,sizeof(power)); + } + if(!done && !root->getProperty("WEXDisplay13TCModeFailure")) { + root->setProperty("WEXDisplay13TCModeFailure",record,sizeof(record)); + uint32_t detail[] {1,ddi,result.phy.before,result.phy.after,result.phy.writes,result.phy.elapsedUs}; + root->setProperty("WEXDisplay13TCModeFailureState",detail,sizeof(detail)); + } + return done ? kIOReturnSuccess : kIOReturnNotReady; +} + +void XE::wrapDisplay13TCEnableClocks(void *controller,void *raw,void *other,uint32_t *params) { + auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; + auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; + if(config && config[0]<3) { + bool keep=false; + if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,true,keep,other)) { + adlnFault(c.clockFaulted,__LINE__);return; + } + FunctionCast(wrapDisplay13TCEnableClocks,self->orgDisplay13TCEnableClocks)(controller,raw,other,params); + comboEndClock();return; + } + if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); + bool ok=false;uint32_t rate=0;Display13Power::Result power; + auto stage=Display13TC::Clock::LinkStage::Preflight; + if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { + const auto tc=ddi-3;Display13FBTCIO io{port,tc};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); + if(self->adlnOutputWiring.dp&(1U<(port,0x128); + const auto lanes=Display13FBMemory::field(port,0x124); + const auto plan=Display13TC::DPClock::calculate(linkRate,c.clock.refKHz);rate=plan.rate; + ok=c.tcSignalReady[tc] && c.tcDPLink[tc].matches(io,p,plan,lanes); + if(ok)stage=Display13TC::Clock::LinkStage::Complete; + else { + ok=Display13TC::DPSignal::startLink(io,p,plan,lanes,&stage); + if(ok)c.tcDPLink[tc]={plan.rate,plan.reference,lanes}; + } + } else { + rate=Display13TC::TMDSClock::nativeRate(params,c.clock.refIndex); + const auto plan=Display13TC::TMDSClock::calculate(rate,c.clock.refKHz); + ok=Display13TC::Clock::startLink(io,p,plan,self->firmwarePorts.port[ddi].hdmiPreset,&stage); + } + power=io.linkResult; + c.tcSignalReady[tc]=ok; + } + if(!ok)adlnFault(c.clockFaulted,__LINE__); + uint32_t record[]={2,ddi,rate,ok,uint32_t(stage),uint32_t(power.status),power.domain,power.writes,power.rollbackFailed};IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockEnable",record,sizeof(record)); +} +void XE::wrapDisplay13TCDisableClocks(void *controller,void *raw,void *other) { + 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) { + bool keep=false; + if(!comboClockTarget(controller,raw,other) || !comboBeginClock(controller,raw,false,keep)) { + adlnFault(c.clockFaulted,__LINE__);return; + } + FunctionCast(wrapDisplay13TCDisableClocks,self->orgDisplay13TCDisableClocks)(controller,raw,keep?raw:nullptr); + comboEndClock();return; + } + // Native AGDC End ignores the port-disable return. Independently require + // transcoder stop before dropping DDI/PLL, including retry and abort paths. + if(config && config[0]<7 && (self->adlnOutputWiring.dp&(1U<display13ClockOrderReady && + !display13StopTranscoder(raw)) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ClockStopRejected",true);return; + } + if(!c.lock)return;IOLockLock(c.lock);const auto ddi=display13TCTarget(port,true);bool ok=false; + if(controller==c.controller && (!other || other==raw) && ddi<7 && !c.tcBusy[ddi-3]) { + Display13FBTCIO io{port,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); + ok=Display13TC::Clock::stopLink(io,p); + if(ok){c.tcSignalReady[ddi-3]=false;c.tcDPLink[ddi-3]={};} + } + if(!ok)adlnFault(c.clockFaulted,__LINE__); + uint32_t record[]={1,ddi,ok};IOLockUnlock(c.lock); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TCClockDisable",record,sizeof(record)); +} +uint32_t XE::wrapDisplay13TCSignal(void *raw,uint32_t swing,uint32_t emphasis,void *drive) { + auto self=callbackXE;auto port=static_cast(raw);auto &c=display13FBPower; + auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548):nullptr; + if(config && config[0]<3)return FunctionCast(wrapDisplay13TCSignal,self->orgDisplay13SignalLevels)(raw,swing,emphasis,drive); + if(!c.lock)return kIOReturnNotReady;IOLockLock(c.lock);const auto ddi=display13TCTarget(port); + // TMDS signal parameters were programmed while the path was stopped, + // before native enablePipe. Do not run TGL DP's late voltage sequence. + bool ok=false;if(ddi<7){ + Display13FBTCIO io{port,ddi-3};ok=io.owned(ddi) && io.linkHeld() && c.tcSignalReady[ddi-3]; + if(ok && (self->adlnOutputWiring.dp&(1U<(port,0x128),swing,emphasis); + } + } + if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); + return ok ? 0 : kIOReturnNotReady; +} + +uint32_t XE::wrapDisplay13DPLightUp(void *controller,void *fb,void *path,const void *timing) { + auto object=static_cast(path); + if(!object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; + auto port=Display13FBMemory::field(path,0x3648); + if(port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(port,0x120)>=3) + return wrapDisplay13TCLightUp(controller,fb,path,timing); + return FunctionCast(wrapDisplay13DPLightUp,callbackXE->orgDisplay13DPLightUp)(controller,fb,path,timing); +} +uint32_t XE::wrapDisplay13DPLaneMask(void *port) { + auto s=callbackXE;auto object=static_cast(port); + auto config=object && object->getMetaClass()->getClassSize()>=0x580?Display13FBMemory::field(port,0x548):nullptr; + if(!config || config[0]>=7 || !(s->adlnOutputWiring.dp&(1U<orgDisplay13DPLaneMask)(port); + auto &c=display13FBPower;if(!c.lock)return 0;IOLockLock(c.lock); + const auto ddi=display13TCTarget(object);bool ok=false; + if(ddi>=3 && ddi<=6){Display13FBTCIO io{object,ddi-3};ok=io.owned(ddi);} + IOLockUnlock(c.lock);return ok?15U:0; +} +void XE::wrapDisplay13DPPhyGating(void *port,bool gate) { + auto s=callbackXE;auto object=static_cast(port); + auto config=object && object->getMetaClass()->getClassSize()>=0x580?Display13FBMemory::field(port,0x548):nullptr; + if(!config || config[0]>=7 || !(s->adlnOutputWiring.dp&(1U<orgDisplay13DPPhyGating)(port,gate);return; + } + auto &c=display13FBPower;if(!c.lock)return;IOLockLock(c.lock); + const auto ddi=display13TCTarget(object);bool ok=false; + if(ddi>=3 && ddi<=6) { + Display13FBTCIO io{object,ddi-3};Display13TC::Port p;Display13TC::Port::resolve(ddi,p); + ok=io.owned(ddi) && io.linkHeld(); + // Same fixed-DP contract as TGL: Linux leaves DP_MODE bits5:1 + // intact during training. Do not run the donor's clear operation. + if(ok && gate)for(unsigned group=0;group<2;++group) { + uint32_t value=0x3e;ok=Display13TC::PHY::access(io,p,0x1000*group+0xa0,0x3e,value,true) && ok; + } + } + if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock); +} +void XE::wrapDisplay13DPPortWrite(void *port,uint32_t reg,uint32_t value) { + auto s=callbackXE; + if(reinterpret_cast(__builtin_return_address(0))==s->display13DPImage+0x5fba0) { + auto object=static_cast(port);auto &c=display13FBPower; + if(c.lock && object && object->getMetaClass()->getClassSize()>=0x580) { + IOLockLock(c.lock);const auto ddi=display13TCTarget(object); + if(ddi>=3 && ddi<=6 && (s->adlnOutputWiring.dp&(1U<(port,0x128); + if(io.owned(ddi) && io.linkHeld() && (rate==6 || rate==10 || rate==20 || rate==30)) + value=(value&~0xff00U)|(((rate*27+9)/10)<<8); + else {adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return;} + } + IOLockUnlock(c.lock); + } + } + FunctionCast(wrapDisplay13DPPortWrite,s->orgDisplay13DPPortWrite)(port,reg,value); +} + +uint32_t XE::wrapDisplay13TCLightUp(void *controller,void *rawFB,void *rawPath,const void *timing) { + auto self=callbackXE;auto &c=display13FBPower; + auto fb=static_cast(rawFB);auto path=static_cast(rawPath); + if(!c.lock || !path || path->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; + auto port=Display13FBMemory::field(path,0x3648); + if(port && port->getMetaClass()->getClassSize()>=0x580 && Display13FBMemory::field(port,0x120)<3) + return FunctionCast(wrapDisplay13TCLightUp,self->orgDisplay13TCLightUp)(controller,rawFB,rawPath,timing); + struct IO { + XE *self;void *controller;IOService *fb;OSObject *path,*object;const void *timing; + unsigned ddi {UINT32_MAX},pipeIndex {UINT32_MAX}; + bool dp {}; + bool ready(bool clock) { + auto &c=display13FBPower;IOLockLock(c.lock); + const auto current=display13TCTarget(object); + const auto binding=adlnInspectPathBinding(fb,path,self->adlnPlatform,self->adlnOutputWiring,false); + auto scaler=Display13FBMemory::field(path,0x32C0); + Display13FBPowerIO power{c.map}; + bool ok=controller==c.controller && timing && current<7 && current==ddi && + binding.status==TGLFBBinding::Status::Complete && binding.ddi==ddi && binding.pipe==pipeIndex && + (!dp || !Display13FBMemory::field(object,0x117)) && + scaler && scaler->getMetaClass()->getClassSize()>=0x58 && + Display13FBMemory::field(scaler,0x10)==fb && Display13FBMemory::field(scaler,0x28)==c.access && + !c.memoryFaulted && !c.watermarkFaulted && !c.clockStopped && !c.tcBusy[ddi-3] && + (clock ? c.bridge.outputHeld(power,ddi,pipeIndex) : c.bridge.pipeHeld(power,pipeIndex)); + if(ok){Display13FBTCIO tc{object,ddi-3};ok=tc.owned(ddi) && (!clock || c.tcSignalReady[ddi-3]);} + IOLockUnlock(c.lock);return ok; + } + void clocks() { + auto &c=display13FBPower;IOLockLock(c.lock);if(!dp)c.tcSignalReady[ddi-3]=false;IOLockUnlock(c.lock); + reinterpret_cast(self->display13TCFullClocks)(controller,fb,path); + } + bool transClock(uint32_t reg,uint32_t value) { + auto &c=display13FBPower;IOLockLock(c.lock);Display13FBTCIO tc{object,ddi-3}; + const bool held=tc.owned(ddi) && c.tcSignalReady[ddi-3]; + bool ok=false;if(held){tc.write(reg,value);ok=tc.read(reg)==value;} + if(!ok)adlnFault(c.clockFaulted,__LINE__);IOLockUnlock(c.lock);return ok; + } + void plane(){reinterpret_cast(self->display13TCPlane)(Display13FBMemory::field(path,0x32C8),false);} + void scaler(){reinterpret_cast(self->display13TCScaler)(Display13FBMemory::field(path,0x32C0),path);} + void pipe(){reinterpret_cast(self->display13TCPipe)(controller,fb,path,timing);} + void signal(){wrapDisplay13TCSignal(object,0,0,nullptr);} + uint32_t port(){return wrapDisplay13EnablePort(object);} + uint32_t train(){ + const auto r=wrapDisplay13PortTraining(object,nullptr); + *(reinterpret_cast(fb)+0x4b84)=r!=0;return r; + } + void packets(){ + if(dp)reinterpret_cast(self->display13DPSDPs)(controller,fb,path,timing); + else reinterpret_cast(self->display13TCPackets)(controller,fb,path); + } + } io{self,controller,fb,path,port,timing}; + IOLockLock(c.lock);io.ddi=display13TCTarget(port);io.pipeIndex=Display13FBMemory::field(path,0x3640);IOLockUnlock(c.lock); + io.dp=io.ddi<7 && (self->adlnOutputWiring.dp&(1U<setProperty("WEXDisplay13TCLightUp",record,sizeof(record)); + return result.status; +} + +uint32_t XE::wrapDisplay13TCSetMode(void *controller,void *fb,const void *timing) { + auto self=callbackXE; + return Display13TC::finishMode( + [&](){return FunctionCast(wrapDisplay13TCSetMode,self->orgDisplay13TCSetMode)(controller,fb,timing);}, + [&](){reinterpret_cast(self->display13TCNotify)(controller,fb,1);}); +} + +bool XE::processDisplay13FramebufferTC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace Display13TC::Native; + // Do not alter the successful Combo machines' native mode-return behavior. + if(!(adlnOutputWiring.selected()&0x78) || !display13FBOutputHooksReady || !display13FBSSTHooksReady || + !display13FBABIReady || size<=0x6e000)return false; + for(const auto &e:entries) { + const auto f=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); + if(f!=address+e.offset || memcmp(reinterpret_cast(f),e.prefix,sizeof(e.prefix)))return false; + } + struct Span {uint32_t offset;const uint8_t *bytes;size_t size;}; + const Span spans[]={ + {bootPortInitOffset,bootPortInit,sizeof(bootPortInit)}, + {bootPowerCallOffset,bootPowerCall,sizeof(bootPowerCall)}, + {bootSetupCallOffset,bootSetupCall,sizeof(bootSetupCall)}, + {modeStopCallOffset,modeStopCall,sizeof(modeStopCall)}, + {dpLightCallOffset,dpLightCall,sizeof(dpLightCall)}, + {ddiEnableTailOffset,ddiEnableTail,sizeof(ddiEnableTail)}, + {ddiDisableCallOffset,ddiDisableCall,sizeof(ddiDisableCall)}, + {lightBodyOffset,lightBody,sizeof(lightBody)},{innerBeforeOffset,innerBefore,sizeof(innerBefore)}, + {outerBeforeOffset,outerBefore,sizeof(outerBefore)},{modeExitOffset,modeExit,sizeof(modeExit)}, + {innerCallOffset,innerCall,sizeof(innerCall)},{outerCallOffset,outerCall,sizeof(outerCall)}, + {modeBeginOffset,modeBegin,sizeof(modeBegin)},{modeEndOffset,modeEnd,sizeof(modeEnd)} + }; + for(const auto &span:spans)if(memcmp(reinterpret_cast(address+span.offset),span.bytes,span.size))return false; + if(!display13FBPower.tcBankLock)display13FBPower.tcBankLock=IOSimpleLockAlloc(); + if(!display13FBPower.tcBankLock)return false; + display13TCBaseMode=address+entries[BaseMode].offset; + display13TCNotify=address+entries[Notify].offset;display13TCFullClocks=address+entries[FullClocks].offset; + display13TCPlane=address+entries[Plane].offset;display13TCScaler=address+entries[Scaler].offset; + display13TCPipe=address+entries[Pipe].offset;display13TCPackets=address+entries[Packets].offset; + display13DPTraining=address+entries[Training].offset;display13DPSDPs=address+entries[SDPs].offset; + display13DPImage=address; + KernelPatcher::RouteRequest routes[]={ + {entries[Mode].symbol,wrapDisplay13TCMode,orgDisplay13TCMode}, + {entries[Clocks].symbol,wrapDisplay13TCEnableClocks,orgDisplay13TCEnableClocks}, + {entries[DisableClocks].symbol,wrapDisplay13TCDisableClocks,orgDisplay13TCDisableClocks}, + {entries[LightUp].symbol,wrapDisplay13TCLightUp,orgDisplay13TCLightUp}, + {entries[SetMode].symbol,wrapDisplay13TCSetMode,orgDisplay13TCSetMode}, + {entries[DPLightUp].symbol,wrapDisplay13DPLightUp,orgDisplay13DPLightUp}, + {entries[PhyGating].symbol,wrapDisplay13DPPhyGating,orgDisplay13DPPhyGating}, + {entries[LaneMask].symbol,wrapDisplay13DPLaneMask,orgDisplay13DPLaneMask}, + {entries[PortWrite].symbol,wrapDisplay13DPPortWrite,orgDisplay13DPPortWrite} + }; + // Signal is already routed by the common SST frontend; its wrapper + // dispatches TC here. Never install a second hook over the same entry. + if(!patcher.routeMultipleShort(index,routes,arrsize(routes),address,size)){patcher.clearError();return false;} + patcher.clearError(); + struct Change {uint32_t offset;const uint8_t *before,*after;size_t size;}; + const Change changes[]={ + {innerBeforeOffset,innerBefore,innerAfter,sizeof(innerAfter)}, + {outerBeforeOffset,outerBefore,outerAfter,sizeof(outerAfter)} + }; + for(const auto &change:changes) { + KernelPatcher::LookupPatch patch{currentFramebuffer,change.before,change.after,change.size,1}; + patcher.applyLookupPatch(&patch,reinterpret_cast(address+change.offset),change.size+1); + const bool applied=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+change.offset),change.after,change.size); + patcher.clearError();if(!applied)return false; + } + display13FBDPHooksReady=true;display13FBPower.tcSupported=true;return processDisplay13Training(patcher,index,address,size); +} + bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // S1's Combo A/B share the Display>=12 CFGCR address branch with TGL. // Reuse the proven r319 table correction, without entering the Display12 @@ -6028,6 +6817,8 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m 0x49,0x8B,0x7D,0x00,0x48,0x8B,0x07,0xFF,0x90,0x30,0x01,0x00,0x00, 0x90,0x90,0x90,0x90,0x90,0x90,0x90}; const uint8_t busEntry[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56}; + const uint8_t pinLimit[] {0x83,0x7D,0xCC,0x08,0x72,0x2A}; + const uint8_t tcPinLimit[] {0x83,0x7D,0xCC,0x0D,0x72,0x2A}; // Same nominal CFGCR0 write as the Display12 adapter. ADL-P/N also // requires WA 22010492432 when the physical reference is 38.4 MHz. const uint8_t fractionWrite[] {0x89,0xfa,0x49,0x8b,0xbd,0x40,0x0c,0x00,0x00,0x48,0x8b,0x07, @@ -6047,16 +6838,12 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m {"__ZN21AppleIntelFramebuffer16enableControllerEv",0x3A6F4,false}, {"__ZN21AppleIntelFramebuffer25getAttributeForConnectionEijPm",0x34692,false}, {"__ZN14AppleIntelPort11getDDCBlockEjPhb",0x1F5B2,false}, - {"__ZN21AppleIntelFramebuffer11getDDCBlockEijjjPhPy",0x2FEE8,false}, - {"__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj",0x1FC22,true} + {"__ZN21AppleIntelFramebuffer11getDDCBlockEijjjPhPy",0x2FEE8,false} }; for(auto &e:frontend) { auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.shortBase ? readEntry : busEntry,e.shortBase ? sizeof(readEntry) : sizeof(busEntry)))return false; } - const uint8_t laneCall[] {0xE8,0x2A,0xF7,0xFE,0xFF,0x84,0xC0,0x74,0x1A}; - if(memcmp(reinterpret_cast(address+0x304F3),laneCall,sizeof(laneCall)))return false; - adlnHDMILaneCaller=address+0x304F8; auto read=patcher.solveSymbol(index, "__ZN31AppleIntelRegisterAccessManager14ReadRegister32Em",address,size);patcher.clearError(); auto write=patcher.solveSymbol(index, @@ -6070,12 +6857,14 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m memcmp(reinterpret_cast(write),busEntry,sizeof(busEntry)) || memcmp(reinterpret_cast(address+0x9A0E8),before,sizeof(before)) || memcmp(reinterpret_cast(bus+0x335),busBefore,sizeof(busBefore)) || + memcmp(reinterpret_cast(bus+0xEA),pinLimit,sizeof(pinLimit)) || memcmp(reinterpret_cast(bus+0x2E0),resetBefore,sizeof(resetBefore)))return false; struct Change {uintptr_t offset;const uint8_t *find,*replace;size_t length;}; const Change changes[] { {0x9A0E8,reinterpret_cast(before),reinterpret_cast(after),sizeof(before)}, {0x53DB1,busBefore,busAfter,sizeof(busBefore)}, - {0x53D5C,resetBefore,resetAfter,sizeof(resetBefore)} + {0x53D5C,resetBefore,resetAfter,sizeof(resetBefore)}, + {0x53B66,pinLimit,tcPinLimit,sizeof(pinLimit)} }; for(auto &change:changes) { KernelPatcher::LookupPatch patch {currentFramebuffer,change.find,change.replace,change.length,1}; @@ -6094,12 +6883,25 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m {frontend[0].symbol,wrapDisplay13FBEnable,orgDisplay13FBEnable}, {frontend[1].symbol,wrapAlderLakeNConnectionAttr,orgAlderLakeNConnectionAttr}, {frontend[2].symbol,wrapAlderLakeNPortDDC,orgAlderLakeNPortDDC}, - {frontend[3].symbol,wrapDisplay13FBDDC,orgDisplay13FBDDC}, - {frontend[4].symbol,wrapAlderLakeNComputeLanes,orgAlderLakeNComputeLanes} + {frontend[3].symbol,wrapDisplay13FBDDC,orgDisplay13FBDDC} }; const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGMBusObservationReady",ready); - const bool complete=ready && processDisplay13FramebufferColor(patcher,index,address,size) && + bool comboReady=true; + if(ready && !(adlnOutputWiring.selected()&0x78)) { + const char *off="__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_"; + const auto fn=patcher.solveSymbol(index,off,address,size);patcher.clearError(); + const uint8_t onPrefix[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + const uint8_t offPrefix[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x49,0x89,0xd7}; + comboReady=fn==address+0x6DAE6 && !memcmp(reinterpret_cast(clocks),onPrefix,sizeof(onPrefix)) && + !memcmp(reinterpret_cast(fn),offPrefix,sizeof(offPrefix)); + KernelPatcher::RouteRequest clocksRoutes[]={ + {"__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams",wrapDisplay13TCEnableClocks,orgDisplay13TCEnableClocks}, + {off,wrapDisplay13TCDisableClocks,orgDisplay13TCDisableClocks} + }; + comboReady=comboReady && patcher.routeMultipleShort(index,clocksRoutes,arrsize(clocksRoutes),address,size);patcher.clearError(); + } + const bool complete=ready && comboReady && processDisplay13FramebufferColor(patcher,index,address,size) && processDisplay13FramebufferSST(patcher,index,address,size) && processDisplay13FramebufferAUX(patcher,index,address,size); adlnComboPLLFractionCaller=complete ? address+0x6D973 : 0; return complete; @@ -6145,11 +6947,16 @@ bool XE::display13AUXReady(void *port,bool command) { Display13FBMemory::field(c.controller,0x1BD8) || !Display13FBMemory::field(c.controller,0xC30))return false; auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); - if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || config[0]>=2 || - Display13FBMemory::field(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)>1 || + if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || config[0]>=7 || config[0]==2 || + Display13FBMemory::field(p,0x120)!=config[0] || + (config[0]<3 && Display13FBMemory::field(p,0x550)>1) || Display13FBMemory::field(p,0x578)!=c.access || Display13FBMemory::field(c.power,0x10)!=c.controller || Display13FBMemory::field(c.power,0x78)!=c.access)return false; + if(config[0]>=3) { + Display13FBTCIO tc{object,config[0]-3}; + if(!(s->adlnOutputWiring.dp&(1U<(p,0x594+4*n)!=ctl+4*n)return false; @@ -6193,6 +7000,185 @@ uint32_t XE::wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,ui IOLockUnlock(c.lock);return r ? r : complete ? 0 : kIOReturnNotReady; } +// The donor protocol owns reservation, adoption and pool serialization once. +// Installing these hooks grants no port/power eligibility; backend adapters do. +bool XE::processFramebufferTraining(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace FramebufferTrainingNative; + if(size<=0x91DA0)return false; + for(const auto &e:entries) { + const auto fn=patcher.solveSymbol(index,e.symbol,address,size);patcher.clearError(); + if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.prefix,sizeof(e.prefix)))return false; + } + for(const auto &span:spans)if(memcmp(reinterpret_cast(address+span.offset),span.bytes,span.length))return false; + tglPathPoolLock=IORecursiveLockAlloc();if(!tglPathPoolLock)return false; + for(unsigned ddi=0;ddi(index,e.symbol,address,size);patcher.clearError(); + if(fn!=address+e.offset || memcmp(reinterpret_cast(fn),e.prefix,sizeof(e.prefix)))return false; + KernelPatcher::RouteRequest route(e.symbol,wrapDisplay13PortTraining,orgDisplay13PortTraining); + const bool routed=patcher.routeMultipleShort(index,&route,1,address,size);patcher.clearError(); + if(!routed)return false; + trainingLeaseDP13Mask=adlnOutputWiring.dp; + trainingLeaseValidate=display13LeaseValidate;trainingLeasePower=display13LeasePower;trainingLeaseStop=display13LeaseStop; + tglDisablePort=reinterpret_cast(wrapDisplay13DisablePort); + tglDisableClocks=reinterpret_cast(wrapDisplay13TCDisableClocks); + display13TrainingReady=tglTrainingLeaseReady=true; + display13FramingReady=display13ClockOrderReady=true; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13TrainingReady",true); + return true; +} + +// The native reservation/adoption algorithm is shared with Display12. This +// adapter proves Display13 ownership and uses its IRQ-aware PG bridge. +bool XE::display13LeaseValidate(void *controller,void *port,uint32_t pipe) { + auto s=callbackXE;auto &c=display13FBPower;if(!c.lock)return false; + IOLockLock(c.lock);auto p=display13SSTPort(port); + const bool bound=p && controller==c.controller && controller==s->adlnTopologyController && + Display13FBMemory::field(controller,0xD40)==s->adlnPlatform && + Display13FBMemory::field(controller,0xC8)==c.power && + !c.bridge.state.faulted && !c.bridge.state.suspended; + Display13FBPowerIO io{c.map}; + const bool ok=bound && (pipe==UINT32_MAX || c.bridge.pipeHeld(io,pipe)); + if(ok)s->tglObservationController=controller; + IOLockUnlock(c.lock);return ok; +} +bool XE::display13LeasePower(void *power,uint32_t pg,bool acquire) { + return adlnPowerReference(power,acquire?1:2,pg); +} +bool XE::display13LeaseStop(void *controller,void *port) { + auto &c=display13FBPower;if(!c.lock || controller!=c.controller)return false; + const auto stopped=[&]() { + IOLockLock(c.lock);auto p=display13SSTPort(port);bool ok=false; + if(p) { + const auto ddi=Display13FBMemory::field(p,0x120);Display13TC::Port phy; + if(Display13TC::Port::resolve(ddi,phy)) { + Display13FBTCIO io{static_cast(port),ddi-3}; + if(io.powerHeld()) { + const auto pll=io.read(phy.pll); + ok=pll!=UINT32_MAX && !(pll&0xcc000000U) && !c.bridge.nativeDDIRefs(ddi) && !c.tcSignalReady[ddi-3]; + } + } + } + IOLockUnlock(c.lock);return ok; + }; + if(stopped())return true; + // Native abort is void and can have left a retained clock. The TC teardown + // checks ownership/idle and never puts an absent DDI reference on a retry. + wrapDisplay13TCDisableClocks(controller,port,nullptr);return stopped(); +} +bool XE::display13TrainingPath(void *path,uint32_t &ddi,uint32_t &pipe) { + auto s=callbackXE; + if(!s->display13TrainingReady || !s->tglPathPoolLock || !path)return false; + bool found=false;IORecursiveLockLock(s->tglPathPoolLock); + for(const auto &lease:s->tglTrainingLeases)if(lease.path==path && lease.busy && + lease.trainingThread==current_thread() && lease.powerOwned && lease.prepared && !lease.adopted && !lease.endCleanupPending) { + auto fb=static_cast(lease.fb);auto object=static_cast(path); + found=fb && fb->getMetaClass()->getClassSize()>=0x4B90 && object->getMetaClass()->getClassSize()>=0x3730 && + lease.pipe<3 && lease.ddi<7 && (s->trainingLeaseDP13Mask&(1U<(path,0x32B0)==fb && + Display13FBMemory::field(path,0x3648)==lease.port && + Display13FBMemory::field(path,0x3640)==lease.pipe && + Display13FBMemory::field(path,0x3644)==1 && + !Display13FBMemory::field(fb,0x49E8) && !Display13FBMemory::field(fb,0x4A00) && + Display13FBMemory::associations(s->adlnTopologyController,0x1C20,fb,fb->getMetaClass()->getClassSize(), + display13FBExpectedOwner(s->adlnTopologyController),true); + if(found){ddi=lease.ddi;pipe=lease.pipe;}break; + } + IORecursiveLockUnlock(s->tglPathPoolLock);return found; +} +uint32_t XE::wrapDisplay13PortTraining(void *port,void *request) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->display13FramingReady || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13PortTraining,s->orgDisplay13PortTraining)(port,request); + if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);auto p=display13SSTPort(port); + const bool valid=p && p[0x115]==1 && !p[0x117] && p[0x10]<=1 && p[0x11A]<=1; + if(valid)p[0x11A]&=p[0x10]; + IOLockUnlock(c.lock);if(!valid)return kIOReturnNotReady; + return FunctionCast(wrapDisplay13PortTraining,s->orgDisplay13PortTraining)(port,request); +} +// Deferred native clock writes are per pipe and protected by the display +// owner lock. The native port disable finishes them before PLL/DDI teardown. +struct Display13DeferredClock {void *port {};uint32_t value {};bool pending {};}; +static Display13DeferredClock display13DeferredClocks[3]; +bool XE::display13DeferClock(void *access,uint64_t reg,uint32_t value,uintptr_t caller) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->display13ClockOrderReady || caller!=s->display13DPImage+0x4D990 || !c.lock || + reg<0x46140 || reg>0x46148 || (reg&3))return false; + const auto pipe=unsigned((reg-0x46140)/4);void *port=nullptr; + // The native caller owns the path/FB mutex. Look up the real pool entry, + // including partial modesets not yet published to the scanout allocator. + for(unsigned ddi=3;ddi<7;++ddi)if(s->adlnOutputWiring.dp&(1U<(s->tglPortByDDI)(ddi); + auto path=candidate?reinterpret_cast(s->orgTGLPathLookup)(candidate):nullptr; + if(path && Display13FBMemory::field(path,0x3640)==pipe && + Display13FBMemory::field(path,0x3648)==candidate) {port=candidate;break;} + } + IOLockLock(c.lock);Display13FBPowerIO power{c.map}; + const bool selected=port && access==c.access; + if(selected) { + auto &deferred=display13DeferredClocks[pipe]; + if(display13SSTPort(port) && c.bridge.pipeHeld(power,pipe) && !deferred.pending && !(value&0xf0000000U))deferred={port,value,true}; + else adlnFault(c.clockFaulted,__LINE__); + } + IOLockUnlock(c.lock);return selected; +} +bool XE::display13StopTranscoder(void *port) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->display13ClockOrderReady || !s->orgTGLPathLookup || !c.lock)return false; + auto path=reinterpret_cast(s->orgTGLPathLookup)(port); + if(!path)return true; // A failed early probe may have no pipe at all. + auto object=static_cast(path); + if(object->getMetaClass()->getClassSize()<0x3730)return false; + const auto pipe=Display13FBMemory::field(path,0x3640); + if(pipe>=3 || Display13FBMemory::field(path,0x3648)!=port)return false; + IOLockLock(c.lock);auto p=display13SSTPort(port);Display13FBPowerIO power{c.map};bool ok=false; + if(p && c.bridge.pipeHeld(power,pipe)) { + const auto ddi=Display13FBMemory::field(port,0x120);Display13FBSSTIO io{c.map}; + const auto buffer=io.read(0x64000+0x100*ddi),conf=io.read(0x70008+0x1000*pipe), + func=io.read(0x60400+0x1000*pipe),tp=io.read(0x60540+0x1000*pipe),clock=io.read(0x46140+4*pipe); + auto &deferred=display13DeferredClocks[pipe]; + ok=buffer!=UINT32_MAX && conf!=UINT32_MAX && func!=UINT32_MAX && tp!=UINT32_MAX && clock!=UINT32_MAX && + !(buffer&0x80000000U) && !(conf&0xc0000000U) && !((func|tp)&0x80000000U) && + (!(clock&0xf0000000U) || (clock>>28)==ddi+3) && (!deferred.pending || deferred.port==port); + if(ok) { + if(!(buffer&0x80U))IODelay(8); // DDI disable: idle or >=8us before clock removal. + const auto value=deferred.pending?deferred.value:(clock&0x0fffffffU); + io.write(0x46140+4*pipe,value);ok=io.read(0x46140+4*pipe)==value; + if(ok)deferred={}; + } + } + IOLockUnlock(c.lock);return ok; +} + bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // Install the bounded Combo backend independently of selecting DP at startup. // Startup still requires AUX/probe and DP boot-handoff operation closure. @@ -6239,7 +7225,7 @@ uint8_t *XE::display13SSTPort(void *port) { auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || !(s->adlnOutputWiring.dp&(1U<(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=1 || + Display13FBMemory::field(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=(config[0]>=3?4U:1U) || Display13FBMemory::field(p,0x578)!=c.access)return nullptr; return p; } @@ -6259,6 +7245,11 @@ bool XE::display13MainLinkAUX(void *port,bool acquire) { Display13FBMemory::field(c.power,0x10)==c.controller && Display13FBMemory::field(c.power,0x78)==c.access) { const auto ddi=Display13FBMemory::field(p,0x120); + if(ddi>=3 && ddi<=6) { + // Mode owns AUX from before first DPCD through buffer/PLL stop. Do not + // add the Combo main-link lease or release mode power at buffer-disable. + Display13FBTCIO tc{static_cast(port),ddi-3};ok=tc.owned(ddi) && tc.linkHeld(); + } if(ddi<2 && Display13FBMemory::field(p,0x590)==0x64000+0x100*ddi) { auto &link=c.mainLink[ddi];const auto previous=link.owner;Display13FBPowerIO io{c.map}; const auto result=acquire ? link.begin(io,c.bridge,reinterpret_cast(port),ddi) : @@ -6289,13 +7280,19 @@ uint32_t XE::wrapDisplay13DisablePort(void *port) { return FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; const auto result=FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); + if(!result && s->display13ClockOrderReady && !display13StopTranscoder(port))return kIOReturnNotReady; const bool released=display13MainLinkAUX(port,false); return result ? result : released ? 0 : kIOReturnNotReady; } uint32_t XE::wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig) { auto s=callbackXE;auto &c=display13FBPower; - // No protocol replacement for HDMI; preserve the donor's existing behavior. + // The one installed signal hook dispatches TC TMDS before Combo handling. + auto object=static_cast(port); + if(s->display13FBTCHooksReady && object && object->getMetaClass()->getClassSize()>=0x580 && + Display13FBMemory::field(port,0x120)>=3) + return wrapDisplay13TCSignal(port,swing,emphasis,overrideConfig); + // No protocol replacement for Combo HDMI; preserve the donor's behavior. if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) return FunctionCast(wrapDisplay13SignalLevels,s->orgDisplay13SignalLevels)(port,swing,emphasis,overrideConfig); if(!c.lock)return kIOReturnNotReady; @@ -6322,13 +7319,19 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { if(!c.lock || !object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; if(display13HDMIPort(Display13FBMemory::field(path,0x3648))) return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + uint32_t reservedDDI=UINT32_MAX,reservedPipe=UINT32_MAX; + const bool reserved=display13TrainingPath(path,reservedDDI,reservedPipe); // Never hold the nonrecursive power lock over donor training or AUX calls. // Both register stages revalidate the graph, logical leases and live power. const auto program=[&](bool before) { IOLockLock(c.lock);bool ok=false; auto p=display13SSTPort(Display13FBMemory::field(path,0x3648)); auto fb=Display13FBMemory::field(path,0x32B0); - const auto binding=p ? adlnInspectPathBinding(fb,object,s->adlnPlatform,s->adlnOutputWiring,false) : TGLFBBinding::Readout{}; + auto binding=p ? adlnInspectPathBinding(fb,object,s->adlnPlatform,s->adlnOutputWiring,false) : TGLFBBinding::Readout{}; + if(p && reserved && reservedDDI==Display13FBMemory::field(p,0x120) && + reservedPipe==Display13FBMemory::field(path,0x3640)) { + binding.status=TGLFBBinding::Status::Complete;binding.ddi=reservedDDI;binding.pipe=reservedPipe; + } Display13FBPowerIO io{c.map}; if(binding.status==TGLFBBinding::Status::Complete && c.bridge.outputHeld(io,binding.ddi,binding.pipe)) { Display13FBSSTIO registers{c.map}; @@ -6337,7 +7340,7 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { if(before) { ok=true; if(pattern==1) { - const auto r=XELP::SSTProgram::prepare(read,write,binding.ddi,binding.pipe,Display13FBMemory::field(p,0x124),true); + const auto r=XELP::SSTProgram::prepare(read,write,binding.ddi,binding.pipe,Display13FBMemory::field(p,0x124),true,binding.ddi>=3?binding.ddi+3:binding.ddi+1); // An already-active, identical route needs no rewrite. Never repair an // active neighbor or a failed posting read by falling through to native. ok=r.bufferBefore!=UINT32_MAX && r.clockBefore!=UINT32_MAX && r.funcBefore!=UINT32_MAX && @@ -6356,6 +7359,11 @@ uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { return program(false)?0:kIOReturnNotReady; } uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request) { + // Extending the donor's pin bound is not permission to use an arbitrary + // Type-C DDC bus. Only the retained controller's selected TMDS wiring + // may issue requests to pins9..12; pin8 remains excluded. + if(bus>=8 && (controller!=callbackXE->adlnTopologyController || + !callbackXE->adlnOutputWiring.usesDDC(bus)))return kIOReturnBadArgument; static uint32_t count {}; const auto call=__sync_add_and_fetch(&count,1); uint64_t r[21] {3,call,bus,speed};auto req=static_cast(request); if(req) {memcpy(r+4,req,16);r[6]=*reinterpret_cast(req+0x28);r[7]=*reinterpret_cast(req+0x38);} @@ -6388,6 +7396,7 @@ uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t spee uint32_t XE::wrapDisplay13GMBusRead(void *access,uint64_t reg) { const auto caller=reinterpret_cast(__builtin_return_address(0)); + if(comboClockAccess(access))reg=comboClockTransaction.translation.reg(reg,XELPComboClock::donorPC(callbackXE->framebufferClockImage,caller)); auto value=FunctionCast(wrapDisplay13GMBusRead,callbackXE->orgDisplay13GMBusRead)(access,reg); const auto start=callbackXE->adlnGMBusFunction; adlnObserveGMBus(false,reg,value,caller,start); @@ -6613,6 +7622,7 @@ void XE::wrapAlderLakeNGamma10(void *path,void *transaction,uint32_t control) { } void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { + if(display13DeferClock(access,reg,value,reinterpret_cast(__builtin_return_address(0))))return; // DISPLAY13_COMBO_FRACTION: qualified nominal writer, physical reference, // retained controller/manager. No MMIO or other port's PLL inference. if(callbackXE->display13FramebufferRequested && callbackXE->xeProfile.display==XELP::Display::XeLPD && @@ -6622,11 +7632,13 @@ void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { TGLComboPLLFraction::adjust(*reinterpret_cast(controller+0xEA8),reg,value); } // DISPLAY13_COMBO_FRACTION_END + unsigned long clockReg=reg; + comboClockTranslateWrite(access,clockReg,value,reinterpret_cast(__builtin_return_address(0)));reg=clockReg; // This route also carries direct native cursor writes, outside the WM // trigger queue. Apply Display 13's slot WA only to our proven owner. if(reg==0x70080 || reg==0x71080 || reg==0x72080) { const auto cursorBase=__atomic_load_n(&display13FBPower.arbitrationWriterBase,__ATOMIC_ACQUIRE); - if(callbackXE->xeProfile.display==XELP::Display::XeLPD && callbackXE->adlnNativeReady && access && + if(callbackXE->xeProfile.display==XELP::Display::XeLPD && callbackXE->display13BootReady && access && access==display13FBPower.access && cursorBase && Display13FBMemory::field(access,0x50)==cursorBase) value=Display13Cursor::controlWrite(reg,value); } @@ -6670,7 +7682,7 @@ void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { const bool arbitrationRegister=reg==0x70008 || reg==0x71008 || reg==0x72008 || reg==0x70180 || reg==0x71180 || reg==0x72180; const auto writerBase=__atomic_load_n(&display.arbitrationWriterBase,__ATOMIC_ACQUIRE); - if(arbitrationRegister && callbackXE->display13FramebufferRequested && callbackXE->adlnNativeReady && access==display.access && + if(arbitrationRegister && callbackXE->display13FramebufferRequested && callbackXE->display13BootReady && access==display.access && display.engineEnabled && writerBase && Display13FBMemory::field(access,0x50)==writerBase) { for(unsigned pipe=0;pipe<3;++pipe) { const auto bank=0x1000*pipe; @@ -6748,6 +7760,74 @@ static void adlnPublishFrontend(const char *name,const char *phase,uint64_t (&r) char key[64];snprintf(key,sizeof(key),"N97FB%s%u%s",name,static_cast(r[2]),phase); IORegistryEntry::getRegistryRoot()->setProperty(key,r,sizeof(r)); } +bool XE::processFramebufferAGDC(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + // Both short trampolines end before the donor's RIP-relative counters. + const char *client="__ZN21AppleIntelFramebuffer13newUserClientEP4taskPvjPP12IOUserClient"; + const char *callback="__ZN31AppleIntelFramebufferController12CallBackAGDCE31kAGDCRegisterLinkControlEvent_tmj"; + const uint8_t clientEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56}; + const uint8_t callbackEntry[]={0x55,0x48,0x89,0xe5,0x89,0xf0}; + const uint8_t dispatch[]={0x4c,0x8b,0x87,0xf8,0x1b,0,0,0x48,0x8b,0xbf,0,0x1c,0,0, + 0x48,0x89,0xd6,0x89,0xca,0x89,0xc1,0x5d,0x41,0xff,0xe0}; + if(size<0x69E60)return false; + const auto c=patcher.solveSymbol(index,client,address,size);patcher.clearError(); + const auto a=patcher.solveSymbol(index,callback,address,size);patcher.clearError(); + if(c!=address+0x273B4 || a!=address+0x69DF2 || + memcmp(reinterpret_cast(c),clientEntry,sizeof(clientEntry)) || + memcmp(reinterpret_cast(a),callbackEntry,sizeof(callbackEntry)) || + memcmp(reinterpret_cast(address+0x69E1E),dispatch,sizeof(dispatch)))return false; + KernelPatcher::RouteRequest routes[] { + {client,wrapFramebufferUserClient,orgFramebufferUserClient}, + {callback,wrapFramebufferAGDCCallback,orgFramebufferAGDCCallback} + }; + const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13AGDCGuardReady",ready); + return ready; +} + +uint32_t XE::wrapFramebufferUserClient(IOService *fb,void *task,void *security,uint32_t type,void **client) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){if(client)*client=nullptr;return kIOReturnNotReady;} + // Only type0 enters IOFramebuffer::open. Refuse before entering IOG: an + // error from enableController permanently sets IOG's dead flag instead. + // Native controller start publishes FB children before its AGDC nub. + // A later open can retry after both the controller and callback are ready. + if(type==0) { + auto s=callbackXE; + const bool framebuffer=fb && fb->getMetaClass()->getClassSize()>=0x4B90; + auto controller=framebuffer ? + Display13FBMemory::field(fb,0x1D0) : nullptr; + const bool bound=controller && controller->getMetaClass()->getClassSize()>=0x1C20 && + (!s->display13FramebufferRequested || controller==s->adlnTopologyController); + // Publication follows the common start result, never a mutable diagnostic + // controller pointer that a training adapter can populate early. + const bool started=bound && controller==__atomic_load_n(&s->framebufferStartedController,__ATOMIC_ACQUIRE); + const bool registered=bound && Display13FBMemory::field(controller,0x1BF8); + uint64_t record[]={1,bound,started,registered, + bound && Display13FBMemory::field(controller,0x1C00), + framebuffer ? Display13FBMemory::field(fb,0x1DC) : UINT32_MAX}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13ClientReadiness",record,sizeof(record)); + if(!started || !registered) { + if(client)*client=nullptr; + return kIOReturnNotReady; + } + } + const auto result=FunctionCast(wrapFramebufferUserClient,callbackXE->orgFramebufferUserClient)(fb,task,security,type,client); + if(!callbackXE->display13FramebufferRequested)observeFramebufferUserClient(fb,type,client,result); + return result; +} + +uint32_t XE::wrapFramebufferAGDCCallback(IOService *controller,uint32_t event,unsigned long argument,uint32_t value) { + // Other native notifications can also precede policy registration. Return + // failure for a real notification with no receiver; never synthesize ACK. + // Keep the native event filter, argument conversion and registered path. + const bool notification=event==4 || event==0x80 || (event<=11 && (0x90FU&(1U<getMetaClass()->getClassSize()<0x1C20 || + !Display13FBMemory::field(controller,0x1BF8)))return kIOReturnNotReady; + const auto result=FunctionCast(wrapFramebufferAGDCCallback,callbackXE->orgFramebufferAGDCCallback)(controller,event,argument,value); + if(!callbackXE->display13FramebufferRequested)tglTrace(7,event,true,event,value,result,argument); + return result; +} + uint32_t XE::wrapDisplay13FBEnable(IOService *fb) { uint64_t r[16] {1,1};adlnFrontendSnapshot(fb,r);adlnPublishFrontend("Enable","Entry",r); const auto result=FunctionCast(wrapDisplay13FBEnable,callbackXE->orgDisplay13FBEnable)(fb); @@ -6784,105 +7864,59 @@ uint32_t XE::wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,b } return result; } -bool XE::wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes) { - const auto caller=reinterpret_cast(__builtin_return_address(0)); - const auto native=FunctionCast(wrapAlderLakeNComputeLanes,callbackXE->orgAlderLakeNComputeLanes)(port,timing,bpp,available,lanes); - auto config=Display13FBMemory::field(port,0x548); - const bool corrected=lanes && Display13FBTopology::skipDPLanes(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring, - caller==callbackXE->adlnHDMILaneCaller,native,available,*lanes); - static uint32_t count {};uint64_t r[] {1,__sync_add_and_fetch(&count,1),config ? config[0] : 9,config ? config[2] : 0, - bpp,available,native,lanes ? *lanes : UINT32_MAX,caller==callbackXE->adlnHDMILaneCaller,corrected}; - IORegistryEntry::getRegistryRoot()->setProperty("N97FBHDMILaneLast",r,sizeof(r)); - return native || corrected; -} - -bool XE::processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { - // OS/donor ABI corrections are shared with Display12. Keep hardware - // routing and topology in the selected Display13 backend. - if(!display13FramebufferRequested || size<=0xCCCD8)return false; - const bool surface=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); - const bool nvram=xelpInstallNVRAMABI(patcher,index,address,size); - auto table=patcher.solveSymbol(index,"__ZTV17AppleIntelPortHAL",address,size); - auto pure=patcher.solveSymbol(KernelPatcher::KernelID,"___cxa_pure_virtual"); - const uint8_t halSize[] {0xB9,0xB0,0x06,0,0}; - bool probe=table==address+0xCCAD0 && pure && xelpProviderProbe && xelpProviderConfigureProbe && - !memcmp(reinterpret_cast(address+0x5EE5E),halSize,sizeof(halSize)) && - *reinterpret_cast(table+0x188)==pure; - patcher.clearError(); - if(probe) { - auto replacement=xelpProviderProbe; - patcher.routeBlock(table+0x188,reinterpret_cast(&replacement),sizeof(replacement)); - probe=patcher.getError()==KernelPatcher::Error::NoError && - *reinterpret_cast(table+0x188)==replacement; - patcher.clearError(); - } - const char *initSymbol="__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj"; - auto init=patcher.solveSymbol(index,initSymbol,address,size); - const uint8_t initEntry[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x10, - 0x41,0x89,0xD6,0x49,0x89,0xF7,0x48,0x89,0xFB}; - bool managers=init==address+0x29EC8 && !memcmp(reinterpret_cast(init),initEntry,sizeof(initEntry)); - patcher.clearError(); - KernelPatcher::RouteRequest binding(initSymbol,wrapDisplay13FramebufferInit,orgDisplay13FramebufferInit); - managers=managers && patcher.routeMultiple(index,&binding,1,address,size); - patcher.clearError(); - uint32_t record[] {2,nvram,probe,managers,surface}; - IORegistryEntry::getRegistryRoot()->setProperty("N97FBCommonABIHooks",record,sizeof(record)); - return surface && nvram && probe && managers; -} -bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32_t index) { - auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; - if(!callbackXE->display13FBABIReady || !fb || !pci || index>=3 || - !xelpDisplay13Device(pci) || - controller->getMetaClass()->getClassSize()<0x1C20 || fb->getMetaClass()->getClassSize()<0x4B90)return false; - if(!FunctionCast(wrapDisplay13FramebufferInit,callbackXE->orgDisplay13FramebufferInit)(fb,controller,index))return false; - auto manager=Display13FBMemory::field(controller,0xC40); - if(Display13FBMemory::field(fb,0x1D0)!=controller || - Display13FBMemory::field(fb,0x1DC)!=index || !manager || - manager->getMetaClass()->getClassSize()<0x68 || !Display13FBMemory::field(manager,0x50) || - Display13FBMemory::field(manager,0x60)!=0x1000000)return false; - // Validate every donor-owned CPU object before filling any borrowed manager - // field. A conflicting non-null reference is failure, not a successful bind. - const size_t arrays[] {0xF58,0xF28},identities[] {0x7C,0x1C},slots[] {0x90,0x28}; - const unsigned counts[] {15,6},minimum[] {0x1C8,0x58}; - for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+8*i); - if(!object || object->getMetaClass()->getClassSize()(object,identities[group])!=i)return false; - auto stored=Display13FBMemory::field(object,slots[group]); - if(stored && stored!=manager)return false; - } - auto stored=Display13FBMemory::field(fb,0x4A40); - if(stored && stored!=manager)return false; - for(unsigned group=0;group<2;++group)for(unsigned i=0;i(controller,arrays[group]+8*i); - memcpy(object+slots[group],&manager,sizeof(manager)); - } - memcpy(reinterpret_cast(fb)+0x4A40,&manager,sizeof(manager)); - fb->setProperty("N97FBManagersBound",true); - return true; +bool XE::prepareDisplay13BootScanout(IOPCIDevice *pci) { + if(!display13BootReady || !xelpDisplay13Device(pci) || !xelpMemoryMatches(pci) || + !(pci->configRead16(4)&2))return false; + auto map=pci->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(!map)return false; + struct IO { + volatile uint8_t *base;uint64_t bytes; + bool readable(uint32_t reg) const {return base && bytes>=4 && reg<=bytes-4;} + uint32_t read(uint32_t reg){return *reinterpret_cast(base+reg);} + uint64_t readPTE(uint32_t reg){return *reinterpret_cast(base+reg);} + void write(uint32_t reg,uint32_t value){*reinterpret_cast(base+reg)=value;} + void delay(){IODelay(100);} + } io{reinterpret_cast(map->getVirtualAddress()),map->getLength()}; + const bool mapped=io.base && io.bytes==0x1000000; + const auto boot=AlderLakeN::readDisplay(io,mapped); + uint32_t trans=UINT32_MAX;AlderLakeN::value(boot,0x60400,trans); + const auto ddi=Display13FBTopology::bootstrapDDI(adlnOutputWiring, + boot.status==AlderLakeN::ReadoutStatus::Complete,boot.poweredPipes,boot.activePipes,trans,true); + const bool dp=ddi<7 && (adlnOutputWiring.dp&(1U<=3,ddi,0,dp); + if(ddi>=3 && ddi<7)pci->setProperty("WEXDisplay13TCBoot",&tc,sizeof(tc)); + auto result=Display13Boot::prepareLinear(io,boot,mapped,ddi,xelpMemory,ddi>=3?&tc:nullptr, + dp?AlderLakeN::GOPProtocol::DP:AlderLakeN::GOPProtocol::TMDS); + map->release(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXDisplay13BootHandoff",&result,sizeof(result)); + SYSLOG("igfx","Display13 native handoff status=%u writes=%u restored=%u", + static_cast(result.status),result.writes,result.restored); + return result.status==AlderLakeN::LinearStatus::Success; } bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); + if(s->tglObservationController==that)s->tglObservationController=nullptr; if(!pci)return false; - auto property=provider->getProperty("xelp-hdmi-wiring"); - if(!property)property=provider->getProperty("tgl-hdmi-wiring"); - auto wiring=OSDynamicCast(OSData,property); - s->adlnOutputWiring=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, - wiring?wiring->getLength():0,s->firmwarePorts,3); - if(!Display13FBTopology::supportedWiring(s->adlnOutputWiring))return false; + const auto board=s->display13BoardWiring(provider); + if(!Display13FBTopology::supportedWiring(board,s->xeProfile.display==XELP::Display::XeLPD) || + board.selected()!=s->adlnOutputWiring.selected())return false; + 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 bool ready=s->display13FramebufferRequested && contract.ready() && pci && xelpDisplay13Device(pci); + const bool ready=s->display13FramebufferRequested && s->display13BootReady && s->tglActive && + contract.ready() && pci && xelpDisplay13Device(pci); uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; that->setProperty("WEXDisplayStartupContract",contractRecord,sizeof(contractRecord)); if(!contract.ready())for(uint64_t bit=1;bit;bit<<=1)if(contract.missing()&bit) SYSLOG("igfx","Display13 missing operation %s",XELP::DisplayContract::name(bit)); that->setProperty("N97FramebufferBackendReady",ready); if(!ready)return false; - const bool dualReady=s->adlnNativeReady && s->adlnScanoutDisableFB && s->orgAlderLakeNSysMemAllocate && s->orgAlderLakeNSysMemFree && + const bool dualReady=s->display13BootReady && s->adlnScanoutDisableFB && s->orgAlderLakeNSysMemAllocate && s->orgAlderLakeNSysMemFree && (!adlnFixedSysMemEnabled || display13FBSysMem.lock) && display13FBSysMem.retireLock && s->adlnWorkLoopInGate && s->adlnWorkLoopTryClose && s->adlnWorkLoopOpen; - uint64_t dualPolicy[] {1,s->adlnOutputWiring.selected(),dualReady,s->adlnNativeReady,s->adlnScanoutDisableFB!=0, + uint64_t dualPolicy[] {1,s->adlnOutputWiring.selected(),dualReady,s->display13BootReady,s->adlnScanoutDisableFB!=0, s->orgAlderLakeNSysMemAllocate!=0,s->orgAlderLakeNSysMemFree!=0,display13FBSysMem.lock!=nullptr,display13FBSysMem.retireLock!=nullptr,adlnFixedSysMemEnabled}; IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferDualPolicy",dualPolicy,sizeof(dualPolicy)); if(Display13FBTopology::multiOutputWiring(s->adlnOutputWiring) && !dualReady)return false; @@ -6903,7 +7937,10 @@ bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { (provider->getProperty("AAPL,GfxYTile") || provider->setProperty("AAPL,GfxYTile",&enabled,sizeof(enabled))); OSSafeReleaseNULL(yTiling);OSSafeReleaseNULL(mode);OSSafeReleaseNULL(features); if(!featuresSelected)return false; - const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgDisplay13FramebufferStart)(that,provider); + if(Display13FBTopology::deferredBootPrepare(board,s->adlpDisplaySelected) && !s->prepareDisplay13BootScanout(pci))return false; + SYSLOG("igfx","Display13 native controller start enter offline=%u",display13Offline); + const auto started=FunctionCast(wrapDisplay13FramebufferStart,s->orgFramebufferStart)(that,provider); + SYSLOG("igfx","Display13 native controller start return=%u",started); that->setProperty("N97FramebufferOriginalStart",started); IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferOriginalStart",started); bool irqEnabled=false; @@ -7035,15 +8072,23 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) if(valid) { if(!c.bridge.bootAdopted) { const auto ddi=c.bridge.bootPort; - const bool dp=ddi<2 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.dp&(1U<(port),ddi); - linked=result.status==Display13Power::Status::Complete; - if(linked)port->retain(); // Released only after real buffer stop/idle. + if(ddi>=3) { + const auto source=Display13TC::Boot::capture(io,true,ddi,0); + Display13FBTCIO tc{port,ddi-3}; + linked=tc.owned(ddi) && tc.linkHeld() && boot.activePipes==1 && + AlderLakeN::dpSourceUnchanged(io,boot,0,ddi,0,&source); + } else if(ddi<2) { + result=AlderLakeN::adoptDPBootAUX(io,boot,c.bridge,c.mainLink[ddi],reinterpret_cast(port),ddi); + linked=result.status==Display13Power::Status::Complete; + if(linked)port->retain(); // Released only after real buffer stop/idle. + } } if(linked)result=c.bridge.adoptBootPath(reinterpret_cast(path),0,ddi); + else result.status=Display13Power::Status::NotReady; } else if(c.bridge.bootPath==reinterpret_cast(path))result.status=Display13Power::Status::Complete; if(result.status==Display13Power::Status::Complete && @@ -7316,10 +8361,10 @@ void XE::wrapAlderLakeNScanoutFree(IOService *fb) { } // A stopped-pipe release owns only its memory/IRQ/DDB consumers. It does not // stop shared CDCLK/engine or destroy the native path/DDI power lease. -void XE::adlnFreeStoppedPipe(IOService *fb) { +bool XE::adlnFreeStoppedPipe(IOService *fb) { auto &c=display13FBPower;auto &irq=display13FBIRQ; - if(!c.lock || !display13FBSysMem.retireLock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; - auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; + if(!c.lock || !display13FBSysMem.retireLock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return false; + auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; IORecursiveLockLock(mutex);IORecursiveLockLock(display13FBSysMem.retireLock);IOLockLock(c.lock); const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); uint64_t record[24] {1,pipe};bool freed=false,held=false;uint32_t master=0,blocked=0;bool enabled=false; @@ -7417,18 +8462,19 @@ void XE::adlnFreeStoppedPipe(IOService *fb) { record[19]=pool.state;record[20]=binding.ddi;record[21]=held;record[22]=c.dbuf.consumers;record[23]=c.dbuf.unretired; IOLockUnlock(c.lock);IORecursiveLockUnlock(display13FBSysMem.retireLock);IORecursiveLockUnlock(mutex); adlnPublishMemory("N97FBStoppedMemoryFree",pipe,record,sizeof(record)); + return freed; } -void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { +uint32_t XE::releaseDisplay13Path(IOService *fb,OSObject *path) { if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { - FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return; + FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return 0; } auto &c=display13FBPower; if(!c.lock || !fb || !path || fb->getMetaClass()->getClassSize()<0x4B90 || path->getMetaClass()->getClassSize()<0x3730) { - FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return; + return kIOReturnNotReady; } auto mutex=Display13FBMemory::field(fb,0x4220); - if(!mutex) {FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return;} + if(!mutex) {return kIOReturnNotReady;} fb->retain();path->retain();IORecursiveLockLock(mutex); uint64_t record[16] {1};bool released=false,detached=false;IOService *withdrawn=nullptr; IOLockLock(c.lock);const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); @@ -7438,12 +8484,12 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { const auto countOffset=Display13FBMemory::pathCountOffset(fb); record[4]=countOffset ? Display13FBMemory::field(fb,countOffset) : UINT32_MAX; IOLockUnlock(c.lock); - if(selected)adlnFreeStoppedPipe(fb); + const bool memoryReleased=!selected || adlnFreeStoppedPipe(fb); IOLockLock(c.lock); if(selected) { c.pipe[pipe].destroyingPath=true; bool pinned=false;for(const auto &stored:display13FBSysMem.slot)if(stored.framebuffer==fb && stored.buffer)pinned=true; - released=!c.memoryFaulted && Display13FBMemory::field(fb,0x4288)==0xFF && + released=memoryReleased && !c.memoryFaulted && Display13FBMemory::field(fb,0x4288)==0xFF && !Display13FBMemory::field(fb,0x4348) && !pinned && !c.pipe[pipe].publishedScanout; if(released) { Display13FBDBUFIO io {c.map};const auto status=Display13DBUF::detach(io,c.dbuf,pipe); @@ -7451,9 +8497,14 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { } } IOLockUnlock(c.lock); - // Native freeDisplayResources loops until path count decreases. Always - // preserve original path release, including memory-retire failure; backing - // stays pinned/faulted instead of forging path-count progress or spinning. + // The enclosing native loop now handles refusal. Never discard the path + // association while WEX still owns its backing or DDB reservation. + if((pipe<3 && !selected) || (selected && (!released || !detached))) { + IOLockLock(c.lock); + if(pipe<3)c.pipe[pipe].destroyingPath=false; + adlnFault(c.memoryFaulted,__LINE__);IOLockUnlock(c.lock); + IORecursiveLockUnlock(mutex);path->release();fb->release();return kIOReturnNotReady; + } FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path); IOLockLock(c.lock); record[6]=countOffset ? Display13FBMemory::field(fb,countOffset) : UINT32_MAX; @@ -7474,6 +8525,11 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex); OSSafeReleaseNULL(withdrawn);path->release();fb->release(); adlnPublishMemory("N97FBPathFree",pipe,record,sizeof(record)); + return selected && !removed ? kIOReturnNotReady : 0; +} + +void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { + (void)releaseDisplay13Path(fb,path); } IOMemoryDescriptor *XE::wrapAlderLakeNAperture(IOService *fb,int apertureIndex) { @@ -8356,6 +9412,10 @@ void XE::wrapDisplay13WMArmUpdate(IOService *fb) { record[11]=static_cast(retired);record[12]=old;record[13]=c.dbuf.unretired; record[14]=c.dbuf.owned;record[15]=c.pipe[pipe].watermarkClockHz; if(result.status!=Display13WM::Status::Complete && result.status!=Display13WM::Status::Unavailable) { + uint64_t proof[16] {1,pipe,static_cast(result.status),static_cast(result.check), + result.pipeControl,result.planeControl,result.cursorControl,result.checkRegister,result.checkValue, + result.clockHz,actualHz,result.frameBefore,result.frameAfter,result.lastReg,result.expected,result.observed}; + adlnPublishMemory("N97FBWMLatchFailure",pipe,proof,sizeof(proof),true); uint64_t cursor[8] {1,pipe};Display13FBDBUFIO readout {c.map}; if(Display13FBIRQ::powered(readout.read(0x45404),pipe)) { cursor[2]=readout.read(0x70080+0x1000*pipe); @@ -8826,6 +9886,7 @@ void XE::wrapAlderLakeNOptimizedDBUF(IOService *controller) {adlnSetupDBUF(contr void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { auto &c=display13FBPower; if(!c.lock)return; uint64_t record[32] {}; Display13Power::Result result; + unsigned tcBootDDI=UINT32_MAX; IOLockLock(c.lock); auto service=static_cast(controller); auto pci=service ? OSDynamicCast(IOPCIDevice,service->getProvider()) : nullptr; @@ -8846,25 +9907,32 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { Display13FBPowerIO io {map}; const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); - const bool dpBoot=bootPort<2 && (callbackXE->adlnOutputWiring.dp&(1U<adlnOutputWiring.dp&(1U<=3) { + Display13FBBootIO bootIO{map};boot=Display13TC::Boot::capture(bootIO,true,bootPort,0,dpBoot).valid(); + } if(boot && dpBoot) { Display13FBBootIO bootIO{map};const auto source=AlderLakeN::readDisplay(bootIO,true);uint32_t aux=0; - boot=AlderLakeN::dpSource(source,0,bootPort) && AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort) && - AlderLakeN::value(source,Display13Power::AUX,aux) && (aux&(1U<<(2*bootPort))); + const auto tc=Display13TC::Boot::capture(bootIO,bootPort>=3,bootPort,0,true); + boot=AlderLakeN::dpSourceUnchanged(bootIO,source,0,bootPort,0,bootPort>=3?&tc:nullptr); + if(bootPort<2)boot=boot && AlderLakeN::value(source,Display13Power::AUX,aux) && (aux&(1U<<(2*bootPort))); + } + if(boot) { + result=c.bridge.begin(io,bootPort,callbackXE->adlnOutputWiring.selected()); + if(result.status==Display13Power::Status::Complete)c.bootDP=dpBoot; } - if(boot)result=c.bridge.begin(io,bootPort,callbackXE->adlnOutputWiring.selected()); else {result.status=Display13Power::Status::InvalidDomains;adlnFault(c.bridge.state.faulted,__LINE__);} - if(result.status==Display13Power::Status::Complete && dpBoot) { + if(result.status==Display13Power::Status::Complete && dpBoot && bootPort<2) { Display13FBBootIO bootIO{map};const auto source=AlderLakeN::readDisplay(bootIO,true); result=AlderLakeN::holdDPBootAUX(bootIO,source,c.bridge,c.mainLink[bootPort],bootPort); if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); @@ -8890,8 +9958,34 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { __atomic_store_n(&display13FBIRQ.controller,service,__ATOMIC_RELEASE); } else {result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} } + if(result.status==Display13Power::Status::Complete && dpBoot && bootPort>=3)tcBootDDI=bootPort; } else OSSafeReleaseNULL(map); } + if(tcBootDDI<7) { + // Exact donor start initializes every port before PowerWell::init. + // Establish the same durable Mode used by probing/hotplug before any + // native engine/client can issue AUX or stop the inherited DP link. + auto getPort=reinterpret_cast(callbackXE->tglPortByDDI); + auto port=getPort?getPort(tcBootDDI):nullptr; + bool adopted=port && display13TCTarget(port)==tcBootDDI; + if(adopted) { + port->retain();IOLockUnlock(c.lock); + adopted=wrapDisplay13TCMode(port,4)==kIOReturnSuccess; + IOLockLock(c.lock); + if(adopted) { + Display13FBBootIO io{c.map};const auto view=AlderLakeN::readDisplay(io,true); + const auto tc=Display13TC::Boot::capture(io,true,tcBootDDI,0); + Display13FBTCIO access{port,tcBootDDI-3}; + adopted=access.owned(tcBootDDI) && access.linkHeld() && view.activePipes==1 && + AlderLakeN::dpSourceUnchanged(io,view,0,tcBootDDI,0,&tc); + } + port->release(); + } + if(!adopted) {result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__);} + // Do not set tcSignalReady/tcDPLink from an enabled GOP. They certify + // our own programmed PLL; the firmware link is preserved until the + // native disable path stops it, then the ordinary cold path owns it. + } display13FBPowerRecord(record,0,0,result); IOLockUnlock(c.lock); adlnPublishPowerRecord("N97FBPower",record); @@ -8900,8 +9994,8 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { if(result.status==Display13Power::Status::Complete)wrapAlderLakeNEngineEnable(power); } -void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { - auto &c=display13FBPower; if(!c.lock)return; +bool XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { + auto &c=display13FBPower; if(!c.lock)return false; if(operation==1 && domain>=1 && domain<=4 && power==c.power) { IOLockLock(c.lock);const bool enable=!c.engineEnabled && !c.bridge.state.suspended && !c.bridge.state.faulted;IOLockUnlock(c.lock); if(enable)wrapAlderLakeNEngineEnable(power); @@ -9052,6 +10146,7 @@ void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { // the gate this invocation acquired without waiting, including failure. // No ACK, queue rewrite, source replacement or refcount substitution. if(acquiredGate)reinterpret_cast(callbackXE->adlnWorkLoopOpen)(acquiredGate); + return result.status==Display13Power::Status::Complete; } void XE::wrapAlderLakeNPowerGetPG(void *p,uint32_t d) { adlnPowerReference(p,1,d); } void XE::wrapAlderLakeNPowerPutPG(void *p,uint32_t d) { adlnPowerReference(p,2,d); } @@ -9078,6 +10173,54 @@ bool XE::processAlderLakeNRejectedAccelerator(KernelPatcher &patcher,size_t inde patcher.clearError();return ready; } +// ADLP_LOAD_ONLY_HOOKS: these replacements never call the original methods. +IOService *XE::rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPFramebufferProbeRejected",true); + SYSLOG("igfx","ADL-P load-only: framebuffer probe rejected before native entry"); + return nullptr; +} +IOService *XE::rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPAcceleratorProbeRejected",true); + SYSLOG("igfx","ADL-P load-only: accelerator probe rejected before native entry"); + return nullptr; +} +bool XE::rejectADLPStart(IOService *,IOService *) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLPLoadOnlyUnexpectedStart",true); + return false; +} +bool XE::processADLPLoadOnly(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const bool fb=index==kextIntelTGLFb.loadIndex; + const char *probe=fb ? "__ZN31AppleIntelFramebufferController5probeEP9IOServicePi" : + "__ZN16IntelAccelerator5probeEP9IOServicePi"; + const char *start=fb ? "__ZN31AppleIntelFramebufferController5startEP9IOService" : + "__ZN16IntelAccelerator5startEP9IOService"; + const uint32_t probeOffset=fb?0x446BC:0x238E2,startOffset=fb?0x3F1FC:0x23ECC; + const uint8_t fbHead[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x83,0xec,0x10,0x48,0x89,0xf3,0x49,0x89,0xfe}; + const uint8_t gtHead[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; + const uint8_t startHead[]={0x55,0x48,0x89,0xe5}; + auto p=patcher.solveSymbol(index,probe,address,size); + auto q=patcher.solveSymbol(index,start,address,size);patcher.clearError(); + bool ready=(adlpLoadOnly || (adlpDisplaySelected && !fb)) && (fb || index==kextIntelTGL.loadIndex) && size>probeOffset+32 && size>startOffset+32 && + p==address+probeOffset && q==address+startOffset && + !memcmp(reinterpret_cast(p),fb?fbHead:gtHead,sizeof(gtHead)) && + !memcmp(reinterpret_cast(q),startHead,sizeof(startHead)); + if(ready) { + // Also block start as an independent boundary. A rejected probe must + // not attach; an unexpected direct start still cannot initialize GPU. + KernelPatcher::RouteRequest routes[]={ + {start,rejectADLPStart}, + {probe,fb?rejectADLPFramebufferProbe:rejectADLPAcceleratorProbe} + }; + ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size); + } + patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty(fb?"ADLPFramebufferLoadOnlyReady":"ADLPAcceleratorLoadOnlyReady",ready); + if(adlpDisplaySelected && !fb)IORegistryEntry::getRegistryRoot()->setProperty("ADLPFBOnlyAcceleratorBlocked",ready); + SYSLOG("igfx","ADL-P load-only: %s probe/start barriers ready=%u",fb?"framebuffer":"accelerator",ready); + return ready; +} +// ADLP_LOAD_ONLY_HOOKS_END + bool XE::processAlderLakeNLoadOnly(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { KernelPatcher::RouteRequest load[] { {"__ZN16IntelAccelerator5probeEP9IOServicePi",wrapTGLAcceleratorProbe,orgTGLAcceleratorProbe}, @@ -9451,7 +10594,7 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add uint8_t urbReplaceAlderLakeN[] {0x83,0xFE,0x01,0x75,0x59,0x83,0xFA,0x04,0x75,0x54,0x41,0xC7,0x87,0x64,0x11,0x00,0x00,0x04,0x00,0x00,0x00}; if (adlnDetected) { urbReplaceAlderLakeN[7]=adlnNativeSubslices;urbReplaceAlderLakeN[17]=adlnL3Banks; } if(display12SmallGTDetected) {urbReplaceAlderLakeN[7]=display12SmallGT.nativeSubslices;urbReplaceAlderLakeN[17]=display12SmallGT.l3Banks;} - const auto urbReplacement = (xeProfile.gt==XELP::GT::ADLNSmall || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : + const auto urbReplacement = (adlnDetected || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : (xeProfile.gt==XELP::GT::TGLG4 ? urbReplaceG4 : urbReplace); auto gpuInfo=patcher.solveSymbol(index, "__ZN16IntelAccelerator10getGPUInfoEv",address,size); @@ -10217,6 +11360,288 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add return true; } +// One entry owns donor ABI/lifecycle repairs for every hardware backend. +// Backend installers cannot independently opt into or replace these hooks. +bool XE::processFramebufferKext(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(framebufferDonorAttempted)return framebufferBackendInstalled; + framebufferDonorAttempted=true; + const auto symbol="__ZN31AppleIntelFramebufferController5startEP9IOService"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x49,0x89,0xf6,0x49,0x89,0xfd}; + const auto start=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(size<=0x3F1FC+sizeof(entry) || start!=address+0x3F1FC || + memcmp(reinterpret_cast(start),entry,sizeof(entry)))return false; + KernelPatcher::RouteRequest gate(symbol,wrapFramebufferStart,orgFramebufferStart); + const bool routed=patcher.routeMultiple(index,&gate,1,address,size);patcher.clearError(); + if(!routed)return false; + framebufferDonorCommonReady=processFramebufferCommon(patcher,index,address,size); + // Install hardware routes even after a common failure so their diagnostics + // remain available. The common start gate never enters native startup then. + framebufferBackendInstalled=display13FramebufferRequested ? + processDisplay13Framebuffer(patcher,index,address,size) : processKextFull(patcher,index,address,size); + uint32_t record[]={1,routed,framebufferDonorCommonReady,framebufferBackendInstalled}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferInstallation",record,sizeof(record)); + return true; +} + +bool XE::processFramebufferCommon(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + tglSonomaSurfaceFramebufferReady=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); + tglPlatformNVRAMReady=xelpInstallNVRAMABI(patcher,index,address,size); + auto table=patcher.solveSymbol(index,"__ZTV17AppleIntelPortHAL",address,size); + auto pure=patcher.solveSymbol(KernelPatcher::KernelID,"___cxa_pure_virtual"); + const uint8_t halSize[]={0xB9,0xB0,0x06,0,0}; + bool probe=size>0xCCCD8 && table==address+0xCCAD0 && pure && xelpProviderProbe && xelpProviderConfigureProbe && + !memcmp(reinterpret_cast(address+0x5EE5E),halSize,sizeof(halSize)) && + *reinterpret_cast(table+0x188)==pure; + patcher.clearError(); + if(probe) { + // Slot repair is common; the target retains the existing hardware adapter. + auto replacement=display13FramebufferRequested ? xelpProviderProbe : reinterpret_cast(wrapTGLProbePort); + patcher.routeBlock(table+0x188,reinterpret_cast(&replacement),sizeof(replacement)); + probe=patcher.getError()==KernelPatcher::Error::NoError && *reinterpret_cast(table+0x188)==replacement; + patcher.clearError(); + } + tglProbeReady=probe; + const auto initSymbol="__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj"; + const auto init=patcher.solveSymbol(index,initSymbol,address,size);patcher.clearError(); + const uint8_t initEntry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x54,0x53,0x48,0x83,0xEC,0x10,0x41,0x89,0xD6,0x49,0x89,0xF7,0x48,0x89,0xFB}; + KernelPatcher::RouteRequest binding(initSymbol,wrapFramebufferInit,orgFramebufferInit); + tglFramebufferInitReady=size>0x29EC8+sizeof(initEntry) && init==address+0x29EC8 && + !memcmp(reinterpret_cast(init),initEntry,sizeof(initEntry)) && patcher.routeMultiple(index,&binding,1,address,size); + patcher.clearError(); + const auto laneSymbol="__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj"; + const auto lanes=patcher.solveSymbol(index,laneSymbol,address,size);patcher.clearError(); + const uint8_t laneEntry[]={0x55,0x48,0x89,0xE5,0x53,0x50}; + const uint8_t laneCall[]={0xE8,0x2A,0xF7,0xFE,0xFF,0x84,0xC0,0x74,0x1A}; + bool laneReady=size>0x304FC && lanes==address+0x1FC22 && + !memcmp(reinterpret_cast(lanes),laneEntry,sizeof(laneEntry)) && + !memcmp(reinterpret_cast(address+0x304F3),laneCall,sizeof(laneCall)); + if(laneReady) { + framebufferLaneCaller=address+0x304F8; + KernelPatcher::RouteRequest lane(laneSymbol,wrapFramebufferComputeLanes,orgFramebufferComputeLanes); + laneReady=patcher.routeMultipleShort(index,&lane,1,address,size);patcher.clearError(); + } + const bool agdc=processFramebufferAGDC(patcher,index,address,size); + const bool runtime=processFramebufferDonorRuntime(patcher,index,address,size); + framebufferTrainingHooksReady=processFramebufferTraining(patcher,index,address,size); + const bool ready=tglSonomaSurfaceFramebufferReady && tglPlatformNVRAMReady && probe && tglFramebufferInitReady && laneReady && agdc && runtime && framebufferTrainingHooksReady; + uint32_t record[]={4,tglPlatformNVRAMReady,probe,tglFramebufferInitReady,tglSonomaSurfaceFramebufferReady,laneReady,agdc,runtime,framebufferTrainingHooksReady,ready}; + auto root=IORegistryEntry::getRegistryRoot(); + root->setProperty("WEXFramebufferCommonHooks",record,sizeof(record)); + root->setProperty("TGLSonomaSurfaceFramebufferReady",tglSonomaSurfaceFramebufferReady); + root->setProperty("TGLHDMILaneRoute317",laneReady); + root->setProperty("TGLHDMILaneGuard316",framebufferLaneCaller!=0); + root->setProperty("TGLWEGProbeVtableFixed",probe); + root->setProperty("TGLWEGFramebufferInitRouted",tglFramebufferInitReady); + return ready; +} + +bool XE::wrapFramebufferStart(IOService *that,IOService *provider) { + auto s=callbackXE; + if(!s->framebufferDonorCommonReady || !s->framebufferBackendInstalled || !s->tglPathPoolLock) { + if(that)that->setProperty("WEXFramebufferCommonRejected",true); + return false; + } + IORecursiveLockLock(s->tglPathPoolLock); + if(s->framebufferStartInProgress || s->tglLeasesStopping || s->framebufferStartedController) { + IORecursiveLockUnlock(s->tglPathPoolLock);return false; + } + s->framebufferStartInProgress=true; + IORecursiveLockUnlock(s->tglPathPoolLock); + if(comboClockTransaction.lock) { + IORecursiveLockLock(comboClockTransaction.lock); + comboClockTransaction.allocation={}; + IORecursiveLockUnlock(comboClockTransaction.lock); + } + const bool native=s->display13FramebufferRequested ? wrapDisplay13FramebufferStart(that,provider) : wrapTGLFramebufferStart(that,provider); + IORecursiveLockLock(s->tglPathPoolLock); + const bool accepted=native && !s->tglLeasesStopping; + __atomic_store_n(&s->framebufferStartedController,accepted?static_cast(that):nullptr,__ATOMIC_RELEASE); + s->framebufferStartInProgress=false; + IORecursiveLockUnlock(s->tglPathPoolLock); + return accepted; +} + +bool XE::wrapFramebufferInit(IOService *fb,IOService *controller,uint32_t index) { + if(!callbackXE->framebufferDonorCommonReady || !fb || !controller || index>=3 || + fb->getMetaClass()->getClassSize()<0x4B90 || controller->getMetaClass()->getClassSize()<0x1C20)return false; + if(!FunctionCast(wrapFramebufferInit,callbackXE->orgFramebufferInit)(fb,controller,index))return false; + struct Objects {size_t size(void *p) const {return p ? static_cast(p)->getMetaClass()->getClassSize() : 0;}} objects; + const bool bound=FramebufferDonor::bindManagers(objects,fb,controller,index); + fb->setProperty("WEXFramebufferManagersBound",bound); + if(callbackXE->display13FramebufferRequested)fb->setProperty("N97FBManagersBound",bound); + auto root=IORegistryEntry::getRegistryRoot(); + root->setProperty("TGLWEGFramebufferManagerBound",bound); + root->setProperty("TGLWEGPlaneManagersBound",bound?15:0,32); + root->setProperty("TGLWEGScalerManagersBound",bound?6:0,32); + return bound; +} + +bool XE::framebufferHDMIEligible(const uint32_t *config) const { + if(display13FramebufferRequested) + return Display13FBTopology::selected(adlnPlatform,config,adlnOutputWiring) && adlnOutputWiring.matchesHDMI(config); + return tglHDMIB && tglHDMIEligible(config); +} + +bool XE::wrapFramebufferComputeLanes(void *argument,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes) { + const auto caller=reinterpret_cast(__builtin_return_address(0)); + const auto native=FunctionCast(wrapFramebufferComputeLanes,callbackXE->orgFramebufferComputeLanes)(argument,timing,bpp,available,lanes); + const auto config=XELPDonor::hdmiLaneConfig(argument,caller,callbackXE->framebufferLaneCaller); + if(!config || !lanes)return native; + const bool corrected=XELPDonor::skipHDMIDPLanes(callbackXE->framebufferHDMIEligible(config),true,native,available,*lanes); + if(corrected) { + static uint32_t count {}; + IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneCorrections316",__sync_add_and_fetch(&count,1),32); + } + return native || corrected; +} + +XELP::LinkRequirements XE::framebufferLinkRequirements(void *port,uint32_t mode) { + auto self=callbackXE;auto p=static_cast(port); + XELP::LinkRequirements req; + if(!p)return req; + auto config=Display13FBMemory::field(p,0x548); + if(!config || config[0]>=9 || p[0x117] || + Display13FBMemory::field(p,0x120)!=config[0] || + Display13FBMemory::field(p,0x590)!=0x64000+0x100*config[0])return req; + const auto ddi=config[0]; + using R=XELP::LinkRequirements; + if(self->display13FramebufferRequested) { + if(!Display13FBTopology::selected(self->adlnPlatform,config,self->adlnOutputWiring) || + mode!=(ddi>=3?4U:1U))return req; + req={self->xeProfile.display,ddi>=3?R::PHY::TC:R::PHY::Combo, + (self->adlnOutputWiring.dp&(1U<(reinterpret_cast(self->tglPlatformCopy)+0x20+24*ddi); + if(!bound)return req; + if(mode==1 && self->tglHDMIEligible(config)) + req={self->xeProfile.display,R::PHY::Combo,R::Protocol::HDMI}; + else { + const auto route=self->tglSSTRoutes().external(ddi,mode); + using T=XELP::Display12SSTRoute::Transport; + if(config[2]==1 && (route.transport==T::Combo || route.transport==T::DekelLegacy)) + req={self->xeProfile.display,route.transport==T::Combo?R::PHY::Combo:R::PHY::TC,R::Protocol::FixedDP}; + } + } + return req; +} + +uint32_t XE::framebufferPortWaitPolls(void *port) { + if(!port)return 100; + return framebufferLinkRequirements(port,Display13FBMemory::field(port,0x550)).activePolls(); +} + +bool XE::processFramebufferPortWait(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + using namespace XELPPortWait; + const char *symbol="__ZN17AppleIntelPortHAL10enablePortEv"; + const auto enable=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + if(size(address+WaitOffset); + if(!waitKnown(wait,sizeof(WaitBefore)))return false; + uint8_t before[sizeof(WaitBefore)],after[sizeof(WaitBefore)]; + memcpy(before,wait,sizeof(before));XELPPortWait::patch(after,before,reinterpret_cast(framebufferPortWaitPolls)); + KernelPatcher::LookupPatch change{currentFramebuffer,before,after,sizeof(after),1}; + patcher.applyLookupPatch(&change,wait,sizeof(after)+1); + const bool ready=patcher.getError()==KernelPatcher::Error::NoError && !memcmp(wait,after,sizeof(after)); + patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXExternalLinkWaitReady",ready); + return ready; +} + +bool XE::processFramebufferDonorRuntime(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + if(framebufferDonorRuntimeReady)return true; + bool hdcpReady=false,meiReady=false; + if(!comboClockTransaction.lock)comboClockTransaction.lock=IORecursiveLockAlloc(); + framebufferClockImage=address; + const auto ddiPut=patcher.solveSymbol(index, + "__ZN19AppleIntelPowerWell19disablePowerWellDDIEj",address,size);patcher.clearError(); + bool ddiRequestReady=ddiPut==address+0x5AA86 && + XELPDonor::ddiReleaseSite(reinterpret_cast(address),size); + if(ddiRequestReady) { + patcher.routeBlock(address+XELPDonor::DDIReleaseOffset,XELPDonor::DDIReleaseAfter,sizeof(XELPDonor::DDIReleaseAfter)); + ddiRequestReady=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+XELPDonor::DDIReleaseOffset),XELPDonor::DDIReleaseAfter,sizeof(XELPDonor::DDIReleaseAfter)); + patcher.clearError(); + } + IORegistryEntry::getRegistryRoot()->setProperty("WEXDonorDDIRequestReady",ddiRequestReady); + + { + const char *symbol="__ZN15AppleIntelHDCP24initEP21AppleIntelDisplayPath"; + const uint8_t init[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x96,0x02,0x06,0x00,0x49,0x89}; + const uint8_t associations[]={0x4c,0x89,0x73,0x20,0x49,0x8b,0xbe,0xb0,0x32,0x00,0x00,0x48,0x89,0x7b,0x18,0x48,0x8b,0x87,0xd0,0x01,0x00,0x00,0x48,0x89,0x43,0x10,0x48,0xff,0x05,0x00,0xf5,0x05,0x00,0x49,0x8b,0x8e,0x48,0x36,0x00,0x00}; + const uint8_t use[]={0x48,0x8b,0x7f,0x38,0xbe,0xc0,0x50,0x06,0x00,0xe8,0x10,0x2c,0xfd,0xff}; + const auto fn=patcher.solveSymbol(index,symbol,address,size);patcher.clearError(); + const bool known=fn==address+0x8AB94 && size>=0x8B734 && + !memcmp(reinterpret_cast(fn),init,sizeof(init)) && + !memcmp(reinterpret_cast(address+0x8AC17),associations,sizeof(associations)) && + !memcmp(reinterpret_cast(address+0x8B726),use,sizeof(use)); + KernelPatcher::RouteRequest route(symbol,wrapTGLHDCP2Init,orgTGLHDCP2Init); + hdcpReady=known && patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGHDCP2InitRouted",hdcpReady); + } + // DONOR_PANEL_PROPERTY_BEGIN + { + // Both power-on and power-off converge on this property update. + // Native getBuiltInFramebuffer returns null when there is no + // built-in port; the donor nevertheless dereferences its vtable. + // Replace only the metadata call, preserving the PPS sequence. + const auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj", address, size); + static const uint8_t expected[] {0x48,0x8B,0x07,0x48,0x8D,0x35,0x1D,0xDB,0x05,0x00,0xFF,0x90,0x68,0x02,0x00,0x00}; + uint8_t replacement[] {0x48,0xB8,0,0,0,0,0,0,0,0,0xFF,0xD0,0x90,0x90,0x90,0x90}; + const auto handler = reinterpret_cast(wrapTGLPanelPowerProperty); + memcpy(replacement+2,&handler,sizeof(handler)); + auto site = reinterpret_cast(fn+0x45A); + if (fn == address+0x48652 && size >= 0x48ABC && !memcmp(site,expected,sizeof(expected))) { + patcher.routeBlock(fn+0x45A,replacement,sizeof(replacement)); + tglPanelPowerPropertyReady = patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(site,replacement,sizeof(replacement)); + } + IORegistryEntry::getRegistryRoot()->setProperty("TGLPanelPowerPropertyGuard",tglPanelPowerPropertyReady); + patcher.clearError(); + } + // DONOR_PANEL_PROPERTY_END + + // Native HDCP TX reported 21 bytes sent but H_CBWP advanced only + // three of seven required FIFO slots (r363). Flush the previous + // FIFO store before each payload store; the native trailing CSR + // read flushes the final word before H_IG. Replace only a profiling + // increment. ESI is reloaded immediately; framing/fragmentation, + // secure flags, queues and completion semantics remain native. + { + static const uint8_t loop[] { + 0x44,0x89,0x38,0x85,0xd2,0x74,0x3d,0x48,0xff,0x05,0xd1,0x4b,0x07,0x00, + 0x89,0xd1,0x31,0xd2,0x48,0xff,0x05,0xce,0x4b,0x07,0x00, + 0x41,0x8b,0x34,0x96,0x48,0xff,0x05,0xd3,0x4b,0x07,0x00, + 0x89,0x30,0x48,0xff,0xc2,0x48,0x39,0xd1,0x75,0xe4}; + static const uint8_t readback[] {0x8b,0x70,0x04,0x90,0x90,0x90,0x90}; + static const uint8_t notify[] {0x8b,0x48,0x04,0x83,0xe1,0xf9,0x83,0xc9,0x04,0x89,0x48,0x04}; + const auto fn = patcher.solveSymbol(index, + "__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", address, size); + bool known = size >= 0x716f7 && fn == address + 0x714b6 && + !memcmp(reinterpret_cast(address + 0x71699), loop, sizeof(loop)) && + !memcmp(reinterpret_cast(address + 0x716eb), notify, sizeof(notify)); + if (known) { + patcher.clearError(); + patcher.routeBlock(address + 0x716ab, readback, sizeof(readback)); + known = patcher.getError() == KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address + 0x716ab), readback, sizeof(readback)); + } + // The four-byte HBM path also checks each accepted FIFO word. + // Keep this functional correction independent of trace allocation. + KernelPatcher::RouteRequest control( + "__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", + wrapTGLMEIWrite,orgTGLMEIWrite); + meiReady=known && patcher.routeMultiple(index,&control,1,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXMEIControlWriteReady",meiReady); + IORegistryEntry::getRegistryRoot()->setProperty("TGLMEITXFIFOReadbackR364", known); + patcher.clearError(); + } + framebufferDonorRuntimeReady=comboClockTransaction.lock && hdcpReady && tglPanelPowerPropertyReady && meiReady && ddiRequestReady && + processFramebufferPortWait(patcher,index,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferDonorRuntimeReady",framebufferDonorRuntimeReady); + return framebufferDonorRuntimeReady; +} + bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { if (adlnRequested || adlnDetected) { if (!adlnDetected || !adlnNativeRequested || !adlnNativeReady || !tglActive || @@ -10225,8 +11650,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (tglRequested) { if (!tglActive) return false; if (currentFramebuffer->loadIndex == index) { - tglSonomaSurfaceFramebufferReady=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); - IORegistryEntry::getRegistryRoot()->setProperty("TGLSonomaSurfaceFramebufferReady",tglSonomaSurfaceFramebufferReady); if (modDVMTCalcFix.enabled && modDVMTCalcFix.available) modDVMTCalcFix.processFramebufferKext(patcher, index, address, size); // Exact donor transaction entries. Each installed wrapper arbitrates @@ -10275,7 +11698,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t tglConnectionImage = address; tglConnectionImageSize = size; tglConnectionLock = IOSimpleLockAlloc(); - tglPlatformNVRAMReady=xelpInstallNVRAMABI(patcher,index,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("TGLPlatformNVRAMABIReady", tglPlatformNVRAMReady); if (tglHDMIB) { // r319: the donor's combo PLL tables still contain ICL MMIO @@ -10546,23 +11969,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } if (tglActive) { - // Both power-on and power-off converge on this property update. - // Native getBuiltInFramebuffer returns null when there is no - // built-in port; the donor nevertheless dereferences its vtable. - // Replace only the metadata call, preserving the PPS sequence. - const auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj", address, size); - static const uint8_t expected[] {0x48,0x8B,0x07,0x48,0x8D,0x35,0x1D,0xDB,0x05,0x00,0xFF,0x90,0x68,0x02,0x00,0x00}; - uint8_t replacement[] {0x48,0xB8,0,0,0,0,0,0,0,0,0xFF,0xD0,0x90,0x90,0x90,0x90}; - const auto handler = reinterpret_cast(wrapTGLPanelPowerProperty); - memcpy(replacement+2,&handler,sizeof(handler)); - auto site = reinterpret_cast(fn+0x45A); - if (fn == address+0x48652 && size >= 0x48ABC && !memcmp(site,expected,sizeof(expected))) { - patcher.routeBlock(fn+0x45A,replacement,sizeof(replacement)); - tglPanelPowerPropertyReady = patcher.getError()==KernelPatcher::Error::NoError && - !memcmp(site,replacement,sizeof(replacement)); - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLPanelPowerPropertyGuard",tglPanelPowerPropertyReady); - patcher.clearError(); + if (framebufferPatchFlags.bits.FPFStolenMemorySize) { const auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController16getOSInformationEv", address, size); // Selected platform +0x14 is copied to controller +0xDE8 @@ -10578,33 +11985,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTopology", tglTopologyReady); patcher.clearError(); } - // DP1 KC vtable inheritance fixup replaces this externally supplied - // override with the base class's pure-virtual slot. Verify and repair - // only this donor slot; never replace the global pure-virtual handler. - auto table = patcher.solveSymbol(index, "__ZTV17AppleIntelPortHAL", address, size); - auto probe = reinterpret_cast(wrapTGLProbePort); - auto pure = patcher.solveSymbol(KernelPatcher::KernelID, "___cxa_pure_virtual"); - bool probeFixed = false; - if (table && probe && pure && xelpProviderProbe && xelpProviderConfigureProbe && table >= address && table - address == 0xCCAD0 && - *reinterpret_cast(table + 0x188) == pure) { - patcher.routeBlock(table + 0x188, reinterpret_cast(&probe), sizeof(probe)); - probeFixed = patcher.getError() == KernelPatcher::Error::NoError && - *reinterpret_cast(table + 0x188) == probe; - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGProbeVtableFixed", probeFixed); - tglProbeReady = probeFixed; - patcher.clearError(); - KernelPatcher::RouteRequest initRequest("__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj", - wrapTGLFramebufferInit, orgTGLFramebufferInit); - tglFramebufferInitReady = patcher.routeMultiple(index, &initRequest, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferInitRouted", tglFramebufferInitReady); - patcher.clearError(); - { - KernelPatcher::RouteRequest init("__ZN15AppleIntelHDCP24initEP21AppleIntelDisplayPath", wrapTGLHDCP2Init, orgTGLHDCP2Init); - const bool routed = patcher.routeMultiple(index, &init, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGHDCP2InitRouted", routed); - patcher.clearError(); - } + { KernelPatcher::RouteRequest hdcp[] { {"__ZN15AppleIntelHDCP113dispatchEventENS_10HDCP1EventE", wrapTGLHDCP1Dispatch, orgTGLHDCP1Dispatch}, @@ -10624,12 +12005,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t if (tglFBIRQReady) tglFBIRQReady=patcher.routeMultiple(index,irq,arrsize(irq),address,size); IORegistryEntry::getRegistryRoot()->setProperty("TGLFBIRQRouted",tglFBIRQReady);patcher.clearError(); } - KernelPatcher::RouteRequest request("__ZN31AppleIntelFramebufferController5startEP9IOService", - wrapTGLFramebufferStart, orgTGLFramebufferStart); - const bool ok = patcher.routeMultiple(index, &request, 1, address, size); - SYSLOG("igfx", "TGL framebuffer start route=%d", ok); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferStartRouted", ok); - patcher.clearError(); + { KernelPatcher::RouteRequest observe("__ZN21AppleIntelFramebuffer11getDDCBlockEijjjPhPy", wrapTGLTopDDC, orgTGLTopDDC); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -10655,18 +12031,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } { - if (tglHDMIB) { - const auto validate = patcher.solveSymbol(index, "__ZN21AppleIntelFramebuffer22validateDetailedTimingEPvy", address, size); - const uint8_t call[] {0xE8,0x2A,0xF7,0xFE,0xFF,0x84,0xC0,0x74,0x1A}; - if (validate && !memcmp(reinterpret_cast(validate+0x529), call, sizeof(call))) - tglHDMILaneCaller = validate+0x52E; - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneGuard316", tglHDMILaneCaller != 0); - patcher.clearError(); - KernelPatcher::RouteRequest laneRoute("__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj", wrapTGLComputeLanes, orgTGLComputeLanes); - const bool ok = patcher.routeMultiple(index, &laneRoute, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneRoute317", ok); - patcher.clearError(); - } + KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController22SetFbStatusOnNextProbeEP14AGDCFBOnline_t", wrapTGLFBOnline, orgTGLFBOnline); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteFBOnline", routed); @@ -10684,12 +12049,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteConnectionAttr", routed); patcher.clearError(); } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController12CallBackAGDCE31kAGDCRegisterLinkControlEvent_tmj", wrapTGLAGDCCallback, orgTGLAGDCCallback); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteAGDCCallback", routed); - patcher.clearError(); - } { KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController16GetGPUCapabilityEP19AGDCGPUCapability_t", wrapTGLGPUCapability, orgTGLGPUCapability); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -10732,12 +12091,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteFBSetAttr", routed); patcher.clearError(); } - { - KernelPatcher::RouteRequest observe("__ZN21AppleIntelFramebuffer13newUserClientEP4taskPvjPP12IOUserClient", wrapTGLFBUserClient, orgTGLFBUserClient); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteFBUserClient", routed); - patcher.clearError(); - } { KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController18GetStreamAccessAUXEP21AGDCStreamAccessAUX_tS1_", wrapTGLAGDCAUX, orgTGLAGDCAUX); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -10757,24 +12110,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController19doLinkTrainingBeginEP25AGDCDPLinkTrainingBegin_t", wrapTGLTrainingBegin, orgTGLTrainingBegin); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTrainingBegin", routed); - patcher.clearError(); - } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController14doLinkTrainingEP18AGDCDPPortConfig_tS1_", wrapTGLTraining, orgTGLTraining); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTraining", routed); - patcher.clearError(); - } - { - KernelPatcher::RouteRequest observe("__ZN31AppleIntelFramebufferController17doLinkTrainingEndEP23AGDCDPLinkTrainingEnd_t", wrapTGLTrainingEnd, orgTGLTrainingEnd); - const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTrainingEnd", routed); - patcher.clearError(); - } { KernelPatcher::RouteRequest observe("__ZN17AppleIntelPortHAL10enablePortEv", wrapTGLEnablePort, orgTGLEnablePort); const bool routed = patcher.routeMultiple(index, &observe, 1, address, size); @@ -10980,8 +12315,6 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); KernelPatcher::RouteRequest observations[] { {"__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t", wrapTGLPortTraining, orgTGLPortTraining}, - {"__ZN21AppleIntelDisplayPath18getFirstPathByPortEP14AppleIntelPort", wrapTGLPathLookup, orgTGLPathLookup}, - {"__ZN21AppleIntelDisplayPath8freePathEv", wrapTGLFreePath, orgTGLFreePath}, {"__ZN31AppleIntelFramebufferController9LightUpDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2", wrapTGLLightUpDP, orgTGLLightUpDP}, {"__ZN31AppleIntelFramebufferController11LightUpTMDSEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2", wrapTGLLightUpTMDS, orgTGLLightUpTMDS}, {"__ZN31AppleIntelFramebufferController14disableDisplayEP21AppleIntelFramebufferP21AppleIntelDisplayPathh", wrapTGLDisableDisplay, orgTGLDisableDisplay}, @@ -11008,9 +12341,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t {"__ZN31AppleIntelFramebufferController31releaseResourceConfigurationSetEP28AGDCFBReleaseConfiguration_t", wrapTGLReleaseResources, orgTGLReleaseResources}, {"__ZN31AppleIntelFramebufferController30commitResourceConfigurationSetEP30AGDCResourceConfigurationSet_tS1_", wrapTGLCommitResources, orgTGLCommitResources}, {"__ZN31AppleIntelFramebufferController28testResourceConfigurationSetEP30AGDCResourceConfigurationSet_tS1_", wrapTGLTestResources, orgTGLTestResources}, - {"__ZN21AppleIntelFramebuffer17attachDisplayPathEP21AppleIntelDisplayPath", wrapTGLAttachPath, orgTGLAttachPath}, - {"__ZN21AppleIntelFramebuffer28allocateDisplayPathResourcesEjj", wrapTGLAllocatePathResources, orgTGLAllocatePathResources}, - {"__ZN21AppleIntelFramebuffer14addDisplayPathEj", wrapTGLAddPath, orgTGLAddPath} + {"__ZN21AppleIntelFramebuffer17attachDisplayPathEP21AppleIntelDisplayPath", wrapTGLAttachPath, orgTGLAttachPath} }; const bool routed = patcher.routeMultiple(index, observations, arrsize(observations), address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteResourceObservations", routed); @@ -11022,34 +12353,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteMEConnectTiming", routed); patcher.clearError(); } - // Native HDCP TX reported 21 bytes sent but H_CBWP advanced only - // three of seven required FIFO slots (r363). Flush the previous - // FIFO store before each payload store; the native trailing CSR - // read flushes the final word before H_IG. Replace only a profiling - // increment. ESI is reloaded immediately; framing/fragmentation, - // secure flags, queues and completion semantics remain native. - { - static const uint8_t loop[] { - 0x44,0x89,0x38,0x85,0xd2,0x74,0x3d,0x48,0xff,0x05,0xd1,0x4b,0x07,0x00, - 0x89,0xd1,0x31,0xd2,0x48,0xff,0x05,0xce,0x4b,0x07,0x00, - 0x41,0x8b,0x34,0x96,0x48,0xff,0x05,0xd3,0x4b,0x07,0x00, - 0x89,0x30,0x48,0xff,0xc2,0x48,0x39,0xd1,0x75,0xe4}; - static const uint8_t readback[] {0x8b,0x70,0x04,0x90,0x90,0x90,0x90}; - static const uint8_t notify[] {0x8b,0x48,0x04,0x83,0xe1,0xf9,0x83,0xc9,0x04,0x89,0x48,0x04}; - const auto fn = patcher.solveSymbol(index, - "__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", address, size); - bool known = size >= 0x716f7 && fn == address + 0x714b6 && - !memcmp(reinterpret_cast(address + 0x71699), loop, sizeof(loop)) && - !memcmp(reinterpret_cast(address + 0x716eb), notify, sizeof(notify)); - if (known) { - patcher.clearError(); - patcher.routeBlock(address + 0x716ab, readback, sizeof(readback)); - known = patcher.getError() == KernelPatcher::Error::NoError && - !memcmp(reinterpret_cast(address + 0x716ab), readback, sizeof(readback)); - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLMEITXFIFOReadbackR364", known); - patcher.clearError(); - } + tglMEILock = IOSimpleLockAlloc(); if (tglMEILock) { KernelPatcher::RouteRequest observations[] { @@ -11057,8 +12361,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t {"__ZN19AppleIntelMEIDriver15handleInterruptEP22IOInterruptEventSourcei", wrapTGLMEIInterrupt, orgTGLMEIInterrupt}, {"__ZN19AppleIntelMEIDriver15filterInterruptEP28IOFilterInterruptEventSource", wrapTGLMEIFilter, orgTGLMEIFilter}, {"__ZN19AppleIntelMEIDriver16readHeaderFromMEEv", wrapTGLMEIHeader, orgTGLMEIHeader}, - {"__ZN19AppleIntelMEIDriver17readMessageFromMEEPvPjPh", wrapTGLMEIRead, orgTGLMEIRead}, - {"__ZN19AppleIntelMEIDriver16writeMessageToMEEPvPjhhb", wrapTGLMEIWrite, orgTGLMEIWrite} + {"__ZN19AppleIntelMEIDriver17readMessageFromMEEPvPjPh", wrapTGLMEIRead, orgTGLMEIRead} }; const bool routed = patcher.routeMultiple(index, observations, arrsize(observations), address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteMEIProtocol", routed); @@ -11145,20 +12448,12 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } if (tglTrainingLeaseRequested) { - tglPathPoolLock = IORecursiveLockAlloc(); - for (uint32_t ddi = 0; ddi < TGLPortCount; ++ddi) tglTrainingLeases[ddi].ddi = ddi; KernelPatcher::SolveRequest symbols[] { {"__ZN17AppleIntelPortHAL11disablePortEv", tglDisablePort}, {"__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_", tglDisableClocks} }; - KernelPatcher::RouteRequest routes[] { - {"__ZN21AppleIntelDisplayPath12allocatePathEv", wrapTGLAllocatePathAny, orgTGLAllocatePathAny}, - {"__ZN21AppleIntelDisplayPath12allocatePathEj", wrapTGLAllocatePathFixed, orgTGLAllocatePathFixed}, - {"__ZN31AppleIntelFramebufferController4stopEP9IOService", wrapTGLControllerStop, orgTGLControllerStop} - }; - tglTrainingLeaseReady = tglPathPoolLock && - patcher.solveMultiple(index, symbols, arrsize(symbols), address, size) && - patcher.routeMultiple(index, routes, arrsize(routes), address, size); + tglTrainingLeaseReady = framebufferTrainingHooksReady && + patcher.solveMultiple(index, symbols, arrsize(symbols), address, size); IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGRouteTrainingLease", tglTrainingLeaseReady); IORegistryEntry::getRegistryRoot()->setProperty("TGLTrainingFreePath320", tglTrainingLeaseReady); patcher.clearError(); @@ -11178,14 +12473,21 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t ready = patcher.routeMultiple(index, &route, 1, address, size); } patcher.clearError(); + if(ready && !adlsDisplaySelected) { + const char *name="__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams"; + const auto fn=patcher.solveSymbol(index,name,address,size);patcher.clearError(); + const uint8_t prefix[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + ready=orgTGLRegisterRead && comboClockTransaction.lock && fn==address+0x6CE10 && !memcmp(reinterpret_cast(fn),prefix,sizeof(prefix)); + KernelPatcher::RouteRequest enable(name,wrapTGLComboEnableClocks,orgComboEnableClocks); + ready=ready && patcher.routeMultiple(index,&enable,1,address,size);patcher.clearError(); + } if (ready) tglClockOffCaller = address + 0x4D990; IORegistryEntry::getRegistryRoot()->setProperty("TGLClockOrderReady", ready); } { KernelPatcher::SolveRequest symbols[] { {"__ZN19AppleIntelPowerWell17enablePowerWellPGEj", tglEnablePG}, - {"__ZN19AppleIntelPowerWell18disablePowerWellPGEj", tglDisablePG}, - {"__ZN14AppleIntelPort12getPortByDDIEj", tglPortByDDI} + {"__ZN19AppleIntelPowerWell18disablePowerWellPGEj", tglDisablePG} }; if (patcher.solveMultiple(index, symbols, arrsize(symbols), address, size)) { KernelPatcher::RouteRequest routes[] { @@ -11343,7 +12645,8 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; const auto symbol="__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams"; const auto fn=patcher.solveSymbol(index,symbol,address,size); - bool ready=adlsPLLTableReady && orgTGLDisableClocks && tglClockOffCaller && tglComboPLLFractionCaller && + bool ready=adlsPLLTableReady && comboClockTransaction.lock && tglTCNativeFunctions.complete() && + orgTGLRegisterRead && orgTGLRegisterWrite && orgTGLDisableClocks && tglClockOffCaller && tglComboPLLFractionCaller && fn==address+0x6CE10 && size>0x6DCBD && !memcmp(reinterpret_cast(fn),entry,sizeof(entry)); struct Site {uint32_t offset;const uint8_t *find,*replace;size_t bytes;}; const Site sites[]={{0x6CFD3,adlsClockOnFind,adlsClockOnReplace,sizeof(adlsClockOnFind)}, @@ -11355,7 +12658,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t for(const auto &site:sites)ready=ready && !memcmp(reinterpret_cast(address+site.offset),site.find,site.bytes); patcher.clearError(); if(ready) { - KernelPatcher::RouteRequest route(symbol,wrapADLSEnableClocks,orgADLSEnableClocks); + KernelPatcher::RouteRequest route(symbol,wrapADLSEnableClocks,orgComboEnableClocks); ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); if(ready)for(const auto &site:sites) { KernelPatcher::LookupPatch patch {currentFramebuffer,site.find,site.replace,site.bytes,1}; @@ -11592,7 +12895,7 @@ bool XE::tglLoadTCWiring(IOService *provider) { auto data=OSDynamicCast(OSData,property); self->tglTCWiring=property ? (data?TGLTCWiring::fromProperty(data->getBytesNoCopy(),data->getLength()):TGLTCWiring{}) : TGLTCWiring::fromMasks(0,0,0); if(!property && XELP::displayAdapter(self->xeProfile.family)==XELP::DisplayAdapter::TGL) - self->tglTCAutoCandidates=self->firmwarePorts.tglDPCandidates(self->tglAUXParentMask&0x18); + self->tglTCAutoCandidates=self->firmwarePorts.tglDPCandidates(self->tglAUXParentMask&0x78); IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTCDPCandidates",self->tglTCAutoCandidates,32); // The qualified donor ready switch covers TC1..4. Resolve modular FIA // coordinates under TC-cold before admitting their mode3 transaction. @@ -11642,6 +12945,7 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { XELP::DisplayContract::Mask XE::display12Available() const { using C=XELP::DisplayContract;C::Mask a=0; + if(framebufferDonorRuntimeReady)a|=C::DonorRuntime; if(tglSonomaSurfaceFramebufferReady)a|=C::Surface; if(tglPlatformNVRAMReady)a|=C::NVRAM; if(tglFBIRQReady)a|=C::IRQ; @@ -11691,6 +12995,7 @@ XELP::DisplayContract::Mask XE::display12Available() const { XELP::DisplayContract::Mask XE::display13Available() const { using C=XELP::DisplayContract;C::Mask a=0; + if(framebufferDonorRuntimeReady)a|=C::DonorRuntime; if(display13FBPowerReferencesReady)a|=C::PowerRefs13; if(display13FBEngineEnableHookReady)a|=C::Engine13; if(display13FBLifecycleHooksReady)a|=C::Lifecycle13; @@ -11709,10 +13014,46 @@ XELP::DisplayContract::Mask XE::display13Available() const { if(display13FBSSTHooksReady)a|=C::SST13; if(display13FBAUXHooksReady)a|=C::AUX13; if(display13DPProbeReady)a|=C::DPProbe13; - // No DPBoot13 until GOP handoff and cold-start have a proven backend. + // Fixed TC DP supports cold training and an explicitly validated SST GOP. + // Boot source, memory, Mode/AUX and native path adoption are all required. + if(display13FBDPHooksReady && display13TCDPProbeReady)a|=C::DPBoot13; + if(display13TrainingReady)a|=C::Training12; + if(display13FramingReady)a|=C::Framing; + if(!display13ClockOrderReady)a&=~C::ClockOrder; return a; } +// Run once before native start creates any path, IRQ or power owner. +// Subsequent starts must never reinterpret an OS-owned pipe as firmware GOP. +bool XE::adlsPrepareBoot(IOService *provider) { + auto self=callbackXE; + if(!self->adlsDisplaySelected)return true; + static uint32_t stage {};static bool accepted {}; + if(__sync_val_compare_and_swap(&stage,0,1)!=0)return stage==2 && accepted; + AlderLakeS::Boot::Result result; + auto pci=OSDynamicCast(IOPCIDevice,provider); + if(pci && XELP::deviceProfile(pci).identity==self->xeProfile.identity && (pci->configRead16(4)&2)) { + auto map=pci->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { + struct IO { + volatile uint8_t *base; + bool readable(uint32_t reg){return reg<=0xFFFFFC;} + uint32_t read(uint32_t reg){return *reinterpret_cast(base+reg);} + void write(uint32_t reg,uint32_t value){*reinterpret_cast(base+reg)=value;} + void delay(){IODelay(100);} + } io {reinterpret_cast(map->getVirtualAddress())}; + result=AlderLakeS::Boot::prepare(io,true,self->adlsWiring); + } + if(map)map->release(); + } + // Unclassified firmware remains untouched. The live training guard still + // rejects it. A partially retired pipe must not enter native startup. + accepted=!result.writes || result.status==AlderLakeS::Boot::Status::Complete; + __atomic_store_n(&self->adlsBootConsumersFailed,!accepted,__ATOMIC_RELEASE); + IORegistryEntry::getRegistryRoot()->setProperty("ADLSBootConsumers",&result,sizeof(result)); + __atomic_store_n(&stage,2,__ATOMIC_RELEASE);return accepted; +} + bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady)) { @@ -11728,6 +13069,9 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(auto live=OSSerializer::forTarget(nullptr,tglDPCDSerialize)) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPCDLive",live);live->release(); } + if(auto live=OSSerializer::forTarget(nullptr,tglComboAUXDecisionSerialize)) { + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeComboAUXDecisions",live);live->release(); + } for(uintptr_t ddi=0;ddi<=8;++ddi) { if(ddi==2)continue; // TGL combo A/B and TC D..I; no combo C. if(auto live=OSSerializer::forTarget(nullptr,tglAUXLiveSerialize,reinterpret_cast(ddi))) { @@ -11768,6 +13112,8 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { return false; } + if(!adlsPrepareBoot(provider))return false; + // r88: use the native provider-workloop mode so IRQ registration does not // depend on a WindowServer framebuffer open after accelerator startup. if (callbackXE->tglAcceleratorRequested && checkKernelArgument("-disablegfxfirmware")) { @@ -11793,7 +13139,7 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { tglTimeEvent(22, 0, false, 0, 0); SYSLOG("igfx", "TGL framebuffer start entered"); - bool result = FunctionCast(wrapTGLFramebufferStart, callbackXE->orgTGLFramebufferStart)(that, provider); + bool result = FunctionCast(wrapTGLFramebufferStart, callbackXE->orgFramebufferStart)(that, provider); tglTimeEvent(22, 0, true, 0, result); tglRecordStartOrder(4,that,9,0,0,result); tglFBIRQPublish(); @@ -11847,7 +13193,8 @@ uint32_t XE::wrapTGLLaneMask(void *port) { bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || - (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return false; + (callbackXE->adlsDisplaySelected && (!callbackXE->adlsDisplayHooksReady || + __atomic_load_n(&callbackXE->adlsBootConsumersFailed,__ATOMIC_ACQUIRE))))return false; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { SYSLOG("igfx", "AlderLakeN native RCS profile qualification rejected accelerator start"); return false; @@ -11856,7 +13203,7 @@ bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { // No parent/native start, private-object reads, engine work or fake success. auto pci=OSDynamicCast(IOPCIDevice,provider); const bool admitted=callbackXE->tglAcceleratorReady && pci && - xelpAlderLakeNDevice(pci); + xelpDisplay13RenderDevice(pci); IORegistryEntry::getRegistryRoot()->setProperty("N97LoadOnlyStartBlocked",true); IORegistryEntry::getRegistryRoot()->setProperty("N97LoadOnlyProviderReady",admitted); if (admitted && callbackXE->adlnParentBoundaryReady && checkKernelArgument("-wex-adln-parent-entry")) @@ -11867,7 +13214,7 @@ bool XE::wrapTGLAcceleratorStart(IOService *that, IOService *provider) { if (!callbackXE->tglAcceleratorReady) return false; if (callbackXE->adlnDetected) { auto pci=OSDynamicCast(IOPCIDevice,provider); - if (!callbackXE->adlnNativeReady || !pci || !xelpAlderLakeNDevice(pci)) return false; + if (!callbackXE->adlnNativeReady || !pci || !xelpDisplay13RenderDevice(pci)) return false; // Standalone startup never calls framebuffer start, which normally // exposes these retained IRQ/submission records. Publish the same // passive serializer here; it makes no driver calls or GPU reads. @@ -11979,7 +13326,7 @@ bool XE::startAlderLakeNParentBoundary(IOService *that, IOService *provider) { bool XE::wrapAlderLakeNConfigureDevice(IOService *that, IOPCIDevice *provider) { auto root=IORegistryEntry::getRegistryRoot(); const bool admitted=callbackXE->adlnParentBoundaryReady && provider && - xelpAlderLakeNDevice(provider); + xelpDisplay13RenderDevice(provider); root->setProperty("N97ConfigureDeviceEntered",admitted); if (admitted) { const auto bytes=reinterpret_cast(that); @@ -12252,7 +13599,7 @@ IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SIn if (callbackXE->adlnDetected && checkKernelArgument("-wex-adln-load-only")) { auto root=IORegistryEntry::getRegistryRoot(); const bool admitted=callbackXE->tglAcceleratorReady && pci && - xelpAlderLakeNDevice(pci); + xelpDisplay13RenderDevice(pci); root->setProperty("N97LoadOnlyProbeEntered",admitted); if (!admitted) return nullptr; // Qualify load/routing before executing the donor probe itself. @@ -12362,7 +13709,7 @@ bool XE::wrapAlderLakeNNativeConfigure(IOService *that, IOPCIDevice *provider) { // retain, factory replacement, or deliberate failure on the normal path. auto root=IORegistryEntry::getRegistryRoot(); root->setProperty("N97NativeConfigureEntered",true); - if (!provider || !xelpAlderLakeNDevice(provider)) return false; + if (!provider || !xelpDisplay13RenderDevice(provider)) return false; auto bar=provider->getDeviceMemoryWithRegister(0x10); auto map=bar && bar->getLength()==0x1000000 ? bar->map() : nullptr; if (!map) return false; @@ -14154,61 +15501,7 @@ bool XE::wrapTGLHDCP2Init(void *hdcp, void *path) { return stored == manager; } -bool XE::wrapTGLFramebufferInit(void *framebuffer, void *controller, uint32_t index) { - tglTimeEvent(23, index, false, 0, 0); - const auto initialized = FunctionCast(wrapTGLFramebufferInit, callbackXE->orgTGLFramebufferInit)(framebuffer, controller, index); - tglTimeEvent(23, index, true, 0, initialized); - if (!initialized) return false; - // This donor initializes the controller's shared MMIO manager but leaves - // the framebuffer's manager unset. Borrow it for direct register access; - // the controller owns it for the lifetime of its child framebuffers. - auto fb = static_cast(framebuffer); - auto ctl = static_cast(controller); - if (*reinterpret_cast(fb + 0x1D0) != controller) return false; - auto manager = *reinterpret_cast(ctl + 0xC40); - if (!manager || !*reinterpret_cast(manager + 0x50) || - *reinterpret_cast(manager + 0x60) <= 0x60404) return false; - // All 15 controller-owned planes are constructed before framebuffer init. - // Check every plane identity before changing any manager reference. - for (uint32_t i = 0; i < 15; ++i) { - auto plane = *reinterpret_cast(ctl + 0xF58 + i * sizeof(void *)); - if (!plane || *reinterpret_cast(plane + 0x7C) != i) { - SYSLOG("igfx", "TGL plane identity check failed index=%u", i); - return false; - } - } - // Six scalers precede the planes in controller construction order. - for (uint32_t i = 0; i < 6; ++i) { - auto scaler = *reinterpret_cast(ctl + 0xF28 + i * sizeof(void *)); - if (!scaler || *reinterpret_cast(scaler + 0x1C) != i) { - SYSLOG("igfx", "TGL scaler identity check failed index=%u", i); - return false; - } - } - uint32_t scalerBound = 0; - for (uint32_t i = 0; i < 6; ++i) { - auto scaler = *reinterpret_cast(ctl + 0xF28 + i * sizeof(void *)); - auto &scalerManager = *reinterpret_cast(scaler + 0x28); - if (!scalerManager) scalerManager = manager; - if (scalerManager == manager) ++scalerBound; - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGScalerManagersBound", scalerBound, 32); - SYSLOG("igfx", "TGL scaler managers bound=%u", scalerBound); - uint32_t bound = 0; - for (uint32_t i = 0; i < 15; ++i) { - auto plane = *reinterpret_cast(ctl + 0xF58 + i * sizeof(void *)); - auto &planeManager = *reinterpret_cast(plane + 0x90); - if (!planeManager) planeManager = manager; - if (planeManager == manager) ++bound; - } - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGPlaneManagersBound", bound, 32); - SYSLOG("igfx", "TGL plane managers bound=%u", bound); - auto &stored = *reinterpret_cast(fb + 0x4A40); - if (!stored) stored = manager; - IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGFramebufferManagerBound", stored == manager); - SYSLOG("igfx", "TGL framebuffer init index=%u manager bound=%d", index, stored == manager); - return true; -} + uint32_t XE::wrapTGLHPDState(void *port) { @@ -14303,20 +15596,23 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { if(transitionLock)IORecursiveLockUnlock(transitionLock); return kIOReturnNotReady; } + // Select fixed-link requirements after the existing native admission. Native + // owns cold/GOP mode acquisition on Display12, with our mapped FIA accesses. + using LR=XELP::LinkRequirements; + const LR fixed{self->xeProfile.display,self->tglLegacyTCEligible(traceDDI)?LR::PHY::TC:LR::PHY::None,LR::Protocol::FixedDP}; const bool fiaEligible=dpAltTransaction || ((mode==0 || mode==4) && traceDDI<9 && - (previousMode==0 || previousMode==4) && self->tglLegacyTCEligible(traceDDI) && - (__atomic_load_n(&self->tglModularProbed,__ATOMIC_ACQUIRE)&(1U<tglNativeFiaContext,reinterpret_cast(current_thread()),fiaEligible?port:nullptr, traceDDI,transitionLock && self->tglNativeFiaReady); auto result = FunctionCast(wrapTGLSetPortMode, callbackXE->orgTGLSetPortMode)(port, mode); - fiaScope.finish(); + const bool fiaFailed=fiaScope.failed();fiaScope.finish(); + if(fiaFailed && !result)result=kIOReturnNotReady; if(fiaEligible && self->tglNativeFiaReady && transitionLock){ - TGLNativeFia mapping; - if(mapping.resolve(traceDDI)){ - uint32_t record[]={1,traceDDI,mapping.fia.instance,mapping.fia.readyMask, + // No topology MMIO after native release; do not report assumed modular + // coordinates for a transaction that may have used non-modular FIA. + uint32_t record[]={2,traceDDI,static_cast(fiaFailed), __atomic_load_n(&self->tglNativeFiaReads,__ATOMIC_RELAXED),__atomic_load_n(&self->tglNativeFiaWrites,__ATOMIC_RELAXED)}; IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeNativeFiaAccess",record,sizeof(record)); - } } // A rejected mode must not look like success to training/lifetime callers. // Read software state only; do not add FIA MMIO outside a power reference. @@ -14997,6 +16293,7 @@ uint32_t XE::wrapTGLTopDDC(void *fb, int connection, uint32_t block, uint32_t ty } uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { + if(comboClockAccess(access))reg=comboClockTransaction.translation.reg(reg,XELPComboClock::donorPC(callbackXE->framebufferClockImage,reinterpret_cast(__builtin_return_address(0)))); const bool trace=tglTransitionRegister(reg); if(trace)tglTransitionRecord(35,false,access,reg,tglConnectionCaller(__builtin_return_address(0)),0); auto value = FunctionCast(wrapTGLRegisterRead, callbackXE->orgTGLRegisterRead)(access, reg); @@ -15235,26 +16532,7 @@ uint32_t XE::wrapTGLSetEDID(void *controller, void *request) { return r; } -bool XE::wrapTGLComputeLanes(void *port, const void *timing, uint32_t bpp, uint32_t available, uint32_t *lanes) { - const auto result = FunctionCast(wrapTGLComputeLanes, callbackXE->orgTGLComputeLanes)(port, timing, bpp, available, lanes); - const auto caller = reinterpret_cast(__builtin_return_address(0)); - // The donor does not consume its first argument. setupOptimalLaneCount - // passes port+0x124 there, whereas the guarded validator passes the port. - // Establish that caller contract before interpreting any object fields. - if (!callbackXE->tglHDMILaneCaller || caller != callbackXE->tglHDMILaneCaller) - return result; - auto config = *reinterpret_cast(static_cast(port)+0x548); - // r316: this exact validator incorrectly asks a TMDS connector for DP link lanes. - // Leave lane count zero and continue the remaining native HDMI/scaler checks. - // Preserve every DP call, other caller, positive result and nonzero lane result. - if (lanes && config && XELPDonor::skipHDMIDPLanes(callbackXE->tglHDMIEligible(config), - caller==callbackXE->tglHDMILaneCaller,result,available,*lanes)) { - static uint32_t corrected {}; - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMILaneCorrections316", __sync_add_and_fetch(&corrected, 1), 32); - return true; - } - return result; -} + uint32_t XE::wrapTGLFBOnline(void *controller, void *request) { auto req = static_cast(request); @@ -15292,12 +16570,7 @@ uint32_t XE::wrapTGLConnectionAttr(void *fb, int connection, uint32_t attribute, return r; } -uint32_t XE::wrapTGLAGDCCallback(void *controller, uint32_t event, unsigned long argument, uint32_t value) { - tglTrace(7, event, false, event, value, 0, argument); - const auto r = FunctionCast(wrapTGLAGDCCallback, callbackXE->orgTGLAGDCCallback)(controller, event, argument, value); - tglTrace(7, event, true, event, value, r, argument); - return r; -} + uint32_t XE::wrapTGLGPUCapability(void *controller, void *output) { tglTrace(8, 0, false, output != nullptr, 0, 0, 0); @@ -15367,16 +16640,13 @@ uint32_t XE::wrapTGLFBSetAttr(void *fb, uint32_t attribute, unsigned long value) return r; } -uint32_t XE::wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t type, void **client) { - const auto id = *reinterpret_cast(static_cast(fb) + 0x1DC); - tglSnapshotFB(fb); - tglTrace(15, id, false, type, 0, 0, 0); - const auto r = FunctionCast(wrapTGLFBUserClient, callbackXE->orgTGLFBUserClient)(fb, task, security, type, client); - tglTrace(15, id, true, type, 0, r, r == 0 && client && *client ? 1 : 0); - tglSnapshotFB(fb); +void XE::observeFramebufferUserClient(void *fb,uint32_t type,void **client,uint32_t result) { + const auto id=FramebufferDonor::field(fb,0x1DC); + tglTrace(15,id,true,type,0,result,result==0 && client && *client ? 1 : 0); + tglSnapshotFB(fb); // A successful standard shared connection provides an on-demand read-only // boundary after desktop composition; never take WindowServer's type0 client. - if (type == 1 && r == 0) { + if (type == 1 && result == 0) { tglConnectionPublish(); tglProgramPublish(); tglPublishCursorLifetime(); @@ -15386,7 +16656,6 @@ uint32_t XE::wrapTGLFBUserClient(void *fb, void *task, void *security, uint32_t tglDPCDPublish(); } - return r; } uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { @@ -15466,7 +16735,8 @@ uint32_t XE::wrapTGLAGDCPortStatus(void *controller, void *request, void *output // the resource pool multi-port must not silently enable an unimplemented PHY. XE::TGLTrainingLease *XE::tglTrainingLeaseForDDI(uint32_t ddi) { auto s = callbackXE; - if (!s->tglTrainingLeaseReady || ddi >= TGLPortCount || !s->tglDPSSTEligible(ddi)) return nullptr; + if (!s->tglTrainingLeaseReady || ddi >= TGLPortCount || + !(s->trainingLeaseValidate ? (s->trainingLeaseDP13Mask & (1U<tglDPSSTEligible(ddi))) return nullptr; auto &lease = s->tglTrainingLeases[ddi]; return &lease; } @@ -15540,8 +16810,10 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) auto port = static_cast(reinterpret_cast(s->tglPortByDDI)(lease.ddi)); if (!ctl || !port) return 1; const auto route=s->tglSSTRoutes().external(lease.ddi,*reinterpret_cast(port + 0x550)); - if (*reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || - !route.needsLease() || port[0x117]) return 1; + if (s->trainingLeaseValidate) { + if(!s->trainingLeaseValidate(controller,port,UINT32_MAX))return 1; + } else if (*reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || + !route.needsLease() || port[0x117]) return 1; auto access = *reinterpret_cast(ctl + 0xC40); auto power = *reinterpret_cast(ctl + 0xC8); if (!access || !power || *reinterpret_cast(access + 0x60) < 0x16A000 || @@ -15554,6 +16826,9 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) } lease.busy = true; if (lease.path) { + if(s->trainingLeaseValidate && (!lease.powerOwned || !lease.prepared || lease.adopted)) { + lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock);return 8; + } IORecursiveLockUnlock(s->tglPathPoolLock); tglObserveTrainingLease(lease, 1, 0); return 0; @@ -15585,11 +16860,11 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) lease.busy = false; IORecursiveLockUnlock(s->tglPathPoolLock); return 7; } lease.path = path; lease.port = port; lease.fb = fb; lease.power = power; - lease.busy = true; lease.adopted = false; ++lease.generation; + lease.busy = true; lease.adopted = false; lease.prepared=false; ++lease.generation; lease.clocksActive = false; lease.endCleanupPending = false; lease.pipe = pipe; // Own both consumers. Pipe A alone needs PW1, but a TC-numbered lane // still needs PW1..3 even when firmware/AUX/other displays hold no refs. - lease.pg = route.powerGroup(pipe); + lease.pg = s->trainingLeaseValidate ? (pipe ? pipe+2 : 1) : route.powerGroup(pipe); static_cast(lease.fb)->retain(); static_cast(lease.port)->retain(); static_cast(lease.power)->retain(); @@ -15599,19 +16874,39 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) // Keep the selected domain powered while checking, training and restoring // its registers. No pool lock is held across native power waits. SYSLOG("igfx", "TGL330 prepare power-enter ddi=%u pipe=%u pg=%u", lease.ddi, pipe, lease.pg); - reinterpret_cast(s->tglEnablePG)(power, lease.pg); + if(s->trainingLeasePower) { + if(!s->trainingLeasePower(power,lease.pg,true)) { + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock); + return 8; // Keep the native reservation on an unproven power transition. + } + } else reinterpret_cast(s->tglEnablePG)(power, lease.pg); SYSLOG("igfx", "TGL330 prepare power-return ddi=%u", lease.ddi); lease.powerOwned = true; + if(s->trainingLeaseValidate && !s->trainingLeaseValidate(controller,port,pipe)) { + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock);return 8; + } const uint32_t clock = *reinterpret_cast(mmio + 0x46140 + 4*pipe); const uint32_t func = *reinterpret_cast(mmio + 0x60400 + 0x1000*pipe); const uint32_t tp = *reinterpret_cast(mmio + 0x60540 + 0x1000*pipe); const uint32_t buffer = *reinterpret_cast(mmio + (0x64000 + 0x100 * lease.ddi)); // physical port, not pipe-indexed const uint32_t pipeState = *reinterpret_cast(mmio + 0x70008 + 0x1000*pipe); if (((func | tp | buffer) & 0x80000000U) || (clock & 0xF0000000U) || (pipeState & 0xC0000000U)) { + // Temporary diagnosis: report the guard's existing powered reads once + // per port. No extra MMIO or power lifetime is introduced. + static uint32_t guardObserved {}; + if (!(__sync_fetch_and_or(&guardObserved,1U<setProperty(key,values,sizeof(values)); + } // No training register was changed. Drop only this reservation and its // power reference; never restore over unexpected live hardware. tglObserveTrainingLease(lease, 0, 5); - reinterpret_cast(s->tglDisablePG)(power, lease.pg); + if(s->trainingLeasePower) { + if(!s->trainingLeasePower(power,lease.pg,false)) { + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock);return 8; + } + } else reinterpret_cast(s->tglDisablePG)(power, lease.pg); IORecursiveLockLock(s->tglPathPoolLock); reinterpret_cast(s->orgTGLFreePath)(path); lease.path = lease.port = lease.fb = lease.power = nullptr; @@ -15622,7 +16917,7 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) return 5; } SYSLOG("igfx", "TGL330 prepare registers-valid ddi=%u", lease.ddi); - lease.savedClock = clock; lease.savedFunc = func; lease.savedTP = tp; + lease.savedClock = clock; lease.savedFunc = func; lease.savedTP = tp;lease.prepared=true; tglObserveTrainingLease(lease, 0, 0); tglObserveTrainingLease(lease, 1, 0); return 0; @@ -15631,11 +16926,29 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { SYSLOG("igfx", "TGL330 release enter ddi=%u disable=%u", lease.ddi, disableLink); auto s = callbackXE; + // A failed extra-reference put after adoption is not a training reservation. + // Native scanout owns (and may already have freed) the path. Retry only our + // retained power/object references, without inspecting/freeing that path. + if(lease.adopted) { + if(lease.powerOwned) { + if(s->trainingLeasePower) {if(!s->trainingLeasePower(lease.power,lease.pg,false))return false;} + else reinterpret_cast(s->tglDisablePG)(lease.power,lease.pg); + } + auto fb=static_cast(lease.fb),port=static_cast(lease.port),power=static_cast(lease.power); + IORecursiveLockLock(s->tglPathPoolLock); + lease.path=lease.port=lease.fb=lease.power=nullptr;lease.powerOwned=false; + lease.adopted=false;lease.adoptThread=nullptr;lease.endCleanupPending=false; + IORecursiveLockUnlock(s->tglPathPoolLock); + fb->release();port->release();power->release();return true; + } + const auto pipe = lease.pipe; if (!lease.path) return true; - if (!lease.powerOwned || pipe >= 3 || lease.ddi >= TGLPortCount) return false; + if (!lease.powerOwned || pipe >= 3 || lease.ddi >= TGLPortCount || + (s->trainingLeaseValidate && (!lease.prepared || lease.adopted))) return false; auto fb = static_cast(lease.fb); auto ctl = *reinterpret_cast(fb + 0x1D0); + if(s->trainingLeaseValidate && !s->trainingLeaseValidate(ctl,lease.port,pipe))return false; auto access = *reinterpret_cast(fb + 0x4A40); auto mmio = *reinterpret_cast(access + 0x50); tglObserveTrainingLease(lease, 4, 0, disableLink); @@ -15665,6 +16978,9 @@ bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { if (!hardwareIdle()) { tglObserveTrainingLease(lease, 4, 1); return false; // never free active or unreadable hardware } + if(s->trainingLeaseStop && !s->trainingLeaseStop(ctl,lease.port)) { + tglObserveTrainingLease(lease,4,6);return false; + } *reinterpret_cast(mmio + 0x46140 + 4*pipe) = lease.savedClock; (void)*reinterpret_cast(mmio + 0x46140 + 4*pipe); *reinterpret_cast(mmio + 0x60400 + 0x1000*pipe) = lease.savedFunc; @@ -15678,7 +16994,8 @@ bool XE::tglReleaseTrainingLease(TGLTrainingLease &lease, bool disableLink) { } if (lease.powerOwned) { tglTransitionRecord(37,false,lease.power,lease.pg,lease.ddi,0); - reinterpret_cast(s->tglDisablePG)(lease.power, lease.pg); + if(s->trainingLeasePower) {if(!s->trainingLeasePower(lease.power,lease.pg,false))return false;} + else reinterpret_cast(s->tglDisablePG)(lease.power, lease.pg); tglTransitionRecord(37,true,lease.power,lease.pg,lease.ddi,0); } lease.powerOwned = false; @@ -15722,49 +17039,60 @@ void *XE::wrapTGLAllocatePathFixed(uint32_t pipe) { } void XE::wrapTGLControllerStop(void *controller, IOService *provider) { + auto s=callbackXE; + if(!__sync_bool_compare_and_swap(&s->tglStopInProgress,false,true))return; + if(__atomic_load_n(&s->tglStopComplete,__ATOMIC_ACQUIRE)) { + __atomic_store_n(&s->tglStopInProgress,false,__ATOMIC_RELEASE);return; + } if(__atomic_load_n(&tglBootstrapAUX.enabled,__ATOMIC_ACQUIRE))__atomic_fetch_add(&tglBootstrapAUX.stopSeen,1,__ATOMIC_RELAXED); tglRecordStartOrder(7,controller); - auto s = callbackXE; - IORecursiveLockLock(s->tglPathPoolLock); - s->tglLeasesStopping = true; - IORecursiveLockUnlock(s->tglPathPoolLock); - // Native resource allocation runs outside the pool lock too. Drain those - // FB claims before touching their leases or tearing down the native pool. - for (;;) { - IORecursiveLockLock(s->tglPathPoolLock); - bool allocating = false; - for (auto fb : s->tglAllocatingFB) allocating |= fb != nullptr; - IORecursiveLockUnlock(s->tglPathPoolLock); - if (!allocating) break; - IOSleep(1); - } - // Block new reservations first, then drain every existing port. Do not hold - // the shared pool lock across native power waits or port shutdown. - for (auto &lease : s->tglTrainingLeases) { - for (;;) { - IORecursiveLockLock(s->tglPathPoolLock); - if (!lease.busy) { lease.busy = true; IORecursiveLockUnlock(s->tglPathPoolLock); break; } - IORecursiveLockUnlock(s->tglPathPoolLock); - IOSleep(1); - } - if (lease.path && !tglReleaseTrainingLease(lease, true)) { - // Native controller teardown owns any still-active path. Never return - // it to the pool, or dereference it after native stop destroys it. - tglObserveTrainingLease(lease, 9, 1); - auto fb = static_cast(lease.fb), port = static_cast(lease.port); - auto power = static_cast(lease.power); - IORecursiveLockLock(s->tglPathPoolLock); - lease.path = lease.port = lease.fb = lease.power = nullptr; - lease.powerOwned = false; lease.adopted = false; lease.adoptThread = nullptr; - IORecursiveLockUnlock(s->tglPathPoolLock); - fb->release(); port->release(); power->release(); - } - } - FunctionCast(wrapTGLControllerStop, s->orgTGLControllerStop)(controller, provider); + IORecursiveLockLock(s->tglPathPoolLock); + s->tglLeasesStopping=true; + FramebufferDonor::Call::close(s->framebufferProtocolCalls); + __atomic_store_n(&s->framebufferStartedController,static_cast(nullptr),__ATOMIC_RELEASE); + IORecursiveLockUnlock(s->tglPathPoolLock); + const auto blocked=[&](unsigned reason) { + // The native stop deletes the pool; it cannot finish an unproven WEX + // reservation's hardware teardown. Preserve its owner and all held refs. + if(__sync_bool_compare_and_swap(&s->tglStopRetained,false,true))static_cast(controller)->retain(); + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferStopBlocked",reason,32); + __atomic_store_n(&s->tglStopInProgress,false,__ATOMIC_RELEASE); + }; + // Drain native startup/allocation before touching reservations. No pool + // lock crosses a wait, hardware transaction or native controller teardown. + for(unsigned waited=0;;++waited) { + IORecursiveLockLock(s->tglPathPoolLock); + bool active=s->framebufferStartInProgress || !FramebufferDonor::Call::drained(s->framebufferProtocolCalls); + for(auto fb:s->tglAllocatingFB)active|=fb!=nullptr; + IORecursiveLockUnlock(s->tglPathPoolLock); + if(!active)break; + if(waited==500){blocked(1);return;} + IOSleep(1); + } + for(auto &lease:s->tglTrainingLeases) { + for(unsigned waited=0;;++waited) { + IORecursiveLockLock(s->tglPathPoolLock); + if(!lease.busy){lease.busy=true;IORecursiveLockUnlock(s->tglPathPoolLock);break;} + IORecursiveLockUnlock(s->tglPathPoolLock); + if(waited==500){blocked(1);return;} + IOSleep(1); + } + const bool released=!lease.path || tglReleaseTrainingLease(lease,true); + IORecursiveLockLock(s->tglPathPoolLock);lease.busy=false;IORecursiveLockUnlock(s->tglPathPoolLock); + if(!released){blocked(2);return;} + } + if(!retireDisplay13Controller(controller)){blocked(3);return;} + FunctionCast(wrapTGLControllerStop,s->orgTGLControllerStop)(controller,provider); tglRecordStartOrder(8,controller); + __atomic_store_n(&s->tglStopComplete,true,__ATOMIC_RELEASE); + IORegistryEntry::getRegistryRoot()->setProperty("WEXFramebufferStopBlocked",static_cast(0),32); + if(__sync_bool_compare_and_swap(&s->tglStopRetained,true,false))static_cast(controller)->release(); + __atomic_store_n(&s->tglStopInProgress,false,__ATOMIC_RELEASE); } uint32_t XE::wrapTGLTrainingBegin(void *controller, void *request) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return kIOReturnBusy;} static uint32_t observed {}; if (__sync_add_and_fetch(&observed, 1) == 1) tglAUXPublish(); auto req = static_cast(request); @@ -15800,6 +17128,8 @@ uint32_t XE::wrapTGLTrainingBegin(void *controller, void *request) { } uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return kIOReturnBusy;} auto req = static_cast(request); const uint32_t id = req ? *reinterpret_cast(req) : 9; uint32_t a = req ? *reinterpret_cast(req + 8) : 0; @@ -15828,6 +17158,7 @@ uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { r = kIOReturnBusy; } else { SYSLOG("igfx", "TGL330 training native-enter id=%u", id); + if(selected) {IORecursiveLockLock(callbackXE->tglPathPoolLock);selected->trainingThread=current_thread();IORecursiveLockUnlock(callbackXE->tglPathPoolLock);} r = FunctionCast(wrapTGLTraining, callbackXE->orgTGLTraining)(controller, request, output); SYSLOG("igfx", "TGL330 training native-return id=%u result=%x", id, r); } @@ -15838,7 +17169,7 @@ uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { lease.clocksActive = true; tglObserveTrainingLease(lease, 2, r, output ? *reinterpret_cast(static_cast(output) + 0x38) : 0xFFFFFFFFU); IORecursiveLockLock(callbackXE->tglPathPoolLock); - lease.busy = false; + lease.busy = false;lease.trainingThread=nullptr; IORecursiveLockUnlock(callbackXE->tglPathPoolLock); } tglObservePath(observedPort, nullptr, 6, true, r, output ? *reinterpret_cast(static_cast(output) + 0x38) : 0, __builtin_return_address(0)); @@ -15865,6 +17196,8 @@ uint32_t XE::wrapTGLTraining(void *controller, void *request, void *output) { } uint32_t XE::wrapTGLTrainingEnd(void *controller, void *request) { + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return kIOReturnBusy;} auto req = static_cast(request); const uint32_t id = req ? *reinterpret_cast(req) : 9; uint32_t a = req ? *reinterpret_cast(req + 8) : 0, b = 0; @@ -16109,6 +17442,9 @@ void XE::tglObserveConfig(void *controller, void *request, void *output, bool te } bool XE::wrapTGLAllocatePathResources(void *fb, uint32_t portIndex, uint32_t stream) { + if(!display13RetirementHealthy())return false; + FramebufferDonor::Call admission(callbackXE->framebufferProtocolCalls); + if(!admission){return false;} SYSLOG("igfx", "TGL330 resources enter ddi=%u stream=%u", portIndex, stream); auto caller = __builtin_return_address(0); tglObserveResource(fb, nullptr, 9, false, 0, portIndex, stream, caller); @@ -16145,7 +17481,18 @@ bool XE::wrapTGLAllocatePathResources(void *fb, uint32_t portIndex, uint32_t str if (selected->adopted) { // Native addDisplayPath populated the map/FB arrays and the caller // acquired its own PG reference. Drop only our extra reservation ref. - if (selected->powerOwned) reinterpret_cast(s->tglDisablePG)(selected->power, selected->pg); + if (selected->powerOwned) { + if(s->trainingLeasePower) { + if(!s->trainingLeasePower(selected->power,selected->pg,false)) { + // Native owns the adopted path; retain only our extra power/object + // references. Never hand that path out for a second adoption. + IORecursiveLockLock(s->tglPathPoolLock); + selected->endCleanupPending=true;selected->adoptThread=nullptr;selected->busy=false; + if(claimed)s->tglAllocatingFB[portIndex]=nullptr; + IORecursiveLockUnlock(s->tglPathPoolLock);return false; + } + } else reinterpret_cast(s->tglDisablePG)(selected->power,selected->pg); + } selected->powerOwned = false; tglObserveTrainingLease(*selected, 8, r ? 0 : 1); auto oldFB = static_cast(selected->fb); @@ -16337,8 +17684,11 @@ volatile uint8_t *XE::tglClockOrderTarget(void *controller, void *port, void *pa } void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { - if(callbackXE->adlsDisplaySelected && !adlsClockTarget(controller,port,otherPort)) { - IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockDisableRejected",true);return; + auto config=port?*reinterpret_cast(static_cast(port)+0x548):nullptr; + const bool combo=callbackXE->adlsDisplaySelected || (config && config[0]<2); + bool keepPLL=false; + if(combo && (!comboClockTarget(controller,port,otherPort) || !comboBeginClock(controller,port,false,keepPLL))) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXComboClockDisableRejected",true);return; } tglTransitionRecord(32,false,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); auto s = callbackXE; @@ -16375,7 +17725,10 @@ void XE::wrapTGLDisableClocks(void *controller, void *port, void *otherPort) { // If hardware/ownership checks did not admit the correction, preserve native // teardown, including any clock-off request deferred by this transaction. if (owned && tx.pending) { tx.pending = false; wrapTGLRegisterWrite(tx.access, tx.clockReg, tx.value); } - FunctionCast(wrapTGLDisableClocks, s->orgTGLDisableClocks)(controller, port, otherPort); + // The donor's self-sharing argument gates the output and releases its DDI + // reference, but leaves the shared PLL running and recalculates voltage. + FunctionCast(wrapTGLDisableClocks, s->orgTGLDisableClocks)(controller, port, combo?(keepPLL?port:nullptr):otherPort); + if(combo)comboEndClock(); tglTransitionRecord(32,true,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); } @@ -16408,7 +17761,117 @@ bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { XELP::ADLSComboBackend::clockSlot(XELP::Donor::C004FC1A,config[0],slot) && AlderLakeS::Port::resolve(config[0],route) && slot==route.phy && access && *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==route.phyBase()+0x10C && - *reinterpret_cast(access+0x60)>=0x16429C; + *reinterpret_cast(access+0x60)>=0x1642C8; +} + +bool XE::comboClockTarget(void *controller,void *port,void *otherPort) { + auto s=callbackXE; + if(s->adlsDisplaySelected)return adlsClockTarget(controller,port,otherPort); + if(!controller || !port || (otherPort && otherPort!=port && !comboClockTarget(controller,otherPort,nullptr)))return false; + auto p=static_cast(port),c=static_cast(controller); + auto config=*reinterpret_cast(p+0x548); + auto access=*reinterpret_cast(c+0xC40); + if(!config || config[0]>=2 || config[3] || config[2]>2 || !access || *reinterpret_cast(p+0x578)!=access || + *reinterpret_cast(access+0x60)<0x164294 || + *reinterpret_cast(p+0x5B8)!=(config[0]?0x6C10CU:0x16210CU))return false; + if(s->display13FramebufferRequested)return controller==s->adlnTopologyController && + Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring); + return s->tglActive && controller==s->tglObservationController && + XELP::displayAdapter(s->xeProfile.family)==XELP::DisplayAdapter::TGL && + config==reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*config[0]); +} + +bool XE::comboClockPower(void *power,bool acquire) { + auto s=callbackXE; + if(s->display13FramebufferRequested)return display13LeasePower(power,1,acquire); + if(!s->tglTCNativeFunctions.complete())return false; + if(acquire)s->tglTCNativeFunctions.getPG(power,1);else s->tglTCNativeFunctions.putPG(power,1); + return true; +} +uint32_t XE::comboClockRead(void *access,unsigned long reg) { + auto s=callbackXE; + return reinterpret_cast(s->display13FramebufferRequested? + s->orgDisplay13GMBusRead:s->orgTGLRegisterRead)(access,reg); +} +void XE::comboClockWrite(void *access,unsigned long reg,uint32_t value) { + auto s=callbackXE; + reinterpret_cast(s->display13FramebufferRequested? + s->orgDisplay13GMBusWrite:s->orgTGLRegisterWrite)(access,reg,value); +} + +bool XE::comboBeginClock(void *controller,void *port,bool enable,bool &keepPLL,void *other) { + auto s=callbackXE;auto &t=comboClockTransaction; + auto c=static_cast(controller); + auto access=*reinterpret_cast(c+0xC40); + auto power=*reinterpret_cast(c+0xC8); + if(!t.lock || !power || *reinterpret_cast(power+0x10)!=controller || + *reinterpret_cast(power+0x78)!=access || !*reinterpret_cast(access+0x50))return false; + IORecursiveLockLock(t.lock); + if(t.thread){IORecursiveLockUnlock(t.lock);return false;} + if(!comboClockPower(power,true)){IORecursiveLockUnlock(t.lock);return false;} + // DISPLAY_CORE must remain referenced from peer scan through readback. + const auto pw=comboClockRead(access,0x45404); + if(pw==UINT32_MAX || (pw&3)!=3) { + comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; + } + const auto ddi=(*reinterpret_cast(static_cast(port)+0x548))[0]; + XELPComboClock::Port route(s->adlsDisplaySelected?(ddi?ddi-2:0):ddi); + XELPComboClock::ClockSnapshot snapshot; + snapshot.layout=s->adlsDisplaySelected?XELPComboClock::Layout::ADLS: + s->display13FramebufferRequested?XELPComboClock::Layout::Display13:XELPComboClock::Layout::TGL; + snapshot.mux[0]=comboClockRead(access,0x164280); + if(s->adlsDisplaySelected)snapshot.mux[1]=comboClockRead(access,0x1642BC); + for(unsigned i=0;i(static_cast(other)+0x548):nullptr; + const auto choice=enable && otherConfig ? t.allocation.share(XELPComboClock::Port(otherConfig[0]),snapshot): + t.allocation.choose(route,snapshot,enable); + if(!choice.valid) { + comboClockPower(power,false);IORecursiveLockUnlock(t.lock);return false; + } + if(enable)t.allocation.binding[route.phy]=choice.pll+1; + t.translation={route.phy,choice.pll,enable};t.before=snapshot; + t.access=access;t.power=power;t.keepPLL=keepPLL=choice.keepPLL; + __atomic_store_n(&t.thread,reinterpret_cast(current_thread()),__ATOMIC_RELEASE); + return true; +} + +void XE::comboEndClock() { + auto &t=comboClockTransaction; + auto tr=t.translation;XELPComboClock::PLL pll; + const auto state=XELPComboClock::PLL::resolve(tr.pll,pll)?comboClockRead(t.access,pll.enable):0; + const auto mux=comboClockRead(t.access,0x164280); + if(!tr.enabling && (t.keepPLL || !(state&0xCC000000U)))t.allocation.binding[tr.phy]=0; + uint32_t record[]={2,tr.phy,tr.pll,tr.enabling,t.keepPLL,t.before.mux[0],t.before.mux[1],mux,state}; + __atomic_store_n(&t.thread,0,__ATOMIC_RELEASE); + comboClockPower(t.power,false); + IORecursiveLockUnlock(t.lock); + char key[40];snprintf(key,sizeof(key),"WEXComboClockPHY%u%s",tr.phy,tr.enabling?"Enable":"Disable"); + IORegistryEntry::getRegistryRoot()->setProperty(key,record,sizeof(record)); +} + +void XE::comboClockTranslateWrite(void *access,unsigned long ®,uint32_t &value,uintptr_t caller) { + if(!comboClockAccess(access))return; + const auto pc=XELPComboClock::donorPC(callbackXE->framebufferClockImage,caller); + const auto &tr=comboClockTransaction.translation; + reg=tr.reg(reg,pc); + if(reg==0x164280 && tr.enabling && pc==0x6D77E) { + value=tr.mux(value,pc); + tr.prepareMux([&](uint32_t r){return comboClockRead(access,r);}, + [&](uint32_t r,uint32_t v){comboClockWrite(access,r,v);},value); + } +} + +void XE::wrapTGLComboEnableClocks(void *controller,void *port,void *other,uint32_t *params) { + auto config=port?*reinterpret_cast(static_cast(port)+0x548):nullptr; + if(config && config[0]<2) { + bool keep=false; + if(!comboClockTarget(controller,port,other) || !comboBeginClock(controller,port,true,keep,other)) { + IORegistryEntry::getRegistryRoot()->setProperty("WEXComboClockEnableRejected",true);return; + } + FunctionCast(wrapTGLComboEnableClocks,callbackXE->orgComboEnableClocks)(controller,port,other,params); + comboEndClock();return; + } + FunctionCast(wrapTGLComboEnableClocks,callbackXE->orgComboEnableClocks)(controller,port,other,params); } void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params) { @@ -16418,8 +17881,13 @@ void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32 !AlderLakeS::HDMIClock::encode(params,*reinterpret_cast(static_cast(controller)+0xEA8),encoded))) { IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; } + bool keepPLL=false; + if(!comboBeginClock(controller,port,true,keepPLL)) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; + } tglTransitionRecord(71,false,port,encoded.cfg0,encoded.cfg1,0); - FunctionCast(wrapADLSEnableClocks,callbackXE->orgADLSEnableClocks)(controller,port,otherPort,params); + FunctionCast(wrapADLSEnableClocks,callbackXE->orgComboEnableClocks)(controller,port,otherPort,params); + comboEndClock(); tglTransitionRecord(71,true,port,encoded.cfg0,encoded.cfg1,0); } @@ -16701,6 +18169,7 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { } } } + comboClockTranslateWrite(access,reg,value,reinterpret_cast(__builtin_return_address(0))); auto &tx = tglClockOffTransaction; if (callbackXE->tglClockOffCaller && reinterpret_cast(__builtin_return_address(0)) == callbackXE->tglClockOffCaller && reg == tx.clockReg && !(value & 0xF0000000U) && @@ -17204,36 +18673,65 @@ bool XE::tglDPAltFia(void *port,TGLFiaPort &fia) { FunctionCast(wrapTGLPortReadReg,self->orgTGLPortReadReg)(port,fia.owned)!=~0U; } +// Fixed DP uses the same FIA topology as DP-alt. Called only under the +// mode mutex at native powered mode/lanes accesses: mode4's AUX acquisition +// precedes the ready/owner accesses and its release follows owner clear. +// Do not substitute a DP-alt bootstrap hold or a boot probe for this lifetime. +bool XE::tglLegacyFia(void *port,TGLFiaPort &fia) { + auto self=callbackXE;auto p=static_cast(port);fia={}; + if(!p || !self->tglActive || !self->tglNativeFiaReady || !self->tglDPAltFiaReady)return false; + auto c=*reinterpret_cast(p+0x548); + auto access=*reinterpret_cast(p+0x578); + if(!c || c[0]<3 || c[0]>6 || c[2]!=1 || c[3]!=1 || + !self->tglLegacyTCEligible(c[0]) || *reinterpret_cast(p+0x550)!=4 || + !access || !*reinterpret_cast(access+0x50) || + *reinterpret_cast(access+0x60)<0x1638C4)return false; + TGLTypeCPort tc;TGLFiaPort actual; + const auto first=FunctionCast(wrapTGLPortReadReg,self->orgTGLPortReadReg)(port,0x1638A0); + if(!TGLTypeCPort::resolve(c[0],tc) || !tc.resolveFia(first,actual) || + *reinterpret_cast(access+0x60)(port);auto c=*reinterpret_cast(p+0x548); - if(c && c[0]<9 && (callbackXE->tglDPAltCandidates&(1U<(p+0x550)==3) { - TGLFiaPort fia;if(!tglDPAltFia(port,fia))return 0; - const auto value=wrapTGLPortReadReg(port,fia.laneSelect); - return value==~0U?0:(value>>fia.laneSelectShift)&15; + auto self=callbackXE;auto p=static_cast(port);auto c=*reinterpret_cast(p+0x548); + const auto mode=*reinterpret_cast(p+0x550); + const bool alt=c && c[0]<9 && (self->tglDPAltCandidates&(1U<=3 && c[0]<=6 && self->tglLegacyTCEligible(c[0]) && mode==4; + if(alt || legacy) { + auto lock=self->tglModeTransitionLock;if(!lock)return 0;IORecursiveLockLock(lock); + TGLFiaPort fia;uint32_t value=0; + if(alt?tglDPAltFia(port,fia):tglLegacyFia(port,fia)) { + const auto raw=wrapTGLPortReadReg(port,fia.laneSelect); + if(raw!=UINT32_MAX)value=(raw>>fia.laneSelectShift)&15; + } + IORecursiveLockUnlock(lock);return value; } - return FunctionCast(wrapTGLConfiguredLanes,callbackXE->orgTGLConfiguredLanes)(port); + return FunctionCast(wrapTGLConfiguredLanes,self->orgTGLConfiguredLanes)(port); } void XE::wrapTGLFIALaneCount(void *port,uint32_t lanes) { - auto p=static_cast(port);auto c=*reinterpret_cast(p+0x548); - if(c && c[0]<9 && (callbackXE->tglDPAltCandidates&(1U<(p+0x550)==3) { - TGLFiaPort fia;if(!tglDPAltFia(port,fia) || (lanes!=1 && lanes!=2 && lanes!=4))return; - // Logical main-link lanes, independent of physical USB-C orientation. - // Use the same shared mutex as ownership transactions for sibling RMWs. - auto lock=callbackXE->tglModeTransitionLock;if(!lock)return; - IORecursiveLockLock(lock); - const auto owned=wrapTGLPortReadReg(port,fia.owned); - auto value=wrapTGLPortReadReg(port,fia.laneSelect); - if(owned!=~0U && (owned&fia.readyMask) && value!=~0U) { - value=(value&~fia.laneSelectMask)|(((1U<(port);auto c=*reinterpret_cast(p+0x548); + const auto mode=*reinterpret_cast(p+0x550); + const bool alt=c && c[0]<9 && (self->tglDPAltCandidates&(1U<=3 && c[0]<=6 && self->tglLegacyTCEligible(c[0]) && mode==4; + if(alt || legacy) { + if(lanes!=1 && lanes!=2 && lanes!=4)return; + // Admitted fixed routes have no VBT lane reversal; both transports use + // logical ML0/ML1_0/ML3_0, not the physical DP-alt lane assignment. + auto lock=self->tglModeTransitionLock;if(!lock)return;IORecursiveLockLock(lock); + TGLFiaPort fia; + if(alt?tglDPAltFia(port,fia):tglLegacyFia(port,fia)) { + const auto owned=wrapTGLPortReadReg(port,fia.owned); + auto value=wrapTGLPortReadReg(port,fia.laneSelect); + if(owned!=UINT32_MAX && (owned&fia.readyMask) && value!=UINT32_MAX) { + value=(value&~fia.laneSelectMask)|(((1U<orgTGLFIALaneCount)(port,lanes); + FunctionCast(wrapTGLFIALaneCount,self->orgTGLFIALaneCount)(port,lanes); } // r323: the donor DKL table has four HBR2 coefficient mismatches against @@ -17425,16 +18923,24 @@ bool XE::tglNativeFiaAccess(void *port,uint32_t reg,uintptr_t caller,bool write, const auto pc=caller-self->tglNativeFiaBase; const auto ddi=self->tglNativeFiaContext.ddi; if(!(write?TGLNativeFia::writeSite(pc,reg):TGLNativeFia::readSite(pc,reg)) || !mapping.resolve(ddi))return false; - if(ddi<9 && (self->tglDPAltCandidates&(1U<(static_cast(port)+0x550); + TGLFiaPort actual; + const bool ok=mode==3 ? tglDPAltFia(port,actual) : tglLegacyFia(port,actual); + // Keep an intercepted failure intercepted: never fall back to FIA1 when + // the selected port's topology is unavailable. Read zero / skip writes. + mapping.fia=ok?actual:TGLFiaPort{}; + if(!ok)self->tglNativeFiaContext.failed=true; return true; } uint32_t XE::wrapTGLPortReadReg(void *port,uint32_t reg) { TGLNativeFia mapping; const bool mapped=tglNativeFiaAccess(port,reg,reinterpret_cast(__builtin_return_address(0)),false,mapping); + if(mapped && !mapping.fia.readyMask)return 0; const auto physical=mapped?(reg==0x163890?mapping.fia.ready:mapping.fia.owned):reg; auto value=FunctionCast(wrapTGLPortReadReg,callbackXE->orgTGLPortReadReg)(port,physical); if(mapped){ + if(value==UINT32_MAX){callbackXE->tglNativeFiaContext.failed=true;return 0;} value=mapping.logical(value); __atomic_add_fetch(&callbackXE->tglNativeFiaReads,1,__ATOMIC_RELAXED); } @@ -17444,12 +18950,12 @@ uint32_t XE::wrapTGLPortReadReg(void *port,uint32_t reg) { void XE::wrapTGLPortWriteReg(void *port, uint32_t reg, uint32_t value) { TGLNativeFia mapping; if(tglNativeFiaAccess(port,reg,reinterpret_cast(__builtin_return_address(0)),true,mapping)) { - if(!mapping.identity()){ - // The complete native transaction holds the shared mode lock. Preserve - // every other physical owner bit, including the sibling modular port. - const auto old=FunctionCast(wrapTGLPortReadReg,callbackXE->orgTGLPortReadReg)(port,mapping.fia.owned); - value=mapping.physical(old,value); - } + if(!mapping.fia.readyMask)return; + // Serialize the whole transaction, and preserve peer owner bits even + // for FIA1 identity mapping. An unavailable owner read cannot be written. + const auto old=FunctionCast(wrapTGLPortReadReg,callbackXE->orgTGLPortReadReg)(port,mapping.fia.owned); + if(old==UINT32_MAX){callbackXE->tglNativeFiaContext.failed=true;return;} + value=mapping.physical(old,value); reg=mapping.fia.owned; __atomic_add_fetch(&callbackXE->tglNativeFiaWrites,1,__ATOMIC_RELAXED); } @@ -17500,46 +19006,66 @@ void XE::wrapTGLPortWriteReg(void *port, uint32_t reg, uint32_t value) { // must not accumulate refs and an unrelated port cannot release our reference. void XE::tglComboAUXReference(void *port,bool acquire) { auto s=callbackXE; + // seq, reason, DDI, acquire, type, flags, mode, owner, IO, buffer, + // native refs before/after, AUX state, wait us. UINT_MAX means unsampled. + uint32_t observed[14] {0,0,UINT32_MAX,acquire,UINT32_MAX,UINT32_MAX,UINT32_MAX,0, + UINT32_MAX,UINT32_MAX,UINT32_MAX,UINT32_MAX,UINT32_MAX,0}; + const auto finish=[&](uint32_t reason){observed[1]=reason;tglComboAUXDecisionRecord(observed);}; auto p=static_cast(port); auto ctl=static_cast(s->tglObservationController); auto &f=s->tglTCNativeFunctions; // shared native AUX API, not TC admission if(!p || !ctl || !s->tglActive || !s->tglComboAUXLifetimeReady || !f.complete() || - (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || *f.controllerSlot!=ctl)return; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || *f.controllerSlot!=ctl)return finish(1); auto config=*reinterpret_cast(p+0x548); - const bool adls=s->adlsDisplaySelected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); + if(config) {observed[2]=config[0];observed[4]=config[2];observed[5]=config[3];} + observed[6]=*reinterpret_cast(p+0x550); + const bool adls=s->adlsDisplaySelected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); const bool fixed=!s->adlsDisplaySelected && TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config); - if(s->adlsDisplaySelected && !adls)return; - if(!config || (config[0]>1 && !adls))return; + if(s->adlsDisplaySelected && !adls)return finish(2); + if(!config || (config[0]>1 && !adls))return finish(3); const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; - if(acquire ? owner!=nullptr : owner!=port)return; + observed[7]=owner==port?1:owner?2:0; + if(acquire ? owner!=nullptr : owner!=port)return finish(4); auto power=*reinterpret_cast(ctl+0xC8); auto access=*reinterpret_cast(ctl+0xC40); if(!power || !access || *reinterpret_cast(power+0x10)!=ctl || *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(p+0x578)!=access || *reinterpret_cast(access+0x60)<(0x64004U+0x100*ddi) || - !*reinterpret_cast(access+0x50))return; + !*reinterpret_cast(access+0x50))return finish(5); const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); - if(*reinterpret_cast(p+0x590)!=buffer)return; + if(*reinterpret_cast(p+0x590)!=buffer)return finish(6); XELP::Display12SSTRoute route; const XELP::Display12SSTRoutes routes{adls?s->adlsWiring.dp:fixed?s->tglComboDPMask:0,0,0}; if(acquire && (config[3]!=0 || p[0x117] || !routes.resolve(ddi,config[2],*reinterpret_cast(p+0x550),route) || !route.retainComboAUX() || !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[ddi].dp() || - (!adls && !fixed && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return; + (!adls && !fixed && (config[2]!=0 || !s->firmwarePorts.port[ddi].internal()))))return finish(7); const auto io=wrapTGLRegisterRead(access,0x45454); - if(io==~0U || (io&mask)!=mask)return; + observed[8]=io; + if(io==~0U || (io&mask)!=mask)return finish(8); const auto bufferState=wrapTGLRegisterRead(access,buffer); - if(bufferState==~0U || (bufferState&0x80000000U))return; + observed[9]=bufferState; + if(bufferState==~0U || (bufferState&0x80000000U))return finish(9); auto &refs=*reinterpret_cast(power+0x54+4*ddi); - const uint32_t before=refs; + const uint32_t before=refs;observed[10]=before; uint32_t aux=0,reason=0; if(acquire) { - if(before==~0U)return; + if(before==~0U)return finish(10); f.getAux(port); aux=wrapTGLRegisterRead(access,0x45444); + // Native AUX get waits only 20 x IODelay(1). Linux v6.12 + // hsw_wait_for_power_well_enable allows 1ms (AUX has no specified + // 20us limit). Keep our reference while the owned request settles. + // Stop on request loss/unavailable MMIO, never turn on the transmitter + // to make AUX ready: Combo AUX must be ready before DDI_BUF_ENABLE. + for(unsigned elapsed=0;refs==before+1 && aux!=~0U && + (aux&mask)!=(mask) && (aux&(mask&0xAAAAAAAAU)) && elapsed<1000;elapsed+=10) { + IODelay(10);aux=wrapTGLRegisterRead(access,0x45444); + observed[13]=elapsed+10; + } if(refs==before+1 && aux!=~0U && (aux&mask)==mask)owner=port; else { reason=1; @@ -17552,6 +19078,8 @@ void XE::tglComboAUXReference(void *port,bool acquire) { } else reason=2; const uint64_t detail[]={before,refs,aux,bufferState,owner==port}; tglTransitionRecord(74,true,port,ddi,acquire,reason,detail,sizeof(detail)); + observed[7]=owner==port?1:owner?2:0;observed[11]=refs;observed[12]=aux; + finish(reason?10+reason:0); } uint32_t XE::wrapTGLDisablePort(void *port) { @@ -17566,40 +19094,20 @@ uint32_t XE::wrapTGLEnablePort(void *port) { tglTrace(21, id, false, 0, 0, 0, 0); tglSnapshotTransmitter(port, false, 0); tglComboAUXReference(port,true); + // A qualified fixed Combo link cannot train after its prerequisite failed. + // Return through the native failure path before any buffer-enable write. + const auto s=callbackXE; + const bool fixedDP=config && (s->adlsDisplaySelected ? + s->adlsWiring.boundSST(s->tglPlatformCopy,config) : + TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config)); + if(fixedDP && s->tglComboAUXOwners[id]!=port) { + tglTrace(21,id,true,kIOReturnNotReady,0,kIOReturnNotReady,0); + return kIOReturnNotReady; + } const auto original = FunctionCast(wrapTGLEnablePort, callbackXE->orgTGLEnablePort)(port); if(original)tglComboAUXReference(port,false); // only releases if buffer stayed off auto r = original; - // The donor only waits 100 * 5us. Linux allows 3ms for TGL TC PHYs. - // Observe another bounded 3ms and accept only a real enabled/non-idle state. - auto bytes = static_cast(port); - TGLTypeCPort mapping; - if (callbackXE->tglLegacyTCEligible(id) && TGLTypeCPort::resolve(id, mapping) && - original == 0xE00002D6 && - *reinterpret_cast(bytes + 0x550) == 4 && - *reinterpret_cast(bytes + 0x590) == mapping.ddiBuffer) { - auto access = *reinterpret_cast(bytes + 0x578); - auto base = access ? *reinterpret_cast(access + 0x50) : nullptr; - if (base && *reinterpret_cast(access + 0x60) >= mapping.ddiBuffer + 4) { - auto reg = reinterpret_cast(base + mapping.ddiBuffer); - uint32_t before = *reg, after = before, delay = 0; - while (delay < 3000 && after != 0xFFFFFFFFU && (after & 0x80000080U) == 0x80000080U) { - IODelay(100); - delay += 100; - after = *reg; - } - if ((after & 0x80000080U) == 0x80000000U) r = 0; - static uint32_t count {}; - const auto seq = __sync_add_and_fetch(&count, 1); - uint32_t record[] = {seq, original, r, delay, before, after, mapping.ddi}; - char key[48]; - snprintf(key, sizeof(key), "TGLEnableLate%uLast%u", mapping.ddi, seq % 64); - IORegistryEntry::getRegistryRoot()->setProperty(key, record, sizeof(record)); - if (seq <= 16) { - snprintf(key, sizeof(key), "TGLEnableLate%uFirst%u", mapping.ddi, seq); - IORegistryEntry::getRegistryRoot()->setProperty(key, record, sizeof(record)); - } - } - } + // Activation waiting is installed once in the native donor loop. tglSnapshotTransmitter(port, true, r); tglTrace(21, id, true, original, 0, r, 0); return r; diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index 71bd7350..e41739b7 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -21,6 +21,7 @@ #include "kern_xelp_panel_pwm.hpp" #include "kern_xelp_donor.hpp" #include "kern_xelp_display_contract.hpp" +#include "kern_xelp_link_requirements.hpp" #include "kern_dvmt.hpp" #include @@ -62,6 +63,11 @@ class XE { bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); private: + bool adlpLoadOnly {}; + bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); + static IOService *rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *); + static bool rejectADLPStart(IOService *,IOService *); XELP::DisplayContract::Mask display12Available() const; XELP::DisplayContract::Mask display13Available() const; // Do not inline either helper into the small kext-load dispatcher. @@ -98,8 +104,17 @@ class XE { mach_vm_address_t orgADLSPowerPG {}; static uint8_t *adlsPowerTarget(void *power); static void wrapADLSPowerPG(void *power,bool enable,uint32_t requested); - mach_vm_address_t orgADLSEnableClocks {}; + mach_vm_address_t orgComboEnableClocks {}; static bool adlsClockTarget(void *controller,void *port,void *otherPort); + mach_vm_address_t framebufferClockImage {}; + static bool comboClockTarget(void *,void *,void *); + static bool comboClockPower(void *,bool); + static uint32_t comboClockRead(void *,unsigned long); + static void comboClockWrite(void *,unsigned long,uint32_t); + static void comboClockTranslateWrite(void *,unsigned long &,uint32_t &,uintptr_t); + static void wrapTGLComboEnableClocks(void *,void *,void *,uint32_t *); + static bool comboBeginClock(void *controller,void *port,bool enable,bool &keepPLL,void *other=nullptr); + static void comboEndClock(); static void wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params); mach_vm_address_t orgADLSPortInit {}; static bool wrapADLSPortInit(void *port,uint32_t *config); @@ -112,8 +127,10 @@ class XE { uint32_t adlnNativeSubslices {}, adlnL3Banks {}, adlnNativeEUs {}; bool adlnKernelReady {false}; bool adlnNativeRequested {false}; - // Shared platform preflight: GT fuses, reserved GGTT/GOP, mapper, - // aperture and native boot policy. Both Render and Display consume it. + // Display handoff needs GOP/GGTT, mapper and aperture qualification, + // independently of GT fuses or an accelerator being allowed to start. + bool display13BootReady {false}; + // Render additionally requires the native GT resource certificate. bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; bool display13FBPowerReferencesReady {false}; @@ -141,12 +158,27 @@ class XE { static uint32_t wrapDisplay13EnablePort(void *port); static uint32_t wrapDisplay13DisablePort(void *port); bool processDisplay13FramebufferSST(KernelPatcher &,size_t,mach_vm_address_t,size_t); + bool display13TrainingReady {},display13FramingReady {},display13ClockOrderReady {}; + mach_vm_address_t orgDisplay13PortTraining {}; + bool processDisplay13Training(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static bool display13LeaseValidate(void *controller,void *port,uint32_t pipe); + static bool display13LeasePower(void *power,uint32_t pg,bool acquire); + static bool display13LeaseStop(void *controller,void *port); + static bool display13TrainingPath(void *path,uint32_t &ddi,uint32_t &pipe); + static uint32_t wrapDisplay13PortTraining(void *port,void *request); + static bool display13StopTranscoder(void *port); + static bool display13DeferClock(void *access,uint64_t reg,uint32_t value,uintptr_t caller); + static uint8_t *display13SSTPort(void *port); static bool display13HDMIPort(void *port); static uint32_t wrapDisplay13TrainingPattern(void *path,uint32_t pattern); static uint32_t wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig); mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; - bool display13DPProbeReady {}; + bool display13DPProbeReady {},display13TCProbeReady {},display13TCDPProbeReady {},display13FBDPHooksReady {}; + bool adlpDisplaySelected {}; + bool adlpRenderRequested {}; + Display13OutputWiring display13BoardWiring(IOService *); + bool prepareDisplay13BootScanout(IOPCIDevice *); bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; Display13OutputWiring adlnOutputWiring; @@ -178,25 +210,44 @@ class XE { // describe installed routes, not successful runtime ownership or scanout. bool display13FBPowerBackendReady {false}; bool display13FBClockReady {false}, display13FBIRQReady {false}, display13FBMemoryReady {false}, display13FBABIReady {false}; - mach_vm_address_t orgDisplay13FramebufferStart {}, orgAlderLakeNPowerReferences[7] {}; + mach_vm_address_t orgAlderLakeNPowerReferences[7] {}; mach_vm_address_t orgAlderLakeNClockInit {}, orgAlderLakeNClockProbe {}; mach_vm_address_t orgAlderLakeNClockSet {}; mach_vm_address_t orgAlderLakeNScanoutAllocate {}, orgAlderLakeNScanoutSetup {}, orgAlderLakeNAperture {}; mach_vm_address_t adlnScanoutDisableFB {}; mach_vm_address_t orgAlderLakeNSetupWorkLoop {}; - mach_vm_address_t orgDisplay13FramebufferInit {}; - __attribute__((noinline)) bool processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); __attribute__((noinline)) bool processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); + bool display13FBTCHooksReady {}; + mach_vm_address_t orgDisplay13TCMode {},display13TCBaseMode {},orgDisplay13TCEnableClocks {},orgDisplay13TCDisableClocks {}; + mach_vm_address_t orgDisplay13TCLightUp {},orgDisplay13TCSetMode {},display13TCNotify {}; + mach_vm_address_t display13TCFullClocks {},display13TCPlane {},display13TCScaler {},display13TCPipe {},display13TCPackets {}; + static uint32_t wrapDisplay13DPLightUp(void *,void *,void *,const void *); + static void wrapDisplay13DPPhyGating(void *,bool); + static uint32_t wrapDisplay13DPLaneMask(void *); + static void wrapDisplay13DPPortWrite(void *,uint32_t,uint32_t); + mach_vm_address_t orgDisplay13DPLightUp {},display13DPTraining {},display13DPSDPs {}; + mach_vm_address_t orgDisplay13DPPhyGating {},orgDisplay13DPLaneMask {},orgDisplay13DPPortWrite {},display13DPImage {}; + static uint32_t wrapDisplay13TCLightUp(void *,void *,void *,const void *); + static uint32_t wrapDisplay13TCSetMode(void *,void *,const void *); + bool processDisplay13FramebufferTC(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static unsigned display13TCTarget(OSObject *,bool cleanup=false); + static uint32_t wrapDisplay13TCMode(OSObject *,uint32_t); + static void wrapDisplay13TCEnableClocks(void *,void *,void *,uint32_t *); + static void wrapDisplay13TCDisableClocks(void *,void *,void *); + static uint32_t wrapDisplay13TCSignal(void *,uint32_t,uint32_t,void *); __attribute__((noinline)) bool processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size); static bool wrapAlderLakeNGetOSInformation(IOService *controller); static uint32_t wrapAlderLakeNPortState(OSObject *port); mach_vm_address_t orgDisplay13GMBus {},orgDisplay13GMBusRead {},orgDisplay13GMBusWrite {},adlnGMBusFunction {}; - mach_vm_address_t orgDisplay13FBEnable {},orgAlderLakeNConnectionAttr {},orgAlderLakeNPortDDC {},orgDisplay13FBDDC {},orgAlderLakeNComputeLanes {},adlnHDMILaneCaller {}; + mach_vm_address_t orgDisplay13FBEnable {},orgAlderLakeNConnectionAttr {},orgAlderLakeNPortDDC {},orgDisplay13FBDDC {}; static uint32_t wrapDisplay13FBEnable(IOService *fb); + bool processFramebufferAGDC(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgFramebufferUserClient {},orgFramebufferAGDCCallback {}; + static uint32_t wrapFramebufferUserClient(IOService *,void *,void *,uint32_t,void **); + static uint32_t wrapFramebufferAGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); static uint32_t wrapAlderLakeNConnectionAttr(IOService *fb,int connection,uint32_t attribute,unsigned long *value); static uint32_t wrapDisplay13FBDDC(IOService *fb,int connection,uint32_t block,uint32_t segment,uint32_t type,uint8_t *data,uint64_t *length); static uint32_t wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,bool force); - static bool wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t bpp,uint32_t available,uint32_t *lanes); static uint32_t wrapDisplay13GMBusRead(void *access,uint64_t reg); static void wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value); mach_vm_address_t orgAlderLakeNGamma10 {},adlnGammaTableUpdate {},orgAlderLakeNPreGamma {}; @@ -205,7 +256,6 @@ class XE { static void wrapAlderLakeNPreGamma(void *path,void *transaction,uint32_t control); static void wrapAlderLakeNGammaTable(void *path,uint16_t *table); static uint32_t wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request); - static bool wrapDisplay13FramebufferInit(IOService *framebuffer,IOService *controller,uint32_t index); static bool wrapDisplay13FramebufferStart(IOService *that, IOService *provider); static void wrapAlderLakeNPowerInit(void *power, void *controller); static void wrapAlderLakeNEngineEnable(void *power); @@ -251,7 +301,7 @@ class XE { static void wrapAlderLakeNPowerPutDDI(void *power, uint32_t ddi); static void wrapAlderLakeNPowerGetAUX(void *power, uint32_t aux); static void wrapAlderLakeNPowerPutAUX(void *power, uint32_t aux); - static void adlnPowerReference(void *power, uint32_t operation, uint32_t domain); + static bool adlnPowerReference(void *power, uint32_t operation, uint32_t domain); static Display13Clock::Readout adlnClockReadout(IOService *controller); static void wrapAlderLakeNClockInit(IOService *controller); static uint64_t wrapAlderLakeNClockProbe(IOService *controller); @@ -263,9 +313,17 @@ class XE { static bool wrapAlderLakeNScanoutAllocate(IOService *framebuffer); static void wrapAlderLakeNScanoutSetup(IOService *framebuffer); static void wrapAlderLakeNScanoutFree(IOService *framebuffer); - static void adlnFreeStoppedPipe(IOService *framebuffer); + static bool adlnFreeStoppedPipe(IOService *framebuffer); static void wrapAlderLakeNFreePath(IOService *framebuffer,OSObject *path); mach_vm_address_t orgAlderLakeNFreePath {}; + bool processDisplay13Retirement(KernelPatcher &,size_t,mach_vm_address_t,size_t); + mach_vm_address_t orgDisplay13FreeResources {},orgDisplay13AllocateResources {}; + static uint32_t releaseDisplay13Path(IOService *,OSObject *); + static uint32_t retireDisplay13Paths(void *); + static uint32_t wrapDisplay13FreeResources(void *); + static bool wrapDisplay13AllocateResources(void *); + static bool display13RetirementHealthy(); + static bool retireDisplay13Controller(void *); mach_vm_address_t orgAlderLakeNScanoutFree {}; static IOMemoryDescriptor *wrapAlderLakeNAperture(IOService *framebuffer,int aperture); static void wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop); @@ -344,6 +402,8 @@ class XE { bool tglHDMIWiringLoaded {},tglHDMIWiringExplicit {},tglHDMIWiringApplied {}; uint32_t tglHDMIBackendMask {3}; // TGL-U combo PHY A/B; C is absent static bool tglLoadHDMIWiring(IOService *provider); + static bool adlsPrepareBoot(IOService *provider); + bool adlsBootConsumersFailed {false}; bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDisplaySelected?adlsWiring.matches(config,false):tglHDMIWiring.matches(config));} bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDisplaySelected?adlsWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} @@ -357,19 +417,28 @@ class XE { IORecursiveLock *tglPathPoolLock {}; mach_vm_address_t orgTGLAllocatePathAny {}, orgTGLAllocatePathFixed {}, orgTGLControllerStop {}; mach_vm_address_t tglDisablePort {}, tglDisableClocks {}; + // Shared donor protocol despite historical TGL names. Registration belongs + // only to processFramebufferTraining; backends supply hardware adapters. // One reservation per physical port; the native pool limits simultaneous pipes. struct TGLTrainingLease { void *path {}, *port {}, *fb {}, *power {}, *adoptThread {}; - bool busy {}, powerOwned {}, adopted {}, clocksActive {}; + bool busy {}, powerOwned {}, adopted {}, clocksActive {}, prepared {}; + void *trainingThread {}; // Native abort-End already ran; retry only our retained cleanup. bool endCleanupPending {}; uint32_t generation {}, savedClock {}, savedFunc {}, savedTP {}; uint32_t pipe {}, pg {1}, ddi {}; }; - static constexpr uint32_t TGLPortCount = 9; + // Optional Display13 adapter. The native pool/adoption protocol is shared; + // register domains, identity and power results remain backend-specific. + uint32_t trainingLeaseDP13Mask {}; + bool (*trainingLeaseValidate)(void *,void *,uint32_t) {}; + bool (*trainingLeasePower)(void *,uint32_t,bool) {}; + bool (*trainingLeaseStop)(void *,void *) {}; + static constexpr uint32_t TGLPortCount = 9; TGLTrainingLease tglTrainingLeases[TGLPortCount] {}; void *tglAllocatingFB[TGLPortCount] {}; - bool tglLeasesStopping {}; + bool tglLeasesStopping {},tglStopRetained {},tglStopInProgress {},tglStopComplete {}; static TGLTrainingLease *tglTrainingLeaseForDDI(uint32_t ddi); static TGLTrainingLease *tglAdoptingLease(); // caller holds the pool lock static bool tglLeaseFBReserved(void *fb, const TGLTrainingLease *except = nullptr); @@ -477,7 +546,8 @@ class XE { (tglTCWiring.legacy|tglTCAutoCandidates)&tglAUXParentMask : 0;} bool tglTCModeAllowed(uint32_t ddi,uint32_t mode) const { if(ddi>=9 || !(tglTCAutoCandidates&(1U< + +namespace XELPComboClock { +// Hardware inventory, independent of connector wiring or native port mode. +// TGL/ADL-P/N allocate only DPLL0/1: PLL2 is not a third Combo resource. +// ADL-S preserves the existing three-slot donor backend and fourth live peer. +enum class Layout {ADLS,TGL,Display13}; +inline uint32_t donorPC(uintptr_t image,uintptr_t caller) { + return image && caller>=image && caller-image<0x6DE58 ? uint32_t(caller-image):UINT32_MAX; +} +struct Port { + uint32_t phy {},clockShift {},clockOff {}; + explicit Port(uint32_t p):phy(p),clockShift(2*(p%3)),clockOff(1U<<(p==0?10:p==1?11:p==2?24:p+1)){} +}; +struct PLL { + uint32_t enable {},cfg0 {},cfg1 {}; + static bool resolve(uint32_t id,PLL &out) { + out={};if(id>3)return false; + const uint32_t enables[]={0x46010,0x46014,0x46018,0x46030}; + const uint32_t configs[]={0x164284,0x16428C,0x164294,0x1642C0}; + out={enables[id],configs[id],configs[id]+4};return true; + } +}; +struct ClockSnapshot { + uint32_t mux[2] {},enable[4] {}; + Layout layout=Layout::ADLS; + unsigned pllCount() const {return layout==Layout::ADLS?4:2;} + unsigned slots() const {return layout==Layout::ADLS?3:2;} + bool valid() const { + for(auto v:mux)if(v==UINT32_MAX)return false; + for(auto v:enable)if(v==UINT32_MAX)return false; + return true; + } + template uint32_t value(const P &p) const {return mux[p.phy<3?0:1];} + template bool active(const P &p) const {return !(value(p)&p.clockOff);} + template uint32_t selected(const P &p) const {return (value(p)>>p.clockShift)&3;} + template uint32_t peers(const P &target) const { + uint32_t mask=0; + for(unsigned phy=0;phy<(layout==Layout::ADLS?5U:2U);++phy) { + Port p(phy); + if(p.phy!=target.phy && active(p))mask|=1U< ClockChoice share(const P &source,const ClockSnapshot &s) const { + if(source.phy>=s.slots() || !s.valid() || !s.active(source))return {}; + const auto pll=s.selected(source); + return pll ClockChoice choose(const P &p,const ClockSnapshot &s,bool on) const { + if(p.phy>=s.slots() || !s.valid())return {}; + auto peers=s.peers(p); + for(unsigned phy=0;phy<3;++phy) + if(phy!=p.phy && binding[phy])peers|=1U<<(binding[phy]-1); + const auto previous=s.active(p)?s.selected(p):binding[p.phy]?binding[p.phy]-1:UINT32_MAX; + if(previous!=UINT32_MAX && previous>=s.pllCount())return {}; + if(!on)return {previous,true,previous==UINT32_MAX || bool(peers&(1U<=0x6CE10 && pc<0x6DA26):(pc>=0x6DAE6 && pc<0x6DE58); + } + unsigned long reg(unsigned long r,uint32_t pc) const { + if(!caller(pc))return r; + PLL from,to; + if(!PLL::resolve(phy,from) || !PLL::resolve(pll,to))return r; + if(r==from.enable)return to.enable; + if(r==from.cfg0)return to.cfg0; + if(r==from.cfg1)return to.cfg1; + return r; + } + uint32_t mux(uint32_t value,uint32_t pc) const { + if(!enabling || pc!=0x6D77E || phy>=3 || pll>=4)return value; + return (value&~(3U<<(2*phy)))|(pll<<(2*phy)); + } + template void prepareMux(Read read,Write write,uint32_t value) const { + const auto old=read(0x164280); + Port route(phy); + if((old^value)&(3U<>16))return &entry; return nullptr; } +inline bool mobileDisplay13Device(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && e->backend==GT::ADLPMobile; +} inline bool desktopGTDevice(uint32_t pci) { const auto e=deviceEntry(pci); return e && (e->family==Family::AlderLakeS || e->family==Family::RaptorLakeS); diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp index 9d21b320..13807bfa 100644 --- a/WhateverXe/kern_xelp_display_contract.hpp +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -70,20 +70,21 @@ struct DisplayContract { AUX13=1ULL<<60, DPProbe13=1ULL<<61, DPBoot13=1ULL<<62, + DonorRuntime=1ULL<<63, }; Mask required {},available {}; Mask missing() const {return required&~available;} bool ready() const {return !missing();} - static constexpr Mask Base12=Surface|NVRAM|IRQ|Probe|Framebuffer|Topology|PanelPower|PanelTranscoder|Dekel; + static constexpr Mask Base12=DonorRuntime|Surface|NVRAM|IRQ|Probe|Framebuffer|Topology|PanelPower|PanelTranscoder|Dekel; static constexpr Mask Training12=Lease|PowerGet|PowerPut|PortLookup|PathLookup|PathAdd|PathFree|TrainingEnd|Training|TrainingBegin|PathAllocate|ClockOrder; static constexpr Mask Combo12=Framing|ComboSignal|ComboAUX|ComboAUXGate|TCNative|MMIORead|MMIOWrite; static constexpr Mask Fixed12=AUXParent|TCNative|Mode|HPDCaller; static constexpr Mask Alt12=TCNative|Mode|ModeLock|AltFIA|NativeFIA; - static constexpr Mask PlatformADLS=ClockAdapter|PowerAdapter|PortInit|EnableCombo|PortLookup|OSInformation|MMIORead|MMIOWrite|HPD; + static constexpr Mask PlatformADLS=DonorRuntime|ClockAdapter|PowerAdapter|PortInit|EnableCombo|PortLookup|OSInformation|MMIORead|MMIOWrite|HPD; static constexpr Mask Power13=PowerRefs13|Engine13|Lifecycle13|DBUF13|WM13; static constexpr Mask Clock13=ClockRead13|ClockSet13|Lifecycle13; static constexpr Mask MemoryGroup13=Memory13|GTT13|ABI13|Handoff13; - static constexpr Mask Backend13=Power13|Clock13|MemoryGroup13|IRQ13|WMLayout13|Output13|Topology13; + static constexpr Mask Backend13=DonorRuntime|Power13|Clock13|MemoryGroup13|IRQ13|WMLayout13|Output13|Topology13; struct Routes { bool hdmi {},panel {},combo {},legacy {},alt {},leaseRequested {}; }; @@ -96,7 +97,7 @@ struct DisplayContract { if(r.panel||r.combo)required|=Combo12; if(r.panel)required|=PanelDepth|PanelTiming|PowerGet|PowerPut; if(r.combo)required|=AUXParent; - if(r.legacy)required|=Fixed12; + if(r.legacy)required|=Fixed12|AltFIA|NativeFIA; // Fixed TC also needs topology-aware FIA lanes/ownership. if(r.legacy||r.alt)required|=Framing|ModeLock; if(r.alt)required|=Alt12; return {required,installed}; @@ -105,10 +106,11 @@ struct DisplayContract { return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0)| (panel?Combo12|PanelDepth|PanelTiming|PanelPWM|AUXParent|PowerGet|PowerPut:0),installed}; } - static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13; + static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13|Training12|Framing; static DisplayContract display13(Mask installed,bool dp=false) {return {Backend13|(dp?DP13:0),installed};} static const char *name(Mask bit) { switch(bit) { + case DonorRuntime:return "DonorRuntime"; case Surface:return "Surface"; case NVRAM:return "NVRAM"; case IRQ:return "IRQ"; diff --git a/WhateverXe/kern_xelp_display_resources.hpp b/WhateverXe/kern_xelp_display_resources.hpp index ad5e7fd0..81570f84 100644 --- a/WhateverXe/kern_xelp_display_resources.hpp +++ b/WhateverXe/kern_xelp_display_resources.hpp @@ -8,12 +8,14 @@ namespace XELP { // v6.12 intel_display_device.c binds ADL-S and RPL-S to adl_s_desc; intel_bios.c // consequently uses the same PHY-based VBT mapping for both. This selector // grants no physical identity, stepping, fuse or donor qualification. -enum class DisplayAdapter : uint32_t { Unknown, TGL, ADLS, ADLN }; +enum class DisplayAdapter : uint32_t { Unknown, TGL, ADLS, ADLN, ADLP }; constexpr DisplayAdapter displayAdapter(Family family) { switch(family) { case Family::TigerLake:return DisplayAdapter::TGL; case Family::AlderLakeS: case Family::RaptorLakeS:return DisplayAdapter::ADLS; + case Family::AlderLakeP: + case Family::RaptorLakeP:return DisplayAdapter::ADLP; case Family::AlderLakeN:return DisplayAdapter::ADLN; default:return DisplayAdapter::Unknown; } diff --git a/WhateverXe/kern_xelp_donor.hpp b/WhateverXe/kern_xelp_donor.hpp index f81d366a..ef8bd760 100644 --- a/WhateverXe/kern_xelp_donor.hpp +++ b/WhateverXe/kern_xelp_donor.hpp @@ -3,7 +3,21 @@ #define KERN_XELP_DONOR_HPP #include #include +#include namespace XELPDonor { +// Exact C004FC1A disablePowerWellDDI incorrectly derives C's request from +// TC1's refs/mode. Match the whole producer block and use C's own counter, +// as enablePowerWellDDI already does. Hardware/domain selection belongs to +// callers; do not discard an actually referenced DDI C on capable hardware. +constexpr uint32_t DDIReleaseOffset=0x5AD86; +static constexpr uint8_t DDIReleaseBefore[] {0x83,0x7b,0x3c,0x00,0x74,0x37,0x48,0xff,0x05,0x3d,0x6c,0x08,0x00,0x48,0xff,0x05,0xd6,0x70,0x08,0x00,0xbf,0x03,0x00,0x00,0x00,0xe8,0x46,0x04,0xfc,0xff,0x48,0x85,0xc0,0x74,0x1e,0x48,0xff,0x05,0xd0,0x70,0x08,0x00,0x48,0xff,0x05,0xa9,0x75,0x08,0x00,0x83,0xb8,0x50,0x05,0x00,0x00,0x02,0x0f,0x94,0xc0,0xeb,0x06,0x31,0xc0,0xeb,0x1b,0x31,0xc0,0x0f,0xb6,0xc0,0x48,0x01,0x05,0xa5,0x70,0x08,0x00,0x34,0x01,0x0f,0xb6,0xc0,0x48,0x01,0x05,0xe9,0x6b,0x08,0x00,0xc1,0xe0,0x05}; +// xor eax,eax; cmp dword [rbx+38h],0; setne al; shl eax,5; +// jmp 5ADE2 (native union of all other DDI requests). +static constexpr uint8_t DDIReleaseAfter[] {0x31,0xc0,0x83,0x7b,0x38,0,0x0f,0x95,0xc0,0xc1,0xe0,5,0xe9,0x4b,0,0,0}; +inline bool ddiReleaseSite(const uint8_t *image,size_t size) { + return image && size>=DDIReleaseOffset+sizeof(DDIReleaseBefore) && + !memcmp(image+DDIReleaseOffset,DDIReleaseBefore,sizeof(DDIReleaseBefore)); +} inline bool displayPoolRequest(uint32_t enabled,uint32_t bytes) { return enabled && bytes && !(bytes&4095) && bytes<=0x4000000; } @@ -42,6 +56,15 @@ template Poll waitEDIDReady(IO &io,uint32_t initial) { } return r; } +// computeLaneCount ignores arg0: setupOptimalLaneCount passes port+0x124, +// while the HDMI validator passes the actual port. Qualify the caller before +// even reading config; correcting only the result does not protect this ABI. +inline const uint32_t *hdmiLaneConfig(const void *argument,uintptr_t caller,uintptr_t validator) { + if(!argument || !validator || caller!=validator)return nullptr; + const uint32_t *config {}; + memcpy(&config,static_cast(argument)+0x548,sizeof(config)); + return config; +} inline bool skipHDMIDPLanes(bool selected,bool exactCaller,bool native,uint32_t available,uint32_t lanes) { return selected && exactCaller && !native && !available && !lanes; } diff --git a/WhateverXe/kern_xelp_gop_stop.hpp b/WhateverXe/kern_xelp_gop_stop.hpp new file mode 100644 index 00000000..172837af --- /dev/null +++ b/WhateverXe/kern_xelp_gop_stop.hpp @@ -0,0 +1,31 @@ +// Shared Display12/13 GOP retirement primitives. Admission, protected peers, +// primary-pipe policy and PHY/DDB release remain with the owning backend. +#ifndef KERN_XELP_GOP_STOP_HPP +#define KERN_XELP_GOP_STOP_HPP +#include +namespace XELPGOP { +// Preserve the caller's diagnostic value, including changes made by stable(). +// Display13's frame-drain deadline omits the final delay; register waits do not. +template +bool poll(IO &io,Stable &stable,Ready ready,uint32_t reg,uint32_t &value, + Status &status,Status drift,bool delayOnTimeout=true) { + for(unsigned n=0;n<1000;++n) { + value=io.read(reg); + if(value==UINT32_MAX || !stable()){status=drift;return false;} + if(ready(value))return true; + if(n<999 || delayOnTimeout)io.delay(); + } + status=Status::Timeout;return false; +} +// Linux intel_ddi_disable_transcoder_func / disable_ddi_buf: the transcoder +// has stopped before entry. Keep shared PLLs/power held until buffer idle. +template +bool stopBuffer(Write &write,Wait &wait,uint32_t bank,uint32_t bufferReg, + uint32_t buffer,uint32_t transport,bool dp) { + if(!write(bufferReg,buffer&~0x80000000U))return false; + if(dp && (!write(0x60540+bank,transport&~0x80000000U) || + !wait(0x60540+bank,0x80000000U,0)))return false; + return wait(bufferReg,0x80000080U,0x80); +} +} +#endif diff --git a/WhateverXe/kern_xelp_gt_resources.hpp b/WhateverXe/kern_xelp_gt_resources.hpp index 4d1dc7cc..156748b1 100644 --- a/WhateverXe/kern_xelp_gt_resources.hpp +++ b/WhateverXe/kern_xelp_gt_resources.hpp @@ -3,9 +3,10 @@ #define KERN_XELP_GT_RESOURCES_HPP #include namespace XELP { -struct SmallGTResources { +struct GTResources { uint32_t slices {}, dss {}, nativeSubslices {}, eus {}, l3Banks {}, urbKiB {}; }; +using SmallGTResources = GTResources; // Gen12 SSEU: one slice, up to two DSS in these small GT SKUs, one disable // bit per EU pair. Native subslices are twice the DSS population. Preserve @@ -35,5 +36,23 @@ inline bool decodeSmallGT(const uint32_t *fuses, SmallGTResources &out) { fuses[2]&0xFF,fuses[3]&0xFF,out); } +// ADL-P Gen12.3 retains the Xe-LP fuse encoding, with up to six DSS and +// eight L3 banks. Capacity comes from fuses rather than a CPU/board model. +// The donor URB backend handles 4/6/8 banks; reject unsupported capacities. +inline bool decodeADLPGT(const uint32_t *fuses, GTResources &out) { + out={}; + if(!fuses)return false; + for(unsigned i=0;i<5;++i)if(fuses[i]==UINT32_MAX)return false; + const auto slice=fuses[0]&0xFF,mask=fuses[1]&0x3F; + const auto video=(~fuses[4])&0xFF,enhance=((~fuses[4])>>16)&0xF; + if(slice!=1 || !mask || (video!=1 && video!=5) || enhance!=1)return false; + const auto dss=static_cast(__builtin_popcount(mask)); + const auto perDSS=2*(8-static_cast(__builtin_popcount(fuses[2]&0xFF))); + const auto banks=8-static_cast(__builtin_popcount(fuses[3]&0xFF)); + if((perDSS!=8 && perDSS!=12 && perDSS!=16) || (banks!=4 && banks!=6 && banks!=8))return false; + out={1,dss,2*dss,dss*perDSS,banks,64*banks}; + return true; +} + } #endif diff --git a/WhateverXe/kern_xelp_link_requirements.hpp b/WhateverXe/kern_xelp_link_requirements.hpp new file mode 100644 index 00000000..8ab49c6e --- /dev/null +++ b/WhateverXe/kern_xelp_link_requirements.hpp @@ -0,0 +1,30 @@ +// External HDMI/fixed-DP requirements, after platform/wiring admission. +// Coordinate mapping and generation-specific power/ownership stay in adapters. +#ifndef KERN_XELP_LINK_REQUIREMENTS_HPP +#define KERN_XELP_LINK_REQUIREMENTS_HPP +#include "kern_xelp_devices.hpp" +namespace XELP { +struct LinkRequirements { + enum class PHY {None,Combo,TC}; + enum class Protocol {None,HDMI,FixedDP}; + Display display {Display::Unknown};PHY phy {PHY::None};Protocol protocol {Protocol::None}; + bool valid() const {return (display==Display::XeD || display==Display::XeLPD) && + phy!=PHY::None && protocol!=Protocol::None;} + // Linux v6.12 intel_wait_ddi_buf_active: Display12/13 share these limits. + // The native loop reads before each 5us delay; the deadline itself times out. + uint32_t activeWaitUS() const {return valid() ? (phy==PHY::TC?3000:1000) : 500;} + enum class Preparation {None,NativeCombo,NativeTC12,ColdTC13,FirmwareTC13}; + // The evidence is supplied by a powered, identity-bound host readout. A mode + // number, HPD bit or an ownership bit alone is never firmware evidence. + Preparation preparation(bool verifiedFirmware) const { + if(!valid())return Preparation::None; + if(phy==PHY::Combo)return Preparation::NativeCombo; + // Display12 native mode owns AUX -> FIA. Display13 must acquire access -> + // ownership -> AUX before base mode. Their ordering must stay distinct. + if(display==Display::XeD)return Preparation::NativeTC12; + return verifiedFirmware?Preparation::FirmwareTC13:Preparation::ColdTC13; + } + uint32_t activePolls() const {return activeWaitUS()/5;} +}; +} +#endif diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index d2276056..0b5ef729 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -14,11 +14,21 @@ struct Profile { GT gt {GT::Unknown}; Donor donor {Donor::Unknown}; uint32_t effectiveIdentity {}; // Table selector; may come from device-id. + // Mobile Display13 selection is distinct from whether Render is requested. + // The donor selector never changes the physical Display13 identity. + bool mobileDisplay13() const { + return mobileDisplay13Device(identity) && revision<=255 && + (effectiveIdentity==identity || effectiveIdentity==0x9A498086) && + display==Display::XeLPD && gt==GT::ADLPMobile && donor==Donor::C004FC1A && + family==deviceEntry(identity)->family; + } + bool displayAdmitted() const {return admitted();} bool overridden() const {return identity!=effectiveIdentity;} // Select the implemented family backend for every PCI revision. Hardware // validation is independent; a stepping split needs a concrete behavior delta. bool admitted() const {return (identity&0xFFFF)==0x8086 && revision<=255 && donor==Donor::C004FC1A && gt!=GT::Unknown && + ((gt!=GT::ADLPMobile && !mobileDisplay13Device(identity)) || mobileDisplay13()) && ((!display12SmallGT(gt) && !display12SmallGTDevice(identity)) || (display12SmallGT(gt) && display12SmallGTDevice(identity) && !overridden()));} }; @@ -26,6 +36,12 @@ inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; if((physical&0xFFFF)!=0x8086 || revision>255)return p; const auto entry=deviceEntry(effective);if(!entry)return p; + const auto physicalEntry=deviceEntry(physical); + if(physicalEntry && physicalEntry->backend==GT::ADLPMobile) { + p.family=physicalEntry->family;p.display=physicalEntry->display;p.gt=physicalEntry->backend; + if(effective==physical || effective==0x9A498086)p.donor=Donor::C004FC1A; + return p; + } p.family=entry->family;p.display=entry->display;p.gt=entry->backend; if(p.gt!=GT::Unknown)p.donor=Donor::C004FC1A; return p; @@ -65,21 +81,28 @@ struct NativePorts { }; // ABI3 includes physical/effective identity and binds a profile and immutable probe policy, not mutable connection state. // legacy permits probing for mode4; it does not force mode4 or classify a -// USB-C-capable connector as physically fixed DP. The provider still checks FIA. +// USB-C-capable connector as physically fixed DP. The provider checks generation-specific live state. // Type-C intent is represented but not admitted by either current backend. -constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U; +constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U,ProbeDisplay13FixedTC=2U,ProbeDisplay13FixedDP=4U; using QueryProbeCapabilities=uint32_t (*)(uint32_t,uint32_t); // Optional export: old ABI3 providers remain valid for their existing routes, // but their lack of a capability reply cannot authorize a new DP route. inline bool display13ProbeCapable(QueryProbeCapabilities query) { return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13ComboDP); } +inline bool display13TCProbeCapable(QueryProbeCapabilities query) { + return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13FixedTC); +} +inline bool display13TCDPProbeCapable(QueryProbeCapabilities query) { + return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13FixedDP); +} + struct ProbeConfig { uint32_t abi {ProbeABI},bytes {sizeof(ProbeConfig)}; Profile profile; uint32_t combo {},legacy {},typeC {},excluded {},comboBackend {},legacyBackend {}; bool valid() const { - if(abi!=ProbeABI || bytes!=sizeof(ProbeConfig) || !profile.admitted() || + if(abi!=ProbeABI || bytes!=sizeof(ProbeConfig) || !profile.displayAdmitted() || !same(profile,identify(profile.identity,profile.revision,profile.effectiveIdentity)))return false; // ADL-S firmware routes use the donor's three Combo PHY slots. // Its TC label grants neither Type-C nor Dekel register access. @@ -91,8 +114,9 @@ struct ProbeConfig { if((combo&~7U) || ((legacy|typeC|excluded|legacyBackend)&~0x1F8U) || (comboBackend&~7U) || (legacy&typeC) || (legacy&excluded) || (typeC&excluded))return false; if(profile.display==Display::XeD)return comboBackend==7; - return profile.display==Display::XeLPD && combo && !(combo&~3U) && - comboBackend && !(comboBackend&~3U) && !legacy && !typeC && !legacyBackend; + return profile.display==Display::XeLPD && (combo|legacy) && !(combo&~3U) && + comboBackend && !(comboBackend&~3U) && !typeC && + !(legacy&~0x78U) && !(legacyBackend&~0x78U) && !(legacy&~legacyBackend); } }; inline bool same(const ProbeConfig &a,const ProbeConfig &b) { diff --git a/WhateverXe/kern_xelp_port_wait.hpp b/WhateverXe/kern_xelp_port_wait.hpp new file mode 100644 index 00000000..0612cb16 --- /dev/null +++ b/WhateverXe/kern_xelp_port_wait.hpp @@ -0,0 +1,32 @@ +// Exact c004fc1a DDI activation wait for admitted Display12/13 external links. Keep native MMIO and return paths. +#ifndef KERN_XELP_PORT_WAIT_HPP +#define KERN_XELP_PORT_WAIT_HPP +#include +#include +#include +namespace XELPPortWait { +constexpr uint32_t WaitOffset=0x5fba0; +constexpr uint8_t WaitBefore[]={ + 0xbb,0x64,0x00,0x00,0x00,0x48,0xff,0x05,0xac,0x2d,0x08,0x00,0x48,0xff,0x05,0xbd,0x2d,0x08,0x00, + 0x41,0x8b,0xb7,0x90,0x05,0x00,0x00,0x4c,0x89,0xff,0xe8,0xcc,0x02,0xfc,0xff,0x84,0xc0,0x79,0x32, + 0xbf,0x05,0x00,0x00,0x00,0xe8,0x00,0x00,0x00,0x00,0xff,0xcb,0x75,0xd1,0x48,0xff,0x05,0x8d,0x2d, + 0x08,0x00,0x48,0x8d,0x3d,0xe6,0xc9,0x04,0x00,0x48,0x8d,0x35,0x33,0x0d,0x04,0x00,0x31,0xc0,0xe8, + 0x00,0x00,0x00,0x00,0x41,0xbe,0xd6,0x02,0x00,0xe0,0xeb,0x0a,0x48,0xff,0x05,0x61,0x2d,0x08,0x00,0x45,0x31,0xf6}; +inline bool waitKnown(const uint8_t *p,size_t n) { + if(!p || n!=sizeof(WaitBefore))return false; + for(size_t i=0;i=44 && i<48) && !(i>=76 && i<80) && p[i]!=WaitBefore[i])return false; + return true; +} + +// The callback runs after the native buffer write, before its first poll. +// Native mode/clock writes, return PC and timeout propagation remain intact. +inline void patch(uint8_t (&out)[sizeof(WaitBefore)],const uint8_t *before,uintptr_t select) { + memcpy(out,before,sizeof(out)); + // mov rdi,r15; movabs rax,select; call rax; mov ebx,eax; nop; nop + // The donor already aligns its stack for calls here; r15 owns the port. + const uint8_t init[]={0x4c,0x89,0xff,0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,0x89,0xc3,0x90,0x90}; + memcpy(out,init,sizeof(init));memcpy(out+5,&select,sizeof(select)); + out[51]=0xdf; // jne original read, without reinitializing the count +} +} +#endif diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index ace6f8bf..4e6981b4 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -4,6 +4,7 @@ #define KERN_XELP_PROBE_HPP #include "kern_xelp_platform.hpp" #include "kern_tgl_port_map.hpp" +#include "kern_display13_tc.hpp" namespace XELP { struct ProbeBinding { uintptr_t controller {},access {},platform {}; @@ -23,7 +24,7 @@ template uint32_t legacyTGLMode(IO &io,uint32_t ddi,uint32_t selected) if(!TGLTypeCPort::resolve(ddi,port) || !io.readable(0xC4000) || !port.legacyHPD(io.read(0xC4000)) || !io.readable(0x1638A0))return 0; const auto first=io.read(0x1638A0); - if(!(first&0x10) || !port.resolveFia(first,fia) || !io.readable(fia.live) || !io.readable(fia.ready))return 0; + if(!port.resolveFia(first,fia) || !io.readable(fia.live) || !io.readable(fia.ready))return 0; const auto live=fia.live==0x1638A0 ? first : io.read(fia.live); return fia.readyForLegacy(live,io.read(fia.ready)) ? 4 : 0; } @@ -38,6 +39,18 @@ template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t man } if(b.config.profile.display==Display::XeD) return legacyTGLMode(io,c[0],b.config.legacy&b.config.legacyBackend); + if(c[0]>=3) { + Display13TC::Port p; + const bool hdmi=c[1]>=9 && c[1]<=12 && c[2]==2 && c[3]==1 && c[4]==0x800 && (c[5]&0xB)==2; + const bool dp=c[1]==c[0]+6 && c[2]==1 && c[3]==1 && c[4]==0x400 && c[5]==0x201; + if(!(b.config.legacy&b.config.legacyBackend&(1U< Prepare prepare(Read read,Write write,uint32_t ddi,uint32_t pipe,uint32_t lanes,bool powered) { +template Prepare prepare(Read read,Write write,uint32_t ddi,uint32_t pipe,uint32_t lanes,bool powered,uint32_t clockSelector=0) { Prepare r; if(ddi>=9 || pipe>=3){r.reason=8;return r;} if(!powered){r.reason=5;return r;} const auto clock=0x46140+4*pipe,func=0x60400+0x1000*pipe,buffer=0x64000+0x100*ddi; - const auto routeClock=(ddi+1)<<28,routePort=(ddi+1)<<27; + const auto routeClock=(clockSelector?clockSelector:ddi+1)<<28,routePort=(ddi+1)<<27; r.clockBefore=r.clockAfter=read(clock);r.funcBefore=r.funcAfter=read(func);r.bufferBefore=read(buffer); if(lanes!=1 && lanes!=2 && lanes!=4)r.reason=1; else if((r.funcBefore|r.bufferBefore)&0x80000000U)r.reason=2; diff --git a/docs/device-selection.md b/docs/device-selection.md index 8ec87e47..f561c542 100644 --- a/docs/device-selection.md +++ b/docs/device-selection.md @@ -7,10 +7,12 @@ machine-name setting is required. By default, the physical Intel GPU device ID selects the backend. The implemented IDs are Tiger Lake `9A40`, `9A49`, `9A60`, `9A68`, `9A70`, `9A78` and Alder Lake-N / Twin Lake -`46D0`–`46D4`, plus the ADL-S/HX and RPL-S/HX IDs below. +`46D0`–`46D4`, plus the S/HX and mobile Display13 IDs below. | Family | Accepted IDs | | --- | --- | +| ADL-H / P / U | `46A3`, `46A6`, `46A8`, `46AA`, `46B3`, `46C3` | +| RPL-H / P / U | `A7A0`, `A7A1`, `A7A8`–`A7AD` | | ADL-S / HX | `4680`, `4682`, `4688`, `468A`, `468B`, `4690`, `4692`, `4693` | | RPL-S / HX | `A780`, `A781`, `A782`, `A783`, `A788`, `A789`, `A78A`, `A78B` | @@ -47,6 +49,12 @@ HX IDs `4688/468B/A788/A78B` select the S-family backend, independent of CPU branding. RKL remains identification-only: its absent PW_2, PLL/PHY mapping and HTI resource reservations require a separate Display adapter. +Mobile Display13 keeps the physical family and accepts its physical ID or the +legacy 9A49 donor selector. Other-family overrides are rejected. GT resources +come from slice/DSS/EU-pair/L3 fuses: one slice, up to six DSS, 8/12/16 EUs per +DSS and four/six/eight L3 banks, with supported media-engine fuses. A shared +backend does not imply every resource combination has been tested. + Do not use an override merely to change the displayed GPU name. ## Display name @@ -76,7 +84,13 @@ must match the implemented path; ambiguous or cross-routed AUX layouts are exclu has CPU coverage; not every panel or link rate has been tested on hardware. - TGL panels requesting HOBL use standard signal settings; the HOBL power-saving optimization is disabled until its retry behavior is supported. -- Display 13: implemented Combo HDMI/DP paths do not imply USB-C/eDP support. +- Display 13: fixed HDMI uses firmware Combo A/B and TC1–4 routes; TC requires + verified legacy mode. Experimental fixed TC1–4 SST DP requires matching VBT + AUX wiring. Boot handoff supports validated linear RGB scanout, including + supported firmware scaling, and retires secondary fixed TC firmware outputs + before native startup. ADL-H has been tested with three outputs and a + DP-to-HDMI adapter. Direct DP sinks remain unverified. Combo DP, eDP, MST, + Type-C-managed output and cross-routed AUX still need further implementation. The Display12 small-GT implementation accepts one slice, up to two DSS, 16/24/32 EUs, four L3 banks and the supported media engines. Different resource diff --git a/tests/adlp_fb_candidate.py b/tests/adlp_fb_candidate.py new file mode 100644 index 00000000..e3534208 --- /dev/null +++ b/tests/adlp_fb_candidate.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Replay production board selection and WEX/provider binding for firmware-selected fixed HDMI.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +f='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in ['Display13OutputWiring XE::display13BoardWiring(', 'bool XE::xelpBindProbe(']) +code=r''' +#include +#include +#include +#include +#include "kern_display13_firmware.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_alderlake_s.hpp" +struct OSData {void *data=nullptr;unsigned bytes=0;void *getBytesNoCopy(){return data;}unsigned getLength(){return bytes;}}; +#define OSDynamicCast(T,p) static_cast(p) +struct IOService {OSData *property=nullptr;OSData *getProperty(const char *){return property;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}}; +XELP::ProbeConfig seen;unsigned calls=0; +bool configure(void *c,const XELP::ProbeConfig *p){assert(c==(void*)1);seen=*p;++calls;return p->valid();} +struct XE { + XELP::Profile xeProfile=XELP::identify(0x46A68086,12,0x9A498086); + XELP::VBT::Ports firmwarePorts; + bool adlpDisplaySelected=true,adlsDisplaySelected=false,tglHDMIWiringApplied=false,tglTCWiringLoaded=false; + bool display13FBTopologyHooksReady=true,display13TCProbeReady=true,display13FBTCHooksReady=true; + bool display13TCDPProbeReady=true,display13DPProbeReady=false,display13FBSSTHooksReady=true,display13FBAUXHooksReady=true; + uintptr_t xelpProviderProbe=1,xelpProviderConfigureProbe=reinterpret_cast(configure); + void *adlnTopologyController=(void*)1;Display13OutputWiring adlnOutputWiring,tglHDMIWiring; + struct {bool valid=false;unsigned excluded=0;unsigned selected(){return 0;}}adlsWiring; + struct {bool valid=false;unsigned typeC=0,excluded=0;}tglTCWiring; + unsigned tglAUXParentMask=0;unsigned tglTCProbeMask(){return 0;} + Display13OutputWiring display13BoardWiring(IOService *); + bool xelpBindProbe(void *); +}; +FUNCTIONS +int main(){ + XE x;IOService pci;x.firmwarePorts.status=XELP::VBT::Status::Valid;x.firmwarePorts.present=0x38; + for(unsigned d=3;d<=5;++d){auto &p=x.firmwarePorts.port[d];p.type=d==5?4:0x12;p.ddc=d;p.capabilities=3;p.hdmiPreset=7;} + auto w=x.display13BoardWiring(&pci);assert(w.described() && w.hdmi==0x18 && w.ddc[3]==9 && w.ddc[4]==10 && !w.dp); + assert(!Display13FBTopology::supportedWiring(w) && Display13FBTopology::supportedWiring(w,true)); + x.adlnOutputWiring=w;assert(x.xelpBindProbe((void*)1)); + assert(seen.valid() && !seen.combo && seen.comboBackend==3 && seen.legacy==0x18 && seen.legacyBackend==0x78); + assert(seen.profile.family==XELP::Family::AlderLakeP && seen.profile.admitted()); + for(unsigned f=0;f<5;++f){XE bad=x; + if(f==0)bad.display13TCProbeReady=false;if(f==1)bad.display13FBTCHooksReady=false; + if(f==2)bad.display13FBTopologyHooksReady=false;if(f==3)bad.xelpProviderConfigureProbe=0; + if(f==4)bad.adlnTopologyController=(void*)2; + const auto before=calls;assert(!bad.xelpBindProbe((void*)1) && calls==before); + } + OSData property;pci.property=&property;assert(!x.display13BoardWiring(&pci).described());pci.property=nullptr; + x.firmwarePorts.port[3].type=4; + assert(x.display13BoardWiring(&pci).hdmi==0x10); // Exclude DP without dropping unrelated HDMI. + x.firmwarePorts.port[4].type=4;assert(!x.display13BoardWiring(&pci).described()); + // DP-capable records with TC AUX F..I compose with the existing HDMI. + for(unsigned ddi=3;ddi<=6;++ddi) { + XE dp;dp.firmwarePorts.status=XELP::VBT::Status::Valid;dp.firmwarePorts.present=1|(1U< +#include +#include +#include +#include +#include +#include "kern_xelp_platform.hpp" +#include "kern_xelp_os.hpp" +#include "kern_alderlake_n.hpp" +#include "kern_xelp_gt_resources.hpp" +using mach_vm_address_t=uintptr_t; +#define arrsize(a) (sizeof(a)/sizeof((a)[0])) +#define SYSLOG(...) do{}while(0) +struct IOService{};using SInt32=int32_t; +struct IORegistryEntry { + static std::map props; + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *k,bool v){props[k]=v;} +};std::map IORegistryEntry::props; +struct Kext {size_t loadIndex;};Kext kextIntelTGLFb{1},kextIntelTGL{2}; +bool arg=true,readout=false;bool checkKernelArgument(const char *s){if(!strcmp(s,"-wex-adlp-readout"))return readout;assert(!strcmp(s,"-wex-adlp-load-only"));return arg;} +const char *version="Darwin Kernel Version 20.6.0: Thu Jul 6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64"; +struct KernelPatcher { + struct RouteRequest {const char *symbol;uintptr_t replacement; + templateRouteRequest(const char *s,T t):symbol(s),replacement(reinterpret_cast(t)){} + }; + bool wrongSymbol=false;unsigned routed=0; + templateT solveSymbol(size_t index,const char *s,uintptr_t base,size_t) { + bool start=strstr(s,"5start")!=nullptr;return base+(index==1?(start?0x3f1fc:0x446bc):(start?0x23ecc:0x238e2))+(wrongSymbol?1:0); + } + void clearError(){} + bool routeMultipleShort(size_t,RouteRequest *routes,size_t count,uintptr_t,size_t) { + assert(count==2 && strstr(routes[0].symbol,"5start") && strstr(routes[1].symbol,"5probe")); + // Calling the actual replacements must reject independently of object data. + assert(!reinterpret_cast(routes[0].replacement)(nullptr,nullptr)); + assert(!reinterpret_cast(routes[1].replacement)(nullptr,nullptr,nullptr)); + routed+=2;return true; + } +}; +constexpr unsigned kIOMapInhibitCache=0; +void IODelay(unsigned){assert(false);} +struct DiagnosticGPU { + unsigned command=2,maps=0; + unsigned configRead16(unsigned r){assert(r==4);return command;} + struct Map {uintptr_t getVirtualAddress(){return 0;}unsigned getLength(){return 0;}void release(){}}map; + Map *mapDeviceMemoryWithRegister(unsigned r,unsigned){assert(r==0x10);++maps;return ↦} + void setProperty(const char*,const void*,unsigned){} + void setProperty(const char*,uint32_t,unsigned){} +}; +struct XE { + bool adlpLoadOnly=false,adlpDisplaySelected=false;XELP::Profile xeProfile;unsigned initialized=0; + DiagnosticGPU *physicalGPU=nullptr; + void select(){SELECTION ++initialized;} + void diagnose(){DIAGNOSTIC ++initialized;} + bool processADLPLoadOnly(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static IOService *rejectADLPFramebufferProbe(IOService *,IOService *,SInt32 *); + static IOService *rejectADLPAcceleratorProbe(IOService *,IOService *,SInt32 *); + static bool rejectADLPStart(IOService *,IOService *); +}; +BODY +int main(){ + unsigned cases=0; + for(unsigned physical:{0x46a6U,0x46a3U,0xa7a8U,0x9a49U,0x4680U})for(bool flag:{false,true})for(bool override:{false,true}){ + XE xe;arg=flag;xe.xeProfile=XELP::identify((physical<<16)|0x8086,12,((override?0x9a49:physical)<<16)|0x8086);xe.select(); + bool expected=(physical==0x46a6 || physical==0x46a3 || physical==0xa7a8)&&flag; + assert(xe.adlpLoadOnly==expected && xe.initialized==!expected);++cases; + } + // Failed/unavailable diagnostic mappings must still stop both native drivers. + for(unsigned physical:{0x46a6U,0xa7a8U,0x4c8aU})for(bool enabled:{false,true})for(bool powered:{false,true}) { + XE xe;DiagnosticGPU gpu;gpu.command=powered?2:0;xe.physicalGPU=&gpu;readout=enabled; + xe.xeProfile=XELP::identify((physical<<16)|0x8086,12);xe.diagnose(); + const bool blocked=enabled && physical!=0x4c8a; + assert(xe.adlpLoadOnly==blocked && xe.initialized==!blocked && gpu.maps==unsigned(blocked&&powered));++cases; + } + readout=false; + for(unsigned index:{1U,2U})for(unsigned bad=0;bad<4;++bad){ + std::vector image(0x50000);auto base=reinterpret_cast(image.data()); + uint8_t fb[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0x83,0xec,0x10,0x48,0x89,0xf3,0x49,0x89,0xfe}; + uint8_t gt[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; + unsigned p=index==1?0x446bc:0x238e2,q=index==1?0x3f1fc:0x23ecc; + memcpy(image.data()+p,index==1?fb:gt,17);memcpy(image.data()+q,gt,4); + XE xe;xe.adlpLoadOnly=bad!=1;KernelPatcher patcher;patcher.wrongSymbol=bad==2; + if(bad==3)image[p+8]^=1; + IORegistryEntry::props.clear(); + assert(xe.processADLPLoadOnly(patcher,index,base,image.size())==(bad==0)); + assert(patcher.routed==(bad==0?2U:0U)); + if(!bad){assert(IORegistryEntry::props.at(index==1?"ADLPFramebufferProbeRejected":"ADLPAcceleratorProbeRejected"));assert(IORegistryEntry::props.at("ADLPLoadOnlyUnexpectedStart"));} + ++cases; + } + for(unsigned module:{1U,2U}){ + std::vector image(0x50000);auto base=reinterpret_cast(image.data()); + const uint8_t gt[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28}; + memcpy(image.data()+0x238e2,gt,17);memcpy(image.data()+0x23ecc,gt,4); + XE xe;xe.adlpDisplaySelected=true;KernelPatcher p; + assert(xe.processADLPLoadOnly(p,module,base,image.size())==(module==2));assert(p.routed==(module==2?2U:0U)); + if(module==2)assert(IORegistryEntry::props.at("ADLPFBOnlyAcceleratorBlocked"));++cases; + } + printf("PASS %u production ADLP load-only selection/probe/start checks\n",cases); +} +'''.replace('SELECTION',selection).replace('BODY',body).replace('DIAGNOSTIC',diagnostic) +with tempfile.TemporaryDirectory(prefix='adlp-load-only-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/adlp_render.py b/tests/adlp_render.py new file mode 100644 index 00000000..2b13c8ce --- /dev/null +++ b/tests/adlp_render.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Run production profile selection: ADL-P Render must preserve Display13.""" +from pathlib import Path +import re, subprocess, tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +f=re.search(r'void XE::selectProfile\(.*?\n\}',s,re.S).group() +code=r''' +#include "kern_xelp_platform.hpp" +#include "kern_xelp_os.hpp" +#include +#include +#include +const char *version="Darwin Kernel Version 20.6.0: Thu Jul 6 22:12:47 PDT 2023; root:xnu-7195.141.49.702.12~1/RELEASE_X86_64"; +bool noaccel=false; +bool checkKernelArgument(const char *s){return noaccel && !strcmp(s,"-wex-noaccel");} +int kextIntelTGL,kextIntelTGLFb; +bool adlnNativeRCSProfileRequested=false,display13TraceEnabled=false,display13Offline=false; +struct XE { + XELP::Profile xeProfile; + bool display12SmallGTDetected=false,adlsDisplaySelected=false; + bool adlnRequested=false,adlnDetected=false,adlnKernelReady=false,adlnNativeRequested=false; + bool adlpDisplaySelected=false,adlpRenderRequested=false,display13FramebufferRequested=false; + bool tglRequested=false,tglAcceleratorRequested=false,tglInactivePorts=false,tglPG1Poll=false; + bool tglClockOrderRequested=false,tglTrainingLeaseRequested=false,tglProgramEnabled=false; + int *currentFramebuffer=nullptr,*currentGraphics=nullptr; + struct {bool available=false;}modDVMTCalcFix; + void selectProfile(const XELP::Profile &); +}; +FUNCTION +int main(){ + const uint32_t mobile[]={0x46a3,0x46a6,0x46a8,0x46aa,0x46b3,0x46c3,0xa7a0,0xa7a1,0xa7a8,0xa7a9,0xa7aa,0xa7ab,0xa7ac,0xa7ad}; + unsigned cases=0; + for(unsigned which=0;which<30;++which)for(unsigned rev=0;rev<256;++rev)for(unsigned off=0;off<2;++off) { + noaccel=off;XE x;const bool isMobile=which<28,isN=which==28; + const auto pci=(isMobile?mobile[which/2]:isN?0x46d1:0x9a49)<<16|0x8086; + const auto effective=isMobile && (which&1)?0x9a498086:pci; + const auto p=XELP::identify(pci,rev,effective);x.selectProfile(p); + assert(XELP::same(x.xeProfile,p) && p.admitted()); + assert(x.currentFramebuffer==&kextIntelTGLFb && x.currentGraphics==&kextIntelTGL); + assert(x.display13FramebufferRequested==(isMobile || isN)); + assert(x.adlpDisplaySelected==isMobile); + assert(x.adlpRenderRequested==(isMobile&&!off)); + assert(x.adlnDetected==(isN || (isMobile&&!off))); + assert(x.tglAcceleratorRequested==!off); + assert(adlnNativeRCSProfileRequested==(isN || (isMobile&&!off)));++cases; + } + noaccel=false;XE unknown;unknown.selectProfile(XELP::identify(0x4c8a8086,12)); + assert(!unknown.adlpRenderRequested && !unknown.adlnDetected && !unknown.tglAcceleratorRequested); + assert(cases==15360);puts("PASS 15360 production profile selections: all mobile Display13 IDs/revisions, physical and legacy donor selectors, noaccel, ADL-N and TGL"); +} +'''.replace('FUNCTION',f) +with tempfile.TemporaryDirectory(prefix='wex-adlp-render-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/adlp_resources.cpp b/tests/adlp_resources.cpp new file mode 100644 index 00000000..971f40ac --- /dev/null +++ b/tests/adlp_resources.cpp @@ -0,0 +1,57 @@ +#include "kern_xelp_gt_resources.hpp" +#include "kern_display13_firmware.hpp" +#include "kern_display13_fb_topology.hpp" +#include +#include +#include +using namespace XELP; +int main() { + // Hardware captures: 46A6, six DSS, 96 EU, eight L3 banks, VCS0/2. + uint32_t f[]={1,0x3f,0,0,0xe00fa};GTResources r; + assert(decodeADLPGT(f,r) && r.dss==6 && r.nativeSubslices==12 && r.eus==96 && r.l3Banks==8 && r.urbKiB==512); + assert(!decodeSmallGT(f,r)); + for(unsigned mask=1;mask<64;++mask)for(unsigned eu=0;eu<256;++eu)for(unsigned l3=0;l3<256;++l3) { + uint32_t v[]={1,mask,eu,l3,0xe00fa}; + const auto per=2*(8-__builtin_popcount(eu)),banks=8-__builtin_popcount(l3); + const bool expected=(per==8 || per==12 || per==16) && (banks==4 || banks==6 || banks==8); + assert(decodeADLPGT(v,r)==expected); + if(expected)assert(r.eus==unsigned(per*__builtin_popcount(mask)) && r.l3Banks==unsigned(banks)); + else assert(!r.eus); + } + assert(!decodeADLPGT(nullptr,r)); + for(unsigned i=0;i<5;++i){auto save=f[i];f[i]=~0U;assert(!decodeADLPGT(f,r));f[i]=save;} + for(unsigned bad:{0U,2U,3U}){f[0]=bad;assert(!decodeADLPGT(f,r));}f[0]=1; + f[4]=0xfffff;assert(!decodeADLPGT(f,r)); + VBT::Ports ports;ports.status=VBT::Status::Valid;ports.present=0x3a; + ports.port[1]={0x68c6,7,0,0x10,0xc0,0}; + ports.port[3]={0x60d2,13,3,0,0xc0,3}; + ports.port[4]={0x60d2,15,4,0,0xc0,3}; + ports.port[5]={0x60d6,17,5,0x80,0xc0,3}; + auto w=VBT::display13HDMI(ports,0x7b); + assert(w.described() && w.hdmi==0x18 && w.ddc[3]==9 && w.ddc[4]==10); + assert(!ports.hdmi(3).hdmi); // Existing Combo policy is unchanged. + assert(Display13FBTopology::xeLPDWiring(w)); + assert(!Display13FBTopology::supportedWiring(w)); // Runtime stays closed until hooks are installed. + assert(identify(0x46a68086,12).admitted()); + for(unsigned d=3;d<7;++d) { + assert(VBT::ddi(Family::AlderLakeP,13+2*(d-3))==d); + auto p=ports;p.present=1U<=3?1U:0U,0x400,0x201}; + const uint32_t panel[]={0,0,0,0,2,0x18}; + memcpy(native+0x20+24*k,k?rec:panel,24); + } + assert(Display13FBTopology::apply(out,sizeof(out),native,v)); + const auto c=reinterpret_cast(out+0x20+24*d); + assert(v.matchesHDMI(c) && c[3]==1 && c[1]==d+6); + assert(Display13FBTopology::boundPorts(out,v).selected==(1U< +#include +#include +#include +#include +#include "kern_adls_boot.hpp" +using namespace AlderLakeS; +struct IO { + std::map m;std::vector writes; + unsigned ticks=0;bool stallFrames=false,stallPipe=false,stallBuffer=false,drift=false,driftConfig=false; + bool readable(uint32_t r){return r<0x1000000;} + uint32_t read(uint32_t r){return m[r];} + void write(uint32_t r,uint32_t v){ + writes.push_back(r); + for(unsigned p=0;p<3;++p){ + auto b=p*0x1000; + if(r==0x70008+b && !(v&0x80000000)){ + assert(!(m[0x70180+b]&0x80000000) && ticks>=2); + if(!stallPipe)v&=~0x40000000U; + } + if(r==0x60400+b && !(v&0x80000000)){ + auto encoding=(m[r]>>27)&15; + if(encoding && !stallBuffer)m[0x64000+0x100*(encoding-1)]|=0x80; + if(encoding && driftConfig)m[0x64000+0x100*(encoding-1)]^=1; // live idle indication after transport consumer stops + } + if(r==0x46140+4*p && !(v&0xf0000000)){ + assert(!(m[0x70008+b]&0xc0000000) && !(m[0x60400+b]&0x80000000) && !(m[0x60540+b]&0x80000000)); + } + } + if((r==0x64000 || r==0x64300 || r==0x64400) && !(v&0x80000000)){ + if(!stallBuffer)v|=0x80; + } + m[r]=v; + } + void delay(){++ticks;for(unsigned p=0;p<3;++p)if(!stallFrames && (m[0x70008+0x1000*p]&0x80000000))++m[0x70040+0x1000*p]; + if(drift && !writes.empty())m[0x70188]^=1; + } + IO(){m[0x45404]=0xff;m[0x164280]=0x01e07c00;m[0x1642bc]=0x30;for(unsigned d:{0U,3U,4U,5U,6U})m[0x64000+0x100*d]=0x80;} + void gop(unsigned p,unsigned d,bool dp){ + auto b=0x1000*p;Port port;assert(Port::resolve(d,port)); + m[0x70008+b]=0xc0000024;m[0x60400+b]=0x80000000|((d+1)<<27)|(dp?2U<<24:0); + m[0x60540+b]=dp?0x80040300:0;m[0x46140+4*p]=(d+1)<<28; + m[port.buffer]=0x80000000|(dp?6:0);m[port.clock]&=~port.clockOff; + m[0x70180+b]=0x84000008;m[0x70188+b]=120;m[0x70190+b]=0x0437077f; + m[0x60000+b]=0x0897077f;m[0x6000c+b]=0x04640437; + } +}; +Wiring wiring(){Wiring w;w.valid=true;w.hdmi=1U<<3;w.dp=1|(1U<<4);return w;} +void protectedState(IO &io,const std::map&before,unsigned primary){ + for(auto pair:before){auto reg=pair.first; + bool peer=(reg>=0x60000+0x1000*primary && reg<0x61000+0x1000*primary) || + (reg>=0x70000+0x1000*primary && reg<0x71000+0x1000*primary && reg!=0x70040+0x1000*primary) || + reg==0x46140+4*primary || reg==0x45404 || reg==0x164280 || reg==0x1642bc || (reg>=0x46010&®<=0x46030); + if(peer)assert(io.m[reg]==pair.second); + } +} +int main(){ + unsigned cases=0; + for(unsigned first=0;first<2;++first)for(unsigned count=2;count<=3-first;++count){ + IO io;io.gop(first,3,false);io.gop(first+1,4,true);if(count==3)io.gop(2,0,true); + auto before=io.m;auto r=Boot::prepare(io,true,wiring()); + assert(r.status==Boot::Status::Complete && r.primary==first && r.parked==(((1U<=0x46010&®<=0x46030)); + ++cases; + } + // Physical output order is independent of pipe order, including a DP primary. + const unsigned orders[][3] {{0,3,4},{0,4,3},{3,0,4},{3,4,0},{4,0,3},{4,3,0}}; + for(const auto &ports:orders){IO io;for(unsigned p=0;p<3;++p)io.gop(p,ports[p],ports[p]!=3); + const auto before=io.m;auto r=Boot::prepare(io,true,wiring()); + assert(r.status==Boot::Status::Complete && r.primary==0 && r.parked==6);protectedState(io,before,0);++cases; + } + // Stop/ack failures retain remaining consumers and every primary/shared field. + for(unsigned f=0;f<5;++f){IO io;io.gop(0,3,false);io.gop(1,4,true); + io.stallFrames=f==0;io.stallPipe=f==1;io.stallBuffer=f==2;io.drift=f==3;io.driftConfig=f==4; + auto before=io.m;auto r=Boot::prepare(io,true,wiring());assert(r.status==Boot::Status::Timeout || r.status==Boot::Status::Drift); + assert(!r.parked);if(f!=3)protectedState(io,before,0); + assert(io.m[0x46144]==0x50000000);++cases; + } + // Reject foreign/ambiguous firmware before any write. + for(unsigned f=0;f<9;++f){IO io;io.gop(0,3,false);io.gop(1,4,true);auto w=wiring(); + switch(f){case 0:io.m[0x71280]=0x80000000;break; + case 1:io.m[0x71180]|=1U<<10;break;case 2:io.m[0x61540]|=1U<<27;break; + case 3:io.m[Display13FBIRQ::DSBControl(1,0)]=1;break; + case 4:io.m[0x61400]=io.m[0x60400];break;case 5:w.dp=1;break; + case 6:io.m[0x46144]=0x70000000;break;case 7:io.m[0x711ac]=4096;break; + case 8:io.m[0x61404]=1;break; + } + auto r=Boot::prepare(io,true,w);assert(r.status==Boot::Status::Topology && io.writes.empty());++cases; + } + {IO io;io.gop(0,3,false);assert(Boot::prepare(io,true,wiring()).status==Boot::Status::Unchanged && io.writes.empty());++cases;} + {IO io;io.gop(0,3,false);io.gop(1,4,true);assert(Boot::prepare(io,false,wiring()).status==Boot::Status::Unavailable && io.writes.empty());++cases;} + printf("PASS %u ADL-S firmware consumer cases: multi-GOP retirement, first-active preservation, timeout and foreign-state rejection\n",cases); +} diff --git a/tests/adls_boot_runtime.py b/tests/adls_boot_runtime.py new file mode 100644 index 00000000..cc5c80db --- /dev/null +++ b/tests/adls_boot_runtime.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""A rejected ADL-S boot consumer transition must not start the accelerator.""" +from pathlib import Path +import subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('bool XE::wrapTGLAcceleratorStart(');b=s.index(' if (callbackXE->adlnDetected',a) +entry=s[a:b]+' return true;\n}' +code=''' +#include +struct IOService {}; +struct XE { + bool display12SmallGTDetected=false,display12SmallGTReady=true; + bool adlsDisplaySelected=true,adlsDisplayHooksReady=true,adlsBootConsumersFailed=false; + static bool wrapTGLAcceleratorStart(IOService*,IOService*); +} xe,*callbackXE=&xe; +'''+entry+''' +int main(){ + assert(XE::wrapTGLAcceleratorStart(nullptr,nullptr)); + xe.adlsBootConsumersFailed=true; + assert(!XE::wrapTGLAcceleratorStart(nullptr,nullptr)); + xe.adlsDisplaySelected=false; + assert(XE::wrapTGLAcceleratorStart(nullptr,nullptr)); + xe.adlsDisplaySelected=true;xe.adlsBootConsumersFailed=false;xe.adlsDisplayHooksReady=false; + assert(!XE::wrapTGLAcceleratorStart(nullptr,nullptr)); +} +''' +with tempfile.TemporaryDirectory(prefix='adls-boot-runtime-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined',str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) +print('PASS actual accelerator admission: failed ADL-S boot retirement blocks native start; unrelated backend unchanged') diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp index d3c55547..e61bcfec 100644 --- a/tests/adls_clock_native_fixture.cpp +++ b/tests/adls_clock_native_fixture.cpp @@ -8,6 +8,7 @@ #include #include "kern_alderlake_s.hpp" #include "kern_tgl_port_map.hpp" +#include "kern_adls_clock.hpp" templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} void jump(uint8_t *p,uintptr_t t){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&t,8);p[10]=0xff;p[11]=0xe0;} struct Event {uint32_t kind,reg,value;}; @@ -15,13 +16,29 @@ std::vector events;std::map mmio; alignas(8) uint8_t ctl[0xF00] {},port[0x700] {},access[0x70] {}; uintptr_t vtable[64] {};uint32_t config[]={3,9,2,0,0x800,0x202}; uint8_t *image;unsigned fault,delays,errors; -uint32_t readReg(void *p,unsigned long reg){assert(p==access);assert(mmio.count(reg));return mmio.at(reg);} +AlderLakeS::ClockAllocation allocation; +AlderLakeS::ClockTranslation translation; +bool managed; +uint32_t readReg(void *p,unsigned long reg){ + if(managed)reg=translation.reg(reg,reinterpret_cast(__builtin_return_address(0))-reinterpret_cast(image)); + assert(p==access);assert(mmio.count(reg));return mmio.at(reg); +} void writeReg(void *p,unsigned long reg,uint32_t v){ - assert(p==access && mmio.count(reg)); + assert(p==access); + const auto pc=reinterpret_cast(__builtin_return_address(0))-reinterpret_cast(image); if(reinterpret_cast(__builtin_return_address(0))==reinterpret_cast(image+0x6d973)) TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg==0x164294?0x164284:reg,v); + if(managed){ + reg=translation.reg(reg,pc); + if(reg==0x164280 && translation.enabling && pc==0x6d77e) { + v=translation.mux(v,pc); + translation.prepareMux([](uint32_t r){return mmio.at(r);}, + [](uint32_t r,uint32_t value){events.push_back({1,r,value});mmio[r]=value;},v); + } + } + assert(mmio.count(reg)); events.push_back({1,uint32_t(reg),v}); - if(reg==0x46010 || reg==0x46014 || reg==0x46018) { + if(reg==0x46010 || reg==0x46014 || reg==0x46018 || reg==0x46030) { // Model hardware ACK/LOCK independently from request bits. Failure cases // exercise the donor's bounded waits, not successful hardware operation. v&=~0x44000000U; @@ -94,6 +111,73 @@ int main(int argc,char **argv){ assert(mmio[pll.cfg0]==cfg0 && mmio[pll.cfg1]==cfg1); disable(ctl,port,nullptr);assert(!errors && !delays && !mmio[pll.enable] && (mmio[0x164280]&route.clockOff));++cases; } + // Firmware can route any live Combo PHY to any PLL. Replay the production + // allocation and operand translation with the real enable/disable bodies. + auto snapshot=[] { + AlderLakeS::ClockSnapshot s;s.mux[0]=mmio.at(0x164280);s.mux[1]=mmio.at(0x1642bc); + for(unsigned i=0;i<4;++i){AlderLakeS::PLL p;AlderLakeS::PLL::resolve(i,p);s.enable[i]=mmio.at(p.enable);}return s; + }; + auto setup=[] { + allocation={};fault=errors=delays=0;events.clear();mmio.clear(); + mmio[0x164280]=0x01e07c00;mmio[0x1642bc]=0x30; + for(unsigned i=0;i<4;++i){AlderLakeS::PLL p;AlderLakeS::PLL::resolve(i,p);mmio[p.enable]=mmio[p.cfg0]=mmio[p.cfg1]=0;} + }; + auto routeOn=[](unsigned phy,unsigned pll) { + AlderLakeS::Port p;AlderLakeS::Port::resolve(phy?phy+2:0,p); + auto &v=mmio[p.clock];v=(v&~(p.clockOff|(3U<(port,0x114)=1;at(port,0x128)=20; + AlderLakeS::Port p;AlderLakeS::Port::resolve(config[0],p); + auto choice=allocation.choose(p,snapshot(),on);assert(choice.valid); + translation={phy,choice.pll,on};managed=true; + if(on){allocation.binding[phy]=choice.pll+1;enable(ctl,port,nullptr,nullptr);} + else {disable(ctl,port,choice.keepPLL?port:nullptr); + AlderLakeS::PLL r;if(choice.keepPLL || (AlderLakeS::PLL::resolve(choice.pll,r)&&!(mmio[r.enable]&0xcc000000)))allocation.binding[phy]=0; + } + managed=false;assert(config[0]==(phy?phy+2:0));return choice; + }; + unsigned sharedCases=0; + for(unsigned target=0;target<3;++target)for(unsigned peer=0;peer<5;++peer)for(unsigned occupied=0;occupied<4;++occupied) { + if(target==peer)continue;setup();routeOn(peer,occupied); + const auto before=mmio; + // A disabled output has no ownership merely because its stale selector is0. + auto off=run(target,false);assert(off.keepPLL); + auto on=run(target,true);assert(on.pll!=occupied); + AlderLakeS::Port targetPort;AlderLakeS::Port::resolve(target?target+2:0,targetPort); + uint32_t lastMux=before.at(0x164280); + for(auto e:events)if(e.kind==1 && e.reg==0x164280) { + if((e.value^lastMux)&(3U< +#include +#include +struct IORecursiveLock {unsigned depth=0;}; +void IORecursiveLockLock(IORecursiveLock *l){++l->depth;} +void IORecursiveLockUnlock(IORecursiveLock *l){assert(l->depth);--l->depth;} +uintptr_t thread=1; +void *current_thread(){return reinterpret_cast(thread);} +unsigned refs=0,getCount=0,putCount=0,reads=0; +std::map mmio; +alignas(8) uint8_t ctl[0xf00]{},port[0x700]{},access[0x80]{},power[0x100]{}; +uint32_t config[6]{}; +templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry o;return &o;} + void setProperty(const char *,void *,unsigned){} +}; +uint32_t readNative(void *a,unsigned long reg){assert(a==access && refs && mmio.count(reg));++reads;return mmio.at(reg);} +void getPG(void *p,uint32_t pg){assert(p==power && pg==1);++refs;++getCount;} +void putPG(void *p,uint32_t pg){assert(p==power && pg==1 && refs);--refs;++putCount;} +struct XE { + bool adlsDisplaySelected=true,display13FramebufferRequested=false; + struct Native { + bool ready=true;bool complete(){return ready;} + void (*getPG)(void*,uint32_t)=::getPG,(*putPG)(void*,uint32_t)=::putPG; + } tglTCNativeFunctions; + uintptr_t orgTGLRegisterRead=reinterpret_cast(readNative); + static uint32_t wrapTGLRegisterRead(void*,unsigned long); + static uint8_t *adlsPowerTarget(void *p){return p==power?power:nullptr;} + static bool comboClockPower(void *p,bool on){if(on)getPG(p,1);else putPG(p,1);return true;} + static uint32_t comboClockRead(void *a,unsigned long r){return readNative(a,r);} + static bool comboBeginClock(void*,void*,bool,bool&,void *other=nullptr); + static void comboEndClock(); +} xe,*callbackXE=&xe; +#define FunctionCast(f,p) reinterpret_cast(p) +STATE +FUNCTIONS +int main(){ + IORecursiveLock lock;comboClockTransaction.lock=&lock; + at(ctl,0xc40)=access;at(ctl,0xc8)=power;at(port,0x548)=config; + at(access,0x50)=ctl;at(power,0x10)=ctl;at(power,0x78)=access; + mmio={{0x45404,3},{0x164280,0x01e07420},{0x1642bc,0x30}, + {0x46010,0xcc000000},{0x46014,0},{0x46018,0},{0x46030,0}}; + bool keep=false; + assert(XE::comboBeginClock(ctl,port,true,keep)); + assert(refs==1 && lock.depth==1 && comboClockAccess(access)); + assert(!comboClockAccess(port) && comboClockTransaction.translation.pll==1); + assert(comboClockTransaction.allocation.binding[0]==2); + assert(!XE::comboBeginClock(ctl,port,true,keep));assert(refs==1 && lock.depth==1); + thread=2;assert(!comboClockAccess(access));thread=1; + // Partial enable: retain the powered PLL's reservation until native teardown. + mmio[0x46014]=0x0c000000;XE::comboEndClock(); + assert(!refs && !lock.depth && !comboClockAccess(access)); + assert(XE::comboBeginClock(ctl,port,false,keep) && !keep); + assert(comboClockTransaction.translation.pll==1); + mmio[0x46014]=0;XE::comboEndClock();assert(!comboClockTransaction.allocation.binding[0]); + // A gated port without an owned attempt must not stop its stale selector0. + assert(XE::comboBeginClock(ctl,port,false,keep) && keep);XE::comboEndClock(); + assert(mmio[0x46010]==0xcc000000); + // PW1 not acknowledged: no clock-bank reads or unbalanced refs. + mmio[0x45404]=0;auto n=reads; + assert(!XE::comboBeginClock(ctl,port,true,keep));assert(reads==n+1); + mmio[0x45404]=3;mmio[0x164280]=UINT32_MAX; + assert(!XE::comboBeginClock(ctl,port,true,keep)); + assert(!refs && !lock.depth && getCount==putCount && !comboClockTransaction.thread); + // TGL and Display13 must never scan or allocate ADL-S extra PLL banks. + for(bool d13:{false,true}) { + xe.adlsDisplaySelected=false;xe.display13FramebufferRequested=d13; + comboClockTransaction.allocation={}; + mmio={{0x45404,3},{0x164280,0x01000C00},{0x46010,0},{0x46014,0}}; + assert(XE::comboBeginClock(ctl,port,true,keep)); + assert(comboClockTransaction.translation.pll==0); + XE::comboEndClock(); + assert(!refs && !lock.depth); + } + puts("PASS production common Combo clock scope: core power, nested rejection, thread/access isolation, failed-enable reservation and balanced teardown"); +} +'''.replace('STATE',state).replace('FUNCTIONS',functions) +with tempfile.TemporaryDirectory(prefix='adls-clock-runtime-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) diff --git a/tests/adls_ddi_power_native.py b/tests/adls_ddi_power_native.py new file mode 100644 index 00000000..06dc7b26 --- /dev/null +++ b/tests/adls_ddi_power_native.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Replay exact donor DDI accounting and ACK polling through the production wrapper.""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile +r=Path(__file__).resolve().parents[1] +b=Path(sys.argv[1]).read_bytes() +assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('uint8_t *XE::adlsPowerTarget');z=s.index('// Software-only transmitter boundary.',a) +fixture=(r/'tests/adls_ddi_power_native_fixture.cpp').read_text().replace('// PRODUCTION_WRAPPERS',s[a:z]) +with tempfile.TemporaryDirectory(prefix='adls-ddi-native-') as d: + d=Path(d);(d/'fixture.cpp').write_text(fixture) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I'+str(r/'WhateverXe'),str(d/'fixture.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test'),str(Path(sys.argv[1]).resolve())],check=True) diff --git a/tests/adls_ddi_power_native_fixture.cpp b/tests/adls_ddi_power_native_fixture.cpp new file mode 100644 index 00000000..6aa04221 --- /dev/null +++ b/tests/adls_ddi_power_native_fixture.cpp @@ -0,0 +1,140 @@ +#include "kern_alderlake_s.hpp" +#include "kern_xelp_donor.hpp" +#include +#include +#include +#include +#include +#include +#define FunctionCast(fn,p) reinterpret_cast(p) +struct XE { + bool adlsDisplaySelected=true,adlsPowerReady=true;void *tglObservationController; + AlderLakeS::Wiring adlsWiring;uintptr_t orgADLSPowerPG,orgTGLPowerDDIState; + static uint8_t *adlsPowerTarget(void*); + static void wrapADLSPowerPG(void*,bool,uint32_t); + static void wrapTGLPowerDDIState(void*,bool,uint32_t); +}; +XE state;XE *callbackXE=&state; +templatevoid tglTransitionRecord(T...){} +templatevoid tglObserveActivation(T...){} +templateT &at(void*p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} +// PRODUCTION_WRAPPERS +alignas(8) uint8_t controller[0xF00]{},power[0x90]{},access[0x70]{},ports[9][0x600]{}; +uintptr_t vtable[64]{};uint8_t *image; +struct Event {unsigned kind,value;};std::vector events; +unsigned requested,ackMissing,delays,errors,reads,physicalMask=0x79; +void pg(void *p,bool,unsigned mask){assert(p==power);events.push_back({0,mask});} +void *lookup(unsigned d){assert(d<9);return ports[d];} +void writeReg(void *p,unsigned long reg,unsigned value){ + assert(p==access && (reg==0x45450 || reg==0x45454)); + events.push_back({unsigned(reg),value});if(reg==0x45454)requested=value; +} +uint32_t readReg(void *p,unsigned long reg){ + assert(p==access && reg==0x45454);++reads; + // Model only physical ADL-S IO wells. An unsupported request receives no + // ACK; explicit fault injection removes a real port's ACK as well. + unsigned ack=0; + for(unsigned d=0;d<9;++d)if((physicalMask&(1U<>(2*d+1)&1)<<(2*d); + return requested|ack; +} +void delay(unsigned n){assert(n==1);++delays;} +void error(const char*,...){++errors;} +void jump(unsigned off,uintptr_t target){image[off]=0x48;image[off+1]=0xb8;memcpy(image+off+2,&target,8);image[off+10]=0xff;image[off+11]=0xe0;} +void redirect(unsigned site,unsigned stub,uintptr_t target,uint8_t opcode){assert(image[site]==opcode);jump(stub,target);int32_t rel=stub-site-5;memcpy(image+site+1,&rel,4);} +void setup(unsigned assignment){ + memset(power,0,sizeof(power));memset(controller,0,sizeof(controller)); + at(power,0x10)=controller;at(controller,0xC8)=power; + at(power,0x78)=access;at(controller,0xC40)=access; + XELP::VBT::Ports fw;fw.status=XELP::VBT::Status::Valid; + const unsigned physical[]={0,3,4},hdmiDVO[]={0,1,2},dpDVO[]={10,7,8}; + unsigned hdmi=0,dp=0; + for(unsigned n=0;n<3;++n){unsigned d=physical[n],kind=assignment%3;assignment/=3; + if(kind){ + AlderLakeS::Port route;assert(AlderLakeS::Port::resolve(d,route)); + fw.present|=1U<(ports[d],0x550)=0; + } + state.adlsWiring=AlderLakeS::Wiring::fromFirmware(fw); + assert(state.adlsWiring.valid && state.adlsWiring.hdmi==hdmi && state.adlsWiring.dp==dp && !state.adlsWiring.excluded); + events.clear();requested=ackMissing=delays=errors=reads=0; +} +unsigned expectedRequest(){unsigned out=0;for(unsigned d:{0u,3u,4u})if(at(power,0x30+4*d))out|=2U<<(2*d);return out;} +int main(int argc,char **argv){ + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{}); + image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0));assert(image!=MAP_FAILED);memcpy(image,b.data(),b.size()); + jump(0x1b1ea,(uintptr_t)lookup);jump(0x5e344,(uintptr_t)readReg); + redirect(0x5ae05,0x300000,(uintptr_t)XE::wrapTGLPowerDDIState,0xe9); + redirect(0x5c2a9,0x300000,(uintptr_t)XE::wrapTGLPowerDDIState,0xe9); + redirect(0x5c460,0x300020,(uintptr_t)delay,0xe8); + redirect(0x5c484,0x300040,(uintptr_t)error,0xe8); + redirect(0x5c4a3,0x300040,(uintptr_t)error,0xe9); + vtable[0x130/8]=(uintptr_t)writeReg;at(access,0)=vtable; + state.tglObservationController=controller;state.orgADLSPowerPG=(uintptr_t)pg; + state.orgTGLPowerDDIState=(uintptr_t)(image+0x5c2ae); + auto get=(void(*)(void*,unsigned))(image+0x5bfa8); + auto put=(void(*)(void*,unsigned))(image+0x5aa86); + // Reproduce the observed defect with the correction gated off: acquiring + // TC1/TC2 works, releasing TC2 requests absent C while TC1 remains held. + setup(12);state.adlsDisplaySelected=false; + get(power,3);get(power,4);assert(requested==0x280 && !errors); + put(power,4);assert(requested==0xA0 && delays==20 && errors==2); + assert(XELPDonor::ddiReleaseSite(image,b.size())); + assert(!XELPDonor::ddiReleaseSite(image,XELPDonor::DDIReleaseOffset+1)); + for(unsigned i=0;i(ports[3],0x550)=mode; + if(realC)get(power,2); + get(power,3);get(power,4);put(power,4); + assert(requested==((mode==2?0U:0x80U)|(realC?0x20U:0U)) && !delays && !errors); + put(power,3);assert(requested==(realC?0x20U:0U)); + if(realC)put(power,2); + assert(!requested && !delays && !errors); + } + physicalMask=0x79;state.adlsDisplaySelected=true; + unsigned assignments=0,sequences=0,operations=0; + for(unsigned assignment=1;assignment<27;++assignment){ + setup(assignment);std::vector acquire; + for(unsigned d:{0u,3u,4u})if(state.adlsWiring.selected()&(1U<(power,0x30+4*d);events.clear(); + if(on){get(power,d);++expectedRefs[d];}else{put(power,d);--expectedRefs[d];} + for(unsigned i=0;i<9;++i)assert(at(power,0x30+4*i)==expectedRefs[i]); + assert(!errors && !delays && requested==expectedRequest()); + const bool transition=on?before==0:before==1; + if(!transition)assert(events.empty()); + else { + assert(events.size()==3);bool lane=requested&0x280; + assert(events[lane?0:2].kind==0 && events[lane?0:2].value==(lane?0x2Au:0u)); + assert(events[lane?1:0].kind==0x45450 && events[lane?2:1].kind==0x45454); + assert(events[lane?1:0].value==requested && events[lane?2:1].value==requested); + } + ++operations; + }; + for(auto d:acquire)for(unsigned h=0;h(power,0x38)=refsC;at(power,0x3C)=refsTC1; + uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power)); + calls.clear();XE::wrapTGLPowerDDIState(power,on,mask); + unsigned expected=mask; + unsigned seen=0;for(auto c:calls)if(c.kind==1){assert(c.target==expected && c.enable==on);++seen;} + assert(seen==1 && !memcmp(before,power,sizeof(power)));++count; + } for(unsigned bad=0;bad<6;++bad){ state.adlsDisplaySelected=bad!=0;state.adlsPowerReady=bad!=1;state.adlsWiring.valid=bad!=2; state.adlsWiring.hdmi=bad==3?0:8;state.adlsWiring.dp=0;at(ctl,0xC8)=bad==4?nullptr:power;at(power,0x78)=bad==5?nullptr:access; assert(!XE::adlsPowerTarget(power));calls.clear();XE::wrapADLSPowerPG(power,false,2);assert(calls.size()==1&&calls[0].target==2); - calls.clear();XE::wrapTGLPowerDDIState(power,false,0x80);assert(calls.size()==1&&calls[0].kind==1); + at(power,0x38)=0;at(power,0x3C)=1; + calls.clear();XE::wrapTGLPowerDDIState(power,false,0xA0);assert(calls.size()==1&&calls[0].kind==1&&calls[0].target==0xA0); } printf("PASS %u lane/pipe conditions; acquire/release order; unchanged refs; 6 owner/profile rejections\n",count); } diff --git a/tests/alderlake_n.cpp b/tests/alderlake_n.cpp index 763a7fa2..1942776f 100644 --- a/tests/alderlake_n.cpp +++ b/tests/alderlake_n.cpp @@ -176,6 +176,20 @@ int main() { assert(t8Ready.resources.slices==1 && t8Ready.resources.dss==1 && t8Ready.resources.nativeSubslices==2); assert(t8Ready.resources.eus==16 && t8Ready.resources.l3Banks==4 && t8Ready.resources.urbKiB==256); assert(t8Ready.fuses[4]==0xe00fe && !(t8.regs[0x130044]&1) && !(t8.regs[0xD84]&1)); + // ADL-P shares forcewake/readout, not the small-GT/media capacity rule. + for(unsigned failure=0;failure<5;++failure) { + auto p=firmwareGT();p.regs[0x913C]=0x3f;p.regs[0x9134]=0;p.regs[0x9118]=0;p.regs[0x9140]=0xe00fa; + if(failure==1)p.failGT=true; + if(failure==2)p.failReleaseGT=true; + if(failure==3)p.regs[0x913C]=UINT32_MAX; + if(failure==4)p.regs[0x9140]=0xe00ff; + auto a=AlderLakeN::readGT(p,true,nullptr,XELP::decodeADLPGT); + using S=AlderLakeN::GTStatus; + const S expected[]={S::Complete,S::AckTimeout,S::ReleaseFailed,S::Unavailable,S::ProfileMismatch}; + assert(a.status==expected[failure]); + if(!failure)assert(a.resources.eus==96 && a.resources.nativeSubslices==12 && + a.resources.l3Banks==8 && a.resources.urbKiB==512 && a.releasedMask==3); + } auto excludedGT=firmwareGT();assert(AlderLakeN::readGT(excludedGT,false).status==AlderLakeN::GTStatus::NotAdmitted && excludedGT.reads.empty()); auto busyGT=firmwareGT();busyGT.regs[0xA188]=1; assert(AlderLakeN::readGT(busyGT,true).status==AlderLakeN::GTStatus::ExistingOwner && busyGT.writes.empty()); diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index a07e0be7..b5fe488d 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -59,13 +59,15 @@ bool checkKernelArgument(const char *name) { return args.count(name); } #define SYSLOG(...) ((void)0) #define IOLog(...) ((void)0) +static bool display13Offline; static int kextIntelTGLFb, kextAppleIntelTGLCompat, kextIntelTGL; static struct { void onKextLoadForce(int *) {} } lilu; struct XE { XELP::Profile xeProfile; bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; - bool display13FramebufferRequested=false, display13TraceEnabled=false; + bool display13FramebufferRequested=false, display13TraceEnabled=false, adlpDisplaySelected=false, adlpRenderRequested=false; bool display12SmallGTReady=false, adlsDisplayHooksReady=false; + bool adlsBootConsumersFailed=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; bool tglProgramEnabled=false, tglTrainingLeaseRequested=false; @@ -105,6 +107,10 @@ assert(adlnNativeRCSProfileRequested && xe.tglAcceleratorRequested); assert(xe.currentGraphics==&kextIntelTGL && xe.currentFramebuffer==&kextIntelTGLFb); } + xe=boot({"-wex-display13-offline","-wex-noaccel"},0x46A68086U,0x9A498086U); + assert(display13Offline && xe.adlpDisplaySelected && !xe.tglAcceleratorRequested); + xe=boot({"-wex-display13-offline"},0x9A498086U); + assert(!display13Offline); xe=boot({"-wex-display13-trace"}); assert(xe.display13TraceEnabled && xe.adlnNativeRequested && adlnNativeRCSProfileRequested); xe=boot({"-wex-adln-kernel-ctx"}); @@ -148,27 +154,34 @@ const bool desktop=id==0x4690 || id==0x4680 || id==0xA780 || id==0x4688 || id==0x468B || id==0xA788 || id==0xA78B; const bool gt1=id==0x9A60 || id==0x9A68 || id==0x9A70; const bool mobile=id==0x9A49 || id==0x9A78 || gt1; + const bool mobile13=id==0x46A6 || id==0xA7A0; const bool small13=id==0x46D2; + const bool render13=small13 || (mobile13 && !(option&2)); assert(selected.display12SmallGTDetected==(desktop || gt1) && selected.adlsDisplaySelected==desktop); - assert(selected.adlnDetected==small13 && selected.display13FramebufferRequested==small13); + assert(selected.adlnDetected==render13 && selected.display13FramebufferRequested==(small13 || mobile13)); const bool d12=(desktop || mobile) && !(option&1); - assert(selected.tglRequested==(small13 || d12)); - assert(selected.tglAcceleratorRequested==(small13 || (d12 && !(option&2)))); - assert((selected.currentFramebuffer==&kextIntelTGLFb)==(small13 || d12)); + assert(selected.tglRequested==(render13 || d12)); + assert(selected.tglAcceleratorRequested==((render13 || d12) && !(option&2))); + assert((selected.currentFramebuffer==&kextIntelTGLFb)==(small13 || mobile13 || d12)); } version="Darwin Kernel Version 21.6.0: Mon Jun 24 00:56:10 PDT 2024; root:xnu-8020.240.18.709.2~1/RELEASE_X86_64"; xe=boot({},0x9A498086);assert(xe.tglRequested && xe.tglAcceleratorRequested); xe=boot({"-wex-noaccel"},0x9A498086); assert(xe.tglRequested && !xe.tglAcceleratorRequested && xe.currentFramebuffer==&kextIntelTGLFb); - // The existing noaccel diagnostic is a Display12 option. - xe=boot({"-wex-noaccel"},0x46D28086);assert(xe.adlnNativeRequested); + // Display13 keeps FB and the graphics-kext rejection route selected, + // while noaccel removes permission to execute the accelerator. + xe=boot({"-wex-noaccel"},0x46D28086); + assert(xe.adlnNativeRequested && xe.display13FramebufferRequested && xe.tglRequested && !xe.tglAcceleratorRequested); version=qualified; xe=boot({},0x12348086,0x46D28086); assert(xe.adlnRequested && xe.adlnNativeRequested && xe.xeProfile.identity==0x12348086); xe=boot({},0x12348086,0x9A498086);assert(xe.tglRequested); - xe=boot({},0x46A68086);assert(!xe.tglRequested && !xe.adlnRequested); - for(uint32_t pci:{0x46A68086U,0x46808086U,0xA7A08086U}) { + xe=XE{};xe.selectProfile(XELP::identify(0x46A68086,12,0x9A498086)); + assert(xe.adlpDisplaySelected && xe.adlpRenderRequested && xe.display13FramebufferRequested && xe.tglAcceleratorRequested && xe.adlnRequested && xe.tglRequested); + assert(xe.xeProfile.family==XELP::Family::AlderLakeP && xe.xeProfile.gt==XELP::GT::ADLPMobile); + xe=boot({},0x46A68086);assert(xe.tglRequested && xe.adlnRequested); + for(uint32_t pci:{0x4C8A8086U,0x46808086U}) { xe=boot({},pci);assert(!xe.adlnNativeRequested && !xe.display13FramebufferRequested && !adlnNativeRCSProfileRequested); } xe=XE{};xe.selectProfile(XELP::identify(0x46D28086,1)); diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 56fb2e1d..f31fd319 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -21,11 +21,11 @@ int main() { // TGL/ADL-N explicit override semantics remain unchanged. auto f=select(0x12348086,revision,true,&value,4); assert(f.overridden() && f.identity==0x12348086 && f.effectiveIdentity==pci); - assert(f.admitted()==(p.admitted() && !display12SmallGT(entry.backend))); + assert(f.admitted()==(p.admitted() && !display12SmallGT(entry.backend) && entry.backend!=GT::ADLPMobile)); assert(!strcmp(model(f),entry.model)); auto nonIntel=select(0x123410DE,0,true,&value,4);assert(!nonIntel.admitted()); } - assert(implemented==27); // Recognition of other table rows adds no native backend. + assert(implemented==41); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); assert(select(0x12348086,1,true,&id,4).admitted()); @@ -36,10 +36,13 @@ int main() { assert(!select(0x9A498086,0,true,nullptr,4).admitted()); for(uint32_t bad:{0U,0xFFFFU,0x808646D2U,0x1234U}) assert(!select(0x9A498086,0,true,&bad,4).admitted()); - auto p=select(0x46A68086,0,true,&id,4);assert(p.admitted() && p.family==Family::AlderLakeN); - assert(!strcmp(model(p),"Intel Iris Xe Graphics")); // Name is physical, backend is overridden. + auto p=select(0x46A68086,0,true,&id,4);assert(!p.admitted() && !p.displayAdmitted() && p.family==Family::AlderLakeP); + assert(!strcmp(model(p),"Intel Iris Xe Graphics")); // Physical ADL-P never becomes an ADL-N render backend. auto q=p;q.effectiveIdentity=0x46D18086;assert(!same(p,q)); - ProbeConfig c;c.profile=p;c.combo=c.comboBackend=3;assert(c.valid()); + ProbeConfig c;c.profile=p;c.combo=c.comboBackend=3;assert(!c.valid()); + id=0x9A49;p=select(0x46A68086,12,true,&id,4); + assert(p.displayAdmitted() && p.admitted() && p.mobileDisplay13()); + c={};c.profile=p;c.comboBackend=3;c.legacy=8;c.legacyBackend=0x78;assert(c.valid()); c.abi=2;assert(!c.valid());c.abi=3;c.profile.gt=GT::TGLGT2;assert(!c.valid()); puts("PASS device table, physical/effective identity, strict override bytes/vendor/revision, model choice and ABI3"); } diff --git a/tests/display13_agdc.py b/tests/display13_agdc.py new file mode 100644 index 00000000..f6d2c71a --- /dev/null +++ b/tests/display13_agdc.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Production client admission and optional exact native AGDC dispatch replay.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['processFramebufferAGDC','wrapFramebufferUserClient','wrapFramebufferAGDCCallback'] +functions='\n'.join(re.search(r'(?:bool|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) +arrays={n:re.search(n+r'\[\]=\{([^}]+)',functions).group(1) for n in ['clientEntry','callbackEntry','dispatch']} +assert s.count('processFramebufferAGDC(patcher,index,address,size)')==1 +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(b).hexdigest() in ['c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731','8f83afbe21f216ed18ea798c10d91b7b4fd990b62a5740832c1f90137722c32f'] + for key,offset in [('clientEntry',0x273b4),('callbackEntry',0x69df2),('dispatch',0x69e1e)]: + expected=bytes(int(x,0) for x in arrays[key].split(','));assert b[offset:offset+len(expected)]==expected +code=r''' +#include "kern_framebuffer_donor.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +#define FunctionCast(fn,addr) reinterpret_cast(addr) +#define arrsize(a) (sizeof(a)/sizeof(a[0])) +struct Meta {unsigned size=0x4b90;unsigned getClassSize(){return size;}}; +struct IOService {alignas(16) uint8_t bytes[0x4b90]{};Meta meta;Meta *getMetaClass(){return &meta;}}; +namespace Display13FBMemory {template T field(void *p,unsigned n){T v;memcpy(&v,(uint8_t*)p+n,sizeof(v));return v;}} +template void put(void *p,unsigned n,T v){memcpy((uint8_t*)p+n,&v,sizeof(v));} +struct IORegistryEntry { + void setProperty(const char *,bool){} void setProperty(const char *,void *,size_t){} + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} +}; +struct KernelPatcher { + unsigned routes=0;bool fail=false;uintptr_t delta=0; + struct RouteRequest {template RouteRequest(const char *,F,uintptr_t &) {}}; + template T solveSymbol(unsigned,const char *s,uintptr_t a,size_t){return a+(strstr(s,"newUserClient")?0x273b4:0x69df2)+delta;} + void clearError(){} + bool routeMultipleShort(unsigned,RouteRequest*,unsigned count,uintptr_t,size_t){assert(count==2);++routes;return !fail;} +}; +struct XE { + static XE *callbackXE;bool display13FramebufferRequested=true,display13FBABIReady=true; + IOService *adlnTopologyController=nullptr,*tglObservationController=nullptr; + uint32_t framebufferProtocolCalls=0;void *framebufferStartedController=nullptr; + uintptr_t orgFramebufferUserClient=0,orgFramebufferAGDCCallback=0; + bool processFramebufferAGDC(KernelPatcher &,size_t,uintptr_t,size_t); + static uint32_t wrapFramebufferUserClient(IOService *,void *,void *,uint32_t,void **); + static uint32_t wrapFramebufferAGDCCallback(IOService *,uint32_t,unsigned long,uint32_t); + static void observeFramebufferUserClient(IOService*,uint32_t,void**,uint32_t){}; + static void tglTrace(unsigned,unsigned,bool,unsigned,unsigned,unsigned,unsigned long){}; +}; +XE *XE::callbackXE; +FUNCTIONS +IOService fb,controller,other;unsigned clientCalls=0,callbackCalls=0; +uintptr_t expectedArgument=0;uint32_t expectedEvent=0,expectedValue=0,result=0xe00002bc; +uint32_t receiver(void *context,uintptr_t arg,uint32_t value,uint32_t event){ + assert(!context&&arg==expectedArgument&&value==expectedValue&&event==expectedEvent);++callbackCalls;return result; +} +uint32_t originalClient(IOService *f,void *task,void *security,uint32_t type,void **client){ + assert(f==&fb&&task==(void*)0x1234&&security==(void*)0x5678);++clientCalls; + if(client)*client=&controller;return result+type; +} +uint32_t originalCallback(IOService *c,uint32_t e,unsigned long a,uint32_t v){ + const bool inc=e<=11&&(0x90fU&(1U<(c,0x1bf8); + return fn(Display13FBMemory::field(c,0x1c00),inc?uint32_t(a+1):a,v,e); +} +void reset(XE &s){fb={};controller={};controller.meta.size=0x1c20;clientCalls=0; + s.framebufferProtocolCalls=0;s.framebufferStartedController=&controller; + s.adlnTopologyController=s.tglObservationController=&controller;put(&fb,0x1d0,&controller);put(&controller,0x1bf8,(uintptr_t)receiver);} +uint32_t open(void **client,unsigned type=0){return XE::wrapFramebufferUserClient(&fb,(void*)0x1234,(void*)0x5678,type,client);} +void clients(XE &s){ + for(unsigned bad=0;bad<7;++bad){reset(s); + switch(bad){case 0:put(&controller,0x1bf8,uintptr_t(0));break;case 1:s.framebufferStartedController=nullptr;break; + case 2:s.adlnTopologyController=&other;break;case 3:fb.meta.size=0x100;break;case 4:controller.meta.size=0x100;break; + case 5:put(&fb,0x1d0,(IOService*)nullptr);break;case 6:FramebufferDonor::Call::close(s.framebufferProtocolCalls);break;} + auto before=fb;void *client=(void*)1;assert(open(&client)==kIOReturnNotReady&&!client&&!clientCalls); + assert(!memcmp(&before,&fb,sizeof(fb))); + } + reset(s);put(&controller,0x1bf8,uintptr_t(0));void *client=(void*)1; + assert(open(&client)==kIOReturnNotReady&&!client&&!clientCalls); + put(&controller,0x1bf8,(uintptr_t)receiver); + assert(open(&client)==result&&client==&controller&&clientCalls==1); + put(&controller,0x1bf8,uintptr_t(0));s.tglObservationController=nullptr; + for(unsigned type:{1U,2U,~0U})assert(open(&client,type)==uint32_t(result+type)); +} +void events(){ + for(bool registered:{false,true})for(unsigned event=0;event<256;++event){ + put(&controller,0x1bf8,registered?(uintptr_t)receiver:0);put(&controller,0x1c00,(void*)nullptr); + const uintptr_t arg=0x12345678fffffff0;const bool inc=event<=11&&(0x90fU&(1U<b(0x150000); + memcpy(b.data()+0x273b4,clientEntry,sizeof(clientEntry));memcpy(b.data()+0x69df2,callbackEntry,sizeof(callbackEntry));memcpy(b.data()+0x69e1e,dispatch,sizeof(dispatch)); + for(unsigned bad=2;bad<8;++bad){KernelPatcher p;auto copy=b;s.display13FramebufferRequested=s.display13FBABIReady=true;size_t size=b.size(); + switch(bad){case 0:s.display13FramebufferRequested=false;break;case 1:s.display13FBABIReady=false;break;case 2:size=0x69e5f;break; + case 3:p.delta=1;break;case 4:copy[0x273b4]^=1;break;case 5:copy[0x69df2]^=1;break;case 6:copy[0x69e21]^=1;break;case 7:p.fail=true;break;} + assert(!s.processFramebufferAGDC(p,1,(uintptr_t)copy.data(),size));assert(p.routes==(bad==7?1U:0U)); + } + s.display13FramebufferRequested=s.display13FBABIReady=true;KernelPatcher p; + assert(s.processFramebufferAGDC(p,1,(uintptr_t)b.data(),b.size())&&p.routes==1); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);b.assign(std::istreambuf_iterator(f),{}); + size_t length=((b.size()>0x150000?b.size():0x150000)+4095)&~size_t(4095); + auto mem=(uint8_t*)mmap(nullptr,length,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);assert(mem!=MAP_FAILED);memcpy(mem,b.data(),b.size()); + uintptr_t baseVtable[256]{};baseVtable[0x788/8]=(uintptr_t)originalClient;put(mem,0xc8088,(uintptr_t)baseVtable); + assert(!mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)); + s.orgFramebufferUserClient=(uintptr_t)(mem+0x273b4);s.orgFramebufferAGDCCallback=(uintptr_t)(mem+0x69df2); + clients(s);events(); + reset(s);put(&controller,0x1bf8,uintptr_t(0));assert(open(&client)==kIOReturnNotReady); + using Register=uint32_t(*)(void*,void*);void *request[]={nullptr,(void*)receiver}; + assert(!reinterpret_cast(mem+0x69dce)(&controller,request)); + assert(open(&client)==result&&client==&controller);munmap(mem,length); + puts("PASS exact donor client tail call, registration and 512 guarded/native event cases"); + } + puts("PASS production AGDC admission, retry without FB mutation, passthrough, start/stop publication and installer refusals"); +} +'''.replace('FUNCTIONS',functions) +for old,key in [('CLIENT_ENTRY','clientEntry'),('CALLBACK_ENTRY','callbackEntry'),('DISPATCH','dispatch')]:code=code.replace(old,arrays[key]) +with tempfile.TemporaryDirectory(prefix='display13-agdc-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-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_boot.cpp b/tests/display13_boot.cpp index e46a8135..b1452155 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -1,4 +1,6 @@ #include "kern_display13_boot.hpp" +#include "kern_display13_tc_mode.hpp" +#include "kern_display13_tc_dp.hpp" #include #include #include @@ -12,7 +14,8 @@ struct IO { assert(reg==0x800000+8*pteReads);++pteReads; return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); } - bool noFrames {},noStop {},noIdle {},changePeer {}; + bool noFrames {},noStop {},noIdle {},changePeer {},stuckPLL {}; + uint32_t timeUs {}; uint32_t changeRegister {},failWrite {}; IO() { regs[0x45404]=0xFC0F;regs[0x4438C]=0x1C000700; @@ -32,11 +35,15 @@ struct IO { void write(uint32_t reg,uint32_t value) { writes.push_back({reg,value});if(reg==failWrite)return;regs[reg]=value; if((reg==0x71008 || reg==0x72008) && !(value&0x80000000) && !noStop)regs[reg]&=~0x40000000; - if((reg==0x64000 || reg==0x64100 || reg==0x64200) && !(value&0x80000000) && !noIdle)regs[reg]|=0x80; + if((reg>=0x64000 && reg<=0x64600 && !(reg&255)) && !(value&0x80000000) && !noIdle)regs[reg]|=0x80; + for(unsigned t=0;t<4;++t)if(reg==0x46038+8*t && !stuckPLL) { + regs[reg]=(value&~0x44000000U)|((value&0x80000000U)?0x40000000U:0)|((value&0x08000000U)?0x04000000U:0); + } if(writes.size()==1)regs[0x4500C]=0; if(changePeer && writes.size()==1)regs[0x70180]^=8; if(changeRegister && writes.size()==1)regs[changeRegister]^=1; } + void delayUs(unsigned us) {timeUs+=us;} void delay() {if(!noFrames) {++regs[0x70040];if(regs[0x71008]&0xC0000000)++regs[0x71040];if(regs[0x72008]&0xC0000000)++regs[0x72040];}} }; const uint32_t prop[] {1,7,1,2,3}; @@ -96,8 +103,227 @@ static void prefixCertificates() { } printf("PASS %u Display boot certificates: physical layouts, PTE corruption, no-read rejection\n",cases); } +static Display13OutputWiring multiGOP(IO &io,const unsigned *ports,unsigned count,uint32_t dpMask,bool owned=true) { + Display13OutputWiring w;w.valid=true;w.dp=dpMask; + io.regs[0x42000]=0x8e380000;io.regs[Display13TC::Clock::Gate]=0x01e0ffff; + for(unsigned p=0;p<3;++p) { + const auto b=p*0x1000; + io.regs[0x70008+b]=io.regs[0x70180+b]=io.regs[0x60400+b]=0; + io.regs[0x46140+4*p]=0; + if(p>=count)continue; + const auto d=ports[p];if(!(dpMask&(1U<=3?6:1);} + io.regs[0x70008+b]=0xc0000000;io.regs[0x70180+b]=0x84000008; + // Linear 720p firmware geometry, independent of protocol and DDI. + io.regs[0x70188+b]=80;io.regs[0x70190+b]=0x02cf04ff; + io.regs[0x60000+b]=0x067104ff;io.regs[0x6000c+b]=0x02ed02cf; + io.regs[0x7027c+b]=0x03ff0000; + io.regs[0x60400+b]=0x80030100|((d+1)<<27); + io.regs[0x64000+0x100*d]=0x80000000; + if(dpMask&(1U<=3?3:1))<<28; + if(d>=3) { + Display13TC::Port port;assert(Display13TC::Port::resolve(d,port)); + io.regs[0x45454]|=3U<<(2*d);io.regs[0x45444]|=3U<<(2*d); + io.regs[port.ready]=0xc;io.regs[port.pll]=0xcc000000;io.regs[port.clock]=0x80000000; + io.regs[Display13TC::Clock::Gate]&=~Display13TC::Clock::gateBit(port.tc); + if(owned || !(dpMask&(1U<=3) { + const auto retained=Display13TC::Boot::capture(io,true,c.ports[0],0,(c.dp&(1U<=3) { + const auto d=c.ports[p];Display13TC::Port port;assert(Display13TC::Port::resolve(d,port)); + assert((io.regs[port.buffer]&0x800000c0U)==0x80 && !(io.regs[port.pll]&0xcc000000U)); + assert(!(io.regs[port.clock]&0xf0000000U) && !io.regs[0x46140+4*p]); + assert(io.regs[Display13TC::Clock::Gate]&Display13TC::Clock::gateBit(port.tc)); + Runtime runtime(io,d); + assert(runtime.mode.connect(runtime,1,2,d).status==Display13TC::Status::Complete); + // No firmware-adoption exemption is supplied to the new Mode. + const bool started=(c.dp&(1U<0x68280); + auto after=AlderLakeN::readDisplay(io,true); + auto tc=Display13TC::Boot::capture(io,true,5,0,true); + AlderLakeN::LinearPlan plan; + assert(AlderLakeN::linearPlan(after,plan,5,AlderLakeN::GOPProtocol::DP,&tc)==AlderLakeN::LinearStatus::Ready); + assert(plan.geometry.width==1280 && plan.geometry.height==720); + assert(Display13Boot::reservedPrefix(io,after,true,5,memory,&tc,AlderLakeN::GOPProtocol::DP)); + io.regs[0x68174+scaler*0x100]^=1; + assert(!Display13Boot::reservedPrefix(io,after,true,5,memory,&tc,AlderLakeN::GOPProtocol::DP)); + } + // A timing mismatch alone is never permission to accept scaling. + for(unsigned fault=0;fault<11;++fault) { + IO io;auto w=multiGOP(io,ports,3,1U<<5);setup(io,0,0); + if(fault==0)io.regs[0x68180]=0; + if(fault==1)io.regs[0x68180]|=1U<<25; + if(fault==2)io.regs[0x68180]|=1U<<29; + if(fault==3)io.regs[0x68180]|=1U<<19; + if(fault==4)io.regs[0x68170]=1; + if(fault==5)io.regs[0x68174]^=1; + if(fault==6)io.regs[0x6001C]^=1; + if(fault==7) {io.regs[0x68280]=0x80000000;io.regs[0x68274]=io.regs[0x68174];} + if(fault==8)io.regs[0x60000]=0x400004FF; + if(fault==9)io.regs[0x60000]=0x0100077F; + if(fault==10)io.regs[0x6000C]=0x046401FF; + auto n=Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory); + if(n.status!=Display13Boot::Status::Topology || !io.writes.empty())fprintf(stderr,"fault=%u status=%u writes=%zu\n",fault,unsigned(n.status),io.writes.size()); + assert(n.status==Display13Boot::Status::Topology && io.writes.empty()); + } + IO drift;auto w=multiGOP(drift,ports,3,1U<<5);setup(drift,0,0); + drift.changeRegister=0x68174; + auto n=Display13Boot::normalize(drift,AlderLakeN::readDisplay(drift,true),true,w,memory); + assert(n.status==Display13Boot::Status::PeerChanged && drift.writes.size()==2); + puts("PASS scaled multi-GOP: binding, full destination, primary preservation, secondary detach and drift rejection"); +} +static void unusedTCBoot() { + const unsigned ports[] {5,4}; + const auto setup=[&](IO &io) { + auto w=multiGOP(io,ports,2,1U<<5);w.hdmi|=1U<<3;w.ddc[3]=9; + assert(Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,w,memory).status==Display13Boot::Status::Complete); + io.writes.clear();io.regs[0x64300]=0xC0;io.regs[0x161500]=0xC; + io.regs[0x4610C]=io.regs[0x46038]=0;io.regs[Display13TC::Clock::Gate]|=1U<<12; + io.regs[0x45444]|=3U<<6; + return w; + }; + IO io;auto w=setup(io);const auto before=io.regs; + auto r=Display13Boot::releaseUnusedTC(io,AlderLakeN::readDisplay(io,true),true,w); + assert(r.status==Display13Boot::Status::Complete && r.cleared==8 && r.writes==1 && io.regs[0x64300]==0x80); + for(auto x:before)if(x.first!=0x64300)assert(io.regs[x.first]==x.second); + assert(io.writes.size()==1 && io.writes[0].offset==0x64300 && io.writes[0].value==0x80); + auto again=Display13Boot::releaseUnusedTC(io,AlderLakeN::readDisplay(io,true),true,w); + assert(again.status==Display13Boot::Status::Complete && again.writes==0); + // Any live/ambiguous resource prevents release of this unclassified port. + for(unsigned fault=0;fault<9;++fault) { + IO t;auto wiring=setup(t); + if(fault==0)t.regs[0x64300]|=0x80000000; + if(fault==1)t.regs[0x64300]&=~0x80U; + if(fault==2)t.regs[0x161500]|=1; + if(fault==3)t.regs[0x4610C]=0x80000000; + if(fault==4)t.regs[0x46038]=0xCC000000; + if(fault==5)t.regs[Display13TC::Clock::Gate]&=~(1U<<12); + if(fault==6)t.regs[0x64310]=0x80000000; + if(fault==7)t.regs[0x64300]=UINT32_MAX; + if(fault==8)t.regs[0x161500]=UINT32_MAX; + auto r=Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring); + assert(r.status!=Display13Boot::Status::Complete && t.writes.empty()); + } + for(unsigned kind=0;kind<2;++kind) { + IO t;auto wiring=setup(t); + t.regs[kind?0x46148:0x62400]=kind?0x60000000:0x20000000; + auto r=Display13Boot::releaseUnusedTC(t,AlderLakeN::readDisplay(t,true),true,wiring); + assert(r.status==Display13Boot::Status::Complete && r.writes==0 && t.regs[0x64300]==0xC0); + } + IO changed;auto wiring=setup(changed);auto boot=AlderLakeN::readDisplay(changed,true);changed.regs[0x68174]=1; + r=Display13Boot::releaseUnusedTC(changed,boot,true,wiring); + assert(r.status==Display13Boot::Status::PeerChanged && changed.writes.empty()); + IO failed;wiring=setup(failed);failed.failWrite=0x64300; + r=Display13Boot::releaseUnusedTC(failed,AlderLakeN::readDisplay(failed,true),true,wiring); + assert(r.status==Display13Boot::Status::Readback && failed.writes.size()==1); + puts("PASS unused fixed TC firmware ownership: cold-only release, peers, live-resource refusal and readback"); +} int main() { + unusedTCBoot(); + scaledBoot(); prefixCertificates(); + tcComposition(); IO io;const auto before=io.regs; const auto r=Display13Boot::normalize(io,AlderLakeN::readDisplay(io,true),true,wiring,memory); assert(r.status==Display13Boot::Status::Complete && r.after==1 && r.stage==7 && r.writes==10); @@ -170,7 +396,7 @@ int main() { if(dpMask&(1U< #include #include @@ -30,12 +31,25 @@ def array(name): alignas(8) uint8_t ctl[0xf00]{},port[0x700]{},access[0x80]{}; uint32_t config[6]{};std::map mmio; uint8_t *image;unsigned delays=0,errors=0,get=0,put=0,fault=0; -bool correction=true; +bool correction=true,managed=false; +XELPComboClock::ClockTranslation translation; +XELPComboClock::ClockAllocation allocation; void jump(uint8_t*p,uintptr_t to){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&to,8);p[10]=0xff;p[11]=0xe0;} void redirect(unsigned at,uintptr_t to,unsigned stub){assert(image[at]==0xe8);jump(image+stub,to);int32_t d=stub-at-5;memcpy(image+at+1,&d,4);} -uint32_t read(void*p,unsigned long reg){assert(p==access&&mmio.count(reg));return mmio.at(reg);} +uint32_t read(void*p,unsigned long reg){ + if(managed)reg=translation.reg(reg,(uintptr_t)__builtin_return_address(0)-(uintptr_t)image); + assert(p==access&&mmio.count(reg));return mmio.at(reg); +} void write(void*p,unsigned long reg,uint32_t value){assert(p==access&&mmio.count(reg)); if(correction)value=adjust(p,reg,value,(uintptr_t)__builtin_return_address(0)); + if(managed) { + const auto pc=(uintptr_t)__builtin_return_address(0)-(uintptr_t)image; + reg=translation.reg(reg,pc); + if(reg==0x164280 && translation.enabling && pc==0x6d77e) { + value=translation.mux(value,pc); + translation.prepareMux([](uint32_t r){return mmio.at(r);},[](uint32_t r,uint32_t v){mmio[r]=v;},value); + } + } if(reg==0x46010 || reg==0x46014){value&=~0x44000000U; if((value&0x08000000)&&fault!=1)value|=0x04000000; if((value&0x80000000)&&fault!=2)value|=0x40000000;} @@ -88,7 +102,53 @@ def array(name): assert(mmio[cfg1]==(hdmi?0xe84U:rate==6?0x90U:rate==10?0x88U:rate==20?0x48U:0x44U)); disable(ctl,port,nullptr);assert(!errors&&!delays&&put==1&&!mmio[pll]&&(mmio[0x164280]&off));++cases; } - assert(cases==60 && counterexamples==10);munmap(image,0x400000); + assert(cases==60 && counterexamples==10); + // Same native bodies and shared allocator on TGL/Display13. Occupied peer + // PLLs have deliberately different coefficients, making clobber visible. + unsigned sharing=0; + for(auto layout:{XELPComboClock::Layout::TGL,XELPComboClock::Layout::Display13}) + for(unsigned target=0;target<2;++target)for(unsigned peer=0;peer<2u;++peer) + for(unsigned occupied=0;occupied<2;++occupied)for(bool hdmi:{false,true}) { + if(peer==target)continue; + allocation={};get=put=delays=errors=fault=0;correction=true; + const auto off=1U<<(peer==2?24:10+peer),shift=2*peer; + mmio={{0x164280,(0x01000C00&~(off|(3U<(port,0x114)=hdmi?0:1;field(port,0x128)=20; + field(ctl,0xea8)=2; + translation={target,choice.pll,true};managed=true; + uint32_t params[]={464,2049,2,3,2};enable(ctl,port,nullptr,hdmi?params:nullptr); + assert(mmio[busy]==0xcc000000 && mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890); + assert(((mmio[0x164280]>>(2*target))&3)==choice.pll); + const auto nominal=hdmi?(464|(2049<<10)):0x1c001a5; + const auto expected=(nominal&~0x1fffc00U)|(((((nominal>>10)&32767)+1)/2)<<10); + assert(mmio[0x164284+8*choice.pll]==expected); + translation.enabling=false;disable(ctl,port,nullptr); + assert(!mmio[0x46010+4*choice.pll] && mmio[busy]==0xcc000000); + assert(mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890 && !errors && !delays && get==1 && put==1); + if(peer<2) { + alignas(8) uint8_t other[0x700]{};uint32_t otherConfig[]={peer,peer+1,hdmi?2U:1U,0,0,0}; + field(other,0x548)=otherConfig; + snap.mux[0]=mmio.at(0x164280);snap.enable[choice.pll]=0; + const auto shared=allocation.share(XELPComboClock::Port(peer),snap); + assert(shared.valid && shared.pll==occupied); + translation={target,shared.pll,true};enable(ctl,port,other,hdmi?params:nullptr); + assert(mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890 && mmio[busy]==0xcc000000); + assert(((mmio[0x164280]>>(2*target))&3)==occupied); + translation.enabling=false;disable(ctl,port,port); + assert(mmio[cfg0]==0x12345 && mmio[cfg1]==0x67890 && mmio[busy]==0xcc000000); + assert(!errors && !delays && get==2 && put==2); + } + managed=false;++sharing; + } + printf("PASS %u TGL/Display13 native shared-PLL DP/HDMI cycles; live A/B peer coefficients retained\n",sharing); + munmap(image,0x400000); printf("PASS %u exact native Display13 Combo A/B DP/HDMI clock cycles, %u old 38.4 MHz counterexamples\n",cases,counterexamples); } puts("PASS production Display13 nominal-write owner/caller/reference/register guards"); diff --git a/tests/display13_ddc.py b/tests/display13_ddc.py new file mode 100644 index 00000000..00408803 --- /dev/null +++ b/tests/display13_ddc.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Check the production TC pin guard and optional exact-donor pin-bound patch.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +root=Path(__file__).resolve().parents[1] +s=(root/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('uint32_t XE::wrapDisplay13GMBus(') +guard=s[s.index(' if(bus>=8',a):s.index(' static uint32_t count',a)] +cpp=r''' +#include "kern_display13_output_wiring.hpp" +#include +#include +struct IOService{}; +constexpr unsigned kIOReturnBadArgument=0xe00002c2; +struct State {IOService *adlnTopologyController{};Display13OutputWiring adlnOutputWiring;}state,*callbackXE=&state; +unsigned request(IOService *controller,unsigned bus){GUARD return 0;} +int main(){ + IOService owner,other;state.adlnTopologyController=&owner; + for(unsigned ddi=3;ddi<=6;++ddi) { + auto &w=state.adlnOutputWiring;w={};w.valid=true;w.hdmi=1U<1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + def array(name): + match=re.search(r'const uint8_t '+name+r'\[\] \{([^}]+)\}',s) + return bytes(int(v.strip(),0) for v in match.group(1).split(',')) + before,after=array('pinLimit'),array('tcPinLimit') + assert donor[0x53b66:0x53b66+len(before)]==before==bytes.fromhex('837dcc08722a') + assert after==bytes.fromhex('837dcc0d722a') + assert '{0x53B66,pinLimit,tcPinLimit,sizeof(pinLimit)}' in s + print('PASS exact c004fc1a GMBUS pin-bound site: comparison 8 -> 13; branch and remaining bytes unchanged') diff --git a/tests/display13_fb_irq.cpp b/tests/display13_fb_irq.cpp index 01bd5953..c3a2e184 100644 --- a/tests/display13_fb_irq.cpp +++ b/tests/display13_fb_irq.cpp @@ -48,6 +48,53 @@ struct IO { } }; int main() { + // Actual TC1-only firmware enable masks: north TC3/TBT3 and PCH DDI B. + {IO io;State state;io.r[HPDBank+4]=0x40004;io.r[HPDBank-4]=0; + io.r[0xC400C]=0x07020000;io.r[0xC4004]=0x38853f07;io.r[0xC4008]=0x20000; + assert(enable(io,state,0x3e070000,NativeGT,1,8)==Status::Complete); + assert(!(io.r[0xC400C]&0x3e070000) && (io.r[0xC4004]&0x3e070000)==0x3e070000); + assert(io.r[0xC4008]==0x20000 && !(io.r[HPDBank+4]&0x40004));} + // DDI D and other PCH events must still refuse admission, not be hidden. + for(uint32_t bit:{1U<<19,1U<<23,1U<<30}) { + IO io;State state;io.r[0xC400C]=bit;io.r[0xC4004]=0; + assert(enable(io,state,0x3e070000,NativeGT,1,8)==Status::ForeignIRQ); + assert(state.failureRegister==0xC400C && state.failureBits==bit); + } + for(uint32_t excluded:{1U<<19,1U<<24,1U<<30}) { + IO io;State state;assert(enable(io,state,excluded,NativeGT,1,8)==Status::Topology); + } + {IO io;State state;assert(enable(io,state,1U<<17,NativeGT,1,2)==Status::Topology);} + // Firmware may leave north TC/TBT enables for excluded connectors. + // Cover all exclusion subsets and each selected TC1..4 independently. + for(unsigned selected=0;selected<4;++selected)for(unsigned mask=0;mask<64;++mask) { + if(mask&(1U<{Master});} + for(uint32_t bits:{1U<<15,1U<<22,1U<<30}) { + IO io;State state;io.r[HPDBank+4]=0x40004|bits;io.r[HPDBank-4]=0; + assert(enable(io,state,0x3e000000,NativeGT,1,8)==Status::ForeignIRQ && io.writes==std::vector{Master}); + assert(state.failureRegister==HPDBank+4 && state.failureBits==bits); + } + for(auto reg:{HPDBank-4,HPDBank+4}) { + IO io;State state;io.r[HPDBank+4]=0x40004;io.r[HPDBank-4]=0;io.rejectRegister=reg; + assert(enable(io,state,0x3e000000,NativeGT,1,8)==Status::Readback && !(io.r[Master]&Enable)); + } + // HDMI TC1/2 + cold DP TC3: GOP left north TC3/TBT3 enabled. + {IO io;State state;io.r[HPDBank+4]=0x40004;io.r[HPDBank-4]=0; + io.r[HPDBank]=0x40004; + assert(enable(io,state,0x38070000,NativeGT,1,0x38)==Status::Complete); + assert(!(io.r[HPDBank+4]&0x40004) && (io.r[HPDBank-4]&0x40004)==0x40004); + assert(io.r[HPDBank]==0x40004 && (io.r[0xC400C]&0x07000000)==0x07000000);} // ADL-P/N TC legacy HPD is PCH bit24+tc and a separate control bank. // Foreign masked statuses must survive both enable and collect. for(unsigned tc=0;tc<4;++tc)for(unsigned pulse=0;pulse<4;++pulse) { diff --git a/tests/display13_fb_memory.cpp b/tests/display13_fb_memory.cpp index 23d9342a..3643fdde 100644 --- a/tests/display13_fb_memory.cpp +++ b/tests/display13_fb_memory.cpp @@ -37,7 +37,7 @@ struct Fixture { if(page==2 && deadPower)registers[0x45404]=UINT32_MAX; return ptes[page]; } - Readout inspect(unsigned ddi=1) {return Display13FBMemory::inspect(*this,controller,sizeof(controller),framebuffer,sizeof(framebuffer),0x10000000,memory,true,0,ddi);} + Readout inspect(unsigned ddi=1,const Display13TC::Boot *tc=nullptr) {return Display13FBMemory::inspect(*this,controller,sizeof(controller),framebuffer,sizeof(framebuffer),0x10000000,memory,true,0,ddi,tc);} }; static void testRelocatedPools() { for(auto ggc : {0xFEC1U,0x02C1U,0x03C1U,0x04C1U}) { @@ -75,6 +75,19 @@ static void testSlotClaims() { } int main() { + for(unsigned ddi=3;ddi<=6;++ddi) { + Fixture f;const auto tc=ddi-3; + f.registers[0x45454]=3U<<(2*ddi);f.registers[0x161500+4*tc]=0xC; + f.registers[0x64000+0x100*ddi]=0x80000040;f.registers[0x46100+4*ddi]=0x80000000; + f.registers[0x46038+8*tc]=0xCC000000;f.registers[0x46140]=(ddi+3)<<28; + f.registers[0x60400]=0x80030100|((ddi+1)<<27); + const auto boot=Display13TC::Boot::capture(f,true,ddi,0); + assert(boot.valid()); + assert(f.inspect(ddi).status==Status::DisplayState && !f.reads); + assert(f.inspect(ddi,&boot).status==Status::Complete); + f.changeRegister=0x46038+8*tc; + assert(f.inspect(ddi,&boot).status==Status::StateChanged); + } uint64_t platform[0x1C0/8],savedPlatform[0x1C0/8];uint8_t poolController[0x1C20] {}; memset(platform,0x5A,sizeof(platform));put(platform,0x14,uint32_t(0x4000000)); memcpy(savedPlatform,platform,sizeof(platform));put(poolController,0xDE8,uint32_t(0x4000000)); diff --git a/tests/display13_fb_topology.cpp b/tests/display13_fb_topology.cpp index 9f4f0e62..f6854b7d 100644 --- a/tests/display13_fb_topology.cpp +++ b/tests/display13_fb_topology.cpp @@ -10,7 +10,7 @@ struct Platform { data()[0x12]=Ports;data()[0x13]=3; const uint32_t panel[] {0,0,0,0,2,0x18};memcpy(config(0),panel,24); for(unsigned d=1;d(1),w)); assert(!memcmp(out.data(),in.data(),0x20) && !memcmp(out.data()+0xF8,in.data()+0xF8,Bytes-0xF8)); auto malformed=w;malformed.ddc[0]=4;auto keep=out; assert(!apply(out.data(),Bytes,in.data(),malformed) && !memcmp(out.data(),keep.data(),Bytes)); - if(mask==3){auto broken=out;broken.config(0)[5]=0x18;assert(!excludedTypeC(broken.data(),Bytes,w));} + if(mask==3){auto broken=out;broken.config(0)[5]=0x18;assert(!excludedHotplug(broken.data(),Bytes,w));} } // All fixed Combo HDMI/DP assignments compose CPU topology/resources. // DP runtime/boot admission stays closed until the hardware hooks exist. @@ -103,12 +99,11 @@ int main() { for(unsigned d=0;d<9;++d) { const bool on=w.selected()&(1U<>3)<<24))|((7U&~mask)<<16))); + for(unsigned d=0;d<9;++d)assert(selected(out.data(),out.config(d),w)==bool(mask&(1U< +#include +#include +#include +struct OSObject { unsigned char bytes[0x580]{}; OSObject *getMetaClass(){return this;}unsigned getClassSize(){return sizeof(bytes);} }; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char *,bool){}void setProperty(const char *,const void *,unsigned){}}; +namespace Display13FBMemory {templateT field(void *p,unsigned off){T v;memcpy(&v,(char*)p+off,sizeof(v));return v;}} +struct Wiring {unsigned selected(){return 8;}}; +namespace Display13FBTopology {constexpr unsigned Ports=9;bool selected(void *p,uint32_t *c,Wiring){return c==(uint32_t*)p;}bool bound(void *,uint32_t *,unsigned){return false;}} +static bool display13Offline;static unsigned calls,binds; +uint32_t native(OSObject *){++calls;return 1;} +#define FunctionCast(type,fn) fn +struct XE {bool display13FBTopologyHooksReady=true;void *adlnTopologyController=(void*)1;void *adlnPlatform;Wiring adlnOutputWiring;uint32_t (*orgAlderLakeNPortState)(OSObject*)=native;bool xelpBindProbe(void*){++binds;return true;}static uint32_t wrapAlderLakeNPortState(OSObject*);}; +static XE *callbackXE; +BODY +int main(){XE x;callbackXE=&x;uint32_t config[3]={3,9,1};x.adlnPlatform=config;OSObject port;auto cp=config;memcpy(port.bytes+0x548,&cp,sizeof(cp));uint32_t connected=4;memcpy(port.bytes+0x554,&connected,4); + display13Offline=true;assert(XE::wrapAlderLakeNPortState(&port)==0);assert(!calls && !binds);assert(Display13FBMemory::field(&port,0x554)==0); + display13Offline=false;assert(XE::wrapAlderLakeNPortState(&port)==1);assert(calls==1 && binds==1); + display13Offline=true;x.display13FBTopologyHooksReady=false;assert(XE::wrapAlderLakeNPortState(&port)==0);assert(calls==1 && binds==1); + puts("PASS production offline boundary: disconnected state, no provider/native probe, normal path preserved");} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='display13-offline-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_port_wait.py b/tests/display13_port_wait.py new file mode 100644 index 00000000..f2752521 --- /dev/null +++ b/tests/display13_port_wait.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +"""Real common installer/selector and exact native polling; MMIO/time are simulated.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(r'(?:bool|uint32_t|XELP::LinkRequirements) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in ['framebufferLinkRequirements','framebufferPortWaitPolls','processFramebufferPortWait']) +assert s.count('processFramebufferPortWait(patcher,index,address,size)')==1 +runtime=re.search(r'bool XE::processFramebufferDonorRuntime\(.*?\n\}',s,re.S).group() +assert 'tglPanelPowerPropertyReady && meiReady && ddiRequestReady &&\n processFramebufferPortWait' in runtime +assert 'while (delay < 3000' not in s +header=(r/'WhateverXe/kern_xe.hpp').read_text() +routing='\n'.join(re.search(pattern,header,re.S).group() for pattern in [ + r'bool tglHDMIEligible\(.*?\n',r'XELP::Display12SSTRoutes tglSSTRoutes\(.*?\n \}']) +h=(r/'tests/xelp_port_wait_harness.hpp').read_text().replace('// FUNCTIONS',functions).replace('// ROUTING',routing) +with tempfile.TemporaryDirectory(prefix='external-link-wait-') as d: + p=Path(d) + for native in [False]+([True] if len(sys.argv)>1 else []): + if native: + donor=Path(sys.argv[1]);assert hashlib.sha256(donor.read_bytes()).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + code=(r/'tests/display13_port_wait_fixture.cpp').read_text().replace('// PRODUCTION_HARNESS',h) + else:code=h+'\nint main(){checkInstallerAndSelection();}\n' + (p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+([str(donor)] if native else []),check=True) diff --git a/tests/display13_port_wait_fixture.cpp b/tests/display13_port_wait_fixture.cpp new file mode 100644 index 00000000..3ce2ce98 --- /dev/null +++ b/tests/display13_port_wait_fixture.cpp @@ -0,0 +1,82 @@ +#include "kern_xelp_port_wait.hpp" +#include +#include +#include +#include +#include +using namespace XELPPortWait; +// PRODUCTION_HARNESS +alignas(8) uint8_t port[0x700]{}; +uint32_t *config,buffer,baseResult; +unsigned writes,reads,delays,baseCalls,nowUS,ackUS; +bool unreadable; +uintptr_t expectedWritePC; +uint32_t read(void *p,uint32_t reg) { + assert(p==port && reg==0x64000+256*config[0]);++reads; + if(unreadable && writes)return ~0U; + if(writes && nowUS>=ackUS)buffer&=~0x80U; + return buffer; +} +void write(void *p,uint32_t reg,uint32_t value) { + assert(p==port && reg==0x64000+256*config[0]); + assert(reinterpret_cast(__builtin_return_address(0))==expectedWritePC); + ++writes;buffer=value; +} +uint32_t base(void *p) {assert(p==port);++baseCalls;return baseResult;} +void logMock(const char *,...) {} +void delay(unsigned us) {assert(us==5);nowUS+=us;++delays;assert(nowUS<=3000);} +void jump(uint8_t *p,uintptr_t target) {p[0]=0x48;p[1]=0xb8;memcpy(p+2,&target,8);p[10]=0xff;p[11]=0xe0;} +void redirect(uint8_t *image,unsigned site,unsigned stub) {assert(image[site]==0xe8);int32_t rel=stub-site-5;memcpy(image+site+1,&rel,4);} +templatevoid field(unsigned off,T value){memcpy(port+off,&value,sizeof(value));} +int main(int argc,char **argv) { + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{}); + assert(b.size()>0x5fc55 && waitKnown(reinterpret_cast(b.data())+WaitOffset,sizeof(WaitBefore))); + // Every non-relocation byte is guarded, including loop-back and epilogue branches. + for(unsigned i=0;i=44&&i<48)||(i>=76&&i<80); + assert(waitKnown(reinterpret_cast(changed.data())+WaitOffset,sizeof(WaitBefore))==relocation); + } + unsigned lateAccepted=0; + for(bool patched:{false,true}) { + auto image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0)); + assert(image!=MAP_FAILED && b.size()<0x400000);memcpy(image,b.data(),b.size()); + if(patched) { + uint8_t after[sizeof(WaitBefore)];patch(after,image+WaitOffset,reinterpret_cast(XE::framebufferPortWaitPolls)); + // Only initialization/profiling bytes and loop-back displacement change. + for(unsigned i=19;i(image+0xc95a0)=reinterpret_cast(base); + jump(image+0x1fe8e,reinterpret_cast(read));jump(image+0x1fea8,reinterpret_cast(write)); + jump(image+0x300000,reinterpret_cast(logMock));jump(image+0x300020,reinterpret_cast(delay)); + for(unsigned site:{0x5fa33U,0x5fac4U,0x5fbebU,0x5fc4eU})redirect(image,site,0x300000); + redirect(image,0x5fbcb,0x300020); + auto native=reinterpret_cast(image+0x5f9f4); + expectedWritePC=reinterpret_cast(image+WaitOffset); + for(unsigned platform=0;platform<3;++platform) + for(unsigned ddi:{0U,1U,3U,4U,5U,6U})for(unsigned protocol:{1U,2U}) + for(unsigned ack:{0U,495U,500U,600U,995U,1000U,2000U,2995U,3000U,~0U}) + for(unsigned fault=0;fault<3;++fault) { + if(platform==1 && !(AlderLakeS::ComboBackendMask&(1u<=3?1:protocol); + memcpy(&config,selectionPort+0x548,sizeof(config)); + const unsigned mode=platform==1 || ddi<3?1:4; + field(0x548,config+0);field(0x120,ddi);field(0x590,0x64000+256*ddi);field(0x550,mode); + field(0x124,4U);field(0x128,20U);port[0x114]=1; + writes=reads=delays=baseCalls=nowUS=0;buffer=0x80; + baseResult=fault==1?42:0;unreadable=fault==2;ackUS=ack; + const unsigned budget=patched?(platform==1 || ddi<3?1000:3000):500; + const auto result=native(port); + const auto expected=fault==1?42U:(fault==2||ack>=budget)?0xe00002d6U:0; + assert(result==expected && baseCalls==1 && writes==(fault==1?0U:1U)); + assert(nowUS<=budget && delays==nowUS/5); + if(expected==0xe00002d6U)assert(nowUS==budget && reads==1+budget/5); + if(!fault && ack==0)assert(nowUS==0 && reads==2); // no unconditional delay + if(patched && !fault && platform!=1 && ddi>=3 && ack==2000){assert(!result);++lateAccepted;} + } + munmap(image,0x400000); + } + assert(lateAccepted==16); + puts("PASS original vs patched exact enablePort: late ACK, bounded timeout, base failure, all-ones, immediate ACK, preserved write PC and native return; MMIO/time mocked"); +} diff --git a/tests/display13_power_bridge.cpp b/tests/display13_power_bridge.cpp index 4ca59fad..8cee27bc 100644 --- a/tests/display13_power_bridge.cpp +++ b/tests/display13_power_bridge.cpp @@ -30,6 +30,11 @@ static void slots(const NativeBridge &b, uint32_t a,uint32_t two,uint32_t three, b.nativeRefs(3)==three && b.nativeRefs(4)==four); } int main() { + { IO io;NativeBridge b;assert(ok(b.begin(io)) && ok(b.adoptBootPath(1,0,1))); + assert(b.pipeHeld(io,0) && b.outputHeld(io,1,0)); + assert(ok(b.putDDI(io,1)) && b.pipeHeld(io,0) && !b.outputHeld(io,1,0)); + assert(!b.pipeHeld(io,1) && !b.pipeHeld(io,3)); + assert(ok(b.getDDI(io,1)) && b.outputHeld(io,1,0)); } // Resource leases depend on selected physical ports, not HDMI labels. // Start cold, drop the peer before the boot route, and require all counts // to return to zero for each fixed HDMI/DP assignment and boot choice. diff --git a/tests/display13_retirement.py b/tests/display13_retirement.py new file mode 100644 index 00000000..72525538 --- /dev/null +++ b/tests/display13_retirement.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Run the production Display13 path/loop/return adapters with fault injection. + +Optional exact c004fc1a replay preserves free/AGDC control flow. MMIO, memory +allocator and native path disposal are leaves; this does not prove hardware. +""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['uint32_t XE::releaseDisplay13Path(', 'uint32_t XE::retireDisplay13Paths(', + 'uint32_t XE::wrapDisplay13FreeResources(', 'bool XE::display13RetirementHealthy(', + 'bool XE::wrapDisplay13AllocateResources(', 'bool XE::retireDisplay13Controller('] +functions='\n'.join(re.search(re.escape(n)+r'.*?\n\}',s,re.S).group() for n in names) +code=(r/'tests/display13_retirement_fixture.cpp').read_text().replace('// FUNCTIONS',functions) +# Keep the tested adapter connected at the real memory-return boundary and +# at both allocation entrances; declarations alone cannot satisfy this check. +assert 'return freed;' in s[s.index('bool XE::adlnFreeStoppedPipe('):s.index('uint32_t XE::releaseDisplay13Path(')] +assert 'if(!retireDisplay13Controller(controller)){blocked(3);return;}' in s +assert 'if(!display13RetirementHealthy())return false;' in s[s.index('bool XE::wrapTGLAllocatePathResources('):] +install=s[s.index('bool XE::processDisplay13Retirement('):s.index('bool XE::display13RetirementHealthy(')] +assert 'freeAll,wrapDisplay13FreeResources,orgDisplay13FreeResources' in install +assert 'allocate,wrapDisplay13AllocateResources,orgDisplay13AllocateResources' in install +assert 'processDisplay13Retirement(patcher,index,address,size)' in s +hooks='' +if len(sys.argv)>1: + donor=Path(sys.argv[1]).resolve();b=donor.read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + symbols={line.split()[-1]:int(line.split()[0],16) for line in subprocess.check_output(['nm','-n',str(donor)],text=True).splitlines() if len(line.split())==3 and re.fullmatch('[0-9a-fA-F]+',line.split()[0])} + for site,target in [(0x6780b,0x67e8e),(0x6ba3a,0x67e8e)]: + assert b[site]==0xe8 and site+5+int.from_bytes(b[site+1:site+5],'little',signed=True)==target + # Native allocation failure still returns false after cleanup; no new success. + assert b[0x67810:0x67816]==bytes.fromhex('4531f64489f0') + for symbol,fn in { + '__ZN21AppleIntelFramebuffer16StopTransactionsEv':'transactions', + '__ZN21AppleIntelFramebuffer14disableDisplayEb':'disable', + '__ZN21AppleIntelFramebuffer17freeScanoutMemoryEv':'post', + '__ZN21AppleIntelFramebuffer25cleanupLogicalFramebufferEv':'post', + '__ZN21AppleIntelFramebuffer21setPortNumberPropertyEv':'post'}.items(): + hooks+='hook(image+'+hex(symbols[symbol])+',(uintptr_t)'+fn+');\n' + code=code.replace('// NATIVE_HOOKS',hooks) +with tempfile.TemporaryDirectory(prefix='d13-retirement-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + cmd=['clang++','-std=c++17','-O0','-g','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')] + if len(sys.argv)==1:cmd+=['-fsanitize=address,undefined','-fno-sanitize-recover=all'] + subprocess.run(cmd,check=True) + subprocess.run([str(p/'test'),*([str(donor)] if len(sys.argv)>1 else [])],check=True) diff --git a/tests/display13_retirement_fixture.cpp b/tests/display13_retirement_fixture.cpp new file mode 100644 index 00000000..52613ec9 --- /dev/null +++ b/tests/display13_retirement_fixture.cpp @@ -0,0 +1,146 @@ +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_retirement.hpp" +#include +#include +#include +#include +#include +#include +using IORecursiveLock=int;using IOLock=int; +static int controllerMutex,fbMutex,ownerMutex;static unsigned ctlDepth,fbDepth,ownerDepth; +void IORecursiveLockLock(int *m){if(m==&controllerMutex){assert(ctlDepth||!fbDepth);++ctlDepth;}else{assert(ctlDepth);++fbDepth;}} +void IORecursiveLockUnlock(int *m){auto &depth=m==&controllerMutex?ctlDepth:fbDepth;assert(depth);--depth;} +void IOLockLock(int*){assert(!ownerDepth);++ownerDepth;}void IOLockUnlock(int*){assert(ownerDepth);--ownerDepth;} +struct OSObject {void **table;unsigned size,refs;OSObject *getMetaClass(){return this;}unsigned getClassSize(){return size;} + void retain(){++refs;}void release(){assert(refs);--refs;}}; +using IOService=OSObject; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + templatevoid setProperty(T...) {}}; +#define FunctionCast(f,p) reinterpret_cast(p) +#define OSSafeReleaseNULL(p) do{if(p){p->release();p=nullptr;}}while(0) +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +namespace Display13FBTopology {bool multiOutputWiring(unsigned w){return w!=0;}} +namespace TGLFBBinding {enum class Status {Unavailable,Complete};struct Readout {Status status{};unsigned pipe{},ddi{};};} +namespace Display13WM {enum class Status {Unavailable,Complete};} +namespace Display13DBUF { + enum class Status {Complete,Conflict};struct State {bool faulted{};unsigned selected{},consumers{},unretired{};}; + static bool refuseDetach;static unsigned detachCalls; + templateStatus detach(IO&,State&,unsigned){++detachCalls;return refuseDetach?Status::Conflict:Status::Complete;} +} +struct Pipe {IOService *memoryFramebuffer{};uintptr_t publishedScanout{};bool destroyingPath{}; + int powerPath{},memory{},scanout{};uint64_t watermarkClockHz{};unsigned wmPending{};}; +struct Power {int *lock{};IOService *controller{};void *map{};bool memoryFaulted{}; + struct Bridge {unsigned forgotten{};void forgetBootPath(uintptr_t){++forgotten;}}bridge; + Pipe pipe[3];Display13DBUF::State dbuf;}display13FBPower; +struct {struct Slot{IOService *framebuffer{};void *buffer{};}slot[3];}display13FBSysMem; +struct {unsigned admittedPipes{};}display13FBIRQ; +struct Display13FBDBUFIO {void *map;}; +void adlnFault(bool &fault,unsigned){fault=true;} +void adlnPublishMemory(const char*,unsigned,void*,size_t){} +struct XE { + bool display13FramebufferRequested=true;unsigned adlnOutputWiring=0x78;void *adlnPlatform{}; + uintptr_t orgAlderLakeNFreePath{},orgDisplay13FreeResources{},orgDisplay13AllocateResources{}; + static bool adlnFreeStoppedPipe(IOService*); + static void wrapAlderLakeNFreePath(IOService*,OSObject*); + static uint32_t releaseDisplay13Path(IOService*,OSObject*),retireDisplay13Paths(void*),wrapDisplay13FreeResources(void*); + static bool display13RetirementHealthy(),wrapDisplay13AllocateResources(void*),retireDisplay13Controller(void*); +}state,*callbackXE=&state; +templatevoid put(void*p,unsigned off,T value){memcpy(static_cast(p)+off,&value,sizeof(value));} +TGLFBBinding::Readout adlnInspectPathBinding(IOService*f,OSObject*p,void*,unsigned){ + const auto pipe=Display13FBMemory::boundPipe(display13FBPower.pipe,f); + if(!p||Display13FBMemory::field(p,0x32b0)!=f)return {}; + return {TGLFBBinding::Status::Complete,pipe,3}; +} +// FUNCTIONS +alignas(16) static uint8_t ctl[0x1c20],fb[0x4b90],peer[0x4b90],path[0x3730]; +static unsigned failure,memoryCalls,pathCalls,postCalls,transactionStops,allocCalls; +static uintptr_t backing=0x1234000; +static unsigned (*agdc)(void*,void*); +static void *frame=fb; +bool XE::adlnFreeStoppedPipe(IOService*f){ + // Substitute the native memory/IRQ leaf, not releaseDisplay13Path. Production + // code must check its return AND actual slot/backing state before disposal. + ++memoryCalls;if(failure==1)return false; + if(failure==2)return true; // false success: outstanding backing must reject it + put(f,0x4288,uint8_t(0xff));put(f,0x4348,uintptr_t(0)); + display13FBPower.pipe[0].publishedScanout=0;return true; +} +void nativePath(IOService*f,OSObject*p){ + assert(ctlDepth&&fbDepth&&!ownerDepth);++pathCalls; + assert(!Display13FBMemory::field(f,0x4348)); + if(failure==4)return; // no progress must terminate the enclosing native loop + put(f,0x49e8,0U);put(f,0x49f0,static_cast(nullptr)); + put(p,0x3644,0U);put(p,0x32b0,static_cast(nullptr));put(p,0x3648,static_cast(nullptr)); +} +void transactions(void*){assert(ctlDepth&&!fbDepth);++transactionStops;} +void disable(void*f,bool){assert(ctlDepth&&!fbDepth);put(path,0x3644,failure==5?2U:1U);} +void post(void*){assert(ctlDepth&&!fbDepth);++postCalls;} +void zero(void*p,size_t n){memset(p,0,n);} +void property(void*,const char*,const void*,unsigned){++postCalls;} +void syntheticFree(void*f){transactions(f);disable(f,false);if(XE::retireDisplay13Paths(f))return; + post(f);post(f);post(f);post(f);put(f,0x1e7,uint8_t(0));} +unsigned syntheticAGDC(void*,void*){return XE::wrapDisplay13FreeResources(frame);} +bool allocate(void*){++allocCalls;return true;} +void hook(uint8_t*p,uintptr_t target){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&target,8);p[10]=0xff;p[11]=0xe0;} +void call(uint8_t*m,unsigned at,unsigned target){assert(m[at]==0xe8);const int32_t d=target-(at+5);memcpy(m+at+1,&d,4);} +void setup(uintptr_t native){ + memset(ctl,0,sizeof(ctl));memset(fb,0,sizeof(fb));memset(peer,0,sizeof(peer));memset(path,0,sizeof(path)); + static void *table[82]{};table[0x288/8]=(void*)property; + for(auto p:{ctl,fb,peer,path}){auto o=reinterpret_cast(p);o->table=table;o->size=p==ctl?sizeof(ctl):p==path?sizeof(path):sizeof(fb);o->refs=1;} + put(ctl,0x9b8,&controllerMutex);put(ctl,0xd5c,1U);put(ctl,0xd60,frame); + put(fb,0x1d0,static_cast(ctl));put(fb,0x4220,&fbMutex);put(fb,0x3fd8,int32_t(-1)); + put(fb,0x1e7,uint8_t(1));put(fb,0x49e8,failure==6?3U:1U);put(fb,0x49f0,static_cast(path)); + put(fb,0x4288,uint8_t(0xff));put(fb,0x4348,backing); + put(path,0x32b0,frame);put(path,0x3648,static_cast(peer));put(path,0x3644,2U); + display13FBPower={};auto &c=display13FBPower;c.lock=&ownerMutex;c.controller=reinterpret_cast(ctl); + c.pipe[0].memoryFramebuffer=reinterpret_cast(fb);c.pipe[0].memoryFramebuffer->retain();c.pipe[0].publishedScanout=backing; + c.pipe[1].memoryFramebuffer=reinterpret_cast(peer);c.pipe[1].publishedScanout=0x5678000;c.pipe[1].wmPending=7; + display13FBSysMem={};display13FBIRQ.admittedPipes=3; + Display13DBUF::refuseDetach=failure==3;Display13DBUF::detachCalls=0; + state={};state.orgAlderLakeNFreePath=(uintptr_t)nativePath;state.orgDisplay13FreeResources=native; + state.orgDisplay13AllocateResources=(uintptr_t)allocate; + memoryCalls=pathCalls=postCalls=transactionStops=allocCalls=0; +} +int main(int argc,char**argv){ + uint8_t *image=nullptr;size_t bytes=0;uintptr_t native=(uintptr_t)syntheticFree;agdc=syntheticAGDC; + if(argc==2){ + std::ifstream file(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(file)),{}); + const auto cave=unsigned((b.size()+4095)&~4095ULL);bytes=cave+4096; + image=(uint8_t*)mmap(nullptr,bytes,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);assert(image!=MAP_FAILED);memcpy(image,b.data(),b.size()); + using namespace Display13Retirement; + assert(!memcmp(image+Loop,Before,sizeof(Before)));assert(!memcmp(image+ReleaseCall,ReleaseBefore,sizeof(ReleaseBefore))); + uint8_t patched[sizeof(Before)];patch(patched,(uintptr_t)XE::retireDisplay13Paths);memcpy(image+Loop,patched,sizeof(patched)); + memcpy(image+ReleaseCall,ReleaseAfter,sizeof(ReleaseAfter)); + // NATIVE_HOOKS + hook(image+cave,(uintptr_t)XE::wrapDisplay13FreeResources);call(image,ReleaseCall,cave); + hook(image+cave+32,(uintptr_t)zero);call(image,0x67f5e,cave+32); + native=(uintptr_t)(image+0x67e8e);agdc=(decltype(agdc))(image+0x6ba08); + } + for(failure=0;failure<7;++failure){ + setup(native);const auto peerBefore=display13FBPower.pipe[1];unsigned handle=1; + auto result=agdc(ctl,&handle);assert(result==(failure?kIOReturnNotReady:0U)); + assert(!ctlDepth&&!fbDepth&&!ownerDepth&&transactionStops==1); + assert(display13FBPower.pipe[1].memoryFramebuffer==peerBefore.memoryFramebuffer&&display13FBPower.pipe[1].publishedScanout==peerBefore.publishedScanout&&display13FBPower.pipe[1].wmPending==7); + if(failure){ + assert(!postCalls&&Display13FBMemory::field(fb,0x49e8)==(failure==6?3U:1U)); + assert(Display13FBMemory::field(path,0x32b0)==fb&&Display13FBMemory::field(path,0x3648)==peer); + assert(!XE::wrapDisplay13AllocateResources(fb)&&!allocCalls); + assert(!XE::retireDisplay13Controller(ctl)); + if(failure<=2)assert(Display13FBMemory::field(fb,0x4348)==backing&&!pathCalls); + if(failure==3)assert(!Display13FBMemory::field(fb,0x4348)&&!pathCalls); // partial backing release + }else{ + assert(postCalls==4&&pathCalls==1&&!Display13FBMemory::field(fb,0x49e8)); + assert(!display13FBPower.pipe[0].memoryFramebuffer&&!(display13FBIRQ.admittedPipes&1)); + assert(XE::wrapDisplay13AllocateResources(fb)&&allocCalls==1); + } + } + failure=0;setup(native); + // Unretired peer ownership prevents pool destruction even after this path + // completes. Clear only that synthetic peer for the empty-controller case. + assert(!XE::retireDisplay13Controller(ctl));assert(pathCalls==1); + display13FBPower.pipe[1]={};assert(XE::retireDisplay13Controller(ctl));assert(pathCalls==1); + setup(native);state.display13FramebufferRequested=false;display13FBPower.memoryFaulted=true; + assert(XE::display13RetirementHealthy()&&XE::retireDisplay13Controller(nullptr)); // other backends unchanged + if(image)munmap(image,bytes); + puts("PASS D13 production path/loop/return: memory refusal, false memory success, DDB refusal after partial release, no progress, active path, excess count, AGDC error, allocation/pool blocking, peer preservation and success"); +} diff --git a/tests/display13_shared_registration.py b/tests/display13_shared_registration.py new file mode 100644 index 00000000..23b33228 --- /dev/null +++ b/tests/display13_shared_registration.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Replay production shared registration across physical Combo/TC topologies.""" +from pathlib import Path +import re, subprocess, tempfile, sys +r=Path(__file__).resolve().parents[1] +s=(Path(sys.argv[1]) if len(sys.argv)>1 else r/'WhateverXe/kern_xe.cpp').read_text() +f='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in [ + 'uint32_t XE::adlnRequestedSharedReservation(', 'IOReturn XE::wrapDisplay13FramebufferPlatformCall(']) +code=r''' +#include "kern_display13_fb_memory.hpp" +#include "kern_display13_fb_topology.hpp" +#include +#include +#include +struct OSSymbol {}; +struct Meta {unsigned getClassSize(){return 0x1C20;}}; +struct IOService {alignas(8) unsigned char data[0x1C20] {};Meta *getMetaClass(){static Meta m;return &m;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,const void*,unsigned){}}; +using IOReturn=int;constexpr int kIOReturnSuccess=0,kIOReturnNotReady=1; +using Call=IOReturn(*)(IOService*,const OSSymbol*,bool,void*,void*,void*,void*); +#define FunctionCast(name,addr) reinterpret_cast(addr) +template void put(void *p,unsigned off,T value){memcpy(static_cast(p)+off,&value,sizeof(value));} +struct {IOService *controller{};bool initialized=true,faulted=false;uint32_t sharedNativePrefix{},sharedPoolPrefix{};}display13FBGTT; +struct {bool memoryFaulted=false;}display13FBPower; +IOService *adlnSharedOwner;uintptr_t adlnSharedCallback=0x12345678; +bool adlnFixedSysMemEnabled=false;unsigned calls=0;int originalResult=0;bool badBinding=false,badInfo=false; +IOReturn original(IOService *c,const OSSymbol*,bool,void *owner,void*,void *info,void*) { + ++calls;if(originalResult)return originalResult; + put(c,0xC0,owner);put(c,0xD38,badBinding?uintptr_t(0):adlnSharedCallback); + put(info,0,badInfo?0U:Display13FBMemory::field(c,0xDF8));return 0; +} +struct XE { + Display13OutputWiring adlnOutputWiring;uintptr_t orgDisplay13FramebufferPlatformCall=reinterpret_cast(original); + static uint32_t adlnRequestedSharedReservation(IOService*); + static IOReturn wrapDisplay13FramebufferPlatformCall(IOService*,const OSSymbol*,bool,void*,void*,void*,void*); +}; +XE *callbackXE; +FUNCTIONS +void initialize(IOService &c,unsigned stolen) { + c={};put(&c,0xD5C,3U);put(&c,0xDF5,uint8_t(3));put(&c,0xDE4,stolen); + put(&c,0xDC8,0x180000U);put(&c,0xDCC,0x12000U);put(&c,0xDD0,uint64_t(stolen+0x192000)); + put(&c,0xDD8,0x3000U);put(&c,0xDF8,stolen+0x195000U);put(&c,0xDE8,0x4000000U); + put(&c,0x1A10,1U);put(&c,0x1A28,uint64_t(0x37800000)); + for(unsigned slot=0;slot<3;++slot) { + const auto o=0x1A10+40*slot,base=slot?stolen+0x195000U+(slot-1)*0x4000000U:0; + put(&c,o+4,base);put(&c,o+8,base/4096);put(&c,o+0x20,0x4000000U);put(&c,o+0x24,0x4000U); + } +} +int main() { + XE x;callbackXE=&x;IOService c,owner;OSSymbol symbol;unsigned cases=0; + for(unsigned mask=1;mask<128;++mask)if(!(mask&~Display13OutputWiring::Physical) && (mask&(mask-1))) + for(unsigned stolen:{0x04000000U,0x06000000U,0x08000000U,0x0c000000U}) + for(unsigned failure=0;failure<10;++failure) { + initialize(c,stolen);adlnFixedSysMemEnabled=false;x.adlnOutputWiring={};x.adlnOutputWiring.valid=true;x.adlnOutputWiring.hdmi=mask; + for(unsigned d=0;d<7;++d)if(mask&(1U<=5 && failure<=7))); + if(!failure) { + assert(info[0]==prefix && info[1]==0x10000000 && info[2]==0x9a490000); + assert(XE::adlnRequestedSharedReservation(&c)==prefix); + put(&c,0xDF8,prefix+4096);assert(!XE::adlnRequestedSharedReservation(&c)); + } else assert(!display13FBGTT.sharedPoolPrefix); + ++cases; + } + // Fixed backing enabled still requires its entire extent inside the aperture. + for(unsigned stolen:{0x04000000U,0x06000000U,0x08000000U,0x0c000000U}) { + initialize(c,stolen);put(&c,0x1560,&symbol);display13FBGTT={&c,true,false,0,0}; + adlnSharedOwner=&owner;adlnFixedSysMemEnabled=true;originalResult=0;badBinding=badInfo=false; + uint32_t info[3]{};const auto before=calls; + const auto result=XE::wrapDisplay13FramebufferPlatformCall(&c,&symbol,false,&owner,&symbol,info,nullptr); + const bool fits=stolen<0x08000000U; + assert((result==0)==fits && calls==before+fits); + if(fits)assert(info[0]==stolen+0x8195000U && XE::adlnRequestedSharedReservation(&c)==info[0]); + else assert(!display13FBGTT.sharedPoolPrefix); + } + assert(cases==2280);printf("PASS %u production shared registration cases: Combo/TC, native prefix, owner, callback, stale layout and failure propagation\n",cases); +} +'''.replace('FUNCTIONS',f) +with tempfile.TemporaryDirectory(prefix='wex-shared-registration-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp index ab8b8a44..0d828d80 100644 --- a/tests/display13_sst_fixture.cpp +++ b/tests/display13_sst_fixture.cpp @@ -25,7 +25,7 @@ struct Map { uint32_t read(uint32_t reg){reads.push_back(reg);return regs.at(reg);} void write(uint32_t reg,uint32_t v){writes.push_back(reg);if(reg==drop)return; if(reg==0x45404)v=(v&~0x5405U)|((v&0xA80A)>>1); - if(reg==0x45454||reg==0x45444)v=(v&~21U)|((v&42)>>1); + if(reg==0x45454||reg==0x45444)v=(v&~0x1555U)|((v&0x2aaaU)>>1); if(reg==base+0x694)for(unsigned n=0;n<4;++n)regs.at(base+0x894+0x100*n)=v; regs.at(reg)=v; } @@ -36,9 +36,19 @@ struct Context { Map *map=nullptr;bool clockFaulted=false,memoryFaulted=false,clockStopped=false; }display13FBPower; struct Display13FBPowerIO { - Map *map;bool readable(uint32_t r){return map&&map->regs.count(r);}uint32_t read(uint32_t r){return map->read(r);} + Map *map;bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned tc){return tc<4;}bool tcAuxHealthy(unsigned tc){return tc<4;} + bool readable(uint32_t r){return map&&map->regs.count(r);}uint32_t read(uint32_t r){return map->read(r);} void write(uint32_t r,uint32_t v){map->write(r,v);}void delayUs(unsigned us){map->delay+=us;if(map->idleReg&&map->delay>=map->idleAfter)map->regs.at(map->idleReg)|=0x80;} }; +// TC lifecycle itself is exercised by display13_tc_mode. This fixture +// supplies the ownership witness and verifies the frontend does not borrow +// Combo leases; false is the default for all existing Combo regressions. +bool tcOwned=false,tcLinked=false; +struct Display13FBTCIO { + OSObject *object;unsigned tc; + bool owned(unsigned d){return tcOwned && tc<4 && d==tc+3 && Display13FBMemory::field(object,0x550)==4;} + bool linkHeld(){return tcLinked && owned(tc+3);} +}; struct Display13FBSSTIO { Map *map;uint32_t read(uint32_t r){assert(locked);return map->read(r);} void write(uint32_t r,uint32_t v){assert(locked);map->write(r,v);} @@ -46,13 +56,17 @@ struct Display13FBSSTIO { bool adlnWriterMappingBound(){return bound;} uintptr_t display13FBExpectedOwner(void*){return 0;} struct XE { - bool display13FramebufferRequested=true,display13FBSSTHooksReady=true;XELP::Profile xeProfile; + bool display13ClockOrderReady=false; + static bool display13StopTranscoder(void*){assert(false);return false;} + static bool display13TrainingPath(void*,uint32_t&,uint32_t&){return false;} + bool display13FramebufferRequested=true,display13FBSSTHooksReady=true,display13FBTCHooksReady=false;XELP::Profile xeProfile; Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; uintptr_t orgDisplay13EnablePort=0,orgDisplay13DisablePort=0; static bool display13MainLinkAUX(void*,bool); static uint32_t wrapDisplay13EnablePort(void*);static uint32_t wrapDisplay13DisablePort(void*); static uint8_t *display13SSTPort(void*);static bool display13HDMIPort(void*); + static uint32_t wrapDisplay13TCSignal(void*,uint32_t,uint32_t,void*){assert(false);return 1;} static uint32_t wrapDisplay13SignalLevels(void*,uint32_t,uint32_t,void*); static uint32_t wrapDisplay13TrainingPattern(void*,uint32_t); }state,*callbackXE=&state; @@ -103,11 +117,13 @@ unsigned nativePattern(void *path,unsigned pattern){ } void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigned rate){ display13FBPower.~Context();new(&display13FBPower)Context;state={};calls=nativeResult=0;bound=true;loseAfterNative=false; - assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; + tcOwned=tcLinked=false;assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; auto &c=display13FBPower;c.controller=f.ctl;c.access=f.access;c.map=↦ currentPath=f.path; put(f.ctl,0xC8,c.power);put(c.power,0x10,static_cast(f.ctl));put(c.power,0x78,static_cast(f.access)); - f.wiring.hdmi=1U<<(1-ddi);f.wiring.dp=1U<=3?0:1-ddi; + f.wiring.hdmi=1U<=3){put(f.donor,0x20+24*ddi+4,ddi+6);put(f.donor,0x20+24*ddi+12,1U);} assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,f.wiring)); state.adlnOutputWiring=f.wiring;state.adlnPlatform=f.platform;state.adlnTopologyController=f.ctl;state.xeProfile.display=XELP::Display::XeLPD; state.orgDisplay13TrainingPattern=reinterpret_cast(nativePattern);state.orgDisplay13SignalLevels=reinterpret_cast(nativeSignal); @@ -115,7 +131,7 @@ void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigne static uintptr_t vtable[64]{};vtable[0x130/8]=reinterpret_cast(nativeWrite);put(f.access,0,static_cast(vtable)); put(f.ctl,0,unsigned(sizeof(f.ctl)));put(f.fb,0,unsigned(sizeof(f.fb)));put(f.path,0,unsigned(sizeof(f.path))); put(f.plane,0,unsigned(sizeof(f.plane)));put(f.port,0,unsigned(sizeof(f.port))); - put(f.port,0x578,static_cast(f.access));put(f.port,0x550,1U);put(f.port,0x124,lanes);put(f.port,0x128,rate);f.port[0x11A]=1; + put(f.port,0x578,static_cast(f.access));put(f.port,0x550,ddi>=3?4U:1U);put(f.port,0x124,lanes);put(f.port,0x128,rate);f.port[0x11A]=1; map.base=ddi?0x6C000:0x162000; put(f.port,0x620,map.base+0x804);put(f.port,0x5C8,map.base+0x28);put(f.access,0x60,0x200000U); put(f.port,0x590,0x64000+0x100*ddi); @@ -123,13 +139,13 @@ void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigne for(unsigned n=0;n<4;++n)for(auto reg:{0x888U,0x890U,0x894U,0x89CU})map.regs[map.base+0x100*n+reg]=0; map.regs[0x46140+4*pipe]=0;map.regs[0x60400+0x1000*pipe]=0;map.regs[0x64000+0x100*ddi]=0;map.regs[0x60540+0x1000*pipe]=0; Display13FBPowerIO io{&map};using Display13Power::Status; - assert(c.bridge.begin(io,1-ddi,3).status==Status::Complete); + assert(c.bridge.begin(io,peer,f.wiring.selected()).status==Status::Complete); const auto pg=pipe?pipe+2:1; assert(c.bridge.getPG(io,pg).status==Status::Complete&&c.bridge.getDDI(io,ddi).status==Status::Complete); assert(c.bridge.getAUX(io,ddi).status==Status::Complete&&c.bridge.putAUX(io,ddi).status==Status::Complete); // Remove boot peer first: training must depend only on its actual resources. - assert(c.bridge.adoptBootPath(1,0,1-ddi).status==Status::Complete); - assert(c.bridge.putDDI(io,1-ddi).status==Status::Complete&&c.bridge.putPG(io,1).status==Status::Complete); + assert(c.bridge.adoptBootPath(1,0,peer).status==Status::Complete); + assert(c.bridge.putDDI(io,peer).status==Status::Complete&&c.bridge.putPG(io,1).status==Status::Complete); assert(c.bridge.outputHeld(io,ddi,pipe));map.reads.clear();map.writes.clear(); } int main(int argc,char **argv){ @@ -193,6 +209,27 @@ int main(int argc,char **argv){ assert(XE::wrapDisplay13TrainingPattern(f.path,1)==kIOReturnNotReady&&calls==completedCalls&&map.writes.empty()&&map.reads.empty()); ++count; } + // TC SST frontend: real graph/bridge and native pattern replay. The + // separate lifecycle suite proves the ownership witness supplied here. + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe) + for(unsigned lanes:{1U,2U,4U})for(unsigned rate:{6U,10U,20U,30U}){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,lanes,rate); + Display13FBPowerIO power{&map};auto &bridge=display13FBPower.bridge; + Display13Power::Lease modeAUX; + assert(Display13Power::acquire(power,bridge.state,modeAUX,Display13Power::auxDomain(ddi)).status==Display13Power::Status::Complete); + assert(!XE::display13MainLinkAUX(f.port,true));tcOwned=true;tcLinked=true; + assert(XE::display13MainLinkAUX(f.port,true)); + assert(!XE::wrapDisplay13TrainingPattern(f.path,1)); + assert(map.regs[0x46140+4*pipe]==((ddi+3)<<28)); + assert(map.regs[0x60400+0x1000*pipe]==(((ddi+1)<<27)|0x02000000|((lanes-1)<<1))); + assert(!XE::wrapDisplay13EnablePort(f.port)); + assert(!XE::wrapDisplay13TrainingPattern(f.path,0)); + assert(!XE::wrapDisplay13DisablePort(f.port)); + assert(objectRefs.empty() && Display13Power::held(power,bridge.state,Display13Power::auxDomain(ddi))); + assert(!bridge.nativeAUXRefs(ddi)); + tcOwned=false;assert(!XE::display13MainLinkAUX(f.port,true)); + assert(Display13Power::release(power,bridge.state,modeAUX).status==Display13Power::Status::Complete); + } for(unsigned fault=0;fault<25;++fault){ Fixture f(2,1,1);Map map;setup(f,map,1,1,4,20);auto &c=display13FBPower; if(fault==0)c.bridge.state.suspended=true;if(fault==1)c.bridge.state.faulted=true; diff --git a/tests/display13_tc.cpp b/tests/display13_tc.cpp index f086131a..c3335b68 100644 --- a/tests/display13_tc.cpp +++ b/tests/display13_tc.cpp @@ -62,18 +62,26 @@ int main(){ assert(b.begin(io,0,1U|(1U<(Domain::PW2)]); + assert(b.state.refs[static_cast(Domain::PW2)]==1); assert(b.getAUX(io,port).status==Status::Complete && io.prepares==1 && io.preparedTC==tc); assert((io.regs[0x45444]&(3U<<(2*(tc+3))))==(3U<<(2*(tc+3)))); - assert(b.state.refs[1]==1 && b.state.refs[3]==0 && b.state.refs[4]==0); // No phantom pipe B/C. + assert(b.state.refs[1]==2 && b.state.refs[3]==0 && b.state.refs[4]==0); // DDI + AUX, no phantom pipe B/C. assert(b.getAUX(io,port).status==Status::Complete && b.nativeAUXRefs(port)==2 && io.prepares==1); - assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==1); + assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==2); assert(suspend(io,b.state).status==Status::Complete); assert(restore(io,b.state).status==Status::Complete && io.prepares==2); - assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==0); + assert(b.putAUX(io,port).status==Status::Complete && b.state.refs[1]==1 && (io.regs[Driver]&0xC)==0xC); assert(b.putDDI(io,port).status==Status::Complete && !b.nativeDDIRefs(port)); assert((io.regs[0x45404]&0xA80A)==0x802 && (io.regs[0x45454]&0x2AAA)==2); assert(!b.nativeDDIRefs(7) && !b.nativeAUXRefs(8)); + // A TC boot GOP on pipe A must retain PW2 without any AUX client. + // Dropping another native PG2 user cannot turn its lanes off. + PowerIO bootIO;NativeBridge boot; + assert(boot.begin(bootIO,port,1U< +#include +#include +#include +using namespace AlderLakeN; +struct IO { + std::map regs; + std::vector writes; + XELPMemory::Layout memory=XELPMemory::decode(0x04C1,0x38000000,0); + unsigned ticks {},changeTick {},pteReads {}; + uint32_t changeReg {},changePTEReg {},badPTE {UINT32_MAX}; + bool noFrames {},failEnable {}; + bool readable(uint32_t reg) const {return reg<=0xFFFFFC;} + uint32_t read(uint32_t reg) {return reg==0x70040 ? ticks : regs[reg];} + uint64_t readPTE(uint32_t reg) { + ++pteReads; + if(pteReads==3 && changePTEReg)regs[changePTEReg]^=1; + return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); + } + void write(uint32_t reg,uint32_t value) { + writes.push_back({reg,value}); + if(failEnable && reg==0x70180 && value==0x94000008){failEnable=false;return;} + regs[reg]=value; + } + void delay() { + if(!noFrames)++ticks; + if(changeTick && ticks==changeTick)regs[changeReg]^=1; + } +}; +static IO firmware(unsigned ddi) { + IO io;const auto tc=ddi-3; + // G660i single TC1 GOP geometry and clock/ownership, translated only + // by the Display13 physical TC stride for the other register instances. + io.regs={{0x45404,0xFC0F},{0x45454,3U<<(2*ddi)}, + {0x161500+4*tc,0xC},{0x64000+0x100*ddi,0x80000040}, + {0x46038+8*tc,0xCC000000},{0x46100+4*ddi,0x80000000}, + {0x46140,(ddi+3)<<28},{0x60400,0x80030100|((ddi+1)<<27)}, + {0x60000,0x0897077F},{0x6000C,0x04640437},{0x70008,0xC0000000}, + {0x70028,0xA0F},{0x70180,0x84000008},{0x70188,120},{0x70190,0x0437077F}}; + return io; +} +template static void put(void *p,size_t offset,T value) {memcpy(static_cast(p)+offset,&value,sizeof(value));} +static Display13FBMemory::Readout nativeMemory(IO &io,unsigned ddi,const Display13TC::Boot &tc, + const DisplayReadout *dp=nullptr) { + uint8_t controller[0x1C20] {},fb[0x4B90] {}; + put(fb,0x1D0,reinterpret_cast(controller)); + put(controller,0xD5C,uint32_t(1));put(controller,0xD60,reinterpret_cast(fb)); + put(controller,0xDF5,uint8_t(1));put(controller,0xDF4,uint8_t(1));put(controller,0xDF0,uint32_t(1)); + put(controller,0x1A10,uint32_t(1));put(controller,0x1A28,io.memory.base); + put(controller,0x1A30,uint32_t(0x4000000));put(controller,0x1A34,uint32_t(0x4000)); + return Display13FBMemory::inspect(io,controller,sizeof(controller),fb,sizeof(fb),0x10000000,io.memory,true,0,ddi,&tc,dp); +} +static IO dpFirmware(unsigned ddi,unsigned lanes,bool enhanced) { + auto io=firmware(ddi); + // Synthetic SST source from Linux's Display13 register fields. The + // measured HDMI fixture supplies only geometry/physical TC coordinates; + // this is not a claim about a particular firmware's DP startup values. + io.regs[0x60400]=0x82030000|((ddi+1)<<27)|((lanes-1)<<1); + io.regs[0x64000+0x100*ddi]=0x80000040|((lanes-1)<<1); + io.regs[0x60540]=0x80000300|(enhanced?1U<<18:0); + io.regs[0x60030]=0x7E100000;io.regs[0x60034]=0x800000; + io.regs[0x60040]=0x100000;io.regs[0x60044]=0x800000; + return io; +} +static void dpHandoff() { + unsigned cases=0; + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned lanes:{1U,2U,4U})for(bool enhanced:{false,true}) { + auto io=dpFirmware(ddi,lanes,enhanced);const auto original=io.regs; + const auto tc=Display13TC::Boot::capture(io,true,ddi,0); + const auto source=readDisplay(io,true);LinearPlan plan; + assert(dpSource(source,0,ddi,&tc) && dpSourceUnchanged(io,source,0,ddi,0,&tc)); + // No unpowered TC register is pulled into the shared reader, and + // no DP acceptance is obtained by omitting either source/TC proof. + uint32_t ignored=0;assert(!value(source,tc.port.buffer,ignored)); + assert(!dpSource(source,0,ddi)); + assert(!Display13Boot::reservedPrefix(io,source,true,ddi,io.memory,&tc)); + assert(linearPlan(source,plan,ddi,GOPProtocol::DP)==LinearStatus::InvalidTCSource); + auto foreign=tc;foreign.port.ddi=ddi==6?3:ddi+1; + assert(!dpSource(source,0,ddi,&foreign)); + assert(!dpSourceUnchanged(io,source,0,ddi,2U<<(2*ddi),&tc)); + assert(Display13Boot::reservedPrefix(io,source,true,ddi,io.memory,&tc,GOPProtocol::DP)); + assert(io.writes.empty()); + const auto result=Display13Boot::prepareLinear(io,source,true,ddi,io.memory,&tc,GOPProtocol::DP); + assert(result.status==LinearStatus::Success && result.writes==10 && tc.unchanged(io)); + for(auto pair:original)if(pair.first!=0x70028 && pair.first!=0x70180)assert(io.regs[pair.first]==pair.second); + auto after=readDisplay(io,true); + assert(nativeMemory(io,ddi,tc).status==Display13FBMemory::Status::DisplayState); + assert(nativeMemory(io,ddi,tc,&after).status==Display13FBMemory::Status::Complete); + // Reject loss/drift at entry, while waiting for a frame, and during + // the PTE walk. A changed active link forbids speculative rollback. + for(auto reg:{0x45404U,0x45454U,0x45444U,0x60400U,0x60540U,0x60030U, + 0x60034U,0x60040U,0x60044U,0x46140U,0x78000U,0x78004U,tc.port.buffer}) { + auto changed=dpFirmware(ddi,lanes,enhanced);changed.regs[reg]^=1; + auto r=reinitializeLinear(changed,source,true,ddi,GOPProtocol::DP,&tc); + assert(r.status==LinearStatus::StateChanged && changed.writes.empty()); + for(unsigned tick:{1U,3U,5U}) { + auto drift=dpFirmware(ddi,lanes,enhanced);drift.changeReg=reg;drift.changeTick=tick; + r=reinitializeLinear(drift,source,true,ddi,GOPProtocol::DP,&tc); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); + } + auto prefix=dpFirmware(ddi,lanes,enhanced);prefix.changePTEReg=reg; + assert(!Display13Boot::reservedPrefix(prefix,source,true,ddi,prefix.memory,&tc,GOPProtocol::DP)); + assert(prefix.writes.empty()); + auto pool=io;pool.changePTEReg=reg;pool.pteReads=0; + assert(nativeMemory(pool,ddi,tc,&after).status==Display13FBMemory::Status::StateChanged); + } + for(auto invalid:std::vector{{0x60400,0x83030006|((ddi+1)<<27)}, + {0x60540,0x80000000},{0x60540,0x80000380},{0x60034,0},{0x60040,0}, + {0x78000,0x80000000},{0x78004,0x80000000},{0x60404,1}, + {tc.port.buffer,0x80000040|((lanes==4?0:3)<<1)}}) { + auto bad=dpFirmware(ddi,lanes,enhanced);bad.regs[invalid.offset]=invalid.value; + const auto witness=Display13TC::Boot::capture(bad,true,ddi,0); + assert(!dpSource(readDisplay(bad,true),0,ddi,&witness)); + } + auto rollback=dpFirmware(ddi,lanes,enhanced);rollback.failEnable=true; + auto r=reinitializeLinear(rollback,source,true,ddi,GOPProtocol::DP,&tc); + assert(r.status==LinearStatus::EnableTimeout && r.restored && tc.unchanged(rollback)); + ++cases; + } + printf("PASS %u TC SST source/plane/pool handoffs, proof mismatch, drift and rollback\n",cases); +} +static void unownedDP() { + for(unsigned ddi=3;ddi<=6;++ddi) { + auto io=dpFirmware(ddi,4,true);const auto reg=0x64000+0x100*ddi; + io.regs[reg]&=~64U; + assert(!Display13TC::Boot::capture(io,true,ddi,0).valid()); + const auto tc=Display13TC::Boot::capture(io,true,ddi,0,true); + const auto view=readDisplay(io,true);LinearPlan plan; + assert(tc.valid() && dpSourceUnchanged(io,view,0,ddi,0,&tc)); + assert(Display13Boot::prepareLinear(io,view,true,ddi,io.memory,&tc,GOPProtocol::DP).status==LinearStatus::Success); + assert(!(io.regs[reg]&64)); // Plane preparation cannot silently acquire PHY ownership. + const auto unowned=readDisplay(io,true); + assert(nativeMemory(io,ddi,tc,&unowned).status==Display13FBMemory::Status::DisplayState); + io.regs[reg]|=64; + assert(!tc.unchanged(io)); // Ownership transfer needs a new strict witness. + const auto owned=Display13TC::Boot::capture(io,true,ddi,0); + const auto after=readDisplay(io,true); + assert(owned.valid() && nativeMemory(io,ddi,owned,&after).status==Display13FBMemory::Status::Complete); + auto hdmi=firmware(ddi);hdmi.regs[reg]&=~64U; + const auto optional=Display13TC::Boot::capture(hdmi,true,ddi,0,true); + assert(optional.valid()); + assert(linearPlan(readDisplay(hdmi,true),plan,ddi,GOPProtocol::TMDS,&optional)==LinearStatus::InvalidTCSource); + assert(nativeMemory(hdmi,ddi,optional).status==Display13FBMemory::Status::DisplayState); + } +} +static void sstPayloadReadout() { + for(unsigned ddi=3;ddi<=6;++ddi) { + auto io=dpFirmware(ddi,4,true); + // Observed firmware SST source: bit8 set, enhanced framing, four lanes. + // The coordinate translation is synthetic for the other TC slots. + io.regs[0x60400]|=1U<<8; + io.regs[0x60030]=0x7e1a6666;io.regs[0x60040]=0x23333;io.regs[0x60044]=0x80000; + const auto tc=Display13TC::Boot::capture(io,true,ddi,0); + const auto source=readDisplay(io,true); + assert(Display13Boot::prepareLinear(io,source,true,ddi,io.memory,&tc,GOPProtocol::DP).status==LinearStatus::Success); + assert(io.regs[0x60400]==(0x82030106U|((ddi+1)<<27))); + const auto after=readDisplay(io,true); + assert(nativeMemory(io,ddi,tc,&after).status==Display13FBMemory::Status::Complete); + io.regs[0x60400]^=1U<<8; + assert(!dpSourceUnchanged(io,source,0,ddi,0,&tc)); + io.regs[0x60400]|=1U<<24; // Real MST mode still rejected. + assert(!dpSource(readDisplay(io,true),0,ddi,&tc)); + } +} +int main() { + sstPayloadReadout(); + unownedDP(); + unsigned cases=0; + for(unsigned ddi=3;ddi<=6;++ddi) { + auto io=firmware(ddi);const auto original=io.regs; + auto tc=Display13TC::Boot::capture(io,true,ddi,0); + auto display=readDisplay(io,true);LinearPlan plan; + assert(tc.valid() && tc.unchanged(io)); + assert(linearPlan(display,plan,ddi)==LinearStatus::InvalidTCSource); + assert(Display13Boot::reservedPrefix(io,display,true,ddi,io.memory,&tc)); + assert(io.writes.empty()); + for(auto reg:original)assert(io.regs[reg.first]==reg.second); + assert(nativeMemory(io,ddi,tc).status==Display13FBMemory::Status::DisplayState); + io.pteReads=0; + auto result=Display13Boot::prepareLinear(io,display,true,ddi,io.memory,&tc); + io.pteReads=0; + assert(result.status==LinearStatus::Success && result.writes==10 && tc.unchanged(io)); + for(auto w:io.writes)assert(w.offset==0x42084 || w.offset==0x70028 || w.offset==0x70180 || + w.offset==0x70188 || w.offset==0x7018C || w.offset==0x70190 || w.offset==0x701A4 || w.offset==0x7019C); + for(auto r:original)if(r.first!=0x70028 && r.first!=0x70180)assert(io.regs[r.first]==r.second); + assert(Display13Boot::reservedPrefix(io,readDisplay(io,true),true,ddi,io.memory,&tc)); + assert(io.pteReads==io.memory.bytes/4096); + const auto native=nativeMemory(io,ddi,tc); + assert(native.status==Display13FBMemory::Status::Complete && native.verifiedPages==0x4000); + auto rejected=firmware(ddi);rejected.badPTE=0x800010; + auto preparation=Display13Boot::prepareLinear(rejected,readDisplay(rejected,true),true,ddi,rejected.memory,&tc); + assert(preparation.status==LinearStatus::StateChanged && rejected.writes.empty()); + auto denied=firmware(ddi); + preparation=Display13Boot::prepareLinear(denied,readDisplay(denied,true),false,ddi,denied.memory,&tc); + assert(preparation.status==LinearStatus::NotAdmitted && denied.writes.empty() && !denied.pteReads); + auto bad=firmware(ddi);bad.badPTE=0x800010; + assert(!Display13Boot::reservedPrefix(bad,readDisplay(bad,true),true,ddi,bad.memory,&tc) && bad.pteReads==3); + for(unsigned wrong:{0U,1U,2U,7U,8U,UINT32_MAX})assert(!Display13TC::Boot::capture(io,true,wrong,0).valid()); + assert(!Display13TC::Boot::capture(io,false,ddi,0).valid()); + assert(!Display13TC::Boot::capture(io,true,ddi,3).valid()); + const auto p=tc.port; + const uint32_t link[]={0x45404,0x45454,p.ready,p.buffer,p.clock,p.pll,0x46140}; + for(auto reg:link) { + auto changed=firmware(ddi);auto source=readDisplay(changed,true);changed.regs[reg]^=1; + auto r=reinitializeLinear(changed,source,true,ddi,GOPProtocol::TMDS,&tc); + assert(r.status==LinearStatus::StateChanged && changed.writes.empty()); + for(unsigned tick:{1U,3U,5U}) { + auto drift=firmware(ddi);drift.changeReg=reg;drift.changeTick=tick; + r=reinitializeLinear(drift,readDisplay(drift,true),true,ddi,GOPProtocol::TMDS,&tc); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); + } + auto unavailable=firmware(ddi);unavailable.regs[reg]=UINT32_MAX; + assert(!Display13TC::Boot::capture(unavailable,true,ddi,0).valid()); + } + for(unsigned state:{0U,1U,2U,5U,6U,7U}) { + auto alt=firmware(ddi);alt.regs[p.ready]=state; + assert(!Display13TC::Boot::capture(alt,true,ddi,0).valid()); + } + auto wrongClock=firmware(ddi);wrongClock.regs[0x46140]=(ddi+1)<<28; + assert(!Display13TC::Boot::capture(wrongClock,true,ddi,0).valid()); + auto rollback=firmware(ddi);rollback.failEnable=true; + result=reinitializeLinear(rollback,readDisplay(rollback,true),true,ddi,GOPProtocol::TMDS,&tc); + assert(result.status==LinearStatus::EnableTimeout && result.restored && tc.unchanged(rollback)); + auto stopped=firmware(ddi);stopped.noFrames=true; + result=reinitializeLinear(stopped,readDisplay(stopped,true),true,ddi,GOPProtocol::TMDS,&tc); + assert(result.status==LinearStatus::NoVblank && stopped.writes.empty()); + ++cases; + } + printf("PASS %u TC GOP handoffs: power/ownership/clock preservation, plane rollback, GGTT prefix and link drift\n",cases); + dpHandoff(); +} diff --git a/tests/display13_tc_clock.cpp b/tests/display13_tc_clock.cpp new file mode 100644 index 00000000..22cdf99f --- /dev/null +++ b/tests/display13_tc_clock.cpp @@ -0,0 +1,171 @@ +#include "kern_display13_tc_dp.hpp" +#include +#include +#include +#include +using namespace Display13TC; +struct IO { + Port p;std::map regs,bank; + std::vector> writes; + bool held {},power {true},owner {true},idle {true},stuckPower {},stuckLock {},fault {}; + unsigned time {},locks {},unlocks {};uint32_t reject {},absent {}; + unsigned expectedLanes {4}; + bool link {},failLink {},coldCleanup {};unsigned linkGets {},linkPuts {}; + bool linkPower(bool enable) { + if(enable){assert((regs[Clock::laneRegister(p.tc)]&Clock::laneMask(p.tc))==((1U<=p.phy && r>p.shift)&15)<<12)|(r-p.phy);} + uint32_t read(uint32_t r){assert(readable(r));return regs.count(r)?regs[r]:bank[key(r)];} + void write(uint32_t r,uint32_t v) { + assert(held && readable(r));writes.emplace_back(r,v);if(r==reject)return; + if(r==p.pll) { + auto status=regs[r]&(Clock::Locked|Clock::Powered); + if(!stuckPower)status=(status&~Clock::Powered)|((v&Clock::Power)?Clock::Powered:0); + if(!stuckLock)status=(status&~Clock::Locked)|((v&Clock::Enable)?Clock::Locked:0); + regs[r]=(v&~(Clock::Locked|Clock::Powered))|status; + } else if(regs.count(r))regs[r]=v;else bank[key(r)]=v; + } + unsigned lock(){assert(!held);held=true;++locks;return 7;} + void unlock(unsigned irq){assert(held && irq==7);held=false;++unlocks;} + void delayUs(unsigned n){assert(!held);time+=n;} + void bankFault(){fault=true;} +}; +int main() { + // Independent DP divisor oracle: 8.1 GHz / (5 * link symbol clock). + const unsigned rates[]={6,10,20,30},high[]={5,3,3,2},div[]={2,2,1,1}; + // Existing independent Linux capture of TC3 at RBR x4. + const uint32_t liveDP[]={0x100,0xa00,0x6200,0x84269,0x1c0027,0x40002000,0x5e000000,0x52}; + auto measured=DPClock::calculate(6,38400);for(unsigned i=0;i<8;++i)assert(measured.words[i].value==liveDP[i]); + for(unsigned ref:{19200U,24000U,38400U})for(unsigned i=0;i<4;++i) { + const auto plan=DPClock::calculate(rates[i],ref); + assert(plan.valid() && plan.dco==8100000 && plan.hs==high[i] && plan.ds==div[i]); + assert(TMDSClock::frequency(ref,plan.words[3].value,plan.words[6].value,plan.words[2].value)==rates[i]*27000); + assert(!(plan.words[2].value&0x10000)); + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned lanes:{1U,2U,4U}) { + IO io(ddi);io.expectedLanes=lanes; + for(unsigned group=0;group<2;++group)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[group*0x1000+reg]=0xa5a5a5a5; + const auto peer=io.regs[Clock::laneRegister(ddi-3)]&~Clock::laneMask(ddi-3),hip=io.regs[io.p.hip]; + Clock::LinkStage stage;assert(DPSignal::startLink(io,io.p,plan,lanes,&stage)); + assert(stage==Clock::LinkStage::Complete && io.link && io.linkGets==1); + assert((io.regs[Clock::laneRegister(ddi-3)]&~Clock::laneMask(ddi-3))==peer && io.regs[io.p.hip]==hip); + for(unsigned group=0;group<2;++group) { + assert((io.bank[group*0x1000+0xa0]&0xc0)==(lanes==1?(group?0x40U:0U):0x80U)); + assert(!(io.bank[group*0x1000+0x2c8]&0x7c)); + } + DPRetainedLink retained{plan.rate,plan.reference,lanes}; + assert(retained.matches(io,io.p,plan,lanes)); + auto mismatch=retained;mismatch.lanes=lanes==4?2:4;assert(!mismatch.matches(io,io.p,plan,lanes)); + const auto pll=io.regs[io.p.pll];io.regs[io.p.pll]&=~Clock::Locked; + assert(!retained.matches(io,io.p,plan,lanes));io.regs[io.p.pll]=pll; + io.idle=false;assert(retained.matches(io,io.p,plan,lanes)); // Active training link is retained. + // Training signal changes are valid with a live buffer. + for(unsigned swing=0;swing<4;++swing)for(unsigned pre=0;pre+swing<4;++pre)assert(DPSignal::program(io,io.p,rates[i],swing,pre)); + assert(!DPSignal::program(io,io.p,rates[i],3,1)); + io.idle=true;assert(Clock::stopLink(io,io.p) && io.linkPuts==1); + } + } + for(unsigned rate:{0U,12U,19U,31U})assert(!DPClock::calculate(rate,38400).valid()); + // ADLP RBR/HBR and HBR2/HBR3 independently expected coefficient vectors. + const unsigned expectedLow[]={0x107,0x605,0xb02,0x1700,5,0x802,0x1400,2,0xb00,0}; + const unsigned expectedHigh[]={7,0x405,0xa02,0x1800,5,0x602,0x1400,2,0x900,0}; + unsigned index=0;for(unsigned v=0;v<4;++v)for(unsigned pre=0;v+pre<4;++pre,++index) { + unsigned value;assert(DPSignal::coefficients(6,v,pre,value) && value==expectedLow[index]); + assert(DPSignal::coefficients(20,v,pre,value) && value==expectedHigh[index]); + } + const auto full=TMDSClock::calculate(148500,38400),hd=TMDSClock::calculate(74250,38400); + // Independent live i915 shared-PLL captures: central FHD / outer 720p. + const uint32_t f[]={0x100,0x500,0x15400,0x84274,0x1c0027,0x40002000,0x41000000,0x4a}; + const uint32_t h[]={0x100,0x500,0x16500,0xa5278,0x1c0027,0x40002000,0x76600000,0x47}; + assert(full.valid() && hd.valid()); + for(unsigned i=0;i<8;++i){assert(full.words[i].value==f[i]);assert(hd.words[i].value==h[i]);} + for(unsigned ref:{19200U,24000U,38400U})for(unsigned rate=25000;rate<=600000;rate+=125) { + auto plan=TMDSClock::calculate(rate,ref); + // Finite PLL divider range has real clock holes; do not round those + // into a different display timing. Independently enumerate solutions. + unsigned oscillator=0;for(unsigned h:{7U,5U,3U,2U})for(unsigned d=10;d;--d) + if(!oscillator && 5*rate*h*d>=7992000 && 5*rate*h*d<=10000000)oscillator=5*rate*h*d; + assert(plan.valid()==(oscillator && oscillator/(2*ref)<=255)); + if(!plan.valid())continue; + const auto actual=TMDSClock::frequency(ref,plan.words[3].value,plan.words[6].value,plan.words[2].value); + assert(actual==rate); + for(auto w:plan.words)assert(!(w.value&~w.mask)); + } + for(unsigned rate:{0U,24999U,600001U,UINT32_MAX})assert(!TMDSClock::calculate(rate,38400).valid()); + for(unsigned ref:{0U,1U,25000U,UINT32_MAX})assert(!TMDSClock::calculate(148500,ref).valid()); + for(unsigned ddi=3;ddi<=6;++ddi) { + IO io(ddi);const auto before=io.regs;auto previous=io.bank; + assert(Clock::enable(io,io.p,full));assert(io.regs[io.p.hip]==before.at(io.p.hip)); + assert(io.regs[io.p.pll]==0xec000000 && io.regs[io.p.clock]==0x80001234); + assert(io.regs[Clock::Gate]==(before.at(Clock::Gate)&~Clock::gateBit(ddi-3))); + for(auto w:full.words)assert(io.bank[w.reg]==((previous[w.reg]&~w.mask)|w.value)); + const auto count=io.writes.size();assert(!Clock::enable(io,io.p,hd) && io.writes.size()==count); + assert(Clock::disable(io,io.p));assert(io.regs[io.p.pll]==0x20000000 && io.regs[io.p.clock]==0x1234); + assert(io.regs[Clock::Gate]==before.at(Clock::Gate) && io.locks==io.unlocks); + assert(Clock::enable(io,io.p,hd));assert(Clock::disable(io,io.p)); + } + const uint32_t native[]={464,2048,3,2,2}; + assert(TMDSClock::nativeRate(native,2)==148500); + assert(!TMDSClock::nativeRate(native,3) && !TMDSClock::nativeRate(nullptr,2)); + for(unsigned d=3;d<=6;++d)for(unsigned preset=0;preset<10;++preset) { + IO io(d);auto before=io.regs[io.p.hip]; + for(unsigned lane=0;lane<2;++lane)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[lane*0x1000+reg]=0xa5a5a5a5; + assert(TMDSSignal::program(io,io.p,preset)); + const unsigned swing[]={7,6,4,2,0,0,0,0,0,0},emphasis[]={0,0,0,0,0,5,6,7,8,10}; + for(unsigned lane=0;lane<2;++lane) { + assert(io.bank[lane*0x1000+0xa0]==((0xa5a5a5a5&~0xc0U)|0x80)); + assert(io.bank[lane*0x1000+0xd00]==0); + for(unsigned tx=0;tx<2;++tx)assert(io.bank[lane*0x1000+0x2c0+4*tx]==((0xa5a5a5a5&~0x3ff07U)|swing[preset]|(emphasis[preset]<<8))); + assert(io.bank[lane*0x1000+0x2c8]==((0xa5a5a5a5&~0x7cU)|(lane?0x78U:0x40U))); + } + assert(io.regs[io.p.hip]==before); + const auto count=io.writes.size();assert(!TMDSSignal::program(io,io.p,10) && io.writes.size()==count); + io.owner=false;assert(!TMDSSignal::program(io,io.p,7) && io.writes.size()==count); + } + for(unsigned failure=0;failure<8;++failure) { + IO io(3); + if(failure==0)io.power=false;if(failure==1)io.owner=false;if(failure==2)io.idle=false; + if(failure==3)io.regs[io.p.ready]=5;if(failure==4)io.regs[io.p.buffer]|=1U<<31; + if(failure==5)io.regs[io.p.buffer]&=~64U;if(failure==6)io.absent=io.p.pll;if(failure==7)io.regs[io.p.pll]=~0U; + assert(!Clock::enable(io,io.p,full) && io.writes.empty()); + assert(!Clock::disable(io,io.p) && io.writes.empty()); + } + {IO io(3);io.stuckPower=true;assert(!Clock::enable(io,io.p,full) && io.time==1000 && io.writes.size()==1);} + {IO io(3);io.stuckLock=true;assert(!Clock::enable(io,io.p,full) && io.time==1000);assert(!(io.regs[io.p.clock]&0xf0000000));} + {IO io(3);io.reject=io.p.phyRegister(0x2200);auto bank=io.regs[io.p.hip];assert(!Clock::enable(io,io.p,full));assert(io.regs[io.p.hip]==bank && !(io.regs[io.p.pll]&Clock::Enable));} + for(unsigned d=3;d<=6;++d) { + IO io(d);for(unsigned lane=0;lane<2;++lane)for(unsigned reg:{0xa0U,0xd00U,0x2c0U,0x2c4U,0x2c8U})io.bank[lane*0x1000+reg]=0; + // Cold control/AUX is held, but DDI IO is off until the PLL is locked. + const auto prior=io.regs[Clock::laneRegister(io.p.tc)]; + assert(Clock::startLink(io,io.p,full,7) && io.link && io.linkGets==1); + assert(io.regs[Clock::laneRegister(io.p.tc)]==(prior|Clock::laneMask(io.p.tc))); + assert(Clock::stopLink(io,io.p) && !io.link && io.linkPuts==1); + assert(Clock::startLink(io,io.p,hd,7) && io.linkGets==2); + assert(Clock::stopLink(io,io.p) && io.linkPuts==2); + } + for(unsigned d=3;d<=6;++d) { + IO io(d);auto reg=Clock::laneRegister(io.p.tc);io.regs[reg]=0xa5a500a5; + auto prior=io.regs[reg];assert(Clock::lanes(io,io.p)); + assert(io.regs[reg]==(prior|Clock::laneMask(io.p.tc))); + io.regs[reg]=UINT32_MAX;auto n=io.writes.size();assert(!Clock::lanes(io,io.p) && io.writes.size()==n); + } + {IO io(4);io.stuckLock=true;assert(!Clock::startLink(io,io.p,full,7) && !io.linkGets);} + {IO io(4);io.failLink=true;assert(!Clock::startLink(io,io.p,full,7) && io.linkGets==1 && !io.link);} + {IO io(4);io.power=io.owner=false;io.coldCleanup=true;const auto count=io.writes.size(); + assert(Clock::stopLink(io,io.p) && io.writes.size()==count); // local acquisition failure, PLL never started + io.regs[io.p.pll]|=Clock::Enable;assert(!Clock::stopLink(io,io.p));} + puts("PASS Display13 TC HDMI PLL live fixtures, clock sweep, reserved bits, bounded failures and lifecycle"); +} diff --git a/tests/display13_tc_lightup.cpp b/tests/display13_tc_lightup.cpp new file mode 100644 index 00000000..49ad8162 --- /dev/null +++ b/tests/display13_tc_lightup.cpp @@ -0,0 +1,87 @@ +#include "kern_display13_tc_lightup.hpp" +#include "kern_display13_tc_native.hpp" +#include +#include +#include +#include +#include +using namespace Display13TC; +struct IO { + unsigned count {},fail {},last {},reg {},value {}; + bool admission=true,posting=true; + uint32_t portError {}; + bool ready(bool clock){return admission && (!clock || count>0) && (!fail || last!=fail);} + void step(unsigned n){assert(n==++count);last=n;} + void clocks(){step(1);} + bool transClock(unsigned r,unsigned v){step(2);reg=r;value=v;return posting;} + void plane(){step(3);}void scaler(){step(4);}void pipe(){step(5);} + void signal(){step(6);}uint32_t port(){step(7);return portError;} + void packets(){step(8);} +}; +struct DPIO { + unsigned count {},fail {};uint32_t trainingError {};bool admission=true; + bool ready(bool clock){return admission && (!clock || count>0) && count!=fail;} + void step(unsigned n){assert(n==++count);} + void clocks(){step(1);}uint32_t train(){step(2);return trainingError;} + bool transClock(unsigned r,unsigned v){step(3);assert(r>=0x46140 && r<=0x46148 && v>=0x60000000);return count!=fail;} + void plane(){step(4);}void scaler(){step(5);}void pipe(){step(6);}void packets(){step(7);} +}; +int main(){ + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ + IO io;const auto r=lightUpTMDS(io,ddi,pipe); + assert(!r.status && r.stage==LightStage::Complete && io.count==8); + assert(io.reg==0x46140+4*pipe && io.value==(ddi+3)<<28); + } + for(unsigned fail=1;fail<=8;++fail){ + IO io;io.fail=fail;if(fail==2)io.posting=false; + const auto r=lightUpTMDS(io,3,0); + assert(r.status && io.count==fail && unsigned(r.stage)==fail); + } + for(unsigned ddi: {0U,2U,7U,~0U}){IO io;assert(lightUpTMDS(io,ddi,0).status && !io.count);} + IO unbound;unbound.admission=false;assert(lightUpTMDS(unbound,3,0).status && !unbound.count); + IO pipe;assert(lightUpTMDS(pipe,3,3).status && !pipe.count); + IO port;port.portError=0xe00002d6;const auto r=lightUpTMDS(port,3,0); + assert(r.status==port.portError && r.stage==LightStage::Port && port.count==7); + unsigned notify=0;assert(finishMode([](){return 0U;},[&](){++notify;})==0 && notify==0); + assert(finishMode([](){return 0xe00002d8U;},[&](){++notify;})==0xe00002d8U && notify==1); + + for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ + DPIO io;io.fail=99;const auto r=lightUpDP(io,ddi,pipe); + assert(!r.status && r.stage==LightStage::Complete && io.count==7); + } + const LightStage dpStages[]={LightStage::Binding,LightStage::Clocks,LightStage::Training,LightStage::TransClock, + LightStage::Plane,LightStage::Scaler,LightStage::Pipe,LightStage::Packets}; + for(unsigned fail=1;fail<=7;++fail){DPIO io;io.fail=fail;const auto r=lightUpDP(io,5,1); + assert(r.status && r.stage==dpStages[fail] && io.count==fail);} + for(auto error:{1U,0xe00002d6U,0xe00002d8U}){DPIO io;io.fail=99;io.trainingError=error; + const auto r=lightUpDP(io,5,1);assert(r.status==error && io.count==2 && r.stage==LightStage::Training);} + DPIO rejected;assert(lightUpDP(rejected,5,0).status && !rejected.count); + + // Execute the actual x86 patch bytes with both native flag outcomes. + // Error keeps eax and skips all post-mode work. Success reaches the same + // flag-dependent destinations as the donor. No real driver code executes. + auto mem=static_cast(mmap(nullptr,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0)); + assert(mem!=MAP_FAILED);memset(mem,0xcc,4096); + const unsigned char prefix[]={0x41,0x56,0x49,0x89,0xfe,0x89,0xf0}; // save r14; r14=rdi; eax=esi + memcpy(mem,prefix,sizeof(prefix));memcpy(mem+sizeof(prefix),Native::outerAfter,sizeof(Native::outerAfter)); + const unsigned char yes[]={0xb8,1,0,0,0,0x41,0x5e,0xc3},no[]={0xb8,2,0,0,0,0x41,0x5e,0xc3}; + const unsigned char error[]={0x41,0x5e,0xc3}; + memcpy(mem+sizeof(prefix)+sizeof(Native::outerAfter),yes,sizeof(yes)); + memcpy(mem+sizeof(prefix)+0x50,no,sizeof(no)); + memcpy(mem+sizeof(prefix)+Native::modeExitOffset-Native::outerBeforeOffset,error,sizeof(error)); + // Inner adapter: eax input -> r13d -> eax, with the callee-saved register restored. + const unsigned char innerPrefix[]={0x41,0x55,0x89,0xf8}; + const unsigned char innerExit[]={0x44,0x89,0xe8,0x41,0x5d,0xc3}; + memcpy(mem+1024,innerPrefix,sizeof(innerPrefix));memcpy(mem+1028,Native::innerAfter,sizeof(Native::innerAfter)); + memcpy(mem+1031,innerExit,sizeof(innerExit)); + assert(!mprotect(mem,4096,PROT_READ|PROT_EXEC)); + auto outer=reinterpret_cast(mem); + auto inner=reinterpret_cast(mem+1024); + unsigned char controller[0xc60]{}; + for(unsigned flag=0;flag<2;++flag){controller[0xc5c]=flag; + assert(outer(controller,0)==(flag?1U:2U)); + for(auto error: {1U,0xe00002d8U,0xe00002d6U})assert(outer(controller,error)==error && inner(error)==error); + } + assert(inner(0)==0);munmap(mem,4096); + puts("PASS TC LightUp: clock/each-stage failure stop, 12 selectors, native error branches, balanced notification"); +} diff --git a/tests/display13_tc_mode.cpp b/tests/display13_tc_mode.cpp new file mode 100644 index 00000000..dc2a5fe1 --- /dev/null +++ b/tests/display13_tc_mode.cpp @@ -0,0 +1,112 @@ +#include "kern_display13_tc_mode.hpp" +#include +#include +#include +#include +using namespace Display13TC; +struct IO { + Port port;std::map regs; + Display13Power::State power;bool ready {true},adopt {},quiet {true},nativeFail {},auxOK {true}; + unsigned native {},time {},failedTC {99};bool rollbackStuck {};std::vector sequence; + IO(unsigned d){assert(Port::resolve(d,port));regs={{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8e380000}}; + for(unsigned t=0;t<4;++t){regs[0x161500+4*t]=4;regs[0x64300+0x100*t]=0;} + assert(Display13Power::initialize(*this,power,Display13Power::nativeDomains(0x7b)));} + bool readable(uint32_t r){return regs.count(r);} + uint32_t read(uint32_t r){assert(readable(r));return regs[r];} + void write(uint32_t r,uint32_t v) { + assert(readable(r));sequence.push_back(r); + if(r==0x45404 || r==0x45454 || r==0x45444){ + const auto mask=r==0x45404?0xa80aU:0x2aaaU;auto ack=(v&mask)>>1; + // Model ADLP dependencies, not unconditional request -> state. + // AUX needs ownership; DDI IO needs the cold block already held. + if(r!=0x45404)for(unsigned t=0;t<4;++t) { + const auto state=1U<<(6+2*t),request=state<<1; + const bool phy=(regs[0x64300+0x100*t]&64) && (regs[0x161500+4*t]&4); + if(!phy || (r==0x45454 && !(regs[0x45444]&state)) || t==failedTC)ack&=~state; + if(rollbackStuck && t==failedTC && (regs[r]&request) && !(v&request))v|=request; + } + regs[r]=(v&~(mask>>1))|ack; + } + else regs[r]=v; + } + void delayUs(unsigned t){time+=t;} + bool admissionReady(){return ready;} + unsigned nativeMode(){return native;} + unsigned nativeSetMode(unsigned v){sequence.push_back(0x550);if(nativeFail)return 1;native=v;return 0;} + bool tcAuxSupported(){return true;} + bool prepareTCAux(unsigned t){return (regs[0x45404]&5)==5 && (regs[0x64300+0x100*t]&64);} + bool tcAuxHealthy(unsigned){return auxOK;} + Display13Power::Result acquireDomains(Display13Power::Lease &l,uint32_t d){return Display13Power::acquire(*this,power,l,d,true);} + Display13Power::Result releaseDomains(Display13Power::Lease &l){return Display13Power::release(*this,power,l);} + bool powerHeld(uintptr_t owner,uintptr_t object,unsigned d){return owner==1 && object==2 && d==port.ddi && Display13Power::held(*this,power,Mode::accessDomains());} + bool inheritedVerified(uintptr_t,uintptr_t,unsigned){return adopt;} + bool quiescent(uintptr_t,uintptr_t,unsigned d){return quiet && !(regs[0x64000+0x100*d]&0x80000000U);} +}; +int main() { + for(unsigned d=3;d<=6;++d) { + IO io(d);Mode m;assert(m.connect(io,1,2,d).status==Status::Complete); + assert(m.phase==Mode::Phase::Active && io.native==4 && m.ownership.active && m.auxLease.owner==&io.power); + assert(io.sequence.back()==0x550 && io.regs[io.port.buffer]&64); + assert(!(io.power.owned & Display13Power::ddiDomain(d))); // DDI waits for PLL enable. + auto count=io.sequence.size();assert(m.connect(io,1,2,d).status==Status::Complete && io.sequence.size()==count); + assert(m.connect(io,1,3,d).status==Status::InUse && io.sequence.size()==count); + io.quiet=false;assert(m.disconnect(io,1,2,d).status==Status::InUse && m.disconnectPending && m.auxLease.owner); + assert(m.connect(io,1,2,d).status==Status::Complete && !m.disconnectPending); + io.quiet=true;io.regs[io.port.buffer]|=1U<<31; + assert(m.disconnect(io,1,2,d).status==Status::InUse && m.auxLease.owner); + io.regs[io.port.buffer]&=~(1U<<31); + assert(m.disconnect(io,1,2,d).status==Status::Complete); + assert(m.phase==Mode::Phase::Idle && !m.ownership.owner && !m.auxLease.owner && !io.native); + assert(m.connect(io,1,2,d).status==Status::Complete);assert(m.disconnect(io,1,2,d).status==Status::Complete); + } + // Firmware may leave a fixed legacy DP source enabled but unowned (Linux + // intel_tc_port_init_mode). Transfer only ownership; preserve width/link. + for(unsigned d=3;d<=6;++d)for(unsigned owned:{0U,64U}) { + IO io(d);Mode m;const auto firmware=0x80000006U|owned; + io.regs[io.port.buffer]=firmware;io.adopt=true; + assert(m.connect(io,1,2,d).status==Status::Complete); + assert(io.regs[io.port.buffer]==(firmware|64)); + assert(m.disconnect(io,1,2,d).status==Status::InUse && m.auxLease.owner); + io.regs[io.port.buffer]&=~(1U<<31); + assert(m.disconnect(io,1,2,d).status==Status::Complete); + assert(io.regs[io.port.buffer]==6 && !m.ownership.owner && !m.auxLease.owner); + } + {IO io(3);Mode m;io.regs[io.port.buffer]=0x80000040; + assert(m.connect(io,1,2,3).status==Status::NeedsAdoption && m.phase==Mode::Phase::Cleanup && m.accessLease.owner && !m.auxLease.owner); + assert(!io.native && m.connect(io,1,2,3).status==Status::InUse);} + {IO io(3);Mode m;io.nativeFail=true; + assert(m.connect(io,1,2,3).status==Status::Readback && m.ownership.owner && m.auxLease.owner); + io.nativeFail=false;assert(m.disconnect(io,1,2,3).status==Status::Complete);} + {IO io(3);Mode m;io.auxOK=false; + assert(m.connect(io,1,2,3).status==Status::NoPower && m.ownership.owner && !io.native && !io.power.faulted); + assert(m.disconnect(io,1,2,3).status==Status::Complete && !m.accessLease.owner && !m.auxLease.owner); + io.auxOK=true;assert(m.connect(io,1,2,3).status==Status::Complete);} + {IO io(3);Mode m;io.regs[0x45404]=15;io.regs[0x45454]=io.regs[0x45444]=0xc0; + io.regs[io.port.buffer]=64;io.adopt=true; + assert(m.connect(io,1,2,3).status==Status::Complete && !io.power.owned); + assert(m.disconnect(io,1,2,3).status==Status::Complete); + assert(io.regs[0x45404]==15 && io.regs[0x45454]==0xc0 && io.regs[0x45444]==0xc0);} + {IO io(3);Mode m;io.ready=false;assert(m.connect(io,1,2,3).status==Status::NotReady && io.sequence.empty());} + // Cold second instances share PW1/PW2, not the first port's ownership. + for(unsigned second=4;second<=6;++second) { + IO io(3);Mode first,other;assert(first.connect(io,1,2,3).status==Status::Complete); + io.regs[0x64300]|=1U<<31;const auto neighbor=io.regs[0x64300]; + assert(Port::resolve(second,io.port));io.native=0; + assert(other.connect(io,1,2,second).status==Status::Complete); + assert(other.disconnect(io,1,2,second).status==Status::Complete && !other.accessLease.owner); + assert(io.regs[0x64300]==neighbor && (io.regs[0x45444]&0xc0)==0xc0 && !(io.regs[0x45454]&0xc0)); + assert(Display13Power::held(io,io.power,Display13Power::auxDomain(3))); + io.failedTC=second-3; + const auto failure=other.connect(io,1,2,second); + assert(failure.status==Status::NoPower && failure.power.status==Display13Power::Status::StateTimeout); + assert(failure.power.domain==12+2*(second-3) && failure.power.isolated && !io.power.faulted); + assert(Display13Power::held(io,io.power,Display13Power::auxDomain(3))); + assert(other.disconnect(io,1,2,second).status==Status::Complete); + io.failedTC=99;assert(other.connect(io,1,2,second).status==Status::Complete); + } + {IO io(4);Mode m;io.failedTC=1;io.rollbackStuck=true; + const auto r=m.connect(io,1,2,4); + assert(r.status==Status::NoPower && r.power.rollbackFailed && io.power.faulted && m.accessLease.owner); + assert(m.disconnect(io,1,2,4).status!=Status::Complete);} + puts("PASS Display13 legacy TC mode lifecycle: power/ownership/native order, adoption, retries and pending cleanup"); +} diff --git a/tests/display13_tc_native.py b/tests/display13_tc_native.py new file mode 100644 index 00000000..3d188eff --- /dev/null +++ b/tests/display13_tc_native.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Qualify the TC native hook entries, mode-return patches and caller ABI.""" +from pathlib import Path +import hashlib,re,subprocess,sys +r=Path(__file__).resolve().parents[1] +h=(r/'WhateverXe/kern_display13_tc_native.hpp').read_text() +s=(r/'WhateverXe/kern_xe.cpp').read_text() +arrays={name:bytes(int(v,0) for v in body.split(',')) for name,body in + re.findall(r'uint8_t (\w+)\[\]=\{([^}]+)\}',h)} +offsets={name:int(value,0) for name,value in re.findall(r'uint32_t (\w+)Offset=(0x[0-9a-f]+);',h)} +assert len(arrays['innerBefore'])==len(arrays['innerAfter'])==3 +assert len(arrays['outerBefore'])==len(arrays['outerAfter'])==24 +assert int.from_bytes(arrays['outerAfter'][4:8],'little',signed=True)+0x695ac+8==0x698ed +assert 0x695ac+18+arrays['outerAfter'][17]==0x695fc +install=s[s.index('bool XE::processDisplay13FramebufferTC('):s.index('bool XE::processDisplay13FramebufferOutput(')] +assert '!(adlnOutputWiring.selected()&0x78)' in install +assert 'entries[LightUp].symbol,wrapDisplay13TCLightUp' in install +assert 'entries[SetMode].symbol,wrapDisplay13TCSetMode' in install +assert 'wrapDisplay13TCSignal,' not in install # one common SST signal hook +assert '{innerBeforeOffset,innerBefore,innerAfter,sizeof(innerAfter)}' in install +assert '{outerBeforeOffset,outerBefore,outerAfter,sizeof(outerAfter)}' in install +print('PASS TC native mode-patch destinations, installation scope and single signal dispatcher') +if len(sys.argv)>1: + path=Path(sys.argv[1]);donor=path.read_bytes() + assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + symbols={line.split()[-1]:int(line.split()[0],16) for line in + subprocess.check_output(['nm','-n',str(path)],text=True).splitlines() + if len(line.split())==3 and re.fullmatch('[0-9a-fA-F]+',line.split()[0])} + entries=re.findall(r'\{"([^"\n]+)",(0x[0-9a-f]+),\{([^}]+)\}\}',h) + assert len(entries)==18 + for symbol,value,body in entries: + offset=int(value,0);prefix=bytes(int(v,0) for v in body.split(',')) + assert symbols[symbol]==offset and donor[offset:offset+len(prefix)]==prefix + for name,offset in offsets.items(): + assert donor[offset:offset+len(arrays[name])]==arrays[name] + # Independently resolve the native startup and first full DP modeset calls. + # Byte matching alone must not be mistaken for a call-graph explanation. + for at,symbol in [ + (0x3fdd2,'__ZN14AppleIntelPort14getPortByIndexEj'), + (0x3fe3c,'__ZN19AppleIntelPowerWell4initEP31AppleIntelFramebufferController'), + (0x3ff44,'__ZN31AppleIntelFramebufferController16setupBootDisplayEv'), + (0x4fec7,'__ZN31AppleIntelFramebufferController14disableDisplayEP21AppleIntelFramebufferP21AppleIntelDisplayPathh'), + (0x4ffd4,'__ZN31AppleIntelFramebufferController9LightUpDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2')]: + assert donor[at]==0xe8 and at+5+int.from_bytes(donor[at+1:at+5],'little',signed=True)==symbols[symbol] + for name in ['bootPortInit','bootPowerCall','bootSetupCall','modeStopCall','dpLightCall']: + assert '{'+name+'Offset,'+name+',sizeof('+name+')}' in install + print('PASS native port initialization before boot Mode acquisition, full stop before DP LightUp') + # The path error branch calls disableDisplay before returning through its + # normal unlock path. These are independent of the new outer early return. + assert donor[0x50004:0x50009]==bytes.fromhex('4585ed7437') + assert donor[0x5001b:0x50020]==bytes.fromhex('e82cd3ffff') + print('PASS exact c004fc1a: 18 entries, full TMDS sequence, native cleanup, both mode-return boundaries') + + # Ordinary probe classifies and acquires mode before any DP AUX access. + # Qualify the virtual slots and direct call targets independently of wrappers. + for at,expected in [(0x1faad,'ff9078010000'),(0x1fabb,'ff9138010000'), + (0x1fb38,'ff9038010000'),(0x1bdbe,'ff9088010000'), + (0x1be04,'ff9040010000'),(0x1be1e,'ff9028010000')]: + assert donor[at:at+6]==bytes.fromhex(expected),hex(at) + for at,target in [(0x50979,0x1ba02),(0x50b0c,0x51f56),(0x5fb9b,0x1fea8)]: + assert donor[at]==0xe8 and at+5+int.from_bytes(donor[at+1:at+5],'little',signed=True)==target + print('PASS native DP mode-before-AUX, training signal/gating/enable slots and SDP/stagger caller ABI') diff --git a/tests/display13_tc_preparation.py b/tests/display13_tc_preparation.py new file mode 100644 index 00000000..e0006512 --- /dev/null +++ b/tests/display13_tc_preparation.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Call the real TC mode entry, selector, GOP proof and power/ownership core.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +f='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in ['XELP::LinkRequirements XE::framebufferLinkRequirements(', 'unsigned XE::display13TCTarget(', 'uint32_t XE::wrapDisplay13TCMode(']) +install=s[s.index('bool XE::processDisplay13FramebufferTC('):s.index('bool XE::processDisplay13FramebufferOutput(')] +assert '{entries[Mode].symbol,wrapDisplay13TCMode,orgDisplay13TCMode}' in install +assert 'display13FBTCHooksReady=processDisplay13FramebufferTC(patcher,index,address,size)' in s +# Admission never substitutes an installed prototype for the real mode route. +assert 'display13FBTCHooksReady' in s[s.index('unsigned XE::display13TCTarget('):s.index('uint32_t XE::wrapDisplay13TCMode(')] +code=(r/'tests/display13_tc_preparation_fixture.cpp').read_text().replace('// FUNCTIONS',f) +with tempfile.TemporaryDirectory(prefix='tc-preparation-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_tc_preparation_fixture.cpp b/tests/display13_tc_preparation_fixture.cpp new file mode 100644 index 00000000..df3abb25 --- /dev/null +++ b/tests/display13_tc_preparation_fixture.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include "kern_display13_boot.hpp" +#include "kern_display13_tc_mode.hpp" +#include "kern_display13_fb_memory.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_sst_route.hpp" +struct OSObject {unsigned size=0x700;unsigned refs=0;const OSObject*getMetaClass(){return this;}unsigned getClassSize()const{return size;}void retain(){++refs;}void release(){assert(refs);--refs;}}; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}templatevoid setProperty(T...){} bool getProperty(const char*){return false;}}; +constexpr unsigned kIOReturnSuccess=0,kIOReturnNotReady=0xe00002d8; +#define OSSafeReleaseNULL(p) do{(p)->release();(p)=nullptr;}while(0) +#define FunctionCast(f,p) reinterpret_cast(p) +bool locked=false;void IOLockLock(void*){assert(!locked);locked=true;}void IOLockUnlock(void*){assert(locked);locked=false;} +struct MMIO { + std::map regs;std::vector writes;bool failOwner=false; + bool readable(unsigned r){return r<0x1000000;}unsigned read(unsigned r){return regs[r];} + void delayUs(unsigned){} + void write(unsigned r,unsigned v){ + writes.push_back(r); + if(r==0x45404 || r==0x45444 || r==0x45454){ + const auto mask=r==0x45404?0xa80aU:0x2aaaU;unsigned ack=(v&mask)>>1; + if(r!=0x45404)for(unsigned t=0;t<4;++t){unsigned bit=1u<<(6+2*t); + if(!(regs[0x64300+256*t]&64) || (r==0x45454 && !(regs[0x45444]&bit)))ack&=~bit;} + regs[r]=(v&~(mask>>1))|ack; + } else if(!(failOwner && r>=0x64300 && r<=0x64600))regs[r]=v; + } +}; +struct Context { + Display13Power::NativeBridge bridge;Display13TC::Mode tcMode[4]; + bool tcSupported=true,tcBankFaulted=false,clockFaulted=false;unsigned tcBusy[4]{};OSObject*tcObject[4]{}; + void *lock=(void*)1,*controller=(void*)2,*access=(void*)3;MMIO*map=nullptr; + bool tcSignalReady[4]{};unsigned tcDPLink[4]{}; +} display13FBPower; +struct Display13FBPowerIO {MMIO*map;bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned t){return (map->read(0x45404)&5)==5 && (map->read(0x64300+256*t)&64);}bool tcAuxHealthy(unsigned){return true;}bool readable(unsigned r){return map->readable(r);}unsigned read(unsigned r){return map->read(r);}void write(unsigned r,unsigned v){map->write(r,v);}void delayUs(unsigned n){map->delayUs(n);}}; +using Display13FBBootIO=Display13FBPowerIO; +struct Display13FBTCIO:Display13FBPowerIO { + OSObject*object;unsigned tc; + Display13FBTCIO(OSObject*p,unsigned t):Display13FBPowerIO{display13FBPower.map},object(p),tc(t){} + bool admitted(unsigned t){return t==tc && t<4;} + bool accessHeld(){Display13FBPowerIO io{map};return Display13Power::held(io,display13FBPower.bridge.state,Display13TC::Mode::accessDomains());} + bool tcAuxSupported(){return true;}bool prepareTCAux(unsigned t){return t==tc && accessHeld() && (read(0x64300+256*t)&64);} + bool tcAuxHealthy(unsigned){return true;} + bool quiescent(unsigned d){return !(read(0x64000+256*d)&0x80000000);} +}; +struct XE { + bool display13FramebufferRequested=true,display13FBTCHooksReady=true;XELP::Profile xeProfile; + uint64_t adlnPlatform[0x1c0/sizeof(uint64_t)]{},tglPlatformCopy[0x1c0/sizeof(uint64_t)]{};Display13OutputWiring adlnOutputWiring; + uintptr_t display13TCBaseMode=0,orgDisplay13TCMode=0; + bool tglHDMIEligible(const uint32_t*){return false;}XELP::Display12SSTRoutes tglSSTRoutes(){return {};} + static XELP::LinkRequirements framebufferLinkRequirements(void*,uint32_t); + static unsigned display13TCTarget(OSObject*,bool=false); + static uint32_t wrapDisplay13TCMode(OSObject*,uint32_t); +} instance,*callbackXE=&instance; +// FUNCTIONS +alignas(8) uint8_t portBytes[0x700]; +templatevoid put(unsigned o,T v){memcpy(portBytes+o,&v,sizeof(v));} +unsigned nativeCalls=0; +unsigned base(OSObject*p,unsigned mode){assert(!locked);++nativeCalls;memcpy(reinterpret_cast(p)+0x550,&mode,4);return 0;} +int main(){ + unsigned cases=0; + for(unsigned ddi=3;ddi<=6;++ddi)for(bool dp:{false,true})for(unsigned fault=0;fault<6;++fault){ + display13FBPower.~Context();new(&display13FBPower)Context;auto &c=display13FBPower; + instance=XE{};instance.xeProfile=XELP::identify(0x46a68086,3);instance.display13TCBaseMode=(uintptr_t)base; + MMIO map;c.map=↦map.regs[0x42000]=0x8e380000; + for(unsigned d=3;d<=6;++d){map.regs[0x161500+4*(d-3)]=4;map.regs[0x64000+256*d]=0;} + const unsigned peer=ddi==6?3:6;map.regs[0x64000+256*peer]=0x80000046; + Display13FBPowerIO power{&map};assert(Display13Power::initialize(power,c.bridge.state,Display13Power::nativeDomains(0x7b))); + c.bridge.begun=true;c.bridge.selectedPorts=0x78;c.bridge.bootPort=ddi; + auto &w=instance.adlnOutputWiring;w.valid=true;if(dp)w.dp=1u<(reinterpret_cast(instance.adlnPlatform)+0x20+24*ddi); + unsigned values[]={ddi,ddi+6,dp?1u:2u,1u,dp?0x400u:0x800u,dp?0x201u:0x202u};memcpy(cfg,values,24); + memset(portBytes,0,sizeof(portBytes));auto p=new(portBytes)OSObject; + put(0x548,cfg);put(0x578,c.access);put(0x120,ddi);put(0x590,0x64000+256*ddi);nativeCalls=0; + // cold; valid GOP; live without retained GOP; omitted route; owner readback failure; invalid record. + if(fault==1 || fault==2){ + map.regs[0x45404]=0xfc0f;map.regs[0x45454]=3u<<(2*ddi); + map.regs[0x64000+256*ddi]=0x80000046;map.regs[0x46038+8*(ddi-3)]=0xcc000000; + map.regs[0x46100+4*ddi]=0x80000000;map.regs[0x46140]=(ddi+3)<<28; + map.regs[0x60400]=(dp?0x82030006:0x80030100)|((ddi+1)<<27);map.regs[0x70008]=0xc0000000; + map.regs[0x60540]=0x80040300;map.regs[0x60030]=0x7e100000;map.regs[0x60034]=map.regs[0x60044]=0x800000;map.regs[0x60040]=0x100000; + if(fault==1){auto &l=c.bridge.ddiLease[ddi];l.owner=&c.bridge.state;l.domains=Display13Power::dependencies(Display13Power::ddiDomain(ddi));c.bridge.ddiRefs[ddi]=1;} + } + if(fault==3)instance.display13FBTCHooksReady=false; + if(fault==4)map.failOwner=true; + if(fault==5)cfg[4]=1; + const auto before=map.regs;auto result=XE::wrapDisplay13TCMode(p,4); + if((result==0)!=(fault<=1))fprintf(stderr,"case ddi=%u dp=%u fault=%u phase=%u buffer=%x power=%x aux=%x\n",ddi,dp,fault,unsigned(c.tcMode[ddi-3].phase),map.regs[0x64000+256*ddi],map.regs[0x45404],map.regs[0x45444]); + assert((result==0)==(fault<=1));assert(nativeCalls==(fault<=1?1u:0u)); + if(result==0){assert(c.tcMode[ddi-3].ownership.active && c.tcMode[ddi-3].auxLease.owner);if(fault==1)assert(map.regs[0x64000+256*ddi]==before.at(0x64000+256*ddi));} + for(unsigned other=3;other<=6;++other)if(other!=ddi){assert(map.regs[0x64000+256*other]==before.at(0x64000+256*other));assert(!c.tcMode[other-3].controller);} + assert(!locked);++cases; + } + puts("PASS real TC mode entry: HDMI/fixedDP cold and validated GOP, missing route/identity/ownership refusal, mandatory power/ownership/AUX ordering and peer preservation; simulated MMIO"); +} diff --git a/tests/display13_tiling.py b/tests/display13_tiling.py index eb25a818..f1a51d2a 100644 --- a/tests/display13_tiling.py +++ b/tests/display13_tiling.py @@ -10,8 +10,11 @@ block=source[source.index('\tauto old=OSDynamicCast(OSDictionary,',start):end] code=r''' #include +#define SYSLOG(...) ((void)0) #include #include +#include "kern_display13_fb_topology.hpp" +Display13OutputWiring board; #include #include #include @@ -43,7 +46,12 @@ }; #define OSDynamicCast(T,p) dynamic_cast(p) #define OSSafeReleaseNULL(p) do{p=nullptr;}while(0) +struct Handoff { + bool adlpDisplaySelected=true,ready=true;unsigned calls=0; + bool prepareDisplay13BootScanout(IOService *provider){assert(provider);++calls;return ready;} +} handoff; bool prepare(IOService *that,IOService *provider){ + auto s=&handoff;auto pci=provider; PRODUCTION return true; } @@ -53,8 +61,9 @@ prior.items={{"UnrelatedFeature",&untouched},{"YTiling",&disabled}}; if(oldFeatures)that.items["FeatureControl"]=&prior; if(oldKey)provider.items["AAPL,GfxYTile"]=&key; - step=0;failAt=failure;bool ok=prepare(&that,&provider); - if(failure || !workLoop){assert(!ok);return;} + step=0;failAt=failure;handoff.calls=0;bool ok=prepare(&that,&provider); + if(failure || !workLoop){assert(!ok && !handoff.calls);return;} + assert(handoff.calls==unsigned(handoff.adlpDisplaySelected || (board.hdmi&0x78))); assert(ok);auto f=dynamic_cast(that.getProperty("FeatureControl"));assert(f); assert(dynamic_cast(f->items.at("YTiling"))->value==1); assert(dynamic_cast(f->items.at("ThreadingMode"))->value==0); @@ -63,7 +72,11 @@ assert(oldKey ? data==&key && data->value==keyValue : data->value==1); } int main(){ + {IOService that,provider;handoff.ready=false;assert(!prepare(&that,&provider));assert(handoff.calls==1);handoff.ready=true;} + + for(bool mobile:{false,true})for(unsigned mask:{2U,8U}) for(bool features:{false,true})for(bool key:{false,true})for(uint32_t value:{0U,1U}){ + handoff.adlpDisplaySelected=mobile;board.hdmi=mask; check(features,key,value,0);unsigned operations=step; for(unsigned failure=1;failure<=operations;++failure)check(features,key,value,failure); check(features,key,value,0,false); @@ -72,6 +85,6 @@ '''.replace('PRODUCTION',block) with tempfile.TemporaryDirectory() as tmp: p=Path(tmp);(p/'test.cpp').write_text(code) - subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) subprocess.run([str(p/'test')],check=True) print('PASS: pre-start Y selection, existing-property preservation and setup failure propagation') diff --git a/tests/display13_training_frontend.py b/tests/display13_training_frontend.py new file mode 100644 index 00000000..28ebce3e --- /dev/null +++ b/tests/display13_training_frontend.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Production Display13 temporary binding, framing and deferred clock frontend. + +Uses real native object contracts and the power bridge with mock MMIO; no claim +about physical output or completeness follows from these regression cases. +""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() +def fn(sig):return re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() +functions='\n'.join(fn(x) for x in ['uint8_t *XE::display13SSTPort(', 'bool XE::display13HDMIPort(', 'bool XE::display13MainLinkAUX(', 'uint32_t XE::wrapDisplay13EnablePort(', 'uint32_t XE::wrapDisplay13DisablePort(', 'uint32_t XE::wrapDisplay13SignalLevels(', 'uint32_t XE::wrapDisplay13TrainingPattern(', 'bool XE::display13TrainingPath(', 'uint32_t XE::wrapDisplay13PortTraining(', 'bool XE::display13DeferClock(', 'bool XE::display13StopTranscoder(']) +binding=fn('static TGLFBBinding::Readout adlnInspectPathBinding(') +native=(r/'tests/tgl_fb_binding.cpp').read_text().split('int main()')[0].replace('port[0x580]','port[0x700]') +code=(r/'tests/display13_sst_fixture.cpp').read_text().split('int main(')[0] +code=code.replace('// NATIVE_OBJECTS',native).replace('// BINDING',binding) +lease=re.search(r'\tstruct TGLTrainingLease \{.*?\n\t\};',h,re.S).group() +code=code.replace('struct XE {','''struct XE { + LEASE + TGLTrainingLease tglTrainingLeases[9]; + bool display13TrainingReady=false,display13FramingReady=false; + unsigned trainingLeaseDP13Mask=0;IORecursiveLock *tglPathPoolLock=nullptr; + uintptr_t orgDisplay13PortTraining=0,orgTGLPathLookup=0,tglPortByDDI=0,display13DPImage=0; + static uint32_t wrapDisplay13PortTraining(void*,void*); + static bool display13DeferClock(void*,uint64_t,uint32_t,uintptr_t); +'''.replace('LEASE',lease)) +code=code.replace('static bool display13StopTranscoder(void*){assert(false);return false;}','static bool display13StopTranscoder(void*);') +code=code.replace('static bool display13TrainingPath(void*,uint32_t&,uint32_t&){return false;}','static bool display13TrainingPath(void*,uint32_t&,uint32_t&);') +code=code.replace('// FUNCTIONS',re.search(r'struct Display13DeferredClock .*?static Display13DeferredClock display13DeferredClocks\[3\];',s,re.S).group()+'\n'+functions) +prefix='''#include +#include +#include +using IORecursiveLock=std::recursive_mutex; +void IORecursiveLockLock(IORecursiveLock*p){p->lock();}void IORecursiveLockUnlock(IORecursiveLock*p){p->unlock();} +void *current_thread(){return (void*)1;}unsigned delayUs=0;void IODelay(unsigned n){delayUs+=n;} +void adlnFault(bool &v,unsigned){v=true;} +''' +extra=r''' +void *testPort;void *testLookup(void*p){return p==testPort?currentPath:nullptr;} +void *testByDDI(unsigned ddi){return Display13FBMemory::field(testPort,0x120)==ddi?testPort:nullptr;} +unsigned nativeTrain(void*p,void*){assert(p==testPort&&!locked);return XE::wrapDisplay13TrainingPattern(currentPath,1);} +int main(){IORecursiveLock pool; + for(unsigned ddi=3;ddi<7;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned cap=0;cap<2;++cap)for(unsigned wanted=0;wanted<2;++wanted){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,4,6);testPort=f.port; + state.display13TrainingReady=state.display13FramingReady=state.display13ClockOrderReady=true; + state.trainingLeaseDP13Mask=0x78;state.tglPathPoolLock=&pool;state.display13DPImage=0x100000; + state.orgTGLPathLookup=(uintptr_t)testLookup;state.tglPortByDDI=(uintptr_t)testByDDI;state.orgDisplay13PortTraining=(uintptr_t)nativeTrain; + put(f.path,0x32c8,static_cast(nullptr));put(f.path,0x3644,1U);f.port[0x115]=1;f.port[0x10]=cap;f.port[0x11a]=wanted; + auto &lease=state.tglTrainingLeases[ddi];lease.path=f.path;lease.port=f.port;lease.fb=f.fb;lease.power=display13FBPower.power; + lease.busy=lease.powerOwned=lease.prepared=true;lease.trainingThread=current_thread();lease.ddi=ddi;lease.pipe=pipe; + unsigned d=99,p=99;assert(XE::display13TrainingPath(f.path,d,p)&&d==ddi&&p==pipe); + lease.trainingThread=nullptr;assert(!XE::display13TrainingPath(f.path,d,p));lease.trainingThread=current_thread(); + lease.adopted=true;assert(!XE::display13TrainingPath(f.path,d,p));lease.adopted=false; + put(f.fb,0x49e8,1U);assert(!XE::display13TrainingPath(f.path,d,p));put(f.fb,0x49e8,0U); + assert(!XE::wrapDisplay13PortTraining(f.port,nullptr));assert(f.port[0x11a]==(cap&wanted)); + assert((map.regs[0x60540+0x1000*pipe]&0x40000U)==((cap&wanted)<<18)); + const auto clock=0x46140+4*pipe;assert(map.regs[clock]==((ddi+3)<<28)); + assert(!XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d991)); + assert(XE::display13DeferClock(f.access,clock,0,state.display13DPImage+0x4d990));assert(map.regs[clock]==((ddi+3)<<28)); + map.regs[0x70008+0x1000*pipe]=0;map.regs[0x64000+0x100*ddi]=0x80000000; + assert(!XE::display13StopTranscoder(f.port)&&map.regs[clock]==((ddi+3)<<28)); + map.regs[0x64000+0x100*ddi]=0x80;map.regs[0x60540+0x1000*pipe]&=~0x80000000U; + assert(XE::display13StopTranscoder(f.port)&&map.regs[clock]==0&&!display13DeferredClocks[pipe].pending); + } + puts("PASS production Display13 pre-plane reservation binding, sink framing and DDI-before-transcoder-clock stop; mock MMIO only"); +} +''' +with tempfile.TemporaryDirectory(prefix='display13-training-front-') as d: + p=Path(d);(p/'test.cpp').write_text(prefix+code+extra) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_training_lease.py b/tests/display13_training_lease.py new file mode 100644 index 00000000..53b10498 --- /dev/null +++ b/tests/display13_training_lease.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Shared production lease/adoption with exact donor AGDC and native path lookup. + +Power/PHY/sink boundaries are mocks. This detects integration and ownership +regressions; it does not establish hardware readiness or requirement coverage. +""" +from pathlib import Path +import hashlib,re,struct,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() +fields=h[h.index('\tstruct TGLTrainingLease {'):h.index('\tmach_vm_address_t orgTGLGetOSInformation')] +body=s[s.index('XE::TGLTrainingLease *XE::tglTrainingLeaseForDDI'):s.index('void XE::tglObserveActivation(')] +adopt=s[s.index('bool XE::wrapTGLAllocatePathResources'):s.index('void *XE::wrapTGLAddPath')] +f=(r/'tests/tgl_training_leases_fixture.cpp').read_text().split('int main(')[0] +f=f.replace('// TC_WIRING_FIELDS',h[h.index('\tbool tglAUXParentReady'):h.index('\tstatic void wrapTGLEnableAUXPower')]) +f=f.replace('// LEASE_FIELDS',fields).replace('// LEASE_FUNCTIONS',body+adopt) +b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +# Symbols resolve pool globals and native boundary entries from the accepted donor. +pos=32;symbols={} +for _ in range(struct.unpack_from(' +#include +#include +uint8_t *image;void *nativePaths[4];unsigned linkCalls,clockStarts,clockStops;bool rejectPowerGet,rejectPowerPut,stoppedProof; +bool clockProof(void*,void*){return stoppedProof;} +void jump(unsigned off,uintptr_t target){image[off]=0x48;image[off+1]=0xb8;memcpy(image+off+2,&target,8);image[off+10]=0xff;image[off+11]=0xe0;} +void *builtIn(){return nullptr;}bool hpd(void*){return false;} +uint32_t dpcd(void*){return 0;} +void setLink(void *p,void*){assert(p);} +void setupClock(void*,void*){} +void enableClock(void*,void*,void*,void*){++clockStarts;} +void stopClock(void*c,void*p,void*o){++clockStops;disableClocks(c,p,o);} +void *poolAllocate(){auto p=reinterpret_cast(image+0x9188a)(); + if(p)for(unsigned i=0;i<3;++i)if(p==paths[i].data())used[i]=true;return p;} +void *poolLookup(void*p){return reinterpret_cast(image+0x91bca)(p);} +uint32_t sinkTraining(void*p,void*out){ + ++linkCalls;auto path=poolLookup(p);assert(path); // r23 failed here, before sink/PHY training. + auto &lease=obj.tglTrainingLeases[reinterpret_cast(p)==ports[3].data()?3: + reinterpret_cast(p)==ports[4].data()?4:reinterpret_cast(p)==ports[5].data()?5:6]; + assert(path==lease.path && lease.powerOwned && lease.trainingThread==current_thread()); + assert(pgRefs[lease.pipe?lease.pipe+2:1]==1); + assert(!*reinterpret_cast(static_cast(path)+0x32c8)); // No primary plane yet. + static_cast(out)[0x38/4]=0;return 0; +} +bool adapterValidate(void*c,void*p,uint32_t pipe){ + if(c!=ctl.data() || !p)return false; + return pipe==UINT32_MAX || (pipe<3 && pgRefs[pipe?pipe+2:1]>0); +} +bool adapterPower(void*p,uint32_t pg,bool get){ + if((get&&rejectPowerGet)||(!get&&rejectPowerPut))return false; + if(get)enablePG(p,pg);else disablePG(p,pg);return true; +} +void init(unsigned ddi,unsigned pipe){ + setup();rejectPowerGet=rejectPowerPut=false;linkCalls=clockStarts=clockStops=0; + obj.trainingLeaseDP13Mask=0x78;obj.trainingLeaseValidate=adapterValidate;obj.trainingLeasePower=adapterPower; + obj.orgTGLPathLookup=(uintptr_t)poolLookup;obj.orgTGLAllocatePathAny=(uintptr_t)poolAllocate; + obj.orgTGLTrainingBegin=(uintptr_t)(image+0x6aae0);obj.orgTGLTraining=(uintptr_t)(image+0x6ab7e);obj.orgTGLTrainingEnd=(uintptr_t)(image+0x6ad06); + for(unsigned i=0;i<3;++i){nativePaths[i]=paths[i].data();paths[i].at(0x3644)=i==pipe?0:2;} + // Other native pipes are reserved; free pipe is selected by the actual allocator. + static uint32_t config[6];config[0]=ddi;config[2]=1;ports[ddi].at(0x548)=config;ports[ddi].at(0x11)=4; + static uintptr_t vt[64];vt[0x130/8]=(uintptr_t)disablePort;ports[ddi].at(0)=vt; +} +int main(int argc,char**argv){ + std::ifstream input(argv[1],std::ios::binary);std::vector b((std::istreambuf_iterator(input)),{}); + image=(uint8_t*)mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0);assert(image!=MAP_FAILED);memcpy(image,b.data(),b.size()); + INSTALL + assert(image[0x6aba6]==0xe8);jump(0x300000,(uintptr_t)memcpy); + int32_t copyCall=0x300000-0x6aba6-5;memcpy(image+0x6aba7,©Call,4); + *reinterpret_cast(image+PATHS)=nativePaths;*reinterpret_cast(image+COUNT)=3; + for(unsigned ddi=3;ddi<7;++ddi)for(unsigned pipe=0;pipe<3;++pipe){ + init(ddi,pipe);uint32_t req[20]={ddi+1},out[20]={};req[4]=4U<<24; + assert(!XE::wrapTGLTrainingBegin(ctl.data(),req));assert(!poolLookup(ports[ddi].data())); + assert(!XE::wrapTGLTraining(ctl.data(),req,out));auto &lease=obj.tglTrainingLeases[ddi];auto path=lease.path; + assert(path==paths[pipe].data()&&linkCalls==1&&clockStarts==1); + assert(!XE::wrapTGLTraining(ctl.data(),req,out));assert(lease.path==path&&clockStops==1&&clockStarts==2); + req[2]=1;assert(!XE::wrapTGLTrainingEnd(ctl.data(),req));assert(lease.path==path); + // Native resource allocation acquires its own PG before returning. The + // production wrapper must return only the reservation's extra reference. + nativeBoundary=[&]{enablePG(power.data(),lease.pg);}; + assert(XE::wrapTGLAllocatePathResources(fbs[0].data(),ddi,0));nativeBoundary={}; + assert(!lease.path&&pgRefs[pipe?pipe+2:1]==1);disablePG(power.data(),pipe?pipe+2:1);freePath(path);clean(); + // Abort End consumes native stop exactly once and releases the reservation. + init(ddi,pipe);req[2]=0;assert(!XE::wrapTGLTraining(ctl.data(),req,out)); + assert(!XE::wrapTGLTrainingEnd(ctl.data(),req));assert(!obj.tglTrainingLeases[ddi].path&&clockStops==1);clean(); + } + // A void native clock-stop return is not a completed cleanup. + init(5,1);uint32_t endReq[20]={6},endOut[20]={}; + assert(!XE::wrapTGLTraining(ctl.data(),endReq,endOut));obj.trainingLeaseStop=clockProof;stoppedProof=false; + assert(XE::wrapTGLTrainingEnd(ctl.data(),endReq)==kIOReturnBusy&&clockStops==1); + assert(obj.tglTrainingLeases[5].path&&obj.tglTrainingLeases[5].powerOwned); + stoppedProof=true;assert(!XE::wrapTGLTrainingEnd(ctl.data(),endReq)&&clockStops==1);clean(); + // An in-flight call blocks native teardown. The refusal is bounded and the + // controller stays retained until a later real stop can drain the pool. + init(5,1);assert(!XE::wrapTGLTraining(ctl.data(),endReq,endOut)); + obj.tglTrainingLeases[5].busy=true;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(!stopCalled&&obj.tglStopRetained&&retained[ctl.data()]==1); + obj.tglTrainingLeases[5].busy=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(stopCalled&&!obj.tglStopRetained);clean(); + // Adoption owns the native path even when our extra PG put fails. Stop + // retries only that extra reference and must not inspect the adopted path. + init(5,1);assert(!XE::wrapTGLTraining(ctl.data(),endReq,endOut));rejectPowerPut=true; + nativeBoundary=[&]{enablePG(power.data(),obj.tglTrainingLeases[5].pg);}; + assert(!XE::wrapTGLAllocatePathResources(fbs[0].data(),5,0));nativeBoundary={}; + auto &adopted=obj.tglTrainingLeases[5];auto nativePath=adopted.path;auto nativePG=adopted.pg; + assert(adopted.adopted&&adopted.powerOwned&&pgRefs[nativePG]==2); + adopted.path=(void*)1; // Native-owned path may already be destroyed. + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(!stopCalled&&obj.tglStopRetained&&adopted.adopted); + rejectPowerPut=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(stopCalled&&!adopted.path&&pgRefs[nativePG]==1); + disablePG(power.data(),nativePG);freePath(nativePath);clean(); + // Failed power acquisition retains the reservation and cannot train on retry. + init(5,1);uint32_t req[20]={6},out[20]={};rejectPowerGet=true; + assert(XE::wrapTGLTraining(ctl.data(),req,out)==kIOReturnBusy&&linkCalls==0); + assert(XE::wrapTGLTraining(ctl.data(),req,out)==kIOReturnBusy&&linkCalls==0); + XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(!stopCalled&&obj.tglStopRetained&&obj.tglTrainingLeases[5].path&&!obj.tglTrainingLeases[5].powerOwned); + assert(retained[ctl.data()]==1); // Unproven transition remains quarantined, not synthetic cleanup. + munmap(image,0x400000); + puts("PASS production Display13 lease dispatch with native AGDC Begin/Train/retry/End and pool lookup; mock power/sink/adoption boundaries"); +} +''' +install=[] +for symbol,fn in [('__ZN14AppleIntelPort14getPortByIndexEj','getPort'),('__ZN14AppleIntelPort14getBuiltInPortEv','builtIn'),('__ZN14AppleIntelPort8isHPDLowEv','hpd'),('__ZN14AppleIntelPort11getDPCDInfoEv','dpcd'),('__ZN14AppleIntelPort13setLinkConfigEP18AGDCDPLinkConfig_t','setLink'),('__ZN31AppleIntelFramebufferController11setupClocksEP14AppleIntelPort','setupClock'),('__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams','enableClock'),('__ZN31AppleIntelFramebufferController13disableClocksEP14AppleIntelPortS1_','stopClock'),('__ZN14AppleIntelPort12linkTrainingEP18AGDCDPPortConfig_t','sinkTraining')]: + install.append(f'jump(0x{symbols[symbol]:x},(uintptr_t){fn});') +extra=extra.replace('INSTALL','\n '.join(install)).replace('PATHS',str(symbols['__ZN21AppleIntelDisplayPath6fPathsE'])).replace('COUNT',str(symbols['__ZN21AppleIntelDisplayPath10fPathCountE'])) +with tempfile.TemporaryDirectory(prefix='display13-training-') as d: + p=Path(d);(p/'check.cpp').write_text(f+extra) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'check.cpp'),'-o',str(p/'check')],check=True) + subprocess.run([str(p/'check'),sys.argv[1]],check=True) diff --git a/tests/display13_wm.cpp b/tests/display13_wm.cpp index 80a91d05..85130b0d 100644 --- a/tests/display13_wm.cpp +++ b/tests/display13_wm.cpp @@ -349,9 +349,58 @@ int main() { assert(latch(wrongWM,wrongWM.snapshot(p)).status==Status::Readback); LatchIO wrongLine(p);wrongLine.moveToPipe(pipe);wrongLine.regs[0x45270+4*pipe]=118; assert(latch(wrongLine,wrongLine.snapshot(p)).status==Status::Readback); + // The diagnostic must retain the rejecting branch and the values + // already consumed by it, including pipe C, without probing farther. + LatchIO disabledPlane(p);disabledPlane.moveToPipe(pipe); + disabledPlane.regs[0x70180+0x1000*pipe]&=~Enabled; + const auto controls=latch(disabledPlane,disabledPlane.snapshot(p)); + assert(controls.status==Status::Conflict && controls.check==LatchCheck::Controls && + controls.pipeControl==0xC0000000 && controls.planeControl==0x04000000 && + !controls.cursorControl && disabledPlane.reads.back()==0x70080+0x1000*pipe && disabledPlane.writes.empty()); + // Native scaler flush before the first attached scanout: timing and + // WM are programmed, but the primary remains disabled and SURF is 0. + // This is pending, not success; a later enabled surface must earn the + // ordinary frame/live proof. The same state applies on every pipe. + LatchIO preparing(p);preparing.moveToPipe(pipe);const auto bank=0x1000*pipe; + preparing.regs[0x70180+bank]=0;preparing.regs[0x7019C+bank]=0; + preparing.regs[0x70040+bank]=0;preparing.surface=0; + const auto pending=latch(preparing,preparing.snapshot(p)); + assert(pending.status==Status::Unavailable && pending.check==LatchCheck::AwaitingScanout && + preparing.reads.back()==Display13FBIRQ::DSBControl(pipe,2) && preparing.writes.empty() && !preparing.elapsed); + // Pending cannot hide a foreign consumer or a changed shared clock. + preparing.regs[0x70280+bank]=Enabled; + assert(latch(preparing,preparing.snapshot(p)).status==Status::Conflict); + preparing.regs[0x70280+bank]=0;preparing.regs[0x70B08+bank]=1; + assert(latch(preparing,preparing.snapshot(p)).status==Status::Conflict); + preparing.regs[0x70B08+bank]=0;auto intendedClock=preparing.snapshot(p); + preparing.clock=384000000; + assert(latch(preparing,intendedClock).status==Status::Conflict); + preparing.clock=614400000; + preparing.regs[0x70180+bank]=0x84000000; + preparing.regs[0x7019C+bank]=preparing.surface=0x100000; + assert(latch(preparing,preparing.snapshot(p)).status==Status::Complete && preparing.elapsed==2000); + LatchIO badBlank(p);badBlank.moveToPipe(pipe);badBlank.surface=0; + badBlank.regs[0x70180+bank]=UINT32_MAX; + assert(latch(badBlank,badBlank.snapshot(p)).status==Status::Conflict); + LatchIO blankWithCursor(p);blankWithCursor.moveToPipe(pipe);blankWithCursor.surface=0; + blankWithCursor.regs[0x70180+bank]=0;blankWithCursor.regs[0x70080+bank]=7; + assert(latch(blankWithCursor,blankWithCursor.snapshot(p)).status==Status::Conflict); + LatchIO peerPlane(p);peerPlane.moveToPipe(pipe);peerPlane.regs[0x70280+0x1000*pipe]=Enabled; + const auto peer=latch(peerPlane,peerPlane.snapshot(p)); + assert(peer.status==Status::Conflict && peer.check==LatchCheck::OtherPlane && + peer.checkRegister==0x70280+0x1000*pipe && peer.checkValue==Enabled && + peerPlane.reads.back()==peer.checkRegister && peerPlane.writes.empty()); + LatchIO otherClock(p);otherClock.moveToPipe(pipe);auto beforeClock=otherClock.snapshot(p); + otherClock.clock=384000000; + const auto clockProof=latch(otherClock,beforeClock); + assert(clockProof.status==Status::Conflict && clockProof.check==LatchCheck::Clock && + clockProof.clockHz==384000000 && otherClock.writes.empty()); for(auto control:{1U,0x80000000U,UINT32_MAX}) { LatchIO dsb(p);dsb.moveToPipe(pipe);dsb.regs[0x70B08+0x1000*pipe]=control; - assert(latch(dsb,dsb.snapshot(p)).status==Status::Conflict && !dsb.elapsed); + const auto proof=latch(dsb,dsb.snapshot(p)); + assert(proof.status==Status::Conflict && !dsb.elapsed && proof.check==LatchCheck::DSB && + proof.checkRegister==0x70B08+0x1000*pipe && proof.checkValue==control && + dsb.reads.back()==proof.checkRegister && dsb.writes.empty()); } } // PW2 is a physical prerequisite for pipe B/C even if its own well is on. diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py index cad926a2..f4576490 100644 --- a/tests/display_backend_dispatch.py +++ b/tests/display_backend_dispatch.py @@ -12,24 +12,27 @@ kernel=s[kernel_start:kernel_end] bootstrap=s[s.index('void XE::adlnBootstrap('):s.index('bool XE::xelpBindProbe(')] def expression(name): - return re.findall(r'\b'+name+r'=(.*?);',bootstrap,re.S)[-1] + return re.findall(r'\b'+name+r'\s*=\s*(.*?);',bootstrap,re.S)[-1] code=r''' #include +#define SYSLOG(...) ((void)0) #include #include #include #include using mach_vm_address_t=uintptr_t; struct KernelPatcher {}; -struct Kext {size_t loadIndex;}; +struct Kext {size_t loadIndex;};Kext kextIntelTGL{2}; bool loadOnly=false; bool checkKernelArgument(const char *arg) {assert(!strcmp(arg,"-wex-adln-load-only"));return loadOnly;} struct XE { + bool adlpDisplaySelected=false,adlpRenderRequested=false,tglRequested=false; bool display13FramebufferRequested=false,adlnRequested=false,adlnDetected=false; - bool adlnNativeRequested=false,adlnNativeReady=false,tglActive=false; + bool adlnNativeRequested=false,adlnNativeReady=false,display13BootReady=false,tglActive=false; Kext *currentFramebuffer=nullptr,*currentGraphics=nullptr; enum Call {None,Display13,Render,Rejected,LoadOnly,Display12};Call call=None; - bool processDisplay13Framebuffer(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display13;return true;} + bool processFramebufferKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=display13FramebufferRequested?Display13:Display12;return true;} + bool processADLPLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} bool processTGLAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Render;return true;} bool processAlderLakeNRejectedAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} bool processAlderLakeNLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=LoadOnly;return true;} @@ -61,13 +64,16 @@ def expression(name): if(!strcmp(name,"-allow3d"))return bool(args&1); if(!strcmp(name,"-disablegfxfirmware"))return bool(args&2); if(!strcmp(name,"-wex-adln-scanout"))return bool(args&4); - assert(!strcmp(name,"-wex-adln-gpu"));return bool(args&8); + if(!strcmp(name,"-wex-adln-gpu"))return bool(args&8); + assert(!strcmp(name,"-wex-noaccel"));return bool(args&16); }; struct PCI {unsigned command;unsigned configRead16(unsigned r){assert(r==4);return command;}}device; auto pci=&device; struct Aperture {uint64_t length;uint64_t getLength(){return length;}}bar; struct {AlderLakeN::GTStatus status;}gt; - for(unsigned mask=0;mask<4096;++mask) { + bool adlnRequested=true,adlnKernelReady=true; + const auto xelpDisplay13RenderDevice=[](PCI*){return true;}; + for(unsigned mask=0;mask<8192;++mask) { gt.status=(mask&1)?AlderLakeN::GTStatus::Complete:AlderLakeN::GTStatus::Failed; unsigned edram=(mask&2)?0:1; bool prefix=mask&4; @@ -75,15 +81,35 @@ def expression(name): unsigned page_size=(mask&32)?4096:16384; bool physicalOptions=mask&64;device.command=(mask&128)?6:2; args=mask>>8; - const bool renderReady=RENDER; + const bool renderRequested=RENDER_REQUEST; + const bool renderReady=renderRequested && (RENDER); const bool memoryReady=MEMORY; + const bool display13BootReady=DISPLAY; const bool adlnNativeReady=COMBINED; - // All eight platform prerequisites + both required arguments, no diagnostics. + // Display needs every memory prerequisite, independently of GT or noaccel. + assert(display13BootReady==((mask&0xffc)==0x3fc)); + // Render still needs all prerequisites and must be disabled by noaccel. assert(adlnNativeReady==(mask==0x3ff)); } - puts("PASS 4096 shared preflight combinations retain every admission prerequisite"); + puts("PASS 8192 FB/render preflight combinations: noaccel preserves FB memory, never admits render"); } int main() { + for(unsigned module=1;module<=3;++module){ + XE x;KernelPatcher p;Kext fb{1};x.adlpDisplaySelected=true;x.display13FramebufferRequested=true;x.currentFramebuffer=&fb; + const bool result=x.dispatch(p,module,0,0); + assert(x.call==(module==1?XE::Display13:module==2?XE::Rejected:XE::None));assert(result==(module!=3)); + } + + for(bool ready:{false,true})for(unsigned module=1;module<=3;++module) { + XE x;KernelPatcher p;Kext fb{1},graphics{2}; + x.adlpDisplaySelected=x.adlpRenderRequested=x.display13FramebufferRequested=true; + x.adlnRequested=x.adlnDetected=x.adlnNativeRequested=true;x.adlnNativeReady=x.tglActive=ready; + x.currentFramebuffer=&fb;x.currentGraphics=&graphics; + const bool result=x.dispatch(p,module,0,0); + assert(x.call==(module==1?XE::Display13:module==2?(ready?XE::Render:XE::Rejected):XE::None)); + assert(result==(module!=3)); + } + certificates(); Kext fb{1},graphics{2};KernelPatcher patcher;unsigned cases=0; // Removing/rejecting Render must not redirect a selected Display13 kext @@ -100,11 +126,11 @@ def expression(name): else assert(result && x.call==(render==1?XE::Rejected:render==2?XE::Render:XE::LoadOnly)); ++cases; } - for(bool render:{false,true})for(bool display:{false,true})for(bool ready:{false,true}) { + for(bool render:{false,true})for(bool display:{false,true})for(bool ready:{false,true})for(bool gt:{false,true}) { XE x;x.adlnDetected=render;x.display13FramebufferRequested=display; - x.adlnNativeRequested=render;x.adlnNativeReady=ready;x.kernel(patcher); + x.adlnNativeRequested=render;x.adlnNativeReady=ready&>x.display13BootReady=ready;x.kernel(patcher); assert(x.firmware==int(render||display) && x.pool==x.firmware && x.bootstrap==x.firmware); - assert(x.vm==int(render&&ready) && x.mmio==x.vm); + assert(x.vm==int((render||display)&&ready) && x.mmio==x.vm); } // Missing graphics object and missing native request never bypass rejection. for(bool detected:{false,true}) { @@ -115,7 +141,7 @@ def expression(name): } printf("PASS %u independent Display/Render dispatch cases, native preflight rejection and load-only routing\n",cases); } -'''.replace('BODY',body).replace('KERNEL',kernel).replace('RENDER',expression('renderReady')).replace('MEMORY',expression('memoryReady')).replace('COMBINED',expression('adlnNativeReady')).replace('#include ','#include \n#include ') +'''.replace('BODY',body).replace('KERNEL',kernel).replace('RENDER_REQUEST',expression('renderRequested')).replace('RENDER',expression('renderReady')).replace('DISPLAY',expression('display13BootReady')).replace('MEMORY',expression('memoryReady')).replace('COMBINED',expression('adlnNativeReady')).replace('#include ','#include \n#include ') with tempfile.TemporaryDirectory(prefix='wex-display-dispatch-') as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py index a1800d1b..7f3674fd 100644 --- a/tests/display_contract_adapter.py +++ b/tests/display_contract_adapter.py @@ -16,6 +16,7 @@ using C=XELP::DisplayContract; struct State { FIELDS + bool display13FBDPHooksReady=false,display13TCDPProbeReady=false,display13ClockOrderReady=true; struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; bool tglHDMIB=false,adlsDisplaySelected=false,tglTrainingLeaseRequested=true; struct {struct {bool valid=false;}panel;}firmwarePorts; @@ -27,8 +28,12 @@ }; int main() { State s; - assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM)); - assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13)); + s.framebufferDonorRuntimeReady=false; + assert(s.start().missing()==C::DonorRuntime); + assert(C::display13(s.display13Available()).missing()==C::DonorRuntime); + s.framebufferDonorRuntimeReady=true; + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM|C::DonorRuntime)); + assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13|C::Training12|C::Framing)); assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); s.display13DPProbeReady=false; assert(C::display13(s.display13Available()).ready()); // Existing provider/HDMI stays valid. @@ -38,6 +43,14 @@ s.display13FBAUXHooksReady=true;s.display13FBSSTHooksReady=false; assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::SST13)); s.display13FBSSTHooksReady=true; + s.display13FBDPHooksReady=true;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); + s.display13TCDPProbeReady=true;assert(C::display13(s.display13Available(),true).ready()); + s.display13TrainingReady=false;assert(C::display13(s.display13Available(),true).missing()==C::Training12); + s.display13TrainingReady=true;s.display13ClockOrderReady=false; + assert(C::display13(s.display13Available(),true).missing()==C::ClockOrder); + s.display13ClockOrderReady=true;s.display13FramingReady=false; + assert(C::display13(s.display13Available(),true).missing()==C::Framing);s.display13FramingReady=true; + s.display13FBDPHooksReady=false;assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); // TGL: candidate not yet registered, native donor setup is complete. // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. s.tglTCAutoCandidates=8;assert(s.start().ready()); @@ -60,7 +73,7 @@ return 0; } ''' -code=code.replace('FIELDS','\n'.join(' bool '+f+'=true;' for f in fields)).replace('METHODS','\n'.join(methods)).replace('ADAPTER',adapter) +code=code.replace('FIELDS','\n'.join(' bool '+f+'=true;' for f in dict.fromkeys(fields))).replace('METHODS','\n'.join(methods)).replace('ADAPTER',adapter) with tempfile.TemporaryDirectory(prefix='display-contract-') as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index 206cab7e..be3f2d0b 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -51,14 +51,16 @@ int main(int argc,char **argv){ // Type-C capability never becomes an automatic fixed-DP mode decision. assert(r.port[4].capabilities==3&&r.port[4].dp()); // All six port coordinates and capability combinations, independent of - // CPU model. Current runtime auto backend is TC1/2 only (native FIA1). + // CPU model. Runtime automatic fixed-DP backend covers native TC1..4. + const unsigned directAUX[]={0x30,0x50,0x60,0x70,0x80,0x90}; for(unsigned d=3;d<9;++d)for(unsigned caps=0;caps<4;++caps){ const unsigned dvo[]={9,11,13,15,17,19}; - auto b=table({{0x60d6,dvo[d-3],d+1,(d+1)*0x10,0xc0,caps}}); + auto b=table({{0x60d6,dvo[d-3],d+1,directAUX[d-3],0xc0,caps}}); auto q=parse(b.data(),b.size(),Family::TigerLake); assert(q.tglDPCandidates(0x1f8)==(1U< +#include +#include "kern_xelp_platform.hpp" +#include "kern_vbt.hpp" +constexpr unsigned kIODirectionIn=1,kIOMapReadOnly=2; +struct OSData {static OSData *withBytes(const void*,unsigned){static OSData d;return &d;}void release(){}}; +struct IOMemoryDescriptor { + struct Map {uintptr_t getVirtualAddress(){return 0;}unsigned getLength(){return 0;}void release(){}}; + static IOMemoryDescriptor *withPhysicalAddress(uint64_t,unsigned,unsigned){return nullptr;} + Map *map(unsigned){return nullptr;}void release(){} +}; +struct IOPCIDevice {unsigned reads=0,publications=0;uint32_t configRead32(unsigned off){assert(off==0xfc);++reads;return 0;} + void setProperty(const char*,uint32_t,unsigned){++publications;}void setProperty(const char*,const void*,unsigned){++publications;}void setProperty(const char*,OSData*){++publications;} +}; +struct XE {XELP::Profile xeProfile;XELP::VBT::Ports firmwarePorts;void xelpReadFirmware(IOPCIDevice*);}; +BODY +int main(){ + for(unsigned kind=0;kind<5;++kind){XE x;IOPCIDevice pci; + x.xeProfile=kind==0?XELP::identify(0x46a68086,12,0x9a498086):kind==1?XELP::identify(0x46a68086,12):kind==2?XELP::identify(0x9a498086,1):kind==3?XELP::identify(0x46d18086,0):XELP::identify(0x4c8a8086,0); + const bool expected=kind!=4; + if(!kind)assert(x.xeProfile.admitted() && x.xeProfile.displayAdmitted()); + x.xelpReadFirmware(&pci);assert(pci.reads==unsigned(expected));assert(bool(pci.publications)==expected); + x.xelpReadFirmware(nullptr); + } + puts("PASS production VBT acquisition: physical and legacy-selector ADL-P plus TGL/ADL-N read firmware; unsupported RKL remains closed");} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='firmware-readout-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/fixtures/boot_argument_names.json b/tests/fixtures/boot_argument_names.json index d9d7603d..afbfff46 100644 --- a/tests/fixtures/boot_argument_names.json +++ b/tests/fixtures/boot_argument_names.json @@ -135,5 +135,8 @@ "-wxebeta" ], "-wex-noaccel": [], - "-wex-display-trace": [] + "-wex-display-trace": [], + "-wex-adlp-readout": [], + "-wex-adlp-load-only": [], + "-wex-display13-offline": [] } diff --git a/tests/framebuffer_common.py b/tests/framebuffer_common.py new file mode 100644 index 00000000..38c9a3b3 --- /dev/null +++ b/tests/framebuffer_common.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Replay common-before-backend installation and the mandatory start boundary.""" +from pathlib import Path +import re,subprocess,tempfile,sys +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['processFramebufferKext','processFramebufferCommon','wrapFramebufferStart'] +f='\n'.join(re.search(r'bool XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) +# The ABI entries have one installer and one trampoline owner. This checks +# architecture, separately from the CPU/runtime behavioral fixtures below. +for symbol in ['__ZN21AppleIntelFramebuffer4initEP31AppleIntelFramebufferControllerj', + '__ZN14AppleIntelPort16computeLaneCountEPK29IODetailedTimingInformationV2jjPj', + '__ZN31AppleIntelFramebufferController5startEP9IOService', + '__ZN31AppleIntelFramebufferController12CallBackAGDCE31kAGDCRegisterLinkControlEvent_tmj', + '__ZN21AppleIntelFramebuffer13newUserClientEP4taskPvjPP12IOUserClient']: + enabled=re.sub(r'bool XE::processADLPLoadOnly\(.*?\n\}', '', s, flags=re.S) + assert enabled.count('"'+symbol+'"')==1,symbol +assert s.count('processFramebufferCommon(patcher,index,address,size)')==1 +assert s.count('tglInstallSonomaSurfaceABI(patcher,index,address,size,true)')==1 +assert s.count('xelpInstallNVRAMABI(patcher,index,address,size)')==1 +assert s.count('processFramebufferDonorRuntime(patcher,index,address,size)')==1 +code=r''' +#include +#include "kern_xelp_combo_clock.hpp" +struct {void *lock=(void*)1;XELPComboClock::ClockAllocation allocation;} comboClockTransaction; +#include +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +void IORecursiveLockLock(void*){}void IORecursiveLockUnlock(void*){} +struct IOService {void setProperty(const char*,bool) {}}; +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + templatevoid setProperty(T...){} +}; +bool surfaceOK=true,nvramOK=true,tglSonomaSurfaceFramebufferReady=false; +uintptr_t offset(const char *s){ + if(strstr(s,"5start"))return 0x3f1fc; + if(strstr(s,"__ZTV"))return 0xccad0; + if(strstr(s,"4init"))return 0x29ec8; + assert(strstr(s,"computeLaneCount"));return 0x1fc22; +} +struct KernelPatcher { + enum class Error {NoError,Failed};static constexpr unsigned KernelID=999; + struct RouteRequest {const char *name;uintptr_t *original; + templateRouteRequest(const char*s,F,uintptr_t &o):name(s),original(&o) {}}; + unsigned attempts=0,failAt=0;bool error=false; + templateT solveSymbol(size_t,const char *s,uintptr_t a=0,size_t=0){return a? a+offset(s):0x1234;} + bool routeMultiple(size_t,RouteRequest *rs,unsigned count,uintptr_t a,size_t){ + for(unsigned i=0;i(*rs[i].original)=0xcc;} + return true; + } + bool routeMultipleShort(size_t n,RouteRequest *r,unsigned c,uintptr_t a,size_t z){return routeMultiple(n,r,c,a,z);} + void routeBlock(uintptr_t at,const uint8_t *data,size_t size){++attempts;if(attempts==failAt){error=true;return;}memcpy((void*)at,data,size);} + Error getError(){return error?Error::Failed:Error::NoError;}void clearError(){error=false;} +}; +bool tglInstallSonomaSurfaceABI(KernelPatcher&,size_t,uintptr_t,size_t,bool){return surfaceOK;} +bool xelpInstallNVRAMABI(KernelPatcher&,size_t,uintptr_t,size_t){return nvramOK;} +struct XE { + bool framebufferDonorAttempted=false,framebufferDonorCommonReady=false,framebufferBackendInstalled=false; + bool display13FramebufferRequested=false,tglPlatformNVRAMReady=false,tglProbeReady=false,tglFramebufferInitReady=false; + void *tglPathPoolLock=(void*)1,*framebufferStartedController=nullptr;bool framebufferStartInProgress=false,tglLeasesStopping=false; + bool runtimeOK=true,agdcOK=true,backendOK=true,trainingOK=true,framebufferTrainingHooksReady=false; + uintptr_t orgFramebufferStart=0,orgFramebufferInit=0,orgFramebufferComputeLanes=0,framebufferLaneCaller=0; + uintptr_t xelpProviderProbe=0x2000,xelpProviderConfigureProbe=0x3000; + unsigned commonRuntimeCalls=0,backendCalls=0,startCalls=0;bool stopDuringStart=false,nativeOK=true; + bool processFramebufferKext(KernelPatcher&,size_t,uintptr_t,size_t); + bool processFramebufferCommon(KernelPatcher&,size_t,uintptr_t,size_t); + bool processFramebufferDonorRuntime(KernelPatcher&,size_t,uintptr_t,size_t){++commonRuntimeCalls;return runtimeOK;} + bool processFramebufferAGDC(KernelPatcher&,size_t,uintptr_t,size_t){return agdcOK;} + bool processFramebufferTraining(KernelPatcher&,size_t,uintptr_t,size_t){return trainingOK;} + bool backend(KernelPatcher&,size_t,uintptr_t,size_t){++backendCalls; + assert(framebufferDonorAttempted && orgFramebufferStart && commonRuntimeCalls==1); + IOService c;assert(!wrapFramebufferStart(&c,&c));return backendOK;} + bool processDisplay13Framebuffer(KernelPatcher&p,size_t i,uintptr_t a,size_t z){assert(display13FramebufferRequested);return backend(p,i,a,z);} + bool processKextFull(KernelPatcher&p,size_t i,uintptr_t a,size_t z){assert(!display13FramebufferRequested);return backend(p,i,a,z);} + static bool wrapFramebufferStart(IOService*,IOService*); + static bool wrapDisplay13FramebufferStart(IOService*,IOService*); + static bool wrapTGLFramebufferStart(IOService*,IOService*); + static void wrapTGLProbePort(){}static void wrapFramebufferInit(){}static void wrapFramebufferComputeLanes(){} +}; +XE *callbackXE; +bool XE::wrapDisplay13FramebufferStart(IOService*,IOService*){assert(callbackXE->display13FramebufferRequested);assert(!callbackXE->framebufferStartedController);if(callbackXE->stopDuringStart)callbackXE->tglLeasesStopping=true;++callbackXE->startCalls;return callbackXE->nativeOK;} +bool XE::wrapTGLFramebufferStart(IOService*,IOService*){assert(!callbackXE->display13FramebufferRequested);assert(!callbackXE->framebufferStartedController);if(callbackXE->stopDuringStart)callbackXE->tglLeasesStopping=true;++callbackXE->startCalls;return callbackXE->nativeOK;} +FUNCTIONS +int main(int argc,char **argv){ + std::vector image(0x150000); + if(argc>1){std::ifstream f(argv[1],std::ios::binary);image.assign(std::istreambuf_iterator(f),{});assert(image.size()>0xcccd8);} + const uint8_t start[]={START},init[]={INIT},lane[]={LANE},call[]={CALL},hal[]={HAL}; + for(auto span: {std::pair>{0x3f1fc,{start,start+sizeof(start)}}, + {0x29ec8,{init,init+sizeof(init)}},{0x1fc22,{lane,lane+sizeof(lane)}}, + {0x304f3,{call,call+sizeof(call)}},{0x5ee5e,{hal,hal+sizeof(hal)}}}){ + if(argc>1)assert(!memcmp(image.data()+span.first,span.second.data(),span.second.size())); + else memcpy(image.data()+span.first,span.second.data(),span.second.size()); + } + for(bool display13:{false,true})for(unsigned failure=0;failure<13;++failure){ + auto b=image;uintptr_t pure=0x1234;memcpy(b.data()+0xccad0+0x188,&pure,sizeof(pure)); + XE x;callbackXE=&x;x.display13FramebufferRequested=display13;KernelPatcher p; + surfaceOK=failure!=5;nvramOK=failure!=6;x.runtimeOK=failure!=7;x.agdcOK=failure!=8;x.backendOK=failure!=9;x.trainingOK=failure!=10;x.stopDuringStart=failure==11;x.nativeOK=failure!=12; + if(failure>=1&&failure<=4)p.failAt=failure; + bool handled=x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); + assert(handled==(failure!=1)); + IOService c;assert(XE::wrapFramebufferStart(&c,&c)==(failure==0)); + assert(x.startCalls==((failure==0||failure==11||failure==12)?1U:0U)); + assert(x.framebufferStartedController==(failure==0?static_cast(&c):nullptr)); + if(failure==0)assert(!XE::wrapFramebufferStart(&c,&c)&&x.startCalls==1); + const auto calls=p.attempts,backends=x.backendCalls; + x.processFramebufferKext(p,1,(uintptr_t)b.data(),b.size()); + assert(p.attempts==calls&&x.backendCalls==backends); // no second trampoline + } + puts("PASS common-before-backend order, single ownership, partial failures and mandatory native-start gate"); +} +'''.replace('FUNCTIONS',f) +for key,name in [('START','entry'),('INIT','initEntry'),('LANE','laneEntry'),('CALL','laneCall'),('HAL','halSize')]: + code=code.replace(key,re.search(r'\b'+name+r'\[\]\s*=\s*\{([^}]+)',f).group(1)) +with tempfile.TemporaryDirectory(prefix='wex-common-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/framebuffer_donor.cpp b/tests/framebuffer_donor.cpp new file mode 100644 index 00000000..96a923d8 --- /dev/null +++ b/tests/framebuffer_donor.cpp @@ -0,0 +1,53 @@ +#include "kern_framebuffer_donor.hpp" +#include +#include +#include +#include +#include +struct Graph { + std::vector fb=std::vector(0x4b90),controller=std::vector(0x1c20),manager=std::vector(0x68); + std::array,15> planes; + std::array,6> scalers; + std::map sizes; + size_t size(void *p){auto i=sizes.find(p);return i==sizes.end()?0:i->second;} + templatevoid put(void *p,size_t offset,T value){memcpy(static_cast(p)+offset,&value,sizeof(value));} + Graph(){ + sizes[fb.data()]=fb.size();sizes[controller.data()]=controller.size();sizes[manager.data()]=manager.size(); + put(fb.data(),0x1d0,controller.data());put(fb.data(),0x1dc,1U);put(controller.data(),0xc40,manager.data()); + put(manager.data(),0x50,uintptr_t(0x100000));put(manager.data(),0x60,0x1000000U); + for(unsigned i=0;i<15;++i){planes[i].resize(0x1c8);auto p=planes[i].data();sizes[p]=planes[i].size();put(p,0x7c,i);put(controller.data(),0xf58+i*8,p);} + for(unsigned i=0;i<6;++i){scalers[i].resize(0x58);auto p=scalers[i].data();sizes[p]=scalers[i].size();put(p,0x1c,i);put(controller.data(),0xf28+i*8,p);} + } + bool bind(){return FramebufferDonor::bindManagers(*this,fb.data(),controller.data(),1);} +}; +int main(){ + for(unsigned mapBytes:{0x200000U,0x1000000U}){ + Graph g;g.put(g.manager.data(),0x60,mapBytes);assert(g.bind()&&g.bind()); + for(auto &p:g.planes)assert(FramebufferDonor::field(p.data(),0x90)==g.manager.data()); + for(auto &p:g.scalers)assert(FramebufferDonor::field(p.data(),0x28)==g.manager.data()); + assert(FramebufferDonor::field(g.fb.data(),0x4a40)==g.manager.data()); + } + for(unsigned failure=0;failure<7;++failure){ + Graph g; + if(failure==0)g.put(g.fb.data(),0x1d0,static_cast(nullptr)); + if(failure==1)g.put(g.fb.data(),0x1dc,2U); + if(failure==2)g.put(g.manager.data(),0x60,0x60404U); + if(failure==3)g.put(g.manager.data(),0x50,uintptr_t(0)); + if(failure==4)g.put(g.controller.data(),0xc40,static_cast(nullptr)); + if(failure==5)g.sizes[g.controller.data()]=0x100; + if(failure==6)g.sizes[g.fb.data()]=0x100; + auto fb=g.fb;auto planes=g.planes;auto scalers=g.scalers;assert(!g.bind()); + assert(g.fb==fb&&g.planes==planes&&g.scalers==scalers); + } + // A failure at the last borrower must not mutate any earlier empty slot. + for(unsigned index=0;index<22;++index)for(unsigned kind=0;kind<3;++kind){ + Graph g;void *p=index<15?g.planes[index].data():index<21?g.scalers[index-15].data():g.fb.data(); + auto slot=index<15?0x90:index<21?0x28:0x4a40; + if(kind==0)g.put(p,slot,reinterpret_cast(0xdead)); + if(kind==1)g.sizes[p]=1; + if(kind==2)g.put(p,index<15?0x7c:index<21?0x1c:0x1dc,99U); + auto fb=g.fb;auto planes=g.planes;auto scalers=g.scalers;assert(!g.bind()); + assert(g.fb==fb&&g.planes==planes&&g.scalers==scalers); + } + puts("PASS common FB/plane/scaler binding: all-object validation, conflicting borrowers, no partial commit"); +} diff --git a/tests/framebuffer_donor_runtime.py b/tests/framebuffer_donor_runtime.py new file mode 100644 index 00000000..ba6b912c --- /dev/null +++ b/tests/framebuffer_donor_runtime.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Exercise the borrowed HDCP2 manager binding; qualify its native consumer.""" +from pathlib import Path +import re, subprocess, tempfile, sys + +r = Path(__file__).resolve().parents[1] +s = (r / 'WhateverXe/kern_xe.cpp').read_text() +install = s[s.index('bool XE::processFramebufferDonorRuntime('):s.index('bool XE::processKextFull(')] +wrapper = re.search(r'bool XE::wrapTGLHDCP2Init\(.*?\n\}', s, re.S).group() +assert s.count('processFramebufferDonorRuntime(patcher,index,address,size);') == 1 +assert s.count('wrapTGLMEIWrite,orgTGLMEIWrite') == 1 +assert 'wrapTGLMEIWrite, orgTGLMEIWrite' not in s +assert 'tglPanelPowerPropertyReady && meiReady && ddiRequestReady' in install +assert 'XELPDonor::ddiReleaseSite' in install and 'WEXDonorDDIRequestReady' in install +if len(sys.argv) > 1: + b = Path(sys.argv[1]).read_bytes() + for name, offset in [('init',0x8ab94), ('associations',0x8ac17), ('use',0x8b726), + ('loop',0x71699), ('notify',0x716eb)]: + raw = re.search(r'\b'+name+r'\[\]\s*(?:=)?\s*\{([^}]+)\}',install).group(1) + expected = bytes(int(x.strip(),0) for x in raw.split(',') if x.strip()) + assert b[offset:offset+len(expected)] == expected, name + # HDCP1's reference binding and HDCP2's null-manager consumer are independent + # donor evidence, not values generated by the production adapter. + assert b[0x8674b:0x86756] == bytes.fromhex('48 8b 80 40 0c 00 00 48 89 43 38') + +code = r''' +#include +#include +#include +#include +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} + void setProperty(const char *,const void *,size_t){} +}; +#define SYSLOG(...) do {} while (0) +#define FunctionCast(f,p) reinterpret_cast(p) +struct XE {uintptr_t orgTGLHDCP2Init;static bool wrapTGLHDCP2Init(void *,void *);}; +XE state{},*callbackXE=&state; +bool result=true;unsigned calls=0; +bool native(void *,void *) {++calls;return result;} +BODY +alignas(8) uint8_t hdcp[0x100],path[0x3700],fb[0x5000],ctl[0xD00],manager[0x80]; +templatevoid put(void *b,unsigned o,T v){memcpy(static_cast(b)+o,&v,sizeof(v));} +void setup(){ + memset(hdcp,0,sizeof(hdcp));memset(path,0,sizeof(path));memset(fb,0,sizeof(fb)); + memset(ctl,0,sizeof(ctl));memset(manager,0,sizeof(manager)); + result=true;calls=0;state.orgTGLHDCP2Init=reinterpret_cast(native); + put(hdcp,0x10,ctl+0);put(hdcp,0x18,fb+0);put(hdcp,0x20,path+0); + put(path,0x32b0,fb+0);put(fb,0x1d0,ctl+0);put(ctl,0xc40,manager+0); + put(manager,0x50,reinterpret_cast(0x100000));put(manager,0x60,0x200000u); +} +int main(){ + setup();assert(XE::wrapTGLHDCP2Init(hdcp,path));assert(calls==1); + assert(*reinterpret_cast(hdcp+0x38)==manager); + assert(XE::wrapTGLHDCP2Init(hdcp,path)); // already bound is preserved. + for(unsigned bad=0;bad<9;++bad){ + setup();void *p=path; + if(bad==0)result=false; + if(bad==1)p=nullptr; + if(bad==2)put(hdcp,0x18,static_cast(nullptr)); + if(bad==3)put(hdcp,0x10,static_cast(nullptr)); + if(bad==4)put(path,0x32b0,static_cast(nullptr)); + if(bad==5)put(fb,0x1d0,static_cast(nullptr)); + if(bad==6)put(ctl,0xc40,static_cast(nullptr)); + if(bad==7)put(manager,0x50,static_cast(nullptr)); + if(bad==8)put(manager,0x60,0x650c4u); + assert(!XE::wrapTGLHDCP2Init(hdcp,p));assert(calls==1); + assert(!*reinterpret_cast(hdcp+0x38)); + } + setup();put(hdcp,0x38,ctl+0);assert(!XE::wrapTGLHDCP2Init(hdcp,path)); + assert(*reinterpret_cast(hdcp+0x38)==ctl); // never overwrite another owner. +} +'''.replace('BODY',wrapper) +with tempfile.TemporaryDirectory(prefix='donor-runtime-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined', + str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) +print('PASS: borrowed HDCP2 binding, rejected associations and conflicting owner; common installation') diff --git a/tests/framebuffer_training_registration.py b/tests/framebuffer_training_registration.py new file mode 100644 index 00000000..b0da766d --- /dev/null +++ b/tests/framebuffer_training_registration.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Replay the production common installer; hardware eligibility is separate.""" +from pathlib import Path +import re,subprocess,tempfile,sys +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +f=re.search(r'bool XE::processFramebufferTraining\(.*?\n\}',s,re.S).group() +# Single owner for the native protocol routes and pool lock. Hardware adapters +# may set lease eligibility, but must not re-install the common protocol. +wrappers=re.findall(r'\{entries\[\w+\].symbol,(wrap\w+),',f) +assert len(wrappers)==9 +for w in wrappers: + assert len(re.findall(r'(?:,|\s)'+w+r',org',s))==1,w +assert s.count('tglPathPoolLock=IORecursiveLockAlloc()')==1 +assert s.count('processFramebufferTraining(patcher,index,address,size)')==1 +originals=re.findall(r',(?:wrap\w+),(org\w+)\}',f)+['orgTGLPathLookup','tglPortByDDI'] +code=r''' +#include "kern_framebuffer_training_native.hpp" +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +#define arrsize(x) (sizeof(x)/sizeof((x)[0])) +unsigned lockCalls;bool failLock; +void *IORecursiveLockAlloc(){++lockCalls;return failLock?nullptr:(void*)1;} +struct KernelPatcher { + struct RouteRequest {const char *symbol;uintptr_t *original; + templateRouteRequest(const char*s,F,uintptr_t&o):symbol(s),original(&o){}}; + unsigned routed=0,failRoute=0;const char *badSymbol=nullptr; + templateT solveSymbol(size_t,const char *s,uintptr_t a,size_t){ + for(const auto&e:FramebufferTrainingNative::entries)if(!strcmp(s,e.symbol))return a+e.offset+(badSymbol==s?1:0); + assert(false);return 0; + } + void clearError(){} + bool routeMultipleShort(size_t,RouteRequest *rs,unsigned n,uintptr_t a,size_t size){ + for(unsigned i=0;i(0,rs[i].symbol,a,size); + *reinterpret_cast(*rs[i].original)=0xcc; + }return true; + } +}; +struct XE { + static constexpr unsigned TGLPortCount=9; + bool display13FramebufferRequested=false;void *tglPathPoolLock=nullptr; + struct {unsigned ddi=99;} tglTrainingLeases[TGLPortCount]; + ORIGINALS + WRAPPERS + bool processFramebufferTraining(KernelPatcher&,size_t,uintptr_t,size_t); +}; +FUNCTION +int main(int argc,char **argv){ + using namespace FramebufferTrainingNative; + std::vector image(0x150000); + if(argc>1){std::ifstream f(argv[1],std::ios::binary);image.assign(std::istreambuf_iterator(f),{});assert(image.size()>0x91da0);} + for(const auto&e:entries){if(argc>1)assert(!memcmp(image.data()+e.offset,e.prefix,16));else memcpy(image.data()+e.offset,e.prefix,16);} + for(const auto&t:spans){if(argc>1)assert(!memcmp(image.data()+t.offset,t.bytes,t.length));else memcpy(image.data()+t.offset,t.bytes,t.length);} + for(bool d13:{false,true}) { + for(unsigned failure=0;failure<=11;++failure){ + auto b=image;XE x;x.display13FramebufferRequested=d13;KernelPatcher p;lockCalls=0;failLock=failure==11; + if(failure<=10)p.failRoute=failure; + bool success=x.processFramebufferTraining(p,0,(uintptr_t)b.data(),b.size()); + assert(success==(failure==0||(d13&&failure==10))); + assert(lockCalls==1);if(failLock)assert(p.routed==0); + if(success){assert(p.routed==(d13?9U:10U));for(unsigned i=0;i<9;++i)assert(x.tglTrainingLeases[i].ddi==i);} + } + for(const auto&e:entries){ + auto b=image;XE x;x.display13FramebufferRequested=d13;KernelPatcher p;lockCalls=0;failLock=false; + b[e.offset]^=1;assert(!x.processFramebufferTraining(p,0,(uintptr_t)b.data(),b.size()));assert(!lockCalls&&!p.routed); + } + for(const auto&t:spans){ + auto b=image;XE x;x.display13FramebufferRequested=d13;KernelPatcher p;lockCalls=0; + b[t.offset]^=1;assert(!x.processFramebufferTraining(p,0,(uintptr_t)b.data(),b.size()));assert(!lockCalls&&!p.routed); + } + } + puts("PASS shared training registration: both backends, exact entries/spans, partial routes and lock failure"); +} +'''.replace('FUNCTION',f).replace('ORIGINALS','uintptr_t '+','.join(n+'=0' for n in originals)+';').replace('WRAPPERS','\n'.join('static void '+w+'(){}' for w in wrappers+['wrapTGLPathLookup'])) +with tempfile.TemporaryDirectory(prefix='wex-training-registration-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/host_publication.py b/tests/host_publication.py index 21050ccc..f3fb4138 100644 --- a/tests/host_publication.py +++ b/tests/host_publication.py @@ -4,7 +4,7 @@ import subprocess,tempfile r=Path(__file__).resolve().parents[1] adapter=(r/'WhateverXe/kern_xelp_device.hpp').read_text();adapter=adapter[adapter.index('namespace XELP {'):adapter.rindex('#endif')] -s=(r/'WhateverXe/kern_host.cpp').read_text();body=s[s.index('void XeHost::processKernel'):s.index('void XeHost::processKext')] +s=(r/'WhateverXe/kern_host.cpp').read_text();body=s[s.index('void XeHost::processKernel'):s.index('bool XeHost::wrapPanelSetDisplay')] code=r''' #include #include @@ -13,6 +13,8 @@ #include #include #include "kern_xelp_platform.hpp" +using mach_vm_address_t=uintptr_t; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;}void setProperty(const char*,bool){}}; std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086;uint8_t revision=0; struct Object {virtual ~Object()=default;}; struct OSData:Object {uint32_t value=0;void *getBytesNoCopy(){return &value;}size_t getLength(){return 4;}}; @@ -33,15 +35,29 @@ };DeviceInfo DeviceInfo::value; namespace WIOKit {bool awaitPublishing(IOPCIDevice *){events.push_back(1);published=publish;return publish;}void renameDevice(IOPCIDevice *device,const char *name){device->name=name;++device->renames;events.push_back(5);}} // DEVICE_ADAPTER -struct KernelPatcher {static constexpr int KernelID=0;uintptr_t solveSymbol(int,const char *){return 0;}void clearError(){}}; -namespace XeDataAccess {void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} -struct Graphics {bool off=false;void switchOff(){off=true;}} graphics; +struct KernelPatcher { + static constexpr int KernelID=0; + uintptr_t solveSymbol(int,const char *){return 0;} + templateT solveSymbol(size_t,const char*,mach_vm_address_t,size_t){return nullptr;} + struct RouteRequest {templateRouteRequest(const char*,T,uintptr_t&) {}}; + bool routeMultiple(size_t,RouteRequest*,unsigned,mach_vm_address_t,size_t){return true;} + void clearError(){} +}; +namespace XeDataAccess {bool processKext(KernelPatcher&,size_t,mach_vm_address_t,size_t){return false;}void processKernel(KernelPatcher &){assert(published);events.push_back(7);}} +struct Graphics {size_t loadIndex=4;bool off=false;void switchOff(){off=true;}} graphics; +struct {size_t loadIndex=5;}backlight; struct XeHost { bool active=false,backlightEnabled=false;enum {FB_DETECT,FB_RESET,FB_COPY,FB_ZEROFILL,FB_NONE,FB_TOTAL};uint32_t resetFramebuffer=FB_NONE; IOPCIDevice *backlightGPU=nullptr; struct vc_info{};vc_info consoleVinfo;bool gotConsoleVinfo=false; - struct XE {void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);}} xe; + struct XE { + void processKernel(KernelPatcher &,DeviceInfo *){events.push_back(8);} + void processKext(KernelPatcher&,size_t index,mach_vm_address_t,size_t){events.push_back(100+index);} + } xe; void processKernel(KernelPatcher &); + void processKext(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static void wrapPanelSetDisplay(){}static void wrapFramebufferInit(){} + uintptr_t orgPanelSetDisplay=0,orgFramebufferInit=0;uint8_t *gIOFBVerboseBootPtr=nullptr; }; '''.replace('// DEVICE_ADAPTER',adapter)+body+r''' int main(){KernelPatcher patcher;IOPCIDevice gpu,mei; @@ -70,6 +86,25 @@ XeHost host;host.processKernel(patcher);assert(host.active); assert(host.backlightEnabled==!apple);assert(host.backlightGPU==(!apple?&gpu:nullptr)); } +for(unsigned rev:{0U,12U,255U}) { + DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; + publish=true;published=false;deviceProperty=nullptr;identity=0x4C8A8086;revision=rev;events.clear();graphics.off=false; + XeHost host;host.processKernel(patcher);assert(!host.active && graphics.off && !host.backlightEnabled); + for(auto e:events)assert(e!=8); // Unsupported family never dispatches the GPU. +} +for(unsigned rev:{0U,12U,255U})for(bool override:{false,true}) { + 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(events[events.size()-2]==8); + const auto profile=XELP::identify(identity,revision,0x9A498086); + assert(profile.displayAdmitted() && profile.admitted()); + // Execute the real outer dispatcher; neither FB hooks nor Accelerator + // dispatch must remain connected to the selected mobile Display13 backend. + host.processKext(patcher,1,0,0);assert(events.back()==101); + host.processKext(patcher,2,0,0);assert(events.back()==102); +} puts("PASS actual host admission: absent/unpublished/unsupported GPUs never enter Xe; published PCI reads precede selected-driver dispatch");} ''' with tempfile.TemporaryDirectory(prefix='wxe-host-') as d: diff --git a/tests/provider_binding.py b/tests/provider_binding.py index f67ba41d..88f54895 100644 --- a/tests/provider_binding.py +++ b/tests/provider_binding.py @@ -22,5 +22,5 @@ "-fno-omit-frame-pointer", "-I" + str(root.parent / "AppleIntelTGLCompat"), "-I" + str(root.parent / "WhateverXe"), str(cpp), "-o", str(exe)], check=True) - for mode in ["tgl", "tgl-9a40", "tgl-g4", "adln", "adln-16eu", "adln-abi1", "unknown", "adln-revision", "adln-fake", "tgl-fake", "adln-abi1-fake", "adls", "adls-revision", "rpls", "rpls-revision"]: + for mode in ["adlp-fb", "adlp-auto", "rplp-auto", "tgl", "tgl-9a40", "tgl-g4", "adln", "adln-16eu", "adln-abi1", "unknown", "adln-revision", "adln-fake", "tgl-fake", "adln-abi1-fake", "adls", "adls-revision", "rpls", "rpls-revision"]: subprocess.run([str(exe), mode], check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index 64720428..00a00bad 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -27,13 +27,20 @@ struct IOMemoryMap:OSObject {uintptr_t address {};uint64_t bytes {}; int main(int argc,char **argv) { assert(XELP::display13ProbeCapable(xelpProbeCapabilities)); assert(!XELP::display13ProbeCapable(nullptr)); + assert(XELP::display13TCProbeCapable(xelpProbeCapabilities)); + assert(!XELP::display13TCProbeCapable(nullptr)); + assert(!XELP::display13TCProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return XELP::ProbeDisplay13ComboDP;})); + assert(XELP::display13TCProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return XELP::ProbeDisplay13FixedTC;})); + 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:0)); + assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?(XELP::ProbeDisplay13ComboDP|XELP::ProbeDisplay13FixedTC|XELP::ProbeDisplay13FixedDP):0)); assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 0;})); assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 2;})); assert(argc==2);const bool adln=!strncmp(argv[1],"adln",4); const bool rpls=!strncmp(argv[1],"rpls",4); const bool adls=!strncmp(argv[1],"adls",4) || rpls; + const bool adlp=!strncmp(argv[1],"adlp",4) || !strncmp(argv[1],"rplp",4); const bool legacy=!strncmp(argv[1],"adln-abi1",9); const bool fake=strstr(argv[1],"fake")!=nullptr; const bool rejected=!strcmp(argv[1],"unknown"); @@ -46,10 +53,12 @@ int main(int argc,char **argv) { if(!strcmp(argv[1],"tgl-g4"))pci.identity=0x9A788086; if(!strcmp(argv[1],"tgl-9a40"))pci.identity=0x9A408086; if(!strcmp(argv[1],"adln-16eu"))pci.identity=0x46D28086; - if(!strcmp(argv[1],"unknown"))pci.identity=0x46A68086; + if(!strcmp(argv[1],"unknown"))pci.identity=0x4C8A8086; if(!strcmp(argv[1],"adln-revision"))pci.revision=1; OSData fakeID; if(fake){pci.identity=0x12348086;fakeID.value=adln?0x46D2:0x9A49;pci.property=&fakeID;} + if(adlp){pci.identity=!strncmp(argv[1],"rplp",4)?0xA7A88086:0x46A68086;pci.revision=12; + if(!strcmp(argv[1],"adlp-fb")){fakeID.value=0x9A49;pci.property=&fakeID;}} controller.provider=&pci;platform[0x12]=9; map.address=reinterpret_cast(mmio.data());map.bytes=mmio.size(); field(&controller,0xC40)=&access;field(&controller,0xD40)=platform; @@ -62,21 +71,22 @@ int main(int argc,char **argv) { field(&port,0x548)=initial; assert(!tglProbePort(&port)); assert(!hardwareAllowed(nullptr));IOService orphan;assert(!hardwareAllowed(&orphan)); - const bool physicalTGL=!adln && !adls && !rejected; - assert(hardwareAllowed(&controller)==(!adln && !rejected)); + const bool physicalTGL=!adln && !adls && !adlp && !rejected; + assert(hardwareAllowed(&controller)==(!adln && !adlp && !rejected)); assert(tglProbePort(&port)==(physicalTGL?1U:0U)); field(&port,0x578)=&foreign;assert(!tglProbePort(&port));field(&port,0x578)=&access; XELP::ProbeConfig config;config.profile=XELP::deviceProfile(&pci); xelpSelectedGPU=&pci;xelpSelectedProfile=config.profile; - assert(xelpSelectedDevice(&pci)==config.profile.admitted()); - assert(xelpAlderLakeNDevice(&pci)==(adln && !rejected)); - assert(xelpDisplay13Device(&pci)==(adln && !rejected)); + assert(xelpSelectedDevice(&pci)==config.profile.displayAdmitted()); + assert(xelpDisplay13RenderDevice(&pci)==((adln || adlp) && !rejected)); + assert(xelpDisplay13Device(&pci)==((adln || adlp) && !rejected)); IOPCIDevice foreignPCI;foreignPCI.identity=pci.identity;foreignPCI.revision=pci.revision;foreignPCI.property=pci.property; assert(!xelpSelectedDevice(&foreignPCI)); assert(!xelpDisplay13Device(&foreignPCI)); config.combo=3;config.comboBackend=adln?3:7;config.legacy=adln?0:0x1F8;config.legacyBackend=adln?0:0x1F8; if(adls){config.combo=8;config.comboBackend=0x19;config.legacy=config.legacyBackend=0;} if(adls && !rejected){assert(hardwareAllowed(&controller));assert(!tglProbePort(&port));} + if(adlp){config.combo=0;config.comboBackend=3;config.legacy=8;config.legacyBackend=0x78;} // The contract derives generation from physical identity, not boot args. assert(!xelpConfigurePortProbe(&controller,2,3,3)); if(!adln)assert(!xelpConfigurePortProbe(&controller,1,3,3)); @@ -106,6 +116,21 @@ int main(int argc,char **argv) { assert(xelpConfigureDisplay(&controller,&config));assert(controller.retains==1); malformed=config;malformed.combo=1;assert(!xelpConfigureDisplay(&controller,&malformed)); if(adln){assert(xelpConfigurePortProbe(&controller,1,3,3));assert(!xelpConfigurePortProbe(&controller,2,3,3));} + if(adlp){ + auto rec=reinterpret_cast(platform+0x20+24*3); + const uint32_t values[]={3,9,2,1,0x800,0x202};memcpy(rec,values,sizeof(values));field(&port,0x548)=rec; + for(unsigned ready=0;ready<16;++ready)for(unsigned hpd=0;hpd<2;++hpd){ + field(mmio.data(),0x161500)=ready;field(mmio.data(),0xc4000)=hpd<<24; + assert(tglProbePort(&port)==(hpd && (ready&7)==4?4U:0U)); + } + rec[2]=1;rec[4]=0x400;rec[5]=0x201; + for(unsigned ready=0;ready<16;++ready)for(unsigned hpd=0;hpd<2;++hpd){ + field(mmio.data(),0x161500)=ready;field(mmio.data(),0xc4000)=hpd<<24; + assert(tglProbePort(&port)==(hpd && !(ready&3)?4U:0U)); + } + rec[1]=10;assert(!tglProbePort(&port)); + assert(!hardwareAllowed(&controller));puts("PASS actual ADL-P Display13 provider: physical identity, legacy TC probe, no Display12/GT fallback");return 0; + } for(unsigned d=0;d<9;++d){auto rec=reinterpret_cast(platform+0x20+24*d); const uint32_t values[]={d,adls?9:d+1,2,0,0x800,0x202};memcpy(rec,values,sizeof(values)); field(&port,0x548)=rec; diff --git a/tests/tgl_aux_live.py b/tests/tgl_aux_live.py index 237bf07a..1bde887c 100644 --- a/tests/tgl_aux_live.py +++ b/tests/tgl_aux_live.py @@ -3,13 +3,15 @@ from pathlib import Path import re,subprocess,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() -bodies='\n'.join(re.search(p,s,re.S).group() for p in [r'void tglAUXRecord\(.*?\n\}',r'static bool tglAUXControlSerialize\(.*?\n\}',r'void tglDPCDRecord\(.*?\n\}',r'static bool tglDPCDSerialize\(.*?\n\}',r'static bool tglAUXLiveSerialize\(.*?\n\}']) +bodies='\n'.join(re.search(p,s,re.S).group() for p in [r'static void tglComboAUXDecisionRecord\(uint32_t \*word\) \{.*?\n\}',r'static bool tglComboAUXDecisionSerialize\(void \*,void \*,OSSerialize \*serializer\) \{.*?\n\}',r'void tglAUXRecord\(.*?\n\}',r'static bool tglAUXControlSerialize\(.*?\n\}',r'void tglDPCDRecord\(.*?\n\}',r'static bool tglDPCDSerialize\(.*?\n\}',r'static bool tglAUXLiveSerialize\(.*?\n\}']) f=r'''#include #include #include #include #include #include +struct TGLComboAUXDecision {uint64_t ns;uint32_t word[14];}; +TGLComboAUXDecision tglComboAUXFirst[64]{},tglComboAUXLast[64]{};uint32_t tglComboAUXCount{}; struct TGLAUXRecord { uint64_t ns,thread;uint32_t word[28]; }; TGLAUXRecord tglAUXFirst[9][256]{},tglAUXLast[9][128]{},tglAUXControl[9][64]{}; uint32_t tglAUXCounts[9]{},tglAUXControlCounts[9]{}; @@ -37,6 +39,17 @@ }; // BODIES int main(){ + {OSSerialize out;uint32_t w[14]{}; + for(unsigned i=0;i<160;++i){w[1]=i;tglComboAUXDecisionRecord(w);} + assert(tglComboAUXDecisionSerialize(nullptr,nullptr,&out)); + assert(out.bytes.size()==16+128*sizeof(TGLComboAUXDecision)); + uint32_t h[4];memcpy(h,out.bytes.data(),16);assert(h[0]==1&&h[1]==160&&h[2]==64&&h[3]==64); + for(unsigned i=0;i<64;++i){TGLComboAUXDecision v;memcpy(&v,&out.bytes[16+i*64],64);assert(v.word[0]==i+1&&v.word[1]==i);} + for(unsigned i=96;i<160;++i){TGLComboAUXDecision v;memcpy(&v,&out.bytes[16+4096+(i%64)*64],64);assert(v.word[0]==i+1&&v.word[1]==i);} + failAlloc=true;assert(!tglComboAUXDecisionSerialize(nullptr,nullptr,&out));failAlloc=false; + failData=true;assert(!tglComboAUXDecisionSerialize(nullptr,nullptr,&out));failData=false; + } + alignas(8) uint8_t port[0x600]{};uint32_t config[]={4}; *reinterpret_cast(port+0x548)=config; auto noRead=mmap(nullptr,4096,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0);assert(noRead!=MAP_FAILED); diff --git a/tests/tgl_clock_order.py b/tests/tgl_clock_order.py index 5eb8a550..29a17c2e 100644 --- a/tests/tgl_clock_order.py +++ b/tests/tgl_clock_order.py @@ -15,7 +15,9 @@ void IODelay(unsigned n){assert(n==8);++delays;} struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry v;return &v;}void setProperty(const char*,bool){} }; struct XE { - bool adlsDisplaySelected=false;static bool adlsClockTarget(void*,void*,void*){return false;} + static bool comboBeginClock(void*,void*,bool,bool&){assert(false);return false;} + static void comboEndClock(){assert(false);} + bool adlsDisplaySelected=false;static bool comboClockTarget(void*,void*,void*){return false;} void *tglObservationController;uintptr_t tglClockOffCaller=1,orgTGLPathLookup,orgTGLDisableClocks; bool tglDPSSTMode(unsigned d,unsigned mode){return d>=3&&d<=8&&(mode==3||mode==4);} static volatile uint8_t *tglClockOrderTarget(void*,void*,void*,void*); diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index c14e19e0..3e318600 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -4,6 +4,7 @@ import subprocess,sys,tempfile root=Path(__file__).resolve().parents[1];s=(root/'WhateverXe/kern_xe.cpp').read_text() a=s.index('void XE::tglComboAUXReference(');fn=s[a:s.index('uint32_t XE::wrapTGLDisablePort',a)].replace('XE::tglComboAUXReference','run') +a=s.index('uint32_t XE::wrapTGLEnablePort(');enable=s[a:s.index('// Exact 20A2314a table.',a)].replace('XE::wrapTGLEnablePort','enable').replace('tglComboAUXReference','run') if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert b[0x5fc56:0x5fc5f]==bytes.fromhex('554889e553504889fb');assert b[0x5fc6d:0x5fc73]==bytes.fromhex('8bb790050000') code=r''' @@ -14,21 +15,28 @@ #include #include struct Functions {bool ready=true;void **controllerSlot=nullptr;void(*getAux)(void*)=nullptr;void(*putAux)(void*)=nullptr;bool complete(){return ready;}}; -struct State {uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +struct State {uint32_t (*orgTGLEnablePort)(void*)=nullptr;uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts;Functions tglTCNativeFunctions;void *tglComboAUXOwners[9]{}; } state,*callbackXE=&state; -std::map regs;unsigned getCount,putCount,reads;bool missingAck;uint32_t *refs; +std::map regs;unsigned getCount,putCount,reads,nativeEnableCount;bool missingAck;unsigned elapsed,ackDelay;bool dropRequest;unsigned badForGet;uint32_t *refs; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return regs[reg];} +void IODelay(unsigned us){elapsed+=us;if(dropRequest)regs[0x45444]=0;else if(ackDelay && elapsed>=ackDelay)regs[0x45444]=regs[0x45454];} void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} +void tglComboAUXDecisionRecord(uint32_t *){} FUNCTION +#define FunctionCast(name,target) (target) +constexpr uint32_t kIOReturnNotReady=0xe00002d8U; +template void tglTrace(A...){} +void tglSnapshotTransmitter(void*,bool,unsigned){} +ENABLE int main(){ - for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<15;++bad){ + for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<18;++bad)for(unsigned initialRefs:{0u,2u}){ const bool adls=kind==1||kind==3,fixed=kind==2;const bool panel=kind==3;if(panel && testDDI!=0)continue;if(adls?testDDI==1:testDDI>1)continue; alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t local[6]{};uint32_t *config=local; unsigned ddi=testDDI,mask=3U<<(2*ddi),buffer=0x64000+256*ddi; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; - state={};if(adls) { + state={};nativeEnableCount=0;state.orgTGLEnablePort=[](void*){++nativeEnableCount;return 0U;};if(adls) { state.adlsDisplaySelected=true;state.adlsWiring.valid=true;state.adlsWiring.dp=1U<(state.tglPlatformCopy+0x20+24*ddi);const uint32_t c[]={ddi,ddi?ddi+6:1,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); } @@ -43,9 +51,9 @@ store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(p,0x590,buffer); store(ctl,0xC8,power+0);store(ctl,0xC40,access+0);store(power,0x10,ctl+0);store(power,0x78,access+0); store(access,0x60,0x200000U);store(access,0x50,ctl+0); - refs=reinterpret_cast(power+0x54+4*ddi);*refs=2;getCount=putCount=reads=0;missingAck=bad==14; + badForGet=bad;refs=reinterpret_cast(power+0x54+4*ddi);*refs=initialRefs;getCount=putCount=reads=0;missingAck=bad>=14;elapsed=0;ackDelay=bad==15?500:0;dropRequest=bad==16; regs.clear();regs[0x45454]=mask;regs[buffer]=0x81; - state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?0:regs[0x45454];}; + state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?(badForGet==17?~0U:regs[0x45454]&0xAAAAAAAAU):regs[0x45454];}; state.tglTCNativeFunctions.putAux=[](void*){--*refs;++putCount;}; if(bad==1)state.tglActive=false; if(bad==2)state.tglComboAUXLifetimeReady=false; @@ -61,19 +69,24 @@ if(bad==12){state.xeProfile.family=XELP::Family::AlderLakeN;state.adlsDisplaySelected=false;} if(bad==13)*refs=~0U; auto before=*refs;run(p,true); - if(bad){assert(*refs==before&&!state.tglComboAUXOwners[ddi]);assert(getCount==(bad==14));assert(putCount==(bad==14));continue;} - assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[ddi]==p); - run(p,true);assert(getCount==1); // duplicate enable is idempotent + if(bad && bad!=15){assert(*refs==before&&!state.tglComboAUXOwners[ddi]);assert(getCount==(bad>=14));assert(putCount==(bad>=14)); + if(bad>=14) { + assert(elapsed==(bad==14?1000:bad==16?10:0)); + if(adls||fixed){assert(enable(p)==kIOReturnNotReady);assert(nativeEnableCount==0);} + } + continue;} + assert(getCount==1&&*refs==initialRefs+1&&state.tglComboAUXOwners[ddi]==p);assert(elapsed==(bad==15?500:0)); + assert(enable(p)==0 && nativeEnableCount==1);run(p,true);assert(getCount==1); // duplicate enable is idempotent // Native per-transaction ref pairs cannot consume the held main-link ref. - ++*refs;--*refs;assert(*refs==3); + ++*refs;--*refs;assert(*refs==initialRefs+1); run(nullptr,false);assert(putCount==0); regs[buffer]=0x80000001;run(p,false);assert(putCount==0); // active main link - regs[buffer]=0x81;run(p,false);assert(putCount==1&&*refs==2&&!state.tglComboAUXOwners[ddi]); - run(p,false);assert(putCount==1);run(p,true);assert(getCount==2);run(p,false);assert(putCount==2&&*refs==2); + regs[buffer]=0x81;run(p,false);assert(putCount==1&&*refs==initialRefs&&!state.tglComboAUXOwners[ddi]); + run(p,false);assert(putCount==1);run(p,true);assert(getCount==2);run(p,false);assert(putCount==2&&*refs==initialRefs); } puts("PASS main-link AUX acquire/duplicate/native transaction/disable/retry, inherited refs, ownership/power/ACK guards"); } -'''.replace('FUNCTION',fn) +'''.replace('FUNCTION',fn).replace('\nENABLE\n','\n'+enable+'\n') with tempfile.TemporaryDirectory() as tmp: p=Path(tmp);(p/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) diff --git a/tests/tgl_combo_pll_write.py b/tests/tgl_combo_pll_write.py index 3c5ecaf9..4b8447fa 100644 --- a/tests/tgl_combo_pll_write.py +++ b/tests/tgl_combo_pll_write.py @@ -5,7 +5,7 @@ root=Path(__file__).resolve().parents[1] s=(root/'WhateverXe/kern_xe.cpp').read_text() start=s.index('void XE::wrapTGLRegisterWrite(') -guard=s[s.index('\n',start)+1:s.index('\tauto &tx = tglClockOffTransaction;',start)] +guard=s[s.index('\n',start)+1:s.index(' comboClockTranslateWrite(',start)] guard=guard.replace('reinterpret_cast(__builtin_return_address(0))','caller') install=s[s.index('// Qualify the reference-clock field producer'):s.index('if (tglHDMIB)',s.index('// Qualify the reference-clock field producer'))] if len(sys.argv)>1: diff --git a/tests/tgl_dp_alt_fia.py b/tests/tgl_dp_alt_fia.py index 704f4998..17b41f31 100644 --- a/tests/tgl_dp_alt_fia.py +++ b/tests/tgl_dp_alt_fia.py @@ -4,7 +4,7 @@ import re, subprocess, tempfile r=Path(__file__).resolve().parents[1] s=(r/'WhateverXe/kern_xe.cpp').read_text() -patterns=[r'bool XE::tglDPAltFia\(.*?\n\}',r'uint32_t XE::wrapTGLConfiguredLanes\(.*?\n\}',r'void XE::wrapTGLFIALaneCount\(.*?\n\}',r'uint32_t XE::wrapTGLLaneMask\(.*?\n\}'] +patterns=[r'bool XE::tglLegacyFia\(.*?\n\}',r'bool XE::tglDPAltFia\(.*?\n\}',r'uint32_t XE::wrapTGLConfiguredLanes\(.*?\n\}',r'void XE::wrapTGLFIALaneCount\(.*?\n\}',r'uint32_t XE::wrapTGLLaneMask\(.*?\n\}'] production='\n'.join(re.search(p,s,re.S).group() for p in patterns) f=(r/'tests/tgl_dp_alt_fia_fixture.cpp').read_text().replace('// PRODUCTION',production) with tempfile.TemporaryDirectory(prefix='tgl-dp-alt-fia-') as d: diff --git a/tests/tgl_dp_alt_fia_fixture.cpp b/tests/tgl_dp_alt_fia_fixture.cpp index a42f7a14..bdd1466f 100644 --- a/tests/tgl_dp_alt_fia_fixture.cpp +++ b/tests/tgl_dp_alt_fia_fixture.cpp @@ -13,10 +13,11 @@ bool tglDPAltColdReady(void *controller){return controller && coldReady;} struct {volatile uint8_t *base;} tglTCBootstrap; struct XE { bool tglDPAltFiaReady=true,tglNativeFiaReady=true,tglActive=true; - uint32_t tglDPAltCandidates=0x78;void *tglObservationController=(void*)1; + uint32_t legacyMask=0x78;uint32_t tglDPAltCandidates=0x78;void *tglObservationController=(void*)1; uintptr_t orgTGLPortReadReg,orgTGLLaneMask,orgTGLConfiguredLanes,orgTGLFIALaneCount; IORecursiveLock *tglModeTransitionLock; - bool tglLegacyTCEligible(uint32_t)const{return false;} + bool tglLegacyTCEligible(uint32_t d)const{return d<9 && (legacyMask&(1u<(XE::wrapTGLPortReadReg); instance.orgTGLLaneMask=instance.orgTGLConfiguredLanes=reinterpret_cast(fallback); instance.orgTGLFIALaneCount=reinterpret_cast(fallbackSet); - mode=3;config[3]=1; - for(bool modular:{false,true})for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned physical:{3u,12u,15u}) { - config[0]=ddi;TGLTypeCPort tc;TGLFiaPort fia; + mode=3;config[2]=1;config[3]=1; + for(unsigned transport:{3u,4u})for(bool modular:{false,true})for(unsigned ddi=3;ddi<=6;++ddi)for(unsigned physical:{3u,12u,15u}) { + mode=transport;config[0]=ddi;TGLTypeCPort tc;TGLFiaPort fia; assert(TGLTypeCPort::resolve(ddi,tc)&&tc.resolveFia(modular?16:0,fia)); mmio.clear();mmio[0x1638a0]=modular?16:0; mmio[fia.live]|=(physical|32u)<<(8*fia.index); mmio[fia.ready]=fia.readyMask;mmio[fia.owned]=fia.readyMask;mmio[fia.laneSelect]=0xabcdefff; TGLFiaPort actual;assert(XE::tglDPAltFia(port,actual)&&actual.live==fia.live&&actual.index==fia.index); - assert(XE::wrapTGLLaneMask(port)==physical); + assert(XE::wrapTGLLaneMask(port)==(transport==4?15u:physical)); + // Independent register coordinates, not an expectation generated by resolveFia. + const uint32_t laneReg[]={0x1638c0,0x1638c0,0x16e8c0,0x16e8c0}; + assert(fia.laneSelect==(modular?laneReg[ddi-3]:0x1638c0)); + assert(fia.laneSelectShift==(modular?4*((ddi-3)%2):4*(ddi-3))); + if(transport==4){TGLFiaPort fixed;assert(XE::tglLegacyFia(port,fixed)&&fixed.laneSelect==fia.laneSelect);} for(unsigned lanes:{1u,2u,4u}) { const auto before=mmio[fia.laneSelect]; XE::wrapTGLFIALaneCount(port,lanes); @@ -74,7 +80,7 @@ int main(){ config[3]=1;config[0]=5;size=0x170000;instance.tglDPAltCandidates=0x78; mmio[0x1638a0]=~0u;assert(!XE::tglDPAltFia(port,fia)); mmio[0x1638a0]=16;size=0x16e8c0;assert(!XE::tglDPAltFia(port,fia)); - mode=4;assert(XE::wrapTGLLaneMask(port)==0xbeef&&XE::wrapTGLConfiguredLanes(port)==0xbeef); + instance.legacyMask=0;mode=4;assert(XE::wrapTGLLaneMask(port)==0xbeef&&XE::wrapTGLConfiguredLanes(port)==0xbeef); XE::wrapTGLFIALaneCount(port,2);assert(fallbacks==3); - puts("PASS production DP-alt FIA: TC1..4 modular/non-modular, both orientations, logical lanes, sibling preservation, power/map/backend/all-ones guards and native fallback"); + puts("PASS production fixed/DP-alt FIA: TC1..4 modular/non-modular, both orientations, logical lanes, sibling preservation, power/map/backend/all-ones guards and native fallback"); } diff --git a/tests/tgl_hdmi_wiring.py b/tests/tgl_hdmi_wiring.py index c7e130d0..22b4478c 100644 --- a/tests/tgl_hdmi_wiring.py +++ b/tests/tgl_hdmi_wiring.py @@ -1,13 +1,27 @@ #!/usr/bin/env python3 """Test the actual HDMI loader/topology/GMBUS/lane entry points with IOKit mocks.""" from pathlib import Path -import re,subprocess,tempfile +import hashlib,re,subprocess,sys,tempfile r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text();h=(r/'WhateverXe/kern_xe.hpp').read_text() fields=h[h.index(' TGLHDMIWiring tglHDMIWiring;'):h.index('\tbool tglGMBusReady')] -functions='\n'.join(re.search(p,s,re.S).group() for p in [r'bool XE::tglLoadHDMIWiring\(.*?\n\}',r'bool XE::wrapTGLGetOSInformation\(.*?\n\}',r'uint32_t XE::wrapTGLGMBus\(.*?\n\}',r'bool XE::wrapTGLComputeLanes\(.*?\n\}']) +functions='\n'.join(re.search(p,s,re.S).group() for p in [r'bool XE::tglLoadHDMIWiring\(.*?\n\}',r'bool XE::wrapTGLGetOSInformation\(.*?\n\}',r'uint32_t XE::wrapTGLGMBus\(.*?\n\}',r'bool XE::wrapFramebufferComputeLanes\(.*?\n\}',r'bool XE::framebufferHDMIEligible\(.*?\n\}']) observations=s[s.index('struct TGLGMBusObservation {'):s.index('struct TGLMEIRecord {')] code=(r/'tests/tgl_hdmi_wiring_fixture.cpp').read_text().replace('// FIELDS',fields).replace('// OBSERVATIONS',observations).replace('// FUNCTIONS',functions) with tempfile.TemporaryDirectory(prefix='tgl-hdmi-wiring-') as d: d=Path(d);(d/'test.cpp').write_text(code) subprocess.run(['clang++','-std=c++14','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer','-I',str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) subprocess.run([str(d/'test')],check=True) + +if len(sys.argv)>1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + # All three direct callers found in exact-donor disassembly. Only the + # 0x304f8 validator is admitted to the HDMI correction/config reader. + for at in [0x1fc07,0x304f3,0x3c4cf]: + assert donor[at]==0xe8 + assert at+5+int.from_bytes(donor[at+1:at+5],'little',signed=True)==0x1fc22 + # At setupOptimalLaneCount, arg0 is the output pointer, not the object. + assert donor[0x1fbfd:0x1fc07]==bytes.fromhex('4881c7240100004989f8') + # The callee overwrites edi without reading arg0; it writes the output r8. + assert donor[0x1fc2f:0x1fc38]==bytes.fromhex('89d741c70000000000') + print('PASS exact donor lane callers: interior output pointer vs HDMI validator') diff --git a/tests/tgl_hdmi_wiring_fixture.cpp b/tests/tgl_hdmi_wiring_fixture.cpp index a173b4cc..a62f4232 100644 --- a/tests/tgl_hdmi_wiring_fixture.cpp +++ b/tests/tgl_hdmi_wiring_fixture.cpp @@ -12,6 +12,7 @@ #include "kern_alderlake_s.hpp" #include "kern_tgl_combo_dp.hpp" #include "kern_xelp_donor.hpp" +#include "kern_display13_fb_topology.hpp" struct OSObject {virtual ~OSObject()=default;}; struct OSData:OSObject {std::vector data;void *getBytesNoCopy(){return data.data();}uint32_t getLength(){return data.size();}}; struct IOService {OSObject *property {};unsigned reads {};OSObject *getProperty(const char *name){assert(!strcmp(name,"tgl-hdmi-wiring"));++reads;return property;}}; @@ -32,12 +33,15 @@ struct XE { struct {uint32_t fStolenMemorySize=0;} framebufferPatch; XELP::VBT::Ports firmwarePorts; // FIELDS - uintptr_t orgTGLGetOSInformation {},orgTGLGMBus {},orgTGLComputeLanes {},tglHDMILaneCaller=0x100; + uintptr_t orgTGLGetOSInformation {},orgTGLGMBus {},orgFramebufferComputeLanes {},framebufferLaneCaller=0x100; uint64_t tglPlatformCopy[0x1C0/8] {}; static void tglBeginTCColdProtection(void*){} static bool wrapTGLGetOSInformation(void *); static uint32_t wrapTGLGMBus(void *,uint32_t,uint32_t,void *); - static bool wrapTGLComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); + static bool wrapFramebufferComputeLanes(void *,const void *,uint32_t,uint32_t,uint32_t *); + bool display13FramebufferRequested=false; + bool framebufferHDMIEligible(const uint32_t *) const; + void *adlnPlatform {};Display13OutputWiring adlnOutputWiring; }; XE object,*callbackXE=&object; // OBSERVATIONS @@ -50,7 +54,7 @@ uint32_t nativeGMBus(void*,uint32_t,uint32_t,void*){++nativeGMBusCalls;edidSelec for(auto &o:tglGMBusObservations)if(o.thread==1)edidSelected=o.edid;return 0x91;} uint8_t *&platform(){return *reinterpret_cast(controller+0xD40);} void setup(){ - object=XE{};object.orgTGLGetOSInformation=(uintptr_t)nativeOS;object.orgTGLComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGMBus=(uintptr_t)nativeGMBus; + object=XE{};object.orgTGLGetOSInformation=(uintptr_t)nativeOS;object.orgFramebufferComputeLanes=(uintptr_t)nativeLanes;object.orgTGLGMBus=(uintptr_t)nativeGMBus; memset(controller,0,sizeof(controller));memset(original,0x5a,sizeof(original));platform()=original;original[0x12]=9; for(unsigned d=0;d<9;++d){uint32_t cfg[]={d,d+1,1,d>=3?1u:0u,0x400,0x201};memcpy(original+0x20+24*d,cfg,24);} const uint32_t panel[]={0,0,0,0,2,0x18};memcpy(original+0x20,panel,24); @@ -104,15 +108,39 @@ int main(){ const auto page=static_cast(sysconf(_SC_PAGESIZE)); void *inaccessible=mmap(nullptr,page,PROT_NONE,MAP_PRIVATE|MAP_ANON,-1,0); assert(inaccessible!=MAP_FAILED); - testCaller=0x101; - for(bool native:{false,true}){ - nativeLanesResult=native;uint32_t lanes=4; - assert(XE::wrapTGLComputeLanes(inaccessible,nullptr,24,20,&lanes)==native); - assert(lanes==4); + for(bool display13:{false,true}) { + object.display13FramebufferRequested=display13;auto wrapper=XE::wrapFramebufferComputeLanes; + testCaller=0x101;nativeLanesCalls=0; + for(bool native:{false,true}){ + nativeLanesResult=native;uint32_t lanes=4; + assert(wrapper(inaccessible,nullptr,24,20,&lanes)==native); + assert(lanes==4); + } + object.framebufferLaneCaller=0;testCaller=0; + assert(wrapper(inaccessible,nullptr,24,20,nullptr)); + assert(nativeLanesCalls==3); + object.framebufferLaneCaller=0x100; + } + assert(munmap(inaccessible,page)==0); + // The production Display13 wrapper keeps HDMI correction and DP pass-through + // under the exact validator, across both Combo and TC topology records. + for(unsigned d:{0U,1U,3U,4U,5U,6U})for(bool hdmi:{false,true}) { + setup();object.display13FramebufferRequested=true;Display13OutputWiring w;w.valid=true; + original[0x13]=3; + for(unsigned i=3;i<9;++i)reinterpret_cast(original+0x20+24*i)[1]=i+6; + if(hdmi){w.hdmi=1U<(out+0x20+24*d); + memcpy(port+0x548,&config,sizeof(config)); + for(bool native:{false,true})for(unsigned available:{0U,20U})for(unsigned lanes:{0U,4U}) { + nativeLanesResult=native;auto before=lanes; + assert(XE::wrapFramebufferComputeLanes(port,nullptr,24,available,&lanes)== + (native || (hdmi && !available && !lanes))); + assert(lanes==before); + } } - object.tglHDMILaneCaller=0;testCaller=0; - assert(XE::wrapTGLComputeLanes(inaccessible,nullptr,24,20,nullptr)); - assert(nativeLanesCalls==3);assert(munmap(inaccessible,page)==0); setup();auto current=property(2,0,2,0);IOService absent{¤t};assert(XE::tglLoadHDMIWiring(&absent));assert(XE::wrapTGLGetOSInformation(controller)); const auto p=platform();assert(p!=original&&object.tglHDMIWiringApplied&&object.tglHDMIWiring.ddc[1]==2); expectUnchangedExcept(p,{0x40,0x48,0x4C}); // HDMI protocol/type/flags; panel unchanged @@ -170,11 +198,11 @@ int main(){ expectUnchangedExcept(platform(),{0x3C,0x40,0x48,0x4C}); auto cfg=reinterpret_cast(platform()+0x38);assert(cfg[1]==pin&&object.tglHDMIEligible(cfg)); *reinterpret_cast(port+0x548)=cfg; - uint32_t lanes=0;assert(XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes)&&lanes==0&&nativeLanesCalls==1); - testCaller=0x101;assert(!XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes));testCaller=0x100; - lanes=1;assert(!XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes));lanes=0; - assert(!XE::wrapTGLComputeLanes(port,nullptr,32,1,&lanes)); - auto saved=cfg[1];cfg[1]=pin==1?2:1;assert(!XE::wrapTGLComputeLanes(port,nullptr,32,0,&lanes));cfg[1]=saved; + uint32_t lanes=0;assert(XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes)&&lanes==0&&nativeLanesCalls==1); + testCaller=0x101;assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes));testCaller=0x100; + lanes=1;assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes));lanes=0; + assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,1,&lanes)); + auto saved=cfg[1];cfg[1]=pin==1?2:1;assert(!XE::wrapFramebufferComputeLanes(port,nullptr,32,0,&lanes));cfg[1]=saved; for(unsigned bus=0;bus<=14;++bus)for(unsigned fault=0;fault<5;++fault){ uint32_t request[16] {};request[1]=1;request[3]=0xA0;request[14]=128; uint32_t speed=fault==1?1:0;if(fault==2)request[1]=0;if(fault==3)request[3]=0xA2;if(fault==4)request[14]=64; diff --git a/tests/tgl_mode_native.py b/tests/tgl_mode_native.py index 6d3a39d0..013f72ba 100644 --- a/tests/tgl_mode_native.py +++ b/tests/tgl_mode_native.py @@ -1,12 +1,23 @@ #!/usr/bin/env python3 """Replay qualified native TGL setPortMode with the production result repair.""" from pathlib import Path -import argparse,hashlib,re,subprocess,tempfile +import argparse,hashlib,re,struct,subprocess,tempfile p=argparse.ArgumentParser(description=__doc__);p.add_argument('donor',type=Path);p.add_argument('--serialization',action='store_true');p.add_argument('--source',type=Path);a=p.parse_args() if hashlib.sha256(a.donor.read_bytes()).hexdigest()!='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731':p.error('unqualified donor SHA256') r=Path(__file__).resolve().parents[1];s=(a.source or r/'WhateverXe/kern_xe.cpp').read_text() # Verify the production admission bytes, not just the fixture's ready flag. donor=a.donor.read_bytes() +# Verify reachability of the lane wrapper in the actual fixed-DP clock path, +# independently of direct wrapper tests. TC mode != TBT selects this branch; +# the port's negotiated/default lanes feed setFIALaneCount before clocks and +# DP_MODE. These are static donor assertions, not a full clock/MMIO replay. +for slot,target in [(0x198,0x603e8),(0x150,0x61d5c),(0x180,0x60930),(0x1a0,0x602cc)]: + assert struct.unpack_from(' #include "kern_tgl_native_fia.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_platform.hpp" #include void *current_thread(){thread_local char token;return &token;} #include @@ -28,6 +30,8 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + XELP::Profile xeProfile=XELP::identify(0x9a498086,1); + bool tglActive=true,tglDPAltFiaReady=true; bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; static void tglRecordStartOrder(unsigned,void*,unsigned=9,uint64_t=0,uint64_t=0,uint64_t=0) {} @@ -40,6 +44,7 @@ struct XE { auto config=*reinterpret_cast(static_cast(port)+0x548); TGLTypeCPort tc;return coldReady && TGLTypeCPort::resolve(config[0],tc) && tc.resolveFia(0x10,fia); } + static bool tglLegacyFia(void*,TGLFiaPort&); static bool tglNativeFiaAccess(void*,uint32_t,uintptr_t,bool,TGLNativeFia&); static uint32_t wrapTGLPortReadReg(void*,uint32_t); static void wrapTGLPortWriteReg(void*,uint32_t,uint32_t); @@ -56,15 +61,16 @@ XE instance,*callbackXE=&instance; bool tglQuietTraining(){return true;} // PRODUCTION_WRAPPER #undef __builtin_return_address +// LEGACY_FIA_WRAPPER // FIA_WRAPPERS -alignas(8) uint8_t port[0x600],controller[0x1000]; +alignas(8) uint8_t port[0x600],controller[0x1000],access[0x80]; uint32_t config[8]; unsigned powerGets,powerPuts,sleeps,reads,writes; uint32_t ready,owned,baseError; -uint32_t ready2=0,owned2=0; +uint32_t ready2=0,owned2=0,topology=0x10; uint32_t &mode(){return *reinterpret_cast(port+0x550);} uint32_t readReg(void *p,uint32_t r){ - assert(p==port);++reads;assert(r==0x163890 || r==0x163894 || r==0x16e890 || r==0x16e894); + assert(p==port);++reads;if(r==0x1638a0){assert(powerGets>powerPuts);return topology;}assert(r==0x163890 || r==0x163894 || r==0x16e890 || r==0x16e894); if(r==0x16e890)return ready2;if(r==0x16e894)return owned2; return r==0x163890?ready:owned; } @@ -81,7 +87,7 @@ void jump(uint8_t *p,uintptr_t t){p[0]=0x48;p[1]=0xb8;std::memcpy(p+2,&t,8);p[10 void callTo(uint8_t *p,unsigned site,unsigned target){assert(p[site]==0xe8);int32_t rel=target-site-5;std::memcpy(p+site+1,&rel,4);} void reset(unsigned ddi,uint32_t value=0){ powerGets=powerPuts=sleeps=reads=writes=0;ready=value;owned=0x40000000;mode()=0;baseError=0; - config[0]=ddi;config[5]=8;instance.tglModeResultCorrections=0;instance.tglModeResultReady=true; + config[0]=ddi;config[2]=1;config[3]=1;config[5]=8;instance.tglModeResultCorrections=0;instance.tglModeResultReady=true; } int main(int argc,char **argv){ assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{}); @@ -193,6 +199,9 @@ int main(int argc,char **argv){ mapped.bank=1;mapped.ready=1; assert(TGLFia::waitLegacyReady(mapped,5).ok() && TGLFia::setOwned(mapped,5,true).ok()); assert(mapped.ownership[0]==0x40000000 && mapped.ownership[1]==0x40000001 && mapped.ownership[2]==0x40000000); + *reinterpret_cast(port+0x578)=access; + *reinterpret_cast(access+0x50)=controller; + *reinterpret_cast(access+0x60)=0x170000; // Replay the coordinate adapter through the actual native call sites. IORecursiveLock lock;instance.tglModeTransitionLock=&lock;instance.tglNativeFiaReady=true; instance.tglNativeFiaBase=reinterpret_cast(m);instance.tglModularProbed=0x78; @@ -226,9 +235,25 @@ int main(int argc,char **argv){ assert(instance.tglNativeFiaContext.matches(reinterpret_cast(current_thread()),port)); } assert(!instance.tglNativeFiaContext.owner && instance.tglNativeFiaReads && instance.tglNativeFiaWrites); - // Missing probe qualification or mutex must leave the native FIA1 failure. - instance.tglModularProbed=0;reset(5,0);ready2=1; - assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && reads==100 && !writes); + // Hotplug needs no previous powered probe or DP-alt bootstrap. Native + // acquisition must precede topology reads; the read mock enforces AUX debt. + instance.tglDPAltCandidates=0;instance.tglModularProbed=0;coldReady=false;reset(5,0);ready2=1; + assert(XE::wrapTGLSetPortMode(port,4)==0 && mode()==4 && owned2&1); + assert(XE::wrapTGLSetPortMode(port,0)==0 && powerGets==powerPuts);coldReady=true; + for(unsigned d=3;d<=6;++d) { + topology=0;reset(d,1U<<(d-3)); + assert(XE::wrapTGLSetPortMode(port,4)==0 && (owned&(1U<<(d-3)))); + assert(XE::wrapTGLSetPortMode(port,0)==0 && owned==0x40000000 && powerGets==powerPuts); + } + topology=UINT32_MAX;reset(5,0);ready2=1; + assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && !writes && powerGets==powerPuts); + topology=16;reset(5,0);ready2=UINT32_MAX; + assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && !writes && powerGets==powerPuts); + // Even when native returns zero after an unavailable ownership access, + // the wrapper reports failure and never overwrites the peer owner word. + topology=16;reset(5,0);ready2=1;owned2=UINT32_MAX; + assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && !writes && owned2==UINT32_MAX); + // Missing mutex keeps this unqualified path outside the adapter. instance.tglModularProbed=0x78;instance.tglModeTransitionLock=nullptr;reset(5,0); assert(XE::wrapTGLSetPortMode(port,4)==kIOReturnNotReady && reads==100 && !writes); puts("PASS qualified native FIA adapter: TC1/2 identity and TC3/4 modular bank/bit, sibling preservation, paired AUX, scoped caller guards"); diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index 91a53551..e365996d 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,6 +1,8 @@ #include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_platform.hpp" #include void *current_thread(){thread_local char token;return &token;} #include @@ -27,6 +29,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + XELP::Profile xeProfile=XELP::identify(0x9a498086,1); bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; uint32_t tglTCAutoRegistered=0,tglTCAutoCandidates=0x10,tglAUXParentMask=0x18; @@ -42,6 +45,7 @@ struct XE { auto config=*reinterpret_cast(static_cast(port)+0x548); TGLTypeCPort tc;return coldReady && TGLTypeCPort::resolve(config[0],tc) && tc.resolveFia(0x10,fia); } + static bool tglLegacyFia(void *port,TGLFiaPort &fia) {return tglDPAltFia(port,fia);} static bool tglNativeFiaAccess(void*,uint32_t,uintptr_t,bool,TGLNativeFia&); static uint32_t wrapTGLPortReadReg(void*,uint32_t); static void wrapTGLPortWriteReg(void*,uint32_t,uint32_t); diff --git a/tests/tgl_panel_power.py b/tests/tgl_panel_power.py index 3f076fbe..87f53a76 100644 --- a/tests/tgl_panel_power.py +++ b/tests/tgl_panel_power.py @@ -5,7 +5,7 @@ r=Path(__file__).resolve().parents[1] s=(r/'WhateverXe/kern_xe.cpp').read_text() begin=s.index('\t\t\t\t// Both power-on and power-off converge') -end=s.index('\n\t\t\t\tif (framebufferPatchFlags.bits.FPFStolenMemorySize)',begin) +end=s.index('\n }\n // DONOR_PANEL_PROPERTY_END',begin) install=s[begin:end] wrapper=re.search(r'bool XE::wrapTGLPanelPowerProperty\(.*?\n\}',s,re.S).group() if len(sys.argv)>1: diff --git a/tests/tgl_probe_fixture.cpp b/tests/tgl_probe_fixture.cpp index 0888b8d6..c40417d8 100644 --- a/tests/tgl_probe_fixture.cpp +++ b/tests/tgl_probe_fixture.cpp @@ -111,7 +111,7 @@ int main(int argc,char **) { unsigned cases=0; for(unsigned hp:{0u,1u})for(unsigned modular:{0u,1u})for(unsigned rd:{0u,1u})for(unsigned alt=0;alt<4;++alt) { hpd=hp<<25;live=(modular<<4)|(alt<<13);ready=rd<<1; - const auto expected=hp&&modular&&rd&&!alt?4u:0u; + const auto expected=hp&&rd&&!alt?4u:0u; assert(XE::wrapTGLProbePort(port)==expected);++cases; } hpd=1u<<25;live=0x10;ready=2;length=0x1638A4; diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index ec6a4478..5f90829b 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -1,4 +1,5 @@ #include "kern_alderlake_s.hpp" +#include "kern_framebuffer_donor.hpp" templatevoid tglTransitionRecord(T...){ } // Native boundaries are mocks; LEASE_FUNCTIONS is extracted unchanged from production. #include @@ -45,11 +46,14 @@ template void tglObserveResource(T...) {} void tglAUXPublish() {} void tglDPCDPublish() {} struct XE { + static bool display13RetirementHealthy(){return true;} + static bool retireDisplay13Controller(void*){return true;} bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // TC_WIRING_FIELDS // LEASE_FIELDS IORecursiveLock *tglPathPoolLock; bool tglTrainingLeaseReady = true; + bool framebufferStartInProgress=false;uint32_t framebufferProtocolCalls=0;void *framebufferStartedController=nullptr; void *tglObservationController; uintptr_t tglPortByDDI, orgTGLPathLookup, orgTGLAllocatePathAny, orgTGLAllocatePathFixed, orgTGLFreePath; uintptr_t tglEnablePG, tglDisablePG, tglDisablePort, tglDisableClocks, orgTGLControllerStop; @@ -244,6 +248,25 @@ void shutdownStatus() { setup();auto &end=reserve(4);mmio.at(0x64400)=0; assert(!XE::tglReleaseTrainingLease(end,false) && end.path && end.powerOwned); mmio.at(0x64400)=0x80;assert(XE::tglReleaseTrainingLease(end,false));clean(); + // Controller stop must not turn the same failed release into pool destruction. + setup();auto &held=reserve(4);bufferIdlePending=true; + XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(!stopCalled&&held.path&&held.powerOwned&&!held.busy&&obj.tglStopRetained&&retained[ctl.data()]==1); + bufferIdlePending=false;XE::wrapTGLControllerStop(ctl.data(),nullptr); + assert(stopCalled&&obj.tglStopComplete&&!obj.tglStopRetained);clean(); + // The real unselected Begin wrapper also participates in the drain. + setup();uint32_t noPort[20]={0};nativeBoundary=[&]{ + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(!stopCalled&&obj.tglStopRetained); + }; + assert(!XE::wrapTGLTrainingBegin(ctl.data(),noPort));nativeBoundary={}; + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(stopCalled&&!obj.tglStopRetained);clean(); + // Unselected protocol calls are drained too, not just per-port busy leases. + setup();{ + FramebufferDonor::Call active(obj.framebufferProtocolCalls);assert(active); + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(!stopCalled&&obj.tglStopRetained); + FramebufferDonor::Call rejected(obj.framebufferProtocolCalls);assert(!rejected); + } + XE::wrapTGLControllerStop(ctl.data(),nullptr);assert(stopCalled&&!obj.tglStopRetained);clean(); puts("shutdown status: buffer idle missing / pipe enable or active / allones retain resources; no premature clock teardown; explicit retry succeeds on all 3 pipes"); } void trainingRouteRestoration() { diff --git a/tests/tgl_tx_wrappers_fixture.cpp b/tests/tgl_tx_wrappers_fixture.cpp index 50a3d2fc..28503a0e 100644 --- a/tests/tgl_tx_wrappers_fixture.cpp +++ b/tests/tgl_tx_wrappers_fixture.cpp @@ -79,7 +79,7 @@ int main(){ if(kind==4)reg(buffer())=0x80000000; if(kind==5){nativeResult=0xBAD;assert(!mprotect(mmio,span,PROT_NONE));} auto r=XE::wrapTGLEnablePort(port);assert(enableCalls==1); - assert(r==((kind==0||kind==4)?0u:nativeResult));assert(delays==(kind==0?2u:kind==1?30u:0u)); + assert(r==nativeResult && delays==0); // Waiting belongs to the common native-loop adapter. if(kind!=5)neighbors(); } for(unsigned reason=0;reason<4;++reason){reset(d); @@ -102,5 +102,5 @@ int main(){ reset(d);readable=false;XE::wrapTGLPhyClockGating(port,false);assert(clockCalls==1&&writes==0); } assert(!mprotect(mmio,span,PROT_READ|PROT_WRITE));munmap(mmio,span); - puts("PASS actual 5 TX wrappers across 6 mock-qualified ports, quiet/on: native coefficient/clock/mode semantics, all lane counts/rates, buffer-local stagger, 3ms late-enable success/timeout/all-ones, protected-MMIO wiring/backend exclusions, native errors and neighboring outputs preserved. No physical qualification."); + puts("PASS actual 5 TX wrappers across 6 mock-qualified ports, quiet/on: native coefficient/clock/mode semantics, all lane counts/rates, buffer-local stagger, native enable return (shared wait tested separately), protected-MMIO wiring/backend exclusions, native errors and neighboring outputs preserved. No physical qualification."); } diff --git a/tests/tgl_wiring_fixture.cpp b/tests/tgl_wiring_fixture.cpp index 7d6461e3..f8a72061 100644 --- a/tests/tgl_wiring_fixture.cpp +++ b/tests/tgl_wiring_fixture.cpp @@ -61,7 +61,16 @@ int main(){ assert(!object.tglRegisterLegacyHPD(4,4,0x1234));object.tglAUXParentMask=0x18; assert(object.tglRegisterLegacyHPD(4,4,0x1234)); assert(object.tglLegacyTCEligible(4)&&object.tglTCModeAllowed(4,4)); - assert(!object.tglTCModeAllowed(4,2)&&!object.tglTCModeAllowed(4,3)); + assert(!object.tglTCModeAllowed(4,2)&&object.tglTCModeAllowed(4,3)); + // Fixed DP and DP-alt share the exact AUX mapping and native TC1..4 limit. + for(unsigned d=3;d<=8;++d) { + reset();const uint32_t aux[]={0x30,0x50,0x60,0x70,0x80,0x90}; + object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=1U< +#include +#include +using namespace XELPComboClock; +int main(){ + assert(donorPC(0x100000000,0x10006D77E)==0x6D77E); + assert(donorPC(0x100000000,0x20006D77E)==UINT32_MAX); + assert(donorPC(0x100000000,0xFFFF0000)==UINT32_MAX); + unsigned cases=0; + for(auto layout:{Layout::TGL,Layout::Display13})for(unsigned target=0;target<2;++target) + for(unsigned peer=0;peer<2u;++peer)for(unsigned occupied=0;occupied<2;++occupied) { + if(target==peer)continue; + ClockSnapshot s;s.layout=layout;s.mux[0]=0x01000C00; + Port p(target),q(peer);s.mux[0]=(s.mux[0]&~(q.clockOff|(3U< +#include +#include +#include +struct XE { + bool adlsDisplaySelected=false,display13FramebufferRequested=false,tglActive=true; + struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; + void *tglObservationController,*adlnTopologyController; + uint64_t tglPlatformCopy[0x1c0/sizeof(uint64_t)]{},adlnPlatform[0x1c0/sizeof(uint64_t)]{}; + Display13OutputWiring adlnOutputWiring; + static bool adlsClockTarget(void*,void*,void*){return false;} + static bool comboClockTarget(void*,void*,void*); +} state,*callbackXE=&state; +FUNCTION +alignas(8) uint8_t ctl[0xf00]{},ports[2][0x700]{},access[0x80]{}; +templateT &at(void *p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} +int main(){ + for(bool d13:{false,true}) { + state.display13FramebufferRequested=d13;state.adlnTopologyController=state.tglObservationController=ctl; + state.adlnOutputWiring.valid=true;state.adlnOutputWiring.hdmi=3;state.adlnOutputWiring.ddc[0]=1;state.adlnOutputWiring.ddc[1]=2; + auto platform=reinterpret_cast(d13?state.adlnPlatform:state.tglPlatformCopy); + at(ctl,0xc40)=access;at(access,0x60)=0x200000; + for(unsigned d=0;d<2;++d) { + const uint32_t c[]={d,d+1,2,0,0x800,0x202};memcpy(platform+0x20+24*d,c,sizeof(c)); + at(ports[d],0x548)=platform+0x20+24*d;at(ports[d],0x578)=access;at(ports[d],0x5b8)=d?0x6c10c:0x16210c; + } + for(unsigned d=0;d<2;++d) { + auto p=ports[d];assert(XE::comboClockTarget(ctl,p,nullptr));assert(XE::comboClockTarget(ctl,p,ports[1-d])); + assert(!XE::comboClockTarget(nullptr,p,nullptr));assert(!XE::comboClockTarget(access,p,nullptr)); + at(p,0x578)=ctl;assert(!XE::comboClockTarget(ctl,p,nullptr));at(p,0x578)=access; + auto config=at(p,0x548);uint32_t clone[6];memcpy(clone,config,sizeof(clone)); + at(p,0x548)=clone;assert(!XE::comboClockTarget(ctl,p,nullptr));at(p,0x548)=config; + config[3]=1;assert(!XE::comboClockTarget(ctl,p,nullptr));config[3]=0; + config[0]=3;assert(!XE::comboClockTarget(ctl,p,nullptr));config[0]=d; + at(p,0x5b8)^=0x1000;assert(!XE::comboClockTarget(ctl,p,nullptr));at(p,0x5b8)^=0x1000; + at(access,0x60)=0x164290;assert(!XE::comboClockTarget(ctl,p,nullptr));at(access,0x60)=0x200000; + } + } + puts("PASS actual TGL/Display13 Combo entry guards, bound shared source, TC/foreign/cloned/unmapped rejection"); +} +'''.replace('FUNCTION',f) +with tempfile.TemporaryDirectory() as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp index 616af9c2..562603ac 100644 --- a/tests/xelp_display_contract.cpp +++ b/tests/xelp_display_contract.cpp @@ -7,7 +7,7 @@ static void faults(C c) { assert(c.required && c.ready()); // Every dependency must independently close admission. An unrelated failed // operation must not disable this route. Reinstalling restores admission. - for(unsigned i=0;i<63;++i) { + for(unsigned i=0;i<64;++i) { auto broken=c;auto bit=1ULL<(platform),c};assert(b.valid()); + auto rec=reinterpret_cast(platform+0x20+24*d); + const uint32_t native[]={d,d+6,2,1,0x800,0x202};memcpy(rec,native,sizeof(native)); + IO io;io.regs={{0xc4000,h<<(21+d)},{0x161500+4*(d-3),ready}}; + assert(probeMode(io,b,2,reinterpret_cast(rec),rec)==(h && (ready&7)==4?4U:0U)); + assert(io.reads[0]==0x161500+4*(d-3)); + for(unsigned f:{0U,1U,2U,3U,4U,5U}) { + auto old=rec[f];rec[f]=~0U;io.reads.clear(); + assert(!probeMode(io,b,2,reinterpret_cast(rec),rec) && io.reads.empty());rec[f]=old; + } + auto bad=c;bad.legacyBackend=0;assert(!bad.valid());bad=c;bad.typeC=c.legacy;assert(!bad.valid()); + } static_assert(sizeof(Profile)==28 && sizeof(ProbeConfig)==60,"Keep existing provider ABI3 layout"); for(auto id:{0xA7808086U,0xA7828086U,0xA7888086U,0xA78B8086U}) { auto p=identify(id,12); @@ -26,7 +49,9 @@ int main() { const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78 || id==0x9A60 || id==0x9A68 || id==0x9A70,adln=id>=0x46D0 && id<=0x46D4; const bool adls=id==0x4680 || id==0x4682 || id==0x4688 || id==0x468A || id==0x468B || id==0x4690 || id==0x4692 || id==0x4693; const bool rpls=id==0xA780 || id==0xA781 || id==0xA782 || id==0xA783 || id==0xA788 || id==0xA789 || id==0xA78A || id==0xA78B; - assert(p.admitted()==(tgl || adln || adls || rpls)); + const bool mobile=id==0x46a3 || id==0x46a6 || id==0x46a8 || id==0x46aa || id==0x46b3 || id==0x46c3 || id==0xa7a0 || id==0xa7a1 || (id>=0xa7a8 && id<=0xa7ad); + assert(p.admitted()==(tgl || adln || adls || rpls || mobile)); + if(mobile){assert(p.mobileDisplay13());assert(!identify((id<<16)|0x8086,rev,0x46d18086).admitted());} if(adls||rpls)assert(!identify((id<<16)|0x8086,rev,0x9A498086).admitted()); assert(!adln || p.display==Display::XeLPD); assert(!identify((id<<16)|0x1234,rev).admitted()); @@ -35,10 +60,10 @@ int main() { ProbeConfig c;c.profile=identify(0x9A498086,1);c.combo=2;c.comboBackend=7;c.legacyBackend=0x1F8;c.legacy=0x1F8; ProbeBinding b {1,2,reinterpret_cast(platform),c};assert(b.valid()); unsigned cases=0; - // Independent old WEG equations, plus exact MMIO read order for every TC. + // Independent Linux FIA layouts and exact MMIO read order for every TC. for(unsigned d=3;d<=8;++d)for(unsigned h=0;h<2;++h)for(unsigned mod=0;mod<2;++mod) for(unsigned ready=0;ready<2;++ready)for(unsigned alt=0;alt<4;++alt) { - const unsigned tc=d-3,instance=tc/2,index=tc%2;const uint32_t bases[]={0x163000,0x16E000,0x16F000}; + const unsigned tc=d-3,instance=mod?tc/2:0,index=mod?tc%2:tc<4?tc:0;const uint32_t bases[]={0x163000,0x16E000,0x16F000}; const auto liveReg=bases[instance]+0x8A0,readyReg=bases[instance]+0x890; IO io;io.regs={{0xC4000,h<<(24+tc)},{0x1638A0,mod<<4},{liveReg,(mod<<4)|(alt<<(5+8*index))},{readyReg,ready<(platform+0x20+24*d);record[0]=d; const auto mode=probeMode(io,b,2,reinterpret_cast(record),record); - assert(mode==(h && mod && ready && !alt?4U:0U)); + assert(mode==(h && (mod || tc<4) && ready && !alt?4U:0U)); std::vector expected {0xC4000}; - if(h){expected.push_back(0x1638A0);if(mod){if(instance)expected.push_back(liveReg);expected.push_back(readyReg);}} + if(h){expected.push_back(0x1638A0);if(mod || tc<4){if(instance)expected.push_back(liveReg);expected.push_back(readyReg);}} assert(io.reads==expected);++cases; } // Selection never grants an unknown, Type-C or excluded port access. diff --git a/tests/xelp_port_wait_harness.hpp b/tests/xelp_port_wait_harness.hpp new file mode 100644 index 00000000..42acc4aa --- /dev/null +++ b/tests/xelp_port_wait_harness.hpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include "kern_xelp_port_wait.hpp" +#include "kern_xelp_link_requirements.hpp" +#include "kern_xelp_sst_route.hpp" +#include "kern_tgl_tc_boot.hpp" +#include "kern_tgl_hdmi_wiring.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_display13_fb_topology.hpp" +#include "kern_display13_fb_memory.hpp" +using mach_vm_address_t=uintptr_t; +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} void setProperty(const char*,bool){}}; +struct KernelPatcher { + enum class Error{NoError,Failed};bool fail=false,corrupt=false;unsigned writes=0; + struct LookupPatch {void *info;const uint8_t *before,*after;size_t n;unsigned count;}; + templateT solveSymbol(size_t,const char*,uintptr_t a,size_t){return a+0x5f9f4;} + void clearError(){} Error getError(){return fail?Error::Failed:Error::NoError;} + void applyLookupPatch(LookupPatch *p,uint8_t *at,size_t){++writes;assert(!memcmp(at,p->before,p->n));if(!fail)memcpy(at,p->after,p->n);if(corrupt)at[0]^=1;} +}; +struct XE { + bool display13FramebufferRequested=false;XELP::Profile xeProfile; + uint64_t tglPlatformCopy[0x1c0/sizeof(uint64_t)]{},adlnPlatform[0x1c0/sizeof(uint64_t)]{}; + Display13OutputWiring adlnOutputWiring; + TGLHDMIWiring tglHDMIWiring;AlderLakeS::Wiring adlsWiring; + TGLTCWiring tglTCWiring {TGLTCWiring::fromMasks(0,0,0)}; + bool adlsDisplaySelected=false,tglHDMIWiringApplied=false; + uint32_t tglAUXParentMask=TGLTCWiring::Mask,tglTCAutoCandidates=0,tglTCAutoRegistered=0; + uint32_t tglDPAltCandidates=0,tglComboDPMask=0; + void *currentFramebuffer=nullptr; + // ROUTING + static XELP::LinkRequirements framebufferLinkRequirements(void*,uint32_t); + static uint32_t framebufferPortWaitPolls(void*); + bool processFramebufferPortWait(KernelPatcher&,size_t,uintptr_t,size_t); +} instance,*callbackXE=&instance; +// FUNCTIONS +alignas(8) uint8_t selectionPort[0x700]{}; +void setup(unsigned platform,unsigned ddi,unsigned protocol){ + instance=XE{};instance.display13FramebufferRequested=platform==2; + instance.adlsDisplaySelected=platform==1; + instance.xeProfile.display=platform==2?XELP::Display::XeLPD:XELP::Display::XeD; + auto &w=instance.adlnOutputWiring;w.valid=true; + if(protocol==2){w.hdmi=1u<(platform==2?instance.adlnPlatform:instance.tglPlatformCopy); + bytes[0x12]=9;auto c=reinterpret_cast(bytes+0x20+24*ddi); + c[0]=ddi;c[1]=ddi<3?ddi+1:ddi+6;c[2]=protocol;c[3]=platform==1 || ddi<3?0:1;c[4]=protocol==2?0x800:0x400;c[5]=protocol==2?0x202:0x201; + if(platform==1) { + assert(AlderLakeS::ComboBackendMask&(1u< b(0x70000);memcpy(b.data()+WaitOffset,WaitBefore,sizeof(WaitBefore)); + if(fault==1)b[WaitOffset+50]^=1; + KernelPatcher p;p.fail=fault==2;p.corrupt=fault==3; + const bool ok=instance.processFramebufferPortWait(p,0,reinterpret_cast(b.data()),b.size()); + assert(ok==!fault);assert(p.writes==(fault==1?0:1)); + if(ok){uintptr_t target=0;memcpy(&target,b.data()+WaitOffset+5,8);assert(target==reinterpret_cast(XE::framebufferPortWaitPolls));} + } + for(unsigned platform=0;platform<3;++platform)for(unsigned ddi:{0u,1u,3u,4u,5u,6u})for(unsigned protocol:{1u,2u}){ + if(platform==0 && ddi>=3 && protocol==2)continue; + if(platform==1 && !(AlderLakeS::ComboBackendMask&(1u<(selectionPort,0x548); + uint32_t clone[6];memcpy(clone,config,sizeof(clone));auto clonePtr=clone; + memcpy(selectionPort+0x548,&clonePtr,sizeof(clonePtr));assert(XE::framebufferPortWaitPolls(selectionPort)==100); + memcpy(selectionPort+0x548,&config,sizeof(config)); + if(platform!=2 && protocol==2){ + instance.tglHDMIWiringApplied=false;assert(XE::framebufferPortWaitPolls(selectionPort)==100); + instance.tglHDMIWiringApplied=true;config[1]^=1;assert(XE::framebufferPortWaitPolls(selectionPort)==100);config[1]^=1; + } + if(platform==1 && protocol==1){ + instance.adlsWiring.valid=false;assert(XE::framebufferPortWaitPolls(selectionPort)==100);instance.adlsWiring.valid=true; + } + if(platform==0 && ddi>=3){ + instance.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);assert(XE::framebufferPortWaitPolls(selectionPort)==100); + instance.tglTCAutoCandidates=1u<